Forum: Mikrocontroller und Digitale Elektronik HSV->RGB-Methode: Speicherplatz optimieren


von matze (Gast)


Lesenswert?

Hallo,

ich benutze eine Funktion, welche HSV-Koordinaten in RGB-Koordinaten 
umrechnet. Leider ist diese Funktion 378 Byte groß, was auf einem 
ATtiny13 deutlich zu viel ist (damit der Rest des Programms auch noch 
Platz hat).

Daher würde ich diese Methode optimieren, finde aber keinen 
Optimierungsspielraum mehr:
1
void setColorHSV(char hue, char saturation, char value)
2
{
3
    unsigned char i, f;
4
    unsigned int  p, q, t;
5
    unsigned char red = 0, green = 0, blue = 0;
6
7
    if (saturation == 0)  // Farbsättigung ist Null -> Grau
8
    {
9
        red = green = blue = 0;
10
    }
11
    else                  // Farbsättigung ist nicht Null -> Einteilen in 60°-Winkel des Farbtons
12
    {
13
        i = hue / 43;
14
        f = hue % 43;
15
        p = (value * (255 - saturation)) / 256;
16
        q = (value * ((10710 - (saturation * f)) / 42)) / 256;
17
        t = (value * ((10710 - (saturation * (42 - f))) / 42)) / 256;
18
19
        switch (i)
20
        {
21
            case 0: red = value;   green = t;     blue = p;   break;
22
            case 1: red = q;     green = value;   blue = p;   break;
23
            case 2: red = p;     green = value;   blue = t;   break;
24
            case 3: red = p;     green = q;     blue = value; break;      
25
            case 4: red = t;     green = p;     blue = value; break;        
26
            case 5: red = value;  green = p;     blue = q;    break;
27
      }
28
      setColorRGB(red, green, blue);
29
   }
30
}

P.S.: Die Größe der Funktion habe ich ermittelt indem ich einmal mit 
obigem Code kompiliert habe und einmal nachdem ich den gesamten Inhalt 
des Funktionsrumpfes auskommentiert hatte.

von Gast (Gast)


Lesenswert?

1
    if (saturation == 0)  // Farbsättigung ist Null -> Grau
2
        return;

Ich weiß nicht, obs was bring. Je nach Optimierung vielleicht. Du setzt 
die Variablen bereits auf Null.

von Gast (Gast)


Lesenswert?

Müssen p, q, t alle int sein?
Du weist sie ja später eh einem unsigned char zu.

von matze (Gast)


Lesenswert?

@Gast:

In der Tat müssen sie keine 16-Bit-Variablen sein. Daher habe ich
1
unsigned int  p, q, t;
ersetzt durch
1
unsigned char  p, q, t;


Der benötigte Flash-Speicherplatz reduziert sich jedoch nicht. Ich 
vermute, dass es daran liegt, dass während der Berechnung von p,q,t 
große Zahlen verwendet werden.

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

Das teilen durch 256 erscheint mir hier merkwürdig ist das korrekt?
Und ich würde mir da mal das ASM listing ansehen da kannst du dann sehen 
wo er wirklich was verbraucht.

von Gast (Gast)


Lesenswert?

Dann bleibt wohl oder überl nur noch Assembler übrig.

von Reinhard Kern (Gast)


Lesenswert?

Hallo,

du verwendest ja alle 4 Grundrechenarten plus MOD und DIV, da ist das 
doch nicht viel, also wird sich auch kaum noch was wegoptimieren lassen.

Ich versuche bei µC oft, alle (!) Divisionen zu vermeiden, weil das die 
aufwendigste Operation ist. Also statt / 43 verwende ich * 0,023; das 
ist natürlich nur bei Konstanten sinnvoll. Es kann aber auch nach hinten 
losgehen, je nach Library und Linker, also ausprobieren.

Gruss Reinhard

von matze (Gast)


Lesenswert?

Wie komme ich am schnellsten an den ASM-Code dieser Funktion?

Ich nutze das AVR-Studio.

von /* Kommentator */ (Gast)


Lesenswert?

Strg-F7
Alt V a

oder via GUI: Build&Run, View:Disassembler

von Fabian B. (fabs)


Lesenswert?

AVR Studio + GCC legt im Arbeitsordner einen Ordner "default" an und 
darin sollte eine .lss Datei sein. Falls nicht, kannst du dem Compiler 
im AVRStudio unter Project->Conf.Options sagen, er solle bitte eins 
erstellen.

Gruß
Fabian

von holger (Gast)


Lesenswert?

>Der benötigte Flash-Speicherplatz reduziert sich jedoch nicht. Ich
>vermute, dass es daran liegt, dass während der Berechnung von p,q,t
>große Zahlen verwendet werden.

Bei den meisten der Multiplikationen drohen Überläufe.
8 Bit x 8Bit kann größer als 8 Bit werden. Da fehlen
dann noch geeignete casts. Dadurch wird der
Code aber noch größer.

von holger (Gast)


Lesenswert?

So, ich hab mal ein bißchen rumgespielt:

Casting für die Multiplikationen eingesetzt und ein bißchen
ausgeklammert. Ist dabei von 514 auf 436 Bytes runter gegangen.
Komischerweise schrumpft der Code durch das Casting sogar;)
1
void setColorHSV(unsigned char hue, unsigned char saturation, unsigned char value)
2
{
3
    unsigned char i, f;
4
    unsigned char  p, q, t;
5
    unsigned char red = 0, green = 0, blue = 0;
6
7
    if (saturation == 0)  // Farbsättigung ist Null -> Grau
8
    {
9
        red = green = blue = 0;
10
    }
11
    else                  // Farbsättigung ist nicht Null -> Einteilen in 60°-Winkel des Farbtons
12
    {
13
        i = hue / 43;
14
        f = hue % 43;
15
        p = ((unsigned int)value * (255 - saturation)) / 256;
16
        q = (value * (10710 - ( (unsigned int)saturation * f) ) ) / 256 / 42;
17
        t = (value * (10710 - ((unsigned int)saturation * (42 - f)) ) ) / 256 / 42;
18
19
        switch (i)
20
        {
21
            case 0: red = value;   green = t;     blue = p;   break;
22
            case 1: red = q;     green = value;   blue = p;   break;
23
            case 2: red = p;     green = value;   blue = t;   break;
24
            case 3: red = p;     green = q;     blue = value; break;      
25
            case 4: red = t;     green = p;     blue = value; break;        
26
            case 5: red = value;  green = p;     blue = q;    break;
27
      }
28
29
      setColorRGB(red, green, blue);
30
   }
31
}

von holger (Gast)


Lesenswert?

Ach shit:

        q = (value * (10710 - ( (unsigned int)saturation * f) ) ) / 256 
/ 42;

value * (10710 - ( (unsigned int)saturation * f)

Könnte wieder größer unsigned int werden.
Dann das ausklammern der 42 rückgängig machen.

von holger (Gast)


Lesenswert?

Neuer Versuch;)
1
void setColorHSV(unsigned char hue, unsigned char saturation, unsigned char value)
2
{
3
    unsigned char i, f;
4
    unsigned char  p, q, t;
5
    unsigned char red = 0, green = 0, blue = 0;
6
7
    if (saturation == 0)  // Farbsättigung ist Null -> Grau
8
    {
9
        red = green = blue = 0;
10
    }
11
    else                  // Farbsättigung ist nicht Null -> Einteilen in 60°-Winkel des Farbtons
12
    {
13
        i = hue / 43;
14
        f = hue % 43;
15
        p = ((unsigned int)value * (unsigned char)(255 - saturation)) / 256;
16
        q = (value * ((10710 - ( (unsigned int)saturation * f) )  / 42) ) / 256;
17
        t = (value * ((10710 - ((unsigned int)saturation * (unsigned char)(42 - f)) )  / 42) ) / 256;
18
19
        switch (i)
20
        {
21
            case 0: red = value;   green = t;     blue = p;   break;
22
            case 1: red = q;     green = value;   blue = p;   break;
23
            case 2: red = p;     green = value;   blue = t;   break;
24
            case 3: red = p;     green = q;     blue = value; break;      
25
            case 4: red = t;     green = p;     blue = value; break;        
26
            case 5: red = value;  green = p;     blue = q;    break;
27
      }
28
29
      setColorRGB(red, green, blue);
30
   }
31
}

Ergibt nur noch 434 Bytes. Besser als 514 allemal.

von avr (Gast)


Lesenswert?

Hallo Holger,

hier hast du eine Vorbelegung
1
    unsigned char red = 0, green = 0, blue = 0;

und hier wird im If diese bestädigt
1
    if (saturation == 0)  // Farbsättigung ist Null -> Grau
2
    {
3
        red = green = blue = 0;
4
    }
5
    else                  // Farbsättigung ist nicht Null -> Einteilen in 60°-Winkel des Farbtons


Besser:
1
    unsigned char red = 0, green = 0, blue = 0;
2
3
    if (saturation != 0)  // Farbsättigung ist nicht Null -> Einteilen in 60°-Winkel des Farbtons
4
    {

avr

von holger (Gast)


Lesenswert?

>Hallo Holger,
>
>hier hast du eine Vorbelegung

Die ist nicht von mir;)

>    if (saturation != 0)  // Farbsättigung ist nicht Null -> Einteilen in 
>60°-Winkel des Farbtons

Ok, ausprobiert, bleibt bei 434 Bytes.

von holger (Gast)


Lesenswert?

Schmeisst man das komplett raus

    if (saturation == 0)  // Farbsättigung ist Null -> Grau

Gibts noch mal 6 Bytes geschenkt.
Macht man aus dem switch() einen if(i==x) Spaghetti Code
nochmal 4 Bytes.

Ich glaub das wars dann aber auch. 424 Bytes.

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

Ich würde es trozdem mal mit 255 anstelle von 256 versuchen... Das 
sollte dir auf jedenfall ein 16bit Division erstparen und das Ergebnis 
nicht merklich beeinflussen.
Ebenso die gemsichten 42/43 kommen mir komisch vor.
Kompilierst du auch mit Optimierung auf Platz?

von holger (Gast)


Lesenswert?

>Ich würde es trozdem mal mit 255 anstelle von 256 versuchen... Das
>sollte dir auf jedenfall ein 16bit Division erstparen und das Ergebnis
>nicht merklich beeinflussen.

Bloß das nicht! Mit 256 zieht er sich nur das Highbyte des
Ergebnisses rein. Passt schon. Bei 255 müsste wirklich
dividiert werden.

>Kompilierst du auch mit Optimierung auf Platz?

Ja, Os.

von holger (Gast)


Lesenswert?

>Bloß das nicht! Mit 256 zieht er sich nur das Highbyte des
>Ergebnisses rein. Passt schon. Bei 255 müsste wirklich
>dividiert werden.

So, kurz ausprobiert, 438 Bytes.

von matze (Gast)


Lesenswert?

Also erst einmal vielen Dank! Das ist in jedem Fall eine verbesserung, 
wenn auch eine kleine. Tatsächlich hat sich in der If-Bedingung ein 
Programmierfehler eingeschlichen, den aber keiner bemerkt hat:
1
void setColorHSV(unsigned char hue, unsigned char saturation, unsigned char value)
2
{
3
    unsigned char i, f;
4
    unsigned char  p, q, t;
5
    unsigned char red = 0, green = 0, blue = 0;
6
7
    if (saturation == 0)  // Farbsättigung ist Null -> Grau
8
    {
9
        red = green = blue = value;
10
    }
11
    else                  // Farbsättigung ist nicht Null -> Einteilen in 60°-Winkel des Farbtons
12
    {
13
        i = hue / 43;
14
        f = hue % 43;
15
        p = ((unsigned int)value * (unsigned char)(255 - saturation)) / 256;
16
        q = (value * ((10710 - ( (unsigned int)saturation * f) )  / 42) ) / 256;
17
        t = (value * ((10710 - ((unsigned int)saturation * (unsigned char)(42 - f)) )  / 42) ) / 256;
18
19
        switch (i)
20
        {
21
            case 0: red = value;   green = t;       blue = p;     break;
22
            case 1: red = q;       green = value;   blue = p;     break;
23
            case 2: red = p;       green = value;   blue = t;     break;
24
            case 3: red = p;       green = q;       blue = value; break;      
25
            case 4: red = t;       green = p;       blue = value; break;        
26
            case 5: red = value;   green = p;       blue = q;     break;
27
      }
28
29
      setColorRGB(red, green, blue);
30
   }
31
}
Das ist der Code mit Fehlerkorrektur und der Behebung des 
Programmierfehlers.


