Timers and Interrupts on SparkFun ProMicro

So last time I talked about blinking an LED directly from using the registers. Now I will move on to something a bit more complicated. Not everything is working quite right but I know I am headed in the right direction. This time around I am using a timer that will count to a certain value and then create an interrupt and then reset from the top. Again I am using this board.

The main goal of this exercise will be to work with a timer on the arduino and when a certain time is reached, the counter stop, does something and then restarts from the beginning. I have not converted completely to C code but I am accessing the registers directly. Most of the work I did was done on Timer1, I tried messing around with Timer0 but it became apparent that I should stay away from it, mainly because arduino’s built in functionality uses Timer0.

Timer0 is an 8 bit timer and Timer1 is a 16bit timer.

In order to first get started we to understand timers.  I have one way the timer can work (timers can run in multiple different modes, but we will look at one that is close to ours):

Screenshot from 2017-07-12 23:06:58

Figure 1 (image found here)

So let’s start with nomenclature:

  1. TCNTnH: Counter register high byte for Timer n. Ex: TCNT1H is the counter register high byte for Timer1.
  2. TCNTnL: Counter register low byte for Timer n. Ex: TCNT1L is the counter register low byte for Timer1.
  3. OCRnxH: Output compare register A high byte for timer n. OCR1AH is the output compare register A high byte for timer 1.
  4. OCRnxL: Output compare register A low byte for timer n. OCR1AL is the output compare register A low byte for timer 1.
  5. OCFnX: Output Compare flag for X on timer X.  OCF1A sets up a flag that lets the MCU know that TCNT1 will be compared with OCR1A. This is set up by the TIFRn register, also known as Timer Interrupt Flag Register.

TCNT1 is the timer/counter that is counting and as soon as TCNT1 hits the value specified in OCR1A OCF1A goes high until the interrupt corresponding to the flag is executed (this can result in setting another pin high or in our case printing out to the serial monitor.)  Don’t worry about the Waveform Generator and OCnx for this explanation.  The other major take away is that the OCRnx is divided into high and low bytes and so are TCNTn. So when you try to write TCNTnH you will always write to a temp register first and when you write to TCNTnL directly will TCNTnH be written to from the temp register.  The same thing is happening in OCRnx. Just a side note I don’t know why there are multiple compare registers, but my best guess is that they can be used for different things and compare at different times.

So then how is TCNT1 is counting? The following is shown in the image below:

Screenshot from 2017-07-12 23:26:00

Figure 2 (image found here)

So how does the counting occur.  We can select the clock source (Tn) we want, we can have an external clock source or an internal clock source (the clock provided by your MCU). On top that there is a concept of prescalar.  This concept simply allows you to choose longer units of time (output clock time is defined by clkTn). Let’s take for example the internal clock which runs at 8MHz. This means if you have a 16bit counter (can count up to 2^16=65535), your maximum time available before an interrupt is generated is ~8ms. So the clock will be counting at 8MHz and will go back to 0 as soon as TCNTn will hit 65536. What if you want to wait for longer? The solution is prescalar. What if we counted every 2 cycles as one or every 4 cycles as one we could spread out the time it takes to count. Let’s take an example when the prescalar is 2.  This will effective make the counter move at 4MHz which will cause the counter to max out at ~16MHz (((1/4000000)*65536)*1000, multiplied by a 1000 to get to ms).  So we set the counting rate by prescalar which impacts how quickly TCNTn will count and that is then compared to OCRnX.  I do want to warn you that I am talking about a specific mode that I am using the timer in, the timer can be used for also generating PWMs which I am not getting into it now, but we can choose those settings based on WGMn3:0, COMn1:0. As we move forward you will see me set those settings in order to work under the CTC mode (non-pwm mode).

But before you do everything you have disable the global interrupts in order to make sure that interrupts don’t get created while you are trying to set up the OCR registers and TCNTn register.

