ADC module from AT32UC3C

Go To Last Post
34 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello!

I'm using the µC AT32UC3C1128C:
http://www.atmel.com/Images/doc3...

For Programming i'm using AtmelStudio6.1.

I want to read in 4 ADC channels with one sequencer. The voltages are between GND (0V) and 2,5V, therefore singlesided. The reference voltage is 2,51V.

Here is the Code:

	// ADC config
	adcifa_opt_t adc_options = {
        .frequency= 100000,// internal ADC frequency (has to be between 32 kHz and 1,5 MHz)
	.reference_source= ADCIFA_ADCREF0,// external reference ADC Ref 0	(2,51 V)
	.sample_and_hold_disable= false,// higher accuracy when disabled (but in this case only single sequencer availaible -> we need dual sequencer, so leave S&H enabled)
	.single_sequencer_mode= true,// only one sequencer
	.free_running_mode_enable= false,// don't run free (trigger with provided timer)
	.sleep_mode_enable= false
		
	};


	//ADC sequencer config
	adcifa_sequencer_opt_t adc_sequencer_options = {
	.convnb	= 4,	// 4 channels
	.resolution= ADCIFA_SRES_12B,// resolution 12 Bit
	.trigger_selection= ADCIFA_TRGSEL_ITIMER,// trigger with internal timer
	.start_of_conversion= ADCIFA_SOCB_ALLSEQ,// A complete sequence is performed on a SOC event
	.sh_mode= ADCIFA_SH_MODE_OVERSAMP,// oversampling acivated, higher accuracy, but a conversion takes two clock cycles
	.half_word_adjustment= ADCIFA_HWLA_NOADJ,// default adjustment
	.software_acknowledge=ADCIFA_SA_NO_EOS_SOFTACK// Sequencer will restart a new sequence on a new Start-Of-Conversion command
	};
local_seq_options = adc_sequencer_options;// needed for GetAdcValue function


	adcifa_sequencer_conversion_opt_t adc_seq_conv_opt[ADC_CHANNELS_TEC] = {
		{
		.channel_n= AVR32_ADCIFA_INN_GNDANA,
		.channel_p= VR32_ADCIFA_INP_ADCIN0, // TEC0_CUR
		.gain= ADCIFA_SHG_1,			
		},
		{
		.channel_n= AVR32_ADCIFA_INN_GNDANA,
		.channel_p= AVR32_ADCIFA_INP_ADCIN1, // TEC1_CUR
		.gain= ADCIFA_SHG_1,
		},
		{
		.channel_n= AVR32_ADCIFA_INN_GNDANA,
		.channel_p= AVR32_ADCIFA_INP_ADCIN2, // TEC0_TEMP
		.gain= ADCIFA_SHG_1,
		},		
		{
		.channel_n= AVR32_ADCIFA_INN_GNDANA,
		.channel_p= AVR32_ADCIFA_INP_ADCIN3, // TEC1_TEMP
		.gain= ADCIFA_SHG_1,
		},		
		
	};

	adcifa_get_calibration_data(&AVR32_ADCIFA, &adc_options);
	adcifa_configure(&AVR32_ADCIFA, &adc_options, CPU_SPEED);
	ADCIFA_set_offset_calibration(238);
	adcifa_configure_sequencer(&AVR32_ADCIFA,ADCIFA_SEQ0, &adc_sequencer_options, adc_seq_conv_opt);
	
	adcifa_start_itimer(&AVR32_ADCIFA, ITIMER_1s);
	adcifa_start_sequencer(&AVR32_ADCIFA, ADCIFA_SEQ0);

Now I havr written a function to read in the current adc values:

S16 GetAdcValue(U8 channel_ID)
{
	S16 adc_value[ADC_CHANNELS_TEC];
	//adcifa_start_sequencer(&AVR32_ADCIFA,0);
	adcifa_get_values_from_sequencer(&AVR32_ADCIFA,ADCIFA_SEQ0,&local_seq_options,adc_value);
	return adc_value[channel_ID];
}

By reading in adc values (calling this function), the values are (reference voltage ADC = 2,51V):
Channel 1: Voltage at ADCIN-PIN: 16mV ADC value: 65527
Channel 2: Voltage at ADCIN-PIN: 207mV ADC value: 260
Channel 3: Voltage at ADCIN-PIN: 3,3V ADC value: 2039
Channel 4: Voltage at ADCIN-PIN: 990mV ADC value: 1990

