1
\$\begingroup\$

So, I'm trying to achieve a 1HZ PWM frequency with a Duty Cycle of about 70%. The problem that I encountered was with the minimum PWM frequency that the PIC18F14K50 can reach, which is about 1.892HZ, using PR2=255(max),FOSC equals to 31kHZ(changed from 16MHZ) and prescaler of TIMER2=16:

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

So my question resides in a manner to achieve that 1HZ for the PWM Frequency with a Duty Cycle of 70%.

Notes: Can't use delays(function) and can't change the hardware(PIC), so the only option is via Software.

Bellow is the code used to achieve a frequency of 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//
}

EDIT: Trying to follow the comments of all of you I tried this with TIMER0 and now with 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
}

But led wont turn on. 100% certain that the fault is in how the code is constructed.

\$\endgroup\$
  • 1
    \$\begingroup\$ Have you tried using an interrupt instead? \$\endgroup\$ – Ignacio Vazquez-Abrams Jan 19 '17 at 15:11
  • 1
    \$\begingroup\$ For something as slow as 1Hz, I'd just use a timer interrupt and bit-bang the "PWM". \$\endgroup\$ – brhans Jan 19 '17 at 15:19
  • 1
    \$\begingroup\$ I agree with the others. I personally go for a timer-interrupt approach when the MCU does not have any PWM output. For example, setup the timer for every 1ms and under interrupt routine, increment a register/variable until the desired value then set or clear the corresponding bit of the output register. \$\endgroup\$ – Rohat Kılıç Jan 19 '17 at 15:53
  • \$\begingroup\$ Edited in original post. \$\endgroup\$ – Lip Jan 19 '17 at 16:31
2
\$\begingroup\$

Final answer to anyone wondering here's the code that worked for me. It's simple , because I really don't need a very specific code or precision for the work I'm doing. Wanna thank you all that commented and helped me get the right ideas.

#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();
}  
| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

You can get precise PWM at low frequencies by using the CCP module to toggle the output pin (perhaps after a set number of compare 'hit's generating interrupts).

Each time the CCP comparison is a match you set it up for the next comparison and configure it to flip the output pin (or not).

That way you get nanosecond precision over arbitrarily long times (limited only by the clock accuracy and jitter).

If you don't care that much about jitter in the output you can do everything with a fixed interrupt- for example, set a 250usec interrupt and do the PWM in the ISR (interrupt service routine). That would give you 0.025% resolution at 1Hz. That's somewhat simpler, and has the advantage that you can use any pin you like for the output.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Edited in original post. Trying to do the ISR. \$\endgroup\$ – Lip Jan 19 '17 at 16:31
1
\$\begingroup\$

For something as slow as 1 Hz, you can do the PWM easily enough in firmware. Go back to the full speed clock and set up a periodic 1 ms (frequency 1 kHz) interrupt. With your original 16 MHz oscillator, the instruction rate would be 4 MHz, so there would be 4000 instruction cycles per interrupt. That's a lot compared to what the interrupt needs to do.

Now you have code that gets run every 1/1000 of your PWM period. The logic to do PWM is pretty easy from here. You keep one counter for the PWM period. You start it at 999 and decrement it each 1 ms time slice. When it goes negative, you reset it to 999 instead.

When that happens, you also reset another counter to the desired duty cycle. In your case that is 700. Set the output high whenever this counter is greater than 0. Then decrement the counter, except that you leave it at 0 if already 0.

What I described above should only take a few 10s of cycles at most. Even if it took 50 cycles (way longer than it should), that's only 1¼% of the CPU. You could easily run the interrupt faster, like at 4 kHz for higher resolution, but it seems you don't even need the 10 bits of resolution that 1 kHz gets you.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Will try this method. Will contact soon. \$\endgroup\$ – Lip Jan 19 '17 at 17:00
0
\$\begingroup\$

Your logic isn't that clear try something like this in the use!

Clear the flag if not done by the hardware. If (DC toggle++ & 0x01) TxCNT = - DC Led on Else TxCNT = -_DC Led off End

DC is the period where the led is turned on, and _dc is the period where the led is turned off.

TXCNT is the counter, assumed to be an up counter here. An precalculate DC and _DC so you don't have to take the compliments there.

| improve this answer | |
\$\endgroup\$
0
\$\begingroup\$

to give you a sense of how this can be done, here is an example of running TIMER0 alternately between DC and 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
        }
    }
}   

Here is the output it produces on a 12f675, using the main loop below:

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

I was running it at 75% dc, 1Hz pwm.

enter image description here

what it does is to isolate the execution into the isr and relieve the user of having to pay any attention to the pwm generation in the main loop. If you need to change the duty cycle, all you needed to do is to call spwm_setdc() on an as needed basis.

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.