Path: Home => AVR-overview => R/2R-DAC
 Tutorial for learning avr assembler language of AVR-single-chip-processors AT90Sxxxx of ATMEL using practical examples. Simple 8-bit-digital-to-analog-converter using a R/2R-network

A conversion of digital values to an analogue voltage can be performed using an integrated circuit. A cheaper and less sophisticated method is a R/2R-network, followed by an opamp.

A R/2R-network is made of resistors like shown in the picture. The bits, either at 0 or the operating voltage, enter the network via a resistor of a double value than the rest of the network. Each bit contributes its part to the resulting voltage on the output. Believe it or not, this works fine. The commercial Digital-to-Analog-Converters have those resistor networks within their integrated circuits.

The output of an AVR port doesn't drive much current, if its voltages should stay near either ground or the operating voltage. So, the resistors should be above several tens of kilo-ohms. Because these networks do not provide much current, an opamp decouples the R/2R-network from the following rest of the circuit.

The resistor values should be as accurate as the whole network is expected to be accurate. Deviations are especially relevant for the higher bits. The following table shows some stepwise increases in the output voltage of a R/2R-network when using a 51/100k combination of resistors. (Calculations were made with a Free-Pascal program, free source code can be downloaded from here).

```
R2R-network calculation tool, (C)2004 info!at!avr-asm-tutorial.net
------------------------------------------------------------------
N bits resolution: nr=8[bits], Bits=00000000
Voltages: ub=5.000[V], ul=0.000[V], uh=5.000[V]
Resistors: R1= 51k0, R2=100k0

Input combinations and output voltages
00000000: 0.000[V]
00000001: 0.019[V] (Delta=   18.69[mV])
00000010: 0.038[V] (Delta=   19.06[mV])
00000011: 0.056[V] (Delta=   18.69[mV])
00000100: 0.076[V] (Delta=   19.62[mV])
00000101: 0.095[V] (Delta=   18.69[mV])
00000110: 0.114[V] (Delta=   19.06[mV])
00000111: 0.132[V] (Delta=   18.69[mV])
00001000: 0.153[V] (Delta=   20.67[mV])
00001001: 0.172[V] (Delta=   18.69[mV])
00001010: 0.191[V] (Delta=   19.06[mV])
00001011: 0.210[V] (Delta=   18.69[mV])
00001100: 0.229[V] (Delta=   19.62[mV])
00001101: 0.248[V] (Delta=   18.69[mV])
00001110: 0.267[V] (Delta=   19.06[mV])
00001111: 0.286[V] (Delta=   18.69[mV])
00010000: 0.308[V] (Delta=   22.72[mV])
00010001: 0.327[V] (Delta=   18.69[mV])
00010010: 0.346[V] (Delta=   19.06[mV])
00010011: 0.365[V] (Delta=   18.69[mV])
00010100: 0.384[V] (Delta=   19.62[mV])
00010101: 0.403[V] (Delta=   18.69[mV])
00010110: 0.422[V] (Delta=   19.06[mV])
00010111: 0.441[V] (Delta=   18.69[mV])
00011000: 0.462[V] (Delta=   20.67[mV])
...
01111110: 2.446[V] (Delta=   19.06[mV])
01111111: 2.465[V] (Delta=   18.69[mV])
10000000: 2.517[V] (Delta=   51.72[mV])
10000001: 2.535[V] (Delta=   18.69[mV])
10000010: 2.554[V] (Delta=   19.06[mV])
10000011: 2.573[V] (Delta=   18.69[mV])
...

```
Note the extraordinary step in voltage when Bit 7 goes from low to high! It jumps up more than two ordinary steps. This might be unacceptable for an 8-bit-network, but its fine for a four-bit-network.

## Required hardware

The hardware is easy to built, it is a real resistor grave-yard.

The CA3140 is an opamp with a FET stage on the input, that can be driven down to the negative operating voltage. You might use a 741 opamp instead, but the consequences are discucssed below.
The operating voltage of 5 Volt here is taken from the 10-pin-connector. This connector fits to a STK200 or STK500 board. It is also possible to feed the opamp by a higher voltage, which has some advantages (see below).
Instead of two parallel resitors, you can use resistors that have approximately half the value of 100k, e.g. 51k. As the network has a resolution of 256, this brings some inaccuracy, see above.

To the top of that page

## Applying the R/2R-network

### Generating a sawtooth

The following program produces a sawtooth voltage on the output of the R/2R-network. The source code is available for download here.

``````
; **************************************************************
; * R/2R-Network produces a sawtooth signal on Port D          *
; * (C)2005 by info!at!avr-asm-tutorial.net                    *
; **************************************************************
;
.INCLUDE "8515def.inc"
;
; Register definitions
;
.DEF rmp = R16 ; Multipurpose register
;
ldi rmp,0xFF;  Set all pins of Port D as output
out DDRD,rmp
sawtooth:
out PORTD,rmp
inc rmp
rjmp sawtooth
``````

The result is somewhat surprising. Doesn't look like a sawtooth but like a saw that has done some efforts in cutting steel.

The reason for this is not the R/2R-network but the opamp. He doesn't work fine at the upper bounds of the operating voltage.

So we have to limit the output of the R/2R-network to approximately 2.5 Volts. This is done by software (source code to be downloaded here).

``````
; **************************************************************
; * R/2R-Network produces a sawtooth signal on Port D          *
; * (C)2005 by info!at!avr-asm-tutorial.net                    *
; **************************************************************
;
.INCLUDE "8515def.inc"
;
; Register definitions
;
.DEF rmp = R16 ; Multipurpose register
;
ldi rmp,0xFF;  Set all pins of Port D as output
out DDRD,rmp
sawtooth:
out PORTD,rmp
inc rmp
andi rmp,0x7F ; set bit 7 to zero
rjmp sawtooth
``````
That looks much better now.

Note that we would not need Bit 7 of the port now, it can be tied to ground, as it is always low.

Here's the result of exchanging the CA3140 buffer to a cheaper 741 opamp. The 741 is neither operating near the operating voltage nor does he work near ground. So the voltages of our R/2R-network would have to be limited from a minimum of approximately 2 Volts to a maximum of 4 Volts.

To the top of that page

### Generating a triangle

Generating a triangle is a similiarly easy task: just counting up and down. The source code can be downloaded here. It allows adjusting the frequency by changing the value of the constants "delay" and the upper bound by adjusting the constant "maxAmp".

``````
; **************************************************************
; * R/2R-Network produces a triangle signal on Port D          *
; * (C)2005 by info!at!avr-asm-tutorial.net                    *
; **************************************************************
;
.INCLUDE "8515def.inc"
;
; Register definitions
;
.DEF rmp = R16 ; Multipurpose register
.DEF rdl = R17 ; Delay counter
;
; Constants
;
.EQU maxAmp = 127 ; Maximum amplitude setting
.EQU delay = 1 ; Delay, higher value causes lower frequency
;
; Main program start
;
ldi rmp,0xFF;  Set all pins of Port D as output
out DDRD,rmp
triangle:
clr rmp
loopup:
out PORTD,rmp ; to port
ldi rdl,delay
delayup:
dec rdl
brne delayup
inc rmp ; next higher value
cpi rmp,maxAmp
brcs loopup
loopdwn:
out PORTD,rmp
ldi rdl,delay
delaydwn:
dec rdl
brne delaydwn
dec rmp
brne loopdwn
rjmp triangle ; and again forever
``````

To the left, the voltage is limited to 2.5 V, to the right it's the full 5.0 V.

To the top of that page

### Generating a sinewave

