0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

STM32F103C8T6使用外部中断法和输入捕获法进行编码器测速

CHANBAEK 来源:月月望归鸟 作者:K.Fire 2023-06-15 16:52 次阅读

01 前言

如果我们想对电机进行速度或者转角的精确控制,需要使用到很多算法,比如非常经典的PID控制算法,或者一些只能算法,但这些算法都需要传感器来提供转速或转角的反馈值,对于电机来说,编码器是非常流行并且实用的电机配套传感器,本文使用STM32F103C8T6+L298N+MG513P30电机进行直流电机的编码器测速。

02 编码器原理

1.分类

图片

光电式编码器的精准度比霍尔式要高,但是由于它需要红外线发生器和接收器,相对来说造价要贵一些。现在我们比较常用的是霍尔式增量编码器,有很多电机都会自带编码器。

image.png

2.测速方法分类

(1)M法测速

编码器输出的脉冲个数代表了位置,那么单位时间里的脉冲个数表示这段时间里的平均速度。因此,我们可以通过计量单位时间脉冲个数即可以估算出平均速度,称为M法测速(测脉冲个数)测速原理如图所示。

图片

例如,若编码器每转产生N个脉冲,在T时间(单位s)产生m个脉冲,那么平均转速如下式所示:

图片

式中 n——平均转速(r/min);

T——测速采样时间(s);

m——T时间内测得的编码器脉冲个数;

N——编码器每转脉冲数。

(2)T法测速

若用M法测速,在记录时间短、速度低的时候,只能记录几个脉冲,则分辨率降低。针对该问题,目前解决方法为:可以采用输出码盘脉冲为一个时间间隔,然后用计数器记录在这段时间里高速脉冲源发出的脉冲数。即通过采集到脉冲源脉冲数来计量编码器两个脉冲时间间隔,从而估算出速度,称为T法测速(测脉冲周期),测速原理图如图所示。

图片

T法测速,利用编码器产生的脉冲用作门电路的触发信号;用已知频率f的时钟信号做输入。若控制门电路在编码器脉冲上升沿到来时开始导通,再次上升沿到来时关闭,即计数器只记录一个编码器脉冲周期内的时钟脉冲个数。若在编码器相邻脉冲之间记录的脉冲时钟个数为m,那么,可以计算两个编码器脉冲的时间间隔为m /f;若编码器每转有N个线脉冲输出,那么我们就知道编码器转过1/N转时需要时间m /f。据此,可计算与编码器同轴转速为公式所示。

图片

式中 n ——平均转速(r/min);

f ——时钟脉冲频率(个/s);

m ——两个编码器脉冲之间的时钟脉冲个数;

N ——编码器每转脉冲数。

编码器一般会输出两路信号,分别称为A相和B相,它们相差90°,因此编码器也称为十字码盘,通过捕获两路输出信号可以测算出电机的转速和转向。

STM32使用编码器的方法有两种分别是外部中断法和输入捕获法,这两种方法都属于M法测速,两种方法比较来说外部中断法占用CPU资源较多,平时比较常用的是输入捕获法,但博主两种方法都调试出来了,因此记录下来跟大家分享一下。

03 外部中断法测速

对于外部中断的知识,各个讲STM32的教程都有,我就不过多赘述,外部中断的初始化都一样,主要是出发外部中断时需要进行的操作。

图片

看这张图,正转方向是信号向右走,因为我们是同时捕获两路信号,有以下几种情况:

图片

然后我们设置两个变量用来存储捕获的脉冲数,每捕获到一次脉冲信号根据上表进行判断,正转时使其加1;反转时使其减1;然后配置一个定时器,每隔一段时间反馈一次测速值。

1.外部中断配置

先编写一个函数初始化外部中断,使用PB12-15引脚复用为外部中断输入,外部中断配置步骤如下:

1.端口初始化:RCC_APB2PeriphClockCmd()、GPIO_Init()

2.使能复用功能时钟:RCC_APB2PeriphClockCmd()

3.设置IO口与中断线的映射关系:GPIO_EXTILineConfig()

4.初始化线上中断:EXTI_Init()

5.配置中断分组:NVIC_Init()

完整函数如下:

/**************************************************************************
功能:应用外部中断方式采集编码器数据,使用M法测速反馈车轮实时速度
函数:Encoder_EXTIX_Init(void)  EXTI15_10_IRQHandler(void)
作者:K.Fire
日期:2022.01.30
引脚:PB12(左轮A相) PB13(左轮B相) PB14(右轮A相) PB15(右轮B相)
参数:void
*************************************************************************/


