news 2026/9/7 14:14:01

FPGA实现曼彻斯特编码:从原理、Verilog代码到仿真的完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FPGA实现曼彻斯特编码:从原理、Verilog代码到仿真的完整指南

简介:一份面向数字通信和 FPGA 学习者的曼彻斯特编码完整工程,基于硬件描述语言与原理图方式实现了编码器、解码器及仿真验证,帮助读者解决在可编程逻辑器件上完成该编码的电路设计与调试问题。压缩包包含 87 个文件,涵盖 VHDL 源文件、原理图文件、编译适配报告、仿真波形、配置文件及多种中间过程文件,容量约 175KB。已有 618 人学习,适合希望边读代码边仿真复现的入门到中级开发者。通过工程中的编码模块、解码模块和测试文件,可以直观理解曼彻斯特码的上升沿/下降沿编码规则、时钟同步思路以及锁相环恢复时钟的方法;配套报告和总结文件还展示了完整实现流程与排错线索。借助这些资料,还能快速搭建测试环境,对比不同编码规则的波形差异,复现从综合到仿真的整个设计链路。 搞FPGA开发的人,迟早会遇到曼彻斯特编码。它是一种物理层线路编码,核心规则是每个bit中间强制跳变,既表示数据又携带时钟。正因为这种自同步特性,它在10BASE-T以太网、RFID、红外遥控、工业总线里被广泛使用。用FPGA实现曼彻斯特编码,最大的好处是跳变位置完全由硬件时钟决定,不会像单片机那样受中断抖动干扰,而且编解码和上层协议可以全部集成在一颗芯片里。这篇文章,我把自己在FPGA上从编码器、解码器到仿真上板的完整过程写出来,给正在做接口通信、或者只是在课程设计里被曼彻斯特编码折磨的朋友一个参考。

1. 曼彻斯特编码原理与FPGA方案选择

1.1 编码规则与自同步原理

标准曼彻斯特编码把一个bit周期分成前后两个半周期。约定逻辑1由高电平跳变到低电平,逻辑0由低电平跳变到高电平。也就是说,电平在bit正中间必然出现一次跳变,跳变方向就是数据。比如发送1,前半周期输出高,后半周期输出低;发送0,前半周期输出低,后半周期输出高。如果连续发送相同的bit,波形会呈现周期性方波,接收端可以通过这个周期性跳变提取出位时钟,不需要额外的时钟线。另外,每个bit周期内高电平和低电平的时间完全相等,从频域上看没有直流分量,这对变压器耦合、红外LED调制和电容隔离的链路特别友好。

实际工程中,有些协议会把“1”定义为低到高,“0”定义为高到低,比如IEEE 802.3里的10BASE-T。这个方向本身没有对错,只要收发双方约定一致就行。但我在写代码时建议固定成“1高到低、0低到高”,因为很多现成的IP核、示波器解码插件默认这个方向,避免后期比对波形时还要反转。

1.2 为什么不在单片机上做

很多人第一次接触曼彻斯特编码是在单片机课堂上,用定时器中断翻转IO。低速没有问题,可一旦数据速率超过几百kbps,中断延迟抖动会让跳变时刻偏移,接收端恢复出来的位时钟就会抖动,误码率直线上升。FPGA则完全不同,编码器的输出寄存器直接由全局时钟驱动,跳变沿的确定性在纳秒级,不管系统里跑多少任务,都不会影响发射波形。更关键的是,曼彻斯特编码通常只是链路层的一部分,前面要加前导码、帧长度、CRC,后面要接解码和FIFO。把这些都塞进单片机,CPU负载会很重;而在FPGA里,它们只是几段并行逻辑。资源占用方面,一个简单的曼彻斯特编码器用不了几个LUT和触发器,入门级的FPGA芯片完全能同时承担其他功能。所以从工程可控性和后续扩展来看,FPGA方案是更省心的选择。

2. 编码器设计:核心代码与参数计算

2.1 编码器顶层结构与时钟分频

