车轮位置的确定是在制作小车的过程中必不可少的部件,好在STM32中包含了硬件的编码器。但使用的过程中却存在诸多不方便。下面由我一一道来:
1、编码器原理
什么是正交?如果两个信号相位相差90度,则这两个信号称为正交。由于两个信号相差90度,因此可以根据两个信号哪个先哪个后来判断方向、根据每个信号脉冲数量的多少及整个编码轮的周长就可以算出当前行走的距离、如果再加上定时器的话还可以计算出速度。
2、为什么要用编码器
从上图可以看出,由于ti,T2一前一后有个90度的相位差,所以当出现这个相位差时就表示轮子旋转了一个角度。但有人会问了:既然都是脉冲,为什么不用普通IO中断?实际上如果是轮子一直正常旋转当然没有问题。仔细观察上图,如果出现了毛刺呢?这就是需要我们在软件中编写算法进行改正。于是,我们就会想到如果有个硬件能够处理这种情况那不是挺好吗?
3、STM32编码器
还是刚才那张图,但这时候我们看到STM32的硬件编码器还是很智能的,当T1,T2脉冲是连续产生的时候计数器加一或减一一次,而当某个接口产生了毛刺或抖动,则计数器计数不变,也就是说该接口能够容许抖动。在STM32中,编码器使用的是定时器接口,通过数据手册可知,定时器1,2,3,4,5和8有编码器的功能,而其他没有。编码器输入信号TI1,TI2经过输入滤波,边沿检测产生TI1FP1,TI2FP2接到编码器模块,通过配置编码器的工作模式,即可以对编码器进行正向/反向计数。如果用的是定时器3,则对应的引脚是在PA6和PA7上。根据stmn32手册上编码器模式的说明,有6中组合计数方式,见下表。
由此可知,通过选择可以确定使用定时器的哪种方式来得到我们所要的结果。STM32编码器的使用也非常简单,其基本步骤和开发STM32其他部件的操作一致,都是打开时钟,配置接口,配置模式,如果要用中断则打开中断。具体可以参考以下代码(这里使用的是TIM4,引脚采用GPIOA 11和GPIOA12):
- bool EncodeInit(u8 none1,u32 period)
- {
- TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
- TIM_ICInitTypeDef TIM_ICInitStructure;
- GPIO_InitTypeDef GPIO_InitStructure;
- NVIC_InitTypeDef NVIC_InitStructure;
- EXTI_InitTypeDef EXTI_InitStructure;
- RCC_APB1PeriphclockCmd(RCC_APB1Periph_TIM4, ENABLE);//??TIM3??
- RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);//??GPIOA??
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11 | GPIO_Pin_12;
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
- GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
- GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
- GPIO_Init(GPIOA, &GPIO_InitStructure);
- GPIO_PinAFConfig(GPIOA,GPIO_PinSource11,GPIO_AF_10);
- GPIO_PinAFConfig(GPIOA,GPIO_PinSource12,GPIO_AF_10);
- TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
- TIM_TimeBaseStructure.TIM_Prescaler = 0x0; // No prescaling
- TIM_TimeBaseStructure.TIM_Period = 1333;
- 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_Rising, TIM_ICPolarity_Rising);
- TIM_ICStructInit(&TIM_ICInitStructure);
- TIM_ICInitStructure.TIM_ICFilter = 10;
- TIM_ICInit(TIM4, &TIM_ICInitStructure);
- // Clear all pending interrupts
- TIM_ClearFlag(TIM4, TIM_FLAG_Update);
- TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE);
- //Reset counter
- TIM_SetCounter(TIM4,0);
- TIM_Cmd(TIM4, ENABLE);
- return 0;
- }
[color=rgb(51, 102, 153) !important]复制代码
4、编码器的中断
由于编码器是基于定时器的,所以编码器的中断实际上就是定时器的中断啦。也就是说定时器是每隔一定时间加一个数(或减一个数 ),当数到达预设值时就产生中断,而编码器是每一个有效脉冲就加一个数(或减一个数 ),当数到达预设值时就产生中断。若预设值为1000则编码器与定时器中断不同的是,当编码器反转时值到达999产生一次中断,而当编码器正转到达0时同样产生一次中断。在硬件上这两个中断是没法区分的,这也就造成了有种情况的误判。
5、STM32编码器没有考虑的情况
想象一下,如果编码器的预设值为1000,当某次我们使得编码器正转产生中断后,立即反转则又该怎么办呢?根据上面的说法,这时候会产生两次一样的中断。如果在算法上没有处理的话,极有可能认为是行走了两次正向。但实际上并没有。所以这个时候必须结合方向来判断行走的情况(判断方向使用的是DIR寄存器位)或者在产生中断后读一次count寄存器位(看看是999还是0,以此来判断当前的方向)。只有上一次为正且这一次同样为正,距离才是相加的。
具体中断处理函数代码如下:
- void TIM4_IRQHandler(void)
- {
- temp=(TIM_GetCounter(TIM4)&0xffff);
- if(TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET)
- {
- if(temp==9999)
- {
- count--;
- if(predir==0)//只有当前一次是负向走,这一次还是负向走才上传数据
- {
- upcount--;
- }else{
- predir=0;//表示往负向走
- }
- }else if(temp==0)
- {
- count ;
- if(predir==1)//只有当前一次是正向走,这次又是正向走才上传数据
- {
- upcount ;
- }else{
- predir=1;//表示往正向走
- }
- }
- TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
- }else{
- #ifdef debug
- printf("ENCODE TIMER INTERRUP ERROR! n");
- #endif
- while(1)
- {
- ;
- }
- 6、最后一个问题
- 那么如果当前并没有到一个中断怎么办?难道这个时候就不能得到编码器的精确位置了吗?
- 其实只是个非常简单的算法:
- u32 EncodeGetMileage(u8 none1,u8 none2)
- {
- u8 i=0;
- temp=(TIM_GetCounter(TIM4)&0xffff);
- if(count<0)
- {
- temp_mileage=(abs(count)-1)*1000 (1000-temp);
- }else{
- temp_mileage=count*1000 temp;
- }
- }
- return temp_mileage; //返回编码器的脉冲个数,一个脉冲相当于125/1333 mm,这个返回值用于本次里程的计算,给总里程用。
[color=rgb(51, 102, 153) !important]复制代码
每次把中断的次数记录下来,然后再把距离上次中断共走了多少个脉冲,再把两者相加即可。
|