This week we need to take a board we've made and give it some new functionality. No more flashing with pre-written code.
Time to lose the training wheels. This week we're writing our own microcontroller code.
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.
### Turning on the LED
```
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.
{{<highlightc>}}
#include <avr/io.h>
#include <avr/io.h>
#define led_pin (1 << PB2)
#define led_pin (1 << PB2)
...
@@ -30,11 +31,21 @@ int main(void) {
...
@@ -30,11 +31,21 @@ int main(void) {
return 0;
return 0;
}
}
```
{{</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
Next let's use the button. The easiest approach is to use the button as a contact switch, turning on 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.
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.
```
{{<highlightc>}}
#include <avr/io.h>
#include <avr/io.h>
#define led_pin (1 << PB2)
#define led_pin (1 << PB2)
...
@@ -63,6 +74,99 @@ int main(void) {
...
@@ -63,6 +74,99 @@ int main(void) {
return 0;
return 0;
}
}
```
{{</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.)
{{<highlightc>}}
#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.
{{<highlightc>}}
#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) {