avatar🌌
零醇零醇の小屋

樱花树下,再续前缘

鹤望兰无凋零之时,比翼鸟永世长存

STM32 HAL库入门教程(七):ADC 模数转换 —— 测电压与读取真实世界模拟量!

先来想一个很哲学的问题: 现实世界里的东西 比如气温、光线强弱、声音大小、电池电量 它们全都是“连续平滑变化”的 也就是说世界是模拟的(✧ω✧) 但是在前面我们也说过 单片机的 IO 口,有四种模式:数字输入输出、模拟输入输出 在数字状态下 单片机只认识0和1 在模拟状态下 单片机就可以识别到更细的电压了 那单片机为什么在模拟状态下可以识别电压呢?

这就轮到今天的主角登场了——ADC(Analog-to-Digital Converter,模数转换器)! 按教科书的说法 adc 分为什么逐次逼近型(SAR,其实我们 stm32 用的就是这个喵~)、Delta-Sigma型、双积分型、并联比较型 还要算什么冲击函数 抗混叠滤波器 奈奎斯特采样率~~)~~ 用人话来说 它就是单片机里的一个外设专门负责把现实世界连续变化的模拟电压 转换成单片机能听懂的数字喵~

ADC有几个比较重要的特征 1、分辨率! 看图 图0

我们 STM32F103C8T6 内部自带的 ADC 是一个 12 位的 ADC 什么叫 12 位呢?就是它把 0V 到 3.3V 这一段电压 均匀切成了 2 的 12 次方(也就是 4096 块)!

  • 当引脚检测到 0V 时 翻译出的数字是 0
  • 当引脚检测到 3.3V 时 翻译出的数字是 4095
  • 每一块刻度只有大概 3.3V / 4095 ≈ 0.8mV!极其敏感! 但是实际上你懂的 由于杜邦线 输入电压3.3的不稳定 地的不稳定 实际的分辨率根本达不到(

所以真实电压计算公式超级简单: 真实电压 = (ADC读取的数字 / 4095.0) * 3.3V

这里要插播一条超级严重的保命警告!( >﹏<。) “STM32 的 ADC 引脚极限只能吃 3.3V!千万不要拿 5V 或者更高的电压往里面捅!不然芯片当场冒烟变成熟透的炸弹喵!!”

2.转换时间 这个和采样率有密切联系 这个问题值得后面讨论 本文暂时不讨论(

接线准备也非常简单: 如果你手头有电位器或者电阻 最好不过啦 如果你手头什么传感器都没有 也完全不用慌! 只要拿一根杜邦线 插在板子的 PA1 上 另一头先悬空 等待会烧好代码后 去戳一戳这根杜邦线 屏幕上的数字就会跟着蹦极啦!

原理搞明白了 打开我们熟悉的 cubemx! 老规矩选好 c8t6 配好时钟和调试口 顺便把左侧 Connectivity 里的 USART1 依然配成 Asynchronous 波特率 115200 待会要用 printf 把测出来的电压打在电脑上喵~

重点来啦!在左侧找到 Analog -> 点击 ADC1: 在 IN1(通道1)前面打上勾! 你会发现右边芯片上的 PA1 自动变成绿色的 ADC1_IN1 啦! 下面的参数全部保持默认就行(也就是独立模式、数据右对齐、不开启连续转换)

图2

然后你发现时钟报错了 稍微修改一下 图1

配置好工程 点击生成代码 打开 Keil 工程!

写代码前 别忘了第五篇说过的老规矩:

  1. 魔术棒 Target 勾选 Use MicroLIB
  2. 在 main.c 最上方引入 #include <stdio.h>
  3. 在 USER CODE BEGIN 0 加上那几行老朋友 fputc 重定向代码(忘掉的回去罚看第五篇

现在我们来看看 HAL 库读取 ADC 的步骤:

  1. HAL_ADC_Start(&hadc1); -> 启动 ADC 转换
  2. HAL_ADC_PollForConversion(&hadc1, 10); -> 等待转换完成 10ms超时 防止阻塞
  3. uint16_t adc_val = HAL_ADC_GetValue(&hadc1); -> 读取 0 ~ 4095 的原始数值

在 while(1) 主循环里 我们来写上完整的测电压逻辑:

c
/* USER CODE BEGIN WHILE */
while (1)
{
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    // 1. 启动转换
    HAL_ADC_Start(&hadc1);
    
    // 2. 等待转换完成(超时时间给 10ms)
    if (HAL_ADC_PollForConversion(&hadc1, 10) == HAL_OK)
    {
        // 3. 读取原始值(0 ~ 4095)
        uint16_t adc_val = HAL_ADC_GetValue(&hadc1);
        
        // 4. 换算成真实电压(V)
        float voltage = (float)adc_val * 3.3f / 4095.0f;
        
        // 打印到串口助手
        printf("原始值: %4d | 检测电压: %.2f V Nya~\r\n", adc_val, voltage);
    }
    
    HAL_Delay(500); // 半秒测一次
}
/* USER CODE END 3 */
图3

编译 下载 烧录! 用杜邦线插在 PA1 上 把 USB 转 TTL 连上电脑 打开串口助手(波特率 115200):

  • 当杜邦线另一头戳在 3.3V 时 原始值跳到 4095 附近 电压显示 3.30V附近!
  • 当杜邦线另一头戳在 G(GND)时 原始值掉到 0 电压显示 0.00V!
  • 如果你悬空拿手指捏着引脚 会看到数据在几百到一千左右疯狂跳动 那是人体感应到的空气电磁杂波喵~
图4

看!单片机不仅有了嘴巴和耳朵 现在还长出了能感知真实世界的“眼睛”!(✧ω✧) 有了 ADC 以后不管是测电池电量、接摇杆打游戏 还是接温度光敏传感器 全都是一套完全一样的逻辑!

完结撒花!(=^・ω・^=) 快去拿你的单片机当一把可爱的数字电压表玩玩吧喵~


💻 本教程完整工程源码已开源:
👉 TomSorachan/stm32-hal-tutorials

STM32 HAL库入门教程(六):定时器定时中断 —— 彻底摆脱被 HAL_Delay 绑架的 CPU!