BlinkFromScratch ProjectProject DescriptionBlink is the "Hello World" of the microcontroller world. In this project I want to show the hardware setup for a blink project, not much there, and two software approaches, the simple Arduino 8 liner and a WinAVR built from scratch. A follow on project with RS232 communication to the PC can be found at Blink Talks to PC ProjectProject Hardware SetupI am using the BareBones Board Rev C on a bread board. Our current BBB is a Rev D. The main advantage of Rev. D is that you don't have to press the reset button before loading a program. Also I will be using a P3 RS232 adapter. Our current adapter is the P4 with the extra pin to handle the automatic reset. For both the Arduino code, which is their sample code, and the WinAVR code, I need to add the LED between pin 13 and ground. I have added a second LED between pin 12 and ground as well as a push button between pin 8 and ground. This last LED and push button are for just a bit added to the WinAVR BlinkFromScratch code. The LEDs that I am using are those with internal resistors that I got from PHAnderson. One less thing when you're working with a breadboard. The push button is a RadioShack 275-1547, normally open momentary switch. I solder a lead to each of its terminals and it fits the breadboard quite well. As you can see, I save trimmed leads from other projects to use with breadboards.
Project SoftwareI want to show you both the Arduino code and the WinAVR code so that you know what Arduino is doing for you under the covers. 1. Ports and PinsBoth sets of code will blink the LED at pin 13. Notice how the Arduino code calls it pin 13 (int ledPin = 13;). If you look at the BareBones Board, sure enough that pin is labeled 13. But that's just Arduino's name for it. If you traced it back to the ATmega168 chip, it is pin 19. But the code underneath, both assemblar and C know it as Port B, pin 5. That's why in the WinAVR code you talk about Port B and use bits to point to pin 5. In the Arduino code setup it says pinMode(ledPin, OUTPUT); Remember ledPin = 13. This says that pin 13 is output. In WinAVR you say DDRB = 0x30; data direction register for port B set pins high. Which ones? Well, hex 30 is binary 00110000. Start from right to left and the left most is pin 0. Pins 0 = 3 are set to 0. Pins 4 and 5 are set to 1 and pins 6 and 7 are 0. That means that pins 4 and 5 are output and the rest are input only. We added pin 4 because we'll do something else with it in the WinAVR program. The Arduino code will then set the pin (13) high, wait a second, set the pin low, wait a second and loop. The WinAVR code sets it on and off by turning the 5 bit in Port B on and off. The _BV(5) creates a byte with all bits set to zero except bit 5 (0010 0000). It then ORs it with Port B so that nothing is changed in Port B except bit 5 if it is 0. To set it off you take a ones complement ~_BV(5) (1101 1111) and AND that with Port B. So that will change nothing in Port B except force bit 5 to 0 if it is not. 2. Clocks and TimersThe Arduino code sits in a loop turning the LED on, waiting a second, turning it off, waiting a second and so forth. It's very handy to have the delay(1000) function for your timing. But what is really happening? How does the delay know a second has gone by? It uses one of the chip's timer/clocks. The ATmega chips tend to have 3 timers that you can use, 0, 1 and 2. So with the WinAVR code I use timer 0. The ATmega168 with its resonator runs at 16 MHz. That's a bit fast so I used register TCCR0B to divide the frequency by 1024. Now you have a clock running at 15625 Hz, still kind of fast. What it does it increment a counter (TCNT0) 15625 times a second. But the counter can only hold 256, then it overflows. Every time it overflows it send an interrupt (TIMER0_OVF_vect) saying, "I'm full, what do you want me to do?" So what we do is start the counter at 100 so it only counts 156 before overflowing. That gives you an interrupt that occurs 100 times a second. Now what you do is have an interrupt handler waiting for the interrupt, in our case ISR (TIMER0_OVF_vect). A hundred times a second he gets called so he has a variable (time_count) that he uses as a counter. Each time he's called he increments it by one. When the counter reaches 100 he does his thing and resets time_count to 0. So there's your one second timer from scratch. 3. Loops - Polling vs InterruptsIf you look at the "main" loops that the Arduino code and the WinAVR code are doing they are quite different. The Arduino code is doing the work. It turns the LED on, waits a second, turns the LED off, waits a second, and goes back and does it all over again. The WinAVR code, on the other hand does absolutely nothing. It starts a forever loop while (1) and the only thing in the loop is a semicolon. All the work is being done in the interrupt service routine - ISR. The key things to get interrupts to work are
4. InputsFor the WinAVR code we have added a push button to control the second LED. This is where polling vs interrupts really comes to play. Do you sit back and wait for an interrupt or do you periodically go out and check its status? In our case, even though the code is in an interrupt handler, we are polling the status of the push button every second. The interrupt handler is for the timer, not the external event of the push button. We could have set up a handle and enabled an interrupt for an external event. I didn't because of something called debouncing. Whenever you do a switch you don't get a clean change of state. In the micro seconds that you turn a switch on there is a lot of on/off going on, remember the chip is running at 16 MHz. So you need hardware, software or a combination of both to debounce the switch. By polling the switch one time only every second I have definitely debounced the switch. Arduino Example
/*
* Blink
*
* The basic Arduino example. Turns on an LED on for one second,
* then off for one second, and so on... We use pin 13 because,
* depending on your Arduino board, it has either a built-in LED
* or a built-in resistor so that you need only an LED.
*
* http://www.arduino.cc/en/Tutorial/Blink
*/
int ledPin = 13; // LED connected to digital pin 13
void setup() // run once, when the sketch starts
{
pinMode(ledPin, OUTPUT); // sets the digital pin as output
}
void loop() // run over and over again
{
digitalWrite(ledPin, HIGH); // sets the LED on
delay(1000); // waits for a second
digitalWrite(ledPin, LOW); // sets the LED off
delay(1000); // waits for a second
}
WinAVR Example
//TIMER0 interrupt routine
#include < inttypes.h >
#include < avr/io.h >
#include < avr/interrupt.h >
#include < avr/sleep.h >
#define ONE_SEC 100
void initPorts (void);
void initTimer (void);
ISR (TIMER0_OVF_vect);
static unsigned int time_count; // counts overflows
/* * * * * * * * * * * * * * * * * * * * *
main will call the initialization for ports and timer,
then sit in a loop waiting for the timer interrupt.
* * * * * * * * * * * * * * * * * * * * */
int main (void) {
initPorts();
initTimer();
while (1) {
; // do nothing
}
return 0;
}
/* * * * * * * * * * * * * * * * * * * * *
We will put the LEDs on BBB pins 12 and 13 --> PortB 4:5
We will put the push button on pin 8 --> PortB 0 and set it low
* * * * * * * * * * * * * * * * * * * * */
void initPorts (void) {
DDRB = 0x30; //set two middle bits (4,5) for output
PORTB |= _BV(5); // blink LED starts on
}
/* * * * * * * * * * * * * * * * * * * * *
We will set the timer up for 1 second counts.
The ATmega168 has a 16 MHz resonator.
Use prescalar to divide it by 1024 giving 15625 Hz
Set TCNT0 to overflow 100 times a second by starting it at 100.
I guess the output compare register needs to be set, but we are in normal mode.
Unmask the Timer 0 overflow interrupt and enable interrupts
* * * * * * * * * * * * * * * * * * * * */
void initTimer (void) {
TCCR0B = 0x05; // set TIMER0 prescalar to clk/1024
TCNT0 = 100; // set TCNT0 to start at 100 giving 156 counts to overflow
OCR0A = 0x00; // output compare register initialized but not used.
TIMSK0 = 0x01; // unmask TIMER0 overflow interrupt
sei(); // enable interrupts
time_count = 0; // init to no overflows
}
/* * * * * * * * * * * * * * * * * * * * *
Every overflow interrupt increment the counter.
If we have reached the 1 second count, do the things.
* * * * * * * * * * * * * * * * * * * * */
ISR (TIMER0_OVF_vect) {
TCNT0 = 100; //set for timeout
++time_count; // increment the loop counter
if (time_count == ONE_SEC) { // have we reached the 1 second count?
if (PORTB & _BV(5)) { // is output bit 5 on??
PORTB &= ~_BV(5); // clear bit, turn LED off
} else {
PORTB |= _BV(5); // set bit, turn LED on
}
time_count = 0; // reset the loop counter
if ((PINB & _BV(0)) == 0) { // is input bit 0 low, button pushed??
if (PORTB & _BV(4)) {
PORTB &= ~_BV(4); // clear bit, turn LED off
} else {
PORTB |= _BV(4); // set bit, turn LED on
}
}
PORTB |= _BV(0); // reset the push button high, not pushed.
}
}
|