PIC16F877A Datasheet

jpanhalt

Joined Jan 18, 2008
11,087
@JohnInTX
Thanks for pointing that out;

I assumed wrongly that the tutorial was doing that for the other interrupts it discussed, not TMR0. However, on re-reading this:
Code
Below are the steps for configuring and using the Timer0 for delay generation:

  1. Calculate the Timer Count for the required delay.
  2. Set the Presaclar bits in OPTION_REG as per the delay calculations.
  3. Clear the PSAbit for using the prescalar.
  4. Select the Clock Source Internal/External using TOCS bit.
  5. Load the timer value into TMRO register.
  6. Enable the Timer0 Interrupt by setting TMR0IE bit
  7. Enable the Global and Peripheral interrupts by setting GIE and PIE bits
I see the author intended it for TMR0 too in Step 7. Glad to see you agree it's not needed.
 

Thread Starter

Gajyamadake

Joined Oct 9, 2019
310
The 'tutorials' show PEIE=1 but as you say they are wrong. The interrupt controls for timer 0 are in INTCON. You're correct about the origins of the INTCON controls.
Let's do a thought experiment:
1) You are setting TMR0IF = 1
2) You enable TMR0 interrupt: TMR0IE = 1
3) You enable GIE = 1
What will happen at that point?
MCU will immediately go to the ISR

You need to flesh out your ISR, including what command is used for return (presumably RETFIE).
I suspect that your program may idle in "FOREVER" and have an interrupt every 256 clicks (Your forever does not preload TMR0 = 5. If you want a preload, you need to do it EVERY time there is an interrupt.)

Finally, you need to set appropriate bits in OPTION_REG. If your MCU operates at some MHz frequency, TMR0 will rollover quite quickly.
I have not configured OPTION_REG.I believe flow chart in post #17 gives the basic idea to use timer interrupt of PIC16F877A
 

jpanhalt

Joined Jan 18, 2008
11,087
With no schematic or configuration, I assumed you intended to use the system clock and a pre-scale:
1573397433912.png

Since the default for OPTION_REG is 0xFF, you need to change the highlighted bits and set your pre-scale. If you do not want a pre-scale >1:1, then leave bit<3> set. NB: Instruction clock is Fosc/4.
 

JohnInTX

Joined Jun 26, 2012
4,787
You should really get into the habit of initializing ALL of the control and I/O registers during your initialization routines. That would pick up things like the necessity to set OPTION as well as making sure that multi-function pins are set correctly.

A common problem is not to visit the A/D converter configuration registers. You must explicitly set those pins to digital to use them as general purpose I/O.
You should always initialize ALL of the I/O pins. When you don't, you wind up with floating digital or analog inputs which isn't a good thing. Set all unused pins to outputs (TRISx = 0) and set their values to 0 as well (PORTx = 0). Floating inputs increase power dissipation and noise susceptibility.
A good rule is to visit ALL of the SFRs and decide if their default values are OK for your application.
Also, visit ALL of the CONFIG bits to be sure that the PIC is actually configured like you expect.

It is unfortunate how many 'tutorials' omit these simple but necessary steps.

Have fun!
 

Thread Starter

Gajyamadake

Joined Oct 9, 2019
310
You should really get into the habit of initializing ALL of the control and I/O registers during your initialization routines. That would pick up things like the necessity to set OPTION as well as making sure that multi-function pins are set correctly.
Have fun!
Hi, JohnInTX That I asked in my first post. I will remember your advice

Back to the topics, I think timer1 is also work same as timer 0 does, I see only one difference that it' hold the 16 bit value.

Have fun!
 

JohnInTX

Joined Jun 26, 2012
4,787
Hi, JohnInTX That I asked in my first post. I will remember your advice

Back to the topics, I think timer1 is also work same as timer 0 does, I see only one difference that it' hold the 16 bit value.
If you mean that TIMER 1 counts up and can generate an interrupt when it rolls over, you are correct. But there are enough other differences to treat it as a different thing:
The interrupt control uses the PIE/PIR registers and PEIE in INTCON.
Unlike TIMER 0, you can start and stop it (and have to explicitly start it during init)
It can use CCP1 to count from 0000h to whatever value is in CCPR1 then automatically reset. This makes it a period register i.e. set it up once and it can generate capture interrupts on a precise period without having to reset the value each time. This is a great feature - keep in mind that you lose a few cycles each time you set any of the timers so even with some tuning, your timing will be a bit slippery. By using CCP1 as a period register such problems are avoided. See 8.2.4 SPECIAL EVENT TRIGGER in the datasheet.

Timer 2/PR2 is a good choice for a period register as well.
 

Thread Starter

Gajyamadake

Joined Oct 9, 2019
310
Unlike TIMER 0, you can start and stop it (and have to explicitly start it during init)
It can use CCP1 to count from 0000h to whatever value is in CCPR1 then automatically reset.
Does it means TIMER 0 start from 0 and stop when it reaches to its maximum value 255? We cant set timer at any value For Example if I want to start counter from 60. I can't do that in timer 0 . It will always start from 0

CCPI mode : For Example we set CCP1 to 12000 so timer will start from 0 and when it will reaches 12000 timer will generate interrupt
 

jpanhalt

Joined Jan 18, 2008
11,087
As JohninTX said, there is no practical way to "stop" Timer0. That is one of the differences between it and Timer1 (or other timers). Of course, you might consider setting Timer0's clock to increment on an edge of T0CKI pin, then tie that pin to GND or VCC, but that seems like a lot of work to stop something running in the background. Resetting Timer0 to 0 effectively starts it, and since it is just an 8-bit timer/counter. you don't have to do some of the things people do when they zero out Timer1.
 

JohnInTX

Joined Jun 26, 2012
4,787
Does it means TIMER 0 start from 0 and stop when it reaches to its maximum value 255? We cant set timer at any value For Example if I want to start counter from 60. I can't do that in timer 0 . It will always start from 0

CCPI mode : For Example we set CCP1 to 12000 so timer will start from 0 and when it will reaches 12000 timer will generate interrupt
Using CCP1 is different way to do the same thing.

Both timer 0 and 1 count up from 0 to their max count 255 and 65535 respectively then they both roll over to 0, generate an interrupt and continue counting from 0. This means that if you want 100 counts you must load timer 0 with 256-100 or timer 1 with 65536-100 so that there are 100 counts between the loaded count and rollover. As noted above, having to load either timer has some overhead taking additional time that should be accounted for if you want an accurate period.

Using the CAPTURE register CCP1 with TIMER1 is different in that the count end is what you load into the CAPTURE register - if you want 100 counts, you load the capture register to 100. If set up as a SPECIAL EVENT TRIGGER, a CCP interrupt (CCP1IF) will be generated when the timer counts up from 0000 to the value in CCP1. At the same time, the timer will automatically be reset to 0000 and start counting from there without having to be reloaded or losing cycles. Note that in this mode, the timer never rolls over from FFFFh to 0000h, it counts over the range 0000h to whatever's in CCP1. That's why you use the CCP1 interrupt and not the TIMER1 interrupt.

It takes a little more study but isn't real hard. The payoff is that your timing is accurate and you don't have to reload the timer on each interrupt. Nice!

Note that your interrupt stuff will now use CCP1IF as the interrupt flag. CCP1IE and PEIE must be 1 to enable the peripheral interrupt along with the GIE global interrupt enable.

Note also that you still have to configure the timer/prescaler and mode using T1CON and the capture using CCP1CON. For a period register use CCP1CON = 00001011. Your init must:
Configure T1CON with the prescaler, clock source etc. Start with TMR1ON=0 to stop the timer.
Clear TMR1H/L registers
Load CCPR1H/L with the number of counts between interrupts
Disable TMR1IE
Clear CCP1IF
Enable CCP interrupt CCP1IE=1
Enable peripheral interrupts PEIE=1 (now it's proper)
Enable global interrupts GIE=1
Start the Timer TMR1ON = 1

In the interrupt routine manage CCPIF, not TMR1IF.
Count the interrupts as before if necessary. Between 16 bit timer/capture and the prescaler, you might get the time period you want just from the timer/capture itself. As noted above, you can also run the timer from another source but I always just use the internal clock.

Have fun!
 
Last edited:

be80be

Joined Jul 5, 2008
2,072

If the TS looked at the link I posted you done be on your way to big and better things
https://www.exploreembedded.com/wiki/PIC16f877a_Timer
Code:
void interrupt timer_isr()
{
    if(TMR1IF==1)
    {
        value=~value;   // complement the value for blinking the LEDs
        TMR1H=0x0B;     // Load the time value(0xBDC) for 100ms delay
        TMR1L=0xDC;
        TMR1IF=0;       // Clear timer interrupt flag
    }
}
Code:
void main()
{
    TRISD=0x00;    //COnfigure PORTD as output to blink the LEDs

    OPTION_REG = (1<<SBIT_PS2);  // Timer0 with external freq and 32 as prescalar
    TMR0=100;       // Load the time value for 1ms delay
    TMR0IE=1;       //Enable timer interrupt bit in PIE1 register
    GIE=1;          //Enable Global Interrupt
    PEIE=1;         //Enable the Peripheral Interrupt

    while(1)
    {
        PORTD = value;
    }
}
 
Last edited:

Thread Starter

Gajyamadake

Joined Oct 9, 2019
310
You should really get into the habit of initializing ALL of the control and I/O registers during your initialization routines.
It is unfortunate how many 'tutorials' omit these simple but necessary steps
@jpanhalt and @JohnInTX I need yours attention on initializing port

It's dummy program and it should supposed to check the status of sensor when timer0 interrupt happen

C:
#include<pic16f877a.h>
void  initialize (void)
{
   //Make all PORTD pins low
   PORTA = 0;
   PORTB = 0;
   PORTC = 0;
   PORTD = 0;

   //Configured Port pin as input
  // TRISC2 = 0b 0000 0100;  //RC2 pin as input for sensor
     TRISC = 0b 0000 0100;  //RC2 pin as input for sensor updated line


// Configured PORT pins as Output
   TRISA = 0b00000000;
   TRISB = 0b00000000;  //RB0 as Output PIN for buzzer and RB1 for buzzer
   TRISD = 0b00000000;

}

void main()
{
    initialize ();   //initialize port pins

  //  OPTION_REG = 0b00000101;  // Timer0 with external freq and 64 as prescalar // Also Enables PULL UP
     OPTION_REG = 0b00000101;  // Timer0 with internal instruction clock  and 64 as prescalar // Also Enables PULL UP  updated line 

    //TMR0IF = 1;       // set timer interrupt flag
     TMR0IF = 0;       // clear timer interrupt flag updated line
    TMR0IE = 1;       //Enable timer interrupt bit
    GIE = 1;          //Enable Global Interrupt

    TMR0 = 0;       // Load the time value

    while(1)
    {

        if(RC2 == 1) check sensor is active
         {
           RB0 = 1; //LED ON
         }
         else
         {
             RB0 = 0; //LED ON
         }
    }
}

void interrupt timer_isr()
{
    if(TMR0IF==1)
    {
         if(RC2 == 0)  // check sensor is working
         {
           RB1 = 1; // Alert Buzzer
         }
   
        TMR0 = 0;     //Load the timer Value
        TMR0IF=0;       // Clear timer interrupt flag
    }
}
 
Last edited:

jpanhalt

Joined Jan 18, 2008
11,087
I can't really comment reliably on anything in C, but a few things stand out:

1) "TRISC2 = 0b 0000 0100; //RC2 pin as input for sensor" I don't know how the compiler will interpret "TRISC2". It seems that since you give the full binary, one should say simply, "TRISC =" or if you just want to set a bit, then "TRISC,2 = 1"

2) This
OPTION_REG = 0b00000101; // Timer0 with external freq and 64 as prescalar // Also Enables PULL UP
Does not do that. When bit<5> is clear, it will use the internal instruction clock, not an external clock.

3)
TMR0IF = 1; // set timer interrupt flag
TMR0IE = 1; //Enable timer interrupt bit
GIE = 1; //Enable Global Interrupt

TMR0 = 0; // Load the time value
Setting TMR0IF = 1 will initiate an immediate interrupt regardless of the number of counts in TMR0 when you set GIE=1 Nothing will get done until you clear TMR0IF, which is much latter. I don't believe your program will ever get there. Unless that is what you want to happen, clear TMR0IF before you enable interrupts by setting GIE=1.
 

JohnInTX

Joined Jun 26, 2012
4,787
Agree with everything above plus:

You missed the init of PORTE.
You turn the buzzer on but never turn it off.

Good programming practice would dictate:
Load your timer value before GIE=1 to give a full period before the interrupt. As written you don't know what the value of TMR0 is when you enable interrupts.

In the interrupt routine, load the timer value as soon as possible so that you lose as few counts as possible. Consider what the timer is doing. It rolls from FF -> 00 and keeps accumulating counts while the interrupt routine is executing. Placing TMR0=0 at the end of the interrupt routine means that it may accumulate several counts that get lost when you reload the timer. If you load the timer sooner in the interrupt routine, you lose fewer counts.

If you MUST use TMR0, consider adding the load value to the timer. That way, if there are a few counts in the timer when you reload it, they don't get lost.
Use #define for things like the timer load value. You load the timer with the same value in more than one place. If you change the value, you have to ensure that you find all of those places - if you miss one, it's trouble. Using something like:
#define TMR0_LOAD_VALUE 0
and then
TMR0 += TMR0_LOAD_VALUE // reload timer by adding the load value
will allow you to change the value in ONE place and the compiler will fix it up for you.

Clear the interrupt flag as soon as possible in the interrupt routine instead of the end. It doesn't matter here but there are times that it will so get into the habit of doing it right.

Have fun!
 

Thread Starter

Gajyamadake

Joined Oct 9, 2019
310
TMR0 += TMR0_LOAD_VALUE // reload timer by adding the load value
will allow you to change the value in ONE place and the compiler will fix it up for you.
I have rewritten and I hope it's all in sequence
C:
#include<pic16f877a.h>

#define Timer_Load_Value  0

void  initialize (void)
{
   //Make all PORTD pins low
   PORTA = 0;
   PORTB = 0;
   PORTC = 0;
   PORTD = 0;
   PORTE = 0;

   //Configured Port pin as input
  // TRISC2 = 0b 0000 0100;  //RC2 pin as input for sensor
     TRISC = 0b 0000 0100;  //RC2 pin as input for sensor updated line

// Configured PORT pins as Output
   TRISA = 0b00000000;
   TRISB = 0b00000000;  //RB0 as Output PIN for buzzer and RB1 for buzzer
   TRISD = 0b00000000;
   TRISE = 0b00000000;
}

void main()
{
       initialize ();   //initialize port pins

     // Timer0 with internal instruction clock  and 64 as prescalar // Also Enables PULL UP  updated line
     OPTION_REG = 0b00000101;

     TMR0IF = 0;                 // clear timer interrupt flag updated line
     TMR0IE = 1;                 //Enable timer interrupt bit
     TMR0 = Timer_Load_Value;    // Load the time value
     GIE = 1;                    //Enable Global Interrupt

    while(1)
    {
        if(RC2 == 1)   //check sensor is active
         {
           RB0 = 1; //LED ON
         }
         else
         {
             RB0 = 0; //LED ON
         }
    }
}

void interrupt timer_isr()
{
    if(TMR0IF==1)
    {
         if(RC2 == 0)  // check sensor is working
         {
           RB1 = 1; // Alert Buzzer
         }
         else
         {
            RB1 = 0; //  Buzzer OFF
         }
      
        TMR0 += Timer_Load_Value;     //Load the timer Value
      
        TMR0IF=0;       // Clear timer interrupt flag
    }
}
I have doubt in below line
Code:
    TMR0 += Timer_Load_Value;     //Load the timer Value
TMR0 += TMR0_LOAD_VALUE or TMR0 = TMR0 + TMR0_LOAD_VALUE

Am I using correctly above line in ISR
 

JohnInTX

Joined Jun 26, 2012
4,787
TMR0 += TMR0_LOAD_VALUE or TMR0 = TMR0 + TMR0_LOAD_VALUE
Either of those does the same thing. += is just a shorthand. I think += is easier to read.

C:
void interrupt timer_isr()
{
    if(TMR0IF==1)
    {
        TMR0 += Timer_Load_Value;     //Load the timer Value
        TMR0IF=0;       // Clear timer interrupt flag
     
         if(RC2 == 0)  // check sensor is working  
           RB1 = 1; // Alert Buzzer  
         else      
           RB1 = 0; //  Buzzer OFF
     
    } //TMR0IF
}// interrupt
Relocated timer load and clearing interrupt flag as discussed earlier.
Also, you don't need brackets if you only have one statement following the 'if' and 'else'. It compiles the same but clutters up your code and makes it harder to read.

C:
TMR0 = Timer_Load_Value;    // Load the time value
TMR0IF = 0;                 // clear timer interrupt flag updated line
TMR0IE = 1;                 //Enable timer interrupt bit
  GIE = 1;                    //Enable Global Interrupt
This is a better sequence to init the timer - set the value first, then clear any interrupt flag that may occur as a result of setting that value.

But, good work!
 

djsfantasi

Joined Apr 11, 2010
9,163
Either of those does the same thing. += is just a shorthand. I think += is easier to read.

C:
void interrupt timer_isr()
{
    if(TMR0IF==1)
    {
        TMR0 += Timer_Load_Value;     //Load the timer Value
        TMR0IF=0;       // Clear timer interrupt flag
    
         if(RC2 == 0)  // check sensor is working 
           RB1 = 1; // Alert Buzzer 
         else     
           RB1 = 0; //  Buzzer OFF
    
    } //TMR0IF
}// interrupt
Also, you don't need brackets if you only have one statement following the 'if' and 'else'. It compiles the same but clutters up your code and makes it harder to read.

I disagree with just this point. Using braces for both clauses unambiguously identifies each clause. IMHO, omitting the second set of braces can the second clause to erroneously be identified as executing in all conditions.
 

JohnInTX

Joined Jun 26, 2012
4,787
I disagree with just this point. Using braces for both clauses unambiguously identifies each clause. IMHO, omitting the second set of braces can the second clause to erroneously be identified as executing in all conditions.
Interesting observation and good point. I've always found it easier to know that there is only one statement associated with the clause. Also, I like to minimize the number of brackets K&R like. Matter of style, I suppose.
 
Last edited:
Top