一、前提背景
这篇文章记录了当时在实习期间做了一个四驱的智能移动平台,通过四路PWM控制电机转速,8个IO口控制电机方向。由于起初电机内部霍尔传感器精度较差,所以选择外部扩展增量式编码器,然后通过与电机同轴的编码器反馈实时转速,从而实现电机的闭环PID控制方法。
二、了解编码器
以下列出几种常用的编码器:
1.增量式编码器
增量式编码器是将位移转换成周期性的电信号,再把这个电信号转变成计数脉冲,用脉冲的个数表示位移的大小。增量型编码器一般有二个输出,分别称为A和B,二个输出是正交输出,相位差为90度。增量型编码器的单圈脉冲数(PPR)为其旋转一圈时会输出的方波数,如PPR为600表示旋转一圈时A和B都会输出600个方波,但先后顺序不同。光学式(光电)增量型编码器可以有较高的单圈脉冲数,例如 2500 到 10000。
通过采集编码器的AB相输出,我们可以根据脉冲数量计算出电机转动圈数,从而换算为里程。同时,AB相变化规律受转动方向影响,从而可以根据此原理推断出电机转向。
2.绝对值编码器
绝对式旋转编码器是将设备运动时的位移信息通过二进制编码的方式变成数字量直接输出。这种编码器与增量式编码器的区别主要在内部的码盘。绝对式编码器的码盘利用若干透光和不透
光的线槽组成一套二进制编码,这些二进制码与编码器转轴的每一个不同角度是唯一对应的,读
取这些二进制码就能知道设备的绝对位置,所以叫它绝对式编码器。绝对式编码器一般常用自然
二进制、格雷码或者 BCD 码等编码方式。
绝对编码器由机械位置决定的每个位置是唯一的,它无需记忆,无需找参考点,而且不用一直计数,什么时候需要知道位置,什么时候就去读取它的位置。这样,编码器的抗干扰特性、数据的可靠性大大提高了。
3.正余弦编码器
一般的正余弦编码器可以有绝对值和增量式两种,光电的编码器,一般的对应伺服器的要求,有AB两路,这个信号可以用来做分辨率,相当于增量编码器的AB信号,只不过不是TTL电平,而是1V的正余弦信号,每圈的正余弦波形的个数就是分辨率,一样的可以做4倍频,这两路信号可以做分辨率,还可以判断马达正反转,根据相位超前滞后的关系来判断出马达运行。还有一路R信号,每周一个波形,周期和AB相一样,用来做原点用,ABR有了但是没有提供马达所需要的磁极信号,所以就出现了CD信号,CD信号其实也是个正余弦信号,每周一个脉冲,根据这个信号可以分解出马达的位置,用来磁极信号。
三、定时器配置
1.定时器的编码器模式
本文用的是基于STM32的标准库配置的(现在好像基本用HAL来配置了,不过大同小异),采用四路定时器分别对应电机的编码器输出(这里用的是增量式编码器)。配置方法依旧是使能相应的时钟、配置相关引脚、设置定时器参数、开启相关的中断、然后使能定时器开始采集。
一个外部的增量编码器可以直接与MCU连接而不需要外部接口逻辑。但是,一般会使用比较器将编码器的差动输出转换到数字信号,这大大增加了抗噪声干扰能力。编码器输出的第三个信
号表示机械零点,可以把它连接到一个外部中断输入并触发一个计数器复位。
2.初始化配置函数:
双边沿触发模式(如下图,TI1,TI2的上升沿和下降沿都会进行计数),使用了四个定时器,TIM2,TIM3,TIM4,TIM8,中断是否需要根据自己情况修改,通过查询编码器 TIM2->CNT可知产生的脉冲数,如果用来测速,可以定时读取计数值并清零,速度=脉冲数/时间(单位自己确定),总体来说STM32的编码器器方式还是挺方便的,硬件自动计数无需软件计数,需要注意的就是计数器的临界值0和65536,都会触发溢出中断,为了防止电机启动瞬间抖动造成的误判断,需要自己写个判断的函数,最好再加个软件上的滤波算法
初始化定时器TIM2的编码器模式和PA0、PA1引脚
void ENC_Init1(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
GPIO_StructInit(&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
TIM_DeInit(TIM2);
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.TIM_Period = (4*ENCODER2_PPR)-1;
TIM_TimeBaseStructure.TIM_Prescaler = 0;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
TIM_EncoderInterfaceConfig(TIM2, TIM_EncoderMode_TI12, TIM_ICPolarity_BothEdge, TIM_ICPolarity_BothEdge);
TIM_ICStructInit(&TIM_ICInitStructure);
TIM_ICInitStructure.TIM_ICFilter = ICx_FILTER;
TIM_ICInit(TIM2, &TIM_ICInitStructure);
NVIC_InitStructure.NVIC_IRQChannel =TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
TIM_ClearFlag(TIM2, TIM_FLAG_Update);
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
TIM2->CNT = 0;
TIM_Cmd(TIM2, ENABLE);
}
初始化定时器TIM3的编码器模式和PA6、PA7引脚
void ENC_Init2(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
GPIO_StructInit(&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
TIM_DeInit(TIM3);
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.TIM_Period = (4*ENCODER2_PPR)-1;
TIM_TimeBaseStructure.TIM_Prescaler = 0;
TIM_TimeBaseStructure.TIM_ClockDivision =TIM_CKD_DIV1 ;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_BothEdge ,TIM_ICPolarity_BothEdge);
TIM_ICStructInit(&TIM_ICInitStructure);
TIM_ICInitStructure.TIM_ICFilter = ICx_FILTER;
TIM_ICInit(TIM3, &TIM_ICInitStructure);
NVIC_InitStructure.NVIC_IRQChannel =TIM3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
TIM_ClearFlag(TIM3, TIM_FLAG_Update);
TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
TIM3->CNT = 0;
TIM_Cmd(TIM3, ENABLE);
}
初始化定时器TIM4的编码器模式和PB6、PB7引脚
void ENC_Init3(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_StructInit(&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
TIM_DeInit(TIM4);
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.TIM_Period = (4*ENCODER2_PPR)-1;
TIM_TimeBaseStructure.TIM_Prescaler = 0;
TIM_TimeBaseStructure.TIM_ClockDivision =TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);
TIM_EncoderInterfaceConfig(TIM4, TIM_EncoderMode_TI12, TIM_ICPolarity_BothEdge ,TIM_ICPolarity_BothEdge);
TIM_ICStructInit(&TIM_ICInitStructure);
TIM_ICInitStructure.TIM_ICFilter = ICx_FILTER;
TIM_ICInit(TIM4, &TIM_ICInitStructure);
TIM_ClearFlag(TIM4, TIM_FLAG_Update);
TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE);
TIM4->CNT = 0;
TIM_Cmd(TIM4, ENABLE);
}
初始化定时器TIM8的编码器模式和PC6、PC7引脚
void ENC_Init4(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC | RCC_APB2Periph_TIM8, ENABLE);
GPIO_StructInit(&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
TIM_DeInit(TIM8);
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.TIM_Period = (4*ENCODER2_PPR)-1;
TIM_TimeBaseStructure.TIM_Prescaler = 0;
TIM_TimeBaseStructure.TIM_ClockDivision =TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM8, &TIM_TimeBaseStructure);
TIM_EncoderInterfaceConfig(TIM8, TIM_EncoderMode_TI12, TIM_ICPolarity_BothEdge ,TIM_ICPolarity_BothEdge);
TIM_ICStructInit(&TIM_ICInitStructure);
TIM_ICInitStructure.TIM_ICFilter = ICx_FILTER;
TIM_ICInit(TIM8, &TIM_ICInitStructure);
TIM_ClearFlag(TIM8, TIM_FLAG_Update);
TIM_ITConfig(TIM8, TIM_IT_Update, ENABLE);
TIM8->CNT = 0;
TIM_Cmd(TIM8, ENABLE);
}
好了,到这里完成了四路定时器的编码器模式配置。
下面是定时器计数溢出中断函数,这里只列出两路,另外两路的中断函数改下名字即可。在此函数中需要实现记录溢出时间间隔,溢出次数以做出更好的抗干扰性能。由于电机的机械特性,在计数的临界点容易出现抖动,造成方向或者计数的误判,这里工作原因不便贴出代码,自己思考下如何抗干扰。
void TIM2_IRQHandler (void)//执行TIM4(电机A编码器采集)计数中断
{
TIM_ClearFlag(TIM2, TIM_FLAG_Update);
}
void TIM3_IRQHandler (void)//执行TIM3(电机B编码器采集)计数中断
{
TIM_ClearFlag(TIM3, TIM_FLAG_Update);
}
本文到此结束,谢谢大家的观看!