The adc.h file has an adc_init() function for initialize ADC conversion, 5 variables declared externally so inline functions work and 7 inline functions for fast access to power flags and values:
The adc initialization function
void adc_init(void);
Externally declared variables, and volatile because their value can change in no close code.
Inline functions for fast access to power flags and values:
inline static uint16_t adc_get_power_value(void)
// Return the signed 16-bit ADC power value.
{
// Clear the ready ADC value ready flag.
adc_power_ready = 0;
// Return the value.
return adc_power_value;
}
inline static uint8_t adc_power_value_is_ready(void)
// Return the ADC power value ready flag.
{
// Return the value ready flag.
return adc_power_ready;
}
inline static void adc_power_value_clear_ready(void)
// Clear the ready ADC power value ready flag.
{
adc_power_ready = 0;
}
inline static uint16_t adc_get_position_value(void)
// Return the 16-bit ADC position value.
{
// Clear the ready ADC value ready flag.
adc_position_ready = 0;
// Return the value.
return adc_position_value;
}
inline static uint8_t adc_position_value_is_ready(void)
// Return the ADC position value ready flag.
{
// Return the value ready flag.
return adc_position_ready;
}
inline static void adc_position_value_clear_ready(void)
// Clear the ready ADC power value ready flag.
{
adc_position_ready = 0;
}
inline static void adc_read_voltage(void)
// Set a flag to start a adc on supply voltage channel.
{
adc_voltage_needed = 1;
}
The 10-bit Analog to Digital Converter (ADC) on the ATmega MCU is used to provide power and position feedback from the servo circuitry. The analog inputs are assigned as follows:
Then in the file a variable is defined for the last 3 bits of the ADCSRA (ADC Status Register A) the ADPS (ADC clock prescaler). The variable value is '111' and looking in the ADPS table this configuration is to divide the system clock of 20 Mhz by 128 having a 156.25 KHz ADC Clock. This is a timeslice used when analog sample is read.
#define ADPS ((1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0)) // a bit shifted to the ADPSX position.
Then the timer clock prescaler of 1024 is selected to yield a 19.531 KHz ADC clock from a 20 MHz system clock.
#define CSPS ((1<<CS02) | (0<<CS01) | (1<<CS00))
A variable to compare register value to generate a timer interrup and initiate a ADC sample every 9.984 ms.
#define CRVALUE 195
After this defines there are global used variables to maintain ADC state and values.
Once we have the global variables and defines we are going to explain the initialization adc function and the two ISR functions defined in the adc.c file:
adc_init()
Initialize ADC conversion for reading current monitoring and position
Set the ADC multiplexer selection registre to value '010-0010' setting the voltage reference, the bits right adjusted and position as the first channel to sample.
// Set the ADC multiplexer selection register.
ADMUX = (0<<REFS1) | (1<<REFS0) | // Select AVCC as voltage reference.
(0<<ADLAR) | // Keep high bits right adjusted.
ADC_CHANNEL_POSITION; // Position as the first channel to sample.
Set the ADC control status registers A and B.
7
6
5
4
3
2
1
0
ADCSRA
ADEN
ADSC
ADATE
ADIF
ADIE
ADPS2
ADPS1
ADPS0
Enable ADC converter
'1' when start conversion
'1' enable autotrigering
conversion complete, data written to registers(ADCL/ADCH)
activate interrupt
timeslice used when analog sample is read
7
6
5
4
3
2
1
0
ADCSRB
-
ACME
-
-
-
ADTS2
ADTS1
ADTS0
In the ADCSRB the value set is '011' that matches to Timer/Counter0 Compare Match A option
// Set the ADC control and status register B.
ADCSRB = (0<<ADTS2) | (1<<ADTS1) | (1<<ADTS0); // Timer/Counter0 Compare Match A.
// Set the ADC control and status register A.
ADCSRA = (1<<ADEN) | // Enable ADC.
(0<<ADSC) | // Don't start yet, will be triggered manually.
(0<<ADATE) | // Don't start auto triggering.
(1<<ADIE) | // Activate ADC conversion complete interrupt.
ADPS; // Prescale -- see above.
Overview of the two 8 bit timer registers
Counter0
Counter2
Description
TCCR0A
TCCR2A
Timer/Counter Control Register A. WGM0 control counter's mode(table)
TCCR0B
TCCR2B
Timer/Counter Control Register B. CS0 to slow down the rate.
TCNT0
TCNT2
Timer/Counter Register. Compared with OCR0A and increment.
OCR0A
OCR2A
Output Compare Register A. Threshold
OCR0B
OCR2B
Output Compare Register B. Threshold
TIMSK0
TIMSK2
Timer/Counter Interrupt Mask Register
TIFR0
TIFR2
Timer/Counter Interrupt Flag Register
Clear pending interrupts
TIFR0 = (1<OCF0B) | (1<<OCF0A) | (1<TOV0);
Set the timer/counter0 interrupt mask
TIMSK0 = (1<<OCIE0A) | // Interrupt on compare match A.
(0<<OCIE0B) | // No interrupt on compare match B.
(0<<TOIE0); // No interrupt on overflow.
// Set timer/counter0 control register A.
TCCR0A = (0<<COM0A1) | (0<<COM0A0) | // Disconnect OCOA.
(0<<COM0B1) | (0<<COM0B0) | // Disconnect OCOB.
(1<<WGM01) | (0<<WGM00); // Mode 2 - clear timer on compare match.
// Set timer/counter0 control register B.
TCCR0B = (0<<FOC0A) | (0<<FOC0B) | // No force output compare A or B.
(0<<WGM02) | // Mode 2 - clear timer on compare match.
CSPS; // Timer clock prescale -- see above.
ISR
_SIGNAL(SIG_OUTPUTCOMPARE0A): When the timer is equal to the compare value this kind of interruption happens. Handles timer/counter0 compare match A.
Increment the timer
timer_increment();
Set the ADC multiplexer selection register.
ADMUX = (0<<REFS1) | (1<<REFS0) | // Select AVCC as voltage reference.
(0<<ADLAR) | // Keep high bits right adjusted.
ADC_CHANNEL_POSITION; // Position as the first channel to sample.
Start the ADC reading of the position channel.
ADCSRA |= (1<<ADSC);
_SIGNAL(SIGADC):This interruption handles the AD conversion.
First the 10 bit ADC value is stored in new_value variable:
// Read the 10-bit ADC value.
uint16_t new_value = ADCW;
Then depending on the channel is being read different thigs are done inside a switch case structure:
// Which channel is being read?
switch (ADMUX & 0x0f)
- case _ADC_CHANNEL_POSITION_: new position value is save, flag the position value as ready and set Power as the next channel to sample.
// Save the new position value.
adc_position_value = new_value;
// Flag the position value as ready.
adc_position_ready = 1;
// Set the ADC multiplexer selection register.
ADMUX = (0<<REFS1) | (1<<REFS0) | // Select AVCC as voltage reference.
(0<<ADLAR) | // Keep high bits right adjusted.
ADC_CHANNEL_CURRENT; // Power as the next channel to sample.
// Start the ADC of the power channel now
ADCSRA |= (1<<ADSC);
break;
case _ADC_CHANNELCURRENT: new power value is save, flag the power value as ready and check if a battery voltage measurement is required.
// Save the new power value.
adc_power_value = new_value;
// Flag the power value as ready.
adc_power_ready = 1;
// Check the flag to see if a battery voltage measurement is required.
if (adc_voltage_needed)
{
// Set the ADC multiplexer selection register.
ADMUX = (0<<REFS1) | (1<<REFS0) | // Select AVCC as voltage reference.
(0<<ADLAR) | // Keep high bits right adjusted.
ADC_CHANNEL_BATTERY; // Battery voltage as the next channel to sample.
// Start the ADC of the voltage channel now
ADCSRA |= (1<<ADSC);
}
break;
case ADC_CHANNEL_BATTERY: remove adc_voltage_needed flag and save voltage values to registers
// Remove flag
adc_voltage_needed = 0;
// Save voltage value to registers
registers_write_word(REG_VOLTAGE_HI, REG_VOLTAGE_LO, new_value);
break;
Links for a better understanding of timers and adc in a microcontroller:
ADC module
The adc.h file has an adc_init() function for initialize ADC conversion, 5 variables declared externally so inline functions work and 7 inline functions for fast access to power flags and values:
The adc initialization function
Externally declared variables, and volatile because their value can change in no close code.
Inline functions for fast access to power flags and values:
The 10-bit Analog to Digital Converter (ADC) on the ATmega MCU is used to provide power and position feedback from the servo circuitry. The analog inputs are assigned as follows:
We can see that at the beginning of the adc.c
Then in the file a variable is defined for the last 3 bits of the ADCSRA (ADC Status Register A) the ADPS (ADC clock prescaler). The variable value is '111' and looking in the ADPS table this configuration is to divide the system clock of 20 Mhz by 128 having a 156.25 KHz ADC Clock. This is a timeslice used when analog sample is read.
Then the timer clock prescaler of 1024 is selected to yield a 19.531 KHz ADC clock from a 20 MHz system clock.
A variable to compare register value to generate a timer interrup and initiate a ADC sample every 9.984 ms.
After this defines there are global used variables to maintain ADC state and values.
Once we have the global variables and defines we are going to explain the initialization adc function and the two ISR functions defined in the adc.c file:
adc_init() Initialize ADC conversion for reading current monitoring and position
Overview of the 6 different registers used by the ATMega168 when performing analog to digital conversion.
In the ADCSRB the value set is '011' that matches to Timer/Counter0 Compare Match A option
Overview of the two 8 bit timer registers
ISR
_SIGNAL(SIG_OUTPUTCOMPARE0A) : When the timer is equal to the compare value this kind of interruption happens. Handles timer/counter0 compare match A.
_SIGNAL(SIGADC) :This interruption handles the AD conversion.
Links for a better understanding of timers and adc in a microcontroller: