普中STM32F103ZET6开发攻略(四)
接续上文:普中STM32F103ZET6开发攻略(三)-CSDN博客
点关注不迷路哟。你的点赞、收藏,一键三连,是我持续更新的动力哟!!!
目录
接续上文:普中STM32F103ZET6开发攻略(三)-CSDN博客
4. 定时器中断实验
4.1 实验目的
4.2 实验原理
4.3 实验环境
4.4 实验思路
4.5 实验代码
4.6 实验思考与拓展
4.6.1 如何修改定时器参数,实现不同的闪烁频率?
4.6.2 如何使用不同的定时器(如 TIM2、TIM3)同时控制多个 LED?
4.6.3 如何实现两个 LED 交替闪烁的效果?
4.6.4 如何通过修改中断优先级,实现不同定时器中断间的优先级控制?
4.6.5 定时器中断与轮询延时方式控制 LED 闪烁各有什么优缺点?
4.6.6 如何利用定时器的 PWM 功能实现 LED 亮度渐变效果?
4.7 注意事项
一、核心概念区分
1. APB 总线时钟(PCLK)
2. 定时器时钟频率(TIMx_CLK)
二、定时器定时参数计算逻辑
定时时间计算公式:
4. 定时器中断实验
4.1 实验目的
-
熟悉STM32F10x微控制器的定时器中断系统结构和基本操作
-
掌握STM32标准库函数对定时器中断的配置方法
-
学会使用定时器中断方式控制LED闪烁
-
理解定时器参数计算、中断优先级设置和中断服务函数编写原则
4.2 实验原理
1. 定时器基本原理
STM32F1的定时器非常多,由2个基本定时器(TIM6、TIM7)、4个通用定时器(TIM2-TIM5)和2个高级定时器(TIM1、TIM8)组成。STM32的定时器系统可以按照预设的时间间隔产生中断,主要包括以下功能:
定时计数:可设置预分频系数和自动重装载值
触发中断:计数到达设定值时触发中断服务函数
多通道定时:拥有多个通道可独立配置
2. 通用定时器简介
STM32F1的通用定时器TIMx (TIM2-TIM5 )具有如下功能:
(1)16 位向上、向下、向上/向下自动装载计数器(TIMx_CNT)。
(2)16 位可编程(可以实时修改)预分频器(TIMx_PSC),计数器时钟频率的分频系数为 1~65535之间的任意数值。
(3)4个独立通道(TIMx_CH1-4),这些通道可以用来作为:
A.输入捕获
B.输出比较
C. PWM 生成(边缘或中间对齐模式)
D.单脉冲模式输出
(4)可使用外部信号(TIMx_ETR)控制定时器,且可实现多个定时器互连(可以用1个定时器控制另外一个定时器)的同步电路。
(5)发生如下事件时产生中断/DMA请求:
A.更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)
B.触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
C.输入捕获
D.输出比较
(6)支持针对定位的增量(正交)编码器和霍尔传感器电路
(7)触发输入作为外部时钟或者按周期的电流管理
3. 中断优先级控制
STM32微控制器通过嵌套向量中断控制器(NVIC)管理各种中断请求。中断优先级分为抢占优先级和响应优先级。抢占优先级决定是否可以打断当前正在执行的中断服务函数;响应先级决定同时发生的同级抢占优先级中断的执行顺序。
4. 定时器时钟来源与计算
STM32的定时器时钟与系统时钟密切相关,TIM2-TIM7连接在APB1总线上。当APB1分频系数为1时,定时器时钟等于APB1总线时钟;当APB1分频系数大于1时,定时器时钟等于APB1总线时钟的2倍。
定时时间计算公式:
其中,Tout为定时时间(秒),per为自动重装载值,psc为预分频系数,Tclk为定时器时钟频率(通常为72MHz)。
4.3 实验环境
-
开发板:STM32F103ZET6
-
IDE:Keil MDK 5 /Visual Studio
-
调试工具:CMSIS-DAP
4.4 实验思路
将上一个实验的外部中断控制改为定时器实现即可。
编写Time相关的头、源文件即可。
4.5 实验代码
time.h
#ifndef __TIME_H #define __TIME_H #include "stm32f10x.h" void TIM3_Int_Init(uint16_t arr, uint16_t psc); // TIM3 初始化 void TIM4_Int_Init(uint16_t arr, uint16_t psc); // TIM4 初始化 #endif
time.c
#include "time.h" #include "led.h" void TIM3_Int_Init(uint16_t arr, uint16_t psc) {TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;NVIC_InitTypeDef NVIC_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); TIM_TimeBaseStructure.TIM_Period = arr; // 自动重装载值TIM_TimeBaseStructure.TIM_Prescaler = psc; // 预分频系数TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE); // 允许更新中断TIM_Cmd(TIM3, ENABLE); // 启动定时器 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure); } void TIM4_Int_Init(uint16_t arr, uint16_t psc) {TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;NVIC_InitTypeDef NVIC_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); TIM_TimeBaseStructure.TIM_Period = arr;TIM_TimeBaseStructure.TIM_Prescaler = psc;TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE);TIM_Cmd(TIM4, ENABLE); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure); }
以上关于定时器源文件的代码:
通过配置两个定时器(TIM3 和 TIM4),实现了两个独立的周期性中断源,可用于执行不同频率或优先级的任务。典型应用场景包括:
-
TIM3:按键扫描、低优先级定时任务。
-
TIM4:LED 闪烁控制、高优先级定时任务。
通过调整arr
和psc
参数,可灵活控制中断频率。
led.h
#ifndef __LED_H #define __LED_H #include "stm32f10x.h" #define LED0_GPIO_PORT GPIOB #define LED0_GPIO_PIN GPIO_Pin_5 //LED0 #define LED1_GPIO_PORT GPIOE #define LED1_GPIO_PIN GPIO_Pin_5 //LED1 void LED_Init(void); void LED0_TOGGLE(void); void LED1_ON(void); void LED1_OFF(void); #endif
led.c
#include "led.h" void LED_Init(void) {GPIO_InitTypeDef GPIO_InitStructure;// 开启 GPIOB 和 GPIOE 的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOE, ENABLE); //配置PB5(LED0)GPIO_InitStructure.GPIO_Pin = LED0_GPIO_PIN;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(LED0_GPIO_PORT, &GPIO_InitStructure); //配置PE5(LED1)GPIO_InitStructure.GPIO_Pin = LED1_GPIO_PIN;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(LED1_GPIO_PORT, &GPIO_InitStructure); //低电平输出GPIO_ResetBits(LED0_GPIO_PORT, LED0_GPIO_PIN);GPIO_ResetBits(LED1_GPIO_PORT, LED1_GPIO_PIN); } void LED0_TOGGLE(void) {LED0_GPIO_PORT->ODR ^= LED0_GPIO_PIN; } void LED1_ON(void) {GPIO_ResetBits(LED1_GPIO_PORT, LED1_GPIO_PIN); } void LED1_OFF(void) {GPIO_SetBits(LED1_GPIO_PORT, LED1_GPIO_PIN); }
实验现象:
-
将工程程序编译后下载到开发板内,可以看到 DS0 指示灯不断闪烁(闪烁频率不做要求),表示程序正常运行。DS1 指示灯每隔 500ms 状态取反一次,实现 1 秒钟闪烁一次。
-
修改 TIM4 初始化函数参数值,设定 1 秒钟的定时中断,让 DS1 指示灯 1 秒钟状态反转一次,实现 2 秒钟闪烁一次。
-
使用 TIM3 的更新中断控制 DS1 指示灯闪烁,闪烁时间自定义。
4.6 实验思考与拓展
4.6.1 如何修改定时器参数,实现不同的闪烁频率?
定时器的闪烁频率由 arr
(自动重装载值)和 psc
(预分频系数)决定。计算公式如下:
定时器时钟 = APB1时钟 × 2 = 72MHz // 假设APB1分频系数为2 计数频率 = 定时器时钟 / (psc + 1) 中断周期 = (arr + 1) / 计数频率 闪烁频率 = 1 / (2 × 中断周期) // 亮灭各一次
若要实现 1Hz 闪烁(亮 0.5 秒,灭 0.5 秒):
// 1Hz闪烁 void TIM3_Int_Init(void) {uint16_t arr = 35999; // 自动重装载值uint16_t psc = 999; // 预分频系数// 其他配置代码保持不变 }
计算过程:
-
计数频率 = 72MHz / (999+1) = 72kHz
-
中断周期 = (35999+1) / 72kHz = 0.5 秒
-
闪烁频率 = 1 / (2×0.5 秒) = 1Hz
4.6.2 如何使用不同的定时器(如 TIM2、TIM3)同时控制多个 LED?
步骤:
-
分别初始化不同定时器:
// 初始化TIM2控制LED1 void TIM2_Int_Init(uint16_t arr, uint16_t psc) {// 配置TIM2,代码类似TIM3NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;// 其他配置... } // 初始化TIM3控制LED2 void TIM3_Int_Init(uint16_t arr, uint16_t psc) {// 配置TIM3,代码类似之前 }
-
编写独立的中断服务函数:
void TIM2_IRQHandler(void) {if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {LED1_Toggle(); // 切换LED1状态TIM_ClearITPendingBit(TIM2, TIM_IT_Update);} } void TIM3_IRQHandler(void) {if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) {LED2_Toggle(); // 切换LED2状态TIM_ClearITPendingBit(TIM3, TIM_IT_Update);} }
-
在主函数中初始化两个定时器:
int main(void) {TIM2_Int_Init(35999, 999); // 1HzTIM3_Int_Init(17999, 999); // 2Hzwhile(1); }
4.6.3 如何实现两个 LED 交替闪烁的效果?
方法一:使用单个定时器,在中断中交替控制两个 LED:
uint8_t led_state = 0; void TIM3_IRQHandler(void) {if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) {if (led_state == 0) {LED1_On();LED2_Off();} else {LED1_Off();LED2_On();}led_state = !led_state; // 切换状态TIM_ClearITPendingBit(TIM3, TIM_IT_Update);} }
方法二:使用两个定时器,配置相同频率但相位差 180°:
// TIM2初始化(控制LED1) void TIM2_Int_Init(void) {// 配置TIM2,启动后立即计数 } // TIM3初始化(控制LED2) void TIM3_Int_Init(void) {// 配置TIM3,但延迟启动(例如在main中延时后启动)HAL_Delay(500); // 延迟0.5秒TIM_Cmd(TIM3, ENABLE); }
4.6.4 如何通过修改中断优先级,实现不同定时器中断间的优先级控制?
步骤:
-
配置优先级分组:
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 2位抢占+2位子优先级
-
设置不同的抢占优先级:
// TIM2(高优先级) NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; // 最高抢占优先级 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; // TIM3(低优先级) NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 次高抢占优先级 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
规则:
-
抢占优先级高的中断可打断低优先级中断。
-
抢占优先级相同的中断按子优先级排序,但不能互相打断。
4.6.5 定时器中断与轮询延时方式控制 LED 闪烁各有什么优缺点?
特性 | 定时器中断 | 轮询延时 |
---|---|---|
CPU 占用 | 空闲时不占用 CPU | 延时期间 CPU 被阻塞 |
响应速度 | 即时响应其他事件 | 延时期间无法响应其他事件 |
精度 | 由定时器时钟决定,精度高 | 受系统负载影响,精度低 |
多任务支持 | 可并行处理多个任务 | 难以同时执行其他任务 |
资源消耗 | 需要配置定时器和中断 | 仅需简单循环 |
推荐使用定时器中断的场景:
-
需要高精度定时(如 PWM 控制、通信协议)。
-
低功耗设计(中断唤醒,平时休眠)。
-
同时处理多个任务(如按键检测与 LED 控制)。
4.6.6 如何利用定时器的 PWM 功能实现 LED 亮度渐变效果?
步骤:
-
配置定时器为 PWM 模式:
void TIM3_PWM_Init(uint16_t arr, uint16_t psc) {TIM_OCInitTypeDef TIM_OCInitStructure;// 基本定时器配置(同前)// ...// PWM模式配置TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;TIM_OCInitStructure.TIM_Pulse = 0; // 初始占空比TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;TIM_OC1Init(TIM3, &TIM_OCInitStructure); // 配置通道1TIM_Cmd(TIM3, ENABLE); // 启动定时器 }
-
在主循环中调整占空比:
int main(void) {uint16_t pulse_val = 0;uint8_t direction = 1; // 1: 增加,0: 减少TIM3_PWM_Init(999, 71); // 1kHz PWM频率while(1) {if (direction) {pulse_val++; // 增加亮度if (pulse_val >= 1000) direction = 0;} else {pulse_val--; // 减少亮度if (pulse_val == 0) direction = 1;}TIM_SetCompare1(TIM3, pulse_val); // 更新占空比HAL_Delay(5); // 延时控制渐变速度} }
效果:
-
占空比从 0% 到 100% 逐渐变化,LED 亮度从暗到亮再到暗循环。
-
通过调整
arr
和psc
可改变 PWM 频率,影响 LED 闪烁效果。
4.7 注意事项
-
定时器初始化前必须先使能相应的定时器时钟
-
中断服务函数中必须清除中断标志位,否则会造成中断反复触发
-
定时器参数计算需考虑时钟频率与所需时间的关系
定时时间计算公式:
-
中断服务函数应尽量简短,避免长时间占用CPU
-
使用标准库函数时,需要注意头文件的包含和依赖关系
-
避免在中断服务函数中使用过长的延时函数
-
注意区分定时器时钟频率与APB总线时钟的关系,正确计算定时参数
一、核心概念区分
1. APB 总线时钟(PCLK)
-
定义:APB(Advanced Peripheral Bus)是微控制器中连接低速外设(如定时器、UART、I2C 等)的总线,其时钟频率由系统时钟(如 HSI、HSE)分频得到。
-
分类:
-
APB1:通常用于低速外设(如 TIM2~TIM7),最大频率一般≤36MHz(不同芯片型号可能不同)。
-
APB2:用于高速外设(如 TIM1、TIM8 等高级定时器),最大频率可达系统时钟频率(如 72MHz)。
-
-
来源:系统时钟经AHB 总线分频器分频后,再经APB 预分频器得到 PCLK1(APB1)和 PCLK2(APB2)。
2. 定时器时钟频率(TIMx_CLK)
-
定义:定时器实际运行的时钟频率,直接决定定时器的计数速度。
-
来源:
-
对于基本定时器(如 TIM6、TIM7)*和*通用定时器(如 TIM2~TIM5): 定时器时钟频率 TIMx_CLK = PCLKx × 2(当 APB 预分频系数≠1 时),或 TIMx_CLK = PCLKx(当 APB 预分频系数 = 1 时)。 这是因为 STM32 的定时器在 APB 总线预分频系数不为 1 时,会自动将时钟频率加倍(补偿机制)。
-
对于高级定时器(如 TIM1、TIM8): 定时器时钟频率 TIMx_CLK = PCLKx(无论 APB 预分频系数是否为 1)。
-
二、定时器定时参数计算逻辑
定时器的核心参数包括:
-
预分频器(PSC):对定时器时钟频率进行分频,得到计数时钟频率。
-
自动重装载值(ARR):计数器从 0 开始向上计数,达到 ARR 值后触发中断或事件。
定时时间计算公式:
-
参数说明:
-
(TIMx_CLK):定时器时钟频率(单位:Hz)。
-
PSC:预分频器值(寄存器中写入的是 PSC 值,实际分频系数为 PSC+1)。
-
ARR:自动重装载值(实际计数周期为 ARR+1)。因为是从0开始计数的。
文章有写的不当的地方,欢迎在评论区中指正修改。如果感觉文章实用对你有帮助,欢迎点赞收藏和关注,你的点赞关注就是我动力,大家一起学习进步。
有不懂的可以在评论区里提出来哟,博主看见后会及时回答的。
-