Forum: Mikrocontroller und Digitale Elektronik Sinusberechnung auf Controller STM32F030


von Ralph S. (jjflash)


Lesenswert?

Ich muss auf einem Controller (leider) einen Sinus berechnen und habe 
für die MATH Bibliothek nicht genügend Speicher übrig, deswegen habe ich 
altes Schulwissen ausgekramt und händisch gecoded, aber zufrieden bin 
ich damit nicht (allerdings reichen 4 Stellen Genauigkeit aus).

Folgender Code belegt auf einem STM32F030F4P6 ca. 4500 Bytes:
1
#define M_PI     3.14159265359
2
3
float tiny_pow(int n, float value)
4
{
5
  float tmp;
6
7
  tmp= value;
8
  for (int i= 0; i < n-1; i++)
9
  {
10
    tmp= tmp*value;
11
  }
12
  return tmp;
13
}
14
15
float tiny_sin(float value)
16
{
17
18
  float degree;
19
  float p3;
20
  float p5;
21
  float p7;
22
  float sinx;
23
24
  int   mflag= 0;
25
26
  while (value > 360) value -= 360;
27
  if (value > 180)
28
  {
29
    mflag= - 1;
30
    value -= 180;
31
  }
32
33
  if (value > 90) value = 180 - value;
34
35
  degree= (value * M_PI) / 180;
36
37
  p3 = tiny_pow(3, degree);
38
  p5  = tiny_pow(5, degree);
39
  p7  = tiny_pow(7, degree);
40
41
  sinx= (degree - (p3/6) + (p5/120) - (p7/5040));
42
43
  if (mflag) sinx = sinx * (-1);
44
  return sinx;
45
}

Hat hier jemand vllt. etwas in Petto, das kürzeren Code produziert ?

Gruß, Ralph

von H. K. (spearfish)


Lesenswert?

Welche Wertebereich brauchst du? Wie fein muss die Unterteilung sein?

Du kannst eine Look-Up-Table bauen. Du brauchst nur den Wertebereich 0° 
bis 90° abspeichern. Bei 1° Winkelgenauigkeit reichen dann eben 90 
Werte.

von Pandur S. (jetztnicht)


Lesenswert?

Und den Sinus benoetigst du wozu ? Allenfalls ist man mit einer Tabelle 
sowieso besser bedient. Falls es immer wieder dieselben Werte sind. Ich 
wuerd mir aber eher 2^N Werte abspeichern. Also 64, 128, 256, das machts 
dann einfacher mit Wrap-aroud

: Bearbeitet durch User
von Sepp (Gast)


Lesenswert?

Ich verwendete auch meist die Taylor-Reihenentwicklung.

Wenn man ein paar Stützstellen speichert (z.B. 16 oder 32) kann man den 
Summensatz sin(a+-b) = sin(a)*cos(b)+-cos(a)*sin(b) sehr gut verwenden 
um schnell den Sinus zu berechnen.

a sind dann einfach die Stützstellen und b die Abweichung zum 
gewünschten Wert. sin(a) und cos(a) sind in der Stützstellentabelle (da 
braucht man auch nur die Werte von 0 bis PI/2, danach spiegeln bzw. 
invertieren), sin(b) und cos(b) kann man mit Taylor-Reihenentwicklung 
machen, braucht aber viel weniger Potenzen für ein gutes Ergebnis, da b 
immer nur sehr klein ist.

Auf einem DSP schaffe ich so eine Sinusberechnung im Schnitt 
(Interleaved) in 11 Zyklen bei 16 Bit Genauigkeit bei einer geringen 
Codegröße.

Wenn die Performance egal ist und nur die Codegröße zählt, dann dürfte 
obige Version sehr gut sein.

von Dergute W. (derguteweka)


Lesenswert?

Moin,

Wenns denn unbedingt eine Potenzreihe sein muss (ich bin auch ein 
grosser Fan der Lookup-Table), dann kann man die noch etwas umformen.
Aus dem Polynom einmal mit Gewalt x ausklammern, danach noch aus den 
verbliebenen Teilen mit Gewalt (1/bla*x²) ausklammern.
Das gibt dann z.b. sowas:

sin(x)~x*(1-1/6*x²*(1-1/20*x²*(1-1/42*x²)))

Einmal x² berechnen, dann das ganze von hinten her ausrechnen.
Damit braucht man statt der ganzen Potenzen von x nur noch das Quadrat. 
Insgesamt wird man auch weniger Probleme mit Rechenungenauigkeiten 
haben, weil die Faktoren nicht so gross/klein werden, wie mit den echten 
Potenzen im Zaehler und echten Fakultaeten im Nenner.
Ob der Code dann kuerzer wird? Keine Ahnung. Aber wahrscheinlich 
schneller.

Gruss
WK

von Sepp (Gast)


Lesenswert?