int Encoder_L_EXTI=0;
int Encoder_R_EXTI=0;


void Encoder_EXTIX_Init(void)
{
  //1.端口初始化
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
  GPIO_InitTypeDef GPIO_InitStruct;
  GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPD;
  GPIO_InitStruct.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13 |GPIO_Pin_14 | GPIO_Pin_15;


  GPIO_Init(GPIOB,&GPIO_InitStruct);


  //2.使能复用功能时钟
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);


  //3.设置IO口与中断线的映射关系
  GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource12);
  GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource13);
  GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14);
  GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource15);


  //4.初始化线上中断
  EXTI_InitTypeDef EXTI_InitStruct;
  EXTI_InitStruct.EXTI_Line = EXTI_Line12 | EXTI_Line13 | EXTI_Line14 | EXTI_Line15;
  EXTI_InitStruct.EXTI_LineCmd = ENABLE;
  EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
  EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising_Falling;//跳变沿触发
  EXTI_Init(&EXTI_InitStruct);


  //5.配置中断分组
  NVIC_InitTypeDef NVIC_InitStruct;
  NVIC_InitStruct.NVIC_IRQChannel = EXTI15_10_IRQn;
  NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
  NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
  NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
  NVIC_Init(&NVIC_InitStruct);
}

2.外部中断服务函数

函数的判断逻辑与上表一致,外部中断捕获判断函数如下:

void EXTI15_10_IRQHandler(void)
{
  if(EXTI_GetITStatus(EXTI_Line12) != RESET)//左轮A相 PB12
  {
    EXTI_ClearITPendingBit(EXTI_Line12);  //清除LINE上的中断标志位
    if(PBin(12)==0)   //这里判断检测到的是否是下降沿
    {
      if(PBin(13)==0)   Encoder_L_EXTI++;//B相的电平如果是低,电机就是正转加1
      else             Encoder_L_EXTI--;//否则就是反转减1
    }
    else                  //上升沿
    { 
      if(PBin(13)==1)  Encoder_L_EXTI++; //B相电平如果为高,电机就是正转加1
      else             Encoder_L_EXTI--;//否则就是反转减1
    }
  }


  if(EXTI_GetITStatus(EXTI_Line13) != RESET)//左轮B相 PB13
  {
    EXTI_ClearITPendingBit(EXTI_Line13);  //清除LINE上的中断标志位
    if(PBin(13)==0)   //这里判断检测到的是否是下降沿
    {
      if(PBin(12)==1)   Encoder_L_EXTI++;//B相的电平如果是高,电机就是正转加1
      else             Encoder_L_EXTI--;//否则就是反转减1
    }
    else                  //上升沿
    { 
      if(PBin(12)==0)  Encoder_L_EXTI++; //B相电平如果为高,电机就是正转加1
      else             Encoder_L_EXTI--;//否则就是反转减1
    }
  }


  if(EXTI_GetITStatus(EXTI_Line14) != RESET)//右轮A相 PB14
  {
    EXTI_ClearITPendingBit(EXTI_Line14);  //清除LINE上的中断标志位
    if(PBin(14)==0)   //这里判断检测到的是否是下降沿
    {
      if(PBin(15)==0)   Encoder_R_EXTI++;//B相的电平如果是低,电机就是正转加1
      else             Encoder_R_EXTI--;//否则就是反转减1
    }
    else                  //上升沿
    { 
      if(PBin(15)==1)  Encoder_R_EXTI++; //B相电平如果为高,电机就是正转加1
      else             Encoder_R_EXTI--;//否则就是反转减1
    }
  }


  if(EXTI_GetITStatus(EXTI_Line15) != RESET)//右轮B相 PB15
  {
    EXTI_ClearITPendingBit(EXTI_Line15);  //清除LINE上的中断标志位
    if(PBin(15)==0)   //这里判断检测到的是否是下降沿
    {
      if(PBin(14)==1)   Encoder_R_EXTI++;//A相的电平如果是高,电机就是正转加1
      else             Encoder_R_EXTI--;//否则就是反转减1
    }
    else                  //上升沿
    { 
      if(PBin(14)==0)  Encoder_R_EXTI++; //A相电平如果为低,电机就是正转加1
      else             Encoder_R_EXTI--;//否则就是反转减1
    }
  }


}