编码器最基础的功能是:输入并行数据,输出一串符合曼彻斯特规则的串行波形。放在FPGA里,顶层可以拆成三个模块:并转串移位寄存器、bit定时器、输出电平生成器。bit定时器负责把一个bit周期切出前半和后半两个阶段,它本质上是一个分频计数器。这里的时钟设计是最容易出问题的地方。假设数据速率是1Mbps,曼彻斯特编码每个bit需要两个半周期,所以编码时钟必须大于等于2MHz。如果系统时钟是50MHz,那么bit定时器每个半周期要数25个系统时钟,代码里的分频参数就是25,计算公式为CLK_FREQ / DATA_RATE / 2。很多初学朋友直接把分频参数设成50,结果波形速率只有预期的一半,接收端按照1Mbps去解,数据全部错位。实际在上板之前,先把这几个参数用计算器按一遍,比自己抓波形猜根因快得多。

2.2 Verilog实现与状态控制

下面给一个参数化的编码器代码。这个模块用50MHz时钟输入,通过DATA_RATE参数适配不同速率,输出曼彻斯特编码波形。实现上我用了移位寄存器和半周期计数器,half_flag用于区分当前是前半周期还是后半周期。核心逻辑是:前半周期直接输出移位寄存器最低位,后半周期输出它的反相,并触发移位。

module manchester_encoder #( parameter CLK_FREQ = 50_000_000, parameter DATA_RATE = 1_000_000 )( input wire clk, input wire rst_n, input wire tx_valid, input wire [7:0] tx_data, output reg tx_out ); localparam HALF_CYCLE = CLK_FREQ / DATA_RATE / 2 - 1; reg [7:0] shift_reg; reg [3:0] bit_cnt; reg half_flag; reg sending; reg [15:0] div_cnt; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin tx_out <= 1'b1; sending <= 1'b0; half_flag <= 1'b0; div_cnt <= 0; end else begin if (!sending) begin if (tx_valid) begin sending <= 1'b1; shift_reg <= tx_data; bit_cnt <= 8'd8; half_flag <= 1'b0; div_cnt <= 0; tx_out <= 1'b1; end end else begin if (div_cnt == 0) begin div_cnt <= HALF_CYCLE; half_flag <= ~half_flag; if (half_flag) begin tx_out <= ~shift_reg[0]; end else begin tx_out <= shift_reg[0]; end if (half_flag) begin shift_reg <= {1'b0, shift_reg[7:1]}; if (bit_cnt == 1) begin sending <= 1'b0; bit_cnt <= 0; end else begin bit_cnt <= bit_cnt - 1'b1; end end end else begin div_cnt <= div_cnt - 1'b1; end end end end endmodule

代码里我一开始在idle状态把tx_out保持在高电平,这是绝大多数曼彻斯特接收端都能接受的空闲电平。发送数据时,每个bit的前半周期输出数据位原值,后半周期输出反相值,两个半周期消耗完,这个bit就完成了,shift_reg右移一次准备下一个bit。tx_valid拉高的那一个时钟周期完成数据加载,之后就可以拉低,发送状态机自己会跑完8个bit。bit_cnt在这里做字节内的位计数,当最后一个bit的后半周期结束时,sending拉低,回到idle状态。如果想支持连续发送多帧,可以在外面加一个FIFO缓存,encoder空闲时只要fifo里非空就自动发送,这样能避免主状态机等待发送完成,吞吐率会好很多。

2.3 差分曼彻斯特编码与参数速查表

有朋友问差分曼彻斯特编码怎么改。普通曼彻斯特编码看“bit中间跳变方向”,差分曼彻斯特编码看“bit起始边界有没有跳变”。有的协议定义边界有跳变表示0,没有跳变表示1,有的相反。FPGA里比较稳妥的做法是:先产生普通曼彻斯特码流,再把当前bit的第一个半周期电平与上一个bit最后一个半周期电平做异或,通过异或结果决定边界要不要翻转。要注意的是差分编码后,bit中间仍然必须保留跳变,不能简单把整段波形取反,否则中心跳变丢了,接收端就失去了位同步信息。用表格整理一下常用参数,方便大家直接套用。

数据速率编码时钟频率50MHz系统时钟下的半周期计数值
100kbps200kHz250
250kbps500kHz100
1Mbps2MHz25
10Mbps20MHz2

