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。
<br>
1.2 实验条件
硬件:
- NUCLEO-C542RC 开发板
- 支持数据传输的 USB Type-C 线(用于连接 ST-LINK 接口)
- 外部电位器(两端接 3.3 V 与 GND,中间滑动端接 PA1)
软件:
- STM32CubeMX2(用于引脚、时钟、ADC 配置与代码生成)
- VS Code + STM32Cube 插件(用于编译、烧录、调试)
<br>
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 输入,通过外部电位器提供模拟信号源。
<br>
2.2 引脚说明
| 引脚 |
功能 |
说明 |
| PA1 |
ADC1_IN1 |
ADC1 通道 1,模拟输入 |
PA1(ADC1_IN1):配置为模拟输入模式。ADC1 通道 1 对应 PA1 引脚,不是 PA2(ADC1_IN2)。决定采集哪个引脚的是引脚连接关系——电位器接到 PA1,就必须配置为 ADC1_IN1。
<br>
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 和项目保存路径。项目名称避免使用特殊字符,路径建议使用纯英文目录。
<br>
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 等。

<br>
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) |
<br>
4.2 关键代码
在 VS Code 中打开生成的 CMake 工程,找到 Core/Src/main.c,添加以下代码。
(1)文件头部添加头文件与宏定义
#include "main.h"
#include "mx_adc1.h"
/* Private define ------------------------------------------------------------*/
#define ADC_SAMPLE_PERIOD_MS 100U /* 采样周期:每 100ms 启动一次转换 */
#define ADC_REF_VOLTAGE_MV 3300U /* 模拟参考电压 Vref+ = 3.3V (mV) */
#define ADC_RESOLUTION HAL_ADC_RESOLUTION_12_BIT /* 分辨率 12 位 */
(2)全局变量与句柄
/* Private variables ---------------------------------------------------------*/
static hal_adc_handle_t *s_adc1_handle; /* ADC1 句柄(来自 mx_adc1_gethandle)*/
/* 供调试器监视窗口查看的全局变量 */
volatile uint32_t g_adc_raw_value = 0U; /* ADC 原始值 0 ~ 4095 */
volatile uint32_t g_adc_voltage_mv = 0U; /* 换算电压 (mV) 0 ~ 3300 */
/* Private functions prototype -----------------------------------------------*/
static void adc_start_conversion(void);
(3)主函数
int main(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。
*/
static void adc_start_conversion(void)
{
static uint32_t last_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 句柄指针
*/
void HAL_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,供调试器监视窗口查看。
<br>
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 中的运行按钮,将固件下载到开发板。

<br>
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 位原始值读取与电压换算正确
功能正常。
<br>
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() 不同,使用时需注意区分。
<br>
版权声明:
Copyright by [email]ai4mcu@qq.com[/email] 2026
Crated:2026-10