注意一下,在使用引脚时不要重复,因为Px0(x=AB..F)都是使用的同一条外部中断线(EXIT0)

3.读取捕获脉冲数值

配置完定时器后,每过一段时间调用读取函数,通过公式可以计算出实际的脉冲值。

函数如下:

/**************************************************************************
功能:获取不同方式下的脉冲值
函数:int Read_Encoder(u8 TIMX)
作者:K.Fire
日期:2022.01.30
参数:1:外部中断法左轮 2:外部中断法右轮 3:输入捕获法左轮 4:输入捕获法右轮
**************************************************************************/

int Read_Encoder(u8 TIMX)
{
    int Encoder_TIM;    
    switch(TIMX)
  {
    case 1:Encoder_TIM=Encoder_L_EXTI;  Encoder_L_EXTI=0; break;
    case 2:Encoder_TIM=Encoder_R_EXTI;  Encoder_R_EXTI=0; break;
    case 3:Encoder_TIM=TIM3 - > CNT;  TIM3 - > CNT=0;break;  
    case 4:Encoder_TIM=TIM4 - > CNT;  TIM4 - > CNT=0;break;
      default:Encoder_TIM=0;break;
  }
    return Encoder_TIM;
}

/**************************************************************************
功能:获取并打印输出实际速度值
函数:void Get_SpeedNow(float* CurrentVelcity_L,float* CurrentVelcity_R)
作者:K.Fire
日期:2022.01.30
参数:CurrentVelcity_L:左轮实时速度(地址)  CurrentVelcity_R:右轮实时速度(地址)
**************************************************************************/

extern int Current_LN,Current_RN;//速度脉冲数

void Get_SpeedNow(int* CurrentVelcity_L,int* CurrentVelcity_R)
{

  /*外部中断方式*/
  Current_LN = Read_Encoder(1);
  Current_RN = Read_Encoder(2);

  *CurrentVelcity_L = Current_LN * MPN * 100;//计算实际速度值
  *CurrentVelcity_R = Current_RN * MPN * 100;

  printf("The Current Left Wheel Velocity is: %d mm/s.\\r\\n",*CurrentVelcity_L);
  printf("The Current Right Wheel Velocity is: %d mm/s.\\r\\n",*CurrentVelcity_R);
}

4.测试结果

通过USART1串口向电脑输出实时速度值,结果如下:

图片

04 输入捕获发测速

输入捕获是将TIM定时器的CHx通道配置为输入捕获模式,每捕获到一个信号会将响应定时器的CNT计数器的值加/减1,然后每隔一段时间提取并清空计数器的值就可以测算出电机的实时转速。

1.输入捕获模式配置

输入捕获模式的初始化,各个STM32的教学视频都有,只是在配置编码器模式时,需要用到一个编码器模式的配置函数:TIM_EncoderInterfaceConfig()

输入捕获模式的初始化步骤如下:

1.使能定时器和通道对应的时钟:RCC_APB1PeriphClockCmd()

2.初始化IO口:GPIO_Init()

3.初始化定时器:TIM_TimeBaseInit()

4.配置编码器模式:TIM_EncoderInterfaceConfig()

5.初始化输入捕获通道:TIM_ICInit()

6.开启更新中断:TIM_ITConfig()

7.使能定时器:TIM_Cmd()

完整的代码如下:

/**************************************************************************
功能:应用输入捕获方式采集编码器数据,使用M法测速反馈车轮实时速度
函数:void Encoder_CAP_Init()
作者:K.Fire
日期:2022.01.30
引脚:PA6(左轮A相) PA6(左轮B相) PB6(右轮A相) PB7(右轮B相)
参数:无
**************************************************************************/

void Encoder_CAP_Init_L()
{
  /*1.使能定时器和通道对应的时钟*/
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);

  /*2.初始化IO口*/
  GPIO_InitTypeDef GPIO_InitStruct;
  GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPD;//推挽下拉输入
  GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
  GPIO_Init(GPIOA,&GPIO_InitStruct);

  /*3.初始化定时器*/
  TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
  TIM_TimeBaseInitStruct.TIM_ClockDivision = 0;
  TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
  TIM_TimeBaseInitStruct.TIM_Period = 0xffff;
  TIM_TimeBaseInitStruct.TIM_Prescaler = 0x0;
  TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct);

  //配置编码器模式
  TIM_EncoderInterfaceConfig(TIM3,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);

  /*4.初始化输入捕获通道*/
  TIM_ICInitTypeDef TIM_ICInitStruct;
  TIM_ICStructInit(&TIM_ICInitStruct);
  TIM_ICInitStruct.TIM_ICFilter = 10;//设置滤波器,重复检测10次,防止抖动,增加精度
  TIM_ICInit(TIM3,&TIM_ICInitStruct);

  TIM_ClearFlag(TIM3, TIM_FLAG_Update);//清除TIM的更新标志位

  /*5.开启更新中断*/
  TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);

  TIM_SetCounter(TIM3,0);//计数器置零
  TIM3- >CNT = 0x7fff;

