ARTICLE DETAIL

资讯详情

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

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

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_WriteNibble和LCD_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选择 R127kR210k那么 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直接接 ADCDS18B20 走单总线协议输出数字量不需要 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主控模拟 STC89C52RCLCD1602LM016L液晶显示模块电位器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.0RW 接 P2.1E 接 P2.2DB4-DB7 接 P0.4-P0.7。P0 口在 Proteus 里不用接上拉电阻也可以但真实电路务必加上。电压采样部分用一个 DC 电压源设为 12V串联 R127k再串联 R210k 到地从 R2 两端引出信号到 P1.0。这个连接在仿真里等效于 12V 电池经过分压得到 3.24V 的样子。为了模拟电池实时变化可以把 R2 改为电位器反复调节观察 LCD 数字变化。温度部分如果使用 LM35直接把输出脚接 P1.1GND 和 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.0V_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 的电池电压与温度显示仿真设计才算真正具备了交付到实际硬件上的底子。本文还有配套的精品资源点击获取
返回列表