Beliebige PWM Frequenz erzeugen

Guten Abend,

Gibt es eine Möglichkeit eine beliebige PWM Frequenz zu erzeugen.
Ich arbeite mit einem Arduinio UNO und würde gerne ein PWM Signal mit 180Hz erzeugen, um damit eine Zumesseinheit (ZME) anzusteuern.

Zwar kann man die PWM Frequenz einiger Pins ändern, allerdings ist man da doch sehr beschränkt. Woran liegt das eigentlich?

Theoretisch könnte ich ein eigenes PWM Signal selbst erzeugen ?!?
analogWrite(Pin, HIGH);
delay(Zeit1);
analogWrite(Pin, LOW);
delay(Zeit2);
etc.

Allerdings bin ich dann doch sehr stark von meiner Programmdurchkaufzeit abhängig, die mein so erzeugtes Signal beeinflusst. Das macht also wenig Sinn und fällt daher eher flach.

Gibt es eine andere Möglichkeit? Gibt es dafür vielleicht irgendwelche Shields/Platinen?

Für PWM werden Timer benötigt, für 2-3 PWM Ausgänge pro Timer. Beim Uno sind zwei Timer nicht ganz fest belegt, man kann also einen davon für eine spezielle Frequenz benutzen. Vielleicht hilft da die TimerOne Bibliothek, ansonsten ist Feinarbeit mit den Timer-Registern erforderlich.

Dein Code könnte funktionieren, wenn digitalWrite statt analogWrite verwendet wird, und das Ganze nach BlinkWithoutDelay gesteuert wird.

Du musst lernen die Timer per Hand zu programmieren. Da gibt es viel mehr Optionen als in der IDE implementiert sind

Hagi-Hagi:
Zwar kann man die PWM Frequenz einiger Pins ändern, allerdings ist man da doch sehr beschränkt. Woran liegt das eigentlich?

1.) am Prescaler der Timer. Der hat halt nur wenige Werte.
2.) in der Software ist nur 8 Bit PWM implementiert. Wenn man das per Hand machst sind auch 9 und 10 Bit möglich. Damit kommt man auch auf andere Zeiten.

Außerdem gibt es beim Timer1 auch Modus 10 oder 14 wo man den Wert des TOP Registers frei wählen kann. So sind dann bis zu 16 Bit Auflösung möglich! Damit ginge 180 Hz ohne Probleme:
16 MHz / 8 / 180 Hz = 11111

Das ist für Fast PWM. Für Phase Correct muss man da glaube ich noch / 2 machen weil sich die Frequenz halbiert. Dann ginge auch Precaler 1 statt 8. Phase Correct wird für Motoren empfohlen.
Letztlich muss der Wert der da raus kommt nur in ein 16 Bit Register passen

Das bedeutet die 16 MHz CPU Takt werden durch den Prescaler geteilt. Mit diesem Takt zählt der Timer hoch bis er den Wert des TOP Registers erreicht und läuft dann wieder auf 0 über. An der Stelle gibt es dann einen Flankenwechsel, durch den die Frequenz bestimmt wird. Der Flankenwechsel dazwischen - durch den such der Duty Cycle ergibt - wird durch das Vergleichsregister (z.B. OCR1A) bestimmt.

Hier gibt es schöne Bilder dazu:
http://maxembedded.com/2011/08/avr-timers-pwm-mode-part-i/
http://maxembedded.com/2012/01/avr-timers-pwm-mode-part-ii/
(bitte beachten dass das Beispiel mit Timer0 realisiert ist. Auf dem läuft auf dem Arduino aber schon millis(). Den sollte man daher nicht verwenden)

Man kann TOP für jeden Timer beliebig einstellen, das ermöglicht sehr viele Frequenzen.

Hagi-Hagi:
Theoretisch könnte ich ein eigenes PWM Signal selbst erzeugen ?!?

Ja, genau so würde ich es machen.

