简述
遇到问题:不知道怎么使用旋转编码器;不知道判断左旋右旋;编码器硬件消抖后还是抖动、乱跳,不符合编程预想结果。
编程思路:通过信号打拍进行信号跳变检测;当一个端口的跳变时,判断另一个端口的电平状态,来判断是左旋还是右旋;通过计数器计数来消除编码器的抖动。
正文
最近学习FPGA的时候有用到旋转编码器,学习过程也遇到许多的困难,也有看许多关于旋转编码器的文章,不过许多的文章都是关于C语言的,关于Verilog的非常少,因此准备写一篇FPGA驱动旋转编码器的文章。代码亲测可用。
遇到的问题
1、如何判断左旋右旋;
2、编码器加了硬件消抖后仍然有抖动、乱跳。
编码器介绍
目前我所遇到的编码器有两种,一种是拧动一下,端口电平跳变一次;另一种是拧动一下,端口产生一个高电平脉冲。不过两种编码器都有共同特点,左旋旋钮左端口电平先跳变,右旋旋钮右端口先跳变。(图片是自己画的有点简陋,凑合一下吧)
第一种编码器
第二种编码器
硬件电路
这里附上我的硬件电路图
代码编写
思路:
通过打拍,再进行逻辑判断,检测信号的上升沿和下降沿。
一个端口跳变的时候,判断另一个端口的电平。(如左端口出现上升沿时右端口是低电平代表左拧一次,以此类推。)
通过设置计数器计数1ms用于消除信号抖动。(计数时长根据自己需求修改)
代码:(亲测可用)
module rotary_encoder
(
input wire sys_clk ,//时钟信号
input wire sys_rst_n ,//复位信号
input wire left_io ,//左/A端口
input wire right_io ,//右/B端口
// input wire button_io ,
output reg left_flag ,//左旋单脉冲信号
output reg right_flag //右旋单脉冲信号
// output reg button_flag
);
parameter CNT_MAX = 16'd49_999;//计数1ms
//用于打拍
reg left_io1 ;
reg right_io1;
//计数延迟消抖
reg [15:0] cnt_left ;
reg [15:0] cnt_right;
//稳定的电平信号
reg left_deb ;
reg right_deb;
//用于打拍
reg left_deb1 ;
reg right_deb1;
//各端口上升、下降沿信号
wire left_pose ;
wire right_pose;
wire left_nege ;
wire right_nege;
//左端口计数消抖
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_left <= 16'd0;
else if(left_io != left_io1)
cnt_left <= 16'd0;
else if(cnt_left == CNT_MAX)
cnt_left <= CNT_MAX;
else
cnt_left <= cnt_left + 16'd1;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
left_deb <= 1'b0;
else if(cnt_left == (CNT_MAX - 16'd1))
left_deb <= left_io1;
else
left_deb <= left_deb;
//右端口计数消抖
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_right <= 16'd0;
else if(right_io != right_io1)
cnt_right <= 16'd0;
else if(cnt_right == CNT_MAX)
cnt_right <= CNT_MAX;
else
cnt_right <= cnt_right + 16'd1;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
right_deb <= 1'b0;
else if(cnt_right == (CNT_MAX - 16'd1))
right_deb <= right_io1;
else
right_deb <= right_deb;
//打拍
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
begin
left_io1 <= 1'b0;
right_io1 <= 1'b0;
left_deb1 <= 1'b0;
right_deb1 <= 1'b0;
end
else
begin
left_io1 <= left_io ;
right_io1 <= right_io ;
left_deb1 <= left_deb ;
right_deb1 <= right_deb;
end
//检测跳变
assign left_pose = left_deb & (!left_deb1);
assign right_pose = right_deb & (!right_deb1);
assign left_nege = left_deb1 & (!left_deb);
assign right_nege = right_deb1 & (!right_deb);
// 左端口上升沿时,右端口低电平||左端口下降沿时,右端口高电平
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
left_flag <= 1'b0;
else if((left_pose==1'b1) && (right_deb==1'b0))
left_flag <= 1'b1;
else if((left_nege==1'b1) && (right_deb==1'b1))
left_flag <= 1'b1;
else
left_flag <= 1'b0;
// 右端口上升沿时,左端口低电平||右端口下降沿时,左端口高电平
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
right_flag <= 1'b0;
else if((right_pose==1'b1) && (left_deb==1'b0))
right_flag <= 1'b1;
else if((right_nege==1'b1) && (left_deb==1'b1))
right_flag <= 1'b1;
else
right_flag <= 1'b0;
endmodule
我使用的是第一种旋转编码器,最后的判断能实现每拧一次,获得一个对应信号脉冲,当然也可以根据自己的需求拆解开。
注意!如果使用的是第二种旋转编码器,最后的判断会输出两个对应信号脉冲,需要自己拆开。
这段代码已实现左旋和右旋的判断,还缺少按下的判断,如有需求,可自行仿照左旋和右旋的代码逻辑编写。
希望这篇文章能够帮助到你,如有不足欢迎各位大佬指正。