蓝桥嵌入式之 PWM波输出相关总结

微信搜索ReCclay,即可免费阅读博主蓝桥系列所有文章,后台回复“代码”即可获取蓝桥所有备赛代码!关注博主公众号,还可拥有加入博主粉丝群实时沟通技术难题、免费下载CSDN资源等多项福利,还在等什么呢?快快扫码关注,学习才不会迷路

在这里插入图片描述

这里再向各位同学推荐一个CSDN博主 ReRrain 的蓝桥备赛博客,博主秉持初学者思路,向你讲述自己蓝桥备赛的心路历程,娓娓道来蓝桥备赛经验,个人觉得非常不错,值得细细品读。


导读:《蓝桥杯嵌入式组》专栏文章是博主2019年参加蓝桥杯的嵌入式组比赛所做的学习笔记,在当年的比赛中,由于忙于准备考研及保研相关工作,博主仅仅参加了当年的省赛,并获得了省赛一等奖的成绩。成绩虽谈不上最好,但至少问心无愧。如今2021年回头再看该系列文章,仍然感触颇多。为了能更好地帮助到单片机初学者,今年特地抽出时间对当年的文章逻辑和结构进行重构,以达到初学者快速上手的目的。需要指出的是,由于本人水平有限,如有错误还请读者指出,非常感谢。那么,接下来让我们一起开始愉快的学习吧。

“一叶遮目,不见泰山”。不论何事,只有把握事情的总体趋势,才能做到心中有数。


几个留待补充的程序:


1、高级定时器实现互补PWM波


2、同一TIM不同通道,产生相位不同的PWM波


3、高级定时器的输出比较产生PWM


之前在蓝桥的单片机比赛阶段对于PWM的一次又一次深入,印象颇深。而今到了嵌入式,更感觉尤为重要,这里也算做个小结,如有错误,不吝指教。


阅读此篇博文的同时,也可参阅之前写的一篇ST32之PWM小结<传送门>。


几个带着写博客的问题回答下:

1、关于PWM的上限频率呢?

跟周期有关即PWM模式下的arr和输出比较中的pulse…,也不太好说,没具体环境,具体情况再分析吧。

2、PWM模式下同时可以几个通道输出呢?

有几个通道就能输出几个呗。


一、通用和高级定时器的PWM模式产生PWM

PWM模式的特点:同一定时器中,不同的通道下,输出的频率固定,占空比可变。

通用定时器PWM模式,TIM3_CH1 和 TIM3_CH2输出,引脚为PA6 PA7…工程代码移步Github<传送门>

1、通用定时器PWM模式输出主要代码

在这里插入图片描述

小于Pluse为高电平,大于Pluse小于ARR为低电平。

main.c

/*******************************************************************************
* 文件名:main.c
* 描  述:
* 作  者:CLAY
* 版本号:v1.0.0
* 日  期: 2019年1月25日
* 备  注:通用定时器输出硬件PWM PA6-TIM3_CH1 PA7-TIM3_CH2
*         并通过按键4改变占空比,初始占空比40%,每次加10%
*******************************************************************************
*/

#include "config.h"
#include "led.h"
#include "key.h"
#include "timer.h"
#include "beep.h"
#include "lcd.h"
#include "stdio.h"
#include "usart.h"
#include "i2c.h"
#include "eeprom.h"
#include "PWMMode.h"

int main(void)
{
	
	STM3210B_LCD_Init();
	LCD_Clear(Blue);
	LEDInit();
	KeyInit();
	BeepInit();
	TIM2Init(2000, 72);//定时2ms

	TIM3_PWMInit(1000, 40, 80);//频率2K CH1占空比40% CH2占空比80%
	
	while(1)
	{	
		KeyDriver();
	}
}

