Friday, November 12, 2010

Breathing LED effect with the LaunchPad!

Have you noticed how the blinking LED of the MacBooks causes a soothing, kind of hypnotic effect? No wonder why...

Apparently, the behavior of the LED while the computer is sleeping is tuned to resemble the human breathing rhythm at rest! Things like these give a better understanding of Apple's meticulous attention to detail and how much of your MacBook's $999 went into patent attorne-- ALL GLORY TO THE BREATHING LED!

The breathing effect is a natural step forward from the dull and classic "hello world" blinking LED. If you want to add it to your project, you need a spare Timer and a few lines of code. Here is how:

The guys at Adafruit reversed the waveform and, to me, it resembles something like this:

We are going to use PWM to drive the LED and change its brightness according to the above graph.

Since math operations are expensive on such a limited instruction set, we'll start off with a pre-calculated sine table. We only need the "breath in" portion of the wave (between -PI/2 and 0) as we can easily calculate the "breath out" side by mirroring the wave:

#include <io.h>
#include <signal.h>

int index = 0;

const unsigned char curve[] = {
1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 2, 2, 2, 2, 2,
2, 2, 3, 3, 3, 3, 3, 3,
4, 4, 4, 4, 4, 5, 5, 5,
5, 6, 6, 6, 6, 7, 7, 7,
8, 8, 8, 8, 9, 9, 9, 10,
10, 10, 11, 11, 11, 12, 12, 13,
13, 13, 14, 14, 15, 15, 15, 16,
16, 17, 17, 18, 18, 18, 19, 19,
20, 20, 21, 21, 22, 22, 23, 23,
24, 24, 25, 25, 26, 26, 27, 27,
28, 29, 29, 30, 30, 31, 31, 32,
33, 33, 34, 34, 35, 36, 36, 37,
38, 38, 39, 39, 40, 41, 41, 42,
43, 43, 44, 45, 46, 46, 47, 48,
48, 49, 50, 50, 51, 52, 53, 53,
54, 55, 56, 56, 57, 58, 59, 59,
60, 61, 62, 62, 63, 64, 65, 66,
66, 67, 68, 69, 70, 70, 71, 72,
73, 74, 75, 75, 76, 77, 78, 79,
80, 80, 81, 82, 83, 84, 85, 86,
87, 87, 88, 89, 90, 91, 92, 93,
94, 95, 95, 96, 97, 98, 99, 100,
101, 102, 103, 104, 105, 106, 106, 107,
108, 109, 110, 111, 112, 113, 114, 115,
116, 117, 118, 119, 120, 121, 122, 122,
123, 124, 125, 126, 127, 128, 129, 130,
131, 132, 133, 134, 135, 136, 137, 138,
139, 140, 141, 142, 143, 144, 145, 146,
147, 148, 149, 150, 151, 152, 153, 154,
155, 156
};

 

Now, let's do all the necessary set-up. This is common to most projects: stop the watchdog and set MCLK at 1 MHz:

int main(void)
{
WDTCTL = WDTPW + WDTHOLD;
DCOCTL= 0;
BCSCTL1= CALBC1_1MHZ;
DCOCTL= CALDCO_1MHZ;

We are going to use SMCLK as the clock source. We'll set it up so that it uses MCLK / 8, that's 125 KHz (see SLAU144E p.5-15):

  BCSCTL2 |= DIVS_3;

Next, we'll make pin 1.6, which is hooked up to the green LED, an output (SLAU144E p.8-3). We also set pin 1.6 to follow TA0.1 (timer A's output, more on it below). Per SLAS694C p.41:

  P1DIR |= BIT6;
P1SEL |= BIT6;

Now we'll set up Timer A to count up to 625. At 125 KHz, that will give us a period of 5 milliseconds, or a frequency of 200 Hz. 200 Hz is high enough to avoid flicker. Higher values will flicker less, but waste more power since we're waking up the CPU more often. So... set TACCR0 to 625:

  TACCR0 = 625;

Ok, let's start Timer A. We'll source it from SMCLK (by setting TASSEL_2) and run it in "up" mode (MC_1). In "up" mode, the timer counts up to TACCR0 and then starts again from 0 (SLAU144E p.12-20):

  TACTL = TASSEL_2 | MC_1;

This is where the PWM magic happens: as it turns out, the LED brightness is proportional to its duty cycle (ie. the ratio of time it stays lit on each period). Remember we set pin 1.6 to follow "timer A's output"? Now, here's where we define how that output behaves. We configure it so that it's high (set) when it counts to TACCR1 and low (reset) when it counts to TACCR0. That's what OUTMOD_7 does. So, by giving TACCR1 values within the [0..625] range, we'll make the LED glow more or less (the higher, the brighter). CCIE enables interrupts every time the timer hits TACCR1. We will use the interrupt to update the TACCR1 value according to the breathing curve.

  TACCTL1 = OUTMOD_7 | CCIE;

Set the initial TACCR1 to 0 (the LED is fully off):

  TACCR1 = 0;

Now put the CPU to sleep. We are just waiting for interrupts here. The return is kind of silly (we're never returning anywhere) but keeps gcc happy.

  __bis_SR_register(CPUOFF | GIE);
return 0;
}

Finally, our ISR (interrupt service routine). It is called once every 5 ms period, right after timer A hits TACCR1. We just count to 1000. From 0..499 we read the table forward and from 500-999 we read it backward. 1000 times 5 milliseconds gives a 5 seconds breathing rate, which is quite close to people's rate at rest:

// This will be called when timer counts to TACCR1.
interrupt(TIMERA1_VECTOR) ta1_isr(void)
{
int new_ccr1 = 1;

// Clear interrupt flag
TACCTL1 &= ~CCIFG;

if (index < 500) {
new_ccr1 = curve[index++ >> 1];
} else if (index < 1000) {
new_ccr1 = curve[(999 - index++) >> 1];
} else {
index = 0;
}

We are almost done. When setting a new TACCR1 value, we need to wait until TAR (the timer counter) has gone past it, otherwise we'll hit TACCR1 twice or more in the same period!

  while (TAR <= new_ccr1) ;
TACCR1 = new_ccr1;
}

That's it. I hope this helps anyone getting started with timers and PWM on the MSP430 family!

The whole source code for the file is here:

http://code.google.com/p/osx-launchpad/downloads/list

Unzip it. Then, run 'make install' to download it to your LaunchPad.

 

