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
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
| /*
File: prj_12.cpp
Author: Jonathan Dale Kirwan
Creation Date: Wed 03-Oct-2007 18:20:07
Last Modified: Wed 03-Oct-2007 18:20:07
Copyright 2007, 2009, Jonathan Dale Kirwan, jonk@infinitefactors.org
This source code module is distributed under the terms of the GNU General
Public License (aka GNU GPL.)
You should have received a copy of this license as COPYING. But if not,
it can currently be accessed over the web at:
http://www.gnu.org/licenses/gpl.txt
Or receive a copy by writing to the Free Software Foundation, Inc., the
current address of which (at this time of writing) is:
51 Franklin St
Fifth Floor
Boston, MA 02110
USA
DESCRIPTION
This is project 12 -- Causing the LED to adjust its brightness in a
smoothly (to the eye) ramping fashion that proceeds up and then down,
continuing over and over, again.
This project takes on a lot of issues. If you recall from Project 11,
the LED intensity didn't appear to be increasing and decreasing in
brightness quite as smoothly as perhaps would be preferred. This was
due to the Weber-Fechner law. (See Projects 10 and 11 for a short
discussion of that law.)
Here are the steps for project 12:
(1) Compile the initial version included here and verify that
the LED appears to gradually increase and decrease in its
intensity, smoothly.
(2) Modify the program to change the value of SYSCLK to 1MHz
and LEDRATE to 3. This should make the LED appear to
distinctly flicker during the rising and falling cycle, so
that you can see what is going on underneath the hood.
(3) Return SYSCLK to 16MHz and set LEDRATE to 100. Now play
with the BLUNT parameter. Change it to 0.5, for example,
and compare it with the appearance when you use 0.01.
There is a lot to explore in this program. I am holding back from writing
a long discussion about the design, because at this point readers should
have already been through the other 11 projects _and_ should be able to
sift through the differences found here and Project 11, itself, to see
what has changed and to study just that part of it. However, I haven't
completely ignored some discussion and the DESIGN section below here does
talk about most of what's needed to follow the code arrangement.
DESIGN
The problem to solve is varying the LED intensity smoothly as the human
eye perceives it. It doesn't have to be perfect, but perhaps better than
what Project 11 achieves. The solution used here builds several concepts
upon each other, so tackle this project realizing that there is a process
in getting from A to B. Taken together with the other projects, the code
here isn't too difficult. It's not even particularly long. But it's not
entirely trivial, either, and it requires some attention to detail.
--- Non-linear human response to light ---
I've already discussed some laws to consider, in Projects 10 and 11. One
claims that humans don't perceive intensity of light, linearly, but more
as a logarithmic function of physical intensity. This is a biological
necessity. We need to use our eyes in broad daylight, at high noon, on
the equator. And we still need to use them on a dark, moonless night.
To cope with all this, our eyes have evolved responses that vary in more
of a logarithmic fashion. As the Weber-Fechner law states.
When we ask ourselves to "adjust the brightness, linearly" this should
roughly (1st order estimate) mean we need to vary the current in an LED
exponentially. (Our eyes will take the logarithm of that and that will
flatten the resulting 'curve.') This means we need to take a different
approach than just trying to vary the current in a linear way, or else
just give up on that idea and do what "comes easy." For this project,
we are going to take a path that is roughly exponential and try to do it
"right," or something near a linear ramping of "human-apparent intensity."
--- Pulse width modulation ---
Okay. So we know we have to face that problem. Is there more bad news?
Yes, there is. The LED we have on this eZ430-F2013 kit is attached to
pin P1.0 and it's digital. So we can turn the LED full-on or else turn it
off. There is no in between. So although the intent is to vary the
brightness, we don't have an apparent means to do that -- even if we want
to. So now what? Well, it turns out that human vision also "integrates"
light. By this, I mean if I pulse the LED very fast, it will "look
steady."
This is the Talbot-Plateau law, discussed in Projects 10 and 11 and which
also states that once the critical flicker fusion frequency (CFF) is
reached, the brightness will appear to be the same "as if" the light
source were steadily operated at the time-averaged luminance. In other
words, if you operate the light source at twice the luminance but only
half the time (50% on and 50% off, flickering faster than the CFF), then
it will appear to have the same luminance as that similar light source
operated at the lower luminance.
An oscilloscope will show the pulses, easily. But our eyes won't, if the
LED blinks fast enough. So we can use this fact to our advantage. If we
blink the LED on and off a bit faster than say 40-50Hz (the CFF is often
said to be about 70Hz or so for 100% modulated light), most folks won't
see the LED blinking but will instead see it somewhat dimmer. If we blink
it at 50Hz, this means for a total of 20 milliseconds per cycle. If we
turn the LED on for 10ms and turn it off for the other 10ms, then we have
achieved a 50Hz blinking rate but it is only emitting light for half the
time. Our eyes pick this up as "somewhat dimmer." If we turned it on for
1ms and turned it off for 19ms, then it would appear even dimmer, still.
In other words, we can hold the blinking rate essentially fixed (50Hz, for
example), and just vary the time we keep the LED on, within each cycle.
The fact that humans won't see the blinking and will instead turn that
into a brightness sensation can be used in our circumstance, here and now.
And that's what we will do.
Doing this is called "varying the duty cycle" or "pulse width modulation"
or PWM. It is a term you will learn to recognize, if you haven't already.
If you have a cycle time of 20ms and you turn on the LED for 15ms and off
for 5ms, then your 'on' duty cycle is said to be 75%. That's how it goes.
--- Steady, flicker, blink ---
The problem at first blush seems a simple one. Just smoothly ramp up and
down the brightness of an LED. But already there are important details
needed to more fully apprehend what that means when talking about human
vision and the fixed LED we have access to in the eZ430-F2013 kit. There
is still more. Problems in embedded programming are often like that.
Let's say you start out with an LED using a 50% duty cycle and blinking at
a rate of 100Hz. At the faster rates, the LED looks steady. As you slow
the pulse rate down, though, you gradually become aware of the pulsing.
You won't notice it all at once. Instead, there is a point where you begin
to imagine some pulsing and if you move your head back and forth you will
see a much clearer pulsing effect because you are "smearing" the pulse
across parts of your retina and that gives it a spatial as well as a time
effect, enough to let you notice better. And then easier and easier to
notice until you'd just say "it's blinking."
For me, the rate where without moving my head I tend to believe that a red
LED is no longer flickering is around 50Hz+/-6Hz and faster. If I want to
take care of cases where I'm moving my head a little, too, I'd push that to
70Hz and more, perhaps. Part of that depends on the ambient lighting, as
well. For example, 60Hz/120Hz 120VAC incandescent light itself flickers --
with perhaps 3-5% variation, that we usually don't notice. But it can beat
against a 70Hz blinking LED to construct an apparent flicker at 10Hz, that
is noticeable at rare times. For the most part, I don't notice it, though.
--- Short summary ---
We need to set up a blinking rate for the LED that is at least 50Hz. I'm
going to choose 70Hz, mostly because I talked about it above. We need to
be able to vary the duty cycle we use, as well, to adjust the brightness.
And on top of this, we need to vary that brightness in an exponential, not
linear, way.
Will there be more detail to master, yet ahead? Yes. There will be. But
we are ready to start some of the design thinking, now that we've explored
some important issues related to the problem at hand. Before I start on
that, one more thing comes to mind.
--- Side bar ---
Why would an electronics designer ask an embedded software programmer to go
to all this trouble? Well, pulsing lights tend to get peoples' attention.
Another one of those evolved traits we have. And being able to go to all
this trouble to vary the intensity smoothly instead of just ON and OFF, may
be helpful in separating this particular "indicator" from other ones on
other equipment. And if an electronics designer wanted the LED to vary
like this, they would either need to add a somewhat complex circuit to the
design -- which costs money and space on the board -- or else they may ask
you to do this in software and just "stick an LED there" for you. That is
a lot cheaper, takes less space, and is generally more reliable -- as well
as permitting additional variations on a theme without having to actually
change the board design. So yes, you might be asked to do a lot with very
little to work with. Which is part of why embedded programming requires a
broader knowledge about physics and nature and mathematics than does most
other programming areas. You should be able to read schematics, understand
physics and physical models, be trained in physics theory, adept at math at
least up through 1st and 2nd degree ordinary differential equations, and...
okay. Maybe I can relax that statement a little. But you get the idea.
We are just being asked to vary the brightness of an LED, for gosh sake. I
mean, how hard can that be? How much knowledge do you need? A lot. As
you can already see. And that's just one LED, doing one simple thing.
--- Design Time ---
The main idea here takes fuller advantage of the timer_A2 system. This has
two compare registers along with the counter, and both are required. We
will use the first compare register, together with a special timer _mode_
that counts upwards and then turns around and counts downwards, to set the
blink/flicker rate to about 70Hz. This first register sets the upper point
where the timer turns around and starts counting backwards towards zero.
With that compare register value and knowing how fast the counter is going,
we can figure the "cycle time," which is the twice the time it takes to
count upwards in the first place. We will use the second compare register
to set a threshold between 0 and the value of the first compare register,
and use the point when the timer counter rises over this second compare
register (on its way up towards the value of the first compare register) to
turn on the LED and use the point when the timer counter has turned around
and is counting downward and again crosses the value of this second compare
register, to signal when to turn the LED back off. Used well, this lets us
set the duty cycle without changing the total period. And we are fortunate
that this MSP430F2013 microcontroller's timer A just happens to have the
two compare registers we need. (That is way they call it an "A2" timer.)
All this is fine, but to vary the intensity we need to vary that second
compare register. And we need to vary it in a way that causes the human-
perception of the light to vary linearly. Which means we can't just move
that value up and down by fixed amounts. To solve this, we choose to add
or subtract a percentage of the on-time. With short on-times, when the LED
appears dim, this will increase the on-time slowly at first and then faster
later on. Which is just how it should be.
Finally, in order to avoid the appearances of a "sharp corner" of intensity
right at the point of peak brightness, when the normal behavior would be to
just sharply turn around and head back towards dimmer again, we will impose
a limit on the rate of change near the peak brightness area to "blunt that
turn-around effect" a bit. This is a brute force approach, but it gets the
job done well enough and adds only two lines of code.
More details are needed at this point. Those are -- how to actually use
the A2 timer in this MSP430F2013 microcontroller. For that, you will need
to visit the user's guide for that family of cpus. One of the many such
diversions that are often necessary in embedded programming.
TIMER A2 DETAILS:
It's probably best to include a diagram of how Timer A2 is being used. The
upper part of the diagram helps show the timer counter's up-down motion,
over time. The line made with text, showing peaks and valleys, represents
the timer counter, itself, as it counts upwards and then downwards, etc.
The timer automatically starts counting upwards when the counter reaches
zero and automatically starts counting downwards when the counter reaches
a value that is set into TACCR0. In this application, TACCR0 is set just
once and isn't adjusted afterwards. It sets the basic cycle time for the
LED pulsing. However, TACCR1 is moved around in the application in order
to cause the LED to be on and off for different periods of time.
TACCR0 ----> ^ ^ ^
/ \ / \ / \ Up-Down
/ \ / \ / \ Counter
TACCR1 ...../.....\......./.....\......./.....\... Diagram
\ / \ / \ / \
\ / \ / \ / \
\ / \ / \ / \
ZERO v v v
---|---|-----|---|---|-----|---|---|-----|---
T C C T C C T C C
A C C A C C A C C Event Names
I I I I I I I I I as Described
F F F F F F F F F in the User's
G G G G G G G G G Guide from TI
1 1 1 1 1 1
---|---|-----|---|---|-----|---|---|-----|---
ON ,-----, ,-----, ,-----,
| | | | | | LED
| | | | | | State
OFF -----' '-------' '-------' '---
time ----> ----> ----> ----> ----> ----> ----> ---->
The middle part of the diagram just shows when certain named interrupt
events take place. I used the names found in the User's Guide from Texas
Instruments on their MSP430F2xx Family.
The lower part of the diagram just shows what the LED is doing, on the
basis of these events.
You should be able to understand that if the value of TACCR1 is changed
(moved up or down) that the effect is to change the point in time when the
CCIFG1 events take place relative to the TAIFG event. This is what causes
different on/off times for the LED. So it just takes a single change to
TACCR1 to adjust the LED duty cycle.
Just keep in mind that instead of adjusting TACCR1 in a simple, linear
fashion, we adjust it instead as a geometric progression (which will result
in an exponential growth and decay given the factors of 5/4 and 3/4 that
are used in the code.) So the 'line' moves up and down at varying rates.
TARGET COMPILER
This module is designed to be compiled with the IAR C++/C compiler that
comes on a CD with the Texas Instruments "MSP320 Ultra-Low-Power MCUs
eZ430-F2013 Development Tool." This IAR compiler is also known as the
"IAR Kickstart" compiler and it can be downloaded from the web site for
Texas Instruments, on pages concerning the MSP430 processor.
IAR provides the Kickstart version of their compiler, but imposes some
limitations on your code size to encourage you to buy their full compiler
license if you try and use it for professional work. But for the purposes
of education here, the IAR Kickstart tools for the MSP430 are sufficient.
MODIFICATIONS
Original source.
*/
/* ---------------------------------------------------------------------- */
#include <msp430x20x3.h>
/* ----------------------------------------------------------------------
Include definitions appropriate for the MSP430F2013 microcontroller.
It is part of the compiler vendor's product, designed to describe key
datasheet details, and was not written separately for this project.
See the file itself for details, under the installation directory for
the IAR compiler toolset.
*/
/* ---------------------------------------------------------------------- */
#define ADJRATE (0.02)
/* ----------------------------------------------------------------------
This value selects percent change in the value of TACCR1, per unit
time. 0.02 represents 2%.
*/
/* ---------------------------------------------------------------------- */
#define LIMIT (0.001)
/* ----------------------------------------------------------------------
This value sets the minimum and maximin values for TACCR1 as a percent
of the total range. For example, 0.002 represents 0.2%, which means
that the lower limit is 0.2% and the upper limit is 99.8%. ADJRATE
is how fast TACCR1 is moved between these limits.
*/
/* ---------------------------------------------------------------------- */
#define BLUNT (0.03)
/* ----------------------------------------------------------------------
This sets a limit to how fast the brightness is allowed to increase
or decrease, near the peak brightness point. The purpose is to smooth
out the effect instead of just allowing the geometric progression to
operate without limit.
Better values are from about 1% to 10%. The value 0.03 means 3%.
*/
/* ---------------------------------------------------------------------- */
#define LEDRATE (100) // in Hertz
/* ----------------------------------------------------------------------
Choose some basic LED pulse rate here. I recommend something at 70Hz
or faster, since the LED shouldn't appear to the eye as blinking. But
other rates can be chosen.
*/
/* ---------------------------------------------------------------------- */
#define SYSCLK (16000000) // in Hertz
/* ----------------------------------------------------------------------
Defines the basic SYSCLK rate we want to use here. Use calibrated
values only, unless you are willing to set up the DCO and BC1 values
and create the appropriate SYSCLK value for them. For now, an error
is generated if SYSCLK isn't one of the standard calibration values.
If you want to add an uncalibrated mode here, you need to provide some
definitions in the #else clause where the #error statement is found.
*/
#if SYSCLK == 16000000
# define DCO (CALDCO_16MHZ)
# define BC1 (CALBC1_16MHZ)
#elif SYSCLK == 12000000
# define DCO (CALDCO_12MHZ)
# define BC1 (CALBC1_12MHZ)
#elif SYSCLK == 8000000
# define DCO (CALDCO_8MHZ)
# define BC1 (CALBC1_8MHZ)
#elif SYSCLK == 1000000
# define DCO (CALDCO_1MHZ)
# define BC1 (CALBC1_1MHZ)
#else
# error Uncalibrated SYSCLK requested.
#endif
/* ----------------------------------------------------------------------
COMPUTE USEFUL PREPROCESSOR SYMBOLS
----------------------------------------------------------------------
Don't change the following preprocessor statements unless you know
what you are doing and understand their current design. They are
here to compute proper programming values for registers used in
configuring the timer_A2 device. An error is generated if the
LEDRATE and SYSCLK cannot be simultaneously accomodated.
The purpose of each is as follows:
TICKS number of SYSCLK ticks per LED pulse period
IDVAL the SYSCLK pre-divisor to use; must be power of 2
IDCTL the TACCTL field value needed to achieve IDVAL
TAC 1 plus the required TACCR0 value
*/
#define TICKS (SYSCLK/2/LEDRATE)
#define IDVAL (1+(TICKS >> 16))
#if IDVAL > 4
# undef IDVAL
# define IDVAL (8)
# define IDCTL (ID_3)
#elif IDVAL > 2
# undef IDVAL
# define IDVAL (4)
# define IDCTL (ID_2)
#elif IDVAL == 2
# define IDCTL (ID_1)
#elif IDVAL == 1
# define IDCTL (ID_0)
#else
# error LEDRATE is negative; try a positive value.
#endif
#if (TICKS/IDVAL) > 65535
# error LEDRATE is too small for the given SYSCLK; try a lower SYSCLK.
#endif
#define TAC ((unsigned int) (TICKS/IDVAL))
#define PROXIMITY ((unsigned int) (TICKS*LIMIT/IDVAL))
#define TACADJ ((unsigned int) (TICKS*ADJRATE/IDVAL))
#define BLUNTING ((unsigned int) (TICKS*BLUNT/IDVAL))
/* ---------------------------------------------------------------------- */
int main( ) {
/* ----------------------------------------------------------------------
Project 12 starts here. See discussion in the file header for detailed
information about the project.
*/
/* Turn the watchdog timer off.
*/
WDTCTL= WDTPW | WDTHOLD;
/* Enable port pin P1.0 for output (P1.0 is the LED pin.)
*/
P1OUT &= ~0x01;
P1DIR |= 0x01;
/* Set up the processor to run at a calibrated 16MHz rate.
*/
BCSCTL1= BC1;
DCOCTL= DCO;
/* Set up the timer.
*/
TACTL= MC_0 | TASSEL_2 | IDCTL | TACLR; // reset it, first.
TACCTL0= CM_0 | CCIS_2 | SCS | OUTMOD_1; // set the mode.
TACCTL1= CM_0 | CCIS_2 | SCS | OUTMOD_1 | CCIE;
TACCR0= TAC - 1;
TACCR1= TAC - PROXIMITY - 1;
TACTL= MC_3 | TASSEL_2 | IDCTL | TAIE; // turn it on.
/* Enable interrupt events, globally, and go to sleep.
*/
__enable_interrupt( );
__low_power_mode_0( );
return 0;
}
#define TAIFG_EVENT 0x0A
#define CCIFG1_EVENT 0x02
/* ---------------------------------------------------------------------- */
#pragma vector= TIMERA1_VECTOR
__interrupt void timer_A2_CCIFG1_TAIFG( void ) {
/* ----------------------------------------------------------------------
This interrupt routine is a "collect-all" for timer A2 events, other
than a CCIFG0 event (which we aren't using and is disabled.) There
are two of these: (1) when the timer counter reaches zero (TAIFG) and
(2) when the second compare register matches the counter (CCIFG1.)
In case (1), the direction is heading back up for the beginning of a
new cycle. So we need to recompute a new threshold value for the
second compare register, TACCR1, and also record the direction.
In case (2), just toggle the LED pin.
*/
static enum { brighter, dimmer } mode= brighter;
switch ( TAIV ) {
/* -------------------------------------------------------------- */
case TAIFG_EVENT:
/* --------------------------------------------------------------
New cycle is starting. So recompute a new TACCR1 value based on
the current value and then annotate the fact that we are now on
the rising phase of a new cycle. The basic idea for recomputing
a new TACCR1 value is to multiply the on-time by 5/4ths if we are
increasing the brightness or else multiply the on-time by 3/4ths,
if we are decreasing the brightness. This isn't an even-handed
method, as the dimming period will be shorter. But it is cheaper
in terms of code and doesn't harm things much. So that's used.
*/
{
auto unsigned int tac= TACCR1;
auto unsigned int tacadj= ((TAC - tac) >> 4);
if ( tacadj > BLUNTING )
tacadj= BLUNTING;
switch ( mode ) {
case brighter:
tacadj= tac - tacadj;
if ( tacadj < tac && tacadj >= PROXIMITY )
tac= tacadj;
else {
tac= PROXIMITY;
mode= dimmer;
}
break;
case dimmer:
tacadj= tac + tacadj;
if ( tacadj < (TAC - 20) )
tac= tacadj;
else {
tac= TAC - 20;
mode= brighter;
}
break;
default:
break;
}
TACCR1= tac;
break;
}
/* -------------------------------------------------------------- */
case CCIFG1_EVENT:
/* --------------------------------------------------------------
We either turn the LED on or else off by using an XOR opration
to set the state of the LED to its current opposite.
*/
P1OUT ^= 0x01;
break;
}
return;
} |