void KeyAction(int code)
{
	static u32 x = 400; 
		
	if(code == 1)//按下B1,切换灯状态,蜂鸣器鸣叫0.1s
	{
		GPIOC->ODR ^= (1<<8);//PC8不断取反
		GPIOD->ODR |= (1<<2);//PD2置1,使能573锁存器
		GPIOD->ODR &= ~(1<<2);//PD2清0,关闭573锁存器
		Beep(100);
	}
	else if(code == 2)
	{
		Beep(-1);
	}
	else if(code == 3)
	{
		Beep(0);
	}
	else if(code == 4)
	{
		x += 100;
		TIM_SetCompare1(TIM3, x);//改变占空比
	}
}



PWMMode.c

#include "PWMMode.h"

void TIM3_IOInit(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
	//***注意这里是APB2***
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能 GPIOA时钟
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7; //TIM3通道1-PA6 TIM3通道2-PA7
	//***复用推挽输出(PWM模式)***
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; 
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化 GPIO
}

void TIM3Init(u32 arr, u8 ch1_duty, u8 ch2_duty)
{
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
	
	//***注意这里是APB1***
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);  //使能定时器3时钟 
	TIM_TimeBaseStructure.TIM_Period = arr-1; //设置在自动重装载周期值
	TIM_TimeBaseStructure.TIM_Prescaler = 72-1; //设置预分频值
	TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM 向上计数模式
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //初始化 TIM3
}

void TIM3_PWMInit(u32 freq, u8 ch1_duty, u8 ch2_duty)
{
	u32 arr;
	
	TIM_OCInitTypeDef TIM_OCInitStructure;
	arr = 1000000 / freq; 

	TIM3_IOInit();//TIM3通道1-PA6 TIM3通道2-PA7配置
	TIM3Init(arr, ch1_duty, ch2_duty);//TIM3定时器配置
	
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;//PWM1模式
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;//输出极性高
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
	TIM_OCInitStructure.TIM_Pulse = (arr - 1) * ch1_duty / 100;
	//TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);//预装载使能位无所谓,不影响
	TIM_OC1Init( TIM3, &TIM_OCInitStructure);
			
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;//PWM2模式
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;//输出极性低
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;//比较输出使能
	//***(arr-1)是总的计数值,而(arr-1)*占空比是电平反转时的计数值,故控制pluse就可以控制占空比***
	TIM_OCInitStructure.TIM_Pulse = (arr - 1) * ch2_duty / 100;
	//TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);//预装载使能位无所谓,不影响
	TIM_OC2Init( TIM3, &TIM_OCInitStructure);
	//***高级定时器必须有这一句,而通用就不必了***
	//TIM_CtrlPWMOutputs(TIM3, ENABLE);//使能PWM输出 
	TIM_Cmd(TIM3, ENABLE); //使能 TIM3
}

PWMMode.h

#ifndef _PWMMODE_H
#define _PWMMODE_H

#include "config.h"

void TIM3_PWMInit(u32 freq, u8 ch1_duty, u8 ch2_duty);

#endif

2、通用定时器PWM模式输出需要注意的地方

通用定时器的硬件PWM模式,改变占空比通过TIM_SetCompare1函数,注意最大数值是等于arr即周期值!

①、我们要用TIM3的CH1和CH2通道,这两个可以从数据手册(P19)查阅到对应的复用引脚是PA6和PA7。
在这里插入图片描述
②、我们要想使用定时器的PWM模式,配置流程需要遵循 IO引脚配置 -> 定时器配置 -> PWM模式配置的顺序。

③、IO引脚为PA,对应时钟总线在APB2,而TIM3对应时钟总线是APB1。
在这里插入图片描述

④、定时器配置中的TIM_OCInitStructure结构体,我们通过程序溯源可以找到其成员变量,可以发现其共有8个,但是其中4个通用定时器无需配置,高级定时器才需要配置。

在这里插入图片描述

⑥、PWM配置中TIM_OCInitStructure结构体的两个成员需要注意下,TIM_OCModeTIM_OCPolarity

