FPGA实现电机霍尔编码器模块

一. 简介

想要知道直流电机的转速,就需要用到编码器,常用的编码器有霍尔和光电两种,但是光电编码器比较贵(性能好于霍尔),所以平常的时候使用最多的是霍尔编码器了。

霍尔编码器一般有AB两相信号输出,默认的时候为低电平,当电机转动的时候,AB两相会输出具有一定相位差的脉冲,根据相位差可以确定电机旋转的方向,另外根据单位时间内,脉冲的数量可以确定电机的转速(线数X(编码器参数):电机旋转一圈产生的脉冲数,减速电机有一个减速比Y(Y越大,扭矩越大,负载转速越小),也就是说负载(一般为轮胎)旋转一圈所产生的脉冲数为: X * Y)。AB输出的脉冲波形如下

编码器的使用方法如上,非常简单,下面就用FPGA来写一个编码器模块吧!

二. 编码器模块架构

首先,需要明确一下,编码器模块需要实现那些功能,经过简单的思考,可以确定要实现的功能如下,其中加速度不是必须的。

功能确定了之后,就可以简单设计一下框图如下,根据框图可以很清晰来编写各个模块了。

三.定时器模块

定时器模块的功能非常简单。就是每隔一段时间产生一个触发信号,用来清除脉冲计数 和 计算一次速度与加速度。我这里是设置的5ms触发一次,具体多少时间触发一次合适,可以根据实际的测试效果而定。

四. 编码器脉冲检测模块

该模块的主要功能是用来检测AB两相的脉冲并且判断脉冲的方向。一共有两种方法来检测:

1. 单边沿检测 : 检测AB某一相的A沿,然后判断另外一相的电平。例如检测A相的上升沿为有效脉冲,然后判断B相的电平为电机旋转方向。该方法非常简单,但是抗干扰能力并没有那么强,容易误判。

2. 四边沿检测 : 依次检测每一个边沿,如果四个边沿均满足要求,则该脉冲为有效脉冲,否则为无效脉冲。该方法抗干扰能力比较强,但逻辑上稍微有点点复杂相较于第一种方法来说。

两种方法的实现,对于FPGA来说,不是什么难事,第一种就不提了;第二种方法的话,一个状态机就可以了,在对应的状态里面检测一相的边沿信号和另外一相的电平,两相都满足的话,就跳到下一个状态,四个状态均满足的话,则输出一个有效电平,否则的话,均回到初始状态。本次使用的是一段式状态机,代码片段如下(两个状态)。

