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