Ausgabe von 2 PWM-Wellenformen mit 90-Grad-Phasenverschiebung

Bisher konnte ich mit dem folgenden Code zwei Ausgänge mit der richtigen Auflösung (35 kHz-75 kHz bei einer Auflösung von nicht schlechter als 0,7 kHz) erhalten. Ich frage mich jetzt, wie ich eine Phasenverschiebung zwischen den beiden PWM-Ausgängen erreichen kann (die beide den 16-Bit-Timer1 und verwenden ICR1).

Ich habe versucht, die Zeile TCNT1 += 1/freq/4;zwischen der letzten und vorletzten Codezeile ( OCR1A=...und OCR1B =...) zu schreiben, aber das hat nichts gebracht.

   //set port B to output 
   DDRB |= 0xFF; 

   // wgm mode 1110 (fast pwm w/ TOP = ICR1
  TCCR1A |= 1<<WGM11 ; 
  TCCR1B |= 1<<WGM12 | 1<<WGM13; 

   //set OC1/A and OC1B on compare match w/ ICR1 , clear them at bottom
   TCCR1A |=  1<<COM1A1 | 1<<COM1A0| ; 
   TCCR1A |=  1<<COM1B1 | 1<<COM1B0 ; 

   //pre-scaler = 1
   TCCR1B |=  1<<CS10; 


   ICR1 = 16000000/freq; // input compare value = (clock freq) / (desired freq)

   // 50% duty cycle on OCR1A/B
   OCR1A  = ICR1/2;
   //TCNT1 += 1/freq/4; //this line did not do anything
   OCR1B = ICR1/2;
Möchten Sie nur, dass beide Wellenformen immer 50 % Tastverhältnis haben und Sie die Frequenz und die Phasenverschiebung anpassen können?
Ich simuliere die Ausgabe eines Quadratur-Encoders. Ich muss also in der Lage sein, die Frequenz zu variieren, aber das Tastverhältnis beträgt immer 50 % und die Phasenverschiebung beträgt immer plus oder minus 90 Grad, obwohl sich die Verzögerung dafür zusammen mit der Frequenz ändert. Siehe Antwort 2 unten für meinen aktuellen Status.
Der TCNT ist ein Zähler, der von der Hardware aktualisiert wird, sodass alle Änderungen, die Sie daran vornehmen, den Wert in dem Moment zurücksetzen, in dem Sie ihn ändern. Schauen Sie sich meine Antwort unten an, um zu erfahren, wie Sie die beiden Ausgänge zu unterschiedlichen Zeiten im TCNT-Zyklus auslösen können.

Antworten (4)

Wenn Ihre Anwendung nur Wellenformen mit einem Arbeitszyklus von 50 % erfordert, können Sie die Umschalt-Vergleichs-Ausgangsmodi verwenden, um ein Signalpaar mit einstellbarer Phasenverschiebung zwischen ihnen zu erzeugen.

Die Toggle-Modi schalten ihren jeweiligen Ausgang jedes Mal um, wenn es eine Vergleichsübereinstimmung gibt, so dass Sie die Phasenbeziehung ändern, indem Sie die 2 Ausgangsvergleichsregister relativ zueinander anpassen. Sie stellen die Frequenz beider Signale gemeinsam ein, indem Sie den TOP für den Zähler ändern.

Sinn ergeben?

Hier ist ein Demo-Code für ein Arduino Uno. Es gibt 50-kHz-Rechteckwellen an den Arduino-Pins 9 und 10 aus und durchläuft Phasenverschiebungen von 0, 90 und 180 Grad – mit jeweils einer Pause von einer Sekunde.

// This code demonstrates how to generate two output signals
// with variable phase shift between them using an AVR Timer 

// The output shows up on Arduino pin 9, 10

// More AVR Timer Tricks at http://josh.com

