ATtiny85: Blinking Without Clock Cycles


In the previous post we learned how to blink an LED with an ATtiny85 by using a _delay_ms() function to halt the program execution after turning on and off the LED. The downside to this approach is that the _delay_ms() causes our code to hang up while the function spins in a loop up to the specified time. This is a waste of CPU clock cycles, and makes doing anything else in the event loop nearly impossible. Let’s stop the pointless spinning.

In this post I will introduce the two timer/counter hardware peripherals that are inside the ATtiny85 chip, and show how we can offload the job of blinking an LED to one of them with the added benefit of using no clock cycles on the task. This frees the CPU up to do whatever else we wish while our LED reliably blinks away. If you are coming from a typical Arduino upbringing, this sort of flexibility and power is what makes learning how to truly program a microcontroller and its peripherals worth the effort.

The ATtiny85 comes with two built in timers, timer/counter 0 and timer/counter 1. Both of these timers have different features, so knowing which will suit our project goals is important. We will need to look at the datasheet to get an idea of what each timer is capable of and how to set them up to do what we want.

I am going to specify our goal as blinking an LED at a frequency of 2 Hz, that is on for 250 ms, and off for 250 ms, which is what we did last time.

Let’s first consider timer 0, which is an 8 – bit timer. Timer/counter 0 has a mode called CTC mode, which allows us to toggle a GPIO line every time the counter reaches the specified compare value in an 8-bit register. If we connect our LED to the GPIO line that is toggled, we would be able to turn it on and off at a regular interval. Sounds promising!

The timer/counter 0 has a setting that determines how fast it counts up to the compare value we specified. The system clock is configured to the default 1 MHz, so each clock cycle has a period of 1/1,000,000 s = 1 µs. Since this counter is an 8 bit counter, we can only count up to an 8 bit compare number before we toggle the LED, so with a 1MHz clock this limits us to a maximum on/off period of 255*1 µs = 0.000255 s = 0.255 ms, which is not going to work since we want an on/off period of 250 ms. There is what is called a prescaler that we can select, which will divide down the clock frequency from the system clock frequency to a much lower frequency, thus making it take more time to count up to our compare value. For timer/counter 0 we can divide the clock by 0, 8, 64, 256, and 1024. If we use a prescaler of 1024 to divide the provided system time used by the timer to 1 MHz / 1024, this gives a period of 1024 ms between every count up to the compare value. So if we set the compare value as 244, we have a total period between toggling the LED on and off of 244*(1 / (1MHz / 1024) ) = 0.249856 s = 249.856 ms, which is very close to our goal of 250 ms.

Lets briefly consider the situation of needing a period between toggling of 500 ms. With timer 0, and the max prescaler of 1024, we can get a maximum period between toggling the LED of 255*(1 / (1MHz / 1024) ) = 0.26112 s = 261.12 ms. This means that we cannot solely use timer/counter 0 for this task. Luckily timer/counter 1 has not only more available prescalers but larger ones as well. So if you needed to toggle an LED with a smaller frequency/larger period), timer/counter 1 would be your best bet.

Let’s settle on using timer/counter 0 for our task, and consider how we can get this up and running in hardware and code.

We will need to connect our programmer as before, but this time our LED needs to be connected to OC0A, which is pin 5 or PB0 on the ATtiny85.

Above is the wiring schematic we will use. Let’s consider the code.

#include <avr/io.h>

static inline void initTimer0(void)
{
  TCCR0A |= (1 << COM0A0);             // toggle OC0A output on compare match
  TCCR0A |= (1 << WGM01);              // clear counter on compare match
  TCCR0B |= (1 << CS02) | (1 << CS00); //clock prescaler 1024
  OCR0A = 244;
}

int main(void) 
{
  // inits

  initTimer0();
  DDRB |= (1 << PB0); 

  
  // event loop
  while (1) 
  {
     
  }                                                
  return (0);                           
}

 

As before we include io.h for the use of our macros to the timer registers.

The initTimer0() function is what sets up timer/counter 0 to do exactly as we specified above: toggle the output line on a compare match of a specified compare value, clear the counter when the compare match is made, thus resetting it, and divide the system clock speed of 1 MHz to what we need for the counter to count at the slower speed.

Inside the datasheet there is chapter 11 which is dedicated to the 8 bit timer/counter 0. From this chapter we can find which bits to set in which register to configure the timer/counter.

Scrolling down we find the register TCCR0A, which is the timer/counter control register A.

From table 11-2 we can see that by setting the COM0A0 bit in TCCR0A, we will toggle OC0A on a compare match with the value in the register OCR0A. To enable this feature we write the bit to the register with the line TCCR0A |= (1  << COM0A0);

From table 11-5 we see that to enable the clear timer on compare match (CTC) mode we need to write the WGM01 bit to TCCR0A as well, which we do in the line TCCR0A |= (1  << WGM01);


Scrolling down further we find the register TCCR0B, which is the timer/counter control register B. By setting certain bits in this register we can set the prescaler that will divide down the system clock by to extend the period of time between counting up to the compare value. From table 11-6 we see that we need to set bits CS02 and CS00 in the register to get a prescaler of 1024, which we do in the line TCCR0B |= (1 << CS02) | (1 << CS00).

Finally we set our compare value OCR0A to 244, so that pin OC0A is toggled on a compare match of the counter value and the LED blinks on and off at the right frequency. That completes our timer0 initialization function.

Next we enter our main() function where we call the initTimer0() function and then set the pB0 pin on the data direction register B (DDRB) as an output, which will let timer/counter 0 toggle the voltage for our LED.

That is it! We can see that our infinite while loop that had code in it before is now empty and free to do other things. That is the beauty of offloading work to a dedicated hardware peripheral; we can now do something else with our clock cycles.

From a logic analyzer, the frequency of my setup with this code is 2.046 Hz with a total period of 0.4914s. This is a tad off from what we told it to do, but this is just a consequence of relying on the internal RC oscillator with 10% factory tolerance. If we wanted a spot on frequency we would need to attach and configure an external crystal oscillator.

To recap, we can use the included timer/counters in the ATtiny85 to offload tasks such as toggling a pin. This frees up what would have been wasted clock cycles used to delay, and allows us to build more robust and capable projects. Enjoy your clock cycles.

Advertisements

2 thoughts on “ATtiny85: Blinking Without Clock Cycles

  1. Absolutely great article overall, thanks so much for posting it! A couple of typos though. I hope this doesn’t sound too nit-picky, I just know I would want to know if I made them on my blog.

    In the 6th paragraph (it starts “The timer/counter 0”), you say you want the period to be 255ms instead of the 250 you meant to say. In the code example, you have initTimer1 instead of initTimer0. Also in the code example, I think you should be using a prescaler of 1024, not 2048.

    Again, great work and thanks for posting.

    Liked by 1 person

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 )

Google+ photo

You are commenting using your Google+ 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 )

w

Connecting to %s