Tutorials: Microcontroller systems (MICSY)

AD/DA converter

last updated: 2022-09-14

Quick links to the subchapters

Song of this chapter: Joe Walsh > Analog Man > Analog Man

Introduction

Physical quantities are analog and even in a digital world we need conversion from analog to digital and back!

Modern control systems use Analog to Digital Converter (ADC) to convert physical quantities to a digital value, that can be processed in a microcontroller or computer by software. In newer sensors the ADC is already integrated in the sensor and a digital signal is delivered by the sensor (e.g. 18B20 1-wire temperature sensor). This prevents measurements to be altered by noise, a problem with analog signals. Most microcontroller host one ore more ADC and even preamplifiers if the sensors output is analog.

If our actuators in the system don't work with digital values (DAC possibly integrated in the actuator if the actuator needs an analog signal) we use an Digital to Analog Converter (DAC) to aquire an analog voltage or current. DAC's are also often integrated in microcontroller (Teensy 3.2, ESP32). Another possibility is to use a PWM signal with a low pass filter. PWM is slower than DAC's, and so not suited for fast real time systems.

control system


A bigger project

To start with this chapter, let's combine our knowledge to build and analyse a bigger project.

We will build an audio recorder with variable playback. For this we first convert an analog audio signal caught by a microphone to an array of bytes that is saved to an external EEPROM (24LC512). After this the array of bytes can be played back, by using a PWM signal and a low pass filter to reconvert the digital audio to an analog signal that can be passed to an amplifier and loudspeaker (audio jack).

BOM:

recorcer circuit


    // recorder.ino  weigu.lu
    // pushbuttons on pin 2 and 3, speaker (PWM Timer1) on 9 
    // mic on A0, poti an A1 

    #include "extEEPROM.h"                    // Tools -> Manage Libraries... inst.

    extEEPROM eep(kbits_512, 1, 128, 0x50);   //create EEPROM object

    const byte PIN_MICROPHONE = A0;
    const byte PIN_POTENTIOMETER = A1;
    const byte PIN_BUTTON_RECORD = 2;
    const byte PIN_BUTTON_PLAY = 3;
    const byte PIN_SPEAKER = 9;
    const byte PRESCALER_DEFAULT = 4;         // 1-7 (1, 8, 32, 64, 128, 256, 1024)
    const byte OCR_DEFAULT = 59;              // (16*10^6/(64*3000))-1 (must be <256)  
    const byte PAGE_LENGTH = 128;
    const short PAGE_NUMBER = 512;
    volatile int address_counter = 0;  
    volatile byte flag_array_end = 0;
    volatile byte flag_array_toggle = 0;
    byte page_data0[PAGE_LENGTH];
    byte page_data1[PAGE_LENGTH];
    int delay_it = 512;                       // potentiometer middle = 512 (0-1023)

    ISR(TIMER2_COMPA_vect) {                  // timer2 CTC_A (ISR) 3kHz read mic
      if (address_counter == PAGE_LENGTH) {    
        address_counter = 0;
        flag_array_end = 1;    
      }
      else {
        page_data1[address_counter] = analogRead(PIN_MICROPHONE)/4; // only 8 bit      
        address_counter++;
      }  
    }

    ISR(TIMER2_COMPB_vect) {                  // timer2 CTC_B (ISR) 3kHz write PWM
      if (address_counter == PAGE_LENGTH) {    
        address_counter = 0;   
        flag_array_end = 1;
      }  
      else {
        if (flag_array_toggle) {
          analogWrite(PIN_SPEAKER,page_data1[address_counter]);
        }
        else {
          analogWrite(PIN_SPEAKER,page_data0[address_counter]);
        }
        address_counter++;
      }
    }

    void setup() {
      pinMode(PIN_BUTTON_RECORD, INPUT_PULLUP);
      pinMode(PIN_BUTTON_PLAY, INPUT_PULLUP);
      pinMode(PIN_SPEAKER, OUTPUT);
      pinMode(LED_BUILTIN, OUTPUT);
      eep.begin(eep.twiClock400kHz);          // init I2C bus (use 400kHz) 
      //ADCSRA &= 0b11111010;                 // speed up conversion
      TCCR1B &= 0b11111001;                   // higher PWM frequency: 31250.00 Hz
      TCCR1B |= 0b00000001;   
      TCCR2A = 0b00000010;                    // turn on CTC mode (WGM21 = 1)
      TCCR2B = 0b00000000 + PRESCALER_DEFAULT; 
      OCR2A = OCR_DEFAULT;             
      OCR2B = OCR_DEFAULT;             
    }
    // enable compare B int. (OCIE2B = 1)    
    void loop() {    
      if (!digitalRead(PIN_BUTTON_RECORD)) {  // record
        OCR2A = OCR_DEFAULT;
        OCR2B = OCR_DEFAULT;
        digitalWrite(LED_BUILTIN,HIGH);
        TIMSK2 |= 0b00000010;                 // enable compare A int. (OCIE2A = 1)    
        for (word page=0; page<(PAGE_NUMBER); page++) {       
          while (flag_array_end == 0) {};     // wait until array is full                          
          flag_array_end = 0;        
          eep.write(page*PAGE_LENGTH, page_data1, PAGE_LENGTH); // write page                  
        }  
        TIMSK2 &= 0b11111101;                 // disable compare A int (OCIE2A = 0)
        digitalWrite(LED_BUILTIN,LOW);
      }
      if (!digitalRead(PIN_BUTTON_PLAY)) {    // play
        digitalWrite(LED_BUILTIN,HIGH);       //
        eep.read(0, page_data0, PAGE_LENGTH); // load page 0 (1. array)    
        TIMSK2 |= 0b00000100;                 // enable compare B int. (OCIE2B = 1)    
        for (word page = 1; page<(PAGE_NUMBER); page++) {
          delay_it = (analogRead(PIN_POTENTIOMETER)-512)/10; // change frequency with pot
          OCR2A = OCR_DEFAULT + delay_it;   
          OCR2B = OCR_DEFAULT + delay_it;   
          if (flag_array_toggle) { 
            eep.read(page*PAGE_LENGTH, page_data0, PAGE_LENGTH); // read page 
            while (flag_array_end == 0) {};   // wait until 2. array is played         
            flag_array_toggle = 0; 
            flag_array_end = 0;  
          }  
          else {  //toggle = 0
            eep.read(page*PAGE_LENGTH, page_data1, PAGE_LENGTH); // read page  
            while (flag_array_end == 0) {};   // wait until 1. array is played                          
            flag_array_toggle = 1; 
            flag_array_end = 0;          
          }
        }    
        TIMSK2 &= 0b11111011;                 // disable compare B int. (OCIE2B = 0)
        digitalWrite(LED_BUILTIN,LOW);
      } 
    }

Some explanations concerning the recorder:

microphone breadboard hack



hacked board fronthacked board back

"Just do it" ADDA1:

Analog to Digital Converter (ADC) (wiki)

An analog signal is a continuous signal in amplitude and time. The signal has a real value at every instant of time. Amplitude and time domains are a continuum (an uncountable set), and may or may not be finite. Amplitude and time are real numbers. Their precision is infinite, but would also need an infinite amount of positions in the numeral system and an infinite memory to store the values. To compute numbers, they have to be finite, so we loose precision when converting from analog to digital.

The digital signal is discrete in amplitude and time. The domains are countable like the natural numbers. As seen in the first digital electronics won over analogue electronics because analog signals are subject to electronic noise and distortion which can progressively degrade the signal. Digital signals have a finite resolution and can be processed and transmitted without introducing significant additional noise or distortion. Degradation can even be detected and corrected.

All codes for the finite set of values used could be used, but mostly the binary numeral system is used.

An analog-to-digital converter (ADC) can be modelled as two processes: quantization and sampling.

Quantization: discrete in amplitude (wiki)

Quantization replaces each real number with an approximation from a finite set of discrete values. With 8 bit we reach 28 = 256 voltage values or levels. Here an example with 3 bit, reaching 23 = 8 levels.

amplitude discrete