30 comments:

  1. This is really helpful for beginners. It's hard to find nice simple stuff for the MSP430 for people coming from the Arduino

    ReplyDelete
  2. You do realize that Apple has a patent on the breathing status light...

    ReplyDelete
  3. Really cool idea, will need to try it out. Good first step for PWM test as next I was going to try to drive some motor for robotics.

    I like your source code formating - how did you do it? :)

    BTW, I wrote article about using Launchpad to read Digital Caliper just in case you'r wondering what to do next with your Launchpad :)

    ReplyDelete
  4. @Anonymous... Yeah, but I think the patent only claims the use in laptop computers, since I've seen this effect in other places (the Roomba vacuum has a similar pattern), even on christmas lights, etc.

    @Maris... Nice article on the caliper. If you mean the sine table, I generated it separately. The key to formatting is printf("%3d"), if you were curious about that :)

    ReplyDelete
  5. Thanks. Not that important, but I was refering to how quoted source code looks in your blog - different background and red dashed border.

    ReplyDelete
  6. @Maris: the CSS for that:


    body{ text-align: justify; }
    pre {
    font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace;
    color: #000;
    background-color: #eee;
    font-size: 12px;
    border: 1px dashed #990000;
    line-height: 14px;
    padding: 5px;
    overflow: auto;
    width: 100%;
    text-indent: 0px;
    }

    ReplyDelete
  7. Just wanted to say Thank you.

    ReplyDelete
  8. Nice tutorial, really cool for beginners, thanks!

    ReplyDelete
  9. Thanks for the great information. I 'ported' your code over to CCSv4 using the Grace plugin to set the registers. The code is posted at https://github.com/GWDeveloper/msp430-Breathe-with-Grace if anyone is interested.

    ReplyDelete
  10. Didn't know about Grace... Thanks for the link. Makes it look so much simpler.

    ReplyDelete
  11. Hi Javi -

    I'm having an issue installing the breathing demo and was hoping you could shed some light on the problem:


    Arrakis:demo-breathing-led csammis$ make install
    msp430-gcc -Os -Wall -g -mmcu=msp430x2012 -c main.c
    main.c:5: warning: built-in function ‘index’ declared as non-function
    msp430-gcc -Os -Wall -g -mmcu=msp430x2012 -o main.elf main.o
    mspdebug rf2500 "prog main.elf"
    dyld: Library not loaded: /usr/local/Cellar/libusb-compat/0.1.3/lib/libusb-0.1.4.dylib
    Referenced from: /usr/local/bin/mspdebug
    Reason: no suitable image found. Did find:
    /usr/local/lib/libusb-0.1.4.dylib: stat() failed with errno=13
    make: *** [install] Trace/BPT trap

    ----

    The directory /usr/local/Cellar doesn't exist at all. I installed libusb and libusb-compat via macports...but I'm so new to OS X that I'm not sure where to go from here. Any thoughts?

    ReplyDelete
  12. Undo everything you've done and install this package:

    http://code.google.com/p/osx-launchpad/downloads/list

    Then, you'll come across another problem... Mspdebug won't be able to program the LaunchPad due to OSX driver behavior changes in the update from 10.6.5 -> 10.6.6. No one know how to fix it (yet).

    ReplyDelete
  13. Javier, I did install the osx-launchpad package first. When I attempted to program the device then I got the message pasted above. It was then that I tried to install libusb and libusb-compat from macports to try and resolve the problem.

    ...but apparently since I'm on 10.6.7 this is a lost cause? :(

    ReplyDelete
  14. Not a lost cause at all! I've resolved the issue. This error message:

    dyld: Library not loaded: /usr/local/Cellar/libusb-compat/0.1.3/lib/libusb-0.1.4.dylib

    was caused by the osx-launchpad package installing the /usr/local/lib directory with some odd permissions. It was owned by root and modded rwxr--r-- ...which means that my user could not access it. Executing "sudo chown :wheel /usr/local/lib" fixed that and mspdebug could now find the libusb libraries.

    With that done the "make install" for the demo app worked perfectly and my Launchpad is now emitting a calming pulse :) Thank you for this!

    ReplyDelete
  15. I modified it to work with TI CCS tool

    https://gist.github.com/922329

    ReplyDelete
  16. didn't get what I need to change it from green to red.
    i tried replacing BIT6 with BIT0, but it just glows, without breathing.
    These are the lines:
    // Make P1.6 (green led) an output. SLAU144E p.8-3
    P1DIR |= BIT0;

    // P1.6 = TA0.1 (timer A's output). SLAS694C p.41
    P1SEL |= BIT0;
    by the way, why the SLAs are different?

    ReplyDelete
  17. That's because the G2231 can only do hardware PWM on pins 1.2, 1.6 and 2.6. You could do "software" PWM by using two interrupt routines, one turns on the LED when TAR counts to TACCR0, and the other turns it off when TAR counts to TACCR1. It's a bit more involved because the interrupt for TACCR0 is multiplexed, but you can base it off of this article, which explains software PWM with the LaunchPad.

    The SLAs are different because one is generic for the whole MSP430 family and the other is specific to the G2231.

    ReplyDelete
  18. So does this not work on 10.6.7 and 10.6.8?

    ReplyDelete
  19. agg23: if you read my comments it does work on 10.6.7, and it turns out that it also works on 10.6.8.

    But mspdebug does NOT appear to work on 10.7!

    msp430-gcc -Os -Wall -g -mmcu=msp430x2012 -I../includes -c main.c
    msp430-gcc -Os -Wall -g -mmcu=msp430x2012 -I../includes -o main.elf main.o
    mspdebug rf2500 "prog main.elf"
    MSPDebug version 0.12 - debugging tool for MSP430 MCUs
    Copyright (C) 2009, 2010 Daniel Beer
    This is free software; see the source for copying conditions. There is NO
    warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

    Trying to open interface 1 on 005
    rf2500: can't claim interface: Permission denied
    rf2500: failed to open RF2500 device
    make: *** [install] Error 255

    ReplyDelete
  20. What about "sudo mspdebug ..."?

    ReplyDelete
  21. Javi: No difference. It looks like the kernel extension meant to stop OS X from taking over the Launchpad device when it is plugged in does not properly work with 10.7

    I use these instructions: http://mspdebug.sourceforge.net/faq.html#rf2500_osx

    ReplyDelete
  22. FWIW I recompiled the source linked from the mspdebug site using XCode 4.1 and the Mac OS 10.7 base SDK, then installed the resulting kext using the instructions for the distributed version. No luck.

    ReplyDelete
  23. Hey Javi - I got the kext properly updated for 10.7 and mspdebug works again!

    The new extension is at https://github.com/csammis/csammis-MSP430/tree/master/ez430rf250-kext ... maybe you could make a new bundle for the OS X toolchain with this driver? It would help a lot of people who want to use the Launchpad on their Macs.

    ReplyDelete
  24. Thanks for this blog!! Works like a charm.

    ReplyDelete
  25. Anyone see this, OSX vcp drivers for the Launchpad. Looks like it is stable now:
    http://www.43oh.com/2011/10/launchpad-osx-usb-cdc-vcp-driver-released/

    ReplyDelete
  26. Anonymous: that's awesome news. I have just released a new version of the toolchain installer. It works really well with PentiumPC's driver and comes with the latest uniarch gcc, which supports a greater variety of MSP430 chips. See the top post!

    ReplyDelete
  27. Thank you for this good work.

    To keep gcc happy, you can change in build scripts

    msp430-gcc -Os -Wall -g -mmcu=$MCU -o breathing-led-$MCU.elf breathing-led.c

    to

    msp430-gcc -Os -Wall -g -mmcu=$MCU -Wno-main -o breathing-led-$MCU.elf breathing-led.c


    In breathing-led.c, you can change

    int main(void)

    to

    void main(void)

    and you can remove the silly return 0 in the main function.

    Thanks for all.

    Xavier Thomassin

    ReplyDelete
  28. Xavier: great, I didn't know that trick.

    ReplyDelete
  29. Ever heard about the makefiles ? ;)
    Btw, does MSP have PWM hardware generator like AVRs ?

    ReplyDelete
  30. Greg: people installing this package might not have 'make' in their computers. And not that you would need it for single file projects anyway.

    And yes, the MSP on the launchpad does have hardware PWM. That's what the breathing led demo uses.

    ReplyDelete