表中的半周期计数值 = CLK_FREQ / DATA_RATE / 2 - 1。比如1Mbps时,50M / 1M / 2 = 25,再从0开始计就要减1,所以代码里HALF_CYCLE=24。上板之前把系统时钟频率和期望速率代入这个表格,可以省掉一大半时钟分频相关的低级错误。

3. 解码器设计:从波形恢复数据

3.1 解码难点与适用场景

编码容易解码难,这是所有线路编码的共性。接收端不知道发送端什么时候开始发、bit边界在哪里,而且信号经过线缆、连接器、电平转换之后还会叠加噪声。对FPGA来说,解码算法要在“不知道自己什么时候该采样”的情况下,把波形还原成bit流。好在曼彻斯特编码自带时钟信息,每个bit中间一定有跳变,只要能识别出这个中心跳变,采样点就能对准。对于低速短帧应用,比如几十厘米板间通信、RFID模拟前端,可以用过采样和边沿检测来做,逻辑简单,资源占用低。如果数据速率高或者帧很长,就得上数字锁相环,这个问题稍后细说。

3.2 过采样解码器的实现

过采样解码的思路是:用比数据速率高得多的系统时钟持续采样输入信号,同时记录跳变沿的位置。以1Mbps数据、50MHz采样为例,一个bit周期有50个采样点,半个bit周期是25个点。曼彻斯特码流中,中心跳变和前一个中心跳变之间的距离通常是一个bit周期,而相邻bit相同时边界还会多出一次跳变,这个跳变与中心跳变的距离是半个bit周期。因此,解码器的第一步是测跳变间隔:如果间隔大约等于一个bit周期,认为是中心跳变;如果大约等于半个bit周期,认为只是边界跳变,忽略它。每检测到一次中心跳变,就把它作为采样基准,往后数大约1/4和3/4个bit周期,分别对电平采样,得到前半电平和后半电平。前半为高、后半为低就恢复出1,前半为低、后半为高就恢复出0。

parameter CLKS_PER_BIT = 50; // 50MHz采样1Mbps reg [5:0] cnt_edge; reg [5:0] pos; reg edge_det; reg sample1, sample2; reg rx_bit; always @(posedge clk) begin // edge_det是跳变沿脉冲,由两级同步后的信号生成 if (edge_det) begin // 跳变间隔判断:cnt_edge ~= CLKS_PER_BIT 为中心跳变 if (cnt_edge == CLKS_PER_BIT/2) begin // 边界跳变,校准计数器但不用来采样 pos <= CLKS_PER_BIT/4; end else if (cnt_edge >= CLKS_PER_BIT*3/4 && cnt_edge <= CLKS_PER_BIT*5/4) begin // 中心跳变,作为采样基准 pos <= 0; end cnt_edge <= 0; end else begin cnt_edge <= cnt_edge + 1'b1; pos <= pos + 1'b1; end if (pos == CLKS_PER_BIT/4) sample1 <= man_in; if (pos == CLKS_PER_BIT*3/4) sample2 <= man_in; if (pos == CLKS_PER_BIT*3/4) begin if (sample1 && !sample2) rx_bit <= 1'b1; else if (!sample1 && sample2) rx_bit <= 1'b0; else rx_bit <= 1'bx; end end

这段代码我写的时候故意去掉了一些同步寄存器和复位逻辑,留下的是核心思路。实际工程里,man_in必须经过两级触发器同步,否则跨时钟域采样的亚稳态会导致cnt_edge乱跳。另外,pos在中心跳变后从0开始计数,遇到边界跳变时只校准到1/4周期,不要重置整个采样窗口,因为边界跳变不是bit起点,只是两个bit之间的额外翻转。

3.3 时钟同步与抗干扰

上面这个过采样方案,隐含了一个前提:收发两端时钟标称频率一致,并且短时间内偏差可以忽略。如果发送端用1.000MHz晶体,接收端用0.999MHz,短帧问题不大,长帧跑到后面采样点就会逐渐偏离bit中心,最终错位。工程上解决时钟漂移的办法有两种。一种是帧级别重同步,每一帧开头加一段可识别的同步头或前导码,接收端每次收到同步头就重新对齐采样相位,适用于数据包较短、两帧之间有间隔的场景。另一种是数字锁相环(DPLL),根据跳变沿位置和期望位置之间的相位误差,每周期微调计数器长度,让采样点一直锁定在bit中心。DPLL逻辑比边沿计数复杂,但在10BASE-T和连续码流的场景里几乎是必须的。如果只是做项目验证,我建议先把帧同步方案跑通,再去磕DPLL,毕竟调试难度是跨数量级上升的。

抗干扰方面,一个常见陷阱是输入信号经过连接器或线缆后沿变缓,FPGA内部同一时刻可能采到不稳的中间电平。除了两级同步寄存器,还可以在解码前加一个简单的施密特触发器逻辑,或者把采样点从跳变沿拉开一点,不要在跳变沿附近采样。实际调试时,我通常会把跳变沿检测窗口放宽到20%容限,宁可损失一点抗噪能力,也要保证时钟恢复稳定。

4. 仿真验证与上板调试验证

4.1 测试用例设计

写Testbench时,不要一上来就给编码器灌随机数据,容易掩盖边界问题。我习惯分三步。第一步,连续发送0x55。0x55的二进制是01010101,曼彻斯特编码后每个bit中心都会跳变,同时bit边界也会跳变,波形是一个固定频率的方波,这样可以快速验证时钟分频是否正确。第二步,连续发送0x00和0xFF,这两种码型在bit边界没有额外跳变,只靠中心跳变维持同步,最适合检查接收端会不会误判边界。第三步,发送一组递增数或者带CRC的伪随机序列,配合解码器Testbench,自动比对发送数据和恢复数据是否一致。下面这段Testbench代码实现了最基本的字节发送任务,方便大家在此基础上扩展。

task send_byte(input [7:0] byte_in); begin @(posedge clk); tx_valid = 1'b1; tx_data = byte_in; @(posedge clk); tx_valid = 1'b0; repeat (16) @(posedge clk); // 8bit * 2半周期 = 16个编码时钟周期 end endtask

注意repeat后面的时钟数,是编码时钟,不是系统时钟。如果编码器内部用分频后的慢时钟处理数据,Testbench里的等待时间要重新计算,否则当前字节还没发完,下一个字节就覆盖进去了。

4.2 上板波形测量与物理层注意点

仿真过了不代表上板就好,物理层经常是第一杀手。我之前用入门级FPGA开发板做实验,直接把FPGA输出的LVCMOS信号用杜邦线接到另一块板子,示波器上看到的波形沿口全是振铃,边沿检测电路一会儿多跳一次一会儿少跳一次。后来检查发现两个问题:一个是IO驱动强度设成了最大档,驱动电流太大;另一个是信号地回路过长。解决办法是,在约束文件里把驱动强度改到合适的档位,同时在被测信号上串联一个33Ω电阻,靠近发送端放置。测量时,示波器探头的地线夹尽量短,直接夹在信号地旁边,不要用长鳄鱼夹跨接,否则会引入地环路噪声,把曼彻斯特波形的跳变沿搅得一塌糊涂。如果板卡支持,还可以用FPGA内部的ILA抓内部信号,但ILA通过JTAG回传数据有带宽限制,适合看低速码流,高速场景建议在FPGA内部先做存储再回读。

4.3 常见问题速查表

下面把调试过程中比较常见的现象和排查思路整理成一张表,按“现象-原因-方向”来排查,能省下不少对着时序图发呆的时间。

问题现象可能原因排查方向
输出波形长时间为高/低,无跳变编码时钟分频错误,发送状态机没触发检查HALF_CYCLE参数和tx_valid时序
波形跳变频率是预期的两倍每个bit用了2个编码时钟,但分频算成了数据速率核对CLK_FREQ / DATA_RATE / 2
位中间电平不对,前后半序颠倒编码约定的逻辑相反统一“1高到低,0低到高”,或反向
接收端恢复数据全是0/1采样点没有对准bit中心检查位同步计数器是否被边界跳变干扰
长帧后面数据错乱收发两端时钟偏差累积改用DPLL或定期重新同步帧结构
示波器看到振铃/台阶驱动强度过大、阻抗不匹配调低驱动、加串阻、检查连接器地

第一行的问题最常出现在刚写完编码器没仿真直接上板的情况。分频参数算错,时钟频率不对,输出波形自然就不对。第二行则是把半周期计数值按全周期算导致的,也属于参数问题。第三行如果前面都对,那大概率是编码方向约定和接收端不一致,逻辑功能没改,只是协议方向的统一问题。后面几行涉及到接收端同步和物理层,排查优先级建议按表格顺序来:先确认波形频率,再确认电平方向,然后看采样点,最后才怀疑物理层。

最后再分享一个我自己的调试习惯:曼彻斯特编码的项目,无论看起来多简单,我都会在工程里加一个“回环自测”模块,把编码器输出直接接到解码器输入端,中间再插一个可控制的噪声/毛刺注入逻辑。上板后先跑回环,确认编解码链路没问题,再接外部接口。这个习惯帮我避开了一大半“编码器好好的,解码器也单独验证过,连起来就是不通”的尴尬。你在做这个项目时,也可以用同样的方式把问题边界划清楚。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/6 7:49:17

LabVIEW经典实例全解析:从数据采集到架构设计

简介&#xff1a;本资源是一套面向LabVIEW初学者与工程实践者的经典实例合集&#xff0c;涵盖数据处理、仪器控制、界面交互与系统集成等核心应用场景&#xff0c;有效解决图形化编程入门难、典型功能实现无参考、跨VI数据共享不清晰等常见问题。压缩包共287个文件&#xff0c;…

作者头像 李华
网站建设 2026/9/4 7:35:11

基于YOLOv8的高速公路抛洒物检测工具包:从训练到部署全流程实战

简介&#xff1a;面向高速公路路面抛洒物检测场景&#xff0c;这份基于YOLOv8的开箱即用工具包&#xff0c;适合本科毕设、课程设计或工程原型验证&#xff0c;兼顾算法复现与可视化操作。资源内置轻量级模型与训练好的权重文件&#xff0c;通过可视化界面即可对图片、视频及摄…

作者头像 李华
网站建设 2026/9/6 5:06:16

智能家居Android项目实战:MQTT通信与App开发全解析

简介&#xff1a;本资源是一套完整的智能家居Android应用开发实战资料包&#xff0c;面向计算机、物联网、自动化、电子信息等相关专业在校学生及初入行的开发者&#xff0c;解决从零构建智能设备控制App的学习与项目落地难题。压缩包共220个文件&#xff0c;含62个Java核心逻辑…

作者头像 李华
网站建设 2026/9/6 9:14:06

掌门流系统流开篇设计:破落宗门、召唤老祖与逆天神徒的创作拆解

这个标题放到玄幻网文里&#xff0c;辨识度很高&#xff1a;主角刚接手一个快散架的宗门&#xff0c;系统立刻激活&#xff0c;召唤无上大帝老祖坐镇&#xff0c;再靠系统收拢一堆天赋异禀还很能惹事的徒弟。熟悉网文的人一眼就能看出&#xff0c;这是“掌门流”加“系统流”的…

作者头像 李华
网站建设 2026/9/7 8:27:11

读懂二极管参数:从数据手册到实测选型指南

在日常焊接和调试电路时&#xff0c;很多人都有过这样的经历&#xff1a;明明照着原理图买了“同型号”的二极管&#xff0c;焊上去之后电路要么不工作&#xff0c;要么发热严重&#xff0c;严重一点的直接冒烟。问题往往不在焊接手艺&#xff0c;而在一个容易被忽略的步骤——…

作者头像 李华
网站建设 2026/9/6 5:56:42

Unity迁移Godot实战:核心差异、脚本对比与选型指南

最近和几个做游戏开发的朋友聊到一个共同感受&#xff1a;Unity 这几年的路并不好走&#xff0c;而 Godot 反而一次次出现在正面讨论里。甚至在一些招聘群里&#xff0c;已经能看到“熟悉 Godot 优先”的需求。作为一个经常在 Unity 里做项目、也在业余时间折腾 Godot 的开发者…

作者头像 李华