void setup() {
  pinMode( 9 , OUTPUT );    // Arduino Pin  9 = OCR1A
  pinMode( 10 , OUTPUT );   // Arduino Pin 10 = OCR1B

  // Both outputs in toggle mode  
  TCCR1A = _BV( COM1A0 ) |_BV( COM1B0 );


  // CTC Waveform Generation Mode
  // TOP=ICR1  
  // Note clock is left off for now

  TCCR1B = _BV( WGM13) | _BV( WGM12);

  OCR1A = 0;    // First output is the base, it always toggles at 0


}

// prescaler of 1 will get us 8MHz - 488Hz
// User a higher prescaler for lower freqncies

#define PRESCALER 1
#define PRESCALER_BITS 0x01

#define CLK 16000000UL    // Default clock speed is 16MHz on Arduino Uno

// Output phase shifted wave forms on Arduino Pins 9 & 10
// freq = freqnecy in Hertz (  122 < freq <8000000 )
// shift = phase shift in degrees ( 0 <= shift < 180 )

// Do do shifts 180-360 degrees, you could invert the OCR1B by doing an extra toggle using FOC

/// Note phase shifts will be rounded down to the next neared possible value so the higher the frequency, the less phase shift resolution you get. At 8Mhz, you can only have 0 or 180 degrees because there are only 2 clock ticks per cycle.  

int setWaveforms( unsigned long freq , int shift ) {

  // This assumes prescaler = 1. For lower freqnecies, use a larger prescaler.

  unsigned long clocks_per_toggle = (CLK / freq) / 2;    // /2 becuase it takes 2 toggles to make a full wave

  ICR1 = clocks_per_toggle;

  unsigned long offset_clocks = (clocks_per_toggle * shift) / 180UL; // Do mult first to save precision

  OCR1B= offset_clocks;

  // Turn on timer now if is was not already on
  // Clock source = clkio/1 (no prescaling)
  // Note: you could use a prescaller here for lower freqnencies
  TCCR1B |= _BV( CS10 ); 

}

// Demo by cycling through some phase shifts at 50Khz  

void loop() {

  setWaveforms( 50000 , 0 );

  delay(1000); 

  setWaveforms( 50000 , 90 );

  delay(1000); 

  setWaveforms( 50000 , 180 );

  delay(1000); 


}

Hier sind einige Oszilloskopspuren der 0-, 90- bzw. 180-Grad-Verschiebungen ...

0 Grad Verschiebung

90 Grad Verschiebung

180 Grad Verschiebung

Wow vielen Dank! Das funktioniert gut, bis auf eine Sache – manchmal endet die Welle, die um 90 Grad führen sollte, tatsächlich hinterher. Ich kann sehen, wie es auf dem Oszilloskop umkippt, und ich bin mir nicht sicher, warum es passiert - es scheint zufällig zu sein, und manchmal dreht es sich tatsächlich wieder auf und korrigiert sich selbst. Könnte es sein, dass Taktzählungen im Vergleichsregister übersprungen werden? Danke noch einmal!
Ich nehme an, Sie sehen den Flip, wenn Sie aktiv die Parameter ändern? Wenn ja, ist dies wahrscheinlich ein Timing-Problem. Da es im CTC-Modus keine doppelte Pufferung der OCR-Register gibt, müssen Sie sehr vorsichtig sein, wenn Sie Aktualisierungen vornehmen. Es ist möglich, dass Sie einen Vergleich verpassen, was bedeuten würde, dass Sie einen Toggle verpassen, was die Welle auf den Kopf stellen würde, bis Sie das nächste Mal einen Toggle verpassen.
Wie Sie dieses Problem am besten vermeiden, hängt stark von Ihren spezifischen Anwendungsanforderungen ab. Eine Möglichkeit besteht darin, den Timer jedes Mal neu zu starten, wenn Sie die Phase oder Frequenz ändern. Dies ist super einfach und führt beim Laufen immer zu einer korrekten Wellenformerzeugung, verursacht jedoch Störungen in dem Moment, in dem Sie umschalten. Würde so etwas für Ihre Anwendung funktionieren?
Soll ich zu diesem Thema eine neue Frage stellen? Ich denke, der Code würde es besser veranschaulichen. Aber im Grunde lasse ich eine Eingangsrechteckwelle die Frequenz bestimmen, und die Ausgänge müssen dieser Frequenz entsprechen. Ich verwende pulseIn(), um die Periode der Eingabe zu bestimmen, sie in Frequenz umzuwandeln und sie dann der Funktion setWaveforms zuzuführen. Bei hohen Frequenzen ist pulseIn mit der Auflösung etwas minderwertig (eine mögliche Lösung für mein Problem wäre, dies zu verbessern, denke ich), also glätte ich es, indem ich jeweils 100 Samples mittele.
Dann füttere ich dies kontinuierlich in die Funktion setWaveforms ein. Ich nehme an, ich könnte warten, bis es ein paar hundert Mal iteriert, und erst dann die Wellenform drehen - also beginnt es nicht bei 8 MHz und geht jedes Mal dorthin, wo es sein muss ... Ich gebe das eigentlich ein Schuss. Update: Das hat tatsächlich wie ein Zauber funktioniert, nochmals vielen Dank!
Haben Sie Vorschläge, wie ich die Frequenz eines Rechteckwelleneingangs im Bereich von 35 K bis 75 K Hz messen könnte? Ich untersuche Interrupts, aber laut dieser Webseite ( gammon.com.au/interrupts ) dauert ein externer Interrupt mindestens 5 Mikrosekunden. In einer 75-kHz-Rechteckwelle liegen zwischen zwei aufeinanderfolgenden fallenden oder steigenden Flanken nur 13 Mikrosekunden. Wie soll ich das angehen?
Die AVR-Timer haben eine eingebaute Funktion namens Input Capture, die dafür ausgelegt ist, solche Dinge genau zu messen. Besuchen Sie atmel.com/Images/doc8365.pdf und lassen Sie mich wissen, wenn Sie Fragen haben!
Glaubst du, du könntest dir diesen neuen Beitrag ansehen, wenn du die Gelegenheit dazu hast? electronic.stackexchange.com/questions/175549/…
Ich stoße auf eines von zwei Problemen: Entweder kann ich nicht hoch genug auflösen (wovon ich weiß, dass es kein Problem mit dem ICR sein sollte) oder die Zustandsmaschine bleibt hängen und gibt nichts Nützliches zurück. Der Link im obigen Kommentar hat meinen aktuellen Status.
Ist es möglich, dass die Funktion setWaveforms einen Wert zurückgibt, damit die Diagramme im seriellen Plotter gezeichnet werden können?
Sie könnten einfach den Wert des Pins mit überprüfen uint8_t v = digitalRead();und diesen Wert dann mit etwas wie Serial.writeln(v);in Ihrer loop().

Sie können keine Phasenverschiebung zwischen mehreren Signalen im PWM-Modus mit einem einzigen Timer haben. Jede Schicht muss auf einem separaten Timer sein, und Sie müssen jeden Zähler um den entsprechenden Betrag versetzen.

