Sinus signal mit variable frequenz

hallo Gemeinde,
ich muss ein Sinus Signal erstellen mit 200 werten, 10khz taktfrequenz, 2,5v amplitude und 2,5v offset.

hier mein code


// Init Serieller Plotter - Oszi-Funktion über AnalogRead
unsigned int AD_Value = 0 ; //Parameterwert für analogRead
// Init Serieller Plotter ende
int PWM_Value = 799 ; //Parameterwert für analogWrite
void setup() {
Serial.begin(9600); // Init Serielle Schnittstelle
pinMode(10, OUTPUT); // Ausgabepin Timer1
//Init Timer1
TCCR1A = 0xa3; // Timer1 Register A-Teil
 //Serial.print("\nTCCR1A = ");
 //Serial.println(TCCR1A, BIN);
TCCR1B = 0x19; // Timer1 Register B-Teil
 //Serial.print("\nTCCR1B = ");
 //Serial.println(TCCR1B, BIN);
OCR1A = 1599; // Setzen des fixen oberen Zählwertes für 10 kHz (der untere Wert ist "0"
OCR1B = 799; //Dieser Wert ist der eigentliche PWM-Wert, für jeden Takt wird ein neuer gebraucht
 // PWM-Out erscheint an Pin 10 - 799 sind 50% PWM -> "Ua = 0"
TIMSK1 = 2; // enable Timer Compare Interrupt = "PWM"-Interrupt
//Init Timer1 ende
// sei(); // Interrupts freigeben
} // Setup ende
int z = 0; // Zählvariable
// hier ist ein 200-Werte-10-Bit-Sinus mit Nullwert auf 799 abgelegt / min: 0; max: 1599
int a[200]={ 0, 2, 4, 6, 10, 14, 19, 25, 32, 39, 47, 56, 66,
76, 87, 99, 111, 124, 138, 153, 168, 183, 200, 217, 234, 252, 271, 290, 309, 329,
350, 371, 392, 414, 436, 459, 482, 505, 528, 552, 576, 600, 625, 649, 674, 699,
724, 749, 774, 799, 799, 824, 849, 874, 899, 924, 949, 973, 998, 1022, 1046, 1070, 1093,
1116, 1139, 1162, 1184, 1206, 1227, 1248, 1269, 1289, 1308, 1327, 1346, 1364,
1381, 1398, 1415, 1430, 1445, 1460, 1474, 1487, 1499, 1511, 1522, 1532, 1542,
1551, 1559, 1566, 1573, 1579, 1584, 1588, 1592, 1594, 1596, 1598, 1598, 1598,
1596, 1594, 1592, 1588, 1584, 1579, 1573, 1566, 1559, 1551, 1542, 1532, 1522,
1511, 1499, 1487, 1474, 1460, 1445, 1430, 1415, 1398, 1381, 1364, 1346, 1327,
1308, 1289, 1269, 1248, 1227, 1206, 1184, 1162, 1139, 1116, 1093, 1070, 1046,
1022, 998, 973, 949, 924, 899, 874, 849, 824, 799, 774, 749, 724, 699, 674, 649,
625, 600, 576, 552, 528, 505, 482, 459, 436, 414, 392, 371, 350, 329, 309, 290,
271, 252, 234, 217, 200, 183, 168, 153, 138, 124, 111, 99, 87, 76, 66, 56, 47, 39,
32, 25, 19, 14, 10, 6, 4, 2, 0};
ISR(TIMER1_COMPA_vect) { // Interruptprogramm-PWM-Interrupt - läuft einmal pro PWM-Periode und holt neuen PWM-Wert
 z = (z + 1); //
 if(z == 200) z = 0;
 OCR1B = a[z]; //hole Wert aus a[200]-Array
}
void loop() {
 // Ausgabe
 Serial.println(analogRead(A0)); // es wird direkt der AD-Wert ausgegeben zwischen 0 ... 1023
 delay(20); // erforderlich sonst keine Anzeige über ser. Plotter
}

jetzt muss ich eine sich ständig ändernden frequenz im bereich von 45hz und 55hz ertsellen. und die periodendauerende änderung soll 2s sein.
ich habe keine ahnung was ich in meinem code ändern soll !!
Kann mir vielleicht jemand helfen ?

Dann nützt Dir die Tabelle nichts mehr, da TOP (OCR1A) von der Frequenz abhängt. Die duty cycle müssen für jede Schrittzahl, jedes TOP, jede Frequenz und jeden Schritt neu berechnet werden.