The resolution in bit (bit resolution) of an ADC defines the number of levels and is a measure of the precision of the ADC.

Number of voltage levels N (n = resolution in bits):

ADC formula levels


The voltage resolution, also called the least significant bit (LSB) voltage is equal to its reference voltage (overall voltage measurement range) divided by the number of intervals:

Voltage resolution: ΔU:

ADC formula_resolution


ADC resolution


Quantizing a sequence of numbers produces a sequence of quantization (rounding) errors, called quantization noise because of their random values. The more bit we use, the lower is the quantization noise.

Sampling: discrete in time

Sampling converts a time-varying voltage signal into a discrete-time signal, a sequence of real numbers (e.g. conversion of an analog sound wave to a sequence of samples (CD)). Sampling is performed by measuring the value of a continuous function every T seconds, which is called the sampling interval (sampling period). The sampling frequency (sampling rate), fs is the average number of samples obtained in one second (samples per second), fs=1/T.

Here is the quantized signal from above also discrete in time:

amplitude time discrete


The higher the sampling rate, the better the digital signal corresponds to the original signal.

As seen in our recorder a continuously varying band-limited signal can be sampled (the sampling frequency is the interrupt frequency), stored (here in an EEPROM) and then the original signal can be reproduced from the memorised discrete-time values with the help of a PWM signal or a DAC. The accuracy in this procedure is dictated by the combined effect of sampling (maximum sampling frequency, here limited by the EEPROM write time) and quantization (bit resolution).

It is clear, that if the sampling frequency is not high enough, information (high frequency parts of the signal) is lost. So what is the minimum sampling frequency needed to rebuild a signal without errors?

The Nyquist-Shannon sampling theorem gives the answer.

A faithful reproduction of a continuous band-limited signal is only possible if the sampling rate fs is higher than twice the highest frequency of the signal (2·fmax).

ADC formula nyquist


"Just do it" ADDA2:

Analog to digital conversion

Frequencies above 2·fmax must be eliminated before using an ADC. If this is not the case, frequencies higher than 2·fmax would be interpreted as lower frequencies. Higher frequencies impersonate lower frequencies, that's why the effect is called Aliasing effect.

The used low pass filter to avoid the effect is called anti-aliasing filter.

During the periodical sampling, the signal (input value) must necessarily be held constant during the conversion time. A circuit called a sample and hold performs this task with a switch (transistor) to sample the voltage and a capacitor to store it. Many ADC IC's include the sample and hold internally.

ADC block diagram

ADC block diagram


Types of ADC

There exist different many different types of ADC, with different properties. There is no ADC that is very fast and has a high resolution, because these properties exclude each other. Let's begin with the slowest ADC:

Dual slope integrating (ramping) ADC

It uses the principle of measuring time when charging and discharging a capacitor. Dual slope ADC are slow, but can have a high resolution. They are used in multimeter to measure voltage and current.

Sigma-Delta ADC

It adds two signals and looks at the difference of the sum and the real signal. It is also an integrating converter, so it has a high-resolution, and a low- to moderate-speed.

Successive approximation register (SAR) ADC

A binary search algorithm makes successive approximations by determining each bit one by one until the complete conversion is finalized. It needs only a sample and hold, a comparator, a DAC and the successive approximation register.

The SAR DAC is a good compromise between speed and resolution and is often used in microcontroller.

Pipeline ADC

Pipeline ADC are quite new on the market. They use cascaded conversion stages, each performing a very fast low resolution conversion, followed by the calculation of the residue that is provided to the next stage for further processing.

We get a good resolution (12- to 16-bit) and high speed (up to 1 GHz).

Parallel comparator (Flash) ADC

Flash ADC use parallel comparators and perform the data conversion in a single step, making them very fast. The Flash ADC needs much power and the number of comparators and flip-flops increases exponentially with the number of bits. It's usage is limited to low-resolution applications.

Oscilloscopes use often a combination of pipeline ADC and flash ADC.

As the flash ADC is easy to understand let's take a deeper look at it's circuit:

ADC flash 8 bit


