Forum: Mikrocontroller und Digitale Elektronik Skalierung von Integerwerten


von Axel D. (axel_jeromin) Benutzerseite


Angehängte Dateien:

Lesenswert?

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:
1
hilf= (sinustabelle[omega]*sollwert);
2
hilf=hilf/64;
3
zwischenspeicher_a=(top-hilf);


und hier das ganze Hauptprogramm:
1
int main(void)
2
{
3
char sollwert=0;  // Ausgangssollwert
4
long hilf=0;
5
6
// ******** Initialisierung ****************************************
7
initial();  // Initialisierung Ports etc.
8
// ******** Hauptschleife ******************************  
9
    
10
  while(1)      // 33,45 µsec Bearbeitungszeit @20MHz
11
  {
12
13
sollwert= (PIND & 0x3f);  // Sollwert von 0 bis 63 entspricht 0 bis 100% (na ja, fast)
14
15
if (omega<=160) {   hilf= (sinustabelle[omega]*sollwert);
16
          hilf=hilf/64;
17
          zwischenspeicher_a=(top-hilf); 
18
          }
19
          else zwischenspeicher_a=top;
20
21
if (omega>=160) {  hilf= (sinustabelle[omega]*sollwert);
22
          hilf=hilf/64;
23
          zwischenspeicher_b=(top-hilf);
24
          }
25
          else zwischenspeicher_b=top;
26
27
// ************************************************************** 
28
  } // Ende while(1)Endlosschleife
29
  return 0;
30
}// Ende Hauptprogramm

Grüße
Axel

von micha (Gast)


Lesenswert?

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;
}

von Axel D. (axel_jeromin) Benutzerseite


Lesenswert?

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.

von Anja (Gast)


Lesenswert?

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

von Anja (Gast)


Lesenswert?

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

von Axel D. (axel_jeromin) Benutzerseite


Lesenswert?

Hallo Anja,
guter Hinweis mit den unsigned Werten, hatte mich gerade gefragt, warum 
das mit dem Schieben nicht funktioniert hatte.

Danke

von micha (Gast)


Lesenswert?

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!)

von Axel D. (axel_jeromin) Benutzerseite


Lesenswert?

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?

von Anja (Gast)


Lesenswert?

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

von Axel D. (axel_jeromin) Benutzerseite


Lesenswert?

Danke an Alle


jetzt klappt es
1
int main(void)
2
{
3
unsigned char sollwert=0;  // Ausgangssollwert
4
unsigned long hilf=0; 
5
6
7
// ******** Initialisierung **********************************
8
initial();  // Initialisierung Ports etc.
9
// ******** Hauptschleife ************************************  
10
    
11
  while(1)      // 6,50 µsec Bearbeitungszeit @20MHz
12
  {
13
14
sollwert= (PIND & 0x3f);  // Sollwert von 0 bis 63 entspricht 0 bis 100% (na ja, fast)
15
16
hilf = ((uint32_t)sinustabelle[omega]*(uint32_t)sollwert);
17
hilf=hilf >> 6;
18
19
if (omega<=160)
20
{
21
  zwischenspeicher_a=(top-hilf);
22
  zwischenspeicher_b=top;
23
}
24
else
25
{
26
  zwischenspeicher_b = (top-hilf);
27
  zwischenspeicher_a = top;
28
}
29
30
// ******************************************************** 
31
  } // Ende while(1)Endlosschleife
32
  return 0;
33
}// Ende Hauptprogramm

von jonas biensack (Gast)


Lesenswert?

>hilf = ((uint32_t)sinustabelle[omega]*(uint32_t)sollwert);

Warum der hintere Cast ?

von jonas biensack (Gast)


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

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.

von eProfi (Gast)


Lesenswert?

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.

von eProfi (Gast)


Lesenswert?

naja, sagen wir lieber unter 2 µs.

von Anja (Gast)


Lesenswert?

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

von Ralf (Gast)


Lesenswert?

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?)

von eProfi (Gast)


Lesenswert?

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.

von Ralf (Gast)


Lesenswert?

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.

von Axel D. (axel_jeromin) Benutzerseite


Lesenswert?

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

von Ralf (Gast)


Lesenswert?

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 :-(

von Düsendieb (Gast)


Lesenswert?

Man hat schon ein hartes Leben als OCR1x Register.


Aber: wer zwei mal mit der Gleichen pennt......

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.