零基础STM32入门教程--10-PWM 呼吸灯

专栏:STM32F103入门教程

环境:Keil‑MDK + STM32F103 标准库3.5版本

一、PWM

PWM (Pulse‑Width Modulation,脉冲宽度调制):周期固定,改变高电平 / 低电平的占空比,等效输出平均电压。

核心概念

周期 T / 频率 f:完整脉冲重复一次的时间;f=1/T

占空比 D:一个周期内,高电平时间 ÷ 整个周期时间 ×100%

占空比 0%:一直低电平 → 等效输出 0V

占空比 50%:高低各一半 → 等效 ½ 电源电压

占空比 100%:一直高电平 → 等效满电源电压

⚠️输出还是方波数字脉冲,不是真正改变电源电压;靠占空比得到等效模拟效果,后级加 LC 滤波可以变成平滑直流。

PWM 基于"面积等效原理",简要理解面积 = 脉冲电平*脉冲宽度,面积越大输出能量越多,脉冲电平固定得出脉冲宽度(占空比)越大输出能量越多。

二、呼吸灯

灯光由暗→亮→暗循环,模拟呼吸效果。

LED 亮度由 PWM 占空比决定:

占空比越大 → LED 平均电压越高,越亮

占空比越小 → LED 越暗

呼吸效果就是:占空比从小到大,再从大到小,循环往复。

LED 本身只有亮 / 灭,PWM 高速开关,人眼视觉残留,感知为亮度渐变。

三、STM32F103 PWM

stm32f103的PWM也属于定时器部份,上一节讲过定时器的基准设置,再增加输出控制功能就可以了。通用定时器有输出PWM的功能,哪个引脚是对应定时器X的哪个通道要查一下数据手册。

这里采用PA1引脚,定时器2的通道2。定时器通常有几个通道。

3.1 接线

3.2 PWM控制相关

TIM_TimeBaseInitTypeDef 定时器基准初始化结构体,本次设计的频率是1Khz,也就是周期1ms。

TIM_Prescaler 预分频值设为72,频率变为1Mhz,周期就是1微秒。

TIM_Period 自动重装载寄存器周期1000,1us*1000=1ms。

TIM_OCInitTypeDef 定时器输出比较结构体
c 复制代码
typedef struct
{
  uint16_t TIM_OCMode;        /*!< Specifies the TIM mode.
                                   This parameter can be a value of @ref TIM_Output_Compare_and_PWM_modes */

  uint16_t TIM_OutputState;   /*!< Specifies the TIM Output Compare state.
                                   This parameter can be a value of @ref TIM_Output_Compare_state */

  uint16_t TIM_OutputNState;  /*!< Specifies the TIM complementary Output Compare state.
                                   This parameter can be a value of @ref TIM_Output_Compare_N_state
                                   @note This parameter is valid only for TIM1 and TIM8. */

  uint16_t TIM_Pulse;         /*!< Specifies the pulse value to be loaded into the Capture Compare Register. 
                                   This parameter can be a number between 0x0000 and 0xFFFF */

  uint16_t TIM_OCPolarity;    /*!< Specifies the output polarity.
                                   This parameter can be a value of @ref TIM_Output_Compare_Polarity */

  uint16_t TIM_OCNPolarity;   /*!< Specifies the complementary output polarity.
                                   This parameter can be a value of @ref TIM_Output_Compare_N_Polarity
                                   @note This parameter is valid only for TIM1 and TIM8. */

  uint16_t TIM_OCIdleState;   /*!< Specifies the TIM Output Compare pin state during Idle state.
                                   This parameter can be a value of @ref TIM_Output_Compare_Idle_State
                                   @note This parameter is valid only for TIM1 and TIM8. */

  uint16_t TIM_OCNIdleState;  /*!< Specifies the TIM Output Compare pin state during Idle state.
                                   This parameter can be a value of @ref TIM_Output_Compare_N_Idle_State
                                   @note This parameter is valid only for TIM1 and TIM8. */
} TIM_OCInitTypeDef;

重点成员简要解释

TIM_OCMode:输出比较模式

TIM_OCMode_PWM1:PWM 模式 1,向上计数:CNT<CCR 输出有效电平。最常用,做 PWM、呼吸灯

TIM_OCMode_PWM2:PWM 模式 2,向上计数:CNT<CCR 输出无效电平

TIM_OCPolarity:输出极性

  • TIM_OCPolarity_High:有效电平为高电平
  • TIM_OCPolarity_Low:有效电平为低电平(LED 低电平点亮就选这个)

TIM_Pulse

就是 CCR 寄存器初始值。

例如 ARR=999,TIM_Pulse=300,初始占空比 30%;后续运行中用TIM_SetCompare1()动态修改,不要反复改这个结构体。