To summarize: disable the global interrupt –> define the clock source and pre scalar –> set up the mode you want to run in (PWM, CTC, etc, this is found in p130-132 in the datasheet) –> set up output compare register –> in order to use the output compare register (A, B, or C) we have to setup the TIMSKx (Timer1 Interrupt Mask Register) which will tell the processor to look for which Output compare to use –> re-enable the global interrupt –> do other things now.

Here is the raw code:

/* Pro Micro Test Code
 by: Nathan Seidle
 modified by: Jim Lindblom
 SparkFun Electronics
 date: September 16, 2013
 license: Public Domain - please use this code however you'd like.
 It's provided as a learning tool.

This code is provided to show how to control the SparkFun
 ProMicro's TX and RX LEDs within a sketch. It also serves
 to explain the difference between Serial.print() and
 Serial1.print().
*/

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>




//Register variables

unsigned char OCR1AL_reg = 0x88; //output compare low 8 bits for Counter 1
unsigned char OCR1AH_reg = 0x89; //output compare high 8 bits for Counter 1

unsigned char TCCR1B_reg = 0x81; //Timer/Counter0 Control Register B 
unsigned char TCCR1A_reg = 0x80; //Timer/Counter0 Control Register A

unsigned char TIMSK1_reg = 0x6F; //Timer/Counter0 Interrupt Mask Register

unsigned char TIFR1_reg = 0x36; //Timer/Counter0 Interrupt Flag Register

unsigned char TCNT1H_reg = 0x85; //Timer/Counter0 Interrupt Flag Register
unsigned char TCNT1L_reg = 0x84; //Timer/Counter0 Interrupt Flag Register




unsigned char SREG_reg = 0x5F; //Status register used for disabling and enabling global interrupts

int stopeverything = 0;

void setup()
{

Serial.begin(9600); //This pipes to the serial monitor

//resetting the Timer/Counter1
 (*(volatile unsigned char*)(TCNT1H_reg)) = 0;
 (*(volatile unsigned char*)(TCNT1L_reg)) = 0; 
 
 //disabling all global interrupts
 (*(volatile unsigned char *)(SREG_reg)) = 0b00000000;

//defining prescalar
 (*(volatile unsigned char *)(TCCR1B_reg)) = 0b01001101; //the last 3 bits set to 101 will make the clock scalar 1024;
 //setting up CTC mode
 (*(volatile unsigned char *)(TCCR1A_reg)) = 0b10000000; //the last 3 bits set to 101 will make the clock scalar 1024;

//defining the output compare value which is calculated to be 3906 assuming 8MHz clock speed: OCRNA = (fClk_I/O)/(2*N*fOCRNA) where N is the prescalar fOCRNA is the compare value and fClk_I/O is the clock speed
 //the value calculated for fOCRNA to be 1Hz is ~3096
 //(*(volatile unsigned char *)(OCR1AH_reg)) = 0b00001111; //writing to high first which will write to a temp register first
 //(*(volatile unsigned char *)(OCR1AL_reg)) = 0b01000010; //writing to low will write both high and low in the same clock cycle

//defining the output compare value which is calculated to be 3906 assuming 8MHz clock speed: OCRNA = (fClk_I/O)/(2*N*fOCRNA) where N is the prescalar fOCRNA is the compare value and fClk_I/O is the clock speed
 //the value calculated for fOCRNA to be 0.1Hz is ~30960
 (*(volatile unsigned char *)(OCR1AH_reg)) = 0b11110001; //writing to high first which will write to a temp register first
 //(*(volatile unsigned char *)(OCR1AH_reg)) = 0b11111111; //writing to high first which will write to a temp register first
 (*(volatile unsigned char *)(OCR1AL_reg)) = 0b11100000; //writing to low will write both high and low in the same clock cycle
 //(*(volatile unsigned char *)(OCR1AL_reg)) = 0b11111111; //writing to low will write both high and low in the same clock cycle




(*(volatile unsigned char *)(TIMSK1_reg)) = 0b00100010; //writing so that output compare A is set up

//enable global interrupts
 (*(volatile unsigned char*)(SREG_reg)) = 0b10000000;







}

