STM32编码器
最近在做毕业设计,需要计算小车的行驶距离,需要用到编码器计算具体走了多远,就了解学习了一下编码器。
一、编码器简介
编码器(encoder)是将信号 如比特流或数据进行编制、转换为可用以通讯、传输和存储的信号形式的设备。编码器把角位移或直线位移转换成电信号,前者称为码盘,后者称为码尺。按照读出方式编码器可以分为接触式和非接触式两种;按码盘的刻孔方式可分为绝对编码器和增量编码器。绝对编码器由机械位置决定的每个位置是唯一的,它无需记忆,无需找参考点,而且不用一直计数,什么时候需要知道位置,什么时候就去读取它的位置。并且没有累计误差,这样,编码器的抗干扰特性、数据的可靠性大大提高了。增量式编码器不能返回位置信息,它只能返回脉冲,告知自己是否在转,速度,以及正反转等信息。
我这里只需要知道正反转转了多少算行驶距离而已,用的是增量等待AB相正交编码器。那么什么是正交呢,如果两个信号相位差为90度,则这两个信号称为正交。由于两个信号相差90度,因此可以根据两个信号哪个先哪个后来判断方向、根据每个信号脉冲数量的多少及整个编码轮的周长就可以算出当前行走的距离、如果再加上定时器的话还可以计算出速度。编码器的线数代表转一圈所发出的脉冲数。
二、STM32编码器接口
由于TI,T2一前一后有个90度的相位差,所以当出现这个相位差时就表示*旋转了一个角度。既然都是脉冲,为什么不用普通IO中断?实际上如果是*一直正常旋转当然没有问题。如果出现了毛刺呢?STM32就有对应的编码器接口去处理
STM32的硬件编码器还是很智能的,当T1,T2脉冲是连续产生的时候计数器加一或减一一次,而当某个接口产生了毛刺或抖动,则计数器计数不变,也就是说该接口能够容许抖动。
编码器输入信号TI1,TI2经过输入滤波,边沿检测产生TI1FP1,TI2FP2接到编码器模块,通过配置编码器的工作模式,即可以对编码器进行正向/反向计数。比如如果用的是定时器2,则对应的引脚是在PA0和PA1上。通常为了提高精度在上升沿和下降沿都进行计数!
提高精度会在A、B两相的上升沿和下降沿都进行计数,那么对应在一个周期就可以计数四次,计数次数的增加也就意味着精度的提高!
一个脉冲信号周期完成4次跳变。精度提高
TI2为低电平,TI1上升沿跳变,计数器向上计数;
TI1为高电平,TI2上升沿跳变,计数器仍然向上计数;
TI2为高电平,TI1下降沿跳变,计数器仍然向上计数;
TI1为低电平,TI2下降沿跳变,计数器仍然向上计数。
这样一来出现毛刺的部分向上计数和向下计数相抵掉了,相当于没计数。
三、代码
芯片型号:STM32F103RET
使用定时器:TIM2
编码器接口引脚:PA0、PA1
编码器线数:600线
编码器模式配置,TIM_Period计数器自动重装值我设定为(600-1)*4如上面所说,一转发出脉冲数600,但是AB两相上下沿都计数计数4次,所以转一圈触发一次中断。
void TIM2_Mode_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
/* 时钟配置 */
//PA0 ch1 A,PA1 ch2
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
/* 引脚配置 */
GPIO_StructInit(&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; //端口配置PA0,和PA1
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
//时基配置
TIM_DeInit(TIM2);
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.TIM_Period = (600-1)*4;//设定计数器自动重装值
TIM_TimeBaseStructure.TIM_Prescaler = 0;//预分频器
TIM_TimeBaseStructure.TIM_ClockDivision =TIM_CKD_DIV1;//选择时钟分频:不分频
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;//TIM向上计数
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 = 6;//滤波器值
TIM_ICInit(TIM2, &TIM_ICInitStructure); //将TIM_ICInitStructure中的指定参数初始化TIM2
TIM_ClearFlag(TIM2, TIM_FLAG_Update);//清除TIM的更新标志位
//--------溢出中断设置-------------
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);//允许TIM2溢出中断
NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x01;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x01;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_InitStructure);
//Reset counter
TIM2->CNT = 0;
TIM_Cmd(TIM2, ENABLE);
}
读取编码器数据
/************************************************
函数名称 : ENCODER_Read
功 能 : 读取编码器数据
参 数 : Dir --- 方向
Cnt --- 计数
返 回 值 : 无
*************************************************/
void ENCODER_Read(uint32_t *Dir, uint32_t *Cnt)
{
*Dir = (TIM2->CR1) & TIM_CR1_DIR;
*Cnt = TIM_GetCounter(TIM2);
}
编码器定时器中断处理函数
/************************************************
函数名称 : TIM2_IRQHandler
功 能 : 编码器定时器中断处理函数
参 数 : Dir --- 方向
Cnt --- 计数
返 回 值 : 无
*************************************************/
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)
{
if((TIM2->CR1>>4 & 0x01)==0) //DIR==0
circle_count++;
else if((TIM2->CR1>>4 & 0x01)==1)//DIR==1
circle_count--;
}
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
转动编码器便,打印出编码器的数据
正转,第一圈转到第二圈
各个值的计算:
转速计算方法:用捕获值(一秒内输出的脉冲数)/编码器线数(转速一圈输出脉冲数)/电机减数比(内部电机转动圈数与电机输出轴转动圈数比,即减速齿轮比 没有则不用除)
运动距离计算:输出的总脉冲数 / 编码器线数*编码器齿轮周长
所转角度计算: 输出的总脉冲数 / 编码器线数 *360 或 溢出中断次数*360+当前计数值
转动方向: 方向在定时器CR1的DIR位 dir=(TIMX->CR1 & 0x0010)>>4; //取方向标志位if(dir > 0) //向下计数 else //向上计数
参考:https://blog.csdn.net/as480133937/article/details/98750922
上一篇: request对象常用方法总结(及示例)----JSP内置对象
下一篇: STM32控制编码器