The SAMD20’s ASF ADC callback driver works great and here are some basic code examples based off of Atmel’s own examples. These are simple i.e. your plain old micro reading an analog pin snippets.
Initialization
First thing declare your adc module object somewhere
struct adc_module adc_instance;
Then configure your ADC in one of three possible cases:
void configure_adc(void)
{
struct adc_config config_adc;
adc_get_config_defaults(&config_adc);
config_adc.gain_factor = ADC_GAIN_FACTOR_1X;
config_adc.clock_prescaler = ADC_CLOCK_PRESCALER_DIV8;
config_adc.reference = ADC_REFERENCE_AREFA;
config_adc.positive_input = ADC_POSITIVE_INPUT_PIN0;
config_adc.resolution = ADC_RESOLUTION_12BIT;
adc_init(&adc_instance, ADC, &config_adc);
adc_enable(&adc_instance);
adc_register_callback(&adc_instance, adc_complete_callback, ADC_CALLBACK_READ_BUFFER);
adc_enable_callback(&adc_instance, ADC_CALLBACK_READ_BUFFER);
}
What you really want to pay attention to do is the positive_input setting, this is your channel you are reading, you must set it to your channel. Missing is also negative_input in case you want to use a single channel for negative input instead of the default chip ground.
For the enums and defines for these settings you will want to look at the /ASF/sam0/drivers/adc/adc.h header file which contains all the possible options. There are quite a additional settings avaliable such as windowed ADC reads.
Pin scan initialization
In case you have a range of analog pins that are in order(i.e. AIN6,AIN7,AIN8), you will want to initialize via pin scan.
Random assortment initialization
Sometimes life sucks and you can’t pin scan because somebody picked random pins for analog inputs much to your joy. Not to worry, you can still initialize them!
You have to simply manually initialize the pins to analog input functionality like shown below:
/* Configure analog pins */
struct system_pinmux_config config;
system_pinmux_get_config_defaults(&config);
/* Analog functions are all on MUX setting B */
config.input_pull = SYSTEM_PINMUX_PIN_PULL_NONE;
config.mux_position = 1;
system_pinmux_pin_set_config(PIN_PA02, &config);
system_pinmux_pin_set_config(PIN_PA07, &config);
mux_position = 1 sets it to the analog peripheral. While input_pull is being set to SYSTEM_PINMUX_PIN_PULL_NONE to cancel out the default pinmux_config struct setting of a pull-up enabled.
Simply repeat the system_pinmux_pin_set_config for all pins that are meant to be analog inputs.
Then simply use the same configure_adc as the single channel example. Making sure to adjust the positive_input and negative_input channels. Also adjust any other settings you might need.
Triggering Conversions
Simple
The quickest way to trigger conversions is by calling adc_read_buffer_job. This function starts a background conversion.
adc_read_buffer_job(&adc_instance,adc_buffer,4);
You must pass it:
- Pointer to ADC instance
- Pointer to array where result is stored (adc_buffer in this case which is an array of four 16-bit words)
- Number of samples to take before callback occurs (which is 4)
And that’s it!
Callback
The callback function will be called every-time a conversion is completed as long as adc_register_callback and adc_callback_enable are used in the configure_adc. The callback name in the examples here is adc_complete_callback. It of course can be renamed to literally anything.
Simple callback
This is the easiest case to handle.
void adc_complete_callback(const struct adc_module *const module)
{
//compute the average
uint32_t avg = 0;
for(uint8_t i=0;i<4;i++)
{
avg += adc_buffer[i];
}
avg >>= 2;
//do something with the average
}
## Changing the channel in case of scattered pins If you have the case of scattered analog pins, ideally in your callback you change to the next channel. Optionally you also start the next conversion but you may want to tie that to an timer or event instead. It’s pretty simple thing to do adc_set_positive_input.
void adc_complete_callback(const struct adc_module *const module)
{
//compute the average
uint32_t avg = 0;
for(uint8_t i=0;i<4;i++)
{
avg += adc_buffer[i];
}
avg >>= 2;
//do something with the average
//change our channel
if( adc_current_channel == ADC_POSITIVE_INPUT_PIN0 )
{
adc_set_positive_input(&adc_instance, ADC_POSITIVE_INPUT_PIN11);
}
else
{
adc_set_positive_input(&adc_instance, ADC_POSITIVE_INPUT_PIN0);
}
//OPTIONAL: trigger next conversion
adc_read_buffer_job(&adc_instance,adc_buffer,4);
}
*adc_set_negative_input& is also available in case you need to change that.