ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

二进制速率乘法器BRM详解:脉冲分频、FPGA实现与工程应用

二进制速率乘法器BRM详解:脉冲分频、FPGA实现与工程应用 最近手头一个项目里需要把一路低频脉冲信号按非整数比例进行分频输出用传统的整数分频器数了半天发现除不尽后来换成Binary Rate Multipliers二进制速率乘法器简称BRM这个老掉牙但极其实用的方案问题迎刃而解。这东西不是什么新鲜玩意上世纪七十年代就经常出现在仪器仪表里但现在做频率合成、脉冲速率控制、电机调速甚至一些传感器信号处理时依然非常好用关键是它原理简单、占资源少、时序可控适合在FPGA或者分立逻辑电路里快速落地。这篇就围绕BRM展开讲清楚它的工作原理、设计方法、电路实现和调试心得顺便把我踩过的坑也一并交代了。如果你正在做脉冲分频、频率微调或者需要产生特定平均频率的脉冲序列这篇文章应该能帮你省不少时间。1. 整体设计与思路拆解1.1 BRM到底是什么它解决了什么问题先看名字。Binary Rate Multipliers直译过来是“二进制速率乘法器”但这个“乘法”跟算术乘法完全是两码事它本质上是一个脉冲计数与选通控制电路。它的功能是输入一个基准时钟脉冲序列在固定数量的时钟周期内根据一个二进制控制字N位输出对应数量的脉冲。比如4位BRM控制字设为5那么每16个输入时钟周期就会输出5个脉冲。输出脉冲总数与输入时钟总数之间的比例就是控制字与2的N次方之比所以叫“速率乘法”而不是“频率乘法”。那它能解决什么问题呢最典型的就是分数分频。整数分频器只能做到÷N想要÷2.5、÷4.7这种比例就抓瞎了。传统做法是小数分频器比如双模预分频累加器但那个结构复杂对时序要求高还要考虑相位累加器的位数和进位逻辑。BRM的思路更直接我不保证每个输出脉冲之间的间隔完全均匀但我保证在一段固定窗口内的脉冲总数精确等于目标值。这种“平均频率精确、瞬时频率抖动”的特征决定了它适合用在那些对短期抖动不敏感、但对长期累积计数精度很敏感的场合。我自己最常用到BRM的场景是工业仪表里的流量计标定。流量计输出脉冲频率跟流速成正比标定的时候需要用一个精确的脉冲数去驱动计数器比如让计数器在10秒内正好走10000个脉冲。如果基准时钟是1MHz直接用计数器分频是做不到10000/10s这种非整数比例的但用BRM配合合适的控制字就能在较长时间窗口内把平均脉冲率精确控制在目标值附近。1.2 为什么选BRM而不是用DDS或Σ-Δ小数分频经常有人问我现在DDS直接数字频率合成满地都是Σ-Δ小数分频器也很成熟干嘛还折腾BRM这种老古董这问题问得对但得分场景。DDS的优势是频率分辨率极高、相位连续但它输出的是正弦波或者经过比较器转出来的方波内部要跑一个相位累加器和查找表或者CORDIC资源开销大而且输出频率上限受时钟和查找表位宽限制。如果只需要一个几十kHz的方波脉冲序列用DDS纯属杀鸡用牛刀。Σ-Δ小数分频的优点是输出频率精度高且相噪性能好但它的核心是Sigma-Delta调制器反馈环路一旦参数没调好容易出现极限环振荡导致输出频谱出现杂散调试起来相当费劲。BRM的定位恰好介于两者之间比整数分频灵活得多比DDS省资源比Σ-Δ小数分频结构简单得多。它只需要一个二进制计数器加一组与门和一个加法器如果用累加型结构在FPGA里也就是几十个LUT的事。而且它的行为是确定性的给定输入时钟和控制字输出脉冲序列的模式是固定的不像Σ-Δ那种统计型调制器输出时序每次上电都一样这样仿真验证的时候就特别省心。我后来在实际项目里总结出一条经验如果需求只是“在某个时间窗口内输出精确数量的脉冲”BRM几乎总是最优解如果需求是“输出一个低抖动、高精度的连续时钟频率”那直接上DDS或者PLL更合适。搞清楚需求边界方案选型就不会纠结。2. 核心细节解析与实操要点2.1 BRM的内部结构与工作过程BRM的标准实现方式有两种一种是查表型或者叫组合逻辑选通型另一种是累加型也叫Mersenne累加器结构。我分别说一下。查表型BRM的结构是这样一个N位二进制计数器对输入时钟计数计数器的每一位输出分别接到一组与门的一个输入端每个与门的另一个输入端接控制字的对应位可能需要反相。然后所有与门的输出再经过一个或门合并得到最终的BRM输出脉冲。这个过程本质上是在每个时钟周期里检查当前计数值是否符合某种位模式如果符合就输出一个脉冲。举个例子4位查表型BRM控制字M5二进制0101。计数器从0数到15在每个计数值下BRM输出为1的条件是计数值的bit0为1对应M的bit0或者计数值的bit2为1对应M的bit2具体还跟电路接法有关。总的来说每16个周期里恰好有5个周期输出高电平但这5个脉冲在16个周期里的分布并不均匀有时候相隔1个周期有时候相隔4个周期。这种不均匀分布就是BRM的固有特性——“时间抖动”。累加型BRM的原理不太一样它用一个N位累加器每个输入时钟周期把控制字M累加一次当累加器溢出时输出一个脉冲并保留余数。这个结构跟DDS的相位累加器几乎一模一样区别在于DDS用累加器的高位作为相位地址去查表而BRM把溢出标志直接当作输出脉冲。累加型BRM的脉冲分布特性跟查表型不同它的抖动更随机一些频谱上的杂散会低一点。我一般喜欢用累加型因为在FPGA里面实现起来更顺手而且可以通过增加累加器位数来降低量化误差。2.2 关键参数控制字位数、输出频率范围、抖动特性BRM有三个关键参数需要认真对待分别是控制字位数N、输入时钟频率Fclk和目标平均输出频率Fout。它们之间的关系其实就是一个公式平均输出频率Fout (M / 2^N) × Fclk其中M是控制字取值范围从0到2^N - 1。这个公式意味着BRM的输出频率分辨率是Fclk / 2^N。比如输入时钟1MHz8位BRM频率分辨率大约是3906Hz也就是说你能输出的平均频率只能是3906Hz的整数倍。如果觉得分辨率不够那就把N做大。16位BRM在1MHz时钟下分辨率是15.26Hz很多场景就够用了。这里要特别留意一个容易被忽视的点BRM输出脉冲之间最小间隔是1个输入时钟周期也就是说瞬时输出频率最高等于Fclk。当M接近2^N的时候输出脉冲会密密麻麻甚至可能连续几个周期都是高电平如果后级电路对脉冲最小宽度有要求这时候就会出问题。我调试过一个电机驱动电路BRM输出直接接步进电机驱动器结果M调大后发现驱动器偶尔丢步用示波器一抓发现输出脉冲在几个周期内连续为高驱动器把连着的两个高电平当成了一个长脉冲直接导致计数错误。后来我在输出端增加了一个单稳整形电路强制每个输出脉冲宽度不超过一个时钟周期问题就消失了。还有一个参数是“窗口周期”。BRM输出的平均频率是在2^N个输入时钟周期这个窗口内定义的所以如果后级电路需要在一个更短的时间尺度上保证频率准确BRM就不合适。这个特性在选型时要先想清楚。2.3 控制字M与输出脉冲序列的对应关系很多初学者搞不清楚M和输出脉冲序列之间到底怎么对应。我自己当初也犯过迷糊后来总结了一个直观的观察方法。以4位BRM为例N4总共16个时钟周期。当M1时16个周期里输出1个脉冲M2时16个周期里输出2个脉冲M7时输出7个脉冲。但具体是哪几个周期输出并不是简单地把前M个周期都置高而是分散在整个窗口内。为什么因为如果前M个周期连续输出那瞬时频率就是Fclk跟后级电路的设计预期会冲突而且失去了“平均”的意义。更关键的一点查表型BRM输出脉冲的位置是由计数器和M的位模式决定的所以同样的M值输出序列是固定的、可预测的。这对于那些需要精确知道每个脉冲出现时刻的应用比如某些测量仪器的时序控制是有利的因为你可以提前算好每个脉冲落在哪个时钟周期。但如果你的系统里同时有多个BRM在并行工作它们的输出脉冲可能会在时间上重叠导致后级总线上出现突发的高密度脉冲簇这时候就需要做输出仲裁或者缓冲。累加型BRM的情况会好一些因为累加器的余数反馈天然会把脉冲尽量摊开。但累加型也有一个不太直观的特性当M和2^N不互质的时候输出序列会呈现周期性簇状分布。比如N4、M616和6有公约数2那么输出脉冲就会在短周期内重复同样的模式而不是在整个16周期窗口内均匀散开。这一点在系统设计时要注意如果要追求最优的脉冲均匀性可以故意把M设计成与2^N互质的奇数但这样控制字的有效范围会受限。3. 实操过程与核心环节实现3.1 用74系列逻辑芯片搭一个4位BRM电路如果你是想在面包板上快速验证BRM原理完全可以用74系列芯片手工搭一个。我用过74LS163同步计数器加74LS08与门和74LS32或门搭过4位BRM效果不错而且调试过程能帮助理解整个逻辑关系。先搭计数器部分74LS163接成4位二进制计数器时钟输入接信号源输出QA到QD接到与门阵列。控制字M的4位M0到M3也接到与门阵列的另一组输入。这里的关键是每个与门的逻辑关系对于计数器输出为1的位用原变量参与与运算对于控制字为1的位也用原变量。然后每个输出位对应一个与门最后四个与门的输出接一个4输入或门得到最终的BRM输出。搭完之后可以做这样一个实验把M设为0101即5然后用示波器同时观察输入时钟和BRM输出数一下16个时钟周期内是不是恰好有5个输出脉冲。再用频率计测输出频率会发现它恰好等于输入时钟频率的5/16。这个实验做完你对BRM的理解就真正落地了比看一百遍资料都有用。我搭的时候踩了一个小坑74LS163是同步计数器清零和预置都是时钟上升沿有效而BRM要求计数器连续运行不能中途清零所以清零和预置引脚都要接高电平无效状态。另外74LS08的输入不能悬空不用的与门输入端要接地或接高否则会有抖动噪声混进输出在示波器上看起来就像多了很多毛刺。3.2 在FPGA里实现可配置位宽的BRM模块实际工程项目里我不会用分立芯片搭BRM太费劲了直接在FPGA里写一个参数化模块更实在。下面这个Verilog代码就是我在项目里用的累加型BRM代码我反复用过好几次稳定可靠直接复制就能用。module brm #( parameter N 16 )( input wire clk, input wire rst_n, input wire [N-1:0] rate, // M, 控制字 output reg pps_out // BRM输出脉冲 ); reg [N-1:0] accum; always (posedge clk or negedge rst_n) begin if (!rst_n) begin accum {N{1b0}}; pps_out 1b0; end else begin {pps_out, accum} accum rate; end end endmodule这段代码的核心就一行{pps_out, accum} accum rate;。它把累加器和溢出进位打包在一起每个时钟周期累加一次rate当accum加rate之后超过2^N-1时进位位即pps_out自动置1同时累加器保留余数。这就是前面说的Mersenne累加器结构用HDL写出来特别简洁。要说一下这个写法里容易被忽视的细节我把pps_out和accum拼在一起做加法这样能保证溢出的时候pps_out严格保持一个时钟周期的高电平。如果分开写比如先加accum再判断if溢出那么pps_out1很容易因为判断逻辑引入组合逻辑延迟导致输出脉冲宽度不可控。用拼接加法器的方式进位直接来自加法器本身时序上最干净。3.3 输出脉冲均匀性优化与抖动抑制经验BRM最大的短板是输出脉冲间隔不均匀有些应用对此敏感需要做一些优化。我自己试过几种方法按效果排序第一种方法最简单提高N也就是提高内部精度。比如原来用8位BRMM变化一个最小步进输出频率就跳变Fclk/256这个跳变在宏观上可能还能接受但脉冲间隔的抖动会比较大。换成16位BRM之后同样用最小步进调节频率跳变变成Fclk/65536脉冲分布相对平滑很多。本质上是用更多的位数换更好的统计均匀性。第二种方法是加输出缓冲FIFO。BRM输出脉冲先写进一个小FIFO比如深度8后级以均匀速率从FIFO读脉冲这样就能消除大部分突发性抖动。代价是引入额外延迟对实时性要求高的场合不能用。这个方法在需要后级计数器准确计数时很管用。第三种方法是用多个BRM交错。把总输出频率拆成多路每路BRM的相位错开最后再合并。这样能有效降低输出频谱上的杂散幅度但实现复杂度上去了一般用在射频或精密测量领域普通控制场景不必这么做。3.4 BRM在步进电机控制中的应用实例我做过一个用BRM驱动步进电机的项目这里分享一下设计思路很有参考价值。步进电机控制里脉冲频率决定转速脉冲总数决定转角。通常控制器输出一个脉冲序列给驱动器驱动器每收到一个脉冲就让电机走一步。如果直接用MCU定时器产生脉冲CPU会被频繁中断占用尤其是高速运转时MCU根本忙不过来。这时BRM就有用了用MCU把目标频率换算成控制字M写入FPGA里的BRM模块BRM自动从基准时钟产生对应频率的脉冲序列MCU只要定期更新M值就能实现加减速控制彻底解放CPU。我当时的做法是基准时钟用50MHzBRM位宽是24位频率分辨率大约3Hz对于步进电机来说绰绰有余。加减速曲线用查表法预先算好一串M值MCU每隔一定时间更新一次M值电机就能按照预设轨迹平滑加减速。实测下来同样的电机和驱动器用BRM驱动的板子比用MCU定时器中断驱动的板子在高速时丢步率明显降低而且软件代码简单了不止一个量级。要特别注意一个问题步进电机驱动器对脉冲宽度和最小间隔有要求不同驱动器差异很大有的要求最小脉冲宽度2μs有的要求5μs。BRM输出的脉冲宽度等于基准时钟周期如果基准时钟太快可能导致脉冲宽度不满足驱动器规格。这时候需要在BRM输出端加一个脉宽扩展电路或者降低基准时钟频率确保最小脉冲宽度达标。4. 常见问题与排查技巧实录4.1 输出频率与理论值不符多半是控制字或时序问题如果BRM输出的频率用频率计测量跟理论值对不上多半是以下几个原因。第一个原因是控制字M的位序接反了。查表型BRM对M的每一位都有对应关系如果把MSB和LSB接反M1和M128就颠倒了输出频率自然不对。排查方法很简单把M设为1看输出频率是不是Fclk/2^N再把M设为2^N-1看输出频率是不是接近Fclk。如果这两个极值都符合说明位序没问题。第二个原因是异步复位信号没做好。FPGA里如果复位信号释放时间不在时钟边沿附近累加器可能从非零初值开始运行导致第一个窗口内的脉冲数不对。解决方法是把复位做成同步复位或者复位后让BRM先跑一个完整的2^N窗口再启用输出。第三个原因是时钟域问题。如果BRM的输出被另一个时钟域的电路采样必须加两级同步器否则会出现亚稳态导致偶发的多计数或少计数。这是数字电路里老生常谈的问题但在BRM场景里尤其隐蔽因为脉冲本身就不均匀偶尔丢一个脉冲很难用肉眼从示波器上发现必须用计数器累加比对才能暴露。4.2 BRM输出波形“花”得没法看怎么办第一次用示波器看BRM输出波形的时候很多人会吓一跳脉冲间隔忽大忽小波形看起来乱七八糟完全没有规律。这不是电路坏了这就是BRM的本性。它输出的是“脉冲密度调制”信号而不是“频率调制”信号所以短期波形看起来就是“花”的。如果你用手头的频率计测量会发现读数跳来跳去。这其实是正常的因为频率计的闸门时间如果比2^N个时钟周期短它测到的只是窗口内某个片段的平均频率自然跟理论值有偏差。解决办法是把频率计的闸门时间设得足够长至少是BRM窗口周期的5到10倍或者用周期测量法测多个脉冲周期再取平均。实际项目中我会先用示波器看波形是否存在完全无输出的时间段再用频率计长时间测量验证平均频率两者结合起来判断。当然如果波形“花”到完全不可接受比如输出频率很高、M接近满量程的时候连续高电平过多那就得考虑前面提到的脉宽整形或者改用更高N值。记住一条N越大波形越“干净”。4.3 快速排查速查表我这些年遇到的BRM问题汇总现象可能原因排查步骤输出频率是理论值一半控制字位序反了检查M的位序与计数器输出的对应关系输出偶尔多脉冲亚稳态或跨时钟域采样在输出采样点加两级同步器输出偶尔少脉冲复位初值不对改为同步复位检查复位释放时序频率计读数乱跳闸门时间过短加大频率计闸门时间≥5个BRM窗口输出脉冲宽度过窄基准时钟频率太高降低基准时钟或在输出端扩展脉宽M调大后波形出现连片高电平M接近2^N、输出天然密集增加N位宽或对输出加单稳整形多个BRM输出合并后脉冲簇输出相位重叠加入FIFO缓冲或调整各BRM初相这张表是我做BRM相关项目时的经验汇总遇到问题优先对着表排查能省不少无头苍蝇式的时间。尤其是“频率计读数乱跳”那条最容易被误判成电路故障其实BRM本身工作得很好只是测量方式不对。4.4 关于累加型BRM的一个易错细节累加型BRM在Verilog里实现简单但有一个细节必须想清楚累加器从0开始累加和从非零初值开始累加只影响第一个输出脉冲的相位不影响窗口内的总脉冲数。因为一旦进入稳定状态累计溢出的次数只跟M和窗口长度有关跟初值无关。所以如果你不关心启动瞬间的相位关系复位时不需要把累加器清零直接上电随机初值都行。这个特性在多个BRM同步的应用里有妙用。我做过一个多轴电机同步驱动的项目三台电机需要按照严格比例保持同步旋转。每个电机一个BRM三个BRM共用同一个基准时钟但它们的累加器初值分别设为0、2^N/3、2*2^N/3这样三路输出脉冲自然就错开了相位后级驱动器的电流峰值也被摊开电源纹波明显减小。这种情况如果不用相位偏移三路脉冲很可能同时到达电源瞬间电流会顶得很高严重时甚至引起电压跌落。5. 性能评估与适用边界5.1 重新审视BRM的适用边界讲了这么多最后还是想认真聊聊BRM的适用边界因为我在项目评审时见过太多次因为选型不当导致的返工。BRM最适合的场景是需求是“在给定的时间窗口内输出精确数量的脉冲”或者“用简单资源实现低频高分辨率的可变速率”。它对功耗极其友好对资源占用极其节省对实时性也没有特殊要求。如果你要做的是一个工业控制里的辅助信号发生器、一个仪器仪表的标定源、一个电机控制的脉冲产生器BRM绝对是好选择。BRM不适合的场景包括需要极低相位噪声的时钟产生需要频谱纯净的频率源需要逐周期频率精确的PWM生成。这些场合哪怕你把N做得再大BRM固有的时间量化抖动也消除不掉必须老老实实用DDS或者PLL。另外提醒一句BRM的输出频率上限大约在Fclk/2以下才有实际意义。如果M超过2^N/2输出脉冲会变得非常密集虽然平均频率仍然正确但很多后级电路已经无法正常响应了。在实际工程中我一般让BRM工作在Fclk/4以下的输出频率范围留给后级足够的裕量系统稳定性会好很多。5.2 后续可以扩展的方向如果看完这篇你还想继续深入可以考虑几个方向。一个是把BRM和PID控制结合用BRM作为执行机构的脉冲输出级实现闭环控制比如温控系统里的加热功率控制和流量控制系统里的阀门开度控制。另一个方向是研究BRM输出序列的频谱特性用一种叫“确定性抖动整形”的技术优化脉冲分布这在高精度测量领域很有价值。还有一个比较有意思的方向是用BRM做数字电源里的DPWM发生器虽然它不如Σ-Δ DPWM那么精细但胜在实现简单、行为可预测在一些成本敏感的场合仍然有人用。我在实际使用中体会最深的一点是BRM最好的使用方式不是把它当成一个独立器件而是把它当成整个数字系统里的一个“可控脉冲水龙头”——给它一个精确的水压基准时钟通过调节阀门控制字M它就能稳定地滴出你想要的脉冲滴数。这种思维方式在工程上非常有用因为它强迫你去量化需求而不是凭感觉设计。最后再分享一个小技巧如果在FPGA里调试BRM可以在仿真里直接数脉冲个数比对理论值比接示波器高效十倍。把M设为随机值跑2^N个时钟周期用计数器累计输出脉冲数断言它等于M。这个testbench三十行代码就能写完却能在上板之前帮你挡掉大部分低级错误。
返回列表