Tutorial for learning avr assembler language ofAVR-single-chip-processors AT90Sxxxx
of ATMEL using practical examples.Simple 8-bit-analog-digital-converter with PWM on the STK500 board

Task

Using the analog-inputs AIN0 and AIN1 and a few hardware components, one can build up a simple
AD-converter. The build-in analog comparator is used here to compare an input voltage with a
voltage produced with the timer/counter 1 (TC1) in PWM mode. This compare voltage is adjusted,
until the result is as accurate as 8 bit resolution requires. TC1 works as pulse-width
modulator, the pulse width relation determines the output voltage.
The conversion result is displayed on the LEDs of the STK500 board.

Required hardware

The picture on the left shows the whole test hardware to connect to the STK500
board. The port pins are viewed from above the board.
The voltage to be measured varies between 0.00 to 5.00 volt. It is setup by
the 10k potentiometer, using the board's supply voltage, and is filtered by a
100nF. This voltages goes to the analog comparator input AIN0, which is port
PB2 on the AT90S8515 of the STK500 board.
The compare voltage is generated by the PWM output OC1A, which is Port PD5 of
a AT90S8515 on the board. The PWM rectangle produced by TC1 is filtered by
three RC combinations of 10k/100nF and fed into the analog comparator input
AIN1, which is port bit PB3 of the AT90S8515. That's it all.

Some further hints on that test application. The hardware components can be
placed on a small external test board. The connections to the STK500 board
can be done by using the STK500 supplied two-wire cables, if proper pins are
mounted to the small additional board. Don't forget that the LEDs in this
test application must be connected to port C, because we need port B for the
analog inputs. Due to this, we can't use this hard- and software for the
STK200 board (which has attached the LEDs to port B by hard wiring). After
finishing work with this test setting, don't forget to reconnect the LEDs
to port B again, using the 10-wire parallel cable, other programs might
not work correctly with the LEDs connected to port C.
To the top of that page

How it works

Generating the compare voltage by pulse-width modulation

A pulse-width-modulator generates a rectangle with a certain pulse width.
This modulator holds a 1-signal for a certain time period, the rest of the
whole time period it is zero. If this rectangle signal is filtered, the
pulse width produces a certain analogue voltage. If the relation between
pulse and pause of the PWM output is 50%, the voltage output is at 2.50 Volt,
if it is at 25% the voltage is at 1.25 Volt, etc.
Loading and unloading of the RC filter is shown in the simulation graphic.
The modulation of the PWM output by the rectangle U(PWM) is still visible
in the voltage of RC1. This modulation is strongly reduced on RC2. But U(RC2)
shows a delay, its lacking behind the mean voltage on U(RC1). This delay of
the mean value is even higher on U(RC3).
If the mean value has neared its end value very closely, there are still load-
and unload-swings around this mean value. The RC filter must be dimensioned in
a way that the remaining swings are well below the resolution of the AD
converter. With 8 bits resolution and 5.0 Volt operating voltage the remaining
swings have to be less than 5/256 = 19.5 mV. Higher resistor and capacitor
values or adding further RC combinations reduce the voltage swings.
On the other hand, the needed time to reach the mean end value gets longer
then. Therefore a PWM based ADC is not very fast (in our case around five
values per second). In practice, the compromise between delay time and remaining
voltage swing should be optimised. The time value until the voltage reaches
its end value and the sensing can take place is selected by the number of
necessary PWM cycles. This number of cycles is represented by a constant in the
software. In our case this software constant has been set to 128 cycles, which
is a conservative value. Without changing the software itself, the constant
can be set to up to 65536 PWM cycles.
To play around with selecting R and C values, simulating load- and unload
cycles, and selecting optimised cycle values, a small Pascal program was
devellopped. After compilation it runs on a command line. The
source code avr_pwm1.pas can be compiled
with a compiler to yield an executable.
The frequency that the PWM runs with, can be calculated for 8 bits resolution as

f (PWM) = clockfrequency / 510 (9 bit: 1022, 10 bit: 2046)

