ADC — 软件触发单次转换(多通道)
固件库: PY32F0xx_Firmware V1.5.0
芯片: PY32F030
源文件: PUYA/PY32F0xx_Firmware_V1.5.0/Projects/PY32F030-STK/Example/ADC/ADC_MultiChannelSingleConversion_TriggerSW/Src/main.c
功能简介
ADC(模数转换器)把引脚上的模拟电压转换成数字量,是读取电位器、温湿度、光照等模拟传感器的基础。
本示例演示软件触发 + 单次转换 + 轮询等待的经典用法:
- 配置 ADC1,12 位分辨率,扫描通道 0、1、4;
- 上电后先做校准(Calibration),再用
HAL_ADC_Start() 由软件启动一次转换;
- 用
HAL_ADC_PollForConversion() 轮询等待转换结束,读取结果;
- 通过串口(USART2,PA2/PA3,115200)把采样值打印出来。
文件名虽带 MultiChannel,但 ContinuousConvMode = DISABLE、ExternalTrigConv = ADC_SOFTWARE_START,本质就是“软件触发、扫描多个通道、各转换一次”的模式,最贴合“软件触发单次转换”的教学需求。
硬件准备
- PY32F030 开发板
- 可调电位器(中间抽头接 PA0 / PA1 / PA4 任一处)或信号源
- 串口调试工具(USB 转串口接 PA2=TX、PA3=RX,波特率 115200)
- 板载串口打印由
BSP_USART_Config() 初始化(见 py32f030xx_Start_Kit.c)
完整代码
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
|
#include "main.h"
/* ADC 句柄与通道配置、保存 3 路采样值 */
ADC_HandleTypeDef AdcHandle;
ADC_ChannelConfTypeDef sConfig;
uint32_t adc_value[3];
/* 静态函数声明 */
static void APP_AdcInit(void);
int main(void)
{
/* 1. HAL 初始化(含 SysTick 时基) */
HAL_Init();
/* 2. 初始化 ADC(含校准) */
APP_AdcInit();
/* 3. 初始化调试串口(printf 重定向到 USART2) */
DEBUG_USART_Config();
while (1)
{
/* 4. 软件启动一次 ADC 转换 */
HAL_ADC_Start(&AdcHandle);
for (uint8_t i = 0; i < 3; i++)
{
/* 5. 轮询等待本通道转换完成(超时 10000ms) */
HAL_ADC_PollForConversion(&AdcHandle, 10000);
/* 6. 读取转换结果(12 位,右对齐) */
adc_value[i] = HAL_ADC_GetValue(&AdcHandle);
printf("adc[%u]:%u\r\n", i, (unsigned int)adc_value[i]);
}
HAL_Delay(1000); /* 每秒采样一轮 */
}
}
/**
* @brief ADC 初始化:先复位与校准,再配时钟/分辨率/通道
*/
static void APP_AdcInit(void)
{
/* 复位 ADC 外设,保证起始状态干净 */
__HAL_RCC_ADC_FORCE_RESET();
__HAL_RCC_ADC_RELEASE_RESET();
__HAL_RCC_ADC_CLK_ENABLE(); /* 开启 ADC 时钟 */
AdcHandle.Instance = ADC1;
/* 上电校准:必须在使用 ADC 前执行,否则精度受影响 */
if (HAL_ADCEx_Calibration_Start(&AdcHandle) != HAL_OK)
{
APP_ErrorHandler();
}
AdcHandle.Instance = ADC1;
AdcHandle.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4; /* ADC 时钟 = PCLK/4 */
AdcHandle.Init.Resolution = ADC_RESOLUTION_12B; /* 12 位分辨率 */
AdcHandle.Init.DataAlign = ADC_DATAALIGN_RIGHT; /* 数据右对齐 */
AdcHandle.Init.ScanConvMode = ADC_SCAN_DIRECTION_FORWARD; /* 按通道号顺序扫描 */
AdcHandle.Init.EOCSelection = ADC_EOC_SINGLE_CONV; /* 每完成一次转换产生 EOC */
AdcHandle.Init.LowPowerAutoWait = ENABLE; /* 等待转换模式,省功耗 */
AdcHandle.Init.ContinuousConvMode = DISABLE; /* 单次转换(不连续) */
AdcHandle.Init.DiscontinuousConvMode = DISABLE; /* 关闭间断模式 */
AdcHandle.Init.ExternalTrigConv = ADC_SOFTWARE_START; /* 软件触发 */
AdcHandle.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE; /* 无外部触发边沿 */
AdcHandle.Init.DMAContinuousRequests = DISABLE; /* 不使用 DMA */
AdcHandle.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN; /* 溢出时覆盖旧值 */
AdcHandle.Init.SamplingTimeCommon = ADC_SAMPLETIME_239CYCLES_5; /* 采样时间 239.5 周期 */
if (HAL_ADC_Init(&AdcHandle) != HAL_OK)
{
APP_ErrorHandler();
}
/* 依次把通道 0、1、4 加入扫描序列(Rank 方式排队) */
sConfig.Channel = ADC_CHANNEL_0;
sConfig.Rank = ADC_RANK_CHANNEL_NUMBER;
if (HAL_ADC_ConfigChannel(&AdcHandle, &sConfig) != HAL_OK) APP_ErrorHandler();
sConfig.Channel = ADC_CHANNEL_1;
if (HAL_ADC_ConfigChannel(&AdcHandle, &sConfig) != HAL_OK) APP_ErrorHandler();
sConfig.Channel = ADC_CHANNEL_4;
if (HAL_ADC_ConfigChannel(&AdcHandle, &sConfig) != HAL_OK) APP_ErrorHandler();
}
void APP_ErrorHandler(void)
{
while (1) {}
}
|
代码分段讲解
1. 校准与时钟
PY32F0xx 的 SAR ADC 上电后必须执行 HAL_ADCEx_Calibration_Start() 消除内部失调,否则读数偏差很大。ADC 时钟通过 ADC_CLOCK_SYNC_PCLK_DIV4 分频得到,需满足数据手册规定的最大 ADC 时钟(通常 ≤ 16MHz)。
2. 软件触发 vs 硬件触发
ExternalTrigConv = ADC_SOFTWARE_START 表示转换由 HAL_ADC_Start() 启动;若用定时器触发,则设为 ADC_EXTERNALTRIGCONV_Tx_TRGO 等并配置边沿。教学阶段用软件触发最简单直观。
3. 单次 / 连续 / 扫描
ContinuousConvMode = DISABLE:每调用一次 HAL_ADC_Start() 只转换一轮(这里 3 个通道各一次)。
ScanConvMode = FORWARD:多通道时按 Rank 顺序自动切换通道。
- 每个通道转换完都会置 EOC,
HAL_ADC_PollForConversion() 就是在等这个标志。
4. 结果换算
12 位右对齐时,读数与电压关系为:
1
|
电压(V) = 读数 / 4095 × VREF+
|
PY32F030 内部参考电压 VREF+ 一般等于 VDDA(多为 3.3V)。
实验现象
常见问题
-
读数一直是 0 或 4095(满偏)?
- 检查引脚是否真的接到模拟信号;悬空输入会被上/下拉拉到极端值。
- 确认
VREF+/VDDA 供电正常。
-
读数跳动很大?
- 增大
SamplingTimeCommon(采样时间),给内部采样电容更长的充电时间;信号源内阻大时尤其需要。
- 软件上可做多次采样取平均。
-
想用 DMA 自动搬运多路数据?
- 参考同目录
ADC_ContinuousConversion_DMA,把 DMAContinuousRequests 打开并用 HAL_ADC_Start_DMA()。
-
想固定单通道采样?
- 只
ConfigChannel 一个通道即可,for 循环删掉多余通道。
小结
- ADC 标准流程:开时钟 → 校准 → 配参数 → 配通道 → 启动 → 等待 EOC → 读值。
- 软件触发 + 轮询是最易理解的入门方式;对实时性要求高时用定时器触发 + DMA。
- 12 位分辨率下满量程 4095,注意参考电压与采样时间对精度的影响。
拓展应用
- 接 NTC 热敏电阻做温度计(查表/公式换算温度)。
- 接光敏电阻做光照检测,配合 PWM 自动调光。
- 用 DMA + 定时器触发做“示波器”式连续采样,再用串口绘图。
- 读取内部
ADC_CHANNEL_VREFINT(参考电压)与 ADC_CHANNEL_TEMPSENSOR(温度)做自校准/测温。