Ach ja, ich würde die Zwischenergebnisse nutzen. Spart bei den meisten 
Compilern Code Größe und Performance.
1
float tiny_sin(float value)
2
{
3
4
  float degree;
5
  float p3;
6
  float p5;
7
  float p7;
8
  float sinx;
9
10
  int   mflag= 0;
11
12
  while (value > 360) value -= 360;
13
  if (value > 180)
14
  {
15
    mflag= - 1;
16
    value -= 180;
17
  }
18
19
  if (value > 90) value = 180 - value;
20
21
  degree= (value * M_PI) / 180;
22
23
  p2 = degree * degree
24
  p3 = p2 * degree;
25
  p5  = p3 * p2;
26
  p7  = p5 * p2;
27
28
  sinx= (degree - (p3/6) + (p5/120) - (p7/5040));
29
30
  if (mflag) sinx = sinx * (-1);
31
  return sinx;
32
}

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Hmm, ich habs mal in AVR Studio kopiert - 3742 Bytes mit einem minimalen 
main().
Wenn man alles auf 'long' statt 'float' umstellt und dann mit z.B. 
'1825000' für 182,5 Grad arbeitet, sind nur noch 2780 oder so Bytes fürs 
Programm.

: Bearbeitet durch User
von Ralph S. (jjflash)


Lesenswert?

... ich hatte zuerst eine Lookup-Tabelle mit 0,5° Schritten gehabt, 
allerdings nur bis 45° (und die Werte sogar als Integer-Werte * 100000 
gespeichert gehabt = > 90 Werte * 32 Bit = 720 Byte).

Von 45 Grad dann auf den Vollkreis umrechnen und das Interpolieren 
dazwischen war dann in der Codegröße insgesamt größer als das, was ich 
jetzt habe.

Geschwindigkeit spielt nicht die Rolle.

Ich brauche dieses, weil ich nach Vorgaben eines unserer Wissenschaftler 
eine von ihm vorgegebenen Meßwert verrechnen muß, in dem Sinus, Wurzel 
und natürlicher Logarithmus vorkommt.

Wenn es nicht hinhaut, muß ich wohl (was mich ärgern würde) auf einen 
größeren Controller umsteigen.

von Ralph S. (jjflash)


Lesenswert?

Matthias S. schrieb:
> Hmm, ich habs mal in AVR Studio kopiert - 3742 Bytes mit einem minimalen
> main().
> Wenn man alles auf 'long' statt 'float' umstellt und dann mit z.B.
> '1825000' für 182,5 Grad arbeitet, sind nur noch 2780 oder so Bytes fürs
> Programm.

Im AVR wäre das so nicht ein Problem, weil ein 8 Bit breit, bei einem 
ARM dieses jedoch 32 Bit breit.

Es wäre mal einen Vergleich wert, wie groß der Code auf einem AVR und 
wie groß der Code auf einem ARM wird.

Den ARM (mit seiner höheren Taktfrequenz) brauch ich, weil an diesem die 
I/O Pins deutlich höhere Frequenzen möglich als auf einem AVR sind.

von Ralph S. (jjflash)


Lesenswert?

Sepp schrieb:
> ch ja, ich würde die Zwischenergebnisse nutzen. Spart bei den meisten
> Compilern Code Größe und Performance.

Tut es leider nicht, scheinbar ist der arm-none-eabi-gcc so gut in der 
Optimierung, dass es keinen Unterschied macht...

auch etwas in der Art:

p3= degree  degree  degree;

anstelle von tiny_pow bringt nichts (abgesehen davon , dass ich das 
tiny_pow an anderer Stelle auch noch benötige).

Hmmm, mach ich mal weiter in der Hoffnung, dass nicht mehr zu viel an 
Code hinzukommt und es noch "reinpasst"...

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Ralph S. schrieb:
> Im AVR wäre das so nicht ein Problem, weil ein 8 Bit breit, bei einem
> ARM dieses jedoch 32 Bit breit.

Genau das ist deinen Chance, hatte nur keine Zeit mehr, zu editieren. 
Der ARM ist nativ 32 bit breit und würde damit bei int32 Typen besser 
abschneiden als der olle AVR.
Ich probier das nochmal für den VL Discovery auf CooCox...

Der Haken ist der Wertebereich. MAX_LONG liegt bei 0x7fffffff und würde 
bei 4 Digits nach dem Komma maximal bis 214748,3647 Grad gehen.

: Bearbeitet durch User
von Georg (Gast)


Lesenswert?

Ralph S. schrieb:
> und habe
> für die MATH Bibliothek nicht genügend Speicher übrig

Ich weiss nicht, was dein Compiler kann, aber in vergleichbaren Fällen 
habe ich die Meldungen des Linkers ausgewertet, welche Routinen er nicht 
findet, und die einzeln solange eingebunden, bis der Linker zufrieden 
ist. Das Progamm kann dann Sinus berehcnen, aber sonst nichts. Man 
braucht dazu ja nicht eine ganze Math-Bibliothek. Ist natürlich Arbeit, 
Selberschreiben aber auch.

Das beruhte auf dem klassischen Workflow mit Compiler, Linker, 
Librarian. Moderne IDEs haben da viel weniger Möglichkeiten, das ist 
eben der Fortschritt, beruhend auf dem Dogma dass man immer und überall 
mehr als genug Speicher hat.

