PWM 1HZ PIC18F14K50

Entonces, estoy tratando de lograr una frecuencia PWM de 1HZ con un ciclo de trabajo de aproximadamente el 70%. El problema que encontré fue con la frecuencia PWM mínima que puede alcanzar el PIC18F14K50, que es de aproximadamente 1.892 HZ, usando PR2 = 255 (máx.), FOSC es igual a 31 kHz (cambiado de 16 MHz) y preescalador de TIMER2 = 16:

FRECUENCIA PWM= (PR2+1)*4*TOSC*prescaler

Entonces mi pregunta radica en cómo lograr ese 1HZ para la frecuencia PWM con un ciclo de trabajo del 70%.

Notas: No se pueden usar retrasos (función) y no se puede cambiar el hardware (PIC), por lo que la única opción es a través del software.

A continuación se muestra el código utilizado para lograr una frecuencia de 1.892HZ:

//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//
}

EDITAR: tratando de seguir los comentarios de todos ustedes, probé esto con TIMER0 y ahora con FOSC = 16MHZ:

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
}

Pero el led no enciende. 100% seguro de que la falla está en cómo se construye el código.

¿Has intentado usar una interrupción en su lugar?
Para algo tan lento como 1 Hz, solo usaría una interrupción del temporizador y golpearía el "PWM".
Estoy de acuerdo con los demás. Personalmente, opto por un enfoque de interrupción del temporizador cuando la MCU no tiene ninguna salida PWM. Por ejemplo, configure el temporizador para cada 1 ms y bajo la rutina de interrupción, incremente un registro/variable hasta el valor deseado y luego configure o borre el bit correspondiente del registro de salida.
Editado en la publicación original.

Respuestas (5)

La respuesta final para cualquiera que se pregunte aquí está el código que funcionó para mí. Es simple, porque realmente no necesito un código o precisión muy específico para el trabajo que estoy haciendo. Quiero agradecerles a todos los que comentaron y me ayudaron a obtener las ideas correctas.

#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();
}  

Puede obtener PWM preciso a bajas frecuencias utilizando el módulo CCP para alternar el pin de salida (quizás después de un número determinado de interrupciones de generación de "golpes" de comparación).

Cada vez que la comparación de CCP coincide, la configura para la siguiente comparación y la configura para cambiar el pin de salida (o no).

De esa manera, obtiene una precisión de nanosegundos durante tiempos arbitrariamente largos (limitados solo por la precisión del reloj y la fluctuación).

Si no le importa mucho el jitter en la salida, puede hacer todo con una interrupción fija; por ejemplo, configure una interrupción de 250 useg y haga el PWM en la ISR (rutina de servicio de interrupción). Eso le daría una resolución de 0.025% a 1Hz. Eso es algo más simple y tiene la ventaja de que puede usar cualquier pin que desee para la salida.

Editado en la publicación original. Tratando de hacer el ISR.

Para algo tan lento como 1 Hz, puede hacer el PWM con bastante facilidad en el firmware. Regrese al reloj de velocidad máxima y configure una interrupción periódica de 1 ms (frecuencia de 1 kHz). Con su oscilador original de 16 MHz, la velocidad de instrucción sería de 4 MHz, por lo que habría 4000 ciclos de instrucción por interrupción. Eso es mucho en comparación con lo que debe hacer la interrupción.

Ahora tiene un código que se ejecuta cada 1/1000 de su período PWM. La lógica para hacer PWM es bastante fácil desde aquí. Mantiene un contador para el período PWM. Lo inicia en 999 y lo disminuye cada intervalo de tiempo de 1 ms. Cuando se vuelve negativo, lo restablece a 999 en su lugar.

Cuando eso sucede, también reinicia otro contador al ciclo de trabajo deseado. En su caso, eso es 700. Establezca la salida alta siempre que este contador sea mayor que 0. Luego disminuya el contador, excepto que lo deja en 0 si ya está en 0.

Lo que describí anteriormente solo debería tomar unas decenas de ciclos como máximo. Incluso si tomó 50 ciclos (mucho más de lo que debería), eso es solo el 1¼% de la CPU. Fácilmente podría ejecutar la interrupción más rápido, como a 4 kHz para una resolución más alta, pero parece que ni siquiera necesita los 10 bits de resolución que obtiene con 1 kHz.

Probaré este método. Se contactará pronto.

¡Tu lógica no es tan clara, prueba algo como esto en el uso!

Borre la bandera si no lo hace el hardware. If (DC toggle++ & 0x01) TxCNT = - DC Led encendido Else TxCNT = -_DC Led apagado Fin

DC es el período en el que se enciende el led y _dc es el período en el que se apaga el led.

TXCNT es el contador, que aquí se supone que es un contador ascendente. Un precalculo de DC y _DC para que no tenga que aceptar los elogios allí.

para darle una idea de cómo se puede hacer esto, aquí hay un ejemplo de la ejecución de TIMER0 alternativamente entre DC y 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
        }
    }
}   

Aquí está la salida que produce en un 12f675, usando el bucle principal a continuación:

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) {
    }
}

Lo estaba ejecutando al 75% de CC, 1 Hz pwm.

ingrese la descripción de la imagen aquí

lo que hace es aislar la ejecución en el isr y liberar al usuario de tener que prestar atención a la generación de pwm en el bucle principal. Si necesita cambiar el ciclo de trabajo, todo lo que debe hacer es llamar a spwm_setdc() según sea necesario.