尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
51单片机光照强度显示:光敏电阻与BH1750方案对比与实现
简介51单片机光照强度显示程序面向单片机初学者与嵌入式开发人员提供从传感器数据采集到LCD1602实时显示的完整方案。资源共23个文件、39KB核心代码包括main.c、lcd.c、i2c.c源程序以及lcd.h、i2c.h头文件还包含pro.hex烧录文件、STARTUP.A51启动代码和Keil工程文件可直接打开工程学习或烧录到开发板验证。程序实现了LCD1602液晶驱动、I2C接口光照传感器读取、ADC转换与亮度阈值提示覆盖寄存器选择、使能控制、数据线配置等底层细节并加入滤波与校准思路以提升测量稳定性。目前已有1249人学习代码结构清晰、注释明确适合课程设计、毕业设计或自学练手也可迁移至智能照明、农业环境监测等场景。1. 先把光照强度显示讲清楚读到的数字到底是不是 Lux“51单片机光照强度显示程序”常出现在课程设计和入门电子制作里。它要解决的问题其实很简单把环境光的强弱变成传感器信号再让单片机换算成照度单位 Lux送到 LCD 或数码管上显示出来。很多新手把模拟输入引脚直接读到的 0—255 当成光照强度结果开灯时数值变小、关灯时反而变大或者换块板子数据就不对了。这篇文章会沿着“传感器选型→电路→代码→校准→踩坑”这条线给出能直接照做的方案。适合手上有常见 51 内核单片机、能编写 C51 程序的读者哪怕刚接触 ADC 和 I2C也可以照着把程序跑通再逐步理解背后的换算关系。2. 光照强度显示的两条主流路线光敏电阻加 ADC还是数字光传感器2.1 光敏电阻加 ADC最便宜的模拟量方案光敏电阻的特点是暗处阻值很高强光下阻值很低。常见的硫化镉光敏电阻暗阻可以到几百千欧强光下可能只有一两千欧。这个阻值变化不能直接进单片机 GPIO因为 GPIO 只能识别高低电平读不出中间量。常见做法是把光敏电阻和一个固定电阻组成分压电路中间点接到 ADC 的输入引脚。比如光敏电阻接 VCC固定电阻接地那么光照越强、光敏电阻阻值越小分压点电压就越高。ADC 承担“把电压变成数字”的工作。经典 51 内核芯片大多不带片上 ADC所以需要外接。常见的 ADC 芯片有 ADC0832、PCF8591、XPT2046 等。ADC0832 是 8 位速度够用时序简单适合第一次做PCF8591 走 I2C 总线还能顺便学 I2CXPT2046 是 12 位精度更高但时序稍复杂。如果你只是想在数码管上显示个大概光照值8 位 ADC 已经够用毕竟光敏电阻本身的离散性和温漂比 ADC 分辨率带来的误差大得多。以 ADC0832 为例典型接线是 CS、CLK、DI/DO 三根线接单片机普通 IOCH0 接分压中点。换算流程是先读回 0—255 的 ADC 值除以满量程得到电压再根据分压公式算出当前光敏电阻阻值最后通过阻值—照度曲线或校准表得到 Lux。这条路线总成本最低也能帮你理解采样、参考电压、线性度这些概念缺点是每个光敏电阻都要单独校准否则显示值只能看趋势不能当准确照度用。2.2 数字光传感器 BH1750I2C 直接读 Lux如果你不想碰标定这件事BH1750 这类数字环境光传感器是更省心的选择。它把光敏二极管、积分放大、模数转换和 Lux 计算公式都封装在芯片内部单片机只需要通过 I2C 总线发几条命令就能读回 16 位照度数据分辨率可以到 1 Lux。对课程设计来说这几乎是“开箱即用”的方案。BH1750 只有 VCC、GND、SCL、SDA 四根核心引脚ADDR 引脚接地时7 位 I2C 地址是 0x23对应的写地址是 0x46、读地址是 0x47。如果 ADDR 接 VCC地址会变成 0x5C写地址 0xB8、读地址 0xB9。测量流程一般是这样先发 0x01 让芯片上电再发连续高分辨率模式命令 0x10等待 180ms 左右然后读两个字节合成后的数值除以 1.2 就是 Lux。这个 1.2 是芯片手册里的固定转换系数不是我们自己估计出来的。// 读取 BH1750 光照强度返回单位Lux unsigned short BH1750_ReadLux(void) { unsigned char b0, b1; unsigned short val; I2C_Start(); I2C_SendByte(0x46); // ADDR 接地时的写地址 I2C_SendByte(0x01); // 上电命令 I2C_Stop(); delay_ms(10); I2C_Start(); I2C_SendByte(0x46); // 写地址 I2C_SendByte(0x10); // 连续高分辨率模式分辨率 1 Lux I2C_Stop(); delay_ms(180); // 等待芯片完成一次测量 I2C_Start(); I2C_SendByte(0x47); // 读地址 b0 I2C_RecvByte(1); // 收到第一个字节后回 ACK b1 I2C_RecvByte(0); // 收到最后一个字节后回 NACK I2C_Stop(); val ((unsigned short)b0 8) | b1; return (unsigned short)((float)val / 1.2f); }这段代码里的 I2C_Start、I2C_SendByte、I2C_RecvByte 需要用 GPIO 模拟实现51 内核芯片即使没有硬件 I2C 也能跑。发送地址后要判断 ACK否则容易卡在等待上。180ms 延时是 BH1750 的测量周期改太短会读到上一次的数据。如果你用的是 0x20 单次测量命令读完一次后芯片会自动掉电适合低功耗场景但每次测量前都要重新发送测量命令。2.3 方案怎么选不是越贵越好对比项光敏电阻 ADCBH1750 数字传感器硬件成本很低几元到十几元稍高但仍有不少低成本模块精度低受元件离散性影响大高芯片内部已完成换算是否校准必须校准不需要占用引脚CS/CLK/DI 三根加一路模拟输入SCL/SDA 两根学习价值能学到分压、ADC、非线性换算能学到 I2C 协议适合场景课程设计、传感器原理实验快速做成品、智能家居项目我一般的建议是如果你是奔着“把流程跑通、能交差”去做的优先选 BH1750少走弯路如果你是想把 ADC、分压、标定这些概念都亲手实践一遍就做光敏电阻加 ADC。两条路线都逃不开两个共同问题一是采样值要稳定二是最终显示单位要是 Lux。下面我以光敏电阻加 ADC0832 加 LCD1602 为例把完整程序拆开讲。3. 手把手把程序跑通光敏电阻加 ADC0832 加 LCD16023.1 最小电路光敏电阻分压和 LCD1602 接线这个例子里的硬件组合很典型51 内核单片机负责控制ADC0832 负责采集光敏电阻分压点的电压LCD1602 负责显示。接线分成两部分先看 ADC 部分。ADC0832 的 CS 接 P1.0CLK 接 P1.1DI/DO 复用同一个引脚接 P1.2。分压点接 CH0VCC 和 GND 接电源。LCD1602 的数据口 D0—D7 接 P0.0—P0.7RS、RW、EN 分别接 P2.0、P2.1、P2.2。P0 口内部没有上拉电阻外部要接一个 10kΩ 排阻否则 LCD 容易花屏。分压电路建议这样接光敏电阻一端接 VCC另一端接 ADC 的 CH0 和固定电阻固定电阻另一端接 GND。固定电阻先选 10kΩ实际调试时可以换 20kΩ 或用电位器调到动态范围最合适。判断标准是在室内灯光下分压点电压大概在 1—2V用手电筒照时电压接近 3V 以上用手捂住光敏电阻时电压降到接近 0V。这个电压范围能保证 ADC 的 8 位分辨率不被浪费。器件引脚51 引脚说明ADC0832 CSP1.0片选低电平有效ADC0832 CLKP1.1时钟输入ADC0832 DI/DOP1.2配置时发数据读取时收数据ADC0832 CH0分压点光敏电阻和固定电阻的中点LCD1602 RSP2.0寄存器选择LCD1602 RWP2.1读写选择LCD1602 ENP2.2使能信号LCD1602 D0—D7P0.0—P0.7数据总线需要外部上拉上电后先用万用表量分压点电压确认电压随光照变化方向正确再接单片机。很多翻车事故都发生在电路阶段等代码写完再回头量电压反而要拆半天。3.2 读取 ADC 0832 并换算成光敏电阻阻值ADC0832 的时序不算复杂但网上代码版本很多。常见做法是先片选拉低然后在时钟配合下发送配置位起始位、单端模式选择、通道选择。这里用 CH0配置位依次是 1、1、0。之后 DI/DO 引脚释放进入输出状态在时钟下降沿或上升沿逐位读出 8 位数据。下面这段是实际可用的读取函数。sbit ADC_CS P1^0; sbit ADC_CLK P1^1; sbit ADC_DIO P1^2; // 读取 ADC0832 指定通道ch0 表示 CH0ch1 表示 CH1 unsigned char ADC0832_Read(unsigned char ch) { unsigned char i, dat; ADC_CS 0; ADC_CLK 0; ADC_DIO 1; // 起始位 1 ADC_CLK 1; ADC_CLK 0; ADC_DIO 1; // 单端模式 1 ADC_CLK 1; ADC_CLK 0; ADC_DIO ch ? 1 : 0; // 通道选择CH0 为 0CH1 为 1 ADC_CLK 1; ADC_CLK 0; ADC_DIO 1; // 释放数据线等待 ADC 输出 for (i 0; i 8; i) { ADC_CLK 1; ADC_CLK 0; dat (dat 1) | ADC_DIO; } ADC_CS 1; return dat; }这个函数返回 0—255 的采样值。使用时要注意ADC0832 的 DI 和 DO 是同一根线所以在配置阶段它是输入读到数据阶段要把它释放为输出。很多代码在配置完通道后忘了把 DI 拉高释放导致读取阶段一直读到 1 或 0显示值就固定在某一个数。读回来的 8 位数据是原码不需要额外反转。拿到 ADC 值后要先换成电压再算光敏电阻阻值。分压公式是光敏电阻在上、固定电阻在下时分压点电压等于 VCC 乘以固定电阻除以光敏电阻加固定电阻。反推光敏电阻阻值就是固定电阻乘以VCC 减分压电压再除以分压电压。代码里要处理分压电压等于 0 或接近 VCC 的极端情况否则除零会让程序跑飞。#define VREF 5.0f // 实测 VCC别盲信稳压输出 #define R_FIX 10000.0f // 分压固定电阻单位欧姆 // 根据 ADC 采样值计算当前光敏电阻阻值单位 kΩ float GetLightResistanceKohm(void) { unsigned char adc_value; float v_adc; adc_value ADC0832_Read(0); v_adc (float)adc_value * VREF / 255.0f; if (v_adc 0.01f) // 接近 0V可视为无光照或断路 return 999.0f; if (v_adc VREF - 0.01f) // 接近 VCC可视为短路或强光 return 0.1f; return (R_FIX * (VREF - v_adc) / v_adc) / 1000.0f; }这段代码把阻值单位换成干欧方便后面做 Lux 映射。VREF 一定要用万用表实测而不是直接写 5.0V。很多 USB 供电的板子实际电压只有 4.6V 左右写错会导致两块板子显示差异明显。R_FIX 也要和你实际焊的固定电阻一致最好用万用表量一下因为普通电阻误差就能到 ±5%。3.3 从电阻换算成 Lux先跑通波形再谈准确光敏电阻的阻值和照度不是线性关系而是近似指数关系。同一颗光敏电阻在不同照度下阻值可以差两个数量级。因此直接从电阻计算 Lux 的通用公式并不存在必须针对手头传感器校准。程序第一次跑通时可以先用分段查表的方式占位让显示值能随光照变化之后再用第 5 章的两点校准法替换。// 粗略映射正常室内日光灯下约 100-300 Lux手电筒直射约 5000 Lux // 注意这是临时查表正式使用前请做两点校准 unsigned int LuxFromResistance(float r_kohm) { if (r_kohm 100.0f) return 10; // 很暗 if (r_kohm 50.0f) return 30; // 黄昏 if (r_kohm 20.0f) return 80; // 室内暗角 if (r_kohm 10.0f) return 200; // 普通室内 if (r_kohm 5.0f) return 500; // 靠窗 if (r_kohm 2.0f) return 2000; // 强光 return 8000; // 直射光 }这个分段查表的边界值不用背重点是用它把“电阻变化”转成“光照值变化”的流程走通。调试时先看 ADC 原始值再看电阻值最后看输出 Lux。如果 ADC 值正常但 Lux 不变化说明查表没被调用如果电阻值算出来对但 Lux 一直落在某个分段说明固定电阻选得不合适导致采样值只在一个小范围内变。3.4 在 LCD1602 上显示光照值LCD1602 显示光照值最直接的方法是调用标准库函数把浮点数转成字符串。但 C51 编译器对浮点格式化的支持比较耗资源所以我会把 Lux 转成无符号整数再用自写的整数转字符串函数显示。这样代码更小也更容易控制显示位置。#include stdio.h // 把无符号整数转成字符串最多 5 位数字 void UintToStr(unsigned int val, char *buf) { unsigned char i; char tmp[5]; for (i 0; i 5; i) { tmp[i] (char)(0 val % 10); val / 10; } for (i 0; i 5; i) { buf[i] tmp[4 - i]; } buf[5] \0; } void DisplayLux(unsigned int lux) { char buf[6]; UintToStr(lux, buf); // 第一行显示 Lux: 00123 一样的效果 LCD_WriteCmd(0x80); // 设置 DDRAM 地址到第一行 LCD_WriteString(Lux:); LCD_WriteString(buf); LCD_WriteCmd(0xC0); // 第二行显示状态 LCD_WriteString(Light Sensor OK); }LCD_WriteCmd 和 LCD_WriteString 是 LCD1602 的底层驱动函数网上有很多成熟写法这里不再展开。要注意的是刷新频率不要太高。很多新手在主循环里不加延时把 LCD 刷个不停结果显示闪烁、字符错位。我一般会把显示刷新放到 500ms 一次或者只在数值变化超过 5 Lux 时才刷新这样眼睛看起来非常稳定。4. 光照强度显示调不通这 4 个高频坑按“现象-原因-解决”排查4.1 现象ADC 数值来回跳显示的光照强度像抽风用光敏电阻做采集时ADC 读数不稳定是很常见的。现象是 LCD 上最后一位数字不停乱跳手靠近传感器时更明显。原因有两层一是光敏电阻本身有响应噪声光线稍微抖动就会造成阻值波动二是 ADC 的参考电压来自主板稳压输出USB 供电时纹波大采样值自然跟着漂。解决方法是先软件滤波再考虑硬件滤波。软件上可以做“连续采 8 次取平均”或者用一阶递推滤波filter filter * 0.7 new_sample * 0.3。一阶滤波平滑效果好但响应会变慢适合光照变化不快的场景。硬件上在 ADC 输入引脚对地并联一个 0.1μF 陶瓷电容滤掉高频噪声。不要靠拉长延迟来假装稳定那样只会掩盖问题。4.2 现象光照越强显示反而越小甚至一直是 255这个坑我第一次做时也踩过。现象是拿手电筒照光敏电阻LCD 上 Lux 值反而下降或者一直满量程。原因通常是把分压电阻位置接反了。如果光敏电阻接地、固定电阻接 VCC那么光照越强光敏电阻阻值越小分压点电压就越低ADC 值变小显示值自然反着走。解决方法是先确认电路拓扑光敏电阻上 VCC固定电阻下地分压点接 ADC。用万用表量分压点电压暗处低、亮处高再继续调软件。另一个常见情况是固定电阻太大导致强光下光敏电阻阻值小于固定电阻分压电压已经接近 VCCADC 提前饱和。把固定电阻换成 10kΩ 电位器边调边看电压找到在目标光照范围内变化最明显的阻值。4.3 现象同一个光照下两块板子显示值差很多如果你把程序从一块板子烧到另一块板子发现显示值差 50% 以上不要先怀疑光敏电阻坏了。原因一般是参考电压没校准。很多稳压模块实际输出电压不是标准的 5.00V4.7V 或 5.2V 都很正常。代码里如果把 VREF 固定写成 5.0fADC 值相同对应的电压就不一样算出来的阻值当然也不一样。解决方法是拿到板子先量 VCC实测多少就写多少进宏定义。光敏电阻的离散性也很大同一型号不同个体在相同光照下阻值可能差 30%。所以光敏电阻方案在批量使用时必然要做逐个校准。如果不想校唯一的办法是换 BH1750 这类数字传感器芯片出厂时已经把校准值固化在内部。4.4 现象LCD 显示乱码、缺笔画或者主循环跑起来后亮度值不更新LCD 乱码大多出现在第一次上电时屏幕上出现一片方块或随机字符。原因多数是 P0 口没有上拉导致数据总线电平不稳定也有可能是 LCD 初始化时序太短芯片还没准备好就开始写数据。解决方法是P0 口接 10kΩ 上拉排阻初始化里在延时和命令之间加足够的时间通常每个命令之后延时 5ms 以上尽量做忙检查在写数据前确认 BF 位不为 1。亮度值不更新则往往是刷新逻辑的问题。程序主循环里每次都调用显示函数但又没有清除残留字符或者用了 delay 阻塞导致看起来像“卡死”。我习惯把“采样、计算、显示”拆成三个独立步骤显示函数内部先清屏再写新内容并且刷新间隔控制在 500ms 左右。这样即使采样偶尔抖动显示也不会跟着闪。5. 两点校准与弱光强光提示让显示程序真正能日常用5.1 两点校准法没有照度计也能标出 Lux分段查表能跑通流程但显示值不是真实照度。要变成可用的 Lux 显示推荐做两点校准。找个手机光线传感器 App能显示大致 Lux 值虽然精度有限但作为参照足够。先在室内灯光下记录一个点读当前光敏电阻值 R1 和手机照度 L1再用手电筒近距离照传感器记录第二个点 R2 和 L2。假设阻值 R 和照度 L 满足指数关系 L a 乘以 R 的 b 次方那么可以算出b ln(L1 / L2) / ln(R1 / R2) a L1 / (R1 的 b 次方)然后把 a、b 写死在程序里替换掉之前的查表函数。// 两点校准参数单位R 为 kΩLux 为 lx // 例子暗处 20k 对应 50Lux强光 3k 对应 3000Lux #define CAL_A 135.0f #define CAL_B -0.85f unsigned int LuxFromResistanceCal(float r_kohm) { float lux; if (r_kohm 0.5f) r_kohm 0.5f; // 防止数值过小导致结果溢出 lux CAL_A * pow(r_kohm, CAL_B); if (lux 0) lux 0; if (lux 65535) lux 65535; return (unsigned int)lux; }校准后如果显示值与参照偏差大别急着改参数先确认两个采样点是否覆盖了实际使用环境。两点校准只在这两个点附近比较准离得越远误差越大。想要更宽范围可以增加采样点用分段指数插值但课程设计里两点基本够用。5.2 显示里加上光照档位读起来更直观Lux 数字是给工程师看的非专业用户更习惯“暗、正常、强光”这样的概念。可以在 LCD 第二行把光照强度映射成档位低于 30 Lux 显示“Dark”30—300 Lux 显示“Normal”高于 300 Lux 显示“Strong”。这样整个显示程序就不再只是“测了个数字”而是一个能直接看懂实际环境状态的装置。5.3 一个让我少走弯路的小习惯我自己调试这类程序时习惯先让串口或 LCD 单独显示 ADC 原始值确认采样链路正常后再切换到换算后的 Lux。很多翻车问题其实都出在“显示模块正常、传感器接线正常但中间换算写错了”这种情况。比如计算时把分压公式方向搞反或者忘记把千欧单位换算进去。先看原始值再算电阻最后换 Lux这个顺序能帮你快速定位是哪一步出了问题。校准参数也别一次到位先用临时查表跑通再用两点校准替换。希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