The result is in two's complement, so there are only 11 Bits solution left (max value 2048). If the voltage to be read in is very low, there may be an offset, so that the value is very high because of two's complement. This would explain the high value of the 16-bit int of channel 1.

channel 2: this value is approximately correct.

channel 3: also this value is approx. correct

channel 4: ??? no idea

Has somebody an idea what could be wrong?

Another questions:
1.) How does the offset calibration work? Do i have to apply GROUND to the input pin, and save the value of the adc result in a specific register? Is this correct?

2.) How has the sequencer to be initialized, when there is an array of [4] for adc_seq_conv_opt? Is the statement

adcifa_configure_sequencer(&AVR32_ADCIFA,ADCIFA_SEQ0, &adc_sequencer_options, adc_seq_conv_opt);

valid anyway?
Or do i have to call the function adcifa_configure_sequencer four times for each element?

Thanks a lot!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I just can't find a mistake...
Could somebody at least tell me how i have to do the offset calibration?

Thanks!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Are you sure the ADC sequencer has finished before you try to read out the values?
Keep in mind the ADC is pipelined so the first output isn't going to be ready until atleast 9 ADC clock cycles after SOC even if each sample only takes 2(oversampling) ADC clock cycles.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello Duker!

Thank you for answering!
That's a good argument, i will pursue this way...

Are there any other conspicuousnesses in the code? DO you also know how i have to do the offset calibration?

Thx, Werner

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The offset calibration is simply a predefined value that is added to the conversion result by hardware.

I've been lookig through the ADCIFA driver (yuck).
adcifa_get_values_from_sequencer() returns ADCIFA_STATUS_NOT_COMPLETED until the 1st conversion sequence has completed.
After that it always retruns ADCIFA_STATUS_COMPLETED even if a new sequence is triggered by the internal timer.

The reference driver is rather poor.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello,

I have solved the problem!
It wasn't the conversion time, but rather a mistake in configuring the adc. I only changed the parameter "sample_and_hold_disable" in adcifa_opt_t to true instead of false before. Now the values are correct!
Maybe, as you have said, the conversion takes too long if sample and hold is activated?!

What do you mean with the reference driver is poor?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ok, the meaning of the offset value is clear in my mind.

I have now measured a GND signal with the adc, the result is -12.
2 questions:
what value ocal do i have to give to the funktion "ADCIFA_set_offset_calibration(ocal)" in this case? (page 1117 in data sheet) I don't feel certain because of the negative value... I think it has to be 51, right?

do i have to measure this value at each start or can i always leave it at a fixed value (e.g.51) ?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Personally, I wouldn't use the OCAL at all as it is just a fixed number that is added or subtracted from the sampled values.
To get accurate data you need to calibrate your inputs anyway and most likely would end up using different offsets for each channel due to measurement offsets outside the UC3.

This:

nSample[0] += 3;
nSample[1] += 0;
nSample[2] += -5;
nSample[3] += 19;
or dynamic offset...
nSample[0] += nOffset[0];
nSample[1] += nOffset[1];
nSample[2] += nOffset[2];
nSample[3] += nOffset[3];

looks better then this:

nSample[0] += 3;
nSample[2] += -5;
nSample[3] += 19;
or dynamic offset...
nSample[0] += nOffset[0];
nSample[2] += nOffset[2];
nSample[3] += nOffset[3];
// nSample[1] is corrected through ADCIFA_set_offset_calibration() somewhere else in the code as it must be set before data is sampled.
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Okay, but wher do i get the values for the offset (3,0,-5,19 in your example) ? do i have to measure GND potential on each channel? from these measurements i will get the values for the offset of each channel?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

In theory you could sample GND-GND... but there might be some additional errors when sampling voltage levels very close to the supply rails (AGND and AVCC). In any case, this would get you the internal ADC offset.
When I mentioned calibration earlier I meant you could apply some known signals to your input so you can calculate and eliminate any offset in your entire signal paths.
This would also be a suitable time to perform any output scaling.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello Duker, thanks for helping me...

So i have to switch a know signal to the inputs of each channel, thereout i get the values for caibration. Do i have to do this by hardware? Could you give me a hint how you would do this?!

What do you mean with "output scaling"?

THX, werner

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

It appears you are trying to measure two currents and two temperatures. To calibrate them you would perform two measurements for each channel. For a current channel, pass a known current through the current sensing circuitry and note the sampled value. Then pass a different known current through the same circuit and note the new sampled value. Now you can do linear interpolation to calculate what the sampled value should be if there is no current flowing in the measurement circuitry and that would be your offset to add to the sampled value for that channel. You'd get the best results if you use known currents closer to (but not exactly) your design minimum and maximum.

