首页
/
行业洞察
/
正文
INDUSTRY INSIGHT · 深度
基于FPGA之光电编码器的M法测数模块
📅 2026/10/4 5:41:18
✍️ 爱科研究院
👁 阅读 3,247
M 法测速模块 Speed_M 详解光电编码器一、模块是干什么的Speed_M实现的是经典的M 法测速也叫测频法在固定的采样窗口内数脉冲用单位时间内有多少个脉冲反推转速。它假设上游Encoder_Reader之类已经对光电编码器的 A/B 相信号做过四倍频解码送进来的是单周期有效的计步脉冲up正转和dn反转。模块自己开一个采样窗口窗口结束的瞬间算一次转速再由外部triger触发把结果锁存到输出端口。适合中高速低速时一个采样窗内脉冲数太少量化误差大——这是 M 法天生的短板文末会再展开。二、核心公式推导1. 基本关系设符号含义F_CLK系统时钟频率HzSAMPLE_CYCLES采样窗口的时钟周期数PPR编码器每转原始脉冲数Pulses Per Revolutiondelta_p采样窗口内累计的脉冲增量四倍频后的计步数n转速采样窗口的物理时间$$ T \frac{\text{SAMPLE_CYCLES}}{F_CLK} \quad (\text{秒}) $$四倍频后编码器每转产生的计步数是PPR × 4所以在时间 $T$ 内转过$$ \text{转数} \frac{\delta_p}{PPR \times 4} $$转速转/秒$$ n \frac{\delta_p}{PPR \times 4 \times T} $$换算成 RPM×60$$ RPM \frac{60 \times \delta_p}{PPR \times 4 \times T} \frac{60 \times \delta_p \times F_CLK}{PPR \times 4 \times \text{SAMPLE_CYCLES}} $$把与脉冲无关的常数提出来记$$ \boxed{K \frac{60 \times F_CLK}{PPR \times 4 \times \text{SAMPLE_CYCLES}}} $$于是得到整个模块最核心的公式$$ \boxed{RPM \delta_p \times K} $$$K$ 的物理意义每一个计步脉冲对应多少 RPM。2. 定点化Q16——为什么没有用浮点/除法器$K$ 是个小数默认参数下约 0.009155FPGA 里不想做浮点也不希望每个数据点都调用除法 IP。做法是把 $K$ 左移 16 位存成整数$$ K_Q16 \text{round}(K \times 2^{16}) \text{round}(K \times 65536) $$计算时$$ \delta_p \times K_Q16 \delta_p \times K \times 2^{16} $$再算术右移 16 位把 $2^{16}$ 抵消掉$$ RPM (\delta_p \times K_Q16) \ggg 16 $$这就是Q16 定点用一个 16 位的隐含小数点换掉一个除法器。注意$2^{16}65536$ 是这里的缩放因子后面定位 bug 全靠这个数。3. 默认参数算例代入默认值F_CLK 50 MHz、PPR 16384、SAMPLE_CYCLES 5_000_000即 100 ms$$ K \frac{60 \times 50 \times 10^6}{16384 \times 4 \times 5 \times 10^6} \frac{3.0 \times 10^9}{3.2768 \times 10^{11}} \approx 0.009155273 \ \text{RPM/脉冲} $$$$ K_Q16 \text{round}(0.009155273 \times 65536) 600 $$这里恰好是整数 600很好记。意味着一个计步脉冲 0.009155 RPM分辨率delta_p 1000个脉冲 → RPM 1000 × 600 / 65536 ≈ 9.155 RPM最大转速每拍一个脉冲delta_p 5e6→ RPM ≈ 5e6 × 0.009155 ≈45 776 RPM三、端口说明端口方向位宽说明clkin1系统时钟rst_nin1异步复位低有效up/dnin1正/反转计步脉冲单周期有效trigerin1外部锁存触发上升沿锁存结果来自Triger_Timerrpm_q16outRPM_WIDTH转速输出文档约定为整数 RPMdelta_poutCNT_WIDTH窗口内脉冲增量调试用rpm_validout1转速有效脉冲1 拍四、代码逐段详解1. 编译期常数K_Q16第 73–75 行localparam signed [63:0] K_NUM 64sd60 * F_CLK * 65536; localparam signed [63:0] K_DEN PPR * 4 * SAMPLE_CYCLES; localparam signed [63:0] K_Q16 (K_NUM (K_DEN 1)) / K_DEN;三个设计细节都值得抄全程 64 位60 × F_CLK × 65536 ≈ 1.97×10¹⁴远超 32 位有符号上限2.15×10⁹不升 64 位会被综合器静默截断且不报错——这是新手最常踩的坑。四舍五入(K_NUM K_DEN1) / K_DEN是在分子上先加半个分母再除截断误差 ≤ 0.5 LSB比直接截断更准。纯localparam综合期算好占用 0 个硬件除法器。2. 饱和限幅常数第 82–86 行localparam signed [63:0] RPM_MAX {1b0, {(63){1b1}}} (64 - RPM_WIDTH); localparam signed [63:0] RPM_MIN ~RPM_MAX; localparam [RPM_WIDTH-1:0] SAT_MAX {1b0, {(RPM_WIDTH-1){1b1}}}; localparam [RPM_WIDTH-1:0] SAT_MIN {1b1, {(RPM_WIDTH-1){1b0}}};用位拼接生成2^(W-1)-1和-2^(W-1)避免对localparam做位选部分工具不支持。RPM_MIN ~RPM_MAX是补码的经典技巧~(2^31-1) -2^31成立。3. 内部测速窗口计时块第 93–110 行localparam WIN_CNT_W (SAMPLE_CYCLES 1) ? 1 : $clog2(SAMPLE_CYCLES); ... if (win_cnt SAMPLE_CYCLES - 1) begin win_cnt 0; window_end 1b1; // 单拍脉冲 end$clog2自适应位宽每SAMPLE_CYCLES拍产生一个单周期window_end脉冲。这个模块最值得称道的架构决策是内部测速窗口和外部triger解耦——内部窗口负责算速度外部触发只负责什么时候把结果读出去。这样SAMPLE_CYCLES不必和Triger_Timer的周期一致时序约束上更自由。4.triger上升沿检测第 116–126 行trig_d1 triger; trig_d2 trig_d1; wire trig_rise trig_d1 ~trig_d2;经典的两级打拍 上升沿检测消除亚稳态与毛刺产生单拍trig_rise。5. 有符号累加器abs_cnt第 134–153 行if (window_end) begin if (up !dn) abs_cnt 1; // 清零但保留本拍脉冲不丢数 else if (dn !up) abs_cnt -1; else abs_cnt 0; end else begin if (up !dn) abs_cnt abs_cnt 1; else if (dn !up) abs_cnt abs_cnt - 1; end关键点up1、dn-1up/dn同时为 1 时净变化为 0。窗口清零的那一拍把当前拍的up/dn计入下一窗abs_cnt ±1而不是0避免窗口边界丢脉冲——这是测速里非常容易忽略的细节。因为每个窗口从零重计delta_p直接取abs_cnt即可无需再存上一窗快照。6. 窗口结束处理 饱和第 163–183 行wire signed [CNT_WIDTH-1:0] delta_new abs_cnt; wire signed [63:0] prod delta_new * K_Q16; // 64b 有符号乘 wire signed [63:0] rpm_full prod 10; // ← 问题在这里 ... if (rpm_full RPM_MAX) rpm_latest $signed(SAT_MAX); else if (rpm_full RPM_MIN) rpm_latest $signed(SAT_MIN); else rpm_latest rpm_full[RPM_WIDTH-1:0];delta_new × K_Q16是 32 位 × 64 位有符号乘映射到 DSP结果 64 位再做饱和钳位到RPM_WIDTH位。7. 外部锁存输出第 189–200 行if (trig_rise) begin rpm_q16 rpm_latest; rpm_valid 1b1; // 拉高 1 拍 endrpm_q16在trig_rise时刻锁存rpm_valid同步给出 1 拍握手信号方便 CPU/上位机做数据采样。这跟Speed_T里 trigger 锁存的用法保持一致是一套统一的定时源触发读取协议。五、⚠️ 重点第 165 行存在一个位移位数 bug这是本文最需要提醒你的地方。看第 165 行wire signed [63:0] rpm_full prod 10; // 算术右移 整数 RPM但按前面推导K_Q16 K × 65536要还原成整数 RPM 必须右移 16 位$$ RPM (\delta_p \times K_Q16) \ggg 16 $$模块头部的注释第 27、35 行自己也是这么写的rpm_q16 (delta_p * K_Q16) 16 delta_p * K影响有多大正确的缩放因子是 $2^{16}65536$代码用的是 $2^{10}1024$两者相差 $\frac{65536}{1024} 64$ 倍结果就是输出的 RPM 比真实值放大了 64 倍。用默认参数验算一下delta_p正确 RPM16代码输出10真实转速代码输出100100×600/65536 ≈ 0.916100×600/1024 ≈ 58.60.916 RPM58.6×6410 00091.6585991.6 RPM5859×64顺带还带来一个副作用放大了 64 倍后稍高转速就会顶到饱和限幅把本不该饱和的正常量程给钳平了。修复方式一行wire signed [63:0] rpm_full prod 16; // Q16 还原成整数 RPM改完后再决定输出语义是否要保留现在这种整数 RPMQ0。如果你希望保留小数精度、真正输出 Q16那就别移位让 CPU 端读回去再/65536但现在端口名叫rpm_q16、文档又说输出整数 RPM属于名实不符——建议顺手把端口改名为rpm或明确注释成 Q0。六、精度与溢出分析溢出方面默认参数下都安全量最大值是否溢出abs_cnt5 000 000 2³²安全prod delta × K_Q165e6 × 600 3×10⁹用 64 位乘安全若用 32 位会溢出K_NUM综合期1.97×10¹⁴ 2⁶³安全最终 RPM≈ 45 776 2³¹不饱和精度方面M 法固有特性M 法的量化误差是±1 个脉冲相对误差$$ \varepsilon \frac{1}{\delta_p} $$默认参数下 $\delta_p$ 与转速的关系一个脉冲 0.009155 RPM。所以在1000 RPM时 $\delta_p \approx 10.9$ 万误差 ≈ 0.0009%而0.1 RPM时 $\delta_p \approx 11$误差 ≈9%。低速掉分辨率这是 M 法的原理性短板不是 bug。低速要准就得上 T 法测周期或 M/T 混合法——这正好呼应了工程里同时存在Speed_T和speed_select的设计。七、代码建议 / 改进推荐按优先级排序必修改 10为 16见第五节这是当前唯一的正确性错误。强烈建议把×4 四倍频参数化。现在PPR注释里写明上游已四倍频每圈 PPR×4但 ×4 硬编码在K_DEN里。建议加一个QUADRATURE参数默认 4公式改成K_DEN PPR * QUADRATURE * SAMPLE_CYCLES模块可复用性更好。建议端口名rpm_q16与实际 Q0 语义对齐。要么输出真 Q16保留小数、CPU 端还原要么改名叫rpm。名实不符是后患。建议明确up/dn互斥约定。当前两者同拍 净变化 0的处理是对的但最好在接口注释里写明由上游保证互斥否则调试时遇到异常脉冲很难定位。可选低速均值滤波。若应用确实会运行在低转速区间可以在rpm_latest之后加一个滑动平均压低量化抖动或直接换 T/M 法。可选加generate保护。K_DEN PPR×4×SAMPLE_CYCLES若被配成 0 会在综合期除零报错可加if (K_DEN 0)的编译期断言报错信息更友好。可选测试台补充。现有tb_Speed_M.v建议补两个用例①delta_p 100时验证输出 ≈ 0.916修完 bug 后② 构造window_end边界拍同时来up脉冲验证不丢数。八、小结Speed_M是一个架构清晰、细节考究的 M 法测速模块64 位综合期常数、四舍五入、饱和限幅、内部窗口与外部触发解耦、窗口边界不丢脉冲这些都是值得借鉴的工程写法。唯一的硬伤是第 165 行的 10把结果放大了 64 倍——一行改回 16即可。改完后再顺手把端口命名和 ×4 参数化理顺这个模块就相当干净了。需要的话我可以直接帮你把 10改成 16并顺手加上QUADRATURE参数和编译期除零断言。
📌 标签:
工业官网
设计趋势
AI 建站
SEO
获取完整报告 →
RELATED ARTICLES
推荐阅读
2026/10/4 5:41:18
Stata中的自相关矩阵与ARMA模型:从AR(2)原理到实操
2026/10/4 5:41:18
VASP表面吸附能计算全流程:以CO在Pt(111)表面为例
2026/10/4 5:36:18
Python实现四叉树:空间索引与范围查询性能优化实战
2026/10/4 6:21:20
Codex++卡顿怎么办?从版本兼容到UI渲染的根因排查与优化
2026/10/4 6:21:20
为什么AI越聊越笨?Token Optimizer质量评分S-F等级体系与上下文衰减悬崖完全指南
2026/10/4 6:21:20
无人机视角河道及周边垃圾检测数据集VOC+YOLO格式907张15类别
2026/10/4 6:21:20
omega-K算法精解:Stolt插值实现SAR高精度距离徙动校正
2026/10/4 6:21:20
MATLAB工程思维:从EduCoder卡点到向量化实战
2026/10/4 6:16:20
K210人脸检测与识别实战:KPU底层调优与边缘部署全链路
2026/10/4 0:00:57
Jev+Agent接管浏览器:browser-use实战与jev-ultrafast性能优化
2026/10/4 0:00:57
多智能体集群实战:DeepAgents编排、MCP与A2A协议及Skills体系
2026/10/4 0:00:57
hindsight:面向LLM应用的事后可观测性工程实践
2026/10/4 0:00:57
Jev+Agent接管浏览器:browser-use实战与jev-ultrafast性能优化
2026/10/4 0:00:57
多智能体集群实战:DeepAgents编排、MCP与A2A协议及Skills体系
2026/10/4 0:00:57
hindsight:面向LLM应用的事后可观测性工程实践
2026/10/4 2:41:08
我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
2026/10/3 12:41:10
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026/10/3 15:20:14
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)