一、PWM简介

1.定义

脉冲宽度调制(PWM) 是一种数字信号,最常用于控制电路。该信号在预定义的时间和速度中设置为高(5v或3.3v)和低(0v)。通常,我们将PWM的高电平称为1,低电平为0。

2.主要参数

(1)PWM占空比
PWM信号保持高电平的时间百分比称为占空比。如果信号始终为高电平,则它处于100%占空比,如果它始终处于低电平,则占空比为0%。如图1所示,T1为占空比,T为一个PWM周期。
在这里插入图片描述
图1 图片来源于网络

(2)PWM的频率
PWM信号的频率决定PWM完成一个周期的速度。STM32的MDK编译器可以选择5MHZ,10MHZ,20MHZ和50MHZ。

二、PWM产生方式

通过STM32控制板,有两种方式能产生PWM,第一是利用普通IO口输出PWM,第二种是利用定时器的PWM的IO口或复用IO口

1.普通IO口与PWM口

(1)PWM端口
STM32 的定时器除了 TIM6 和 7。其他的定时器都可以用来产生 PWM 输出。其中高级定时器 TIM1 和 TIM8 可以同时产生多达 7 路的 PWM 输出。而通用定时器也能同时产生多达 4路的 PWM 输出,这样,STM32 最多可以同时产生 30 路 PWM 输出。

(2)普通IO口
一般能够输出PWM的端口都会在主要功能那一栏出现CHx的标志,而普通定时器没有出现这种标志。如图所示,上面的红框就是普通的定时器,不是专用的PWM端口。
在这里插入图片描述
图2 普通IO定时器与PWM定时器
(3)两者区别
1)一般而言,尽量选用PWM口进行PWM输出,因为普通IO口模拟PWM的输出频率越高,进入定时器中断的次数就越快,中断间隔的时间越短,如果再有其他类型的中断也要处理时,会因为中断的优先级嵌套等待响应,影响控制精度,PWM输出误差增大,也会影响其他如ADC等中断处理,甚至会较出现单片机逻辑出错,死机或者跑飞的情况。

2)普通IO也可以输出PWM,只是产生PWM一般用转用芯片(开关电源上用的较多)或者单片机的PWM内置模块如定时器,很小直接用MCU的IO口线直接输出因为那样太耗MCU资源了。

(4)STM32f103c8t6的PWM口
因为自己在用stm32最小系统,因此在此贴出其PWM口配置。
[注]不是所有的芯片都有重映像功能的,STM32f103c8t6这四个定时器就不需要重映像。

详细配置如下:
TIM1_CH1->PA8;
TIM1_CH2->PA9;
TIM1_CH3->PA10;
TIM1_CH4->PA11;

TIM2_CH1->PA0;
TIM2_CH2->PA1;
TIM2_CH3->PA2;
TIM2_CH4->PA3;

TIM3_CH1->PA6;
TIM3_CH2->PA7;
TIM3_CH3->PB0;
TIM3_CH4->PB1;

TIM4_CH1->PB6;
TIM4_CH1->PB7;
TIM4_CH1->PB8;
TIM4_CH1->PB9;

2.普通IO口产生PWM

(1)方法:定时器中断
普通IO口如何产生一个pwm?其实就是通过一个高低电平周期性的变化。这种思想很重要,确定频率就可以确定周期(T=1/f)也就是在一个周期内产生pwm的时间可以确定下来了。

如何改变占空比?确定了时间,高电平的时间不就是想要的占空比么,比如要产生一个频率1khz,占空比为70%的pwm,根据频率我们知道了周期为1ms,产生一个占空比为70%的不就是0.7ms的时间给高电平么,(我们用定时器中断的方式,使0.1ms产生一次中断,计数中断次数,中断处理函数前七次中断都给高电平就ok了。
参考链接:https://blog.csdn.net/m0_51095029/article/details/116426226
代码如下(示例)

//#include "stm32f10x.h"
#include "stdio.h"
#include "usart.h"
#include "tim.h"
#include "led.h"
#include "misc.h"

void TIM2_NVIC(void);
void TIM2_Init(void);
unsigned char ucLed;
unsigned char ucLCK;

int main(void)
{
  SysTick_Config(72000);			// 定时1ms(HCLK = 72MHz)
  TIM2_Init();
	TIM2_NVIC();
	LED_Init();
 
	while(1)
		        { 
				 LED_Disp(ucLed);
				}
  
}  

//unsigned int count;
//中断服务函数
unsigned int count=0;
unsigned int  i;
void TIM2_IRQHandler(void) 
{  count++;//计中断次数
	
	if(TIM_GetITStatus(TIM2,TIM_IT_Update) != RESET)
	{	
	  if(count%10<i)//i:改变占空比值,这里i为变量,且让占空比一直变化
		ucLed =0xff;//高电平
		
		else
		ucLed =0;
	
	 TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
 }
}

 void TIM2_NVIC(void)
 {
  NVIC_InitTypeDef NVIC_InitStructure;
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断分组
  NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;//设置TIM2中断
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;//抢占优先级为0
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//响应优先级为1
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能中断源
  NVIC_Init(&NVIC_InitStructure);
 }

 void TIM2_Init(void)//0.1ms定时
{
  TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
 // RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
  
  TIM_TimeBaseInitStruct.TIM_Prescaler = (2-1);//预分屏系数
  TIM_TimeBaseInitStruct.TIM_Period = (36000/10)-1;//自动重载计数周期值  
  TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;//设置为向上计数方式
  TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;//时钟分频系数
  TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStruct);
  TIM_ClearFlag(TIM2,TIM_FLAG_Update);//清除中断
  TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);//使能TIM2的更新中断
  TIM_Cmd(TIM2, ENABLE);//使能TIM2定时器
}  

//此函数必须有
void SysTick_Handler(void)
{
	//ucLCK++;
	if(ucLCK++%500==0)
	{
		i++;
		if(i==10)
		i=0;
	}	
}

3.PWM口产生PWM

(1)方法:比较匹配+溢出中断
(2)使用前的基本知识

  • 寄存器
  1. 定时器中断相关寄存器:CNT、ARR、PSC、 CR1
    (1)计数器当前寄存器CNT
    在这里插入图片描述
    (2)预分频寄存器PSC
    在这里插入图片描述
    (3)自动重装载寄存器PSC
    在这里插入图片描述
    (4)控制寄存器 CR1
    在这里插入图片描述
  2. 控制 PWM 的输出寄存器:CCMR1/2、CCER、CCR1~4、BDTR
    (1)捕获/比较模式寄存器( TIMx_CCMR1/2)
    (2)捕获/比较使能寄存器( TIMx_CCER)
    (3)捕获/比较寄存器( TIMx_CCR1~4)
    (4)刹车和死区寄存器( TIMx_BDTR)

[注]刹车和死区寄存器( TIMx_BDTR)仅在高级定时器中使用,普通定时器只需要配置前三个寄存器即可。

有关这几个寄存器的学习内容请点击下面的链接
原文链接:https://blog.csdn.net/wwt18811707971/article/details/74906149

  • 定时器
  1. 定时器框图
将通用定时器分为四个部分:
      1,选择时钟
      2,时基电路
      3,输入捕获
      4,输出比较

在这里插入图片描述

  • PWM工作原理
  1. 工作框图
    在这里插入图片描述
1,TIMx_CCMR1寄存器的OC1M[2:0],设置输出模式控制器
    110:PWM模式1
    111:PWM模式2
 
2,计数器值TIMx_CNT与通道1捕获比较寄存器CCR1进行比较,通过比较结果输出有效电平和无效电平
    OC1REF=0 无效电平
    OC1REF=1 无效电平
 
3,通过输出模式控制器产生的信号
TIMx_CCER寄存器的CC1P位,设置输入/捕获通道1输出极性
    0:高电平有效
    1:低电平有效
 
4,TIMx_CCER:CC1E位控制输出使能电路,信号由此输出到对应引脚
    0:关闭
    1:开启

2.占空比产生原理

如图为向上计数:
     定时器重装载值为ARR,比较值CCRx
     t时刻对计数器值和比较值进行比较
     如果计数器值小于CCRx值,输出低电平
     如果计数器值大于CCRx值,输出高电平
PWM的一个周期
    定时器从0开始向上计数
    当0-t1段,定时器计数器TIMx_CNT值小于CCRx值,输出低电平
    t1-t2段,定时器计数器TIMx_CNT值大于CCRx值,输出高电平
    当TIMx_CNT值达到ARR时,定时器溢出,重新向上计数...循环此过程
    至此一个PWM周期完成
影响因素
    ARR : 决定PWM周期(在时钟频率一定的情况下,当前为默认内部时钟CK_INT)
    CCRx : 决定PWM占空比(高低电平所占整个周期比例)

(3)PWM配置过程
这里参考原子哥的TIM3时钟产生一个PWM的过程。
在这里插入图片描述
(4)PWM口输出示例

/*
***************************************************
实验功能:
通过比较和溢出功能,模拟pwm输出,在PC8-PC15输出可变pwm
***************************************************
*/
#include "stdio.h"
#include "led.h"
#include "misc.h"
 
 void TIM2_Config(uint16_t duty2);
 unsigned char ucLed;
 unsigned int Compare2=100;
 unsigned int ulTick;
 
int main(void)
{
  SysTick_Config(72000);			// 定时1ms(HCLK = 72MHz)
  TIM2_Config(999/4);     //25%
  TIM_SetCompare2(TIM2,Compare2);//直接调节占空比函数
  LED_Init();
	
while(1)
 { 
	    if(ulTick%1000==0)
					 {
						Compare2+=50;
						if(Compare2>999)
						Compare2=100;
							 
						TIM_SetCompare2(TIM2,Compare2);
					 }	
 }

}  

//输出比较
void TIM2_Config(uint16_t duty1) //TIM2 CH2 
{
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure; 
	TIM_OCInitTypeDef  TIM_OCInitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	
	//RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); 
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);	
	
	TIM_TimeBaseStructure.TIM_Period = 999;  //1KHz  //自动重装载值
	TIM_TimeBaseStructure.TIM_Prescaler = 71;  //预分频系数
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数
	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;//时钟分频系数
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); //基本定时器配置
	TIM_ClearFlag(TIM2,TIM_FLAG_Update);//清除中断
	TIM_ITConfig(TIM2,TIM_IT_Update|TIM_IT_CC2,ENABLE);//使能TIM2的更新中断
	TIM_PrescalerConfig(TIM2,71, TIM_PSCReloadMode_Immediate);	 //	TIM2  预分频值   预分频重载模式
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //选择定时器模式,脉冲宽度调制模式2
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Disable;//禁用比较输出功能
	TIM_OCInitStructure.TIM_Pulse = duty1;//占空比
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;//设置比较输出有效电平
	TIM_OC2Init(TIM2, &TIM_OCInitStructure);//通道二
    TIM_OC2PreloadConfig(TIM2,TIM_OCPreload_Enable);//使能寄存器TIM_CCMR1
	
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断分组
	NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;//设置TIM2中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;//抢占优先级为0
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;//响应优先级为0
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能中断源
    NVIC_Init(&NVIC_InitStructure);
	
	TIM_Cmd(TIM2, ENABLE);//使能TIM2定时计数器
}

//中断处理函数
 void TIM2_IRQHandler(void) 
{  
	
	if(TIM_GetITStatus(TIM2,TIM_IT_Update) != RESET)  //溢出中断
	 {	
	 ucLed = 0x00;  //关闭所有LED
	 LED_Disp(ucLed);
	 TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
	 }
 else if(TIM_GetITStatus(TIM2,TIM_IT_CC2) != RESET) //比较匹配
	 {
	 ucLed = 0xff;//打开所有LED
	 LED_Disp(ucLed);
	 TIM_ClearITPendingBit(TIM2,TIM_IT_CC2);
	 }
}

//此函数必须有
void SysTick_Handler(void)
{
	ulTick++;
}


总结

本人在学习STM32过程中发现网上的PWM相关知识比较散乱,对于小白或新手而言会有很多不了解的地方。因此,这里结合自己的学习过程,将PWM这一版块的知识整合,如有错误之处,请批评指正!

参考链接归纳

链接:
霁风AI:STM32学习笔记一一PWM 输出
位文杰TOP:STM32 PWM基本知识及配置过程
菜鸟江多多:STM32普通io口模拟pwm输出的三种方法

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