At a clock frequency of 3.685 Mcs/s on the STK500 board we yield 7,225.5 cs/s
or 138.4 microseconds per PWM cycle. If we wait for 128 cycles for settlement
of the mean voltage before sensing and 8 bit resolution, the conversion time
is around 142 milliseconds per measurement or seven measurements per second.
Not very fast, but even faster than a man's eye.

Reference value of this circuitry is the operating voltage of the processor.
This refence voltage can be adjusted with the studio software supplied with
the STK500. The accuracy of the PWM signal in the lower range from 0.00 to 0.10 V,
but even more in the most upper range, is limited, because the MOS output drivers
do not reach exactly the operating voltage bounds. This causes some inaccuracy
in the midrange and some non-linearity at the upper and lower bound of the
operating voltage. Therefore it doesn't make much sense to design this software
for 9 or 10 bits resolution. If you need more accurate values, use a processor
with built-in ADC.

The method of sukzessive approximation

To measure the input voltage, we could step up with the pulse width of the
comparation voltage and stop this increase, if the comparision voltage
is higher than the voltage on the measuring input pin. At 8 bit resolution,
this would require up to 255 steps, at 10 bit resolution up to 1,023 steps.
To speed this up, the conversion method of sukzessive approximation is applied.
In the first step the comparator voltage is set to half of the operating
voltage, 2.50 Volt. It is determined, if the input voltage is higher or lower
than this. If the input voltage is lower, the next comparator voltage is set to
1.25 Volt. If is is higher, the next comparator value is set to 3.75 V. This
step is repeated, until we know exact enough, how high the input voltage is.
At 8 bits resolution we need to make eight steps, at 10 bits ten steps. This
is by a factor of 30 resp. 100 faster than the stepwise increase of the
comparator value.
This algorithm can be programmed very easily. The fist five steps for an 8 bit
conversion are shown in the table. The table lists the comparator voltages at
step n and the corresponding value of the 8-bit PWM. If the comparator is
higher than the measuring input, the next value is in the upper row of the
table. Vice versa, if its smaller.

11000.0000

20100.0000

30010.0000

40001.0000

50000.1000

V=2,5V1000.0000

V=1,25V0100.0000

V=0,625V0010.0000

V=0,3125V0001.0000

V=0,15625V0000.1000

V=0,46875V0001.1000

V=0,9375V0011.0000

V=0,78125V0010.1000

V=1,09375V0011.1000

V=1,875V0110.0000

V=1,5625V0101.0000

V=1,40625V0100.1000

V=1,71875V0101.1000

V=2,1875V0111.0000

V=2,03125V0110.1000

V=2,34375V0111.1000

V=3,75V1100.0000

V=3,125V1010.0000

V=2,8125V1001.0000

V=2,65625V1000.1000

V=2,96875V1001.1000

V=3,4375V1011.0000

V=3,28125V1010.1000

V=3,59375V1011.1000

V=4,375V1110.0000

V=4,0625V1101.0000

V=3,90625V1100.1000

V=4,21875V1101.1000

V=4,6875V1111.0000

V=4,53125V1110.1000

V=4,84375V1111.1000

The steps are easy: at every step of the approximation the appropriate bit is first set
to one. If the voltage of the PWM is too high, this bit is set to zero in the next step.
Easy to program!

Software

The program spents very long times waiting for the PWM to settle for the mean value of the
RC filter. This is ideal for using interrupts, because otherwise we would have to program
very long and complicated timing loops. Because TC1 has nothing else to do than generating
the PWM signal by counting up and down, we can use it for our whole timing too. The
processor itself remains sleeping the whole time, and is woken up every 142 microseconds,
when TC1 places an interrupt. After woken up and worked through the interrupt routine of
TC1, the processor checks the ADC-ready flag. If it is not set, the processor goes back to
sleep again. If the flag is set, the result is displayed on the LED output port and the
flag is cleared.

Main program

The main program runs the following steps:

The stack is set up. This is necessary because the program uses timer interrupts.
Interrupts use the stack to store their return address on the stack. The stack is located
and set up on the top of the SRAM space.

The cycle counter is set. The cycle counter determines the number of cycles of the PWM
before the sensing of the comparision between the PWM-generated voltage and the analog
input takes place. In the example, 128 cycles are selected as constant. Change the constant
CycLen0, if you want to set the resampling rate to other values (e.g. to one measurement
per second).

