简介:一套完整的三路LCD数字电压表嵌入式工程包,基于STM32微控制器与LCD1602液晶模块设计,面向电子爱好者、嵌入式初学者及课程设计场景,解决多路模拟电压实时采集与屏显问题。包内共有183个文件,以C语言源码、H头文件、Keil工程配置、编译中间文件以及最终hex烧录文件为主,并包含STM32标准外设库驱动、启动链接脚本、工程备份等文件,压缩包仅2.95MB,可直接下载使用。已有386人学习使用。通过分析这套工程,可系统掌握ADC模拟量采集与通道配置方法,理解参考电压和采样时间对测量精度的影响,熟悉LCD1602字符显示驱动的初始化、写指令与写数据流程,并能综合运用定时刷新和数值换算完成数字电压表设计。工程源码结构清晰,既可烧录到开发板直接运行,也适合按模块拆解学习,是STM32入门和嵌入式仪表开发的实用参考项目。
1. 一块3位LCD数字电压表,才是嵌入式ADC和显示链路的完整练兵场
很多人一上来就追STM32F407加TFT屏做FFT频谱分析仪,结果卡在驱动、内存和电源纹波上。反过来,如果只想把“3 LCD数字电压表”做成能稳定读数、误差可控的仪表,反而要在ADC基准、分压网络、LCD引脚时序、数字滤波、校准这些最底层的细节上全部过一遍。这个项目看起来简单,但它同时在考验三件事:模拟前端把信号调理到ADC能吃的范围,固件把采样和滤波做准,以及LCD在有限引脚和时序约束下正确显示字符。对想从“点灯”跨到“仪器”的人来说,这是性价比最高的一站。带你的目标是:最终能用一块常见MCU加1602或者段码LCD,做一个量程0到30V、分辨率10mV、误差在±0.1V以内的表头,而且清楚每一个参数是怎么调出来的。
2. 从传感器到屏幕:数字电压表的测量链路与器件选型
把标题里的“3”先解释清楚:常见数字电压表头上的“3”指的是显示位数,也就是最大显示1999或者3999。一个3位半的数字电压表,显示范围是0.000到1.999,或者按量程扩展为0到19.99V。这里的核心不是LCD本身,而是“ADC分辨率 + 显示位数 + 量程”三者怎么匹配。
2.1 量程、分辨率与ADC位数的关系
假设你想做0到30V的电压表,分辨率20mV,那满量程30V除以20mV等于1500,需要至少2的11次方,也就是2048个码。这意味着10位ADC的理论分辨率只有30V/1024,约29.3mV,直接用还是差一点。
所以工程上的习惯是:把量程先分压到0到3.3V或者0到5V,然后再用ADC采样。常见的组合是:
- 8位ADC:分辨率256个码,适合0到25.6V、0.1V分辨率的应急表头。
- 10位ADC:常见于STM32F103、ESP32,理论1024个码,配合分压能做到30mV级别。
- 12位ADC:STM32F4、GD32F303这类芯片,4096个码,0到30V时分辨率约7.3mV,配合过采样还能再压一压噪声。
- 24位Σ-Δ ADC:ADS1232、HX711这类外部ADC,适合要测微伏级信号,但电压表量程大时前级分压的温漂会盖过ADC精度,反而没必要。
我的建议是:先用MCU内置12位ADC做第一版,跑通LCD显示和标定流程后,再决定要不要换成外部ADC。直接上24位ADC的人,最后大多会卡在校准系数和基准源的选择上。
2.2 LCD选型:段码屏、字符屏与TFT屏的取舍
“LCD数字电压表”的LCD不是单指一种屏。这里按驱动复杂度和成本排个顺序:
- 段码LCD(例如HTN/STN玻璃屏,驱动芯片如HT1621、TM1622):显示固定数字和单位,功耗极低,是万用表和仪表的主流。缺点是只能显示预设图形,不能自由画字符。
- 字符LCD(1602、2004):内置HD44780控制器,8条数据线加3条控制线,或者用I2C转接板变2根线。字数够用,非常适合显示“12.34V”。
- TFT LCD(SPI接口的ST7735、ILI9341):能显示中文、曲线和彩色UI,但驱动代码长,背光耗电,小尺寸下看数字反而没有字符屏清晰。
搜索热搜里常出现“lcd显示屏引脚介绍”和“40pin lcd接口”,那是针对RGB接口TFT屏的,和字符屏完全是两套玩法。做数字电压表,我一般会推荐先用1602字符屏或HT1621段码屏。1602最大好处是调试时能直接打印ADC原始值和换算后的电压,段码屏则在装进外壳后更接近商品仪表的手感。
2.3 主控选型:从51单片机到STM32的差异
主控决定ADC精度、低功耗能力和开发难度。以“基于stc89c52rc的数字电压表原理图”这类经典方案为例,STC89C52RC内置8位ADC需要外挂ADC0809,或者用PWM加RC滤波做积分式测量,代码写起来绕,精度也一般。
我更推荐用带12位ADC的MCU,例如STM32F103系列。理由有三个:
- ADC支持规则组扫描,可同时采多个通道;
- 内置温度传感器可做冷端补偿(虽然对电压表作用不大);
- 进入睡眠模式方便,适合电池供电的表头设计。
如果团队熟悉国产芯片,GD32E230、CH32V003这类也是好的替代。选型的底线是:ADC参考电压稳定,主频不要太低以至于LCD刷新都卡顿。1602 LCD在5V供电下用51单片机正好,STM32的3.3V逻辑则需要确认LCD模块是否有内置升压或5V兼容设计。
3. 硬件搭建:LCD接口、分压网络与参考电压设计
硬件上的功夫占整个项目一半以上。很多“数字电压表跳数”的问题,根源不是固件代码,而是模拟前端没做干净。这一节把LCD引脚、分压、基准、电源去耦四件事拆开讲。
3.1 LCD显示屏引脚定义与连接示例
以1602字符LCD为例,最常用的接口是16引脚,其中真正影响显示的引脚有这些:VSS、VDD、V0(对比度),RS、RW、E,以及D0到D7。4位模式可以只接D4到D7,省4个GPIO。常见连接见下:
| 引脚 | 功能 | 接法说明 |
|---|---|---|
| VSS | 电源地 | 接GND |
| VDD | 电源正 | 接5V或3.3V,不能接错 |
| V0 | 对比度 | 接10k电位器中间抽头,两端接VDD和GND |
| RS | 数据/命令选择 | 接MCU GPIO,0写命令,1写数据 |
| RW | 读/写 | 接地固定写模式,减少一根GPIO |
| E | 使能 | 高电平有效,下降沿锁存数据 |
| D4-D7 | 4位数据总线 | 接MCU GPIO,先高4位后低4位 |
| A/K | 背光正负 | 串联100欧电阻,或者直接接模块自带三极管控制 |
// 1602 LCD 4位模式初始化片段,基于STM32 HAL库 void LCD_Init(void) { HAL_Delay(50); // 等待LCD上电稳定 LCD_WriteNibble(0x03); // 第一次复位序列 HAL_Delay(5); LCD_WriteNibble(0x03); HAL_Delay(5); LCD_WriteNibble(0x03); HAL_Delay(5); LCD_WriteNibble(0x02); // 切换到4位模式 LCD_WriteCommand(0x28); // 4位数据线、2行、5x8字型 LCD_WriteCommand(0x0C); // 显示开、光标关 LCD_WriteCommand(0x06); // 写入后地址加1 LCD_Clear(); }注意4位模式初始化时,必须先向LCD发送三次0x03,再发0x02。如果跳过这一步,LCD会保持在8位模式,后续所有命令都会被错误解析。这也是新手把代码抄对后屏依然不显示的最常见原因。
3.2 前端分压电路与阻抗匹配
ADC引脚通常只能承受0到VREF,超过上限就可能烧引脚。测0到30V必须加分压电阻。最简单是:
Vin -- R1 -- ADC引脚 -- GND | R2分压比 = R2 / (R1 + R2)。当R1=100k、R2=10k时,分压比约0.091,30V输入得到约2.73V。这个选择有两个好处:输入阻抗110k,对被测电路影响小;电阻值常见,温漂场效应相对容易控制。
但分压电阻不是越精确越好,关键是比例稳定。使用1%精度电阻时,R1和R2各自偏差1%,分压比可能偏差约2%。这会让满量程读数偏差很大,所以必须靠软件校准修正,而不是换高精度电阻。
实际设计中还要在ADC引脚并联一个0.1uF电容,与分压电阻构成低通滤波,抑制高频噪声。电容值不宜过大,否则会影响采样建立时间。STM32的ADC采样时间配置为239.5周期、12位分辨率时,建议分压网络等效阻抗不超过10k。如果R1+R2过大,就要在分压点和ADC之间加一级运放跟随器,或者增加采样保持时间。
3.3 参考电压与电源去耦
ADC是数字电路里最像模拟电路的模块。如果参考电压VREF跟着电源波动,采样结果会直接反映出来。常见做法有:
- STM32内置VREFINT,可以校准通道,但精度有限;
- 外部基准芯片,如TL431、REF3030,给VREF引脚提供2.5V或3.0V稳定电压;
- 如果直接用3.3V供电做参考,确保电源芯片是低噪声LDO,不要用DC-DC直接给ADC供电。
去耦方面,每个电源引脚附近放0.1uF陶瓷电容是底线。模拟电源和数字电源如果MCU支持独立引脚,比如STM32的VDDA,要用磁珠和10uF电容隔离。我见过不少项目,LCD背光电流突变导致ADC读数波动,就是因为共用了电源路径。最简单的验证方法:测量时关闭背光,看读数是否变化,变化就说明去耦不足。
4. 软件实现:LCD驱动、ADC采样与数字滤波
硬件搭完后,软件才是把“3 LCD数字电压表”真正跑起来的环节。这里给出三个可直接抄的模块:LCD显示函数、ADC多次采样滤波、电压换算显示。每个模块都解释参数为什么这么定。
4.1 最小驱动:初始化、写命令、写数据
1602的软件驱动核心只有两件事:写命令和写数据。写命令用于设置显示模式、清屏、移动光标;写数据用于往当前地址填字符。
// LCD 写命令与写数据函数 void LCD_WriteCommand(uint8_t cmd) { LCD_RS_LOW(); // RS=0,选中指令寄存器 LCD_WriteNibble(cmd >> 4); // 先写高4位 LCD_WriteNibble(cmd & 0x0F); // 再写低4位 HAL_Delay(2); // 指令执行时间约1.5ms } void LCD_WriteData(uint8_t data) { LCD_RS_HIGH(); // RS=1,选中数据寄存器 LCD_WriteNibble(data >> 4); LCD_WriteNibble(data & 0x0F); HAL_Delay(2); } void LCD_WriteNibble(uint8_t nibble) { // 按位设置D4-D7,然后拉高E,延时,拉低E HAL_GPIO_WritePin(LCD_D4_GPIO_Port, LCD_D4_Pin, (nibble & 0x01)); HAL_GPIO_WritePin(LCD_D5_GPIO_Port, LCD_D5_Pin, (nibble & 0x02)); HAL_GPIO_WritePin(LCD_D6_GPIO_Port, LCD_D6_Pin, (nibble & 0x04)); HAL_GPIO_WritePin(LCD_D7_GPIO_Port, LCD_D7_Pin, (nibble & 0x08)); HAL_GPIO_WritePin(LCD_E_GPIO_Port, LCD_E_Pin, GPIO_PIN_SET); HAL_Delay(1); HAL_GPIO_WritePin(LCD_E_GPIO_Port, LCD_E_Pin, GPIO_PIN_RESET); }这段代码里的时序值得说清楚:E引脚在数据稳定之后拉高、再拉低,下降沿触发LCD内部锁存。先写高4位再写低4位,是4位模式的固定顺序。HAL_Delay(2) 在64MHz主频下实际延时约2毫秒,足够覆盖HD44780的最小指令周期。如果主频更高或改用RTOS,延时最好改用定时器计数,避免影响其他任务。
4.2 ADC采样与均值滤波代码
STM32的ADC单次采样直接读数据寄存器,但实际读数会因为电源噪声、参考电压波动而上下跳动。最常用的处理是先连续采样多次,去掉最大最小值,再取平均。
#define ADC_SAMPLE_COUNT 16 uint32_t ADC_GetStableValue(void) { uint32_t sum = 0; uint16_t min_val = 0xFFFF, max_val = 0, val; for (int i = 0; i < ADC_SAMPLE_COUNT; i++) { HAL_ADC_Start(&hadc1); HAL_ADC_PollForConversion(&hadc1, 10); val = HAL_ADC_GetValue(&hadc1); if (val < min_val) min_val = val; if (val > max_val) max_val = val; sum += val; } sum = sum - min_val - max_val; // 去掉最高和最低 return sum / (ADC_SAMPLE_COUNT - 2); // 再求平均 }参数说明:采样16次是因为ADC噪声通常是白噪声,均值滤波可以将标准差降低到原来的约1/4(根号下少2个样本)。去掉最大最小值能抑制偶发的脉冲干扰,比如LCD刷新瞬间的电源毛刺。如果被测信号本身变化快,比如要测PWM波形的电压,这种滤波方式会把真实波动也抹平,此时应该提高采样率并用滑动窗口。
4.3 电压换算与小数点显示
ADC值转换成电压值,不能简单按“ADC值 / 4096 * 3.3V”算,因为参考电压实际值、分压电阻比例都存在偏差。正确做法是先按理论值算一个初始结果,再通过校准系数修正。
#define VREF 3.30f // 实际测量的参考电压 #define DIVIDER_RATIO 11.0f // (R1+R2)/R2 的理论值 #define CAL_GAIN 1.004f // 校准增益系数 #define CAL_OFFSET 0.02f // 校准零偏,单位V float ConvertADCToVoltage(uint32_t adc_value) { float v_adc = (float)adc_value / 4096.0f * VREF; float v_in = v_adc * DIVIDER_RATIO; return v_in * CAL_GAIN + CAL_OFFSET; } void LCD_DisplayVoltage(float voltage) { char buf[16]; if (voltage < 0) voltage = 0; snprintf(buf, sizeof(buf), "%5.2fV", voltage); LCD_SetCursor(0, 0); // 第0行第0列 LCD_PrintString(buf); }换算逻辑分成三步:ADC原始值转换成引脚电压,再乘以分压比还原成输入电压,最后做增益和偏移校准。注意DIVIDER_RATIO=11.0表示(R1+R2)/R2 = 110k/10k,这个值每个板子都要按实际电阻计算,不能照抄。
显示格式%5.2f的5表示总宽度,2表示两位小数。如果电压超过99.99V,字符串会撑开,1602一行16个字符不够放,所以量程设计时最好把最大显示控制在99.99V以内。
5. 校准、排错与低功耗优化:把测量结果变成可信数据
大部分数字电压表做到能显示,精度却一塌糊涂。差别往往不在硬件元件贵不贵,而在有没有做校准和环境补偿。最后一章讲几个可以直接落地的技巧。
5.1 用两点校准消除分压电阻和参考电压误差
最实用的校准方法是用万用表作为标准源:给电压表输入两个已知电压V1和V2,读取当前显示值D1和D2,然后计算增益和偏移。
校准公式:
gain = (V2 - V1) / (D2 - D1) offset = V1 - D1 * gain实际代码里可以在Flash中保存校准系数,每次上电读取。具体步骤如下:
- 输入接近0.5V作为V1,记录显示值D1;
- 输入接近30V作为V2,记录显示值D2;
- 计算gain和offset,写回Flash;
- 重新采样一次,确认显示值和万用表偏差在±0.1V内。
校准要找精度高于待校表至少一个数量级的源。普通万用表3位半精度,校准一个3位半电压表只能保证“相对值一致”,绝对值仍可能偏差。要求高时可以用稳压电源加精密电阻分压作为参考源。
除了两点校准,还要注意温度影响。分压电阻温漂50ppm/°C时,30V量程在温差30°C的情况下会漂移45mV,几乎到10mV分辨率的边缘。如果要提高温漂表现,选择低温漂电阻是最直接的办法,其次才是软件做温度补偿。
5.2 常见显示异常与排查思路
显示问题通常集中在LCD上,但根因不一定在LCD驱动。整理一套排查顺序:
| 现象 | 优先检查 | 背后的原因 |
|---|---|---|
| 屏幕全黑无反应 | V0对比度电压、VDD电压 | 对比度太低或电源未到位 |
| 只显示一排方块 | 初始化时序,E延时 | 4位模式下复位序列不完整 |
| 数字乱跳 | ADC去耦、分压电容、刷新频率 | 高频噪声叠加,滤波不足 |
| 某位固定不亮 | 对应数据线GPIO配置 | 初始化时该引脚被复用或配置错误 |
| 首字符花屏 | 清屏前是否有延时 | LCD上电未稳定就执行清屏指令 |
排查LCD最笨也最有效的方法是写一个测试程序,固定显示“8.88V”,看屏幕能否稳定显示。如果能,说明LCD驱动正常,问题在ADC采样链路;如果不能,就要回过来检查GPIO、时序和对比度。
如果是ADC读数跳,建议先用串口打印原始ADC值,而不是盯着LCD看。LCD刷新本身会产生电磁干扰,串口数据比视觉判断更准确。打印出来的值如果跳动量小于3个LSB,在12位ADC下对应约2.4mV,属于正常范围。
5.3 用低功耗和看门狗让表头“跑得久、不会死”
桌面电压表插USB供电无所谓,但做成便携式或嵌入式设备时,功耗就很重要。1602背光加MCU全速跑,电流可能超过20mA。低功耗优化的常见做法:
- 降低采样频率,从每秒10次降到每秒1次;
- 两次采样之间让MCU进入睡眠模式,用定时器唤醒;
- 关闭LCD背光或降低对比度,需要读数时按键唤醒;
- 使用段码LCD替代1602,功耗可以从mA级降到uA级。
void LowPowerLoop(void) { while (1) { adc_val = ADC_GetStableValue(); LCD_DisplayVoltage(ConvertADCToVoltage(adc_val)); HAL_Delay(500); // 刷新间隔500ms HAL_SuspendTick(); HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI); HAL_ResumeTick(); } }这个循环里,WFI指令让CPU挂起,直到定时器中断唤醒。配合看门狗复位时,要注意喂狗要在唤醒后、进入睡眠前完成,否则睡眠期间看门狗可能溢出重启。还有一点,ADC采样在睡眠恢复后会立刻执行,这时电源还没完全稳定,所以我在LCD显示后先延时500ms再进睡眠,让读数有一段时间稳定。
把这一整套链路走完,你会发现3位LCD数字电压表的价值不在那个显示数字,而在背后的模拟、时序、滤波、校准和功耗控制。把这些边界摸清楚,再去碰更高精度的数据采集或更复杂的仪表设计,心里就有底了。
本文还有配套的精品资源,点击获取