Launchpad LED Chaser

LED chasers are the Hello World of microcontrollers. Here’s mine for the TI Launchpad.

Download source and Makefile here.

The MSP430G2231 doesn’t have enough timers or hardware Pulse Width Modulation channels for a good chaser effect. I want brightness control of 8 independent channels.

My solution is to use a single timer and Binary Code Modulation. BCM uses less processor time than PWM but doesn’t provide a single on and a single off period per cycle. Instead, it uses several bursts. This is fine for LEDs, where given a fast enough period the human eye can’t tell.

The reason for BCM’s efficiency is that the on/off periods are directly related to the binary representation of the desired duty cycle. Here’s a great tutorial.

LEDs really need a diffuser. I’m using an opaque plastic bowl.

(it looks like this without the bowl)

The circuit is simple. 8 LEDs to P1 (via resistors).

#include 
#include 

#define     LED_DIR               P1DIR
#define     LED_OUT               P1OUT
#define     TICKS_PER_SEC (1000000 / TACCR0)    // 1MHz clock
#define     LED_WRAP8(n) (((n)+8)%8)

// #define PINGPONG 1 // Do the Knight Rider thing

volatile static uint8_t led_brightness[8] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
volatile static uint32_t sys_ticks = 0;

static void cpu_init(void)
{
    WDTCTL = WDTPW + WDTHOLD; // Stop WDT

    BCSCTL1 = CALBC1_1MHZ; // Set range
    DCOCTL = CALDCO_1MHZ; // SMCLK = DCO = 1MHz
}

// Binary Code Modulation
static void bcm_tick(uint8_t led_ticks)
{
    uint8_t bcm = 0x00;

    switch(led_ticks)
    {
        case 0x1:
        case 0x2:
        case 0x4:
        case 0x8:
        case 0x10:
        case 0x20:
        case 0x40:
        case 0x80:
            // led_ticks is a power of 2
            if (led_brightness[0] & led_ticks)
                bcm |= BIT0;
            if (led_brightness[1] & led_ticks)
                bcm |= BIT1;
            if (led_brightness[2] & led_ticks)
                bcm |= BIT2;
            if (led_brightness[3] & led_ticks)
                bcm |= BIT3;
            if (led_brightness[4] & led_ticks)
                bcm |= BIT4;
            if (led_brightness[5] & led_ticks)
                bcm |= BIT5;
            if (led_brightness[6] & led_ticks)
                bcm |= BIT6;
            if (led_brightness[7] & led_ticks)
                bcm |= BIT7;

            LED_OUT = bcm;
    }
}

// Timer0 ISR
interrupt(TIMERA0_VECTOR) TIMERA0_ISR(void)
{
    sys_ticks++;
    bcm_tick(sys_ticks);
}

int main(void)
{
    uint32_t t = 0; // time of last pattern change
    uint8_t pos = 0;  // LED index of head of pattern
    int8_t dir = -1;  // direction of motion

    cpu_init();

    // Setup LED pins
    LED_DIR |= 0xFF; // All LED pins as outputs
    LED_OUT = 0x00; // Turn off LEDs

    // TimerA SMCLK in UP mode
    TACTL = TASSEL_2 | MC_1;
    // Enable interrupt for TACCR0 match
    TACCTL0 = CCIE;
    // Set TACCR0, starts timer
    TACCR0 = 80;    // This must be short enough to look good and long enough for TIMER0_ISR to complete

    eint();

    while(1)
    {
        // to set an LED to a brightness level, set led_brightness[index] = 0 to 255

        if (sys_ticks - t > (TICKS_PER_SEC/8))  // pattern will repeat at 1Hz (8 LEDs)
        {
            t = sys_ticks;

            led_brightness[pos] = 0xFF;
            led_brightness[LED_WRAP8(pos-dir)] = 0x7F;
            led_brightness[LED_WRAP8(pos-(dir*2))] = 0x3F;
            led_brightness[LED_WRAP8(pos-(dir*3))] = 0x1F;
            led_brightness[LED_WRAP8(pos-(dir*4))] = 0xF;
            led_brightness[LED_WRAP8(pos-(dir*5))] = 0x7;
            led_brightness[LED_WRAP8(pos-(dir*6))] = 0x3;
            led_brightness[LED_WRAP8(pos-(dir*7))] = 0x1;

            pos = LED_WRAP8(pos+dir);
#ifdef PINGPONG
            if (pos == 0)
                dir = -dir;
            if (pos == 7)
                dir = -dir;

            pos += dir;
#endif
        }
    }
}