Файл: C Programming for microcontrollers (Joe Pardue, 2005).pdf
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 13.06.2025
Просмотров: 2470
Скачиваний: 2
Chapter 9 – Digital Meets Analog – ADC and DAC
Figure 27 shows a simplified diagram of a successive approximation. This method uses an analog comparator and a digital to analog converter. An analog comparator, in this case, is a device that has two analog inputs one for the external voltage and the other for the voltage from the DAC (Digital to Analog Converter that can accept a digital number and output a voltage proportional to that number
– we’ll examine these later). If the voltage on the DAC is lower than the external voltage, the analog comparator outputs a 0, if it is higher it outputs a 1. The ATmega169 DAC is 10-bits, meaning that it can output 1024 steps from 0 and its maximum voltage.
Let’s look at the case where the maximum voltage that the DAC can produce is 3.0 volts. We have 10-bits to play with so we can keep approximating in 0.00293 (3.0/1024) volt steps. Let’s set the input voltage to 1.234 volts. We use a binary search technique that starts in the middle and bisects each successive voltage. We start by bisecting the 3 volts by sending the number 512 to the DAC, which then outputs 1.5 volts; the comparator will output a 1, meaning that the DAC voltage is too high. So we bisect the 1.5 volts by sending the DAC 256 to reset it to 0.75 volt and get a 0 meaning that the DAC voltage is now lower than the input voltage. Next we bisect the 1.5-0.75 volts by sending 384 to the DAC output 1.215 volts and get a 0, too low. We keep successively approximating until we find that the voltage is between1.233 and 1.236 volts. This is the best we can do with 0.003 volt steps.
Analog to Digital Conversion with the ATMEGA169
The ATmega169 has a 10-bit successive approximation Analog to Digital Converter connected to an 8-channel analog multiplexer allowing connection to one of eight voltage inputs on PortF. During conversion the voltage is held constant by a sample and hold circuit. Look in the data book on page 195, figure 82 for a block diagram of the ADC circuit.
The minimum value is GND and the maximum is determined by the voltage on the AREF pin (minus 1-bit in the least significant bit). We can use an external voltage reference attached to this pin, or we can tell the AVR to connect it to either AVCC or to an internal 1.1 volt reference. This setup allows us to improve noise immunity by connecting a decoupling capacitor to the AREF to help
212
Chapter 9 – Digital Meets Analog – ADC and DAC
stabilize the internal voltage reference. The Butterfly uses a 100 nF capacitor for this purpose.
There are 8 analog input channels ADC0 – ADC7 on the Port F pins PF0 – PF7. These pins are connected to an analog multiplexer that can connect any of the pins to the analog comparator. The channel is selected by setting the MUX0 – MUX4 bits in the ADMUX register.
The ADC is enabled/disabled by setting/clearing the ADEN bit in the ADCSRA (ADC Control and Status Register A). Since the ADC consumes power it is recommended that you turn it off when not in use.
The ADC readings are put in the ADC Data Registers ADCH and ADCL. In normal operation you read the ADCL first, then the ADCH to ensure that both registers contain the value of a single conversion.
An ADC interrupt can be set to trigger when a conversion is complete.
Starting a Conversion
There are several ways to start a conversion.
Write a 1 to the ADC Start Conversion bit ADSC to start a conversion. This bit stays high while the conversion is in progress and is cleared by the hardware when the conversion completes.
You can enable auto triggering by setting the ADC Auto Trigger bit, ADATE. The trigger source is determined by setting the ADTS2:0 ADC Auto Trigger Source bits in the ADCSRB register. The triggers can be:
•Free Running mode
•Analog Comparator
•External Interrupt Request 0
•Timer/Counter0 Compare Match
•Timer/Counter0 Overflow
•Timer Counter Compare Match B
•Timer/Counter1 Overflow
213
Chapter 9 – Digital Meets Analog – ADC and DAC
•Timer/Counter 1 Capture Event
Conversion Timing
The successive approximations are clocked between 50kHz and 200kHz to get the maximum resolution. You can use higher sampling rate frequencies, but you get lower resolution. The sampling rate is determined by the input clock and by a prescaler value set in the ADPS bits of the ADCSRA register. The first conversion takes 25 clock cycles to initialize the hardware. Normal conversions take 13 clock cycles and auto triggered conversions take 13.5.
Changing Channels
There are some complexities involved in changing channels and voltage references that lead to the recommendation that you always wait till a conversion is complete before making a change. If this is inconvenient, read the data book and figure it out yourself.
Digital Noise Reduction
The CPU and I/O peripherals can generate a lot of electrical noise that affect the accuracy of the ADC. We can put the system to sleep to shut it up and then take our ADC readings in the quietened environment. Details in the data book.
Conditioning the Analog Input Signal
The accuracy of the conversion will depend on the quality of the input signal. A few recommendations:
•Filter out signal components higher than the Nyquist sampling frequency (double the frequency of interest) with a low pass filter.
•Sampling time depends on the time needed to charge the sample and hold circuit so always use a source with an output impedance of 10 kOhm or less.
•Use only slowly varying signals.
•Keep analog signal paths as short as possible.
•Use the ADC noise canceller function.
214
Chapter 9 – Digital Meets Analog – ADC and DAC
•If any of the ADC port pins are used for digital output, don’t switch them while a conversion is going on.
Accuracy
The data book has some cursory discussion of the extremely dense topic of ADC accuracy. Just be aware that in the accompanying project we don’t use any of these recommendations, so take the accuracy of our measurements with a grain of salt.
215
Chapter 9 – Digital Meets Analog – ADC and DAC
Projects
We will write code to allow us to use HyperTerminal to request a reading from the light, temperature and voltage sensors of the Butterfly. You’ve already seen the debugging tale above so you know how much fun I had writing this stuff, so enjoy it or else.
Initializing the ADC
The Butterfly has the ATmega169 pin 62, (AREF) connected to a bypass capacitor to help lessen noise on the ADC, so we set the ADMUX bits 6 and 7 to 0 to select the 'use external reference' option. We use the ‘input’ variable to set the multiplexer. to connect the ADC to pin 61 (ADC0) using the ADMUX register (data book p 207).
ADMUX = input; |
// external AREF and ADCx |
Next we set the ADC Control and Status Register A. The ADEN bit enables the ADC. The ADPSx bits select the prescaler.
// set ADC prescaler to , 1MHz / 8 = 125kHz ADCSRA = (1<<ADEN) | (1<<ADPS1) | (1<<ADPS0);
Finally we take a dummy reading, which basically allows the ADC to hack up any hairballs before we take any real readings
input = ADC_read();
void ADC_init(char input)
{
ADMUX = input; |
// external AREF and ADCx |
// set ADC prescaler to , 1MHz / 8 = 125kHz ADCSRA = (1<<ADEN) | (1<<ADPS1) | (1<<ADPS0);
input = ADC_read(); // clear hairballs
}
216
Chapter 9 – Digital Meets Analog – ADC and DAC
Reading the ADC
We save power by turning off the voltage on the light and temperature sensors when they are not used, so now we turn them on, in case they are being used.
sbi(PORTF, PF3); sbi(DDRF, DDF3);
Next we enable the ADC.
sbi(ADCSRA, ADEN); |
// Enable the ADC |
Then we do another hairball clearing dummy read.
ADCSRA |= (1<<ADSC); |
// do single conversion |
And we wait till the conversion is complete.
while(!(ADCSRA & 0x10));//wait for conversion done, ADIF flag active
Now we repeat this 8 times for better accuracy.
// do the ADC conversion 8 times for better accuracy for(i=0;i<8;i++)
{
ADCSRA |= (1<<ADSC); // do single conversion
// wait for conversion done, ADIF flag active while(!(ADCSRA & 0x10));
ADC_temp = ADCL; // read out ADCL register ADC_temp += (ADCH << 8); // read out ADCH register
// accumulate result (8 samples) for later averaging ADCr += ADC_temp;
}
We divide by 8, which conveniently is done by left shifting 3 bits. Weren’t we lucky that we chose to do 8 samples and save processing time by avoiding a division?
ADCr = ADCr >> 3; // average the 8 samples
217
Chapter 9 – Digital Meets Analog – ADC and DAC
We turn the sensors off to save power.
cbi(PORTF,PF3); // mt cbi(PORTF, PORTF3); // disable the VCP cbi(DDRF,DDF3); // mt cbi(DDRF, PORTF3);
And we disable the ADC and return the calculated value.
cbi(ADCSRA, ADEN); |
// disable the ADC |
return ADCr;
Giving us the ADC_read function:
int ADC_read(void)
{
char i;
int ADC_temp;
//mt int ADC = 0 ; int ADCr = 0;
//To save power, the voltage over the LDR and the NTC is
//turned off when not used. This is done by controlling the
//voltage from an I/O-pin (PORTF3)
sbi(PORTF, PF3); // Enable the VCP (VC-peripheral) sbi(DDRF, DDF3); // sbi(DDRF, PORTF3);
sbi(ADCSRA, ADEN); |
// Enable the ADC |
//do a dummy readout first |
|
ADCSRA |= (1<<ADSC); |
// do single conversion |
//wait for conversion done, ADIF flag active while(!(ADCSRA & 0x10));
//do the ADC conversion 8 times for better accuracy for(i=0;i<8;i++)
{
ADCSRA |= (1<<ADSC); |
// do single conversion |
// wait for conversion done, ADIF flag active |
|
while(!(ADCSRA & 0x10)); |
|
ADC_temp = ADCL; |
// read out ADCL register |
218
Chapter 9 – Digital Meets Analog – ADC and DAC
ADC_temp += (ADCH << 8); |
// read out ADCH register |
// accumulate result (8 samples) for later averaging ADCr += ADC_temp;
}
ADCr = ADCr >> 3; // average the 8 samples
cbi(PORTF,PF3); // disable the VCP cbi(DDRF,DDF3); // mt cbi(DDRF, PORTF3);
cbi(ADCSRA, ADEN); |
// disable the ADC |
return ADCr;
}
Light Meter
The Butterfly has a Light Dependent Resistor, LDR, connected to ADC channel 2. The resistance of the LDR decreases as the light increases, so the voltage measured will decrease as light decreases.
We write the getLight function:
void getLight()
{
char light[]= {'0','0','0','\0'}; int ADCresult = 0;
// Initialize the ADC to the light sensor channel ADC_init(2);
ADCresult = ADC_read();
itoa(ADCresult, light, 10);
// Send the temperature to the PC sendString("The light reading is "); sendString(light);
sendString(" somethings.\r");
}
This is straightforward and returns a value for the light. The light units ‘somethings’ is a precise scientific measure that means: ‘I don’t have a clue as to
219