HAL库的TIM中断和输入捕获

硬件: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);
 }
}
在使用STM32HAL库进行编程时,获取定时器的输入捕获值通常涉及到定时器的配置,以及设置相应的输入捕获通道。以下是获取定时器输入捕获值的基本步骤: 1. 初始化定时器和相关的GPIO引脚:首先需要通过HAL库函数来初始化定时器,包括时钟配置、定时器模式设置等,并配置连接到定时器输入捕获通道的GPIO引脚为输入模式。 2. 配置定时器输入捕获参数:通过HAL库的函数设置定时器的预分频器(Prescaler)、计数模式、计数方向、捕获边沿等参数。这些参数决定了定时器的工作方式以及如何捕获信号。 3. 启用输入捕获中断或DMA:根据实际需求,可以选择通过中断方式或DMA(直接内存访问)方式来获取输入捕获值。如果使用中断方式,则需要实现相应的中断服务函数来读取捕获值;如果使用DMA方式,则需要配置DMA传输,并将捕获值读取到内存中。 4. 启动定时器:配置完成后,启动定时器,并在需要的时候通过定时器的输入捕获功能获取所需的值。 5. 获取捕获值:在中断服务函数中或DMA传输完成回调函数中,可以通过读取定时器的捕获/比较寄存器(例如TIMx_CCRx,其中x代表定时器的编号,x代表通道号)来获取捕获值。 例如,使用HAL库获取TIM3定时器通道1的输入捕获值的代码片段可能如下所示: ```c // 定时器初始化和配置代码(略) // 启用输入捕获中断 __HAL_TIM_ENABLE_IT(&htim3, TIM_IT_CC1); // 在定时器中断服务函数中获取输入捕获值 void TIM3_IRQHandler(void) { HAL_TIM_IRQHandler(&htim3); } void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) { // 读取捕获值 uint32_t captureValue = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); // 使用捕获值进行后续操作 } } ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值