2023-06-22 09:34 AM
We have ongoing issues with calibration of the ADC.
Two things stand out, and I have (sort of) fixed them, but I would like to know WHY its needed (and possibly if there is a better way.)
1) I'm getting inconsistent calibration results. Causing the ADC readings to be of by a significant amount. I currently solve this by looping till I get the same result twice... It works, but its ugly.
2) I have to disable the ADC after calibration. Otherwise calibration is ignored for the first ADC sample. (Adding a delay don't help here. Need to exility disable the ADC.)
Configuration is done before calibration.
See code below (I included code for configuration too):
/**
* @fn bool ADC_Calibaration(void)
* @brief Calibrating the ADC (should be done before enable.)
* @return
*/
bool ADC_Calibaration(void)
{
uint32_t DMA_backUp;
uint32_t old_cal;
if (ADC1->CR & ADC_CR_ADEN) {
return(false);
}
DMA_backUp = ADC1->CFGR1 & (ADC_CFGR1_DMAEN | ADC_CFGR1_DMACFG);
ADC1->CFGR1 &= ~(ADC_CFGR1_DMAEN | ADC_CFGR1_DMACFG); // disable DMA
do { // should cure inconsistent calibration results (gets wrong about 1 in 30)
old_cal = ADC1->CALFACT;
ADC1->CR |= ADC_CR_ADCAL; // Initiate calibration
while ((ADC1->CR & ADC_CR_ADCAL) != 0) ; // wait for calibration
} while(old_cal != ADC1->CALFACT);
ADC1->CR |= ADC_CR_ADDIS; // first sample after calibration will get wrong result without this. It's not in the doc's.
ADC1->CFGR1 |= DMA_backUp;
return(true);
}
/**
* @fn void ADC_Config(void)
* @brief 160.5us sample time, 16x oversampling. Uses DMA for auto transfer to buffer.
*/
void ADC_Config(void)
{
/* Configure ADC1 */
// When selecting an analog ADC clock frequency lower than 3.5 MHz, it is mandatory to first
// enable the Low Frequency Mode by setting bit LFMEN = 1 into the ADC_CCR register
ADC->CCR |= ADC_CCR_LFMEN;
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; // Enable the peripheral clock on ADC1
ADC1->CFGR2 |= ADC_CFGR2_CKMODE_0; // PCLK/2 (Synchronous clock mode) (2MHz)
ADC1->CFGR1 |= ADC_CFGR1_AUTOFF; // Stop after scan.
ADC1->SMPR |= ADC_SMPR_SMP_0 | ADC_SMPR_SMP_1 | ADC_SMPR_SMP_2; // 111: 160.5 ADC clock cycles. (2MHz gives -> (1/2)*2*160.5) = 160.5us)
ADC1->IER = ADC_IER_OVRIE; // Enable interrupt on overrun.
ADC->CCR |= ADC_CCR_VREFEN; // Wake-up the VREFINT
ADC1->CR |= ADC_CR_ADVREGEN;
/* Enable over-sampling with ratio 16 and no shift */
ADC1->CFGR2 |= (ADC_CFGR2_OVSE | ADC_CFGR2_OVSR_1 | ADC_CFGR2_OVSR_0); // 16x
/* Configure NVIC for ADC */
NVIC_EnableIRQ(ADC1_COMP_IRQn); // Enable Interrupt on ADC
NVIC_SetPriority(ADC1_COMP_IRQn, 0); // Set priority for ADC
/* Configure DMA1 */
RCC->AHBENR |= RCC_AHBENR_DMA1EN; // Enable the peripheral clock on DMA1
ADC1->CFGR1 |= ADC_CFGR1_DMAEN; // Enable DMA transfer on ADC.
DMA1_Channel1->CPAR = (uint32_t) &(ADC1->DR); // Configure the peripheral data register address
DMA1_Channel1->CCR |= DMA_CCR_MINC | DMA_CCR_MSIZE_0 | DMA_CCR_PSIZE_0 | DMA_CCR_TEIE; // Interrupt on error, NO Interrupt on complete.
DMA1_Channel1->CCR |= DMA_CCR_EN; // Enable DMA Channel 1
/* Configure NVIC for DMA */
NVIC_EnableIRQ(DMA1_Channel1_IRQn); // Enable Interrupt on DMA Channel 1
NVIC_SetPriority(DMA1_Channel1_IRQn,0); // Set priority for DMA Channel 1
return;
}