STM32 HAL库高级定时器输入捕获脉宽测量

STM32 HAL库高级定时器输入捕获脉宽测量


  • ✨相比于上面所使用的高级定时器输入捕获从模式来测量PWM信号,实现方法更为复杂一下,但是还是将实现的方法记录下来。
  • 🔖想了解详细的有关实现方法可以前往相关篇内容了解。
  • 🌿本篇基于STM32f030R8单片机。
📓本工程主要改善了,减少测量误差,将测量换算内容,换到了中断函数中执行,用时间换精度。并且采用多次读取测量结果,取多次连续测量值中,出现频率最高的数值作为输出值,有效减少单次数据异常情况。
  • 📑测量原理介绍
    通过定时器输入捕获功能,当一个外部信号输入,上升沿输入捕获,定时器产生中断,让定时器计数复位清零,并设置下一次输入捕获方式,改为下降沿输入捕获。在下一次输入捕获得到的计数值就是脉宽值。
  • 这种方法和采用定时器双通道输入捕获不同,只利用了定时器的一个输入捕获通道。
  • 🌿双通道输入捕获功能图:
    在这里插入图片描述
  • 双通道输入捕获测量原理:一个通道配置上升沿输入捕获中断,一个通道配置下降沿输入捕获通道。当有一个完整的PWM周期信号输入时,上升沿信号输入时,定时器配置的上升沿捕获模式CCR1通道产生中断,复位计数器,开始记录CCR1的值,当下升沿信号输入时,定时器配置的下降沿输入捕获通道CCR2产生中断,记录CCR2的值,此时CCR2的值就是脉宽值,再下一个上升沿信号过来,则是一个完整的信号周期捕获。

🛠STM32CubeMX配置

  • 🔧高级定时器1配置:(👉🏻在参数配置上,尽可能的将捕获定时器的自动载计数值设置大一点,减少定时器溢出更新事件)
    在这里插入图片描述
  • 🌿时钟源:外部时钟
  • 🌿定时器1分频系数,更具个人使用的芯片型号而定。
  • 🌿向上计数方式。
  • 🌿不开启自动重装载(auto-reload preload),相对于不使能影子寄存器。
  • 其他都是默认选项配置。
  • 🔨 配置定时器14作为一个PWM信号输出源:
    在这里插入图片描述

  • 🔰使能定时器捕获、更新中断,以及中断优先级配置:(TIM1更新中断 > TIM1捕获中断)
    在这里插入图片描述

📑功能代码实现部分

  • 📝中断回调函数:
/**
  * @brief 输入捕获回调函数
  * @retval None
  */
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef* htim)
{
    static uint8_t RisingEdge_count = 0;
    if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
    {
        if(capture_flag & 0x40)										//0X40是0100 0000,高电平期间捕获到下降沿
        {
            capture_flag &= 0x3F;										//0X3F是0011 1111,清除捕获到上升沿的标记位和捕获完成的标记位
            value2 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);	//获取当前的捕获值

            __HAL_TIM_DISABLE(htim);        						//关闭定时器
            __HAL_TIM_SET_COUNTER(htim, value2);						//以value2为基准重新计数
            TIM_RESET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1);      	//复位极性选择才能进行下行配置
					/*输入捕获功能的重配与开启,硬件启动会产生几个时钟的延迟*/
            TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_ICPOLARITY_RISING);	//下次上升沿捕获
					 
            __HAL_TIM_ENABLE(htim);									//重开定时器
        }
        else	            								 		//捕获到上升沿
        {
            capture_flag |= 0x40;										//0X40是0100 0000,标记捕捉到了一次上升沿

            RisingEdge_count++;
            if((RisingEdge_count % 2 == 0))								//每捕获两次相同跳变沿表示过了一个PWM周期
            {
                value3 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); //检测完一个周期的那个上升沿为value3
                capture_flag |= 0x80;								 //标记捕获完了一个周期
								Pulse_Width = value2 + OverflowCount_high * TIM1_Period_Value - value1 + 4;	//经验值:再进行4个时钟的补偿
                PWM_Period_ARR[PWM_Period_CNT++] = value3 + OverflowCount_high * TIM1_Period_Value + OverflowCount_low * TIM1_Period_Value - value1 + 7;
                if(PWM_Period_CNT == 5)
                {
                    PWM_Period = findMostFrequentNum(PWM_Period_ARR, 5);
                    PWM_Period_CNT = 0;
                }
                OverflowCount_high = OverflowCount_low = 0;//清空溢出计数
                __HAL_TIM_CLEAR_IT(&htim1, TIM_IT_UPDATE);//清零中断标志位
            }
            else				 								   //正在检测PWM信号的第一个上升沿,意味着下次捕获下降沿
            {
                capture_flag &= 0x7F;								 //0X7F是0111 1111,清除PWM捕获完成标志,开始新一轮PWM周期捕获
                value1 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); //第一个上升沿是value1
            }
            __HAL_TIM_DISABLE(htim);
            __HAL_TIM_SET_COUNTER(htim, value1);					//以value1为基准重新计数
            TIM_RESET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1);  	//复位极性选择才能进行下行配置
            TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_ICPOLARITY_FALLING);	//下次下降沿捕获
            __HAL_TIM_ENABLE(htim);								//重开定时器
        }
    }
}


