STM32F407编码器的demo应用

2019-07-20 13:05发布

本帖最后由 五花肉老乌龟 于 2017-5-3 12:28 编辑

最近再做CNC的电子手轮用到了编码器给大家上传点资料,感谢原子哥提供了案例"实验10 输入捕获实验"






上面是马云家的编码器实物图,他们被用在了电机测速,音响功放,鼠标滚轮以及CNC数控机床上。


1。编码器原理

       什么是正交?如果两个信号相位相差90度,则这两个信号称为正交。由于两个信号相差90度,因此可以根据两个信号哪个先哪个后来判断方向、根据每个信号脉冲数量的多少及整个编码轮的周长就可以算出当前行走的距离、如果再加上定时器的话还可以计算出速度。
       2。为什么要用编码器
      
       从上图可以看出,由于TI,T2一前一后有个90度的相位差,所以当出现这个相位差时就表示轮子旋转了一个角度。但有人会问了:既然都是脉冲,为什么不用普通IO中断?实际上如果是轮子一直正常旋转当然没有问题。仔细观察上图,如果出现了毛刺呢?这就是需要我们在软件中编写算法进行改正。于是,我们就会想到如果有个硬件能够处理这种情况那不是挺好吗?
    3. STM32编码器
   
     还是刚才那张图,但这时候我们看到STM32的硬件编码器还是很智能的,当T1,T2脉冲是连续产生的时候计数器加一或减一一次,而当某个接口产生了毛刺或抖动,则计数器计数不变,也就是说该接口能够容许抖动。在STM32中,编码器使用的是定时器接口,通过数据手册可知,定时器1,2,3,4,5和8有编码器的功能,而其他没有。编码器输入信号TI1,TI2经过输入滤波,边沿检测产生TI1FP1TI2FP2接到编码器模块,通过配置编码器的工作模式,即可以对编码器进行正向/反向计数。如果用的是定时器3,则对应的引脚是在PC6和PC7上。根据stmn32手册上编码器模式的说明,有6中组合计数方式,见下表。
当我们关注TI1FP1的边沿时,第二列就是指TI2的电平。
举个例子。TI1FP1上升(或下降)时,看一下TI2的电平,如果TI2为高电平,则CNT向下计数,如果TI2位低电平,则CNT向上计数。TI1FP2下降时,看一下TI1的电平,如果TI1位高电平,则CNT向上计数。

由此可知,通过选择可以确定使用定时器的哪种方式来得到我们所要的结果。STM32编码器的使用也非常简单,其基本步骤和开发STM32其他部件的操作一致,都是打开时钟,配置接口,配置模式,如果要用中断则打开中断。具体可以参考以下代码(这里使用的是TIM8,引脚采用GPIOC 6和GPIOC7):


正转向上计数,反转向下计数,方向在CR1的DIR位里

1.编码器有个转速上限,超过这个上限是不能正常工作的,这个是硬件的限制,原则上线数越多转速就越低,这点在选型时要注意,编码器的输出一般是开漏的,所以单片机的io一定要上拉输入状态.
2.定时器初始化好以后,任何时候CNT寄存器的值就是编码器的位置信息,正转他会加反转他会减这部分是不需要软件干预的,初始化时给的TIM_Period 值应该是码盘整圈的刻度值,在减溢出会自动修正为这个数.加超过此数值就回0.
3.如果要扩展成多圈计数需要溢出中断像楼主说的,程序上圈计数加减方向位就行了.
4.每个定时器的输入脚可以通过软件设定滤波
5.应用中如果没有绝对位置信号或者初始化完成后还没有收到绝对位置信号前的计数只能是相对计数.收到绝对位置信号后重新修改一次CNT的值就行了.码盘一般都有零位置信号,结合到定时器捕获输入就行.上电以后要往返运动一下找到这个位置.
6.即便有滤波计数值偶尔也会有出错误的情况,一圈多计一个或少计一个数都是很正常的特别是转速比较高的时候尤其明显,有个绝对位置信号做修正是很有必要的.绝对位置信号不需要一定在零位置点,收到这个信号就将CNT修正为一个固定的数值即可.
7.开启定时器的输入中断可以达到每个步计数都作处理的效果,但是高速运转的时候你可能处理不过来.