//  /*4.初始化输入捕获通道*/
//  TIM_ICInitTypeDef TIM_ICInitStruct;
//  TIM_ICInitStruct.TIM_Channel = TIM_Channel_1 | TIM_Channel_2;//通道1和2
//  TIM_ICInitStruct.TIM_ICFilter = 10;//设置滤波器,重复检测10次,防止抖动,增加精度
//  TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_TIM_ICPolarity_Rising;//上升沿捕获
//  TIM_ICInitStruct.TIM_ICPrescaler = TIM_ICPSC_DIV1;//配置输入分频,不分频
//  TIM_ICInitStruct.TIM_ICSelection = TIM_ICSelection_DirectTI;//不设置重映射
//  TIM_ICInit(TIM2,&TIM_ICInitStruct);
//  
//  /*5.开启捕获中断,并配置中断*/
//  TIM_ITConfig(TIM2, TIM_IT_CC1 | TIM_IT_CC2, ENABLE);
//  
//  NVIC_InitTypeDef NVIC_InitStruct;
//  NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn;
//  NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
//  NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
//  NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
//  NVIC_Init(&NVIC_InitStruct);

  /*6.使能定时器*/
  TIM_Cmd(TIM3,ENABLE);

  /*7.编写中断服务函数*/

}

一个编码器需要占用一个定时器,这里我使用的是TIM3和TIM4

注意:在初始化IO口时,选择输入输出模式有点问题。

网上有的说选择输入浮空模式、有的说选择输入上拉模式,但是我调试的时候这两种都不出结果,我改成输入下拉模式的是才调试成功,大家在实际调试过程中可以几个输入模式都试一下。

2.读取捕获脉冲数值

这部分和上面外部中断法的函数一样,输入3和4参数使用输入捕获方式提取计数器的值。

/**************************************************************************
功能:获取不同方式下的脉冲值
函数:int Read_Encoder(u8 TIMX)
作者:K.Fire
日期:2022.01.30
参数:1:外部中断法左轮 2:外部中断法右轮 3:输入捕获法左轮 4:输入捕获法右轮
**************************************************************************/

int Read_Encoder(u8 TIMX)
{
    int Encoder_TIM;    
    switch(TIMX)
  {
    case 3:Encoder_TIM=TIM3- >CNT-0x7fff;  TIM3- >CNT=0x7fff;break;  
    case 4:Encoder_TIM=TIM4- >CNT-0x7fff;  TIM4- >CNT=0x7fff;break;
      default:Encoder_TIM=0;break;
  }
    return Encoder_TIM;
}

/**************************************************************************
功能:获取并打印输出实际速度值
函数:void Get_SpeedNow(float* CurrentVelcity_L,float* CurrentVelcity_R)
作者:K.Fire
日期:2022.01.30
参数:CurrentVelcity_L:左轮实时速度(地址)  CurrentVelcity_R:右轮实时速度(地址)
**************************************************************************/

extern int Current_LN,Current_RN;//速度脉冲数

void Get_SpeedNow(int* CurrentVelcity_L,int* CurrentVelcity_R)
{
  /*输入捕获方式*/
  Current_LN = -Read_Encoder(3);
  Current_RN = Read_Encoder(4);

  *CurrentVelcity_L = (MPN * Current_LN * 1000 * 10)/T;//单位:mm/s
  *CurrentVelcity_R = (MPN * Current_RN * 1000 * 10)/T;

  printf("The Current Left Wheel Velocity is: %d mm/s.\\r\\n",*CurrentVelcity_L);
  printf("The Current Right Wheel Velocity is: %d mm/s.\\r\\n",*CurrentVelcity_R);

}

3.测试结果

图片

05 总结

