news 2026/9/9 0:37:07

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于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 结果就会偏低;如果太小,分压电路自身的功耗又太大,对于电池供电场景不友好。

这里有个取舍。我的方案是:R1=30KΩ,R2=10KΩ,当 VIN=30V 时,分压到 ADC 引脚是 7.5V,还是超过了 3.3V,所以又加了一级 10K/4.7K 的细调网络,最终把满量程映射到 3.0V 左右,留了一点裕量。

综合计算下来,分压比约为 10:1,即外部电压 10V 对应 ADC 引脚 1V。具体计算过程不复杂:

V_ADC = V_IN × (R2∥R4) / (R1 + R2∥R4)

选 R1=36KΩ, R2=4KΩ 是最省事的。用 0.1% 精度的低温漂电阻,分压误差能控在 0.2% 以内,比普通 5% 电阻好很多。强烈建议分压电阻用精密电阻,一套 10 颗也就几块钱,但精度提升是实打实的。

2.2 ADC 输入端的 RC 滤波与阻抗匹配

很多教程只画一个分压电阻进 ADC,实际用起来会发现数值跳得厉害,尤其是测量开关电源输出时,纹波直接体现在 ADC 采样结果上。这时候需要在 ADC 引脚前加一阶 RC 低通滤波器。

我用的参数是 R=1KΩ,C=0.1μF,截止频率约 1.6kHz。这个滤波器有两个作用:一是滤掉高频噪声,二是降低源阻抗,让 ADC 内部的采样电容更快充满。要注意的是,R 不能太大,否则和 ADC 输入电容组成的分压效应会引入新的误差,1KΩ 是个平衡点。

还有一点容易被忽视:STM32F103 的 ADC 引脚和内部温度传感器、VREFINT 共用部分电路。如果你的板子上 PB0(ADC1_IN8)和 PC0(ADC1_IN10)同时使用,要确认没有复用冲突。我在第一版 PCB 上把按键接到了 PC0,结果 ADC 通道初始化后按键没反应,查了半天才发现引脚冲突。

2.3 LCD 显示模块的接线方案

显示模块我选的是 LCD1602,理由很简单:便宜、稳定、驱动资料多。如果你手上正好有 OLED SSD1306(I2C 接口),用起来更舒服,显示内容也更灵活。不过我在这篇博文里还是以 LCD1602 为例,因为它是新手最容易上手的。

接线方式采用 4 线数据模式(D4~D7),节省 4 个 GPIO。RS 接 PB0,RW 接 PB1(直接接地也行,只写不读),E 接 PB10,D4~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 按下时切换量程:

  • 量程 1(0~3.3V):直接采样,不经过分压计算
  • 量程 2(0~30V):启用分压比换算

切换量程时有一件必须注意的事:分压网络的响应不是瞬时的,切换后需要等待 50ms 再开始采样,留给外部 RC 网络一个稳定时间。如果切换后立即采样,前几次读数值会明显偏离真实值,原因就是 RC 滤波电容还没充到对应电压。

低电压提示我实现得很简单:当检测到电压低于 1.5V 时,在 LCD 第二行显示 "LOW" 并背光闪烁。这个功能在测电池没电时特别实用。

5. 常见问题与排查技巧实录

5.1 ADC 数值偏低的根因分析

电压表做完后你会发现一个奇怪的现象:用万用表量 3.3V 是准的,但量 15V 时总是偏低,而且不是普通的线性偏差,是分压后越低越偏。

这个问题的根源在输入阻抗不匹配。前面提到 STM32F103 的 ADC 输入阻抗不是无限大,虽然 RC 滤波加了 1KΩ 电阻,但等效源阻抗还是不够低。当外部电压较高时,通过分压电阻的电流在源阻抗上产生的压降就比较明显,导致 ADC 引脚电压比理论值低。

解决思路是降低分压网络的整体阻值,或者用电压跟随器做隔离。最省事的办法是把 R1=36KΩ 改成 18KΩ,R2=2KΩ,保持分压比不变但等效阻抗降低一半。当然,功耗会翻倍,对于市电场景完全没问题。

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 采集与处理框架上,代码架构不用大改,成就感却翻倍。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 0:35:46

VKS118静态驱动段码屏实战:从选型到调试全解析

做产品这几年,凡是涉及段码屏显示的项目,我几乎都会先问一句:这块屏是静态驱动还是动态驱动?很多人一开始不太在意这个区别,等到画板、调驱动、做EMC的时候才回头改,那就非常被动了。VKS118这类静态液晶显示…

作者头像 李华
网站建设 2026/9/9 0:32:42

鸟类检测数据集VOC+YOLO格式实操指南:训得稳、训得准的关键基建

简介:本资源为面向计算机视觉初学者与算法工程师的鸟类目标检测专用数据集,覆盖10种常见亚洲鸟类,适用于YOLO、Faster R-CNN等主流检测模型的训练与验证。数据集同时提供Pascal VOC格式(含16283个XML标注文件)与YOLO格…

作者头像 李华
网站建设 2026/9/9 0:30:57

Qt无边框窗口实现:自定义标题栏拖拽与拉伸完整指南

简介:面向需要实现无边框窗口自定义外观的 C Qt 开发者,本工程提供了一个基于 QMainWindow 的完整示例,解决无边框窗口无法拖拽移动、拉伸缩放以及缺少阴影圆角等常见问题。工程在 Win10/Win11 下运行,可呈现窗口阴影与圆角效果。…

作者头像 李华
网站建设 2026/9/9 0:28:26

多频外差解相位原理与C++实现:从包裹相位到绝对相位

简介:多频外差解相位C代码包面向光学干涉、声纳与水声、无线通信及射电天文等信号处理场景的工程开发者,解决从多个混合频率分量中提取待测相位信息的问题,适合具备C基础并希望动手实现该算法的学习者。压缩包内共3个文件,包含两个…

作者头像 李华
网站建设 2026/9/9 0:26:23

Python爬虫实战:网易云音乐歌单与评论数据采集全解析

简介:基于Java实现的网易云音乐数据抓取爬虫项目,面向正在学习网络爬虫与数据采集的初中级开发者,也适合作为数据分析场景下的数据获取参考。项目采用Maven标准结构,包含歌曲、评论等信息的抓取逻辑,演示了从页面请求、…

作者头像 李华
网站建设 2026/9/9 0:24:32

盒子维数与多重分形谱的MATLAB实现:原理、代码与实战

简介:多重分形谱算法与盒子维数计算是分形几何中分析复杂系统自相似结构的重要工具,这套基于Matlab编写的代码包面向需要量化研究多维数据特征的科研人员和工程技术人员,可直接用于数值实验与教学演示。压缩包内共包含2个m文件,分…

作者头像 李华