The software is all written in C using Codevision AVR, a pretty nice C compiler. It has a very nice wizard that lets you say that you are running your chip at 8MHz and then allows you to set (for example) the timer1 refresh rate to by 52000Hz from a pulldown menu rather than knowing that you need to set a specific register to a specific value. It also writes the shell of your interrupt servicing for you. Oh, and it knows what AVR microcontrollers support which features, so it won’t let you configure the chip to do something it can’t.
The only place where it falls down is setting the fuse bits (burned in settings for things like clock speed, whether you use the internal/external clock, and some pin assignments), which it just labels by name. For those I use AVR studio, Atmel’s free development tool. If you want to go this route there is a version of GCC that you can use.
For the base station, all of the important operations are handled in interrupt code – the main loop just loops endlessly.
Timer1 code
The timer code runs at 10Hz. We get there by the following:
The timer rate is set to 31,250Hz. Since I’m looking for 10Hz, I need a base frequency that is evenly divisible by 10. The timer is set so that an interrupt is generated whenever the timer overflows (ie goes from 0xFFFF to 0x0000). We need this to happen every 3125 counts, so we set the initial value to 0xFFFF – 0x0C35 = 0xF3CA. That gives us the heartbeat.
Here’s the code:
interrupt [TIM1_OVF] void timer1_ovf_isr(void) { // Reinitialize Timer1 value TCNT1H=TCNT1H_VALUE; TCNT1L=TCNT1L_VALUE; // read the buttons from the remote, and turn on the output channels as necessary.
HandleButtons(); if (timeRemainingTicks > 0) {
// In the last 5 minutes, blink the lights off for 1/10th of a second every minute. if ((timeRemainingTicks % 600 == 0) && (timeRemainingTicks <= 3000)) { PORTB = 0; } else { PORTB = outputState; } timeRemainingTicks--; } else { outputState = 0; PORTB = 0; }
// every second or if the state is changed, we send out the heartbeat over the serial link.
ticks++; if (outputState != outputStateLast || ticks == 10) { SendOutputState(); outputStateLast = outputState; }
// every second we blink the led for 1/10th of a second if (ticks == 10) { ticks = 0; if (outputState != 0 && timeRemainingTicks > 0) { STATUS = 1; } } else { STATUS = 0; } }
Serial port code
The interrupt handler is simple:
interrupt [USART_RXC] void usart_rx_isr(void) { char status,data; status=UCSRA; data=UDR; if ((status & (FRAMING_ERROR | PARITY_ERROR | DATA_OVERRUN))==0) { HandleChar(data); }; } char state = 0; void HandleChar(char c) { switch (state) { case 0: // ready; if (c == 'S') state = 5; break; case 5: // 'S' if (c == '1') { AllOn(); } else if (c == '0') { AllOff(); } state = 0; break; } }
HandleChar is an implementation of a finite state machine – it looks for “S0†or “S1†and performs the appropriate action.
Remote Code
The remote uses the same timer approach as the base station.
// Timer1 overflow interrupt service routine interrupt [TIM1_OVF] void timer1_ovf_isr(void) { // Reinitialize Timer1 value TCNT1H=0xF3CA >> 8; TCNT1L=0xF3CA & 0xff;
// The timeout is set to 20 ticks. If two seconds go by without
// getting a heartbeat from the base station, we turn off the link LED
if (linkDetectedTimeout > 0) { linkDetectedTimeout--; LED_LINK = LED_ON; } else { LED_LINK = LED_OFF; LED_LIGHTS = LED_OFF; currentLightState = 0; }
// Set the light LED based on the current light state if (currentLightState == 1) { LED_LIGHTS = LED_ON; } else { LED_LIGHTS = LED_OFF; }
// If the button is pressed (and it's newly pressed),
// send the appropriate on or off command. if (BUTTON == 0) { if (!buttonPressed) { buttonPressed = 1; if (currentLightState == 1) { SendString("S0"); } else { SendString("S1"); } } } else { buttonPressed = 0; }
// Flash the power LED on for one cycle out of 10 tenths++; if (tenths == 10) { tenths = 0; LED_POWER = LED_ON; } else { LED_POWER = LED_OFF; } }
Serial port
void HandleChar(char c) { putchar(c); switch (state) { case 0: // ready; if (c == 'E') state = 3; break; case 3: // "E" if (c == 'G') state = 4; else state = 0; break; case 4: // 'EG' if (c == '1') { SetLightState(1); LinkDetected(); } else if (c == '0') { SetLightState(0); LinkDetected(); } state = 0; break; } }
Another finite state machine. Whenever it gets “EG0” or “EG1”, it sets the link timeout and the appropriate light state.