TIM_OutputState

TIM_OutputState_Enable / Disable,开启 / 关闭该通道 OC 输出到 IO 引脚。

两个预装载,作用对象不一样

TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable); // CH2通道CCR预装载

TIM_ARRPreloadConfig(TIM2, ENABLE); // ARR自动重装载预装载

1、TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable)

对象:CCR2 寄存器(通道 2 的比较值,控制占空比)

CCR:决定 PWM 占空比。

开启 OC 预装载:调用TIM_SetCompare2()修改 CCR2,新数值不会立刻生效,等到当前 PWM 周期结束(更新事件)才加载到影子寄存器真正生效。

关闭:修改 CCR2,马上立刻生效,有可能在一个周期中间改变占空比,产生毛刺、抖动。

2、TIM_ARRPreloadConfig(TIM2, ENABLE)

对象:ARR 寄存器(自动重装载,控制 PWM 周期 / 频率)

ARR:定时器计数最大值,决定 PWM 周期(频率)。

开启 ARR 预装载:修改 ARR,新值要等下一次更新事件才生效。

关闭 ARR 预装载:写完 ARR 马上生效。

上面两个不打开对于呼吸灯来说感觉不到,如果是数字电源或电机通常都是打开的

STM32 定时器重装载有两套寄存器:

写入寄存器(程序员操作):CPU 写的是这个;

影子寄存器(真正参与计数):定时器硬件实际在跑的。

预装载开启:CPU 写 → 写入寄存器,等到更新事件,才复制到影子寄存器。

预装载关闭:CPU 写,直接改写影子寄存器,立即生效。

四、工程与程序编写

4.1工程复制

复制TIME2工程文件夹为PWM文件夹,TIME2.uvprojx复制为PWM.uvprojx。

4.2 添加驱动文件

  1. 直接在原time.c文件里面添加。
    time.h
c 复制代码
#ifndef __TIME_H   //防止重复包含,编译报错
#define __TIME_H

#include "stm32f10x.h"

void time2_init(uint16_t arr, uint16_t psc); //time2初始化

void time2_ch2_pwm_init(uint16_t arr, uint16_t psc);

#endif

time.c

c 复制代码
#include "time.h"
#include "led.h"




//通用定时器中断初始化
void time2_init(uint16_t arr, uint16_t psc)
{
    TIM_TimeBaseInitTypeDef  tim_time_base_init_structure;			//定时器基准初始化结构体变量
	NVIC_InitTypeDef nvic_init_structure;							//

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); 			//时钟使能

	tim_time_base_init_structure.TIM_Period = arr; 						//设置在下一个更新事件装入活动的自动重装载寄存器周期的值	
	tim_time_base_init_structure.TIM_Prescaler = psc; 					//设置用来作为TIMx时钟频率除数的预分频值  
	tim_time_base_init_structure.TIM_ClockDivision = TIM_CKD_DIV1; 		//设置时钟分割  
	tim_time_base_init_structure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
	TIM_TimeBaseInit(TIM2, &tim_time_base_init_structure); 				//根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
 
	TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE );
		
	nvic_init_structure.NVIC_IRQChannel = TIM2_IRQn;  				//TIM2中断
	nvic_init_structure.NVIC_IRQChannelPreemptionPriority = 0;  	//抢占优先级0级
	nvic_init_structure.NVIC_IRQChannelSubPriority = 0;  			//从优先级0级
	nvic_init_structure.NVIC_IRQChannelCmd = ENABLE; 				//IRQ通道被使能
	NVIC_Init(&nvic_init_structure);  								//根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器

	TIM_Cmd(TIM2, ENABLE);  //使能TIMx外设
							 
}



void TIM2_IRQHandler(void)   //TIM2中断
{
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) //检查指定的TIM中断发生与否:TIM 中断源 
	{			
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update  );  //清除TIMx的中断待处理位:TIM 中断源 
		led_toggle();
	}
}




