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;    endend


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

当定时器信号输入时,根据脉冲计数模块输入的脉冲数来计算。速度的单位为 圈数/分钟,而检测周期为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;    endend


七. 角度检测模块

当需要旋转对应角度的时候,就需要对其旋转角度进行检测。旋转一圈,对应的脉冲数为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


八. 小结

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

需要完整模块文件的话可以回复 编码器1.0 获取