蓝桥杯嵌入式STM32G431定时器入门:从原理到CubeMX配置(LL库)

一、STM32G431有多少个定时器?

STM32G431RBT6是蓝桥杯嵌入式竞赛的指定芯片,它的定时器资源非常丰富,共有10个定时器,分为四种类型:

类型 定时器编号 数量 特点
高级控制定时器 TIM1、TIM8 2 16位,支持互补输出、死区插入、编码器接口,常用于电机控制
全功能通用定时器 TIM2、TIM3、TIM4 3 16位,支持输入捕获、输出比较、PWM、编码器接口
通用定时器(简化版) TIM15、TIM16、TIM17 3 只有1~2个通道,功能较少
基本定时器 TIM6、TIM7 2 16位,只能做定时中断和触发DAC,没有输入输出通道

另外还有SysTick(内核滴答定时器)、看门狗定时器和RTC等,但这些不属于通用定时器范畴。

💡 蓝桥杯常用选择:TIM2、TIM3、TIM4这三个全功能通用定时器最常用,功能齐全且配置简单。TIM6/TIM7只能做定时中断,如果只需要简单的定时功能也可以用。

二、定时器的内部结构

STM32定时器的核心是一个16位计数器,围绕三个关键寄存器工作:

复制代码
时钟源(CK_INT) → 预分频器(PSC) → 计数器(CNT) → 与ARR比较 → 触发更新事件

1. 预分频器 PSC

预分频器对输入的时钟信号进行分频。PSC是一个16位寄存器,可设置0~65535。实际分频系数 = PSC + 1。比如PSC设为7999,就是把时钟除以8000。

2. 计数器 CNT

计数器根据分频后的时钟脉冲进行计数。每来一个脉冲,CNT加1(向上计数模式)。

3. 自动重装载寄存器 ARR

ARR决定了计数器的上限。当CNT的值等于ARR时,产生更新事件,CNT自动清零,重新开始计数。同时可以触发中断、DMA请求等。

4. 影子寄存器

ARR和PSC都有影子寄存器(也叫预装载寄存器)。修改ARR的值时,新值不会立即生效,而是在下一次更新事件时才被载入影子寄存器。这样保证定时器在运行过程中修改参数不会产生毛刺。

💡 通俗理解:把定时器想象成一个沙漏。PSC决定沙子流的速度(分频),CNT是已经流下的沙子量,ARR是沙漏的总容量。沙子流满(CNT=ARR)就翻转一次,产生一个"更新事件"。

三、定时器的模式简介(本实验只用到一种)

定时器有很多种"玩法",但按大类分其实就两类:

  • 时基模式 :不占用通道,只利用计数器本身。包括向上计数、向下计数、中央对齐三种。本实验用的就是向上计数模式,配合更新中断实现固定周期的定时。
  • 通道模式:必须绑定通道才能用。包括输出比较、PWM输出、输入捕获、编码器模式等。这些模式用于输出波形或测量外部信号,本实验暂时用不到,知道名字即可,后续用到再查。

所以间隔闪烁LED只需要最基础的向上计数模式 + 更新中断,其他模式不用管。CubeMX里默认就是向上计数,不用额外修改。

四、频率计算方法

核心公式

复制代码
定时器计数频率 = 定时器时钟源频率 / (PSC + 1)
中断周期 = (ARR + 1) / 计数频率

合并成一个公式:

复制代码
中断周期 = (PSC + 1) × (ARR + 1) / 定时器时钟源频率

时钟源从哪里来?

蓝桥杯开发板的主频配置为80MHz。定时器的时钟源来自APB总线:

  • TIM1、TIM8、TIM15、TIM16、TIM17 挂在 APB2 总线上
  • TIM2、TIM3、TIM4、TIM6、TIM7 挂在 APB1 总线上

当APB预分频器为1时,定时器时钟 = APB总线时钟 = 80MHz。

1秒定时怎么算?

假设使用TIM3,时钟源80MHz,要产生1秒的定时中断:

复制代码
方案一:PSC = 8000 - 1 = 7999,ARR = 10000 - 1 = 9999
  计数频率 = 80MHz / 8000 = 10kHz
  中断周期 = 10000 / 10kHz = 1秒 ✓

