ADC — 内部温度传感器 ADC_TempSensor
固件库: PY32F403_Firmware V1.4.5
芯片: PY32F403(Cortex-M4)
源文件: PY32F403-STK/Example/ADC/ADC_TempSensor/Src/main.c
核心外设: ADC1(内部通道 TEMPSENSOR)
1. 功能简介(大白话)
芯片内部本身集成了一个 温度传感器,它就挂在 ADC 的一个特殊通道上(不像外部温度传感器要焊在板子上)。本例程做的事非常简单:
- 让 ADC 去「量」内部温度传感器输出的电压;
- 用出厂时固化在芯片里的校准值,把电压换算成摄氏度;
- 每隔 1 秒通过串口打印一次温度。
零外部元件就能测芯片自身的温度,是入门 ADC 最省事的例子——你连一根线都不用接(串口线除外)。
注意:这个温度反映的是 芯片内核温度,会受自身发热影响,不是环境温度,但用来体会 ADC 用法已经足够。
2. 硬件准备
| 物品 |
说明 |
| PY32F403-STK 开发板 |
自带 MCU、LED、调试/串口 |
| USB 转 TTL 模块 |
接 STK 的 USART2(调试串口) |
| 杜邦线 ×3 |
TX、RX、GND |
串口接线(STK 的 DEBUG_USART = USART2):
1
2
3
4
|
STK 板 USB 转 TTL 模块
PA02 (TX) --> RX
PA03 (RX) --> TX
GND --> GND
|
串口参数:波特率 115200,数据位 8,停止位 1,无校验(8-N-1)。
本例无需接任何模拟信号线,温度传感器是片内的。
3. 完整代码(带中文注释)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
|
/* 头文件:HAL 库 + STK 板级支持包(含 LED、USART printf 等) */
#include "main.h"
/* ============ 温度换算相关的校准常量 ============ */
/* 这两个地址是芯片出厂时写入的「温度传感器校准值」存放位置,必须按手册来 */
#define HAL_ADC_TSCAL1 (*(uint32_t *)(0x1fff5310)) /* 低温校准值(30℃ 对应)*/
#define HAL_ADC_TSCAL2 (*(uint32_t *)(0x1fff5400)) /* 高温校准值(85℃ 或 105℃ 对应)*/
#define Vcc_Power 3.3l /* 实际供电电压,按需修改 */
#define TScal1 (float)((HAL_ADC_TSCAL1) * 3.3 / Vcc_Power) /* 30℃ 对应的「电压数字」*/
#define TScal2 (float)((HAL_ADC_TSCAL2) * 3.3 / Vcc_Power) /* 高温对应的「电压数字」*/
#define TStem1 30l /* 低温校准点:30℃ */
/* 关键!不同芯片高温校准点是 85℃ 还是 105℃,要看数据手册选择 */
/* #define HighTemp_85 */
#define HighTemp_105
#define TStem2_85 85
#define TStem2_105 105
/* 斜率 k = (高温点 - 30℃) / (高温校准数字 - 低温校准数字) */
#define Temp_k_85 ((float)(TStem2_85 - TStem1) / (float)(TScal2 - TScal1))
#define Temp_k_105 ((float)(TStem2_105 - TStem1) / (float)(TScal2 - TScal1))
/* ============ 全局变量 ============ */
ADC_HandleTypeDef AdcHandle; /* ADC 句柄 */
uint16_t aADCxConvertedData; /* 存放一次 ADC 原始值 */
__IO uint16_t hADCxConvertedData_Temperature_DegreeCelsius = 0; /* 换算后的温度 */
/* 函数声明 */
static void APP_SystemClockConfig(void);
static void APP_AdcConfig(void);
int main(void)
{
/* 1) 初始化 HAL(没事钟、Systick 等)*/
HAL_Init();
/* 2) 配置系统时钟:本例用内部 HSI 8MHz,不分频 */
APP_SystemClockConfig();
/* 3) 初始化 LED(示例里虽未闪烁,但 BSP 默认初始化)*/
BSP_LED_Init(LED_GREEN);
/* 4) 初始化 USART2,并把它重定向为 printf 输出 */
BSP_USART_Config();
/* 5) 配置 ADC(分辨率、通道、采样时间等)*/
APP_AdcConfig();
/* 6) ADC 校准:上电后必须做一次,消除工艺偏差 */
if (HAL_ADCEx_Calibration_Start(&AdcHandle) != HAL_OK)
{
APP_ErrorHandler();
}
/* 7) 启动 ADC(连续转换模式,启动后一直转)*/
HAL_ADC_Start(&AdcHandle);
while (1)
{
/* 轮询等待一次转换完成,超时 2000ms */
if (HAL_ADC_PollForConversion(&AdcHandle, 2000) == HAL_OK)
{
/* 取出 12 位转换结果 */
aADCxConvertedData = HAL_ADC_GetValue(&AdcHandle);
/* 用两点线性公式换算成温度(℃)*/
/* 公式:Temp = k × ADC值 - k × TScal1 + 30 */
#if defined(HighTemp_85)
hADCxConvertedData_Temperature_DegreeCelsius =
(int16_t)(Temp_k_85 * aADCxConvertedData - Temp_k_85 * TScal1 + TStem1);
#else
hADCxConvertedData_Temperature_DegreeCelsius =
(int16_t)(Temp_k_105 * aADCxConvertedData - Temp_k_105 * TScal1 + TStem1);
#endif
/* 串口打印温度 */
printf("Temperature: %d", (int)hADCxConvertedData_Temperature_DegreeCelsius);
}
HAL_Delay(1000); /* 每秒打印一次 */
}
}
/**
* @brief ADC 配置:打开时钟、设分辨率/模式、绑定温度传感器通道
*/
static void APP_AdcConfig(void)
{
ADC_ChannelConfTypeDef sConfig = {0};
RCC_PeriphCLKInitTypeDef RCC_PeriphCLKInit = {0};
__HAL_RCC_ADC1_CLK_ENABLE(); /* 使能 ADC1 时钟 */
/* 配置 ADC 专用时钟:PCLK2 分频(这里 /8)*/
RCC_PeriphCLKInit.PeriphClockSelection = RCC_PERIPHCLK_ADC;
RCC_PeriphCLKInit.AdcClockSelection = RCC_ADCPCLK2_DIV8;
HAL_RCCEx_PeriphCLKConfig(&RCC_PeriphCLKInit);
AdcHandle.Instance = ADC1;
AdcHandle.Init.Resolution = ADC_RESOLUTION_12B; /* 12 位精度 */
AdcHandle.Init.DataAlign = ADC_DATAALIGN_RIGHT; /* 数据右对齐 */
AdcHandle.Init.ScanConvMode = ADC_SCAN_DISABLE; /* 单通道,不扫描 */
AdcHandle.Init.ContinuousConvMode = ENABLE; /* 连续转换 */
AdcHandle.Init.NbrOfConversion = 1; /* 转换序列长度 1 */
AdcHandle.Init.DiscontinuousConvMode = DISABLE;
AdcHandle.Init.NbrOfDiscConversion = 1;
AdcHandle.Init.ExternalTrigConv = ADC_SOFTWARE_START; /* 软件触发 */
if (HAL_ADC_Init(&AdcHandle) != HAL_OK)
{
APP_ErrorHandler();
}
/* 配置要采的通道 = 内部温度传感器 */
sConfig.Channel = ADC_CHANNEL_TEMPSENSOR;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; /* 温度信号「软」,采样久一点更准 */
if (HAL_ADC_ConfigChannel(&AdcHandle, &sConfig) != HAL_OK)
{
APP_ErrorHandler();
}
}
/**
* @brief 系统时钟配置:仅使能 HSI 8MHz 作系统时钟,PLL 关闭
*/
static void APP_SystemClockConfig(void)
{
RCC_OscInitTypeDef OscInitstruct = {0};
RCC_ClkInitTypeDef ClkInitstruct = {0};
OscInitstruct.OscillatorType = RCC_OSCILLATORTYPE_HSI; /* 只用内部 8MHz */
OscInitstruct.HSIState = RCC_HSI_ON;
OscInitstruct.HSI48MState = RCC_HSI48M_OFF;
OscInitstruct.LSEState = RCC_LSE_OFF;
OscInitstruct.LSIState = RCC_LSI_OFF;
OscInitstruct.PLL.PLLState = RCC_PLL_OFF; /* 本例不需要 PLL */
if (HAL_RCC_OscConfig(&OscInitstruct) != HAL_OK)
{
APP_ErrorHandler();
}
ClkInitstruct.ClockType = RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK |
RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
ClkInitstruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI; /* 系统时钟 = HSI 8MHz */
ClkInitstruct.AHBCLKDivider = RCC_SYSCLK_DIV1; /* AHB = 8MHz */
ClkInitstruct.APB1CLKDivider = RCC_HCLK_DIV1; /* APB1 = 8MHz */
ClkInitstruct.APB2CLKDivider = RCC_HCLK_DIV1; /* APB2 = 8MHz */
if (HAL_RCC_ClockConfig(&ClkInitstruct, FLASH_LATENCY_0) != HAL_OK)
{
APP_ErrorHandler();
}
}
/**
* @brief 错误处理:死循环
*/
void APP_ErrorHandler(void)
{
while (1)
{
}
}
|
4. 代码分段讲解
4.1 温度怎么算出来?(校准值 + 两点法)
芯片出厂时,厂家在 固定 Flash 地址 烧录了两个校准值:
0x1fff5310:TSCAL1 —— 芯片在 30℃ 时温度传感器对应的 ADC 读数;
0x1fff5400:TSCAL2 —— 芯片在 85℃ 或 105℃ 时对应的 ADC 读数(不同批次高温点不同)。
温度与 ADC 读数近似线性关系,于是用「两点确定一条直线」:
1
2
|
斜率 k = (高温点温度 - 30) / (TSCAL2数字 - TSCAL1数字)
温度 Temp = k × 当前ADC值 - k × TSCAL1 + 30
|
之所以代码里把数字先乘 3.3 / Vcc_Power,是因为校准值是在 VREF+=3.3V 条件下标定的;如果你的板子实际供电不是 3.3V,就改成 Vcc_Power 的真实值,否则会有偏差。
HighTemp_85 / HighTemp_105 怎么选? 查你手头芯片的数据手册,看高温校准点是 85 还是 105。选错斜率会整体偏冷或偏热。
4.2 ADC 时钟与采样时间
1
2
|
RCC_PeriphCLKInit.AdcClockSelection = RCC_ADCPCLK2_DIV8; // PCLK2(8M) / 8 = 1MHz
sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; // 239.5 个 ADC 周期采样
|
温度传感器内阻较大、信号「软」,所以采样时间给到最长的 239.5 周期,保证采样电容充得够满,读数才准。若用外部快速信号可适当缩短。
4.3 校准不能省
1
|
HAL_ADCEx_Calibration_Start(&AdcHandle);
|
SAR 型 ADC 每个芯片都有微小工艺偏差,HAL 提供的校准函数会测出偏移并写入内部寄存器。必须在 HAL_ADC_Start 之前、且 ADC 已初始化之后调用一次。
4.4 连续转换 + 轮询
ContinuousConvMode = ENABLE 让 ADC 启动后不停地转同一个通道;主循环用 HAL_ADC_PollForConversion(..., 2000) 阻塞等待本次转换完成(超时 2 秒),完成后 HAL_ADC_GetValue 取数即可。
4.5 printf 去哪了?
BSP_USART_Config() 会把 USART2(PA2/PA3,115200)初始化,并把 printf 通过 fputc 重定向到该串口。所以代码里的 printf("Temperature: %d", ...) 实际是从 PA2 发出去,你在电脑串口助手就能看到。
5. 实验现象
- 编译下载程序,串口助手(115200-8-N-1)打开对应 COM 口;
- 每隔约 1 秒,串口打印一行
Temperature: xx;
- 用手捏住芯片或用电吹风(低温档、保持距离)吹一下板子,温度数值会随之变化。
6. 常见问题
| 现象 |
可能原因 |
解决办法 |
| 串口无任何输出 |
波特率/接线错、未重定向 printf |
确认 115200-8-N-1;TX↔RX 交叉接;MDK 勾选 Use MicroLIB |
| 温度恒为 0 或负数 |
HighTemp_85/105 选错、校准值地址错 |
核对数据手册高温点;勿改 0x1fff... 地址 |
| 温度明显偏高/偏低 |
Vcc_Power 与实际供电不符 |
用万用表量 VDD,改成真实值(如 3.28) |
| 温度跳动很大 |
采样时间太短、供电噪声 |
改用 ADC_SAMPLETIME_239CYCLES_5;软件多采几次取平均 |
| 读数一直不变 |
忘了 HAL_ADC_Start 或卡在校准 |
确认启动顺序:Init→ConfigChannel→Calibration→Start |
| 想测环境温度而非内核温度 |
片内传感器只测内核 |
外接 NTC/DS18B20 等,走外部 ADC 通道 |
7. 小结
- PY32F403 的 ADC1 是 12 位 SAR 型,支持内部温度传感器、Vrefint、Vbat 等内部通道;
- 用内部温度传感器测温度无需外部元件,但测的是芯片内核温度;
- 温度换算依赖 出厂校准值 + 两点线性公式,且
HighTemp_85/105 必须按手册选对;
- 标准流程:Init → ConfigChannel → Calibration → Start → PollForConversion → GetValue;
- ADC 时钟来自 PCLK2 分频,采样时间要匹配信号源阻抗。
8. 拓展应用(学了能做什么项目)
- 锂电池供电设备:用 Vbat 通道监测电池电压,低电量报警;
- 恒温箱 / 加热台:片内温度做粗略反馈,配合外部 NTC 精测;
- 环境监测节点:外接 NTC、光敏、土壤湿度传感器,做一个采集器;
- 模拟看门狗报警:参考
ADC_AnalogWatchdog,当温度/电压越限时进中断保护电路;
- 多通道扫描 + DMA:参考
ADC_MultiChannelsSingleConversion_TriggerSW_DMA,一次扫多个传感器,用 DMA 自动搬数据不占 CPU;
- 等间隔采样:参考
ADC_SingleConversion_TriggerTimer_IT,用定时器触发 ADC 采集电机电流波形,做简单示波器。
返回:ADC 外设总览