首页 / 资讯中心 / 文章详情

FPGA实现BISS-C协议读取绝对式编码器:从状态机到CRC校验调试指南

FPGA实现BISS-C协议读取绝对式编码器:从状态机到CRC校验调试指南 ★ FEATURED ARTICLE
简介面向FPGA开发者与工业编码器应用工程师的BISS-C协议编码器Verilog实现源码包解决了在高端运动控制与位置反馈系统中读取雷尼绍BISS-C协议编码器的问题。方案通过parameter参数化支持18/26/32/36bit位宽灵活切换最高10MHz时钟频率并采用CRC并行计算技术将校验耗时压缩至单时钟周期同时模块化设计可支撑四路编码器并行读取每路约9.8MHz对实时性和多轴场景尤为友好。包体共10个文件包括3个Verilog源文件核心解码模块、多通道模块及testbench、1个VCD仿真波形、1个仿真运行脚本以及README说明文档等30KB压缩包轻量易移植。方案不依赖IP核附带全局异步复位与动态时序约束处理便于导入不同FPGA平台。已有192人学习下载适合希望快速落地BISS-C协议或研究CRC并行加速与状态机超时检测的工程师参考。1. FPGA读绝对式编码器为什么先碰BISS-C协议做运动控制和伺服驱动的人迟早要跟绝对式编码器打交道。BISS-C协议在国产伺服和高端设备里出现频率越来越高它比EnDat和Hiperface开放、比SSI带宽高一根MA时钟线加一根SLO数据线就能把位置、状态、CRC一次性读回来。我拆过的这套FPGA实现BISS-C协议编码器源码包解决的就是“FPGA做主站、怎么稳定读编码器”这件事不是简单搭个移位寄存器而是把时序状态机、CRC校验、多圈处理都做进去了。适合想绕过数据手册直接看工程实现的人也适合正在为编码器读取出错发愁的一线调试工程师。下面按我从仿真到上板的路径来说清楚。2. BISS-C协议帧结构从数据手册到寄存器级理解2.1 BISS-C的物理层与时序参数BISS-C物理层是主从同步串行接口主站负责产生MA时钟从站编码器在SLO数据线上同步回传数据。注意它跟SPI有点相似但MA时钟不是持续自由的而是在一次读取周期里按帧突发。源码包里把MA时钟频率做成可配置我通常设在2MHz到5MHz之间。这个参数直接影响整帧读取时间也影响编码器从站的上电响应能力。传输方向只有两根线MA从主站到编码器SLO从编码器回主站。MA时钟上升沿和下降沿各有用途标准里常用上升沿锁存SLO数据但不少编码器在下降沿之后数据才真正稳定这个细节是后面调试踩坑的重灾区。物理层还要注意RS-422差分还是单端源码里默认按差分输入输出处理管脚约束时就要对应到FPGA的差分输入缓冲器。帧与帧之间MA时钟要保持高电平空闲。如果主站连续发时钟编码器会认为是一帧超长数据状态机直接乱掉。源码的状态机里专门有一段超时检测超过设定时钟周期数还没收到有效响应就强制回到IDLE否则整机联调时编码器线松动一次后续所有读取全废。2.2 一帧数据里藏了哪些字段BISS-C一帧从编码器回传的数据结构可以分成几段我拆源码时习惯先把每一段的位宽和使用场景列清楚。下表是BISS-C标准帧的典型字段分布具体位宽由编码器厂商和源码参数决定。字段段位宽作用说明Ack1位编码器对主站起始请求的应答Start1位数据开始标志通常为低电平Control1位控制位区分位置数据还是附加数据Data多比特单圈位置数据位宽可配置如17位或25位Error1位编码器内部错误标志CRC6位对Data和Error字段的CRC校验Timeout1位数据结束段主站感知传输结束注意一帧里并不只有位置数据。Control位决定这帧是普通位置帧还是带诊断信息的扩展帧源码包里把Control位引出来供上层逻辑判断。CRC是6位多项式校验覆盖范围从Data最高位到Error位不包括Ack和Start。很多第一次接触BISS-C的人会把CRC算到整个帧最后校验永远不过这是协议理解上的第一个坎。2.3 协议状态机与CRC校验范围源码的核心是一个有限状态机状态跳转跟着MA时钟和SLO反馈走。我从代码里理出的状态序列是IDLE、ACK、START、DATA、ERROR、CRC、COMPLETE。IDLE状态下MA时钟保持高电平置位读请求后状态机拉低MA启动传输编码器在第一个周期内回Ack。数据位在DATA状态逐一进入移位寄存器同时CRC计算模块并行处理进入的比特。CRC校验范围要卡准。BISS-C标准用的CRC多项式是x^6x^11初值通常为0处理的是从Data字段最高位到Error字段最后一位的比特流。源码里单独做了一个CRC子模块由状态机控制使能Data段开始时就启动CRC段来临时把接收到的CRC值与本地计算值做比较。比较结果不进数据通路只生成crc_error标志这样读取逻辑不会因为CRC报错就中断整机运行。实现时还有一点容易被忽视CRC移位方向。标准里是按高位先发还是低位先发编码器厂商之间并不统一。源码在参数表里提供crc_lsb_first选项默认配合大多数国产编码器选LSB first。如果发现CRC总报错先去看编码器手册里单圈位数据的发送顺序而不是马上怀疑代码逻辑。3. 源码包功能拆解工程结构、IP核与顶层模块划分3.1 源码目录与文件职责拆源码包第一步是看目录和文件划分。这套工程的常见结构是sources、tb、constraints三级目录。sources里放RTL代码和可选IP核tb里放仿真testbenchconstraints放管脚约束和时序约束。源码包拿到手先把目录树拉出来确认哪些是核心RTL哪些是工程生成文件避免把缓存文件当代码看。核心RTL文件职责可以按下表划分。实际文件命名以包内为准我拆过的版本里会有一个顶层模块和若干子模块文件/模块职责bissc_top顶层封装对外接口统一内部实例化下面的子模块bissc_clk_genMA时钟发生器参数控制时钟频率和空闲电平bissc_rxSLO数据接收完成移位和位宽拼接bissc_crc6位CRC计算和校验带使能和复位bissc_state主状态机协调发送MA时钟和接收数据bissc_reg读写寄存器接口对接Avalon或AXI-LiteAXI-Lite对接在工程里是可选的。如果你的系统是纯逻辑读取不经过CPU那bissc_reg可以去掉顶层直接输出position和error标志位。如果要做参数化配置比如运行中切换MA频率或单双圈模式保留寄存器接口更方便。3.2 时钟域与复位策略FPGA工程里系统时钟和MA时钟是两个域。系统时钟通常50MHz或100MHzMA时钟由分频产生。跨时钟域处理不好位置数据会偶发跳变。源码里MA时钟生成没有用计数器直接翻转而是先做同步使能再用使能沿去驱动状态机保证MA的每个周期对应固定的系统时钟周期数。复位策略也不能马虎。标准做法是整个BISS-C链路使用异步复位、同步释放复位信号由顶层统一控制。复位有效时要同时清掉状态机、移位寄存器、CRC模块不能只复位主状态机否则重新读取时从站还是旧状态。源码里每个子模块都带rst_n端口顶层用同一个复位网络代码里可以看到复位释放逻辑单独处理。我还会留意全局复位和局部复位。工程里如果有软复位寄存器和硬复位引脚两个复位信号要做一个或门软复位也要能清状态机。不然在线升级或断线重连后只能整板掉电才能恢复。源码包里这部分逻辑做在顶层仿真时不容易暴露上板联调才会发现。3.3 主要模块接口说明顶层接口设计直接决定接入难度。我按下表列出最常见的一组端口信号方向以FPGA主站视角为准端口名方向说明clkin系统时钟如50MHzrst_nin全局复位低有效ma_clkout差分MA时钟输出ma_clk_nout差分负端单端模式悬空slo_datainSLO数据输入slo_data_nin差分负端单端模式不接read_reqin外部发起读取请求脉冲positionout解析后的位置数据validout位置数据有效标志crc_erroroutCRC校验错误标志busyout状态机忙碌指示差分负端在单端编码器下可以不接但MA必须区分正负。源码里用OBUFDS差分输出缓冲器原语单端接入时直接把负端约束到地。实现时建议把SLO输入加一个IBUFDS再进逻辑避免信号质量差导致采样误判。顶层模块的Verilog端口声明通常长这样我贴一段精简过的接口代码module bissc_top #( parameter SINGLE_TURN_BITS 17, parameter MA_CLK_DIV 20 )( input wire clk, input wire rst_n, input wire read_req, output wire ma_clk, output wire ma_clk_n, input wire slo_data, input wire slo_data_n, output wire [31:0] position, output wire valid, output wire crc_error, output wire busy ); wire [SINGLE_TURN_BITS-1:0] raw_position; bissc_state u_state ( .clk (clk), .rst_n (rst_n), .read_req (read_req), .ma_clk (ma_clk), .ma_clk_n (ma_clk_n), .slo_data (slo_data), .slo_data_n (slo_data_n), .valid (valid), .raw_position (raw_position) ); bissc_crc #( .DATA_WIDTH(SINGLE_TURN_BITS) ) u_crc ( .clk (clk), .rst_n (rst_n), .data_in (raw_position), .enable (valid), .error (crc_error) ); assign position {15d0, raw_position}; endmodule逻辑说明顶层模块把MA时钟生成、状态机、CRC校验分开实例化。SINGLE_TURN_BITS控制单圈数据位宽MA_CLK_DIV控制分频系数。read_req是发起一次完整读取的触发脉冲valid拉高时表示position已经更新同时CRC结果也出来了。position拼接成32位方便接CPU总线。注意这里把CRC校验放在valid之后实际工程里valid或crc_error是同时变的要让上层逻辑同一拍采样这两个信号避免读位置时校验结果还是上一帧。参数说明SINGLE_TURN_BITS默认17对应常见的17位单圈编码器如果是25位就改成25。MA_CLK_DIV是系统时钟分频系数50MHz系统时钟配20就是2.5MHz MA如果编码器支持10MHz这个参数可以压到5。分频系数不能太小因为状态机在MA每个周期的处理逻辑也要时间分频系数低于4时大概率时序紧张。4. 在FPGA上跑通BISS-C读取仿真、管脚约束与板上调试4.1 搭建仿真Testbench的步骤源码包里的tb目录通常带一个简化编码器模型不依赖真实硬件就能把状态机和采样逻辑跑通。这个模型会按BISS-C协议把位置数据、CRC和错误位拼接成串行比特流再按MA时钟回推给主站。第一次跑仿真时务必先看波形里的MA时钟频率确认分频系数生效接着再看ACK段和Data段是否按预期对齐。tb里核心逻辑是模拟编码器从站的串行反馈我一般会写一个简化版的行为模型当作参考代码大致思路是这样的module bissc_slave_model #( parameter DATA_WIDTH 17 )( input wire ma_clk, input wire read_start, output reg slo_data ); reg [DATA_WIDTH-1:0] position_data; reg [5:0] crc_value; reg sending; reg [5:0] bit_cnt; always (posedge ma_clk or negedge read_start) begin if (!read_start) begin sending 1b1; bit_cnt 6d0; position_data 17h1A2B3; end else if (sending) begin bit_cnt bit_cnt 1b1; case (bit_cnt) 6d0: slo_data 1b0; // Ack 6d1: slo_data 1b0; // Start 6d2: slo_data 1b0; // Control default: begin if (bit_cnt 3 DATA_WIDTH) slo_data position_data[DATA_WIDTH-1 - (bit_cnt-3)]; else slo_data crc_value[5 - (bit_cnt - 3 - DATA_WIDTH)]; end endcase end end endmodule逻辑说明这个模型在read_start信号有效后把Ack、Start、Control依次拉低然后按高位到低位的顺序把position_data打出去最后输出CRC。它没包含Error位实际使用时可以在Control之后插入一个可变Error位再把CRC宽度对应调整。仿真时用这个模型可以验证主站是否采到正确位置、CRC是否匹配也能用来检查状态机是否能正确处理单帧结束。参数说明DATA_WIDTH要跟主站的SINGLE_TURN_BITS保持一致不一致时CRC必然报错。read_start在真实编码器里是由MA时钟起始沿触发的这里用独立信号是为了让模型知道一帧开始避免在仿真初始时刻误发数据。position_data作为测试源随便填但要注意别填超过DATA_WIDTH范围的值否则移位时会丢高位。4.2 管脚约束与电气特性注意管脚约束是上板后最容易出问题的地方。BISS-C的MA时钟是差分对SLO数据如果也是差分就必须用FPGA的差分IO组不能随便选两个普通IO当差分对。差分正端和负端要约束在同一个差分组内否则综合工具会报错或者生成的时序特别差。我在约束文件里会用set_property PACKAGE_PIN指定正负端同时用set_property DIFF_TERM TRUE打开片上差分端接。电气特性上编码器端的线缆长度会直接影响最高MA频率。线长超过1米时2.5MHz的MA一般没问题超过5MHz之后上升沿变缓SLO数据采样点就会漂。源码包里的MA_CLK_DIV留了很大的调整空间但硬件上最好在编码器供电端加滤波电容信号线走差分对避免因为电机动力线耦合噪声导致偶发CRC错误。约束文件里时序约束要关注MA输出到SLO输入的路径。SLO是编码器随MA时钟回传的数据从MA输出沿到SLO有效沿之间存在编码器响应时间这个时间通常为几百纳秒。FPGA综合工具不能自动知道这条外部路径要在约束里用set_max_delay或虚拟时钟把这个外部延迟约束进去。我建议把MA时钟设成generated clockSLO数据按该时钟域做input delay约束否则布局布线后采样时序没保证。4.3 用ILA抓实际时序上板调试时ILA是最直接的验证工具。先在顶层把ma_clk、slo_data、busy、valid、crc_error全部引到ILA探针里采样深度建议设成4096采样频率用系统时钟。触发条件设为read_req上升沿这样能看到完整一帧从发起请求到valid拉高的过程。抓到的波形先看MA时钟发包个数再数SLO有效位数。如果MA时钟数量固定但SLO数据全为高或全为低通常是SLO输入管脚约束不对或者差分正负接反。如果MA时钟数量多了说明状态机没有在CRC结束后及时停止时钟编码器把多余时钟当作下一帧起始波形里会看到连续两帧粘连。ILA只在调试阶段使用量产工程里要把ILA核去掉或置为不例化不然逻辑资源和时序性能会被拖累。我之前调试一片板子时忘了屏蔽ILA结果显示时序收敛没问题但运行时CRC偶尔报错后来定位到是ILA核插入导致MA时钟路径变长。从那以后我把探针做成宏开关仿真和调试开启正式综合时强制关闭。5. BISS-C调试避坑时序翻车、CRC错位与热插拔问题5.1 现象MA时钟频率过高导致编码器无响应现象把MA_CLK_DIV设为4系统时钟50MHzMA跑到12.5MHz编码器完全没反应SLO一直高电平状态机卡在ACK阶段valid永远不拉高。原因编码器从站对MA时钟有最大频率限制常规编码器支持到5MHz或10MHz12.5MHz超出协议要求从站内部逻辑跟不上直接不应答。解决查编码器手册的通信频率范围把MA_CLK_DIV调回合法范围。调试阶段从低速开始先用5分频跑通再逐步调高。如果必须跑高频就要考虑缩短线缆长度、降低负载电容。MA时钟不是越快越好它只影响读取刷新率伺服控制周期一般几百微秒2.5MHz完全够用。5.2 现象SLO数据在第二拍才稳定采样点选错现象仿真时数据正确上板后position偶发跳变ILA抓波形发现SLO数据在MA上升沿之后还有抖动采样点打在了数据稳定之前。原因编码器并非在MA上升沿瞬间更新SLO数据线上存在从站输出延迟。MA时钟路径与SLO回传路径的累计延迟不一定能被FPGA内部时钟对齐单纯用上升沿采样是碰运气。解决在状态机里把SLO采样点改为MA下降沿之后半个系统时钟周期或者直接生成本地延迟采样使能让采样点避开数据变化沿。具体做法是计数MA周期在周期中间位置产生采样脉冲。这个逻辑源码包里可以参数化控制我一般默认开延迟采样只有对延迟极小的编码器才切回上升沿直接采样。注意延迟采样不能跨时钟域要在系统时钟域内生成同步使能。5.3 现象CRC校验总报错数据移位一位现象valid正常拉高position值看起来有规律变化但数值整体偏小或每位都像左移了一位crc_error一直为高。原因CRC校验范围或位序配置不对。常见是编码器数据帧里多了一位Error字段而源码里把Error位当成了Data的一部分或者CRC计算时把Ack和Start也纳入了范围导致校验结果对不上。解决先逐项核对Data字段位宽和CRC覆盖宽度。用ILA抓一帧完整SLO序列人工把里面的Ack、Start、Control、Data、Error、CRC字段标出来再跟源码的状态机位宽对照。CRC位序也在这里看如果编码器手册写明数据高位先发就把crc_lsb_first设为0。还有一个细节CRC计算需要把Error位包含进去即使Error位固定为0也不能跳过否则CRC计算结果会整体偏移。5.4 现象上电后第一次读卡死状态机不复位现象系统上电后第一次发read_reqbusy信号拉高后一直不拉低valid和crc_error都不变化但断电重新上电后第一次读取正常再触发一次卡死。原因状态机在上电或复位释放瞬间收到未定义的SLO电平把状态误判成别的状态而状态机没有看门狗超时机制卡在某个错误状态不返回IDLE。另一种可能是在线调试时复位信号比系统时钟先释放状态机寄存器处于亚稳态。解决源码里需要有一个超时计数器始发读取后如果超过预设周期数没有收到完整帧就把状态机强制复位回IDLE。我是直接把超时计数并到MA时钟产生模块里数满2048个MA周期没完成就自动拉resync信号清掉内部状态。另外把复位信号的同步释放逻辑单独做了两级同步器确保复位释放时跟着系统时钟沿不产生毛刺。加完这两个措施后热拔插编码器再接回去也能自动恢复读取。6. 让这套源码更通用参数化设计、多圈处理与后续扩展6.1 参数化总线宽度与CRC多项式源码包里最值得改的是把位宽做成参数。单圈位宽、MA分频系数、CRC多项式系数、采样延迟周期全部参数化后同一套RTL可以适配多款编码器不用每次复制工程再手改逻辑。我通常会在顶层增加一个参数组parameter SINGLE_TURN_BITS 17, parameter MULTI_TURN_BITS 0, parameter MA_CLK_DIV 20, parameter CRC_POLY 6b000011, parameter CRC_INIT 6d0, parameter SAMPLE_DELAY 1逻辑说明MULTI_TURN_BITS为0时表示只有单圈输出拼接时把多圈段去掉。CRC_POLY和CRC_INIT直接传到CRC子模块换编码器时只需查手册改这两个值。SAMPLE_DELAY控制采样点相对MA时钟沿的延迟周期数改成参数后不用动状态机。这些参数在综合时都是常量不会额外消耗寄存器。参数化的意义在于维护成本。一个项目里同时用了17位和21位编码器只需要例化两次顶层模块分别配不同参数不需要维护两套代码。后续如果换上带多圈功能的编码器只需把MULTI_TURN_BITS设为12之类输出位宽自动扩展。6.2 多圈计数与掉电保存的处理技巧BISS-C本身是把多圈位数直接从编码器读出来的FPGA端只负责把多圈段拼进位置数据。如果编码器不带电池备份多圈主站侧的计数掉电后会丢失。源码包通常不包含掉电保存逻辑但工程里一般会留出接口。我常用的做法是外接SPI Flash或者铁电存储器在每帧数据valid有效时把多圈计数值和单圈原始值一起写入备份区写入频率不能是每一帧都写Flash寿命会很快耗尽。实际做法是快速连续变化时只缓存到RAM断电瞬间检测掉电信号再紧急写入一次。掉电检测需要用模拟比较器监测24V或5V供电FPGA里可以留一个电平输入给中断处理。如果编码器本身支持多圈并且内置电池那FPGA端就不需要备份直接读出来的就是完整位置。判断依据是编码器手册里的“多圈类型”字段是电池备份还是纯机械多圈。机械多圈掉电后读数仍在但需要上电后做一次绝对位置零点校准。6.3 一条经验强制走一遍完整回环测试最后的建议是把这套源码当作一个整体测试对象而不是只测读取功能。我最常做的是在FPGA内部做回环把顶层模块的position和valid信号引出到另一个仿真子模块模拟CPU读写总线让数据在内部转一圈再写回编码器模型对比是否完全一致。回环测试能同时覆盖状态机、CRC信号和valid时序比单看ILA波形更能发现问题。我曾经在改参数时漏掉了CRC_POLY的位宽回环测试跑到一半crc_error拉高位置数据跟预期差了几个值顺着回环链路才定位到是CRC子模块的输入位宽没有跟随顶层参数变化。从那以后我每次改参数都强制走一遍完整回环测试仿真通过才允许上板这套流程已经帮我拦下了不少低级错误。如果只是做一次性读取验证也可以把读到的位置值通过串口或以太网发出来但串口带宽低高速运动时数据更新率不够。回环测试和ILA配合是最稳的组合既能看到协议层细节又能验证数据正确性。希望这套调试流程能帮你在BISS-C项目上少走点弯路。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站