前者TIM_OCMode取值有两个分别为TIM_OCMode_PWM1TIM_OCMode_PWM2。后者取值也有两个TIM_OCPolarity_HighTIM_OCPolarity_Low


PWM1和PWM2参考正点原子的《STM32F1开发指南-寄存器版本_V3.1 》P255…

说白了,这两个都是管PWM初始状态的,惯用的搭配是TIM_OCMode_PWM1TIM_OCPolarity_High一对,TIM_OCMode_PWM2TIM_OCPolarity_Low一对,这样搭配是正逻辑,否则为反逻辑。什么意思呢?看下面的图吧。

主函数调用TIM3_PWMInit(1000, 40, 80);

I、CH1为TIM_OCMode_PWM1TIM_OCPolarity_High,CH2为TIM_OCMode_PWM1TIM_OCPolarity_High时:
在这里插入图片描述
在这里插入图片描述

是符合主函数调用逻辑的,故称之为正逻辑。

II、CH1为TIM_OCMode_PWM2TIM_OCPolarity_High时:
在这里插入图片描述

此时占空比主函数调用时40%,而实际是60%,故称之为反逻辑。

III、CH1为TIM_OCMode_PWM2TIM_OCPolarity_Low时:

在这里插入图片描述
此时变又是正逻辑了,所以记住固定搭配就行了。

⑦、关于PWMMode.c中注释的一句

	//***高级定时器必须有这一句,而通用就不必了***
	//TIM_CtrlPWMOutputs(TIM3, ENABLE);//使能PWM输出 

参考正点原子的《STM32F1开发指南-寄存器版本_V3.1 》P318…

只有高级定时器PWM输出时,必须加上这一句,而这里我们是通用定时器,所以没有加。

⑧、关于TIM_OCInitStructure结构体重的TIM_Pulse理解

(arr-1)是总的计数值,而(arr-1)*duty是电平反转时的计数值,故pluse = 占空比

⑨、关于PWMMode.c中的TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);用来使能输出/比较预装载使能位。

输出比较预装载使能位OCxPE位于捕获/比较模式寄存器TIMx_CCMRx中。
在这里插入图片描述
若使能了该位,写入到TIMx_CCRx寄存器的比较值将在更新事件到来时才会传入到当前捕获/比较寄存器,否则未使能,比较值将立即写入当前捕获比较寄存器

在PWM模式下,预装载使能位无论使能与否都不影响。

哈,注意了,当前捕获比较寄存器是一个名词。

每个通用定时器拥有4路捕获/比较寄存器,每路通道都拥有一个捕获/比较寄存器TIMx_CCRx用于装载比较值。该寄存器同样包括两个寄存器,一个是用户可以操作的:用于写入比较值;另一个是用于和计数器CNT进行比较的当前捕获比较寄存器

对了,关于这个函数也有要说的,我们在程序中顺着这个函数寻找它的定义会找到
在这里插入图片描述
也许会有一个疑问,CH2的预装载使能位在手册上写的不是第11位吗?

在这里插入图片描述
怎么到了库函数里面变成了8,

在这里插入图片描述
莫急我们继续看看TIM_OCPreload,再寻找这个的定义

在这里插入图片描述

看,在这里插入图片描述

原来使能的时候不是0x0001啊,而是0x0008,那么这么算起来刚好不就对应11位了么?完美~~


附几个寄存器名,混个眼熟

寄存器名称功能
TIM_OCMode设置模式( PWM模式/输出比较模式 )
TIM_OutputState设置比较输出使能
TIM_OCPolarity设置极性高低
寄存器(CN)Register(EN)
捕获/比较模式寄存器1TIMx_CCMR1
捕获/比较使能寄存器TIMx_CCER
计数器TIMx_CNT
预分频器TIMx_PSC
自动重装载寄存器TIMx_ARR
捕获比较寄存器1TIMx_CCR1
3、高级定时器PWM模式输出主要代码

既然上面详细介绍了通用定时器的PWM输出功能,这里也再附上高级定时器的PWM输出功能吧,方便日后查阅… 工程代码移步Github<传送门>

main.c

/*******************************************************************************
* 文件名:main.c
* 描  述:
* 作  者:CLAY
* 版本号:v1.0.0
* 日  期: 2019年1月26日
* 备  注:高级定时器PWM输出 PA9-TIM_CH2 PA10-TIM_CH3
*         并通过按键4增加占空比,初始占空比为40%,每次增加10%
*******************************************************************************
*/

#include "config.h"
#include "led.h"
#include "key.h"
#include "timer.h"
#include "beep.h"
#include "lcd.h"
#include "stdio.h"
#include "usart.h"
#include "i2c.h"
#include "eeprom.h"
#include "PWMMode.h"
#include "PWMMode_Advance.h"

int main(void)
{
	STM3210B_LCD_Init();
	LCD_Clear(Blue);
	LEDInit();
	KeyInit();
	BeepInit();
	TIM2Init(2000, 72);//定时2ms

	TIM3_PWMInit(1000, 40, 80);//频率2K CH1占空比40% CH2占空比80%
	TIM1_PWMInit(1000, 40, 80);//频率2K CH2占空比40% CH3占空比80%
	
	while(1)
	{	
		KeyDriver();
	}
}

void KeyAction(int code)
{
	static u32 x = 400;
	
	if(code == 1)//按下B1,切换灯状态,蜂鸣器鸣叫0.1s
	{
		GPIOC->ODR ^= (1<<8);//PC8不断取反
		GPIOD->ODR |= (1<<2);//PD2置1,使能573锁存器
		GPIOD->ODR &= ~(1<<2);//PD2清0,关闭573锁存器
		Beep(100);
	}
	else if(code == 2)
	{
		Beep(-1);
	}
	else if(code == 3)
	{
		Beep(0);
	}
	else if(code == 4)
	{
		x += 100;
		TIM_SetCompare2(TIM1, x);//改变TIM1_CH2占空比
	}
}

PWMMode_Advance.c

#include "PWMMode_Advance.h"

void TIM1_IOInit(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
	//***注意这里是APB2***
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能 GPIOA时钟
	//***根据实际电路选用了USART1的复用功能。***(要注意的是,拔掉跳线帽PA9 PA10)
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9|GPIO_Pin_10; //TIM_CH2 - PA9 //TIM_CH3 - PA10
	//***复用推挽输出(PWM模式)***
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; 
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化 GPIO
}

void TIM1Init(u32 arr, u8 ch1_duty, u8 ch2_duty)
{
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
	
	//***注意这里是APB2***
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);  //使能定时器1时钟 
	TIM_TimeBaseStructure.TIM_Period = arr-1; //设置在自动重装载周期值
	TIM_TimeBaseStructure.TIM_Prescaler = 72-1; //设置预分频值
	TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM 向上计数模式
	TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); //初始化 TIM1
}

void TIM1_PWMInit(u32 freq, u8 ch1_duty, u8 ch2_duty)
{
	u32 arr;
	
	TIM_OCInitTypeDef TIM_OCInitStructure;
	arr = 1000000/ freq;
	
	TIM1_IOInit();//TIM1通道2-PA9 TIM1通道3-PA10配置
	TIM1Init(arr, ch1_duty, ch2_duty);//TIM1定时器配置
	
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;//PWM1模式
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;//输出极性高
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
	TIM_OCInitStructure.TIM_Pulse = (arr - 1) * ch1_duty / 100;
	//TIM_OC2PreloadConfig(TIM1, TIM_OCPreload_Enable);//预装载使能位无所谓,不影响
	TIM_OC2Init( TIM1, &TIM_OCInitStructure);
			
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;//PWM2模式
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;//输出极性低
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;//比较输出使能
	//***(arr-1)是总的计数值,而(arr-1)*占空比是电平反转时的计数值,故控制pluse就可以控制占空比***
	TIM_OCInitStructure.TIM_Pulse = (arr - 1) * ch2_duty / 100;
	//TIM_OC3PreloadConfig(TIM1, TIM_OCPreload_Enable);//预装载使能位无所谓,不影响
	TIM_OC3Init( TIM1, &TIM_OCInitStructure);
	
	//***高级定时器必须有这一句,而通用就不必了***
	TIM_CtrlPWMOutputs(TIM1, ENABLE);//使能PWM输出 
	TIM_Cmd(TIM1, ENABLE); //使能 TIM1
}


PWMMode_Advance.h

#ifndef _PWMMODE_ADVANCE_H
#define _PWMMODE_ADVANCE_H

#include "config.h"

void TIM1_PWMInit(u32 freq, u8 ch1_duty, u8 ch2_duty);


#endif


4、高级定时器PWM模式输出需要注意的地方

①、TIM1是高级定时器,它是挂载到APB2总线上的.

②、根据TIM的四个捕获比较通道位置:PA8/PA9/PA10/PA11。再结合实际电路,确定了选择CH2-PA9和CH3-PA10作为PWM输出,但是此时要注意,拔掉对应的跳线帽。

在这里插入图片描述

③、对应的通道初始化应为TIM_OC2InitTIM_OC3Init,即对应的通道2和通道3。

④、高级定时器必须加这一句TIM_CtrlPWMOutputs(TIM1, ENABLE);//使能PWM输出

⑤、对应的逻辑分析仪波形
在这里插入图片描述
在这里插入图片描述

二、通用定时器输出比较产生PWM

输出比较的特点:不同通道产生不同频率不同占空比的方波

理解输出比较的一张重要图。
在这里插入图片描述

特别感受一种叠加的概念!总的计数值是0~0xFFFF(通用定时器是16位嘛),溢出重新从0开始,再往上加。而Pluse是我们的周期,也就是产生的波形频率,这个Pluse并不是对应计数器CNT的绝对范围,而是相对CNT的相对范围。就是Pluse加到设定值了,不管此刻对应的CNT是多少,我继续从当前CNT再计我的频率数。而占空占空比的是其中的Duty,上面这个示意图了解了,也就不难理解整个过程了。

小于Duty为高,大于Duty小于Pluse为低。

工程代码可见Github<传送门>

1、主要代码

main.c

/*******************************************************************************
* 文件名:main.c
* 描  述:
* 作  者:CLAY
* 版本号:v1.0.0
* 日  期: 2019年1月26日
* 备  注:通用定时器的输出比较 TIM4_CH1 - PB6    TIM4_CH2 - PB7
*         
*******************************************************************************
*/

#include "config.h"
#include "led.h"
#include "key.h"
#include "timer.h"
#include "beep.h"
#include "lcd.h"
#include "stdio.h"
#include "usart.h"
#include "i2c.h"
#include "eeprom.h"
#include "PWMMode.h"
#include "PWMMode_Advance.h"
#include "PWM_Compare.h"

int main(void)
{
	STM3210B_LCD_Init();
	LCD_Clear(Blue);
	LEDInit();
	KeyInit();
	BeepInit();
	TIM2Init(2000, 72);//定时2ms

	TIM3_PWMInit(2000, 40, 80);//频率2K CH1占空比40% CH2占空比80%
	TIM1_PWMInit(2000, 40, 80);//频率2K CH2占空比40% CH3占空比80%
	TIM4_PWMCompare(2000, 1000, 40, 80);//CH1占空比2K 40% CH2 1K占空比80%
	
	while(1)
	{	
		KeyDriver();
	}
}

