A Valentine Example for Blinking Lights

 In embedded C, Embedded Systems, hard hacks, MSP430

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:

  1. Racing a light up and down an array (lines 131 to 145)
  2. Count down (lines 151 to 156)
  3. Arbitrary time and intensity (lines 172 to 179)
  4. 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

valentine rocket.c:

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
}

Leave a Comment

Contact Us

We're not around right now. But you can send us an email and we'll get back to you, asap.

Start typing and press Enter to search