stm32定时器输出pwm&IO口模拟pwm——呼吸灯


前言

什么是pwm波?pwm就是脉冲宽度调制,就是占空比可变的脉冲波形,通过改变占空比,输出不同的pwm波,就能实现许多有趣的功能,比如说我们生活中常见的呼吸灯就是通过这样实现的。接下来我们就以呼吸灯为例,学习stm32通过定时器输出pwm波和io口模拟输出pwm波。


一、pwm(脉冲宽度调制)

1.基本原理

控制方式就是对逆变电路开关器件的通断进行控制,使输出端得到一系列幅值相等的脉冲,用这些脉冲来代替正弦波或所需要的波形。也就是在输出波形的半个周期产生多个脉冲,使各个脉冲的等值电压为正弦波形,所获得的输出平滑且低次谐波少,按一定的规则对各脉冲的宽度进行调制,既可改变逆变电路输出电压的大小,也可以改变输出频率。

2.PWM的优点

  • PWM的一个优点是从处理器到被控系统信号都是数字形式的,无需进行数模转换。让信号保持为数字形式可将噪声影响降到最小。噪声只有在强到足以将逻辑1改变为逻辑0或将逻辑0改变为逻辑1时,也才能对数字信号产生影响。
  • 对噪声抵抗能力的增强是PWM相对于模拟控制的另外一个优点,而且这也是在某些时候将PWM用于通信的主要原因。从模拟信号转向PWM可以极大地延长通信距离。在接收端,通过适当的RC或LC网络可以滤除调制高频方波并将信号还原为模拟形式。总之,PWM既经济、节约空间、抗噪性能强,是一种值得广大工程师在许多设计应用中使用的有效技术。

3.PWM波的控制方法

  • 等脉宽PWM法
    等脉宽PWM法是PWM法中最为简单的一种,它是把每一脉冲的宽度均相等的脉冲列作为PWM波,通过改变其周期,达到调频的效果,改变脉冲的宽度或占空比可以调压,采用适当控制方法即可使电压与频率协调变化。
  • SPWM法
    SPWM法是一种比较成熟的,如今使用较广泛的PWM法,前面提到的采样控制理论中的一个重要结论:冲量相等而形状不同的窄脉冲加在具有惯性的环节上时,其效果基本相同的。
  • 电流控制PWM
    电流控制PWM的基本思想是把希望输出的电流波形作为指令信号,把实际的电流波形作为反馈信号,通过两者瞬时值的比较来决定各开关器件的通断,使实际输出随指令信号的改变而改变。

二、定时器的相关介绍

1.stm32定时器

定时器,顾名思义,就是拿来定时的机器。在stm32单片中,定时器是存在于stm32中的一个外设。分别有高级定时器通用定时器基本定时器,也有些型号的单片机没有高级定时器,具体要去查看芯片手册。

定时器种类 位数 计数模式 捕获/比较 互补输出 应用场景
高级定时器 16 向上,向下,向上/下 4 带死区用来紧急刹车或者产生pwm控制
通用定时器 16 向上,向下,向上/下 4 pwm输出,输入捕获,输出比较
基本定时器 16 向上,向下,向上/下 4 主要应用于驱动DAC

2.通用定时器计数模式

  • 向上计数模式:计数器从0计数到自动加载值(TIMx_ARR),然后重新从0开始计数并且产生一个计数器溢出事件。
  • 向下计数模式:计数器自动装入的值(TIMx_ARR)开始向下计数到0,然后从自动装入的值重新开始,并且产生一个计数器向下溢出事件。
  • 中央对齐模式:计数器从0开始计数到自动装入的值-1,产生一个计数器溢出事件,然后向下计数到1,并且产生一个计数器溢出事件;然后再从0开始重新计数。

在这里插入图片描述

3.定时器的基本工作原理

  • 定时器的框图
    在这里插入图片描述
  • 时钟产生单元

内部时钟(CK_INT)
外部时钟模式:外部触发输入(ETR)
内部触发输入(ITRx):使用一个定时器作为另一个定时器的与分屏器,如可以配置一个定时器Timer1而作为另一个定时器Timer2的预分频器。
外部时钟模式:外部输入脚(TIx)

  • 时基单元

计数寄存器(TIMx_CNT)
向上计数、向下计数或者中心对齐计数;
预分频寄存器(TIMx_PSC)
可将时钟频率按1到65535之间的任意值进行分频,可在运行的时候改变其设置值;
自动重装载寄存器(TIMx_ARR)
如果TIMx_CR1寄存器中的ARPE位为0,ARR寄存器的内容将直接写入影子寄存器;如果ARPE为1,ARR寄存器的内容将在每次的更新时间发生时传送到影子寄存器;
如果TIMx_CR1中的UDIS位为0,当计数器产生溢出条件时,产生更新事件。

  • 输入捕获通道

IC1、2和IC3、4可以分别通过软件设置将其映射到TI1、TI2和TI3、TI4;
4个16位捕捉比较寄存器可以编程用于存放检测到对应的每一次输入捕捉时计数器的值;
当产生一次捕捉,相应的CCxIF标志位被置1;同时如果中断或DMA请求使能,则产生中断或DMA请求。
如果当CCxIF标志位已经为1,当又产生一个捕捉,则捕捉溢出标志位CCxOF将被置1。

