当前位置: 代码网 > 科技>人工智能 > STM32编码器测速

STM32编码器测速

2024年08月02日 人工智能 我要评论
编码器是一种能将直线位移、角位移数据转换为脉冲信号、二进制编码的设备。它本质上就是一个传感器,可以把角位移或直线位移转换成电信号,并反馈给控制器,使控制器知道当前机械运动的位置、角度等信息。编码器按照检测原理可以分为光电式和磁电式;按照编码类型可分为增量式和绝对式。在实际的应用中,这四类编码器并不是相对独立的,它们经过组合后,就变成了光电绝对式、光电增量式、磁电绝对式和磁电增量式这四种编码器。

        本文是用stm32f103c8t6,通过磁电增量式编码器测量直流有刷电机速度的学习笔记。

编码器简介

        编码器是一种能将直线位移、角位移数据转换为脉冲信号、二进制编码的设备。它本质上就是一个传感器,可以把角位移或直线位移转换成电信号,并反馈给控制器,使控制器知道当前机械运动的位置、角度等信息。

        编码器按照检测原理可以分为光电式磁电式;按照编码类型可分为增量式和绝对式。在实际的应用中,这四类编码器并不是相对独立的,它们经过组合后,就变成了光电绝对式、光电增量式、磁电绝对式和磁电增量式这四种编码器。

磁电增量式编码器

        原理:利用霍尔效应,将位移转换成计数脉冲,用脉冲个数计算位移和速度。磁电增量式编码器的具体工作原理如图:

         磁电增量式编码器的结构包含:磁盘、霍尔传感器以及信号转换电路 3 个部分,其中,磁盘是由交替排布的 s 极和 n 极磁极组成;霍尔传感器可以把磁场的变化转换成电信号的变化,它通常有 a、b 两相(有的还有 z 相),这两相的安装位置形成一定的夹角,这使得输出的 a、b 两相信号有 90°的相位差;信号转换电路可以把电信号转换成脉冲信号。在实际应用中,磁盘会装在电机的转轴上,它会随着电机的转轴旋转,而磁盘上面的 s 极和 n 极就会交替地经过霍尔传感器的 a、b 两相,霍尔传感器就可以把磁盘上的磁场变化转换为电信号的变化,输入到信号转换电路中,经过信号的转换之后,我们就可以得到 a、b 两相脉冲信号了。从上图中可以看到,a、b 两相脉冲信号存在 90°的相位差,而磁盘的正反转方向就决定了是 a 相信号在前还是 b 相在前。

光电绝对式编码器

        原理:当码盘处于不同位置(角度)时,光敏元件根据受光与否转换出相应的电平信号,最后转换成二进制数输出。光电绝对式编码器的结构总体来说和光电增量式很类似,都是由光电码盘、光源、透镜、受光元件以及信号转换电路 5 个部分,但是它们码盘结构和输出信号含义不同。光电绝对式编码器的自然二进制码盘如图:

         光电绝对式编码器的二进制码盘上有很多圈线槽,我们称为码道。上图中的二进制码盘有 4 个码道,按照自然二进制的方式排列,这个码盘一共被分为 2^4 = 16 个区域,这些区域中,黑块不透光,代表 1;白块透光,代表 0。当码盘随着电机转轴旋转,光线会照射到不同的区域,受光元件就能感受到不同的光线情况,最后经过信号的处理,就可以直接输出该区域对应的二进制码了,而我们通过这个二进制码即可得出码盘(电机转轴)的当前位置(角度)。大家需要注意:二进制码盘的每一个位置对应一个确定的二进制码,因此这一类编码器常被应用于位置以及角度测量。上述的自然二进制码盘读数很方便直观,但是它在实际应用中容易造成读数偏差很大,例如:当码盘停止旋转时,光线照射在 0000 和 1111 这两个相邻的区域之间,此时输出的二进制数可能是 0000~1111 中的任何一个,此时的读数和码盘的实际位置可能就相差很远了。为了避免读数和实际位置出现巨大偏差,我们可以改进一下二进制码的排列方式,使用格雷码形式,如图:

        格雷码盘有 4 个码道,同样的也能表示 16 个二进制数,但是任意相邻的两个区域之间的二进制码只有一位不同。当我们采用格雷码盘时,如果码盘停止旋转,光线照射到码盘相邻两个区域之间,其最终输出的二进制数最多只会相差一位,此时位置的偏差范围就很小了。