void KeyAction(int code)
{
	if(code == 1)//按下B1,切换灯状态,蜂鸣器鸣叫0.1s
	{
		GPIOC->ODR ^= (1<<8);//PC8不断取反
		GPIOD->ODR |= (1<<2);//PD2置1,使能573锁存器
		GPIOD->ODR &= ~(1<<2);//PD2清0,关闭573锁存器
		Beep(100);
	}
	else if(code == 2)
	{
		Beep(-1);
	}
	else if(code == 3)
	{
		Beep(0);
	}
}



PWM_Compare.c

#include "PWM_Compare.h"

void TIM4_IOInit(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
	
	//***注意这里是APB2***
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //使能 GPIOB时钟
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7; //TIM4通道1-PB6 TIM4通道2-PB7
	//***复用推挽输出(PWM模式)***
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; 
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化 GPIO
}

void NVIC_TIM4Enable(void)
{
    NVIC_InitTypeDef NVIC_initstructure;

    NVIC_initstructure.NVIC_IRQChannel = TIM4_IRQn;           //选择TIM4中断通道
    NVIC_initstructure.NVIC_IRQChannelCmd = ENABLE;           //使能中断通道
    NVIC_initstructure.NVIC_IRQChannelPreemptionPriority = 0; //设定抢占优先级为0
    NVIC_initstructure.NVIC_IRQChannelSubPriority = 0;        //设定响应优先级为0
    NVIC_Init(&NVIC_initstructure);
}

void TIM4Init(void)
{
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
	
	//***注意这里是APB1***
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);  //使能定时器4时钟 
	//***注意这里设置的是0xFFFF
	TIM_TimeBaseStructure.TIM_Period = 0xFFFF; //设置计数值最大
	TIM_TimeBaseStructure.TIM_Prescaler = 72-1; //设置预分频值
	TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM 向上计数模式
	TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); //初始化 TIM4
	//***别忘了设置这个***
	NVIC_TIM4Enable();
}

u32 CH1_Val, CH2_Val, CH1_Duty, CH2_Duty;

void TIM4_PWMCompare(u32 ch1_freq, u32 ch2_freq, u32 ch1_duty, u32 ch2_duty)
{
//	u32 CH1_Val,  CH2_Val;//FUCK!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
	
	TIM_OCInitTypeDef TIM_OCInitStructure;

	CH1_Val = 1000000 / ch1_freq;
	CH2_Val = 1000000 / ch2_freq;
	CH1_Duty = CH1_Val * ch1_duty / 100;
	CH2_Duty = CH2_Val * ch2_duty / 100;
	
	TIM4_IOInit();//TIM4通道1-PB6 TIM4通道2-PB7配置
	TIM4Init();//TIM4定时器配置
	
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Toggle;//触发模式
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;//输出极性高
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
	TIM_OCInitStructure.TIM_Pulse = CH1_Val;
	TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Disable);预装载使能位失能
	TIM_OC1Init( TIM4, &TIM_OCInitStructure);
			
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Toggle;//触发模式
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;//输出极性高
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;//比较输出使能
	TIM_OCInitStructure.TIM_Pulse = CH2_Val;
	TIM_OC2PreloadConfig(TIM4, TIM_OCPreload_Disable);//预装载使能位失能
	TIM_OC2Init( TIM4, &TIM_OCInitStructure);
	
	TIM_SetCounter(TIM4, 0);//定时器计数值清0
	TIM_SetCompare1(TIM4, 0);//定时器捕获比较1寄存器值清0
	TIM_SetCompare2(TIM4, 0);//定时器捕获比较2寄存器值清0
	
	TIM_ClearITPendingBit(TIM4, TIM_IT_CC1|TIM_IT_CC2);//清除中断标志位 
	TIM_ITConfig(TIM4, TIM_IT_CC1|TIM_IT_CC2, ENABLE);//使能通道比较中断
	TIM_Cmd(TIM4, ENABLE); //使能 TIM4
}

PWM_Compare.h

#ifndef _PWM_COMPARE_H
#define _PWM_COMPARE_H

#include "config.h"

extern u32 CH1_Val, CH2_Val, CH1_Duty, CH2_Duty;

