08_embedded_programming.md 6.17 KB
Newer Older
Erik Strand's avatar
Erik Strand committed
1 2 3 4 5 6 7 8 9
+++
title = "Embedded Programming"
date = "2018-10-19"
menu = "main"
weight = 9
+++

Files: [blink.c](/designs/07_blink.c)

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

Erik Strand's avatar
Erik Strand committed
12

13
### Turning on the LED
Erik Strand's avatar
Erik Strand committed
14

15 16 17
Two weeks ago I added an LED and a button to Neil's [hello world](http://academy.cba.mit.edu/classes/embedded_programming/index.html#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.

{{< highlight c >}}
Erik Strand's avatar
Erik Strand committed
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
#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;
}
34 35 36 37 38 39 40 41 42 43 44
{{< /highlight >}}

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

 - Are you using the right pin? Yours may not be attached to port `PB2`.
 - Is the LED mounted the correct way? The line should be on the ground side.
 - Are your solder joints smooth and shiny?
 - Is your schematic ok? Your LED will need a resistor of an appropriate value (I used 499 ohms) in parallel.


### Using the Button as a Contact Switch
Erik Strand's avatar
Erik Strand committed
45

46
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.
Erik Strand's avatar
Erik Strand committed
47

48
{{< highlight c >}}
Erik Strand's avatar
Erik Strand committed
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
#include <avr/io.h>

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

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

    // Configure switch_pin as an input.
    DDRA |= switch_pin;

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

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

    return 0;
}
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
{{< /highlight >}}


### 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](https://www.arduino.cc/en/Tutorial/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 step is to set the prescalar. Page 8 of the datasheet gives the options. My ATTINY is clocked by a 20MHz resonator, so each clock cycle is only 50 nanoseconds. So 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.)

{{< highlight c >}}
#include <avr/io.h>

#define led_pin (1 << PB2)

int main(void) {
    // 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) {
                // Toggle the LED state.
                PORTB ^= led_pin;
                count = 0;
            }
        }
    }

    return 0;
}
{{< /highlight >}}


#### 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.

{{< highlight c >}}
#include <avr/io.h>

#define led_pin (1 << PB2)

int main(void) {
    // Set the 8 bit timer's prescaler to 1/1024.
    TCCR0B |= 0b00000101;

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

    int bouncy_switch_state = 0;
    int debounced_switch_state = 0;

    // Toggle the LED when the button is pressed.
    while (1) {
        // Use the timer to count how long it's been
        // since we've seen switch_pin change state.
        if ((PINA & switch_pin) != bouncy_switch_state) {
            bouncy_switch_state = PINA & switch_pin;
            TCNT0 = 0;
        }

        // If it's been 10ms since PA7 changed state,
        // it's not bouncing.
        if (TCNT0 >= 195) {
            // If the button is in a new state,
            // it's been pressed (or released).
            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;
}
{{< /highlight >}}
Erik Strand's avatar
Erik Strand committed
172