Saturday, September 21, 2019

Programming the ATmega328P Registers and Interrupts

Why Use Register Programming?


Figure 1. The registers of interest

Normally you wouldn't bother to use register programming for the Arduino family. The libraries provided with the Arduino IDE do all the heavy lifting and make it easy to program the microprocessor without knowing exactly how it works. This convenience and readability is not without a cost though and sometimes for reasons of speed, code size or power consumption you will need to get closer to the metal. An example of this is writing a flight controller for a drone. For a realtime application like this (depending on the Arduino model) you are probably going to need to directly access the I/O registers and interrupts.

I’m afraid this is a fairly tedious way to code! I will try to explain why we are selecting the values in the code, otherwise it looks like gibberish! The comment numbers (e.g. C1) are referenced in the explanation below.

Hello World AKA Blink


The hardware equivalent of Hello World is to blink a LED. To demonstrate what you can do with registers and interrupts we will start with that example. There are many different ways to write this code. The complete listing is shown below.


C1:: We are using Timer 0 which is an 8 bit timer with two independent Output Compare Units, and PWM support (see Figure 1). The PWM outputs are mapped to D5 and D6 but we don’t need these here. We want to detect when the counter reaches the value stored in the OCR0A Register. The Output Compare Registers (OCR0A and OCR0B) are compared with the Timer/Counter value and can be used to generate an Output Compare interrupt request. We can use this to toggle our LED.

Figure 2. TCCR0A & TCCR0B Registers 

The meaning of the TCCR0A & TCCR0B register bits are shown in Figure 2. You turn on the bits required in these registers to get a certain behaviour. To work out what does what, have a look at Figure 3.

Figure 3. Explanation of Register Bits

We want CTC mode 2. So the Waveform Generator Mode (WGM01) bit needs to be 1. That is:

TCCR0A = 0b00000010;

another way to write this is:

TCCR0A = (1 << WGM01);

C2:: The Timer/Counter can be clocked internally, via the pre-scaler, or by an external clock source on the T0 pin. We will use the pre-scaler set to 256. The clock source is selected by the Clock Select logic which is controlled by the Clock Select (CS) bits located in the Timer/Counter Control Register (TCCR0B). For TCCR0B to get a pre-scaler of 256, CS02 needs to be 1 (see Figure 3).

TCCR0B = 0b00000100;

or

TCCR0B = (1 << CS02);

C3:: Next we set the Output Compare Register. The number of ticks for a delay of 4ms is 250, so let's run with that.

OCR0A = 250;

C4:: When the timer/counter reaches the OCR0A number, an interrupt will be triggered by setting the OCIE1A flag in TIMSK1. We can do that by:

TIMSK0 = 0b00000010;

or

TIMSK1 = (1 << OCIE1A);

The specific interrupt vector that will be called for this CTC event is:

ISR(TIMER0_COMPA_vect)

As an aside, a list of the available AVR interrupt vectors can be found at: http://ee-classes.usc.edu/ee459/library/documents/avr_intr_vectors/

Figure 4. Port B Registers

C5:: I used digital output D13 since it is attached to the onboard LED and saves me wiring one up, but obviously you can use any output pin. Here we are using the Data Direction Register (DDR) to set D13 as an output (see Figure 4).

DDRB = 0b00100000;

C6:: We need to set the global interrupt flag to enable interrupts:

sei();

C7:: Finally, we need the Interrupt Service Routine which is called when an interrupt occurs. All we do here is toggle D13 which turns the LED on and off (very quickly)! With a LED toggling every 4ms, it just looks on all the time albeit a bit dimmer. We are effectively using PWM to dim the LED, but we want to be able to see the blinks so we use the extraTime variable to slow things down.

No comments:

Post a Comment