方案二:PSC = 800 - 1 = 799,ARR = 100000 - 1 = 99999
  计数频率 = 80MHz / 800 = 100kHz
  中断周期 = 100000 / 100kHz = 1秒 ✓

两个方案都能实现1秒定时,区别在于计数频率不同。方案一计数慢一些,适合低功耗场景;方案二精度更高。蓝桥杯比赛中,常用 PSC=7999、ARR=9999 这组参数。

⚠️ 注意:ARR的值不能超过65535(16位寄存器的上限),所以如果PSC设得比较小,ARR可能放不下。这时候需要增大PSC。

五、CubeMX配置步骤

这边工程创建和led配置就不讲不了有需要的可以去看看嵌入式蓝桥杯从零点亮第一个LED

1. 选择定时器

在CubeMX的Pinout & Configuration界面,左侧找到Timers,展开后选择TIM1(或其他定时器)。

2. 配置时钟源

Clock Source 设置为 Internal Clock ,表示使用内部时钟。

3. 配置参数

在Parameter Settings选项卡中设置:

  • Prescaler (PSC):7999(即8000分频)
  • Counter Mode:Up(向上计数,本实验就用这个模式)
  • Counter Period (ARR):9999
  • 其他保持默认

4. 使能中断

切换到 NVIC Settings 选项卡,勾选 TIM1 global interrupt ,这样定时器就配置好了

5. 配置RCC和SYS

  1. 在左侧 Categories 分类栏,点击展开 System Core(第一个红框)。
  2. 在 System Core 的子列表里,选中 RCC(第二个红框),页面右侧会弹出 RCC 配置面板。
  3. 在右侧 RCC Mode and Configuration 区域找到 High Speed Clock (HSE) 下拉菜单。
  4. 点击下拉框,选择 Crystal/Ceramic Resonator
  5. 在右侧 SYS Mode and Configuration 区域找到 Debug 下拉选项。
  6. 点击下拉框,选择 Serial Wire(上方红框)。
  7. led配置点击前面发的链接

6. 配置时钟树

  1. 点击顶部标签栏 Clock Configuration(上方红色框),切换到时钟树配置页面。
  2. 在时钟树框图找到 HCLK (MHz) (右侧红色框),此处为 AHB 总线时钟,当前配置为 80MHz,芯片最大支持 170MHz。

7. 设置工程名称与开发工具链

  1. 点击顶部标签栏 Project Manager(上方红框),切换到工程管理页面。
  2. Project Settings 区域找到 Project Name ,输入工程名:TIM1(左上红框)。
  3. 找到 Toolchain / IDE 下拉菜单,点击展开,选择 MDK-ARM (下方红框,Keil MDK 开发环境)。

8. 配置代码生成选项

切换到 Project Manager 页面下的 Code Generator 选项卡,勾选Generate peripheral initialization as a pair of '.c/.h' files per peripheral,使每个外设生成独立的初始化源文件与头文件。

9. 生成代码

确保工程使用的库是 LL库 (在Project Manager → Advanced Settings中将TIM3的库选择改为LL)。

六、实验:间隔闪烁LED

6.1 实验目标

用TIM3每1秒产生一次中断,在中断中翻转LED的电平,实现LED每隔1秒闪烁一次。

6.2 CubeMX生成的初始化代码

c 复制代码
// tim.c 中自动生成
void MX_TIM3_Init(void)
{
    LL_TIM_InitTypeDef TIM_InitStruct = {0};

    // 使能TIM3时钟
    LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_TIM1);
		
	//配置中断
	NVIC_SetPriority(TIM1_UP_TIM16_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(),0, 0));
  	NVIC_EnableIRQ(TIM1_UP_TIM16_IRQn);
    // 配置时基参数
    TIM_InitStruct.Prescaler = 7999;
  	TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP;
  	TIM_InitStruct.Autoreload = 9999;
  	TIM_InitStruct.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1;
  	TIM_InitStruct.RepetitionCounter = 0;
  	LL_TIM_Init(TIM1, &TIM_InitStruct);
  	
  	LL_TIM_DisableARRPreload(TIM1);
  	LL_TIM_SetClockSource(TIM1, LL_TIM_CLOCKSOURCE_INTERNAL);
  	LL_TIM_SetTriggerOutput(TIM1, LL_TIM_TRGO_RESET);
 	LL_TIM_SetTriggerOutput2(TIM1, LL_TIM_TRGO2_RESET);
  	LL_TIM_DisableMasterSlaveMode(TIM1);
  	
  	//下面这二行要自己加进去
    LL_TIM_EnableIT_UPDATE(TIM1);//使能中断更新
	LL_TIM_EnableCounter(TIM1);//使能计数
}

