简介这份PPT资料面向通信工程、光通信与物联网方向的学习者与研究人员系统讲解基于以太网的可见光通信系统实现原理与关键电路设计。压缩包内仅含1个pptx文件大小约1.13MB以图文并茂的幻灯片形式组织内容便于课堂讲解与自学查阅。资料从可见光通信的基本概念切入依次展开基于以太网的LED可见光通信系统架构、OFDM技术应用并深入剖析信号接口处理电路、以太网介质转换模块、光源LED工作原理与调制特性、光发射机与光接收机等核心模块完整呈现从计算机A到计算机B的以太网数据经电/光转换、空间传输再到光电恢复的全链路流程。目前已有121人学习下载适合希望理解可见光通信系统组成、掌握LED调制带宽优化与均衡技术思路的读者参考也可作为相关课程汇报或实验设计的辅助材料。1. 基于以太网的可见光通信系统从实验室Demo到可复现的工程链路第一次接触基于以太网的可见光通信系统很多人会以为这是把网线换成LED灯那么简单。实际动手才发现真正卡住进度的往往不是调制解调算法而是以太网帧怎么塞进可见光链路、收发两端时钟怎么对齐、环境光怎么把信噪比吃掉。这套系统解决的核心问题是在射频受限或电磁敏感的封闭场景里用照明LED同时完成照明和数据传输并且让上层业务以为自己在用一条普通以太网链路。适合谁做做工业现场通信的、做室内定位与通信融合的、做嵌入式网络设备的一线工程师。它不需要你重新发明TCP/IP但需要你把PHY层和MAC层之间的边界重新画一遍。下面按我实际搭过的一条最小链路把选型、参数、代码和踩坑一次讲透。2. 系统架构与关键选型为什么不是简单地把网线剪断2.1 以太网与可见光链路的边界在哪里基于以太网的可见光通信系统本质是把以太网PHY替换成“LED驱动自由空间光路光电探测器”这一段。MAC层以上完全不变这样上层业务、协议栈、甚至现成的网络调试工具都能直接复用。常见做法是保留标准以太网MAC在MII/RMII接口和LED驱动之间加一层FPGA或高速MCU做码型转换和时钟恢复。选型时第一个要定的是你走半双工还是全双工。可见光下行用LED发光容易上行如果也用LED终端侧需要额外光源成本和体积都上去。我一般建议下行可见光、上行红外或射频回传这样系统复杂度可控且不破坏“照明为主”的定位。第二个要定的是调制方式。OOK开关键控最简单但频谱效率低适合低速控制指令VPPM可变脉冲位置调制能兼顾调光和通信适合照明场景OFDM频谱效率高但LED非线性会带来严重失真需要预失真或削峰。实测下来如果目标速率在100 Mbps以内、传输距离3米以内OOK加曼彻斯特编码最稳FPGA资源占用也小。超过这个速率再考虑OFDM或CAP。2.2 光电前端与驱动电路的参数怎么定LED驱动电路的核心参数是偏置电流和调制深度。偏置电流决定照明亮度调制深度决定信号摆幅。两者是矛盾的偏置电流越大LED线性区越宽但发热和光衰加快。我一般把偏置点设在额定电流的60%70%调制深度控制在30%40%这样既保证照明又留出足够的信号裕量。光电探测器侧跨阻放大器TIA的反馈电阻决定带宽和增益的乘积电阻越大增益越高但带宽越低。对于10 Mbps级信号反馈电阻取10 kΩ左右比较合适到100 Mbps要降到1 kΩ量级同时注意PCB走线的寄生电容。环境光是最大的干扰源。工频荧光灯有100 Hz闪烁阳光有宽谱背景。常见做法是在探测器前加窄带滤光片中心波长匹配LED峰值波长带宽2030 nm。电路上再加一级高通滤波截止频率设在1 kHz以下把工频干扰压掉。这些参数不是拍脑袋要用示波器看眼图来调。2.3 最小可复现链路的搭建步骤下面这条链路是我在实验室里反复搭过的最小系统PC1通过USB转以太网口接FPGA开发板FPGA驱动LED板光路1.5米对面是光电探测器板再回FPGA最后接PC2。两端用Python脚本发UDP包测吞吐和误码。第一步准备硬件。FPGA开发板选带千兆以太网PHY和足够IO的型号LED板用白光LED阵列加恒流驱动探测器板用PIN光电二极管加TIA。第二步配置FPGA的MAC核让它工作在MII模式时钟25 MHz。第三步写码型转换模块把MII的4位数据转成曼彻斯特编码的串行比特流再送LED驱动。第四步接收端做时钟恢复和数据对齐。第五步用Python脚本打流测试。# udp_throughput_test.py # 发送端向指定IP和端口持续发送UDP包统计发送速率 import socket import time TARGET_IP 192.168.1.10 # 接收端FPGA映射的IP TARGET_PORT 8080 PKT_SIZE 1024 # 每包字节数避免超过MTU导致分片 DURATION 10 # 测试时长秒 sock socket.socket(socket.AF_INET, socket.SOCK_DGRAM) payload b\xAA * PKT_SIZE sent_bytes 0 start time.time() while time.time() - start DURATION: sock.sendto(payload, (TARGET_IP, TARGET_PORT)) sent_bytes PKT_SIZE elapsed time.time() - start print(fsent {sent_bytes} bytes in {elapsed:.2f}s, fthroughput {sent_bytes * 8 / elapsed / 1e6:.2f} Mbps)这段脚本只做发送端吞吐统计接收端可以用类似逻辑收包并统计丢包。参数说明PKT_SIZE取1024是为了避开以太网MTU 1500的分片分片会引入额外抖动DURATION取10秒是经验值太短受缓存影响太长测试效率低。跑之前先把两端IP配在同一网段关闭防火墙的ICMP过滤否则丢包统计会失真。3. 从MII到LEDFPGA侧码型转换与时钟恢复的实现细节3.1 MII接口时序与曼彻斯特编码的映射关系MII接口在100 Mbps模式下TX_CLK是25 MHzTXD[3:0]每周期送4位正好100 Mbps。曼彻斯特编码把每个比特变成两个码片0变011变10所以码片速率是200 Mbps。FPGA内部要用200 MHz时钟去采样和输出。常见做法是在25 MHz域收MII数据用异步FIFO跨到200 MHz域再做并串转换和曼彻斯特编码。这里最容易翻车的是FIFO深度不够导致突发数据丢失。我一般把FIFO深度设成至少两倍最大帧长也就是2048字节以上。// mii_to_manchester.v // 将MII 4位数据转为曼彻斯特编码串行输出 // 假设已有时钟200MHz和25MHz以及异步FIFO module mii_to_manchester ( input wire clk25, // MII TX_CLK input wire clk200, // 编码时钟 input wire rst_n, input wire [3:0] mii_txd, // MII发送数据 input wire mii_txen, // MII发送使能 output reg led_out // 曼彻斯特编码输出 ); // 省略FIFO实例化假设fifo_dout为8位数据fifo_empty为读空标志 wire [7:0] fifo_dout; wire fifo_empty; reg [7:0] shift_reg; reg [2:0] bit_cnt; reg [1:0] chip_cnt; always (posedge clk200 or negedge rst_n) begin if (!rst_n) begin led_out 1b0; shift_reg 8d0; bit_cnt 3d0; chip_cnt 2d0; end else begin if (bit_cnt 0 !fifo_empty) begin shift_reg fifo_dout; // 加载新字节 bit_cnt 3d7; end // 曼彻斯特编码0-01, 1-10 case (chip_cnt) 2d0: led_out shift_reg[bit_cnt] ? 1b1 : 1b0; 2d1: led_out shift_reg[bit_cnt] ? 1b0 : 1b1; default: led_out led_out; endcase chip_cnt chip_cnt 1b1; if (chip_cnt 2d1) begin chip_cnt 2d0; if (bit_cnt 0) bit_cnt 3d0; else bit_cnt bit_cnt - 1b1; end end end endmodule这段代码的核心逻辑是每个比特用两个码片表示chip_cnt在0和1之间翻转bit_cnt从7递减到0。参数说明clk200必须严格是clk25的8倍否则码片周期不均匀眼图会闭合。实际工程里我会用MMCM把25 MHz倍频到200 MHz并确保两者同源。如果时钟抖动超过100 ps接收端时钟恢复会变得非常困难。3.2 接收端时钟恢复与数据对齐的工程做法接收端从TIA出来的信号是模拟的先过比较器变成数字电平再送FPGA。时钟恢复常用过采样法用200 MHz时钟去采200 Mbps的曼彻斯特码片每个码片采一个点然后检测跳变沿来对齐。更稳的做法是用4倍过采样即800 MHz采样然后数字PLL锁相。但800 MHz对FPGA的IO和逻辑资源要求高低速场景下200 MHz过采样够用。数据对齐的关键是找到帧头。我一般会在发送端每帧前加一个32位的训练序列比如0xAAAAAAAA接收端用移位寄存器匹配这个序列匹配到之后开始按字节组装。训练序列的长度要足够长太短容易误匹配太长浪费带宽。32位是经验值误匹配概率在1e-9量级。// manchester_rx.v // 接收端过采样恢复时钟匹配训练序列后输出字节 module manchester_rx ( input wire clk200, input wire rst_n, input wire rx_in, // 比较器输出 output reg [7:0] data_out, output reg data_valid ); reg [1:0] sample_cnt; reg last_rx; reg [31:0] shift_reg; reg [7:0] byte_reg; reg [2:0] bit_cnt; reg synced; always (posedge clk200 or negedge rst_n) begin if (!rst_n) begin sample_cnt 2d0; last_rx 1b0; shift_reg 32d0; synced 1b0; data_valid 1b0; end else begin last_rx rx_in; // 检测跳变沿重置采样相位 if (rx_in ! last_rx) sample_cnt 2d0; else sample_cnt sample_cnt 1b1; // 在每个码片中间采样 if (sample_cnt 2d1) begin shift_reg {shift_reg[30:0], rx_in}; if (!synced shift_reg 32hAAAAAAAA) begin synced 1b1; bit_cnt 3d0; end else if (synced) begin byte_reg {byte_reg[6:0], rx_in}; bit_cnt bit_cnt 1b1; if (bit_cnt 3d7) begin data_out byte_reg; data_valid 1b1; bit_cnt 3d0; end end end else begin data_valid 1b0; end end end endmodule这段代码用跳变沿重置采样相位保证在每个码片中间采样。参数说明sample_cnt 2d1表示在码片开始后的第二个200 MHz周期采样这个位置最接近码片中心。如果信道延迟较大这个值要相应调整。shift_reg匹配0xAAAAAAAA时进入同步态之后每8个码片输出一个字节。注意这里没有做曼彻斯特解码因为训练序列本身是交替码片同步后直接按码片采样就能恢复原始比特前提是发送端编码正确。4. 避坑与排查那些让链路一夜回到解放前的细节4.1 眼图闭合但误码率不高问题出在比较器阈值现象示波器看眼图已经闭合但打流测试误码率只有1e-6勉强能用。原因比较器阈值固定在电源中点而LED驱动存在直流偏置信号均值偏离中点导致判决门限偏移。解决用DAC或PWM加RC滤波生成可调阈值或者用交流耦合加自偏置比较器。我一般会在TIA后加一级隔直电容再用电阻分压把阈值拉到信号均值附近。4.2 吞吐量上不去先查FIFO溢出而不是调制方式现象UDP打流只能跑到30 Mbps远低于设计的100 Mbps。原因MII到200 MHz域的异步FIFO深度不够突发帧来的时候写满溢出。解决把FIFO深度从512字节加到4096字节同时把FIFO的写时钟和读时钟用同一MMCM生成减少跨时钟域抖动。实测下来FIFO深度每增加一倍突发丢包率下降一个数量级。4.3 环境光一变链路就断滤光片不是万能药现象白天测试正常晚上开灯后误码率飙升。原因荧光灯的100 Hz闪烁落在信号频带内窄带滤光片只能滤波长滤不掉频率。解决在TIA后加一级高通滤波截止频率设在1 kHz把工频干扰压掉。同时把曼彻斯特编码的码片速率提高到200 Mbps以上让信号频谱远离100 Hz。如果还不行考虑用扩频或跳频但复杂度会上去。4.4 训练序列误匹配导致偶发丢帧现象大部分时间正常偶尔整帧丢失。原因训练序列0xAAAAAAAA在数据段中也可能出现接收端误判为新帧头。解决用更长的训练序列比如64位并且前后加保护间隔。或者用伪随机序列做帧头接收端用相关器匹配。我一般用64位PN序列误匹配概率可以降到1e-12以下。4.5 电源噪声串进光路眼图抖动大现象眼图抖动超过0.2 UI误码率随温度漂移。原因LED驱动和FPGA共用一路电源开关噪声通过电源耦合到TIA。解决LED驱动用独立LDO供电TIA用电池或低噪声LDO两地之间用磁珠隔离。PCB布局上TIA的输入走线尽量短包地处理远离时钟线。5. 进阶技巧用误码率扫描反推系统裕量5.1 误码率扫描的脚本与参数设置系统调通之后怎么知道它离崩溃还有多远我习惯用误码率扫描固定发送端速率逐步降低LED驱动幅度或增加环境光强度记录误码率变化。当误码率从1e-9跳到1e-6时对应的幅度或光强就是系统裕量。下面这个脚本用Python控制可调衰减器自动扫描并记录。# ber_sweep.py # 误码率扫描逐步调整衰减器记录误码率 import serial import time import numpy as np SER_PORT COM3 # 衰减器串口 BAUD 9600 STEPS 20 # 扫描点数 START_ATT 0 # 起始衰减 dB STOP_ATT 20 # 终止衰减 dB ser serial.Serial(SER_PORT, BAUD, timeout1) att_values np.linspace(START_ATT, STOP_ATT, STEPS) ber_results [] for att in att_values: cmd fATT {att:.1f}\n.encode() ser.write(cmd) time.sleep(0.5) # 等待衰减器稳定 # 这里调用误码率测试函数返回当前误码率 ber run_ber_test(duration5) ber_results.append(ber) print(fattenuation{att:.1f} dB, BER{ber:.2e}) ser.close() # 找BER从1e-9跳到1e-6的拐点 for i in range(1, len(ber_results)): if ber_results[i-1] 1e-9 and ber_results[i] 1e-6: print(fsystem margin at {att_values[i]:.1f} dB) break参数说明STEPS取20是平衡扫描精度和耗时每个点等0.5秒让衰减器稳定。run_ber_test需要你根据实际接收端实现通常是发固定长度的伪随机序列接收端比对统计错误比特数。拐点判断用1e-9到1e-6的跳变因为前向纠错通常能纠正1e-9以下的误码超过1e-6就不可用了。5.2 从裕量反推LED偏置和滤光片带宽扫描出裕量之后可以反推设计参数。比如裕量只有3 dB说明LED偏置太低或滤光片太窄。我一般会把偏置电流提高10%再扫一次看裕量是否增加。如果增加不明显说明瓶颈在探测器或环境光要换滤光片或加高通滤波。这个过程通常要迭代两三次每次改一个参数避免多变量耦合。5.3 一个让我后悔半天的习惯早期调试时我总喜欢先把所有参数调到“看起来最好”再一次性打流测试。结果每次出问题都不知道是哪个参数引起的。后来养成一个习惯每次只改一个参数改完立刻用短包打流30秒记录误码率和吞吐。虽然慢但省下了大量回溯时间。希望帮到你。本文还有配套的精品资源点击获取