ISR(TIMER1_COMPA_vect) // timer compare interrupt service routine
{
 Serial.println("interrupt!");
 stopeverything=1;
}




void loop()
{
 Serial.println(
 (*(volatile unsigned char*)TCNT1H_reg << 8) | (*(volatile unsigned char*)TCNT1L_reg),BIN);
 //Serial.println("");
 if(stopeverything==1){
 Serial.println("Coming from interrupt!!");
 delay(2000);
 stopeverything=0;
 }
}

Just look at the stuff that I doesn’t have “//” in front of it. I found all the registers address on 408-411 in the data sheet.  I define all the registers address above setup() and then in the setup function I set the value for each of registers be dereferencing the pointers associated with the variables (you might get warning while compiling it but ignore it).  Remember SREG is the one that sets the global interrupts on or off.  I go through and reset the Timer1 to 0 then set the clock source and prescalar, set up the value to compare to and then set up TIMSK1 and then enable all the interrupts.  What will happen is TCNT1 will keep counting up until it hits OCR1 and then restart back to 0.

When TCNT1 does hit OCR1 then an ISR (interrupt service routine) is generated and at I go in a write something to serial monitor.  I am still trying to wrap my head around the internals of an ISR but for now, I know that when TCNT1 = OCR1 an ISR is generated and I have to do something so that the interrupt is cleared otherwise I will have to clear it some other way.

In the meantime in loop function I printout the raw binary values associated with TCNT1 and as soon as the ISR hits it toggles a variable that causes the loop function to enter a special if clause which pauses the loop for 2000 seconds and prints a special message.  Printing in binary for TCNT1 is extremely useful because then I can use it to compare with OCR1. And thats it! This whole thing took quite a while for all those different parts to come together. The code can also be found on my github! I have found a document that does a pretty good job of describing timers which can be found on my here.

There is a lot of useful information out there and here are some useful links that I found:

http://www.robotshop.com/letsmakerobots/arduino-101-timers-and-interrupts

http://web.engr.oregonstate.edu/~traylor/ece473/lectures/tcnt1-3.pdf

http://www.gammon.com.au/interrupts

http://www.avrfreaks.net/forum/tut-c-bit-manipulation-aka-programming-101?name=PNphpBB2&file=viewtopic&t=37871

http://forum.arduino.cc/index.php?topic=134611.0

http://www.avrfreaks.net/forum/tut-newbies-guide-avr-interrupts?name=PNphpBB2&file=viewtopic&t=89843

https://oscarliang.com/arduino-timer-and-interrupt-tutorial/

https://forum.arduino.cc/index.php?topic=382681.0http://ee-classes.usc.edu/ee459/library/documents/avr_intr_vectors/

https://stackoverflow.com/questions/10854466/how-convert-two-bytes-into-one-16-bit-number

http://web.csulb.edu/~hill/ee346/Lectures/12%20Timer%20Interrupts.pdf

https://exploreembedded.com/wiki/AVR_Timer_programming

http://www.atmel.com/Images/Atmel-2505-Setup-and-Use-of-AVR-Timers_ApplicationNote_AVR130.pdf

http://class.ece.iastate.edu/cpre288/lectures/lect26_1pp.pdf

https://arduino.stackexchange.com/questions/12382/where-is-documentation-on-arduinos-internal-interrupts

https://arduinodiy.wordpress.com/author/yersinia/page/8/

http://www.avrfreaks.net/forum/tut-c-bit-manipulation-aka-programming-101?name=PNphpBB2&file=viewtopic&t=37871

http://forum.arduino.cc/index.php?topic=134611.0

http://www.avrfreaks.net/forum/tut-newbies-guide-avr-interrupts?name=PNphpBB2&file=viewtopic&t=89843

http://www.embedded.com/design/programming-languages-and-tools/4418929/An-introduction-to-function-pointers–Part-1

http://denniskubes.com/2013/03/22/basics-of-function-pointers-in-c/

 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s