Embedded Programming

Files: attiny44a_blink

Time to lose the training wheels. This week we’re writing our own microcontroller code.

Turning on the LED

Two weeks ago I added an LED and a button to Neil’s echo board, so I’ll start by making the LED turn on. I attached my LED to port PB2, and my switch to port PA7. To turn on the LED, we only need to do two things: enable PB2 as an output, and set it high. Here’s code that does just that.

#include <avr/io.h>

#define led_pin (1 << PB2)

int main(void) {
    // Configure led_pin as an output.
    DDRB |= led_pin;

    // Set led_pin high.
    PORTB |= led_pin;

    // Nothing left to do, so just spin.
    while (1) {}

    return 0;
}

If this doesn’t work after programming, there are the usual suspects to check.

Using the Button as a Contact Switch

The easiest approach is to illuminate the LED only for the duration of the button press. To do this we’ll need to configure pin PA7 as an input. I didn’t add a pullup resistor on my board, so I also have to enable PA7’s internal one. This will ensure that PA7 reads high when the button isn’t pressed.

#include <avr/io.h>

#define led_pin (1 << PB2)
#define button_pin (1 << PA7)

int main(void) {
    // Configure led_pin as an output.
    DDRB |= led_pin;

    // Configure button_pin as an input.
    DDRA &= ~button_pin;

    // Activate button_pin's pullup resistor.
    PORTA |= button_pin;

    while (1) {
        // Turn on the LED when the button is pressed.
        if (PINA & button_pin) {
            // Turn off the LED.
            PORTB &= ~led_pin;
        } else {
            PORTB |= led_pin;
        }
    }

    return 0;
}

Using the Button as a Toggle

Another way to use the button is to toggle the state of the LED. So each time we press it, the LED will switch from on to off, or from off to on. For this to work reliably we’ll have to debounce the the signal we read on PA7. I’d like to use a timer for this purpose, so first let’s make sure we can enable and read a timer at all.

Testing a Timer

Let’s ignore the button for now, and use a timer to blink the LED. As the ATTINY44A datasheet describes in chapters 11 and 12, there are two timers we can use: one 8 bit, and one 16 bit. The primary difference is that the 8 bit timer can only count to 255, while the 16 bit timer can count up to 65535. To debounce the button input, I shouldn’t need to time anything longer than 10ms, so I bet I can make the 8 bit timer work.

The only initialization steps are to set the clock and timer prescalers. Page 31 of the datasheet gives the options for the former. We’ll use the full 20MHz provided by the resonator. Page 8 gives the options for the timer. I’ll use a prescaler of 1024 so that my timer ticks on the order of microseconds instead of nanoseconds. Reading the timer, or setting it to a particular value, is easy: just use the TCNT0 register.

There’s only one issue left: even though we scale the clock down by a factor of 1024, the maximum amount of time the timer can record is 255 * 1024 / 20,000,000 = 0.013056 seconds. If we turn the timer off and on with this period, we might notice it’s dimmer than before but we’ll never be able to see it change from on to off or vice versa. To get around this I’ll let the timer tell me when 195 * 1024 / 20,000,000 ≈ 10ms have passed, and I’ll only toggle the LED when this happened 250 times. So we should see the LED blink every 2.5 seconds. (The time to evaluate the additional instructions is negligible compared to the amount of time we’re waiting for the timer.)

#include <avr/io.h>

#define led_pin (1 << PB2)

int main(void) {
    // Set the clock prescaler to 1.
    CLKPR = (1 << CLKPCE);
    CLKPR = (0 << CLKPS3) | (0 << CLKPS2) | (0 << CLKPS1) | (0 << CLKPS0);

    // Set the 8 bit timer's prescaler to 1/1024.
    TCCR0B |= 0b00000101;

    // Configure led_pin as an output.
    DDRB |= led_pin;

    // Blink the LED with ~2.5s half-period.
    // 250 * 200 * 1024 / 20M ~ 2.5s
    int count = 0;
    while (1) {
        // If it's been 10ms, reset the timer and increment count.
        if (TCNT0 >= 200) {
            TCNT0 = 0;
            // When count reaches 250, reset it and toggle the LED.
            if (++count == 250) {
                PORTB ^= led_pin;
                count = 0;
            }
        }
    }

    return 0;
}

Debouncing with a Timer

Now that we can use the timer let’s go back to the button. We’ve seen all the pieces of the next block of code before, so I’ll let the comments do the talking.

#include <avr/io.h>

#define led_pin (1 << PB2)
#define button_pin (1 << PA7)

int main(void) {
    // Set the clock prescaler to 1.
    CLKPR = (1 << CLKPCE);
    CLKPR = (0 << CLKPS3) | (0 << CLKPS2) | (0 << CLKPS1) | (0 << CLKPS0);

    // Set the 8 bit timer's prescaler to 1/1024.
    TCCR0B |= 0b00000101;

    // Configure led_pin as an output.
    DDRB |= led_pin;

    // Configure button_pin as an input.
    DDRA &= ~button_pin;

    // Activate button_pin's pullup resistor.
    PORTA |= button_pin;

    int bouncy_switch_state = 0;
    int debounced_switch_state = 0;

    while (1) {
        // Use the timer to count how long it's been
        // since button_pin changed state.
        if ((PINA & button_pin) != bouncy_switch_state) {
            bouncy_switch_state = PINA & button_pin;
            TCNT0 = 0;
        }

        // If it's been 10ms or more since PA7 changed state,
        // it's not bouncing.
        if (TCNT0 >= 195) {
            // It's been 10ms since the switch changed.
            if (bouncy_switch_state != debounced_switch_state) {
                debounced_switch_state = bouncy_switch_state;
                // If the button is newly pressed, toggle the LED.
                if (debounced_switch_state == 0) {
                    PORTB ^= led_pin;
                }
            }
        }
    }

    return 0;
}