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.
This is really helpful for beginners. It's hard to find nice simple stuff for the MSP430 for people coming from the Arduino
ReplyDeleteYou do realize that Apple has a patent on the breathing status light...
ReplyDeleteReally 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.
ReplyDeleteI 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 :)
@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.
ReplyDelete@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 :)
Thanks. Not that important, but I was refering to how quoted source code looks in your blog - different background and red dashed border.
ReplyDelete@Maris: the CSS for that:
ReplyDeletebody{ 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;
}
Just wanted to say Thank you.
ReplyDeleteNice tutorial, really cool for beginners, thanks!
ReplyDeleteDidn't know about Grace... Thanks for the link. Makes it look so much simpler.
ReplyDeleteHi Javi -
ReplyDeleteI'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?
Undo everything you've done and install this package:
ReplyDeletehttp://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).
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.
ReplyDelete...but apparently since I'm on 10.6.7 this is a lost cause? :(
Not a lost cause at all! I've resolved the issue. This error message:
ReplyDeletedyld: 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!
I modified it to work with TI CCS tool
ReplyDeletehttps://gist.github.com/922329
didn't get what I need to change it from green to red.
ReplyDeletei 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?
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.
ReplyDeleteThe SLAs are different because one is generic for the whole MSP430 family and the other is specific to the G2231.
So does this not work on 10.6.7 and 10.6.8?
ReplyDeleteagg23: if you read my comments it does work on 10.6.7, and it turns out that it also works on 10.6.8.
ReplyDeleteBut 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
What about "sudo mspdebug ..."?
ReplyDeleteJavi: 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
ReplyDeleteI use these instructions: http://mspdebug.sourceforge.net/faq.html#rf2500_osx
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.
ReplyDeleteHey Javi - I got the kext properly updated for 10.7 and mspdebug works again!
ReplyDeleteThe 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.
Thanks for this blog!! Works like a charm.
ReplyDeleteAnyone see this, OSX vcp drivers for the Launchpad. Looks like it is stable now:
ReplyDeletehttp://www.43oh.com/2011/10/launchpad-osx-usb-cdc-vcp-driver-released/
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!
ReplyDeleteThank you for this good work.
ReplyDeleteTo 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
Xavier: great, I didn't know that trick.
ReplyDeleteEver heard about the makefiles ? ;)
ReplyDeleteBtw, does MSP have PWM hardware generator like AVRs ?
Greg: people installing this package might not have 'make' in their computers. And not that you would need it for single file projects anyway.
ReplyDeleteAnd yes, the MSP on the launchpad does have hardware PWM. That's what the breathing led demo uses.