DAC calibration UC3

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

Hi Freaks,

has anyone DAC calibration code for the UC3(C) you're willing to share? ASF is still missing this functionality:(

Many thanks in advance!

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

ASF 3.7.3 has dacifb_get_calibration_data(,,) to read and apply the factory-supplied calibration.
I haven't checked previous versions.

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

Hi mikech,

that's right. But: The AVR32 (at least UC3, don't know about others) don't get a factory calibration, making this function useless. :roll: Actually more than that: if you use it, you'll get wrong DAC outputs because the offset is way off. See ASF bugzilla 2785.

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

I'm trying to do my own calibration function now but i stumbled pretty fast. This is what the UC3 data sheet says about DAC calibration:

Quote:
To achieve optimal accuracy, it is possible to calibrate both gain (GOC.GCR) and offset error
(GOC.OCR) in the DAC. The MSB of the GCR and OCR fields is the sign bit.
Gain and Offset are not calibrated automatically and this must be done by software. To perform
this operation, the DAC must be enabled and AOE and BOE bits in the control register must be
set to zero. These settings ensure that the DAC internal output is routed to the ADC.
The test values converted by the DAC are sampled by the ADC which returns a measured value
of the DAC output. Calculating the difference between a series of DACIFB channel input values
and their respective effective levels after conversion makes it possible to deduce both DAC gain
and offset biases.

Unfortunatly i cannot find any information what DAC output channel gets routed to what ADC input channel. Without that it's pure guessing what happens with disabled DAC outputs :cry:

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

Ok, found it: Not in the datasheet, but poking around in adcifa.h shows these lines:

#define AVR32_ADCIFA_INP_DAC0_INT            8
#define AVR32_ADCIFA_INN_DAC1_INT            8

Looks promising, lets see...

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

Those internal inputs are described in the datasheet at the very end of the ADCIFA section !

I think your factory-page was erased.

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

Thanks, now i've found it in the datasheet as well. Lucily it describes the values i've already found:)

Regarding the factory page erasing, i don't think so. The only thing that gets factory-calibrated is the adcifa gain. Everything else(adcifa offset, dacifb gain and offset) must get user-calibrated. See my excerpt of the datasheet above or have a look at the bugzilla discussion.

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

You are correct.
I checked a UC3Cxxx in one of my projects and the only useful values in the factory page are the ADCIF gain and offset., the DACIF values are all 0xFF :(

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

Did you ever produce working DAC calibration code? I'm interested in this as well.

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

Over 3,000 views and still no support in the ASF. People obviously want this.

Did anyone ever produce working code? I've managed to configure the DACs by connecting the outputs to pins that can then be routed to the ADC. I never was able to get a calibration routine to work using the internal outputs.

Here's my code since no one else seems to be willing to share. It's really frustrating when people won't post code for others to see because everyone has to solve the problem on their own instead of building on the work of others.

Configure the DACs and ADC (Note: the external reference is 2.048 V and I'm setting my DACs to swing between 0.5 and 2.0 V):


// Connect both DAC channel A outputs to
// ADC pins (DAC0 -> ADCIN6, DAC1 -> ADCIN15).
// Ideally internal routing would be used, but
// I haven't been able to get it to work
// For internal routing, set dac0->CFR.chc = 0
// (disable S/H) and then use the correct ADC
// inputs (AVR32_ADCIFA_INP_DAC0_INT and
// AVR32_ADCIFA_INN_DAC1_INT).

volatile avr32_dacifb_t *dac0 = &AVR32_DACIFB0;
volatile avr32_dacifb_t *dac1 = &AVR32_DACIFB1;
volatile avr32_adcifa_t *adc = &AVR32_ADCIFA;

/////////////////////////////////////////////////////////////////////////
// Configure ADC, DAC, and ADC sequencer 0 for ADC and DAC calibration //
/////////////////////////////////////////////////////////////////////////
adcifa_opt_t adc_cfg = {
.frequency = 50000,
.reference_source = ADCIFA_ADCREF1,
.sample_and_hold_disable = true,
.single_sequencer_mode = true,
.free_running_mode_enable = false,
.sleep_mode_enable = false,
.mux_settle_more_time = false,
.offset_calibration_value = 0
};
adcifa_sequencer_opt_t seq_cfg = {
.convnb = 8,
.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
};
dacifb_opt_t dacX_cfg = {
.reference = DACIFB_REFERENCE_EXT,
.channel_selection = DACIFB_CHANNEL_SELECTION_A,
.low_power = false,
.dual = false,
.prescaler_clock_hz = F_CPU/8
};

// Calibrate and configure ADC
adcifa_get_calibration_data(adc, &adc_cfg);
adcifa_calibrate_offset(adc, &adc_cfg, sysclk_get_cpu_hz());
adcifa_configure(adc, &adc_cfg, sysclk_get_cpu_hz());

// Configure DACs
dacifb_configure(dac0, &dacX_cfg, F_CPU);
dacifb_configure(dac1, &dacX_cfg, F_CPU);

dac0->CR.aoe = 1; // Channel A Output Enable
dac0->CR.arae = 1; // auto-refresh enabled
dac0->CFR.chc = 1; // Channel A only
dac0->TCR.chi = 8;
dac0->TCR.presc = 2;
dac0->CR.en = 1; // DAC Enable

dac1->CR.aoe = 1; // Channel A Output Enable
dac1->CR.arae = 1; // auto-refresh enabled
dac1->CFR.chc = 1; // Channel A only
dac1->TCR.chi = 8;
dac1->TCR.presc = 2;
dac1->CR.en = 1; // DAC Enable

Calibrate the N output (in my case, DAC0A) to be 0.5 V, then the P output (DAC1A) to be equal to the N output, and finally find the DAC1A value that corresponds to 2.0 V.

Note: I know this could be refactored, but I left it this way so I could make changes to each calibration routine.


////////////////////////////////////////
// Calibrate the N output to be 0.5 V //
////////////////////////////////////////
for(U16 i = 0; i < 2000; i++) {
dac0->DR0.dca = i; // Write the DAC value
delay_us(20); // Let the DAC output settle

// Configure ADC sequencer 0
adcifa_sequencer_conversion_opt_t seq_inp[8];
for (U8 j = 0; j < 8; j++){
seq_inp[j].channel_p = UC3_ADCIN6_TUNNEL_N_INP;
//seq_inp[j].channel_p = AVR32_ADCIFA_INP_DAC0_INT;
seq_inp[j].channel_n = AVR32_ADCIFA_INN_GNDANA;
seq_inp[j].gain = ADCIFA_SHG_1;
}
adcifa_configure_sequencer(adc, 0, &seq_cfg, seq_inp);
adcifa_start_sequencer(adc, 0);
while(!ADCIFA_is_eos_sequencer_0());
delay_us(250); // Doesn't work without this - unknown reason

float avg = 0;
for(U8 k = 0; k < 8; k++) {
avg += (float)(ADCIFA_read_resx_sequencer_0(7) << 1);
}
avg /= 8.0;
// (0.5 V / 2.048 V) * 4096 = 1000
if( (avg > 998) && (avg < 1002) ) {
dac0->DR0.dca = i;
break;
}
}
////////////////////////////
// Calibrate P to equal N //
////////////////////////////
for(U16 i = 0; i < 3000; i++) {
dac1->DR0.dca = i; // Write the DAC value
delay_us(20); // Let the DAC output settle

// Configure ADC sequencer 0
adcifa_sequencer_conversion_opt_t seq_inp[8];
for (U8 j = 0; j < 8; j++){
//seq_inp[j].channel_p = AVR32_ADCIFA_INP_DAC0_INT;
//seq_inp[j].channel_n = AVR32_ADCIFA_INN_DAC1_INT;
seq_inp[j].channel_p = UC3_ADCIN6_TUNNEL_N_INP;
seq_inp[j].channel_n = UC3_ADCIN15_TUNNEL_P_INN;
seq_inp[j].gain = ADCIFA_SHG_1;
}
adcifa_configure_sequencer(adc, 0, &seq_cfg, seq_inp);
adcifa_start_sequencer(adc, 0);
while(!ADCIFA_is_eos_sequencer_0());
delay_us(250); // Doesn't work without this - unknown reason

float avg = 0;
for(U8 k = 0; k < 8; k++) {
avg += (float)(ADCIFA_read_resx_sequencer_0(7) << 1);
}
avg /= 8.0;
if( (avg > -2) && (avg < 2) ) {
dac_zero = i;
break;
}
}
////////////////////////////////////////////////
// Calibrate the P output to be 1.5 V above N //
////////////////////////////////////////////////
for(U16 i = dac_zero; i < 4096; i++) {
dac1->DR0.dca = i; // Write the DAC value
delay_us(20); // Let the DAC output settle

// Configure ADC sequencer 0
adcifa_sequencer_conversion_opt_t seq_inp[8];
for (U8 j = 0; j < 8; j++){
//seq_inp[j].channel_p = AVR32_ADCIFA_INP_DAC0_INT;
//seq_inp[j].channel_n = AVR32_ADCIFA_INN_DAC1_INT;
seq_inp[j].channel_p = UC3_ADCIN6_TUNNEL_N_INP;
seq_inp[j].channel_n = UC3_ADCIN15_TUNNEL_P_INN;
seq_inp[j].gain = ADCIFA_SHG_1;
}
adcifa_configure_sequencer(adc, 0, &seq_cfg, seq_inp);
adcifa_start_sequencer(adc, 0);
while(!ADCIFA_is_eos_sequencer_0());
delay_us(250); // Doesn't work without this - unknown reason

float avg = 0;
for(U8 k = 0; k < 8; k++) {
avg += (float)(-ADCIFA_read_resx_sequencer_0(7) << 1);
}
avg /= 8.0;
// (1.5 V / 2.048 V) * 4096 = 3000
if( (avg > 2998) && (avg < 3002) ) {
dac_rail = i;
dac_range = dac_rail - dac_zero;
break;
}
}

Note: I shift the resx results left because I want a 12 bit result and the ADC only measures 11 bits because the 12 bit is for the sign. If you think this is wrong, please let me know, but it seems to work. I also have no idea why I need a 250 us delay for the results to be correct, but if I remove that delay the conditional statement at the end of each loop never works.

After configuring the PDCA and PEVC to send values to DAC1A based on triggers from a PWM channel, the output works as expected as you can see in the attached image. N (DAC0A) is green and P (DAC1A) is yellow. In case you're curious, the DACs are driving a differential amplifier which also has an absolute value feedback circuit to allow the UC3 to measure the offset of the whole system (the output amplifier swings between -15 and +300 V).

Attachment(s): 

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

So far I have not needed to use the DAC therefore I have not tried to write any code for it, but I do appreciate the effort you've put in.

I wonder if the delay_us(250); // Doesn't work without this - unknown reason is due to the sequencer restarting again ( .software_acknowledge = ADCIFA_SA_NO_EOS_SOFTACK; )

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

Thank you for reviewing my code so thoroughly. I'm glad you mentioned that because I was able to remove the delay entirely after looking into the issue to respond to your post. I must have been doing something wrong when I first setup the routine that has inadvertently been corrected.

I read the datasheet closer to understand the software acknowledge feature. The defines seem to suggest something other than what they actually do. Using ADCIFA_SA_EOS_SOFTACK means you have to write to a register to acknowledge that the sequencer finished before another "start of conversion" (SOC) is issued. If you use ADCIFA_SA_NO_ESO_SOFTACK, you can issue another SOC without doing the aforementioned write. The data in the result registers could be overwritten. Using the _NO_ define does what my code originally intended - the while loop blocks until the sequencer finishes.

Here's my modified calibration code. The setup code is the same. I modified the following code to be less hard coded in terms of finding the optimal spot. It keeps track of the lowest average value and the corresponding DAC code that produced it instead of bailing immediately after a code is found that falls within a certain range. I found that the following code outperforms the accuracy of my previous code by approximately 10 mV.


////////////////////////////////////////
// Calibrate the N output to be 0.5 V //
////////////////////////////////////////
U16 closestDACValue = 0;
float lastAvg = 9999;
for(U16 i = 0; i < DAC_TUNNEL_N_REF * 2; i++) {
dac0->DR0.dca = i;
delay_us(DAC_CONFIG_SETTLE_TIME_US);

adcifa_sequencer_conversion_opt_t seq_inp[8];
for (U8 j = 0; j < 8; j++){
seq_inp[j].channel_p = UC3_ADCIN6_TUNNEL_N_INP;
seq_inp[j].channel_n = AVR32_ADCIFA_INN_GNDANA;
seq_inp[j].gain = ADCIFA_SHG_1;
}
adcifa_configure_sequencer(adc, 0, &seq_cfg, seq_inp);
adcifa_start_sequencer(adc, 0);
while(!ADCIFA_is_eos_sequencer_0());

float avg = 0;
for(U8 k = 0; k < 8; k++)
avg += (float)(ADCIFA_read_resx_sequencer_0(7) << 1);
avg = fabs(DAC_TUNNEL_N_REF - avg / 8.0);
if(avg < lastAvg) {
lastAvg = avg;
closestDACValue = i;
}
}
dac0->DR0.dca = closestDACValue;

////////////////////////////
// Calibrate P to equal N //
////////////////////////////
lastAvg = 9999;
for(U16 i = 0; i < DAC_TUNNEL_N_REF * 2; i++) {
dac1->DR0.dca = i;
delay_us(DAC_CONFIG_SETTLE_TIME_US);

adcifa_sequencer_conversion_opt_t seq_inp[8];
for (U8 j = 0; j < 8; j++){
seq_inp[j].channel_p = UC3_ADCIN6_TUNNEL_N_INP;
seq_inp[j].channel_n = UC3_ADCIN15_TUNNEL_P_INN;
seq_inp[j].gain = ADCIFA_SHG_1;
}
adcifa_configure_sequencer(adc, 0, &seq_cfg, seq_inp);
adcifa_start_sequencer(adc, 0);
while(!ADCIFA_is_eos_sequencer_0());

float avg = 0;
for(U8 k = 0; k < 8; k++)
avg += (float)(ADCIFA_read_resx_sequencer_0(7) << 1);
avg = fabs(DAC_TUNNEL_P_REF0 - avg / 8.0);
if (avg < lastAvg) {
lastAvg = avg;
dac_zero = i;
}
}

////////////////////////////////////////////////
// Calibrate the P output to be 1.5 V above N //
////////////////////////////////////////////////
lastAvg = 9999;
for(U16 i = dac_zero; i < 4096; i++) {
dac1->DR0.dca = i;
delay_us(DAC_CONFIG_SETTLE_TIME_US);

adcifa_sequencer_conversion_opt_t seq_inp[8];
for (U8 j = 0; j < 8; j++){
seq_inp[j].channel_p = UC3_ADCIN6_TUNNEL_N_INP;
seq_inp[j].channel_n = UC3_ADCIN15_TUNNEL_P_INN;
seq_inp[j].gain = ADCIFA_SHG_1;
}
adcifa_configure_sequencer(adc, 0, &seq_cfg, seq_inp);
adcifa_start_sequencer(adc, 0);
while(!ADCIFA_is_eos_sequencer_0());

float avg = 0;
for(U8 k = 0; k < 8; k++)
avg += (float)(-ADCIFA_read_resx_sequencer_0(7) << 1);
avg = fabs(DAC_TUNNEL_P_REF1 - avg / 8.0);
if (avg < lastAvg) {
lastAvg = avg;
dac_rail = i;
dac_range = dac_rail - dac_zero;
}
}

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

I was fighting also with this DAC calibration, let me share my experiences, maybe someone can find it useful.

To get a working ADC-DAC internal routing I have used the following code snippet:

void dacifb_calibrate(avr32_dacifb_t *dacifb, uint16_t *gain, uint16_t *offset){

	int16_t adc_values[DAC_CALIBRATION_NB_ADC_SAMPLES];
	int16_t adc_read;

	adcifa_opt_t adc_config_t ;
	adcifa_sequencer_opt_t adcifa_sequence_opt ;
	adcifa_sequencer_conversion_opt_t adcifa_sequence_conversion_opt[DAC_CALIBRATION_NB_ADC_SAMPLES];

	print_dbg("Start Calibration Procedure");

	// Step 1 : Configure the DACIFB to generate output on internal channel,
	dacifb_opt_t dacifb_opt;
	dacifb_channel_opt_t dacifb_channel_opt;

	dacifb_opt.reference                  = DACIFB_REFERENCE_EXT;
	dacifb_opt.channel_selection          = DACIFB_CHANNEL_SELECTION_AB;
	dacifb_opt.low_power                  = false;
	dacifb_opt.dual                       = false;
	dacifb_opt.prescaler_clock_hz         = 500000;

	dacifb_channel_opt.auto_refresh_mode    = false;
	dacifb_channel_opt.trigger_mode         = DACIFB_TRIGGER_MODE_MANUAL;
	dacifb_channel_opt.left_adjustment      = false;
	dacifb_channel_opt.data_shift           = 0;
	dacifb_channel_opt.data_round_enable    = false;

	//Set gain and offset correction values to 0
	dacifb_opt.gain_calibration_value = 0;
	dacifb_opt.offset_calibration_value = 0;

	// Configure DACIFB without starting the channel (indeed the output is internally routed to the ADCIFA in that case).
	dacifb_configure(dacifb, &dacifb_opt, sysclk_get_cpu_hz());
	// Enable the DACIFB channels. 
	dacifb_configure_channel(dacifb, DACIFB_CHANNEL_SELECTION_AB, &dacifb_channel_opt, 500000);

	// ADC Configure. 
	adc_config_t.frequency                = 1000000;
	adc_config_t.reference_source         = ADCIFA_ADCREF0;
	adc_config_t.sample_and_hold_disable  = true;
	adc_config_t.single_sequencer_mode    = true;
	adc_config_t.free_running_mode_enable = false;
	adc_config_t.sleep_mode_enable        = false;
    adc_config_t.mux_settle_more_time     = false;

	// Get ADCIFA Factory Configuration 
	adcifa_get_calibration_data(&AVR32_ADCIFA, &adc_config_t);

	// Calibrate offset first
	adcifa_calibrate_offset(&AVR32_ADCIFA, &adc_config_t, sysclk_get_cpu_hz());

	// Configure ADCIFA core 
	adcifa_configure(&AVR32_ADCIFA, &adc_config_t, sysclk_get_cpu_hz());

	// ADCIFA sequencer 0 configuration structure
	adcifa_sequence_opt.convnb               = DAC_CALIBRATION_NB_ADC_SAMPLES;
	adcifa_sequence_opt.resolution           = ADCIFA_SRES_12B;
	adcifa_sequence_opt.trigger_selection    = ADCIFA_TRGSEL_SOFT;
	adcifa_sequence_opt.start_of_conversion  = ADCIFA_SOCB_ALLSEQ;
	adcifa_sequence_opt.sh_mode              = ADCIFA_SH_MODE_OVERSAMP;
	adcifa_sequence_opt.half_word_adjustment = ADCIFA_HWLA_NOADJ;
	adcifa_sequence_opt.software_acknowledge = ADCIFA_SA_NO_EOS_SOFTACK;

	// Intialize ADCIFA conversions for sequencer 0
	for (uint8_t i=0;i< DAC_CALIBRATION_NB_ADC_SAMPLES;i++){
		adcifa_sequence_conversion_opt[i].channel_p = AVR32_ADCIFA_INP_DAC0_INT;
		adcifa_sequence_conversion_opt[i].channel_n = AVR32_ADCIFA_INN_GNDANA;
		adcifa_sequence_conversion_opt[i].gain      = ADCIFA_SHG_1;
	}

	// Configure ADCIFA sequencer 0
	adcifa_configure_sequencer(&AVR32_ADCIFA, 0, &adcifa_sequence_opt, adcifa_sequence_conversion_opt);


	// Make some measurements
	for ( int x = 100; x < 4000; x=x+200 )
	{
		dacifb_set_value(dacifb, DACIFB_CHANNEL_SELECTION_AB, true, x);
		delay_us(100);
		// Start ADCIFA sequencer 0 
		adcifa_start_sequencer(&AVR32_ADCIFA, 0);
		while(adcifa_get_values_from_sequencer(&AVR32_ADCIFA, 0, &adcifa_sequence_opt, adc_values) != ADCIFA_STATUS_COMPLETED);

		adc_read = 0;
		for ( int i = 0; i < DAC_CALIBRATION_NB_ADC_SAMPLES; i++ )
		{
			adc_read += adc_values[i];
		}
		adc_read = adc_read / 8;
		
		//DAC has effective 12 bits, ADC only has 11, that's why multiplied by 2
		adc_read = 2 * adc_read;
		/* Clear end-of-sequence for sequencer 0, ready for next conversion */
		ADCIFA_clear_eos_sequencer_0();
		print_dbg("\r\nDAC:"); print_dbg_ulong(x); print_dbg(" - ADC: "); print_dbg_ulong(adc_read);	
	}
	
	print_dbg("\r\nEnd of Calibration Procedure");
}

With this code, on my UC3C-EK board, I can read back some ADC measurements from the internal DAC. Though, my dacifb library is a bit modified, because of the bug described here: http://asf.atmel.com/bugzilla/show_bug.cgi?id=3106
 

So now the internal routing seems to work.

https://docs.google.com/spreadsheets/d/1rbq8_lRD4YsanVuvcF45zczxOYj8fSXeFc1eBLnmhsw/edit?usp=sharing

Then, I just played a bit with gain and offset values. Unfortunately I couldn't find in the manual the exact meaning of these values, so tried to figure out by experiment. Offset is clear, just shifts the data. 

The gain value is a bit more tricky - I don't know the exact connection between the gain factor value and the steepness change In the Google sheet there are some measurement value sequences. (measured adc values are multiplied by 2)

Unfortunately it seems, that on my uc3c-ek it's not possible to find a gain correction value which makes the output of the DAC really good. I calculated the measured gain between the set 1100 and 3100 dac values. Closest measured change value was 2074.

 

That's for now, I will continue investigations when I will have time again.

 

 

 

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

Here you can find my Atmel project which makes DAC calibration with internal routing.

https://drive.google.com/file/d/0B3b6NYRTQru0SmZFM0g5ejY1dFU/view?usp=sharing