茶叶萃取程序:用代码建模水温时间与茶多酚析出量

茶叶萃取程序:用代码建模水温时间与茶多酚析出量

先声明一下:这篇文章不是“科普茶学理论”,而是我从一个写程序、又真的每天泡茶喝的人的角度,去梳理一遍“茶叶萃取程序”这件事。标题里那几个关键词——水温、时间、茶多酚析出量、口感、健康浓度——看着像是实验室课题,实际上…

📅 2026/10/15 19:50:07
计算机组成原理课设:从指令集到数据通路的CPU设计实战指南

计算机组成原理课设:从指令集到数据通路的CPU设计实战指南

简介:面向山东大学20级计算机专业学生的《计算机组成原理》课程设计资料,围绕电路图设计与命令集定义展开,内容涵盖数据表示、运算器、控制器、存储系统等核心知识点,并涉及原理图绘制、指令集设计、实验报告撰写等完整环节&#…

📅 2026/10/15 19:45:04
两串锂电池均衡实战:MP2642GR-Z与PIC32MZ1024EFK144的软硬协同设计

两串锂电池均衡实战:MP2642GR-Z与PIC32MZ1024EFK144的软硬协同设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/15 19:45:04
MORE NEWS

更多资讯

📰

边缘-云协同架构下低延迟数据传输优化实践

我们团队在推进模拟项目X的过程中,面临过一个很现实的性能瓶颈:边缘端的数据要全部汇聚到云端处理,再决定是否下发控制指令。当节点数量从几十扩展到几百,链路波动、带宽占用、决策时延这些老问题全部被放大。后来我们花了大概三个…

📰

Python电磁场可视化实战:从电偶极子到射频调试

1. 这不是物理课,是“电磁场可视化”实战手记你有没有过这种体验:翻开《电磁场与电磁波》教材,第一页就看到麦克斯韦方程组的积分形式和微分形式并排而立,旁边还配着矢量算符∇、旋度curl、散度div——字都认识,连起来…

📰

博彦科技搜狗外包面试全解析:流程、技术考点与避坑指南

“博彦科技-搜狗面试”这个标题,在求职圈里一眼就能看懂:这是一条外包驻场的招聘链路。博彦科技作为人力外包服务商,把候选人派遣到搜狗的项目组,负责具体的开发、测试或运营类工作。很多同学第一次收到这种面试邀请时会一头雾水&…

📰

docker-lambda 的 Dump Runtime 项目实战:在 AWS Lambda 内打包运行环境并回传 S3

开发工具后端测试 【免费下载链接】docker-lambda Docker images and test runners that replicate the live AWS Lambda environment 项目地址: https://gitcode.com/gh_mirrors/do/docker-lambda 点击查看 免费下载 本篇技术指南围绕 docker-lambda 仓库中 base/…

📰

NGO-GRNN多特征回归预测:北方苍鹰算法优化平滑因子实战

开头先亮个场景:我最近在多特征拟合预测上栽了个跟头,输入特征十几个、输出连续值、数据噪声还带漂移,一开始用GRNN(广义回归神经网络)直接跑,结果曲线该尖的地方钝、该平的地方抖,怎么看都别扭…

📰

流放之路 Path of Building 完全上手指南:5 个阶段跑通你的第一个 Build,天赋树与伤害计算一次讲清

流放之路 Path of Building 完全上手指南:5 个阶段跑通你的第一个 Build,天赋树与伤害计算一次讲清 【免费下载链接】PathOfBuilding Offline build planner for Path of Exile. 项目地址: https://gitcode.com/GitHub_Trending/pa/PathOfBuilding …

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