Anfangen kann man mit der Frequenz und daraus TOP berechnen und daraus die Anzahl Schritte pro Periode und die Werte für den minimalen und maximalen duty cycle (OCR1B) für den Amplitudenbereich. Dann mit der Scrittweite und dem Schrittindex den Winkel für den Sinus berechnen und daraus den duty cycle.

Also alles nur ganz stupide Rechnerei. Aber war da nicht noch ein einfacheres Verfahren, irgendwas mit einem Sägezahn?

if you want to change to a faster frequency, one approach is to skip samples by incrementing thru the table by a fractional value (e.g. 1.13)

Wie ist denn die genaue Aufgabenstellung? Muss die Taktfrequenz erhalten bleiben? Wenn nein, würde ich die variieren.

Hallo,

der Ansatz sieht eigentlich schon so aus als wenn es in die richtige Richtung gehen würde.

Der Suchbegriff für dich ist DDS. Man erstellt sich eine Tabelle die Stützwerte eines Einheitssinus enthält. Die Anzahl der Stützstellen bestimmt die Auflösung und ist nur durch die Rechengeschwindigkeit einhergehend mit der höchsten gewollten Frequenz begrenzt. Bei deinen 55Hz kein Thema. Die Tabelle wird permanent durchlaufen. Die Schrittweite bestimmt die Frequenz. Auf dem Oszi sieht das alles nach PWM aus, weil ist PWM, mal dichter und weniger dichter. Nach einem RC Filter sieht man den Sinus. Oder man gibt die Werte an einen DAC raus. Dann hat man ohne Filter kein PWM sondern Treppenstufen in der Sinuskurve..

Wenn du dich in DDS einliest wirst du auf den Begriff Phasenakkumulator treffen der von einem Phasendelta aufaddiert wird. Phasendelta ist Frequenz bestimmend.

Das ist die einzige Möglichkeit mit einer Tabelle. PWM mit variabler Frequenz ändert zu viele Parameter.

Hallo,

Warum? Bei Frequenzänderung ändern sich genau zwei Parameter. Der Topwert des Timers und das Delta für den Phasenakkumulator. Diese bleiben für die gewählte Frequenz konstant. Die Tabelle bleibt immer die Gleiche. Die Tabelle wird nur anders durchlaufen je nach Frequenz.
Wie sollte er sonst machen?

Eine lineare Interpolation der Sinus-Tabelle verursacht Verzerrungen. Zudem wird die maximale Frequenz durch die notwendige Rechenzeit reduziert - vor allem bei den kleinen Arduinos die nicht nur keine Floatingpoint Hardware haben sondern nicht einmal eine Division.

Hallo,

es gibt keine Verzerrungen, weil die Tabelle einen Sinus abbildet. Die Sinusform bleibt erhalten. Fließkommarechnungen benötigt man dabei auch nicht.

Das stimmt zwar, hatte ich ja selbst schon erwähnt, hat aber bei 55Hz keine Bedeutung.

Wie lautet deine Idee damit er das Problem lösen kann?

Kannst Du das bitte erklären? Soll da ein weiterer Timer mitlaufen, der die Abtastrate der Tabelle bestimmt?

Hallo,

der Phasenakkumulator wird immer zusammen mit der aktuellen PWM Periodendauer aktualisiert. Mittels Phasenakkumulator holt man sich aus der Tabelle den nächsten Pulsweitenwert. Im Endeffekt bewirkt das alles das man die Sinuskurve auseinanderzieht oder zusammenstaucht. Es bleibt aber ein Sinus. Die Feinheit bzw. Auflösung der Kurve wird von der Größe der Tabelle bestimmt.

Ich stelle dir jetzt zum 3. und letzten Mal die Frage. Wie wäre deine Lösung?

Siehe #2.

Wenn Du etwas präziser gewesen wärst, könnte man früher draufkommen, was Du meinst.

Die verlangten 200 Tabelleneinträge funktionieren nur, wenn man eine dazu passende PWM Taktfrequenz erzeugen kann, hier 10kHz +- 10%. Die resultierende 20% Amplitudenänderung muß dann immer noch berücksichtigt werden.

Dann fehlt im Programm aus #1 noch:

  • Überprüfung von PWM Modus, Prescaler und TOP
  • Anpassung TOP an die gewünschte Frequenz
  • Anpassung der Tabelle an die verlangte Amplitude und Offset
  • Anpassung des Tabellenwerts an das frequenzabhänige TOP