Hagi-Hagi:
Allerdings bin ich dann doch sehr stark von meiner Programmdurchkaufzeit abhängig, die mein so erzeugtes Signal beeinflusst.

Das hängt davon ab, wie Du programmierst. 180 Hz ist eine ziemlich niedrige Frequenz. Ca. 5 ms Laufzeit je loop()-Durchgang sind eher gemächlich.

Gruß

Gregor

DrDiettrich:
Man kann TOP für jeden Timer beliebig einstellen, das ermöglicht sehr viele Frequenzen.

Ja, aber mit einem 16 Bit Timer ist man flexibler. Timer2 hat zwar mehr Prescaler Optionen, was nicht schlecht ist. Aber wenn TOP größer ist kann man mit dem Prescaler weiter nach unten gehen, wodurch man näher an der gewünschten Frequenz ist

Guten Morgen,

vorab schonmal vielen Dank für Eure Antworten.

2.) in der Software ist nur 8 Bit PWM implementiert. Wenn man das per Hand machst sind auch 9 und 10 Bit möglich. Damit kommt man auch auf andere Zeiten.

Außerdem gibt es beim Timer1 auch Modus 10 oder 14 wo man den Wert des TOP Registers frei wählen kann. So sind dann bis zu 16 Bit Auflösung möglich! Damit ginge 180 Hz ohne Probleme:
16 MHz / 8 / 180 Hz = 11111

Wie muss ich dabei vorgehen? Wo kann ich mich einlesen? Ist das schwierig?

Was ist ein TOP Register? Wofür stehen die einzelnen Buchstaben?
Manchmal verstehe ich hier nur Bahnhof :confused:

Viele Grüße

Hier kannst du lesen.
Timer 1, ab Seite 149

TOP ist in dem Fall kein Akronym. Top = oben

Ich habe oben zwei Artikel verlinkt. Da wird das mit Timer0 erklärt. Mit Oszi-Bildern und Code. Das muss man nur für Timer1 anpassen. Timer1 ist ein 16 Bit Timer und die Register sind etwas anders, aber das Prinzip ist genau das gleiche

Dazu brauchst du noch das Datenblatt:
https://cdn-shop.adafruit.com/datasheets/ATMEGA328P.pdf

Es gibt auch neuere Datenblätter, da Atmel inzwischen zu Microchip gehört, aber ich finde das etwas besser. z.B. steht die Register-Abkürzung in der Überschrift und nicht nur der vollständige Name.

Die Registerbeschreibung für Timer1 gibt es ab Seite 132

Grundeinstellungen, nicht getestet:

void setup()
{
   pinMode(9, OUTPUT);     //Pin auf Ausgang
   ICR1 = 11110;           //TOP = CPU Freq / Prescaler / gewünschte Frequenz - 1
   TCCR1A = _BV(COM1A1) | _BV(WGM11);   //Kanal A nicht-invertierend, Mode 14
   TCCR1B = _BV(WGM12) | _BV(WGM13) | _BV(CS11);   //Mode 14, Prescaler = 8
 
}

Das -1 wird oft in Tutorials unterschlagen. Ist auch nicht superwichtig da man selten eine extrem genaue PWM Frequenz braucht.

Duty Cycle wird über OCR1A eingestellt. Das ist ein 16 Bit Register, aber der gültige Wert geht von 0 bis <= TOP. Du kannst z.B. einen Prozent Wert von 0-100 auf 0-TOP umsetzen.

Eine weitere Kleinigkeit die man eventuell beachten muss ist dass auch bei OCR1A = 0 ein kurzer Impuls erzeugt wird. Siehe Datenblatt:

The extreme values for the OCR1x Register represents special cases when generating a PWM waveform output in the fast PWM mode. If the OCR1x is set equal to BOTTOM (0x0000) the output will be a narrow spike for each TOP+1 timer clock cycle.

Das ist nicht intuitiv. Die Arduino Software löst das einfach indem sie bei 0 den Pin mit digitalWrite() auf LOW setzt. Dadurch wird PWM automatisch deaktiviert. Das kannst du auch tun. Danach muss man wieder das COM1A1 Bit setzen damit wieder PWM läuft!