void TIM4_PWMCompare(u32 ch1_freq, u32 ch2_freq, u32 ch1_duty, u32 ch2_duty);

#endif

2、需要注意的地方

①、查阅数据手册可知,TIM4的四个输出比较通道在PB6/PB7/PB8/PB9,而结合实际电路PB6/PB7是接在EEPROM的,而PB8/PB9是用作LCD,LCD显然使用的较多,我们选择了牺牲I2C,即TIM3_CH1 - PB6和 TIM3_CH2 - PB7…
在这里插入图片描述

②、注意细节,局部变量和全局变量~~
在这里插入图片描述
③、配置好定时器后别忘了,中断向量的配置

在这里插入图片描述

④、关于TIM_OC1PreloadConfig

前面也有提及如使能了该位,写入到TIMx_CCRx寄存器的比较值将在更新事件到来时才会传入到当前捕获/比较寄存器,否则未使能,比较值将立即写入当前捕获比较寄存器

输出比较模式,当然是立即生效了,所以这里TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Disable);为Disable或者不写。

⑤、TIM_ClearITPendingBit对输出极性影响

固件库定时器初始化函数默认会产生更新事件并触发更新事件中断标志位,如果此时中断使能,程序会立即执行中断函数。

知道了这个先决条件,再结合程序就不难理解了,一起来分析分析吧。

当有TIM_ClearITPendingBit并且设置输出极性为TIM_OCPolarity_High的情况下,设置2K频率40%占空比和1K频率80%占空比的波形如下所示。

2k 40%
在这里插入图片描述
1k 80%
在这里插入图片描述
契合程序的要求,但是如果现在还是输出进行为TIM_OCPolarity_High没有了清除中断标志位这一操作。对应的波形又是什么样子呢?

2K 60%
在这里插入图片描述
1K 20%
在这里插入图片描述

刚好反过来了,为什么呢?说了半天还在卖关子,别介咱不是在分析问题么,问题现象出来了,再结合程序继续来看看

定时器4的中断源是输出比较,也就是说计数器一直在和输出比较寄存器的数进行比较,一旦相等就要进入中断。

初始的时候是计数器为0,假如现在初始的时候清除了中断标志位,一开始计数器为0,比较寄存器也是0,相等要进入中断,设置高电平时间持续时间t1=当前比较值(0)+duty,持续时间完毕后,计数器又和比较器相等,再进入中断,设置低电平持续时间t2 = 当前比较值+(pulse-duty)

由于没有使能预装载位,故在中断里一旦赋值,直接写入寄存器中。

直到再进中断,即完成了一个周期的方波输出,整个方波是先高后低(设置TIM_OCPolarity_High的前提下),接着再赋值t3=当前比较值+duty,就这样周而复始的循环…便产生了符合我们要求的方波信号。

说到这就不难立即为什么没有清除中断标志位就会刚好相反了,没有清除中断标志位一上来就直接进入中断(前面说了这是库函数决定了),然后瞬间又发现计数器和比较值一样,再进一次中断。整个过程当前比较值几乎没有更新还是0(整个过程想成很短的时间?),相当于直接设置了低电平输出duty时间,所以我们看到的波形刚好相反了。

哎呀呀,,,想不好想,解释起来更麻烦,感觉还没有解释好,就先这样吧,如果真有人看到了,有问题再留言交流吧。。

结语:以上就是本篇文章的全部内容啦,希望大家可以多多支持我的原创文章。如有错误,请及时指正,非常感谢。


微信搜索ReCclay,即可免费阅读博主蓝桥系列所有文章,后台回复“代码”即可获取蓝桥所有备赛代码!关注博主公众号,还可拥有加入博主粉丝群实时沟通技术难题、免费下载CSDN资源等多项福利,还在等什么呢?快快扫码关注,学习才不会迷路

在这里插入图片描述

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ReCclay

如果觉得不错,不妨请我喝杯咖啡

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值