Nicht ganz klar ist der Verlauf der Frequenzänderung zwischen 45 und 55 Hz. Soll man alle 0,2s die Frequenz um 1 Hz erhöhen?

gang genau...

hier ist meine neu programm. Das hat aber leider nicht funktionert.

unsigned int AD_Value = 0 ; //Parameterwert für analogRead
// Init Serieller Plotter ende
void setup() {
Serial.begin(9600); // Init Serielle Schnittstelle
pinMode(10, OUTPUT); // Ausgabepin Timer1
//Init Timer1
ICR1 = 1599;
TCCR1A = 0xc0;
TCCR1B = 0x14;
OCR1A = 1388;
OCR1B = 1135;
TIMSK1 = 6;
} // Setup ende
int z = 0; // Zählvariable
// hier ist ein 222-Werte-10-Bit-Sinus mit Nullwert auf 799 abgelegt / min: 0; max: 1599
int a[222]={799,821,844,866,889,911,934,956,978,1000,1022,1043,1065,1086,1107,1128,1148,1168,1188,1208,1227,1246,1265,1283,1300,1318,1335,1351,1367,1383,1398,
1413,1427,1441,1454,1467,1479,1490,1501,1512,1522,1531,1540,1548,1556,1562,1569,1574,1580,1584,1588,1591,1594,1595,1597,1597,1597,1597,1595,1594,1591,1588,1584,
1580,1574,1569,1562,1556,1548,1540,1531,1522,1512,1501,1490,1479,1467,1454,1441,1427,1413,1398,1383,1367,1351,1335,1318,1300,1283,1265,1246,1227,1208,1188,1168,
1148,1128,1107,1086,1065,1043,1022,1000,978,956,934,911,889,866,844,821,799,776,754,731,708,686,664,642,619,597,576,554,533,511,490,470,449,429,409,390,370,351,
333,315,297,280,263,246,230,214,199,184,170,157,143,131,119,107,96,86,76,66,58,49,42,35,29,23,18,13,10,6,4,2,1,0,0,1,2,4,6,10,13,18,23,29,35,42,49,58,66,76,86,
96,107,119,131,143,157,170,184,199,214,230,246,263,280,297,315,333,351,370,390,409,429,449,470,490,511,533,554,576,597,619,642,664,686,708,731,754,776};
ISR(TIMER1_COMPA_vect) { // Interruptprogramm-PWM-Interrupt - läuft einmal pro PWM-Periode und holt neuen PWM-Wert
z = (z + 1); //
if(z == 222) z = 0;
OCR1B = a[z]; //hole Wert aus a[200]-Array
}
void loop() {
// Ausgabe
Serial.println(analogRead(A0)); // es wird direkt der AD-Wert ausgegeben zwischen 0 ... 1023
delay(20); // erforderlich sonst keine Anzeige über ser. Plotter
}

Wie glättest Du denn das PWM-Signal?

RC Tiefpass

Und was funktioniert dann nicht mit dem Code? Außer den fehlenden Codetags...

Was meinst du?

Hallo,

:thinking:

Zum Thema Timer.

// Init Timer1
ICR1 = 1599;
TCCR1A = 0xc0;
TCCR1B = 0x14;
OCR1A = 1388;
OCR1B = 1135;
TIMSK1 = 6;

Das kann niemand entziffern, außer mit viel Aufwand.
In der Arduino IDE sollte man bei eigenen Timerkonfigurationen aller Art immer zuerst den Timer stoppen, alle Register Nullen und dann neu konfigurieren. Das ist die sicherste Methode. Wenn man weiß was man macht kann man das auch etwas abkürzen.
Da mittels den CS Bits im TCCR1B Register der Timer gestartet und gestoppt wird, nulle ich zuerst dieses Register zum stoppen und setze die CS Bits am Schluss zum starten. Dann läuft der Timer zwischendrin nicht Amok.

Diese Schreibweise ist für alle leichter lesbar und wartbar.

Bsp.: TCCR1B = _BV(WGM12) | _BV(CS10);  

Dazu vielleicht noch ein Kommentar welcher Timermode gemeint ist.

Codetags. Ja es ist blöd das der Button dafür fehlt.
Darum schreibe:

[code]
...
[/code]

Uops? Ich hab den </>
Text markieren - drauf klicken.
Oder draufklicken und an Stelle der Meldung den Code einfügen.