普中STM32F103ZET6开发攻略(五)
接续上文:普中STM32F103ZET6开发攻略(四)-CSDN博客
点关注不迷路哟。你的点赞、收藏,一键三连,是我持续更新的动力哟!!!
目录
5. 定时器实验——PWM呼吸灯
5.1 实验目的
5.2 实验原理
5.3 实验环境
5.4 实验思路
5.5 实验代码
5.5.1 delay头、源
🔍 它们的区别:
✅ 一、重点解释 |=
📌 |= 是按位或赋值运算符,它的意思是:“将右侧的位按位或后赋值给左侧变量”。
🚀 具体解释这句作用:
✅ 二、对比两个延时函数的差异
5.5.2 pwm头、源
5.5.3 led头、源
5.6 实验思考和拓展
5.6.1 如何修改PWM参数,实现不同的呼吸频率?
5.6.2 如何实现多个LED的同步或交替呼吸效果?
5.6.3 PWM频率对LED呼吸效果有何影响?如何选择合适的PWM频率?
5.6.4 如何结合按键控制,实现多种呼吸模式的切换?
5.6.5 相比于使用延时函数控制LED亮度变化,
使用定时器中断方式有什么优势?
5.7 注意事项
5. 定时器实验——PWM呼吸灯
5.1 实验目的
-
熟悉STM32F10x微控制器的PWM输出功能和基本操作
-
掌握STM32标准库函数对定时器PWM模式的配置方法
-
学会使用PWM技术控制LED亮度,实现呼吸灯效果
-
理解PWM频率、占空比的计算与设置方法
5.2 实验原理
1.PWM基本原理
PWM(脉宽调制)是一种对模拟信号电平进行数字编码的方法。通过调制PWM波的占空比,可以控制输出电压的平均值,从而控制LED的亮度。PWM信号的占空比是指在一个周期内,高电平持续时间与总周期时间的比值。
2.通用定时器PWM功能简介
STM32F1的通用定时器TIMx (TIM2-TIM5)具有PWM输出功能,高级定时器 TIM1 和 TIM8 可以同时产生多达7路的PWM输出:
(1)每个通道可独立配置PWM模式
(3)可设置PWM频率与占空比
(4)PWM模式1:当计数值小于比较值时,通道输出有效电平
(5)PWM模式2:当计数值小于比较值时,通道输出无效电平
表1 PWM模式
PWM 模式根据计数器 CNT 计数方式,可分为边沿对齐模式和中心对齐模式。
(1)PWM 边沿对齐模式 当 TIMx_CR1 寄存器中的 DIR 位为低时执行递增计数,计数器 CNT 从 0 计 数到自动重载值(TIMx_ARR 寄存器的内容),然后重新从 0 开始计数并生成计 数器上溢事件。
(2)PWM 中心对齐模式 在中心对齐模式下,计数器 CNT 是工作在递增/递减模式下。开始的时候, 计数器 CNT 从 0 开始计数到自动重载值减 1(ARR-1),生成计数器上溢事件; 然后从自动重载值开始向下计数到 1 并生成计数器下溢事件。之后从 0 开始重 新计数。
中心对齐模式又分为中心对齐模式 1/2/3 三种,具体由寄存器 CR1 位 CMS[1:0]配置。具体的区别就是比较中断标志位 CCxIF 在何时置 1:中心模式 1 在 CNT 递减计数的时候置 1,中心对齐模式 2 在 CNT 递增计数时置 1,中心 模式 3 在CNT递增和递减计数时都置为1。
3.PWM参数设置与计算
(1)PWM频率计算公式:
(2)PWM占空比计算公式:
占空比
其中,Tclk为定时器时钟频率,ARR为自动重装载值,PSC为预分频系数,CCRx为比较寄存器值。
4.呼吸灯原理
呼吸灯效果是通过逐渐增加和减小PWM信号的占空比,使LED亮度逐渐增加和减小,形成类似呼吸的视觉效果。通过控制占空比变化的速度,可以调整"呼吸"的频率。
表2 TIM3复用功能重映像
5.3 实验环境
-
开发板:STM32F103ZET6
-
IDE:Keil MDK 5 /Visual Studio
-
调试工具:CMSIS-DAP
5.4 实验思路
硬件连接与外设准备
-
选择一个支持 PWM 输出的通用定时器通道(如 TIM3_CH1);
-
将该通道对应的 GPIO(如 PA6)配置为复用推挽输出;
-
将 LED 灯连接到该 GPIO 端口,通过其电平变化调节亮度。
GPIO 和定时器初始化
-
使用标准库函数初始化 GPIO 引脚为复用功能;
-
配置定时器 TIMx:
-
设置计数模式为向上计数;
-
配置预分频器 PSC 和自动重装载值 ARR,设定 PWM 输出频率;
-
启用 PWM 模式(模式1或模式2);
-
设置 CCR 寄存器初值,控制初始亮度;
-
启动定时器并使能 PWM 输出通道。
-
实现呼吸灯效果逻辑
-
在主循环或定时中断中周期性修改 CCRx 值,改变占空比;
-
通过逐步增加 CCRx(LED变亮)和逐步减少 CCRx(LED变暗)模拟“呼吸”效果;
-
可设置方向标志(例如
dir = 1
表示亮度增加,dir = 0
表示亮度减小)控制 PWM 占空比自动反转; -
设置一定的延时以控制“呼吸”频率平滑。
调试与效果观察
-
下载程序后观察 LED 亮度是否平滑变化;
-
调整 ARR、PSC、占空比步长和延时时间,以达到理想的“呼吸”频率和视觉效果;
-
可扩展:尝试不同的定时器通道或多个 LED 实现多色呼吸灯。
5.5 实验代码
5.5.1 delay头、源
delay.h
#ifndef __DELAY_H #define __DELAY_H #include "stm32f10x.h" void Delay_Init(void); void Delay_ms(u32 nms); #endif
delay.c
#include "delay.h" static u32 fac_ms = 0; void Delay_Init(void) {SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); // HCLK/8fac_ms = SystemCoreClock / 8000; } void Delay_ms(u32 nms) {u32 temp;SysTick->LOAD = nms * fac_ms;SysTick->VAL = 0x00;SysTick->CTRL = 0x01;do{temp = SysTick->CTRL;} while ((temp & 0x01) && !(temp & (1 << 16)));SysTick->CTRL = 0x00;SysTick->VAL = 0X00; }
🔍 它们的区别:
对比点 | delay.h 实现 | SysTick.h 实现 |
---|---|---|
时钟源 | HCLK/8 | HCLK (通常) |
初始化方式 | Delay_Init() | SysTick_Init(72) |
延时精度 | 只有 ms 延时 | 支持 ms 和 us 延时 |
使用寄存器 | 直接操作 SysTick 寄存器 | 也是用 SysTick ,但逻辑可能更复杂 |
精度 & 速度 | 相对较慢一些(因为频率被除以8) | 精度高,速度快 |
推荐程度 | 简单项目足够 | 推荐用于需要更高精度的场景 |
注意:
void Delay_ms(u32 nms) {u32 temp;SysTick->LOAD = nms * fac_ms;SysTick->VAL = 0x00;SysTick->CTRL = 0x01;do{temp = SysTick->CTRL;} while ((temp & 0x01) && !(temp & (1 << 16)));SysTick->CTRL = 0x00;SysTick->VAL = 0X00; } void delay_ms(u16 nms) { u32 temp; SysTick->LOAD=(u32)nms*fac_ms; //时间加载(SysTick->LOAD为24bit)SysTick->VAL =0x00; //清空计数器SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //开始倒数 do{temp=SysTick->CTRL;}while((temp&0x01)&&!(temp&(1<<16))); //等待时间到达 SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器SysTick->VAL =0X00; //清空计数器 } 对比以上两个函数写法以及着重解释:SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //开始倒数 |=这是什么意思?
[以上两个函数解释] 以上两个函数都是实现毫秒级延时的,基于STM32的SysTick定时器实现的。
✅ 一、重点解释 |=
这句:
c 复制代码 SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
可以拆解为:
c 复制代码 SysTick->CTRL = SysTick->CTRL | SysTick_CTRL_ENABLE_Msk;
📌 |=
是按位或赋值运算符,它的意思是:“将右侧的位按位或后赋值给左侧变量”。
🚀 具体解释这句作用:
假设当前 SysTick->CTRL = 0x00000002
(比如只使能了中断),而 SysTick_CTRL_ENABLE_Msk = 0x00000001
(表示开启计数器),
执行后结果:
c 复制代码 SysTick->CTRL = 0x00000002 | 0x00000001 = 0x00000003
即:原有配置不变,只新增使能计数器的那一位。
✅ 二、对比两个延时函数的差异
项目 | Delay_ms (你的) | delay_ms (SysTick.h中) |
---|---|---|
CTRL 写法 | SysTick->CTRL = 0x01; 直接赋值 | `SysTick->CTRL |
CTRL 清除方式 | SysTick->CTRL = 0x00; 全部清除 | SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; 只关闭使能位 |
延时单位 | u32 可延时更长 | u16 只适合较短延时(约65秒) |
灵活性 | 不保留中断配置等其他功能,简单粗暴 | 保留其他控制位(如中断等)更安全 |
5.5.2 pwm头、源
pwm.h
#ifndef _pwm_H #define _pwm_H #include "system.h" void TIM3_CH2_PWM_Init(u16 per,u16 psc); // PWM初始化函数 void PWM_SetDuty(uint8_t duty); // 设置占空比函数 #endif
pwm.c
#include "pwm.h" void TIM3_CH2_PWM_Init(u16 per, u16 psc) {TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;// 定义定时器基本初始化结构体,用于配置定时器的核心参数TIM_OCInitTypeDef TIM_OCInitStructure; // 定义定时器输出比较初始化结构体,用于配置PWM输出或电平比较模式GPIO_InitTypeDef GPIO_InitStructure; // 定义GPIO初始化结构体,用于配置引脚的工作模式、速率和上下拉等参数 /* 开启时钟 */RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE); // 使能GPIOB和AFIO时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); // 使能TIM3时钟 /* 配置PB5为复用推挽输出模式 */GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure); /* 配置TIM3的部分重映射,使TIM3_CH2映射到PB5 */GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); /* 初始化定时器基本参数 */TIM_TimeBaseInitStructure.TIM_Period = per; // 自动重装载值TIM_TimeBaseInitStructure.TIM_Prescaler = psc; // 预分频器TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; // 时钟分频TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数模式TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure); /* 配置PWM模式 */TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; // PWM模式1TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; // 输出使能TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; // 输出极性低有效TIM_OCInitStructure.TIM_Pulse = 0; // 初始CCR值TIM_OC2Init(TIM3, &TIM_OCInitStructure); // 初始化TIM3通道2 /* 使能预装载 */TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable); // CCR寄存器预装载使能TIM_ARRPreloadConfig(TIM3, ENABLE); // 自动重装载寄存器预装载使能 /* 使能定时器 */TIM_Cmd(TIM3, ENABLE); // 使能TIM3 } void PWM_SetDuty(uint8_t duty) {uint16_t compareValue; /* 限制占空比范围在0-100之间 */if (duty > 100) duty = 100; // 超过100时强制设置为100 /* 将占空比值(0-100)转换为比较值(0-500) */compareValue = (uint16_t)((duty * 500) / 100); // 根据公式计算CCR值 /* 设置PWM比较值 */TIM_SetCompare2(TIM3, compareValue); // 设置TIM3通道2比较寄存器的值 }
5.5.3 led头、源
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); }
5.6 实验思考和拓展
5.6.1 如何修改PWM参数,实现不同的呼吸频率?
关键参数:呼吸频率由 PWM 占空比变化的速率决定,与定时器更新中断频率相关。
(1)PWM频率计算公式:
(2)PWM占空比计算公式:
占空比
修改方法:
-
调整定时器更新频率:通过修改 PSC(预分频器)和 ARR(自动重装载值)改变中断周期。
// 增大ARR或PSC会降低更新频率,减缓呼吸速度 TIM_TimeBaseStructure.TIM_Period = 999; // ARR值增大,呼吸变慢 TIM_TimeBaseStructure.TIM_Prescaler = 7199; // PSC值增大,呼吸变慢
-
改变占空比调整步长:在中断服务函数中,每次更新占空比时调整步长值。
// 步长越大,亮度变化越快,呼吸频率越高 static uint16_t pulse = 0; static int8_t step = 5; // 增大步长(如从2→5)可加快呼吸 void TIM3_IRQHandler(void) {if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) {pulse += step;if (pulse >= 1000 || pulse <= 0) step = -step;TIM_SetCompare1(TIM3, pulse); // 更新PWM占空比TIM_ClearITPendingBit(TIM3, TIM_IT_Update);} }
5.6.2 如何实现多个LED的同步或交替呼吸效果?
同步呼吸:
使用同一个定时器的不同通道控制多个 LED,共享占空比变化。
// 配置TIM3通道1和通道2分别控制LED1和LED2 TIM_OC1Init(TIM3, &TIM_OCInitStructure); // 通道1 TIM_OC2Init(TIM3, &TIM_OCInitStructure); // 通道2 // 中断服务函数中同时更新两个通道的占空比 void TIM3_IRQHandler(void) {TIM_SetCompare1(TIM3, pulse); // LED1TIM_SetCompare2(TIM3, pulse); // LED2 }
交替呼吸:
-
使用两个定时器,配置相同频率但相位差 180°;或在中断中控制占空比相位。
// 方法1:使用两个定时器,延时启动 TIM2_Int_Init(999, 71); // 控制LED1 HAL_Delay(500); // 延迟0.5秒 TIM3_Int_Init(999, 71); // 控制LED2,与LED1相位差180° // 方法2:单定时器控制,占空比互补 void TIM3_IRQHandler(void) {TIM_SetCompare1(TIM3, pulse); // LED1TIM_SetCompare2(TIM3, 1000 - pulse); // LED2(与LED1互补) }
5.6.3 PWM频率对LED呼吸效果有何影响?如何选择合适的PWM频率?
影响:
-
频率过低(如 < 100Hz):人眼会感知到 LED 闪烁,呼吸效果不流畅。
-
频率过高(如 > 20kHz):虽然消除了闪烁,但可能增加功耗,且某些 LED 驱动电路对高频响应不佳。
选择原则:
-
可见光谱范围:推荐 PWM 频率在 100Hz~20kHz 之间,避免人眼感知闪烁。
-
LED 类型:普通 LED 建议 1kHz~5kHz;对频率敏感的 LED(如 RGB 混光)需更高频率(10kHz+)。
-
功耗优化:在满足视觉效果的前提下,尽量降低频率以减少开关损耗。
// 配置PWM频率为1kHz(适合大多数LED) TIM_TimeBaseStructure.TIM_Period = 999; // ARR TIM_TimeBaseStructure.TIM_Prescaler = 71; // PSC // 实际频率 = 72MHz / (72 × 1000) = 1kHz
5.6.4 如何结合按键控制,实现多种呼吸模式的切换?
实现步骤:
-
定义多种呼吸模式:如慢呼吸、快呼吸、交替呼吸等。
-
按键检测:使用外部中断或轮询方式检测按键状态。
-
模式切换:在按键触发时切换模式标志,并更新 PWM 配置。
示例代码:
typedef enum {MODE_SLOW, // 慢呼吸MODE_FAST, // 快呼吸MODE_ALTERNATE // 交替呼吸 } BreatheMode; static BreatheMode currentMode = MODE_SLOW; // 按键中断服务函数 void EXTI0_IRQHandler(void) {if (EXTI_GetITStatus(EXTI_Line0) != RESET) {// 切换模式currentMode = (BreatheMode)((currentMode + 1) % 3);EXTI_ClearITPendingBit(EXTI_Line0);} } // 定时器中断服务函数 void TIM3_IRQHandler(void) {switch (currentMode) {case MODE_SLOW:// 慢呼吸参数step = 2;break;case MODE_FAST:// 快呼吸参数step = 10;break;case MODE_ALTERNATE:// 交替呼吸参数TIM_SetCompare1(TIM3, pulse);TIM_SetCompare2(TIM3, 1000 - pulse);return; // 跳过下面的通用设置}// 通用呼吸逻辑pulse += step;if (pulse >= 1000 || pulse <= 0) step = -step;TIM_SetCompare1(TIM3, pulse); }
5.6.5 相比于使用延时函数控制LED亮度变化,
使用定时器中断方式有什么优势?
特性 | 定时器中断 | 延时函数 |
---|---|---|
CPU 占用 | 仅在中断时执行代码,空闲时 CPU 可处理其他任务 | 延时期间 CPU 被阻塞,无法响应其他事件 |
实时性 | 可精确控制 PWM 周期和占空比变化,亮度曲线平滑 | 延时时间受系统负载影响,亮度变化不均匀 |
多任务支持 | 可同时处理按键检测、通信等其他任务 | 难以实现并行操作,功能扩展受限 |
功耗 | 可配合低功耗模式(如 STOP 模式),中断唤醒 | 持续运行,功耗高 |
精度 | 由定时器时钟决定,精度高(如 ±0.1%) | 受系统时钟和函数调用开销影响,精度低 |
推荐场景: 当需要:
-
精确控制 LED 亮度曲线(如渐变效果);
-
同时处理多个任务(如按键响应、数据传输);
-
低功耗设计(如电池供电设备); 使用定时器中断更合适。
5.7 注意事项
-
PWM初始化前必须先使能相应的定时器时钟和GPIO时钟
-
GPIO必须配置为复用推挽输出模式
-
根据LED连接方式(共阳或共阴),正确设置PWM极性
-
PWM频率不宜过低,避免LED闪烁现象
-
占空比调节范围通常为0-100%,对应比较值0-ARR
-
避免在循环中频繁修改占空比,会导致CPU负担过重
-
使用标准库函数时,需要注意头文件的包含和依赖关系