Issue with PY32F002A's ADC. - Page 1
Forum section: electronics / general

author: Sacodepatatas
author_rank: Regular Contributor
timestamp: October 08, 2025, 10:18:59 am
content: Some months back i successfully programmed a thermostat using the PY32F002BW15 (because of the pinout of the 8 bits MCU i was replacing)...This week i've received an earler version of the thermostat module that has a FT61E145A that matches the pinout of the PY32F002AF15. I replaced the MCU and tried porting the code from the 002B to the 002A. It sounds easy, both are from the same manufacturer although different dies, so there would be minor diferences. Both running at 24MHz (more than enough). Code modifications are just a few, replacing libraries, makefile defs, GPIO redefines and "Option Byte" managing.Ok, then I program the IC, power the device, and everything seems to work except that the displayed temperature is frozen at the display. No response from the NTC resistor.Then i see that after one minute, the display updates again. Let me explain:My code does a first cyclic ADC calibration one minute after power up, to let the device warm to a stationary regime, then the calibration repeats every one hour. The part of the calibration is in main(), while the Systick interrupt handler will flag for calibration once enough time has been reached.The calibration has to do with the ADC getting a new value, but "why?", this is how the code enters calibration:Code:if(Calibration_Flag){Calibration_Flag=0;LL_ADC_Disable(ADC1);ADCCalibrate();LL_ADC_Enable(ADC1);}Nothing weird. ADCCalibrate manages the calibration timeout as shown in the ADC examples from the PY32 template.ADC is set up as single convertion, just 1 channel, no power saving, no fancy stuff, just for a slow conversion (about 100us accounting the sampling period) software triggered from the Systick once every 12ms.Here the ADC config routine from the 002B:Code:static void ADCConfig(void){LL_APB1_GRP2_EnableClock(LL_APB1_GRP2_PERIPH_ADC1);// Enable ADC1 clockLL_GPIO_SetPinMode(GPIOA, LL_GPIO_PIN_6, LL_GPIO_MODE_ANALOG);// Configure PA6 (ADC_IN3) pin in analog input modeLL_ADC_SetClock(ADC1, LL_ADC_CLOCK_SYNC_PCLK_DIV16);// Set ADC clock to pclk/16LL_ADC_SetResolution(ADC1, LL_ADC_RESOLUTION_12B);// Set ADC resolution to 12 bitLL_ADC_SetDataAlignment(ADC1, LL_ADC_DATA_ALIGN_RIGHT);// ADC conversion data alignment: right alignedLL_ADC_SetLowPowerMode(ADC1, LL_ADC_LP_MODE_NONE);// No low power modeLL_ADC_SetSamplingTimeCommonChannels(ADC1, LL_ADC_SAMPLINGTIME_239CYCLES_5); // Sampling time 239.5 ADC clock cycles (80us)LL_ADC_REG_SetTriggerSource(ADC1, LL_ADC_REG_TRIG_SOFTWARE);// ADC regular group conversion, software triggeredLL_ADC_REG_SetContinuousMode(ADC1, LL_ADC_REG_CONV_SINGLE);// Set ADC conversion mode to single modeLL_ADC_REG_SetOverrun(ADC1, LL_ADC_REG_OVR_DATA_OVERWRITTEN);// ADC in case of overrun: data overwrittenLL_ADC_REG_SetSequencerDiscont(ADC1, LL_ADC_REG_SEQ_DISCONT_DISABLE);// Disable ADC regular group sequencer discontinuous modeLL_ADC_REG_SetSequencerChannels(ADC1, LL_ADC_CHANNEL_3);// Set PA6:ADC_IN3 channelLL_ADC_SetCommonPathInternalCh(__LL_ADC_COMMON_INSTANCE(ADC1), LL_ADC_PATH_INTERNAL_NONE ); //No Vrefint or TB conversion.LL_ADC_EnableIT_EOC(ADC1); // Enable End of conversion interruptNVIC_SetPriority(ADC_COMP_IRQn, 0);NVIC_EnableIRQ(ADC_COMP_IRQn);}Here the minor changes in the 002A (just switching what GPIO channel would be connected to the NTC)...Code:static void ADCConfig(void){LL_APB1_GRP2_EnableClock(LL_APB1_GRP2_PERIPH_ADC1);// Enable ADC1 clockLL_GPIO_SetPinMode(GPIOA, LL_GPIO_PIN_1, LL_GPIO_MODE_ANALOG);// Configure PA1 (ADC_IN1) pin in analog input modeLL_ADC_SetCommonPathInternalCh(__LL_ADC_COMMON_INSTANCE(ADC1), LL_ADC_PATH_INTERNAL_NONE ); //No Vrefint or TB conversion.LL_ADC_SetClock(ADC1, LL_ADC_CLOCK_SYNC_PCLK_DIV16);// Set ADC clock to pclk/16LL_ADC_SetResolution(ADC1, LL_ADC_RESOLUTION_12B);// Set ADC resolution to 12 bitLL_ADC_SetDataAlignment(ADC1, LL_ADC_DATA_ALIGN_RIGHT);// ADC conversion data alignment: right alignedLL_ADC_SetLowPowerMode(ADC1, LL_ADC_LP_MODE_NONE);// No low power modeLL_ADC_SetSamplingTimeCommonChannels(ADC1, LL_ADC_SAMPLINGTIME_239CYCLES_5); // Sampling time 239.5 ADC clock cycles (80us)LL_ADC_REG_SetTriggerSource(ADC1, LL_ADC_REG_TRIG_SOFTWARE);// ADC regular group conversion, software triggeredLL_ADC_REG_SetContinuousMode(ADC1, LL_ADC_REG_CONV_SINGLE);// Set ADC conversion mode to single modeLL_ADC_REG_SetOverrun(ADC1, LL_ADC_REG_OVR_DATA_OVERWRITTEN);// ADC in case of overrun: data overwrittenLL_ADC_REG_SetSequencerDiscont(ADC1, LL_ADC_REG_SEQ_DISCONT_DISABLE);// Disable ADC regular group sequencer discontinuous modeLL_ADC_REG_SetSequencerChannels(ADC1, LL_ADC_CHANNEL_1);// Set PA1:ADC_IN1 channelLL_ADC_EnableIT_EOC(ADC1); // Enable End of conversion interruptNVIC_SetPriority(ADC_COMP_IRQn, 0);NVIC_EnableIRQ(ADC_COMP_IRQn);}The ADC ISR is also very simple:Code:void ADC_COMP_IRQHandler(void){if(LL_ADC_IsActiveFlag_EOC(ADC1) != 0) {LL_ADC_ClearFlag_EOC(ADC1);Analog_Read = Filter(LL_ADC_REG_ReadConversionData12(ADC1));Analog_Flag = 1;}}When i faced the issue, it leaded me to a headache while reading both RM's ADC section, trying to find any substantial difference, looking over the ADC examples for the 002A... I didn't find nothing remarkable. Until I traced the execution of the code in the 002A, mainly the ADC firing part within the Systick handler:Code:if(ADC_Ready&&(!Calibration_Flag)){if(ADC_Cal_Time<TheOneHour) LL_ADC_REG_StartConversion(ADC1);else {Calibration_Flag=1;ADC_Cal_Time=0;}}This is what it is done in the 002B's code and work flawless... But, when executing on the 002A's, and after inspecting all ADC's registers, the last one checked was ADC->CR (Murphy's law of not checking it first) and realized that ADEN bit was beeing disabled after the first conversion. The calibration part, when exitting the calibration, was enabling the ADEN bit again, that's why the device adquired a new value after one minute of power up. Then i tried a workaroung...Code:if(ADC_Ready&&(!Calibration_Flag)){if(ADC_Cal_Time<TheOneHour) {if(!LL_ADC_IsEnabled(ADC1)) LL_ADC_Enable(ADC1);LL_ADC_REG_StartConversion(ADC1);}else {Calibration_Flag=1;ADC_Cal_Time=0;}}}And this worked. Now both temperature modules, PY32F002B and PY32F002A behave exactly as expected....But the question is... ¿Why ADEN is being disabled after the first convertion? I just use the LL ADC library functions that should be atomic, no direct access to the peripheral registers, furthermore, what it fails at the 002A should also fail at the 002B unless there is a hidden flaw that i'm unaware of. Since i know, performing a single convertion should not disable ADEN after EOC, EOS, or even OVR conditions are present.Then only thing that goes beyond the specs is that my original code for the 002B occupies 24KB, hence i program the 002A as it was a 003x6 for enabling the 32KB, but i build the binaries with the 002A start-up code and the LD script being modified for 32KB/4KB boundaries.

author: mikerj
author_rank: Super Contributor
timestamp: October 10, 2025, 03:24:40 pm
content: Shouldn't LL_ADC_Enable(ADC1) be called from the ADCConfig() function? I don't see how the ADC was ever being enabled until the first calibration otherwise.

author: Sacodepatatas
author_rank: Regular Contributor
timestamp: October 11, 2025, 11:47:17 am
content: Quote from: mikerj on October 10, 2025, 03:24:40 pmAs long as the ADC enable function is called before the software trigger function, it should be fine. This is a chunk of code from main() right after the checks for "entering programming mode" but early before the main "while loop"...Code:GPIO_Config_2();ADCConfig();MsDelay(10);ADCCalibrate();MsDelay(10);LL_ADC_Enable(ADC1);MsDelay(1);The last two lines are redundant because the ADC has been already enabled within ADCCalibrate, but, anyway...To prevent an early trigger of the ADC (the systick interrupt is running), the ADC_Ready variable boots as false until the execution is reaching the main's while loop, where such variable becomes true and lets the firing of the LL_ADC_REG_StartConversion(ADC1) function.Code:ADC_Ready=1;while(!Analog_Flag);Get_Temperature(Put_Temp_On_Buffer);while (1){...