STM32定时器实战全解析:SysTick+TIM外设+PWM应用

定时器是STM32嵌入式开发中当之无愧的“核心外设”,从简单的毫秒级延时、定时中断,到复杂的PWM调速、输入捕获,几乎所有项目都离不开它。本文结合实战经验,从基础概念到代码落地,全方位拆解STM32定时器的使用技巧,尤其适合嵌入式新手快速上手。

一、定时器基础认知

1.1 定时器分类(以STM32F407ZGT6为例)

STM32的定时器按功能和复杂度分为三类:

  • 基本定时器(TIM6、TIM7):仅支持定时中断,无IO通道,常用于简单定时;
  • 通用定时器(TIM2-TIM5、TIM9-TIM14):支持定时中断、PWM输出、输入捕获,功能全面;
  • 高级定时器(TIM1、TIM8):在通用定时器基础上,增加死区控制、刹车功能,适合电机控制等场景。

1.2 时基单元核心组成

无论哪种定时器,核心都是“时基单元”,由4部分组成:

  • 时钟源:定时器的计数时钟(内部时钟/外部时钟/其他定时器触发);
  • 预分频器(PSC):对时钟源分频,降低计数频率;
  • 自动重装载寄存器(ARR):计数上限,计数器到该值后溢出;
  • 计数器(CNT):当前计数值,支持向上/向下/中心对齐计数。

二、两类核心定时器:SysTick vs TIM外设

STM32中有两类“定时器”,定位不同,需按需选择:

2.1 内核级定时器:SysTick(滴答定时器)

SysTick是Cortex-M4内核自带的24位递减定时器,无需占用片上TIM外设,核心用于提供系统时基(比如HAL库的HAL_Delay)。

2.1.1 核心特性
  • 归属:ARM Cortex-M4内核,所有M4芯片通用;
  • 位数:24位递减计数器,最大计数值2^24=16777216
  • 时钟源:可选“内核时钟(168MHz)”或“AHB8分频(21MHz)”,HAL库默认AHB8分频;
  • 触发机制:设定初值后,每1个时钟周期计数值-1,减至0触发中断,自动重装初值循环。
2.1.2 HAL_Delay实现原理(源码级解析)

HAL库的HAL_Delay是基于SysTick实现的阻塞延时函数,源码如下:

#define HAL_MAX_DELAY      0xFFFFFFFFU
HAL_TickFreqTypeDef uwTickFreq = HAL_TICK_FREQ_DEFAULT; // 默认1KHz(1ms)

__weak void HAL_Delay(uint32_t Delay)
{
  uint32_t tickstart = HAL_GetTick(); // 获取初始时间戳
  uint32_t wait = Delay;

  // 防0操作:避免Delay=0时无等待,引入1ms误差
  if (wait < HAL_MAX_DELAY)
  {
    wait += (uint32_t)(uwTickFreq);
  }

  // 死循环等待,直到时间差≥目标延时
  while((HAL_GetTick() - tickstart) < wait)
  {
  }
}

⚠️ 关键注意
HAL_Delay是阻塞函数,中断中禁止调用(会导致中断阻塞,甚至程序卡死)。

2.1.3 核心寄存器

SysTick仅需3个寄存器即可控制:

  • CTRL:控制寄存器(定时器使能、时钟源选择、中断使能、计数标志);
  • LOAD:重装载寄存器(存储计数初值);
  • VAL:当前值寄存器(存储当前计数值)。

2.2 片上外设:TIM定时器(TIM1~TIM14)

TIM外设是嵌入式开发的“主力定时器”,支持定时中断、PWM、输入捕获等复杂功能,以下是实战配置与代码解析。

2.2.1 CubeMX配置步骤(核心)
  1. 时钟来源:默认选择“内部时钟(internal clock)”;
  2. 时基配置:设置预分频值(PSC)、计数方式(向上/向下)、自动重装载值(ARR);
  3. 中断配置:NVIC中开启定时器中断,设置中断优先级;
  4. 功能扩展:如需PWM/输入捕获,配置对应通道为“PWM输出/输入捕获模式”,并使能对应引脚。

TIM配置cube界面图

📌 计算示例
若时钟频率为4MHz,需实现1秒定时:

  • 预分频值:4000-1(分频后计数频率=4MHz/4000=1kHz,计数1次耗时1ms);
  • 自动重装载值:1000-1(计数1000次耗时1ms×1000=1s)。

TIM配置cube界面2