Georg

von Dergute W. (derguteweka)


Lesenswert?

Moin,

Ralph S. schrieb:
> Ich brauche dieses, weil ich nach Vorgaben eines unserer Wissenschaftler
> eine von ihm vorgegebenen Meßwert verrechnen muß, in dem Sinus, Wurzel
> und natürlicher Logarithmus vorkommt.

Haste schonmal geguckt, ob du nicht die gesamte Rechnerei mittels einer 
Taylor- oder Sonstigen-Reihenentwicklung erschlagen kannst? Das koennte 
dann etwas Platz schaffen, weil du "nur noch" eine Reihenberechnung hast 
und keine weiteren Rechnungen mehr.

Gruss
WK

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Matthias S. schrieb:
> Ich probier das nochmal für den VL Discovery auf CooCox...

So, ganz kurz habe ich das mal auf einem STM32F030 Build probiert, 
allerdings habe ich kein Board hier.
Das Originalprogramm war bei mir bei 7800 Bytes... keine Ahnung, woher 
der Unterschied zu deinem kommt.
Alle 'float' auf 'long' und mit langen Integern gefüttert (also z.B. 
1800000 statt 180,0000) als Festkomma Arithmetik.
https://www.mikrocontroller.net/articles/Festkommaarithmetik

Das Programm ist nur noch 540 Bytes lang.

von H. K. (spearfish)


Lesenswert?

Ralph S. schrieb:
> Ich brauche dieses, weil ich nach Vorgaben eines unserer Wissenschaftler
> eine von ihm vorgegebenen Meßwert verrechnen muß, in dem Sinus, Wurzel
> und natürlicher Logarithmus vorkommt.

Muss denn diese "große" Umrechnung am µC passieren? Evtl wäre es 
zielführender nur das am µC zu berechnen, was für den nächsten 
Prozessschritt benötigt wird und den Rest der Datenauswertung am PC zu 
machen.

von ./. (Gast)


Lesenswert?

1
float sintk[]={0.0000027F, -0.0001984F, 0.0083333F, -0.1666667F};
2
3
float sint(float arg)
4
{
5
int i;
6
float q,s;
7
for(q=(float)(arg*arg),i=0,s=0.0F;i<4;i++)
8
  s = (s + sintk[i]) * q;
9
return (s + 1.0F) * arg;
10
}
11
12
float cost(float arg)
13
{
14
  return sint((float)_PI/2.0F - arg);
15
}

Auf einem M4:
sint                  0x08000041  0x32  Code  Gb  sint.o [1]
sintk                 0x20000000  0x10  Data  Gb  sint.o [1]

Auf einem M3:
sint                  0x080002e5  0x3a  Code  Gb  sint.o [1]
sintk                 0x20000000  0x10  Data  Gb  sint.o [1]
dazu aus der Runtimelib:
    FltAdd.o              132
    FltMul.o              216
    FltSub.o              214

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


Lesenswert?

Der Klassiker für trigonometrische Funktionen:

https://en.wikipedia.org/wiki/CORDIC

von Reinhard M. (Gast)


Lesenswert?

Axel S. schrieb:
> Der Klassiker für trigonometrische Funktionen:

Auf jeden Fall.
Zum Testen:

cordic-32bit.c
1
//Cordic in 32 bit signed fixed point math
2
//Function is valid for arguments in range -pi/2 -- pi/2
3
//for values pi/2--pi: value = half_pi-(theta-half_pi) and similarly for values -pi---pi/2
4
//
5
6
7
//Constants
8
#define cordic_1K 0x26DD3B6A
9
#define half_pi 0x6487ED51
10
//#define MUL 1073741824.000000
11
#define CORDIC_NTAB 32
12
int cordic_ctab [] = {0x3243F6A8, 0x1DAC6705, 0x0FADBAFC, 0x07F56EA6, 0x03FEAB76, 0x01FFD55B, 0x00FFFAAA,
13
                      0x007FFF55, 0x003FFFEA, 0x001FFFFD, 0x000FFFFF, 0x0007FFFF, 0x0003FFFF, 0x0001FFFF,
14
                      0x0000FFFF, 0x00007FFF, 0x00003FFF, 0x00001FFF, 0x00000FFF, 0x000007FF, 0x000003FF,
15
                      0x000001FF, 0x000000FF, 0x0000007F, 0x0000003F, 0x0000001F, 0x0000000F, 0x00000008,
16
                      0x00000004, 0x00000002, 0x00000001, 0x00000000, };
17
18
void cordic(int theta, int *s, int *c, int n)
19
{
20
  int k, d, tx, ty, tz;
21
  int x=cordic_1K,y=0,z=theta;
22
  n = (n>CORDIC_NTAB) ? CORDIC_NTAB : n;
23
  for (k=0; k<n; ++k)
24
  {
25
    d = z>>31;
26
    //get sign. for other architectures, you might want to use the more portable version
27
    //d = z>=0 ? 0 : -1;
28
    tx = x - (((y>>k) ^ d) - d);
29
    ty = y + (((x>>k) ^ d) - d);
30
    tz = z - ((cordic_ctab[k] ^ d) - d);
31
    x = tx; y = ty; z = tz;
32
  }
33
 *c = x; *s = y;
34
}