At last we need the famous sinewave. If you think that I'll program a sine function generator for that in assembler, you're wrong. I'll let this do the PC in Pascal (by use of the small free-pascal program that can be downloaded here) and include the resulting sinewave table (download here) to my program (download here.

``````
; **************************************************************
; * Produces a sinewave on a R/2R-network connected to PORTD   *
; * (C)2005 by avr-asm-tutorial.net                            *
; **************************************************************
;
.INCLUDE "8515def.inc"
;
; Register definitions
;
.DEF rmp = R16 ; Multipurpose register
;
; Start of program source code
;
ldi rmp,0xFF ; Set all pins of port D to be output
out DDRD,rmp
ldi ZH,HIGH(2*SineTable) ; Point Z to Table in flash
ldi ZL,LOW(2*SineTable)
clr rmp
loop1:
nop
nop
nop
loop2:
out PORTD,R0 ; Write value to port D
adiw ZL,1 ; point to next value
dec rmp ; End of Table reached
brne loop1
ldi ZH,HIGH(2*SineTable) ; Point Z to Table in flash
ldi ZL,LOW(2*SineTable)
rjmp loop2
;
; End of source code
;
; Include sinewave table
;
.INCLUDE "sine8_25.txt"
;
; End of program
;
;
; Sinewave table for 8 bit D/A
; VCC=5.000V, uLow=0.000V, uHigh=2.500V
; (generated by sinewave.pas)
;
Sinetable:
.DB 64,65,67,68,70,72,73,75
.DB 76,78,79,81,82,84,85,87
.DB 88,90,91,92,94,95,97,98
.DB 99,100,102,103,104,105,107,108
.DB 109,110,111,112,113,114,115,116
.DB 117,118,118,119,120,121,121,122
.DB 123,123,124,124,125,125,126,126
.DB 126,127,127,127,127,127,127,127
.DB 128,127,127,127,127,127,127,127
.DB 126,126,126,125,125,124,124,123
.DB 123,122,121,121,120,119,118,118
.DB 117,116,115,114,113,112,111,110
.DB 109,108,107,105,104,103,102,100
.DB 99,98,97,95,94,92,91,90
.DB 88,87,85,84,82,81,79,78
.DB 76,75,73,72,70,68,67,65
.DB 64,62,61,59,58,56,54,53
.DB 51,50,48,47,45,44,42,41
.DB 39,38,36,35,34,32,31,30
.DB 28,27,26,25,23,22,21,20
.DB 19,18,17,15,14,13,13,12
.DB 11,10,9,8,8,7,6,5
.DB 5,4,4,3,3,2,2,2
.DB 1,1,1,0,0,0,0,0
.DB 0,0,0,0,0,0,1,1
.DB 1,2,2,2,3,3,4,4
.DB 5,5,6,7,8,8,9,10
.DB 11,12,13,13,14,15,17,18
.DB 19,20,21,22,23,25,26,27
.DB 28,30,31,32,34,35,36,38
.DB 39,41,42,44,45,47,48,50
.DB 51,53,54,56,58,59,61,62
``````

That's all. It produces a nice sine. You wouldn't expect here to be a digital AVR at work but a nice and clean LC-oscillator.

Unfortunately this method cannot be applied to produce sinewaves with more than 1,800 cycles/s, because 4 Mcs/s divided by 256 is only 15,625. And you need at least some clock cycles in between to read from the sinewave table.

To the top of that page

### Playing musical notes with the R/2R network

The following program uses the R/2R network to play musical notes with the keyboard. It works without changes on a STK200 board with a 4 Mcs/s 8515, with the eight keys connected to port D and the R/2R network connected to port B (download here).

``````
; ******************************************************************
; * Music on an STK200, using a R/2R network for playing tunes     *
; * PortD has eight keys (active low), PortB generates the         *
; * output for the R/2R-network, plays notes when key is activated *
; * ATMEL AT90S8515 at 4 Mcs/s                                     *
; * (C)2005 by info!at!avr-asm-tutorial.net                        *
; ******************************************************************
;
.NOLIST
.INCLUDE "8515def.inc"
.LIST
;
; Constants
;
.EQU clock = 4000000 ; processor clock
.EQU cNSine = 32 ; Table length of sine wave
;
.DEF rLen = R1 ; length of delay for the sine wave
.DEF rCnt = R2 ; Counter for length delay
.DEF rmp = R16 ; multi purpose register
.DEF rTab = R17 ; counter for table length
;
ldi rmp,0xFF ; all bits of port B = output => R/2R network
out DDRB,rmp ; set data direction register
wtloop:
in rmp,PIND ; read the keys
cpi rmp,0xFF ; all keys inactive?
breq wtloop ; yes, wait until active
ldi ZH,HIGH(2*MusicTable) ; Point Z to music table
ldi ZL,LOW(2*MusicTable)
tabloop:
rol rmp ; rotate next lowest bit into carry
brcc tabfound ; found that key pressed
adiw ZL,1 ; point Z to next table value
rjmp tabloop ; check next bit
tabfound:
lpm ; read music table value to R0
mov rlen,R0 ; copy to delay, R0 used otherwise
;
; Play a tone until all keys are inactive
;
startsine:
ldi ZH,HIGH(2*SineTable) ; Set Z to sine wave table
ldi ZL,LOW(2*SineTable)
ldi rTab,cNSine ; Length of SineWave table
;
; The following code is optimized for reaching equal runtimes
; for all loop runs, clocks cycles needed are calculated
; for all instructions after their execution,
; clock cycles during table read / clock cycles at table end
;
loopsine:
in rmp,PIND ; 1/- Check if input still active
cpi rmp,0xFF ; 2/-
breq wtloop ; 3/- key not active any more, restart
nop ; 4/- delay to synchronize
loopnext:
lpm ; 7/3 read sinewave value to R0
out PORTB,R0 ; 8/4 copy to R/2R network
mov rCnt,rLen ; 9/5 Set delay counter value
; delay loop, requires 3*rLen-1 clock cycles
loopdelay:
dec rCnt ; (1) next counter value
brne loopdelay ; (2/1) not yet zero
; 3*rLen+8/3*rLen+4 at end of delay loop
adiw ZL,1 ; 3*rLen+10/3*rLen+6 next value in table
dec rTab ; 3*rLen+11/3*rLen+7 table counter
brne loopsine ; 3*rLen+13/3*rLen+8 play next value in table
ldi ZH,HIGH(2*SineTable) ; -/3*rLen+9 reload first table value
ldi ZL,LOW(2*SineTable) ; -/3*rLen+10
ldi rTab,cNSine ; -/3*rLen+11 Length of sine wave table
rjmp loopnext ; -/3*rLen+13 restart loop
;
; Table for delays to generate the 8 different frequencies
;
; frequency = clock / TableLength / ( 3 * rLen + 13 )
; rLen = ( clock /TableLength / frequency - 13 ) / 3
;
MusicTable:
; f=261.6 293.7 329.6 349.2 392.0 440.0 493.9 523.2 (should be)
.DB 155,  138,  122,  115,  102,  90,   80,   75
; f=261.5 292.7 329.8 349.2 391.9 441.7 494.1 525.2 (is really)
; differences caused by rounding and the limited resolution)
;
; Sinewave table for 8 bit D/A
; Table length = 32 values
; VCC=5.000V, uLow=0.000V, uHigh=2.500V
; (generated by sinewave.pas)
;
Sinetable:
.DB 64,76,88,99,109,117,123,126
.DB 128,126,123,117,109,99,88,76
.DB 64,51,39,28,19,11,5,1
.DB 0,1,5,11,19,28,39,51
;
; End of program
;
``````

Of cource, you need to attach a small speaker or an earphone to the buffer output. Otherwise the nice sinewaves remain silent.

To the top of that page