Es gibt keine Möglichkeit, eine Verzögerung bei der Initialisierung oder ähnlichem hinzuzufügen? Das Problem ist, dass die anderen 2 Timer kein Eingangserfassungsregister haben, sodass ich die Frequenz nicht so einfach einstellen kann – und sie haben auch keine so genaue Auflösung.
Wenn nicht, sollte ich einen anderen Mikrocontroller verwenden? Vielleicht könnten Sie etwas vorschlagen ... Ich habe mir den dsPIC30F4011 / 4012 angesehen und die PWM der Motorsteuerung scheint den Trick zu machen (nämlich PWM mit zwei Ausgängen mit variabler Frequenz und 90-Grad-Phasenverschiebung), aber ich bin mir nicht sicher wenn das übertrieben ist. Vielleicht ein AtTiny?
Wenn Sie die relevanten Teile des Datenblatts zur Funktionsweise des Timers lesen, sollte es offensichtlich sein, warum Sie dies nicht tun können - der Timer setzt oder löscht die entsprechenden Pins, wenn der Timer überläuft, sodass alle Ausgänge des Timers dies haben Rand gemeinsam. Welcher AVR ist das? Die meisten von ihnen haben >1 Timer, der einstellbare TOP-Werte unterstützt.
Entschuldigung, hätte das enthalten sollen: ATMEL328P
Für alle drei Timer auf dem ATMega328P können TOP-Werte konfiguriert werden, indem in ihre jeweiligen OCRxA-Register geschrieben wird.
Beachten Sie jedoch, dass die Arduino-Bibliotheken Timer 0 für ihre Timing-Routinen verwenden ( delay()et alia).
Richtig, ich habe gesehen, dass die Timing-Routinen durcheinander geraten, wenn ich mit Timer 0 herumspiele. Können Sie vielleicht erklären, was der Unterschied zwischen der Verwendung von ICR und OCR ist, um den Höchstwert des Timers einzustellen? Ich denke, das könnte es sein, was mich aus der Fassung bringt. Ich weiß, dass sie ihre jeweiligen Flaggen beim Match setzen, aber ich bin mir nicht sicher, was danach passiert und wie sich das auf die tatsächliche PWM auswirkt.
Nur OCRxn kann Pin-Änderungen auslösen.
Ist ICR1 nicht der Wert des Zählers, bei dem der Pin im obigen Code hoch geht?
Weil es TOP ist, ja. Aber es ist die Tatsache, dass es für diesen Modus TOP ist, nicht weil es ICRn ist.
Sie sagen also, OCRxn kann Pins auslösen, unabhängig davon, ob es TOP ist, verstanden. Was ist in diesem Fall der Sinn von ICRn? Ist es buchstäblich nur da, um die Zeit zu markieren, zu der Ereignisse passieren? Was ist der Vorteil bei der Verwendung von ICRn als TOP anstelle von OCRxn?
Es gibt OCRxn frei, um Pin-Änderungen an einem beliebigen Punkt in der Zählung auszulösen.
Vielen Dank für deine Hilfe. Ich versuche jetzt, die Timer 0 und 2 zum Laufen zu bringen, aber ich glaube, mir fehlt etwas; Hier ist mein aktueller Code: // wgm mode 101 (phasenkorrigiertes pwm mit TOP = OCRA) TCCR0A |= 1<<WGM00 ; TCCR0B |= 1<<WGM02; // COM0B1 setzen (nicht invertierender Modus) TCCR0A | = 1<<COM0B1 ; //Prescaler = 1 TCCR0B |= 1<<CS00; // beliebige Frequenz OCR0A = 55; DDRD = 0xFF;
Macht nichts - verstanden! Nochmals vielen Dank für Ihre Hilfe, Sie sind unglaublich nett, Antworten an Noobs zu posten.

Dieser Code ist immer noch nicht ganz korrekt – die Phasenverschiebung wird mit abnehmender Frequenz immer mehr durcheinander gebracht, und ich muss auch einen Prescaler für alles unter 62 kHz hinzufügen – aber bei 75 kHz funktioniert es!

Ich denke, jetzt ist die Frage nicht "Wie bekomme ich eine Verzögerung zwischen den beiden PWMs?" sondern "Wie bekomme ich die Verzögerung mit der Frequenz zu skalieren?"

