STM32 PWM波形详细图解

目录

前言

[一 PWM介绍](#一 PWM介绍)

[1.1 PWM简介](#1.1 PWM简介)

[1.2 STM32F103 PWM介绍](#1.2 STM32F103 PWM介绍)

[1.3 时钟周期与占空比](#1.3 时钟周期与占空比)

二.引脚映像关系

2.1引脚映像与寄存器

[2.2 复用功能映像](#2.2 复用功能映像)

[三. PWM 配置步骤](#三. PWM 配置步骤)

3.1相关原理图

3.2配置流程

[3.2.1 步骤一二:](#3.2.1 步骤一二:)

[3.2.2 步骤三:](#3.2.2 步骤三:)

[3.2.3 步骤四五六七:](#3.2.3 步骤四五六七:)

[3.2.4 步骤八:](#3.2.4 步骤八:)

[3.3 PWM 详细代码](#3.3 PWM 详细代码)

[3.3.1 PWM.C](#3.3.1 PWM.C)

[3.3.2 main.c](#3.3.2 main.c)

四.PWM波形

[4.1 波形查看](#4.1 波形查看)

[4.2 PWM更新频率](#4.2 PWM更新频率)

4.2.1不使用delay

[4.2.2 使用delay](#4.2.2 使用delay)


前言

步骤一:通过配置ARR****(自动重装载值寄存器)PSC****(预分频器) 的值,来设置CNT****(计数器)的定时周期、计数频率。

步骤二:再改变CCR****(捕获/比较寄存器)的值,通过CNT与CCR的比较,可对PWM占空比进行调整

经过步骤一和步骤二:即可输出频率占空比都可以调制的PWM波形

注:

ARR(自动重装载值寄存器)

PSC(预分频器)

CNT(计数器)

CCR(捕获/比较寄存器)

一 PWM介绍

1.1 PWM简介

脉冲宽度调制:PWM是一种数字信号控制技术,其中数字信号的占空比被用来控制模拟信号的幅度。占空比是指在一个周期内,信号处于高电平状态的时间与总周期时间的比例。

PWM是"Pulse Width Modulation"的缩写,中文意思是"脉冲宽度调制"。这是一种模拟信号控制方法,通过改变电信号的占空比来控制功率输出或模拟信号的幅度。PWM广泛应用于各种电子系统中,包括但不限于以下几个领域:

  1. 电机控制:PWM用于控制电机的转速和力矩,通过调整电机驱动器的输入电压或电流的占空比来实现。

  2. LED调光:在LED照明中,PWM可以控制LED的亮度,通过改变电流的占空比来调节亮度,而不会改变LED的色温。

  3. 音频信号合成:PWM也用于数字音频处理,通过调制脉冲的宽度来合成模拟音频信号。

  4. 电源管理:在开关电源中,PWM用于控制开关元件的开关频率和占空比,以调节输出电压和电流。

  5. 通信:某些通信协议使用PWM来传输数据,通过调制脉冲的宽度来编码信息。

  6. 测量和控制:PWM信号可以用于测量距离、速度等物理量,也可以用于控制各种执行器。

PWM信号的主要特点包括:

  • 周期性:PWM信号是周期性重复的,具有固定的频率。
  • 占空比:PWM信号的占空比是指高电平状态在整个周期中所占的比例。
  • 分辨率:PWM的分辨率取决于信号的周期和能够分辨的最小脉冲宽度,高分辨率的PWM可以提供更平滑的模拟控制。
  • 易于生成和控制:PWM信号可以通过数字电路或微控制器轻松生成和调整。

在实际应用中,PWM信号通常由定时器或专用的PWM硬件生成,然后通过数字到模拟转换器(DAC)或直接通过功率放大器输出到负载。通过精确控制PWM信号的频率和占空比,可以实现对各种电子设备的精确控制。

1.2 STM32F103 PWM介绍

在STM32F103中除了基本定时器(定时器6和定时器7),通用和高级定时器都可以用来进行PWM输出。

1.3 时钟周期与占空比

在时基单元中,我们通过对PSC、ARR 大小进行配置,来设置计数器CNT的定时周期、计数频率。

因为前面的时基单元中,已经设置完定时器时钟频率。可以通过TIMx_CCRx(捕获/比较寄存器,也就是上面的CCR),输出占空比可调的PWM波形

注:因为CNT在前面设置向上或向下计数模式后就不用更改了,所以到这一步只需要对CRR的值进行设置,也可以通过while()循环,不断给TIMx_CCRx寄存器赋新的值,来进行脉宽占空比的调整。

输出频率和占空比都可以调制的PWM波形

•PWM频率: Freq = CK_PSC / (PSC + 1) / (ARR + 1)

•PWM占空比: Duty = CCR / (ARR + 1)

•PWM分辨率: Reso = 1 / (ARR + 1)

二.引脚映像关系

2.1引脚映像与寄存器

高级定时器:TIM1和TIM8是高级定时器

通用定时器:TIM2、TIM3、TIM4和TIM5是通用定时器

基本定时器:TIM6和TIM7是基本的定时器

2.2 复用功能映像

  1. 引脚重映射:比如当您需要将TIM3的某些通道映射到不同的GPIO引脚上时,可以使用复用功能映像。例如,当默认的TIM3通道引脚不能满足您的硬件设计需求,或者您需要将多个通道映射到同一个引脚上时,可以使用复用功能映像来改变引脚映射。

  2. PWM输出到特定引脚:如果您需要将PWM信号输出到特定的GPIO引脚,而这个引脚不是TIM3的默认输出引脚,您可以通过复用功能映像来实现。例如,将TIM3的CH2映射到PB5,或者将所有四个通道映射到PC6、PC7、PC8、PC9。

下面以TIM3为例

TIM3是STM32微控制器中的一个通用定时器,它具有四个独立的通道,分别是CH1、CH2、CH3和CH4。这些通道可以被配置为输入捕获、输出比较或PWM输出模式,用于各种定时和控制应用。

每个通道都有自己的捕获/比较寄存器(CCR),可以独立设置,以实现不同的定时和控制功能。例如,TIM3_CH1默认引脚为PA6 ,TIM3_CH1部分重映像引脚为PB4 ,TIM3_CH1完全重映像引脚为PC6。

三. PWM 配置步骤

3.1相关原理图

这里使用PWM控制LED呼吸灯。这里控制PC8的绿色LED灯,实现呼吸灯的效果。

这里的PC8端口 ,是TIM3通道3 使用完全重映射

cpp 复制代码
	//部分重映射
    GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3,ENABLE);
    //完全重映射
	GPIO_PinRemapConfig(GPIO_FullRemap_TIM3,ENABLE);

既:GPIO_PinRemapConfig(GPIO_FullRemap_TIM3,ENABLE);

3.2配置流程

一:使能定时器3和相关IO口时钟。

使能定时器3时钟:RCC_APB1PeriphClockCmd();

使能GPIOC时钟:RCC_APB2PeriphClockCmd();

二:初始化IO口为复用功能输出。函数:GPIO_Init();

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

三:这里我们是要把PC8用作定时器的PWM输出引脚,所以要重映射配置,所以需要开启AFIO时钟。同时设置重映射。

RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);

GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE);

四:初始化定时器:ARR,PSC等:TIM_TimeBaseInit();

五:初始化输出比较参数:TIM_OC3Init();

六:使能预装载寄存器: TIM_OC3PreloadConfig(TIM3, TIM_OCPreload_Enable);

七:使能定时器:TIM_Cmd();

八:不断改变比较值CCRx,达到不同的占空比效果:TIM_SetCompare4();

3.2.1 步骤一二:

一:使能定时器3和相关IO口时钟。

使能定时器3时钟:RCC_APB1PeriphClockCmd();

使能GPIOC时钟:RCC_APB2PeriphClockCmd();

二:初始化IO口为复用功能输出。函数:GPIO_Init();

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

cpp 复制代码
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_OCInitTypeDef TIM_OCInitStructure;
	GPIO_InitTypeDef GPIO_InitStructure;
	
	/* 步骤一:使能定时器3和相关IO口时钟。开启时钟 */
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	
	/*  步骤二:初始化IO口为复用功能输出 */
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_8;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;//复用推挽输出
	GPIO_Init(GPIOC,&GPIO_InitStructure);
3.2.2 步骤三:

三:这里我们是要把PC8用作定时器的PWM输出引脚,所以要重映射配置,所以需要开启AFIO时钟。同时设置重映射。

RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);

GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE);

cpp 复制代码
	/*  步骤三:设置重映射 */
	GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3,ENABLE);
	GPIO_PinRemapConfig(GPIO_FullRemap_TIM3,ENABLE);//改变指定管脚的映射	
3.2.3 步骤四五六七:

四:初始化定时器:ARR,PSC等:TIM_TimeBaseInit();

五:初始化输出比较参数:TIM_OC3Init();

六:使能预装载寄存器: TIM_OC3PreloadConfig(TIM3, TIM_OCPreload_Enable);

七:使能定时器:TIM_Cmd();

cpp 复制代码
	/*  步骤四:初始化定时器 */
	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);	
	
	/*  步骤五:初始化输出比较参数:TIM_OC3Init(); */
	TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1;
	TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_Low;//计数值与TIM_Pulse匹配,输出低电平
	TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;
	TIM_OC3Init(TIM3,&TIM_OCInitStructure); //输出比较通道3初始化
	
	/*  步骤六:使能预装载寄存器 */
	TIM_OC3PreloadConfig(TIM3,TIM_OCPreload_Enable);//使能TIMx在 CCR3 上的预装载寄存器
	TIM_ARRPreloadConfig(TIM3,ENABLE);//使能预装载寄存器
	
	/*  步骤七:使能定时器3 */
	TIM_Cmd(TIM3,ENABLE); //使能定时器
3.2.4 步骤八:

八:不断改变比较值CCRx,达到不同的占空比效果:TIM_SetCompare4();

cpp 复制代码
int main()
{
	u16 i=0;  
	u8 fx=0;
	delay_init();
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);  //中断优先级分组 分2组
	LED_Init();
	TIM3_CH3_PWM_Init(500,72-1); //0.5毫秒,(频率是2KHZ)

	while(1)
	{
		
		if(fx==0)
		{
			i++;
			if(i==500)
			{
				fx=1;
			}
		}
		else
		{
			i--;
			if(i==0)
			{
				fx=0;
			}
		}
        //可直接改变CCR的值(通道3也就是CCR3的值)
		TIM_SetCompare3(TIM3,i);  //i值最大可以取499,因为ARR最大值是499.
		delay_ms(5);	
	}
}

3.3 PWM 详细代码

3.3.1 PWM.C

pwm.c

cpp 复制代码
#include "pwm.h"
#include "led.h"

/*******************************************************************************
* 函 数 名         : TIM3_CH3_PWM_Init
* 函数功能		   : TIM3通道3 PWM初始化函数
* 输    入         : per:重装载值
					 psc:分频系数
* 输    出         : 无
*******************************************************************************/
void TIM3_CH3_PWM_Init(u16 per,u16 psc)
{
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_OCInitTypeDef TIM_OCInitStructure;
	GPIO_InitTypeDef GPIO_InitStructure;
	
	/* 步骤一:使能定时器3和相关IO口时钟。开启时钟 */
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	
	/*  步骤二:初始化IO口为复用功能输出 */
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_8;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;//复用推挽输出
	GPIO_Init(GPIOC,&GPIO_InitStructure);
	
	/*  步骤三:设置重映射 */
	GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3,ENABLE);
	GPIO_PinRemapConfig(GPIO_FullRemap_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);	
	
	/*  步骤五:初始化输出比较参数:TIM_OC3Init(); */
	TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1;
	TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High;//计数值与TIM_Pulse匹配,输出低电平
	TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;
	TIM_OC3Init(TIM3,&TIM_OCInitStructure); //输出比较通道3初始化
	
	/*  步骤六:使能预装载寄存器 */
	TIM_OC3PreloadConfig(TIM3,TIM_OCPreload_Enable);//使能TIMx在 CCR3 上的预装载寄存器
	TIM_ARRPreloadConfig(TIM3,ENABLE);//使能预装载寄存器
	
	/*  步骤七:使能定时器3 */
	TIM_Cmd(TIM3,ENABLE); //使能定时器
		
}
3.3.2 main.c

main.c

cpp 复制代码
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "key.h"
#include "time.h"
#include "pwm.h"

int main()
{
	u16 i=0;  
	u8 fx=0;
	delay_init();
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);  //中断优先级分组 分2组
	LED_Init();
	TIM3_CH3_PWM_Init(500,72-1); //周期0.5毫秒,(频率是2KHZ)
	/*   PSC = 72;   ARR = 500;     
	    周期 = 500/1000000 = 5/10000 =0.0005秒 = 0.5毫秒 
      频率 = 2KHZ
 	*/

	
	
	/*LED亮灭时间2秒(亮1秒,灭1秒),
	t = 1000/500 =2;   */
	while(1)
	{
		
		if(fx==0)
		{
			i++;
			if(i==500)
			{
				fx=1;
			}
		}
		else
		{
			i--;
			if(i==0)
			{
				fx=0;
			}
		}
		//可直接改变CCR的值(通道3也就是CCR3的值)
		TIM_SetCompare3(TIM3,i);  //i值最大可以取499,因为ARR最大值是499.
		delay_ms(2);	
	}
}

四.PWM波形

4.1 波形查看

假设 CPU 的时钟频率为 1 MHz(1 微秒/周期),并且循环体内的指令执行需要 10 个周期(这是一个粗略的估计,下面实际测试是差不多的),那么每次循环大约需要 10 微秒。因此,一个完整的亮灭周期大约需要:

这里的if(fx==500),递增和递减共需:500*2 = 1000次计算:

1000×10 微秒=10000 微秒=10 毫秒1000×10微秒=10000微秒=10毫秒

所以,LED 亮灭一次的周期大约为 10 毫秒。这意味着 LED 每 10 毫秒亮灭一次。但请注意,这个估计值可能与实际值有所不同,具体取决于 CPU 的执行速度和循环体内的指令数量。

波形大致为:因为pwm周期

4.2 PWM更新频率

注意:

更改delay_ms()延时函数的大小:是修改LED灯亮灭的周期;

LED亮灭周期是由频率(时基单元PSC ARR CNT)和占空比(CCR)控制的;

dalay_ms()函数是为了控制PWM信号的更新速率 :在代码中,delay_ms() 函数控制了 i 值更新的速率,即控制了 PWM 信号占空比变化的速率。如果没有这个延迟,i 的值会非常快地在 0 到 500 之间变化,导致 PWM 信号的频率非常高,这可能超出了人眼的感知范围,使得 LED 的亮度看起来是恒定的。

4.2.1不使用delay

1 不使用delay_ms()函数

循环中没有包含任何延迟 ,这意味着 i 的值会非常快速地在 0 到 500 之间变化,没有任何停留。由于没有延迟,i 的值变化得太快,导致人眼无法察觉到 LED 的亮度变化,看起来就像是 LED 一直亮着。

4.2.2 使用delay

2 使用delay_ms(1)函数后:这里就是LED的亮灭周期为1秒

  • 从 0 增加到 500 需要 500 次循环。
  • 从 500 减少到 0 也需要 500 次循环。
  • 因此,一个完整的亮灭周期需要 1000 次循环。

每次循环的时间为 1 毫秒,所以一个完整的亮灭周期的时间为:

1000×1 毫秒=1000 毫秒=1 秒1000×1毫秒=1000毫秒=1秒

如下图

相关推荐
-Springer-8 小时前
STM32 学习 —— 个人学习笔记5(EXTI 外部中断 & 对射式红外传感器及旋转编码器计数)
笔记·stm32·学习
LS_learner8 小时前
树莓派(ARM64 架构)Ubuntu 24.04 (Noble) 系统 `apt update` 报错解决方案
嵌入式硬件
来自晴朗的明天9 小时前
16、电压跟随器(缓冲器)电路
单片机·嵌入式硬件·硬件工程
钰珠AIOT9 小时前
在同一块电路板上同时存在 0805 0603 不同的封装有什么利弊?
嵌入式硬件
代码游侠9 小时前
复习——Linux设备驱动开发笔记
linux·arm开发·驱动开发·笔记·嵌入式硬件·架构
代码游侠20 小时前
学习笔记——设备树基础
linux·运维·开发语言·单片机·算法
xuxg20051 天前
4G 模组 AT 命令解析框架课程正式发布
stm32·嵌入式·at命令解析框架
CODECOLLECT1 天前
京元 I62D Windows PDA 技术拆解:Windows 10 IoT 兼容 + 硬解码模块,如何降低工业软件迁移成本?
stm32·单片机·嵌入式硬件
BackCatK Chen1 天前
STM32+FreeRTOS:嵌入式开发的黄金搭档,未来十年就靠它了!
stm32·单片机·嵌入式硬件·freertos·低功耗·rtdbs·工业控制