ARTICLE DETAIL

资讯详情

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

三位半LCD数字电压表设计:ADC选型、校准与显示驱动

三位半LCD数字电压表设计:ADC选型、校准与显示驱动 简介一个基于STM32与LCD1602的三路LCD数字电压表嵌入式工程面向电子爱好者、嵌入式初学者及相关专业学生用于实现多通道模拟电压采集、ADC转换与液晶实时显示可灵活调节AD通道以适配不同量程与精度。压缩包共183个文件、约2.95MB除.c/.h源程序外还包含.o/.d/.crf等编译中间文件、UV4工程配置文件、hex烧录镜像与axf调试文件便于直接编译、下载与仿真。目前已吸引386人学习浏览工程结构清晰可帮助读者系统理解ADC工作原理、LCD1602时序控制以及STM32外设初始化流程。通过阅读源码与对照调试文件还可掌握Keil工程组织、编译链接细节和常见排错思路适合作为课程设计或入门项目参考。1. 三位半 LCD 数字电压表的难点不在显示而在稳定把「3 LCD 数字电压表」这个标题拆开看3 指的是三位半3 1/2 digit最大显示 ±1999最高位只出现 0 或 1LCD 是显示载体嵌入式是落地形态。这套东西在课程设计和量产仪表里都常见但两者的评价标准完全不同。课程设计版的常见搭配是 STC89C52 加 ADC0809只要数码管能走出数字就算交差量产版要盯的是最后一位数字跳不跳、温度变了漂不漂、换挡之后回不回得来。所以这篇文章不打算把「3 LCD 数字电压表」当单片机入门题讲而是按「显示位数、ADC 有效位数、前端分压、基准源、采样滤波、校准系数」这条完整链路展开先理清三位半对 ADC 的真实要求再给出一套 STM32 或国产 51 内核驱动 LCD1602 / 段码 LCD 的可复现方案最后落到校准和验证方法上。适合两类人一类是在嵌入式开发里第一次碰模拟前端另一类是做过多年 MCU 但被「显示出来了精度却不对」卡过。2. 三位半数字电压表的精度门槛从显示位数反推 ADC 选型2.1 三位半的含义与有效分辨率三位半数字电压表显示范围 0 到 1999满量程通常是 2000 个计数。2V 档分辨率 1mV20V 档分辨率 10mV200V 档分辨率 100mV。很多人直接拿「满量程 / 1999」当精度用这是第一个误区显示位数只决定读数能分辨到多细真正的误差由 ADC 的线性度、基准源温漂、分压电阻比例和接触电阻共同决定。以 2V 档为例目标精度 ±(0.5% 1d) 时允许误差是 2V × 0.5% 1mV 11mV约 11 个 LSB按 1mV 一个字算。目标精度提到 ±(0.1% 1d) 时允许误差只有 3mV也就是 3 个字。这就是选型的分水岭0.5% 精度级用 MCU 内置 12 位 ADC 就能做0.1% 级必须上 16 位外部 ADC 或 24 位 Σ-Δ并配合校准。显示位数满量程计数对应分辨率目标精度ADC 建议3 1/2 位19992V 档 1mV±0.5% 1d12 位内置校准后3 1/2 位19992V 档 1mV±0.1% 1d16 位 ADS1115 / MCP34214 1/2 位199992V 档 0.1mV±0.05% 2d24 位 Σ-Δ 精密基准5 1/2 位1999992V 档 0.01mV±0.01% 5d24 位 Σ-Δ 计量级基准需要说明的是内置 12 位 ADC 在 3.3V 基准下每 LSB 约 0.8mV显示分辨率 1mV 时还有约 1.2LSB 的余量但余量只够「读出这个数」不保证「这个数是对的」。所以只要目标不是玩具级别外置基准和校准系数不能省。2.2 前端信号链路分压、跟随与滤波常见做法是三个量程共用一个输入网络2V 档直接进运放20V 和 200V 档先经分压器衰减。分压电阻按 10MΩ 输入阻抗设计20V 档用 9MΩ 1MΩ200V 档用 9.9MΩ 0.1MΩ分压比的精度直接影响读数的比例误差电阻至少要 0.1% 精度金属膜温漂选 ±25ppm/°C 以内。如果手头只有 1% 电阻不要直接焊上去后面校准阶段用软件系数把分压比的偏差吃掉这也是嵌入式仪表常见的「软件修硬件」做法。运放跟随的作用是隔离分压网络的高阻抗和 ADC 的采样电容。STM32 内置 ADC 的采样电容只有几 pF但输入阻抗依然不够高用一片国产轨到轨运放如 GS8552、SGM8521做电压跟随输入偏置电流在 pA 级对 10MΩ 分压网络几乎不产生额外压降。运放输出到 ADC 引脚之间串一个 RC 低通R 取 1kΩ、C 取 100nF 时截止频率约 1.6kHz能压掉大部分开关电源纹波和 LCD 刷新耦合进来的毛刺如果想对 50Hz 工频有更强抑制把 R 加到 10kΩ、C 加到 1μF截止频率约 16Hz代价是响应变慢上电后要等两三秒读数才稳定。输入保护是很多人漏掉的一环。档位切错、探针碰到高压时分压电阻后面的运放和 ADC 引脚直接暴露在过压之下。标准做法是在运放输入端并联两个二极管到电源轨或用 BAV99 钳位再串一个 10kΩ 限流电阻让二极管能吃掉冲击电流。2.3 基准源与供电为什么不能直接用 VDDA 当参考MCU 的 VDDA 来自 LDO 输出LDO 的温漂、负载调整率以及数字电路开关带来的纹波都会直接折算成读数的比例误差。内部基准同样不建议用STM32F1 的内部带隙基准VREFINT只有约 1.2V而且校准系数在出厂时只保证 ±几 mV 的水平作为电压表参考远不够。推荐在模拟电源轨上放一颗专用基准REF30333.3V 输出温漂 20ppm/°C或者国产的 TL431 加精密分压网络。这里要特别注意基准的噪声和输出能力REF3033 输出电流只有几十 mA只能给 ADC 的 VREF 引脚供电不能同时给 LCD 背光和运放供电。运放供电直接从模拟 LDO 取背光驱动另走数字电源避免大电流走线串进基准回路。提示ADC 的 VREF 引脚上并一个 1μF 陶瓷电容和 0.1μF 高频电容位置尽量靠近引脚基准芯片的输出电容按数据手册推荐值来不要盲目加大否则会引入环路不稳定表现为读数在最后一位上来回跳。3. LCD 显示方案选型与嵌入式驱动从 LCD1602 引脚到 TM1622 段码屏3.1 三类 LCD 显示方案对比「LCD 数字电压表」的显示方案有三种常见路线LCD1602 字符屏、段码 LCD 加驱动芯片、SPI/I2C 接口的 OLED 或 IPS 彩屏。三者对应不同的产品定位功耗、成本、开发难度差别很大。LCD1602 是课程设计和快速原型最常见的载体内置 HD44780 控制器4 位总线模式只占 6 个 IO带背光和对比度调节段码 LCD 是万用表、温控仪这类电池供电设备的首选自身不发光功耗在微安级但必须配 HT1621、TM1622 这类专用驱动芯片OLED 和 IPS 屏接口简单、显示内容自由适合做波形或调试信息展示但功耗和成本最高。显示方案接口功耗成本适用场景LCD1602并口 4/8 位背光约 20mA低快速原型、学习板段码 LCD HT1621/TM1622SPI 或三线串行液晶 μA 级低手持仪表、电池设备OLED SSD1306I2C/SPI10~30mA中调试面板、富文本显示实际开发中我一般这么定做样机验证逻辑用 LCD1602因为初始化简单、字符显示不需要做字库做量产前的手持表形态换成段码 LCD提前把 COM 和 SEG 映射关系定下来PCB 和玻璃液晶需要一起设计。LCD1602 和段码屏之间不是代码替换关系而是整个显示架构的切换所以下面两小节分开给驱动思路。3.2 LCD1602 四线驱动初始化时序、引脚与背光亮度LCD1602 的引脚功能有固定约定1、2 脚是电源3 脚 VL 是液晶对比度调节4 脚 RS 选寄存器0 指令、1 数据5 脚 RW 读写选择6 脚 E 是使能脉冲7~14 脚是 D0~D7 数据线15、16 脚是背光正负极。四线模式下只用 D4~D7初始化必须按 HD44780 规定的一套序列来很多初学者直接在 4 位模式下发 0x28结果屏上全是乱码原因就是缺少从 8 位模式切换回 4 位模式的引导过程。// 四线模式初始化必须先通过三次 0x03 让控制器回到 8 位模式 void lcd_init(void) { GPIO_InitTypeDef gpio {0}; // RSPA0, EPA1, D4~D7PA2~PA5 // ... GPIO 初始化代码 delay_ms(50); // 上电等待HD44780 要求 40ms lcd_nibble(0x03); // 第一次发 0x038 位模式 delay_us(5000); // 等待 4.5ms lcd_nibble(0x03); // 第二次发 0x03 delay_us(200); // 等待 150us lcd_nibble(0x03); // 第三次发 0x03 lcd_nibble(0x02); // 关键切换0x02 表示进入 4 位模式 lcd_cmd(0x28); // 4 位总线、2 行显示、5x8 点阵 lcd_cmd(0x0C); // 显示开、光标关、不闪烁 lcd_cmd(0x06); // 写入后地址自动加 1整体不移动 lcd_cmd(0x01); // 清屏等待 1.52ms }lcd_nibble 和 lcd_cmd 的核心是 E 脉冲时序先把 RS 和 D4~D7 数据放好E 拉高保持至少 450ns再拉低控制器在 E 下降沿锁存数据。HD44780 要求 E 高电平时间最小 450nsSTM32 在 72MHz 下一条 NOP 约 13ns所以写进时序里的延时不能用几条空语句糊弄建议用定时器或软件延时库且对照数据手册确认。背光亮度控制有两种方式直接串电阻限流或者用 PWM 调光。PWM 调光时注意背光 LED 的驱动电流是几十 mA不能直接用 GPIO 灌入需要三极管或 P-MOS 管做开关PWM 频率选 1kHz 以上低于 500Hz 会看到明显闪烁。液晶的对比度是调 VL 引脚的电压这个电压不能用 PWM 调VL 脚的等效电容会让 PWM 波形积分成直流反而可用这一点做个简易的软件对比度调节。3.3 段码 LCD 驱动HT1621 与 TM1622 的寄存器兼容坑段码 LCD 的嵌入式驱动思路和字符屏完全不同液晶玻璃上没有现成的字符库所有数字和单位都是 COM/SEG 交叉点上的像素。驱动芯片的作用是把 MCU 通过串行接口发来的显示数据锁存再输出多路 COM 扫描和多路 SEG 波形。国产芯片里 HT1621 和 TM1622 用得最多两者都支持 1/3 偏置、4 COM 模式但寄存器映射和写命令格式有差异。// TM1622 写入 3 位命令 9 位数据HT1621 是 3 位 ID 6 位命令注意区分 void tm1622_write_cmd(uint8_t cmd) { cs_low(); for (int i 0; i 3; i) { // 前 3 位命令 ID101 clk_pulse((cmd 5) 1); // 实际从高位开始逐位移出 } // 第 4 位为 0 表示写命令 for (int i 0; i 8; i) { clk_pulse((cmd (7 - i)) 1); // 8 位命令数据 } cs_high(); }写这段代码时最容易踩的坑是把 TM1622 的指令系统当成 HT1621 直接抄。HT1621 的命令是 3 位 ID 加 6 位数据TM1622 是 3 位 ID 加 9 位数据system enable、bias setting、display on 这些命令的位位置都不完全一样。替换芯片时不能用「引脚兼容」想当然必须逐位核对数据手册量产设计时建议把驱动层封装成 lcd_display_set(seg, com, on) 这样的抽象接口底层是 HT1621 还是 TM1622 只影响最下面一层上层显示逻辑完全不动。亮度在段码屏上通常不是调 PWM而是调 LCD 的偏置电压V0/VLCD。驱动芯片内部有偏置电阻产生 1/3 偏置电压外部 VLCD 引脚串联一个可调电阻到电源调整对比度。这个电阻的取值和液晶玻璃的驱动电压有关3V 液晶和 5V 液晶阻值范围差很多参考驱动芯片数据手册里的公式算一遍再选型。4. 采样滤波与校准系数让末位跳动停下来4.1 工频抑制与去极值平均数字电压表显示的不是瞬时值而是一个时间段内的统计量。ADC 采到的原始数据包含工频感应、开关电源纹波、运放噪声和偶发毛刺。常见的做法是把 20ms 内的采样点做平均20ms 是 50Hz 工频的整数倍周期平均后工频分量被大幅抑制如果系统在 60Hz 地区使用窗口就要取 16.67ms 或它的整数倍。#define SAMPLE_N 32 // 32 点去极值平均去掉最大最小值避免偶发毛刺拉偏均值 uint32_t adc_sample_filter(void) { uint32_t sum 0; uint16_t buf[SAMPLE_N]; uint16_t minv 4095, maxv 0; for (int i 0; i SAMPLE_N; i) { buf[i] adc_read_once(); // 每次转换约 12us32 次约 400us if (buf[i] minv) minv buf[i]; if (buf[i] maxv) maxv buf[i]; sum buf[i]; } sum - minv maxv; // 去掉一个最大一个最小 return sum / (SAMPLE_N - 2); }这个滤波策略分两层理解去掉极值是防偶发干扰比如 LCD 刷新瞬间数字电路窜进来的毛刺平均是压低平稳噪声等效于给测量加了一个低通滤波。32 个点去 2 个极值后剩 30 个点参与平均如果噪声是白噪声均值的标准差降为原来的 1/√30约 5.5 倍。采样点数再往上加改善幅度变缓但响应时间变长电压表每秒刷新 10 次的情况下每个显示周期做 32 点采样已经够用。提示ADC 采样窗口要避开 LCD 的每帧刷新时刻。做 LCD1602 清屏或写大字符串时数据总线翻转产生的数字噪声会耦合到模拟回路表现为读数周期性跳动。一个简单办法是「先更新显示再采样再算数值」把采样放到显示指令完全结束之后。4.2 两点校准与三点校准系数存 Flash 而非硬编码任何 ADC 都有失调和增益误差校准的目标就是用软件把实测曲线修正到理想直线。两点校准是最小可用方案输入 0V 记录零点码值输入一个精确参考电压记录满度码值然后按 y (x - zero) × (ref - zero) / (code_ref - zero) 计算出电压。这里的零点和满度码值就是校准系数 K 和 B存在 MCU 的 Flash 或 EEPROM 里不能写死在源代码中否则换一台设备就得改代码重新编译。typedef struct { float gain; // 增益系数单位 V/LSB float offset; // 零点偏移单位 V uint16_t magic; // 标记 0xA5A5校验数据有效性 } cal_param_t; // 校准后电压 adc_code * gain offset float calc_voltage(uint16_t code) { return ((float)code) * cal.gain cal.offset; }三点校准比两点校准多覆盖一个中间点把 ADC 的积分非线性拉直。做法是利用高精度电压源依次加 0V、1/2 满度、满度三个值记录对应码值后续读数时先查表找到当前码值落在哪个区间再用区间两端做线性插值。五点左右的分段线性校准可以覆盖大多数 12 位 ADC 的线性度短板代价是 Flash 里多存几张表。注意校准系数表的写入次数校准触发不要做成每次上电都写只在进入校准模式并确认数据变化后才擦写一次否则 Flash 寿命会被快速耗尽。4.3 零漂、温漂与热电势三位半最容易忽略的三个坑零点漂移指的是无输入信号时读数不为零。来源包括运放的输入失调电压温漂、ADC 的失调误差以及 PCB 上的热电势。前两者可以通过软件校准掉但校准时的温度和使用时的温度不同就会产生残余零漂。工程上常用「开机自校准」缓解上电后把输入继电器切换到内部短接状态采样一次零点码值替换掉校准表里的 offset再开始正常测量。热电势是低量程200mV、2V 档精度上不去的隐形杀手。PCB 铜箔、焊盘、引脚和探针端子由不同金属材料连接两边存在温度差时会产生微伏级电压塞贝克系数大约每摄氏度几到几十微伏。对 2V 档来说微伏级热电势几乎无影响但做 200mV 档时焊点上差 1°C 就可能产生 1~2 个字按 0.1mV/字计算的误差。缓解方法是让输入回路的焊点尽量等温用大面积铜箔把两个输入端子连起来或者在结构上让探头端子靠近减小温度梯度。这个细节在绝大多数课程设计里没人讲但量产的万用表电路里都会体现。5. 精度验证与自动量程从「能显示」到「敢读数」5.1 用固定电压基准验证精度三组测试定位误差来源校准和验证是两回事校准是修正验证是确认修正后的误差在允许范围内。最省钱的办法是买一颗 AD584 或国产电压基准模块输出 2.5V、5V、7.5V、10V 四档想要更细的验证点用六位半台式万用表作为标准表配合可调直流电源从 0V 到满量程扫描。按照下面三组测试跑一遍基本能把误差来源定位清楚。测试项方法判定标准失败时优先排查零点短接测试输入端短接记录 1 分钟读数最后一位跳动不超过 ±1 个字运放失调、PCB 热电势线性度扫描输入 0/25/50/75/100% 满量程各点误差在 ±(0.1%1d) 内分压电阻比例、ADC 非线性电源变化测试供电从额定值 ±10% 变化读数变化小于 0.05%基准源抑制比、LDO 稳压能力零点短接测试时用铜导线直接短接输入端不要用夹子夹两根独立的表笔线那样等于在一个回路里引入两个不同金属的接触点热电势反而把零点顶起来。线性度扫描时每次改变输入后等 2 秒再读数给前端 RC 滤波和软件去极值平均一个稳定时间如果中间点误差明显大于两端说明 ADC 的 DNL 偏大优先考虑外部高位数 ADC而不是继续堆滤波。5.2 自动量程切换与滞回控制手持电压表没有档位旋钮时就要做自动量程。切换逻辑的核心是滞回升档和降档使用不同阈值防止读数在档位边界反复横跳。常见的做法是用量程上限的 90% 作为升档点用上限的 10% 作为降档点。比如当前 20V 档读数超过 18V 升到 200V 档低于 2V 降到 2V 档。如果升档和降档都用同一个阈值10.01V 的输入会在 10V 档和 100V 档之间来回切换LCD 上的小数点闪个没完。换挡瞬间还要处理一个问题分压电阻网络的建立时间。9.9MΩ 和 100kΩ 的电阻网络等效阻抗很高加上后端滤波电容后时间常数可能到几十毫秒切完档位不能马上采样要等 50~100ms 再做第一次读数。软件里可以加一个 ch_range_change_pending 标记换挡后先把显示清掉或显示「---」等采样稳定后再刷新数值避免显示一个明显错误的中间态。5.3 把电压表变成采集节点串口输出与记录做嵌入式开发时一个只能看不能存的数字电压表价值有限。把读数通过串口输出接上位机或记录仪就变成简易数据采集系统。输出格式用纯文本 TSV 最省事Excel 或 Python 脚本都能直接解析// 主循环每秒输出 10 次逗号分隔时间戳和电压值 printf(t%lu ms, V%.3f V\r\n, millis(), calc_voltage(adc_sample_filter()));printf 在嵌入式中占用不少资源重定向到串口后注意每个字符的发送耗时115200 波特率下每字符约 87us一帧 30 字符要 2.6ms对 10Hz 刷新率毫无压力。如果后续要接 ESP32 做无线数据上传建议在 MCU 端只做采样和格式封装把串口数据通过 ESP32 的 UART 透传或 AT 指令转发这样电压表本身不依赖网络断电单机也能用。这正好和嵌入式学习路线里「裸机显示 → ADC 采集 → 通信协议 → 无线组网」的成长路径衔接起来。# 3 LCD数字电压表_电压表_LCD_嵌入式数字电压表_1. 三位半LCD数字电压表从档位到末位「3 LCD数字电压表」这个标题拆开看3 代表三位半3 1/2 digit也就是最大显示 ±1999LCD 是显示载体嵌入式是它的实现形态。很多人第一次做这个项目是在 51 单片机课上用 STC89C52 加 ADC0809 加 LCD1602焊一块洞洞板把电位器的分压读出来显示成电压。那套东西能跑但离「数字电压表」这四个字还有一段距离。真正的数字电压表要考虑量程切换、输入阻抗、基准源温漂、工频干扰、校准系数存哪、末位跳动怎么压。这不是一个入门级小制作而是一个典型的嵌入式数据采集系统——ADC 选型、信号调理、显示驱动、标定算法全都涉及。这篇文章按工程落地的顺序把这套方案拆开讲前面先解决「精度从哪来」的硬件问题中间把 LCD 驱动写透后面给出采样滤波与校准的完整代码思路。适合已经在做嵌入式但第一次碰模拟前端的开发者也适合想把手上的课设方案从「能显示」做到「大概准」的人。2. 三位半精度不等于ADC位数先把硬件链路理顺2.1 三位半的计数范围与有效分辨率三位半数字电压表的核心指标是计数范围。满量程 1999意味着 2V 档分辨率是 1mV20V 档是 10mV200V 档是 100mV。这个分辨率是由显示位数决定的但它只是「最小读数变化」不等于测量精度。举个具体例子12 位 ADC 满量程 4096理论上能分辨 3.3V/4096≈0.8mV看起来够用但 ADC 的积分非线性INL、增益误差、基准源温漂叠加起来末位跳动 2~3 个字很正常。所以精度要看整个链路的误差预算不能只看 ADC 位数。嵌入式数字电压表常见目标是做到 ±(0.5% 1d) 或 ±(0.1% 1d) 的精度等级。0.5% 精度对 2V 档是 ±10mV 加 1 个字12 位内置 ADC 配合校准完全能做0.1% 精度是 ±2mV 加 1 个字12 位 ADC 的 INL 已经把预算耗掉大半这时候需要外部 16 位 ADC或者用内置 ADC 做多点分段校准。选型时有两条路STC89C52 这类 51 内核没有内置 ADC经典做法是外挂 ADC0809但 8 位分辨率只有 256 步做三位半显示非常勉强STM32F103 这类 Cortex-M 内核自带 12 位 ADC采样保持外部加一级运放跟随是最省事的方案。2.2 量程电阻网络与输入阻抗从 2V 到 200V 靠分压数字电压表必须有多个量程或者至少把输入范围做得够宽。常见做法是 10MΩ 输入阻抗通过电阻分压网络把 200V 缩小到 ADC 可接受的范围。以 STM32 为例ADC 输入范围 0~3.3V2V 档用 1MΩ 1MΩ 分压20V 档用 9MΩ 1MΩ200V 档用 99MΩ 1MΩ。电阻精度直接影响读数比例误差分压电阻用 0.1% 精度金属膜电阻温漂选 ±25ppm/°C 以内否则温度一变读数就飘。分压网络后面要加一级电压跟随器。因为 ADC 采样电容在采样瞬间会抽取电流直接接高阻分压网络会导致采样值偏低用运放做缓冲后输入阻抗由电阻网络决定输出阻抗被运放压到很低。运放选型看三点输入偏置电流要小、轨到轨输出、带宽不必高1MHz 级足够。选型表格供参考方案ADC 位数精度目标成本适用场景STC89C52 ADC08098 位±1% 1d最低51 入门课设STM32F103 内置 ADC12 位±0.5% 1d低通用嵌入式仪表STM32 ADS1115 外部 ADC16 位±0.1% 1d中高精度便携表STM32 24 位 Σ-Δ ADC24 位±0.05% 1d较高台式精密表分压电阻的阻值精度可以做在硬件上也可以留给软件校准。硬件的坑在于高阻值电阻噪声大99MΩ 分压到 3.3V 时源阻抗接近 100MΩ必须在分压后立即接运放不能直接拉进 ADC 引脚。软件坑在于分压比误差是固定比例误差校准系数能吃掉大半。2.3 基准源VREF 飘多少读数就飘多少ADC 的参考电压直接决定转换结果。STM32 的 VDDA 如果直接当 VREF电源纹波和 LDO 温漂会无保留地进到测量结果里。所以正经做法是给 ADC 单独配一颗基准芯片。三位半级别用 TL431 就够但 TL431 的初始精度和温漂一般最好选 REF3033 这类 3.3V 固定输出基准温漂 20~30ppm/°C。如果需要 5V 参考配合 5V 系统REF195 或 ADR4550 也是常见选择。基准源布局要单独处理基准输出引脚靠近 ADC 的 VREF中间尽量短模拟地和数字地在 ADC 引脚附近单点连接基准芯片下方不要走数字信号线。实测中电源纹波对读数的影响通常在 1~3 个 LSB如果你发现某一档读数稳定但换另一档偏差变大先量 VREF 而不是怀疑 ADC。提示不要在 VREF 上并联大电容。基准芯片的输出电容按数据手册选通常 1~10μF过大可能导致环路振荡表现为末两位数不规则跳变。3. LCD显示驱动LCD1602到HT1621的嵌入式写法3.1 LCD1602 四线驱动与引脚功能LCD1602 是最常见的字符型液晶16 列 2 行内置 HD44780 控制器。接口分 8 位和 4 位两种模式嵌入式里为了省引脚几乎都用 4 位模式D4~D7 传数据RS 区分指令/数据RW 控制读写E 是使能脉冲。这里有个初学者常踩的坑4 位模式下初始化必须先把控制器切回 8 位模式再切回 4 位否则后续所有命令都错位。引脚功能要记清楚1 脚 VSS 接地2 脚 VDD 接 5V3 脚 VO 是对比度调节通常接一个 10kΩ 电位器到地4 脚 RS5 脚 RW6 脚 E7~14 是 D0~D715 脚背光正极16 脚背光负极。背光电流约 20~40mA不能直接怼在 GPIO 上要串联限流电阻或用一个三极管开关控制。背光亮度可以用 PWM 调但注意 PWM 频率要高于 1kHz否则人眼能看到闪烁这个需求在嵌入手持设备里很常见。LCD 的读写是异步时序E 引脚每来一个下降沿控制器锁存当前数据引脚上的电平。写命令前要把 RS 拉低写数据前要把 RS 拉高RW 拉低表示写。4 线模式初始化序列必须严格按数据手册走下面是基于 STM32 的参考实现// 4位模式初始化RSPA0 RWPA1 ENPA2 D4~D7PA4~PA7 static void lcd_nibble(uint8_t nibble, uint8_t rs) { GPIO_WriteBit(GPIOA, GPIO_Pin_0, rs ? Bit_SET : Bit_RESET); // RS GPIO_WriteBit(GPIOA, GPIO_Pin_1, Bit_RESET); // RW 写 GPIO_Write(GPIOA, // D4~D7 同时输出 (GPIO_ReadOutputData(GPIOA) 0xFF0F) | (nibble 4)); GPIO_WriteBit(GPIOA, GPIO_Pin_2, Bit_SET); // E 拉高 delay_us(1); GPIO_WriteBit(GPIOA, GPIO_Pin_2, Bit_RESET); // E 拉低数据被锁存 delay_us(50); } void lcd_init(void) { delay_ms(50); // 上电等待 lcd_nibble(0x03, 0); // 切回8位模式 delay_us(4500); lcd_nibble(0x03, 0); delay_us(4500); lcd_nibble(0x03, 0); delay_us(150); lcd_nibble(0x02, 0); // 正式切4位模式 lcd_cmd(0x28); // 4位、2行、5x8点阵 lcd_cmd(0x0C); // 显示开、光标关 lcd_cmd(0x01); // 清屏 delay_ms(2); }这段代码的关键在于HD44780 上电后默认是 8 位模式如果直接发 4 位命令高 4 位和低 4 位会错乱。所以先发三次 0x03 让控制器稳定在 8 位模式再发 0x02 切换到 4 线。E 引脚的高电平维持时间要大于数据手册要求的 450ns这里用 delay_us(1) 足够。清屏指令 0x01 需要等待约 1.52ms初始化时不能省否则后面命令丢。3.2 LCD1602 显示内容排版与刷新策略数字电压表的显示页面按功能分区主读数用大字单位 V 跟随小数点位档位信息2V/20V/200V、校准状态、低电量提示放副行。LCD1602 的行宽只有 16 字符一个「199.9 V」加单位就占了 7 个字符剩下的空间刚好放档位和状态。排版时用 tab 键思路不行LCD 是字符坐标寻址直接在指定行列写即可。刷新策略与 ADC 采样协调不要在 ADC 采样期间刷新 LCD。LCD 的读写时序会让数据线和控制线剧烈翻转如果 PCB 布局不理想数字噪声会耦合进模拟前端导致末位读数跳 1~2 个字。常见的做法是采样和显示分时先采完一批数据滤波再刷新一次 LCD然后进入下一轮采样。10Hz 的显示刷新率对电压表已经足够肉眼看起来是连续更新还能自然滤掉一部分抖动。如果需要显示中文LCD1602 的字符 ROM 里只有 ASCII 和日文假名没有汉字。解决方案有两个自己用 5x8 点阵定义少量汉字字模适合「电压」「档位」这种固定词或者换成带中文字库的 LCD12864SPI 接口成本稍高但省心。如果标题里出现中文说明大概率用的是带字库方案这块在嵌入式里是另一个选型分支。3.3 HT1621 驱动段码 LCDTM1622 兼容与注意点段码 LCD 适合显示数字和固定图标特点是功耗极低适合电池供电的便携万用表。驱动芯片常见的是 HT1621兼容型号 TM1622。HT1621 通过三线串口CS、WR、DATA控制内部有 RAM 映射到 LCD 的 COM 和 SEG。对嵌入式来说关键是弄清「每一位数字由哪几个段组成」——3 位半显示需要 4 个数字位每个数字 7 段加上小数点、单位符号总共 30~40 个段。TM1622 和 HT1621 的指令格式略有差异但驱动思路一样初始化设置偏置和占空比然后往显示 RAM 写段数据。可以参考这个初始化序列void ht1621_init(void) { ht1621_cmd(0x30); // 系统使能内部 RC 振荡 ht1621_cmd(0x52); // 1/3 偏置4 COM 公共端 ht1621_cmd(0x02); // 关闭 LCD 偏置发生器 ht1621_cmd(0x06); // 打开偏置发生器 } void ht1621_display_digit(uint8_t pos, uint8_t seg_data) { // 每个数字占 7 段加上小数点一共 8 位按位写入对应 RAM for (uint8_t i 0; i 8; i) { ht1621_write_bit((seg_data i) 0x01); } }段码 LCD 的调试比 LCD1602 麻烦因为段和 RAM 的映射关系要对着数据手册逐位核对。一个值得用的技巧是「全显测试」上电后把所有段全部点亮对照实物逐个标注 COM 和 SEG 位号这样再写段码映射表就不会搞混。另外注意 HT1621 使用的 32.768kHz 晶振和内部 RC 振荡精度差异段码 LCD 时钟源不稳定会导致显示闪烁优先用外部晶振。4. 采样滤波与校准末位稳定靠的不是运气4.1 均值滤波、去极值与工频抑制的取舍ADC 原始采样值抖动来源有两种随机噪声和工频干扰。随机噪声用多次采样求平均能有效压低均值滤波 N 次信噪比提升 sqrt(N) 倍工频干扰则需要采样窗口长度正好是 20ms 的整数倍这样 50Hz 及其谐波能在平均过程中对消。嵌入式数字电压表里最实用的组合是「去极值 滑动平均」先去掉最大最小值防止毛刺把均值拉偏再对剩余样本做滑动平均。#define ADC_SAMPLE_N 32 uint16_t adc_read_filtered(void) { uint32_t sum 0; uint16_t maxv 0, minv 4096; uint16_t buf[ADC_SAMPLE_N]; for (int i 0; i ADC_SAMPLE_N; i) { buf[i] adc_read_once(); if (buf[i] maxv) maxv buf[i]; if (buf[i] minv) minv buf[i]; sum buf[i]; } return (uint16_t)((sum - maxv - minv) / (ADC_SAMPLE_N - 2)); }这里的采样点数选 32去掉一个最大一个最小剩下 30 个点参与平均。如果采样频率固定32 次采样的总时间要尽量接近 20ms 的整数倍这样工频对消效果最好。具体做法是先用定时器把每次采样的间隔固定下来比如采样率 1.6kHz32 点正好 20ms。不要用「连续转换 轮询标志」的方式那样各次采样的时间间隔受 CPU 干扰工频抑制能力会变差。在嵌入式面试里经常被问到「ADC 采样如何滤除 50Hz 工频」答案核心就是采样窗口与工频周期同步。更严格的做法是加一个硬件低通滤波器RC 的截止频率设在 10Hz 以下但那样响应时间会变慢适合稳态测量。对电压表这种慢变信号RC 低通加软件滑动平均属于标准做法。4.2 校准流程两点校准与系数存储硬件器件都有离散性分压电阻标称 1MΩ 实际可能 997kΩ运放有输入失调电压ADC 有增益误差。这些误差如果不校准读数偏差可能到几十个字。校准的本质是用一个已知电压源建立「ADC 码值 → 实际电压」的映射关系。最简单的是两点校准输入 0V 记录零点码值输入一个参考电压记录增益码值然后线性插值。typedef struct { float k; // 增益单位 V/LSB float b; // 零点偏移单位 V int32_t zero_code; int32_t ref_code; } cal_param_t; cal_param_t cal; void cal_calculate(void) { // ref_voltage 是校准源实际输出电压必须用万用表量过 cal.k (float)REF_VOLTAGE / (cal.ref_code - cal.zero_code); cal.b 0.0f - (float)cal.zero_code * cal.k; } float cal_to_voltage(uint16_t code) { return (float)code * cal.k cal.b; }校准系数需要掉电保存。常见做法是存进 STM32 内部 Flash 的最后一个扇区或者外挂 AT24C02。问题是参数不是每次上电都是有效的如果校准系数全是 0xFF说明 Flash 是空的不能用如果校准源当时没接好校准出的 k 值会异常偏大或偏小。所以存储结构里要加一个 magic number 和 CRC加载时校验通过才采用否则退回默认系数。这一点在做产品时要认真处理开发板上无所谓但用户手里设备一旦校准数据损坏读出的电压就是错的。提示校准时的参考电压源精度决定了整个系统的精度上限。推荐用 AD584 这类电压基准模块输出 2.5V/5V/7.5V/10V 四档比用可调电源的电压表读数当基准可靠得多。4.3 蓝桥杯与课设场景下的常见坑这类题目在蓝桥杯嵌入式国赛和 51 单片机课设里出现频率很高。国赛真题的套路是用 ADC 采电位器电压LCD 显示再加上按键切换量程。常见的坑往往不是算法而是「初始化顺序」LCD 没初始化就写数据、ADC 的采样时间没配置导致转换结果偏小、分压电阻没接运放导致测量值受负载影响。课设场景还有一个高发问题电源用的是 USB 5V而 ADC 参考是 VDDA3.3VLDO 输出的 3.3V 纹波直接进到 VREF造成读数在末位跳个不停。判断方法很简单把输入短路到 GND如果读数仍然上下跳说明是参考或电源问题而不是信号问题。修正方法是把 VREF 和 VDDA 用 π 型滤波隔开或者干脆外接基准芯片。这些点在你投嵌入式简历时经常作为项目经验被追问能说清楚「误差从哪来、怎么消除」比单纯说「做了一个电压表」有说服力得多。5. 自动量程与精度验证让电压表真正可用5.1 自动量程切换逻辑与判定阈值多档位电压表如果只靠手动切换体验大打折扣。自动量程的思路是先放在最高量程测一次如果读数很小逐级降档如果读数接近满量程升档重测。切换带滞回避免在临界点来回跳。参考实现逻辑// 档位枚举2V / 20V / 200V void auto_range_update(float voltage) { switch (cur_range) { case RANGE_2V: if (voltage 1.9f) range_set(RANGE_20V); // 满量程90%升档 break; case RANGE_20V: if (voltage 19.0f) range_set(RANGE_200V); if (voltage 0.2f) range_set(RANGE_2V); // 10%降档 break; case RANGE_200V: if (voltage 2.0f) range_set(RANGE_20V); break; } }升档阈值取满量程的 90%降档阈值取 10%中间留滞回区避免 1.95V 时在 2V 档和 20V 档之间反复切换。切换档位时分压网络切换需要时间稳定切完后要跳过几次采样或做一次短延时等 RC 建立后再读数。滞回区间的大小和档位切换稳定时间是自动量程体验好坏的关键参数。5.2 精度验证的方法与误差来源定位校准完不能直接交付要做精度验证。手头没有六位半万用表时用 AD584 基准模块就可以完成大部分验证在 2V 档输入 1.0000V20V 档输入 10.000V200V 档输入 100V需要外部分压或更高电压源对比实测显示值和标准值的偏差。误差分析时注意区分比例误差和偏移误差比例误差表现为「读数越大偏差越大」通常是分压电阻或增益校准不准偏移误差表现为「输入 0V 时显示不为零」是零点未校准或运放失调。误差来源定位有个实用方法把输入短路到 GND 看零点再输入 90% 满量程看增益两点一算基本能把系统误差拆开。温度变化引起的误差要用温漂数据估算比如分压电阻 100ppm/°C温度变化 10°C比例误差就是 0.1%对三位半精度来说已经需要重视。如果这类评估出现在开源项目的 README 或文章里读起来比「精度很高」四个字可信得多。5.3 低成本扩展串口输出与上位机联动嵌入式数字电压表做完显示和校准后加一个串口输出可以让它从「一个表」变成一个「数据采集节点」。通过 UART 把滤波后的电压值以 ASCII 格式输出电脑端用串口助手、Python 或 Qt 都能接收。如果后续打算做嵌入式学习路线的进阶这套「传感器 → 采样 → 滤波 → 显示 → 通信」的组合是贯穿很多项目的主线。串口输出还有一个实际好处校准和联调时可以看原始码值和滤波后的码值排查哪一级引入误差。示波器能看到信号波形但看不到 ADC 码值的统计分布串口把采样码值打出来用 Python 数一下标准差滤波效果一目了然。这个习惯比任何示波器都更适合判断「末位跳动是被噪声拉动的还是被算法没滤干净」——前者靠硬件后者靠软件定位错了方向就全错了。本文还有配套的精品资源点击获取
返回列表