Hallo in die Runde.
Ich habe einen C-Code erstellt um eine PWM über einen Spannungsteiler
mit Poti in der Frequenz zu regulieren.
Folgendes soll das Programm ausführen:
ADC Wandlung des Spannungsteilers.
ADCL-Register(max 255) schreibt seinen Wert in das Timer1-Register(TOP =
511), somit soll der Startwert des Timers beeinflusst werden.
Die Ausgangsfrequenz soll in 256 Stufen von 50 auf annähernd 100Hz
gestellt werden können
Kann das so klappen?
Ich versteh deinen Code zwar nicht wirklich, aber spätestens hier
1
ISR(ADC)
2
{
3
BOTTOM=ADC;
4
TCNT1=BOTTOM;
5
}
macht sich meine Alarmklingel höllisch bemerkbar.
Lass
den
Timer
in
Ruhe
arbeiten
!
Wenn du dauernd am TCNT1 rumfingerst, wirst du nie auch nur irgendeine
vernünftige PWM kriegen.
> Die Ausgangsfrequenz soll in 256 Stufen von 50 auf> annähernd 100Hz gestellt werden können
Dazu benutzt man üblicherweise eine DDS
> (1<<WGM12) | (1<<WGM11)
Welcher Timer MOdus ist das? (bin zu faul im Datenblatt nachzusehen)
Auch bin ich mir nicht sicher, ob da überhaupt ein Overflow Interrupt
ausgelöst wird. Denn streng genommen gibt es ja keinen Overflow des
Timers.
Aber alles in allem.
Nein, ich denke nicht das das so funktioniert.
Wenn du die Frequenz beeinflussen willst, dann musst du den TOP Wert des
Timers manipulieren. Was dann allerdings auch wieder bedeutet, dass die
Compare-Werte entsprechend skaliert werden müssen, damit sich der
Duty-Cycle jedes deiner Kurven-Stützpunkte aus dem Array nicht
verändert.
In Summ dürfte eine klassische DDS deutlich einfacher sein, auch wenn
bei deiner Lösung die Kurvenform theoretisch besser reproduziert wird,
weil bei allen Frequenzen alle Samples aus dem Array benutzt werden. Bei
einer Sample-Anzahl die groß genug ist, und deinen niedrigen Freuqenzen,
fällt das kaum ins Gewicht, wenn durch das DDS ein paar Sample-Points
pro Wellenzug unter den Tisch fallen.
> Lass> den> Timer> in> Ruhe> arbeiten> !>> Wenn du dauernd am TCNT1 rumfingerst, wirst du nie auch nur irgendeine> vernünftige PWM kriegen.>>> Die Ausgangsfrequenz soll in 256 Stufen von 50 auf>> annähernd 100Hz gestellt werden können>> Dazu benutzt man üblicherweise eine DDS>>>> (1<<WGM12) | (1<<WGM11)>> Welcher Timer MOdus ist das? (bin zu faul im Datenblatt nachzusehen)>> Auch bin ich mir nicht sicher, ob da überhaupt ein Overflow Interrupt> ausgelöst wird. Denn streng genommen gibt es ja keinen Overflow des> Timers.
Ok also keine Timer1 Manipulation außer dem TOP Wert -> d.h. aber auch,
dass dann nur ein PWM_Ausgang verwendet werden kann.
Ich verwende den Fast PWM Modus (Kommentierung ist irgendwo ins Nirvana
verschwunden).
Ich hab den Code aus einem vorher angefertigtem funktionierenden Code
für 50Hz Sinus Ausgansspannung abgeändert (zumindest aufm Oszi sah das
Ganze recht gut aus).
Der Overflow kommt auf jedenfall, sonst würde bei der Ausgabe an 2 LEDs
nicht abwechselnd die Helligkeit verändert werden.
Was meinst du mit DDS?
Peter A. schrieb:> Ok also keine Timer1 Manipulation außer dem TOP Wert -> d.h. aber auch,> dass dann nur ein PWM_Ausgang verwendet werden kann.
Wieso? Timermode 8, 10 und 14.
mfg.
und die Frequenz wird durch Verändern des Wertes 'Schritt' eingestellt.
(Die PWM wird nicht angetastet sondern läuft mit konstanter Frequenz
durch. Lediglich die Zeiten, in denen man der PWM einen jeweils anderen
zu realisierenden Duty-Cycle vorgibt, verändertn sich).
Wobei die Division durch 256 im Grunde nichts anderes als eine Variante
von Fixedpoint Arithmetik darstellt. Schritt hat 8-Bits für die
Nachkommastellen. Was wiederrum notwendig ist, damit man die Frequenz
fein genug einstellen kann, weil sich ja Schritt aus der gewünschten
Frequenz und der Anzahl der Samples durch eine Division errechnet.
Google mal nach DDS.
Dazu sollte sich hier im Forum einiges finden lassen.
Was meinst du mit Timermode 8,10,14 ?
Ich kann unterschiedliche Auflösungen einstellen also von 8-10 Bit für
den Timer1.
Im Datenblatt steht: "When using OCR1A as TOP value in PWM mode, the
OCR1A Register can not be used for generating PWM output."
-> Also kann hier keine PWM Ausgabe an den beiden Ausgängen erfolgen.
Hubert G. schrieb:> Du solltest auch im ADMUX ADLAR aktivieren und nur ADCH abfragen. Damit> hast du den richtigen 8bit-Wert für deinen Timer.
Und ich seh noch nicht, inwiefern die ADC Abfrage mittels Interrupt
irgendwas bringt.
Ganz im Gegenteil, würde ich die ADC Abfrage und Auswertung in die
Hauptschleife legen. Der ADC arbeitet schnell genug, wenn da ein Mensch
am Poti dreht.
Aber: wird die ADC Auswertung in einer ISR gemacht, dann muss der
'Overflow-Code' seinerseits warten, wenn der ADC gerade am Zug ist.
Ist die ADC Auswertung aber in der Hauptschleife, dann kann die
Kurvengenerierung diese unterbrechen und den nächsten Sample ausgeben.
Und das erscheint mir wichtiger zu sein, als ob die Frequenz erst ein
paar µs später vom Poti abgegriffen und aktiviert wird.
Hi
>Ich verwende den Fast PWM Modus (Kommentierung ist irgendwo ins Nirvana>verschwunden).
Warum Fast PWM? Wenn du eine variable Frequenz erzeugen willst ist CTC
der geeignete Mode. PWM ist für ein variables Tastverhältnis bei
konstanter Frequenz.
MfG Spess
Peter A. schrieb:> Was meinst du mit Timermode 8,10,14 ?> Ich kann unterschiedliche Auflösungen einstellen also von 8-10 Bit für> den Timer1.> Im Datenblatt steht: "When using OCR1A as TOP value in PWM mode, the> OCR1A Register can not be used for generating PWM output."> -> Also kann hier keine PWM Ausgabe an den beiden Ausgängen erfolgen.
Dann guck doch in die Tabelle im Datenblatt. Das sind die Modes mit ICR
als Top.
mfg.
Karl Heinz Buchegger schrieb:> Peter A. schrieb:> und die Frequenz wird durch Verändern des Wertes 'Schritt' eingestellt.> (Die PWM wird nicht angetastet sondern läuft mit konstanter Frequenz> durch. Lediglich die Zeiten, in denen man der PWM einen jeweils anderen> zu realisierenden Duty-Cycle vorgibt, verändertn sich).> Wobei die Division durch 256 im Grunde nichts anderes als eine Variante> von Fixedpoint Arithmetik darstellt. Schritt hat 8-Bits für die> Nachkommastellen. Was wiederrum notwendig ist, damit man die Frequenz> fein genug einstellen kann, weil sich ja Schritt aus der gewünschten> Frequenz und der Anzahl der Samples durch eine Division errechnet.>> Google mal nach DDS.> Dazu sollte sich hier im Forum einiges finden lassen.
OK also im prinzipiell kann ich auch einfach meine Array Größe
verändern, was den gleichen Effekt haben sollte.
Spess53 schrieb:> Hi>>>Ich verwende den Fast PWM Modus (Kommentierung ist irgendwo ins Nirvana>>verschwunden).>> Warum Fast PWM? Wenn du eine variable Frequenz erzeugen willst ist CTC> der geeignete Mode. PWM ist für ein variables Tastverhältnis bei> konstanter Frequenz.>> MfG Spess
Hab ich das richtig verstanden, dass im CTC-Modus der Zählerhochlauf
beim eingestellten TOP-Wert abgebrochen wird? Wenn ja, wird mein
Overflow Interrupt dann überhaupt ausgelöst?
Zum ICR1 Register:
Kann ich das dann einfach mit dem eingelesenen ADC-Wert beschreiben?
Zu der ADC-ISR:
Karl Heinz Buchegger hat schon erwähnt, dass es hier Probleme mit der
Overflow-ISR geben wird.
Schreibe ich dann einfach in der Dauerschleife der main meinen ADC-Wert
ins ICR1 Register?
[C]
for(;;)
{
ICR1 = ADC;
}
Peter A. schrieb:> Hab ich das richtig verstanden, dass im CTC-Modus der Zählerhochlauf> beim eingestellten TOP-Wert abgebrochen wird?>
Der Zähler fängt beim Erreichen von Top wieder von vorne an oder zählt
bei einigen PWM-Modes rückwärts.
> Zum ICR1 Register:> Kann ich das dann einfach mit dem eingelesenen ADC-Wert beschreiben?
Ja.
> Zu der ADC-ISR:> Karl Heinz Buchegger hat schon erwähnt, dass es hier Probleme mit der> Overflow-ISR geben wird.
Nein. Beimm Erreichen von Top wird das Overflow-Flag gesetzt und ggf.
der Interrupt ausgelöst.
> Schreibe ich dann einfach in der Dauerschleife der main meinen ADC-Wert> ins ICR1 Register?> [C]> for(;;)> {> ICR1 = ADC;> }
Nein.
Du liest den ADC in der Overfow-ISR vom Timer aus und startest den ADC
neu.
Beim nächsten Overflow hast du dann wieder den zuletzt gemessenen Wert.
Die ADC-ISR brauchst du gar nicht. Das Poti muss nicht mit maximal
möglicher Geschwindigkeit eingelesen werden. Genauso wenig wie das ICR
ständig neu beschrieben werden muss.
Ausserdem stellst du damit sicher, daß du ICR beschreibst, wenn der
Counter nahezu 0 ist. Sonst könnte der neue ICR auch mal <TCNT sein und
der Timer dreht eine grosse Runde über 65535 bis TCNT = ICR ist. In
jedem Fall solltest du den Minimalwert begrenzen.
mfg.
Ah ok danke. Das hilft mir auf jedenfall weiter.
Jetzt muss ich doch nochmal blöd nachfragen, weil ich heute erst mit der
ADC-Thema angefangen habe:
Heißt das, dass ich bei jedem Overflow Interrupt meine Register
ADCSRA |= (1<<ADEN) | (1<<ADSC) | .... setzten muss?
Also ADSC startet meine AD Conversion, so stehts zumindest im Datenblatt
und setzt sich nach Abschluss wieder auf 0(vorausgesetzt ich hab den
free running Modus nicht an).
Peter A. schrieb:> Ah ok danke. Das hilft mir auf jedenfall weiter.> Jetzt muss ich doch nochmal blöd nachfragen, weil ich heute erst mit der> ADC-Thema angefangen habe:>> Heißt das, dass ich bei jedem Overflow Interrupt meine Register> ADCSRA |= (1<<ADEN) | (1<<ADSC) | .... setzten muss?>> Also ADSC startet meine AD Conversion, so stehts zumindest im Datenblatt> und setzt sich nach Abschluss wieder auf 0(vorausgesetzt ich hab den> free running Modus nicht an).
Nur starten:
ICR = ADCH;
if(ICR < MINIMUM) ICR = MINIMUM;
ADCSRA |= (1 << ADSC);
Der Rest ändert sich ja nicht.
Und in der Initialisierung das ADLAR-Bit setzen!
mfg.
Hi
>Nein. Beim Erreichen von Top wird das Overflow-Flag gesetzt und ggf.>der Interrupt ausgelöst.
Im CTC-Mode wird je nach Top-Register das OCR- bzw. IC-Interrupt Flag
gesetzt aber kein Overflow IR Flag. Bei Fast-PWM das Overflow-Interrupt
Flag und bei variablen Top zusätzlich das entsprechende Interrupt Flag
des Registers gesetzt.
MfG Spess
Thomas Eckmann schrieb:> Der Rest ändert sich ja nicht.>> Und in der Initialisierung das ADLAR-Bit setzen!
Was hat es mit dem ADLAR-Bit auf sich? Im Datenblatt steht, dass es die
Werte bei gesetztem Zustand vom MSB abwärts in das High-Byte schreibt
und bei 0 vom LSB aufwärts ins Low-Byte des ADC-Registers.
Werden die beiden Bytes falsch zusammengesetzt beim überschreiben auf
das ICR1 Register, oder warum muss dieses Bit gesetzt sein?
Spess53 schrieb:> Im CTC-Mode wird je nach Top-Register das OCR- bzw. IC-Interrupt Flag> gesetzt aber kein Overflow IR Flag. Bei Fast-PWM das Overflow-Interrupt> Flag und bei variablen Top zusätzlich das entsprechende Interrupt Flag> des Registers gesetzt.
Heißt das, dass ich statt dem Overflow Interrupt-Vektor den Capture
Event Vektor verwenden muss?
Peter A. schrieb:> Thomas Eckmann schrieb:>>> Der Rest ändert sich ja nicht.>>>> Und in der Initialisierung das ADLAR-Bit setzen!>>> Was hat es mit dem ADLAR-Bit auf sich? Im Datenblatt steht, dass es die> Werte bei gesetztem Zustand vom MSB abwärts in das High-Byte schreibt> und bei 0 vom LSB aufwärts ins Low-Byte des ADC-Registers.> Werden die beiden Bytes falsch zusammengesetzt beim überschreiben auf> das ICR1 Register, oder warum muss dieses Bit gesetzt sein?
Mit ADLAR = 1 wird der ADC-Wert um 6 Bit nach links geschoben. Damit
stehen die Bits 2 - 9 im ADCH. Die nimmst du als ADC-Ergebnis.
Wenn du mit ADLAR = 0 die unteren 8 Bits verwendest(BOTTOM = ADC;), hast
du 1. Wackler auf den unteren beiden Bits und wenn du dein Poti drehst,
bist du nach 1/4 Umdrehung wieder auf 0. Und der Wert wackelt dann auch
mindestens zwischen 1 und 255 hin umd her.
Mit ADLAR hast du praktisch einen sauberen 8-Bit-Wandler und drehst dein
Poti von 0 - 255 über den vollen Bereich.
> Im CTC-Mode wird je nach Top-Register das OCR- bzw. IC-Interrupt Flag> gesetzt aber kein Overflow IR Flag.
Stimmt. Der OVF wird zwar bei Timer-Overflow gesetzt. Wenn der Timer
allerdings richtig läuft, wird dieser Wert nie erreicht.
mfg.
> Was hat es mit dem ADLAR-Bit auf sich?
Kurz gesagt (und ein wenig vereinfacht)
* ADLAR nicht gesetzt -> du hast einen 10 Bit ADC und musst aus ADCH
und ADCL den 10 Bit Wert zusammensetzen
* ADLAR gesetzt -> du hast einen 8 Bit ADC und in ADCH steht bereits das
komplette 8 Bit Ergebnis
Für dich heißt das:
Du willst deine Spannung in 256 Stufen aufteilen (also 8 Bit).
Wozu soll der Code dann zuerst ein 10 Bit Ergebnis aus 2 ADC Registern
zusammensetzen, nur um dann dieses Ergebnis durch rechtsverschieben um 2
Bit wieder zu einem 8 Bit Ergebnis zu machen, wenn du dieses 8 Bit
Ergebnis dir auch direkt vom ADC abholen kannst?
Peter A. schrieb:> Kann ich mein ICR1 Register nicht mit dem 10Bit Wert von ADC> überschreiben?>> ICR1=ADC; // ohne das Tauschen mit ADLAR=1
Ja sicher.
Dann hast du bis zu 1024 Schritte. Also die vierfache Auflösung.
In deinem Ausgangspost hattest du aber den ADC-Wert (unsauber) auf 8 Bit
heruntergebrochen. Deswegen der Hinweis auf ADLAR, um das ohne Risiken
und Nebenwirkungen zu machen.
mfg.
falsch.
Du kannst nicht den ADC starten und dir gleich darauf sofort das
Ergebnis abholen.
Der ADC braucht ein bischen Zeit, bis er das Ergebnis fertig hat!
So gehts normalerweise
1
ADCSRA|=(1<<ADSC);// AD-Wandlung starten
2
while(ADCSRA&(1<<ADSC))// Warten bis das Ergebnis vorliegt.
3
;
4
5
ICR1=ADC;// Ergebnis holen
aber das willst du hier nicht machen, weil es in einer ISR passiert.
Jetzt ist es aber so, dass wir wissen, dass er ADC nicht ewig für sein
Ding braucht. D.h. man kann das ganze ein wenig umdrehen. Macht man es
so
1
...
2
3
ICR1=ADC;// den letzten Wert holen
4
5
ADCSRA|=(1<<ADSC);// und den ADC gleich wieder starten
6
// Das Ergebnis dieser WAndlung wird
7
// abgeholt, wenn der Code das nächste
8
// mal aufgerufen wird
9
10
...
dann arbeitet der ADC, während der Rest des Programms abläuft. Und wir
wissen: dieser Rest benötigt mehr Zeit als der ADC für eine Wandlung
braucht. Wird die ISR das nächste mal aufgerufen, dann ist der hier
gestartete Wandlungsdurchgang schon längst fertig und das Ergebnis liegt
bereits zur Abholung bereit.
Ausserdem denke ich nicht, dass deine ganzen sizeof Ausdrücke richtig
sind.
sizeof liefert die gesuchte Größe in Bytes!
d.h. bei
1
uint16_tarray[36];
liefert sizeof(array) den Wert 72.
Und ich denke nicht, dass du das berücksichtigt hast.
Willst du mittels sizeof die Arraygröße feststellen, dann geht das so
1
#define ARRAY_SIZE(x) (sizeof(x)/sizeof(*x))
2
3
....
4
5
if(x<ARRAY_SIZE(array))
6
....
(und nenn die Variable nicht 'array'. Das ist recht nichtssagend. Das
das ein Array ist, das sehe ich auch so.
Welche Funktion hat den dieses Array? Welche WErte sind denn in ihm
gespeichert?
Das sind ja wohl die Sample-Werte zur Kurvenerzeugung. Also könnte man
das zb samples nennen. Oder stuetzpunkte. Oder sinus. Oder ....
Egal wie - aber benenne Variablen nach ihrer Funktion und nicht nach
ihrem Datentyp.
kann zu numerischen Problemen führen! Schritt ist eine Ganzzahlvariable.
D.h. was auch immer theoretisch an Kommastellen bei der Division
entstehen würden, sie sind nicht in Schritt enthalten. Wenn du daher
dann in weiterer Folge aufsummierst, dann summierst du auch den dadurch
eingeführten Fehler mit auf, der dann immer größer wird.
Mal ein Beispiel mit etwas anderen Zahlen, du willst 11 Zahlen, die den
Bereich 0 bis 19 abdecken. 0 (die erste Zahl), soll sich auf 0 abbilden,
der Funktionswert für 10 soll 19 sein.
Also diese Zuordnung ist zu realisieren
wie errechnen sich die Werte, wenn man Nr hat? Ganz einfach
1
Wert = Nr * 1.9;
warum 1.9?
Das folgt aus dem gewünschten Wertebereich und der gewünschten Anzahl
der Zahlen.
1
Schrittweite = ( 19.0 - 0.0 ) / ( 10 - 0 )
2
Schrittweite ist hier also 19.0 / 10.
3
Und das ergibt 1.9
Rechne die Werte nach. stimmt alles
Aber:
Deine variable schritt ist aber nicht in der Lage einen Werte von 1.9 zu
speichern. Die kann nur die 1 Speichern.
Welche Tabelle ergibt sich daraus jetzt bei dir mit diesem Code?
1
for(uint8_ti=0;i<sizeof(array);i++)
2
{
3
array[i]=array[i-1]+Schritt;
4
}
Nun. Da entsteht mit den hier gewählten Beispielzahlen diese Zuordnung
und das ist noch nicht mal annähernd korrekt. Die Funktionswerte laufen
nicht wie gefordert von 0 bis 19.0 sondern von 0 bis 10.0
Warum ist das so?
Weil die korrekte Schrittweite eigentlich 1.9 gewesen wäre, der Code
aber nur mit 1 gerechnet hat. d.h. bei jedem Schritt der aufaddiert
wird, fehlen 0.9. Und: bei jeder jeweils nächsten zu ermittelnden Zahl
wird dieser Fehler mitgeschleppt und es kommen weitere 0.9 Fehler gleich
noch mit dazu!
Mit der ganzen PWM Sache gibt es auch noch ein Problem.
Du kannst nicht einfach die OCR Werte so mir nichts dir nichts zuweisen!
Wenn du ICR1 veränderst, müssen sich auch die OCR Werte verändern, damit
der angestrebte Duty Cycle erreicht wird!
Also ich änder das Ganze jetzt ab. Das mit dem numerischen Fehler und
die fehlende Duty_cycle Anpassung waren mir schon klar. Aber trotzdem
danke für den Hinweis.
Dann würde ich vorschlagen, du präsentierst dein Programm das nächste
mal erst dann, wenn du denkst dass es korrekt ist. Denn dann spare ich
mir in der Zwischenzeit die Analyse, was in deinem Code noch alles
fehlerhaft ist.
Noch eine Frage:
Zur Anpassung meiner Duty Cycle Werte müsste ich meine Werte in der
Dauerschleife der main-Funktion beschreiben oder gibt´s da eine
sinnvollere Lösung? Wie würde ich den numerischen Fehler meiner
Schrittwerte beikommen?
Könnte ich mein array mit float-Zahlen beschreiben und vor der
Index-Auswahl die Zahlen auf eine int Variable überschreiben? Oder
lassen sich float Zahlen auch mit dem Timerwert vergleichen?