你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【STM32C5教程】(8)ADC 模拟量数据采集实验

[复制链接]
HuangXF 发布时间:2026-10-21 14:09

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 为起点,而非开发板方式。

  1. 进入 MCU 选择界面 启动 STM32CubeMX2,在首页点击 MCU,进入芯片型号选择列表。
  2. 搜索并选中芯片
  3. 在 MCU name 输入框中输入 STM32C542RC(或完整型号 STM32C542RCT6),从筛选出的列表中选择对应型号。
  4. 填写项目信息
  5. 输入项目名称 ADC_IT 和项目保存路径。项目名称避免使用特殊字符,路径建议使用纯英文目录。

<br>

3.2 基础配置

工程创建后,软件自动进入 Pinout 视图。

时钟配置:本工程系统时钟为 48 MHz(HSI ÷ 3),ADC 内核时钟为 144 MHz(PSIS 时钟),分频系数为 1。

3.3 ADC1 配置

(1)启用 ADC1

在左侧外设列表中找到 Analog → ADC1,点击展开。

image.png

(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(模拟模式)。

image.png

(4)配置 Regular 组

切换到 Groups Conversions 标签页,配置 Regular 组:

参数 值 说明
Trigger Type Software start 软件触发
Sequencer Length 1 序列长度 1
Continuous Single 单次转换
Overrun Overwritten 覆盖模式

将 IN1 加入正则转换序列。

image.png

(5)使能中断

在 NVIC Settings 中,勾选 ADC1 global interrupt 使能转换完成中断。

image.png

3.4 项目配置

  1. 点击 Project Settings,进入工程设置页面。
  2. 在 IDE Format 下拉选项中选择 CMake。
  3. 在 CMake Toolchain 下拉选项中选择 GCC。
  4. 确认配置无误后,点击 Generate Code。CubeMX2 会执行配置检查,如果没有错误,开始在指定路径下生成 CMake 工程文件。
  5. 生成完成后,项目路径下会出现标准 CMake 工程结构,包含 Core/、Drivers/、CMakeLists.txt 等。

image.png

<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 编译与烧录

  1. 打开工程:用 VS Code 打开 CubeMX2 生成的 CMake 工程文件夹(如 ADC_IT_cmake)。
  2. 编译:点击 VS Code 底部状态栏的 Build 按钮,或使用命令面板执行 CMake 构建。编译完成后,终端应输出 退出代码为 0,无报错提示。
  3. 连接开发板:用支持数据传输的 USB Type-C 线连接 NUCLEO-C542RC 的 ST-LINK USB 口(CN1)。
  4. 确认设备识别:打开设备管理器,在“端口 (COM 和 LPT)”下应能看到 STLink COMx。
  5. 烧录:点击 VS Code 中的运行按钮,将固件下载到开发板。

image.png

<br>

5.2 运行与测试

烧录完成后,程序自动开始运行。使用调试器监视窗口查看 g_adc_raw_value 和 g_adc_voltage_mv 两个全局变量的值。

测试方法:用断点读取稳定值

  1. 在 HAL_ADC_REG_UnitaryConvCpltCallback() 回调内最后一条赋值语句(换算 g_adc_voltage_mv 那一行)设断点。停住时正是本次转换结果刚写入两个变量的瞬间,数值稳定不再变化。
  2. 程序每 100 ms 停在断点一次(停在 ADC 中断上下文,不影响查看全局变量)。
  3. 分别在 PA1 接 GND、接 3.3 V、接电位器中间挡位三种状态下,记录变量 g_adc_raw_value 和 g_adc_voltage_mv 的值。

image.png

  • 测试 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

赞 收藏 评论0 发布时间:2026-10-21 14:09

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版