[STM32F4] stm32f4编码器模式

[复制链接]
2067|20
 楼主| kzlzqi 发表于 2023-8-26 16:43 | 显示全部楼层 |阅读模式
一、编码器简介
1、分类
编码器可按以下方式来分类。

(1)增量型:
每转过单位的角度就发出一个脉冲信号,通常为A相、B相(某些包括Z相)输出。A相、B相为相互延迟1/4周期的脉冲输出(即正交信号),根据延迟关系可以区别正反转,而且通过取A相、B相的上升和下降沿可以进行2或4倍频。Z相为单圈脉冲,即每圈发出一个脉冲,常用于校正累计误差。

(2)绝对值型:
对应一圈,每个基准的角度发出一个唯一与该角度对应二进制的数值,通过外部记圈器件可以进行多个位置的记录和测量。

评论

———————————————— 版权声明:本文为CSDN博主「云端FFF」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/wxc971231/article/details/82322957  发表于 2023-8-26 16:43
 楼主| kzlzqi 发表于 2023-8-26 16:44 | 显示全部楼层
3、编码器原理
4332964e9bb45b24ba.png
前文提到编码器通过发送正交脉冲信号表示角度信息,如图为一个示例。(其中TI1和TI2分别对应编码器输出A、B项)
8407864e9bb55301e1.png
 楼主| kzlzqi 发表于 2023-8-26 16:44 | 显示全部楼层
二、stm32f4编码器模式
1、简介
我们可以利用外部中断分别捕获A、B项边沿,手写逻辑消除毛刺并解析编码器数据,但这是比较复杂的。其实这里的脉冲输入是一种特殊的输入捕获情况,因此stm32专门在定时器中提供了编码器模式,可大大简化解析过程。

(1)、stm32f407中定时器1、2、3、4、5、8提供编码器接口模式
(2)、可以对输入信号TI1,TI2进行滤波处理,数字滤波器由事件器组成,每N个事件才视为一个有效边沿,可以在TIMx_CCMR1、TIMx_CCMR2中的IC1F位域设置
(3)、stm32提供了单项计数(只能测速度)和双项计数模式(可测速度&方向),双项模式可以更好地消除毛刺干扰
 楼主| kzlzqi 发表于 2023-8-26 16:44 | 显示全部楼层
一般使用双项模式,具体见下图
3176964e9bb7673dd3.png
 楼主| kzlzqi 发表于 2023-8-26 16:44 | 显示全部楼层
下图为双项模式下计数效果,可见在A、B中仅一项有毛刺时,计数值加减后保持不变,实现了抖动补偿

1588564e9bb89bf6c0.png
 楼主| kzlzqi 发表于 2023-8-26 16:45 | 显示全部楼层
(4)、 编码器A、B相输入的信号TI1、TI2经滤波和反相后成为TI1FP1 或 TI2FP2 ,定时器的时钟由他们上的每次有效信号转换提供,也就是说最终计数值即反映转过角度。
 楼主| kzlzqi 发表于 2023-8-26 16:45 | 显示全部楼层
(5)、 TI1FP1 或 TI2FP2反相可以改变计数方向,如下图:
5921164e9bb9fd2427.png
 楼主| kzlzqi 发表于 2023-8-26 16:45 | 显示全部楼层
(6)、定时器配置为编码器接口模式时,会提供传感器当前位置的相关信息。使用另一个配置为捕获模式的定时器测量两个编码器事件之间的周期,可获得动态信息(速度、加速度和减速度)。
 楼主| kzlzqi 发表于 2023-8-26 16:45 | 显示全部楼层
(7)、计数溢出后,定时器会装载“重装载值”,并清零重新计数,此值可设置为编码器旋转一周的脉冲个数,这样既可利用溢出中断次数判断转了几圈。但若只要求旋转角度,此值可以任意。任意时刻角度为:溢出中断次数*重装载值+当前计数值
 楼主| kzlzqi 发表于 2023-8-26 16:45 | 显示全部楼层
(8)、TIMx_CR1寄存器的 DIR 位指示当前旋转方向
 楼主| kzlzqi 发表于 2023-8-26 16:45 | 显示全部楼层
2、示例代码
(1)定时器初始化设置
 楼主| kzlzqi 发表于 2023-8-26 16:45 | 显示全部楼层
  1. void TIM3_Int_Init()
  2. {
  3.         TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
  4.         TIM_ICInitTypeDef TIM_ICInitStructure;
  5.         NVIC_InitTypeDef NVIC_InitStructure;
  6.        
  7.         RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);

  8. //定时器设置-------------------------------------------------------------       
  9.   TIM_TimeBaseInitStructure.TIM_Period = 3600;         //重装载值
  10.         TIM_TimeBaseInitStructure.TIM_Prescaler=0x0;  //预分频
  11.         TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数
  12.         TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1; //时钟分割
  13.        
  14.         TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);//初始化TIM3

  15. //编码器模式设置--------------------------------------------------------------                                         

  16.         TIM_EncoderInterfaceConfig(TIM3,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);//计数模式3
  17.        
  18.         TIM_ICStructInit(&TIM_ICInitStructure);
  19.     TIM_ICInitStructure.TIM_ICFilter = 10;//滤波器值
  20.     TIM_ICInit(TIM3, &TIM_ICInitStructure);
  21. //溢出中断设置--------------------------------------------------------------       
  22.         TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE); //允许TIM3溢出中断

  23.         NVIC_InitStructure.NVIC_IRQChannel=TIM3_IRQn;
  24.         NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x01;
  25.         NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x01;
  26.         NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
  27.         NVIC_Init(&NVIC_InitStructure);
  28.   
  29. //Reset counter-----------------------------------------------
  30.   TIM_SetCounter(TIM3,0); //TIM3->CNT=0
  31.   TIM_Cmd(TIM3, ENABLE);
  32. }

 楼主| kzlzqi 发表于 2023-8-26 16:46 | 显示全部楼层
