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

8
Files: [attiny44a_blink](https://gitlab.cba.mit.edu/erik/attiny44a_blink)
Erik Strand's avatar
Erik Strand committed
9

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

{{< 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
{{< /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.

43 44
![](/img/08_led_on.jpg#c)

45 46

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

48
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
49

50
{{< highlight c >}}
Erik Strand's avatar
Erik Strand committed
51 52 53
#include <avr/io.h>

#define led_pin (1 << PB2)
54
#define button_pin (1 << PA7)
Erik Strand's avatar
Erik Strand committed
55 56 57 58 59

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

60 61
    // Configure button_pin as an input.
    DDRA &= ~button_pin;
Erik Strand's avatar
Erik Strand committed
62

63 64
    // Activate button_pin's pullup resistor.
    PORTA |= button_pin;
Erik Strand's avatar
Erik Strand committed
65 66 67

    while (1) {
        // Turn on the LED when the button is pressed.
68
        if (PINA & button_pin) {
Erik Strand's avatar
Erik Strand committed
69 70 71 72 73 74 75 76 77
            // Turn off the LED.
            PORTB &= ~led_pin;
        } else {
            PORTB |= led_pin;
        }
    }

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

80
<video controls src="/img/08_button_contact.mp4"></video>
81 82 83 84 85 86 87 88 89

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

90
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.
91

92
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 &asymp; 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.)
93 94 95 96 97 98 99

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

#define led_pin (1 << PB2)

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

104 105 106 107 108 109 110
    // 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.
111
    // 250 * 200 * 1024 / 20M ~ 2.5s
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
    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;
}
{{< /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)
138
#define button_pin (1 << PA7)
139 140

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

145 146 147 148 149 150
    // Set the 8 bit timer's prescaler to 1/1024.
    TCCR0B |= 0b00000101;

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

151 152 153 154 155 156
    // Configure button_pin as an output.
    DDRA &= ~button_pin;

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

157 158 159 160 161
    int bouncy_switch_state = 0;
    int debounced_switch_state = 0;

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

168
        // If it's been 10ms or more since PA7 changed state,
169 170
        // it's not bouncing.
        if (TCNT0 >= 195) {
171
            // It's been 10ms since the switch changed.
172 173
            if (bouncy_switch_state != debounced_switch_state) {
                debounced_switch_state = bouncy_switch_state;
174
                // If the button is newly pressed, toggle the LED.
175 176 177 178 179 180 181 182 183 184
                if (debounced_switch_state == 0) {
                    PORTB ^= led_pin;
                }
            }
        }
    }

    return 0;
}
{{< /highlight >}}
Erik Strand's avatar
Erik Strand committed
185

186 187
<video controls src="/img/08_button_toggle.mp4"></video>