stm32的编码器接口

        直流有刷电机的编码器有 a、b 两相,它们会输出两个相位差为 90°的脉冲。当电机正转时,a 相脉冲在前;当电机反转时,则是 b 相脉冲在前。有了 a、b 两相脉冲信号之后,我们应该如何去处理这些信号,把它们转换成电机的转速呢?这里就涉及到一个非常重要的功能:定时器编码器接口模式。stm32 定时器的编码器接口模式就相当于带有方向选择的外部时钟,也就是说,在此模式下,外部输入的脉冲信号可以作为计数器的时钟,而计数的方向则是由脉冲相位的先后所决定的。定时器编码器接口模式的原理如图:

         当电机(编码器)正转时,输出两相脉冲信号,a 相脉冲在前,此时编码器接口把脉冲信号作为计数器的脉冲,计数方式为递增计数;当电机(编码器)反转时,计数方式就变成了递减计数

编码器接口框图

         a、b 两相脉冲信号从 timx_ch1 和 timx_ch2 这两个通道输入,经过滤波器和边沿检测器(可以设置滤波和反相)的处理,进入到编码器接口控制器中,生成ck_psc,成为定时器的时钟。大家需要注意,定时器共有四个通道,通道3和通道4是不支持编码器模式的,只有通道1和通道2支持编码器模式,基本定时器tim6和tim7没有编码器模式

输入滤波器:主要用于对输入信号进行滤波,例如设置输入滤波器为10,则会采样十次,如果这十次采样电平都是高或都是低则为有效电平,如果在高低电平之间波动,则为无效电平,直接过滤掉

边沿检测器:主要用于设置输入捕获边沿是否反相,如果设置反相,采集到的脉冲会和实际的脉冲相反,比如实际脉冲是上升沿,会采集成下降沿,一般设置不反相。

编码器接口计数原理

        编码器的计数方向与脉冲信号的关系如下:

        下面以一个例子来讲解上图, 假设我们把 a 相接在 ti1,b 相接在ti2,选择仅在 ti1 处计数(仅检测a 相边沿)。此时编码器接口计数方向和输入脉冲信号的关系如下表:

         a、b 两相输出的脉冲信号有两种情况:当编码器正转,a 相在前;当编码器反转,b 相在前,我们选择仅在 ti1 处计数,也就是只检测 a 相的边沿。接下来我们分别介绍这两种情况下的计数方向:

        正转:当 a 相上升沿到来时(图中①处),我们需要关注 b 相的电平高低,从图中可看到 b 相此时是低电平,结合表 7.2.2,可以得知此时计数方向为递增计数;当 a 相下降沿到来时(图中②处),从图中可以看到 b 相此时是高电平,结合表 7.2.2,可以得知此时计数方向为递增计数;当 a 相上升沿再次到来时(图中③处),同理可得此时计数方向为递增计数。综上所得,我们可以知道此时编码器正转对应的计数方向就是递增计数

        反转:当 a 相上升沿到来时(图中④处),我们需要关注 b 相的电平高低,从图中可看到 b 相此时是高电平,结合表 7.2.2,可以得知此时计数方向为递减计数;当 a 相下降沿到来时(图中⑤处),从图中可以看到 b 相此时是低电平,结合表 7.2.2,可以得知此时计数方向为递减计数;当 a 相上升沿再次到来时(图中⑥处),同理可得此时计数方向为递减计数。综上所得,我们可以知道此时编码器反转对应的计数方向就是递减计数

        注意:选择仅在 ti1 或者 ti2 处计数,就相当于对脉冲信号进行了 2 倍频(两个边沿),此时如果编码器输出 10 个脉冲信号,那么就会计数 20 次。如果选择的是在 ti1 和 ti2 处均计数,就相当于对脉冲信号进行了 4 倍频(四个边沿),此时如果编码器输出 10 个脉冲信号,那么就会计数40 次。

编码器速度换算方法

        先来了解几个必要参数:

①、电机减速比:在对电机输出扭矩(即输出的力)有高要求的场景,我们会给直流有刷电机加上减速齿轮组,以增大输出扭矩,因为电机扭矩和转速成反比,因此可以降低转速来提高扭矩,本实验采用的电机减速比是1:30,也就是说电机输出转轴转一圈,内部线圈,也就是编码器转轴转了30圈。

②、编码器线数:编码器的线数指编码器的转轴每旋转一圈所输出的脉冲数,本实验采用的编码器线数是11,也就是说编码器转轴转一圈,输出11个脉冲,结合减速比可得电机转轴转一圈,输出30*11 = 330个脉冲。可以据此来计算电机的瞬时速度。

③、定时器重装载值arr:编码器输出的脉冲作为定时器的输入,每采集到一个脉冲,定时器的计数器就加一,当加到重装载值arr时会产生溢出,如果是正转会向上溢出,反转会向下溢出,一般设置arr为65535

④、预分频系数:编码器输出的脉冲作为定时器的输入,预分频分的就是脉冲数,如果预分频系数为2,假设电机旋转一圈产生100个脉冲,则此时你单片机只能记录50个脉冲,一般不分频

        综上所述,电机转一圈,编码器采集到330*4/pcs个脉冲,再用定时器定时m秒,记录m秒内溢出的次数为n次,得到速度为 v=( n * arr+当前的计数值) / (330 * 4 / psc )/ m 。

        上式中的330是减速比乘上线数,4是设置在 ti1 和 ti2 处均计数时,采集到的脉冲会乘4,psc是分频值,arr是重装载值,得到的结果单位是转每秒。

实验代码

        以下代码是通过定时器3的通道1(pa6)和通道2(pa7)设置为编码器模式以采集脉冲数。

整体流程如下:

①、使能定时器3时钟和gpioa时钟

②、初始化两个io为浮空输入

③、初始化定时器参数

④、配置定时器为编码器模式

⑤、初始化输入捕获通道

⑥、初始化nvic中断

⑦、使能定时器更新中断,使能定时器

extern int    encoder; //当前速度
void motorencoder_init(u16 arr,u16 psc)
{
    gpio_inittypedef            gpio_initstructure;
    tim_timebaseinittypedef     tim_timebasestructure;
    tim_icinittypedef           tim_icinitstructure;
    nvic_inittypedef            nvic_initstructure;
    rcc_apb1periphclockcmd(rcc_apb1periph_tim3, enable);//使能定时器3时钟 
    rcc_apb2periphclockcmd(rcc_apb2periph_gpioa , enable);  //使能gpio外设时钟使能
    
    //配置定时器复用gpio
    gpio_initstructure.gpio_pin = gpio_pin_6; //tim3_ch1
    gpio_initstructure.gpio_mode = gpio_mode_in_floating;  //浮空输入,输入编码器脉冲
    gpio_init(gpioa, &gpio_initstructure);
    
    gpio_initstructure.gpio_pin = gpio_pin_7; //tim3_ch2
    gpio_initstructure.gpio_mode = gpio_mode_in_floating;  //浮空输入,输入编码器脉冲
    gpio_init(gpioa, &gpio_initstructure);
    
    //初始化定时器
    tim_timebasestructure.tim_period = 65535; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值     80k
    tim_timebasestructure.tim_prescaler =0; //设置用来作为timx时钟频率除数的预分频值  不分频
    tim_timebasestructure.tim_clockdivision = tim_ckd_div1; //一般不使用,默认tim_ckd_div1
    tim_timebasestructure.tim_countermode = tim_countermode_up;  //tim向上计数模式
    tim_timebaseinit(tim3, &tim_timebasestructure); 
    //编码器模式配置,定时器3,双边沿检测,也就是四倍频,输入极性不反向
    tim_encoderinterfaceconfig(tim3, tim_encodermode_ti12,tim_icpolarity_rising, tim_icpolarity_rising);
    
    //输入捕获通道设置
    tim_icinitstructure.tim_channel     = tim_channel_1;//通道1
    tim_icinitstructure.tim_icpolarity  = tim_icpolarity_rising;//上升沿捕获       
    tim_icinitstructure.tim_icselection = tim_icselection_directti;//ti1直接连到ic1
    tim_icinitstructure.tim_icprescaler = tim_icpsc_div1;//捕获不分频,每个上升沿都捕获
    tim_icinitstructure.tim_icfilter    = 0x06;//输入比较滤波器
    tim_icinit(tim3,&tim_icinitstructure);
    
    tim_icinitstructure.tim_channel     = tim_channel_2;//通道2
    tim_icinitstructure.tim_icpolarity  = tim_icpolarity_rising;       
    tim_icinitstructure.tim_icselection = tim_icselection_directti;
    tim_icinitstructure.tim_icprescaler = tim_icpsc_div1;                   
    tim_icinitstructure.tim_icfilter    = 0x06;
    tim_icinit(tim3,&tim_icinitstructure);
    
    //nvic中断配置
    nvic_initstructure.nvic_irqchannel = tim3_irqn;                                  
    nvic_initstructure.nvic_irqchannelpreemptionpriority = 0;    
    nvic_initstructure.nvic_irqchannelsubpriority = 0;                   
    nvic_initstructure.nvic_irqchannelcmd = enable;                               
    nvic_init(&nvic_initstructure);

    tim_clearitpendingbit(tim3, tim_it_update  );//清除中断和捕获标志位
    tim_clearflag(tim3, tim_flag_update);//清除中断标志位

    /*开启编码器溢出中断*/
    tim_itconfig(tim3, tim_it_update, enable);

    tim3->cnt = 0;	//定时器计数归零
    tim_cmd(tim3, enable);   //启动定时器
}
//读取编码器
int read_encoder(void)
{
    uint16_t value_1;
    int16_t value_2;
    value_1=tim_getcounter(tim3);//获取当前计数值
    tim_setcounter(tim3,0);//将当前计数值清零
    value_2 = tim3_circle_count;//获取溢出次数
    tim3_circle_count = 0;//溢出次数清零
		return value_1 + value_2*65535;//计算总计数值
}
void tim3_irqhandler()
{
    if (tim_getitstatus(tim3, tim_it_update) != reset)  //判断如果是定时器溢出中断
	{
//dir==0,判断当前计数方向,如果是向上计数表示正转,则溢出次数++		
        if((((tim3->cr1)>>4) & 0x01)==0)tim3_circle_count++;
//dir==1,向下计数表示反转,溢出次数--
		else                            tim3_circle_count--;
	}
    tim_clearitpendingbit(tim3, tim_it_update  ); //清除中断标志

}

关于编码器测速计算的坑

        使用上述代码计算编码器脉冲时遇到了以下问题,当电机反转时,计数器会向下计数,然而每次计数器都会从0开始计数,所以就会直接溢出,从65535向下计数,tim_getcounter返回值的类型是uint16_t,也就是无符号,只有正数,如果用int型接收,32767到65535的部分会变成-32767到-1,加上溢出次数乘65535就会变成很大的负值,导致出错,所以value1必须用uint16_t接收,另外计算溢出次数时,如果是反转溢出次数会变成负数,所以value2必须用有符号类型int接收。

        下面用通用定时器2产生定时中断,在中断中周期性采集编码器脉冲并打印,定时周期等于1000*(7200/72000000) = 0.1s,两个通道都采集,采集到的脉冲数是正常的四倍,所以要除以4,线数是11,减速比是30,所以还要除以330,要求1s的转数还要乘10,最后转速为speed = (float)encoder*10/4/11/30,单位为转每秒。

/**************************************************************************
函数功能:通用定时器2初始化函数,
入口参数:自动重装载值 预分频系数 默认定时时钟为72mhz时,两者共同决定定时中断时间
返回  值:无
**************************************************************************/
void encoderread_tim2(u16 arr, u16 psc)
{
	tim_timebaseinittypedef tim_timebaseinitstrue; //定义一个定时中断的结构体
	nvic_inittypedef nvic_initstrue; //定义一个中断优先级初始化的结构体
	
  rcc_apb1periphclockcmd(rcc_apb1periph_tim2, enable); //使能通用定时器2时钟
	
	tim_timebaseinitstrue.tim_period=1000; //计数模式为向上计数时,定时器从0开始计数,计数超过到arr时触发定时中断服务函数
	tim_timebaseinitstrue.tim_prescaler=7199; //预分频系数,决定每一个计数的时长
	tim_timebaseinitstrue.tim_countermode=tim_countermode_up; //计数模式:向上计数
	tim_timebaseinitstrue.tim_clockdivision=tim_ckd_div1; //一般不使用,默认tim_ckd_div1
	tim_timebaseinit(tim2, &tim_timebaseinitstrue); //根据tim_timebaseinitstrue的参数初始化定时器tim2
	
	tim_itconfig(tim2, tim_it_update, enable); //使能tim2中断,中断模式为更新中断:tim_it_update
	
	nvic_initstrue.nvic_irqchannel=tim2_irqn; //属于tim2中断
	nvic_initstrue.nvic_irqchannelcmd=enable; //中断使能
	nvic_initstrue.nvic_irqchannelpreemptionpriority=1; //抢占优先级为1级,值越小优先级越高,0级优先级最高
	nvic_initstrue.nvic_irqchannelsubpriority=1; //响应优先级为1级,值越小优先级越高,0级优先级最高
	nvic_init(&nvic_initstrue); //根据nvic_initstrue的参数初始化vic寄存器,设置tim2中断
	
	tim_cmd(tim2, enable); //使能定时器tim2
}

/**************************************************************************
函数功能:tim2中断服务函数 定时读取编码器数值并进行速度闭环控制 10ms进入一次
入口参数:无
返回  值:无
**************************************************************************/
void tim2_irqhandler()
{
  if(tim_getitstatus(tim2, tim_it_update)==1) //当发生中断时状态寄存器(timx_sr)的bit0会被硬件置1
	{
	  encoder=read_encoder();   //读取当前编码器读数
        //因为定时器周期是0.1s,要得到1s的速度需要乘10,4是因为两个通道都采集,采集到的是四倍频,需要除以4,11是线数,30是减速比。
		encoder = (float)encoder*10/4/11/30;
         printf("编码器收到的值为:%d\r\n",encoder);
			
		tim_clearitpendingbit(tim2, tim_it_update); //清除中断标志
	}
}

关于编码器初始化的坑

        在初始化过程中必须要清除定时器溢出中断标志位,此标志位默认置1,如果不清除标志位会默认进入一次溢出中断,第一次采集到的编码器脉冲数会加上65535,导致测速错误。

tim_clearflag(tim3, tim_flag_update);//清除中断标志位

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com