注意:左轮电机和右轮电机是相反的,需要实际测试确定,比如我这里,向前走的时候,右轮是正转,这时左轮反转的时候才是前进,在编写程序时需要给左轮的脉冲值加个负号。

获取到了实时速度值后,就可以用PID算法对电机转速进行准确控制了。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 传感器
    +关注

    关注

    2551

    文章

    51103

    浏览量

    753608
  • 编码器
    +关注

    关注

    45

    文章

    3643

    浏览量

    134525
  • STM32
    +关注

    关注

    2270

    文章

    10900

    浏览量

    356052
  • PID
    PID
    +关注

    关注

    35

    文章

    1472

    浏览量

    85524
  • STM32F103C8T6
    +关注

    关注

    108

    文章

    160

    浏览量

    83580
收藏 人收藏

    评论

    相关推荐

    有关AB相磁编码器STM32F103C8T6之间脉冲计数问题

    AB相磁编码器STM32F103C8T6引脚应该怎么连接?我看手册通用定时四个通道都可以实现输入捕获功能,那是否能实现
    发表于 09-17 12:29

    深圳高价回收STM32F103C8T6 收购STM32F103C8T6

    [[ ST芯片大量收购STM32F103C8T6 ,高价回收意ST电子芯片,实力回收电子芯片,高价回收各个品牌电子芯片 ]]优势回收以下ST型号:STM32F101C6T6
    发表于 08-30 16:10

    MT测速单片机程序设计

    MT测速单片机程序设计MT
    发表于 09-02 06:01

    如何实现STM32F103C8T6编码器测速

    如何实现STM32F103C8T6编码器测速
    发表于 12-13 07:21

    STM32F103C8T6最小系统原理图

    本文件是关于意半导体公司研制的STM32F103C8T6最小系统的电路原理图,供单片机学习使用。
    发表于 04-25 17:07 302次下载

    STM32F103C8T6引脚图

    本文介绍STM32F103C8T6引脚图、STM32F103C8T6开发板电路图及各部分引脚功能图介绍,STM32F103C8T6资料下载。
    发表于 08-03 15:53 43.9w次阅读
    <b class='flag-5'>STM32F103C8T6</b>引脚图

    STM32F103C8T6核心板的资料合集免费下载

    本文档的主要内容详细介绍的是STM32F103C8T6核心板的资料合集免费下载包括了:STM32F103C8T6核心板测试程序(PC13闪烁),STM32F103C8T6核心板测试程序(RTC
    发表于 06-04 08:00 720次下载
    <b class='flag-5'>STM32F103C8T6</b>核心板的资料合集免费下载

    STM32F103C8T6原理图

    STM32F103C8T6原理图免费下载。
    发表于 06-02 10:01 181次下载

    STM32F103C8t6程序下载

    STM32F103C8t6程序下载
    发表于 11-24 18:21 233次下载
    <b class='flag-5'>STM32F103C8t6</b>程序下载

    STM32F103C8T6中断特征介绍 用按钮为STM32 MCU触发一个中断

    中断是MCU的特征之一,STM32F103C8T6也不例外。以下通过STM32F103C8T6 Blue Pill外接一个按钮,向MCU触发外部中断
    的头像 发表于 05-17 08:57 1.1w次阅读
    <b class='flag-5'>STM32F103C8T6</b><b class='flag-5'>中断</b>特征介绍 用按钮为<b class='flag-5'>STM32</b> MCU触发一个<b class='flag-5'>中断</b>

    STM32F103C8T6微控制手册

    STM32F103C8T6微控制手册免费下载。
    发表于 05-24 16:05 48次下载

    使用stm32f103c8t6设计的蓝牙小车

    使用stm32f103c8t6设计的蓝牙小车,具有前进后退,左转右转等功能
    发表于 08-07 16:08 29次下载

    ch32f103c8t6stm32f103c8t6的区别

    ch32f103c8t6stm32f103c8t6的区别 STM32F103C8T6是一款32位微处理,属于STM32F1系列中的一员。
    的头像 发表于 08-22 16:05 9925次阅读

    分享STM32F103C8T6原理图

    STM32F103C8T6最小系统原理图
    发表于 08-29 15:52 104次下载

    stm32f103c8t6怎么连接噪声传感

    连接噪声传感STM32F103C8T6需要以下步骤: 确定传感STM32F103C8T6之间的连接接口。噪声传感通常使用模拟信号输
    的头像 发表于 12-21 16:10 1522次阅读