Skalierung von Integerwerten

Persönliche Seite #2236639
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
Angehängte Dateien:
Gast #2236656
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;
}
Gast #2236678
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
Gast #2236701
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!)
Gast #2236755
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
Persönliche Seite #2236779
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
#2236821
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.
Gast #2236831
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.
Gast #2236863
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
Gast #2236877
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?)
Gast #2236899
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.
Gast #2236949
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.
Persönliche Seite #2237407
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

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren