131 1300 0010
电机伺服
当前位置: 首页>> 电源技术>>电机伺服>>
  • 导航栏目
  • 逆变电源
  • 开关电源
  • 电机伺服
  • 其他电源
  • 电机控制基础2——定时器捕获单输入脉冲原理
    电机控制基础2——定时器捕获单输入脉冲原理
  • 电机控制基础2——定时器捕获单输入脉冲原理
  •   发布日期: 2021-06-24  浏览次数: 1,476

    1 问题引出

    单片机与嵌入式开发中,某些场景需要捕获传感器的高电平(或低电平)信号的持续时间,如红外解码信号、编码器输入信号等。

     

    如下图,以单一的一段高电平输入信号为例,如何测量这段高电平的时间呢?

    从直观上理解,就是要不断的检测这个信号,当信号从0变到1时,记录一个时间,再从1变到0时,记录另一个时间,两个时间差就是高电平的持续时间了。那具体要怎么编程呢?这就要用到定时器了。

    2 定时器的捕获原理

    上篇介绍了定时器的输出功能,本篇是利用定时器的输入功能,来计算脉冲时长。如下图:

    • 定时器的CNT计数器在不停的计数

    • 首先配置定时器的输入通道为上升沿捕获,这样当检测到从0到1的跳变时,CCR1就会先保存当前的CNT值,同时CNT会清零重新开始计数

    • 然后将定时器的输入通道为下降沿捕获,当检测从1到0的跳变时,CCR2就会先保存当前的CNT值

    • 在这期间,CNT的计数值可能会溢出,这不影响,记录下溢出的次数,并重新开始计数即可

    • 最终,t2-t1的高电平时间,就可以通过N次的溢出时间加CCR2保存的时间来计算获得了

    poYBAGDQvDmAVWWNAAELc5qfGa4389.png

     

     

    3 定时器常用的寄存器

    上篇介绍了定时器输出PWM时用到的几个寄存器(CR、CCMR、CNT、PSC、ARR、CCR等),这里再介绍几个捕获信号时需要用到的几个寄存器:

    3.1 捕获/比较模式寄存器CCMR1

    CCMR寄存器上篇已有介绍,只是上篇仅介绍了输出模式下的功能,本篇再介绍一下它在输入模式下的功能:

    这些通道可用于输入(捕获模式)输出(比较模式)模式。通道方向通过配置相应的 CCxS 位进行定义。此寄存器的所有其它位在输入模式和输出模式下的功能均不同。对于任一给定位

    • OCxx 用于说明通道配置为输出时该位对应的功能

    • ICxx 则用于说明通道配置为输入时 该位对应的功能

    因此,必须注意同一个位在输入阶段和输出阶段具有不同的含义。

    poYBAGDQvD-Acon1AABGDNkRr5A476.png

    这里仅先介绍输入模式下的功能:

    • 位 15:12 IC2F:输入捕获 2 滤波器 (Input capture 2 filter)

    • 位 11:10 IC2PSC[1:0]:输入捕获 2 预分频器 (Input capture 2 prescaler)

    • 位 9:8 CC2S:捕获/比较 2 选择 (Capture/compare 2 selection) 用法参照下面的CC1S通道1

    • 位 7:4 IC1F:输入捕获 1 滤波器 (Input capture 1 filter)

      数字滤波器由事件计数器组成,每 N 个事件才视为一个有效边沿:

      • 0000:无滤波器

      • 0001~1111:其它频率的滤波器

    • 位 3:2 IC1PSC:输入捕获 1 预分频器 (Input capture 1 prescaler)

      此位域定义 CC1 输入 (IC1) 的预分频比。只要 CC1E=0(TIMx_CCER 寄存器),预分频器便立即复位。

      • 00:无预分频器,捕获输入上每检测到一个边沿便执行捕获

      • 01~11:每发生 2 (4、8)个事件便执行一次捕获

    • 位 1:0 CC1S:捕获/比较 1 选择 (Capture/Compare 1 selection),此位域定义通道方向(输入/输出)以及所使用的输入。

      • 00:CC1 通道配置为输出

      • 01:CC1 通道配置为输入,IC1 映射到 TI1 上

      • 10:CC1 通道配置为输入,IC1 映射到 TI2 上

      • 11:CC1 通道配置为输入,IC1 映射到 TRC 上。此模式仅在通过 TS 位(TIMx_SMCR 寄存器)选择内部触发输入时有效

    注: 仅当通道关闭时(TIMx_CCER 中的 CC1E = 0),才可向 CC1S 位写入数据。

    3.2 捕获/比较使能寄存器CCER

    我们要用到这个寄存器的最低 2 位, CC1E 和 CC1P。

    pYYBAGDQvECAG18AAAAqgbJouDE128.png

    • 位 15、11、7、3 CCxNP:捕获 /比较x 输出极性 (Capture/Comparex output Polarity)。

      • CCx 通道配置为输出: CCxNP 必须保持清零。

      • CCx 通道配置为输入:此位与 CCxP 配合使用,用以定义 TI1FP1/TI2FP1 的极性。请参见 CCxP 说明。

    • 位 14、10、6、2 保留,必须保持复位值。

    • 位 13、9、5、1 CCxP:捕获 /比较x 输出极性 (Capture/Comparex output Polarity)。

      • 00:非反相/上升沿触发 电路对 TIxFP1 上升沿敏感 (在复位模式、外部时钟模式或触发模式下执行捕获或触发操作), TIxFP1 未反相 (在门控模式或编码器模式下执行触发操作)。

      • 01:反相/下降沿触发 电路对 TIxFP1 下降沿敏感 (在复位模式、外部时钟模式或触发模式下执行捕获或触发操作), TIxFP1 反相 (在门控模式或编码器模式下执行触发操作)。

      • 10:保留,不使用此配置。

      • 11:非反相/上升沿和下降沿均触发 电路对 TIxFP1 上升沿和下降沿都敏感(在复位模式、外部时钟模式或触发模式下执行捕获或触发操作),TIxFP1 未反相(在门控模式下执行触发操作)。编码器模式下不得使用此配置。

      • 0:OCx 高电平有效

      • 1:OCx低电平有效

      • CCx 通道配置为输出

      • CCx 通道配置为输入

        CCxNP/CCxP 位可针对触发或捕获操作选择 TI1FP1 和 TI2FP1 的极性。

    • 位 12、8、4、0 CCxE:捕获 /比较 x 输出使能 (Capture/Comparex output enable)。

      • 0:禁止捕获

      • 1:使能捕获

      • 0:关闭––OCx 未激活

      • 1:开启––在相应输出引脚上输出 OCx信号

      • CCx 通道配置为输出

      • CCx 通道配置为输入

        此位决定了是否可以实际将计数器值捕获到输入捕获/比较寄存器 1 (TIMx_CCR1) 中。

    3.3 DMA/中断使能寄存器DIER

    我们需要用到中断来处理捕获数据,所以必须开启通道 1 的捕获比较中断,即 CC1IE 设置为 1 。

    pYYBAGDQvEGAZLeLAAAkg5HrLUE613.png

    • 位 15、13、7、5 保留,必须保持复位值。

    • 位 14 TDE:触发 DMA 请求使能 (Trigger DMA request enable)

    • 位 12~位9 CCxDE:捕获/比较x DMA 请求使能 (Capture/Compare 1 DMA request enable)

    • 位 8 UDE:更新 DMA 请求使能 (Update DMA request enable)

    • 位 6 TIE:触发信号(TRGI)中断使能 (Trigger interrupt enable)

    • 位 4~位1 CCxIE:捕获/比较x 中断使能 (Capture/Compare 1 interrupt enable)

    • 位 0 UIE:更新中断使能 (Update interrupt enable)

     

    4 编程

    4.1 定时器初始化

    4.1.1 GPIO初始化

    这里用到的是定时器5的通道1,根据STM32F407的数据手册“3 Pinouts and pin description”中的“Table 9. Alternate function mapping”复用引脚说明表,可以看到定时器5通道1对应的引脚位A0,所以使用A0作为信号的输入引脚。poYBAGDQvEKAKgZ3AAD7x9uese0683.png

    因此程序中对A0引脚可以这样配置,注意一定要配置引脚的复用功能

    GPIO_InitTypeDef GPIO_InitStructure;      /*GPIO 结构体*/
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); //使能PORTA时钟 
    
    /*输入信号的GPIO初始化*/
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;      //GPIOA0
    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(GPIOA,&GPIO_InitStructure);        //初始化PA0
    
    GPIO_PinAFConfig(GPIOA,GPIO_PinSource0,GPIO_AF_TIM5); //PA0复用位定时器5

     

    4.1.2 时基初始化

    使用定时器,时基初始化是必不可少的,就是要设置一些计数的频率与溢出值(自动重装载值):

    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; /*时基 结构体*/
    
    /*时基初始化*/
    TIM_TimeBaseStructure.TIM_Period=arr;   /* 自动重装载值 */
    TIM_TimeBaseStructure.TIM_Prescaler=psc; /* 定时器分频 */
    TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
    TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
    TIM_TimeBaseInit(TIM5,&TIM_TimeBaseStructure);

     

    4.1.3 输入通道初始化

    将定时器的通道1设置为输入捕获模式:

    TIM_ICInitTypeDef TIM5_ICInitStructure;    /*输入通道 结构体*/
    
    /*输入通道初始化,初始化TIM5输入捕获参数*/
    TIM5_ICInitStructure.TIM_Channel = TIM_Channel_1;        //CC1S=01 选择输入端 IC1映射到TI1上
    TIM5_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; /* 上升沿捕获 */
    TIM5_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //映射到TI1上
    TIM5_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;     //配置输入分频,不分频 
    TIM5_ICInitStructure.TIM_ICFilter = 0x00;            //IC1F=0000 配置输入滤波器 不滤波
    TIM_ICInit(TIM5, &TIM5_ICInitStructure);
    
    TIM_ITConfig(TIM5,TIM_IT_Update|TIM_IT_CC1,ENABLE); /* 允许更新(溢出)中断 ,允许CC1IE捕获中断 */ 
    
    TIM_Cmd(TIM5,ENABLE ); //使能定时器5
    • 关于配置CCMR1、CCER寄存器

    CCMR1:

    poYBAGDQvD-Acon1AABGDNkRr5A476.png

    CCER:pYYBAGDQvECAG18AAAAqgbJouDE128.png

    TIM_ICInit函数对应于输入通道的初始化,其实就是操作CCMR1CCER寄存器:

    void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct)
    {
     if (TIM_ICInitStruct->TIM_Channel == TIM_Channel_1)
     {  /* TI1 配置 */
       TI1_Config(TIMx, TIM_ICInitStruct->TIM_ICPolarity,
             TIM_ICInitStruct->TIM_ICSelection,
             TIM_ICInitStruct->TIM_ICFilter);
       /* 设置中断捕获预分频值 */
       TIM_SetIC1Prescaler(TIMx, TIM_ICInitStruct->TIM_ICPrescaler);
     }
     else if (TIM_ICInitStruct->TIM_Channel == TIM_Channel_2)
     {
       /*省略...*/
     }
    }
    
    static void TI1_Config(TIM_TypeDef* TIMx, uint16_t TIM_ICPolarity, uint16_t TIM_ICSelection,uint16_t TIM_ICFilter)
    {
     uint16_t tmpccmr1 = 0, tmpccer = 0;
    
     /* 关闭通道1: 复位CC1E位 */
     TIMx->CCER &= (uint16_t)~TIM_CCER_CC1E;
     tmpccmr1 = TIMx->CCMR1;
     tmpccer = TIMx->CCER;
    
     /* 通过设置CC1S选择为输入模式, 并配置滤波器 */
     tmpccmr1 &= ((uint16_t)~TIM_CCMR1_CC1S) & ((uint16_t)~TIM_CCMR1_IC1F);
     tmpccmr1 |= (uint16_t)(TIM_ICSelection | (uint16_t)(TIM_ICFilter << (uint16_t)4));
    
     /* 选择CC1P极性并设置CC1E位 */
     tmpccer &= (uint16_t)~(TIM_CCER_CC1P | TIM_CCER_CC1NP);
     tmpccer |= (uint16_t)(TIM_ICPolarity | (uint16_t)TIM_CCER_CC1E);
    
     /* 写数据到 TIMx 的CCMR1 和 CCER 寄存器 */
     TIMx->CCMR1 = tmpccmr1;
     TIMx->CCER = tmpccer;
    }
    
    void TIM_SetIC1Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC)
    {
     TIMx->CCMR1 &= (uint16_t)~TIM_CCMR1_IC1PSC; /* 复位IC1PSC位 */
     TIMx->CCMR1 |= TIM_ICPSC;          /* 设置IC1PSC值 */
    }
    • 关于配置DIER寄存器

    pYYBAGDQvEGAZLeLAAAkg5HrLUE613.png

    TIM_ITConfig函数对于中断的开启,其实就是操作DIER寄存器:

    void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState)
    { 
     if (NewState != DISABLE)
     {  /* 使能中断 */
       TIMx->DIER |= TIM_IT;
     }
     else
     {  /* 失能中断 */
       TIMx->DIER &= (uint16_t)~TIM_IT;
     }
    }

     

    4.1.4 定时器中断初始化

    定时器中断的使能设置已在上面的定时器配置中设置,这里只是进行常规的配置定时器中断的优先级:

    /*定时器中断配置*/
    NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2; //抢占优先级3
    NVIC_InitStructure.NVIC_IRQChannelSubPriority =0; //子优先级3
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;  //IRQ通道使能
    NVIC_Init(&NVIC_InitStructure);           //根据指定的参数初始化NVIC寄存器

     

    4.2 定时器中断服务函数

    此处用到了两个全局变量,用于辅助实现高电平捕获。其中:

    • TIM5CH1_CAPTURE_VAL用来记录捕获到下降沿的时候 TIM5_CNT的值。

    • TIM5CH1_CAPTURE_STA用来记录捕获状态,我们把它当成一个寄存器那样来使用 。其各位描述下:

    poYBAGDQvEKAFfL2AAEK03xP02U575.png

    u8 TIM5CH1_CAPTURE_STA=0; //输入捕获状态(当中一个自制的寄存器使用,初始为0) 
    u32 TIM5CH1_CAPTURE_VAL; //输入捕获值(TIM2/TIM5是32位)
    
    /**
    * @brief 定时器5中断服务程序
    */
    void TIM5_IRQHandler(void)
    {  
     if((TIM5CH1_CAPTURE_STA&0X80)==0)//还未成功捕获 (1000 0000)
     {
     /*定时器溢出中断*/
     if(TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET)
     {   
      if(TIM5CH1_CAPTURE_STA&0X40)/* 之前标记了开始信号(0100 0000) */
      {
      if((TIM5CH1_CAPTURE_STA&0X3F)==0X3F) /* 高电平太长了,计数溢出了 (0011 1111) */
      {
       TIM5CH1_CAPTURE_STA|=0X80;  /* (强制)标记成功捕获了一次 (1000 0000) */
       TIM5CH1_CAPTURE_VAL=0XFFFFFFFF; /* 因为溢出次数N不能再加了,就将当前的捕获值设置为32位的最大值,等效Nmax+1*/
      }
      else /* 正常情况是不会溢出,最终得出正确的高电平时间 */
      {
       TIM5CH1_CAPTURE_STA++; /* 累计定时器溢出次数N */
      }
      }
      else
      {
      /* 还没有捕获到信号时,定时器溢出后什么也不做,自己清零继续计数即可 */
      }
     }
     
     /*捕获1发生捕获事件*/
     if(TIM_GetITStatus(TIM5, TIM_IT_CC1) != RESET)
     { 
      /*捕获到一个下降沿(结束信号)*/
      if(TIM5CH1_CAPTURE_STA&0X40) /* 之前标记了开始信号(0100 0000) */   
      {   
      TIM5CH1_CAPTURE_STA|=0X80;           /* 标记成功捕获到一次高电平脉宽 (1000 0000) */
      TIM5CH1_CAPTURE_VAL=TIM_GetCapture1(TIM5);     /* 获取当前的捕获值 */
       TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Rising); /* CC1P=0 重新设置为上升沿捕获,用于下次捕捉信号 */
      }   
      /*还未开始,第一次捕获 上升沿(起始信号) */
      else     
      {
      TIM5CH1_CAPTURE_STA=0;  /* 清空 捕获状态寄存器 */
      TIM5CH1_CAPTURE_VAL=0;     /* 清空 捕获值 */
      TIM5CH1_CAPTURE_STA|=0X40; /* 标记捕获到了上升沿 (0100 0000) */
      
      TIM_Cmd(TIM5,DISABLE );   /* 关闭定时器5 */
       TIM_SetCounter(TIM5,0);     /* 清空CNT,重新从0开始计数 */
       TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Falling); /* CC1P=1 设置为下降沿捕获 */
      TIM_Cmd(TIM5,ENABLE );     /* 使能定时器5 */
      }   
     }          
     }
     TIM_ClearITPendingBit(TIM5, TIM_IT_CC1|TIM_IT_Update); //清除中断标志位
    }

    再来对比一下这张图:

    poYBAGDQvDmAVWWNAAELc5qfGa4389.png

    • 初始化时设置为上升沿触发,触发后(起始信号),清空CNT,重新从0开始计数,并设置为下降沿捕获

    • 在之后的过程中可能会有多次定时器计数溢出,即TIM5CH1_CAPTURE_STA++(使用低6位),也即N的值

    • 最后捕捉到下降沿(结束信号),TIM5CH1_CAPTURE_VAL获取当前CNT的值,也即CCRx2的值

    再看主函数中:

    while(1) 
    {
      /* 成功捕获到了一次高电平 (1000 0000) */
      if(TIM5CH1_CAPTURE_STA&0X80)    
      {
        temp=TIM5CH1_CAPTURE_STA&0X3F; /* 获取溢出的次数N (0011 1111) */
        temp*=0XFFFFFFFF;   /* 溢出时间总和 = N*溢出计数值 */
        temp+=TIM5CH1_CAPTURE_VAL; /* 总的高电平时间 = 溢出时间总和 + 下降沿时的计数值*/
    
        printf("HIGH:%lld us\r\n",temp); //打印总的高点平时间
        TIM5CH1_CAPTURE_STA=0;  //开启下一次捕获
      }
    }

    当检查TIM5CH1_CAPTURE_STA为捕获到1次高电平后,打印高电平的持续时间:

    • 总的高电平时间 =N(TIM5CH1_CAPTURE_STA的低6位) * ARR(溢出计数值)+ CCRx2(下降沿时的计数值)

     

     

    附:一些寄存器简写的全称

    • ARR:auto-reload register 自动重载寄存器

    • CCR:capture/compare register 捕获/比较寄存器

    • PSC:prescaler 预分频器

    • CNT:counter 计数器

    • SR:status register 状态寄存器

    • CCMR:capture/compare mode register 捕获/比较模式寄存器

      • CC1S:Capture/Compare 1 selection 捕获/比较1模式选择

      • OC1M: Output compare 1 mode 输出比较1模式

      • OC1PE:Output compare 1 preload enable 输出比较1预装载使能

      • IC1F:Input capture 1 filter 输入捕获1滤波器

      • IC1PSC:Input capture 1 prescaler 输入捕获1预分频器

    • CCER:capture/compare enable register 捕获/比较使能寄存器

      • CC1P:Capture/Comparex output Polarity 捕获 /比较1输出极性

      • CC1E:Capture/Comparex output enable 捕获 /比较1输出使能

    • SMCR:slave mode control register 从模式控制寄存器

    • DCR:DMA control register DMA 控制寄存器

    • DIER:DMA/Interrupt enable register DMA/中断使能寄存器

    • DMAR:DMA address for full transfer 全传输 DMA 地址

    • OR:option register 选项寄存器


  • ·上一篇:
    ·下一篇:
  • 其他关联资讯
    深圳市日月辰科技有限公司
    地址:深圳市宝安区松岗镇潭头第二工业城A区27栋3楼
    电话:0755-2955 6626
    传真:0755-2978 1585
    手机:131 1300 0010
    邮箱:hu@szryc.com

    深圳市日月辰科技有限公司 版权所有:Copyright©2010-2023 www.szryc.com 电话:13113000010 粤ICP备2021111333号