Path: Home => AVR-overview => R/2R-DAC
DAC8 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

Task

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.

R/2R-networkA 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.

Buffered R/2R-network

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


Sawtooth up 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
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.

Sawtooth 741 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


Triangle Triangle up 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:
	lpm ; Read from table
	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


Sinewave 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

©2005 by http://www.avr-asm-tutorial.net