last updated: 2022-09-14
Song of this chapter: Joe Walsh > Analog Man > Analog Man
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.
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:
// 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:
The chosen external EEPROM (24LC512) has a memory of 512kibit (organised in pages of 128 byte) and is connected to the I2C bus. This two wire bus needs apart from GND a data line (SDA, A4 on Arduino Uno) and a clock line (SCL, A5 on Arduino Uno). The ATmega328 (Uno) has integrated hardware for this bus (named TWI by ATMEL) and is the master. The EEPROM is the slave and has an address on the bus. Because the three pins A0
, A1
and A2
are connected to GND
we get the base address 0x50
(look at the creation of the EEPROM object). The handling is done by the wire library (a core library, so already installed). The bus needs pull-up resistors on both lines (1.8 kΩ for 3 mA on 5 V). The I2C bus normally works on 100 kHz, but can also work on 400 kHz. Because speed matters we use 400 kHz.
We find the following lines in our sketch:
extEEPROM eep(kbits_512, 1, 128, 0x50); //create EEPROM object
...
eep.begin(eep.twiClock400kHz); // init I2C bus (use 400kHz)
Writing and reading from EEPROM needs time. Especially writing is slow. Writing one byte needs about 4 ms. The maximum sampling frequency would be 1/4 ms = 250 Hz, a bunch too low for audio!
The extEEPROM library needs about 30 ms to write 128 bytes (another tested library (E24) needed 42 ms). So page (128 byte) writing is much faster. Nonetheless we must sample our 128 bytes of data in parallel to the writing to the EEPROM. This is done inside a Timer2 CTC ISR. Our maximum sampling time is limited by the EEPROM.
It is convenient, that Timer2 has two CTC interrupts (A and B), so we can use one interrupt to record and a second interrupt to play. First we need to read one page to an array, so that the interrupt can play it. Whilst the interrupt is playing we read the next 128 bytes to a second array. The toogle flag indicates the ISR witch array to play.
With our potentiometer we can change the playback frequency by altering the compare register. The built in LED signals when recording or playing. When no button is pressed, we get no Timer2 interrupts and the loop runs idle.
The PWM signal (command analogWrite()
) is generated on Arduino Uno pin 9 with the 16 bit Timer1 set to 9 bit and fast PWM. The default PWM frequency is 490 Hz (prescaler 64), much to low for our purpose. So we set the pre-scaler to 1 and get a frequency of 31250 Hz. A low pass filter separates the audio signal from the PWM signal.
Install the extEEPROM library to access the EEPROM (Tools
-> Manage Libraries...
, search for extEEPROM and click install
). Build the circuit and flash the sketch. Test the program and document your test with an audio or video file. Stop the recording time with a watch (look at the built in LED on Arduino) and document the time.
The EEPROM can save up to 512 kibit. It is filled completely by our program. Calculate the sampling frequency using the measured time.
The sampling frequency is the frequency with witch the Timer2 ISR is called. We use the CTC mode. Calculate the precise ISR frequency with the values from our sketch (OCR = 59, pre-scaler = 64).
Explain the following two lines:
TCCR1B &= 0b11111001;
TCCR1B |= 0b00000001;
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 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.
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):
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:
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 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:
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.
fs
is higher than twice the highest frequency of the signal (2·fmax
).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.
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:
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.
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.
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 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).
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:
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.
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 |
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.
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.
A low pass filter behind the DAC eliminates the steps and gets the original signal back.
There are many different types of DAC's, but one of them is very easy to understand and also to build.
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.
Let's look at a n R-2R ladder for 2 bit connected to an AVR controller (e.g. port B):
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.
3. case: PB1 = 1, PB0 = 0
We get a voltage divider with 2 equal resistors, dividing VCC by 2.
4. case: PB1 = 1, PB0 = 1
The circuit is similar to case 2. The output delivers 12/16 = 3/4 of VCC.
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 |
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.
Build a 4 bit R-2R network on a breadboard with 2R = 10 kΩ resistors (1 %) only. How can you get the R = 5 kΩ resistors?
We need a sawtooth with 2 kHz. Use a whole port on an Arduino (or Teensy) board and use the SPR
for DDR and Port operations. DelayMicroseconds()
will help to reach the right frequency. Document the code and an oscilloscope screen of the sawtooth.
Use one of the 8 bit DAC
's of the ESP32 (pin 25) with the function dacWrite(pin,value)
. What is the highest frequency we can get? Document the code and the the oscilloscope screen.
Do the same with an Teensy 3.6. The Teensy 3.6 has also 2 DAC
's with up to 12 bit! To access the DAC
's analogWrite()
is used. The resolution can be changed with analogWriteResolution()
. Set the resolution to 8 bit. Use pin A21.
DAC
instead of the PWM.