PWM 1HZ PIC18F14K50

Ich versuche also, eine PWM-Frequenz von 1 Hz mit einem Arbeitszyklus von etwa 70% zu erreichen. Das Problem, auf das ich gestoßen bin, war die minimale PWM-Frequenz, die der PIC18F14K50 erreichen kann, die etwa 1,892 Hz beträgt, wenn PR2 = 255 (max.) verwendet wird, FOSC gleich 31 kHz (geändert von 16 MHz) und Prescaler von TIMER2 = 16:

PWM-FREQUENZ = (PR2+1)*4*TOSC*Prescaler

Meine Frage besteht also darin, diese 1 Hz für die PWM-Frequenz mit einem Arbeitszyklus von 70% zu erreichen.

Hinweise: Kann keine Verzögerungen (Funktion) verwenden und die Hardware (PIC) nicht ändern, daher ist die einzige Option über Software.

Unten ist der Code, der verwendet wird, um eine Frequenz von 1,892 Hz zu erreichen:

//Variables 
int DC; // Duty cycle
//
OSCCON=0; // 31kHZ

// Configure PWM Module 1.892 HZ
    OpenTimer2(T2_PS_1_16); // Prescaler 16 
    OpenPWM1(0xFF); //Configure PWM module and initialize 1 HZ ; PR2 =255
    SetDCPWM1(0); // set duty cyle  
    SetOutputPWM1(SINGLE_OUT, PWM_MODE_1); 

void LED(void) { 

    DC=70; // 70%

    SetDCPWM1((int) DC * 10.23); //set the duty cycle 70% in 10bits//
}

EDIT: Um den Kommentaren von euch allen zu folgen, habe ich dies mit TIMER0 und jetzt mit FOSC = 16 MHz versucht:

void highISR(void) {
    // Check if there was an overflow in TIMER0
    if (INTCONbits.TMR0IF == 1) { 
        LATCbits.LATC5=1; // LED ON 


        INTCONbits.TMR0IF = 0; 
        WriteTimer0(Ktimer); 
    }
}

#pragma code HighInterruptVector=0x0008

void HighInterruptVector(void) {
    _asm
            goto highISR
            _endasm
}
#pragma code
 // CONFIG TIMER0 
    OpenTimer0(TIMER_INT_ON & // Use interruptions
            T0_16BIT & // 16 bit timer
            T0_SOURCE_INT & // Use internal clock as source
            T0_EDGE_FALL & // Update on falling edge
            T0_PS_1_128); // 128 prescaler (lowest period possible) 
    // FOSC = 16 MHz ==> FTY = 16/4 = 4MHz ==> -------TCY = 250 ns
    // Timer 0 prescaler = 128 ==> Count on every--------- 250 ns * 128 = 32 us
    // 1 seg counting = --------1s / 32u = 31250
    //31250*0,7 =21875 70% DC

    Ktimer = 65536 - (21875);
    //WriteTimer0(Ktimer); 


// So the flag occurs 
While(1){

WriteTimer0(Ktimer)
LATCbits.LATC5=0; // LED OFF
}

Aber LED geht nicht an. 100% sicher, dass der Fehler in der Konstruktion des Codes liegt.

Haben Sie stattdessen versucht, einen Interrupt zu verwenden?
Für etwas so langsames wie 1 Hz würde ich einfach einen Timer-Interrupt verwenden und die "PWM" bit-bangen.
Ich stimme den anderen zu. Ich persönlich bevorzuge einen Timer-Interrupt-Ansatz, wenn die MCU keinen PWM-Ausgang hat. Stellen Sie beispielsweise den Timer alle 1 ms ein und erhöhen Sie unter Interrupt-Routine ein Register / eine Variable, bis der gewünschte Wert erreicht ist, und setzen oder löschen Sie dann das entsprechende Bit des Ausgangsregisters.
Im Originalbeitrag bearbeitet.

Antworten (5)

Die letzte Antwort für alle, die sich fragen, hier ist der Code, der für mich funktioniert hat. Es ist einfach, weil ich wirklich keinen sehr spezifischen Code oder Genauigkeit für die Arbeit benötige, die ich mache. Ich möchte Ihnen allen danken, die kommentiert und mir geholfen haben, die richtigen Ideen zu bekommen.

#include <p18f14k50.h>
#include <stdlib.h>
#include <delays.h>
#include <timers.h>


int Ktimer; // variable for overflow 
int time1=10; // Period 1HZ 1*0.1 from Ktimer = 1 second 
int time2=7; // Duty Cycle
//--------------------------- End of Variables ------------------------------------
//********************************************************************************* 

void PWM_LED(void){


    if (time2 > 0)
        LATAbits.LATA5=0; // LED ON 
    else
        LATAbits.LATA5=1; // LED OFF
}
#pragma interruptlow highISR

void highISR(void) {
    // Check if there was overflow by the timer 
    if (INTCONbits.TMR0IF == 1) { 

        time1--;
        time2--;

       INTCONbits.TMR0IF = 0; // Put Flag at zero again 
       WriteTimer0(Ktimer);


    }
}

#pragma code HighInterruptVector=0x0008

void HighInterruptVector(void) {
    _asm
            goto highISR
            _endasm
}
#pragma code


//********************************************************************************* 
//-------------------------------------- Main -------------------------------------
//********************************************************************************* 
void main (void)
{

  //****************************Other congigurations********************************* 
  OSCCON=0x70;        // Select 16 MHz internal clock   

  //************************************Setups*************************************** 

    // Interrupts
    INTCONbits.GIEH = 1; // Enable all high priority interrupts (also required for LOW priority)

    INTCONbits.TMR0IF = 0; //TMR0 Overflow Interrupt Flag bit (must be cleared by software) AO rebentar activa a flag

    INTCONbits.TMR0IE = 1; //Enable TMR0 Overflow Interrupt Enable bit
    INTCON2bits.TMR0IP = 1; //Set TMR0 Overflow Interrupt Priority bit: 1 = High priority

    // Configure Timer0  0,1 seconds  
    OpenTimer0(TIMER_INT_ON & // Use interruptions
            T0_16BIT & // 16 bit timer
            T0_SOURCE_INT & // Use internal clock as source
            T0_EDGE_FALL & // Update on falling edge
            T0_PS_1_128); // 128 prescaler (lowest period possible) 
    // FOSC = 16 MHz ==> FTY = 16/4 = 4MHz ==> -------TCY = 250 ns
    // Timer 0 prescaler = 128 ==> Count on every--------- 250 ns * 128 = 32 us
    // 0.1 seg counting = --------0.1s / 32u = 3125
    // 2^16- 3125 = 62411;
    //Ktimer = 62411;  0.1 seconds
    Ktimer = 65536 - (3125);
    WriteTimer0(Ktimer); // Timer0 will overflow in 100ms


  Delay10TCYx( 5 );             // Delay for 50TCY


  //********************************************************************************* 
  //-------------------------------------- Main cycle -------------------------------
  while (1)
  {

    PWM_LED();

    if(time1 <= 0){ //reset
    time1=10; 
    time2=7; 
    }

    Delay10KTCYx(10);   

  }
  //-------------------------------------- End of Main Cycle ------------------------
  //********************************************************************************* 

  /* Close Peripherals */

  CloseTimer0();
}  

Sie können eine präzise PWM bei niedrigen Frequenzen erhalten, indem Sie das CCP-Modul verwenden, um den Ausgangspin umzuschalten (möglicherweise nach einer festgelegten Anzahl von Vergleichstreffern, die Interrupts erzeugen).

Jedes Mal, wenn der CCP-Vergleich eine Übereinstimmung ergibt, richten Sie ihn für den nächsten Vergleich ein und konfigurieren ihn so, dass der Ausgangspin umgedreht wird (oder nicht).

Auf diese Weise erhalten Sie über beliebig lange Zeiträume Nanosekunden-Präzision (nur begrenzt durch die Genauigkeit und den Jitter der Uhr).

Wenn Sie sich nicht so sehr um Jitter in der Ausgabe kümmern, können Sie alles mit einem festen Interrupt machen - zum Beispiel einen 250-usec-Interrupt einstellen und die PWM in der ISR (Interrupt Service Routine) ausführen. Das würde Ihnen eine Auflösung von 0,025 % bei 1 Hz geben. Das ist etwas einfacher und hat den Vorteil, dass Sie jeden beliebigen Pin für die Ausgabe verwenden können.

Im Originalbeitrag bearbeitet. Ich versuche, die ISR zu machen.

Für etwas so langsames wie 1 Hz können Sie die PWM einfach genug in der Firmware ausführen. Gehen Sie zurück zum Takt mit voller Geschwindigkeit und richten Sie einen periodischen 1-ms-Interrupt (Frequenz 1 kHz) ein. Mit Ihrem ursprünglichen 16-MHz-Oszillator wäre die Befehlsrate 4 MHz, also wären 4000 Befehlszyklen pro Interrupt vorhanden. Das ist viel im Vergleich zu dem, was der Interrupt tun muss.

Jetzt haben Sie Code, der alle 1/1000 Ihrer PWM-Periode ausgeführt wird. Die Logik für PWM ist von hier aus ziemlich einfach. Sie behalten einen Zähler für die PWM-Periode. Sie starten es bei 999 und dekrementieren es alle 1 ms Zeitscheibe. Wenn es negativ wird, setzen Sie es stattdessen auf 999 zurück.

In diesem Fall setzen Sie auch einen anderen Zähler auf das gewünschte Tastverhältnis zurück. In Ihrem Fall ist das 700. Setzen Sie den Ausgang immer dann auf High, wenn dieser Zähler größer als 0 ist. Dekrementieren Sie dann den Zähler, außer dass Sie ihn auf 0 belassen, wenn er bereits 0 ist.

Was ich oben beschrieben habe, sollte höchstens ein paar 10s Zyklen dauern. Selbst wenn es 50 Zyklen gedauert hat (viel länger als es sollte), sind das nur 1¼ % der CPU. Sie könnten den Interrupt leicht schneller ausführen, z. B. mit 4 kHz für eine höhere Auflösung, aber es scheint, dass Sie nicht einmal die 10 Bit Auflösung benötigen, die Sie mit 1 kHz erhalten.

Werde diese Methode ausprobieren. Werde bald Kontakt aufnehmen.

Ihre Logik ist nicht so klar, versuchen Sie so etwas in der Verwendung!

Löschen Sie das Flag, wenn dies nicht durch die Hardware erfolgt ist. If (DC toggle++ & 0x01) TxCNT = - DC Led on Else TxCNT = -_DC Led off End

DC ist der Zeitraum, in dem die LED eingeschaltet ist, und _dc ist der Zeitraum, in dem die LED ausgeschaltet ist.

TXCNT ist der Zähler, der hier als Aufwärtszähler angenommen wird. Berechnen Sie DC und _DC im Voraus, damit Sie die Komplimente dort nicht annehmen müssen.

Um Ihnen einen Eindruck davon zu vermitteln, wie dies bewerkstelligt werden kann, ist hier ein Beispiel für den abwechselnden Betrieb von TIMER0 zwischen DC und PR-DC:

    //isr
void interrupt isr(void) {
    static uint8_t toggle=0;                //toggle. 1=DC, 0=PR-DC

    //tmr0 isr
    TMR0IF = 0;                             //clear the flag
    if (++spwm_cnt==0) {                    //counter underflow
        if (toggle++ & 0x01) {              //01->DC
            sPWM_ON();                      //turn on the spwm
            spwm_cnt = -spwm_dc;            //load the offset
        } else {
            sPWM_OFF();                     //DC
            spwm_cnt = -spwm_pr;            //load the offset for the off period
        }
    }
}   

Hier ist die Ausgabe, die es auf einem 12f675 unter Verwendung der folgenden Hauptschleife erzeugt:

int main(void) {

    mcu_init();                             //initialize the mcu
    spwm_init();                            //reset the module
    //spwm_setdc(sPWM_PR * 1 / 4);          //set dc to 1/4
    //spwm_setdc(sPWM_PR * 2 / 4);          //set dc to 2/4
    spwm_setdc(sPWM_PR *3 / 4);             //set dc to 3/4
    ei();
    while (1) {
    }
}

Ich habe es mit 75% DC, 1Hz PWM betrieben.

Geben Sie hier die Bildbeschreibung ein

Was es tut, ist, die Ausführung in den ISR zu isolieren und den Benutzer davon zu befreien, der PWM-Generierung in der Hauptschleife Aufmerksamkeit zu schenken. Wenn Sie den Arbeitszyklus ändern müssen, brauchen Sie nur nach Bedarf spwm_setdc() aufzurufen.