This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

Generation of PWM using Extrenal Interrupts and Timers

Hi everybody,

I'm a beginner trying to generate a PWM wave on a C8051F330 micro controller using C programming language. I have many doubts regarding this exercise. It would be really helpful if someone clarifies my doubtswhich will eventually get me going.

Can anybody please explain me roughly the flow of the following program...??

// Include Files //
// Global constants //
// Function Prototypes //
// Main Routine //

void main (void)
{ PCA0MD &= ~0x40; Oscillator_Init (); Port_Init (); External_Interrupt_Init (); Timer2_Init (); PCA_Init (); EA = 1; while (1);
}

// Oscillator Initialisation //
void Oscillator_Init (void)
{ //do something//
}

//Port Initialisation //
void Port_Init (void)
{ //do something//
}

//External Interrupt Initialisation //
void External_Interupt_Init (void)
{ //do something//
EX0 = 1;
}

//Timer2 Initialisation //
void Timer2_Init (void)
{ TMR2CN=0x00;
TMR2RL= RELOAD_VALUE;
TMR2 = 0xFFFF; // Set to reload immediately //
}

//PCA Initialisation //
void PCA_Init (void)
{ //do something//
}

//Interrupt Service Routine (ISR) //
INTERRUPT(INT0_ISR, INTERRUPT_INT0)
{ TR2 = 1;
}

This program is in C. I'm here trying to understand the program flow. I know that the "main()" is the first function that gets executed here. And then the line "PCA0MD &= ~0x40" followed by Osillator_Init function. The program control actually goes into the called Oscillator_Init function (below main()) and executes it,.and so on...

In case of External_Interrupt_Init(),I have enabled /INT0 interrupt using "EX0=1". Where does the program flow control go from here...??

And in ISR where does the program control go after executing TR2 = 1,.

Please folks help me find answers to my doubts.

Parents
  • Thanks Christian,. The Timer I use here is Timer2 in its 16 bit auto reload state. I have coded in such a way that when Timer overflows I put a new value in the PCA,..But in ISR when I start Timer2 which should eventually causes Timer overflow and for this I want to understand where does the program control go after I start Timer 2 in ISR..?? This is my doubt:(

Reply
  • Thanks Christian,. The Timer I use here is Timer2 in its 16 bit auto reload state. I have coded in such a way that when Timer overflows I put a new value in the PCA,..But in ISR when I start Timer2 which should eventually causes Timer overflow and for this I want to understand where does the program control go after I start Timer 2 in ISR..?? This is my doubt:(

Children
  • Well - the only reason to have individual initialization functions - and to have just initialization in them - is basically a question of maintenance.

    Having many small initialization functions makes it easy to enable/disable individual peripherials when trying to debug specific issues.

    And separate initialization code from action code means that all the initialization can be run first, before the program enters the main loop. Then the action code can do its thing based on whatever stimuli the program sees - keypresses, timers interrupting, ...

    Interrupts aren't magical. There are some minor differences between processor architectures. But you normally have nested interrupts or not. With nested interrupts, a higher-prio interrupt source may activate while the processor is already servicing a lower-prio interrupt source. Interrupt nesting is like a stack. When the high-prio interrupt ends, the processor drops back to the less prioritized interrupt and continues with the next instruction. When that interrupt handler, the program returns to the main loop code.

    If the processor doesn't support interrupt nesting, or the code doesn't enable interrupt nesting, then the first interrupt service routine (whatever prio it may have) must end, before a new interrupt service routine can get activated. Often, the processor manages to do maybe one instruction from the main loop before activating next pending interrupt service handler.

    Enabling interrupts doesn't mean an interrupt will happen. It only means that interrupts are globally allowed, or that interrupts from a specific peripherial is specifically allowed. There still needs to be an event of some kind for that peripherial to actually rise the interrupt service flag asking for an interrupt to happen. Such as the UART receiving a character. Sometimes the interrupt comes directly - like enabling UART transmit interrupts and instantly get a "I'm hungry - feed me" transmit interrupt.

    Anyway - the route to learn a new processor is to test one peripherial at a time. And only combine multiple peripherials as the individual modules have been tested. Easy to do when there is a separate function to initialize. A separate interrupt service handler. Individual "action" functions for interacting - like checking if data have been received from the UART or adding outgoing data to a send queue.

    While learning a new peripherial, it's also possible to test much of the code in polling mode directly from the main loop before upgrading the code to use interrupt handlers, separating the timeline of the peripherial device from the timeline of the main program flow.

    Anyway - in the end it's impossible to help people except when they are stuck with explicit and well-defined problems.

  • The program don't care about you starting a timer.
    The program flow will just continue while the timer does what it should do in the background.

    It isn't until the timer reaches specific states (like counter overflow or whatever the specific timer supports and have been configured for) that the timer hardware may try to request an interrupt.

    So it doesn't matter much if you start ticking the timer while in main loop or inside an interrupt service routine. The action only happens when the timer have done the ticking and wants to go BOOOM :)

  • Thank you very much for your reply. It was indeed helpful. As I told you before I'm trying to generate PWM signal on C8051F330 micro controller using External Interrupts and Timers. I want the External Interrupt to start a Timer. When the Timer overflows I want to put in a new value in the PCA register for PWM. I have developed the following code. I'm stuck with the code in the ISR. I use a 16 bit Timer 2 with auto-reload capability. I'm monitoring the Timer2 overflow flag in the ISR which is not correct,. Can you please suggest me what has to be done in this case?
    //-----------------------------------------------------------------------------
    // Include Files
    //-----------------------------------------------------------------------------

    #include <compiler_defs.h> // Macro definitions for 8051 Compiler //
    #include <C8051F336_defs.h> // SFR declarations //

    //-----------------------------------------------------------------------------
    // Global Constants
    //-----------------------------------------------------------------------------

    #define SYSCLK 24500000/12 // Clock speed in Hz //

    #define TIMER_TICKS_PER_MILLI_SECOND SYSCLK/100/ 1000

    #define RELOAD_VALUE -TIMER_TICKS_PER_MILLI_SECOND

    SBIT (SW2, SFR_P0, 7); // SW1==0 means switch depressed

    //-----------------------------------------------------------------------------
    // Function Prototypes
    //-----------------------------------------------------------------------------

    void Oscillator_Init (void); // Configure the system clock //
    void Port_Init (void); // Configure the Crossbar and GPIO //
    void Ext_Interrupt_Init (void); // Configure External Interrupts (INT0) //
    void Timer2_Init (int counts); // Configure Timer2 //
    void PCA_Init (void); // Configure PCA for PWM mode //

    INTERRUPT_PROTO (INT0_ISR, INTERRUPT_INT0);

    //-----------------------------------------------------------------------------
    // MAIN Routine
    //-----------------------------------------------------------------------------
    void main (void)
    { PCA0MD &= ~0x40; // Disable Watchdog timer //

    Oscillator_Init(); // Initialize the system clock // Port_Init (); // Initialize crossbar and GPIO // Ext_Interrupt_Init(); // Initialize External Interrupts // Timer2_Init(SYSCLK/100000); // Initialize Timer2 to generate interrupts at a 20 Hz rate// PCA_Init (); // Initialize PCA for PWM //

    EA = 1;

    while(1); // Infinite while loop waiting for an interrupt //
    }

    //-----------------------------------------------------------------------------
    // Oscillator_Init
    //-----------------------------------------------------------------------------
    // This routine initializes the system clock to use the precision internal
    // oscillator as its clock source.
    //-----------------------------------------------------------------------------
    void Oscillator_Init (void)
    { CLKSEL = 0x00; // Clock Select. SYSCLK is derived from the Internal High Frequency Oscillator and scaled per IFCN bits in register OSCICN // OSCICN = 0x83; // Internal H-F Oscillator enabled and SYSCLK derived from Internal H-F Oscillator divided by 1 //
    }

    //-----------------------------------------------------------------------------
    // Port_Init
    //-----------------------------------------------------------------------------

    void Port_Init (void)
    { XBR1 = 0x40; // Enable crossbar and weak pull ups // P0SKIP = 0x7F; // Skip unused pins on P0 and P1 // P1SKIP = 0xFF;

    }

    //-----------------------------------------------------------------------------
    // Ext_Interrupt_Init
    //-----------------------------------------------------------------------------

    void Ext_Interrupt_Init (void)
    { TCON = 0x01; // INT 0 is edge triggered // IT01CF = 0x07; // INT0 active low; INT0 on P0.7 // EX0 = 1; // Enable INT0 interrupts //
    }

    //-----------------------------------------------------------------------------
    // Timer2_Init
    //-----------------------------------------------------------------------------

    void Timer2_Init (int counts)
    { TMR2CN = 0x00; // Stop Timer2. Clear T2SPLIT. // Use SYSCLK/12 as timebase, 16-bit auto-reload // CKCON &= 0x30; // Timer2 High and Low byte uses the SYSCLK // TMR2RL = -counts; // Initialize reload value TMR2 = 0xFFFF; // Set to reload immediately

    }

    //-----------------------------------------------------------------------------
    // PCA_Init
    //-----------------------------------------------------------------------------

    void PCA_Init(void)
    {

    PCA0MD = 0x00; PCA0CPM0 = 0xC7; // PWM 16, ECOM, TOG, PWM, ECCF bits set //

    }

    //-----------------------------------------------------------------------------
    // Interrupt Service Routines
    //-----------------------------------------------------------------------------

    INTERRUPT(INT0_ISR, INTERRUPT_INT0)
    {

    TR2 = 1; // Start Timer2 // while (TF2H ==1) { for ( RELOAD_VALUE = 0; RELOAD_VALUE <65535; RELOAD_VALUE++ ) { PCA0CPH0 = RELOAD_VALUE; } continue; }

    }

  • If you check just above the message input box, there is information how to post source code. That would make the post look like:

    if (condition) {
        fix_problem();
    }
    

    Note that you should use two interrupt handlers.
    One to catch the external interrupt, i.e. to trig the start of your PWM generation.
    Then a second interrupt handler to catch the timer events.

    So your main loop can do whatever it likes. Possibly compute new reload values for the PWM output, in case you want to generate a sine wave or something else.

    The only question is - if the external interrupt starts the PWM generation, it will continue forever unless you also add a mechanic to stop it. Maybe let every second external interrupt start or stop PWM output. But that requires that you make sure the signal is debounced so you don't get lots of on/off interrupts from just a single button press.

  • Thanks again for the reply. Okay I will make sure I post source code in that format from next time. Sorry for this time. Will an ISR for Timer 2 get executed without Enabling Timer 2 Interrupt (IT2 = 1;)...?? I know I'm monitoring its Interrupt pending flag (TF2H) but...?

  • You need to enable any interrupt if you want the processor to be allowed to generate any interrupts for that interrupt source.

    If no interrupts are enabled, then all you can do is spend time polling. But that isn't as fun. And generating PWM output means that you want to constantly figure out what next value to use so you can generate a the wanted curve shape. Unless your goal is just to just have a fixed value to generate a DC voltage (after proper filtering of the produced pulse train output).

  • But again the question is, Can I have two interrupts in the same code...??

  • Do you reckon the following...??

    //-----------------------------------------------------------------------------
    // Include Files
    //-----------------------------------------------------------------------------

    #include <compiler_defs.h> // Macro definitions for 8051 Compiler //
    #include <C8051F336_defs.h> // SFR declarations //

    //-----------------------------------------------------------------------------
    // Global Constants
    //-----------------------------------------------------------------------------

    #define SYSCLK 24500000/12 // Clock speed in Hz //

    #define TIMER_TICKS_PER_MILLI_SECOND SYSCLK/100/ 1000

    #define RELOAD_VALUE -TIMER_TICKS_PER_MILLI_SECOND

    SBIT (SW2, SFR_P0, 7); // SW2==0 means switch depressed

    //-----------------------------------------------------------------------------
    // Function Prototypes
    //-----------------------------------------------------------------------------

    void Oscillator_Init (void); // Configure the system clock //
    void Port_Init (void); // Configure the Crossbar and GPIO //
    void Ext_Interrupt_Init (void); // Configure External Interrupts (INT0) //
    void Timer2_Init (int counts // Configure Timer2 //
    void PCA_Init (void); // Configure PCA for PWM mode //

    INTERRUPT_PROTO (INT0_ISR, INTERRUPT_INT0);
    INTERRUPT_PROTO (TIMER2_ISR, INTERRUPT_TIMER2);

    //-----------------------------------------------------------------------------
    // MAIN Routine
    //-----------------------------------------------------------------------------

    void main (void)
    {
       PCA0MD &= ~0x40;                                 // Disable Watchdog timer //
    
       Oscillator_Init();                           // Initialize the system clock //
       Port_Init ();                                        // Initialize crossbar and GPIO //
       Ext_Interrupt_Init();                                // Initialize External Interrupts //
       Timer2_Init(SYSCLK/100000)// Initialize Timer2 to generate interrupts at a 20 Hz rate//
       PCA_Init ();                                 // Initialize PCA for PWM //
    
       EA = 1;
    
       while(1);                            // Infinite while loop waiting for an interrupt //
    }
    
    


    //-----------------------------------------------------------------------------
    // Oscillator_Init
    //-----------------------------------------------------------------------------
    // This routine initializes the system clock to use the precision internal
    // oscillator as its clock source.
    //-----------------------------------------------------------------------------

    void Oscillator_Init (void)
    {
            CLKSEL = 0x00;          // Clock Select. SYSCLK is derived from the Internal     High Frequency Oscillator and scaled per IFCN bits in register OSCICN //
            OSCICN = 0x83;            // Internal H-F Oscillator enabled and SYSCLK derived from Internal H-F Oscillator divided by 1 //
    }
    

    //-----------------------------------------------------------------------------
    // Port_Init
    //-----------------------------------------------------------------------------

    void Port_Init (void)
    {
    
       XBR1      = 0x40;                            // Enable crossbar and weak pull ups //
       P0SKIP    = 0x7F;                    // Skip unused pins on P0 and P1 //
       P1SKIP    = 0xFF;
    
    }
    

    //-----------------------------------------------------------------------------
    // Ext_Interrupt_Init
    //-----------------------------------------------------------------------------

    void Ext_Interrupt_Init (void)
    {
    
       TCON   = 0x01;                      // INT 0 is edge triggered //
       IT01CF = 0x07;                      // INT0 active low; INT0 on P0.7 //
       EX0 = 1;                            // Enable INT0 interrupts //
    
    }
    
    


    //-----------------------------------------------------------------------------
    // Timer2_Init
    //-----------------------------------------------------------------------------

    void Timer2_Init (int counts)
    {
    
       TMR2CN  = 0x00;                     // Stop Timer2. Clear T2SPLIT.
                                    // Use SYSCLK/12 as timebase, 16-bit auto-reload
      // CKCON  &= 0x30;                   // Timer2 High and Low byte uses the SYSCLK //
       TMR2RL  = -counts;              // Initialize reload value
       TMR2    = 0xFFFF;                  // Set to reload immediately
    
    }
    
    

    //-----------------------------------------------------------------------------
    // PCA_Init
    //-----------------------------------------------------------------------------

    void PCA_Init(void)
    {
    
            PCA0MD    = 0x00;
            PCA0CPM0  = 0xC7;               // PWM 16, ECOM, TOG, PWM, ECCF bits set //
    
    }
    


    //-----------------------------------------------------------------------------
    // Interrupt Service Routines
    //-----------------------------------------------------------------------------

    INTERRUPT(INT0_ISR, INTERRUPT_INT0)
    {
    
             TR2  = 1;            // Start Timer2 //
             ET2 = 1;
    
    }
    

    INTERRUPT(Timer2_ISR, INTERRUPT_TIMER2)
    {
    
            while (TF2H ==1)
            {
             for (RELOAD_VALUE = -TIMER_TICKS_PER_MILLI_SECOND ; RELOAD_VALUE <65535; RELOAD_VALUE++)
                            {
                                    PCA0CPH0  = RELOAD_VALUE;
                      }
             continue;
            }
    
    }
    

  • Why did you break up your code into many blocks? Why not just a single pre-block for everything?

    Of course you have have multiple interrupt service routines in your code.

    But you shouldn't have such a busy loop - don't you want your main loop to run?

    You can have the timer generate an interrupt every time it needs servicing. So the processor will jump to the timer ISR multiple times. And in-between, you can do something else.

    An ISR should always be optimized to quickly end. So careful with loops. And try to avoid calling functions from inside the ISR - at least as long as you program a 8051 chip.

    Let main loop fill a ring buffer with reload values to use.
    Let the timer ISR pick up next reload value.

  • Thanks for your reply.

    "But you shouldn't have such a busy loop - don't you want your main loop to run?" - If I let the main () loop to run then it would mean another switch/button hit isn't it...?? I want a continuous PWM signal generated until I hit the reset on the MCU or stop debug session.

    "And try to avoid calling functions from inside the ISR - at least as long as you program a 8051 chip." - Have I implemented this in my code..?? If so please copy and paste in the next post please...

  • the basics (which includes most questions I have seen in this thread) about the '51 are best described in "the bible" In my opinion nobody should set out working with the '51 without studying "the bible" first. www.8052.com/.../120112

    Erik

  • You do realize that your timer can generate an interrupt and then continue to tick?
    So your timer ISR figures out a new reload value for the timer.
    And the timer generates the next interrupt.
    And your timer ISR figures out a new reload value.
    ...

    So the timer hardware + timer ISR can function with zero involvement of your main loop.

    It's just a question _if_ you want your main loop to decide what curve shape to emit or not. But you don't need to involve the main loop if you don't want to.

    No, you haven't called functions from your timer ISR - I just informed you that when you add some intelligence in that ISR you should avoid making function calls. Just as you should not have any wild loops inside the ISR. The ISR should end, while letting the timer continue to do what the timer is designed to do. Until the timer cries for service the next time.

  • Thanks for your reply. Is there any other way of implementing this without having to need a Interrupt service routine for Timer servicing. Can I do the same within the main (),.. as the following...??

    //-----------------------------------------------------------------------------
    // Include Files
    //-----------------------------------------------------------------------------
    
    #include <compiler_defs.h> // Macro definitions for 8051 Compiler //
    #include <C8051F336_defs.h> // SFR declarations //
    
    //-----------------------------------------------------------------------------
    // Global Constants
    //-----------------------------------------------------------------------------
    
    #define SYSCLK 24500000/12 // Clock speed in Hz //
    
    #define TIMER_TICKS_PER_MILLI_SECOND SYSCLK/100/ 1000
    
    #define RELOAD_VALUE -TIMER_TICKS_PER_MILLI_SECOND
    
    SBIT (SW2, SFR_P0, 7); // SW2==0 means switch depressed
    
    //-----------------------------------------------------------------------------
    // Function Prototypes
    //-----------------------------------------------------------------------------
    
    void Oscillator_Init (void); // Configure the system clock //
    void Port_Init (void); // Configure the Crossbar and GPIO //
    void Ext_Interrupt_Init (void); // Configure External Interrupts (INT0) //
    void Timer2_Init (int counts // Configure Timer2 //
    void PCA_Init (void); // Configure PCA for PWM mode //
    
    INTERRUPT_PROTO (INT0_ISR, INTERRUPT_INT0);
    INTERRUPT_PROTO (TIMER2_ISR, INTERRUPT_TIMER2);
    
    //-----------------------------------------------------------------------------
    // MAIN Routine
    //-----------------------------------------------------------------------------
    void main (void)
    {
       PCA0MD &= ~0x40;                                 // Disable Watchdog timer //
    
       Oscillator_Init();                           // Initialize the system clock //
       Port_Init ();                                        // Initialize crossbar and GPIO //
       Ext_Interrupt_Init();                                // Initialize External Interrupts //
       Timer2_Init(SYSCLK/100000)// Initialize Timer2 to generate interrupts at a 20 Hz rate//
       PCA_Init ();                                 // Initialize PCA for PWM //
    
       EA = 1;
    
       while(1)                            // Infinite while loop waiting for an interrupt //
    {
       if (TF2H ==1)
         {
          RELOAD_VALUE = 0;
          while(RELOAD_VALUE < 65535)
             {
               PCA0CPH0  = RELOAD_VALUE;
               TF2H = 0;
               RELOAD_VALUE++;
             }
          }
    
    }
    
    }
    
    
    
     //-----------------------------------------------------------------------------
    // Oscillator_Init
    //-----------------------------------------------------------------------------
    // This routine initializes the system clock to use the precision internal
    // oscillator as its clock source.
    //-----------------------------------------------------------------------------
    void Oscillator_Init (void)
    {
            CLKSEL = 0x00;          // Clock Select. SYSCLK is derived from the Internal     High Frequency Oscillator and scaled per IFCN bits in register OSCICN //
            OSCICN = 0x83;            // Internal H-F Oscillator enabled and SYSCLK derived from Internal H-F Oscillator divided by 1 //
    }
    
     //-----------------------------------------------------------------------------
    // Port_Init
    //-----------------------------------------------------------------------------
    void Port_Init (void)
    {
    
       XBR1      = 0x40;                            // Enable crossbar and weak pull ups //
       P0SKIP    = 0x7F;                    // Skip unused pins on P0 and P1 //
       P1SKIP    = 0xFF;
    
    }
    
     //-----------------------------------------------------------------------------
    // Ext_Interrupt_Init
    //-----------------------------------------------------------------------------
    void Ext_Interrupt_Init (void)
    {
    
       TCON   = 0x01;                      // INT 0 is edge triggered //
       IT01CF = 0x07;                      // INT0 active low; INT0 on P0.7 //
       EX0 = 1;                            // Enable INT0 interrupts //
    
    }
    
    
    
     //-----------------------------------------------------------------------------
    // Timer2_Init
    //-----------------------------------------------------------------------------
    void Timer2_Init (int counts)
    {
    
       TMR2CN  = 0x00;                     // Stop Timer2. Clear T2SPLIT.
                                    // Use SYSCLK/12 as timebase, 16-bit auto-reload
      // CKCON  &= 0x30;                   // Timer2 High and Low byte uses the SYSCLK //
       TMR2RL  = -counts;              // Initialize reload value
       TMR2    = 0xFFFF;                  // Set to reload immediately
    
    }
    
    
     //-----------------------------------------------------------------------------
    // PCA_Init
    //-----------------------------------------------------------------------------
    void PCA_Init(void)
    {
    
            PCA0MD    = 0x00;
            PCA0CPM0  = 0xC7;               // PWM 16, ECOM, TOG, PWM, ECCF bits set //
    
    }
    
    
     //-----------------------------------------------------------------------------
    // Interrupt Service Routines
    //-----------------------------------------------------------------------------
    INTERRUPT(INT0_ISR, INTERRUPT_INT0)
    {
    
             TR2  = 1;            // Start Timer2 //
    
    }
    

  • Is there any other way of implementing this without having to need a Interrupt service routine for Timer servicing
    why? you get a much more precise operation with interrupts than with polling. A timer ISR ia just about the simplest thing there is.

    Erik

  • Again, that's a really basic question - nothing specifically to do with Keil or SiLabs.

    You really should think about getting some training in these foundational areas...