news 2026/9/13 13:19:05

51单片机Proteus仿真:LCD1602电池电压与温度显示设计

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
51单片机Proteus仿真:LCD1602电池电压与温度显示设计

简介:基于LCD1602液晶显示、DS18B20温度传感与TLC549模数转换三大模块,这份单片机仿真设计实现了电池电压和温度的实时监测与显示,适合学习51单片机、入门电池管理系统或进行毕设课题参考。压缩包共29个文件,整体大小仅282KB,包含C语言源程序、对应头文件、Keil工程文件、启动代码、Hex烧录文件以及仿真相关文件。其中LCD1602.c负责字符液晶驱动,DS18B20.c负责单总线温度采集,TLC549.c负责将电池模拟电压转为数字量,main.c串联系统流程,文件结构清晰,便于按模块学习。已有131人浏览学习。用户可直接导入仿真工具验证整体功能,也可借鉴其驱动层与应用层分离的代码组织方式,快速搭建自己的监测界面,或针对不同电池量程修改采集阈值与显示格式,具有较好的二次开发空间。整体代码注释清晰,对初学者友好,能帮助快速理清外设驱动与主循环的协作关系,方便后续扩展显示页面或增加保护逻辑。

1. 在搭 LCD1602 电压温度显示电路前,先想清楚仿真要替你挡住什么

电池供电的设备迟早要面对一个问题:屏幕上显示的电压和温度到底准不准。直接用万用表点一下,可能误差只有几十毫伏,可一旦换成单片机采样、分压电阻分压、温度传感器读数据,误差就变成一堆叠加误差。而这套“LCD1602 电池实时电压与温度显示仿真设计”的价值,恰恰是在焊接之前,先用仿真把整个链路跑通一遍,包括电压采样分压比、温度传感器的时序、LCD1602 的初始化时序和显示刷新策略,全部在虚拟环境里调好,再把程序烧进真实板子。

这个项目常见的载体是 51 单片机,比如 STC89C52RC,配合 LCD1602 字符液晶、一个电位器模拟电池电压、一个温度传感器模拟环境温度。适合刚接触单片机仿真与源程序配合调试的人,也适合做课程设计、电子设计竞赛预研,甚至产品原型的前期验证。我不打算替你写一整份现成工程,而是把仿真电路怎么建、源程序里哪些参数会被真实器件打脸、Proteus 仿真环境里容易忽略的坑一次讲清楚。

2. LCD1602 显示、电池电压采样和温度传感器的选型与匹配

2.1 LCD1602 模块的最小驱动条件:4 线还是 8 线

LCD1602 的驱动方式分 8 线并行和 4 线并行。8 线模式一次传输 8 位数据,占用 DB0-DB7 共 8 个引脚,加上 RS、RW、E,至少 11 个 IO 口。4 线模式只接 DB4-DB7,第一次传高四位,第二次传低四位,总共只用 7 个 IO 口。对于 STC89C52RC 这种 IO 资源不算富裕的芯片,我更推荐 4 线模式,特别是你还得分出引脚给 ADC 和 DS18B20 的时候。

很多人第一次移植 LCD1602 代码时,直接拷一个 8 线驱动,结果调不通。问题往往出在这里:4 线模式要求写入一个字节时,必须先写高四位再写低四位,两次都必须在 E 引脚上产生下降沿。代码里写LCD_WriteNibbleLCD_WriteByte必须严格区分。仿真里如果只用 Proteus 自带 LCD 模型,对时序并不敏感,但换到真实 LCD1602 模块时,延时不满足 datasheet 的 37 微秒建立时间,就可能出现花屏或第二行不显示。

#define LCD_DATA P0 // 数据口接 P0.4-P0.7 sbit LCD_RS = P2^0; sbit LCD_RW = P2^1; sbit LCD_E = P2^2; void LCD_WriteNibble(unsigned char dat) { LCD_DATA &= 0x0F; // 保留低四位 LCD_DATA |= (dat & 0xF0); // 送入高四位 LCD_E = 1; _nop_(); LCD_E = 0; } void LCD_WriteByte(unsigned char dat) { LCD_RS = 0; // 写命令 LCD_WriteNibble(dat); // 先送高四位 LCD_WriteNibble(dat << 4); // 再送低四位 delay_us(50); }

