Monthly Archives: June 2014

ASF: SAMD20/SAMD21 ADC (Callback) Simple Setup

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:

Single Channel Initialization

If you only are using a single channel, then initialization is simple.

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:

  1. Pointer to ADC instance
  2. Pointer to array where result is stored (adc_buffer in this case which is an array of four 16-bit words)
  3. 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.