Sinus signal mit variable frequenz

Hallo,

wir werden sehen was passiert. Habe mich wieder beruhigt.

Ich habe mal deinen Counter Code gegen meinen getestet. Ich habe das mit switch case gemacht, was mehr Zeilen verursacht. Dein Code bewegt sich konstant zwischen 0.9 und 1.2µs. Meiner eigentlich auch, aber bei irgendeiner Richtungsumschaltung sind es plötzlich 6µs. Damit ist dein Code klar besser.

Mal schauen ob der TO noch seinen eigenen bringt, erstmal vollkommen egal wie der aussieht, funktionieren muss er.

Danke, das ist das eigentlich spannende.

Welcher Code und wie schnell - ist eigentlich vollkommen wurscht. Ich wollte nur eine Lösung ohne Schleife in der Schleife.

Was mich wirklich nervt ist der TO. Entweder hat er/sie (kennen wir eigentlich einen Namen?) eine Aufgabe, dann klemmt man sich dahinter und versucht wenigstens, was zu verstehen und ggf. sogar zu lernen. Aber hier alle paar Tage mal reinschneien und einen simplen Zähler nicht selber und in Code-Tags präsentieren zu können ist schon recht dünn.

Habe beruflich gelegentlich auch mit Studierenden der Informatik zu tun, die bei uns Praktikum oder Bachelor-Arbeit machen. Die kommen ganz anders angetrabt; da bin eher ich die Bremse.

Hallo,

interessant zu wissen.

Übrigens das mit schnelleren Code ist nicht ganz unwichtig. Ich bin schon paar Schritte weiter. Wenn man das auf die Spitze treibt und das mache ich öfters aus Interesse ob noch was geht, dann stellt man fest das durch die benötigten Neuberechnungen ein Jitter entsteht. Wenn man die phasendelta Formel nimmt wie sie ist, dann benötigt diese 56µs. Man hat aber nur 16µs Zeit wenn man es ganz genau nimmt. Also bin ich den nächsten Schritt gegangen und habe die benötigten Phasendeltawerte vorberechnet in ein weiteres Array eingetragen. Jetzt springt der vorherige "Bandbreitencode" statt zwischen 45-55Hz Werten nun zwischen 11 Indexe hin und her. Damit bin ich weit unter den erforderlichen 16µs - wenn es Jitterfrei sein soll. Ich könnte mir sogar wieder den Luxus leisten meinen alten Bandbreitencode zu verwenden der sporadisch 6µs benötigt. Mach ich natürlich nicht. :wink:
Eigentlich ist das jetzt fix und fertig. Die zusätzlichen Änderungen waren interessant.

uint16_t FREQUENCY;
int i = 1 ;
void setup()
{
  Serial.begin(9600);
}

void loop()
{
  for (FREQUENCY = 45 ; FREQUENCY > 44 ; FREQUENCY+= i ) {
    
    if (FREQUENCY ==55){
      i=-1;
    }
    else if (FREQUENCY == 45){
    i = +1;
    }
    Serial.print(F("Frequency: "));
    Serial.println(FREQUENCY);
      delay (1000);
 }
     
}

Ps: Ich wusste leider nicht wie ich diese Code-tags einfügen kann, aber hab´s jetzt gegooglt und wird ab nun ein Sketch ohne Code-tags nicht vorkommen.
Ich hatte leider viel um die Ohren deswegen war ich leider paar tage Inaktiv.
Nun zu meinem Code: Ich habe die Aufgabe geschafft. der Code tut war er tun soll. Sprich er zählt hoch und runter von 45 bis 55.
Jetzt ist die frage wie ich diesen Code in den Hauptprogramm verwenden kann ?

Hallo,

okay, soweit so gut. Hab es getestet und es funktioniert. Sehr gut.
Jetzt müssen wir den nächsten Schritt gehen. Der DDS Code muss ständig durchlaufen und aktualisiert werden. Die for Schleife rund um die Hz Änderung würde das blockieren. Kannst du die for Schleifen ausrollen und das mit if vergleichen oder switch case ablaufen lassen? Wir benötigen also einen Code der die gewünschte Frequenz ändert ohne das er irgendwas blockiert. Das heißt keine Schleifenbildung. Bekommst du das hin? Erfordert nur etwas umdenken im Ablauf deines Programmes. Danach erlöse ich dich.

Stimmt, die kann man einfach weglassen. Stattdessen die Änderung der Frequenz nach dem Muster von BlinkWithoutDelay implementieren.

uint16_t FREQUENCY = 44;
int i = 1;
void setup()
{
  Serial.begin(9600);
}

void loop(){
  
  FREQUENCY+= i ;
  
  if (FREQUENCY ==55){
     
      i=-1;
    }
    else if (FREQUENCY == 45){
    
    i = +1;
    
    }
    Serial.print(F("Frequency: "));
    Serial.println(FREQUENCY);
      delay (500);   
}

Guten Abend,
ich habe den Code nun so geschrieben ohne For schleife und es funktioniert.
Ich hoffe, du hast es so gemeint.
LG

Hallo,

einwandfrei, genauso meinte ich das. Gut. Ich verfasse noch etwas Text zum fertigen Code, dann zeige ich dir das Gesamtkunstwerk. Versprochen ist versprochen.

vielen lieben dank.
Du hast mir nicht nur geholfen, sondern auch die Lust gegeben Programmierung zu lernen.

Hallo,

ausgehend von deinem letzten Code muss mit dem aktuellen Frequenzwert das benötigte Phasendelta berechnet werden. Dafür benötigt man folgende Formel.
Phasendelta = 65536UL * frequency / PWM) + 0.5

Jetzt muss sich nur noch der Frequenzwert ändern. Dafür ist deine Vorarbeit notwendig gewesen. Weil der Timer bis 255 zählt mit Prescaler 1 muss das alles jedoch in einem sportlichen Zeitfenster passieren. Das heißt man hat nur 256 Takte Zeit um irgendwas zu machen, spätestens nach 256 Takten muss das OCR1B Register aktualisiert sein. Wenn das länger dauert ändert sich die Frequenz zwar dennoch, aber nicht mehr kontrolliert auf die gewünschte Frequenz. Den sauberen Sinus mit der gewünschten Frequenz erreicht man nur wenn das Timing stimmt.

Deswegen berechnen wir das Phasendelta nicht jedesmal komplett live, weil die Berechnung mit obiger Formel mit ihren 892 Takten bzw. 56µs zu lange dauert. Man hat ja nur 256 Takte Zeit. Deswegen legen wir eine zweite Tabelle an mit den vorberechneten Werten der benötigten Phasendeltawerte für das Frequenzband 45 ... 55 Hz. Das könnte man noch ins RAM legen und automatisch berechnen und eintragen lassen. Wäre noch eine Option für dich wenn es noch flexibler werden soll. Das auslesen der Tabelle dauert unter einer 1µs. Im Code Zeile 109.

Desweiteren verwende ich nicht mehr die Timer 1 Overflow Interrupt Routine, sondern werte das Overflag direkt aus. Das verhindert ein Jitter in der Frequenzerzeugung. Der Code wartet in Zeile 95 bis vom Timer der Zählwert 255 erreicht ist und auf 0 wechselt. Dabei wird der OCR1B Wert übernommen und der nächste Stützwert der Sinuswelle ausgegeben.

Zeile 100. frequenz.counter ist eine Variable um die Frequenzänderung zeitlich steuern zu können. Ansonsten geht das so schnell das man nichts oder ein wildes springen auf dem Oszi sehen würde. Voreingestellt ist mit dem Vergleich auf 6250 ein 100ms Zeitraster. Aller 100ms ändert sich die Frequenz.

Deinen Zählcode findest du nun zwischen Zeile 104 ... 108 in anderer Form wieder. Den hat wno158 nochmal optimiert im Vergleich zu meinem. Es kehrt sich immer die Zählrichtung um wenn der Index Anfang oder Ende erreicht wurde. Hier wird kein Frequenzwert hoch und runtergezählt, sondern der benötigte Index für den Zugriff auf die Tabelle mit den vorberechneten Phasendeltawerten. Deine Programmierübung hilft dir hierbei genau das zu verstehen. Zeile 104 ist nur eine Kurzschreibweise für eine if else Abfrage. Wenn wahr, dann, ansonsten. Genauso wie -- oder ++ Kurzschreibweisen fürs inkrementieren/dekrementieren um 1 ist.

Das Spiel wiederholt sich zyklisch.

Wir haben jetzt aus der festen Frequenzeinstellung eine variable Frequenzeinstellung gemacht mit hier und da paar zusätzlichen Optimierungen.

Die einzige Variable dir mir vom Namen her noch nicht gefällt ist frequenz.counter. Gehört zwar alles zur Frequenzsteuerung struct Frequenz und der Member counter ist ja auch ein Zähler, aber zusammengesetzt könnte man das falsch verstehen. Weil der Zähler sorgt ja für eine Verzögerung in der Frequenzänderung. Vielleicht fällt dir dafür noch eine besserer Name ein.

Ich würde mir wünschen, falls dein Prof. eine Bewertung vornimmt diese mitzuteilen. Ich wäre gespannt was ein Prof. dazu meint. Den Fall hatte ich noch nie.

Das wars erstmal von meiner Seite her. Wenn du an der Programmierung dran bleibst wird das mit der Zeit immer besser. Spaß macht das in der Regel immer.

/*
  Doc_Arduino - german Arduino Forum
  IDE 1.8.16
  Arduino Mega2560
  28.10.2021
  sinus frequency range pass through
  
  https://forum.arduino.cc/t/sinus-signal-mit-variable-frequenz/904132
  Übernahme im Overflow statt Compare Match
  Jitterfrei durch direkte Flagauswertung ohne ISR
  vorberechnete Phasendelta Werte, verkürzt die Berechnungszeit auf unter dem 16µs Limit
*/

constexpr bool DEBUG {true};

const uint8_t Sinus[] PROGMEM = {   // 8 Bit Tabelle 
   127,  130,  133,  136,  139,  143,  146,  149,  152,  155,  158,  161,  164,  167,  170,  173,
   176,  178,  181,  184,  187,  190,  192,  195,  198,  200,  203,  205,  208,  210,  212,  215,
   217,  219,  221,  223,  225,  227,  229,  231,  233,  234,  236,  238,  239,  240,  242,  243,
   244,  245,  247,  248,  249,  249,  250,  251,  252,  252,  253,  253,  253,  254,  254,  254,
   254,  254,  254,  254,  253,  253,  253,  252,  252,  251,  250,  249,  249,  248,  247,  245,
   244,  243,  242,  240,  239,  238,  236,  234,  233,  231,  229,  227,  225,  223,  221,  219,
   217,  215,  212,  210,  208,  205,  203,  200,  198,  195,  192,  190,  187,  184,  181,  178,
   176,  173,  170,  167,  164,  161,  158,  155,  152,  149,  146,  143,  139,  136,  133,  130,
   127,  124,  121,  118,  115,  111,  108,  105,  102,   99,   96,   93,   90,   87,   84,   81,
    78,   76,   73,   70,   67,   64,   62,   59,   56,   54,   51,   49,   46,   44,   42,   39,
    37,   35,   33,   31,   29,   27,   25,   23,   21,   20,   18,   16,   15,   14,   12,   11,
    10,    9,    7,    6,    5,    5,    4,    3,    2,    2,    1,    1,    1,    0,    0,    0,
     0,    0,    0,    0,    1,    1,    1,    2,    2,    3,    4,    5,    5,    6,    7,    9,
    10,   11,   12,   14,   15,   16,   18,   20,   21,   23,   25,   27,   29,   31,   33,   35,
    37,   39,   42,   44,   46,   49,   51,   54,   56,   59,   62,   64,   67,   70,   73,   76,
    78,   81,   84,   87,   90,   93,   96,   99,  102,  105,  108,  111,  115,  118,  121,  124
};

const uint8_t Phasendelta[] PROGMEM = { // 8 Bit : vorberechnet für 45 ... 55Hz
   47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57
};

const uint16_t RESOLUTION {sizeof(Sinus)/sizeof(Sinus[0])}; 
const uint16_t TOP {RESOLUTION - 1}; 
const uint32_t PWM {F_CPU/RESOLUTION};      // 8 Bit: 62500  
uint8_t phasendelta;                        // 50Hz: 8 Bit: 52
uint16_t phasenakku;

struct Frequenz
{
  int8_t index {0};
  const uint8_t MAX_INDEX {sizeof(Phasendelta)/sizeof(Phasendelta[0])-1};
  uint32_t counter{0};
  bool direction {true};
};
Frequenz frequenz;

void setup(void)
{
  if (DEBUG)
  {
    Serial.begin(9600);
    Serial.println(F("\nuC Reset ### ###"));  
    Serial.print(F("RESOLUTION ")); Serial.println(RESOLUTION);
    Serial.print(F("PWM "));        Serial.println(PWM);
    Serial.print(F("TOP "));        Serial.println(TOP);
    Serial.print(F("frequenz.MAX_INDEX "));  Serial.println(frequenz.MAX_INDEX);
    
    for (uint8_t f=45; f<=55; f++)
    {
      Serial.print(F("f "));
      Serial.print(f);
      Serial.print(F("  Phasendelta "));
      Serial.println(static_cast<uint16_t>((65536UL*f/PWM)+0.5));
    }
  }
  
  phasendelta = pgm_read_byte(&(Phasendelta[0])); 
   
  pinMode(12, OUTPUT);  // OC1B = UNO Pin 10 / Mega2560 Pin 12
  // Mode 14, Fast PWM - ICR1
  TCCR1B = 0;
  TCCR1A = 0;
  TIMSK1 = 0;
  OCR1B = 0;
  TCNT1 = 0;
  ICR1 = TOP;
  TCCR1A = _BV(COM1B1) | _BV(COM1B0) | _BV(WGM11);  // Clear OCnx on compare match
  TCCR1B = _BV(WGM13)  | _BV(WGM12)  | _BV(CS10);
}

void loop(void)
{
  // ca. 1,63µs bis while 
  phasenakku += phasendelta;
  OCR1B = pgm_read_byte(&(Sinus[phasenakku >> 8]));  // für 8 Bit Sinustabelle

  // aller 256 Takte bzw. 16µs
  while (!(TIFR1 & _BV(TOV1) ))     // ohne Jitter
  { ; }
  // ca. 1,9µs bis Ende
  TIFR1 = _BV(TOV1);              
   
  frequenz.counter++;             // zählt Vielfache von 16µs
      
  if (frequenz.counter >= 6250)   // 100ms
  {
    frequenz.direction ? frequenz.index++ : frequenz.index--;
    if ( (frequenz.index <= 0) || (frequenz.index >= frequenz.MAX_INDEX) )
    {
      frequenz.direction = !frequenz.direction;
    }
    phasendelta = pgm_read_byte(&(Phasendelta[frequenz.index]));  // ca. 880ns
    frequenz.counter = 0;
  }
}