Forum: Mikrocontroller und Digitale Elektronik pow() durch Festkommaarithmetik ersetzbar?


von Johannes (menschenskind)


Lesenswert?

Hallo,

Ich musste grad feststellen, dass mein ATTiny85 bei der Berechnung von 
separaten PWM-Werten für 3 RGB-LEDs an seine Grenzen stößt.

Die Werte werden für Indizes von 0 bis INDEX_MAX nach dieser Formel 
berechnet(vereinfacht):

>PWM = 255 * pwmIndex^EXPONENT / (INDEX_MAX^EXPONENT);

wobei EXPONENT von 1.5 bis 2 und INDEX_MAX von 50 bis 100 variabel sein 
kann.

D.h. bei index 0 ist ist die LED dunkel und bei index = INDEX_MAX auf 
255.

Die Exponentialberechnung im Quotienten habe ich schon durch einen 
festen Wert ersetzt, der nur berechnet wird, wenn sich einer der beiden 
Werte verändert.
Danach konnte ich einen leichten Geschwindigkeitsgewinn feststellen, 
aber der ist leider nicht ausreichend.

Für eine Variante mit fixen PWM-Tabellen bräuchte ich für meinen Ansatz 
hier 6 Stück mit INDEX_MAX Werten (habe ich noch nicht ausprobiert und 
müsste da auch einiges vom Code wieder verändern).

In Internetz konnte ich nichts finden was mir zu Polynomberechnung in 
Festkommaarithmetik weiterhilft, deswegen hoff ich auf eure netten 
Tipps.

Gruß
Hannes

von S. R. (svenska)


Lesenswert?

Wie wäre es mit einer Tabelle aus vorberechneten Werten und linearer 
Interpolation?

von A. S. (Gast)


Lesenswert?

Bei solchen kleinen Zielbereichen (0-255) lohnt es sich, per Excel ein 
wenig zu nähern und dann Ersatzfunktionen zu suchen. Beispiel:

erster Näherung bei pwmMax=50 und EXPONENT = 2:

PWM = Index*255/50. Da wir die Abweichung schon klein, hat die Form 
eines Halbkreises. Näherung um "halbkreis" verfeinern:

PWM = Index*255/50 - Index*(50-Index)/x.

Mit x=9.8 wird die verbleibende Abweichung schon 0 über den ganzen 
Bereich.

Bei z.B. EXPONENT = 1.5 und x=15 ergeben sich Abweichungen von max. 3 
bis -6.

(nur so als Beispiel)

Spoiler: Die 9.8 bei ^2 ergeben sich als 50²/255. Aber auf so eine 
Formel kommt man in der Regel nicht analytisch, ausprobieren ist völlig 
ok. Und die Teiler bei anderen X sollten dann ebenso einfach sein. Bei 
kleineren Exponenten geht das (wie gesagt) nur näherungsweise, aber 
vermutlich ausreichend präzise.

von Axel S. (a-za-z0-9)


Lesenswert?

Johannes H. schrieb:
> Ich musste grad feststellen, dass mein ATTiny85 bei der Berechnung von
> separaten PWM-Werten für 3 RGB-LEDs an seine Grenzen stößt.
>
> Die Werte werden für Indizes von 0 bis INDEX_MAX nach dieser Formel
> berechnet(vereinfacht):
>
>PWM = 255 * pwmIndex^EXPONENT / (INDEX_MAX^EXPONENT);

Wenn das lediglich eine 8-Bit PWM ist, dann kannst du sowieso nur eine 
überschaubare Anzahl an Zwischenstufen realisieren, vielleicht 50. Und 
dafür reicht dann eine vorberechnete Tabelle. Warum man den Exponenten 
(im Prinzip den Gamma-Wert) im Betrieb ändern können müßte, erschließt 
sich mir auch nicht. Einfach die feinstmögliche Stufung als Tabelle 
wählen und fertig.

In meinem RGB-Moodlight habe ich eine ca. 10-Bit Soft-PWM mit ~200 
exponentiell angeordneten Helligkeitsstufen. Beim langsamen Durchlaufen 
der Farben sieht man keine Helligkeitssprünge. Der limitierende Faktor 
ist bei dir mit Sicherheit die zu grobe 8-Bit PWM.

von Johannes (menschenskind)


Angehängte Dateien:

Lesenswert?

@Achim
Eine lineare Approximation ist doch aber sinnfrei, wegen der 
logarithmischen Wahrnehmung des Auges.
Bitte schau mal das Bild an, bei Deiner zweiten Formel stimmt irgendwas 
nicht ;)
Aber so in der Richtung lässt sich vielleicht was finden.

@Axel
Das Problem ist, ich verwende die WS2812B RGB-LED mit integriertem 
PWM-Treiber und der generiert nur Werte von 0-255.
Die Werte werden nicht im laufenden Betrieb geändert, aber zum 
Rausfinden der optimalen Wertekombination hab ich den Code so gestaltet, 
dass ich nur an wenigen Werten "drehen" muss.
Wie hast Du denn das mit dem Farbwechsel von einer zur anderen Farbe 
gemacht?

von Benedikt S. (benedikt_s)


Lesenswert?

Du sollst zwischen den vorraus errechneten Stützstellen linear 
interpolieren.
Nicht alle Werte.

von Johannes (menschenskind)


Angehängte Dateien:

Lesenswert?

@Benedikt
Aber das macht es ja dann wieder umständlicher. Ich will ja eben keine
vorausberechneten Werte verwenden.
Ich hab auch nicht ganz verstanden, wie Du das jetzt gemeint hast.

@Achim
Ok, wenn man mit dem Teiler (anstatt 9.8) etwas rumspielt sieht es schon
ganz OK raus.
Anbei das Bild mit angepasstem Teiler.

von Axel S. (a-za-z0-9)


Lesenswert?

Johannes H. schrieb:
> @Axel
> Das Problem ist, ich verwende die WS2812B RGB-LED mit integriertem
> PWM-Treiber und der generiert nur Werte von 0-255.

Ja, gut. Dann hast du halt nicht mehr Helligkeitsstufen.

> Die Werte werden nicht im laufenden Betrieb geändert, aber zum
> Rausfinden der optimalen Wertekombination hab ich den Code so gestaltet,
> dass ich nur an wenigen Werten "drehen" muss.

Aha. Und wieso spielt dann die Geschwindigkeit bei der Berechnung der 
Tabelle überhaupt eine Rolle? Den optimalen Gamma-Wert mußt du doch nur 
einmal rausfinden (ich würde vorher mal danach googlen). Für die spätere 
Anwendung hast dann die vorberechnete Tabelle.

> Wie hast Du denn das mit dem Farbwechsel von einer zur anderen Farbe
> gemacht?

Ich habe die Tabelle mit den vorberechneten Helligkeitsstufen, konkret 
sind das 204 Schritte mit einem Faktor von 1.02 zwischen aufeinander 
folgenden Stufen. Durch die exponentielle Kurve steigt der 
Helligkeitseindruck linear und man kann einfach durch die Tabelle 
laufen:

1
for (i=0; i<=204; i++) {
2
    red=  pwmtable[i];
3
    blue= pwmtable[204-i];
4
    delay();
5
}

Das macht z.B. einen Farbverlauf von blau über violett nach rot. Je 
kürzer delay() ist, desto schneller. Real natürlich nicht mit delay(), 
sondern als Zustandsautomat mit einem Timer-Interrupt als Trigger.

Siehe dazu meinen Beitrag "noch ein AVR Moodlight"

Wenn deine PWM nur 8 Bit hat, kannst du keine rein exponentielle Tabelle 
verwenden, weil irgendwann die Schritte kleiner als 1 werden. Man könnte 
dann aber die anfangs exponentielle Tabelle mit Schritten von 1 zu Ende 
führen. Das folgende kleine Perl-Programm spuckt dir eine solche Tabelle 
aus:
1
perl -e '$f=1.02; for($x=255.0; $x>=0; ) { print int($x+0.5), " "; 
2
  $y= $x/$f; $x=(int($y+0.5)<int($x+0.5)) ? $y : $x-1 }'
3
4
255 250 245 240 236 231 226 222 218 213 209 205 201 197 193 189 186
5
182 179 175 172 168 165 162 159 155 152 149 146 144 141 138 135 133
6
130 128 125 123 120 118 115 113 111 109 107 105 103 101 99 97 95 93
7
91 89 88 86 84 82 81 79 78 76 75 73 72 70 69 68 66 65 64 63 61 60
8
59 58 57 56 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37
9
36 35 34 33 32 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15
10
14 13 12 11 10 9 8 7 6 5 4 3 2 1 0

von Johannes (menschenskind)


Lesenswert?

Hallo Axel,

>Aha. Und wieso spielt dann die Geschwindigkeit bei der Berechnung der
>Tabelle überhaupt eine Rolle?
Nein ich verwende ja eben keine Tabelle bzw. Tabellen. Denn für meinen 
jetzigen Ansatz bräuchte ich 6 Stück davon mit folgender PWM 
Charaktaristik:
(0->255), (255->0), (0->127), (127->0), (127->255), (255->127)

Das ist für den Farbübergang von einer zur nächsten Farbe nötig bzw. für 
die möglichen PWM-Kombinationen.

Für meinen 1. Ansatz mit einer LED hatte ich das auch (aber umständlich) 
mit einer Tabelle gemacht, aber für die aktuelle Version mit 3 LEDs den 
Code stark modifiziert.

Ich werde später Achims Formel mal ausprobieren, denn da muss ich ja 
bloß was in meiner bisherigen Formel austauschen. Sozusagen den Weg des 
geringsten Widerstandes ;)

von Axel S. (a-za-z0-9)


Lesenswert?

Johannes H. schrieb:
>>Aha. Und wieso spielt dann die Geschwindigkeit bei der Berechnung der
>>Tabelle überhaupt eine Rolle?

> Nein ich verwende ja eben keine Tabelle bzw. Tabellen.

Jo mei. Dann halt "bei der Berechnung des PWM-Wertes". Ich dachte es 
wäre in dem Kontext klar, was gemeint ist.

> Denn für meinen
> jetzigen Ansatz bräuchte ich 6 Stück davon mit folgender PWM
> Charaktaristik:
> (0->255), (255->0), (0->127), (127->0), (127->255), (255->127)
>
> Das ist für den Farbübergang von einer zur nächsten Farbe nötig bzw. für
> die möglichen PWM-Kombinationen.

Kann es sein, daß du das mit den Tabellen noch nicht so richtig 
verstanden hast? Ich habe natürlich keine Tabellen für die einzelnen 
Programme. Es gibt nur eine Tabelle; die mappt Zahlen zwischen 0 (aus) 
und MAX_BRIGHT (bei mir 204, bei dir mit nur 8 Bit PWM-Auflösung 
entsprechend weniger) auf PWM-Werte. Und zwar derart, daß sich über die 
Tabelle ein als linear empfundener Helligkeitsverlauf ergibt.

Wegen der im wesentlichen logarithmischen Kennlinie des Auges ist das 
eine Exponentialfunktion - das Verhältnis aufeinanderfolgender PWM-Werte 
ist immer gleich. Natürlich gibt es hier Einschränkungen: PWM-Stufen 
sind ganze Zahlen. Bzw. bei meiner Soft-PWM dürfen zwei PWM-Stufen nicht 
näher als 61 Takte liegen (so lange braucht die ISR). Deswegen schaffe 
ich trotz nominal 16 Bit Auflösung nur ca. 10 Bit reale Auflösung.

Alle Lichtprogramme verwenden dann lineare Sweeps über diese Tabelle.

Für meine Anwendung - psychedelische Lichtspielchen - ist das 
ausreichend. Wenn man andere Dinge vor hat, kann es notwendig sein, 
Zwischenwerte zu errechnen.

von W.S. (Gast)


Lesenswert?

Johannes H. schrieb:
> In Internetz konnte ich nichts finden was mir zu Polynomberechnung in
> Festkommaarithmetik weiterhilft

macht man auch nicht wirklich. Mach's lieber per 
Pseudodivision/Pseudomultiplikation. Das geht viel besser.

Prinzip: a^b = a^(b1+b2+b3+...) = a^b1 * a^b2 * a^b3 ... usw.
jetzt brauchst du dein b nur in Stücke zu zerlegen, derart, daß a^bx 
einer glatten Rechtsverschiebung entspricht, also vom Prinzip her
a^b0 = 1
a^b1 = 1.1
a^b2 = 1.01
a^b3 = 1.001
a^b4 = 1.0001
und so weiter. Was im Dezimalen glatt aussieht, ist im binären genauso 
glatt.
Damit wird die Multiplikation zu einer Addition des jeweilig um ein 
weiteres Bit rechtsverschobenen Multplikanden.

Nochwas: man braucht nicht alle Stellen von b auszurechnen, denn der 
Rest gibt fast die doppelte Bitanzahl dazu. Für 16 Bit schätze ich, daß 
du nur b0..b5 ausrechnen mußt und dann den Rest als b6..15 addieren 
kannst.

W.S.

von Johannes (menschenskind)


Lesenswert?

Hallo W.S.

Uff, Deinen Ansatz habe ich leider nicht wirklich verstanden.
Könntest Du das bitte an Hand einer Beispielberechnung erläutern?

Wenn a^b0 = 1, dann muss b0 ja 0 sein, aber wie man das mit b1 bis bn 
weiterführt erschließt sich mir nicht.
Und eine Addition der Werte ergäbe ja dann 1+1.1+1.01+... also einen 
immer größer werdenden Wert, abhängig wieviele Exponentialglieder man in 
die Rechnung einbezieht.


@Achim
Deine Idee funktioniert gut und ich musste nur kleine Anpassungen am 
Code vornehmen. Danke dafür.

von M. K. (sylaina)


Lesenswert?

Johannes H. schrieb:
> Wenn a^b0 = 1, dann muss b0 ja 0 sein

Nein, kann auch 2 oder 3 oder 4 oder ... sein...falls a 1 ist. Sobald du 
eine von den beiden Variablen festlegst fällt die andere automatisch bei 
raus. So recht verstanden hab ich W.S. aber auch nicht. Ich finde z.B. 
dass 100.0 im dezimalen sehr glatt ausschaut. Warum jetzt aber 1100100 
glatt aussehen soll vermag ich nicht zu sagen.

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.