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
|
/* Copyright (c) 2005,2007 Joerg Wunsch
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
* Neither the name of the copyright holders nor the names of
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE. */
/* $Id: largedemo.dox,v 1.5 2007/01/19 22:17:10 joerg_wunsch Exp $ */
/** \defgroup largedemo A more sophisticated project
\ingroup demos
This project extends the basic idea of the
\ref demo_project "simple project" to control a LED with a PWM
output, but adds methods to adjust the LED brightness.
It employs a lot of the basic concepts of avr-libc to achieve
that goal.
Understanding this project assumes the simple project has been
understood in full, as well as being acquainted with the basic
hardware concepts of an AVR microcontroller.
\section largedemo_hw Hardware setup
The demo is set up in a way so it can be run on the ATmega16 that
ships with the STK500 development kit. The only external part needed
is a potentiometer attached to the ADC. It is connected to a 10-pin
ribbon cable for port A, both ends of the potentiometer to pins 9
(GND) and 10 (VCC), and the wiper to pin 1 (port A0).
A bypass capacitor from pin 1 to pin 9 (like 47 nF) is recommendable.
\image html largedemo-setup.jpg "Setup of the STK500"
\image latex largedemo-setup.eps "Setup of the STK500" width=12cm
The coloured patch cables are used to provide various
interconnections. As there are only four of them in the STK500, there
are two options to connect them for this demo. The second option for
the yellow-green cable is shown in parenthesis in the table.
Alternatively, the "squid" cable from the JTAG ICE kit can be used if
available.
<table>
<tr><td><b>Port</b></td><td><b>Header</b></td><td><b>Color</b></td><td><b>Function</b></td><td><b>Connect to</b></td></tr>
<tr><td>D0</td> <td>1</td> <td>brown</td> <td>RxD</td> <td>RXD of the RS-232 header</td></tr>
<tr><td>D1</td> <td>2</td> <td>grey</td> <td>TxD</td> <td>TXD of the RS-232 header</td></tr>
<tr><td>D2</td> <td>3</td> <td>black</td> <td>button "down"</td> <td>SW0 (pin 1 switches header)</td></tr>
<tr><td>D3</td> <td>4</td> <td>red</td> <td>button "up"</td> <td>SW1 (pin 2 switches header)</td></tr>
<tr><td>D4</td> <td>5</td> <td>green</td> <td>button "ADC"</td> <td>SW2 (pin 3 switches header)</td></tr>
<tr><td>D5</td> <td>6</td> <td>blue</td> <td>LED</td> <td>LED0 (pin 1 LEDs header)</td></tr>
<tr><td>D6</td> <td>7</td> <td>(green)</td><td>clock out</td> <td>LED1 (pin 2 LEDs header)</td></tr>
<tr><td>D7</td> <td>8</td> <td>white</td> <td>1-second flash</td><td>LED2 (pin 3 LEDs header)</td></tr>
<tr><td>GND</td><td>9</td> <td></td> <td>unused</td></tr>
<tr><td>VCC</td><td>10</td><td></td> <td>unused</td></tr>
</table>
\image html largedemo-wiring.jpg "Wiring of the STK500"
\image latex largedemo-wiring.eps "Wiring of the STK500" width=12cm
The following picture shows the alternate wiring where LED1 is
connected but SW2 is not:
\image html largedemo-wiring2.jpg "Wiring option #2 of the STK500"
\image latex largedemo-wiring2.eps "Wiring option #2 of the STK500" width=12cm
As an alternative, this demo can also be run on the popular ATmega8
controller, or its successor ATmega88 as well as the ATmega48 and
ATmega168 variants of the latter. These controllers do not have a
port named "A", so their ADC inputs are located on port C instead,
thus the potentiometer needs to be attached to port C. Likewise, the
OC1A output is not on port D pin 5 but on port B pin 1 (PB1). Thus,
the above cabling scheme needs to be changed so that PB1 connects to
the LED0 pin. (PD6 remains unconnected.) When using the STK500, use
one of the jumper cables for this connection. All other port D pins
should be connected the same way as described for the ATmega16 above.
When not using an STK500 starter kit, attach the LEDs through some
resistor to Vcc (low-active LEDs), and attach pushbuttons from the
respective input pins to GND. The internal pull-up resistors are
enabled for the pushbutton pins, so no external resistors are needed.
Finally, the demo has been ported to the ATtiny2313 as well. As this
AVR does not offer an ADC, everything related to handling the ADC is
disabled in the code for that MCU type. Also, port D of this
controller type only features 6 pins, so the 1-second flash LED had to
be moved from PD6 to PD4. (PD4 is used as the ADC control button on
the other MCU types, but that is not needed here.) OC1A is located at
PB3 on this device.
The \c MCU_TARGET macro in the Makefile needs to be adjusted
appropriately for the alternative controller types.
The flash ROM and RAM consumption of this demo are way below the
resources of even an ATmega48, and still well within the capabilities
of an ATtiny2313.
The major advantage of experimenting
with the ATmega16 (in addition that it ships together with an STK500
anyway) is that it can be debugged online via JTAG.
Likewise, the ATmega48/88/168 and ATtiny2313 devices can be debugged
through debugWire, using the Atmel JTAG ICE mkII or the low-cost
AVR Dragon.
Note that in the explanation below, all port/pin names are applicable
to the ATmega16 setup.
\section largedemo_overview Functional overview
PD6 will be toggled with each internal clock tick (approx. 10
ms). PD7 will flash once per second.
PD0 and PD1 are configured as UART IO, and can be used to connect the
demo kit to a PC (9600 Bd, 8N1 frame format). The demo application
talks to the serial port, and it can be controlled from the serial
port.
PD2 through PD4 are configured as inputs, and control the
application unless control has been taken over by the serial
port. Shorting PD2 to GND will decrease the current PWM value,
shorting PD3 to GND will increase it.
While PD4 is shorted to GND, one ADC conversion for channel 0 (ADC
input is on PA0) will be triggered each internal clock tick, and
the resulting value will be used as the PWM value. So the
brightness of the LED follows the analog input value on PC0. VAREF
on the STK500 should be set to the same value as VCC.
When running in serial control mode, the function of the
watchdog timer can be demonstrated by typing an `r'. This will
make the demo application run in a tight loop without retriggering
the watchdog so after some seconds, the watchdog will reset the
MCU. This situation can be figured out on startup by reading the
MCUCSR register.
The current value of the PWM is backed up in an EEPROM cell after
about 3 seconds of idle time after the last change. If that EEPROM
cell contains a reasonable (i. e. non-erased) value at startup, it is
taken as the initial value for the PWM. This virtually preserves the
last value across power cycles. By not updating the EEPROM
immmediately but only after a timeout, EEPROM wear is reduced
considerably compared to immediately writing the value at each change.
\section largedemo_code A code walkthrough
This section explains the ideas behind individual parts of the code.
The \ref largedemo_src "source code" has been divided into numbered
parts, and the following subsections explain each of these parts.
\subsection largedemo_code_p1 Part 1: Macro definitions
A number of preprocessor macros are defined to improve readability
and/or portability of the application.
The first macros describe the IO pins our LEDs and pushbuttons are
connected to. This provides some kind of mini-HAL (hardware
abstraction layer) so should some of the connections be changed, they
don't need to be changed inside the code but only on top. Note that
the location of the PWM output itself is mandated by the hardware, so
it cannot be easily changed. As the ATmega48/88/168 controllers
belong to a more recent generation of AVRs, a number of register and
bit names have been changed there, so they are mapped back to their
ATmega8/16 equivalents to keep the actual program code portable.
The name \c F_CPU is the conventional name to describe the CPU clock
frequency of the controller. This demo project just uses the internal
calibrated 1 MHz RC oscillator that is enabled by default. Note that
when using the <tt><util/delay.h></tt> functions, \c F_CPU needs
to be defined before including that file.
The remaining macros have their own comments in the source code. The
macro \c TMR1_SCALE shows how to use the preprocessor and the
compiler's constant expression computation to calculate the value of
timer 1's post-scaler in a way so it only depends on \c F_CPU and the
desired software clock frequency. While the formula looks a bit
complicated, using a macro offers the advantage that the application
will automatically scale to new target softclock or master CPU
frequencies without having to manually re-calculate hardcoded
constants.
\subsection largedemo_code_p2 Part 2: Variable definitions
The \c intflags structure demonstrates a way to allocate bit variables
in memory. Each of the interrupt service routines just sets one bit
within that structure, and the application's main loop then monitors
the bits in order to act appropriately.
Like all variables that are used to communicate values between an
interrupt service routine and the main application, it is declared
\ref faq_volatile "volatile".
The variable \c ee_pwm is not a variable in the classical C sense that
could be used as an lvalue or within an expression to obtain its
value. Instead, the
\code __attribute__((section(".eeprom"))) \endcode
marks it as belonging to the \ref sec_dot_eeprom "EEPROM section".
This section is merely used as a placeholder so the compiler can
arrange for each individual variable's location in EEPROM. The
compiler will also keep track of initial values assigned, and usually
the Makefile is arranged to extract these initial values into a
separate load file (\c largedemo_eeprom.* in this case) that can be
used to initialize the EEPROM.
The actual EEPROM IO must be performed manually.
Similarly, the variable \c mcucsr is kept in the
\ref sec_dot_noinit ".noinit" section in order to prevent it from
being cleared upon application startup.
\subsection largedemo_code_p3 Part 3: Interrupt service routines
The ISR to handle timer 1's overflow interrupt arranges for the
software clock. While timer 1 runs the PWM, it calls its overflow
handler rather frequently, so the \c TMR1_SCALE value is used as a
postscaler to reduce the internal software clock frequency further.
If the software clock triggers, it sets the \c tmr_int bitfield,
and defers all further tasks to the main loop.
The ADC ISR just fetches the value from the ADC conversion, disables
the ADC interrupt again, and announces the presence of the new value
in the \c adc_int bitfield. The interrupt is kept disabled while not
needed, because the ADC will also be triggered by executing the SLEEP
instruction in idle mode (which is the default sleep mode). Another
option would be to turn off the ADC completely here, but that
increases the ADC's startup time (not that it would matter much for
this application).
\subsection largedemo_code_p4 Part 4: Auxiliary functions
The function \c handle_mcucsr() uses two \c __attribute__ declarators
to achieve specific goals. First, it will instruct the compiler to
place the generated code into the \ref sec_dot_init ".init3" section
of the output. Thus, it will become part of the application
initialization sequence. This is done in order to fetch (and clear)
the reason of the last hardware reset from \c MCUCSR as early as
possible. There is a short period of time where the next reset could
already trigger before the current reason has been evaluated. This
also explains why the variable \c mcucsr that mirrors the register's
value needs to be placed into the .noinit section, because otherwise
the default initialization (which happens after .init3) would blank
the value again.
As the initialization code is not called using CALL/RET instructions
but rather concatenated together, the compiler needs to be instructed
to omit the entire function prologue and epilogue. This is performed
by the \e naked attribute. So while syntactically, \c handle_mcucsr()
is a function to the compiler, the compiler will just emit the
instructions for it without setting up any stack frame, and not even a RET instruction
at the end.
Function \c ioinit() centralizes all hardware setup. The very last
part of that function demonstrates the use of the EEPROM variable \c
ee_pwm to obtain an EEPROM address that can in turn be applied as an
argument to \c eeprom_read_word().
The following functions handle UART character and string output.
(UART input is handled by an ISR.) There are two string output
functions, \c printstr() and \c printstr_p(). The latter function
fetches the string from \ref avr_pgmspace "program memory". Both
functions translate a newline character into a carriage return/newline
sequence, so a simple \c \\n can be used in the source
code.
The function \c set_pwm() propagates the new PWM value to the PWM,
performing range checking. When the value has been changed, the new
percentage will be announced on the serial link. The current value is
mirrored in the variable \c pwm so others can use it in calculations.
In order to allow for a simple calculation of a percentage value
without requiring floating-point mathematics, the maximal value of the
PWM is restricted to 1000 rather than 1023, so a simple division by 10
can be used. Due to the nature of the human eye, the difference in
LED brightness between 1000 and 1023 is not noticable anyway.
\subsection largedemo_code_p5 Part 5: main()
At the start of \c main(), a variable \c mode is declared to keep the
current mode of operation. An enumeration is used to improve the
readability. By default, the compiler would allocate a variable of
type \e int for an enumeration. The \e packed attribute declarator
instructs the compiler to use the smallest possible integer type
(which would be an 8-bit type here).
After some initialization actions, the application's main loop
follows. In an embedded application, this is normally an infinite
loop as there is nothing an application could "exit" into anyway.
At the beginning of the loop, the watchdog timer will be retriggered.
If that timer is not triggered for about 2 seconds, it will issue a
hardware reset. Care needs to be taken that no code path blocks
longer than this, or it needs to frequently perform watchdog resets of
its own. An example of such a code path would be the string IO
functions: for an overly large string to print (about 2000 characters
at 9600 Bd), they might block for too long.
The loop itself then acts on the interrupt indication bitfields as
appropriate, and will eventually put the CPU on sleep at its end to
conserve power.
The first interrupt bit that is handled is the (software) timer, at a
frequency of approximately 100 Hz. The \c CLOCKOUT pin will be
toggled here, so e. g. an oscilloscope can be used on that pin to
measure the accuracy of our software clock. Then, the LED flasher for
LED2 ("We are alive"-LED) is built. It will flash that LED for about
50 ms, and pause it for another 950 ms. Various actions depending on
the operation mode follow. Finally, the 3-second backup timer is
implemented that will write the PWM value back to EEPROM once it is
not changing anymore.
The ADC interrupt will just adjust the PWM value only.
Finally, the UART Rx interrupt will dispatch on the last character
received from the UART.
All the string literals that are used as informational messages within
\c main() are placed in \ref avr_pgmspace "program memory" so no SRAM
needs to be allocated for them. This is done by using the PSTR macro,
and passing the string to \c printstr_p().
\section largedemo_src The source code
\htmlonly
Source file: <a href="../examples/largedemo/largedemo.c">largedemo.c</a>
\endhtmlonly
\latexonly
The source code is installed under
\texttt{\$prefix/share/doc/avr-libc/examples/largedemo/largedemo.c},
where \texttt{\$prefix} is a configuration option. For Unix
systems, it is usually set to either \texttt{/usr} or
\texttt{/usr/local}.
\endlatexonly
*/
|