简介:面向单片机和可见光通信入门开发者,围绕大功率白光LED通信系统,完整给出基于51单片机控制器的发送与接收设计。内容包括数据编码与PWM调制思路、LED驱动电路、光敏传感器接收与解调原理,并配套原理图、源代码和电路设计文件,可直接用于搭建可见光通信实验原型。包内共有158个文件,涵盖C语言源码、头文件、原理图文档、PCB设计、工程配置文件、调试日志及PDF说明等,其中schdoc与pcbdoc可帮助理解硬件连接,uvproj与C源码便于直接编译调试;压缩包整体约22.25MB,目录结构清晰,便于按模块学习。目前已有370人学习下载。通过该资料,读者可理解大功率白光LED如何被单片机精确控制以发送光信号,掌握接收端光信号采集与解调方法,并利用提供的代码与电路图开展实验验证。同时,包内调试日志和烧录工具文件也为排查问题、修改参数提供了参考,能帮助开发者快速搭建自己的可见光通信系统,进一步探索智能照明与无线通信融合的应用场景。 前阵子翻工具柜翻出一块吃灰的STC89C52最小系统板,本来想着做个流水灯打发时间,结果顺手把它变成了一套可见光通信的发射端和接收端。用大功率白光LED来传数据,听起来有点科幻,其实拆开看就是“让LED按照要发送的数据高频闪烁,光里就带了信息,对面用光电二极管把光还原成电信号”。我身边几个朋友听到“可见光通信”这四个字,第一反应都是“这得写多少算法”,实际上这套系统最磨人的地方根本不在代码,而在发送端的驱动电路、接收端的噪声处理和两边的同步。这篇就把我调试过程中真正有效的电路结构和参数选择都摊开讲,给同样想用51单片机做可见光通信的人当个参考。
下面这张链路图我建议先记在脑子里:51单片机发送端通过一个开关管控制大功率白光LED的亮灭,灯光打到几米外的光电二极管上,接收端的运放和比较器把光脉冲还原成方波,再送进另一块51单片机解码。整体就是个“电—光—电”的转换链路,每一个环节都有各自的坑。
1. 先搞清可见光通信的链路,再决定要不要动手
1.1 白光LED为什么能同时干照明和通信两件事
LED本质上是一个电流转光强的器件,它的点亮和熄灭速度极快,远超人眼能感知的刷新频率。我们平时觉得灯一直亮着,是因为人眼有视觉暂留效应,几百赫兹以上的闪烁基本感知不到。可见光通信就是利用这个特性,在照明的同时把数据调制在光强上。实际项目里通常有两种做法:一种是把LED作为照明光源,在原有恒流驱动上叠加高频调制信号;另一种就是我这次做的演示系统,直接用开关管控制LED的亮灭,把载波和编码都做了进去。
这个项目我特意选了大功率白光LED而不是普通指示LED,有两个考虑:第一是驱动力强,1W、3W级别的LED光通量大,接收端不用做特别高的放大倍数就能拿到清晰的信号;第二是白光LED的应用场景更有代表性,以后往室内照明通信方向扩展时,整个驱动和接收方案是可以平移过去的。当然大功率LED也有个明显短板,就是白光LED的荧光粉转换层会拖慢响应速度,调制带宽一般只有几MHz,对单片机这种低速系统来说完全够用,但别指望做到100Mbps级别的通信。
1.2 这套系统能做多远、跑多快
在动手前先把预期盘清楚,不然容易失望。基于51单片机的可见光通信,跑几百kbps以上不现实,我实测最稳定的是1kbps到10kbps这个区间,传输距离在做好透镜对准时能到2到3米,散光环境下基本只有0.5米左右。如果只是做课程设计或者通信原理演示,这个性能已经足够把一帧传感器数据、一段字符串或者遥控指令送出去。
这里有个很多教程不会明说的点:可见光通信的距离瓶颈通常不在发射光功率,而在接收端的灵敏度和环境光干扰。大功率LED再亮,经过几米空气衰减后照到光电二极管上的光强已经很小,而室内照明灯具、阳光这些背景光会形成很强的直流分量,接收电路如果没有做隔直和滤波,信号会直接被淹没。所以我在设计接收端时花了大量精力处理直流偏置和噪声问题,这比单纯把发射端做得更亮更有效。
2. 发送端原理图拆解:从IO口到大功率LED的驱动链路
2.1 为什么不能把LED直接接到单片机引脚上
51单片机的IO口输出能力很弱,典型拉电流只有10到20mA,灌电流稍大一些,但也就几十mA的量级。大功率LED正常工作电流动辄300mA到700mA,直接把LED接在P1口上除了让单片机发热、复位之外,不会有任何通信效果。所以发送端的关键是加一级开关驱动,让单片机的逻辑电平去控制一个“大电流阀门”,而不是自己输出大电流。
我试过几种驱动方案,最后稳定的结构是这样:51单片机的P1.0输出调制信号,经过一个1kΩ基极电阻接到S8050三极管,三极管的集电极通过10kΩ上拉电阻接到12V,同时连接到N沟道MOS管的栅极。这样做的原因是,单片机5V电平直接驱动普通MOS管存在栅极电压不够高的问题,很多普通MOS管需要10V以上才能完全导通,5V只能让它处于线性区,管子上会白白消耗大量功率。用三极管做一级电平转换,把栅极驱动电压抬到12V,MOS管就能工作在可靠开关状态。
发射端驱动电路示意(参考,非PCB实物图) P1.0 ──[1kΩ]── S8050基极 S8050发射极 ── GND S8050集电极 ──[10kΩ]── +12V │ └──────── IRLZ44N栅极 +12V ──[15Ω/10W]── LED正极 LED负极 ──────────── IRLZ44N漏极 IRLZ44N源极 ──────── GND2.2 用逻辑电平MOS管还是三极管驱动,参数怎么算
MOS管这里我选了IRLZ44N,这是逻辑电平MOS管,也可以用IRF540再加一级栅极驱动。选择逻辑电平版的好处是单片机5V直接驱动时导通电阻已经足够低,适合作为接收端实验的最简方案。不过实测下来,加上S8050的电平转换后驱动更干脆,开关沿更陡,对减少码间干扰有明显帮助。如果手头只有IRF540这类普通MOS管,请务必加上栅极电平转换,否则LED亮度上不去,传输距离会明显缩短。
限流电阻的计算是发送端最容易被忽视的细节。我用的3W白光LED,正向压降大约3.3V,12V供电时压差是8.7V,如果按700mA设计电流,电阻值就是8.7V除以0.7A,约等于12.4Ω。但大功率LED发热之后正向压降会变化,电流也会跟着漂,所以实际我取15Ω,让工作电流维持在580mA左右。这个电阻的功耗要仔细算:0.58A的平方乘以15Ω,约5W,所以必须用10W的绕线电阻,普通贴片电阻会直接烧掉。如果想让系统更稳定,可以换成PT4115这类恒流驱动模块,恒流驱动在长时间点亮时优势很明显。
发送端还有几个容易被忽略的细节:一是MOS管栅极要加一个10kΩ下拉电阻到地,防止单片机复位期间栅极悬空导致LED误点亮;二是在LED两端并联一个100nF的陶瓷电容,吸收开关瞬间的振铃;三是整个发送端供电最好用独立的12V适配器,不要和单片机共用一个电源,因为LED开关时的大电流跳动会通过电源线串进单片机,干扰调制信号。
3. 接收端原理图拆解:光信号如何变成可控的0和1
3.1 光电二极管的跨阻放大与偏置
接收端我选BPW34光电二极管,这个型号在可见光波段响应好,速度也够快,而且价格便宜。光电二极管在工作时产生的是微弱电流,必须通过一个高阻值电阻变成电压信号。直接把光电二极管并联一个10kΩ电阻再进单片机是不行的,因为信号幅度太小,而且没有滤波能力,所以我用了两级处理。
第一级是跨阻放大,用LM358运放把光电流转成电压。BPW34工作在光导模式,阴极接运放反相输入端,阳极接地,反馈电阻取100kΩ。这里有个关键点:LM358输入偏置电流很小,但单电源供电时输出有饱和区,所以同相输入端需要给一个2.5V左右的偏置电压,把静态工作点抬到电源中点。这样光电流引起的电压变化才能在线性区摆动,不会切底或切顶。
接收端前端示意(参考,非PCB实物图) BPW34阴极 ── 反馈电阻100kΩ ── 运放输出 │ │ └──────── 反相输入端 ────────┘ BPW34阳极 ── GND 运放同相输入端 ── Vref(2.5V)3.2 包络检波和比较器整形
运放输出后,信号里既有环境光造成的直流偏置,也有载波调制的高频成分。接下来分两步处理:先用一个10μF电容串联耦合,配合10kΩ电阻到地,构成高通滤波,把环境光的直流分量去掉;然后经过二极管和电容组成的包络检波电路,把38kHz载波包络还原成基带数据波形。包络检波的时间常数要匹配数据速率,RC太大恢复慢,太小检波不干净,实测1kbps数据率下用10kΩ电阻和10nF电容比较合适。
最后一级是LM393比较器,参考电压用一个10kΩ电位器调节,把包络信号整形为标准的0到5V方波送进单片机P3.2口。这个比较器要加一点正反馈形成滞回,否则信号在阈值附近抖动会引发一连串误判。滞回量大概0.1V就够,加太大反而会把小信号丢掉。接收端这几级电路顺序不能乱,如果跳过隔直直接进比较器,室内灯光下的直流偏置就能把比较器顶死在固定电平上,什么数据都解不出来。
4. 协议与代码:载波、曼彻斯特编码和帧同步
4.1 用定时器同时生产载波和位时序
发送端程序的核心是用定时器产生38kHz载波。51单片机用12MHz晶振时,机器周期是1μs,38kHz信号的半周期约13μs,用定时器1工作在8位自动重装模式,初始值设为256减13,中断里翻转一次P1.0就能得到约38.5kHz的方波。载波频率不用太精确,只要接收端高通滤波和包络检波能通过就行。真正需要严格的是位时序和帧同步,这两个用定时器0来处理。
数据编码我建议用曼彻斯特编码,不用普通串口。曼彻斯特编码的每一位中间都有一次跳变,这个跳变既是数据也是时钟,接收端不需要额外恢复位时钟。具体做法是:发送逻辑“1”时,前半个位周期输出载波,后半个位周期关闭;发送逻辑“0”时,前半个位周期关闭,后半个位周期输出载波。这样每发送一个数据位,LED必然经历一次明显的光脉冲变化,接收端解调时只看相邻两个半位的顺序就能判断0和1。
// 发送端核心示意,基于STC89C52,12MHz晶振 void send_bit(unsigned char bit_value) { if(bit_value) { // 1 -> 前半位发载波,后半位停 carrier_on(); delay_500us(); carrier_off(); delay_500us(); } else { // 0 -> 前半位停,后半位发载波 carrier_off(); delay_500us(); carrier_on(); delay_500us(); } } void send_byte(unsigned char dat) { unsigned char i; // 先发一个足够长的帧头,接收端用它来同步 // 帧头例如0xAA,连续发2次 for(i = 0; i < 8; i++) { send_bit(dat & 0x01); dat >>= 1; } }4.2 接收解码:从载波包络恢复数据
接收端程序相对复杂一点。前面电路已经给出包络检波后的方波,这个方波对应的是“载波是否存在”的信息,而不是直接的数据0或1。所以接收端要做的是采样这个包络信号,判断每个半位是否有载波,然后根据曼彻斯特规则恢复数据。
我用外部中断加定时器的方式来做。P3.2引脚接比较器输出,平时设置成下降沿触发。检测到跳变后启动定时器0,在半个位周期的中间点再去采样一遍电平,这样能避开信号边沿的抖动干扰。曼彻斯特编码天然包含同步信息,接收端只要根据上一次跳变的位置,预测下一次跳变应该在什么时间点,然后在预测点采样,就能完成自同步。实际代码里我用一个状态机处理帧头检测——先连续收到两字节0xAA就认为帧同步成功,再按固定位宽接收后续数据,最后校验字节对上了才认为一帧完整。
// 接收端解码示意,只列出关键逻辑 void timer0_isr() interrupt 1 { static unsigned char bit_count, dat, frame_state; unsigned char sample; sample = RX_PIN; // 读取比较器整形后的包络信号 // 在采样点判断当前半位是否有载波 // 若是前半位/后半位组合,按照曼彻斯特规则恢复bit if(receiving) { if(bit_count % 2 == 0) { // 前半个位周期 first_half = sample; } else { // 后半个位周期 second_half = sample; if(first_half != second_half) { dat = (dat >> 1) | (first_half ? 0x80 : 0x00); } else { // 两边相同说明同步丢失,重置帧状态 frame_state = 0; } } bit_count++; } }这个方案看起来简单,但有两个细节必须处理好。第一,定时器的重装值要严格匹配发送端的半位周期,两边晶振存在几微秒偏差时,一帧数据长一点就会累积误差丢位。我发送端和接收端都用12MHz晶振,且接收端每收到一帧头就重新校准一次定时器,效果就好很多。第二,接收端的采样尽量选在半位周期中间,不要用中断里连续的软件延时来做,否则主循环一旦被其他事情占用,采样点就会漂移。
5. 实测现场:距离、干扰、散热这三大坑怎么填
5.1 环境光和频闪干扰怎么处理
实测第一晚我是在普通日光灯底下调的,结果接收端波形惨不忍睹。日光灯是工频50Hz点亮,实际闪烁频率是100Hz,它产生的光电流在接收电路里形成很大的100Hz纹波,和信号混在一起。我当时用的载波是几十kHz,理论上高通滤波能滤掉100Hz,但实际调试时发现,包络检波器前面的高通截止频率设得太低的话,100Hz纹波会直接让比较器被触发,解出来的都是乱码。
解决办法是把高通截止频率提高到2kHz以上,同时把载波频率提高到38kHz。这样接收端只对2kHz以上的调制信号有放大作用,日光灯和LED灯具的低频纹波全部被挡在通道外。如果条件允许,最好在光电二极管前面加一个黑色遮光筒,只让正向来的光进入,环境光干扰能再降一个数量级。如果买得到深红色滤光片还可以装在光电二极管前面,让可见光被滤掉大半,不过白光LED发射的光谱也有一部分被滤掉,传输距离会打折,需要根据自己的场景取舍。
5.2 散热、供电纹波和PCB布局经验
大功率LED长时间点亮时发热很凶,我在没有散热片的情况下连续传输测试五分钟,LED外壳温度就到了70摄氏度以上,光通量明显下降,接收端误码率跟着上升。后来在LED背面涂了导热硅脂,加了一块CPU散热器,温度稳定在50度左右,传输才稳定下来。如果电路板是自己画的,要注意LED的焊盘到散热铜皮的通道,过孔要多打几个,让热量能顺畅传导到背面。
供电纹波是另一个容易被忽视的问题。发送端LED以38kHz开关,每次开关都在电源线上拉出几安培的瞬态电流,如果12V电源适配器纹波大,这些毛刺会通过三极管的集电极耦合到MOS管栅极,导致LED开关沿抖动。我最后把发射端和单片机各用一路电源,同时在LED供电入口并联了470μF电解电容和100nF陶瓷电容,问题就基本消失了。接收端PCB布局上,模拟放大部分要尽量远离数字走线,特别是单片机到比较器的那段数字信号线,离BPW34近一点,模拟地和数字地单点连接,否则噪声很难压下去。
6. 优化方向和建议
如果想让传输距离超过三米,不要一上来就堆LED功率,重点做两件事:一是加光学透镜,LED前加聚光透镜,光电二极管前加会聚透镜,能把光能量集中起来,效果比换更大功率LED明显得多;二是给接收端加自动增益控制,用一个可变增益运放替代固定的100kΩ反馈电阻,让强光时放大倍数自动降低、弱光时自动升高,这样可以避免近距离信号饱和、远距离信号太小的问题。
另一个想提醒的是,如果要做半双工通信,发射和接收模块之间要做好光学隔离,否则发射端的光会直接打到自己接收端上造成自干扰。最简单的方法是收发端放在不同方向,或者用遮光板隔开。这个项目往更深处扩展,可以尝试用更理想的恒流驱动方案取代限流电阻,再把编码换成带校验的分组协议,整个系统就会从“能点亮”变成“能可靠传数据”。我做这套东西最大的收获是,以前总觉得通信协议是路由器、交换机里才有的东西,结果用一块51单片机和一盏LED就能把一个完整的调制、编码、同步、解码过程跑通,这对理解通信原理的帮助比看十遍教材都大。
本文还有配套的精品资源,点击获取