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:
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.