6.3 中断服务函数

stm32g4xx_it.c 中编写TIM1的中断处理函数:

c 复制代码
void TIM1_UP_TIM16_IRQHandler(void)
{
	if(LL_TIM_IsActiveFlag_UPDATE(TIM1) == 1 )//读取更新中断标志位
  {
    ucled ^= 0X01;  // 翻转变量
    LL_TIM_ClearFlag_UPDATE(TIM1); // 清除中断标志
  }
}

⚠️ LL库的关键区别 :与HAL库不同,LL库的中断服务函数必须手动清除标志位 LL_TIM_ClearFlag_UPDATE()。如果忘记清除,中断会反复触发,程序会卡死。

6.4 main函数

c 复制代码
int main(void)
{
  LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_SYSCFG);
  LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_PWR);
  
  NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);

  NVIC_SetPriority(SysTick_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(),15, 0));


  LL_PWR_DisableUCPDDeadBattery();


  SystemClock_Config();


  MX_GPIO_Init();
  MX_TIM1_Init();

  while (1)
  {
    /* USER CODE END WHILE */
		Led_Disp(ucled);
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

6.5 实验效果

LED每隔1秒 翻转一次状态,亮1秒、灭1秒,形成周期为2秒的闪烁。如果发现闪烁太快或太慢,检查PSC和ARR的计算是否正确,以及定时器的时钟源是否真的是80MHz。

需要完整工程文档的可以点击下载STM32G431 TIM1 定时器 LL 库工程(蓝桥杯嵌入式)

七、总结

STM32G431有10个定时器,蓝桥杯常用TIM2/3/4。定时器的模式很多,但本实验只需要最简单的向上计数模式 + 更新中断 。定时器本质上是一个"分频计数器",PSC决定计数速度,ARR决定计数上限,两者配合就能产生任意周期的定时中断。用LL库开发的关键三步是:CubeMX配置参数 → 使能中断 → 中断中手动清标志位。把这三点搞清楚,定时器的基础应用就不成问题了。其他模式(PWM、输入捕获等)等需要时再查即可。

相关推荐
捷瑞电子工坊1 天前
嵌入式蓝桥杯从零点亮第一个LED
stm32·蓝桥杯·cubemx·嵌入式·led·ll·锁存
阿钱真强道21 天前
24 极物科技 | DALI协议栈 - 定时器与中断系统
定时器·曼彻斯特编码·极物·极物智能·极物科技·dali协议栈·智慧照明
jufeng130722 天前
STM32F103 定时器输入捕获实战:测频测周、编码器测速、红外解码全流程拆解
stm32·输入捕获·定时器·编码器
啦啦啦啦啦zzzz1 个月前
时间轮定时器
linux·服务器·网络·c++·定时器
保安大队王队长2 个月前
定时器知识
定时器
Dlrb12112 个月前
ARM---IMX6ULL定时器详解与应用:EPIT与GPT定时器,高精度延时函数
arm开发·gpt·arm·定时器·epit·高精度延时
名字还没想好☜2 个月前
Go 的 time.Ticker 陷阱:定时任务里被忽略的内存泄漏与正确关闭
java·数据库·golang·go·定时器
Dlrb12112 个月前
51单片机--GPIO、按键、中断、蜂鸣器、PWM
单片机·嵌入式硬件·51单片机·定时器·中断
Qt程序员3 个月前
内核定时底层核心:时间轮算法
linux·linux内核·定时器·时间轮