仿真里你很容易忽略 E 引脚的下降沿宽度,但真实 LCD1602 对脉宽有要求,不能小于 150 纳秒。_nop_()在 12MHz 晶振下大概 1 微秒,已经足够。另外,很多开发板的 LCD1602 数据线是接排阻上拉的,仿真中没有上拉也能跑,但真实电路必须确认 P0 口是否外接上拉电阻,否则高电平驱动能力不足。

2.2 电池电压采样:分压电阻、ADC 参考电压和换算系数

如果电池电压范围是 0-5V,可以直接接到 STC89C52RC 的 P1 口当作 ADC 输入。但真实电池在放电过程中电压会降到 3V 以下,而 51 单片机的 ADC 参考电压默认是供电电压 VCC,也就是 5V,所以想测 12V 锂电池组就不行了。常见做法是用电阻分压,把被测电压映射到 0-5V 区间。

分压公式是Vout = Vin * R2 / (R1 + R2)。假设电池最高电压 16.8V,选择 R1=27k,R2=10k,那么 Vout 最大约 4.54V,留出一点裕量给 ADC 满量程。这个组合在 Proteus 仿真里非常容易验证,只需一个电位器和一个 DC 电压源,把电位器中心抽头接到 ADC 输入。

仿真里有一件事特别容易骗人:Proteus 的电位器默认不显示当前阻值比例,如果你双击它并在对话框里输入,得到的阻值与程序里的采样结果对不上,往往是忽略了 ADC 输入阻抗的影响。STC89C52RC 的 ADC 是逐次逼近型,内部有采样保持电容,输入阻抗不够高时,串联电阻会形成分压误差。这就解释了为什么仿真里显示 4.86V,实际电路里却只有 4.2V。

unsigned int ADC_Read(unsigned char channel) { ADC_CONTR = 0x80 | (channel & 0x07) | 0x08; // 启动 ADC _nop_(); _nop_(); _nop_(); _nop_(); while (!(ADC_CONTR & 0x10)); // 等待 ADC_FLAG ADC_CONTR &= ~0x10; // 清除标志 return (ADC_RES << 2) | (ADC_RESL & 0x03); // 10 位结果 }

ADC_CONTR是 STC 系列的控制寄存器,ADC_RES是高八位,ADC_RESL的低两位是结果的最低两位。仿真里如果用 AT89C52 代替 STC89C52RC,就没有 ADC 外设,需要在 Proteus 里添加一个 ADC0809 芯片或者改用模拟比较器方案。这也是很多“单片机仿真芯片有哪些”的讨论里反复出现的问题——Proteus 并没有完整的 STC ADC 模型,你只能在回环里用电压源直接接 P1 口,并用电压表测量来模拟 ADC 结果,或者直接换 ATmega328P 做仿真。

2.3 温度传感器选型:DS18B20 和 LM35 在仿真中的差异

温度检测有两个常用路线:LM35 输出模拟电压,1mV/°C,直接接 ADC;DS18B20 走单总线协议,输出数字量,不需要 ADC。对这个项目来说,DS18B20 更合适,因为 51 单片机只有一个 ADC 模块,留给电压检测更合理。但 DS18B20 的单总线时序在 Proteus 里仿真很容易出问题,主要原因是仿真时钟步进和 I/O 读写延时不一致。

如果你选择 LM35,那么温度换算非常直接,温度 = ADC采样值 * 5000.0 / 1024.0 / 10.0,比如采样值 512,对应电压 2.5V,温度 25°C。这个公式在 Proteus 里用直流电压源就能验证。而如果用 DS18B20,你要处理好 48 位序列号跳过、ROM 匹配、温度转换指令0x44和读取暂存器指令0xBE的时序。仿真里经常遇到读取到 0xFFFF 或 0x0550 这种奇怪数据,通常是延时过长或过短导致的。

我一般建议初学阶段先用 LM35 打通整个链路,确认 LCD1602 显示和 ADC 读数没有问题,再换成 DS18B20。因为 DS18B20 的调试难点在于时序,而不是显示本身。把问题拆成两段,仿真里更容易找到错误位置。

3. Proteus 仿真电路搭建与源程序框架:从画图到跑通完整链路

3.1 在 Proteus 里放置元件并确认单片机仿真模型

打开 Proteus 后,我们需要的元件清单如下表所示。这里注意,Proteus 的元件库中不一定有 STC89C52RC,需要优先选用 AT89C52 作为替代模型,或者使用 Wokwi 仿真平台、Arduino 仿真软件来绕过单片机模型的缺失。如果你是做在线仿真,Wokwi 支持自定义芯片比较困难,但闪烁的小项目更容易跑通。

元件Proteus 关键字用途
单片机AT89C52主控,模拟 STC89C52RC
LCD1602LM016L液晶显示模块
电位器POT-HG / POT-LIN模拟电池电压变化
直流电压源DC POWER提供分压输入
LM35LM35温度传感器(模拟方案)
DS18B20DS18B20温度传感器(数字方案)
电阻RES分压和上拉

放置完元件后,先把单片机的晶振电路和复位电路画好,否则仿真会提示驱动能力不足。Proteus 默认没有给单片机接晶振,需要放置一个 CRYSTAL 元件,频率设为 11.0592MHz 或 12MHz,并在 XTAL1 和 XTAL2 两端并联两个 20pF 电容。复位电路用 10uF 电容和 10k 电阻组成上电复位即可。

3.2 接线顺序和电源分配:先电压后温度

我的建议是先接 LCD1602 和单片机之间的数据线,再接电压采样,最后接温度传感器。这样每接一部分就可以用仿真中的虚拟终端或 LCD 显示来验证,避免所有接线一起完成后不知道问题出在哪块。

LCD1602 的引脚 VSS 接地、VDD 接 5V、V0 接一个 10k 电位器用于对比度调节,在 Proteus 里直接接地也能显示,但真实硬件中不调节对比度通常会黑屏。RS 接 P2.0,RW 接 P2.1,E 接 P2.2,DB4-DB7 接 P0.4-P0.7。P0 口在 Proteus 里不用接上拉电阻也可以,但真实电路务必加上。

电压采样部分:用一个 DC 电压源设为 12V,串联 R1=27k,再串联 R2=10k 到地,从 R2 两端引出信号到 P1.0。这个连接在仿真里等效于 12V 电池经过分压得到 3.24V 的样子。为了模拟电池实时变化,可以把 R2 改为电位器,反复调节观察 LCD 数字变化。

温度部分:如果使用 LM35,直接把输出脚接 P1.1,GND 和 VCC 分别接 0V 和 5V。LM35 输出不需要额外上拉。注意 LM35 的封装引脚在 Proteus 里是平面朝上还是平面朝下,接反了会显示负温度。

3.3 源程序的整体框架和 LCD 显示刷新逻辑

程序整体的骨架是:初始化 LCD -> 循环里读 ADC -> 计算电压 -> 读温度数据 -> 格式化并显示-> 延时刷新。这里我把 51 的源码写成可直接编译的函数片段,但你复制后需要根据自己 Keil 工程的寄存器定义做小修改。

void main() { unsigned int adc_val; unsigned int temp_val; float voltage; char buffer[16]; LCD_Init(); LCD_WriteString(0, 0, "Batt Voltage:"); LCD_WriteString(0, 1, "Temp: "); while (1) { // 读取 P1.0 上的电压采样值(10 位) adc_val = ADC_Read(0); voltage = adc_val * 5.0 / 1024.0; // 按分压比还原电池电压 voltage = voltage * (27.0 + 10.0) / 10.0; sprintf(buffer, "%.2fV", voltage); LCD_WriteString(6, 0, buffer); // 读取 P1.1 上的 LM35 电压,换算温度 adc_val = ADC_Read(1); temp_val = adc_val * 500.0 / 1024.0; // 500 对应 5V 满量程下的温度 sprintf(buffer, "%dC", temp_val); LCD_WriteString(6, 1, buffer); delay_ms(200); // 200ms 刷新一次,防止闪烁太快 } }

这里有一个很容易被初学者忽略的细节:sprintf在 51 环境下会引入较大的代码开销,Keil 里默认使用小格式模式,导致%f无法正确打印浮点数。上面的代码为了可读性用了%.2f,但实际工程里我更建议直接用整数运算,把电压值拆成整数部分和小数部分,避免浮点库带来的 800 字节以上代码膨胀。

unsigned int voltage_10x = (unsigned int)(adc_val * 5000.0L / 1024.0L * 37 / 10 / 100);

上述表述处理了分压比和电压显示精度,但远不如直接固定分压系数清晰。你可以用unsigned long保存毫伏值,然后拆成个位和十分位显示。真实项目中宁可多写几行拆分代码,也不要在中断里调用 printf。

4. 源程序里的关键参数校准:ADC 精度、分压比和温度补偿

4.1 ADC 采样结果与真实电压的关系:参考电压不是 5.0V

在 Proteus 仿真里,电源电压是理想精确的 5.0V,所以adc_val * 5.0 / 1024.0是正确的。但是真实单片机开发板上,USB 供电或者稳压芯片出来的电压通常落在 4.92V 到 5.08V 区间,你的换算公式必须改成:

V_actual = adc_val * V_ref / 1024.0

V_ref 可以被你实测得到,然后用程序里的修正系数调整,这也是从仿真向硬件移植时第一个要改的地方。必须用万用表量一下 VCC 和 GND 之间的实际电压,而不是直接信参数表。更稳妥的做法是给 ADC 增加一个已知电压的校正通道,比如在 P1.2 上接一个 2.5V 的参考基准,每次上电后先采样这个通道,再反向推导 V_ref。

float cal_factor; // 上电时读取校准通道 cal_factor = 2500.0f / (unsigned int)ADC_Read(2); // 之后所有电压计算结果乘以 cal_factor

在仿真里这套方法意义不大,因为电源电压固定。但作为工程经验,你需要把它留在源程序里,因为你不能保证拿到你仿真代码的人烧录到真实板子上时电源电压恰好是 5.0V。

4.2 分压电阻的精度与电压表读数的误差分配

分压比 R1/R2 如果使用 1% 精度电阻,那么分压系数误差也会引入约 2% 的最终电压读数误差。比如测量 12V 电池,实际电压可能是 11.9V,而系统显示 12.2V。仿真里你用的电阻阻值是理想值,所以最终显示非常精确。这就导致你拿仿真结果去验收真实项目时,会认为自己的程序有问题,其实问题在硬件偏差。

解决方法是不要用固定分压比直接换算,而是做单点校准。准备一个可调直流稳压电源,分别输入 8V、12V、16V,记录显示值,然后算出线性矫正系数。你可以把校准表放在代码开头,用宏定义接入:

#define ADC_OFFSET 10 // 实测零点偏移(ADC 码值) #define ADC_SLOPE 100 // 实测每伏对应的 ADC 码值

这个思想也可以用在温度传感器上。LM35 在 25°C 时输出 250mV,但实际放在常温空气中,显示 27°C 或者 24°C 很正常。仿真里温度是理想的,因为用的是恒压源模拟,不会引入自热效应。真实硬件中 LM35 自身功耗会使温度读数偏高 0.5°C 左右,尤其是供电电压到 5V 时。如果对精度要求高,建议用 DS18B20 并加上 750ms 转换延时后的读值。

4.3 LCD1602 显示刷新策略:200ms 刷新为什么还会出现残影

很多人在仿真里把刷新写在主循环,看到 LCD 不断闪烁,于是不断缩短延时,结果画面更闪。LCD1602 的写入速度本身就有限制,单条指令至少需要 37 微秒,而显示一个字符要先写地址、再写数据,光刷新一行 16 字符就要几百微秒。如果在刷新过程中穿插 ADC 采样和温度转换,每次采样又需要十几微秒,导致 LCD 的 RW 时序被拉长,最终形成一种人眼可感知的闪烁。

更好的做法是使用定时器中断,设定 250ms 标志位,主循环只在标志位置位时刷新 LCD。这样 ADC 采样和温度读取可以放在主循环里,LCD 写入只在中断服务函数外执行,避免在 E 引脚电平变化过程中被采样代码打断。对于这个项目来说,一个简单方案是:

bit refresh_flag = 0; void Timer0_ISR() interrupt 1 { static unsigned int cnt = 0; TH0 = 0x4C; TL0 = 0x00; if (++cnt >= 200) { // 200ms cnt = 0; refresh_flag = 1; } }

中断服务函数里不要做 LCD 写太复杂的操作,只要置标志位。主循环检查标志位后调用显示函数。这样可以保证 LCD1602 每个字节写入时,不会因为温度传感器转换而延迟过长导致花屏。

5. 从仿真走向实物时最值得加进去的一个技巧:动态刷新防抖与单位切换显示

仿真验证通过后,源程序移植到真实硬件时,最先翻车的往往是“按键切换显示温度还是电压”这种交互需求。常见的做法是把刷新逻辑按页面拆分:第一屏显示电压,第二屏显示温度,用独立按键切换。但按键抖动会造成一次切换连续跳两页,最终显示状态混乱。

我推荐在按键扫描里加入状态机,同时把 ADC 采样结果做去抖动平均。平均算法不是简单连续采样十次求平均,而是用“滑动平均窗口”避免电压突变时显示滞后太多。窗口设 8 次,每次新采样值进来,旧值出去,相当于低通滤波。

#define SAMPLE_NUM 8 unsigned int sample_buf[SAMPLE_NUM]; unsigned char sample_index = 0; unsigned long sample_sum = 0; void Filter_Add(unsigned int value) { sample_sum -= sample_buf[sample_index]; sample_buf[sample_index] = value; sample_sum += value; sample_index = (sample_index + 1) % SAMPLE_NUM; } unsigned int Filter_Get(void) { return (unsigned int)(sample_sum / SAMPLE_NUM); }

这个滑动平均窗口需要在主循环里以固定间隔喂数据,我通常配合定时器标志位,每 100ms 采样一次。仿真里你反复调整电位器,显示值的变化会有明显滞后,这是预期效果,真实电池电压不会瞬间跳变,过快的刷新反而让用户觉得不稳定。

对于单位切换显示,可以将 LCD 第一行固定为电压标签,第二行固定为温度标签;也支持按一次按键对调上下行显示内容。切换时用一个变量page记录当前页,不要直接改写 LCD 整屏,而是只更新数值部分,标签保持不变,避免液晶闪烁。这样实现的源程序更具可读性,也便于后续扩展显示电流或剩余电量百分比。

另外,调试过程中建议在 Keil 的DEBUG模式下打开 UART1 输出采样原始值,用串口助手观察 ADC 码值波动。Wokwi 这类在线仿真平台虽然不支持 UART 虚拟终端,但 Proteus 中有 VIRTUAL TERMINAL 可以直接看串口输出。把 ADC 码值打印出来后,再和 LCD 显示值对比,能很快定位是采样问题还是显示格式化问题。

最后一个从仿真到实物最容易忽略的细节:LCD1602 的对比度电位器在 Proteus 里调节到你看着清楚的位置,但真实模块上每个模块的最佳对比度电压都不同。V0 脚静态电压太高会黑屏,太低会看不清字符。建议给 V0 接一个 10k 电位器,上电后手动调到能清晰显示数字,再用万用表记录这个电压,以便工程化时选固定电阻替代。至此,这套基于 LCD1602 的电池电压与温度显示仿真设计,才算真正具备了交付到实际硬件上的底子。

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

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

Linux常见问题复盘:解压乱码、DNS、WSL磁盘与Python管理

这期内容原本应该顺着 Linux 安装的话题继续往下写&#xff0c;但最近收到的实操问题实在太多&#xff0c;从 ZIP 解压乱码到 WSL 磁盘爆满&#xff0c;再到 Python 版本管理&#xff0c;几乎每一个都让我重新翻了一遍文档。所以我把第 10 期的上半部分直接做成一个“问题复盘集…

作者头像 李华
网站建设 2026/9/13 13:16:15

CoNMF高光谱解混:协同稀疏约束与sunsal初始化工程实践

简介&#xff1a;面向高光谱遥感解混研究的一份CoNMF算法资源包&#xff0c;适合遥感图像处理、目标检测与环境监测方向的科研人员及相关专业学生。内容包括约束非负矩阵分解的完整实现与演示流程&#xff0c;覆盖端元提取、丰度估计和混合像元分解等关键环节。压缩包共30个文件…

作者头像 李华
网站建设 2026/9/13 13:15:09

数字化经营指标解析与实施指南

1. 数字化经营指标的本质解析"数字化经营指标"这个看似高大上的术语&#xff0c;其实质就是企业运用数据来指导和优化经营决策的方式。就像老练的厨师不再凭感觉放盐&#xff0c;而是用精准的秤量控制调味料的用量。数字化经营的核心&#xff0c;在于将传统的经验驱动…

作者头像 李华
网站建设 2026/9/13 13:10:16

UniApp教培中台源码:双端同构+插件化运营解决方案

简介&#xff1a;这是一套面向教育培训行业开发者的微信小程序与公众号双端源码解决方案&#xff0c;专为中小型培训机构、在线教育机构及教育类创业团队设计&#xff0c;解决课程管理、营销转化与用户运营一体化难题。资源包为77.27MB的ZIP压缩文件&#xff0c;含完整前后端代…

作者头像 李华