main.c
1
#include <math.h> // for testing only!
2
3
#define MUL 1073741824.000000
4
void cordic(int theta, int *s, int *c, int n);
5
6
//Print out sin(x) vs fp CORDIC sin(x)
7
int main(int argc, char **argv)
8
{
9
    double p;
10
    int s,c;
11
    int i;
12
    for(i=0;i<50;i++)
13
    {
14
        p = (i/50.0)*M_PI/2;
15
        //use 32 iterations
16
        cordic((p*MUL), &s, &c, 32);
17
        //these values should be nearly equal
18
        printf("%f : %f\n", s/MUL, sin(p));
19
    }
20
21
    return 0;
22
}

gcc main.c cordic-32bit.c -o CordicTest

von MaWin (Gast)


Lesenswert?

Ralph S. schrieb:
> Hat hier jemand vllt. etwas in Petto, das kürzeren Code produziert ?

Alleine wenn du den code auf integer umstellst (bekommst du ein Ergebnis 
zwischen 0 und 32767) wird es schon mal viel kleiner und schneller.

CORDIC ist aber die beste Lösung für exakte Ergebnisse.

Eine Tabelle z.b. für 0, 10, 20, 30, 40...90 Grad und dazwischen linear 
interpolieren (wieder im Interger-Zahlenbereich) wird aber auch gern 
verwendet.

von Dr. Sommer (Gast)


Lesenswert?

Georg schrieb:
> Moderne IDEs haben da viel weniger Möglichkeiten, das ist
> eben der Fortschritt, beruhend auf dem Dogma dass man immer und überall
> mehr als genug Speicher hat

Das braucht man zum Glück bei modernen Compilern auch alles nicht, denn 
der GCC/gnu ld z.B. kann automatisch alle Funktionen/Variablen aus den 
Libraries entfernen und nur exakt die einbinden, die man benötigt. All 
die manuelle Fummelei ist somit vollkommen unnötig.

Das schaltet man ein indem man -ffunction-sections -fdata-sections an 
den Compiler, und -Wl,--gc-sections an den Linker übergibt. -flto an 
Compiler und Linker übergeben optimiert noch mehr.

von Georg (Gast)


Lesenswert?

Dr. Sommer schrieb:
> Das braucht man zum Glück bei modernen Compilern auch alles nicht, denn
> der GCC/gnu ld z.B. kann automatisch alle Funktionen/Variablen aus den
> Libraries entfernen und nur exakt die einbinden, die man benötigt

So stelle ich mir das ja auch vor. Es soll aber auch 
Entwicklungsumgebungen geben, die der Einfachkeit halber die gesamte 
Runtime einbinden und bei denen daher ein "Hello world" schon MByte 
gross ist. Bei Embedded ist das eigentlich untragbar, daher wundert es 
mich wenn eine Sinusberechnung den Speicherplatz sprengt.

Georg

von Ralph S. (jjflash)


Lesenswert?

Heinz K. schrieb:
> Ralph S. schrieb:
icher Logarithmus vorkommt.
>
> Muss denn diese "große" Umrechnung am µC passieren? Evtl wäre es
> zielführender nur das am µC zu berechnen, was für den nächsten
> Prozessschritt benötigt wird und den Rest der Datenauswertung am PC zu
> machen.

Es gibt keinen PC der das später auswertet, das wird ein 
"Handschätzeisen" mit Display, der unterm Strich genau 3 Zahlen anzeigen 
wird.

... ich probiere mal CORDIC aus ... auch wenn ich im Rest des Programms 
so viel abgespeckt habe, dass wohl alles reinpasst was rein soll !

:-) mußte schon lange nicht mehr so mit Speicher geizen wie jetzt !

von Dergute W. (derguteweka)


Lesenswert?

Moin,

Ralph S. schrieb:
> Es gibt keinen PC der das später auswertet, das wird ein
> "Handschätzeisen" mit Display, der unterm Strich genau 3 Zahlen anzeigen
> wird.

Also das taet' imho schon danach schreien, die gesamte Funktion, incl. 
aller sinuesse, wurzeln und logarithmen in eine einzige 
Reihenentwicklung zu quetschen und die dann ggf. auch noch auf 
Integer-Arithmetik zu bringen. Ok, wenn sich das drueber nachdenken und 
die (Kopf)Rechnerei lohnt. Ansonsten wirds billiger sein, eine dickere 
CPU zu nehmen.

Gruss
WK

von Sabun (Gast)


Lesenswert?