/**
  * @brief (高级定时器TIM特有)更新溢出中断函数
  */
//void TIM1_UP_IRQHandler(void)
//{
//    HAL_TIM_IRQHandler(&htim1);
//}
/**
  * @brief 更新溢出回调函数
  * @retval None
  */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef* htim)
{
    if((capture_flag & 0X80) == 0)			//PWM的一个周期没检测完
    {
        if(capture_flag & 0x40)				//在高电平期间溢出M次
        {
            OverflowCount_high++;
        }
        else								//在低电平期间溢出N次
        {
            OverflowCount_low++;
        }
    }
    else								   //PWM的一个周期检测完了
    {

        OverflowCount_high = 0;
        OverflowCount_low = 0;
    }
}

  • ⚡连续读取的数值中获取出现频率最高的数值实现方法:
//比较函数,用于排序
int compare(const void* a, const void* b)
{
    return (*(int*)a - * (int*)b);
}

uint32_t findMostFrequentNum(uint32_t arr[], int size)
{
    // 对数组进行排序
    qsort(arr, size, sizeof(int), compare);//需要包含stdlib.h头文件

    int maxCount = 1; // 最大重复次数
    int currentCount = 1; // 当前元素的重复次数
    uint32_t mostFrequentNumber = arr[0]; // 最频繁出现的数

    for(int i = 1; i < size; i++)
    {
        if(arr[i] == arr[i - 1])
        {
            // 如果当前元素与前一个元素相同,则重复次数加1
            currentCount++;
        }
        else
        {
            // 如果当前元素与前一个元素不同,则重复次数重置为1
            currentCount = 1;
        }

        if(currentCount > maxCount)
        {
            // 如果当前元素的重复次数超过最大重复次数,则更新最大重复次数和最频繁出现的数
            maxCount = currentCount;
            mostFrequentNumber = arr[i];
        }
    }

    return mostFrequentNumber;
}
  • 📗main函数代码以及相关变量定义:
/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */
volatile char capture_flag = 0;				//捕获状态标记变量,0x80最高位标记捕获完一个周期,0x40表示捕获到了上升沿
volatile uint8_t OverflowCount_high = 0;		//高电平期间溢出次数
volatile uint8_t OverflowCount_low = 0;		//低电平期间溢出次数
volatile uint32_t value1, value2, value3;	  //下图中三个边沿中的值
volatile uint32_t Pulse_Width = 0;		  //脉宽
volatile uint32_t PWM_Period = 0;				//周期
uint32_t PWM_Period_ARR[5] = {0};
uint8_t PWM_Period_CNT = 0;
/* USER CODE END PV */

