赞
踩
本文是用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 个二进制数,但是任意相邻的两个区域之间的二进制码只有一位不同。当我们采用格雷码盘时,如果码盘停止旋转,光线照射到码盘相邻两个区域之间,其最终输出的二进制数最多只会相差一位,此时位置的偏差范围就很小了。
直流有刷电机的编码器有 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);//清除中断标志位
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。