Dergute W. schrieb:
> Moin,
>
> Ralph S. schrieb:
>> Es gibt keinen PC der das später auswertet, das wird ein
>> "Handschätzeisen" mit Display, der unterm Strich genau 3 Zahlen anzeigen
>> wird.
>
> Also das taet' imho schon danach schreien, die gesamte Funktion, incl.
> aller sinuesse, wurzeln und logarithmen in eine einzige
> Reihenentwicklung zu quetschen und die dann ggf. auch noch auf
> Integer-Arithmetik zu bringen. Ok, wenn sich das drueber nachdenken und
> die (Kopf)Rechnerei lohnt. Ansonsten wirds billiger sein, eine dickere
> CPU zu nehmen.
>
> Gruss
> WK

Deine Rechtschreibung ist zum Heulen.

Stuss

LJ

von Ralph S. (jjflash)


Lesenswert?

Reinhard M. schrieb:
> Axel S. schrieb:
>> Der Klassiker für trigonometrische Funktionen:
>
> Auf jeden Fall.
> Zum Testen:
>
> cordic-32bit.c

Hab ich jetzt auf dem STM32 ausprobiert und die Genauigkeit ist absolut 
super und auch deutlich schneller... aber leider auch um ca. 700 Byte 
größer.

Werde ich mir dennoch merken, wenn ich etwas in dieser Art nochmal 
benötige.

MaWin schrieb:
> Alleine wenn du den code auf integer umstellst (bekommst du ein Ergebnis
> zwischen 0 und 32767) wird es schon mal viel kleiner und schneller.

Das wird jetzt der nächste "Versuch" werden.

Vielen Dank an alle

von Dergute W. (derguteweka)


Lesenswert?

Moin,

Sabun schrieb:
> Stuss

Ginge es eventuell etwas qualifizierter?

Gruss
WK

von ./. (Gast)


Lesenswert?

Wenn im Programm noch an anderen Stellen mit floats gerechnet wird,
werden die Objekte FltAdd.o, FltMul.o und FltSub.o ohnehin geladen.
Da nuetzt das ganze Herumgepopel mit Integern nichts.
Die Taylorreihenentwicklung aus meinem Beispiel, hat bei
PI/2 einen Fehler von 1.2e-007. Das sollte fuer ein 3-stelliges
Rechenergebnis wohl sicher reichen.

von Sabun (Gast)


Lesenswert?

Dergute W. schrieb:
> Moin,
>
> Sabun schrieb:
>> Stuss
>
> Ginge es eventuell etwas qualifizierter?
>
> Gruss
> WK

Da du dir diese Frage selbst stellst, musst du sie auch selbst 
beantworten.

Stuss

LJ

von Ralph S. (jjflash)


Lesenswert?

./. schrieb:
> Wenn im Programm noch an anderen Stellen mit floats gerechnet wird,
> werden die Objekte FltAdd.o, FltMul.o und FltSub.o ohnehin geladen.
> Da nuetzt das ganze Herumgepopel mit Integern nichts.
> Die Taylorreihenentwicklung aus meinem Beispiel, hat bei
> PI/2 einen Fehler von 1.2e-007. Das sollte fuer ein 3-stelliges
> Rechenergebnis wohl sicher reichen.

Es wird mit floats gerechnet ... und du hast Recht, das Integergeschubse 
hat nicht so viel gebracht.

Aber wie es ausschaut, reicht mir nun der Speicherplatz.

:-) Smile, ich sagte nicht, dass ein Ergebnis 3-stellig ist, sondern 
dass es ein Ergebnis wird, das aus 3 Zahlen besteht. Die Anzeige wird 
zum Schluß sogar nur 2 Stellen hinter dem Komma haben.

Ich muß nur sehr aufpassen, mir meinen Fehler nicht fort zu schleifen. 
Wenn der Mensch für den das ist "zufrieden" ist, kann das schon sein, 
dass das in der Größenordnung 1000 Stück gebaut wird (und dann kann es 
auch schon sein, dass da auf die MCU geachtet wird).

Aber ich bin guten Mutes dass mir der kleine ARM reicht...

Aber hier merkt man wieder: am falschen Ende gegeizt. Mit bspw. einem 
F103 (selbst in der 64 KByte Version)... wäre ich wahrscheinlich schon 
fertig.

Nochmals Dank an alle, die sich hier eingebracht haben ... und gelernt 
hab ich auch wieder was ( schmunzeln muß  und am WE werde ich mir den 
CORDIC Algorithmus mal genauer ansehen wie der funktioniert - hoff ich 
dass ich da dahinter komme).

Ralph

von Gerd E. (robberknight)


Lesenswert?

Ralph S. schrieb:
> STM32F030F4P6

Du weißt daß dieser µC 32KB Flash hat? Das Datenblatt sagt zwar nur 
16KB, in echt sind aber 32KB drauf, genauso der 32Bit-Timer. Der 
STM32F030F4P6 und der STM32F031F6P6 verwenden die selben Dies.

Das einzige was anders ist ist der Wert, der ins Flash size Register 
programmiert wurde. Wenn Du das ignorierst kannst Du den vollen Flash 
verwenden.

Der ST Link prüft das Flash Size Register, den kannst Du also nicht zum 
programmieren verwenden. Wie es mit einem auf Jlink umgestellten ST Link 
aussieht hab ich noch nicht programmiert. Aber mit dem seriellen 
Bootloader funktioniert es.

Also musst Du Dir wegen Deinem Sinus vermutlich keine Gedanken bzgl. der 
Codegröße machen und kannst beim STM32F030F4P6 bleiben.

von temp (Gast)


Lesenswert?

Möchte nochmal was einwerfen weil das häufig nicht so bekannt ist:

Unbedingt alle float - Konstanten auch mit f kennzeichnen! Wenn man 3.14 
als Konstante verwendet wird in double gerechnet bei 3.14f in float. 
Kurzes Beispiel für stm32f030 und Crossworks:
1
int main(void)
2
{
3
  float f=sinf(1.24f);
4
  f=f*2.13f;
5
  return (int)f;
6
}
benötigt 1.9k

1
int main(void)
2
{
3
  float f=sinf(1.24f);
4
  f=f*2.13;                // double Berechnung!
5
  return (int)f;
6
}
benötigt 2.5kb


Am besten mal im map-File nachsehen ob da irgendwas mit float64 zu 
finden ist.

von W.S. (Gast)


Lesenswert?

Ralph S. schrieb:
> Ich muss auf einem Controller (leider) einen Sinus berechnen und habe
> für die MATH Bibliothek nicht genügend Speicher übrig

Jaja, so viele Beiträge und immer noch keine Lösung..

Also, Sinus nach Pedersen: "High speed, low accuracy algorithms for 
computing cos x, sin x" (Danmarks tekniske Hojskole, Mai 1978, via 
Lampe-Jorke-Wengel):

y:= x * (K*x^2 + L + M / (x^2 + N));

mit
K = 0.15625
L = -11.45242628
M = 480.1488517
N = 38.55864669

Der reicht für 6 Stellen bei single. Aber bitte auf den 1. Quadranten 
herunterbrechen.

W.S.

von temp (Gast)


Lesenswert?

W.S. schrieb:
> Also, Sinus nach Pedersen:

es kusieren ja einige ähnliche Algorithmen im Netz, das kannte ich noch 
nicht. Um meine Beispiele von oben zu vervollständigen hab ich das mal 
probiert:
1
#define M_PI_F 3.14159265f
2
float sinws(float x)
3
{
4
  bool bNeg=false;
5
  if (x>M_PI_F)
6
    {
7
    x-=M_PI_F;
8
    bNeg=true;
9
    }
10
11
  if (x>(M_PI_F/2.0f))
12
    x=M_PI_F-x;
13
14
  float xx=x*x;
15
  float y= x * (0.15625f * xx -11.45242628f + 480.1488517f / (xx + 38.55864669f));
16
  if (bNeg)
17
    y*=-1.0f;
18
  return y;
19
}
20
21
int main(void)
22
{
23
  float f=sinws(1.24f);
24
  f=f*2.13f;
25
  return (int)f;
26
}

das benötigt dann 1,6kb. Genau 288Byte weniger als mit der libm. Die 
Genauigkeit ist beachtlich, etwa 1 Zehnerpotenz schlechter als bei 
sinf().
Größte Abweichung beim Probieren mit ganzen Gradzahlen lag bei 0,001%.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Georg schrieb:
> Ich weiss nicht, was dein Compiler kann, aber in vergleichbaren Fällen
> habe ich die Meldungen des Linkers ausgewertet, welche Routinen er nicht
> findet, und die einzeln solange eingebunden, bis der Linker zufrieden
> ist.

Herzlichen Glückwunsch!

Du hast gerade vollkommen unnötig die Arbeitsweise eines Linkers 
nachgefrickelt.

Der links aus einer Lib nämlich nur die Objecte, die nebötogt werden. 
Unfd falls das bei einer bestimmten Lib nicht der Fall ist dann heißt 
dass, dass deren Autoren planlos waren.  In dem Fall wird man von der 
Verwendung der entsprechenden Lib ohnehin absehen.

> Das Progamm kann dann Sinus berehcnen, aber sonst nichts. Man
> braucht dazu ja nicht eine ganze Math-Bibliothek. Ist natürlich Arbeit,
> Selberschreiben aber auch.

Unsinn, wenn gegen eine Lib gelinkt wird, und z.B. nur sin gebraucht 
wird, warum sollte ein Linker dann gegen log oder bessel linken?

von Ralph S. (jjflash)


Lesenswert?

So, den Post von W.S. hab ich jetzt ausprobiert und benötig von allen 
bisherigen Versuchen den wenigsten Speicherplatz und ist von der von mir 
benötigten Genauigkeit absolut ausreichend, Speicherplatz für alles 
andere habe ich nun auch genug.

Herzlichen Dank dafür, (auch für an anderer Stelle den, sagen wir etwas 
derberen aber NOTWENIGEN "Schubser" Dinge lieber selbst zu machen).

Mich würde sehr interessieren, wer sich hinter W.S. verbirgt (aber das 
wird wohl sein Geheimnis bleiben).

von Georg (Gast)


Lesenswert?

Johann L. schrieb:
> Du hast gerade vollkommen unnötig die Arbeitsweise eines Linkers
> nachgefrickelt.

Totales Nichtverständnis. Der Linker linkt im Scan-Modus die Module, die 
für das geschriebene Programm benötigt werden, das ist trivial, auch 
wenn es Linker gibt die das nicht können. D.h. er linkt z.B. ein 
Trigonometriemodul, wenn Sinus oder Tangens usw. benötigt werden.

Um aber noch weiter Speicherplatz zu sparen kann man eben manuell 
steuern, dass nur z.B. die Sinusroutine geladen wird, Tangens aber nicht 
- das ist feinere Granularität als die Library üblicherweise hat.  Recht 
effektiv ist auch, bei den Grundrechenarten auf die Division zu 
verzichten, wenn man sie nicht braucht. Also Linken nach Bedarf nicht 
auf Modul-Ebene, sondern auf der Ebene einzelner Unterprogramme.

Es ist aber wohl von einem heutigen Programmierer entschieden zu viel 
verlangt, das zu verstehen. Alles macht die IDE, und was sie nicht macht 
geht halt nicht.

>
> Der links aus einer Lib nämlich nur die Objecte, die nebötogt werden.

Du hast sicher noch nie etwas von Borland gehört. Es gibt aber auch 
andere, die einfach die ganze Runtime linken.

Georg

von Hp M. (nachtmix)


Lesenswert?

Wenn das Inkrement β des Winkels konstant ist und du bei α = 0 anfängst, 
braucht du nur einmal den Sinus und den Cosinus des Inkrements wirklich 
zu berechnen, für alle anderen Werte verwendet man Additionstheoreme und 
braucht nur noch wenige Multiplikationen pro Wert:

sin(α+β) = sin(α) * cos(β) + cos(α) * sin(β)
cos(α+β) = cos(α) * cos(β) - sin(α) * sin(β]

Für den Anfangswert α=0 wissen wir, dass sin(0)= und cos(0)=1 ist:
sin(0+β) = 0 * cos(β) + 1 * sin(β)
cos(0+β) = 1 * cos(β) - 0 * sin(β]
Die benötigten Sinuswerte für das Inkrement β muss man entweder einmal 
berechnen, oder evtl, falls nur wenige Werte in Frage kommen, einer 
Tabelle entnehmen.
Wenn man die Werte berechnen muss, kann es schneller sein, wenn man den 
bereits berechneten Sinus(β) verwendet und berücksichtigt,
dass cos(β) = Wurzel(1-sin^2(β)) ist.

Wesentlich hierbei ist, dass u=sin(β) und v=cos(β) konstante Werte sind, 
und dass man von nun an nie wieder die langsame Reihenentwicklung von 
sin oder cos benötigt, sondern nur die beiden obigen Formeln in eine 
Schleife stecken muss:
sin(α+β) = sin(α) * v + cos(α) * u
cos(α+β) = cos(α) * v - sin(α) * u
Beim nächsten Schleifendurchlauf nehmen die soeben berechneten sin und 
cos Werte den Platz der Anfangswerte sin(α) und cos(α) ein.

Man braucht also nur noch 4 Multiplikationen und zwei Additionen pro 
sin-cos-Wertepaar.

: Bearbeitet durch User
von Axel S. (a-za-z0-9)


Lesenswert?

Hp M. schrieb:
> Wenn das Inkrement β des Winkels konstant ist und du bei α = 0 anfängst,
> braucht du nur einmal den Sinus und den Cosinus des Inkrements wirklich
> zu berechnen, für alle anderen Werte verwendet man Additionstheoreme und
> braucht nur noch wenige Multiplikationen pro Wert:
>
> sin(α+β) = sin(α) * cos(β) + cos(α) * sin(β)
> cos(α+β) = cos(α) * cos(β) - sin(α) * sin(β]

Im Prinzip ja. In der Praxis überlegst du jetzt mal kurz, welchen Fehler 
du anhäufst, wenn du von 0 .. 90° in Schritten von 0.1° durch den z.B. 
Sinus läufst.

von m.n. (Gast)


Lesenswert?

Georg schrieb:
> Du hast sicher noch nie etwas von Borland gehört. Es gibt aber auch
> andere, die einfach die ganze Runtime linken.

Gibs es jetzt endlich Turbo-C für AVR?
Als Linker empfehle ich immer solche, die nach 1995 ausgeliefert wurden.

von temp (Gast)


Lesenswert?

Georg schrieb:
> Totales Nichtverständnis. Der Linker linkt im Scan-Modus die Module, die
> für das geschriebene Programm benötigt werden, das ist trivial, auch
> wenn es Linker gibt die das nicht können. D.h. er linkt z.B. ein
> Trigonometriemodul, wenn Sinus oder Tangens usw. benötigt werden.
>
> Um aber noch weiter Speicherplatz zu sparen kann man eben manuell
> steuern, dass nur z.B. die Sinusroutine geladen wird, Tangens aber nicht