Am besten mit einem Oszilloskop überprüfen was da rauskommt, sonst ist es schwierig manche Problem festzustellen

Okay, super!

Vielen Dank für Eure Hilfe!

Dann werde ich mal versuchen, ob ich das hinbekomme :slight_smile:

Hallo,

wichtige Ergänzung. Alle Register des eigens verwendeten Timer vorher nullen/reseten.
Andere Erklärung Arduin Timer0/Timer1 - #5 by Doc_Arduino - Deutsch - Arduino Forum

Doc_Arduino:
wichtige Ergänzung. Alle Register des eigens verwendeten Timer vorher nullen/reseten.

Das Problem sollte man kennen. Aber in diesem Fall kann man wie oben einfach eine Zuweisung machen, da man beide Status Control Register sowieso ändern muss.
Interrupts (TIMSK1) z.B. muss man nicht extra abschalten da diese sowieso deaktiviert sind.

Das Problem hat man eher wenn man irgendwas macht wo man TCCR1A eigentlich nicht braucht und dann nicht bemerkt dass es nicht auf 0 steht

Dass der Pin vielleicht irgendwas komisches beim Umschalten der Modi macht kann man auch verhindern wenn man als letzten Schritt TCCR1A |= _BV(COM1A1) macht. Erst damit schaltet der Timer den Pin hin und her. So wie es oben steht wird erst der Pin aktiviert und sofort danach der Modus umgeschaltet.
Also besser vielleicht so:

void setup()
{
   pinMode(9, OUTPUT);     //Pin auf Ausgang
   ICR1 = 11110;           //TOP = CPU Freq / Prescaler / gewünschte Frequenz - 1
   TCCR1A = _BV(WGM11);  // Mode 14
   TCCR1B = _BV(WGM12) | _BV(WGM13) | _BV(CS11);   //Mode 14, Prescaler = 8
   TCNT1 = 0;
   TCCR1A |= _BV(COM1A1)   //Kanal A nicht-invertierend
}

Erst ab der letzten Zeile sieht man dann was am Ausgang

Die Arduino Software fasst für PWM nur die zwei Status Control Register und die Compare Match Register an. Bei Timer0 kommt noch etwas mehr dazu für millis(). Siehe wiring.c und wiring_analog.c

Eine Frage drängt sich mir noch auf: Zu was brauchst Du ein PWM Signal? Genügt Dir nicht ein Rechtecksignal mit fixem Tastverhältnis? wie zB tone() das liefert?

Grüße Uwe

Ich möchtendamit eine Zumesseinheit ansteuern. Die Zumesseinheit ist Teil einer Regelung. Sprich: Das Tastverhältnis wird nicht konstant bleiben.

Hagi-Hagi:
Ich möchtendamit eine Zumesseinheit ansteuern. Die Zumesseinheit ist Teil einer Regelung. Sprich: Das Tastverhältnis wird nicht konstant bleiben.

Dann wäre Deine Idee von oben (das händisch zu erledigen) doch eigentlich ideal. Die Frequenz ist niedrig genug, um das zu Fuß zu machen. Und wenn Du das Spielen mit endlichem Automat und Klassen drauf hast (oder es lernen möchtest), ist das ein idealer Einstieg, finde ich.

Gruß

Gregor

Hier noch ein anderes Beispiel, das du hier nachlesen kannst:

Mit Timer 2 wird ein PWM-Signal generiert. Das Tastverhältnis (duty cycle) lässt sich im Beispiel mit einem Poti an A0 einstellen.

/* Modulating 180 Hz signal with Timer 2 on D3

   The code below uses Timer 2 to generate
   a 180 Hz pulse using fast PWM mode (mode 15).

   PWM: 180 Hz on Pin D3
  
   It then modulates the duty cycle from 0% to 100% based
   on a figure read from a potentiometer connected to A0.
*/