You can also calculate the relationship between ADC units and measurement units (number of ADC ticks per units of current).

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello Duker,

I understand your idea.

When i do so, I would get a value for the offset. This value will be negated and added to the result of a adc-measurement. But now i have a hard value, that is constant for the whole lifetime of the device. I think this offset value is dependent from many factors like temperatur etc. In my opinion, I have to do this calibration more often with certain time intervals. Do you know what i mean and are you also of that opinion? Or do you think a hard value (only one calibration measurement for the offset value) is enough?

Thank you for that nice discussion :)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This is all a trade-off between complexity and accuracy.

Dynamic recalibration during operation would get you the most accurate results but it would be the most complex to implement both in software complexity and BOM count/cost.

Doing a single calibration during manufacturing would get rid of the largest portion of your measurement error as you are now compensating for static tolerance deviations in all the components in your system (as long as it is calibrated at typical operating conditions).

Variations in temperature can also influence the result but usually to a smaller degree. Temperature-induced errors are in most cases predictable and repeatable so it is enough to characterize only one or a small number of manufactured units to find out what is affected by temperature and by how much.
This information is then used to calculate temperature-compensating values that are then used on all manufactured units.
For this to work, you'd need to somehow measure the temperature in your system but it seems you're already measuring some temperatures. If not, I'm sure you could squeeze an MCP9700 between the rest of your analog input stuff to get a rough temperature measurement for that part of your board.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Sounds nice, thank you for your help!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello again,

one open question:
The results of the ad conversion are stored in an signed 16 bit int. The resolution os the adc is 12 Bit, with two's complement resulting in 11 Bit (that gives a range of 2048). The reference voltage of the adc (extern) is 2,5V. How can that be, that a value of 3,3V on the input of an adc channel gives onlay a value of 2039 instead of the maximum 2048? Is this corresponding with the discussed offset?

THX, werner

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Personally, I try to keep my signals well within the measuring range, as I have seen too much strangeness when operating on the upper or lower bounds.
Your result of 2039 is undesirable but not surprising as you are operating beyond the measuring range.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ok, sounds good...

I'm justing thinking about the following:
I trigger the ADC with the internak ITimer every second. How is it guaranteed, that in the case of calling the function GetADCValue and therefore reading out the results no conversion takes place at the same time? I am thinking about this, because i think it isn't allowed to read out the values when a new result is written?

Thank You!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

In the datasheet I did not find any timing restrictions to read the RESn registers.

Note that adcifa_get_values_from_sequencer(,,) will exit and return ADCIFA_STATUS_NOT_COMPLETED if a conversion is in progress.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thank, I've recognized this...

But I'm not shure, wheter my new code is correct or not:

S16 GetAdcValues (U8 channel_ID)
{
	S16 adc_values[ADC_CHANNELS];
	
	while(adcifa_get_values_from_sequencer(&AVR32_ADCIFA,ADCIFA_SEQ0,©_of_seq_options,adc_values) != ADCIFA_STATUS_COMPLETED)
	{
		// wait until sequencer completed conversions
	}
	
	return adc_values[channel_ID];
}

Does this code guarantees, that writing a new result and reading this result don't takes place simultanously?

Thanks, werner

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

adcifa_get_values_from_sequencer() is only going to return ADCIFA_STATUS_NOT_COMPLETED while the first sequence is running. After that, it'll return ADCIFA_STATUS_COMPLETED and update the result array regardless of the actual sequencer state. Even if the ITIMER triggers a new conversion, adcifa_get_values_from_sequencer() is going to return ADCIFA_STATUS_COMPLETED and update the result array, possibly with a mix of data from the ongoing conversion and the previous conversion, depending on how far along the conversion has progressed at that time.

This is solely a "feature" of the driver implementation, as the ADCIFA hardware is ALLOT more capable then what this driver makes it seem like. I absolutely love this new ADC compared to the ones used in UC3A and UC3B chips. It's just too bad the reference driver is so limited.

To really make it shine, the ADCIFA should run be run in DMA and interrupt-driven mode.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

DukerX, you are correct., I forgot that the end-of-sequence and end-of-conversion flags must be be manually cleared.

