资讯动态

FPGA实现BiSS-C接收模块:时序采样、CRC-6校验与上板调试

发布时间:2026/10/26 3:36:36 来源:尧图企业网站定制
简介面向光栅位置采集与伺服反馈场景的BiSS-C单向接收模块实现适合已具备FPGA基础的开发者研究主从同步串行接口的时序与数据解析。资源围绕BiSS-C协议中光栅作为从设备、FPGA作为主控的通信过程给出可综合的接收逻辑并配套仿真测试代码便于在ModelSim/ISim中观察位同步、帧解析与校验行为理解位置数据的获取节奏和传输速率控制。压缩包共58个文件、约358KB除Verilog源文件外还包含ISE工程与项目配置、综合与实现报告、仿真工程与波形数据库、编译日志及脚本等基本覆盖从代码编写、工程搭建到仿真验证的完整环节目录结构保留了原始开发痕迹便于按工程组织方式检索。已有2405人学习下载。读者可据此搭建可复用的BiSS-C接收参考对照仿真波形排查时序与接口问题并借鉴工程配置与验证流程缩短光栅接口调试周期。1. 用 FPGA 写一个 BiSS-C 接收模块一根时钟线换回 26 位位置绝对值编码器在伺服轴上用得极多BiSS-C 是其中开放度比较高的一种串行协议主机发 MA 时钟从机在 SLO 上回数据加上电源和地六根线就能把 20 多位位置信息读回来。落到 FPGA 上这件事的本质是用确定的时序去采一对差分线上的电平MA 由 FPGA 自己产生采样点由 FPGA 自己挑CRC 由 FPGA 自己算没有中断、没有驱动、没有操作系统抖动。所以基于 FPGA 编写的 BiSS-C 接口协议接收模块真正值钱的地方是确定性——多轴可以并行同时刻采样帧间隔可以做到微秒级可控出问题时能用逻辑分析仪把每一拍电平抓出来看而不是对着一个黑匣子猜。这篇写给要把它落地的人协议怎么拆、Verilog 怎么写、参数怎么配、上板会翻在哪。2. 把 BiSS-C 时序翻译成状态机帧结构、时钟相位与采样点2.1 一帧到底有多少个时钟ACK、Start、数据、CRC-6 与 StopBiSS-C 是主机驱动的同步串行协议。FPGA 这边只负责两件事按固定频率翻转 MA然后在每个 MA 上升沿去读 SLO 的电平。从机被动响应帧结构是固定的把每一位的位置排清楚状态机基本就成型了。位序位域方向含义正常值0ACK从机→主机从机应答表示已检测到 MA 并开始输出01Start从机→主机帧起始标志位12 ~ 2n-1Data从机→主机位置数据LSB firstn 为分辨率位数任意2n ~ 7nCRC从机→主机6 位校验LSB first计算得出8nStop从机→主机帧结束从机释放总线拉高1所以主机需要发出的 MA 上升沿总数是 n 9。n 取 26 时是 35 个时钟取 32 时是 41 个。这里有个容易想岔的点ACK 和 Start 并不是额外开销意义上的握手它们就是帧里的前两位跟后面的数据位一样在每个 MA 上升沿被采下来。区别只在于这两位有固定的期望值可以用来做链路健康判断。数据字段的宽度不是协议规定的而是编码器决定的。单圈 17 位、19 位、23 位、26 位都常见多圈型会把圈数和单圈位置拼在一起总位数可能到 30 位甚至更多。所以接收模块的DATA_BITS必须参数化不能写死。这一点在选型阶段就要确认清楚拿编码器手册里的数据长度字段注意它是否已经包含多圈位和状态位有些编码器会在数据段里塞错误标志位和警告位如果包含DATA_BITS就要按总长度算然后在解析阶段再把状态位切出来。CRC 的计算范围也有讲究。协议规范里 CRC 覆盖从 ACK 开始到数据最后一位但实际见到的编码器实现里有一部分只对数据位做校验不把 ACK 和 Start 算进去。这不是谁对谁错的问题是不同厂商的实现差异。我的处理方式是在模块里加一个CRC_INC_HEADER参数置 1 时把 ACK 和 Start 一起喂进 LFSR置 0 时跳过这两位。上板第一件事就是用这个开关各试一次看哪个能稳定通过校验然后写进配置表。硬编码一种写法换一个品牌的编码器就是全红。还有 CRC 输出是否取反的问题。有些实现会在移位完成后把 6 位结果按位取反再发出有些直接输出。同样做成参数CRC_INVERT两个开关一共四种组合实测两分钟就能确定比翻手册快。2.2 MA 时钟相位为什么采样点必须卡在上升沿MA 是由 FPGA 产生的。想清楚采样点在哪得先想清楚从机什么时候更新 SLO。从机的行为是检测到 MA 的某个边沿后延时 t2 把 ACK 拉低之后每一个 MA 周期在下降沿附近更新 SLO 上的数据位。也就是说数据在 MA 低电平期间变化在 MA 高电平期间保持稳定。主机如果在上升沿采样采到的是刚刚稳定下来的值如果在下降沿采样正好撞在数据跳变的时刻上采到 0 还是 1 全看布线延时和温度这就是典型的玄学现场。所以采样沿必须是 MA 上升沿这一点没有商量余地。真正需要算的是余量。假设 MA 频率 2 MHz周期 500 ns高电平和低电平各 250 ns。从机在下降沿后 t2 时间更新数据t2 典型 100~200 nsFPGA 侧对 SLO 做同步和滤波又会引入若干拍延迟。只要下降沿 t2 滤波器延迟仍然小于下一个上升沿采样就是安全的。反过来说如果 MA 频率拉到 10 MHz半周期只有 50 nst2 就吃掉一大半再叠加三级同步寄存器和线缆延时余量直接归零。这就是为什么现场经常出现低频能读、一升频就 CRC 全错的现象——不是 CRC 算错了是采样点撞到边沿上了。工程上的做法是把 MA 频率做成可配置并且从低速往高速试。先跑 1 MHz 确认数据稳定再逐步升到 2 MHz、5 MHz每一步观察 CRC 通过率。线缆越长、差分接收器越慢能跑的最高频率越低。10 米线缆跑 10 MHz 是纸面参数实际留 30% 余量才敢量产。顺带说一句物理层。SLO 和 MA 都是差分信号进 FPGA 之前要经过差分接收器转成单端。MA 输出方向FPGA 引脚出来是单端的需要差分驱动器转成 A/B 一对。这两条路径的延时必须尽可能对称MA 从 FPGA 到编码器和 SLO 从编码器回 FPGA走线长度差 5 cm 就是大约 0.3 ns 的偏斜在 2 MHz 下微不足道在 10 MHz 下就开始咬人。所以 PCB 上这两对线要走等长差分对内也要等长这不是讲究是能不能跑高速的前提。2.3 三种帧尾判定定长计数、超时检测与 stop 位一帧什么时候算结束有三种判定方式我一般三个都用。第一种是定长计数。既然 MA 上升沿总数是 n9那就在第 n9 个上升沿之后收工。这种方式最简单状态机只需要一个位计数器。缺点是它假设编码器的数据长度和自己配置的DATA_BITS完全一致一旦位数配错就会把 CRC 位当数据位、把 Stop 位当 CRC 位结果就是 CRC 永远不过但帧时序看起来完全正常非常迷惑。第二种是 stop 位。第 n9 个上升沿采到的应该是高电平如果不是说明帧结构错位。这个检查几乎不增加成本但能抓住位数配错的问题一定要加。第三种是超时检测。从机在帧结束之后会把 SLO 释放为高电平直到下一次 MA 到来。如果在传输过程中SLO 长时间保持不变比如超过 20~30 µs说明编码器掉线或者线缆断了。注意这个超时判据要用在帧内不能用在帧间——帧间 SLO 本来就是高的。三种方式组合起来的逻辑是定长计数决定什么时候收数据stop 位检查决定帧结构对不对超时检测决定链路还在不在。三个标志分别给出frame_done、frame_err、link_err上层用起来才清楚到底是哪一类问题。只给一个valid信号调试时会非常痛苦因为分不清是没数据还是数据错。3. 手写可综合的 Verilog 接收模块分频、采样、CRC-63.1 顶层端口与参数怎么定先把接口定下来。端口越少越好把复杂度关在模块内部上层只看到给一个触发收一帧数据附带几个状态位。module biss_c_rx #( parameter integer CLK_HZ 100_000_000, // 系统时钟频率 parameter integer MA_HZ 2_000_000, // MA 时钟频率 parameter integer DATA_BITS 26, // 数据位宽按编码器手册填 parameter integer FILTER_LEN 3, // SLO 同步滤波级数1~3 parameter integer CRC_INC_HEADER 1, // CRC 是否覆盖 ACK/Start parameter integer CRC_INVERT 0, // CRC 结果是否取反 parameter integer GAP_CYC 100_000 // 帧间隔系统时钟周期数 )( input wire clk, input wire rst_n, input wire slo_in, // 差分接收器输出的单端信号 input wire start, // 单周期触发 output reg ma_out, // 送往差分驱动器的 MA output reg [DATA_BITS-1:0] data_out, output reg [5:0] crc_rx, output reg frame_done, // 单周期数据有效 output reg crc_err, // 单周期 output reg link_err, // 单周期ACK 或 Stop 异常 output reg busy );参数里有三个要重点说。MA_HZ不是随便填的它和CLK_HZ一起决定了分频系数必须满足CLK_HZ / (2*MA_HZ) FILTER_LEN 4否则同步滤波器的延迟会吃掉整个半周期采样点直接漂到数据跳变区。这个约束在综合时不会报错只会在板子上表现为间歇性误码是典型的血泪经验型坑。稳妥的做法是在模块里加一个 initial 断言或者综合前的 elaboration 检查参数不满足就直接报错别让它跑到硬件上。FILTER_LEN是 SLO 的同步滤波级数。差分信号进 FPGA 后是异步的必须先过两级同步寄存器消除亚稳态第三级用来做多数表决。表决逻辑是三位里取多数能滤掉单周期的毛刺。代价是引入 2~3 拍的确定性延迟。低速时无所谓高速时要把它降到 1也就是只做两级同步不做表决。GAP_CYC控制帧间隔。BiSS-C 的帧间隔下限由编码器的 tBUSY 决定典型 10~30 µs。给太小会打断编码器内部处理读到旧数据给太大浪费带宽。100 MHz 时钟下GAP_CYC 100_000就是 1 ms对大多数伺服应用绰绰有余。3.2 用计数器产生 MA 时钟并锁定采样拍MA 的产生不能直接用分频后的信号当逻辑时钟那样会引入新的时钟域。正确做法是用系统时钟计数翻转把 MA 当作一个普通输出信号同时用计数器的计数值产生这一拍是上升沿的使能脉冲。localparam integer HALF_DIV CLK_HZ / (2*MA_HZ); reg [15:0] div_cnt; reg ma_r; always (posedge clk or negedge rst_n) begin if (!rst_n) begin div_cnt 16d0; ma_r 1b0; end else if (div_cnt HALF_DIV-1) begin div_cnt 16d0; ma_r ~ma_r; // 半周期到翻转 end else begin div_cnt div_cnt 16d1; end end wire ma_tick (div_cnt HALF_DIV-1); wire ma_rise ma_tick ~ma_r; // 本拍翻转后 ma_out 由低变高 always (posedge clk or negedge rst_n) begin if (!rst_n) ma_out 1b0; else ma_out ma_r; end这里的ma_rise是一个单周期脉冲位置在ma_r从 0 翻到 1 的那一拍。ma_out比ma_r晚一拍输出是为了让引脚上的实际电平和内部采样逻辑对齐。这个一拍的偏差在半周期几十上百拍的情况下可以忽略但如果HALF_DIV很小高速 MA就要把ma_rise也相应延后一拍保证内部认为的上升沿时刻和引脚上的真实上升沿一致。SLO 的同步滤波reg [2:0] slo_sync; always (posedge clk or negedge rst_n) begin if (!rst_n) slo_sync 3b111; // 空闲态 SLO 为高 else slo_sync {slo_sync[1:0], slo_in}; end // 三级多数表决FILTER_LEN1 时退化为单级 wire slo_f (FILTER_LEN 3) ? ((slo_sync[2] slo_sync[1]) | (slo_sync[1] slo_sync[0]) | (slo_sync[2] slo_sync[0])) : slo_sync[1];复位值给 3b111 是有意的空闲时 SLO 是高电平复位成高可以避免上电瞬间被误判成 ACK 低电平。这个细节不注意上电第一帧经常是错的。采样动作就发生在ma_rise那一拍读slo_f。因为slo_f是三级寄存器的输出实际反映的是 3 拍之前的引脚电平所以必须满足HALF_DIV远大于 3 的前提。前面说的参数约束就是指这个。3.3 CRC-6 的 LFSR 写法与校验方式BiSS-C 用的是 6 位 CRC生成多项式 x^6 x 1。写成移位寄存器就是最高位与输入位异或后反馈到低位同时整体左移。reg [5:0] crc_r; reg crc_din; wire crc_fb crc_r[5] ^ crc_din; wire [5:0] crc_nxt {crc_r[4:0], 1b0} ^ (crc_fb ? 6b000011 : 6b000000); always (posedge clk or negedge rst_n) begin if (!rst_n) crc_r 6d0; else if (state S_IDLE) crc_r 6d0; // 每帧重新起算 else if (ma_rise crc_en) crc_r crc_nxt; end6b000011对应多项式里的 x^1 和 x^0 两项因为最高位 x^6 在移位时被移出等价于异或回 x1。crc_din由当前位序号决定如果CRC_INC_HEADER为 1那么位 0 和位 1 也要喂进去否则从位 2 开始。crc_en在数据段和 CRC 段之外都为 0。接收侧的 CRC 处理有两种思路。第一种是收到 6 位 CRC 后和本地计算值直接比较wire [5:0] crc_calc CRC_INVERT ? ~crc_r : crc_r; wire crc_ok (crc_rx crc_calc);第二种是把接收到的 CRC 位也继续喂进 LFSR最后看余数是否为零。后者省一个比较器但调试时看不到中间值出问题不好定位。我一般用第一种因为多出来的 6 个比较器在 FPGA 里根本不算资源能看到crc_rx和crc_calc两个值排查起来快得多。用逻辑分析仪抓一次两个值一对比就知道是数据错位还是 CRC 算法选错了。CRC 段本身是 LSB first 传输的也就是说接收到的第一位是 CRC 的最低位。所以在移位时要用crc_rx[i] slo_fi 从 0 开始递增而不是从高位往低位塞。这个地方错了表现是CRC 恒定不过但数据看起来正常很难查。3.4 主状态机从 ACK 到 stop 的移位流程状态机只需要四个状态关键是位计数器和各路使能的配合。localparam integer TOTAL_BITS DATA_BITS 9; localparam [1:0] S_IDLE 2d0, S_XFER 2d1, S_CHECK 2d2, S_GAP 2d3; reg [1:0] state; reg [7:0] bit_idx; reg [DATA_BITS-1:0] shreg; reg ack_r, start_r, stop_r; reg [15:0] gap_cnt; always (posedge clk or negedge rst_n) begin if (!rst_n) begin state S_IDLE; bit_idx 8d0; gap_cnt 16d0; frame_done 1b0; crc_err 1b0; link_err 1b0; busy 1b0; end else begin frame_done 1b0; crc_err 1b0; link_err 1b0; case (state) S_IDLE: begin busy 1b0; if (start) begin bit_idx 8d0; busy 1b1; state S_XFER; end end S_XFER: begin if (ma_rise) begin case (bit_idx) 8d0: ack_r slo_f; 8d1: start_r slo_f; default: begin if (bit_idx 2 DATA_BITS) shreg[bit_idx-2] slo_f; // LSB first else if (bit_idx 2 DATA_BITS 6) crc_rx[bit_idx-2-DATA_BITS] slo_f; // CRC 也 LSB first else stop_r slo_f; end endcase bit_idx bit_idx 8d1; if (bit_idx TOTAL_BITS-1) state S_CHECK; end end S_CHECK: begin data_out shreg; frame_done 1b1; crc_err ~crc_ok; link_err (ack_r ! 1b0) | (start_r ! 1b1) | (stop_r ! 1b1); busy 1b0; gap_cnt 16d0; state S_GAP; end S_GAP: begin gap_cnt gap_cnt 16d1; if (gap_cnt GAP_CYC[15:0]) state S_IDLE; end endcase end end逻辑上值得说明的有几处。位 0 采 ACK、位 1 采 Start这两位不进移位寄存器只做合法性判断判断结果在S_CHECK状态汇总成link_err。数据段从位 2 开始shreg[bit_idx-2]保证第一个数据位落在 bit 0 上符合 LSB first 的传输顺序。CRC 段同样从低位往高位填。最后一位是 Stop单独存一个寄存器。S_CHECK状态只停留一拍把结果全部打出去然后进入S_GAP等帧间隔。frame_done是个单周期脉冲上层用它做握手就行不需要额外的 ready 信号。busy在传输期间为高可以用来阻止重入触发。如果要做超时检测可以在这个状态机外面加一个独立的看门狗在S_XFER期间如果slo_f连续保持同一个值超过TIMEOUT_CYC个时钟就强制拉link_err并跳回S_IDLE。这个逻辑独立于主状态机避免把主流程搞复杂。4. 上板之前先在仿真里跑通从机模型、断言与相位扫描4.1 写一个 40 行的 BiSS-C 从机行为模型没有编码器在手也能验证接收模块方法是自己写一个行为级从机模型。它只做三件事等 MA 上升沿、在下降沿更新 SLO、把准备好的数据一位一位送出去。module biss_c_slave_model #( parameter integer DATA_BITS 26, parameter integer T2_NS 150, parameter integer CRC_INC_HEADER 1 )( input wire ma, output reg slo ); reg [DATA_BITS8:0] frame_bits; // ACKStartDataCRC6Stop reg [DATA_BITS-1:0] pos_data; reg [5:0] crc_val; integer idx; initial begin slo 1b1; pos_data 26h2A_5C_01; // 随便给一组数据后面靠断言比对 build_frame(pos_data, 6h15); // 期望 CRC 由外部 task 算好传入 end task build_frame(input [DATA_BITS-1:0] d, input [5:0] c); begin frame_bits[0] 1b0; // ACK frame_bits[1] 1b1; // Start frame_bits[2 : DATA_BITS] d; // 数据 LSB first frame_bits[2DATA_BITS : 6] c; // CRC frame_bits[2DATA_BITS6] 1b1; // Stop idx 0; end endtask // 第一个上升沿后延时 t2 拉低 ACK之后每个下降沿更新一位 reg ma_d; always (posedge ma) begin ma_d 1b1; if (idx 0) begin #(T2_NS); slo 1b0; end end always (negedge ma) begin if (idx DATA_BITS 9) begin slo frame_bits[idx]; idx idx 1; end else begin slo 1b1; end end endmodule这段模型的关键在时序对齐ACK 是在第一个 MA 上升沿之后延时 t2 才拉低的所以主机在第二个上升沿采到的是 ACK之后每个下降沿更新一位主机在下一个上升沿采样正好采到稳定值。T2_NS做成参数是为了在仿真里扫描它看接收模块能容忍多大的 t2 偏移。需要提醒的是这个模型里的#(T2_NS)是行为级延时只在仿真有效不能综合。写测试平台时可以随手用别不小心把它复制到 RTL 里。4.2 三类必测用例正常帧、CRC 错、编码器掉线仿真激励不需要写得多花哨三个用例覆盖 90% 的问题。第一个是正常帧。跑DATA_BITS 26随机生成 100 组数据每组都配一个正确的 CRC检查frame_done是否每次都来、data_out是否等于输入、crc_err是否恒为 0、link_err是否恒为 0。initial begin for (int i 0; i 100; i) begin pos_data $urandom; crc_val calc_crc(pos_data); // 与 DUT 同算法的参考函数 slave.build_frame(pos_data, crc_val); (posedge clk); start 1b1; (posedge clk); start 1b0; wait (frame_done); if (data_out ! pos_data) $error(data mismatch %0d, i); if (crc_err) $error(crc fail %0d, i); if (link_err) $error(link fail %0d, i); repeat (2000) (posedge clk); // 等帧间隔 end end第二个是 CRC 错。故意把 CRC 值翻一位检查crc_err是否在frame_done同一拍拉高同时data_out还是照常输出——错误码和数据要一起给到上层由上层决定是丢弃还是保留。有些实现一检测到 CRC 错就把frame_done也压掉结果上层根本不知道这一帧错过了反而难处理。第三个是编码器掉线。让从机模型在某一帧中途停止响应SLO 一直保持高电平检查link_err或者超时逻辑是否在预期时间内触发并且状态机能自动回到S_IDLE准备下一帧。这个用例能抓出状态机卡死的问题——如果有任何一个状态在异常输入下没有出口仿真就会超时比上板发现问题便宜太多。4.3 用相位扫描确认采样窗口有多宽仿真跑通正常帧只是及格线真正有价值的是知道采样窗口有多宽。做法是给从机模型加一个可调的相位偏移参数让 SLO 的更新时刻相对于 MA 下降沿提前或滞后若干个纳秒然后扫描这个偏移看接收模块在多大的偏移范围内还能正确解码。相位偏移MA 频率 1 MHzMA 频率 2 MHzMA 频率 5 MHz-80 ns通过通过失败-40 ns通过通过通过0 ns通过通过通过40 ns通过通过通过80 ns通过通过失败120 ns通过失败失败这张表是仿真扫出来的典型结果重点看规律频率越高能容忍的偏移范围越窄。2 MHz 下 ±80 ns 都能过说明半周期 250 ns 里有一大半是安全的5 MHz 下只剩 ±40 ns余量不到半周期的 40%这时候线缆延时、温度漂移、器件批次差异都会开始起作用。这张表还有个用法把它和实测的编码器 t2 参数对一下。如果编码器的 t2 标称 200 ns、偏差 ±50 ns而你的模块在 5 MHz 下只有 ±40 ns 的容忍度那 5 MHz 这个配置就是不可用的哪怕实验室里跑得好好的。选频率的原则是让编码器参数偏差范围落在模块容忍范围的中间三分之一不是取最大值。5. 上板调试与避坑从数据恒为 0到稳定读 26 位5.1 五条现场踩坑记录现象、原因、解决第一条数据恒为 0CRC 恒不过但 MA 波形完全正常。现象看起来很矛盾时钟在动数据线也有跳变可读回来的 26 位全是 0。原因是DATA_BITS配错了。编码器实际输出 29 位26 位位置 3 位状态配置里填了 26导致接收模块提前三个时钟就进入 CRC 段把数据和 CRC 混在一起算结果自然全错。解决方式是把DATA_BITS改成 29然后在解析层把低 3 位当状态位切出来。教训是手册上的分辨率和数据长度是两个字段别混。第二条低速能读频率一升 CRC 通过率掉到 60%。原因是采样点余量不足。1 MHz 时半周期 500 ns三级同步加滤波延迟 30 ns 完全不构成问题5 MHz 时半周期只剩 100 ns延迟占比到 30%再加上差分接收器的传输延迟和线缆延时采样点已经贴到数据跳变沿上。解决方式有两个把FILTER_LEN从 3 降到 1省掉两级延迟或者把 MA 频率退回到 2 MHz用带宽换稳定性。现场最后选了后者因为前者会让毛刺抑制能力下降长线缆环境下误码率反而上升。第三条上电第一帧永远是错的之后恢复正常。原因是 SLO 的同步寄存器复位值给成了 0。空闲时 SLO 是高电平复位成 0 会让状态机在第一个 MA 之前就误判出一个 ACK 低电平把帧结构整体错位一位。解决方式是把slo_sync的复位值改成 3b111和空闲电平一致。这个坑很隐蔽因为只影响上电第一帧跑起来之后就看不到了只有在做上电自检的时候才会暴露。第四条多轴同时读某几个轴的数据偶尔串到别的轴上。原因是多路复用逻辑共用了同一个状态机和移位寄存器而各轴的帧间隔没有对齐导致 A 轴的数据还没移完B 轴的触发就进来了。解决方式有两种一是每轴一份独立的接收模块用参数化在顶层例化 N 次资源占用其实不大一个 26 位模块大概几百个 LUT二是加一个仲裁器保证同一时刻只有一路在传输。第一种更简单也更可靠多轴设备上我一般直接选第一种。第五条CRC 校验偶发失败重试就好频率大概千分之一。这种偶发问题最折磨人。抓波形发现失败的那一帧CRC 值本身是对的但采样到的数据某一位错了。再仔细看错的那一位正好在 MA 从低变高的时刻附近SLO 上有一个几十纳秒的毛刺。原因是差分接收器的输出没有做端接匹配反射造成边沿振铃振铃恰好落在采样窗口里。解决方式是在接收器输出端加一个几十欧姆的串联电阻做阻抗匹配软件上再配合把FILTER_LEN提到 3 做多数表决。这个问题改了硬件之后彻底消失光靠软件滤波只能压到万分之一治标不治本。5.2 用 ILA 抓帧时的观测点与触发条件上板调试绕不开逻辑分析仪。要抓的信号不用多但必须选对否则抓上来的波形没法看。必抓的信号有六个ma_out、slo_in注意抓引脚上的原始信号不是同步之后的、ma_rise、bit_idx、crc_rx、crc_calc。前两个看物理层时序ma_rise和bit_idx看状态机是否按预期推进后两个看 CRC 计算是否正确。触发条件建议设在ma_rise bit_idx 0也就是帧的第一个上升沿这样能完整抓到一帧。采样深度按最大帧长算DATA_BITS 32时是 41 个 MA 周期一个 MA 周期在 100 MHz 系统时钟下是 50 个采样点总共 2050 个点加上前后余量4096 深度足够。抓上来之后按三步看。第一步看slo_in在ma_out上升沿的位置上是否稳定如果正好在跳变说明采样点有问题。第二步看bit_idx是否从 0 走到TOTAL_BITS-1然后进S_CHECK如果中途跳变说明有异常复位。第三步比对crc_rx和crc_calc如果数据位看起来对但 CRC 不对多半是 CRC 覆盖范围或者取反参数选错了把两个参数四种组合依次试一遍即可。6. 进阶多轴级联、把接收模块固化成可复用 IP单轴跑通之后真正花时间的是把它变成能反复用的东西。我的习惯是把整个接收模块封装成一个带 AXI4-Lite 或者简单寄存器接口的 IP对外只暴露四个寄存器控制启动、使能、软复位、状态busy、frame_done、crc_err、link_err 的 sticky 位、数据低 32 位、数据高位与帧计数。sticky 位很重要——错误是瞬时的CPU 轮询很可能错过让它一直保持到软件写入清除才能统计出每千帧的误码率。这个误码率是判断链路质量最直接的指标比看波形靠谱。多轴场景下每轴例化一份但 MA 时钟源可以共享同一个分频计数器保证所有轴的采样时刻严格对齐。这对多轴联动的设备很关键如果各轴独立分频即使频率相同相位也会慢慢漂移做同步插补时会出现微小的位置偏差。共享计数器之后所有轴的 MA 上升沿在同一拍产生采样在同一拍进行位置数据天然同步。代价是所有轴必须用同一个 MA 频率不能单独调速实际用下来这个限制可以接受。还有一个容易被忽略的点是跨时钟域。如果上层是 CPU 或者另一个时钟域的逻辑frame_done这个单周期脉冲必须做脉冲同步不能直接跨域用。稳妥的做法是把它转成一个电平翻转信号跨域后再还原成脉冲或者干脆用一个小 FIFO 缓冲几帧数据。项目早期图省事直接跨域结果就是偶发的丢帧查了两天才定位到同步器上。最后一个建议把DATA_BITS、CRC_INC_HEADER、CRC_INVERT、MA_HZ这四个参数写进一个配置文件跟着产品型号走不要硬编码在 RTL 里。同一套硬件平台换一个编码器型号往往只需要改这四个值重新综合而不是改代码。这件事我在第一个项目上没做结果换编码器时手改了五处地方漏掉一处多花了一整天的返工。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