void time2_ch2_pwm_init(uint16_t arr, uint16_t psc)
{
	GPIO_InitTypeDef 	gpio_init_struct;  						//定义gpio初始化结构体变量
	TIM_TimeBaseInitTypeDef  	tim_time_base_init_structure;	//定时器基准初始化结构体变量
	TIM_OCInitTypeDef  	tim_oc_init_structure;					//定时器输出比较结构体初始化
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);		// 
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);   	//使能GPIO外设时钟使能
	
	//设置该引脚为复用输出功能,输出TIM1 CH1的PWM脉冲波形
    gpio_init_struct.GPIO_Pin = GPIO_Pin_1; 				//TIM2_CH2
    gpio_init_struct.GPIO_Mode = GPIO_Mode_AF_PP;  			//复用推挽输出
    gpio_init_struct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &gpio_init_struct);
	
	tim_time_base_init_structure.TIM_Period = arr; 						//设置在下一个更新事件装入活动的自动重装载寄存器周期的值	
	tim_time_base_init_structure.TIM_Prescaler = psc; 					//设置用来作为TIMx时钟频率除数的预分频值  
	tim_time_base_init_structure.TIM_ClockDivision = TIM_CKD_DIV1; 		//设置时钟分割  
	tim_time_base_init_structure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
	TIM_TimeBaseInit(TIM2, &tim_time_base_init_structure); 				//根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
	
	tim_oc_init_structure.TIM_OCMode = TIM_OCMode_PWM1;	 				//选择定时器模式:TIM脉冲宽度调制模式1
    tim_oc_init_structure.TIM_OutputState = TIM_OutputState_Enable; 	//比较输出使能
    tim_oc_init_structure.TIM_Pulse = 300; 								//设置待装入捕获比较寄存器的脉冲值
    tim_oc_init_structure.TIM_OCPolarity = TIM_OCPolarity_Low; 		//输出极性:TIM输出比较极性高  TIM_OCPolarity_Low  TIM_OCPolarity_High
    TIM_OC2Init(TIM2, &tim_oc_init_structure);  						//根据TIM_OCInitStruct中指定的参数初始化外设TIMx
	
	TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable);  					//CH2预装载使能
    TIM_ARRPreloadConfig(TIM2, ENABLE); 								//使能TIMx在ARR上的预装载寄存器
    TIM_Cmd(TIM2, ENABLE);  //使能TIM2
    
		
}

main.c

c 复制代码
#include "stm32f10x.h"  
#include "led.h"
#include "delay.h"
#include "key.h"
#include "usart.h"
#include "exti.h"
#include "time.h"
#include <stdio.h>


int main(void)
{	
	int direction = 1;
	int pwm_width=0;
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);// 设置中断优先级分组4
	//led_init();
	delay_init();
	usart1_init(115200);
	//key_init();
	//exit_init();	
	//time2_init(20000-1, 3600-1);
	time2_ch2_pwm_init(1000-1,72-1);
		
	while(1)
	{				
		if(direction == 1){
			TIM_SetCompare2(TIM2,pwm_width++);//
			delay_ms(3);
			if(pwm_width > 900){
				direction =0;	
			}
		}else{
			if(pwm_width >1){
				TIM_SetCompare2(TIM2,pwm_width--);
				delay_ms(3);
			}else{
				direction =1;			
			}			
			
		}		
	}
	
}

direction 为方向标志,1表示增加亮度,占空比增加。最多可以增加到1000,实测900~1000之间亮度看不出变化。改变占空比后延长3ms的时间。

4.5 编译烧录

编译烧录,可以看到呼吸灯的效果。大家也可以接到其它的定时器通道,修改定时器的频率和周期值 修改延时时间等看效果。

呼吸灯

资料下载

参考资料网盘 :

链接 : https://pan.baidu.com/s/1BrH0JbUY6z7j7gAihTa4Dw

提取码: qnws

本节课完整工程例程网盘:

链接: https://pan.baidu.com/s/1rl6EIqi9m6kQ1JFjuSqINg

提取码: bumc

相关推荐
记帖1 小时前
STM32C5A3R开发(7)----ADC电压采集
嵌入式硬件·adc·dac·stm32cubeide·stm32c5·stm32cubemx2·stm32c5a3rgt6
自小吃多3 小时前
器件移动、旋转、镜像、对齐、等间距操作笔记
笔记·嵌入式硬件
jianqiang.xue4 小时前
智能体整体架构设计:四大模块与目录组织
单片机·嵌入式硬件·物联网·架构·esp32
Messy create6 小时前
【BMS-AFE-温度检测】
stm32·单片机·能源
东莞市永盛电气6 小时前
三相208V变380V变压器,出海工业设备供电适配方案
python·单片机·嵌入式硬件
桃蹊、7 小时前
串口/网络透传实战:FreeRTOS 多任务架构与连接池设计
stm32·物联网·wifi·freertos
Learn-Share_HY7 小时前
[IT Network]如何配置反向路徑過濾器(rp_filter),以解決路由不對稱問題?
linux·嵌入式硬件·物联网·网络协议·tcp/ip·http·iot
深圳市宝华视联8 小时前
医院内网部署手术示教系统注意事项
嵌入式硬件·实时互动·音视频·实时音视频·视频编解码·嵌入式实时数据库
新晨单片机设计8 小时前
S001A-基于STM32单片机超声波视力保护仪【Proteus仿真+Keil程序+原理图】
stm32·单片机·proteus