尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

51单片机光照强度显示程序:BH1750与LCD1602实战解析

51单片机光照强度显示程序:BH1750与LCD1602实战解析 简介51单片机光照强度显示程序是一份适合嵌入式入门开发者与电子爱好者的完整工程解决如何通过51单片机读取光照传感器信号并借助LCD1602液晶屏实时显示环境光照强度的问题。程序涉及ADC模数转换、I2C总线通信、液晶驱动时序控制等多个知识点代码注释简洁、模块划分清晰。压缩包共23个文件大小仅39KB以main.c、lcd.c、i2c.c等C语言源码为主体配套lcd.h、i2c.h头文件及STARTUP.A51启动文件同时提供pro.uvproj工程配置、pro.hex可直接烧录的机器码文件以及编译生成的lst、obj、m51等辅助文件方便对照学习或二次开发。目前已有1246人学习下载用户可基于源码中的滤波处理、校准算法与光照阈值提示逻辑进一步拓展到智能照明、农业环境监控等场景。 很多人在流水灯和数码管之后第二个想上手的传感器项目往往就是它51单片机光照强度显示程序。这个项目听起来简单真正做起来却绕不开一串问题光敏电阻的阻值怎么变成单片机认识的数字读到的原始量怎么换算成勒克斯如果换数字传感器BH1750I2C时序又该怎么调再加上LCD1602本身还有对比度、4线模式初始化这些坑光是让屏幕稳定显示一个可信的亮度值就能卡住不少人。这篇文章从我实际做过的方案出发以数字光照传感器BH1750为主线把硬件接线、I2C读写、数据换算、显示刷新、常见故障排查全部拆开讲同时给出“光敏电阻ADC0832”模拟方案的接线和要点方便想学ADC原理的同学对照。打算做课程设计、电子竞赛或者单纯想把传感器采集链路搞明白的都可以参考。1. 项目需求拆解光照检测不是“量个亮度”那么简单1.1 光照强度这个连续量怎么变成单片机认识的数字51单片机判断外界信号只有两种状态引脚上是高电平还是低电平。开关、按键、红外对射接收头这类数字量信号直接读IO就行但“光照强度”是模拟量它在现实世界中连续变化电压和阻值都不是规整的0或1单片机不能直接处理。所以光照检测的第一件事是把光信号变成电信号再把电信号量化成数字。常见做法有三种光敏电阻光线越强阻值越小。它必须和一个固定电阻串联分压把阻值变化转换成0~5V的电压再送进ADC芯片量化。光敏二极管/三极管输出电流随光照变化通常要加运放转换电压电路复杂一些。BH1750数字光照传感器内部集成了光电二极管、放大电路、ADC和I2C接口上电后直接输出16位二进制数据按公式换算成以“勒克斯”为单位的照度值。用生活类比来说光敏电阻像水龙头水流大小取决于光线你需要在旁边装一块固定挡板量挡板两侧的压力差来判断水流BH1750则像一块自带刻度和屏幕的水表直接把流量读数告诉你。对于做“显示程序”来说BH1750省去了复杂的标定流程这是它成为主方案的根本原因。1.2 显示端选型为什么又是LCD1602显示端也有几个选择数码管只能显示数字显示“lx”单位很尴尬还要维护段码表点阵屏适合做动态图形和滚动效果做数字显示有点大材小用OLED效果最好但51驱动OLED要自己实现画点、字库难度陡增。LCD1602是字符型液晶2行16列能把“Light: 123 lx”这种完整句子直接打在屏幕上。更重要的是LCD1602在51生态里太成熟了。万年历、电子时钟、电子秤、温度报警器这些经典项目全都在用无论你遇到什么奇怪故障几乎都能搜到对应案例。所以这个项目我选LCD1602做显示并且用4线模式来接目的是省出4个IO口给其他外设。1.3 两种方案的选型对比表方案主要器件输出形式是否需要校准代码复杂度适合场景数字方案BH1750模块I2C输出16位直接算lx不需要中等要理解I2C时序课程设计、需要明确照度值模拟方案光敏电阻ADC08328位ADC范围0~255必须自行标定较低时序简单学习ADC原理、定性判断亮度为了兼顾“能出结果”和“能学原理”后面主线展开BH1750方案同时补充模拟方案的接线和坑。核心的“采集—处理—输出”思想是一致的换传感器只是换驱动层。2. 硬件电路搭建两种方案的引脚连接与关键细节2.1 元器件清单硬件部分很简单不需要额外买太多东西51单片机开发板或最小系统板常见STC89C52RC晶振11.0592MHz或12MHz都行。BH1750光照传感器模块。LCD1602液晶屏加10K电位器。杜邦线若干最好再备一块面包板做扩展。如果走模拟方案光敏电阻5516/5506比较常见、10K定值电阻、ADC0832模块。不管你用的是江科大那类开发板还是金沙滩的板子引脚定义都要以自己板子上的原理图为准。下面给的接线是针对标准开发板缺省引脚的示例实际使用时要灵活调整。2.2 数字方案BH1750与51单片机的接线表BH1750引脚51单片机引脚说明VCC5V模块带稳压电路时可直接接5VGNDGND共地SCLP3.6I2C时钟线SDAP3.7I2C数据线ADDRGND设备地址选择为0x23这里有两个细节值得说。第一为什么选P3.6/P3.7而不是P1口因为很多开发板的P1口已经被LED、矩阵键盘占用了P3.0/P3.1要留给串口P3.2/P3.3是外部中断入口P3.6/P3.7正好空出来做I2C。当然这只是硬件资源规划的习惯你用P1口模拟I2C一样能跑通。第二BH1750裸芯片的工作电压是2.4V~3.6V但市面上绝大多数模块都带稳压和上拉电路5V供电没问题。如果在洞洞板上自己焊裸芯片先确认模块背面没有稳压芯片再上电否则芯片发热、读数异常甚至直接烧毁。2.3 显示模块LCD1602的4线接法LCD1602引脚51单片机引脚说明VSSGND电源地VDD5V电源正VO电位器中间脚对比度调节RSP2.0寄存器选择RWP2.1读写选择ENP2.2使能脉冲D4~D7P2.4~P2.74位数据线A5V背光正极KGND背光负极RW引脚可以直接接地因为本项目只写不读。但建议还是用IO口控制方便以后扩展读忙标志。4线模式只需要接高4位数据线命令先送高4位再送低4位能省出P2.3和P0口全部引脚。VO接电位器这个细节非常关键。新买的1602上电后屏幕全黑十有八九不是接错线而是对比度电位器拧在了不合适的位置。把电位器从一端缓缓拧到另一端会看到屏幕从全黑逐渐过渡到出现清晰字符。有条件的可以用万用表量一下VO电压在0.4V~1V左右时字迹最清楚。2.4 模拟方案光敏电阻ADC0832的接法ADC0832引脚51单片机引脚功能CSP1.0片选CLKP1.1时钟输入DI/DOP1.2数据输入/输出共用引脚CH0光敏分压中间点通道0模拟输入光敏电阻分压电路这样搭光敏电阻一端接VCC另一端与10K定值电阻串联到GND两个电阻的中间节点接ADC0832的CH0。光照越强光敏电阻阻值越小节点电压越低光照越弱阻值越大节点电压越高。建议在CH0对地并联一个0.1uF陶瓷电容过滤高频干扰否则读数容易跳。如果做这个模拟方案10K定值电阻不是固定的要根据实际环境光照范围调整。可以用电位器代替定值电阻调到一个采样值变化最明显的阻值位置再换成同阻值的固定电阻。3. 核心代码实现从I2C时序到LCD显示的完整通路3.1 工程拆分不要把全部代码堆进main.c51程序不大但照样建议拆文件。我在工程里建了lcd1602.c、bh1750.c、main.c三个模块。模块化的好处是出问题容易定位屏幕没显示基本不用看bh1750.c读数全是255问题多半在传感器驱动或接线不会去翻LCD代码。以后想把BH1750换成其他传感器或者把LCD1602换成OLED只改对应模块主程序基本不动。3.2 LCD1602驱动4线模式的初始化顺序LCD1602初始化顺序是最大的坑先看代码void Lcd1602_Init(void) { Lcd_Delay(1000); // 等待液晶上电自检 Lcd1602_WriteCmd(0x30); Lcd_Delay(500); Lcd1602_WriteCmd(0x30); Lcd_Delay(500); Lcd1602_WriteCmd(0x30); Lcd_Delay(500); Lcd1602_WriteCmd(0x20); // 切换为4线模式 Lcd1602_WriteCmd(0x28); // 2行显示5x7点阵 Lcd1602_WriteCmd(0x0C); // 开显示关光标 Lcd1602_WriteCmd(0x01); // 清屏 Lcd_Delay(1000); Lcd1602_WriteCmd(0x06); // 地址指针自动加1 }为什么前面要连续发三个0x30因为LCD1602上电后默认认为自己工作在8线模式如果硬件只接了D4~D7直接发0x28这种4线命令它是听不懂的。必须先用8线模式命令把它唤醒再发0x20切换成4线模式最后才是功能设置。这个顺序错一步屏幕上就会出现乱码或者只有第一行有反应。3.3 BH1750驱动I2C时序代码逐段解读STC89C52没有硬件I2C外设所以只能用软件模拟。BH1750的7位地址由ADDR引脚决定ADDR接GND时地址是0x23左移一位后写地址是0x46读地址是0x47。核心代码void I2C_Start(void) { SDA 1; SCL 1; I2C_Delay(); SDA 0; I2C_Delay(); SCL 0; } void I2C_Stop(void) { SDA 0; SCL 1; I2C_Delay(); SDA 1; I2C_Delay(); } void I2C_WriteByte(unsigned char dat) { unsigned char i; for(i 0; i 8; i) { SDA (dat 0x80) ? 1 : 0; I2C_Delay(); SCL 1; I2C_Delay(); SCL 0; dat 1; } SDA 1; // 释放SDA等待从机应答 SCL 1; I2C_Delay(); SCL 0; }I2C的起始条件是SCL高电平期间SDA产生下降沿停止条件是SCL高电平期间SDA产生上升沿。写字节时数据在SCL高电平期间必须保持稳定所以在SCL拉高之前先把SDA设置好这个先后顺序不能反。BH1750的命令不多这个项目只用到两条0x01上电0x10连续高分辨率模式。连续高分辨率模式分辨率是1lx测量时间约120ms。初始化函数void BH1750_Init(void) { BH1750_WriteCmd(0x01); // 上电 BH1750_WriteCmd(0x10); // 连续高分辨率模式 }读取光强的函数unsigned int BH1750_ReadLux(void) { unsigned char high, low; unsigned int lux; I2C_Start(); I2C_WriteByte(0x47); // 读地址 high I2C_ReadByte(); // 读高字节 I2C_SendAck(0); // 发送应答 low I2C_ReadByte(); // 读低字节 I2C_SendAck(1); // 发送非应答 I2C_Stop(); lux (high 8) | low; lux lux * 10 / 12; // 除以1.2整数运算 return lux; }换算公式是厂商手册规定的高分辨率模式下寄存器值除以1.2就是勒克斯。这里我特意不用浮点除法因为51内核没有硬件浮点单元调用float库会占用几千字节Flash运行速度也慢。用整数运算lux * 10 / 12结果一样代码却小得多。3.4 主循环与显示格式好读比炫技重要主程序逻辑很直接void main(void) { unsigned int lux; Lcd1602_Init(); BH1750_Init(); Lcd1602_ShowString(0, 0, Light: ); while(1) { lux GetAverageLux(10); DisplayLux(lux); DelayMs(500); } }显示函数有个小讲究。LCD1602显示过的字符不会自动清除如果上一次显示“1234 lx”这次变成“5 lx”屏幕上会残留“34”之类的残影。所以显示时要固定输出4位数字单位后面再补两个空格清残影void DisplayLux(unsigned int lux) { unsigned char digit[4]; digit[0] 0 lux / 1000 % 10; digit[1] 0 lux / 100 % 10; digit[2] 0 lux / 10 % 10; digit[3] 0 lux % 10; if(lux 1000) digit[0] ; if(lux 100) digit[1] ; if(lux 10) digit[2] ; Lcd1602_SetCursor(1, 0); Lcd1602_WriteData(digit[0]); Lcd1602_WriteData(digit[1]); Lcd1602_WriteData(digit[2]); Lcd1602_WriteData(digit[3]); Lcd1602_ShowString(1, 4, lx ); }刷新间隔控制在500ms。光照变化本来就是缓慢过程人眼不需要50ms一刷。刷新太快反而会让字符抖动闪烁看起来非常廉价。3.5 软件滤波为什么读数一直在跳光照值跳动有三个来源光照本身波动日光灯有100Hz频闪、电源纹波、LCD和传感器共用电源带来的干扰。数字传感器BH1750还算稳定模拟方案会更明显。解决办法是软件滤波unsigned int GetAverageLux(unsigned char times) { unsigned long sum 0; unsigned char i; for(i 0; i times; i) { sum BH1750_ReadLux(); DelayMs(15); } return (unsigned int)(sum / times); }连续读10次取平均显示立即稳很多。注意BH1750的测量时间是120ms如果循环读太快两次读数之间测量还没完成读到的就是旧数据看上去忽大忽小。加延时不仅是为了滤波也是给传感器足够的测量时间。4. 调试验证与踩坑实录从乱码到0xFF的完整排查链路4.1 LCD1602全黑或全是方块时先查这三处第一处是VO对比度。新屏的电位器可能在一个极端位置导致屏幕全黑。转动10K电位器直到看见清晰字符这个问题五秒钟就能排除。第二处是初始化顺序。4线模式必须按“0x30三次到0x20再到0x28”的顺序来缺少前面三个0x30就会出现乱码或只有第一行有反应。这个在上面代码里已经强调过。第三处是EN引脚脉冲宽度。LCD1602像一台老式打字机EN引脚是空格键只有按的时间足够长它才来得及把字符打上去。很多示例代码里EN拉高时间只有几个空指令在12MHz主频下勉强能跑如果你换了大屏、长线或者仿真器就可能采样不到数据。把写命令函数里EN拉高时间加长到50us左右问题基本能解决。4.2 BH1750读回0xFF的完整排查过程有次我调板子BH1750读回来的一直是0xFF。第一反应是I2C时序错了对着手册改了好几次起始条件和延时都没用。后来用万用表量SDA和SCL的静态电压发现SDA一直低、SCL正常高顺着杜邦线一查才发现SDA插到了P3.6SCL插到了P3.7跟代码里定义正好反了。这个错误之所以隐蔽是因为I2C是双向线SDA上一直有从机拉低的信号从波形上看又像是有应答特别容易让人误以为是时序问题。后来我长记性上电之前一定先对着接线表逐根确认而不是急着改代码。排查步骤总结一下先量模块VCC对地电压确认是5V或3.3V方案排除供电不足。写一段测试代码让SCL置高、SDA置高用万用表量对应引脚电压确认引脚定义和实际接线一致。再测起始条件执行I2C_Start后SDA先从高变低如果没有跳变说明电路连接或IO定义有问题。最后才扫I2C地址向0x46发写命令后对应ADDR接GND的写地址用示波器观察第9个时钟是否有低电平应答位。如果手头有逻辑分析仪直接抓波形是最快的没有的话万用表电压法也能解决大部分问题。提示如果你是在Proteus里做仿真BH1750的仿真模型不一定有。想先验证显示逻辑可以用ADC0832方案代替或者在开发板上直接实测不要过分依赖仿真。4.3 数字跳变先别怀疑传感器检查电源和刷新策略读数跳变时很多人的第一反应是传感器坏了其实问题往往出在三个方面。光照本身就在波动窗户附近的自然光、日光灯的频闪都会让传感器采到变化开发板从USB取电又带LCD又带传感器5V纹波可能偏大杜邦线过长或接触不良也会引入噪声。解决的顺序不要乱先加软件滤波再看刷新间隔最后换独立5V电源或理顺杜邦线。实测下来滤波和刷新间隔两个措施就能让显示稳定很多。如果数据还是跳把USB供电换成DC电源适配器或者给模块单独供电并共地问题基本能解决。4.4 模拟方案ADC0832的特别提醒如果走光敏电阻加ADC0832的路线最坑的是DI和DO共用一根引脚。51的准双向IO在读取外部信号前必须先向引脚写1把内部上拉打开。很多照抄代码的人容易漏掉这一步导致读到的永远是0或者乱跳。通道选择的时序也容易错START之后第一个时钟下降沿选择CH0还是CH1第二个下降沿选择单端还是差分模式顺序反了数据完全不对。这个方案的时序相比BH1750的I2C更繁琐新手如果调不出来建议先换BH1750把整个流程跑通等把ADC原理学明白再回来调。5. 进阶方向从“显示程序”到“闭环控制系统”5.1 阈值控制让光照值自己去开灯显示只是第一步加上阈值判断就能变成自动控制。在while循环里加几行代码if(lux 30) LED 1; // 太暗打开补光灯 else LED 0;把LED换成继电器就能控制真正的照明设备。这个方向本质上是“传感器采集→逻辑判断→执行输出”的闭环和智能窗帘、智能灯光控制是同一套思路也是从“显示程序”升级为“控制系统”的最短路径。5.2 串口上报与数据可视化把光照值通过串口发到上位机是排查问题的最好手段。初始化串口波特率9600直接printf(lux%d lx\r\n, lux)用串口助手就能看到数据曲线。这个功能的额外价值是当LCD显示异常时用串口看数据可以迅速判断问题在传感器还是显示端不用拿着万用表到处戳。5.3 怎么让这个项目在答辩或汇报时更好讲如果这是课程设计建议至少包含三个功能点实时显示、越限报警、串口上报。再手工做一张照度等级说明表比如办公室环境通常是300~500lx户外阳光在10000lx以上答辩时能把显示数值的实际意义讲清楚评委印象分会高不少。再进阶一点可以加两个按键调节阈值第二行显示“TH: xxx lx”项目就有了参数设置功能。把BH1750的数据同时发给OLED和LCD或者加上蓝牙模块让手机看数据都是比较出彩的扩展方向。最后分享一个小技巧LCD1602的刷新不要放在while循环里裸奔。我第一次做的时候每次读完光强立刻写屏结果字符一直在抖看着非常廉价。后来把刷新间隔控制在500ms才稳定下来。光照变化本来就是缓慢过程人眼根本不需要50ms一刷。同样是显示程序刷新策略、格式化输出和滤波处理才是拉开差距的地方。希望这篇能把你的51单片机光照强度显示程序一次调通少走我当年走过的弯路。本文还有配套的精品资源点击获取
返回列表