STM32H5 machine.ADC core temperature sensor not working
Description
While testing with an STM32H5 Nucleo board I noticed the internal ADC channels are not working as expected. External (I/O) pins return expected values. Internal ADC input are not reporting reasonable values.
I understand the STM32H5 port is under work and some features not working. I hope my input helps to fix a problem. Let me know if I can help with more testing.
Problem description:
- ADC(ADC.CORE_TEMP) returns incorrect values.
>>> from machine import ADC
>>> coretemp=ADC(ADC.CORE_TEMP)
Expected result:
- Values should lie around 30°C calibration point, or slightly higher.
>>> from machine import mem16
>>> mem16[0x08FFF814]
760
>>> mem16[0x08FFF818]
1014
Observed result:
- ADC value incorrect, indicates wrong core temperature
- Value is far below 30°C calibration point
>>> coretemp.read_u16() >> 4
173
See detailed log below.
The issue could be in machine_adc.c, adc_config_channel(). ADC object shows channel is an encoded number containing additional information besides the ADC channel number. For ADC.CORE_TEMP channel is 0xc3210000. Using this channel number value leads to incorrect loading of the SMPR register.
Note: The same kind of problem will be present for ADC.CORE_VREF, ADC.CORE_VBAT.
>>> coretemp=ADC(ADC.CORE_TEMP)
>>> coretemp
<ADC1 channel=3273719808>
>>> hex(3273719808)
'0xc3210000'
For G4 cores the channel number is extracted via __LL_ADC_CHANNEL_TO_DECIMAL_NB. Maybe this is required for H5 cores as well.
STATIC void adc_config_channel(ADC_TypeDef *adc, uint32_t channel, uint32_t sample_time) {
[removed]
#if defined(STM32G4)
channel = __LL_ADC_CHANNEL_TO_DECIMAL_NB(channel);
adc->DIFSEL &= ~(1 << channel); // Set channel to Single-ended.
#endif
adc->SQR1 = (channel & 0x1f) << ADC_SQR1_SQ1_Pos | (1 - 1) << ADC_SQR1_L_Pos;
__IO uint32_t *smpr;
if (channel <= 9) {
smpr = &adc->SMPR1;
} else {
smpr = &adc->SMPR2;
channel -= 10;
}
*smpr = (*smpr & ~(7 << (channel * 3))) | sample_time << (channel * 3); // select sample time
#endif
}
Version Information
% git lg1
* 7d66ae603 - (9 days ago) esp32/machine_timer: Switch from legacy driver to timer HAL. - Damien George (HEAD -> master, origin/master, origin/HEAD)
* 671b35cea - (2 weeks ago) py/builtinimport: Fix built-in imports when external import is disabled. - Jim Mussared
* 606ec9bfb - (9 weeks ago) py/compile: Fix async for's stack handling of iterator expression. - Damien George
MicroPython v1.20.0-282-g671b35cea-dirty on 2023-07-14; STM32H573I-DK with STM32H573IIK3Q
Type "help()" for more information.
Log
>>> from machine import ADC
### Working external inputs, PF12 taken as example
>>> adcext=ADC(machine.Pin('F12'))
>>> adcext
<ADC1 channel=6>
# Tied to 3.3 V
>>> adcext.read_u16()
65471
>>> adcext.read_u16()
65519
>>> adcext.read_u16()
65503
>>> adcext.read_u16()
65519
# Tied to GND
>>> adcext.read_u16()
48
>>> adcext.read_u16()
0
>>> adcext.read_u16()
16
### Test with core temperature
>>> coretemp=ADC(ADC.CORE_TEMP)
>>> coretemp
<ADC1 channel=3273719808>
>>> hex(3273719808)
'0xc3210000'
# Check low (30°C) and high (130°C) calibration points
>>> from machine import mem16
>>> mem16[0x08FFF814]
760
>>> mem16[0x08FFF818]
1014
# Read sensor, should be between two calibration points, actually close to 30° value
# Right align 12 bit ADC value
>>> coretemp.read_u16() >> 4
173
>>> coretemp.read_u16() >> 4
176
>>> coretemp.read_u16() >> 4
178
>>> coretemp.read_u16() >> 4
# Computes to a core temperature of -199 °C...
>>> (178-760)/((1014-760)/(130-30))+30
-199.1339
stm32/machine_adc: Add STM32H5 support.
Description
Add STM32H5 ADC support to machine.ADC.
Changes are:
- Run ADC on PCLK/16
- Verify and optimize timings (ADC_STAB_DELAY_US, ADC_SAMPLETIME_DEFAULT)
- Add support for STM32H5 VBAT and COREVDD channels on ADC2.
- Replace ADC constants in
machine_adc_locals_dict_table. Reasoning: The STM32 driver literals are uint32_t that don't work with MP_ROM_INT() which handles signed 31 bit integers only. Introduce enumeratormachine_adc_internal_ch_tto define external channels (0..19), internal channels (256..) and the special channel VREF (0xffff). Values are converted to STM32 literals withadc_ll_channel()when required inadc_config_and_read_u16(). - Convert STM32 literal to channel numbers in
adc_config_channelwith corresponding STM32 LL library functions (__LL_ADC_IS_CHANNEL_INTERNAL(), __LL_ADC_CHANNEL_TO_DECIMAL_NB())
Thanks to @yn386 for discussions.
Environment
- NUCLEO-H563ZI
- Using STM32H573I-DK board with minor modifications (input clock, on-chip flash)
- First ADC Input (PF12) connected externally to GPIO PF14
MicroPython v1.20.0-283-g7d66ae603-dirty on 2023-08-01; STM32H573I-DK with STM32H573IIK3Q
Tests
from machine import ADC, Pin
from machine import mem16
io = Pin('F14', Pin.OUT_PP) # Connect to PF12
io.low()
ts_cal_30 = mem16[0x08FFF814]
ts_cal_130 = mem16[0x08FFF818]
scale = (ts_cal_130 - ts_cal_30)/(130-30)
print(f"Calibration values: 30°C = {ts_cal_30}, 130°C = {ts_cal_130}")
adc_pf12 = ADC(Pin('F12'))
print(adc_pf12)
<ADC1 channel=6 sampletime=1>
adc_vref = ADC(ADC.VREF)
print(adc_vref)
<ADC1 channel=65535 sampletime=1>
adc_vrefint = ADC(ADC.CORE_VREF)
print(adc_vrefint)
<ADC1 channel=256 sampletime=6>
adc_temp = ADC(ADC.CORE_TEMP)
print(adc_temp)
<ADC1 channel=257 sampletime=6>
adc_vbat = ADC(ADC.CORE_VBAT)
print(adc_vbat)
<ADC2 channel=258 sampletime=6>
adc_corevdd = ADC(ADC.CORE_VDD)
print(adc_corevdd)
<ADC2 channel=259 sampletime=6>
# Expect: 3.30 V (fixed value)
adc_vref.read_u16() * 3.3 / 65535
3.3
# Expect: 1.21 V
adc_vrefint.read_u16() * 3.3 / 65535
1.210378
# Expect: 25 to 35 °C
val = adc_temp.read_u16() >> 4
((val - ts_cal_30) / scale)+30
31.5748
# Expect: 3.30V
adc_vbat.read_u16() * 3.3 / 65535 * 4
3.326638
# Expect: 1.30 to 1.40 V
adc_corevdd.read_u16() * 3.3 / 65535
1.360284
# Expect 0.0
io.low()
adc_pf12.read_u16() * 3.3 / 65535
0.002417029
# Expect 3.3
io.high()
adc_pf12.read_u16() * 3.3 / 65535
3.296777