int main(void)
{
  /* USER CODE BEGIN 1 */
    uint32_t TimerUART;
//    uint16_t plusewidth = 500;//脉冲宽度;f=1000 000/500=2000Hz
//    uint16_t plusedelay = 20;//脉宽40us
  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_TIM1_Init();
  MX_TIM14_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
    uint32_t sysclock = 0;
//sysclock =RCC_GetHSIFreq();
    sysclock = HAL_RCC_GetSysClockFreq();//RCC_GetHSIFreq()
    TimerUART = HAL_GetTick();
    printf("STM32F030 SysClockFreq:%d \r\n", sysclock);
//		__HAL_TIM_SET_AUTORELOAD(&htim14, plusewidth - 1); //调整分频系数,可以改变arr以改变频率
//    __HAL_TIM_SET_COMPARE(&htim14, TIM_CHANNEL_1, plusedelay); //PWM脉冲宽度,修改占空比比较值
    /* 使能PWM输出 */
    HAL_TIM_PWM_Start(&htim14, TIM_CHANNEL_1);
    /* 清零中断标志位 */
    __HAL_TIM_CLEAR_IT(&htim1, TIM_IT_UPDATE);
    /* 使能定时器的更新事件中断 */
    __HAL_TIM_ENABLE_IT(&htim1, TIM_IT_UPDATE);
    /* 使能输入捕获 */
    HAL_TIM_IC_Start_IT(&htim1, TIM_CHANNEL_1);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
    while(1)
    {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
        if((HAL_GetTick() - TimerUART) > 1000)
        {
          
            printf("/********************/\r\n");
            printf("脉宽为: %d us\r\n", Pulse_Width);
            printf("周期为:	%d us\r\n", PWM_Period);
            printf("/********************/\r\n");
            TimerUART = HAL_GetTick();
        }
    }
  /* USER CODE END 3 */
}
  • 📜测试调试输出:
    在这里插入图片描述
    在这里插入图片描述
  • ✨修改PWM相关参数测试了几次,结果也没有发现测量的输出结果异常,符合预期。
📚测试工程
链接:https://pan.baidu.com/s/1pgs9OFyiI291pxBMsuutdQ 
提取码:rotp

定时器1双通道输入捕获实现脉宽和周期计数

  • 基于STM32F446
  • 中断处理函数:
/**
  * @brief 输入捕获回调函数
  * @retval None
  */
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef* htim)
{

    if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
    {
      __HAL_TIM_CLEAR_FLAG(htim, TIM_FLAG_CC1);          //清除中断标志位
       __HAL_TIM_SET_COUNTER(htim,0 );    // 重装载计数器值 0
       
      // HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
       PWM_CYCLE = htim1.Instance->CCR1;
      if(OverflowCount > 0)
      {
        PWM_CYCLE += OverflowCount*65536;
      }
       OverflowCount = 0;             // 溢出次数清零 
      }

     if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2)
    {
      __HAL_TIM_CLEAR_FLAG(htim, TIM_FLAG_CC2);          //清除中断标志位
       PWM1_Duty = htim1.Instance->CCR2;
       if (OverflowCount > 0) // 如果计数器溢出过
        {
          PWM1_Duty += OverflowCount * 65536;
			HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); // LED闪烁
        }
      }

}

/**
  * @brief 更新溢出回调函数
  * @retval None
  */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef* htim)
{
	if (__HAL_TIM_GET_IT_SOURCE(htim,TIM_FLAG_UPDATE) != RESET)       //计数器更新中断
	{
  __HAL_TIM_CLEAR_FLAG(htim, TIM_FLAG_UPDATE);          //清除中断标志位
		OverflowCount++;

	}
  if(OverflowCount >= 1000) //每1000个溢出周期更新一次PWM占空比
  {
    OverflowCount = 0;
  //  printf("PWM1_Duty=%d,PWM_CYCLE=%d\r\n",PWM1_Duty,PWM_CYCLE);
    HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); //LED闪烁
  }
}
  • main执行内容:
......
  /* USER CODE BEGIN 2 */
 /* 使能输入捕获 */
    HAL_TIM_IC_Start_IT(&htim1, TIM_CHANNEL_1); //上升沿输入捕获中断
  HAL_TIM_IC_Start_IT(&htim1, TIM_CHANNEL_2); //上升沿输入捕获中断
__HAL_TIM_CLEAR_FLAG(&htim1,TIM_FLAG_UPDATE);			//清除中断标志位
    __HAL_TIM_CLEAR_FLAG(&htim1,TIM_FLAG_CC1);			//清除中断标志位
    __HAL_TIM_CLEAR_FLAG(&htim1,TIM_FLAG_CC2);			//清除中断标志位
 Tick_Time = HAL_GetTick(); //获取当前时间
  /* USER CODE END 2 */

......

 while (1)
  {
 if (Tick_Time+1000 < HAL_GetTick()) //每隔1秒更新一次PWM占空比
    {

      printf("PWM1_Duty=%d,PWM_CYCLE=%d\r\n",PWM1_Duty,PWM_CYCLE);
     Tick_Time = HAL_GetTick();
     HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); //LED闪烁
    }
    }
  • 由于定时器输入捕获计数器(CCR2)溢出问题,只能测量大于12Hz的信号,小于100KHz。

  • 🔖此文章仅作为个人学习探索知识的总结,不作为他人或引用者的理论依据,由于学识所限,难免会出现错误或纰漏,欢迎大家指正。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值