定时器详解以及呼吸灯实现 — STM32(HAL库)

1. 定时器的分类

以下举例以STM32F103ZET6进行举例。

定时器分为3类,分别为基本定时器(2个),通用定时器(4个),高级定时器(2个)。

它们都是16位自动装载计数器(0 ~ 65535)

基本定时器只能配置为向上计数,通用和高级定时器可以配置为向上计数、向下计数、向上/向下计数。

基本定时器:功能最少,只有计时的功能,Tim6Tim7

通用定时器:除了可以进行计时之外,还可以输入捕获、输出比较、生成PWM方波,Tim2~5

高级定时器:除了上述的所有功能外,还额外增加了死区时间插入功能、互补 PWM 输出、刹车(Break)功能、重复计数器(Repetition Counter)等功能,Tim1Tim8

2. 定时器框图

STM32的众多定时器中我们使用最多的是高级定时器和通用定时器,高级定时器功能较复杂,日常开发中通用定时器使用更广泛,下面我们就以通用定时器为例进行讲解,其功能和特点包括:

  • 位于低速的APB1总线上(APB1) (注意:高级定时器位于高速的APB2总线上)
  • 16 位向上、向下、向上/向下(中心对齐)计数模式,自动装载计数器(TIMx_CNT)。
  • 16 位可编程(可以实时修改)预分频器(TIMx_PSC),计数器时钟频率的分频系数 为 0~65535 之间的任意数值。
  • 4 个独立通道(TIMx_CH1~4),这些通道可以用来作为: 
输入捕获测量外部信号的周期、占空比(如编码器、红外信号)
输出比较控制 GPIO 的翻转时间点(如精确延时)
PWM 生成电机驱动、LED 调光、音频输出
单脉冲模式(One Pulse Mode, OPM)用于一次性的脉冲输出(如超声波测距)
  • 可使用外部信号(TIMx_ETR)控制定时器和定时器级连(可以用 1 个定时器控制另外一个定时器)的同步电路。
  • 在 STM32 中,每个定时器的核心功能模块是相互独立的,可以独立配置、独立运行,互不干扰。但在系统层面的资源(如时钟总线、中断向量)存在部分共享。

