KV31 ADC Calibration

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

KV31 ADC Calibration

1,807 Views
ohiogt
Contributor III

Hi,

We've run into a performance limit on our application when using an uncalibrated ADC.  However, we've seen issues when attempting to perform the internal Kinetis calibration.  Device is a MKV31F512.

1)  The major problem is that when running the initialization code below for both ADC0 and ADC1, the ADC1 calibration always returns with 0 values.  I've tried different combinations of COCO and CAL flags but the results are always the same.  The only way I can get a result appear is to breakpoint on the line "tmp32 = ADC1->CLP0 + ADC1->CLP1 + ADC1->CLP2 + ADC1->CLP3 + ADC1->CLP4 + ADC1->CLPS;".  The input bus clock to the ADC modules is 24 MHz.

2)  Minor issue is that occasionally the calibration occasionally gives outlier results.  The offset goes to -1 in these cases.  I've used the breakpoint method above to gather ADC1 data.  I've read you need to run the calibration 20 times and apply a median filter but it appears some thing isn't correct in the hardware.

Any ideas on how to properly poll the calibration status and ensure the ADC1 calibration completes correctly?

Shawn

pastedImage_1.png

void InitADC(void)
{
uint32_t tmp32 = 0x00000000;

ADC0->CFG1 &= ~(ADC_CFG1_ADLPC_MASK | ADC_CFG1_ADLSMP_MASK | ADC_CFG1_ADICLK_MASK | ADC_CFG1_ADIV_MASK| ADC_CFG1_MODE_MASK);
ADC0->CFG1 |= (ADC_CFG1_ADIV(3U) | ADC_CFG1_MODE(1U));
ADC0->CFG2 &= ~(ADC_CFG2_MUXSEL_MASK | ADC_CFG2_ADACKEN_MASK | ADC_CFG2_ADLSTS_MASK | ADC_CFG2_ADHSC_MASK);
ADC0->CFG2 |= (ADC_CFG2_ADHSC_MASK);
ADC0->SC2 &= ~(ADC_SC2_ADTRG_MASK | ADC_SC2_ACFE_MASK | ADC_SC2_ACFGT_MASK | ADC_SC2_ACREN_MASK |
ADC_SC2_DMAEN_MASK | ADC_SC2_REFSEL_MASK);
ADC0->SC3 &= ~(ADC_SC3_ADCO_MASK | ADC_SC3_AVGE_MASK | ADC_SC3_AVGS_MASK);
ADC0->SC3 |= (ADC_SC3_AVGE_MASK | ADC_SC3_AVGS(0x3));
ADC0->SC1[0] &= ~(ADC_SC1_COCO_MASK | ADC_SC1_AIEN_MASK | ADC_SC1_ADCH_MASK);
ADC0->SC1[0] |= (ADC_SC1_DIFF_MASK | ADC_SC1_ADCH(0x00));
ADC0->SC1[1] |= (ADC_SC1_DIFF_MASK | ADC_SC1_ADCH_MASK);

ADC1->CFG1 &= ~(ADC_CFG1_ADLPC_MASK | ADC_CFG1_ADLSMP_MASK | ADC_CFG1_ADICLK_MASK | ADC_CFG1_ADIV_MASK| ADC_CFG1_MODE_MASK);
ADC1->CFG1 |= (ADC_CFG1_ADIV(3U) | ADC_CFG1_MODE(1U));
ADC1->CFG2 &= ~(ADC_CFG2_MUXSEL_MASK | ADC_CFG2_ADACKEN_MASK | ADC_CFG2_ADLSTS_MASK);
ADC1->CFG2 |= (ADC_CFG2_ADHSC_MASK);
ADC1->SC2 &= ~(ADC_SC2_ADTRG_MASK | ADC_SC2_ACFE_MASK | ADC_SC2_ACFGT_MASK | ADC_SC2_ACREN_MASK |
ADC_SC2_DMAEN_MASK | ADC_SC2_REFSEL_MASK);
ADC1->SC3 &= ~(ADC_SC3_ADCO_MASK | ADC_SC3_AVGE_MASK | ADC_SC3_AVGS_MASK);
ADC1->SC3 |= (ADC_SC3_AVGE_MASK | ADC_SC3_AVGS(0x3));
ADC1->SC1[0] &= ~(ADC_SC1_COCO_MASK | ADC_SC1_AIEN_MASK | ADC_SC1_ADCH_MASK);
ADC1->SC1[0] |= (ADC_SC1_DIFF_MASK | ADC_SC1_ADCH(0x00));
ADC1->SC1[1] |= (ADC_SC1_DIFF_MASK | ADC_SC1_ADCH_MASK);

#ifdef ADC_CAL
ADC0->SC3 |= (ADC_SC3_CAL_MASK | ADC_SC3_CALF_MASK);

while (((ADC0->SC1[0] & ADC_SC1_COCO_MASK) != ADC_SC1_COCO_MASK) & ((ADC0->SC3 & ADC_SC3_CAL_MASK) == ADC_SC3_CAL_MASK))
{
if ((ADC0->SC3 & ADC_SC3_CALF_MASK) == ADC_SC3_CALF_MASK)
{
break;
}
}
tmp32 = ADC0->R[0];

if ((ADC0->SC3 & ADC_SC3_CALF_MASK) != ADC_SC3_CALF_MASK)
{
tmp32 = ADC0->CLP0 + ADC0->CLP1 + ADC0->CLP2 + ADC0->CLP3 + ADC0->CLP4 + ADC0->CLPS;
tmp32 = 0x8000U | (tmp32 >> 1U);
ADC0->PG = tmp32;

tmp32 = ADC0->CLM0 + ADC0->CLM1 + ADC0->CLM2 + ADC0->CLM3 + ADC0->CLM4 + ADC0->CLMS;
tmp32 = 0x8000U | (tmp32 >> 1U);
ADC0->MG = tmp32;
}

ADC1->SC3 |= (ADC_SC3_CAL_MASK | ADC_SC3_CALF_MASK);

while (((ADC1->SC1[0] & ADC_SC1_COCO_MASK) != ADC_SC1_COCO_MASK) & ((ADC0->SC3 & ADC_SC3_CAL_MASK) == ADC_SC3_CAL_MASK))
{
if ((ADC1->SC3 & ADC_SC3_CALF_MASK) == ADC_SC3_CALF_MASK)
{
break;
}
}
tmp32 = ADC1->R[0];

if ((ADC1->SC3 & ADC_SC3_CALF_MASK) != ADC_SC3_CALF_MASK)
{
tmp32 = ADC1->CLP0 + ADC1->CLP1 + ADC1->CLP2 + ADC1->CLP3 + ADC1->CLP4 + ADC1->CLPS;
tmp32 = 0x8000U | (tmp32 >> 1U);
ADC1->PG = tmp32;

tmp32 = ADC1->CLM0 + ADC1->CLM1 + ADC1->CLM2 + ADC1->CLM3 + ADC1->CLM4 + ADC1->CLMS;
tmp32 = 0x8000U | (tmp32 >> 1U);
ADC1->MG = tmp32;
}
#else
ADC0->OFS = 0x00000000;
ADC0->PG = 0x82D2;
ADC0->MG = 0x82D2;
ADC0->CLPD = 0xE;
ADC0->CLPS = 0x2E;
ADC0->CLP4 = 0x2C8;
ADC0->CLP3 = 0x16D;
ADC0->CLP2 = 0xB7;
ADC0->CLP1 = 0x5C;
ADC0->CLP0 = 0x2E;
ADC0->CLMD = 0xE;
ADC0->CLMS = 0x2E;
ADC0->CLM4 = 0x2C8;
ADC0->CLM3 = 0x16D;
ADC0->CLM2 = 0xB7;
ADC0->CLM1 = 0x5C;
ADC0->CLM0 = 0x2E;

ADC1->OFS = 0x00000000;
ADC1->PG = 0x82C8;
ADC1->MG = 0x82D0;
ADC1->CLPD = 0xD;
ADC1->CLPS = 0x2E;
ADC1->CLP4 = 0x2BF;
ADC1->CLP3 = 0x166;
ADC1->CLP2 = 0xB6;
ADC1->CLP1 = 0x5B;
ADC1->CLP0 = 0x2D;
ADC1->CLMD = 0xF;
ADC1->CLMS = 0x2E;
ADC1->CLM4 = 0x2C5;
ADC1->CLM3 = 0x16B;
ADC1->CLM2 = 0xB7;
ADC1->CLM1 = 0x5D;
ADC1->CLM0 = 0x2E;
#endif

ADC0->CFG1 &= ~(ADC_CFG1_ADIV_MASK);
ADC0->CFG1 |= ADC_CFG1_ADIV(0U);
ADC0->SC3 &= ~(ADC_SC3_AVGS_MASK | ADC_SC3_AVGE_MASK);
ADC0->SC2 |= (ADC_SC2_ADTRG_MASK | ADC_SC2_DMAEN_MASK);

ADC1->CFG1 &= ~(ADC_CFG1_ADIV_MASK);
ADC1->CFG1 |= ADC_CFG1_ADIV(0U);
ADC1->SC3 &= ~(ADC_SC3_AVGS_MASK | ADC_SC3_AVGE_MASK);
ADC1->SC2 |= (ADC_SC2_ADTRG_MASK | ADC_SC2_DMAEN_MASK);

}

