资讯动态

太阳能锂电池充电监测系统:51单片机ADC采样与LCD显示设计

发布时间:2026/9/13 9:17:04 来源:尧图企业网站定制
简介基于51单片机太阳能锂电池充电电压电流检测液晶显示设计是一套面向电子设计与嵌入式初学者的完整课设/毕设资源覆盖太阳能充电管理、充电电压与电流采集、LCD1602液晶显示及过压过流保护等核心环节可帮助解决从电路搭建到程序调试的系列问题。包内共121个文件压缩包约57MB以jpg电路图、doc/docx设计文档、pdf芯片资料、mp4演示视频以及c/h程序源码为主兼顾原理图、源码、论文和答辩材料目录结构清晰便于按阶段查阅。目前已有1107人学习下载作为含程序、电路、文档的一体化方案适合毕业设计、电子竞赛或单片机实训时快速参考与二次开发。1. 太阳能锂电池充电监测系统在讲什么把一块太阳能板、一颗锂电池、几片充放电管理芯片、一颗51单片机和一块液晶屏拼在一起做成一个能边充电边把电池电压、充电电流实时显示出来的装置这就是这类设计的主体。对做过工程的人来说太阳能板并不是一个稳定电源它的输出随光照飘直接接到锂电池上既充不满也容易过充锂电池又需要恒流/恒压两阶段充电所以中途必须有一颗充电管理IC把关。51单片机在这里承担“看门狗加前台”的角色把电压电流采进来换算成真实值送到液晶上。这类题目常见于单片机课程设计和电子竞赛难点反而不在51而在采样和标定显示的数字能不能跟万用表对上才是验收的关键。2. 硬件选型与采样通路搭建太阳能充电回路与电压电流检测电路2.1 太阳能板的输出特性与充电管理芯片怎么配太阳能板在光照足够时开路电压往往远高于标称值比如标称5.5V的板子空载可能到6V以上而且内阻随光照变化很大。锂电池的充电曲线是先恒流CC充到4.2V再转恒压CV把电流压下去不能靠简单串电阻和二极管解决。所以充电管理芯片的输入范围、耐压和是否带光伏适配逻辑是选型时第一件要想清楚的事。常见做法是三选一芯片类型输入电压典型值特点CN3058线性充电管理4.0~6.0V适合小功率太阳能板直连电流由外部电阻设定TP4056线性充电管理4.0~8.0V最常见使用时要保证VCC稳定在5~6VCN3083线性充电光伏适配4.0~6.0V内部有MPPT逻辑适合光照波动场景做课程设计我一般会优先推荐 CN3058 或 TP4056 配一块小功率太阳能板。如果希望“太阳能板直接怼上去也能正常充电”CN3083 这类带光伏适配的芯片更省心。不要为了图省事直接用一个5V稳压电源做验证因为最后接太阳能板测试时芯片会频繁进入欠压保护到那时再换方案代价很高。2.1.1 输入端保护和电压裕量太阳能板正负极接错会把充电管理芯片打穿输入端至少串一颗肖特基二极管或者依赖芯片自带的防反接电路。选型时还要看数据手册里的绝对最大输入电压留出20%以上的裕量不要卡着6V用标称6V的光板否则光照强时很容易超限。2.2 电流检测采样电阻放高端还是低端电流检测有两种惯用位置低端采样把采样电阻串在地回路里高端采样把采样电阻串在电源正端。低端采样电路简单同相放大即可但采样电阻会把地电位抬起来单片机的地和电池负极之间出现一个随电流变化的电位差系统里又有液晶背光、充电电流这个“地弹”会直接影响ADC精度。所以我一般倾向把采样电阻放在电池正端回路里也就是高端采样用INA180这一类的电流检测放大器把压差放大后再进ADC。INA180的共模输入范围可以到26V太阳能充电系统里不会超。采样电阻取值要同时照顾压降、功耗和信号幅度。锂电池充电电流按0.5A设计时用0.05Ω电阻满电流压降只有25mV经INA180增益20倍后输出0.5V这个幅度对51单片机的10位ADC很合适。如果电阻取0.1Ω压降50mV输出1V分辨率更高但电阻功耗为25mW仍在0.25W电阻的安全范围内。2.2.1 不接运放直接采差分电压行不行有人在PCF8591的两个模拟输入端直接接采样电阻两端测压差这种接法在8位ADC下基本不可用5V参考电压下1LSB约19.5mV对应0.05Ω电阻约0.39A电流显示会一跳一个值。要用51单片机做电压电流检测一级差分放大至少要有10到20倍增益这是硬需求不能省。2.3 电压检测电池电压和太阳能板电压分两路采集锂电池端电压要采太阳能板输入端电压也要采不要为了省一个ADC通道就把两个检测点并在一起。判断太阳能板是否在发电、估算充电效率都需要光伏端电压数据。51单片机的ADC参考电压是5V电池最高4.2V按理可以直接接但为了和充电引起的地电位抖动隔离标准做法还是分压后再采样。电池侧建议R120kΩ接电池正极R220kΩ接地分压比0.54.2V电池送到ADC引脚才2.1V太阳能板侧按板子开路电压选分压比标称6V板子同样按0.5分压。分压电阻的取值原则是阻值不能太大否则采样保持电容充不满也不能太小否则白白耗电。20kΩ20kΩ在电池4.2V时流过约0.1mA电流对课程设计完全够用同时可在ADC引脚并联一个0.1μF电容降低采样瞬间的电压跌落。2.4 采样回路的参数对照表下面这张表是搭这套采集通路时的默认参数。上电后先用万用表量一遍分压点实际电压不要急着把5V和分压比写进代码等真实值测出来再更新。采样点器件/回路设计值备注电池电压R1/R2分压后接P1.0分压比0.5万用表实测后修正代码常量充电电流BAT串0.05Ω采样电阻INA180输出接P1.10.5A对应0.5VINA180增益20倍太阳能电压R3/R4分压后接P1.2按板子空载电压选建议实测量程LCD1602P0.0~P0.7加P2.5~P2.78位模式P0口需要外接上拉把预设计参数集中在代码顶部后续换量程只改宏#define SUPPLY_VOLTAGE_MV 5000UL /* ADC参考电压实测后用真实值替换 */ #define BAT_DIV_RATIO 2.0f /* 1/分压比即(R1R2)/R2 */ #define PV_DIV_RATIO 2.0f /* 太阳能板分压比按实测改 */ #define SENSE_RES_OHM 0.05f /* 采样电阻阻值 */ #define SENSE_GAIN 20.0f /* INA180增益 */这些宏决定了后面所有工程量换算也是整篇代码里最值得先校准的地方。SUPPLY_VOLTAGE_MV如果直接写5000而实际稳压输出只有4.95V显示值会整体偏高1%这就是很多板子“显示电压和万用表对不上”的根源。3. 51单片机ADC采样与工程量换算从原始码到真实电压电流3.1 先看手头芯片有没有片内ADCSTC89C52还是AT89C5251单片机并非原生都有ADC。常见的AT89C52没有片内模数转换器要外挂PCF8591或ADC0809而STC89C52RC、STC12C5A60S2这些增强型51内置了ADC直接省掉一个外设。判断方法非常简单看数据手册里有没有ADC_CONTR这个特殊功能寄存器或者直接查型号后缀。这也是51单片机数字电压表类设计里常见的两条实现路线有片内ADC就用片内没有片内ADC就外挂。在Proteus仿真里STC系列的单片机ADC模型支持度一般很多人会用AT89C52配合电位器加PCF8591来完成仿真这就是为什么网上大量资料默认走外挂ADC。如果你手上的实物板是STC89C52RC优先用片内ADC电路少一根I2C线采样速度也够用。只有用到AT89C52或Proteus仿真需要时才走PCF8591路线。3.2 STC片内ADC的寄存器操作和通道切换STC89C52RC的ADC是10位、8通道挂在P1口上。需要操作三个特殊功能寄存器寄存器地址作用ADC_CONTR0xBCADC开关、启动控制、通道选择与转换完成标志ADC_DATA0xAD转换结果高8位ADC_LOW20xBE转换结果低2位关键位是ADC_CONTR的bit7打开ADC电源bit4是转换完成标志ADC_FLAGbit3写1启动转换bit2~bit0选择模拟通道。下面这段查询式读取函数是常用的基础代码sfr ADC_CONTR 0xBC; sfr ADC_DATA 0xAD; sfr ADC_LOW2 0xBE; unsigned int read_adc(unsigned char channel) { unsigned int result 0; ADC_CONTR 0x80 | 0x08 | (channel 0x07); /* 开ADC电源启动转换选通道 */ _nop_(); _nop_(); /* 等待电源稳定 */ while (!(ADC_CONTR 0x10)); /* 轮询ADC_FLAG等待转换完成 */ ADC_CONTR ~0x10; /* 软件清零标志位 */ result (ADC_DATA 2) | (ADC_LOW2 0x03); /* 拼接10位结果 */ return result; }0x80是ADC_POWER0x08是ADC_STARTchannel 0x07把通道号限制在0~7。while循环如果ADC引脚被外部强拉力拉死会一直卡住工程上通常加一个超时计数到最大计数值就按上一次结果处理。这个细节在实际板子偶发卡程序时很有用。Keil5建工程时芯片型号可以选STC89C52RC没有对应器件就先选AT89C52编译烧录时再用STC-ISP按实际型号下载。3.3 外挂PCF8591的I2C读取写法与首读丢弃做Proteus仿真或者用老式AT89C52时PCF8591是低成本替代方案。它有一个典型坑芯片上电后的第一次读返回的是上一次通道的旧转换值因此连续读取时要先读一次丢掉第二次读到的才是当前通道的有效结果。手写I2C时读写地址、控制字和通道选择要对齐unsigned char pcf8591_read(unsigned char channel) { unsigned char value; I2C_Start(); I2C_SendByte(0x90); /* 器件地址写方向 */ I2C_SendByte(0x40 | (channel 0x03)); /* 0x40打开ADC低2位选通道 */ I2C_Stop(); I2C_Start(); I2C_SendByte(0x91); /* 同一地址读方向 */ value I2C_RecvByte(); /* 第一次读旧数据丢弃 */ value I2C_RecvByte(); /* 第二次读当前通道 */ I2C_SendNack(); /* 主机回NACK结束本次读 */ I2C_Stop(); return value; }控制字0x40表示使能ADC并且使用单端输入低2位选择通道只接两路模拟量时够用。8位ADC的工程量换算比较粗暴raw/256乘以参考电压分辨率约20mV电压显示到0.01V时末位会一直跳这是8位精度极限不是程序写错了。注意PCF8591连续读取时第一次读到的旧数据必须主动丢弃否则显示值会延迟一个采样周期。3.4 采样均值滤波先清飞点再平均ADC第一次上电或通道切换瞬间容易采到异常值直接平均会把一个1023的毛刺带进结果。我习惯先做一次滤波连续采5个值丢弃最大值和最小值再对剩下3个取平均。对充电电流这种变化很慢的直流信号这套处理已经足够不需要上复杂的滑动平均滤波器。unsigned int filter_adc(unsigned char channel) { unsigned char i, j; unsigned int buf[5], sum 0, tmp; for (i 0; i 5; i) { buf[i] read_adc(channel); for (j i; j 0; j--) { /* 插入排序读完一个排一个 */ if (buf[j] buf[j - 1]) { tmp buf[j]; buf[j] buf[j - 1]; buf[j - 1] tmp; } } } for (i 1; i 4; i) sum buf[i]; /* 去掉最小和最大 */ return sum / 3; }注意buf[5]和sum的类型。五个10位数的和最大不超过5115用unsigned int不会溢出但如果你把采样次数提高到32次就必须换unsigned long否则Keil C51下会得到回绕后的错误值。这里取5采3是速度和稳定性的折中。3.5 工程量换算参考电压、分压比和增益集中在一处有了滤波后的原始码换算是固定的几个公式电压等于raw/1024乘以参考电压再乘以分压比倒数电流等于raw/1024乘以参考电压再除以增益和采样电阻的乘积。注意分压比是“真实电池电压/ADC引脚电压”别把R2/(R1R2)搞混。float get_battery_voltage(void) { unsigned int raw filter_adc(CH_BAT); float adc_volt (float)raw / 1024.0f * ((float)SUPPLY_VOLTAGE_MV / 1000.0f); return adc_volt * BAT_DIV_RATIO; } float get_charge_current(void) { unsigned int raw filter_adc(CH_CURR); float adc_volt (float)raw / 1024.0f * ((float)SUPPLY_VOLTAGE_MV / 1000.0f); return adc_volt / (SENSE_GAIN * SENSE_RES_OHM); }SUPPLY_VOLTAGE_MV只在换算入口出现一次标定时把它改成万用表实测电压就能整体修正ADC参考电压带来的误差。51单片机上用float做乘除会多占几百字节ROM但这类采样显示系统代码空间通常够用清晰比极端优化更重要。4. LCD1602液晶显示设计界面布局、刷新与充电状态机4.1 LCD1602走8位还是4位数据线51最小系统板的IO一般够用LCD1602用8位并行模式最稳妥初始化时序简单不容易犯4位模式下半字节错位的毛病。4位模式能省4个IO但写一个字节要拆成两次发送调试时对着逻辑分析仪看不出哪次是高四位课程设计阶段不建议为省IO徒增复杂度。8位接线固定为P0接数据口P2接控制口。#define LCD_DATA P0 sbit LCD_RS P2^6; sbit LCD_RW P2^5; sbit LCD_EN P2^7; void lcd_write_cmd(unsigned char cmd) { LCD_RS 0; /* 命令模式 */ LCD_RW 0; /* 写方向 */ LCD_DATA cmd; LCD_EN 1; _nop_(); _nop_(); LCD_EN 0; /* 下降沿锁存 */ delay_short(40); /* 命令执行时间 */ } void lcd_set_cursor(unsigned char row, unsigned char col) { lcd_write_cmd((row 0) ? (0x80 col) : (0xC0 col)); }LCD_RW始终写0不使用读忙标志靠延时兜底。在11.0592MHz晶振下delay_short(40)大约40微秒能覆盖LCD控制器大多数命令执行时间。如果改用查询忙标志需要先把P0切到输入模式再读BF位代码复杂度更高对显示速度没有实质提升。提示P0口内部没有上拉电阻8位数据线必须外接10kΩ排阻否则LCD1602会偶发乱码或花屏。4.2 显示内容的排版固定字符和刷新变量分开界面可以两行解决第一行显示电池电压和充电电流第二行显示太阳能板电压和充电状态。题目关心的电压电流检测结果要一眼可见单位固定写在字符串里数值部分单独刷新。用sprintf拼字符串在51上是很大的坑stdio.h的整型转换和浮点支持占用大量ROM而且Keil下还要重定向putchar很可能一调sprintf整个LCD就不动了。常见做法是自己把数值拆成字符。屏幕布局建议如下屏幕位置固定内容变量内容刷新周期第1行第0~5列V4.10200ms第1行第8~13列I0.45A200ms第2行第0~6列Solar5.82V200ms第2行第8~15列State:CC/CV/FULL状态变化时电压值先换算成毫伏整数再做除法取位。例如4.15V存成4150整数位是4150/10004小数位分别是(4150/100)%101和(4150/10)%105。这样完全避开浮点格式化void lcd_display_voltage(unsigned char row, unsigned char col, float volt) { unsigned int mv (unsigned int)(volt * 1000.0f 0.5f); /* 四舍五入到毫伏 */ lcd_set_cursor(row, col); lcd_write_char(mv / 1000 0); lcd_write_char(.); lcd_write_char((mv / 100) % 10 0); lcd_write_char((mv / 10) % 10 0); lcd_write_char(V); }0.5f是为了抵消浮点截断误差比如4.199V算成4199还是4200加半毫伏再取整更稳。lcd_write_char内部就是置LCD_RS1后按写命令同样的时序输出一个字节。4.3 刷新策略按节拍只改变化的位置LCD1602本身是静态显示不需要像TFT-LCD液晶显示模组那样不停刷整屏但如果在while循环里每几毫秒重写整行字符会肉眼可见地闪烁。正确做法是用定时器做一个200ms的刷新节拍每次刷新把电压、电流字符串写到固定位置只有按键切页或状态变化时才清屏。unsigned char refresh_flag 0; void timer0_isr(void) interrupt 1 { static unsigned char tick 0; TH0 0x4C; TL0 0x00; /* 11.0592MHz模式150ms重装初值 */ if (tick 4) { /* 4x50ms200ms */ tick 0; refresh_flag 1; } } void main(void) { init_timer0(); lcd_init(); while (1) { if (refresh_flag) { refresh_flag 0; v_bat get_battery_voltage(); i_chg get_charge_current(); v_pv get_pv_voltage(); lcd_display_ui(v_bat, i_chg, v_pv); } if (key_pressed()) handle_key(); /* 按键切页或进入标定状态 */ } }200ms的节拍既能看清电流变化人眼又不会觉得闪烁。51的浮点运算本身较慢采样加换算在200ms周期内完全执行得完不存在实时性压力。如果以后把刷新周期改到50ms就要考虑把ADC采样和显示拆成两个异步任务。4.4 充电状态判断CC、CV、FULL三态比干显示电压直观锂电池充电过程阶段明显只显示4.18V和0.12A不如直接显示“恒流充电”“恒压充电”“已充满”。三态判断只需要电压和电流两个输入量typedef enum { CHG_CC, CHG_CV, CHG_FULL } charge_state_t; charge_state_t get_charge_state(float v_bat, float i_chg) { if (v_bat 4.10f) return CHG_CC; /* 电压低恒流阶段 */ if (v_bat 4.20f i_chg 0.08f) return CHG_CV; /* 接近满电但还在充电 */ return CHG_FULL; /* 充电完成 */ }状态阈值要和充电管理芯片的终止电流对应起来。TP4056默认的截止电流是设定充电电流的1/100.5A设计时约50mA转灯代码里的0.08fA留了一点回差避免在边界反复横跳。状态变化时清屏一次并刷新状态字符比每200ms一直重写状态更稳当。5. 系统标定与误差校验让LCD显示值和万用表实测值对上5.1 两点标定比靠电阻标称值靠谱得多电阻标称值有1%误差ADC参考电压随稳压芯片批次漂移只靠理论分压比和标称Vref算出来的显示值误差普遍在0.1V以上。正确的做法是拿一个可调电源在ADC输入点加两个已知电压读出对应原始码再做增益和零点补偿。两点标定的公式是假设在ADC输入点量到V1和V2对应的滤波原始码是raw1和raw2则增益系数k(V2-V1)/(raw2-raw1)。程序里不要再用1024/5V的理论值直接按volt k*(raw-raw1)V1计算。这样把分压电阻误差、Vref误差和ADC非线性带来的系统误差一次吸收掉。标定参数可以整理成一个结构体typedef struct { int raw_zero; /* 0V输入处的原始码 */ float volts_per_bit; /* 增益系数单位V/bit */ } adc_scale_t; float adc_to_volt(adc_scale_t s, int raw) { return (float)(raw - s.raw_zero) * s.volts_per_bit; }标定顺序是先在上电无信号状态记录raw_zero再输入一个已知电压记录第二点算出volts_per_bit。把这两个值存到STC芯片的DataFlash里上电先读读不到再用默认值。STC的IAP读写例程网上很多直接复制到工程即可这是课程设计里避免每次重新烧录标定值的一个加分层操作。5.2 电流标定用电子负载或压降法别直接串万用表电流通路标定不能只靠电压公式反向推导。我一般用可调电子负载设置恒流0.2A、0.4A、0.6A三个点同时记录LCD显示值和电子负载上的电流读数做三点校验。如果手头没有电子负载可以用大功率电阻并联散热风扇作为负载但电流会随电池电压变化读数不稳定。串万用表测电流时要把表笔插到10A插孔普通200mA挡内阻加上接触电阻会直接改变充电电流测出来的值本身就不准。现场最可靠的自检方法是压降法用万用表毫伏挡测采样电阻两端电压再按IU/R算出真实电流。万用表毫伏挡内阻足够大不会干扰原电路这比任何估算都直接。5.3 三个容易翻车的点现象原因检查方法电压显示稳定但整体偏高0.2VVref不是5.00V或分压电阻标称值与实际偏差大用万用表量ADC引脚电压反推实际分压比电流始终是0.00A采样电阻被跳线短路或INA180输出没接对ADC通道断电后用万用表电阻挡测采样电阻两端上电后LCD只有方块LCD初始化时序太短或P0口没有上拉排阻用逻辑分析仪抓EN下降沿检查初始时序最后一个技巧把标定参数和充电状态阈值放到同一个头文件里管理标定完一组数据立即固化到DataFlash并把采样时间戳一起存进去。这样上课验收时可以直接打开数据显示上次标定结果而不是重新插拔烧录器反复试。校准做完用满电和亏电的锂电池各跑一次完整充电对比万用表实测曲线如果CC/CV切换点的电压误差在±0.03V以内说明采样链路和显示链路都可信下一步再去加数据记录或者蓝牙上报不要让显示端继续消耗调试时间。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价