
简介一款面向51单片机学习者和嵌入式开发入门者的光敏电阻测试与数码管显示源码工程完整演示了从传感器信号采集到显示输出的光强监测流程适用于环境光检测、智能家居、电子课程设计等场景。工程共23个文件包含C语言源程序、头文件、Hex烧录文件及Keil工程配置等整体压缩包仅36KB结构精简便于快速下载和直接烧录到开发板验证。程序针对光敏电阻阻值随光照强度变化的特性通过ADC采样将模拟信号转换为数字值再经数码管动态扫描实时显示当前光照强度源码中分离了系统初始化、ADC采样、数码管驱动和主循环等模块代码层次清楚方便逐段学习51单片机的I/O操作、定时器应用和模块化编程思路。目前已有417人学习下载尤其适合想通过实际硬件项目巩固单片机编程知识的初学者。借助这份资料读者可以快速跑通一个完整的光感显示实例并可将其中ADC采集和数码管动态扫描逻辑迁移到其他嵌入式设计中作为智能感光或环境监测类项目的代码参考。1. 光敏电阻测光的原理51单片机怎么选ADC拿到一个光敏电阻手头又没有照度计最原始的办法是拿万用表电阻挡去量但表笔接触电阻和手持遮挡都会让读数跳来跳去。更麻烦的是光敏电阻的阻值与照度成非线性关系量出来的几K几M只是“当下的阻值”没法直接回答“当前环境到底算亮还是暗”这个问题。把它接进51单片机系统里做成一个数码管显示的测试仪本质是把电阻变化变成电压变化再让单片机读出这个电压并换算成可读的数值。对51单片机这个平台选ADC方案时往往有两个岔路一是用ADC0804这类并行接口的独立8位ADC芯片二是直接用STC15、STC12系列内部集成的ADC模块。前者适合手头只有89C52、学习板资源有限的情况接线直观、时序清晰课程设计和入门调试都愿意走这条路后者省掉一颗芯片但寄存器配置和参考电压调整对新手来说稍显隐晦。这篇内容以一个最常见的外挂ADC0804方案为主线把分压电路、驱动代码、数码管显示和校准换算整个链路讲完整手上有带内部ADC的单片机也能在每一章对照着改因为原理是一样的先建立电压信号再完成采样最后做标定显示。2. 硬件连接与信号准备光敏电阻分压怎么搭2.1 光敏电阻的亮电阻、暗电阻与分压输出范围光敏电阻是半导体材料做成的体电阻光照越强内部激发的载流子越多阻值下降。它没有极性正反接都行这一点跟光敏二极管、光敏三极管完全不同很多第一次搭电路的人把极性搞错实际只要确认分压关系即可。常用的5516系列光敏电阻在典型照度下的参数大致如下具体值因批次和封装差异会浮动做设计时按数量级取就行。参量典型值说明亮电阻10 Lux510 KΩ室内正常照度附近暗电阻0 Lux0.5 MΩ以上完全遮挡后测量响应时间2050 ms阻值随光照变化的过渡时间光谱峰值540 nm左右对可见光中绿色波段最敏感把光敏电阻和一只固定电阻串联接在5V电源与地之间中间抽头就是ADC的输入电压。假设光敏电阻在下、固定电阻10K在上暗环境下光敏电阻趋向0.5MΩ以上抽头电压接近0V强光下光敏电阻降到25K抽头电压往2.5V以上抬升。这个0到接近5V的电压范围正好适合8位ADC的输入要求。固定电阻的取值很关键。取10K可以在室内正常光照下让分压点落在2V到3.5V区间分辨率最高如果测试的场景是强光或者弱光占主导可以把固定电阻换成1K或100K把ADC输入区间压到对应电阻变化最剧烈的段落。这是后面量程归一化能做好看的硬前提。2.2 ADC0804与P0口的接法共阴数码管怎么连ADC0804是8位并行ADC转换时间约100us对光敏电阻这种慢变信号绰绰有余。它的数字输出是8根数据线直接接51的P0口。P0是开漏结构必须外接4.7K10K上拉排阻否则读回的数据全是0xFF或者乱跳这是新手最容易栽的地方。连接方式一般是ADC0804的Vref/2引脚接2.5V可以用两个10K电阻从5V分压得到如果不接芯片内部默认参考电压也是按5V满量程工作的但精度会受影响。CLK引脚通过RC振荡产生内部时钟典型接法是一个150pF电容加一个10K电阻大约能跑出640KHz左右的时钟。INTR引脚接P3.2转换完成后会拉低单片机查询这个引脚就能确定数据是否就绪。数码管用共阴四位数码管做动态扫描。段选线经限流电阻接P0口会跟ADC0804的数据线冲突所以段选用P1口或者P2口高四位都行。比较省事的分法ADC0804的DB0DB7占P0数码管段选接P1位选接P2.0P2.3。动态扫描每隔几毫秒刷新一位靠人眼视觉暂留显示完整的四位数。若用带内部ADC的STC15系列光敏分压点直接进P1口的ADC输入引脚即可省掉并行总线的占用代码思路完全不变只是把读写时序换成ADC_CONTR寄存器的操作。2.3 为什么不用RC振荡测频率的方案市面上还有一种省ADC的做法把光敏电阻和电容组成RC振荡器光照改变电阻充放电时间改变用51的定时器测出频率反推阻值。这个方案只用一个定时器引脚成本更低但实际用起来温度漂移比较大电容的容值误差和老化都会混进结果里而且测一次频率至少要几十毫秒动态响应比ADC方案慢得多。相比之下ADC方案直接读电压标定时只需关心分压网络的比例温度影响集中在光敏电阻本身的温漂上。对“测试光敏电阻”这个目标来说ADC方案更直白调试手段也更丰富用万用表量ADC输入引脚就能校验前端电路是否正确。3. 读取光敏电压ADC0804与51单片机定时器采样的代码实现3.1 工程里的最小代码骨架工程文件在Keil C51里新建芯片选AT89C52代码组织成三部分ADC0804驱动、数码管驱动、主循环调度。下面是去掉无关功能后的最小骨架可以直接拷进工程里验证。#include REGX52.H #include intrins.h sbit DC_CS P3^5; // ADC0804 片选 sbit DC_WR P3^6; // 写信号启动转换 sbit DC_RD P3^7; // 读信号输出数据 sbit DC_INTR P3^2; // 转换完成标志低电平有效 #define ADC_DATA P0 // 数据口接P0P0需外加上拉电阻 unsigned char code SEG_CODE[] { 0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F, 0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71 }; // 共阴数码管0-F段码 void Delay1ms(unsigned int t) { unsigned int i, j; for (i 0; i t; i) for (j 0; j 123; j); } void ADC_Start(void) { DC_CS 0; // 片选拉低 DC_WR 0; // WR低脉冲启动转换 _nop_(); DC_WR 1; DC_CS 1; } unsigned char ADC_Read(void) { unsigned char val; DC_CS 0; DC_RD 0; // RD拉低数据输出到P0 _nop_(); val ADC_DATA; // 读P0 DC_RD 1; DC_CS 1; return val; } void main(void) { while (1) { ADC_Start(); // 启动一次转换 while (DC_INTR 1); // INTR变低表示转换完成 // 采样值暂存在P1或内部变量后续章节处理显示 } }代码里ADC_Start和ADC_Read的时序是配合ADC0804数据手册来的。启动转换时只要WR引脚来一个低电平脉冲INTR会先变高转换完成后拉低。查询INTR而不是固定延时能在时钟偏差时依然保证读到的数据是准确的。读数据时RD低电平把三态输出打开数据出现在P0上读完立刻拉高释放总线避免和后续的数码管操作争抢P0。这个最小骨架不做任何数据处理先确认ADC能稳定读到值再往下走。如果P0显示常为0xFF先量P0上拉电阻有没有焊如果INTR一直不拉低检查CLK引脚的RC振荡是否起振用手摸一下芯片温度异常升温就是接反了电源。3.2 读ADC0804的关键时序参数转换时序里有三个时间参数需要留意写代码时用_nop_()或者短延时满足它。参数最小值说明WR脉冲宽度100 ns实际51指令周期远大于此INTR拉低到RD拉低间隔0查询到后可直接读RD低电平持续时间200 ns保证三态门完全打开12MHz晶振下单条指令周期约1us直接写_nop_()就够用了不需要像驱动高速ADC那样用定时器卡纳秒级时序。切记不要在WR还处于低电平时就去读数据那是启动转换的过程读到的可能是上一次残留值。3.3 定时采样与均值滤波数据抖动的处理光敏电阻在室内照明下本身就有微小波动荧光灯的100Hz频闪、人手在传感器附近的晃动都会让ADC读数在几个LSB之间跳。观察数码管时表现为最后一位不停乱跳解决方案不是提高采样频率而是降低采样频率并做滤波。在51这种慢速平台上常见做法是每50ms采一次连续采4次去掉最大值和最小值后对中间两个数取平均。这个方法在信号处理里叫中位值平均滤波法对付偶发尖峰特别有效而且对光敏电阻这种变化缓慢的信号不会造成明显滞后。#define FILTER_NUM 4 unsigned int Get_ADC_Filtered(void) { unsigned char buf[FILTER_NUM]; unsigned char i, j, tmp; unsigned int sum 0; for (i 0; i FILTER_NUM; i) { ADC_Start(); while (DC_INTR 1); buf[i] ADC_Read(); } // 冒泡排序去掉最大最小值 for (i 0; i FILTER_NUM - 1; i) { for (j i 1; j FILTER_NUM; j) { if (buf[i] buf[j]) { tmp buf[i]; buf[i] buf[j]; buf[j] tmp; } } } sum (unsigned int)buf[1] buf[2]; return sum / 2; }这里把采样值转成16位整型再做运算避免8位乘法溢出。4次采样间隔是连续完成的每次等待转换完成约100us总耗时不到1ms跟50ms的调度周期相比可以忽略。主循环里把读ADC、滤波、显示三个动作分开不要让数码管扫描占用长时间延时不然采样周期会被拖得不稳定。4. 数码管显示与量程校准光敏测试值怎么换算不跳变4.1 段码表与动态扫描的显示逻辑四位数码管显示测试结果需要考虑显示什么单位。最直观的方案是显示电压值单位为伏特保留一位小数例如“2.5V”用两位数字加一位小数显示如果需要显示阻值或照度等级再通过换算后分别显示三位整数或百位整数。动态扫描代码的核心是每2ms切换一位循环往复。注意显示函数里不能加长延时否则主循环处理其他任务时会感觉到明显的闪烁。unsigned char code DIGIT_SEL[] { 0x01, 0x02, 0x04, 0x08 }; // 位选码对应P2.0到P2.3 void Display_Voltage(unsigned char adc_val) { unsigned int voltage_x10; unsigned char n[4]; unsigned char i; unsigned char dp_flag; // 电压的10倍数值例如2.51V记为25 voltage_x10 (unsigned int)adc_val * 500UL / 255; n[0] voltage_x10 / 100; // 个位 n[1] (voltage_x10 / 10) % 10; // 十分位 n[2] voltage_x10 % 10; // 百分位不显示 n[3] 0; // 最高位熄灭 for (i 0; i 3; i) { P2 DIGIT_SEL[i]; P1 SEG_CODE[n[i]]; if (i 1) { P1 | 0x80; // 点亮小数点显示x.xV } Delay1ms(2); P1 0x00; // 消隐防止拖影 P2 0x00; } }段选和位选的顺序有讲究先定位选再送段码延时后先清段码再清位选。如果先清位选再清段码数码管会在切换瞬间出现短暂乱码。小数点靠段码的最高位控制0x80单独或进段码里要在送段码之后加否则被后续赋值覆盖。P1上加限流电阻时段码会有一点压降亮度不够就减小电阻阻值但不要低于220Ω否则段码全亮时电流过大容易把P1口拉垮。4.2 电压换算与光照度标定的两点法ADC0804的8位分辨率对应5V参考电压每一个LSB约19.6mV。直接显示电压时不能简单地用“adc * 5 / 255”取整那样显示的是整数伏特几乎全程都在0V和5V之间跳。先用长整型保留一位小数再拆成各个数位去显示分辨率才能体现出来。标定光照度等级时需要用两点法。找一个暗环境记录ADC读数为ADC_dark用一个已知亮度的光源或者干脆用手机闪光灯近距离照射记录另一个读数为ADC_light。显示值从0到100的百分比映射关系是线性插值unsigned char Map_LightLevel(unsigned int adc_val, unsigned int adc_dark, unsigned int adc_light) { unsigned long level; if (adc_val adc_dark) return 0; if (adc_val adc_light) return 100; level (unsigned long)(adc_val - adc_dark) * 100; level / (adc_light - adc_dark); return (unsigned char)level; }这里用32位运算做中间量避免在8位机上出现乘法溢出。两个标定点之间的区间拉得越大线性近似的误差越小。光线很暗时adc_dark接近0光线很强时adc_light可能接近255整个量程就铺满了。如果发现adc_light和adc_dark之差小于50说明分压电阻选型不合适固定电阻值需要调整软件校准只能修正偏移修正不了量程太窄的问题。4.3 量程归一化的3种显示模式做完电压显示和光照度百分比显示后按一下按键可以在三种模式之间切换电压模式、百分比模式、阻值模式。阻值模式对“测试光敏电阻好坏”这个场景最有用直接显示当前光敏电阻的阻值单位KΩ。反推阻值的公式来自分压关系。固定电阻R_fix在上端、光敏电阻在下端接ADC输入则R_light (5.0 - V_out) * R_fix / V_out。这个除法在单片机里要小心V_out为0时直接判定为暗电阻超出量程显示“OL”避免除零。unsigned int Calc_Resistance_K(unsigned char adc_val) { unsigned long v_out_x1000; unsigned long r_fix_x10; unsigned long r_light; if (adc_val 0) return 0xFFFF; // 返回特殊值显示OL v_out_x1000 (unsigned long)adc_val * 5000UL / 255; // 固定电阻10K先放大10倍避免小数丢失 r_fix_x10 10000UL * 10; r_light r_fix_x10 * (5000UL - v_out_x1000) / v_out_x1000; // 结果单位是0.1K除以10得到整数K return (unsigned int)(r_light / 10); }这个计算里每一步都先用32位最后才缩回16位。固定电阻的实际值最好用万用表量过再填进去标称10K的电阻实际可能是9.8K不修正的话显示的阻值会系统性偏大或偏小。4.4 光线遮挡时的显示漂移与暗电流处理完全遮住光敏电阻时理论上ADC读数应该降到接近0但实际会停在几个LSB上。常见原因有三个ADC0804输入引脚有微弱漏电流分压点到ADC输入之间的走线过长在电路板上形成了天线效应P0口上拉电阻和ADC数据线之间的耦合噪声。处理办法是在ADC输入引脚对地并联一个0.1uF瓷片电容把高频噪声滤掉。这个电容也会让响应时间稍微变慢但对光敏电阻这种秒级变化的信号没有任何影响。如果必要求暗环境归零可以在软件里做一个暗电流扣除遮挡后读一次adc_dark之后所有读数都先减去这个值再参与换算负值截断为0。这个偏移校准和量程标定是两件事不要在标定时光线条件不同步。我习惯先做暗电流扣除再做两点标定顺序错了会得到一组互相矛盾的参数。5. 光敏电阻测试的3个验证技巧从串口抓数到阈值报警5.1 串口打印原始ADC值先别信数码管数码管显示经过滤波、换算多道处理一旦结果不对很难判断是前端电路还是软件问题。调试时先把原始ADC值通过串口发到电脑上用串口助手观察数据曲线这一步能省下大量排查时间。用STC89C52的P3.1 TXD引脚接一个USB转TTL模块波特率9600即可。void UART_Init(void) { SCON 0x50; // 模式18位UART TMOD 0x0F; TMOD | 0x20; // 定时器1工作在模式2 TH1 0xFD; // 波特率9600 11.0592MHz TL1 0xFD; TR1 1; } void UART_SendByte(unsigned char dat) { SBUF dat; while (!TI); TI 0; }注意晶振频率必须是11.0592MHz才能用0xFD得到准确的9600波特率12MHz晶振的波特率误差会大到乱码。串口打印的ADC值如果在小范围内上下波动说明前端电路正常如果数值在高位和低位之间突跳先查分压点电压是不是稳定再查ADC0804的Vref/2是否干净。5.2 用手机闪光灯和遮挡物做两个标准点没有照度计也能做一个快速可靠性验证。把光敏电阻完全用黑胶带缠住数码管显示的阻值应该达到最大量程甚至显示OL用手机闪光灯贴近照射阻值应迅速跌到1K以下并稳定。这个过程反复做三次每次读数回到同一个区间说明传感器和采样链路是自洽的。如果完全遮挡后阻值还在几十K来回飘大概率是遮光不彻底光从侧面漏进去了。光敏电阻的感光面在顶部侧面和引脚根部也会有微弱响应测试时需要把整个TO类封装的管帽都遮住。5.3 带阈值的低照度报警一个实用的代码收尾测试功能稳定后加一个低照度报警用途的代码收尾光照度低于设定阈值时点亮一颗LED并让阈值在测试模式下通过按键调整。这个功能常见于智能路灯控制、暗房报警等场景也顺手验证了整套采集链路对真实控制的响应。#define LIGHT_THRESHOLD 30 // 阈值对应光照度百分比 void Light_Alarm(unsigned char light_level) { static unsigned char alarm_cnt 0; if (light_level LIGHT_THRESHOLD) { if (alarm_cnt 10) alarm_cnt; } else { alarm_cnt 0; } if (alarm_cnt 10) { LED 0; // 连续10次低于阈值点亮LED } else { LED 1; } }报警逻辑里做了一个去抖计数连续10次采样低于阈值才判定为低照度避免飞过的影子让LED闪一下。阈值可以放在ADC域的原始值上也可以放在换算后的百分比域放在百分比域的好处是更换传感器型号后阈值不用重新标定。把这个报警函数挂进50ms的采样调度里整套“光敏电阻测试数码管显示”的工程就从一个读数工具变成了一个能响应环境光变化的小系统。本文还有配套的精品资源点击获取