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/adc: Add STM32H5 support.
Description
Add STM32H5 ADC support to pyb.ADC by correcting existing code.
Changes are:
- Run ADC on PCLK/16
- Use STM32 ADC library channel literals (__HAL_ADC_DECIMAL_NB_TO_CHANNEL)
- Use correct temperature conversion for H5 (30°C, 130°C calibration points)
- Add support for ADC2 input channels
- Optimize sample time for external inputs
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-07-29; STM32H573I-DK with STM32H573IIK3Q
Tests
from pyb import Pin, ADC, ADCAll
io = Pin('F14', Pin.OUT_PP)
io.high()
adc=ADC(Pin('F12'))
adc.read()
# Result: 4095 -> Ok, 3.30 V
io.low()
adc.read()
# Result: 4 --> Ok, ~2 mV
adc.read()
# Result: 2 --> Ok, ~1 mV
# Pin F13 is only available on ADC2, for test connect ADC Input PF13 to GPIO PF14
adc2=ADC(Pin('F13'))
print(adc2)
<ADC2 on Pin(Pin.cpu.F13, mode=Pin.ANALOG) channel=2>
io.high()
adc2.read()
# Result: 4087 --> Ok
io.low()
adc2.read()
# Result: 2 --> Ok
all=ADCAll(12,0x70000)
all.read_core_temp()
# Result: 28.2205 -> Ok
all.read_core_vref()
# Result: 1.207985 -> Ok, nominal 1.21 V