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!
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!
ASF 3.7.3 has dacifb_get_calibration_data(,,) to read and apply the factory-supplied calibration.
I haven't checked previous versions.
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.
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:
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:
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...
Those internal inputs are described in the datasheet at the very end of the ADCIFA section !
I think your factory-page was erased.
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.
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 :(
Did you ever produce working DAC calibration code? I'm interested in this as well.
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 Enabledac1->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 reasonfloat 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 reasonfloat 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 reasonfloat 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).
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; )
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;
}
}
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.
Here you can find my Atmel project which makes DAC calibration with internal routing.
https://drive.google.com/file/d/0B3b6NYRTQru0SmZFM0g5ejY1dFU/view?usp=sharing