enum{


        FORWARD=1,
        BACKWARD

};
extern u16 direction;

//正转向上计数,反转向下计数,方向在CR1的DIR位里
void TIM8_Mode_Config(void)
{
        GPIO_InitTypeDef GPIO_InitStructure;
        TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
        NVIC_InitTypeDef NVIC_InitStructure;
       TIM_ICInitTypeDef        TIM8_ICInitStructure;
        
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM8,ENABLE);          //TIM5时钟使能   
        RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);         //使能PORTA时钟        
        
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7; //GPIOC6/7
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;        //速度100MHz
        //GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
        //GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN; //下拉
        GPIO_Init(GPIOC,&GPIO_InitStructure); //初始化PAC

        GPIO_PinAFConfig(GPIOC,GPIO_PinSource6,GPIO_AF_TIM8); //PC6复用位定时器8
        GPIO_PinAFConfig(GPIOC,GPIO_PinSource7,GPIO_AF_TIM8); //PC7复用位定时器8

         
        TIM_TimeBaseStructure.TIM_Prescaler=0;  //定时器分频
        TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
        TIM_TimeBaseStructure.TIM_Period=(100*4)-1;   //自动重装载值
        TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
        
        TIM_TimeBaseInit(TIM8,&TIM_TimeBaseStructure);

        TIM_EncoderInterfaceConfig(TIM8, TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);
        TIM_ICStructInit(&TIM8_ICInitStructure);
        TIM8_ICInitStructure.TIM_Channel = TIM_Channel_1;
        TIM8_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //映射到TI1上
        TIM8_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;         //配置输入分频,不分频
        TIM8_ICInitStructure.TIM_ICFilter = 0x6;
        TIM_ICInit(TIM8, &TIM8_ICInitStructure);
        TIM8_ICInitStructure.TIM_Channel = TIM_Channel_2;
        TIM8_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //映射到TI1上
        TIM8_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;         //配置输入分频,不分频
        TIM8_ICInitStructure.TIM_ICFilter = 0x6;
        TIM_ICInit(TIM8, &TIM8_ICInitStructure);        
        
        
        TIM_SetCounter(TIM8,0);        
  //TIM_ITConfig(TIM8,TIM_IT_Update|TIM_IT_CC1,ENABLE);//允许更新中断 ,允许CC1IE捕获中断        
TIM_ITConfig(TIM8,TIM_IT_Update,ENABLE);//允许更新中断 ,允许CC1IE捕获中断               
  TIM_Cmd(TIM8,ENABLE );         //使能定时器5
NVIC_EnableIRQ(TIM8_UP_TIM13_IRQn);
  direction = (TIM8->CR1 & TIM_CR1_DIR ? FORWARD : BACKWARD);
//      NVIC_InitStructure.NVIC_IRQChannel = TIM8_UP_TIM13_IRQn;
//        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;//抢占优先级3
//        NVIC_InitStructure.NVIC_IRQChannelSubPriority =0;                //子优先级3
//        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                        //IRQ通道使能
//        NVIC_Init(&NVIC_InitStructure);        //根据指定的参数初始化VIC寄存器、
        
        
}

//定时器8中断服务程序         
//void TIM8_CC_IRQHandler(void)

void TIM8_UP_TIM13_IRQHandler(void)
{                     

         
               
                if(TIM_GetITStatus(TIM8, TIM_IT_Update) != RESET)
                {        
                printf("TIM8_UP_TIM13_IRQHandler=%d ",TIM8->CNT );//               
                 
                direction = (TIM8->CR1 & TIM_CR1_DIR ? FORWARD : BACKWARD);
                }                                                                    
         
        //TIM_ClearITPendingBit(TIM8, TIM_IT_CC1|TIM_IT_Update); //清除中断标志位
        TIM_ClearITPendingBit(TIM8, TIM_IT_Update); //清除中断标志位
}
void TIM8_CC_IRQHandler(void)
{                     

         
                if(TIM_GetITStatus(TIM8, TIM_IT_CC1) != RESET)//溢出
                {            
                 printf("TIM8_CC1_IRQHandler=%d ",TIM8->CNT );//               
                 TIM_ClearITPendingBit(TIM8, TIM_IT_CC1); //清除中断标志位
                }
                if(TIM_GetITStatus(TIM8, TIM_IT_CC2) != RESET)//捕获1发生捕获事件
                {        
                printf("TIM8_CC2_IRQHandler=%d ",TIM8->CNT );//               
                TIM_ClearITPendingBit(TIM8, TIM_IT_CC2); //清除中断标志位
                }else                                                                  
                        {
                                
                        //        TIM_Cmd(TIM8,DISABLE );         //关闭定时器5
                         //        TIM_SetCounter(TIM8,0);
                                 
                        //        TIM_Cmd(TIM8,ENABLE );         //使能定时器5
                        }                    
                                                                           
         
        //TIM_ClearITPendingBit(TIM8, TIM_IT_CC1|TIM_IT_Update); //清除中断标志位
        
}

