4. FPGA编码器接口解码
任务目标
在FPGA中实现增量式编码器(A/B正交信号)的接口解码,输出当前位置(累计脉冲数)与速度信息。本案例以Xilinx Artix-7系列FPGA为硬件平台,使用Verilog HDL完成核心解码逻辑,并通过仿真验证解码正确性。
必要环境与依赖
- 硬件平台:Xilinx Artix-7 FPGA(如XC7A35T)开发板
- 开发工具:Vivado 2019.1及以上版本(含仿真器)
- 编码器信号:增量式编码器A/B两相正交信号(TTL电平,3.3V),每圈脉冲数(PPR)由外部参数配置
核心实现方案
增量式编码器解码的核心是识别A/B两相信号的相位关系与边沿跳变。本设计采用4倍频解码方案:在每个时钟周期采样A/B信号,检测A、B两相的电平组合状态变化,根据状态转移方向判断旋转方向并产生位置计数脉冲。速度测量采用M法(定时计数法),在固定时间窗口内统计位置增量,计算得到速度值。
关键完整代码
// encoder_decoder.v - 增量式编码器4倍频解码与速度测量
module encoder_decoder #(
parameter PPR = 1000, // 编码器每圈脉冲数
parameter CLK_FREQ = 50_000_000, // 系统时钟频率(Hz)
parameter SPEED_WINDOW = 10_000 // 速度测量窗口(时钟周期数)
)(
input wire clk, // 系统时钟
input wire rst_n, // 异步复位,低有效
input wire encoder_a, // 编码器A相
input wire encoder_b, // 编码器B相
output reg [31:0] position, // 当前位置(4倍频计数)
output reg [31:0] speed // 速度(脉冲数/秒,按窗口计算)
);
// 同步采样与去毛刺(两级触发器)
reg a_sync1, a_sync2, b_sync1, b_sync2;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
a_sync1 <= 1'b0; a_sync2 <= 1'b0;
b_sync1 <= 1'b0; b_sync2 <= 1'b0;
end else begin
a_sync1 <= encoder_a;
a_sync2 <= a_sync1;
b_sync1 <= encoder_b;
b_sync2 <= b_sync1;
end
end
// 状态编码:{A,B} 组合
wire [1:0] state = {a_sync2, b_sync2};
reg [1:0] prev_state;
// 位置计数
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
position <= 32'd0;
prev_state <= 2'b00;
end else begin
prev_state <= state;
case ({prev_state, state})
// 正转:状态按 00→01→11→10→00 循环
4'b0001, 4'b0111, 4'b1110, 4'b1000: position <= position + 1;
// 反转:状态按 00→10→11→01→00 循环
4'b0010, 4'b1011, 4'b1101, 4'b0100: position <= position - 1;
default: position <= position; // 无效跳变或静止
endcase
end
end
// 速度测量(M法:固定窗口内计数位置增量)
reg [31:0] window_cnt;
reg [31:0] pos_snapshot;
reg [31:0] pos_delta;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
window_cnt <= 32'd0;
pos_snapshot <= 32'd0;
pos_delta <= 32'd0;
speed <= 32'd0;
end else begin
if (window_cnt == SPEED_WINDOW - 1) begin
// 窗口结束:计算速度并重置
pos_delta <= position - pos_snapshot;
speed <= (position - pos_snapshot) * CLK_FREQ / SPEED_WINDOW;
pos_snapshot <= position;
window_cnt <= 32'd0;
end else begin
window_cnt <= window_cnt + 1;
end
end
end
endmodule
仿真测试代码
// tb_encoder_decoder.v - 仿真激励
`timescale 1ns / 1ps
module tb_encoder_decoder;
reg clk = 0;
reg rst_n = 0;
reg a = 0, b = 0;
wire [31:0] position, speed;
// 实例化被测模块
encoder_decoder #(
.PPR(1000),
.CLK_FREQ(50_000_000),
.SPEED_WINDOW(1000) // 缩短窗口便于仿真观察
) uut (
.clk(clk), .rst_n(rst_n),
.encoder_a(a), .encoder_b(b),
.position(position), .speed(speed)
);
// 50MHz时钟
always #10 clk = ~clk;
// 编码器信号生成:模拟正转(每步1个时钟周期)
task step_forward;
begin
// 状态序列:00→01→11→10
#20 a = 0; b = 1; // 01
#20 a = 1; b = 1; // 11
#20 a = 1; b = 0; // 10
#20 a = 0; b = 0; // 00
end
endtask
// 编码器信号生成:模拟反转
task step_backward;
begin
// 状态序列:00→10→11→01
#20 a = 1; b = 0; // 10
#20 a = 1; b = 1; // 11
#20 a = 0; b = 1; // 01
#20 a = 0; b = 0; // 00
end
endtask
initial begin
// 复位
rst_n = 0;
#100;
rst_n = 1;
#100;
// 正转4个脉冲(4倍频后应计16)
repeat(4) step_forward();
#100;
$display("正转4个脉冲后 position = %0d (期望16)", position);
// 反转2个脉冲(应减8)
repeat(2) step_backward();
#100;
$display("反转2个脉冲后 position = %0d (期望8)", position);
// 等待速度窗口完成
#20000;
$display("速度测量值 speed = %0d 脉冲/秒", speed);
#500;
$finish;
end
endmodule
运行与验证方法
- 仿真验证:在Vivado中创建工程,添加上述两个文件,运行行为仿真。观察
position信号:正转4个完整周期(16个状态跳变)后,位置应为16;反转2个周期后,位置应为8。速度值在窗口结束后更新,可通过波形或打印信息确认。 - 硬件验证:将编码器A/B信号接入FPGA的GPIO引脚(需添加约束文件),使用ILA(集成逻辑分析仪)抓取
position和speed信号。手动旋转编码器,观察位置值随旋转方向增减,速度值随旋转快慢变化。
通过判定标准
- 仿真中位置计数与手动计算的理论值完全一致(正转加、反转减,4倍频关系正确)
- 速度输出在窗口结束后更新,且数值与设定转速成线性关系
- 编码器静止时位置保持不变,速度趋近于0
常见问题
- 计数方向相反:交换A/B两相输入接线,或在代码中交换状态转移判断
- 位置抖动:编码器信号需加RC滤波或使用施密特触发器整形,确保信号边沿干净
- 速度测量窗口选择:窗口过短导致速度分辨率低,过长则响应慢;需根据实际转速范围权衡