Path: Home => AVR-overview => PWM-ADC    (Diese Seite in Deutsch: Flag DE) Logo
ADC8 Tutorial for learning avr assembler language of
AVR-single-chip-processors AT90Sxxxx
of ATMEL using practical examples.
Simple 8-bit-analog-digital-converter with PWM on the STK500 board


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

Hardware of the 8-bit-ADC 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.

ADC8-Connections 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.
Simulation 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.

To the top of that page

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.
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!

To the top of that page


The software is available in HTML format ADC8.html and as ASM source code file ADC8.asm.

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:
  1. 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.
  2. 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).
  3. 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.
  4. The port outputs of port C are selected as outputs to drive the LEDs.
  5. Port bit PD5 of port D is selected as output, because it generates the PWM output signal on this pin.
  6. 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.
  7. 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.
  8. PWM pulse width is set to 50% (0x0080).
  9. 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.
  10. 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.

To the top of that page

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.
TC1-Overflow-Int-Algorithm 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.

To the top of that page

©2003 by