// Example of modulating a 180 Hz frequency duty cycle
// by reading a potentiometer
// Author: Nick Gammon

const byte POTENTIOMETER = A0;
const byte OUTPUT_PIN    = 3;  // Timer 2 "B" output: OC2B

// Clock frequency divided by prescaler and desired frequency
const long timer2_Setting = F_CPU / 1024L / 180L;


void setup() {
  pinMode (OUTPUT_PIN, OUTPUT);

  // set up Timer 2 - gives us 180 Hz on D3
  TCCR2A = bit (WGM20) | bit (WGM21) | bit (COM2B1);            // fast PWM, clear OC2A on compare
  TCCR2B = bit (WGM22) | bit (CS20) | bit (CS21) | bit (CS22);  // fast PWM, prescaler of 1024
  OCR2A = timer2_Setting;
}


void loop() {
  // OCR2B = ((timer2_Setting + 1) / 2) - 1;  // 50% duty cycle

  // alter Timer 2 duty cycle in accordance with pot reading
  OCR2B = (((long) (analogRead (POTENTIOMETER) + 1) * timer2_Setting) / 1024L) - 1;


  // do other stuff here
}

// http://www.gammon.com.au/forum/?id=11504&reply=7#reply7

Mit Timer 1 ginge das natürlich auch in ähnlicher Weise.

Hallo,
ich habe mir diese Posts (und viele weitere dieses Thema betreffend) mehrmals durchgelesen. Die weiter oben erwähnte Library (Arduino PWM Frequency Library v_05.zip) habe ich auch in meiner IDE installiert. Allerdings verweigert der Compiler seinen Dienst, sobald #include im Sketch verwendet wird: "Exit Status1 Fehler beim compilieren für das Board Arduino/ Genuino Uno". Meine Vermutung: mit der PWM.h stimmt etwas nicht. denn auch aus der Beispielbibliothek funktioniert der Sketch "PWM_lib_example" mit gleicher Fehlermeldung ebenfalls nicht.
Eine wählbare PWM wäre mir wichtig. Was kann ich tun?

Hi

Eigener Thread - gerne auf Diesen verwiesen - mit kompletter Fehlermeldung und komplettem Sketch.
Dafür vll. auch die Warnungen in den Einstellungen der IDE aktivieren.
Schuss ins Blaue: gibt's einfach nicht, <PWM.h> müsste Es wenigstens sein.

MfG

PS: Mit Holger85 geht's Hier weiter.

gregorss:
180 Hz ist eine ziemlich niedrige Frequenz. Ca. 5 ms Laufzeit je loop()-Durchgang sind eher gemächlich.

Gruß

Gregor

Diese Zahlen haben aber erstmal nicht viel miteinander zu tun. Bei 180 Hz PWM muss ja nicht erst nach den 5,5ms etwas passieren, sondern irgendwann innerhalb einer Periode muss auf LOW geschaltet werden. Möchte man z. B. bei 10bit Auflösung das kleinste mögliche Tastverhältnis nutzen, dann muss bereits nach 5 Mikrosekunden wieder ausgeschaltet werden. Dementsprechend muss auch die Loop so schnell sein.

Mahimus:
Diese Zahlen haben aber erstmal nicht viel miteinander zu tun. Bei 180 Hz PWM muss ja nicht erst nach den 5,5ms etwas passieren, sondern irgendwann innerhalb einer Periode muss auf LOW geschaltet werden. Möchte man z. B. bei 10bit Auflösung das kleinste mögliche Tastverhältnis nutzen, dann muss bereits nach 5 Mikrosekunden wieder ausgeschaltet werden. Dementsprechend muss auch die Loop so schnell sein.

Ich kann Deinem Gedankengang nicht so recht folgen.

180 Hz entspricht einer Periodenlänge von etwa 5 ms - das ist wohl unstrittig. Und eine loop()-Laufzeit von 5 ms ist locker erreichbar, auch wenn man eher schlampig programmiert.

Was möchtest Du mir konkret sagen?

Gruß

Gregor