当前位置: 首页 > 工具软件 > Tim > 使用案例 >

HAL库的TIM中断和输入捕获

仲涵亮
2023-12-01

硬件:stm32f4xx
软件:keil5(HAL库)

1.TIM中断(TIM3)
首先先把TIM初始化,官方给的初始化函数是HAL_TIM_Base_Init(TIM_HandleTypeDef *htim),也就意味着我们要首先初始化好TIM_HandleTypeDef这个结构体。所以我们先写一个函数来构建句柄。

TIM_HandleTypeDef TIM3_Handler;    
void TIM3_Init(u16 per,u16 psc)
{  
    TIM3_Handler.Instance=TIM3;                          //通用定时器3
    TIM3_Handler.Init.Prescaler=psc;                     //分频系数
    TIM3_Handler.Init.CounterMode=TIM_COUNTERMODE_UP;    //向上计数器
    TIM3_Handler.Init.Period=per;                        //自动装载值
    TIM3_Handler.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;//时钟分频因子
    HAL_TIM_Base_Init(&TIM3_Handler);
    
    HAL_TIM_Base_Start_IT(&TIM3_Handler); //使能定时器3和定时器3更新中断:TIM_IT_UPDATE   
}

因为我们是要实现TIM的更新中断,所以要开启TIM3的更新中断。这里是通过官方给了一个库函数来实现。

在HAL库里面,Init函数里面都会调用一个回调函数,一个函数在官方的库函数里面是_week_定义的一个弱函数,我们一把根MCU有关的代码都写在回调函数里面,比如相关时钟的开启,相关GPIO的设定,中断优先级的设定等等。
一般来说,查找回调函数的名称也比较的容易,我们可以step into到Init函数里面,去查看调用了哪个回调函数,一般命名的规则都是xxx_MspInit,或者我们也可以直接去.h的库文件里面直接去查到,比如我们如果想找HAL_TIM_Base_Init的回调函数,就可以直接去stm32f4xx_hal_tim.h里面查找。经过查找得到,我们所要编写的回调函数是HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim);
所以根据上文的分析,我们下面就直接附上回调函数的代码。

void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{
    if(htim->Instance==TIM3)
 {
  __HAL_RCC_TIM3_CLK_ENABLE();            //使能TIM3时钟
  HAL_NVIC_SetPriority(TIM3_IRQn,1,3);    //设置中断优先级,抢占优先级1,子优先级3
  HAL_NVIC_EnableIRQ(TIM3_IRQn);          //开启ITM3中断   
 }
}

回调函数写完以后,当我们在主函数里面执行HAL_TIM_Base_Init的时候,系统就会优先调用我们写好的回调函数。
初始化结束了以后,当我们每次计数器达到我们设定的per(自动重装载值)的时候,就会产生一次更新中断,然后进入到系统中断服务函数,所以我们接下来的任务就是编写TIM3的中断服务函数。
首先是在启动文件里面找到中断服务函数的名称。这里是叫做TIM3_IRQHandler。然后在hal库的编写逻辑里面,所以的中断服务函数里面,官方都定义了一个中断公用函数,我们可以通过进入这个函数,然后编写里面调用的回调函数来完成我们需要中断完成的任务。这个中断公用函数可以在相应的.h头文件里面找到。所以我们只要在中断里面调用这个函数即可。

void TIM3_IRQHandler(void)
{
    HAL_TIM_IRQHandler(&TIM3_Handler);
}

我们深入HAL_TIM_IRQHandler这个函数里面去看,就可以发现里面调用了一个叫做HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)的回调函数,所以最后我们只要在这个函数里面编写我们所需的逻辑代码即可。这里我们让我们的LED进行电平的反转

//回调函数,定时器中断服务函数调用
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if(htim==(&TIM3_Handler))
    {
        LED2=!LED2;        //LED1反转
    }
}

当然,我们可以发现这样写的话,执行的效率会比较的低。我们也可以在一开始就不调用中断公用函数,直接在TIM3_IRQHandler(void)这个中断服务函数里面直接编写逻辑代码。

2.输入捕获(TIM5)
首先和TIM中断一样,要先配置TIM的结构体。但因为我们还要实现输入捕获的功能,所以在配置完TIM结构体以后,还要配置输入捕获的相关参数,这里官方也定义了一个对应的结构体和结构体初始化函数。

TIM_HandleTypeDef TIM5_Handler;         //定时器5句柄
TIM_IC_InitTypeDef TIM5_CH1Config;     //输入捕获参数句柄
void TIM5_CH1_Input_Init(u32 arr,u16 psc)
{  
    TIM5_Handler.Instance=TIM5;                          //通用定时器5
    TIM5_Handler.Init.Prescaler=psc;                     //分频系数
    TIM5_Handler.Init.CounterMode=TIM_COUNTERMODE_UP;    //向上计数器
    TIM5_Handler.Init.Period=arr;                        //自动装载值
    TIM5_Handler.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;//时钟分频因子
    HAL_TIM_IC_Init(&TIM5_Handler);//初始化输入捕获时基参数
    TIM5_CH1Config.ICPolarity=TIM_ICPOLARITY_RISING;    //上升沿捕获
    TIM5_CH1Config.ICSelection=TIM_ICSELECTION_DIRECTTI;//映射到TI1上
    TIM5_CH1Config.ICPrescaler=TIM_ICPSC_DIV1;          //配置输入分频,不分频
    TIM5_CH1Config.ICFilter=0;                          //配置输入滤波器,不滤波
    HAL_TIM_IC_ConfigChannel(&TIM5_Handler,&TIM5_CH1Config,TIM_CHANNEL_1);//配置TIM5通道1
    HAL_TIM_IC_Start_IT(&TIM5_Handler,TIM_CHANNEL_1);   //开启TIM5的捕获通道1,并且开启捕获中断
    __HAL_TIM_ENABLE_IT(&TIM5_Handler,TIM_IT_UPDATE);   //使能更新中断
}

这里还打开了更新中断,是怕如果高电平的时间太长,导致计数器的技术结果超过了计时器的最大值,而导致最后的结果不准确,所以开启了更新中断。当然最后一行代码也可以写成HAL_TIM_Base_Start_IT(&TIM5_Handler);

接下来和之前分析的一样,Init函数会调用回调函数,我们在回调函数里面编写和MCU相关的一些代码。

void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim)
{
    GPIO_InitTypeDef GPIO_Initure;
    __HAL_RCC_TIM5_CLK_ENABLE();            //使能TIM5时钟
    __HAL_RCC_GPIOA_CLK_ENABLE();   //开启GPIOA时钟
     
    GPIO_Initure.Pin=GPIO_PIN_0;            //PA0
    GPIO_Initure.Mode=GPIO_MODE_AF_PP;      //复用推挽输出
    GPIO_Initure.Pull=GPIO_PULLDOWN;        //下拉,因为要判断高电平的持续时间
    GPIO_Initure.Speed=GPIO_SPEED_HIGH;     //高速
    GPIO_Initure.Alternate=GPIO_AF2_TIM5;   //PA0复用为TIM5通道1
    HAL_GPIO_Init(GPIOA,&GPIO_Initure);
    HAL_NVIC_SetPriority(TIM5_IRQn,2,0);    //设置中断优先级,抢占优先级2,子优先级0
    HAL_NVIC_EnableIRQ(TIM5_IRQn);          //开启ITM5中断通道  
}

配置完所有的初始化参数以后,我们就要进入到TIM5的中断里面,先调用中断公用处理函数。

//定时器5中断服务函数
void TIM5_IRQHandler(void)
{
 HAL_TIM_IRQHandler(&TIM5_Handler);//定时器共用处理函数
}

然后我们是开启了输入捕获中断和更新中断,所有我们要编写输入捕获中断回调处理函数和更新中断回调处理函数这两个函数。

//捕获状态
//[7]:0,没有成功的捕获;1,成功捕获到一次.
//[6]:0,还没捕获到低电平;1,已经捕获到低电平了.
//[5:0]:捕获低电平后溢出的次数(对于32位定时器来说,1us计数器加1,溢出时间:4294秒)
u8  TIM5_CH1_CAPTURE_STA=0; //输入捕获状态          
u32 TIM5_CH1_CAPTURE_VAL; //输入捕获值(TIM2/TIM5是32位)

