3、ABZ信号建模:A/B/Z信号时序生成、正交解码原理、方向与速度提取算法
好,咱们进入第三章节。前面聊了编码器的分类和选型,现在得真刀真枪地处理信号了。ABZ信号,说白了就是编码器跟控制器对话的语言。你听不懂这门语言,电机就转不起来,或者转得乱七八糟。
我个人习惯,在FPGA里验证换相逻辑之前,先把ABZ信号的模型搭好。这一步做扎实了,后面调试能省一半时间。我记得刚入行那会儿,有次项目赶进度,跳过信号建模直接上板,结果方向判断老是反的,查了整整两天才发现是A相和B相的时序关系搞反了。嗯,从那以后,我再也不敢偷这个懒了。
3.1 A/B/Z信号时序生成
增量式编码器输出三路信号:A相、B相和Z相。A和B是正交方波,相位差90度。Z相是零位脉冲,每转一圈输出一个。
咱们先看一个典型的时序图,我习惯用这样的结构来理解:
你看这个图,A相上升沿的时候,B相是什么电平?如果是高电平,那就是正转。反过来,A相上升沿时B相是低电平,那就是反转。这就是正交解码最核心的判断依据。
在FPGA里生成这个时序,我一般用计数器加比较器的方式:
// ABZ信号生成模块(Verilog)
module abz_signal_gen #(
parameter CLK_FREQ = 50_000_000, // 系统时钟50MHz
parameter PULSE_PER_REV = 1024 // 每转脉冲数
)(
input wire clk,
input wire rst_n,
input wire direction, // 1:正转 0:反转
input wire enable,
output reg a_phase,
output reg b_phase,
output reg z_phase
);
// 每半个脉冲周期的时钟计数
localparam HALF_PERIOD = CLK_FREQ / (PULSE_PER_REV * 2);
reg [31:0] cnt;
reg [9:0] pulse_cnt; // 脉冲计数器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt <= 0;
pulse_cnt <= 0;
a_phase <= 0;
b_phase <= 0;
z_phase <= 0;
end else if (enable) begin
if (cnt < HALF_PERIOD - 1)
cnt <= cnt + 1;
else begin
cnt <= 0;
// A相翻转
a_phase <= ~a_phase;
// B相延迟90度(延迟HALF_PERIOD/2个时钟)
// 实际项目中用状态机实现更可靠
b_phase <= direction ? a_phase : ~a_phase;
// Z相:每转一圈输出一个脉冲
if (pulse_cnt == PULSE_PER_REV - 1) begin
pulse_cnt <= 0;
z_phase <= 1;
end else begin
pulse_cnt <= pulse_cnt + 1;
z_phase <= 0;
end
end
end
end
endmodule
3.2 正交解码原理
正交解码,说白了就是从A/B信号里提取出两个信息:转了多少步,往哪个方向转。你想想看,如果只有一路脉冲,你只能知道转了多少,但不知道方向。有了两路正交信号,这个问题就解决了。
解码的核心是状态机。A和B各有两种电平,组合起来就是4种状态。正转时状态按顺序循环:00 → 10 → 11 → 01 → 00。反转时反过来:00 → 01 → 11 → 10 → 00。
我习惯用查表法来实现,速度快,逻辑也清晰:
// 正交解码状态机
module quadrature_decoder (
input wire clk,
input wire rst_n,
input wire a_in,
input wire b_in,
output reg [1:0] direction, // 2'b01:正转 2'b10:反转 2'b00:静止
output reg pulse_out // 每步一个脉冲
);
reg [1:0] state, next_state;
reg [1:0] ab_sync;
// 边沿检测,防抖
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
ab_sync <= 2'b00;
state <= 2'b00;
end else begin
ab_sync <= {a_in, b_in};
state <= next_state;
end
end
// 状态转移与输出
always @(*) begin
next_state = state;
direction = 2'b00;
pulse_out = 1'b0;
case (state)
2'b00: begin
if (ab_sync == 2'b10) begin
next_state = 2'b10;
direction = 2'b01; // 正转
pulse_out = 1'b1;
end else if (ab_sync == 2'b01) begin
next_state = 2'b01;
direction = 2'b10; // 反转
pulse_out = 1'b1;
end
end
2'b01: begin
if (ab_sync == 2'b00) begin
next_state = 2'b00;
direction = 2'b01;
pulse_out = 1'b1;
end else if (ab_sync == 2'b11) begin
next_state = 2'b11;
direction = 2'b10;
pulse_out = 1'b1;
end
end
2'b11: begin
if (ab_sync == 2'b01) begin
next_state = 2'b01;
direction = 2'b01;
pulse_out = 1'b1;
end else if (ab_sync == 2'b10) begin
next_state = 2'b10;
direction = 2'b10;
pulse_out = 1'b1;
end
end
2'b10: begin
if (ab_sync == 2'b11) begin
next_state = 2'b11;
direction = 2'b01;
pulse_out = 1'b1;
end else if (ab_sync == 2'b00) begin
next_state = 2'b00;
direction = 2'b10;
pulse_out = 1'b1;
end
end
endcase
end
endmodule
3.3 方向与速度提取算法
方向提取,上面已经讲了,通过状态机判断。但速度提取就有讲究了。常用的方法有两种:M法和T法。
| 方法 | 原理 | 适用场景 | 优缺点 |
|---|---|---|---|
| M法 | 固定时间内计数脉冲数 | 高速(>100 RPM) | 低速时误差大,高速时精度高 |
| T法 | 测量相邻脉冲的时间间隔 | 低速(<100 RPM) | 高速时精度下降,低速时很准 |
| M/T法 | 结合两者,动态切换 | 全速域 | 实现复杂,但精度最高 |
我个人习惯在FPGA里用M/T法。低速用T法,高速用M法,中间有个过渡区做平滑切换。你看这个流程图:
M法的实现很简单,开一个固定时间窗口(比如10ms),数这个窗口内来了多少个脉冲。速度 = 脉冲数 × 60 / (每转脉冲数 × 时间秒)。
T法稍微复杂一点,需要测量两个相邻脉冲之间的时间间隔。用高频时钟(比如100MHz)去计这个间隔的时钟周期数。速度 = 时钟频率 / (时钟周期数 × 每转脉冲数) × 60。
// M法速度计算
module m_method #(
parameter CLK_FREQ = 50_000_000,
parameter PULSE_PER_REV = 1024,
parameter SAMPLE_TIME = 10_000_000 // 10ms采样窗口
)(
input wire clk,
input wire rst_n,
input wire pulse_in,
output reg [31:0] speed_rpm
);
reg [31:0] pulse_cnt;
reg [31:0] time_cnt;
reg sample_done;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
pulse_cnt <= 0;
time_cnt <= 0;
sample_done <= 0;
end else begin
if (time_cnt < SAMPLE_TIME - 1) begin
time_cnt <= time_cnt + 1;
if (pulse_in) pulse_cnt <= pulse_cnt + 1;
sample_done <= 0;
end else begin
// 计算速度:RPM = (脉冲数 × 60) / (PULSE_PER_REV × 采样时间秒)
speed_rpm <= (pulse_cnt * 60 * 1000) / (PULSE_PER_REV * (SAMPLE_TIME / (CLK_FREQ / 1000)));
pulse_cnt <= 0;
time_cnt <= 0;
sample_done <= 1;
end
end
end
endmodule
嗯,到这里ABZ信号的建模和提取就讲完了。你可能会问,为什么要把方向提取和速度提取分开讲?其实在FPGA里,这两个模块是并行工作的。方向解码器实时输出方向,速度计算器周期性更新速度值。两者互不干扰,这才是硬件设计的思维。