A Valentine Example for Blinking Lights
For Valentine’s Day my son needed to make a “mailbox” for his kindergarten class. He & his mother made a rocket out of an old oat container. Since this was a family project, I decided that my contribution would be lighting it up. My son was quite excited when I told him that I was putting lights on it and it would have a computer to control them. Being sharp as a tack, he quickly asked, “Daddy, where will the keyboard go?” I had to explain that it would be a little computer without a keyboard, just a button. He was still excited.
Hardware
I decided to use an EZ430-T2012 target board (MSP430F2012) that I had laying around to run the LEDs. It had just enough pins for what I needed. I had thought about using a piezospeaker for rocket sounds, but that was more than I wanted to bite off, and it took an extra pin. I decided that I was going to run the processor off of two AA batteries. I used the 2AA battery holder from one of the EZ430-RF2500 kits that I had. You can skip strait to the explanation of the code or continue reading to see the thinking that went into my design choices.
Design Decisions
How Many Lights and Which Pins
The next decision I had was where to put the lights and what did I want to do with them. I decided that I wanted a light on the nose cone, lights on the rocket nozzles, and a light next to each letter of his name. I had almost enough pins to work with that. I tied all the rocket nozzle lights to one pin to get the number of pins needed down. My next decision was which lights I wanted dimmable. Technically I could do that with a loop inside the processor, but I wanted to use the PWM function of the timer to simplify the code. To use the timer, I needed to hook up those lights to pins that can be attached to the Timer_A2 output. The other consideration I had was that I wanted to keep all the letters of his name on P1 pins so that I could use one statement when I changed the lights next to his name (e.g. P1OUT |=
…).
LED & Resistor Choices
The LED choices were pretty simple, what thru-hole LEDs did I have sitting around. In choosing the resistors for them, I remembered that the GPIO output voltage is 0.3V less than the VDD or 0.3 V higher than VSS. Most of the LEDs that I have are rated for 20 mA current. Most LEDs have a forward voltage drop of Vf = 1.7 to 1.9 V. Doing the math, this meant I needed a 30 Ohm resistor to put in series. I didn’t have any that small, and the pins can’t really sink or source that much current without a voltage droop… let I would be drawing too much current for the chip if I had all of them on. So, I used 100 Ohm resistors that I had available. I wouldn’t be driving the LEDs at their maximum current, but the chip could handle their draw.
Timer & PWM Setup
This is a discussion in and of itself. There’s a few things about human biology one needs to know when dealing with light. We view light logarithmically, not linearly. If you didn’t catch the last statement, let me explain. On your ruler, you have 12 inches. Each inch is the same size. On a logarithmic scale, the steps are not a uniform size. Each step might be twice the size of the prior one. Where this catches people working with the timer to dim LEDs is the timer is linear, but our eyes are logarithmic. If you want to “linearly” brighten an LED, you might increase the duty cycle by 5% over 20 steps. How you would view this is the LED brightens quickly at first, but then quickly slows down the pace at which it brightens. To brighten the LED in a way that looks linear, we need to make each step larger than the last. The easiest way to do this is with a bit shift using the <<
or >>
operators. Each shift left doubles the duty cycle. Each shift right halves it. It’s easy, but now we only have n steps, where p is the period of the timer. The formula for n is n = log2p. This gives us a maximum of 17 steps with a 16 bit timer… except we don’t really get that many steps because we won’t be able to see the first several steps in daylight, or even a normally lighted room. And on the top end, we need to have our clock for the timer running fast enough that our timer is running >= 60 Hz so that we don’t see the pulsing.
Since I do not need two PWM outputs, I have the booster and nose cone lights both setup on Timer_A2.TA1 outputs. They are connected on an as needed basis in the code. Look for PxSEL bits being set in the code to enable those. Also, since the booster LEDs are setup using negative logic, the PWM mode is changed for it (TACCTL1 = OUTMOD_3;
).
What does the Button do?
How do I want user interface (UI or button) to work? At first I had it start the light cycle from sleep. Then I realized that five and six year olds are impatient so I had the button reset the light cycle so they didn’t have to wait the minute plus that the light cycle takes. In so doing I have the program enter LPM4 (lowest of the low power modes for this chip) after the light cycle. I originally had it entering LPM4 after initialization, but that would mean that I would have to push the button twice, once to reset and once to wake up, which just made things too complicated.
Code Explanation & Gotchas
Casting values
Through out the code there are many integers suffixed with “U”. This is because using a signed value would have caused a sign change (or overflow) that the compiler doesn’t like and screws with your code. Several values are also cast to unsigned long
to make sure that the intermediate math works out correctly. Without it some of the longer loops have problems.
Compiler Definitions
Compiler #define
are used for almost everything. This is really useful because changing the pin that a certain LED is on does not require changing 100 different entries. Don’t use variables because they take up precious memory space.
Constant Arrays
When a series of arbitrary intensities and times were needed, constant arrays were used (around line 45 to 82). These are stored in flash memory as part of the program and don’t use up RAM. Using them also keeps the code tighter and easier to read. It also keeps your data separate from your execution code.
Clock Setup
We used the calibrated 1 MHz DCO as our master and sub master clocks (lines 93 to 95). Upping the clock speed to about 8 MHz would have given us some more range in the timer, but we were constrained on the low end by what we could see.
Pin Setup
The booster and cone LEDs were connected to two different pins which both may connect to Timer_A2.TA1. The nose cone LED was connected to P2.7 instead of one of the TA1 outputs on P1 so that all the LEDs next to the name would be on P1. The booster LEDs were setup using negative logic because many chips can sink more current than they can source. Since I had three LEDs tied together, I did this. According to the data sheet, it really doesn’t make a difference with this processor.
With the timer set to run off SMCLK (line 112), it was determined empirically that 1 << 12
was the longest period that we could run without the PWM becoming noticeable (line 113).
Lighting Patterns
There are a few different lighting patterns used in the code:
- Racing a light up and down an array (lines 131 to 145)
- Count down (lines 151 to 156)
- Arbitrary time and intensity (lines 172 to 179)
- Ramping up and down intensity (lines 190 to 204)
Main Loop
That is an artifact from initial plans to have the switch wake and start the lights again. It was decided that we wanted the light cycle to be interruptable. To do this easily, we just have the switch interrupt reset the CPU whenever it runs (lines 225 to 229). So, in reality, the main loop only runs once.
Gotchas
The biggest gotcha was the switch. OK, it was keeping the switch working. In an earlier version of the code, LPM4 was entered at the start of the main loop. The switch worked the first time. It never worked the second time. I spent a lot of time pouring through the code and after sleeping on it, I figured out what the problem was. The last place where I turned of the LEDs on P2, I was also changing the pull-up resistor to a pull-down resistor. So, the switch pin was being pulled low, which meant that when the switch was depressed and shorted the pin to ground… nothing happened. Fixing the resistor setting fixed the switch problem. So really, the gotcha was not writing readable code. That was fixed by using the compiler definitions.
Example Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 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 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 | /**************************************** * * Code to Run Charlie's Rocket Lights * * By Chad Kidder * Copyright 2011 * Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License *****************************************/ #include "msp430x20x2.h" #define MSEC (unsigned long)(100U) //Number of times to run the _nop() loop to get //1 ms of delay. #define NAMEIDEL (unsigned long)(65U) //Number of ms between cycling lights when //going between individual letters #define NAMESDEL (unsigned long)(1000U) //Number of ms between cycling lights when //counting down letters #define NTOP (3) //Number of times to cycle top light at end of sequence #define PWM_PERIOD (1 << 12) //PWM Period #define TOP_NSTEPS (30) // Number of steps in Top LED intensity #define TOP_STEP (PWM_PERIOD / TOP_NSTEPS) //Duty Cycle step size #define TOP_STEP_TIME (unsigned long)(100U) //Time at each step in ms #define PINT (0x40) //Pin Corresponding to Top LED on P2 #define PINC (0x01) //Pin Corresponding to C LEDs #define PINH (0x02) //Pin Corresponding to H LEDs #define PINA (0x08) //Pin Corresponding to A LEDs #define PINR (0x10) //Pin Corresponding to R LEDs #define PINL (0x20) //Pin Corresponding to L LEDs #define PINI (0x40) //Pin Corresponding to I LEDs #define PINE (0x80) //Pin Corresponding to E LEDs #define PINB (0x04) //Pin Corresponding to Booster LEDs #define PINSW (0x80) //Pin Corresponding to SWITCH on P2 /**************************************** * Ideally, PINT and PINB are tied to the timer * output so they can be dimmed durring operation ***************************************/ #define PINALL (PINC + PINH + PINA + PINR + PINL + PINI + PINE) //Light up the whole name #define LEDSUMLEN (7) //Number of elements in LEDsum array #define LEDINDVLEN (7) //Number of elements in LEDindv array //Array for cumulative cycling through the name LEDS const unsigned short LEDsum[LEDSUMLEN] = { PINALL, PINALL & ~PINC, PINALL & ~(PINC + PINH), PINALL & ~(PINC + PINH + PINA), PINALL & ~(PINC + PINH + PINA + PINR), PINALL & ~(PINC + PINH + PINA + PINR + PINL), PINE}; //Array for cycling through individual LEDs of his name const unsigned short LEDindv[LEDINDVLEN] = { PINC, PINH, PINA, PINR, PINL, PINI, PINE}; #define BOOSTER_STEPS (30) // Number of Steps in Booster Cycle const unsigned int BoostPWM[BOOSTER_STEPS] = {12, 0, 12, 0, 12, 0, 12, 0, 12, 0, 3, 4, 5, 6, 7, 8, 5, 3, 5, 8, 10, 12, 11, 10, 9, 8, 6, 5, 4, 3}; //Durration in ms of each booster setting const unsigned int BoostDur[BOOSTER_STEPS] = {100, 1000, 100, 1000, 100, 1000, 100, 1000, 100, 100, 100, 100, 100, 100, 1000, 100, 500, 100, 100, 100, 4000, 400, 400, 400, 400, 400, 400, 1000, 1000, 1000}; void main(void) { int ii, ll; //counter variables unsigned long jj; //counter variable //Setup WDTCTL = WDTPW + WDTHOLD;// + WDTNMI + WDTNMIES; // WDT off NMI hi/lo //Setting MCLK to 1 MHz BCSCTL1 = CALBC1_1MHZ + DIVA_0 +XT2OFF; // Set range, VLO = ACLK ~ 10.4kHz DCOCTL = CALDCO_1MHZ; BCSCTL2 = SELM_0 + DIVM_0; // MCLK= DCOCLK, SMCLK = DCOCLK BCSCTL3 = LFXT1S_2; //Setting LF clock to VLO; //Setting up Pins P1DIR = 0xff; //All Output P1SEL = 0; //None connected to PWM at start P1OUT = PINB; //All PINS low to start PINB is wired negative P2SEL = 0x00; //All GPIO at start P2DIR = ~PINSW; //All P2 are set as Output except for P2.7 which has switch on it P2REN = PINSW; //Add pull-up resistor to P2.7 P2OUT = PINSW; //All P2 are set low and P2.7 Resistor is up P2IE = PINSW; //Enable P2 Interupt for PINSW P2IES = PINSW; //High Edge P2IFG = 0; //Clearing flags _BIS_SR(GIE); // Enable Interrupts //Setup Timer_A2 for PWM output on Timer_A2.TA1 output TACTL = TASSEL_2 + MC_0; //SMCLK, Stop Timer TACCR0 = PWM_PERIOD; //PWM Period TACCR1 = 0; //PWM Duty Cycle for (;;) //Main infinite loop { P2IE = PINSW; //Enable P2 Interupt for PINSW P1OUT = PINB; // Making sure lights are off //next step //Blink Name for ( ii = 0; ii<6; ii++) { P1OUT ^= PINALL; for (jj = 0; jj < (200U * MSEC); jj++) _nop(); } P1OUT = PINB; // Making sure lights are off //next step //Race lights up/down name for (ll = 0; ll < 4; ll++) { for (ii = 0; ii < LEDINDVLEN; ii++) { P1OUT |= LEDindv[ii]; for (jj = 0; jj < (NAMEIDEL * MSEC); jj++) _nop(); P1OUT &= ~LEDindv[ii]; } for (ii = LEDINDVLEN -2; ii >0; ii--) { P1OUT |= LEDindv[ii]; for (jj = 0; jj < (NAMEIDEL * MSEC); jj++) _nop(); P1OUT &= ~LEDindv[ii]; } } //Done racing lights up/down P1OUT = PINB; // Making sure lights are off //next step //Now lighting up all lights and counting down for (ii = 0; ii < LEDSUMLEN; ii++) { P1OUT |= LEDsum[ii]; for (jj = 0; jj < NAMESDEL * MSEC; jj++) _nop(); P1OUT &= ~LEDsum[ii]; } //All lights off P1OUT = PINB; //Now starting Booster P2SEL = 0; // Detatching top light from Timer_A2.TA1 P1SEL = PINB; // Attaching booster to TIMER_A2.TA1 TACCR1 = 0; //Setting Duty cycle to 0 for start. TACTL = TASSEL_2 + MC_1; //SMCLK, Up Timer //Setting up Timer A1 output for negative Logic TACCTL1 = OUTMOD_3; //Setting for Negative Duty Cycle //Some type of cycling of LED intensity for (ii = 0; ii< BOOSTER_STEPS; ii++) { TACCR1 = 1 << BoostPWM[ii]; for (jj=0; jj < BoostDur[ii]; jj++) { for (ll = 0; ll < (unsigned int) MSEC; ll++) _nop(); } } //Booster off P1SEL = 0; // Detatching booster from Timer_A2.TA1 P2SEL = PINT; // Attaching top LED to TIMER_A2.TA1 TACCR1 = 1; //Setting Duty cycle to 0 for start. TACCTL1 = OUTMOD_7; //Positive Duty Cycle TACTL = TASSEL_2 + MC_1; //SMCLK, Up Timer //Slowly increase/decrease intensity of top LED for (ii = 0; ii < NTOP; ii++) { for (ll = 0; ll < 12; ll++) { TACCR1 <<= 1; //Double LED duty cycle for (jj=0; jj < TOP_STEP_TIME * MSEC; jj++) _nop(); } for (ll = 0; ll < 12; ll++) { TACCR1 >>= 1; //Halve LED duty cycle for (jj=0; jj < TOP_STEP_TIME * MSEC; jj++) _nop(); } //Delay during dead time for (jj=0; jj < 5000 * MSEC; jj++) _nop(); } //Make sure top LED is off P2SEL = 0; // Detatching top light from Timer_A2.TA1 P2OUT = PINSW; // All LEDs off -- Cannot just set to 0 or switch will not work. //Make sure booster is off P1SEL = 0; // Detatching booster from Timer_A2.TA1 P1OUT = PINB; TACTL = TASSEL_2 + MC_0; //Stop Timer_A2 //Make sure name LEDs are off //End loop //Sleep until wakeup or reset P2IFG &= ~PINSW; // Switch Pin Cleared P2IE = PINSW; //Enable P2 Interupt for PINSW _BIS_SR(LPM4_bits + GIE); // Enter LPM4 w/interrupt } } #pragma vector=PORT2_VECTOR __interrupt void Port_2(void) { WDTCTL = 0; //This resets the processor } |