Werner13, I do not see a problem with simultaneous conversion and reading.
(It can be a problem in some circumstances on the AVR8 processors which require several instructions to produce a 16-bit result from 8-bit registers)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello,

Does this mean, that I could leave my function GetADCValues as it is and havn't to think about problems?

Thanks!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Depends on how you define problems... If reading samples not knowing if it is the ones you've already read before, or if no new sampling has happened at all, or if you have missed samples in between readings doesn't qualify as problems, then yes you should be OK.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ok,
I think in this case this is no problem, because accurate and fast measurements aren't really necessary. This are measurements for a control of temperature and current. If a new value is recognized some seconds later, this doesn't really matters.

I justed worried about the fact, that a conversion overwrites the result while reading out, and so random values occur in the result (because e.g. th LSB is from the old value and the rest from the new...)

Greets, thx werner

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello all, I'm trying to do some basic ADC temperature readings, for now all i can read is FF FF values, i did some debugging and found that in my program, the function "adcifa_start_sequencer" is filling all of sequencer 0 result registers with 0xFFFFFFF. does anyone have an idea of why this may be happenning ?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Semi colon on line 47 perhaps?
.
Seriously, how do you expect anyone to help if you don't show the code?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

My bad, Sorry.

Last Edited: Sat. Jun 24, 2017 - 09:12 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

 

How to properly post source code: http://www.avrfreaks.net/comment...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

#include <asf.h>
#include <user_board.h>
#include <stdio.h>
#include <math.h>
#include <stdint.h>
#include "power_clocks_lib.h"

 

#define TEMP_CONST_A                                   1.12766979300187e-3
#define TEMP_CONST_B                                   2.34444184128213e-4
#define TEMP_CONST_C                                   8.47692130592308e-8
#define DEFAULT_ADC_VREF                            3.2
#define ALT_ADC_VREF                                    1.8
#define R0                                                       10000
#define KELVIN_TO_CELSIUS                           273
#define CALIBRATION_ADCIFA_NB_SEQ           8

 

uint8_t tx_length = 2;
uint8_t i;
volatile uint16_t adc_val;
float_t adc_vref = 3.2;
volatile uint8_t *char_ptr;
volatile uint16_t adcifa_values[8];
uint8_t temperature1 [2];

 

// PIN mapping.
const gpio_map_t USART_GPIO_MAP =
{
    #if defined(AVR32_USART3)
    {83, 5},
    
    {84, 5},
    
    {82, 5},
    
    {117, 0},
    #endif
};

 

const gpio_map_t ADC_GPIO_MAP =
{
    #if defined(TEMP_TS0_CH)
    {TEMP_TS0_PIN, TEMP_TS0_FCN},
    #endif
};

 

// Options.
const usart_options_t USART_OPTIONS =
{
    .baudrate = 9600,
    .channelmode = USART_NORMAL_CHMODE,
    .charlength = 8,
    .paritytype = USART_NO_PARITY,
    .stopbits = USART_1_STOPBIT,
};

 

adcifa_opt_t adcifa_opt =
{
    .frequency = 32000, // internal ADC freq in Hz
    .reference_source = ADCIFA_ADCREF0, //ADCIFA_ADCREF0,
    .free_running_mode_enable = 0,
    .gain_calibration_value = 1,
    .sh0_calibration_value = 0,
    .sh1_calibration_value = 0,
    .offset_calibration_value = 0,
    .single_sequencer_mode = 0,
    .sleep_mode_enable = 1,
    .mux_settle_more_time = 1,
    .sample_and_hold_disable = 1,
};

 

adcifa_sequencer_opt_t     adcifa_sequence_opt =
{    
    .convnb               =     4,
    .resolution           =        ADCIFA_SRES_12B,
    .trigger_selection    =        ADCIFA_TRGSEL_SOFT,
    .start_of_conversion  =        ADCIFA_SOCB_ALLSEQ,
    .sh_mode              =        ADCIFA_SH_MODE_OVERSAMP,
    .half_word_adjustment =        ADCIFA_HWLA_NOADJ,
    .software_acknowledge =        ADCIFA_SA_NO_EOS_SOFTACK,
};

 

// SQUENCER CONVERSIONS OPTIONS

adcifa_sequencer_conversion_opt_t adcifa_sequence_conversion_opt =
{
    .channel_p = AVR32_ADCIFA_INP_ADCIN5,
    .channel_n = AVR32_ADCIFA_INN_GNDANA,
    .gain      = ADCIFA_SHG_1,
};

 

