Sinus Tabelle

Aus der Mikrocontroller.net Artikelsammlung, mit Beiträgen verschiedener Autoren (siehe Versionsgeschichte)
Wechseln zu: Navigation, Suche

von kruemeltee

Da die Sinusberechnung auf einem Mikrocontroller sehr zeitintensiv ist, kann man in vielen Fällen auch mit einer festen Sinustabelle arbeiten, aus welcher die Werte geholt werden. Natürlich lässt sich dieses Prinzip analog auch für Cosinus, Tangens, und jede andere Funktion verwenden.

Hier ein Beispiel in C, welches eine kleine Sinustabelle im Programspace hält, die Werte ausliest und auf einem Display ausgibt. Dieses Beispiel gibt die Werte auf dem Display mit der Fleury-Display-Library aus. Anzumerken ist, dass Sinustabellen beim AVR in der Regel vorberechnet in den Flash-Speicher gelegt werden, da RAM eher knapp ist und nur 1 Takt pro Byte schneller im Zugriff ist.

#include <avr/io.h>
#include <stdio.h>
#include <avr/pgmspace.h>
#include "lcd.h"

const float sine_table_16_entries[16] PROGMEM =
{
     0.00000,
     0.38268,
     0.70711,
     0.92388,
     1.00000,
     0.92388,
     0.70711,
     0.38268,
     0.00000,
     -0.38268,
     -0.70711,
     -0.92388,
     -1.00000,
     -0.92388,
     -0.70711,
     -0.38268
};

int main()
{
    lcd_init (LCD_DISP_ON);
    lcd_clrscr();

    sprintf (buffer, "%f", pgm_read_float (&sine_table_16_entries[3]));
    lcd_gotoxy (0,0);
    lcd_puts (buffer);

    for(;;);
}

Externe Vorberechnung

Zum Erstellen einer Sinustabelle für ein C-Programm nutzt man am besten ein kleines Programm, welches ein „Sourcefile“ mit einer Sinustabelle in beliebiger Grösse erzeugt. Dabei handelt es sich bei dieser Art automatisch generiertem „Sourcefile“ nicht um Quelltext; wer das Generatorprogramm nicht mit ausliefert handelt also letztlich mit Closed Source.

Das Programm lässt sich hier herunterladen: Datei:Sinus gen.zip

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

int main(int argc,char *argv[])
{
    int entries, entries_2PI, phase, amplitude, offset, data_per_line;
    int i, j;
    int max, min, max_digits;

    if (argc != 7) {
        fprintf(stderr,"Generate C source code for sine table\n");
        fprintf(stderr,"Usage:\n");
        fprintf(stderr,"sinus_gen entries entries_per_2PI phase amplitude offset data_per_line >outputfile\n\n");
        fprintf(stderr,"entries         : number of entries in sine table\n");
        fprintf(stderr,"entries_per_2PI : number of samples within full 360 degree\n");
        fprintf(stderr,"phase           : phase offset in samples\n");
        fprintf(stderr,"amplitude       : amplitude of sine wave, integer\n");
        fprintf(stderr,"offset          : offset of sine wave, integer\n");
        fprintf(stderr,"data_per_line   : number of samples per line in source file\n");
        fprintf(stderr,">outputfile     : redirect output from command line to file\n");
        return 1;
    }

    entries = atoi(argv[1]);
    entries_2PI = atoi(argv[2]);
    phase = atoi(argv[3]);
    amplitude = atoi(argv[4]);
    offset = atoi(argv[5]);
    data_per_line = atoi(argv[6]);

    min = abs(offset - amplitude);
    max = abs(offset + amplitude);

    max_digits=2;
    if (min > 99   || max > 99)   max_digits=3;
    if (min > 999  || max > 999)  max_digits=4;
    if (min > 9999 || max > 9999) max_digits=5;

    // header
    printf("const int sinus[%d] = {\n\t", entries);

    for (i=0, j=0; i<entries; i++) {
        switch(max_digits) {
            case 2: printf("%3d", (int)round(offset + amplitude*sin((phase+i)*2*M_PI/entries_2PI))); break;
            case 3: printf("%4d", (int)round(offset + amplitude*sin((phase+i)*2*M_PI/entries_2PI))); break;
            case 4: printf("%5d", (int)round(offset + amplitude*sin((phase+i)*2*M_PI/entries_2PI))); break;
            case 5: printf("%6d", (int)round(offset + amplitude*sin((phase+i)*2*M_PI/entries_2PI))); break;
        }
        if (i < (entries-1)) {
            printf(", ");
        }
        j++;
        if (j==data_per_line) {
            if (i < (entries-1)) {
                printf("\n\t");
            } else {
                printf("\n");
            }
            j=0;
        }
    }
    if (j !=0) printf("\n");

    // footer
    printf("};\n");

    return 0;
}

Vorberechnung durch den Compiler

Lässt man den Einsatz von C++14 zu, kann man die Sinustabelle vom Compiler generieren lassen. Im Fall AVR ist das mit avr-gcc 5.00 und höher möglich. Prinzipiell kann man Vorberechnungen mittels Templatemetaprogrammierung mit jedem C++ machen, aber erst mit costexpr und dem Standard von 2014 ist das sinnvoll.

#include <stdio.h>
#include <avr/pgmspace.h>

constexpr float PI=__builtin_atan(1)*4;
typedef unsigned char byte;

class sintab_t{
  byte a[256] {};   // Dieser Initialisierer {} muss sein, sonst geht's nicht
public:
  constexpr sintab_t() {    // constexpr-Defaultkonstruktor
   for (size_t x=0; x<256; x++)
     a[x]=int(__builtin_sin(x*PI/512)*256+0.5)&0xFF;    // von 0 bis (knapp) π/2
  }                 // Statt 256 wird 0 gespeichert, ansonsten ist die 0 nur bei Tabellenindex 0
  int sin(int w) const{   // liefert bequeme ±256! Und für 0 die 0!
   byte i=w;
   if (w&0x100) i=-i;    // zweiter oder vierter Quadrant: Laufrichtung ändern
// Bei w==256 (π/2) wird w zu 256, der Arrayzugriff geht auf Adresse 0 und liefert 0.
// Wird später zu 256 umgerechnet!
   int v=pgm_read_byte(a+i);
   if (!v && w&0x1FF) v=0x100;   // Null durch Gipfel-Überlauf
   if (w&0x200) v=-v;       // dritter oder vierter Quadrant: Vorzeichen ändern
   return v;    // 16..23 Takte (ohne CALL+RET)
  }
};

constexpr PROGMEM sintab_t sintab;

int main() {
 for (auto i=0; i<1024; i++)
 printf("%d\n",sintab.sin(i));
}

In diesem Beispiel wird nur ein Viertelsinus als 256 Byte lange Tabelle im Flash-Speicher abgelegt, und zwar derart, dass beim Gipfel (π/2) Nullen (Überläufe) statt 256 generiert werden. Die Memberfunktion sin() kümmert sich um die Quadrantenbeziehungen und diese Überläufe und benötigt dafür nur etwa 20 Takte. Die Flash-Tabelle selbst ist private.

Von Vorteil ist der Wertebereich ±256 nicht für PWM und DDS sondern für Signalprozessor-Funktionen: Man kann das Rechenergebnis bequem als Festkommazahl mit 8 Bit Nachkomma interpretieren. Ich spare mir hier eine passende Klassenimplementierung dazu; sowas gibt's schon.

Das Mainprogramm dient nur zur Veranschaulichung der Anwendung. Der Argumentbereich ist nicht auf 0..1023 begrenzt, die Periodizität ergebt sich über den gesamten int-Wertebereich inklusive der negativen Zahlen durch Ignorieren der Bits 15..10.

Siehe auch