Ja das ist nun mal eventuell bei der PC Programmierung so aber nicht bei 
der cortex-Programmierung. Alles Libs die ich kenne sind mit 
-ffunction-sections -fdata-sections gebaut. d.h. jede Funktion! der Lib 
bekommt ihre eigene Section. Ebenso die verwendeten Daten. Was am Ende 
nicht gebraucht wird fliegt raus. Und wenn ich nur sinf benutze ist auch 
nur sinf drin. Und die Funktionen die sinf selbst benötigt u.U.
Auf alle Fälle kann man wohl sagen, die sinf aus der libm ist in der 
Regel klein genug. Wenn man den Teil noch dazubaut um die o.g. Funktion 
kompatibel zu sinf() zu machen kommen ja auch noch ein paar Byte dazu.

von temp (Gast)


Lesenswert?

Ralph S. schrieb:
> Mich würde sehr interessieren, wer sich hinter W.S. verbirgt (aber das
> wird wohl sein Geheimnis bleiben).

Sicherlich jemand der noch jedes Bit vom U880 einzeln kennt und die 
Assembler-hex-Codes per Hand in 1k Ram gekloppt hat. Tastatur 
selbstgebaut und betrachtet am Junost.
ala: C3 xx xx
     ...

Jedenfalls lässt das die Literaturangabe vermuten.

von Ralph S. (jjflash)


Lesenswert?

temp schrieb:
> Assembler-hex-Codes per Hand in 1k Ram gekloppt hat. Tastatur
> selbstgebaut

Also ich komme aus BaWü und hab die Assembler-Hex Codes  vom 8085 von 
Hand reingekloppt ... und die Tastatur war auch selbstgebaut...

Egal wer das ist: heftig aber gut !

von Marcus H. (Firma: www.harerod.de) (lungfish) Benutzerseite


Lesenswert?

Reinhard M. schrieb:
> cordic-32bit.c

-> Sinusberechnung
F030  Atollic  GCC:
-Os  118 Bytes
-O0 ~180 Bytes

Krass. Danke für die Erinnerung.

Hat grad gut reingepasst: Die Tage habe ich auch auf einem STM32F030 für 
ein eichfähiges System eine interne Berechnung in int64_t statt in 
double durchgeführt. Plötzlich reichen auch 16k statt 32k Flash.
Und int64_t war schon der Ansatz für Faule. ;)

von temp (Gast)


Lesenswert?

Marcus H. schrieb:
> Reinhard M. schrieb:
>> cordic-32bit.c
>
> -> Sinusberechnung
> F030  Atollic  GCC:
> -Os  118 Bytes
> -O0 ~180 Bytes

Sorry, aber nicht mit der cordic-32bit.c von oben. Da stehen 32 32bit 
Werte in der Tabelle. Macht 128 Byte. Da würde ich jetzt ins Grübeln 
kommen...

von Marcus H. (Firma: www.harerod.de) (lungfish) Benutzerseite


Lesenswert?

temp, danke, mein Fehler. Hab nur text gelesen und die dec übersehen:

Der gcc hatte folgende Info ausgeworfen:

Print size Information

OHNE CORDIC
   text     data      bss      dec      hex  filename
   1056       28      164     1248      4e0  cordic_test.elf

MIT CORDIC
   text     data      bss      dec      hex  filename
   1164      156      164     1484      5cc  cordic_test.elf

Ich korrigiere auf 118 Byte Code + 128 Byte Tabelle = 236 Bytes Flash.

Legt man die Tabelle per const ins Flash, dann sieht es so aus:
   text     data      bss      dec      hex  filename
   1292       28      164     1484      5cc  cordic_test.elf

Je nach Speichermodell kann man sich jetzt noch raussuchen, was 
geschwindigkeitsoptimal ist (F4+ART wohl const, beim M0+1WS müsste man 
mal messen). Aber das sind Kleinigkeiten im niedrigen Prozentbereich.

: Bearbeitet durch User
von T.U.Darmstadt (Gast)


Lesenswert?

Sepp schrieb:
> Wenn man ein paar Stützstellen speichert (z.B. 16 oder 32) kann man den
> Summensatz sin(a+-b) = sin(a)*cos(b)+-cos(a)*sin(b) sehr gut verwenden
> um schnell den Sinus zu berechnen
Oh Oh Oh ....

Axel S. schrieb:
> Im Prinzip ja. In der Praxis überlegst du jetzt mal kurz, welchen Fehler
> du anhäufst, wenn du von 0 .. 90° in Schritten von 0.1° durch den z.B.
> Sinus läufst.
Das wollte Ich auch gesagt haben. 4x trigonometrisch rechnen in einem 
einzigen Schritt ist eigentlich niemals irgendwo effizient zu lösen.

Dergute W. schrieb:
> sin(x) =  x*(1-1/6*x²*(1-1/20*x²*(1-1/42*x²)))
Das schaut schon ansprechender aus.

von Dieter W. (dds5)


Lesenswert?

Thomas U. schrieb:
> ....

Dein Beitrag bringt den TO nach reichlich einem Jahr Pause sicher 
weiter.

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.