Counting
I have written two articles on timers in one article I discussed timers in general and in the 2nd article I went more into a couple of practical examples.
As I recently started to use the Attiny13 a lot, I thought it would be good to write something specifically on how to use timers in the Attiny13.
As usual a Led flasher is a good example and we will start using the Timer OverFlow register to achive our task but also give an example using the Watchdog Timer.
Before I go on, if you are using the well known Smeezekitty core for the Attiny13 you are in trouble. In order to correct the timing, John Smeezekitty uses the Timer0 Overflow vector in his core for the Attiny13. You may want to use this core. If you continue to use that core you may want to make this modification as well.
Also, if you would want to adopt this for another Attiny chip, apart from changing some of the register names (like TIMSK rather than TIMSK0), you may find that the Timer0 OverFlow Vector is used in a variety of other cores. For the Attiny85 for instance, you may encounter a ‘Vector_5’ error, which is the Attiny85’s version of the Attiny13’s ‘Vector_3’error. Vector5 and Vector3 are the Timer0 Overflow vectors for resp the Attiny85 and the Attiny13
The ATtiny13 datasheet lists the timer interrupts (and other interrupts) in §9.1.
In Table 9.1 we find the Timer/Counter Overflow interrupt vector.
It is listed as TIM0_OVF. We then know the Interrupt Service Routine (ISR) we need to call in our program is “TIM0_OVF_vect”
The next chapter with information on the behaviour of the timer is in §11.7.1, it says that in “normal mode” (WGM02:0 = 0), the counter counts up, and once it gets to the “top” (0xFF as it is an 8 bit timer) it resets back to zero and the TOV0
(Timer/Counter Overflow Flag) bit is set.
How we enable the Timer/Counter0 Overflow Interrupt is found in §11.9.6: it states that if you enable the Timer/Counter0 Overflow Interrupt by setting the TOIE0
bit to 1 in the TIMSK0
register and interrupts are enabled, whenever the TOV0
bit is set, the interrupt will occur.
The timer that we are using to generate an interrupt in the Attiny13 is an 8-bit register. Unless ‘prescaled’ (see later) it increments once per clock cycle. As it is an 8-bit register its maximum value is 255, therefore once every 256 clock cycles the timer reaches its maximum value and resets back to zero. When this occurs the Timer/Counter0 Overflow Interrupt will be generated, and we can can catch this in and Interrupt Service Routine (ISR) in our program, to respond to the generated interrupt, and flash a LED.
The Attiny13, knows 3 major frequencies: 9.6 MHz, 4.8 Mhz and 1.2 MHz.
The frequency of 1.2 MHz is obtained by dividing the internal 9.6 MHz clockspeed by 8 by setting some fuses. Often, when sold, those fuses are set and your brand new Attiny13 is set to the 1.2 Mhz clockspeed.
So, how often does that Timer Interrupt Overflow occur then?
Well that is easy: at 1.2 MHz, the length of a clock cycle is 0.00083333333333333 ms. As the interrupt is set when the timer register reaches 255, that means there are 256 Clock cycles (remember, we are counting from 0). Thus the interrupt occurs every 0.213333333 milisecs which is 1000/0.213333333=4687.5 per second. (I used a bit of an extensive calculation here to make it clear. Normally you would just divide the frequency by the register (1.2Mhz/256=4687.5)). That is a bit fast to see the led flashing.
The standard practice then is to add a counter in the ISR and toggle the LED once the counter reaches a certain number of interrupt overflows. Earlier we calculated that the interrupt will be triggered approx 4688 (1.2 MHz *1/256) times per second. With an 8 bit counter variable we cannot even count to 4688: we can count a max of 256 interrupts. as the interrupts occur every 0.2133333 msecs that would be 54.613333248 msec, which is about 18 times a second. Still a bit fast for a flashing LED.
If we use a 16-bit variable instead: 65536/4688 gives us a maximum flash time of ~13 seconds. So that is one way of doing it, but there is another (better?) way…
Earlier I touched upon ‘prescaling’ the timer. This slows down the increment of the timer counter by using 1 out of every so many clock cycles to increment the timer counter (see §12.1). These prescale factors are found in the datasheet in §11.9.2, table 11-9. The largest slow down or ‘prescale’ factor is 1024 (by setting CS02
and CS00
bits to 1 in the TCCR0B
register).
If prescaling is set to1024, the timer register gets incremented 1172 times a second (1.2MHz/1024), and thus overflows 4.6 times per second. With an 8-bit variable to count the overflows we can count to about 56 seconds.
This gives the final code:
volatile byte counter = 0;// interrupt needs volatile variable
ISR(TIMER0_OVF_vect) {
if (++counter > 5) {// interrupt occurs 4.6 times per second
// Toggle Port B pin 3 output state
PORTB ^= 1<<PB3;// Toggle Port PB3
counter = 0; //reset the counter
}
}
void setup() {
// Clear interrupts, just to make sure
cli();
// Set up Port B pin 3 mode to output by setting the proper bit
// in the DataDirectionRegisterB
DDRB = 1<<DDB3;
// set prescale timer to 1/1024th
TCCR0B |= (1<<CS02) | (1<<CS00);// set CS02 and CS00 bit in TCCR0B
// enable timer overflow interrupt
TIMSK0 |=1<<TOIE0;// left shift 1 to TOIE0 and OR with TIMSK
// = set TOIE0 bit
sei(); //start timer
}
void loop() {
// ISR handles the flashing so no code here
}
Sleeping.. with an alarm clock
With the LED flashing as in the program above, the Attiny13 doesnt do much more than waiting for the right amount of interrupts to pass by and then toggle the LED.
All that time it is using full energy. Now if only we could put it to sleep, only to wake up when the LED needs to be toggled, theat would save energy consumption and thus extend a possible battery life..
There are 3 sleep modes of which ‘Power down’ gives the deepest sleep and thus the least energy consumption. In order to wake from sleep the sleeping chip either needs an external interrupt, or it needs to be woken by the watchdog timer. The watchdog timer is described in chaper 8 of the Attiny13 datasheet. Its operation is quite simple: it needs bits set in the Watchdog Timer Control Register (WDTCR) to determine the time it will run and then it needs to be enabled to start it. When the watchdog timer sends an interrupt, the WDT_vect ISR is called and executed and the program goes back to where it was put asleep.
As we only need to toggle an LED, that can be done in the WDT_vect ISR
ISR(WDT_vect) {
// Toggle PB3 output
PORTB ^= 1<<PB3;
}
void setup(){
// Set PB3 to output
DDRB = 1<<DDB3;
//set timer to 1 sec
WDTCR |= (0<<WDP3) | (1<<WDP2) | (1<<WDP1) | (0<<WDP0);
// set timer to 0.5s
//WDTCR |= (0<<WDP3) | (1<<WDP2) | (0<<WDP1) | (1<<WDP0);
// Set watchdog timer in interrupt mode
WDTCR |= (1<<WDTIE);
WDTCR |= (0<<WDE);
sei(); // Enable global interrupts
}
void loop() {
// Everything is already handled in the ISR
}
This program will let a led flash every second, or should you desire, flash within a second.
Once we have done that we can put the the Attiny to sleep, only to be woken up by the Watchdog timer.
To handle the sleep mode we make use of the sleep.h header file that is part of the avr library. In the setup we set the sleepmode to Power Down. with that we in fact set two bits in the MCUCR register, SM[1:0] to 1 resp 0.
Then in the loop we do a call to sleep_mode()
The command sleep_mode() in fact makes a call to three routines:
sleep_enable() sets the Sleep Enable bit in the MCUCR register
sleep_cpu() issues the SLEEP command
sleep_disable() clears the SE bit.
So what happens is that at the end of the setup routine the watchdog is set and the sleepmode is set. Upon entry in the loop the is put to sleep and kept in sleepmode. When a Watchdog interrupt occurs, the Attiny13 wakes up, the interrupt routine is carried out and the program jumps back to where it was put asleep. there it finds the sleepdisable macro that resets the Sleep Enable bit. This is in case you want to perform more tasks. But as there aren’t any more tasks, the Attiny is brought right back to sleep again, till the next Watchdog interrupt
#include <avr/interrupt.h> #include <avr/sleep.h> ISR(WDT_vect) { // Toggle Port B pin 3 output state PORTB ^= 1<<PB3; } void setup(){ // Set up Port B as Input DDRB = 0; // usually not necessary but it will save current // Set Port B pin 3 mode back to output DDRB = 1<<DDB3; //set timer to 1 sec WDTCR |= (0<<WDP3) | (1<<WDP2) | (1<<WDP1) | (0<<WDP0); // set timer to 0.5s // WDTCR |= (1<<WDP2) | (1<<WDP0); // set timer to 4 sec // WDTCR |= (1<<WDP3); // Set watchdog timer in interrupt mode WDTCR |= (1<<WDTIE); WDTCR |= (0<<WDE); sei(); // Enable global interrupts set_sleep_mode(SLEEP_MODE_PWR_DOWN); } void loop() { sleep_mode(); // go to sleep and wait for interrupt... }
In order to save even more energy, there is more we can do: disable the ADC channels and set all I/O lines to input (but when out of sleep you have to set back the one that toggles the LED).
In the setup routine we can switch the ADC off by the command:
cbi(ADCSRA,ADEN); // switch Analog to Digitalconverter OFF
or
ADCSRA &= ~(1<<ADEN); //
Which is the same
With the commands:
ADCSRA &= ~(1<<ADEN); //turn off ADC ACSR |= _BV(ACD); // disable analog comparator
I got the following results:
sleep: 6.6uA (0.0066mA)
non sleep: 3600 uA (3.6mA)
That is a factor 600. Of course this was with the LED unplugged as I was only interested in the consumption of the chip. The LED would add another 4-5 mA.
Disabling the analog input buffers with the command:
DIDR0 |= (1<< AIN1D)|(1 << AIN0D);//disable
didn’t change the power-consumption. It should be possible to go even lower with lower chip frequency (mine was 9.6Mhz) and lower voltage (mine was 5V). On 3.3 Volts the current went to 5uA. Mind you that also the use of the WDT ‘contributes significantly to the power consumption’ according to the datasheet.
If you are using an attiny85, it may pay to also issue the power_all_disable();
command that sets the PRR, but the attiny13 doesnt have that. The Attiny13A does, but as the core used is thinking it is an Attiny13, the <avr/iotn13a.h> file is not loaded.
One can counter that by adding the following definitions:
#define PRR _SFR_IO8(0x25) #define PRADC 0 #define PRTIM0 1
and for the BOD CONTROL Register
#define BODCR _SFR_IO8(0x30) #define BPDSE 0 #define BPDS 1
However, activating the PRR register or closing the BOD did not change the power consumption of my attiny13A.
Note: if you are using the MicroCore for the attiny13 you may get a vector 8 error. That is because the WDT is already used to set millis(). You then need to disable that in the core_settings.h file. If you installed the core via the boards manager you are most likely to find it here: user\xxxxxxx\AppData\Local\Arduino15\packages\MicroCore\hardware\avr\1.0.2\cores\microcore