Ich versuche, die PWM-Ausgangsfrequenz mit einem dsPIC33FJ256GP710 ungefähr einmal pro Millisekunde zu ändern, und ich habe gemischte Ergebnisse. Ich habe das zuerst probiert:
#include <p33fxxxx.h>
_FOSCSEL(FNOSC_PRIPLL);
_FOSC(FCKSM_CSDCMD & OSCIOFNC_OFF & POSCMD_XT);
_FWDT(FWDTEN_OFF);
static unsigned int PWM_TABLE[7][2] =
{
{132, 66}, {131, 66}, {130, 65}, {129, 65}, {128, 64}, {127, 64}, {126, 63} // Compare, 50% duty
};
static int curFreq = 0;
int main(void)
{
int i;
PLLFBD = 0x009E; // Set processor clock to 32 MHz (16 MIPS)
CLKDIV = 0x0048;
LATCbits.LATC1 = 0; // Make RC1 an output for a debug pin
TRISCbits.TRISC1 = 0;
LATDbits.LATD6 = 0; // Make RD6/OC7 an output (the PWM pin)
TRISDbits.TRISD6 = 0;
T2CONbits.TON = 0; // Disable Timer 2
OC7CONbits.OCM = 0b000; // Turn PWM mode off
PR2 = PWM_TABLE[curFreq][0]; // Set PWM period
OC7RS = PWM_TABLE[curFreq][1]; // Set PWM duty cycle
OC7CONbits.OCM = 0b110; // Turn PWM mode on
T2CONbits.TON = 1; // Enable Timer 2
while (1)
{
for (i = 0; i < 3200; i++) {} // Delay roughly 1 ms
curFreq = (curFreq + 1) % 7; // Bump to next frequency
PR2 = PWM_TABLE[curFreq][0]; // Set PWM period
OC7RS = PWM_TABLE[curFreq][1]; // Set PWM duty cycle
LATCbits.LATC1 = !LATCbits.LATC1; // Toggle debug pin so we know what's happening
}
}
Das Ergebnis ist, dass PWM für etwa 4 ms in einem scheinbar wiederholbaren Intervall ausfällt, das grob auf meinen Debug-Pin-Umschalter ausgerichtet ist (mit anderen Worten, wenn der Code mit den Perioden- und Arbeitszyklusregistern herumspielt). Ich füge ein Foto meiner Zielfernrohrspur bei. Kanal 1 ist PWM und Kanal 2 ist der Debug-Pin, der umgeschaltet wird, wenn der Code versucht, die Frequenz anzupassen.
Wie auch immer, ich fing an, über Timer-Rollovers nachzudenken, und ich habe in einigen Foren gesucht. Ich bin auf ein paar Ideen gekommen, basierend auf ein paar Beiträgen, die ich gelesen habe. Die beste Idee schien zu sein, den Timer 2-Interrupt zu aktivieren, den PWM-Modus darin auszuschalten und nur die Perioden- und Arbeitszyklusregister innerhalb des Timer 2-Interrupts zu ändern. Also, ich habe das geschrieben:
#include <p33fxxxx.h>
_FOSCSEL(FNOSC_PRIPLL);
_FOSC(FCKSM_CSDCMD & OSCIOFNC_OFF & POSCMD_XT);
_FWDT(FWDTEN_OFF);
static int curFreq = 0;
static unsigned int PWM_TABLE[7][2] =
{
{132, 66}, {131, 66}, {130, 65}, {129, 65}, {128, 64}, {127, 64}, {126, 63} // Compare, duty
};
int main(void)
{
int i, ipl;
PLLFBD = 0x009E; // Set processor clock to 32 MHz (16 MIPS)
CLKDIV = 0x0048;
LATCbits.LATC1 = 0; // Make RC1 an output for a debug pin
TRISCbits.TRISC1 = 0;
LATDbits.LATD6 = 0; // Make RD6/OC7 an output (the PWM pin)
TRISDbits.TRISD6 = 0;
OC7CONbits.OCM = 0b000; // Turn PWM mode off
OC7RS = PWM_TABLE[curFreq][1]; // Set PWM duty cycle
PR2 = PWM_TABLE[curFreq][0]; // Set PWM period
OC7CONbits.OCM = 0b110; // Turn PWM mode on
T2CONbits.TON = 0; // Disable Timer 2
TMR2 = 0; // Clear Timer 2 register
IPC1bits.T2IP = 1; // Set the Timer 2 interrupt priority level
IFS0bits.T2IF = 0; // Clear the Timer 2 interrupt flag
IEC0bits.T2IE = 1; // Enable the Timer 2 interrupt
T2CONbits.TON = 1; // Enable Timer 2
while (1)
{
for (i = 0; i < 1600; i++) {} // Delay roughly 1 ms
SET_AND_SAVE_CPU_IPL(ipl, 2); // Lock out the Timer 2 interrupt
curFreq = (curFreq + 1) % 7; // Bump to next frequency
RESTORE_CPU_IPL(ipl); // Allow the Timer 2 interrupt
LATCbits.LATC1 = !LATCbits.LATC1; // Toggle debug pin so we know what's happening
}
}
void __attribute__((__interrupt__)) _T2Interrupt(void)
{
T2CONbits.TON = 0; // Disable Timer 2
TMR2 = 0; // Clear Timer 2 register
OC7CONbits.OCM = 0b000; // Turn PWM mode off
OC7RS = PWM_TABLE[curFreq][1]; // Set the new PWM duty cycle
PR2 = PWM_TABLE[curFreq][0]; // Set the new PWM period
OC7CONbits.OCM = 0b110; // Turn PWM mode on
IFS0bits.T2IF = 0; // Clear the Timer 2 interrupt flag
T2CONbits.TON = 1; // Enable Timer 2
}
Dies scheint stabiler zu sein, soweit ich das auf meinem alten Oszilloskop beurteilen kann, aber jetzt ist die Wellenform nicht mehr regelmäßig geformt (der Arbeitszyklus scheint unerklärlicherweise inkonsistent zu sein), und wenn ich mich nur genug anstrenge, kann ich mich davon überzeugen, dass ich es immer noch sehen Sie eine Millisekunde PWM-Ausfall, wenn mein Oszilloskop auf eine Zeitbasis von 5 oder 10 Millisekunden eingestellt ist.
Es ist sicherlich besser als es war, und ich kann weiter damit herumspielen, in der Hoffnung, die unregelmäßige Wellenform zu beheben, die durch das zweite Codebit erzeugt wird, aber meine Frage ist:
Gibt es einen "richtigen" Weg, dies zu tun? Oder zumindest einen besseren Weg als den Weg, auf dem ich mich befinde?
Jede Hilfe wäre gründlich, gründlich geschätzt.
Scope-Trace http://www.freeimagehosting.net/uploads/c132216a28.jpg
Für alle, die es interessiert, hier ist die Lösung, zu der ich heute gekommen bin:
#include <p33fxxxx.h>
_FOSCSEL(FNOSC_PRIPLL);
_FOSC(FCKSM_CSDCMD & OSCIOFNC_OFF & POSCMD_XT);
_FWDT(FWDTEN_OFF);
static int curFreq = 0;
static int nextFreq = 0;
static unsigned int PWM_TABLE[7][2] =
{
{132, 66}, {131, 66}, {130, 65}, {129, 65}, {128, 64}, {127, 64}, {126, 63} // Compare, duty
};
int main(void)
{
int i, ipl;
PLLFBD = 0x009E; // Set processor clock to 32 MHz (16 MIPS)
CLKDIV = 0x0048;
LATCbits.LATC1 = 0; // Make RC1 an output for a debug pin
TRISCbits.TRISC1 = 0;
OC7CONbits.OCM = 0b000; // Turn PWM mode off
OC7RS = PWM_TABLE[curFreq][1]; // Set PWM duty cycle
PR2 = PWM_TABLE[curFreq][0]; // Set PWM period
OC7CONbits.OCM = 0b110; // Turn PWM mode on
T2CONbits.TON = 0; // Disable Timer 2
TMR2 = 0; // Clear Timer 2 register
IPC1bits.T2IP = 1; // Set the Timer 2 interrupt priority level
IFS0bits.T2IF = 0; // Clear the Timer 2 interrupt flag
IEC0bits.T2IE = 1; // Enable the Timer 2 interrupt
T2CONbits.TON = 1; // Enable Timer 2
while (1)
{
for (i = 0; i < 1600; i++) {} // Delay roughly 1 ms
SET_AND_SAVE_CPU_IPL(ipl, 2); // Lock out the Timer 2 interrupt
curFreq = (curFreq + 1) % 7; // Bump to next frequency
nextFreq = 1; // Signal frequency change to ISR
RESTORE_CPU_IPL(ipl); // Allow the Timer 2 interrupt
}
}
void __attribute__((__interrupt__)) _T2Interrupt(void)
{
IFS0bits.T2IF = 0; // Clear the Timer 2 interrupt flag
if (nextFreq)
{
nextFreq = 0; // Clear the frequency hop flag
OC7RS = PWM_TABLE[curFreq][1]; // Set the new PWM duty cycle
PR2 = PWM_TABLE[curFreq][0]; // Set the new PWM period
}
}
Mit dem Scope und einem Debug-Pin bestätigte ich meine Vermutung: Der ursprüngliche Code litt unter einer Race Condition. Die Hauptschleife hat sich nicht die Mühe gemacht, Änderungen an PR2 mit dem tatsächlichen Zustand des TMR2-Zählers zu synchronisieren, und würde daher gelegentlich PR2 auf einen Wert setzen, der KLEINER (oder vielleicht gleich) dem aktuellen TMR2-Wert ist. Dies wiederum würde dazu führen, dass TMR2 aufwärts zählt, bis es überrollt, und dann weiterzählt, bis es PR2 erreicht und eine steigende Flanke erzeugt. Während der Zeit, in der TMR2 bis 65535 hochzählte, um zu überrollen, wurde kein PWM-Ausgang erzeugt. Bei 16 MIPS beträgt die Rollover-Zeit für einen 16-Bit-Timer wie TMR2 ungefähr 4 ms, was meinen PWM-Ausfall von 4 ms erklärt. Der Code tat also genau das, wofür ich ihn geschrieben hatte :)
Im zweiten Ausschnitt synchronisiert der Code Änderungen an PR2 und dem Arbeitszyklusregister korrekt mit dem TMR2-Rollover-Ereignis, sodass der 4-ms-Ausfall verschwunden war. Ich erwähnte eine "seltsame" Wellenform, die mit diesem Beispiel verbunden ist: Es lag daran, dass der RD6/OC7-Pin als Ausgang konfiguriert war und einen niedrigen Wert im LATD-Register hatte. Das zweite Snippet schaltet den PWM-Modus innerhalb des Timer 2 ISR tatsächlich aus: Dadurch übernimmt die GPIO-Funktionalität und zieht RD6/OC7 für einige Mikrosekunden nach unten, bevor PWM wieder aktiviert und eine steigende Flanke erzeugt wird, was zu einer „Schluckauf“-Wellenform führt.
Das zweite Snippet hat auch ein Problem, da es PR2 und das Arbeitszyklusregister bei jedem Rollover von Timer 2 neu konfiguriert, unabhängig davon, ob die Hauptschleife eine Frequenzänderung befohlen hat oder nicht. Aus der Beobachtung scheint mir, dass der Timer überläuft und eine steigende Flanke am PWM-Pin erzeugt und DANN der Timer 2 ISR einige Nanosekunden später die Kontrolle erhält (aufgrund von Vektorlatenz usw. bin ich mir sicher). Wenn Sie PWM ausschalten und die Register jedes Mal neu justieren, erhalten Sie auf lange Sicht nicht die richtige Frequenz und das richtige Tastverhältnis, da die Hardware bereits eine steigende Flanke erzeugt und damit begonnen hat, bis zum nächsten Vergleichswert zu zählen.
Das bedeutet, dass in dem korrigierten Snippet, den ich heute gepostet habe, die in der Timer 2 ISR geleistete Arbeit minimiert werden muss! Da ich PWM mit einer so hohen Frequenz betreibe und zwischen der von der PWM-Hardware erzeugten steigenden Flanke und dem Aufruf des Timer 2 ISR eine kleine Latenz besteht, hatte TMR2 bereits Zeit, als ich in den ISR einstieg bis zu einer fairen Zahl zählen. Mein Code muss PR2 und das Duty-Cycle-Register sofort und direkt setzen (dh keine Funktionsaufrufe, und sogar die Tabellensuche drückt es), sonst läuft er Gefahr, den Vergleich zu verpassen und den 4-ms-Rollover-Fehler zu verursachen, der mein Original war Problem.
Jedenfalls denke ich, dass dies eine genaue Beschreibung der Dinge ist, und ich führe den Code in meiner "echten" Anwendung mit bisher ermutigenden Ergebnissen aus. Wenn sich sonst noch etwas ändert, werde ich es hier posten, und natürlich wären Korrekturen an den oben genannten Punkten sehr willkommen.
Danke für deine Hilfe, pingswept.
Ich würde versuchen, alles um den Faktor 10 zu verlangsamen, damit Sie genauer sehen können, wann genau die PWM stirbt. Ich würde auch versuchen, die Werte in Ihrer Tabelle für Zeitraum und Arbeitszyklus zu optimieren. Vielleicht konfigurieren Sie die PWM in 4 Ihrer 7 Zyklen falsch? Ich bemerke, dass es 4 PWM-Periodenwerte gibt, die über 128 liegen - vielleicht verursacht das Probleme?
Wenn das nicht hilft, würde ich nach einem größeren Muster suchen. Wie oft wiederholt sich die Lücke von 4 ms?
Sehr gut gestellte Frage übrigens.
Ich bin mir bei der PWM in einem dsPIC nicht sicher, aber bei einem PIC16F1508 sagt die Dokumentation, dass der TMR2, wenn er einen Neuladezyklus erreicht, das TMR2-Register neu lädt und dann die PWMxDC-Register in die tatsächlichen Tastverhältnisregister in der PWM-Hardware lädt. dh das Schreiben des PWM-Tastverhältnisses wird automatisch mit dem TMR2-Reload synchronisiert. Wenn Sie also das Tastverhältnis schreiben, wirkt es sich erst beim nächsten Tastverhältnis aus. Dies bedeutet, dass Ihre Änderung des Arbeitszyklus um bis zu einen TMR2-Nachladezyklus verzögert werden könnte, aber das ist die einzige Änderung, die Sie sehen sollten. Schauen Sie in Ihre Dokumentation.
Ping gefegt
unauslöschlich