欢迎关注 youcans@xidian【嵌入式软件AI编程】专栏
【STM32C5教程】(4)GPIO 外部中断实验
【STM32C5教程】(5)定时器周期中断实验
【STM32C5教程】(6)定时器 PWM 输出实验
【STM32C5教程】(7)TIM1 互补 PWM 输出实验
【STM32C5教程】(8)ADC 模拟量数据采集实验
1. 项目简介
1.1 实验目标
本实验在 NUCLEO-C542RC 开发板上,使用 STM32C542RCT6 的 ADC1 通道 1 采集 PA1 引脚输入的模拟电压,采用软件触发与转换完成中断方式,每 100 ms 启动一次 ADC 采集,读取 12 位 ADC 原始值并换算为输入电压。
功能需求:
- 使用 ADC1 通道 1 采集 PA1 引脚输入的模拟电压
- 采用软件触发方式启动转换
- 采用转换完成中断方式读取结果
- 每 100 ms 启动一次 ADC 采集
- 读取 12 位 ADC 原始值并换算为输入电压(mV)
开发板和 MCU 型号:NUCLEO-C542RC 开发板,搭载 STM32C542RCT6 MCU。
1.2 实验条件
硬件:
- NUCLEO-C542RC 开发板
- 支持数据传输的 USB Type-C 线(用于连接 ST-LINK 接口)
- 外部电位器(两端接 3.3 V 与 GND,中间滑动端接 PA1)
软件:
- STM32CubeMX2(用于引脚、时钟、ADC 配置与代码生成)
- VS Code + STM32Cube 插件(用于编译、烧录、调试)
2. 硬件说明
2.1 NUCLEO-C542RC 开发板
NUCLEO-C542RC 是 ST 基于 STM32C542RCT6 推出的 Nucleo-64 开发板。主要特性:
- MCU:STM32C542RCT6,Arm Cortex-M33 内核,最高主频 144 MHz,256 KB Flash,64 KB RAM
- 调试器:板载 STLINK-V3EC,支持大容量存储、虚拟串口和调试端口三种功能
- 用户 LED:LD1,连接 PA5
- 用户按键:B1,连接 PC13
- 扩展接口:Arduino Uno V3 和 ST morpho 连接器
本实验使用 PA1 作为 ADC 输入,通过外部电位器提供模拟信号源。
2.2 引脚说明
| 引脚 | 功能 | 说明 |
|---|---|---|
| PA1 | ADC1_IN1 | ADC1 通道 1,模拟输入 |
PA1(ADC1_IN1):配置为模拟输入模式。ADC1 通道 1 对应 PA1 引脚,不是 PA2(ADC1_IN2)。决定采集哪个引脚的是引脚连接关系——电位器接到 PA1,就必须配置为 ADC1_IN1。
2.3 电位器接线
| 电位器引脚 | 连接到 |
|---|---|
| 一端 | 3.3 V |
| 另一端 | GND |
| 中间滑动端 | PA1 |
调节电位器时,PA1 引脚电压在 0 ~ 3.3 V 范围内线性变化。
3. STM32CubeMX2 工程配置
3.1 新建工程
本实验采用 MCU 方式创建工程,即以芯片型号 STM32C542RC 为起点,而非开发板方式。
进入 MCU 选择界面
启动 STM32CubeMX2,在首页点击 MCU,进入芯片型号选择列表。搜索并选中芯片
在 MCU name 输入框中输入 STM32C542RC(或完整型号 STM32C542RCT6),从筛选出的列表中选择对应型号。
- 填写项目信息
输入项目名称 ADC_IT 和项目保存路径。项目名称避免使用特殊字符,路径建议使用纯英文目录。
3.2 基础配置
工程创建后,软件自动进入 Pinout 视图。
时钟配置:本工程系统时钟为48 MHz(HSI ÷ 3),ADC 内核时钟为144 MHz(PSIS 时钟),分频系数为 1。
3.3 ADC1 配置
(1)启用 ADC1
在左侧外设列表中找到Analog→ADC1,点击展开。
(2)配置 ADC 参数
在 ADC1 的Parameter Settings中设置:
| 参数 | 值 | 说明 |
|---|---|---|
| Resolution | 12 bit | 12 位分辨率 |
| Sampling Mode | Normal | 普通采样模式 |
| Kernel Clock Source | PSIS | ADC 内核时钟源 |
(3)配置通道
在Channels区域中,将ADC1_IN1的Use Channel设置为启用。
| 参数 | 值 | 说明 |
|---|---|---|
| Channel | ADC1_IN1 | 对应 PA1 引脚 |
| Sampling Time | 25 Cycles | 采样时间 |
| Single/Differential | Single Ended | 单端输入 |
设置完成后,Pinout 视图中PA1自动分配为ADC1_IN1(模拟模式)。
(4)配置 Regular 组
切换到Groups Conversions标签页,配置 Regular 组:
| 参数 | 值 | 说明 |
|---|---|---|
| Trigger Type | Software start | 软件触发 |
| Sequencer Length | 1 | 序列长度 1 |
| Continuous | Single | 单次转换 |
| Overrun | Overwritten | 覆盖模式 |
将IN1加入正则转换序列。
(5)使能中断
在NVIC Settings中,勾选ADC1 global interrupt使能转换完成中断。
3.4 项目配置
- 点击Project Settings,进入工程设置页面。
- 在IDE Format下拉选项中选择CMake。
- 在CMake Toolchain下拉选项中选择GCC。
- 确认配置无误后,点击Generate Code。CubeMX2 会执行配置检查,如果没有错误,开始在指定路径下生成 CMake 工程文件。
- 生成完成后,项目路径下会出现标准 CMake 工程结构,包含
Core/、Drivers/、CMakeLists.txt等。
4. 编程实现
4.1 实现思路
采用“ADC 初始化 + 软件触发 + 转换完成中断 + 100 ms 非阻塞计时”的方案:
mx_system_init() // CubeMX2 生成:时钟、GPIO、ADC1、NVIC 初始化 │ ▼ HAL_ADC_Start() // 激活 ADC(上电、使能,进入 ACTIVE 状态) │ ▼ HAL_ADC_Calibrate() // ADC 校准(要求处于 ACTIVE 状态) │ ▼ 主循环:每 100 ms 调用一次 HAL_ADC_REG_StartConv_IT() │ ▼ 转换完成,触发 ADC1 中断 │ ▼ HAL_ADC_REG_UnitaryConvCpltCallback() │ ▼ 读取原始值 → 换算电压 → 存入全局变量为什么用中断而不是轮询?轮询方式需要 CPU 持续等待转换完成,中断方式下 CPU 可以在等待期间执行其他任务,本实验的主循环只需做 100 ms 计时,转换和读取由硬件和中断自动完成。
ADC 操作顺序:HAL_ADC_Start()→HAL_ADC_Calibrate()→HAL_ADC_REG_StartConv_IT()。HAL_ADC_Calibrate()内部要求 ADC 已处于 ACTIVE 状态,因此顺序不可颠倒。
各步骤使用的 HAL 函数:
| 步骤 | HAL 函数 | 作用 |
|---|---|---|
| 激活 ADC | HAL_ADC_Start() | 使 ADC 进入 ACTIVE 状态 |
| 校准 | HAL_ADC_Calibrate() | ADC 校准 |
| 启动转换 | HAL_ADC_REG_StartConv_IT() | 软件触发启动单次转换 + EOC 中断 |
| 读取原始值 | HAL_ADC_REG_ReadConversionData() | 读取 12 位转换结果 |
| 电压换算 | HAL_ADC_CALC_DATA_TO_VOLTAGE() | 原始值换算为电压(mV) |
4.2 关键代码
在 VS Code 中打开生成的 CMake 工程,找到Core/Src/main.c,添加以下代码。
(1)文件头部添加头文件与宏定义
#include"main.h"#include"mx_adc1.h"/* Private define ------------------------------------------------------------*/#defineADC_SAMPLE_PERIOD_MS100U/* 采样周期:每 100ms 启动一次转换 */#defineADC_REF_VOLTAGE_MV3300U/* 模拟参考电压 Vref+ = 3.3V (mV) */#defineADC_RESOLUTIONHAL_ADC_RESOLUTION_12_BIT/* 分辨率 12 位 */(2)全局变量与句柄
/* Private variables ---------------------------------------------------------*/statichal_adc_handle_t*s_adc1_handle;/* ADC1 句柄(来自 mx_adc1_gethandle)*//* 供调试器监视窗口查看的全局变量 */volatileuint32_tg_adc_raw_value=0U;/* ADC 原始值 0 ~ 4095 */volatileuint32_tg_adc_voltage_mv=0U;/* 换算电压 (mV) 0 ~ 3300 *//* Private functions prototype -----------------------------------------------*/staticvoidadc_start_conversion(void);(3)主函数
intmain(void){if(mx_system_init()!=SYSTEM_OK){return(-1);}else{/* 获取 ADC1 句柄 */s_adc1_handle=mx_adc1_gethandle();if(s_adc1_handle==NULL){return(-1);}/* ADC 操作流程: 1. HAL_ADC_Start() -- 激活 ADC(上电、使能,进入 ACTIVE 状态) 2. HAL_ADC_Calibrate() -- ADC 校准(要求处于 ACTIVE 状态) 3. HAL_ADC_REG_StartConv_IT() -- 软件触发启动单次转换 + EOC 中断 */if(HAL_ADC_Start(s_adc1_handle)!=HAL_OK){return(-1);}if(HAL_ADC_Calibrate(s_adc1_handle)!=HAL_OK){return(-1);}while(1){/* 非阻塞计时:每 100ms 软件触发启动一次 ADC 单次转换 */adc_start_conversion();}}}(4)100 ms 非阻塞触发函数
/** * @brief 每 100ms 软件触发启动一次 ADC 单次转换,转换完成后由中断回调处理。 * @note 单次转换 EOC 后 HAL 自动将 regular 组状态恢复为 IDLE, * 故可反复调用 HAL_ADC_REG_StartConv_IT() 而不会返回 HAL_BUSY。 */staticvoidadc_start_conversion(void){staticuint32_tlast_tick=0U;if((HAL_GetTick()-last_tick)>=ADC_SAMPLE_PERIOD_MS){last_tick=HAL_GetTick();(void)HAL_ADC_REG_StartConv_IT(s_adc1_handle);}}(5)转换完成回调函数
/** * @brief ADC 常规组单次转换完成回调(重载 HAL 弱函数)。 * 读取 12 位 ADC 原始值并换算为输入电压 (mV)。 * @param hadc ADC 句柄指针 */voidHAL_ADC_REG_UnitaryConvCpltCallback(hal_adc_handle_t*hadc){(void)hadc;/* 读取 ADC 原始值(12 位单次转换结果,0 ~ 4095) */g_adc_raw_value=(uint32_t)HAL_ADC_REG_ReadConversionData(s_adc1_handle);/* 电压换算:mV = raw * Vref / 满刻度(4095) (ST 官方宏,第三个参数须为 HAL_ADC_RESOLUTION_12_BIT 枚举值,单位 mV) */g_adc_voltage_mv=(uint32_t)HAL_ADC_CALC_DATA_TO_VOLTAGE(ADC_REF_VOLTAGE_MV,g_adc_raw_value,ADC_RESOLUTION);}代码说明:
- mx_adc1_gethandle() 返回 CubeMX2 生成的 ADC1 句柄。
- HAL_ADC_Start() 使 ADC 进入 ACTIVE 状态,必须先调用。
- HAL_ADC_Calibrate() 校准 ADC,要求 ADC 已处于 ACTIVE 状态,所以必须在 HAL_ADC_Start() 之后调用。
- HAL_ADC_REG_StartConv_IT() 以软件触发方式启动一次单次转换,并使能转换完成中断。
- HAL_ADC_REG_UnitaryConvCpltCallback() 是 HAL2 中的转换完成回调函数,用户重写这个同名函数,中断发生时 HAL 自动调用它。
- HAL_ADC_REG_ReadConversionData() 读取 12 位转换结果。
- HAL_ADC_CALC_DATA_TO_VOLTAGE() 是 ST 官方宏,将原始值换算为电压(mV)。第三个参数必须传 HAL_ADC_RESOLUTION_12_BIT 枚举值。
- g_adc_raw_value 和 g_adc_voltage_mv 声明为 volatile,供调试器监视窗口查看。
5. 编译烧录与运行测试
5.1 编译与烧录
- 打开工程:用 VS Code 打开 CubeMX2 生成的 CMake 工程文件夹(如 ADC_IT_cmake)。
- 编译:点击 VS Code 底部状态栏的Build按钮,或使用命令面板执行 CMake 构建。编译完成后,终端应输出
退出代码为 0,无报错提示。 - 连接开发板:用支持数据传输的 USB Type-C 线连接 NUCLEO-C542RC 的 ST-LINK USB 口(CN1)。
- 确认设备识别:打开设备管理器,在“端口 (COM 和 LPT)”下应能看到STLink COMx。
- 烧录:点击 VS Code 中的运行按钮,将固件下载到开发板。
5.2 运行与测试
烧录完成后,程序自动开始运行。使用调试器监视窗口查看 g_adc_raw_value 和 g_adc_voltage_mv 两个全局变量的值。
测试方法:用断点读取稳定值
- 在 HAL_ADC_REG_UnitaryConvCpltCallback() 回调内最后一条赋值语句(换算 g_adc_voltage_mv 那一行)设断点。停住时正是本次转换结果刚写入两个变量的瞬间,数值稳定不再变化。
- 程序每 100 ms 停在断点一次(停在 ADC 中断上下文,不影响查看全局变量)。
- 分别在 PA1 接 GND、接 3.3 V、接电位器中间挡位三种状态下,记录变量 g_adc_raw_value 和 g_adc_voltage_mv 的值。
测试 1:PA1 接 GND
通过电位器调到头使 PA1 电压为 0 V,观察监视窗口:- g_adc_raw_value 接近 0
- g_adc_voltage_mv 接近 0 mV
测试 2:PA1 接 3.3 V
通过电位器调到头使 PA1 电压为 3.3 V,观察监视窗口:- g_adc_raw_value 接近 4095
- g_adc_voltage_mv 接近 3300 mV
测试 3:PA1 接电位器中间挡位
调节电位器到中间位置,观察监视窗口:- g_adc_raw_value 在 0 ~ 4095 之间
- g_adc_voltage_mv 与原始值按公式 电压 = raw × 3300 / 4095 对应
测试结果:
| PA1 连接 | 原始值 g_adc_raw_value | 电压 g_adc_voltage_mv | 是否正常 |
|---|---|---|---|
| → GND | 1 | 0 mV | ✅ |
| → 3.3 V | 4091 | 3296 mV | ✅ |
| → 电位器中间 | 2463 | 1968 mV | ✅ |
测试结果总结:
☑ 编译通过,无错误
☑ 烧录成功
☑ ADC1 通道 1 正确采集 PA1 电压
☑ 软件触发 + 转换完成中断方式工作正常
☑ 每 100 ms 启动一次采集
☑ 12 位原始值读取与电压换算正确
功能正常。
6. 小结
本实验通过 ADC1 通道 1 采集 PA1 引脚输入的模拟电压,实现了软件触发、转换完成中断、12 位原始值读取与电压换算,掌握了 STM32C5 系列 ADC 的配置与使用方法。
核心逻辑可以概括为:“ADC 激活 → 校准 → 每 100 ms 软件触发单次转换 → 中断中读取原始值 → 换算电压”。
有两点需要注意:
ADC 操作顺序不能颠倒。
HAL_ADC_Start() 先使 ADC 进入 ACTIVE 状态,然后才能调用 HAL_ADC_Calibrate() 校准,最后才能用 HAL_ADC_REG_StartConv_IT() 启动转换。HAL2 的回调函数名与经典 HAL 不同。
转换完成回调是 HAL_ADC_REG_UnitaryConvCpltCallback(),启动函数是 HAL_ADC_REG_StartConv_IT(),与经典 HAL 的 HAL_ADC_ConvCpltCallback() 和 HAL_ADC_Start_IT() 不同,使用时需注意区分。
版权声明:
欢迎关注 youcans@xidian【嵌入式软件AI编程】专栏
转发必须注明原文链接:
【STM32C5教程】(8)ADC 模拟量数据采集实验
Copyright by ai4mcu@qq.com 2026
Crated:2026-10