Ich bedanke mich für eure Unterstützung, möchte aber gleichzeitig auf 
keinen Fall dem Ehrgeiz einiger hier (insbesondere Holgers) einen 
Abbruch tun. Wenns noch besser geht, dann gerne! :-)

von avr (Gast)


Lesenswert?

Etwas geht noch:
1
void setColorHSV(unsigned char hue, unsigned char saturation, unsigned char value)
2
{
3
    unsigned char i, f;
4
    unsigned char  p, q, t;
5
    unsigned char red, green, blue;
6
7
        red = green = blue = value;  //**  neue Vorbesetzung
8
9
//    if (saturation != 0)  //Farbsättigung ist nicht Null -> Einteilen in 60°-Winkel des Farbtons
10
//    {
11
        i = hue / 43;
12
        f = hue % 43;
13
        p = ((unsigned int)value * (unsigned char)(255 - saturation)) / 256;
14
        q = (value * ((10710 - ( (unsigned int)saturation * f) )  / 42) ) / 256;
15
        t = (value * ((10710 - ((unsigned int)saturation * (unsigned char)(42 - f)) )  / 42) ) / 256;
16
17
        switch (i)
18
        {        
19
                         // vorbesetzte Farben nutzen
20
            case 0:              green = t;       blue = p;  break;
21
            case 1: red = q;                      blue = p;  break;
22
            case 2: red = p;                      blue = t;  break;
23
            case 3: red = p;     green = q;                  break;      
24
            case 4: red = t;     green = p;                  break;        
25
            case 5:              green = p;       blue = q;  break;
26
      }
27
28
      setColorRGB(red, green, blue);
29
  // }
30
}


man kann durch die Vorbesetzung mit Value ganz auf
die If verzichten da das Case selektiert (hier nur mit //).

avr

von S. T. (cmdrkeen)


Lesenswert?

eine division durch 256 ist gleichbedeutend mit x>>8  8 bits nach rechts 
schieben, falls dein compiler das nicht schon macht ... probiers doch 
mal :)

von Klaus (Gast)


Lesenswert?

> eine division durch 256 ist gleichbedeutend mit x>>8  8 bits nach rechts
> schieben, falls dein compiler das nicht schon macht ... probiers doch
> mal :)

Das is so ziemlich die älteste Optimierung, die ein Compiler von selber 
macht...

von Frank N. (betafrank)


Lesenswert?

An anderen Stellen kann ja auch noch was rausgeholt werden:

int main(void) __attribute__((noreturn)); //Spart 32 Byte
int main(void){ ... }

von matze (Gast)


Lesenswert?

@Frank:

Hat das irgendwelche Nebenwirkungen? Warum macht das der Compiler nicht 
selbst? Und: Bei mir spart das genau 8 Byte.

von Vlad T. (vlad_tepesch)


Lesenswert?

der compiler ist sogar noch schlauer, der schiebt gar nicht, der nimmt 
einfach das obere byte

von Anja (Gast)


Lesenswert?

hallo,

die beiden Zeilen
((10710 - ( (unsigned int)saturation * f) )  / 42) )
((10710 - ((unsigned int)saturation * (unsigned char)(42 - f)) )  / 42)
ergänzen sich zu einer Konstanten:
-> die 2. Zeile ist durch simple Subtraktion der 1. Zeile von einer 
Konstanten (255/256) möglich.

von Vlad T. (vlad_tepesch)


Lesenswert?

matze schrieb:
> @Frank:
>
> Hat das irgendwelche Nebenwirkungen? Warum macht das der Compiler nicht
> selbst? Und: Bei mir spart das genau 8 Byte.