always@(posedge sys_clk_i or negedge sys_rst_n_i) begin
if( sys_rst_n_i == 1'b0 ) begin
step <= 6'b00_0000;
encoder_AB_detect_dir_reg <= 1'b0;
end
else begin
case (step)
6'b00_0000: begin //处理第一个边沿
if( (encoder_A_posedge | encoder_B_posedge) && encoder_AB_detect_en_i == 1'b1)
step <= 6'b00_0001;
else
step <= step;
end
6'b00_0001: begin //处理第二个边沿
if( encoder_A_posedge == 1'b1)
if( encoder_B_d0 == 1'b1) begin
step <= 6'b00_0100;
encoder_AB_detect_dir_reg <= 1'b0; //在第二个边沿的时候,确定旋转方向
end
else
step <= 6'b00_0000;
else if( encoder_B_posedge == 1'b1)
if( encoder_A_d0 == 1'b1) begin
step <= 6'b00_0010;
encoder_AB_detect_dir_reg <= 1'b1; //在第二个边沿的时候,确定旋转方向
end
else
step <= 6'b00_0000;
else if( encoder_A_negedge | encoder_B_negedge) //检测到下降沿 表示错误
step <= 6'b00_0000;
else
step <= step; //需进行超时处理
end

然后模块的信号如下,

module encoder_AB_detect(
input sys_clk_i ,
input sys_rst_n_i ,

input encoder_AB_detect_en_i , //编码器检测使能
output encoder_AB_detect_valid_o, //编码器检测有效输出
output encoder_AB_detect_dir_o , //编码器检测方向输出
//硬件编码器输入
input motor_encoder_A_i , //编码器A相输入
input motor_encoder_B_i //编码器B相输入
);

五. 脉冲计数模块

计算脉冲数,由于电机在朝一个方向转动的时候,可能会由于不确定因数的影响,产生另外一个方向的脉冲,所以在计算的时候需要对其进行处理。

分别计数两个方向的脉冲数,以脉冲数多的方向为当前方向,多的脉冲数 减去 少的脉冲数 为当前方向的脉冲数。

assign encoder_Phase_count_dir_o = ( encoder_Phase_Cnt_dir0 > encoder_Phase_Cnt_dir1 ) ? 1'b0 : 1'b1;
assign encoder_Phase_count_o = ( encoder_Phase_Cnt_dir0 > encoder_Phase_Cnt_dir1 ) ? (encoder_Phase_Cnt_dir0 - encoder_Phase_Cnt_dir1):
(encoder_Phase_Cnt_dir1 - encoder_Phase_Cnt_dir0);
//编码器脉冲计数
always@(posedge sys_clk_i or negedge sys_rst_n_i) begin
if( sys_rst_n_i == 1'b0) begin
encoder_Phase_Cnt_dir0 <= 16'd0;
encoder_Phase_Cnt_dir1 <= 16'd0;
end
else if( encoder_Phase_count_clear_i == 1'b1 ) begin //定时器输入,重新开始计数
encoder_Phase_Cnt_dir0 <= 16'd0;
encoder_Phase_Cnt_dir1 <= 16'd0;
end
else if( encoder_AB_detect_valid_i == 1'b1 ) //对两个方向的脉冲分别计数
if( encoder_AB_detect_dir_i == 1'b0 ) begin
encoder_Phase_Cnt_dir0 <= encoder_Phase_Cnt_dir0 + 1'b1;
encoder_Phase_Cnt_dir1 <= encoder_Phase_Cnt_dir1;
end
else begin
encoder_Phase_Cnt_dir0 <= encoder_Phase_Cnt_dir0;
encoder_Phase_Cnt_dir1 <= encoder_Phase_Cnt_dir1 + 1'b1;
end
else begin
encoder_Phase_Cnt_dir0 <= encoder_Phase_Cnt_dir0;
encoder_Phase_Cnt_dir1 <= encoder_Phase_Cnt_dir1;
end
end

六. 速度与加速度检测模块

当定时器信号输入时,根据脉冲计数模块输入的脉冲数来计算。速度的单位为 圈数/分钟,而检测周期为5ms,所以RPM = 圈数 *( 1分钟 / 5ms ),然后这里使用的编码器为500线,电机的减速比为20,所以一圈对应的编码器脉冲数为500 * 20 = 10000 ,所以最终RPM = ( 脉冲数 / 10000) * ( 1分钟 / 5ms) = 1.2 * 脉冲数。为了便于计算将RMP扩大10倍, 即 RPM = 12 * 脉冲数 = ( 8 + 4) * 脉冲数。将脉冲数左移3位 加速 脉冲数左移2位 即可。

加速度就更加简单了,只需要将当前速度 减去 上一时刻的速度 即可,加速度有方向,还需要判断一下加速度方向。

assign encoder_speed = ( encoder_Phase_count_i << 3 ) + ( encoder_Phase_count_i << 2 );
always@( posedge sys_clk_i or negedge sys_rst_n_i ) begin
if( sys_rst_n_i == 1'b0 ) begin
encoder_speed_d0 <= 16'd0;
encoder_speed_o <= 16'd0;
encoder_acc_o <= 16'd0;
encoder_acc_dir_o<= 1'b0;
end
else if( cal_en_i == 1'b1 ) begin
encoder_speed_d0 <= encoder_speed_o;
encoder_speed_o <= encoder_speed;
encoder_acc_o <= (encoder_speed > encoder_speed_d0) ? encoder_speed - encoder_speed_d0 : encoder_speed_d0 - encoder_speed;
encoder_acc_dir_o<= (encoder_speed > encoder_speed_d0) ? 1'b1 : 1'b0;
end
else begin
encoder_speed_d0 <= encoder_speed_d0;
encoder_speed_o <= encoder_speed_o;
encoder_acc_o <= encoder_acc_o;
encoder_acc_dir_o<= encoder_acc_dir_o;
end
end

七. 角度检测模块

当需要旋转对应角度的时候,就需要对其旋转角度进行检测。旋转一圈,对应的脉冲数为10000,所以每个脉冲数对应的角度为0.036°,为了便于计算,这里将角度扩大10倍,每个脉冲数对应的角度就变成了0.36°,每5个脉冲数对应的角度就变成了1.8°,这样以5个脉冲数为一个脉冲单位,所以每一个脉冲单位角度就变化了1°,先不管0.8°,而0.8 * 5 = 4°,所以每5个脉冲单位角度就变化了4°,这样就非常方便计算了,当是会有一定的误差,最大误差角度约为: 3.2° + 1° = 4.2°,即实际误差角度为0.42°,还是可以接受的。

always@(posedge sys_clk_i or negedge sys_rst_n_i ) begin
if( sys_rst_n_i == 1'b0 )
encoder_num_angle_o <= 20'd0;
else if( encoder_accumulate_clr_i == 1'b1)
encoder_num_angle_o <= 20'd0;
else if( encoder_accumulate_en_i == 1'b1 && encoder_AB_detect_valid_i == 1'b1 )
if( encoder_AB_Angle_Cnt1 == 'd4 )
encoder_num_angle_o <= encoder_num_angle_o + 'd4 + 'd1;
else if( encoder_AB_Angle_Cnt == 'd4)
encoder_num_angle_o <= encoder_num_angle_o + 1'b1;
else
encoder_num_angle_o <= encoder_num_angle_o;
else
encoder_num_angle_o <= encoder_num_angle_o;
end

八. 小结

现在一个简单的编码器模块,就写完啦,算是一个基础版本吧,后面可以根据需求进行完善。

文章来源:FPGA之旅

最新文章

最新文章