大部分情况,内部时钟源足够使用。不配置时钟源的情况下,默认选择的就是内部时钟源。

    3. 时基单元

    定时器框图中,使用黄色框圈住部分就是定时器的时基单元,包含3个部分的内容:预分频寄存器(PSC)、计数器寄存器(CNT)、自动重装载寄存器(ARR)。

    • 计数器寄存器:可以通过配置,控制其计数方式为向上计数、向下计数、向上/向下计数(中心对齐计数)。
    • 预分频寄存器:是 16 位寄存器,取值范围为 0~65535,而实际的分频系数计算公式为 “分频系数 = PSC + 1”。
    • 自动重装载寄存器:是 16 位寄存器,取值范围为 0~65535,对应的计数范围为1~65536(既ARR + 1)

    eg:如果配置为向上计数,每来一个计数时钟,CNT便会加1,当 CNT 的值等于 ARR时,此时还未产生更新事件,只是达到了预设的最大计数值,下一个计数时钟到来时,CNT 无法继续递增(因 ARR 是最大值),会溢出并重置为 0,溢出的同时,硬件自动触发 “更新事件”(Update Event);若已使能更新中断(通过TIM_ITConfig),则会产生中断。

    4. 使用通用定时器实现1s触发一次中断(Hal库 - Tim2举例)

    使用定时器实现led灯定时亮灭

    • 定时器配置(通过CubeMX软件,配置Tim2的时钟源为内部时钟,PSC为7199以及ARR为9999 LED灯的引脚这里选择PA1,根据电路的原理图配置 —— 生成代码,自动生成如下内容)
    TIM_HandleTypeDef htim2;
    
    /* TIM2 init function */
    void MX_TIM2_Init(void)
    {
    
      /* USER CODE BEGIN TIM2_Init 0 */
    
      /* USER CODE END TIM2_Init 0 */
    
      TIM_ClockConfigTypeDef sClockSourceConfig = {0};
      TIM_MasterConfigTypeDef sMasterConfig = {0};
    
      /* USER CODE BEGIN TIM2_Init 1 */
    
      /* USER CODE END TIM2_Init 1 */
      htim2.Instance = TIM2;
      htim2.Init.Prescaler = 7199;
      htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
      htim2.Init.Period = 9999;
      htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
      htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
      if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
      {
        Error_Handler();
      }
      sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
      if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
      {
        Error_Handler();
      }
      sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
      sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
      if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
      {
        Error_Handler();
      }
      /* USER CODE BEGIN TIM2_Init 2 */
    
      /* USER CODE END TIM2_Init 2 */
    
    }
    • 中断服务函数(通过重写回调函数实现小灯引脚的电平翻转)
    void TIM2_IRQHandler(void) {
        HAL_TIM_IRQHandler(&htim2);     // 调用HAL库中断处理
    }
    
    
    // 当前的中断回调函数触发的条件为:更新事件(CNT 溢出)
    void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
    {
      if (htim->Instance == TIM2)
      {
        // 每秒执行一次翻转LED灯
        HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
      }
    }
    • 调用API启动定时器中断(在main函数中的MX_TIM2_Init();之后调用启动定时器中断的API)
    if (HAL_TIM_Base_Start_IT(&htim2) != HAL_OK) // 启动TIM2并使能更新中断
    {
       Error_Handler();
    }

    5. 使用通用定时器生成PWM(Tim5_CH2为例)

    使用PWM方波实现呼吸灯的效果


    PWM:脉冲宽度调制,“Pulse Width Modulation”的缩写,简称脉宽调制

    • 周期、频率、占空比(高电平占整个周期的比例)
    • 一般用于具有惯性的场景(比如LED亮度调节,控制电机)

    周期:连续两个上升沿或者连续两个下降沿之间的宽度,使用T表示     

    频率:1 / T(周期)           
    一般不改变频率和周期,通过改变占空比达到对外输出的有效电压的值       

    • 定时器配置(除了上面的配置之外,选择通道2 — PWM Generation CH1,配置PSC为71,ARR为99,PWM为mode1,脉冲为50,高电平为有效电平 —— 生成代码)
    TIM_HandleTypeDef htim5;
    
    /* TIM5 init function */
    void MX_TIM5_Init(void)
    {
    
      /* USER CODE BEGIN TIM5_Init 0 */
    
      /* USER CODE END TIM5_Init 0 */
    
      TIM_ClockConfigTypeDef sClockSourceConfig = {0};
      TIM_MasterConfigTypeDef sMasterConfig = {0};
      TIM_OC_InitTypeDef sConfigOC = {0};
    
      /* USER CODE BEGIN TIM5_Init 1 */
    
      /* USER CODE END TIM5_Init 1 */
      htim5.Instance = TIM5;
      htim5.Init.Prescaler = 71;
      htim5.Init.CounterMode = TIM_COUNTERMODE_UP;
      htim5.Init.Period = 99;
      htim5.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
      htim5.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
      if (HAL_TIM_Base_Init(&htim5) != HAL_OK)
      {
        Error_Handler();
      }
      sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
      if (HAL_TIM_ConfigClockSource(&htim5, &sClockSourceConfig) != HAL_OK)
      {
        Error_Handler();
      }
      if (HAL_TIM_PWM_Init(&htim5) != HAL_OK)
      {
        Error_Handler();
      }
      sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
      sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
      if (HAL_TIMEx_MasterConfigSynchronization(&htim5, &sMasterConfig) != HAL_OK)
      {
        Error_Handler();
      }
      sConfigOC.OCMode = TIM_OCMODE_PWM1;
      sConfigOC.Pulse = 50;
      sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
      sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
      if (HAL_TIM_PWM_ConfigChannel(&htim5, &sConfigOC, TIM_CHANNEL_2) != HAL_OK)
      {
        Error_Handler();
      }
      /* USER CODE BEGIN TIM5_Init 2 */
    
      /* USER CODE END TIM5_Init 2 */
      HAL_TIM_MspPostInit(&htim5);
    
    }
    • 启动 TIM5 通道 2 的 PWM 输出(否则无波形输出)
    // 注意:在main函数,MX_TIM5_Init();之后,while循环之前调用
    HAL_TIM_PWM_Start(&htim5, TIM_CHANNEL_2);
    • 实现呼吸灯的逻辑在while循环中(也可以单独创建一个函数,放在函数中实现)
    // 通过改变脉冲值来改变占空比,实现呼吸灯的效果
    // 原理:在PWM mode 1下,计数器CNT < 脉冲值pulse,则输出有效电平(这里配置为高电平,可根据需求修改极性)
    uint16_t pulse = 0;
    for (pulse = 0; pulse <= 99; pulse++)
    {
      __HAL_TIM_SET_COMPARE(&htim5, TIM_CHANNEL_2, pulse);
      HAL_Delay(10);
    
    for (pulse = 99; pulse > 0; pulse--)
    {
      __HAL_TIM_SET_COMPARE(&htim5, TIM_CHANNEL_2, pulse);
      HAL_Delay(10);
    }

    这样就实现了呼吸灯的效果了,但是这样会导致阻塞主循环(如果程序中还包含其他的逻辑,那么就推荐使用中断的方式去修改脉冲值会更好)

    6. 呼吸灯优化(使用中断回调方式)

    思路:

    方案一:当前使用Tim5来输出PWM,可以使用另一个定时器固定周期触发一次中断,每次中断修改定时器5的脉冲值Pluse,控制占空比递增/递减,形成呼吸效果(但是这样就需要使用到两个定时器,如果定时器资源较为紧张,那么则推荐使用方案二)

    方案二:复用 PWM 定时器自身的更新中断,调整 ARR 同时兼顾 PWM 频率和中断周期

    由于现实场景中方案二使用更多,所以通过方案二举例:

    调整 ARR 同时兼顾 PWM 频率和中断周期:中断周期 10ms,PWM 频率 1kHz(仍无频闪),配置如下:

    • PSC=71(计数时钟 = 72MHz/(71+1)=1MHz);
    • ARR=999(PWM 周期 = 1000×1μs=1ms,中断周期 = 1ms,即每 1ms 触发一次更新中断)。
    • 开启定时器5的中断(在CubeMX中进行勾选,设置ARR为999,其他内容保持不变)
    // 开启了定时器5的中断之后,系统会自动调用中断处理函数,如下:
    void TIM5_IRQHandler(void)
    {
      /* USER CODE BEGIN TIM5_IRQn 0 */
    
      /* USER CODE END TIM5_IRQn 0 */
      HAL_TIM_IRQHandler(&htim5);
      /* USER CODE BEGIN TIM5_IRQn 1 */
    
      /* USER CODE END TIM5_IRQn 1 */
    }
    • 通过定时器中断去改变定时器的脉冲值(逻辑为:每触发10次中断 — 10ms,对脉冲值pluse进行加1或者减1的操作,并更新脉冲值)
    // 首先声明全局变量
    uint16_t pwm_pulse = 0;
    int8_t pulse_step = 1;
    
    
    // 同样在main函数的MX_TIM5_Init();之后,调用
    HAL_TIM_PWM_Start_IT(&htim5, TIM_CHANNEL_2);
    
    
    // 重写中断回调函数(可以写在任意位置 - 不要写在某一个函数里面)
    // 这个中断回调函数触发的条件是:比较匹配事件(CNT == CCRx)比之前的中断回调函数更合适
    void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim)
    {
      if (htim->Instance == TIM5)
      {
        static uint8_t interrupt_count = 0;
        if (++interrupt_count >= 10)
        {
          interrupt_count = 0;
    
          pwm_pulse += pulse_step;
          if (pwm_pulse >= 999)
            pulse_step = -1;
          if (pwm_pulse <= 0)
            pulse_step = 1;
    
          __HAL_TIM_SET_COMPARE(&htim5, TIM_CHANNEL_2, pwm_pulse);
        }
      }
    }

    补充内容:“系统定时器(滴答定时器)属于 CM3 内核,内嵌在 NVIC 中,是一个 24bit 的向下递减计数器。计数器寄存器 VAL 每计数一次的时间为 1/SYSCLK(一般配置 SYSCLK 为 72MHz)。当 VAL 递减到 0 时,产生一次中断,随后自动将重装载寄存器 LOAD 的值加载到 VAL 中,循环往复。”

    一般使用系统滴定时器作为系统时基,简单的延时函数,不占用芯片外设资源,仅消耗内核资源。

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

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

    抵扣说明:

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

    余额充值