das spart ein wenig am epilog der main-funktion, der ja nicht gebraucht 
wird, da ja normalerweise eine endlosschleife in der main werkeln.
aber wieviel es spart hängt glaub ich auch aein wenig vom controller ab, 
auf nem tiny13 hat mir das glaub ich 12 Byte gespart. auf nem mega88 
warns nur 8B

von Frank N. (betafrank)


Lesenswert?

Wie Vlad sagt, auf einem µC braucht main nichts zurückgeben. Wieviel man 
spart, hängt wohl von Compilerversion, Controller etc. ab.

Vielleicht läßt sich auch am makefile noch tunen. Eventuell erlaubt die 
Anwendung:

CFLAGS += -mtiny-stack    #Stack auf 256 begrenzt (GCC 4.3.0)

von yalu (Gast)


Angehängte Dateien:

Lesenswert?

Hallo matze,

ich habe mal die Numerik etwas optimiert, den Switch von avr und den
noreturn von Frank übernommen:
1
#include <stdio.h>
2
#include <stdint.h>
3
4
volatile uint8_t dummy;
5
6
void setColorRGB(uint8_t r, uint8_t g, uint8_t b) {
7
  dummy = r; dummy = g; dummy = b;
8
}
9
10
void setColorHSV4(uint8_t h, uint8_t s, uint8_t v) {
11
  uint16_t vs=v*s, h6=6*h, f;
12
  uint8_t i, p, u, r=v, g=v, b=v;
13
14
  p = (v<<8)-vs >> 8;
15
  i = h6 >> 8;
16
  f = ((i|1)<<8) - h6;
17
  if(i&1)
18
    f = -f;
19
  u = ((uint32_t)v<<16) - (uint32_t)vs*f >> 16;
20
  switch(i) {
21
    case 0: g = u; b =  p; break;
22
    case 1: r = u; b =  p; break;
23
    case 2: r =  p; b = u; break;
24
    case 3: r =  p; g = u; break;
25
    case 4: r = u; g =  p; break;
26
    case 5: g =  p; b = u; break;
27
  }
28
  setColorRGB(r, g, b);
29
}
30
31
int main(void) __attribute__((noreturn));
32
int main(void) {
33
  int h=100, s=250, v=255;
34
  setColorHSV4(h, s, v);
35
  for(;;);
36
}

Man kommt jetzt ohne die Divisionen aus, und die Ergebnisse sind
genauer. Da aber statt der Division nun eine 32-Bit-Multiplikation
benötigt wird, ist der Code-Größe gegenüber der Version von avr kaum
geschrumpft, mit dem GCC 5.3.4 ist er sogar ein ganzes Stück größer
geworden.

Die folgenden Codegrößen beziehen sich jeweils auf das fertig gelinkte
Programm incl. der obigen Dummy-setColorRGB-Funktion, dem obigen Main
und den benötigten Bibliotheksfunktionen. Bei der Originalversion habe
ich den Datentyp der Argumente von char in unsigned char geändert, da
sonst nicht richtig gerechnet wird.
1
------------------------------------
2
AVR-GCC          4.2.4  4.3.4  4.4.2
3
------------------------------------
4
Original          506    476    458
5
Version von avr   438    412    398
6
meine Version     442    454    390
7
------------------------------------

Die Diagramme im Anhang zeigen die Abweichungen der Ergebnisse von
matzes ("deins") und meinem ("meins") Programm zum jeweils exakten Wert,
den ich mit Double-Genauigkeit berechnen lassen habe. Dabei ist s=250,
v=255, h läuft von 0 bis 255.

Ich habe mein Programm nur auf dem PC getestet, dabei aber durch
Einfügen zusätzlicher Casts die 16-Bit-Integer-Rechengenauigkeit des AVR
simuliert. Deswegen vermute ich, dass das Programm auch auf dem AVR
richtig rechnet, aber ganz sicher bin ich mir nicht ;-)

Falls Fehler auftreten, einfach meckern! Vielleicht kann ich dann noch
etwas geradebiegen.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.