AVR® 10 ms Analog-to-Digital Converter (ADC) Samples Averaged Over 1 Second
Contents
In This Video
- Modify a millisecond timer function, from Updating Pulse Width Modulator (PWM) duty cycle using a millisecond timer, we create a 10 mS timer.
- Test the frequency, using the Data Visualizer, by toggling a pin in the timer IRQ.
- Change Analog-to-Digital Converter (ADC) auto-trigger source to Timer Counter 0 Compare Match A.
- Create volatile global variables accumulator, average and samples, using these to calculate an average in the ADC IRQ.
- Verify that we are now averaging ADC light sensor readings every second.
Procedure
Pull up the Code from a Previous Project
Once again, we will pull some code from our previous PWM project and add it to the AVR_ADC project to create the 10mS timer. Copy the milliS_timer functiond() and paste it to just below the PWM_Init function() in the AVR_ADC project in the previous lesson.
Here is the code to be copied over...
{
TCCR0A |= (1 << WGM01); //set to CTC mode
TCCR0B |= (1 << CS02) | (1 << CS00); //set prescaler to 1024
OCR0A = milliS * 7.8125 - 1;
TIMSK0 |= (1 << OCIE0A); //enable the interrupt
}
In order to ADC samples every 10 ms, we need to revisit this equation...
OCR0A = (Required_Time * F_CPU) / (Prescaler_Value * 1000) - 1
In this case, the required time is 10ms, the prescaler value is 1024, and the F_CPU is 16,000,000 Hz.
Plugging these values into the formula, we get:
OCR0A = (10ms * 16,000,000 Hz) / (1024 * 1000) - 1 = (10 * 16,000) - 1 = 160,000 - 1 = 159,999
Since OCR0A is an 8-bit register, it can hold values from 0 to 255. Therefore, we need to choose the closest possible value to 159,999 that fits within the 8-bit range. In this case, the closest value is 156, which can be used as the OCR0A value to achieve a 10ms interrupt with a prescaler of 1024.
{
TCCR0A |= (1 << WGM01); //set to CTC mode
TCCR0B |= (1 << CS02) | (1 << CS00); //set prescaler to 1024
OCR0A = 156; // 10mS at 1024
TIMSK0 |= (1 << OCIE0A); //enable the interrupt
}
Test our Sample Frequency
We will test our sample frequency by setting a pin high each time the code steps into the TIMER0 ISR. Place the following code in the main and newly created ISR for TIMER0.
Place in main
Place the ISR below the ISR for the ADC
{
PIND |= (1 << PIND2);
}
Every time the TIMER0 interrupt occurs, PD2 will change states. We can see this in the scope plot below.
Change ADC Trigger Source
Now that we know our TIMER0 is counting down properly, we will set the TIMER0 interrupt as a Trigger Source for the ADC. This will change the ADC Trigger Source from Free Running Mode to Timer/Counter0 Compare Match A.
To make this happen, add the following line to the ADC_init() function just above the sei(); line.
Now every time TIMER0 counts down, it generates and interrupt, and an ADC sample is taken.
Modify the ADC Interrupt Service Routine
Create the following variables just below the #define macros. These will be used to calculate the averages.
volatile uint16_t average = 0;
volatile uint16_t samples = 0
Here is the modified ISR(ADC_vect) which will gather the data and perform calculations for the average.
{
uint16_t duty = ADC;
OCR1B = duty;
accumulator += duty;
samples++;
if (samples == 100)
{
average = accumulator/100;
accumulator = 0;
samples = 0;
}
}
Complete Code
Here is the complete code for the lesson.
#include <avr/interrupt.h>
#define F_CPU 16000000UL
#define LED_ON PORTB |= (1<<PORTB5)
#define LED_OFF PORTB &= ~(1<<PORTB5)
#define LED_TOGGLE PINB |= (1<<PINB5)
volatile uint32_t accumulator = 0;
volatile uint16_t average = 0;
volatile uint16_t samples = 0;
ISR(TIMER1_COMPA_vect)
{
LED_ON;
}
ISR(TIMER1_COMPB_vect)
{
LED_OFF;
}
ISR(ADC_vect)
{
uint16_t duty = ADC;
OCR1B = duty;
accumulator += duty;
samples++;
if (samples == 100)
{
average = accumulator/100;
accumulator = 0;
samples = 0;
}
}
ISR(TIMER0_COMPA_vect)
{
PIND |= (1 << PIND2);
}
void PWM_Init(void)
{
TCCR1B |= (1 << CS10) | (1 << WGM12); //No Prescaler & set mode to CTC
TIMSK1 |= (1 << OCIE1A) | (1 << OCIE1B); //Enable the CTC interrupt
OCR1A = 800;
OCR1B = 400;
}
void milliS_timer0_10()
{
TCCR0A |= (1 << WGM01); //set to CTC mode
TCCR0B |= (1 << CS02) | (1 << CS00); //set prescaler to 1024
OCR0A = 156; // 10mS at 1024
TIMSK0 |= (1 << OCIE0A); //enable the interrupt
}
void ADC_init(void)
{
ADMUX |= (1 << REFS0) | (1 << MUX0); //AVCC equal to VCC and ADC1 input
ADCSRA |= (1 << ADEN) | (1 << ADSC) | (1 << ADATE) | (1 << ADIE) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
ADCSRB |= (1 <<ADTS1) | (1 << ADTS0);
sei();
}
int main(void) {
DDRD |= (1 << DDRD2);
DDRB |= (1 << DDRB5);
ADC_init();
PWM_Init();
milliS_timer0_10();
while (1) {
}
}
Set a breakpoint within the ADC interrupt service routine, enter debug mode, and watch the accumulator and average values. You should these update every time the application hits the breakpoint and is restarted.