A voltage divider produces the voltages for the comparators, beginning with ½LSB. If the input voltage is above ½LSB, but less than 1½LSB, the first comparator delivers a signal. Between 1½LSB and 2½LSB the second comparator reacts etc.. To prevent false results by different transit times, the result is stored in D-flip-flops and passed to the decoder on a rising clock. The priority decoder generates the corresponding binary number.

Comparison diagram

ADC comparision diagram


Comparison table
Architecture Latency Speed Resolution Power consumption
Dual slope integrating (ramping) ADC High Very slow High Low
Sigma-Delta ADC High Slow-medium High Medium
Successive approximation register (SAR) ADC Low High Medium-high Low
Pipeline ADC Low-medium High Medium Medium
Parallel comparator (Flash) ADC Low Very high Low High

Analog digital converter of the ATmega328p

Let's take a closer look at the ADC implemented in the Arduino Uno Chip. The ADC is a 10-bit successive approximation ADC connected to an 8-channel analog multiplexer which allows eight single-ended voltage (referring to GND) inputs (all on port A). If needed the ADC has a separate analog supply voltage pin (AVCC) to reduce noise. It features also an internal reference voltage of 1.1 V and pin to connect an external reference (AREF).

The ADC converts an analog input voltage to a 10-bit digital value through successive approximation. In single-ended mode the minimum value is 0 V (GND) and the maximum value is the reference voltage minus 1 least significant bit (LSB).

The ADC lacks the differential measurements possible with the ADCs of an ATmega32 or ATmega32u4.

Digital to Analog Converter (DAC) (wiki)

To convert digital information with a DAC the information must be contained in a weighted code. The binary numeral system is such a weighted code. The more bits we use the better the resolution. With 8 bit we get already 256 different voltage values. One step with a reference voltage of 5 V is (5 V/256) about 20 mV. With 12 bit we get 1.2 mV. As the noise level in unshielded projects is often above these voltages, 8 bit often suffice for these projects.

DAC formula


A low pass filter behind the DAC eliminates the steps and gets the original signal back.

DAC block diagram

DAC block diagram


There are many different types of DAC's, but one of them is very easy to understand and also to build.

The resistor ladder R-2R DAC (wiki)

The R-2R ladder uses resistors with the values R and 2R. The resistors act as voltage dividers. The total resistance stays always the same. It does not matter if we use a 4 bit or a 16 bit ladder. Changing the output current does not alter the signal sequence but only the overall amplitude of the signal.

r2r 8 bit


2 bit resistor ladder

Let's look at a n R-2R ladder for 2 bit connected to an AVR controller (e.g. port B):

r2r 2 bit


case PB1 (21) PB0 (20)
1
0
0
2
0
1
3
1
0
4
1
1


1. case: PB1 = PB0 = 0

Both controller outputs are 0 V. The output is also 0 V.

2. case: PB1 = 0, PB0 = 1

PB0 has the output voltage of the controller VCC (e.g. 5 V). 2R and R in series (3R) are parallel to 2R. This gives us a resistance of 6/5R in series with 2R (2R + 6/5R = 16/5R), reducing the voltage to 6/16. The output delivers 4/16 = 1/4 of VCC.

r2r 8 bit


3. case: PB1 = 1, PB0 = 0

We get a voltage divider with 2 equal resistors, dividing VCC by 2.

r2r 8 bit


4. case: PB1 = 1, PB0 = 1

The circuit is similar to case 2. The output delivers 12/16 = 3/4 of VCC.

r2r 8 bit


We get the following table:

case decimal value PB1 (21) PB0 (20) Uout Uout (VCC = 5 V)
1
0
0
0
0
0 V
2
1
0
1
1/4 VCC
1.25 V
3
2
1
0
2/4 VCC
2.5 V
4
3
1
1
3/4 VCC
3.75 V

"Just do it" ADDA3:

To draw not too much current the values often reside in the 10th kΩ range. The precision of the resistors should not be higher than 1% (>E96). An operational amplifier (rail to rail) at the output of the ladder prevents a drop of the amplitude and delivers the current for the following circuit.

r2r 8 bit


"Just do it" ADDA4:
"Just do it (if you feel like it)":

Interesting links: