1
/* Copyright (c) 2006, Joerg Wunsch
4
Redistribution and use in source and binary forms, with or without
5
modification, are permitted provided that the following conditions are met:
7
* Redistributions of source code must retain the above copyright
8
notice, this list of conditions and the following disclaimer.
10
* Redistributions in binary form must reproduce the above copyright
11
notice, this list of conditions and the following disclaimer in
12
the documentation and/or other materials provided with the
15
* Neither the name of the copyright holders nor the names of
16
contributors may be used to endorse or promote products derived
17
from this software without specific prior written permission.
19
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
POSSIBILITY OF SUCH DAMAGE. */
31
/* $Id: asmdemo.dox 1368 2007-05-06 05:05:02Z arcanum $ */
33
/** \defgroup asmdemo Combining C and assembly source files
36
For time- or space-critical applications, it can often be desirable to
37
combine C code (for easy maintenance) and assembly code (for maximal
38
speed or minimal code size) together. This demo provides an example
41
The objective of the demo is to decode radio-controlled model PWM
42
signals, and control an output PWM based on the current input signal's
43
value. The incoming PWM pulses follow a standard encoding scheme
44
where a pulse width of 920 microseconds denotes one end of the scale
45
(represented as 0 % pulse width on output), and 2120 microseconds mark
46
the other end (100 % output PWM). Normally, multiple channels would
47
be encoded that way in subsequent pulses, followed by a larger gap, so
48
the entire frame will repeat each 14 through 20 ms, but this is
49
ignored for the purpose of the demo, so only a single input PWM
52
The basic challenge is to use the cheapest controller available for
53
the task, an ATtiny13 that has only a single timer channel. As this
54
timer channel is required to run the outgoing PWM signal generation,
55
the incoming PWM decoding had to be adjusted to the constraints set by
58
As PWM generation toggles the counting direction of timer 0 between up
59
and down after each 256 timer cycles, the current time cannot be
60
deduced by reading TCNT0 only, but the current counting direction of
61
the timer needs to be considered as well. This requires servicing
62
interrupts whenever the timer hits \e TOP (255) and \e BOTTOM (0) to
63
learn about each change of the counting direction. For PWM
64
generation, it is usually desired to run it at the highest possible
65
speed so filtering the PWM frequency from the modulated output signal
66
is made easy. Thus, the PWM timer runs at full CPU speed. This
67
causes the overflow and compare match interrupts to be triggered each
68
256 CPU clocks, so they must run with the minimal number of processor
69
cycles possible in order to not impose a too high CPU load by these
70
interrupt service routines. This is the main reason to implement the
71
entire interrupt handling in fine-tuned assembly code rather than in
74
In order to verify parts of the algorithm, and the underlying
75
hardware, the demo has been set up in a way so the pin-compatible but
76
more expensive ATtiny45 (or its siblings ATtiny25 and ATtiny85) could
77
be used as well. In that case, no separate assembly code is required,
78
as two timer channels are avaible.
80
\section asmdemo_hw Hardware setup
82
The incoming PWM pulse train is fed into PB4. It will generate a pin
83
change interrupt there on eache edge of the incoming signal.
85
The outgoing PWM is generated through OC0B of timer channel 0 (PB1).
86
For demonstration purposes, a LED should be connected to that pin
87
(like, one of the LEDs of an STK500).
89
The controllers run on their internal calibrated RC oscillators, 1.2
90
MHz on the ATtiny13, and 1.0 MHz on the ATtiny45.
92
\section asmdemo_code A code walkthrough
94
\subsection asmdemo_main asmdemo.c
96
After the usual include files, two variables are defined. The first
97
one, \c pwm_incoming is used to communicate the most recent
98
pulse width detected by the incoming PWM decoder up to the main loop.
100
The second variable actually only constitutes of a single bit,
101
<tt>intbits.pwm_received</tt>. This bit will be set whenever the
102
incoming PWM decoder has updated <tt>pwm_incoming</tt>.
104
Both variables are marked \e volatile to ensure their readers will
105
always pick up an updated value, as both variables will be set by
106
interrupt service routines.
108
The function \c ioinit() initializes the microcontroller peripheral
109
devices. In particular, it starts timer 0 to generate the outgoing
110
PWM signal on OC0B. Setting OCR0A to 255 (which is the \e TOP value
111
of timer 0) is used to generate a timer 0 overflow A interrupt on the
112
ATtiny13. This interrupt is used to inform the incoming PWM decoder
113
that the counting direction of channel 0 is just changing from up to
114
down. Likewise, an overflow interrupt will be generated whenever the
115
countdown reached \e BOTTOM (value 0), where the counter will again
116
alter its counting direction to upwards. This information is needed
117
in order to know whether the current counter value of \c TCNT0 is to
118
be evaluated from bottom or top.
120
Further, \c ioinit() activates the pin-change interrupt \c PCINT0 on
121
any edge of PB4. Finally, PB1 (OC0B) will be activated as an output
122
pin, and global interrupts are being enabled.
124
In the ATtiny45 setup, the C code contains an ISR for \c PCINT0. At
125
each pin-change interrupt, it will first be analyzed whether the
126
interrupt was caused by a rising or a falling edge. In case of the
127
rising edge, timer 1 will be started with a prescaler of 16 after
128
clearing the current timer value. Then, at the falling edge, the
129
current timer value will be recorded (and timer 1 stopped), the
130
pin-change interrupt will be suspended, and the upper layer will be
131
notified that the incoming PWM measurement data is available.
133
Function \c main() first initializes the hardware by calling
134
\c ioinit(), and then waits until some incoming PWM value is
135
available. If it is, the output PWM will be adjusted by computing
136
the relative value of the incoming PWM. Finally, the pin-change
137
interrupt is re-enabled, and the CPU is put to sleep.
140
\subsection asmdemo_project project.h
142
In order for the interrupt service routines to be as fast as possible,
143
some of the CPU registers are set aside completely for use by these
144
routines, so the compiler would not use them for C code. This is
145
arranged for in <tt>project.h</tt>.
147
The file is divided into one section that will be used by the assembly
148
source code, and another one to be used by C code. The assembly part
149
is distinguished by the preprocessing macro \c __ASSEMBLER__ (which
150
will be automatically set by the compiler front-end when preprocessing
151
an assembly-language file), and it contains just macros that give
152
symbolic names to a number of CPU registers. The preprocessor will
153
then replace the symbolic names by their right-hand side definitions
154
before calling the assembler.
156
In C code, the compiler needs to see variable declarations for these
157
objects. This is done by using declarations that bind a variable
158
permanently to a CPU register (see \ref faq_regbind). Even in case
159
the C code never has a need to access these variables, declaring the
160
register binding that way causes the compiler to not use these
161
registers in C code at all.
163
The \c flags variable needs to be in the range of r16 through r31 as
164
it is the target of a <em>load immediate</em> (or \c SER) instruction
165
that is not applicable to the entire register file.
168
\subsection asmdemo_isrs isrs.S
170
This file is a preprocessed assembly source file. The C preprocessor
171
will be run by the compiler front-end first, resolving all \c \#include,
172
\c \#define etc. directives. The resulting program text will then be
173
passed on to the assembler.
175
As the C preprocessor strips all C-style comments, preprocessed
176
assembly source files can have both, C-style (<tt>/* ... *</tt><tt>/</tt>,
177
<tt>// ...</tt>) as well as assembly-style (<tt>; ...</tt>) comments.
179
At the top, the IO register definition file <tt>avr/io.h</tt> and the
180
project declaration file <tt>project.h</tt> are included. The
181
remainder of the file is conditionally assembled only if the target
182
MCU type is an ATtiny13, so it will be completely ignored for the
185
Next are the two interrupt service routines for timer 0 compare A
186
match (timer 0 hits \e TOP, as OCR0A is set to 255) and timer 0
187
overflow (timer 0 hits \e BOTTOM). As discussed above, these are kept
188
as short as possible. They only save \c SREG (as the flags will be
189
modified by the \c INC instruction), increment the \c counter_hi
190
variable which forms the high part of the current time counter (the
191
low part is formed by querying \c TCNT0 directly), and clear or set
192
the variable \c flags, respectively, in order to note the current
193
counting direction. The \c RETI instruction terminates these
194
interrupt service routines. Total cycle count is 8 CPU cycles, so
195
together with the 4 CPU cycles needed for interrupt setup, and the 2
196
cycles for the RJMP from the interrupt vector to the handler, these
197
routines will require 14 out of each 256 CPU cycles, or about 5 % of
198
the overall CPU time.
200
The pin-change interrupt \c PCINT0 will be handled in the final part
201
of this file. The basic algorithm is to quickly evaluate the current
202
system time by fetching the current timer value of \c TCNT0, and
203
combining it with the overflow part in \c counter_hi. If the counter
204
is currently counting down rather than up, the value fetched from
205
\c TCNT0 must be negated. Finally, if this pin-change interrupt was
206
triggered by a rising edge, the time computed will be recorded as the
207
start time only. Then, at the falling edge, this start time will be
208
subracted from the current time to compute the actual pulse width seen
209
(left in \c pwm_incoming), and the upper layers are informed of the
210
new value by setting bit 0 in the \c intbits flags. At the same time,
211
this pin-change interrupt will be disabled so no new measurement can
212
be performed until the upper layer had a chance to process the current
216
\section asmdemo_src The source code
220
<li><a href="../examples/asmdemo/asmdemo.c">asmdemo.c</a></li>
221
<li><a href="../examples/asmdemo/project.h">project.h</a></li>
222
<li><a href="../examples/asmdemo/isrs.S">isrs.S</a></li>
227
The source code is installed under
229
\texttt{\$prefix/share/doc/avr-libc/examples/asmdemo/},
231
where \texttt{\$prefix} is a configuration option. For Unix
232
systems, it is usually set to either \texttt{/usr} or