三、定时器输出一路pwm

1.定时器的相关初始化

void PWM_Init(u16 arr,u16 psc)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_OCInitTypeDef TIM_OCInitStructure;
    TIM_DeInit(TIM3);

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO,ENABLE);
    
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    //初始化TIM3
    TIM_TimeBaseStructure.TIM_Period = arr;
    TIM_TimeBaseStructure.TIM_Prescaler = psc;
    TIM_TimeBaseStructure.TIM_ClockDivision = 0;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);

    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCNPolarity_High;
    TIM_OC1Init(TIM3, &TIM_OCInitStructure);
    //TIM_OC4Init(TIM3, &TIM_OCInitStructure);
    TIM_OC1PreloadConfig(TIM3,TIM_OCPreload_Enable);//使能ccr2上的预装载寄存器
    //TIM_OC4PreloadConfig(TIM8,TIM_OCPreload_Enable);
    TIM_ARRPreloadConfig(TIM3,ENABLE);
    TIM_CtrlPWMOutputs(TIM3,ENABLE);
    TIM_Cmd(TIM3,ENABLE);
} 

定时器的初始化需要开启许多时钟,配置许多寄存器,虽然标库已经很简便,但还是略微的有点麻烦,大家可以通过STM32CubeMX进行相关配置。

2.主函数程序

int main(void)
{
	uint8_t dir = 1;
	uint16_t pwm = 0;
    Delay_Init();
    Motor_init();
	PWM_Init(1999,719);
	My_Uart_Init(115200);
    while(1)
    {
			if(dir)
			{
				Delay_ms(1);
				pwm++;
			  	TIM_SetCompare1(TIM3,pwm);
				if(pwm == 2000) dir = 0;
			}
			if(!dir)
			{
				Delay_ms(1);
				pwm--;
			  	TIM_SetCompare1(TIM3,pwm);
				if(pwm == 0) dir = 1;
			}
    }
}

3.实物效果展示

在这里插入图片描述

四、IO口模拟输出pwm

pwm实质上就是在相同的周期输出不同时长的高低电平,用io口也可以模拟出来,因为有时候定时器资源可能不是很够。用io口模拟也是一个好方法。

1.定时器初始化及中断函数

void TIM3_Init(void)
{
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 				//设置NVIC中断分组2:2位抢占优先级,2位响应优先级	

	NVIC_InitTypeDef NVIC_InitStructure;							//中断优先级NVIC设置
	
	NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;  				//TIM3中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  		//先占优先级0级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;  			//从优先级3级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; 				//IRQ通道被使能
	NVIC_Init(&NVIC_InitStructure);  
    
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); 			//时钟使能
	
	//定时器TIM3初始化
	TIM_TimeBaseStructure.TIM_Period = 10;		 				//自动重装载寄存器周期的值1/CK_CNT=1us,1000x1us=1ms	
	TIM_TimeBaseStructure.TIM_Prescaler =71; 						//设置用来作为TIMx时钟频率除数的预分频值CK_CNT=CK_INT/(71+1)=1MHz
	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; 		//TIM_CKD_DIV1是.h文件中已经定义好的,TIM_CKD_DIV1=0,也就是时钟分频因子为0
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  	//TIM向上计数模式
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); 				//根据指定的参数初始化TIMx的时间基数单位
 
	TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE );						 //使能指定的TIM3中断,允许更新中断
	
	TIM_Cmd(TIM3, ENABLE);  										//使能TIMx					 
}
void TIM3_IRQHandler()
{
	if(TIM_GetITStatus(TIM3,TIM_IT_Update) != RESET)
	{
		pwm_val++;
		if(pwm_val<pwm_set) GPIO_ResetBits(GPIOC,GPIO_Pin_13);
		if(pwm_val>pwm_set)	GPIO_SetBits(GPIOC,GPIO_Pin_13);
		if(pwm_val == PWM_MAX) pwm_val = 0;
		TIM_ClearITPendingBit(TIM3,TIM_IT_Update);
	}
}

2.main.c函数

u16 pwm_set = 0;
int main(void)
{
	uint8_t dir = 1;
    Delay_Init();//延时函数初始化
	led_init();//led灯的初始化
	TIM3_Init();//定时器初始化
    while(1)
    {
			if(dir)
			{
				Delay_ms(1);
				pwm_set++;
				if(pwm_set == 2000) dir = 0;
			}
			if(!dir)
			{
				Delay_ms(1);
				pwm_set--;
				if(pwm_set == 0) dir = 1;
			}
			
    }
}

3.实物效果展示

在这里插入图片描述


总结

PWM技术在生活中很多领域都有运用到,大多是对伺服电机的控制,如电机、舵机等等,通过pwm波可以使电机有不同的转速,可以使舵机转动不同的角度,这样在一些特定的场合起着不一样的作用。在学习pwm的过程中还是比较简单的,配置stm32定时器就能输出pwm波,但是运用在不同的场景往往并不是单单的pwm的调制,还要与许多算法的结合,还需要我们不断的去学习,去扩充自己的知识。