The bit counter rBlc is set to 0x80 at the beginning of a measuring cycle. This
results in a comparator voltage of 2.50 V as comparator voltage. The temporary result
rTmp is set to that value accordingly.

The port outputs of port C are selected as outputs to drive the LEDs.

Port bit PD5 of port D is selected as output, because it generates the PWM output
signal on this pin.

The sleep mode is set to the idle mode. This allows the processor to react to the
SLEEP instruction and to wake up on interrupts by TC1.

Timer/Counter 1 is set up as 8-bit-PWM, its clock input is connected to the processor
clock, no prescaling takes place to yield the highest possible PWM clock rate.

PWM pulse width is set to 50% (0x0080).

The Timer Int Mask Register TIMSK is set to allow TC1 overflow interrupts, so that
TC1 causes an interrupt each time that the PWM cycle has reached its end.

The general interrupt flag of the processor is set to one to allow interrupts.

Now the loop starts, which is ran through each time the TC1 overflow interrupt wakes up the
processor. After running through the vectored int routine the ready-flag is asked, if the
AD conversion has ended and the result value is valid. If this is the case, the flag is
cleared, the ADC result is inverted bitwise and the byte is outputted to the LEDs. Then
the processor is set to sleep again, until the ready-flag is set after wake-up.

Interrupt processing

Central routine of the AD conversion is the pulse width generator of timer/counter 1.
Each time TC1 has reached its cycle end the overflow interrupt is generated, the program
counter is copied to the stack and program execution jumps to the interrupt vector. This
routine does all the necessary control of the PWM-ADC and sets the ready-flag on return,
if the AD conversion is ready. After conversion, the next measuring cycle is automatically
restarted. By selecting the PWM cycle numbers as constants on the top of the program, the
repeating frequency of the measurements and the delay before sensing the different bits
can be adjusted.
The graphic left shows the algorithm for the whole control of the AD conversion.
The int vector routine starts with saving the content of the processor's status register.
Prior to returning from the interrupt, the original content of the register is restored.
In the next step the cycle counter is decreased by one. If this cycle counter reaches
zero, the comparator voltage generated by the PWM and filtered by the RC filter has
reached its mean value. If the cycle counter is not zero, further cycles have to be
ran through and the interrupt vector routine ends.
If the necessary number of PWM cycles has been waited for, the PWM cycle counter is
set to the constant value. Then the analog comparator output is asked for the result
of the comparision. If the PWM generated comparation value is higher than the input
voltage, the actual bit, represented by the value in rBlc, in rTmp is cleared. If
not, then this actual bit remains one.
Now the bit counter rBlc with the actual bit is shifted right one bit. By this instruction
a zero enters rBlc from the left, and the content of the bit 0 is shifted to the carry
flag in the status register. If a zero rolls out to carry, the conversion has not
reached its end and the sukzessive approximation has to repeat for the next bit.
If a one rolls out of the bit counter, the conversion is ready. The result in the
temporary result register is copied to the result register, the ready-flag in the
flag register is set, the PWM cycle counter is set to the constant value that has
been defined for the sensing of the highest bit, the temporary result register is
cleared and the bit counter is set to $80 (for the next conversion).
The currently active bit in rBlc is copied to the temporary result register (set
active bit to one), and the current temporary value is copied to the timer/counter's
compare register in order to adjust the comparision voltage on the AIN1 input of
the comparator.
At the end of the interrupt routine, the status register gets back its original
content, and the interrupt routine returns.
The whole interrupt routine requires less than 25 clock cycles or 6 microseconds, and
is very short. If there would be other tasks for the processor, this wouldn't be
a serious delay.

Changes and possible extensions

If you'd like to experiment with 9 or 10 bit resolution instead: The result register,
the temporary result register and the bit counter need to be designed using words
(two registers each), the result doesn't fit to the eight LEDs (decide which bits
to display).
If you like to convert the result to its decimal form and display the result on an
LCD display or send it over the serial transmitter to a computer: use the fixed
point conversion routine provided in
the calculation section of the beginner's course.