u16 direction=3;


        
int main(void)
{
        u16 count=0;//编码器计数
        
        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
        delay_init(168);  //初始化延时函数
        uart_init(115200);//初始化串口波特率为115200
        TIM8_Mode_Config();
         
           while(1)
        {


               count = TIM8->CNT/4;//获取计数值
                printf("count = %d ",count);
                printf("direction = %d ",direction);
        
                 delay_ms(300);
               
        }
}



程序中有两个中断函数void TIM8_CC_IRQHandler(void)和voidTIM8_UP_TIM13_IRQHandler(void)我程序中用到的是更新中断函数void TIM8_UP_TIM13_IRQHandler(void)我的手轮是100线的只要正传一圈反转一圈都会进这个函数。如果你要用voidTIM8_CC_IRQHandler(void)这个函数就必须TIM_ITConfig(TIM8, TIM_IT_CC1 |TIM_IT_CC2,ENABLE);NVIC_EnableIRQ(TIM8_CC_IRQn);这样只要你转动一步都会进这个中断
4。编码器的中断
    由于编码器是基于定时器的,所以编码器的中断实际上就是定时器的中断啦。也就是说定时器是每隔一定时间加一个数(或减一个数 ),当数到达预设值时就产生中断,而编码器是每一个有效脉冲就加一个数(或减一个数 ),当数到达预设值时就产生中断。若预设值为1000则编码器与定时器中断不同的是,当编码器反转时值到达999产生一次中断,而当编码器正转到达0时同样产生一次中断。在硬件上这两个中断是没法区分的,这也就造成了有种情况的误判。(后面再说)

5。STM32编码器没有考虑的情况
     想象一下,如果编码器的预设值为1000,当某次我们使得编码器正转产生中断后,立即反转则又该怎么办呢?根据上面的说法,这时候会产生两次一样的中断。如果在算法上没有处理的话,极有可能认为是行走了两次正向。但实际上并没有。所以这个时候必须结合方向来判断行走的情况(判断方向使用的是DIR寄存器位)或者在产生中断后读一次count寄存器位(看看是999还是0,以此来判断当前的方向)。只有上一次为正且这一次同样为正,距离才是相加的。
具体中断处理函数代码如下:
void TIM8_CC_IRQHandler(void)
{
    temp=(TIM_GetCounter(TIM4)&0xffff);   
if(TIM_GetITStatus(TIM8, 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(TIM8, TIM_IT_Update);
  }else{
        #ifdef DEBUG
         printf("ENCODE TIMER INTERRUP ERROR! ");
    #endif
        while(1)
        {
            ;
        }
  }

注意上面代码来自网络只是对STM32编码器没有考虑的情况
的补充







友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。
该问题目前已经被作者或者管理员关闭, 无法添加新回复
4条回答
taizonglai
1楼-- · 2019-07-20 16:43
 精彩回答 2  元偷偷看……
luwei2058
2楼-- · 2019-07-20 20:00
我将5v编码器输出引脚接到配置的单片机引脚上,用示波器测量只有不到2v,编码器输出端已加上上拉电阻,不能计数,是什么原因呢
taoking_opendev
3楼-- · 2019-07-20 20:31
MARK!
luwei2058
4楼-- · 2019-07-20 22:52
 精彩回答 2  元偷偷看……

一周热门 更多>