Labels (1)
0 Kudos
5 Replies

1,256 Views
jorge_a_vazquez
NXP Employee
NXP Employee

Hi Shawn

I don't see any problem in your Calibration code, the only thing that I notice is that you ADC clock is way too high, according to the reference manual, general recommendations for calibrations are:

For best calibration results:
-Set hardware averaging to maximum, that is, SC3[AVGE]=1 and SC3[AVGS]=11 for an average of 32
-Set ADC clock frequency fADCK less than or equal to 4 MHz
- VREFH=VDDA
- Calibrate at nominal voltage and temperature

Have you check the code from SDK examples? Here you can find an example code of how is set the calibration.

Any ideas on how to properly poll the calibration status and ensure the ADC1 calibration completes correctly?

The only method to verify that the Calibration has completed correctly is with CALF, you use it in:

while (((ADC1->SC1[0] & ADC_SC1_COCO_MASK) != ADC_SC1_COCO_MASK) & ((ADC0->SC3 & ADC_SC3_CAL_MASK) == ADC_SC3_CAL_MASK))
{
    if ((ADC1->SC3 & ADC_SC3_CALF_MASK) == ADC_SC3_CALF_MASK)
    {
        break;
    }
}
tmp32 = ADC1->R[0];

if ((ADC1->SC3 & ADC_SC3_CALF_MASK) != ADC_SC3_CALF_MASK)
{
    tmp32 = ADC1->CLP0 + ADC1->CLP1 + ADC1->CLP2 + ADC1->CLP3 + ADC1->CLP4 + ADC1->CLPS;
    tmp32 = 0x8000U | (tmp32 >> 1U);
    ADC1->PG = tmp32;
    
    tmp32 = ADC1->CLM0 + ADC1->CLM1 + ADC1->CLM2 + ADC1->CLM3 + ADC1->CLM4 + ADC1->CLMS;
    tmp32 = 0x8000U | (tmp32 >> 1U);
    ADC1->MG = tmp32;
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

You can verify this flag after the calibration has complete.

Please check the following links for further information:

http://www.nxp.com/doc/AN5314 

16-bit SAR ADC calibration 

http://www.nxp.com/assets/documents/data/en/supporting-information/Analog-to-digital-Converter-Train... 


Hope this information helps you
Have a great day,
Jorge Alcala

-----------------------------------------------------------------------------------------------------------------------
Note: If this post answers your question, please click the Correct Answer button. Thank you!
-----------------------------------------------------------------------------------------------------------------------

0 Kudos

1,256 Views
ohiogt
Contributor III

Jorge,

Thanks for the response.  I believe I am running the ADC < 4 MHz as suggested.  I set the input clock divider to 8 in the following line:

ADC0->CFG1 |= (ADC_CFG1_ADIV(3U) | ADC_CFG1_MODE(1U));

I used the code from the latest SDK to create my function.  I've copied it below.  I don't see any major differences.  I'm using COCO as the condition flag for determining if the calibration completed.  This seems to be in line with the documentation you suggested.  I'll keep digging to see if I can find the exact timing issue.  If I breakpoint at "tmp = ADC1->R[0]", the CALF flag is 0 and the calibration values look good.  If I allow the function to run to the end the CALF flag is set.  I'm speculating that for some reason the code thinks the calibration is complete and attempts to write the ADC1->PG register.  The would induce a calibration failure per the reference manual.

Thanks,

Shawn

3-6-2017 12-49-00 PM.jpg

status_t ADC16_DoAutoCalibration(ADC_Type *base)
{
 bool bHWTrigger = false;
 volatile uint32_t tmp32; /* 'volatile' here is for the dummy read of ADCx_R[0] register. */
 status_t status = kStatus_Success;

 /* The calibration would be failed when in hardwar mode.
 * Remember the hardware trigger state here and restore it later if the hardware trigger is enabled.*/
 if (0U != (ADC_SC2_ADTRG_MASK & base->SC2))
 {
 bHWTrigger = true;
 base->SC2 &= ~ADC_SC2_ADTRG_MASK;
 }

 /* Clear the CALF and launch the calibration. */
 base->SC3 |= ADC_SC3_CAL_MASK | ADC_SC3_CALF_MASK;
 while (0U == (kADC16_ChannelConversionDoneFlag & ADC16_GetChannelStatusFlags(base, 0U)))
 {
 /* Check the CALF when the calibration is active. */
 if (0U != (kADC16_CalibrationFailedFlag & ADC16_GetStatusFlags(base)))
 {
 status = kStatus_Fail;
 break;
 }
 }
 tmp32 = base->R[0]; /* Dummy read to clear COCO caused by calibration. */

 /* Restore the hardware trigger setting if it was enabled before. */
 if (bHWTrigger)
 {
 base->SC2 |= ADC_SC2_ADTRG_MASK;
 }
 /* Check the CALF at the end of calibration. */
 if (0U != (kADC16_CalibrationFailedFlag & ADC16_GetStatusFlags(base)))
 {
 status = kStatus_Fail;
 }
 if (kStatus_Success != status) /* Check if the calibration process is succeed. */
 {
 return status;
 }

 /* Calculate the calibration values. */
 tmp32 = base->CLP0 + base->CLP1 + base->CLP2 + base->CLP3 + base->CLP4 + base->CLPS;
 tmp32 = 0x8000U | (tmp32 >> 1U);
 base->PG = tmp32;

#if defined(FSL_FEATURE_ADC16_HAS_DIFF_MODE) && FSL_FEATURE_ADC16_HAS_DIFF_MODE
 tmp32 = base->CLM0 + base->CLM1 + base->CLM2 + base->CLM3 + base->CLM4 + base->CLMS;
 tmp32 = 0x8000U | (tmp32 >> 1U);
 base->MG = tmp32;
#endif /* FSL_FEATURE_ADC16_HAS_DIFF_MODE */

 return kStatus_Success;
} ‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
0 Kudos

1,256 Views
eduardo_viramon
NXP Employee
NXP Employee

Hi Shawn,

I've been referred to this issue. I don't have a good answer right now but will work with Jorge to find it. Meanwhile I wanted to check in with you and ask if you've been running these tests on a your own board or on an eval board like a Tower or Freedom board?

If it is on your own board. Can you try loading up this code to an eval board and see if it behaves similarly? This would be a simple way to figure out if there is any underlying issue with hardware (power sources, clocking, etc.) that is affecting the ADC as we already know the SDK calibration code will work in NXP boards correctly.

Regards,

Eduardo

0 Kudos

1,256 Views
ohiogt
Contributor III

Hi Eduardo,

This is our hardware. I'll work on porting it back to the Freedom board I

have but I'll need to modify the clock system settings to work with it. I

have some other tasks I'm working in parallel so it may take till Friday.

Shawn

--

Shawn M. Mason

Sr. Electrical Engineer

TCAS Products

Phone: 321-674-5389

Cell: 321-591-8482

shawn.mason@rockwellcollins.com

rockwellcollins.com

MS 313-E13

1100 W. Hibiscus Blvd.

Melbourne, FL 32901, USA

On Mon, Mar 20, 2017 at 12:06 PM, eduardo_viramontes <

0 Kudos

1,256 Views
jorge_a_vazquez
NXP Employee
NXP Employee

Hi Shawn Mason

Attached is initialization part of the application that uses ADC. Use the ADC initialization in void InitADC(void) function. This worked without issues.

Best Regards

Jorge Alcala

0 Kudos