Hallo Zusammen,
ich möchte Werte aus der Tabelle „sinustabelle[omega]“ mit einem
Sollwert skalieren. Die Werte liegen zwischen 0 und 1250.
Der Sollwert kommt über parallele Eingänge an PortD. 0-63 entspricht
0-100%. Da ich im Programm durch 64 teile stimmt das nicht ganz, ist
aber nicht so schlimm.
Da Werte für eine PWM mit 16KHz berechnet werden sollen, habe ich nur
62,5µsec Zeit, bis der nächste Wert benötigt wird.
Der folgende Programmteil hat eine Bearbeitungszeit von 33,45 µsec
@20MHz in der Simulation. Liegt zwar noch im grünen Bereich, aber geht
das schneller zu berechnen?
Hier ist der entscheidende Teil:
bei Faktoren wie 64=2^6 kannst du schieben (hilf = hilf << 6). Aber je
nach Complier wird das bei festen Faktoren schon optimiert. Ausserdem
testest Du zwei mal "if (omega<=160)" und "if (omega>=160)"
hilf= (sinustabelle[omega]*sollwert);
hilf=hilf << 6;
zwischenspeicher_a=(top-hilf);
if (omega<=160)
{
zwischenspeicher_b=top;
}
else
{
zwischenspeicher_b = zwischenspeicher_a;
zwischenspeicher_a = top;
}
Hallo micha,
das Schieben hat es echt gebracht. Ich dachte auf die Idee kommt der
Compiler auch.
Die Optimierung der If Abfragen brachte auch noch einmal fast zwei µsec.
Bin jetzt bei 4,6µsec Bearbeitungszeit
Super vielen Dank.
Kleiner Tipp: schau dir das ganze mal im Assemblerfile an.
Da findest Du schnell heraus wo der Compiler Probleme hat.
Was mir aufgefallen ist:
- Sinustabelle ist signed enthält aber nur unsigned Werte die
Multiplikation von unsigned Werten ist normalerweise etwas kürzer als
signed
- Wenn du Die Tabelle auf unter 1024 Skalierst (z.B. halbierte Werte)
kommst du bei der Multiplikation mit unsigned int aus. (in dem Fall nur
durch 32 Teilen).
- eventuell kannst Du durch tauschen von if + else Zweig für die lange
Berechnung noch 1-2 Zyklen sparen.
Gruß Anja
micha schrieb:> hilf=hilf << 6;
ist die falsche Richtung: = Multiplikation mit 64.
Das ganze sollte hinterher auch noch das richtige Ergebnis bringen.
Axel Düsendieb schrieb:> 4,6µsec Bearbeitungszeit
= 92 Takte da habe ich Zweifel ob wirklich eine 32*32 Bit Multiplikation
gerechet wird.
Da Fehlt noch ein cast auf long vor der Tabelle.
Gruß Anja
Anja schrieb:> micha schrieb:>> hilf=hilf << 6;>> ist die falsche Richtung: = Multiplikation mit 64.> Das ganze sollte hinterher auch noch das richtige Ergebnis bringen.
ARG, schnell, meeehhhr Kaffee....
(Na ja, immerhin zeigt es die richtige Richtung :-) Danke Anja!)
Anja schrieb:> da habe ich Zweifel ob wirklich eine 32*32 Bit Multiplikation> gerechet wird.> Da Fehlt noch ein cast auf long vor der Tabelle.
Die Zweifel habe ich auch, da das Programm nicht richtig funktioniert.
Wie und wo wird der cast gemacht?
Axel Düsendieb schrieb:> Die Zweifel habe ich auch, da das Programm nicht richtig funktioniert.> Wie und wo wird der cast gemacht?
ich würde es so machen:
hilf= (((long)sinustabelle[omega])*sollwert)
Ansonsten hast Du ein Problem wenn 1249 * 63 gerechnet wird.
sinustabelle ist ja integer (16 Bit signed) sollwert sollwert char (8bit
signed). Im Normalfall wird dann eine signed 16 * 16 Bit Multiplikation
mit 16 Bit Ergebnis aufgerufen. Der Überlauf wird abgeschnitten (oder
wenn Du Glück hast auf +/-32767 limitiert).
Gruß Anja
hilf = ((uint32_t)(sinustabelle[omega]*sollwert));
Wohl eher so ? Sonst gibt es wirklich ne 32 bit MUL...
Oder irre ich mich?
grüße Jonas & sorry für doppelpost
jonas biensack schrieb:> hilf = ((uint32_t)(sinustabelle[omega]*sollwert));>> Wohl eher so ?
So bringt der ganze cast nichts.
Das Problem ist, dass die Multiplikation im 16-Bit Zahlenbereich
überlaufen kann. Wenn du ein falsches Ergebnis nach der Multiplikation
auf 32 Bit aufbläst, ist das sinnlos.
Aber was anderes könnte noch was bringen.
hilf >> 6
ist eine relativ teure Operation, weil der AVR keinen Barrelshifter hat
und 6 mal Einzelbitshiften bei 32 Bit schon heftig ist.
hilf >> 8
könnte der Compiler aber durch eine Bytevertauschung implementieren.
Nun kannst du aber nicht einfach durch 256 dividieren, wenn durch 64
richtig wäre. Es sei denn du gleichst das aus, indem du die Sinuswerte
in der Tabelle überhaupt als das 4-fache vorlegst. Dann kommt
mathematisch am Ende alles wieder richtig raus.
Ob das was bringt, müsste man sich im Assemblercode ansehen.
Anja:
> - Wenn du Die Tabelle auf unter 1024 Skalierst (z.B. halbierte> Werte) kommst du bei der Multiplikation mit unsigned int aus.> (in dem Fall nur durch 32 Teilen).
Genau, jedoch nutzt Du beim Halbieren nicht die vorhandene Dynamik.
Besser: Sinustabelle-Wertebereich 0..1023 und wenn unbedingt nötig,
danach mit 1,25 multiplizieren (63*1023*1,25/64=1258,769)
uint16_t hilf;
hilf = sinustabelle[omega]*sollwert;
hilf >>=1; //um genug Platz für *1,25 zu bilden
hilf += hilf >> 2; //*1,25
hilf >>=5;
jetzt bist Du garantiert unter 1 µs.
eProfi schrieb:> jetzt bist Du garantiert unter 1 µs.
Die hätte man auch gehabt wenn man das ganze in Assembler löst.
dort könne man gezielt eine 8Bit * 16 Bit Multiplikation
= 2 MUL-Befehle + 2 ADD-Befehle = 6 Takte verwenden.
Karl Heinz Buchegger schrieb:> Es sei denn du gleichst das aus, indem du die Sinuswerte> in der Tabelle überhaupt als das 4-fache vorlegst.
oder wenn man "sollwert" um 2 Bits nach links schiebt
Axel Düsendieb schrieb:> ich möchte Werte aus der Tabelle „sinustabelle[omega]“ mit einem> Sollwert skalieren. Die Werte liegen zwischen 0 und 1250.> Der Sollwert kommt über parallele Eingänge an PortD. 0-63 entspricht> 0-100%. Da ich im Programm durch 64 teile stimmt das nicht ganz, ist> aber nicht so schlimm.
Muss bei der Skalierung mit dem Faktor 0 wirklich bei jedem Wert 0
rauskommen? Eventuell mit Faktor 1 anfangen, dann stimmts auch mit der
Division. Ansonsten
eProfi schrieb:> Sinustabelle-Wertebereich 0..1023
Da reichen dann 16Bit.
1
hilf=sinustabelle[omega]*(sollwert+1);
und mögliche Verfeinerungen für die Geschwindigkeit.
(Vielleicht bekommt man das hin, wie ich es in Assembler direkt machen
würde: 2 Bits nach links schieben/ rollen, und dann die oberen 16Bit
verwenden?)
Wenn Du Ausgangswerte von 0 .. 1249 willst, skaliere die Sinustabelle
auf 0..1015 oder 0..1016
63*1015*1,25/64=1248,92578
63*1016*1,25/64=1250,15625
Um genau auf 1249 zu kommen, müsstest Du 1015 nehmen und (auf)runden.
Oder 1023 lassen und die *1,25 korrigieren auf 1,241291563.
//32224+ 8056 - 251 =40029 /32 = 1250
hilf+=hilf>>2-hilf>>7;
Alternativ:
hilf2=hilf>>8; //geht schnell, da nur das untere Byte genommen wird
//32224+ 8056 -125 -125 =40030 /32 = 1250
hilf+=hilf>>2-hilf2-hilf2;
Rest wie oben gepostet.
Axel Düsendieb schrieb:> Da Werte für eine PWM mit 16KHz berechnet werden sollen, habe ich nur> 62,5µsec Zeit, bis der nächste Wert benötigt wird.
Muss das wirklich so sein? Also: Kaum hat sich OCR1x mit dem neuen Wert
angefreundet, zack!, kommt schon der nächste!
Lässt sich an den absoluten Zahlen für OCR1x und der PWM-Frequenz 'noch
was machen'? Ich würde die 1023 (max. Ergebnis) gleich als Topwert
nehmen.
Karl Heinz Buchegger schrieb:> Nun kannst du aber nicht einfach durch 256 dividieren, wenn durch 64> richtig wäre. Es sei denn du gleichst das aus, indem du die Sinuswerte> in der Tabelle überhaupt als das 4-fache vorlegst. Dann kommt> mathematisch am Ende alles wieder richtig raus.
Hallo Karl Heinz,
gute Idee, werde ich gleich mal probieren.
Axel
Ralf schrieb:>> Sinustabelle-Wertebereich 0..1023> Da reichen dann 16Bit.
Stimmt schon, aber dann bin ich bei 10 Bit PWM mit fast 20KHz. Ich
glaube, ich kann mit dem Erreichten schon gut leben.
Danke noch mal an Alle.
Axel
Ralf schrieb:> Muss das wirklich so sein? Also: Kaum hat sich OCR1x mit dem neuen Wert> angefreundet, zack!, kommt schon der nächste!
Oh Mann, konnte glatt nicht wieder einschlafen, als mir diese Nacht
eingefallen ist, was das soll :-(