Monday 23 May 2016

Timers and Interrupts

In the future my posts will be roughly divided into two categories. The first one will be more technical containing the posts where I go through how to do this and program that in a detailed manner. The second one contains all the rest that doesn't make the brain (whether yours or mine) wanting to escape through the ear. The post you're reading right now belongs (unfortunately) to the first category.

Interrupts and timers are important topics what comes to programming of microcontrollers. An interrupt is a signal to the microcontroller that makes it put on hold whatever it is doing at the moment and do something else instead. After the interrupt is handled, the controller continues where it left off. Interrupts can be internal (e.g. a timer reaching a specific value) or external (e.g. a button connected to a pin is pressed).

Timers are simply counters that are synchronized with a clock signal. The clock can be the same that controls the CPU or it can be separate. For example, if I'd like to keep track on seconds, I can just wire an old digital watch to the chip instead of building a circuit of my own.

In ATtiny25/45/85, there are two innovatively named 8-bit timers/counters: Timer/Counter0 and Timer/Counter1. Both can be set to count clock pulses increasing their value from 0 to 255. When 255 is reached, the next pulse causes an overflow and the counting starts from the beginning. A prescaler can be assigned to the clock signal that lowers its frequency dividing it by a factor of 8, 64, 256, or 1024.

The timers can be set to cause interrupts. There are two types of interrupts: timer/counter overflow and compare match. The first one is caused when the timer value jumps from 255 to 0 and the second one when the timer value matches with the one in so called Output Compare Register. There are two Output Compare Registers, A and B, for both timer/counters.


For an example, let's rewrite the blinking LED code using a timer and interrupts. Let's say I want to toggle on-off state of the LED every 250 ms using the timer/counter overflow interrupt. If the clock frequency is 1 Mhz and I set the timer prescaler to 64, the timer ticks every 64/1000000 second or 0.064 milliseconds.  Thus an 8-bit counter overflows every 0.064*256 = 16.384 milliseconds.

For 250 ms I need thus 250/16.384 = 15.258... overflows, so I can't time exactly 250 ms with this approach. There are couple of ways to go around this limitation but I think simply rounding this down to 15 overflows (245.76 ms) is sufficient for my purpose. 250 ms was arbitrarily chosen anyway. I'll return to the other ways in another post.

Once again we start with begin the programming with preprocessing lines:

 #define F_CPU 1000000UL  
 #include <avr/interrupt.h>  
 #include <avr/io.h>  

The first line sets the clock frequency to 1 Mhz and the rest import the necessary declarations and functions for interrupts and pin I/O. Next we need a variable to store the number of overflows:

 volatile uint8_t ovf_count = 0;   

Note the use of the keyword volatile. It tells to the compiler that the value of ovf_count can change 'due to external factors' and thus has to be load every time even if the main program hasn't changed it. In this case the 'external factor' means interrupts, as they stop the execution of the main program while doing their own thing. (I don't use ovf_count outside the interrupt function so I could skip the use of volatile here but I wanted to brought it up, as it is certain to fuck up the code if omitted where needed.) 

Before getting to the main function, we define our Interrupt Service Routine (ISR) that is executed when the interrupt is fired. From the AVR libc reference we see that the so called vector corresponding to the timer0 overflow interrupt is TIM0_OVF_vect. Thus the ISR corresponding this interrupt is:

 ISR(TIM0_OVF_vect){  
   if (ovf_count < 15){  
     //increase the counter by 1  
     ovf_count++;   
   }  
   else {  
     //reset the counter and toggle pin PB0  
     ovf_count = 0;  
     PORTB ^= 1;  
   }  
 }  

Nothing complicated here. The if clause counts the number of overflows and when it reaches 15, it resets the counter and toggles the output of the LED pin. ^= is bitwise XOR that takes the bits of PORTB defined on the right hand side and flips them. The right hand side value 1 = 0b0001 picks the PB0 pin.

The main program looks like this:

 void main(){  
   
   //Init counter  
   TCNT0 = 0;  
     
   //set the clock, prescaler 64  
   TCCR0B |= (1 << CS01) | (1 << CS00);  
   
   //enable overflow interrupt  
   TIMSK |= 1 << TOIE0;  
   
   //Global interrupt enable  
   sei();  
   
   //Set PB0 as output  
   DDRB = 1;  
   
   //Enter the endless loop  
   while(1);  
   
 }  

So, mainly a bunch of acronyms picked up from the datasheet. TCNT0 (Timer/Counter Register) is the counter itself that is initialized to 0 in the beginning.

TCCR0B (Timer/Counter Control Register B) has two bits (0 and 1) that has to be set one to select prescaling factor of 64. I could do it like this:

 TCCR0B |= 0b00000011;  

Bitwise OR |= sets the last two bits 1 but leaves all the rest as they are. However, it is customary to use names of the bits (CS01 and CS00, from the datasheet) and use the bitshift operator << that moves 1 (or 0b00000001) as many bits to the left as CS01 and CS00 say. In this case 1 << CS01 = 0b10 and 1 << CS00 = 0b01. These are combined with bitwise OR so that  0b10 |  0b01 =  0b11. It might need a little practice to get used to this kind of notation (at least for me it did) but it makes the code easier to read.

TIMSK (Timer/Counter Interrupt Mask Register) is used to set which interrupts are active. In this case we are interested only in Timer0 overflow so we set the TOIE0 bit (Timer/Counter Overflow Interrupt Enable).

Thus Timer/Counter0 is all set. All we need now is to enable the interrupts globally with sei() (to disable use cli() ) and set the PB0 pin as output. After that the program enters to an endless loop that is paused only to handle the timer0 overflow interrupts. And tadah, The blinking LED v2 is finished!

But enough with the LEDs. Next time we'll do something that is actually useful. We are going to grow some flowers learn how to use buttons to control the microcontroller.

References:

http://www.atmel.com/images/atmel-2586-avr-8-bit-microcontroller-attiny25-attiny45-attiny85_datasheet.pdf

No comments:

Post a Comment