2.2.2 TIM定时器初始化代码(以TIM6为例)
/* TIM6 init function */
void MX_TIM6_Init(void)
{
  TIM_MasterConfigTypeDef sMasterConfig = {0};

  htim6.Instance = TIM6;
  htim6.Init.Prescaler = 84-1; // 预分频器:84分频→1MHz(1us/次)
  htim6.Init.CounterMode = TIM_COUNTERMODE_UP; // 向上计数
  htim6.Init.Period = 1000-1; // ARR:计数1000次→1ms
  htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htim6) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim6, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  // 开启定时器中断
  HAL_TIM_Base_Start_IT(&htim6);
}
2.2.3 定时器中断回调实战(100ms翻转LED)

定时器中断无需主动调用,达到定时周期后自动触发,重写HAL库的弱回调函数即可实现自定义逻辑:

int count = 0;
// 定时器周期溢出回调函数(弱函数,可重写)
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)		
{
  if(htim == &htim6)	// 判断定时器实例为TIM6
  {
    count++;
    if(count>=100) // 1ms×100=100ms
    {
      count = 0;
      // 翻转PE13引脚电平(控制LED)
      HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_13);
    }
  }
}
2.2.4 实战扩展:1秒串口上报“我来了”

结合定时器与串口,实现1秒定时上报,核心代码如下:

volatile int tim_count = 0; // 中断变量必须加volatile,防止编译器优化
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)		
{
  if(htim == &htim6)	
  {
    tim_count++;
    if(tim_count>=1000) 
    {
      tim_count = 0;
      // 串口非阻塞发送(注意:strlen计算有效字符长度,避免多发\0)
      HAL_UART_Transmit_IT(&huart1,(uint8_t*)"我来了",strlen((char*)"我来了"));
    }
  }
}

⚠️ 易错点:若用sizeof("我来了")会包含末尾的\0,导致串口多发1个无效字符,需用strlen计算有效长度。

三、PWM核心应用(基于TIM定时器)

PWM(脉宽调制)是TIM定时器的核心应用(电机调速、LED调光、舵机控制等),必须掌握原理与参数计算。

3.1 PWM核心原理

通过比较计数器(CNT)与捕获/比较寄存器(CCR)的值,控制IO口电平翻转:

  • 向上计数模式(最常用):CNT < CCR → 高电平;CNT ≥ CCR → 低电平;
  • 一个定时器最多输出4路PWM(4个通道),每个通道对应独立的CCR寄存器。

3.2 关键参数计算

// 分辨率(CCR的最小调整步距)
分辨率 = 1 / (ARR + 1)

// PWM频率
频率 = CK_PSC / [(PSC + 1) * (ARR + 1)]

// 占空比
占空比 = CCR / (ARR + 1)

📌 示例计算:
若CK_PSC=84MHz,PSC=84-1,ARR=100-1:

  • 频率 = 84MHz / (84 × 100) = 10KHz;
  • 分辨率 = 1/100 = 1%;
  • 调整CCR值即可改变占空比(如CCR=50 → 占空比50%)。

四、开发避坑指南(必看)

  1. volatile关键字:定时器/中断相关变量必须加volatile,防止编译器优化(确保每次从内存读取,而非寄存器缓存);
  2. NVIC配置:定时器中断需设置优先级并使能,同一条中断线的定时器(如TIM1/TIM10)需避免优先级冲突;
  3. 总线挂载:TIM定时器分属APB1(低速,如TIM6/7)、APB2(高速,如TIM1/8)总线,需参考芯片手册确认;
  4. SysTick与TIM选择:简单延时用SysTick,复杂定时/PWM/输入捕获用TIM外设;
  5. 寄存器操作:STM32无位操作指令,需通过“按位与/按位或+位移”操作寄存器;
  6. 中断嵌套:定时器中断优先级需合理设置,避免高优先级中断阻塞核心业务逻辑。

五、总结

STM32定时器的核心是“分频+计数+中断/输出”,掌握分类、时基计算、CubeMX配置是基础,结合实战(定时中断、PWM)才能真正落地。

开发中建议:

  • 简单延时用SysTick(HAL_Delay),但中断中禁用;
  • 复杂定时/外设控制用TIM外设,善用HAL库简化开发;
  • 结合芯片手册(定时器章节)理解寄存器细节,兼顾效率与可读性。

希望本文能帮助大家摆脱定时器“配置难、调试卡”的问题,真正做到学以致用!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值