(2)在中断服务函数中进行圈数计算
  1. int circle_count=0;//全局变量-圈数
  2. void TIM3_IRQHandler(void)
  3. {
  4.         if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET)
  5.         {               
  6.                 if((TIM3->CR1>>4 & 0x01)==0) //DIR==0
  7.                         circle_count++;
  8.                 else if((TIM3->CR1>>4 & 0x01)==1)//DIR==1
  9.                         circle_count--;
  10.         }
  11.         TIM_ClearITPendingBit(TIM3,TIM_IT_Update);
  12. }

 楼主| kzlzqi 发表于 2023-8-26 16:46 | 显示全部楼层
(3)获取当前角度值
  1. int Encoder=0;
  2. extern int circle_count;

  3. Encoder=TIM_GetCounter(TIM3)+3600*circle_count;//当前角度
 楼主| kzlzqi 发表于 2023-8-26 16:47 | 显示全部楼层
上述代码在stm32f407平台测试通过
使用另一个配置为捕获模式的定时器测量两个编码器事件之间的周期,可获得动态信息(速度、加速度和减速度)。
 楼主| kzlzqi 发表于 2023-8-26 16:47 | 显示全部楼层
三、update 2023/7/7
有网友想要完整代码,我从19年的上古工程中找了找,这是我们当初 robomaster 比赛时摩擦轮测速的代码,时间太久了我也看不太懂,请大家自行参考
 楼主| kzlzqi 发表于 2023-8-26 16:47 | 显示全部楼层
编码器初始化
  1. //摩擦轮编码器(右)
  2. void TIM2_Encoder_init(void)
  3. {
  4.         GPIO_InitTypeDef         GPIO_InitStructure;
  5.     TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
  6.     TIM_ICInitTypeDef        TIM_ICInitStructure;

  7.     RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
  8.     RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);
  9.   
  10.     GPIO_PinAFConfig(GPIOA,GPIO_PinSource0,GPIO_AF_TIM2);
  11.     GPIO_PinAFConfig(GPIOA,GPIO_PinSource1,GPIO_AF_TIM2);

  12.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1;
  13.     GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF;
  14.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
  15.     GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  16.     GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP ;
  17.     GPIO_Init(GPIOA,&GPIO_InitStructure);

  18.     TIM_TimeBaseStructure.TIM_Period = 60000;
  19.     TIM_TimeBaseStructure.TIM_Prescaler = 0;
  20.     TIM_TimeBaseStructure.TIM_ClockDivision = 0;
  21.     TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  
  22.     TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);

  23.     TIM_EncoderInterfaceConfig(TIM2,
  24.                                                                 TIM_EncoderMode_TI1,
  25.                                                                 TIM_ICPolarity_Falling,
  26.                                                                 TIM_ICPolarity_Falling);
  27.     TIM_ICStructInit(&TIM_ICInitStructure);
  28.     TIM_ICInitStructure.TIM_ICFilter = 10;
  29.     TIM_ICInit(TIM2, &TIM_ICInitStructure);
  30.     TIM_ClearFlag(TIM2, TIM_FLAG_Update);
  31.     TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
  32.     TIM2->CNT = 30000;
  33.     TIM_Cmd(TIM2, ENABLE);
  34. }


  35. //摩擦轮编码器(左)
  36. void TIM8_Encoder_init(void)
  37. {
  38.         GPIO_InitTypeDef         GPIO_InitStructure;
  39.     TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
  40.     TIM_ICInitTypeDef        TIM_ICInitStructure;

  41.     RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM8,ENABLE);
  42.     RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOI,ENABLE);
  43.   
  44.     GPIO_PinAFConfig(GPIOI,GPIO_PinSource5,GPIO_AF_TIM8);
  45.     GPIO_PinAFConfig(GPIOI,GPIO_PinSource6,GPIO_AF_TIM8);

  46.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_6; //GPIOB0,GPIOB1
  47.     GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF;
  48.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
  49.     GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  50.     GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP ;
  51.     GPIO_Init(GPIOI,&GPIO_InitStructure);

  52.     TIM_TimeBaseStructure.TIM_Period = 60000;
  53.     TIM_TimeBaseStructure.TIM_Prescaler = 0;
  54.     TIM_TimeBaseStructure.TIM_ClockDivision = 0;
  55.     TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  
  56.     TIM_TimeBaseInit(TIM8, &TIM_TimeBaseStructure);

  57.     TIM_EncoderInterfaceConfig(TIM8,
  58.                                                                 TIM_EncoderMode_TI1,
  59.                                                                 TIM_ICPolarity_Falling,
  60.                                                                 TIM_ICPolarity_Falling);
  61.     TIM_ICStructInit(&TIM_ICInitStructure);
  62.     TIM_ICInitStructure.TIM_ICFilter = 10;
  63.     TIM_ICInit(TIM8, &TIM_ICInitStructure);
  64.     TIM_ClearFlag(TIM8, TIM_FLAG_Update);
  65.     TIM_ITConfig(TIM8, TIM_IT_Update, ENABLE);
  66.     TIM8->CNT = 30000;
  67.     TIM_Cmd(TIM8, ENABLE);
  68. }

  69. //编码器采集定时器
  70. void TIM6_Init(u16 prd,u16 psc)
  71. {
  72.         TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
  73.         NVIC_InitTypeDef NVIC_InitStructure;
  74.         RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6,ENABLE);  
  75.        
  76.           TIM_TimeBaseInitStructure.TIM_Period = prd;                 //自动重装载值
  77.         TIM_TimeBaseInitStructure.TIM_Prescaler=psc;                  //定时器分频
  78.         TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
  79.         TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
  80.        
  81.         TIM_TimeBaseInit(TIM6,&TIM_TimeBaseInitStructure);
  82.        
  83.         NVIC_InitStructure.NVIC_IRQChannel=TIM6_DAC_IRQn;
  84.         NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x03; //抢占优先级1
  85.         NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x03; //子优先级3
  86.         NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
  87.         NVIC_Init(&NVIC_InitStructure);
  88.        
  89.         TIM_ITConfig(TIM6,TIM_IT_Update,ENABLE);
  90.         TIM_Cmd(TIM6,ENABLE);
  91. }
 楼主| kzlzqi 发表于 2023-8-26 16:48 | 显示全部楼层
中断服务函数
  1. //编码器信息
  2. //编号0对应左边的电机,1对应右边电机
  3. //逆时针转计数增加
  4. uint16_t encoder_data[2];
  5. uint16_t encoder_data_last[2]={30000,30000};
  6. int16_t fric_spd[2];
  7. int16_t spd_last[2]={0,0};

  8. //编码器信息采集
  9. void TIM6_DAC_IRQHandler(void)
  10. {
  11. //        static int i=0;
  12.         if(TIM_GetITStatus(TIM6,TIM_IT_Update)==SET) //溢出中断
  13.         {
  14. //                if(i==100)//0.1s
  15. //                        ANO_Send_UserData(RC_CtrlData.mouse.x,0,0,0);
  16. //                else if(i==200)
  17. //                        ANO_Send_UserData(RC_CtrlData.mouse.x,1,0,0),i=0;
  18. //                i++;
  19.                
  20.                 //LED1=!LED1;//DS1翻转
  21.                 encoder_data[0]=TIM8->CNT;
  22.                 encoder_data[1]=TIM2->CNT;
  23.                
  24.                 for(int i=0;i<2;i++)
  25.                 {
  26.                         fric_spd[i]=encoder_data[i]-encoder_data_last[i];
  27.                
  28.                         //从0到60000的跳变
  29.                         if(fric_spd[i]>30000)
  30.                         {
  31.                                 fric_spd[i]-=60000;
  32.                         }
  33.                         //从60000到0的跳变
  34.                         else if(fric_spd[i]<-30000)
  35.                         {
  36.                                 fric_spd[i]+=60000;
  37.                         }
  38.                         if(fric_spd[i]>700||fric_spd[i]<-700)
  39.                                 fric_spd[i]=spd_last[i];
  40.                         else
  41.                                 spd_last[i]=fric_spd[i];
  42.                         encoder_data_last[i]=encoder_data[i];
  43.                 }
  44.                
  45.                 ClientFrame.boolSet[0]=fabs(fric_spd[0])>70 ? 1:0;
  46.                 ClientFrame.boolSet[1]=fabs(fric_spd[1])>70 ? 1:0;
  47.                
  48.                 if(ClientFrame.boolSet[0] && ClientFrame.boolSet[1])
  49.                         shooterEnable=1;
  50.                 else
  51.                         shooterEnable=0;
  52.         }
  53.         TIM_ClearITPendingBit(TIM6,TIM_IT_Update);  //清除中断标志位
  54. }

morrisk 发表于 2023-8-27 11:44 | 显示全部楼层
通过外部记圈器件可以进行多个位置的记录和测量
Bowclad 发表于 2024-5-12 23:21 | 显示全部楼层
编码器能判断正反转吗
您需要登录后才可以回帖 登录 | 注册

本版积分规则

127

主题

996

帖子

2

粉丝
快速回复 在线客服 返回列表 返回顶部