因为想申请 CSDN 博客认证需要一定的粉丝量,而我写了五年博客才 700 多粉丝,本文开启关注才可阅读全文,很抱歉影响您的阅读体验
stm32f4编码器模式
花费一下午时间研究编码器的使用,简单总结
- E6B2-CWZ1X编码器
- stm32f407 定时器编码器模式
文章目录
一、编码器简介
1、分类
编码器可按以下方式来分类。
(1)增量型:
每转过单位的角度就发出一个脉冲信号,通常为A相、B相(某些包括Z相)输出。A相、B相为相互延迟1/4周期的脉冲输出(即正交信号),根据延迟关系可以区别正反转,而且通过取A相、B相的上升和下降沿可以进行2或4倍频。Z相为单圈脉冲,即每圈发出一个脉冲,常用于校正累计误差。
(2)绝对值型:
对应一圈,每个基准的角度发出一个唯一与该角度对应二进制的数值,通过外部记圈器件可以进行多个位置的记录和测量。
2、编码器中的线、位、分辨率
请参考:链接
3、编码器原理
前文提到编码器通过发送正交脉冲信号表示角度信息,如图为一个示例。(其中TI1和TI2分别对应编码器输出A、B项)
脉冲信号特性 | 表示信息 |
---|---|
两项先后关系 | 旋转方向 |
脉冲个数 | 转过角度 |
注意到A、B项信号都可能出现毛刺,需要算法修正毛刺
二、stm32f4编码器模式
1、简介
我们可以利用外部中断分别捕获A、B项边沿,手写逻辑消除毛刺并解析编码器数据,但这是比较复杂的。其实这里的脉冲输入是一种特殊的输入捕获情况,因此stm32专门在定时器中提供了编码器模式,可大大简化解析过程。
(1)、stm32f407中定时器1、2、3、4、5、8提供编码器接口模式
(2)、可以对输入信号TI1,TI2进行滤波处理,数字滤波器由事件器组成,每N个事件才视为一个有效边沿,可以在TIMx_CCMR1、TIMx_CCMR2中的IC1F位域设置
(3)、stm32提供了单项计数(只能测速度)和双项计数模式(可测速度&方向),双项模式可以更好地消除毛刺干扰,一般使用双项模式,具体见下图
下图为双项模式下计数效果,可见在A、B中仅一项有毛刺时,计数值加减后保持不变,实现了抖动补偿
(4)、 编码器A、B相输入的信号TI1、TI2经滤波和反相后成为TI1FP1 或 TI2FP2 ,定时器的时钟由他们上的每次有效信号转换提供,也就是说最终计数值即反映转过角度。
(5)、 TI1FP1 或 TI2FP2反相可以改变计数方向,如下图:
(6)、定时器配置为编码器接口模式时,会提供传感器当前位置的相关信息。使用另一个配置为捕获模式的定时器测量两个编码器事件之间的周期,可获得动态信息(速度、加速度和减速度)。
(7)、计数溢出后,定时器会装载“重装载值”,并清零重新计数,此值可设置为编码器旋转一周的脉冲个数,这样既可利用溢出中断次数判断转了几圈。但若只要求旋转角度,此值可以任意。任意时刻角度为:溢出中断次数*重装载值+当前计数值
(8)、TIMx_CR1寄存器的 DIR 位指示当前旋转方向
2、示例代码
(1)定时器初始化设置
void TIM3_Int_Init()
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
//定时器设置-------------------------------------------------------------
TIM_TimeBaseInitStructure.TIM_Period = 3600; //重装载值
TIM_TimeBaseInitStructure.TIM_Prescaler=0x0; //预分频
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1; //时钟分割
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);//初始化TIM3
//编码器模式设置--------------------------------------------------------------
TIM_EncoderInterfaceConfig(TIM3,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);//计数模式3
TIM_ICStructInit(&TIM_ICInitStructure);
TIM_ICInitStructure.TIM_ICFilter = 10;//滤波器值
TIM_ICInit(TIM3, &TIM_ICInitStructure);
//溢出中断设置--------------------------------------------------------------
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE); //允许TIM3溢出中断
NVIC_InitStructure.NVIC_IRQChannel=TIM3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x01;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x01;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_InitStructure);
//Reset counter-----------------------------------------------
TIM_SetCounter(TIM3,0); //TIM3->CNT=0
TIM_Cmd(TIM3, ENABLE);
}
(2)在中断服务函数中进行圈数计算
int circle_count=0;//全局变量-圈数
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET)
{
if((TIM3->CR1>>4 & 0x01)==0) //DIR==0
circle_count++;
else if((TIM3->CR1>>4 & 0x01)==1)//DIR==1
circle_count--;
}
TIM_ClearITPendingBit(TIM3,TIM_IT_Update);
}
(3)获取当前角度值
int Encoder=0;
extern int circle_count;
Encoder=TIM_GetCounter(TIM3)+3600*circle_count;//当前角度
- 上述代码在stm32f407平台测试通过
- 使用另一个配置为捕获模式的定时器测量两个编码器事件之间的周期,可获得动态信息(速度、加速度和减速度)。
三、update 2023/7/7
- 有网友想要完整代码,我从19年的上古工程中找了找,这是我们当初 robomaster 比赛时摩擦轮测速的代码,时间太久了我也看不太懂,请大家自行参考
-
编码器初始化
//摩擦轮编码器(右) void TIM2_Encoder_init(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_ICInitTypeDef TIM_ICInitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); GPIO_PinAFConfig(GPIOA,GPIO_PinSource0,GPIO_AF_TIM2); GPIO_PinAFConfig(GPIOA,GPIO_PinSource1,GPIO_AF_TIM2); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ; GPIO_Init(GPIOA,&GPIO_InitStructure); TIM_TimeBaseStructure.TIM_Period = 60000; TIM_TimeBaseStructure.TIM_Prescaler = 0; TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); TIM_EncoderInterfaceConfig(TIM2, TIM_EncoderMode_TI1, TIM_ICPolarity_Falling, TIM_ICPolarity_Falling); TIM_ICStructInit(&TIM_ICInitStructure); TIM_ICInitStructure.TIM_ICFilter = 10; TIM_ICInit(TIM2, &TIM_ICInitStructure); TIM_ClearFlag(TIM2, TIM_FLAG_Update); TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); TIM2->CNT = 30000; TIM_Cmd(TIM2, ENABLE); } //摩擦轮编码器(左) void TIM8_Encoder_init(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_ICInitTypeDef TIM_ICInitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM8,ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOI,ENABLE); GPIO_PinAFConfig(GPIOI,GPIO_PinSource5,GPIO_AF_TIM8); GPIO_PinAFConfig(GPIOI,GPIO_PinSource6,GPIO_AF_TIM8); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_6; //GPIOB0,GPIOB1 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ; GPIO_Init(GPIOI,&GPIO_InitStructure); TIM_TimeBaseStructure.TIM_Period = 60000; TIM_TimeBaseStructure.TIM_Prescaler = 0; TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM8, &TIM_TimeBaseStructure); TIM_EncoderInterfaceConfig(TIM8, TIM_EncoderMode_TI1, TIM_ICPolarity_Falling, TIM_ICPolarity_Falling); TIM_ICStructInit(&TIM_ICInitStructure); TIM_ICInitStructure.TIM_ICFilter = 10; TIM_ICInit(TIM8, &TIM_ICInitStructure); TIM_ClearFlag(TIM8, TIM_FLAG_Update); TIM_ITConfig(TIM8, TIM_IT_Update, ENABLE); TIM8->CNT = 30000; TIM_Cmd(TIM8, ENABLE); } //编码器采集定时器 void TIM6_Init(u16 prd,u16 psc) { TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6,ENABLE); TIM_TimeBaseInitStructure.TIM_Period = prd; //自动重装载值 TIM_TimeBaseInitStructure.TIM_Prescaler=psc; //定时器分频 TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式 TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1; TIM_TimeBaseInit(TIM6,&TIM_TimeBaseInitStructure); NVIC_InitStructure.NVIC_IRQChannel=TIM6_DAC_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x03; //抢占优先级1 NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x03; //子优先级3 NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE; NVIC_Init(&NVIC_InitStructure); TIM_ITConfig(TIM6,TIM_IT_Update,ENABLE); TIM_Cmd(TIM6,ENABLE); }
-
中断服务函数
//编码器信息 //编号0对应左边的电机,1对应右边电机 //逆时针转计数增加 uint16_t encoder_data[2]; uint16_t encoder_data_last[2]={30000,30000}; int16_t fric_spd[2]; int16_t spd_last[2]={0,0}; //编码器信息采集 void TIM6_DAC_IRQHandler(void) { // static int i=0; if(TIM_GetITStatus(TIM6,TIM_IT_Update)==SET) //溢出中断 { // if(i==100)//0.1s // ANO_Send_UserData(RC_CtrlData.mouse.x,0,0,0); // else if(i==200) // ANO_Send_UserData(RC_CtrlData.mouse.x,1,0,0),i=0; // i++; //LED1=!LED1;//DS1翻转 encoder_data[0]=TIM8->CNT; encoder_data[1]=TIM2->CNT; for(int i=0;i<2;i++) { fric_spd[i]=encoder_data[i]-encoder_data_last[i]; //从0到60000的跳变 if(fric_spd[i]>30000) { fric_spd[i]-=60000; } //从60000到0的跳变 else if(fric_spd[i]<-30000) { fric_spd[i]+=60000; } if(fric_spd[i]>700||fric_spd[i]<-700) fric_spd[i]=spd_last[i]; else spd_last[i]=fric_spd[i]; encoder_data_last[i]=encoder_data[i]; } ClientFrame.boolSet[0]=fabs(fric_spd[0])>70 ? 1:0; ClientFrame.boolSet[1]=fabs(fric_spd[1])>70 ? 1:0; if(ClientFrame.boolSet[0] && ClientFrame.boolSet[1]) shooterEnable=1; else shooterEnable=0; } TIM_ClearITPendingBit(TIM6,TIM_IT_Update); //清除中断标志位 }
-