【原创】STM32U5系列与STM32G4系列MCU内部温度传感器精准读取指南
1. 前言
这段时间在使用STM32U5系列的单片机,想通过内部的ADC来获取单片机内部的温度,在网上搜了一圈,发现大多是STM32F1、F4系列的温度读取教程,STM32U5系类的和STM32G4系列的方法较少,有大致的介绍但不全面,因此综合现有的教程,写一个详细一点的教程来实现正确温度值的获取。
2. 读取方式
2.1 手册获取
通过查询STM32U5系列或STM32G4系列的参考手册(Reference Manuel)可以找到详细的读取流程,此处使用的是STM32U585,查询STM32U5的手册RM0456:
手册可以直接百度或者ST官网搜RM0456找到,或者从CubeMX建立好工程后,从Help -> Docs选项卡中找到这个芯片相关的所有文档:
点开即可找到手册位置
2.2 查看温度获取流程
手册33.4.33中详细介绍了温度的获取流程
读取的步骤如下:
翻译总结如下:
1. 选择温度传感器对应的ADC通道(并设置合适的采样时间)
2. 配置合适的采样时间
3. 设置ADC12_CCR寄存的VSENSESEL位,将温度传感器从power down模式唤醒
4. 启动ADC转换
5. 待转换完毕,读取ADC转换结果
6. 根据公式计算真实温度值
转换公式如下:
转换公式中我们需要从MCU内部地址获取两个温度下的校准值,即TS_CAL1和TS_CAL2,再代入公式计算。
在表中可以找到两个校准值变量的对应的存储位置,表中我们可以看到两个校准值给的条件是14-bit的ADC模式下且参考电压为3V的条件下计算出的校准值,因此要得到准确的温度值需要根据自己ADC的采样配置(位数)和参考电压值来换算:
简单解释一下这两个参数值,两个系数值都为16bit,TS_CAL1为使用14bit的ADC在参考电压为3V的情况下,外部温度为30°的时候获取的校准系数;TS_CAL2即为同样条件下温度为130°时候的校准值。
3. CubeMX配置ADC
为了简明扼要就不一步一步的选单片机,配置时钟树这些了,直接配置好一个可以正常运行的CubeMX工程后,打开CubeMX开始配置ADC。
这里选择ADC1,其他通道根据需要配置,主要是选择这个②处的温度传感器通道,勾选后进行参数设置:
勾选完成后即可生成工程,生成的工程参数如下,不需要额外的更改:
void MX_ADC1_Init(void)
{
/** Common config
*/
hadc1.Instance = ADC1;
hadc1.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1;
hadc1.Init.Resolution = ADC_RESOLUTION_12B;
hadc1.Init.GainCompensation = 0;
hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
hadc1.Init.LowPowerAutoWait = DISABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.NbrOfConversion = 1;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.DMAContinuousRequests = DISABLE;
hadc1.Init.TriggerFrequencyMode = ADC_TRIGGER_FREQ_HIGH;
hadc1.Init.Overrun = ADC_OVR_DATA_PRESERVED;
hadc1.Init.LeftBitShift = ADC_LEFTBITSHIFT_NONE;
hadc1.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DR;
hadc1.Init.OversamplingMode = DISABLE;
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN ADC1_Init 2 */
/* USER CODE END ADC1_Init 2 */
}
4. 代码编写
完成基本配置后我们需要找到温度传感器对应的ADC通道,从手册中我们可以找到STM32U5的Vsense对应的通道号为19:
不过我们也可以通过HAL库中的宏定义来直接读取,而不关心具体的通道号:
我们配置的ADC为单次转换,因此我们定义一个函数来方便读取任一通道的ADC值,函数中的ADC配置可以放在ADC1的初始化中,但为了方便切换单次读取通道,我将其放在读取函数中。其中打开CCR寄存器部分可以要也可以不要,测试发现打不打开都可以正确读取到温度值,可能是给MCU休眠后唤醒使用?暂不清楚,若有清楚的可以评论区讲解下,代码如下:
static uint32_t adc_value_get(uint32_t iChan)
{
#define ADC_OBJ &hadc1
ADC_ChannelConfTypeDef sConfig = {
.Channel = iChan,
.Rank = ADC_REGULAR_RANK_1,
.SamplingTime = ADC_SAMPLETIME_814CYCLES,
.SingleDiff = ADC_SINGLE_ENDED,
.OffsetNumber = ADC_OFFSET_NONE,
.Offset = 0
};
if (HAL_ADC_ConfigChannel(ADC_OBJ, &sConfig) != HAL_OK)
{
Error_Handler();
}
// 打开CCR寄存器唤醒温度传感器
LL_ADC_SetCommonPathInternalCh(__LL_ADC_COMMON_INSTANCE(ADC1), LL_ADC_PATH_INTERNAL_TEMPSENSOR);
if (HAL_ADC_Start(ADC_OBJ) != HAL_OK)
{
/* ADC conversion start error */
Error_Handler();
}
// adc timeout set 10ms
if (HAL_ADC_PollForConversion(ADC_OBJ, 10) != HAL_OK)
{
/* ADC conversion start error */
Error_Handler();
}
if (HAL_ADC_Stop(ADC_OBJ) != HAL_OK)
{
/* ADC conversion stop error */
Error_Handler();
}
return HAL_ADC_GetValue(ADC_OBJ);
}
定义好这个读取函数后我们在主函数的循环前定义一些必要的变量,并在使用ADC前对其进行一次校准,两个校准系数为16bit的值因此直接从地址中读取即可,但我使用的评估板是3.3V的参考电压,因此需要进行一下转换,若你使用的参考电压值是其他值,将3.3改成对应值即可,但要注意不要删除浮点数后的 [ f ] ,不然最后计算出来的值会和整数除法混和导致最终的结果不对,代码如下:
/* 定义两个校准系数的存储地址和校准系数的对应温度值 */
#define TS_CAL1_ADDR (0x0BFA0710)
#define TS_CAL2_ADDR (0x0BFA0742)
#define TS_CAL1_TEMP (30.0f) // 30 TEMP
#define TS_CAL2_TEMP (130.0f) // 130 TEMP
uint32_t ADC_Value = 0;
float ftemp, TS_CAL[2];
/* 从地址中读取真实的校准系数 */
TS_CAL[0] = (float)*(__IO uint16_t *)TS_CAL1_ADDR;
TS_CAL[1] = (float)*(__IO uint16_t *)TS_CAL2_ADDR;
/* 转换校准系数为3.3V的条件下的值 */
TS_CAL[0] = TS_CAL[0] * 3.0f / 3.3f;
TS_CAL[1] = TS_CAL[1] * 3.0f / 3.3f;
/* 校准ADC1 */
if (HAL_ADCEx_Calibration_Start(&hadc1, ADC_CALIB_OFFSET, ADC_SINGLE_ENDED) != HAL_OK)
{
/* Calibration Error */
Error_Handler();
}
完成设置后即可在主循环中调用函数,直接读取ADC_CHANNEL_TEMPSENSOR通道的数据,也可以读取通道19,并根据公式计算处对应的温度值,验证温度数据。此处有一个注意点,我们使用的ADC1配置的是12bit,而校准系数给的是14bit,因此需要将12bit的采样值转换成14bit(左移两位),算出来的结果才对,若配置成其他的值对应转换即可,代码如下:
while (1)
{
// 获取温度采样原始值
ADC_Value = adc_value_get(ADC_CHANNEL_TEMPSENSOR);
ADC_Value <<= 2; // 将12bit ADC采样值转换成14bit
// 根据公式计算温度值
ftemp = (TS_CAL2_TEMP - TS_CAL1_TEMP)/(TS_CAL[1] - TS_CAL[0]) * (ADC_Value - TS_CAL[0]) + TS_CAL1_TEMP;
// 分别打印原始值 电压值 和温度值
LOG_INFO("ADC:%d Vol:%.4f Temp:%.4f \n", ADC_Value, ADC_Value*3.3f/(16384-1), ftemp);
HAL_Delay(500);
}
最终的打印采样结果如下:
5. 总结
总结下来,获取准确的温度值的注意点如下:
- 1. 选择正确的通道,若不知道直接使用 ADC_CHANNEL_TEMPSENSOR
- 2. 转换参考电压值
- 3. 转换自己adc的位数为14bit,或者将校准系数转换为adc对应的位数即可
- 4. 浮点除法和整数除法不可混淆
其中这一系列的MCU(如STM32G474)的温度计算方式都可以使用同样的流程,其区别仅在于校准系数的地址不同和温度传感器对应的ADC通道号不同。
内容若有纰漏,评论区欢迎讨论指正。
作者:HongShion