Hey zusammen,
ich brauche mal eure Hilfe. Ich versuche im Moment einen
Frequenzgenerator mit einem AtMega2560 zu bauen.
Anforderungen sind Dreieck und Rechteckspannung bei 10 bis 250Hz und
einstellbarer Amplituden Höhe mit Prozent.
Ich habe bis jetzt nur den Dreieckpart Programmiert da ich diesen für
schwieriger halte.
Ich habe mich dazu entschieden einen Signal Durchlauf (eben mit 10 bis
250Hz) in 512 einzelnen Schritten zu Programmieren um da hinzubekommen
habe ich das Fast PWM des AtMegas genommen und Setze die Grenzwerte so
das während eines Signal Durchlaufes 512 Interrupts ausgelöst werden
Davon werden bei 256mal immer die Positive Halbwelle verlängert und die
anderen 256 diese halt immer Reduziert wobei dann ja am Ende eine
Dreieckspannung entsteht nachdem man das ganze durch einen
Tiefpassfilter geschickt habe.
Soweit funktioniert das ganze auch. Nur bei höheren Frequenzen stimmt
meine Berechnungsformel anscheinend nicht mehr weswegen ich dann die
Werte passend ausprobiert habe die in das OCRnA geschrieben werden
müssen.
1
uint16_tberchnungOCR1A(int32_tfrequenz)
2
{
3
int32_tfrequenzEinesBruchteilsDerAusgangsFrequenz=frequenz*512;//nimmt die gewünschte Frequenz und berechnet die Impulszeit wenn das Signal mit 512 einzelnen Impulsen dargestellt wird
4
return(((16000000/frequenzEinesBruchteilsDerAusgangsFrequenz)/2));//Berechnet den Endwert der Funktion abhängig davon wie oft der gewählte Timer hochzählt
5
}
Könnte durch auch sein das hier bereits ein Fehler in meinen
Einstellungen der Timer ist. Allerdings reicht mir eine Ansteuerung in
10Hz Stufen bereits aus weswegen ich mit den ermittelten Werten auch
weiter machen könnte.
Das ist aber nicht mein konkretes Problem, den mit den ermittelten
Werten kann ich für mich ein ausreichendes Signal Erzeugen. Problem ist
das ich zu der Frequenz Erzeugung noch andere Aufgaben bearbeiten muss
und zwar:
Soll ein 1s Timer laufen damit das Signal nach einer bestimmten Zeit
auch beendet wird
Und es sollen noch Tasten genutzt werden können um das Signal auch
manuel Stoppen kann
Das beides funktioniert bei dem 10Hz Signal zwar noch allerdings kommt
der Controller bei höheren Frequenzen wohl nicht mehr dazu die anderen
Funktionen zu bearbeiten weswegen der Timer steht und und die Tasten
Auswertung übergangen wird.
Unten sieht man den Teil des Programmes der sich mit der sich explizit
mit der Signal Erzeugung beschäftigt. Und angehangen ist das ganze
Projekt. Mit dem kompletten Code geschrieben in der Arduino IDE.
Dabei ist auch die Ansteuerung eines UTFT Displays, die ist aber nur
notwendig wenn das Dreieck bzw. Rechteck Signal nicht von nöten ist.
Der Teil der irgendwann nicht mehr ausgeführt befindet sich in der Loop.
Vielleicht hat ja jemand eine Idee wie ich die Signal Erzeugung umsetzen
könnte und währenddessen noch andere Aufgaben erledigen kann.
Ich wäre auch für ganz andere Ideen offen als über einen Interrupt in
einem Timer das Signal zu Erzeugen falls ich generell falsch an die
Sache gegangen bin.
Ich hoffe ich konnte rüber bringen wie ich daran gegangen.
Edit: Die kleinere der beiden Dateien reicht völlig zum Download. Erst
konnte ich keine von beiden anhängen und dann waren beide da :D
Roger S. schrieb:> ich brauche mal eure Hilfe. Ich versuche im Moment einen> Frequenzgenerator mit einem AtMega2560 zu bauen.
Nun, deine Erklärungen, was du tun willst, der Code und die Kommentare
im Code passen an unzähligen Stellen nicht zusammen.
Bitte erstmal selbst aufräumen, bevor du andere belästigst.
Die Methode ist normal etwas anders.
Du nimmst zuerst ein Sample von Sinus (Dreieck, Rechteck) mit z.B. 512
Werten und schiebst diesen Sample über einen 8-Bit Parallelausgang auf
einen D/A-Wandler.
Wenn du variable Verzögerungen einbaust, dann kannst du die Frequenz
ändern.
Ohne Verzögerungen hast du eine maximale Frequenz.
Noch höher kommst du, wenn du nur jeden 2. Sample ausgibst.
Roger S. schrieb:> int32_t frequenzEinesBruchteilsDerAusgangsFrequenz = frequenz * 512;
Wennmansolchelangenvariablennamenverwendetmussdasjaschiefgehen.
Roger S. schrieb:> Hey zusammen,> ich brauche mal eure Hilfe. Ich versuche im Moment einen> Frequenzgenerator mit einem AtMega2560 zu bauen.> Anforderungen sind Dreieck und Rechteckspannung bei 10 bis 250Hz und> einstellbarer Amplituden Höhe mit Prozent.
Klingt ja recht machbar.
> Ich habe bis jetzt nur den Dreieckpart Programmiert da ich diesen für> schwieriger halte.
Nicht wirklich.
> Ich habe mich dazu entschieden einen Signal Durchlauf (eben mit 10 bis> 250Hz) in 512 einzelnen Schritten zu Programmieren um da hinzubekommen> habe ich das Fast PWM des AtMegas genommen und Setze die Grenzwerte so> das während eines Signal Durchlaufes 512 Interrupts ausgelöst werden> Davon werden bei 256mal immer die Positive Halbwelle verlängert und die> anderen 256 diese halt immer Reduziert wobei dann ja am Ende eine> Dreieckspannung entsteht nachdem man das ganze durch einen> Tiefpassfilter geschickt habe.
Kann man so machen.
> Soweit funktioniert das ganze auch. Nur bei höheren Frequenzen stimmt> meine Berechnungsformel anscheinend nicht mehr weswegen ich dann die> Werte passend ausprobiert habe die in das OCRnA geschrieben werden> müssen.
Ohje ;-) Generation PISA?
> uint16_t berchnungOCR1A(int32_t frequenz)> {> int32_t frequenzEinesBruchteilsDerAusgangsFrequenz = frequenz * 512;
AUA! Bist du Kettenwortliebervereinvorsitzender?
> Das ist aber nicht mein konkretes Problem, den mit den ermittelten> Werten kann ich für mich ein ausreichendes Signal Erzeugen. Problem ist> das ich zu der Frequenz Erzeugung noch andere Aufgaben bearbeiten muss> und zwar:
Das ist der Normalfall, erreicht man mit einem Interrupt und
Multitasking.
> Soll ein 1s Timer laufen damit das Signal nach einer bestimmten Zeit> auch beendet wird> Und es sollen noch Tasten genutzt werden können um das Signal auch> manuel Stoppen kann
Trivial.
> Das beides funktioniert bei dem 10Hz Signal zwar noch allerdings kommt> der Controller bei höheren Frequenzen wohl nicht mehr dazu die anderen> Funktionen zu bearbeiten weswegen der Timer steht und und die Tasten> Auswertung übergangen wird.
Dann hast du einen Programmier- oder Konzeptfehler.
> Unten sieht man den Teil des Programmes der sich mit der sich explizit> mit der Signal Erzeugung beschäftigt.
"Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang"
> Vielleicht hat ja jemand eine Idee wie ich die Signal Erzeugung umsetzen> könnte und währenddessen noch andere Aufgaben erledigen kann.> Ich wäre auch für ganz andere Ideen offen als über einen Interrupt in
Besorg dir mal ein gescheites Buch zum Thema C und lerne, daß Funktionen
etc. NICHT in Headerdateien (.h) gehören! Die gehören in Quelldateien
(.c)
Zu deinem Kernproblem. Ein einfaches Rechtecksignal konstanter Amplitude
kann man mit einem einfachen Digitalausgang erzeugen. Mittels eines 16
Bit Zählers, CTC Mode und Output Compare Funktion. Das ist trivial,
steht alles im Datenblatt. Ein Dreiecksignal kann man so nicht erzeugen.
Du hast aber schon mal den richtigen Ansatz. Mittels PWM gibt man ein
Signal aus, daß nach einer Tiefpaßfilterung dann das gewünschte Signal
ergibt. Das ist eine Form von DA-Wandler. Wenn man 250Hz Dreieck
erzeugen will, muss man, wenn das Dreieck als solches noch erkennbar
sein soll, auch mit ausreichend Oberschwingungen rechnen. Außerdem
sollte die DA-Ausgabefrequenz (Abtastfrequenz) möglichst hoch sein, um
den Filteraufwand zu minimieren. Pi mal Daumen hat ein 250 Hz Dreieck
vielleicht bis 5kHz nennenswerte Signalanteile, die man mit min. 10kHz
ausgeben muss. Nehmen wir 20kHz. Also musst du eine PWM konfigurieren,
die mit mindestens 20kHz läuft. In dieser ISR wird dann aus einer
Tabelle ein Datenwert gelesen und in das OCR0A Register geschrieben. Man
könnte es auch berechnen, ist bei Dreieck ja eher einfach. Die
verschiedenen Frequenzen erreicht man mittels DDS-Akkumulator. Das
ist praktisch nichts weiter, als einfache Festkommaaritmetik. Etwa
so.
Beitrag "Re: SPWM auf Atmega8, bitte um Feedback hinsichtlich Optimierung"
Die variable Amplitude erreicht man dadurch, indem man die Tabellenwerte
mit einer Variable, der Amplitude mutlipliziert und anschließend
skaliert. Sprich, die Tabellenwerte sind einfache vorzeichenbehaftete
Bytes von -127 - 127. Die werden mit einer 8 Bit Variable mit dem
gleichen Wertebereich multipliziert. Das ergibt eine vorzeichenbehaftete
16 Bit Zahl. Die ist zu groß für OCR0A. Also wird skaliert, indem man
die untersten 8 Bit wegwirft und nur die oberen 8 Bits in OCR0A
schreibt.
Noch ein paar Tips zum Quelltext.
In ISRs möglichst keine Funktionen aufrufen, sondern alles direkt
hinschreiben. Denn bei Funktionsaufrufen in einer ISR muss der Compiler
deutlich mehr Register retten und wieder herstellen, das kostet
wertvolle Takte, besonders bei hochfrequenzen ISRs.
Wozu brauchst du 2 ISRs, die das gleiche machen?
Sooo, ich war mal so frei, das Chaos ein wenig aufzuräumen.
1.) Alle Dateien sortiert und getrennte .cpp und .h Dateien erstellt
2.) Die Arrays mit den Dateinamen in den Flash geschoben, spart
wertvollen RAM.
https://www.arduino.cc/reference/en/language/variables/utilities/progmem/
3.) Die vielen Warnungen über die konstanten Strings in den
Funktionsaufrufen durch Umweg über variablen String vermieden. Man will
beim Compilieren praktisch immer NULL Warnungen haben, denn dann muss
man nicht schauen, ob die Warnung wichtig oder unwichtig ist. Mit den
Warnungen aus den Bibliotheken muss man wohl oder übel leben, wenn man
nicht die Drecksarbeit machen will, und diese Fehler korrigieren will
(meistens eher nicht)
1
// Original
2
myFiles.load(0,0,800,480,"Start-Screen.raw",1,1);
3
4
// Neu
5
strcpy_P(filename,PSTR("Start-Screen.raw"));
6
myFiles.load(0,0,800,480,filename,1,1);
4.) Die Arrays mit den systematischen Namen kann man in diesem Fall
einsparen und die Dateinamen berechnen. Etwa so.
1
itoa(counter_Hertz*10,filename,10);// Index in ASCII-String umwandlen
Strukturierte Programmierung auf Mikrocontrollern
ISBN: 3897215675
5.) Du braucht hier keine Sekunde irgendwelche Floats, erst recht NICHT
in einem hochfrequenten Interrupt!!! Das macht man mit
Festkommaarithmetik, im einfachsten Fall mit 16 Bit, bei dem man
nach der Multiplikation die unteren 8 Bit ignoriert!
1
ISR(TIMER1_OVF_vect)
2
{
3
// keine Funktionsaufrufe in einer ISR, das kostet wertvolle Takte für die Registersicherung und Widerherstellung
Falk B. schrieb:> Sooo, ich war mal so frei, das Chaos ein wenig aufzuräumen.
Respekt dass du dich mit dem Arduino-Sch.... so intensiv
auseinandersetzt. Wäre ja alles schön und gut wenn da nicht
immer dieses setup() und loop() da wären ;-)
Naja, man hat ja sonst nix zu tun auf dieser Welt ....
So, Vielen Dank für eure Hilfen besonders dir Falk.
ich habe jetzt viel Zeit damit verbracht das ganze zu Optimieren.
In dem Zuge habe ich auch gemerkt warum meine Funktion mit dem etwas
langgeraten Variablen Namen nicht richtig funktioniert hat :D
Zu meinem Programaufbau mit den Funktionen in header Dateien.
Dass das nicht die schönste Art und Weise zu Programmieren ist ist mir
bekannt aber für die Übersichtlichkeit finde ich es hilfreicher als
nochmal das doppelte an Dateien rumfliegen zu haben. Meines Wissens hat
das ganze auch keine Programmtechnischen Nachteile, wenn ich aber das
nächste mal hier ein Programm hochlade passe ich es aber an.
Deine Vorschläge den Dateinamen immer zusammen zu bauen statt die haufen
an Strings zu speichern werde ich umsetzen sobald die Signal Erzeugung
funktioniert.
Ich habe versucht meine ISR Routine weiter aufzuräumen allerdings ohne
merklichen Erfolg bei der Bearbeitungszeit.
Statt der if else Verschachtelung habe ich auch die Umsetzung mit einem
Array versucht aus dem der aktuelle Wert geholt wird. Allerdings ohne
Besserung der Bearbeitungszeit. Das ganze sieht dann so aus:
Ich bin auch schon mit der Auslösezeit des Interrupts runtergegangen von
anfangs 512 mal zu nur noch 200mal gebracht hat das aber auch nichts
wirklich etwas
Falk B. schrieb:> Wozu brauchst du 2 ISRs, die das gleiche machen?
Das Signal soll später wahlweise an dem einem oder dem anderen Pin
anliegen können.
Falk B. schrieb:> 5.) Du braucht hier keine Sekunde irgendwelche Floats, erst recht NICHT> in einem hochfrequenten Interrupt!!! Das macht man mit> Festkommaarithmetik, im einfachsten Fall mit 16 Bit, bei dem man> nach der Multiplikation die unteren 8 Bit ignoriert!
Ich habe jetzt in meinen ISR die Berechnung mit Floats umgangen auch
wenn ich es anders gemacht habe als du Vorgeschlagen hast könnte hier
die Zeit verloren gehen?
Roger S. schrieb:> Zu meinem Programaufbau mit den Funktionen in header Dateien.> Dass das nicht die schönste Art und Weise zu Programmieren ist ist mir> bekannt aber für die Übersichtlichkeit finde ich es hilfreicher als> nochmal das doppelte an Dateien rumfliegen zu haben.
Das ist kompletter Unsinn. Du produzierst das reinste Chaos!
> Meines Wissens hat> das ganze auch keine Programmtechnischen Nachteile,
Aber sicher! Das hat es massenhaft.
> wenn ich aber das> nächste mal hier ein Programm hochlade passe ich es aber an.
Gewöhn dir den Scheiß ab und mach es wie der Rest der Welt. Das klingt
vielleicht uncool und nach Masse, ist aber totzdem 10x sinnvoller.
> Deine Vorschläge den Dateinamen immer zusammen zu bauen statt die haufen> an Strings zu speichern werde ich umsetzen sobald die Signal Erzeugung> funktioniert.
Das Eine hat mit dem anderen nix zu tun. Außerdem ist das deutlich
einfacher und schneller gemacht.
> Ich habe versucht meine ISR Routine weiter aufzuräumen allerdings ohne> merklichen Erfolg bei der Bearbeitungszeit.
Wie hast du das denn gemessen?
> ISR (TIMER1_OVF_vect)> {> //Erzeugen des Signalverlaufs> if (richtung)> { //abwärts> if (aktuellerWert > 0)> aktuellerWert--;> else> richtung = 0;> }> else> { //aufwärts> if (aktuellerWert < 100)> aktuellerWert++;> else> richtung = 1;> }> //Ausgeben des Signals> OCR1A = (((periodendauer*aktuellerWert)/100)* (prozent /100));
Was soll das? So eine Rechung mit mehreren Multiplikationen und
Divisionen braucht einiges an CPU-Leitung und ist unsinnig. Auch wenn es
nur Integer sind. Und wenn gleich man das Dreiecksignal berechnen kann,
wie du es tust, ist auch das nicht sinnvoll. Nimm eine Tabelle mit 256
Einträgen, wo dein Signal schon vorausberechnet drin steht und gib die
einfach stur aus, so wie ich es skizziert habe.
> Statt der if else Verschachtelung habe ich auch die Umsetzung mit einem> Array versucht aus dem der aktuelle Wert geholt wird. Allerdings ohne> Besserung der Bearbeitungszeit.
Woher weißt du das?
Das ganze sieht dann so aus:
> uint16_t test2 = 0;> const uint8_t test[] = {> 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 ,> 15 , 16 , 17 , 18 , 19 , 20 , 21 , 22 , 23 , 24 , 25 , 26 ,> 27 , 28 , 29 , 30 , 31 , 32 , 33 , 34 , 35 , 36 , 37 , 38 ,> 39 , 40 , 41 , 42 , 43 , 44 , 45 , 46 , 47 , 48 , 49 , 50 ,> 51 , 52 , 53 , 54 , 55 , 56 , 57 , 58 , 59 , 60 , 61 , 62 ,> 63 , 64 , 65 , 66 , 67 , 68 , 69 , 70 , 71 , 72 , 73 , 74 ,> 75 , 76 , 77 , 78 , 79 , 80 , 81 , 82 , 83 , 84 , 85 , 86 ,> 87 , 88 , 89 , 90 , 91 , 92 , 93 , 94 , 95 , 96 , 97 , 98 ,> 99 ,> 99 , 98 , 97 , 96 , 95 , 94 , 93 , 92 , 91 , 90 , 89 , 88> , 87 , 86 , 85 , 84 , 83 , 82 , 81 , 80 , 79 , 78 , 77 , 76> , 75 , 74 , 73 , 72 , 71 , 70 , 69 , 68 , 67 , 66 , 65 , 64> , 63 , 62 , 61 , 60 , 59 , 58 , 57 , 56 , 55 , 54 , 53 , 52> , 51 , 50 , 49 , 48 , 47 , 46 , 45 , 44 , 43 , 42 , 41 , 40> , 39 , 38 , 37 , 36 , 35 , 34 , 33 , 32 , 31 , 30 , 29 , 28> , 27 , 26 , 25 , 24 , 23 , 22 , 21 , 20 , 19 , 18 , 17 , 16> , 15 , 14 , 13 , 12 , 11 , 10 , 9 , 8 , 7 , 6 , 5 , 4 , 3 , 2 , 1> , 0> };> ISR (TIMER1_OVF_vect)> {> //Erzeugen des Signalverlaufs> aktuellerWert = test[test2];> if (test2 < 199)> test2++;> else> test2 = 0;> //Ausgeben des Signals> OCR1A = (((periodendauer * aktuellerWert) / 100) * (prozent / 100));> }
Schon wieder die sinnlose Formel. Lies mal was über
Festkommaarithmetik.
Und warum eine Tabelle mit 200 Einträgen? Ja, kann man machen, bringt
aber wenig Vorteile.
> Ich bin auch schon mit der Auslösezeit des Interrupts runtergegangen von> anfangs 512 mal
Was für ein Ding? Was heißt denn 512 mal? 512 mal pro Sekunde? Das ist
GAR NICHTS für so eine CPU.
> zu nur noch 200mal gebracht hat das aber auch nichts> wirklich etwas
Wie hast du das festgestellt?
>> Falk B. schrieb:>> Wozu brauchst du 2 ISRs, die das gleiche machen?>> Das Signal soll später wahlweise an dem einem oder dem anderen Pin> anliegen können.
Das macht man trotzdem nicht so, nur in SEHR besonderen Fällen. Hier
brauchst du keine verschiedenen Frequenzen für die ISR, also können
beide Zähler mit der gleichen Frequenz laufen. Dann reicht es, die
Berechnungung der neuen Daten in einer ISR zum machen. Das spart massiv
CPU-Leistung.
> Ich habe jetzt in meinen ISR die Berechnung mit Floats umgangen
Toll, dafür ein halbes Dutzend Multiplikationen und Divisionen
reingebracht, wie ein total naiver Anfänger 8-0
auch
> wenn ich es anders gemacht habe als du Vorgeschlagen hast könnte hier> die Zeit verloren gehen?
Ach herje . . . . .
Wir haben doch alle zeit der Welt, wie soll da was verloren gehen?
Die Schiebeoperationen sind sehr schnell, denn der Compiler merkt, daß
er einfach nur das LSB wegwerfen muss und nur mit dem MSB rechnen kann.
Am Ende ist es nur eine echte Multiplikation, die dank
Multiplikationsbefehl nur wenige Takte benötigt.
Amplitude ist auf 256 normiert, sprich 256 == 1 == 100%.
Halt Festkommaarithmetk. Das Zahlenformat wäre I0Q8,
sprich 0 Bits für die Vorkommastellen, 8 Bits Nachkomma, kein
Vorzeichen.
Du gibst neben den beiden Interrupts für Timer 1 und 4 auch den für
Timer 5 frei. Dafür gibt es aber keine ISR! Das heißt aber, daß dadurch
ein "Bad ISR vector" ausgelöst wird, was beim AVR zu einem Reset führt.
Dein Programm stürzt damit dauerhaft ab!
MAN DARF NIEMALS EINEN INTERRUPT FREIGEBEN, FÜR DEN ES KEINE ISR GIBT!
Was zum Geier machst du mit DREI Timern? Nur weil der Mega tonnenweise
davon hat, muss man die nicht so sinnlos einsetzen.