int main ()   
{
  //************************* Timer 0 *********************************

    // wgm mode 111 (fast pwm w/ TOP = OCRA)
TCCR0A |=  1<<WGM00 | 1<<WGM01; 
TCCR0B |= 1<<WGM02; 

    //set COM0x1 (non-inverting mode)
    TCCR0A |=  1<<COM0A1 ; 
    TCCR0A |=  1<<COM0B1 ; 

    //pre-scaler = 1
    TCCR0B |=  1<<CS00; 




    // arbitrary frequency
OCR0A  = 220; //counts until TCNT = OCR0A then resets
    OCR0B = OCR0A/2; //on until TCNT = OCR0B then off

     // turn on pin D5
    DDRD |= 1<<PIND5;

   TCNT2 += OCR0A/4 ; //add a 90 degree delay to TCNT2... or something like that

//**************************** Timer 2 ****************************************
    // wgm mode 111 (phase-corrected pwm w/ TOP = OCRA)
TCCR2A |=  1<<WGM20 | 1<<WGM21; 
TCCR2B |= 1<<WGM22; 

    //set COM0x1 (non-inverting mode)
    TCCR2A |=  1<<COM2A1 ; 
    TCCR2A |=  1<<COM2B1 ; 

    //pre-scaler = 1
    TCCR2B |=  1<<CS20; 




    // same frequency as above pwm on timer 0
OCR2A  = OCR0A; 
    OCR2B = OCR2A/2;



     // turn on pin D3
    DDRD |= 1<<PIND3;
    }
Haben Sie versucht, die Verzögerung jedes Mal zurückzusetzen, wenn Sie die Frequenz einstellen?
Jedes Mal, wenn ich die Frequenz geändert habe (OCR0A = 220), musste ich die Datei erneut auf den Arduino hochladen. Wenn Sie das also meinen, dann ja.

Runtime Micro zeigt Ihnen, wie Sie Single-Timer-phasenverschiebbare Rechteckwellen (z. B. 50% Einschaltdauer) haben. Ein 16-Bit-Timer (Nano, Uno, 2560) verwendet einen Nicht-PWM-Modus, bietet aber eine echte Phasenverschiebungsfunktion. Frequenzänderungen wirken sich nicht auf die von Ihnen eingestellte Phasenbeziehung aus.

Der Code dafür ist unter folgendem Link enthalten...

https://runtimemicro.com/Forums/RTM_TimerCalc/Examples-and-Tips/Arduino-Timer-Phase-Shifted-Square-Waves

Beachten Sie, dass der 16-Bit-Timer-Wellenformgenerator einen Toggle-Modus verwendet - daher ist die Ausgangsfrequenz 1/2 der Zyklusfrequenz des Timers.

Wir raten von Antworten ab, die stark auf einen Link zu einer anderen Website angewiesen sind. Wenn sich diese Site neu organisiert oder auf 404 geht, wird die Antwort wertlos. Bitte versuchen Sie, die wichtigen Informationen von der anderen Seite zusammenzufassen (nicht zu kopieren) und in Ihre Antwort aufzunehmen.
Lassen Sie mich zusammenfassen. Wenn phasenverschobene Rechteckwellen für das OP akzeptabel sind, werden Phase-Flip-Probleme unter Verwendung von FOC-Taktiken (Forced Output Compare) gemildert. Dies ermöglicht eine stabile Laufzeit einstellbare Phasenverschiebung. Diese Informationen sind jetzt zusammen mit dem Build-Diagramm, dem Beispielcode und dem Video zur Bereichserfassung in dem zuvor erwähnten TimerCalc-Artikel enthalten. Auch IMO ist phasenverschobenes PWM mit einem Mega2560 praktisch, der (sagen wir) drei 16-Bit-Timer verwendet, die zu unterschiedlichen Zeiten auf derselben Systemuhr gestartet werden. [Bearbeitet von einem Moderator.]
Ein 4. Timer läuft mit der Hälfte der PWM-Timer-Zyklusfrequenz, um das erforderliche 120-Grad-Timing zu erreichen. Interrupts aktivieren dann Timer an OCR-definierten Punkten. Da der 2560 mit 16 MHz läuft, kann jeder Anlaufversatz in den ISRs durch Trimmen der OCR-Werte minimiert werden. Der mega2560 verfügt über 4 16-Bit-Timer. [Bearbeitet von einem Moderator.]