//Function declarations
void adc_init();
void usart_init();
void temp_calc(volatile uint16_t adc_val, volatile float_t adc_vref, uint8_t *T);

 

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Main.

int main (void)
{    
    board_init();
    sysclk_init();
    adc_init();
    usart_init();
    
    while (1)
{    
    adcifa_get_calibration_data(&AVR32_ADCIFA, &adcifa_opt);
    adcifa_start_sequencer(&AVR32_ADCIFA, ADCIFA_SEQ0); 
    
    while(!adcifa_check_eoc(&AVR32_ADCIFA, ADCIFA_SEQ0));
    delay_us(100);
    
    
    adcifa_get_values_from_sequencer(&AVR32_ADCIFA, ADCIFA_SEQ0, &adcifa_sequence_opt, adcifa_values);
    adc_val = adcifa_values[0];
    gpio_set_gpio_pin(LED_GREEN);
    
    temp_calc(adc_val, adc_vref, char_ptr);
    
        for (i=0; i<tx_length; i++)
        {
            gpio_set_gpio_pin(LED_RED);
         
            usart_putchar(&XBEE_USART, temperature1[i]);
          
        }
}
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Functions.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

 

void adc_init()

{
    adcifa_configure(&AVR32_ADCIFA, &adcifa_opt, FADC);
    
    sysclk_enable_pbc_module(SYSCLK_ADCIFA);
    
    adcifa_configure_sequencer(&AVR32_ADCIFA, ADCIFA_SEQ0, &adcifa_sequence_opt, &adcifa_sequence_conversion_opt);
    
    gpio_enable_module(ADC_GPIO_MAP, sizeof(ADC_GPIO_MAP)/sizeof(ADC_GPIO_MAP[0]));
}

 

 

void usart_init()
{
    gpio_enable_module(USART_GPIO_MAP,sizeof(USART_GPIO_MAP) / sizeof(USART_GPIO_MAP[0]));
    usart_init_rs232(&XBEE_USART, &USART_OPTIONS, sysclk_get_main_hz());
}

 

 

void temp_calc(volatile uint16_t adc_val, volatile float_t adc_vref, uint8_t *T)
{
    volatile int32_t temp_i;
    volatile float temp_f;
    uint8_t i;
    volatile float adc_valf = adc_val*ALT_ADC_VREF/pow(2,10);
    volatile float resistance_thermistor = 0;
    
    float a = TEMP_CONST_A, b = TEMP_CONST_B, c = TEMP_CONST_C;
    resistance_thermistor = R0*(adc_vref-adc_valf)/adc_valf;
    
    temp_f = 1/(a+b*log(resistance_thermistor)+c*pow(log(resistance_thermistor),3))- KELVIN_TO_CELSIUS;    
    
    temp_i = temp_f;
    T[0] = temp_i;
    
    temp_f = (temp_f - temp_i)*10;
    temp_i = temp_f;
    T[1] = temp_i;
}

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Is TEMP_TSO_CH defined ?
What is the value of TEMP_TS0_FCN ?
Is TEMP_TS0_PIN = 9 (ADCIN5) ?


Where does char_ptr point to ?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yes TS0 is defined, TEMP_TS0_FCN is defined as 0 (input), TEMP_TS0_PIN refers to PA09 (ADCIN5) which is my analogue input. Char_ptr points to the value calculated by the function temp_calc which transforms the read values to celcius. (There ia a missing function (temperature1[i] = char_ptr[i] ) that goes before usart_putchar.

I think I have a problem with my parameters as the sequencer value register are being set to FF whenever i start a conversion (seen with debugg I/O window).

HN

Last Edited: Sun. Jun 25, 2017 - 03:53 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

What frequency is FADC ?


Is TEMP_TSO_CH defined ?.
I ask again because if it is not defined then ADCIN5 will not be connected to the physical pin.


Why is .convnb set to 4 ?.,
adcifa_sequence_conversion_opt only defines one ADC input.


Try disabling the ADC sleep-mode.


char_ptr is probably not related to your 'results are 0xFF' problem, but it is still a problem.
temp_calc(adc_val, adc_vref, char_ptr); passes the value of char_ptr into temp_calc(,)
but the code in post #30 never assigns a value to char_ptr
therefore temp_calc(,) will write to unexpected addresses.

Last Edited: Sun. Jun 25, 2017 - 06:52 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

It's working now, I disabled sleep mode, and set conv nb to 1 as I only have one channel, thanks a lot for taking the time to help.