ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于STM32F103R6的数字电压表设计:从ADC采样到LCD显示

基于STM32F103R6的数字电压表设计:从ADC采样到LCD显示 简介这是一套基于STM32F103R6的数字电压表完整设计资源包含Proteus仿真电路与Keil5工程源码面向嵌入式入门者与课程设计开发者解决按键切换量程、ADC采集0-5V电压并驱动4位LED数码管显示的核心问题。压缩包共160个文件包含27个C源文件与28个头文件构成的Keil工程主体同时提供编译生成的hex烧录文件与Proteus8.9工程配置整体仅5.65MB目录清晰便于对照学习。工程基于STM32F10x标准外设库读者可以借助完整的量程变换逻辑、按键处理、数码管动态扫描与ADC采集代码理解模拟电压采集显示的全流程在Proteus中运行即可看到按键换挡和电压实时刷新效果也可将hex烧录到开发板验证。资源中Proteus电路图与Keil工程一一对应省去重新连线与配置外设的麻烦适合快速上手。已有3600人学习这套资源对需要完成单片机课程设计或入门STM32的读者具有较高参考价值。1. 项目概述与方案选型1.1 为什么选 STM32F103R6 做电压表做嵌入式开发的人第一个正经项目十有八九是数字电压表。我当初从 51 单片机跳过来的时候也纠结过是继续用 51 加外部 ADC还是直接上 STM32F103R6。后来发现这个选择几乎没有悬念。51 单片机自身没有 ADC 外设需要外挂 ADC0809 或者 PCF8591 这类芯片接线不少精度也就 8 位还得靠 IO 口模拟时序调起来很煎熬。而 STM32F103R6 这颗芯片内部集成 12 位逐次逼近型 ADC最快采样率能做到 1MHz配合 DMA 和定时器触发采集速度根本不是问题。更重要的是R6 属于 F103 系列的中等容量版本64KB Flash、20KB RAM对于跑一个电压表加显示逻辑绰绰有余成本还压得下来。还有一个容易被忽略的点STM32F103R6 的主频 72MHz跑浮点运算虽然不占优势但处理电压转换、滤波算法、显示刷新这类任务完全够用。我实测下来一帧 5 位数显包含采样、滤波、显示刷新整个循环占用 CPU 不到 30%剩下的资源还能塞进去一些扩展功能。这个项目适合谁如果你正在学 STM32 标准库或者 HAL 库想找一个“不吃灰”的练手项目它非常合适。硬件成本极低核心电路就一个最小系统加几个电阻电容软件上涵盖了 GPIO、ADC、定时器、中断、LCD 驱动多个知识点做完这一套你对这芯片的理解会拔高一个台阶。1.2 数字电压表的核心功能边界先说清楚这个项目到底做了什么用 STM32F103R6 内部的 12 位 ADC 采集外部电压经过滤波和校准算法处理后把结果显示在 LCD 上。量程我设计了两档0~3.3V 直接采集3.3~30V 通过前端分压电阻衰减后采集。为什么这么设计因为 STM32 的 ADC 输入范围是 VREF- 到 VREF板子上一般接的是 3.3V直接测超过 3.3V 的电压会烧引脚必须在前端做衰减。这也是很多新手容易忽略的地方——拿最小系统板的 3.3V 参考直接量 12V 电池一接上去味道就出来了。精度方面12 位 ADC 的理论分辨率是 3.3V/4096 ≈ 0.8mV听起来不错但实际做到 1% 精度就需要下功夫了。这里涉及基准源、分压电阻精度、采样稳定性等多个因素后面我会单独展开讲。整体功能上我加了一个按键切换量程还做了简单的低电压提示纯粹是顺手扩展不影响主体。1.3 项目整体架构一句话版STM32F103R6 的 ADC1 通道通过分压网络采集外部电压软件配合定时器触发多通道采样DMA 搬运数据均值滤波去抖再把真实电压值换算出来通过 LCD1602 显示。整条链路由硬件采集端、软件数据处理端、显示端三段组成每条链路都有坑下面挨个说。2. 硬件电路设计从器件选型到布线避坑2.1 分压网络设计与阻值计算对于超过 3.3V 的电压分压电阻的选择不能拍脑袋。我见过有人拿两个 10K 电阻直接分压测出来的值始终比万用表低 5%原因就在于没有考虑后级输入阻抗对分压比的影响。我的设计思路是这样的ADC 输入端的等效阻抗由采样电容和切换开关决定STM32F103 数据手册给的推荐源阻抗小于 10KΩ。如果分压网络的总阻值太大比如几百 KΩ采样电容充不满ADC 结果就会偏低如果太小分压电路自身的功耗又太大对于电池供电场景不友好。这里有个取舍。我的方案是R130KΩR210KΩ当 VIN30V 时分压到 ADC 引脚是 7.5V还是超过了 3.3V所以又加了一级 10K/4.7K 的细调网络最终把满量程映射到 3.0V 左右留了一点裕量。综合计算下来分压比约为 10:1即外部电压 10V 对应 ADC 引脚 1V。具体计算过程不复杂V_ADC V_IN × (R2∥R4) / (R1 R2∥R4)选 R136KΩ, R24KΩ 是最省事的。用 0.1% 精度的低温漂电阻分压误差能控在 0.2% 以内比普通 5% 电阻好很多。强烈建议分压电阻用精密电阻一套 10 颗也就几块钱但精度提升是实打实的。2.2 ADC 输入端的 RC 滤波与阻抗匹配很多教程只画一个分压电阻进 ADC实际用起来会发现数值跳得厉害尤其是测量开关电源输出时纹波直接体现在 ADC 采样结果上。这时候需要在 ADC 引脚前加一阶 RC 低通滤波器。我用的参数是 R1KΩC0.1μF截止频率约 1.6kHz。这个滤波器有两个作用一是滤掉高频噪声二是降低源阻抗让 ADC 内部的采样电容更快充满。要注意的是R 不能太大否则和 ADC 输入电容组成的分压效应会引入新的误差1KΩ 是个平衡点。还有一点容易被忽视STM32F103 的 ADC 引脚和内部温度传感器、VREFINT 共用部分电路。如果你的板子上 PB0ADC1_IN8和 PC0ADC1_IN10同时使用要确认没有复用冲突。我在第一版 PCB 上把按键接到了 PC0结果 ADC 通道初始化后按键没反应查了半天才发现引脚冲突。2.3 LCD 显示模块的接线方案显示模块我选的是 LCD1602理由很简单便宜、稳定、驱动资料多。如果你手上正好有 OLED SSD1306I2C 接口用起来更舒服显示内容也更灵活。不过我在这篇博文里还是以 LCD1602 为例因为它是新手最容易上手的。接线方式采用 4 线数据模式D4~D7节省 4 个 GPIO。RS 接 PB0RW 接 PB1直接接地也行只写不读E 接 PB10D4~D7 接 PB12~PB15。背光通过一个三极管开关控制由 PB11 控制背光的开关因为在某些实际测量场景下长时间亮背光比较耗电。这里有个经验LCD1602 的数据线不能随便接在 ADC 通道引脚上不然你会面临一个两难——LCD 初始化时电平跳变会干扰 ADC 采样。所以尽量把 LCD 放在 GPIO 丰富的端口与 ADC 通道物理隔离。3. 软件实现从 CubeMX 配置到 ADC 采样链路3.1 用 CubeMX 初始化工程的几个关键配置我不推荐纯寄存器操作来写这种中型项目调试周期太长。用 STM32CubeMX 生成 HAL 库工程把精力放在业务逻辑上才是正路。时钟树配置这块直接用外部 8MHz 晶振倍频到 72MHz 主频。有个细节ADC 时钟最高 14MHz把 ADC 预分频设为 6得到 12MHz这个频率在数据手册推荐范围内采样精度和速度比较均衡。ADC 配置参数如下分辨率12 位默认扫描模式关闭单通道连续转换开启触发方式定时器 2 触发我习惯用软件触发做一锤子买卖DMA 配合连续转换更顺手对齐方式右对齐采样周期239.5 周期最慢采样最稳最后一项采样周期多说一句ADC 采样时间越长内部采样电容充得越满结果越接近真实值。对于电压表这种低速应用完全不需要追求高速采样选最慢的采样周期能明显减少读数跳变。我用 239.5 周期等效采样率大约 50kHz完全够用精度表现比默认的 1.5 周期好得多。3.2 基于定时器触发 DMA 的双缓冲采样刚开始我图省事直接在 while 循环里调用 HAL_ADC_Start() 然后阻塞等待转换完成。用了一阵发现 CPU 占用高、时序不固定而且切换量程时可能采到半路的数据。后来改成这样定时器 2 产生更新事件触发 ADC 转换ADC 转换完成后通过 DMA 把数据搬运到内存DMA 传输完成中断里置一个标志位主循环检测到标志后处理数据。整个过程 CPU 只在 DMA 中断那一刻被打断一下。代码实现核心部分/* 定时器触发配置 */ htim2.Init.Period 1000 - 1; // 1kHz 触发频率 htim2.Init.Prescaler 72 - 1; // 1us 计数周期 HAL_TIM_Base_Start(htim2); /* ADC DMA 启动 */ HAL_ADC_Start_DMA(hadc1, (uint32_t *)adc_buf, ADC_SAMPLE_COUNT); /* DMA 传输完成中断回调 */ void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { if (hadc-Instance ADC1) { adc_data_ready 1; } }ADC_SAMPLE_COUNT 我设了 32也就是每次取 32 个样本做平均。注意这个数值不是随便定的采集 32 个样本的时间大约是 32 × 20μs 640μs对应刷新率约 1.5kHz足够快均值后波动率降了一个数量级。双缓冲没在这个项目里用原因是单 ADC 单通道场景下没有必要。如果你后面要同时测电压和电流用两个 ADC 通道我再建议你开 DMA 循环模式加双缓冲不然采样中间的空窗期无法处理。3.3 滤波算法的选择去掉毛刺不拖尾采集到的原始 ADC 值跳变很厉害是常态。我试过几种滤波方式效果差异挺大简单平均值对随机噪声有效但只要有瞬时尖峰平均值就会被拉偏。中值滤波能很好去掉尖峰但采样窗口大了有滞后。滑动平均 限幅先用限幅去掉超范围跳变再滑动平均平滑波形。实测下来效果最好。我的做法是每次 DMA 传来 32 个原始值先按大小排序取中间 16 个等同于把最高 25% 和最低 25% 丢弃再对这 16 个数求平均。这个方式既保留了平均值的平滑效果又把毛刺限制在外围响应速度也只损失一个采样周期。经验公式最终显示值 上一次显示值 × 0.7 本次滤波值 × 0.3。这个一阶低通式的软件滤波模拟了硬件 RC 的惯性显示跳变幅度明显下降但跟踪斜坡信号时不至于拖沓。3.4 电压换算与校准的实现ADC 原始值到真实电压的换算公式V_real (ADC_Value / 4095) × V_REF × 分压比V_REF 不能直接用 3.3V。板子上的稳压芯片如 AMS1117-3.3实际输出电压可能在 3.28~3.32V 之间波动直接把 3.3V 代入计算会产生固定偏差一个满量程下来差出几十毫伏。我用了一个简单可靠的校准方案第一步用一个精确的 3.3V 基准源或者一个刚充满电且稳定在 3.300V 的锂电池接到 ADC 引脚记录 ADC 值反推出实际的 V_REF V_REF_actual (ADC_measure / 4095) × 3.3第二步把 V_REF_actual 代入换算公式同时做一次两点标定。用可调电源分别输出 2.000V 和 20.00V读取显示值计算增益误差和零点漂移写入 Flash 存储。注意的是校准数据应该在系统启动时读出来而不是每次上电都校准一遍。可以把校准系数存到 STM32F103R6 的内部 Flash 的最后一个扇区主存和系统存储区之外的独立小扇区在 main 函数开头读回。4. 显示驱动与交互逻辑4.1 LCD1602 的底层驱动LCD1602 的驱动代码网上很多但很多代码都忽略了一个小细节初始化时序的延时。如果上电后立即初始化 LCD液晶模块内部还没就绪你知道会发生什么事吗LCD 直接白屏或花屏。解决方法是上电延时 40ms 以上再发初始化指令这条经验是从 12864 液晶上踩坑总结出来的。另外不要每帧都全量刷新 LCD。电压读数每秒刷新几回就够了更新太快人眼也反应不过来还会因为频繁操作 GPIO 引入噪声干扰 ADC 采样。我的刷新频率设在 4Hz也就是每 250ms 更新一次显示这样既不闪烁也把干扰降到最低。4.2 量程切换与低电压提示按键 PB3 按下时切换量程量程 10~3.3V直接采样不经过分压计算量程 20~30V启用分压比换算切换量程时有一件必须注意的事分压网络的响应不是瞬时的切换后需要等待 50ms 再开始采样留给外部 RC 网络一个稳定时间。如果切换后立即采样前几次读数值会明显偏离真实值原因就是 RC 滤波电容还没充到对应电压。低电压提示我实现得很简单当检测到电压低于 1.5V 时在 LCD 第二行显示 LOW 并背光闪烁。这个功能在测电池没电时特别实用。5. 常见问题与排查技巧实录5.1 ADC 数值偏低的根因分析电压表做完后你会发现一个奇怪的现象用万用表量 3.3V 是准的但量 15V 时总是偏低而且不是普通的线性偏差是分压后越低越偏。这个问题的根源在输入阻抗不匹配。前面提到 STM32F103 的 ADC 输入阻抗不是无限大虽然 RC 滤波加了 1KΩ 电阻但等效源阻抗还是不够低。当外部电压较高时通过分压电阻的电流在源阻抗上产生的压降就比较明显导致 ADC 引脚电压比理论值低。解决思路是降低分压网络的整体阻值或者用电压跟随器做隔离。最省事的办法是把 R136KΩ 改成 18KΩR22KΩ保持分压比不变但等效阻抗降低一半。当然功耗会翻倍对于市电场景完全没问题。5.2 显示值跳动的三个隐藏因素电源纹波ST 板子用 USB 或者充电器供电时输出纹波可能达到 100mV 以上直接叠加在被测电压上。解决用 LDO 给芯片供电模拟和数字部分分别滤波。参考引脚噪声VREF 要接一个 1μF 的去耦电容到地且尽量靠近芯片引脚否则参考电压抖动会让 ADC 结果跟着抖。软件刷新时间和采样时间重叠DMA 还在搬运的时候你已经在 main 循环里读了数据。解决用标志位判断DMA 完成中断置 1 后才允许处理。5.3 排查表格速查现象可能原因排查顺序显示数值固定不变ADC 时钟没使能 / 引脚复用没配置检查 CubeMX 初始化数值满量程 4095输入电压超过 VREF 或引脚悬空测量 ADC 引脚电压数值线性偏大分压电阻值偏差万用表测实际电阻并计算分压比数值跳动20mV电源纹波大 / 滤波电容缺失示波器看 VREF 和 VIN 波形切换量程后第一次读数为 0RC 稳定时间不够切换后延时 50ms 再采样5.4 校准顺序和工具准备要用到数字万用表至少 3.5 位和可调直流电源精度 1mV。如果手头没有可调电源用几节干电池串联凑出几个电压点也行但标定效果差一些。先调零点输入短路记录偏移值再调满量程输入标准电压调增益系数。最后验证中间点误差在 ±10mV 内收工。6. 一点实操经验分享这个项目看起来简单但真把它打磨到工业可用级别涉及的知识点并不少。ADC 采样不只是读寄存器模拟域的阻抗匹配、参考源稳定性、数字域的滤波策略、标定补偿任何一环不认真最终精度都会打折扣。我个人最大的体会是“从显示倒推硬件问题”这套排查方法。一开始显示不准我老在软件算法里找原因折腾半天也没效果。后来静下心拿示波器戳 ADC 引脚才看到波形上有高频毛刺加了一颗滤波电容直接解决。硬件工程师的经验就是这样一次次踩坑踩出来的。如果你做完了基础版建议往这几个方向扩展加一个 INA226 电流检测芯片做功率计或者用两个 ADC 通道同时采集电压电流再或者接个蓝牙模块把数据推送到手机。这些扩展都建立在同一个 ADC 采集与处理框架上代码架构不用大改成就感却翻倍。本文还有配套的精品资源点击获取
返回列表