简介:面向单片机课程设计及嵌入式入门开发者,这份压缩包提供了一套基于STC89C51的智能充电电压、电流与温度监测系统完整方案。系统以LCD1602实时显示当前温度、电流和电压值,支持按键自定义阈值,一旦参数越限即刻触发蜂鸣器报警,覆盖数据采集、人机交互与异常告警等典型功能;硬件由最小系统、温度检测、信号采集、报警和电源单元组成,并采用Proteus仿真完成调试。资料包含原理图工程、仿真工程、C语言源代码工程、7804字课题报告及元器件清单,共33个文件,以pdsprj、schdoc、hex、doc、xlsx等类型为主,压缩包整体仅1.4MB,便于快速下载和对照阅读。目前已有88人学习使用,适合需要完成课程设计、熟悉51单片机外设驱动,或希望借助完整仿真验证方案梳理项目思路的读者参考。
1. 用51单片机做智能充电监测,到底在做什么:三路信号与一份完整交付物
锂电池充电过程最怕两件事:过充和过热。要么你一直盯着充电器,要么让单片机替你盯。这个标题给出的方案,是一套以51单片机为控制核心的智能充电监测系统:实时采集充电电压、充电电流和电池温度三路信号,超限就报警、切断充电回路,配套设计报告、原理图和proteus仿真工程。你拿到的是一个能跑、能答辩、能改的完整交付物。
这个系统的典型场景是课设和毕设,但也适合想快速搭一个充电监测原型的入门硬件工程师。51单片机主频低、资源少是事实,可充电监测恰恰是低速率巡检任务,每秒采样两三次就足够,51在这个场景下完全压得住。真正要花心思的是三路模拟信号的调理电路:电压怎么分压进ADC,电流怎么用采样电阻放大,温度怎么把DS18B20的时序读对。这三件事做明白,整套系统就立住了。
下面按落地顺序拆:先讲系统框架和选型逻辑,再给硬件参数计算,然后写可抄的C51代码,接着把proteus仿真从搭建到跑通的步骤和坑位过一遍,最后说怎么校准和验证保护动作。
2. 系统框架与选型逻辑:为什么是51单片机,ADC0809与LM358够不够用
2.1 为什么用51而不是STM32或专用充电芯片
做充电监测,选型的第一原则是“够用就好”,不是“参数越高越好”。STM32确实有12位ADC、DMA、多路定时器,可对一个每秒采样两次的监测系统来说,这些资源大部分时间在空转。51单片机在12MHz晶振下,ADC0809完成一次转换大约100微秒,主循环跑一轮包括读ADC、读DS18B20、刷新LCD1602、判断保护逻辑,总共不超过几毫秒,时间余量非常充足。
ADC0809是8位8通道逐次逼近型ADC,在proteus里有成熟的仿真模型,配合51的IO口控制时序也简单:三根地址线选通道,一根ALE锁存地址,一根START启动转换,一根EOC查询结束,一根OE读数据。相比STM32的ADC配置和校准流程,这套时序逻辑非常直观,适合作为教学演示,也适合写进设计报告里的“方案论证”章节。
专用充电管理芯片(比如BQ系列)集成度更高,但寄存器配置复杂,很多参数是黑匣子,proteus里也找不到对应的仿真模型。你做完它,评审看到的是一个封装好的IC,讲不出信号链路;用51加独立ADC、运放、温度传感器,每一路信号都能用示波器追出来。这恰恰是课设和毕设评审最看重的东西。LM358则纯粹是便宜、单电源工作、5V供电下能覆盖低端电流采样的场景,作为电流放大级足够。
2.2 整机信息流:从传感器到显示到保护执行
整套系统本质是一条信号链:被监测对象(充电中的电池)→ 传感器/调理电路 → ADC或单总线 → 51单片机 → 显示与保护执行。
| 模块 | 信号路径 | 接口 | 作用 |
|---|---|---|---|
| 电压采样 | 电池两端 → 电阻分压 → ADC0809 IN0 | P1.0-P1.2 通道选择 | 测电池端电压 |
| 电流采样 | 充电回路 → 0.1Ω采样电阻 → LM358放大 → IN1 | 同上 | 测充电电流 |
| 温度采样 | DS18B20 → 单总线数据线 | P3.7 + 4.7k上拉 | 测电池表面温度 |
| 显示 | LCD1602 | P0数据口 + RS/RW/E 控制脚 | 显示 V/I/T 三路数值 |
| 保护执行 | 继电器线圈 → NPN三极管驱动 | 任意空闲IO | 超阈值切断充电回路 |
电压采样和电流采样共用ADC0809的不同通道,靠通道地址切换;温度不走ADC,用DS18B20的单总线协议直接把数字量读回。51单片机对读到的数据进行量程换算,超过阈值就把继电器断开,同时驱动蜂鸣器和LED报警。LCD1602在proteus里对应的元件名是LM016L,实物和仿真都能用,字符显示三路数据刚好够用。
2.3 拿到压缩包后,设计报告、原理图、仿真工程怎么配合着看
这类压缩包一般给你三样东西:设计报告、原理图源文件和proteus工程。我拿到手第一件事不是读报告,而是先打开proteus工程跑一次,看它电路怎么搭、程序烧进去是什么效果。有了整体印象,再翻原理图核对引脚分配,最后才读报告里的方案对比和计算过程。顺序反过来的话,很容易被报告的篇幅带偏,抓不住重点。
设计报告一般包含需求分析、方案论证(51与STM32的对比表)、单元电路设计、软件流程、元件清单和调试记录。看报告时重点看“为什么这么选”的部分,而不是照抄参数。原理图要和proteus工程逐点对照,最常发现的问题是proteus里省掉了去耦电容、上拉电阻这类“看着不重要”的元件,实物调试时全部还回来。当你需要改动时,最小路径是:先改proteus里对应电路,跑通后再改报告里的电路参数,最后动实物。
3. 硬件电路与参数计算:分压比、放大倍数和上拉电阻怎么定
3.1 电压采样:电阻分压让输入电压落进ADC 0-5V量程
ADC0809的模拟输入范围由Vref+和Vref-决定,常见接法是Vref+接5V、Vref-接GND,所以输入电压必须被分压到0到5V以内。分压电阻的选取要先定被测电压上限Vmax,再算分压比K:
K = R2 / (R1 + R2),且 Vmax × K ≤ 5V
以多节铅酸电池组为例,最高电压20V,K取0.25,那么选择R1=30kΩ、R2=10kΩ:20V × 0.25 = 5V,正好满量程。如果是单节锂电池,充电上限4.2V、适配器最高5V,K取0.9,R1=1kΩ、R2=9kΩ即可。分压网络的功耗很低,Vmax²/(R1+R2) 在毫瓦级别,完全不用担心电阻发热。
分压后的信号建议在ADC输入端并联一个0.1μF的陶瓷电容,滤掉充电器开关噪声,否则ADC读数会来回跳几个LSB。电阻精度直接影响测量准度,5%的碳膜电阻会让分压比偏差好几个百分点,我用1%金属膜电阻。8位ADC的量化精度是5V/255≈19.6mV,对应到20V量程就是19.6mV/0.25≈78mV,作为监测显示足够,但别指望它做精密计量。
3.2 电流采样:0.1Ω采样电阻加LM358放大10倍的取舍
电流采样最常见的做法是“低端采样”:在充电回路的负极串一个很小的采样电阻,电流流过时产生压降,再用运放放大后进ADC。选0.1Ω是因为这个阻值在2A充电电流下只产生0.2V压降,对充电回路的影响可以忽略,功率损耗也控制在0.4W以内。这个功耗对应电阻选型,我一般选额定功率1W以上的,留足裕量。
LM358接成同相放大电路,增益G = 1 + Rf/Rg,取Rf=9kΩ、Rg=1kΩ时增益为10倍,0.2V×10=2V进ADC,刚好在0-5V量程的40%处,留了过流保护的上探空间。注意LM358不是轨到轨运放,单电源5V供电时输出只能到3.5V左右,所以别把增益调得太高让输出顶到非线性区。
| 参数 | 数值 | 说明 |
|---|---|---|
| 充电电流上限 | 2A | 普通18650电池充电典型值 |
| 采样电阻 Rsense | 0.1Ω / 1W | 2A时压降0.2V,功耗0.4W |
| 放大增益 G | 10 | Rf=9kΩ,Rg=1kΩ |
| ADC输入电压 | 0~2V | 2A满量程时对应2V |
| 电流分辨率 | 约7.8mA/LSB | 5V/255/10/0.1 |
运放增益电阻必须用1%精度。5%电阻会让增益在9.5到10.5之间漂移,换算出来的电流直接偏5%,这比ADC本身的量化误差还大。另外一个容易忽略的点是采样电阻的引脚焊盘会引入额外电阻,10mΩ的接触电阻在2A时就有20mV误差,反映到测量结果是0.2A偏差。实物调试时尽量用四线制接法,或者至少把采样电阻的焊盘做大、走线加粗。
3.3 温度采样:DS18B20单总线电路与上拉电阻要求
DS18B20是单总线数字温度传感器,12位分辨率下精度0.0625°C,测量范围-55°C到+125°C,贴在电池表面测温度完全够用。电路上只需要一个数据引脚,但要特别注意:DQ数据线必须对VCC接4.7kΩ上拉电阻。单总线协议靠主机拉低、释放来产生时序,释放后总线要靠上拉电阻恢复到高电平,没有这个电阻,通讯时序直接失败。
供电方式选外部供电更稳妥:VCC接5V,GND接GND,DQ接单片机IO。寄生供电模式能省一根线,但需要主机在温度转换期间强上拉,proteus仿真和实物调试都容易出问题,没必要省这根线。温度传感器摆放位置也很关键,要贴在电池电芯表面,不能贴在充电器外壳或散热片上,否则测的是环境温度而不是电池温度。还一个坑:不要在DQ线上并联电容滤波,单总线时序对上升沿敏感,加了电容读出来的温度会莫名其妙地偶尔跳变。
4. 软件实现:ADC0809驱动、DS18B20时序与三段保护代码
4.1 ADC0809与单片机握手:地址锁存、启动转换、EOC查询
ADC0809的时序可以拆成四步:先通过ADDA/ADDB/ADDC三根地址线选中通道,再给ALE上升沿锁存地址,然后START给一个高脉冲启动转换,最后等EOC变高后使能OE读数据。下面这段是能在Keil里直接编译的C51代码。
// 引脚映射:P1.0-P1.2 接 ADC0809 的 ADDA/ADDB/ADDC 选通地址 // P1.3=ALE,P1.4=START,P1.5=EOC,P1.6=OE,数据口 P2 接 ADC0809 的 D0-D7 // Vref+接+5V,Vref-接GND,CLK 由外部 500kHz 方波提供 unsigned char adc0809_read(unsigned char ch) { unsigned char dat; // 1. 设置通道地址:ch 取 0~7,对应 IN0~IN7 P1 = (P1 & 0xF8) | (ch & 0x07); _nop_(); // 2. ALE 上升沿把地址锁存进 ADC0809 P1 |= 0x08; // ALE = 1 _nop_(); P1 &= 0xF7; // ALE = 0 _nop_(); // 3. START 高脉冲启动一次转换 P1 |= 0x10; // START = 1 _nop_(); P1 &= 0xEF; // START = 0,转换开始 // 4. 等待 EOC 变高:转换期间 EOC 为低,结束后拉高 while ((P1 & 0x20) == 0); // 5. OE 拉高,把 8 位结果放到数据总线上读取 P1 |= 0x40; // OE = 1 _nop_(); dat = P2; // 读数据 P1 &= 0xBF; // OE = 0,释放总线 return dat; }这段代码里ALE和START分开控制,比常见的“ALE与START短接”做法时序更明确,proteus和实物表现一致。EOC用查询方式等待,因为转换时间约100微秒,主循环轮询完全来得及。如果你想把EOC改成中断触发也可以,但查询方式更简单,也更容易在proteus里观察时序。CLK时钟是ADC0809最容易漏接的引脚,proteus里用信号源发生器给它接500kHz方波,不接的话ADC不会工作。
4.2 DS18B20温度读取:复位时序、跳过ROM与数据拼接
DS18B20属于严格依赖时序的器件,读温度的标准流程是:复位→发0xCC跳过ROM→发0x44启动转换→延时750ms→再复位→发0xCC→发0xBE读暂存器→读取低字节和高字节→拼成16位数据除以16得到温度值。
// DQ 接 P3.7;外部供电,4.7kΩ 上拉到 VCC // 12MHz 晶振,STC89C52/AT89C52 sbit DQ = P3^7; void delay_us(unsigned int us) { while (us--) { _nop_(); } // 12MHz 下每循环约 1us,粗略延时 } void delay_ms(unsigned int ms) { unsigned int i, j; for (i = 0; i < ms; i++) for (j = 0; j < 114; j++); // 12MHz 下约 1ms } bit ds18b20_reset(void) { bit presence; DQ = 0; delay_us(500); // 拉低 480us 以上,产生复位脉冲 DQ = 1; // 释放总线 delay_us(70); // 等待 60~240us 的存在脉冲 presence = DQ; // 读到 0 表示器件存在,1 表示异常 delay_us(400); return ~presence; // 返回 1 表示存在 } void ds18b20_write_byte(unsigned char dat) { unsigned char i; for (i = 0; i < 8; i++) { DQ = 0; _nop_(); DQ = dat & 0x01; // 先送低位 delay_us(60); // 保持写时隙超过 60us DQ = 1; dat >>= 1; } } unsigned char ds18b20_read_byte(void) { unsigned char i, dat = 0; for (i = 0; i < 8; i++) { dat >>= 1; DQ = 0; _nop_(); // 拉低约 2us 产生读时隙 DQ = 1; _nop_(); // 释放总线,15us 内读取 if (DQ) dat |= 0x80; // 高位先出 delay_us(60); } return dat; } float ds18b20_get_temp(void) { unsigned char low, high; int raw; ds18b20_reset(); ds18b20_write_byte(0xCC); // 跳过 ROM,单设备不需要寻址 ds18b20_write_byte(0x44); // 启动温度转换 delay_ms(750); // 12 位分辨率转换时间上限 ds18b20_reset(); ds18b20_write_byte(0xCC); ds18b20_write_byte(0xBE); // 读暂存器 low = ds18b20_read_byte(); // 温度低字节 high = ds18b20_read_byte(); // 温度高字节 raw = (high << 8) | low; return (float)raw / 16.0; // 1 LSB = 0.0625°C }延时参数在proteus和实物上略有差异,proteus仿真时DS18B20模型对时序宽容度高,实物上如果读出来一直是85°C,多半是初始化时序的480us和读时隙的15us没卡准。建议用逻辑分析仪抓一次复位波形,确认拉低时间和存在脉冲宽度。温度值用int类型接收是因为DS18B20的高字节有符号扩展,零下温度时高字节是0xFC开头,直接除16会得到负数,这才符合实际物理含义。
4.3 主循环框架:量程换算、LCD1602显示与三段保护
主循环的逻辑是:依次读ADC0809两个通道和DS18B20温度,做量程换算,显示到1602,再与阈值比较决定继电器和蜂鸣器动作。核心是换算公式,ADC读回来的是一个0到255的整数,必须通过分压比和放大倍数还原成真实电压和电流。
#define CH_VOLT 0 // ADC0809 IN0 接电压分压输出 #define CH_CURR 1 // ADC0809 IN1 接电流放大输出 #define V_REF 5.0 // ADC 参考电压 #define K_DIV 0.25 // 电压分压比,R2/(R1+R2) #define R_SENSE 0.1 // 采样电阻,单位欧姆 #define GAIN_AMP 10.0 // LM358 放大倍数 sbit RELAY = P2^0; // 继电器控制,高电平断开 sbit BUZZ = P2^1; // 蜂鸣器控制 void main(void) { unsigned char adc_v, adc_i; float volt, curr, temp; lcd1602_init(); while (1) { adc_v = adc0809_read(CH_VOLT); adc_i = adc0809_read(CH_CURR); temp = ds18b20_get_temp(); // 量程换算:ADC 值先还原为 ADC 输入电压,再除以分压比/增益/采样电阻 volt = adc_v * V_REF / 255.0 / K_DIV; curr = adc_i * V_REF / 255.0 / GAIN_AMP / R_SENSE; lcd1602_show(volt, curr, temp); // 三段保护:过压、过流、过温,任一超限就切断充电回路 if (volt > 4.20) { // 单节锂电池满电电压 4.2V RELAY = 1; // 断开继电器 BUZZ = 1; // 报警 } else if (curr > 2.00) { // 充电电流上限 2A RELAY = 1; BUZZ = 1; } else if (temp > 45.0) { // 电池表面温度上限 45°C RELAY = 1; BUZZ = 1; } // 全部正常则恢复充电 else { RELAY = 0; BUZZ = 0; } delay_ms(500); // 每 500ms 巡检一次 } }主循环的500ms巡检间隔是经验值:充电电压和温度变化都很缓慢,500ms间隔不会漏报;ADC0809的100us转换时间在500ms里占比可以忽略。如果你被测的是多节电池组,把过压阈值改成电池节数乘以4.2V,但要注意分压比K_DIV也要同步改,代码里的4.20和K_DIV是一对,改动时容易只改一个导致保护永远不触发。LCD1602的读写函数我这里省略,用查忙标志或直接延时都行,proteus里延时更省事。
5. Proteus 仿真从搭建到跑通:最小工程步骤与四类常见问题排查
5.1 Proteus最小仿真工程搭建:元件选型与连线顺序
Proteus里第一步是新建工程,选择AT89C52作为主控芯片。proteus 8.x以后的新版本(我一般用8.17这类较新版本)创建工程时会自动生成原理图工作区,不需要生成PCB。然后从元件库添加以下元件:AT89C52、ADC0809、LM358、DS18B20、LM016L(这就是LCD1602的proteus模型)、RES电阻若干、POT电位器、BUZZER、LED。电位器用来模拟电池电压输入,拖动电位器就能改变电压值,比反复修改DC电源参数方便得多。
连线的顺序有讲究。先把电源树接好:VCC和GND网标,然后接单片机最小系统(晶振、复位电路、EA引脚上拉),再接ADC0809:Vref+接VCC,Vref-接GND,CLK接信号源发生器,输出设置为500kHz方波。信号源可以从工具栏的“Generators”菜单里选DCLOCK,双击设置频率。接着连LM358放大电路和DS18B20上拉,最后接LM016L和继电器驱动。双击AT89C52芯片,在“Program File”里填入Keil编译生成的hex文件路径,即可运行仿真。
Proteus仿真跑通的关键是ADC0809的CLK。很多新手只在原理图里放了个ADC0809但没接CLK,仿真时读数永远不变或者乱跳。另一个常见问题是DS18B20在proteus里的模型对时序要求比实物严,如果仿真的温度显示85°C,先检查4.7k上拉电阻有没有接。
5.2 仿真中四个高频“翻车”点:现象、原因与解决
现象一:仿真启动后画面静止,LCD不亮、蜂鸣器不响。原因:单片机没有加载hex文件,或者电源没有接通。解决:双击AT89C52,在Program File里选择编译好的hex;确认VCC和GND网络确实连接到了所有需要供电的元件。proteus里有些元件默认不带电源引脚显示,但实际需要接电源网络,新手最容易漏。
现象二:ADC0809读数乱跳或者始终显示满量程255。原因:CLK没有接,或者Vref+和Vref-悬空。解决:给CLK接DCLOCK信号源并设置500kHz;Vref+接VCC,Vref-接GND。这个坑在proteus仿真里极高发,因为原理图里这些引脚画起来很不起眼,但少了任何一个ADC都无法正常工作。
现象三:DS18B20温度一直显示85.0°C。原因:DQ线上没有4.7k上拉电阻,或者初始化时序的拉低时间不够480us。解决:DQ到VCC加4.7kΩ上拉;检查ds18b20_reset函数里拉低时间确实超过480us。85°C是DS18B20的上电复位值,读不到真实温度时就会停留在85°C,这个特征能帮你快速定位问题在复位时序而非后级电路。
现象四:LCD1602花屏或者显示全黑方块。原因:E使能脉冲宽度不够,或者读写时序没有满足液晶模块的要求。解决:在proteus里给E引脚的高电平时间加长,或者在软件里每个操作后加延时。我用的是直接延时替代忙标志判断,proteus里跑得更稳。
5.3 仿真与实物的差距:噪声、精度与继电器抖动
仿真通过后别急着焊板子,先搞清楚proteus里哪些是靠不住的部分。proteus里的5V电源是理想电源,没有纹波没有内阻,实物上的7805稳压输出往往只有4.95V左右,这个偏差会直接反映到ADC参考电压上。解决的办法是不要依赖理论换算,实物调试时用万用表实测Vref值,把代码里的V_REF常量改掉。
LM358在proteus里的模型输出特性比实物理想。实物单电源5V供电时,输出在接近0V和接近3.5V处有明显非线性,你按40%量程设计能避开这个区间,但如果你把增益调高到20倍、输出顶到3.7V附近,实物上就会看到明显的“削顶”,proteus里反而看不出来。遇到这种情况,把电流量程或者增益调回去,让最大电流对应的输出电压控制在2.5V左右。
继电器的驱动电路也是仿真和实物差异最大的地方。proteus里直接用一个IO口接继电器线圈就能吸合,实物上必须用NPN三极管或ULN2003驱动,线圈两端还要并一个续流二极管吸收反向电动势,否则每次吸合断开都会产生尖峰干扰,严重的会直接复位单片机。这些在proteus里都测不出来,我焊完实物第一块板就被这个坑教育过。
6. 系统标定与验证:两点校正、动作测试与可选的下一步
6.1 电压电流两路线性标定:两点校正或最小二乘
仿真跑通、实物焊好后,系统精度要靠标定来保证。最常见的是两点校正:电压路用可调直流电源分别输出两个已知电压(比如5V和15V),记录LCD上的显示值,按线性公式 Y = kX + b 求出校正系数,直接写进代码里的换算表达式。电流路用电子负载加固定电流,同样记录两组数据。两点校正的前提是传感器链路的线性度本身够好,LM358和ADC0809在量程范围内线性度都不错,两点足够。
6.2 保护动作验证:过压触发延时与温度阈值
保护逻辑不能只看代码,要实际测动作时间。用可调电源缓慢升压到过压阈值,观察继电器断开需要多久;用万用表或示波器抓IO口电平跳变与继电器触点断开的延时。如果发现阈值附近反复吸合断开,说明需要软件去抖:连续三次采样都超限才动作,或者动作后加一个200ms的锁定时间。温度路用热风枪或恒温水浴加热传感器,实测45°C附近的动作一致性。
6.3 可选的下一步:串口数据上报与SOC估算
系统稳定后想往上走,最省力的方向是加串口上报:用51的UART把“V:4.12 C:0.85 T:32.1”这样的字符串发到上位机,9600波特率就够。更进一步可以按时间积分充电电流,做一个粗略的SOC估算,这就是BMS的雏形了。我第一次做这块板时,proteus里一切正常,实物一上电读数就乱跳,花了半天逐条对着原理图查,最后发现是ADC0809的Vref-悬空,地没接好。这之后我养成了一个习惯:焊板之前先把原理图和proteus工程逐点对照,特别是电源和参考电压引脚。这个习惯帮我省了很多无谓的排查时间,希望也帮到你。
本文还有配套的精品资源,点击获取