//定时器输入捕获中断处理回调函数,该函数在HAL_TIM_IRQHandler中会被调用
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)//捕获中断发生时执行
{
 if((TIM5_CH1_CAPTURE_STA&0X80)==0)//还未成功捕获
 {
  if(TIM5_CH1_CAPTURE_STA&0X40)  //捕获到一个下降沿   
  {      
   TIM5_CH1_CAPTURE_STA|=0X80;  //标记成功捕获到一次高电平脉宽
   TIM5_CH1_CAPTURE_VAL=HAL_TIM_ReadCapturedValue(&TIM5_Handler,TIM_CHANNEL_1);//获取当前的捕获值.
   __HAL_TIM_DISABLE(&TIM5_Handler);
   TIM_RESET_CAPTUREPOLARITY(&TIM5_Handler,TIM_CHANNEL_1);   //一定要先清除原来的设置!!
   TIM_SET_CAPTUREPOLARITY(&TIM5_Handler,TIM_CHANNEL_1,TIM_ICPOLARITY_RISING);//配置TIM5通道1上升沿捕获
   __HAL_TIM_ENABLE(&TIM5_Handler);//使能定时器5
  }
  else          //还未开始,第一次捕获上升沿
  {
   TIM5_CH1_CAPTURE_STA=0;   //清空
   TIM5_CH1_CAPTURE_VAL=0;
   TIM5_CH1_CAPTURE_STA|=0X40;  //标记捕获到了上升沿
   //配置tim前一定要先关闭tim,配置完以后再使能
   __HAL_TIM_DISABLE(&TIM5_Handler);        //关闭定时器5
   __HAL_TIM_SET_COUNTER(&TIM5_Handler,0);  //计数器CNT置0
   TIM_RESET_CAPTUREPOLARITY(&TIM5_Handler,TIM_CHANNEL_1);   //一定要先清除原来的设置!!
   TIM_SET_CAPTUREPOLARITY(&TIM5_Handler,TIM_CHANNEL_1,TIM_ICPOLARITY_FALLING);//定时器5通道1设置为下降沿捕获
   __HAL_TIM_ENABLE(&TIM5_Handler);//使能定时器5
  }      
 }  
}

这里当我们第一次进入捕获中断的时候,要把CNT置为0,然后把定时器设定为下降沿捕获。然后第二次进入的时候,当检测到下降沿的到来时候,把CNT的数值通过HAL_TIM_ReadCapturedValue函数读取出来,赋给全局变量 TIM5_CH1_CAPTURE_VAL。最后我们只要在main函数里面通过计算有N次溢出,和最后一次读取的 TIM5_CH1_CAPTURE_VAL值,就可以通过T = N*0xffffffff+ TIM5_CH1_CAPTURE_VAL;来获得最后的高电平时间。

然后写完了捕获中断处理回调函数以后,最后就是要写一个更新中断服务回调函数,来计数有几次溢出的数值。如果TIM5_CH1_CAPTURE_STA的值都为0x7f了,但都没有检测到下降沿,我们就强行结束这次捕获。

//定时器更新中断(计数溢出)中断处理回调函数, 该函数在HAL_TIM_IRQHandler中会被调用
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)//更新中断(溢出)发生时执行
{
 
 if((TIM5_CH1_CAPTURE_STA&0X80)==0)//还未成功捕获
 {
  if(TIM5_CH1_CAPTURE_STA&0X40)//已经捕获到高电平了
  {
   if((TIM5_CH1_CAPTURE_STA&0X3F)==0X3F)//高电平太长了
   {
    TIM5_CH1_CAPTURE_STA|=0X80;  //标记成功捕获了一次
    TIM5_CH1_CAPTURE_VAL=0XFFFFFFFF;
   }
   else 
    TIM5_CH1_CAPTURE_STA++;
  }  
 }  
}

最后我们只要在main函数里面,计算出结果,并且最后把状态位TIM5_CH1_CAPTURE_STA清0,就可以进行下一次的捕获了(TIM5_CH1_CAPTURE_VAL的清0在捕获中断服务函数里面执行了)。

int main()
{
 u8 i=0;
 long long indata=0;
 
 HAL_Init();                     //初始化HAL库 
 SystemClock_Init(8,336,2,7);   //设置时钟,168Mhz
 SysTick_Init(168);
 USART1_Init(115200);
 LED_Init(); //LED初始化
 TIM5_CH1_Input_Init(0XFFFFFFFF,84-1); //以1MHZ的频率计数
 
 while(1)
 {
  if(TIM5_CH1_CAPTURE_STA&0x80) //成功捕获
  {
   indata=TIM5_CH1_CAPTURE_STA&0x3f;
   indata*=0xffffffff; //溢出次数乘以一次的计数次数时间 us
   indata+=TIM5_CH1_CAPTURE_VAL;//加上高电平捕获的时间
   printf("高电平持续时间为%lld us\r\n",indata); //总的高电平时间
   TIM5_CH1_CAPTURE_STA=0; //开始下一次捕获
  }
  
  i++;
  if(i%20==0)
  {
   LED1=!LED1;
  }
  delay_ms(10);
 }
}
 类似资料: