Path:AVR-EN => Applications => R/2R-DAC
DAC8 Tutorial for learning avr assembler language of
AVR-single-chip-processors AT90S, ATtiny, ATmega, ATxmega

of ATMEL using practical examples.

Simple 8-bit-digital-to-analog-converter using a R/2R-network

A R/2R network as Digital-to_Analog Converter (DAC)

A conversion of digital values to an analogue voltage can be performed using an integrated circuit, such as the 8-bit DAC0808. 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 vertical parts of the network. Each bit contributes its part to the resulting voltage on the output. Bit 0, the least significant bit, contributes 1/256, bit 1 contributes 1/128, etc. Believe it or not, this works fine. For those that are less on believes and believe that math is more correct, the calculation page provides all necessary background for those.

The commercial Digital-to-Analog-Converters have those resistor networks on board, within their integrated circuits, associated with an operational amplifier.

The output of an AVR port doesn't drive much current, if its voltages should stay near either ground (output low) or the operating voltage (output high). So, the resistors should be better above several tens of kilo-ohms. Because these networks do not provide much current (due to their high resulting resistance), 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. So, if an 8-bit DAC is to be selected, resistors with 1% accuracy are required.

Resistors in the E12 row (1.0 - 1.2 - 1.5 - 1.8 - 2.2 - 2.7 - 3.3 - 3.9 - 4.7 - 5.6 - 6.8 - 8.2) and their 10k- and 100k-fold do not offer the opportunity to have an exact two-fold value (2R) for any of the 12 resistors (R). So the two-fold has to be realized by serially adding two resistors.

The E24 row offers the following R/2R combinations for the ladder: 1.00 and 2.00, 1.10 and 2.20, 1.20 and 2.40, 1.50 and 3.00, 1.80 and 3.60 and 7.50 and 15.0. So an 8-bit DAC can be built with 7 resistors R plus 9 resistors 2R.

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 it would be fine for a four-bit-network.

To the top of that page

Required hardware

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

Buffered R/2R-network

The CA3140 (99 ¢) is an opamp with a FET input stage, that can be driven down to below the negative operating voltage. You might use a 741 opamp instead (33 ¢), but the consequences are discucssed below.
In this case the R/2R network is attached to a STK500 board with an AT90S8515, but any board and any AVR can be used that provides eight port pins in a single port. The operating voltage of +5 Volt for the opamp 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 with a higher voltage (up to 36 V), 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, or the above discussed E24 combinations. As the network has a resolution of 256, the 51/100 combination 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 Because the whole loop above takes four clock cycles (1 for out and inc, 2 for rjmp), the frequency of the sawtooth here is
f = clock / 4 / 256 = 3680000 / 4 / 256 = 3,593.75 Hz

But the result on the scope is somewhat surprising. Doesn't look like a sawtooth but rather like a wood saw that has done some previous 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 either 3.0  or 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. The frequency is a bit smaller now, caused by the additional andi, but in fact even higher because of the reduction to 128 steps of the whole cycle:
f = 3680000 / 5 / 128 = 5,750 Hz



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.

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

The frequency calculation is a bit trickier now. The two delay loops require the following clock cyles:

	ldi rdl,delay ; one clock cycle
delay(Up/Down):
	dec rdl ; one clock cycle
	brne delay ; one clock cycle if zero flag set, two clock cycles if not

This requires the following clock cycles:
nDelay = 1 + 3 * (delay - 1) + 2 = 3 * delay

The LoopDwn and LoopUp therefore require the following clock cycles:

	out PORTD,rmp ; one clock cycle
	; (DelayUp/Dwn) 3 * delay
	inc/dec rmp ; one clock cycle
	cpi rmp,maxAmp ; in up cycles only, one clock
	brcs/brne Loopup/Loopdwn, one clock if condition true, two if false

So the LoopUp requires the following clock cycles:
nLoopUp = (maxAmp - 1) * (3 * delay + 5) + 3 * delay + 4 =
maxAmp * (3 * delay + 5) - 3 * delay - 5 + 3 * delay + 4 =
3 * maxAmp * delay + 5 * maxAmp - 1

The LoopDown requires:
nLoopDwn = (maxAmp - 1) * (3 * delay + 4) + 3 * delay + 3 =
maxAmp * (3 * delay + 4) - 3 * delay - 4 + 3 * delay + 3 =
3 * maxAmp * delay + 4 * maxAmp - 1

The whole triangle loop requires the two loops plus one clock for clear (on the beginning) and two clock cycles for rjmp at the end:
nTotal = LoopUp + LoopDwn + 3 =
3 * maxAmp * delay + 5 * maxAmp - 1 + 3 * maxAmp * delay + 4 * maxAmp - 1 + 3 =
6 * maxAmp * delay + 9 * maxAmp + 1

So, for maxAmp = 128 and delay = 1 the number of clock cycles is:
n128/1 = 6 * 128 * 1 + 9 * 128 + 1 = 1,921

for maxAmp = 256 and delay = 256
n256/256 = 6 * 256 * 256 + 9 * 256 + 1 = 395,521

The resulting frequencies are
f128/1 = 3680000 / 1921 = 1,915.7 Hz
f256/256 = 3680000 / 395521 = 9.30 Hz


A considerable frequency range covered with only one 8-bit delay loop inserted.

This demonstrates nearby that assembler algorithms can de determined very exact in respect to their execution times. Try this with C or any other high-level language!


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. Such an effort would end up in a 0.01 Hz sine wave because the controller needs so much time to calculate the next value. 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 (asm source for 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 CA3140 buffer output. Otherwise the nice sinewaves remain silent.

To the top of that page

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