☰
基于以太网的可见光通信系统:从实验室Demo到可复现的工程链路
2026/10/22 17:55:35 网站建设 项目流程

简介:这份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峰值波长,带宽20~30 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(f"sent {sent_bytes} bytes in {elapsed:.2f}s, " f"throughput = {sent_bytes * 8 / elapsed / 1e6:.2f} Mbps")

这段脚本只做发送端吞吐统计,接收端可以用类似逻辑收包并统计丢包。参数说明:PKT_SIZE取1024是为了避开以太网MTU 1500的分片,分片会引入额外抖动;DURATION取10秒是经验值,太短受缓存影响,太长测试效率低。跑之前先把两端IP配在同一网段,关闭防火墙的ICMP过滤,否则丢包统计会失真。

3. 从MII到LED:FPGA侧码型转换与时钟恢复的实现细节

3.1 MII接口时序与曼彻斯特编码的映射关系

MII接口在100 Mbps模式下,TX_CLK是25 MHz,TXD[3:0]每周期送4位,正好100 Mbps。曼彻斯特编码把每个比特变成两个码片:0变01,1变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 <= 1'b0; shift_reg <= 8'd0; bit_cnt <= 3'd0; chip_cnt <= 2'd0; end else begin if (bit_cnt == 0 && !fifo_empty) begin shift_reg <= fifo_dout; // 加载新字节 bit_cnt <= 3'd7; end // 曼彻斯特编码:0->01, 1->10 case (chip_cnt) 2'd0: led_out <= shift_reg[bit_cnt] ? 1'b1 : 1'b0; 2'd1: led_out <= shift_reg[bit_cnt] ? 1'b0 : 1'b1; default: led_out <= led_out; endcase chip_cnt <= chip_cnt + 1'b1; if (chip_cnt == 2'd1) begin chip_cnt <= 2'd0; if (bit_cnt == 0) bit_cnt <= 3'd0; else bit_cnt <= bit_cnt - 1'b1; 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 <= 2'd0; last_rx <= 1'b0; shift_reg <= 32'd0; synced <= 1'b0; data_valid <= 1'b0; end else begin last_rx <= rx_in; // 检测跳变沿,重置采样相位 if (rx_in != last_rx) sample_cnt <= 2'd0; else sample_cnt <= sample_cnt + 1'b1; // 在每个码片中间采样 if (sample_cnt == 2'd1) begin shift_reg <= {shift_reg[30:0], rx_in}; if (!synced && shift_reg == 32'hAAAAAAAA) begin synced <= 1'b1; bit_cnt <= 3'd0; end else if (synced) begin byte_reg <= {byte_reg[6:0], rx_in}; bit_cnt <= bit_cnt + 1'b1; if (bit_cnt == 3'd7) begin data_out <= byte_reg; data_valid <= 1'b1; bit_cnt <= 3'd0; end end end else begin data_valid <= 1'b0; end end end endmodule

这段代码用跳变沿重置采样相位,保证在每个码片中间采样。参数说明:sample_cnt == 2'd1表示在码片开始后的第二个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, timeout=1) att_values = np.linspace(START_ATT, STOP_ATT, STEPS) ber_results = [] for att in att_values: cmd = f"ATT {att:.1f}\n".encode() ser.write(cmd) time.sleep(0.5) # 等待衰减器稳定 # 这里调用误码率测试函数,返回当前误码率 ber = run_ber_test(duration=5) ber_results.append(ber) print(f"attenuation={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(f"system 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秒,记录误码率和吞吐。虽然慢,但省下了大量回溯时间。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询