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秒

如下图

相关推荐
2401_843785232 小时前
STM32 AD多通道
stm32·单片机·嵌入式硬件
厂太_STAB_丝针2 小时前
【自学嵌入式(8)天气时钟:天气模块开发、主函数编写】
c语言·单片机·嵌入式硬件
charlie1145141914 小时前
从0开始使用面对对象C语言搭建一个基于OLED的图形显示框架(协议层封装)
c语言·驱动开发·单片机·学习·教程·oled
简知圈7 小时前
【04-自己画P封装,并添加已有3D封装】
笔记·stm32·单片机·学习·pcb工艺
徐某人..8 小时前
ARM嵌入式学习--第十天(UART)
arm开发·单片机·学习·arm
Ronin-Lotus10 小时前
嵌入式硬件篇---CPU&GPU&TPU
嵌入式硬件·学习·cpu·gpu·tpu
linhhanpy11 小时前
自制虚拟机(C/C++)(二、分析引导扇区,虚拟机读二进制文件img软盘)
c语言·汇编·c++·python·stm32·操作系统
LS_learner11 小时前
42步进电机
嵌入式硬件
LS_learner11 小时前
PCA9685 一款由 NXP Semiconductors 生产的 16 通道、12 位 PWM(脉宽调制)控制器芯片
嵌入式硬件
stm32发烧友16 小时前
基于 STM32 的智能电梯控制系统
stm32·单片机·嵌入式硬件