PWM驱动呼吸灯

新建工程——添加.c,.h文件

根据上一次的博客,我们只需把下面这些模块打通,就可以输出PWM了。

第一步:RCC开启时钟,把我们需要的TIM外设和GPIO外设的时钟打开

第二步:配置时基单元,包括前面时钟源选择

第三步:配置输出比较单元,里面包括CCR的值,输出比较模式,极性选择,输出使能这些参数。在库函数也是用结构体统一来配置的,

第四步:配置GPIO,把PWM对应的GPIO口,初始化为复用推挽输出的配置。

这个PWM与GPIO的对应关系是怎样的呢?可以参考一下引脚定义表,

第五步:运行控制,启动计数器,这样就能输出PWM了。

接下来我们看一下与之相对应的函数。

打开

这些就是TIM外设对应的库函数,我们之前介绍了一部分,接下来我们继续看看。

void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC2Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC3Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC4Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);

这四个函数是用来配置输出比较模块的,OC就是(output compare) 输出比较,配置结构图的

这一部分。一个函数配置一个输出比较单元。参数TIMx,选择定时器,第二个是结构体,就是输出比较的那些参数了,

void TIM_OCStructInit(TIM_OCInitTypeDef* TIM_OCInitStruct);

这个是用来给输出比较结构体赋一个默认值的,到这里,输出比较的配置已经完成了。

下面是一些小功能和运行时更改参数的函数了,

void TIM_ForcedOC1Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction);
void TIM_ForcedOC2Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction);
void TIM_ForcedOC3Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction);
void TIM_ForcedOC4Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction);

这个是用来配置强制输出模式的,如果你在运行中想要暂停输出波形并且强制输出高或低电平。可以用这个函数,不过一般用的不多,

因为 强制输出高或低电平和设置100%,0%占空比是一样的,

void TIM_OC1PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
void TIM_OC2PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
void TIM_OC3PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
void TIM_OC4PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);

这四个函数是用来配置CCR寄存器的预装功能的,这个预装功能,就是影子寄存器,就是你写入的值不会立即生效,而是在更新事件才会生效。这样可以避免一些小问题。一般不用,了解一下即可。

void TIM_OC1FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast);
void TIM_OC2FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast);
void TIM_OC3FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast);
void TIM_OC4FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast);

这四个函数是用来配置快速使能的,这个功能在手册里单脉冲模式,那一节有一小段介绍。用的不多,不需要掌握

void TIM_ClearOC1Ref(TIM_TypeDef* TIMx, uint16_t TIM_OCClear);
void TIM_ClearOC2Ref(TIM_TypeDef* TIMx, uint16_t TIM_OCClear);
void TIM_ClearOC3Ref(TIM_TypeDef* TIMx, uint16_t TIM_OCClear);
void TIM_ClearOC4Ref(TIM_TypeDef* TIMx, uint16_t TIM_OCClear);

这个功能在手册里,外部事件时清除REF信号,那一小节有介绍,这个也不需要掌握,了解即可。

上面的函数就是小功能配置,用的不多,感兴趣自己可以去手册上看看,

void TIM_OC1PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
void TIM_OC1NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity);
void TIM_OC2PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
void TIM_OC2NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity);
void TIM_OC3PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
void TIM_OC3NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity);
void TIM_OC4PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);

这些函数就是用来单独设置输出比较的极性的,带个N的就是高级定时器里的互补通道的配置,OC4没有互补通道,所以没有OC4N的函数,那这里可以设置极性,在结构体初始化的那个函数也可以设置极性,这两个设置极性的作用是一样的,结构体初始化只不过是用结构体一起初始化的,而在这里是一个单独的函数进行修改的,一般来说,结构体里的参数,都会有一个单独的函数可以进行修改的。

void TIM_CCxCmd(TIM_TypeDef* TIMx, uint16_t TIM_Channel, uint16_t TIM_CCx);
void TIM_CCxNCmd(TIM_TypeDef* TIMx, uint16_t TIM_Channel, uint16_t TIM_CCxN);

这两个函数是用来单独修改输出使能参数的

void TIM_SelectOCxM(TIM_TypeDef* TIMx, uint16_t TIM_Channel, uint16_t TIM_OCMode);

选择输出比较模式。这个是用来单独更改输出比较模式的函数

void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1);
void TIM_SetCompare2(TIM_TypeDef* TIMx, uint16_t Compare2);
void TIM_SetCompare3(TIM_TypeDef* TIMx, uint16_t Compare3);
void TIM_SetCompare4(TIM_TypeDef* TIMx, uint16_t Compare4);

这四个是用来单独更改CCR寄存器值的函数。这四个函数比较重要,我们在运行的时候,更改占空比,就需要用到这四个函数

到这里,输出比较的函数就介绍完了

总结一下:红色标记很重要,需要掌握。

补充:

void TIM_CtrlPWMOutputs(TIM_TypeDef* TIMx, FunctionalState NewState);

这个函数仅高级定时器使用,在使用高级定时器输出PWM时,需要调用这个函数,使能主输出,否则PWM将不能正常工作。

我们先把定时器初始化的一部分代码拿过来

#include "stm32f10x.h"                  // Device header

void PWM_Init(void)
{
	//时基单元的代码写过,我们找之前定时中断的代码复制一下
	 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); //打开时钟
	
    TIM_InternalClockConfig(TIM2);//选择内部时钟
	
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;//初始化时基单元
	TIM_TimeBaseInitStructure.TIM_ClockDivision= TIM_CKD_DIV1 ;
	TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up ;
	TIM_TimeBaseInitStructure.TIM_Period=10-1;
	TIM_TimeBaseInitStructure.TIM_Prescaler=1-1;
    TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;
	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
	
	//初始化输出比较单元
	
	
	TIM_Cmd(TIM2,ENABLE);//启动定时器
}

然后进行初始化输出比较单元,你要初始化哪个通道,就调用哪个函数,不同的通道对应的GPIO口也是不一样的,所以这里按照你GPIO口的需求来,这里我使用的是PA0口,所以对应的就是第一个输出比较通道。所以我使用下面这个函数。

void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);

建立结构体,,可以看到结构体有很多变量,其中大部分都是高级定时器才用到的。比如带N的参数,OCIDlestate,所以一般我们只把我们需要的参数列出来即可

第一个需要的参数是OCMode,设置输出比较模式。

TIM_OCPolarity,设置输出比较的极性

OutputState,设置输出使能

Pulse,用来设置CCR的

void TIM_ICStructInit(TIM_OCInitTypeDef* TIM_ICInitStruct);

就是用来给结构体赋初始值的。

这个给结构体赋初始值的方法也非常简单,就是一个一个手动地给个值

这就是结构体赋值的注意事项,也是 StructInit的用途,就是如果你不想把所有成员都列一遍复制,就要StructInit赋一个初始值,再更改你想更改的值就OK了。

这些就是我们上一篇博客介绍的那几张输出比较模式。 

 Timing :  冻结模式

Active :   相等时置有效电平

Inactive :相等时置无效电平

Toggle :相等时电平翻转

PWM1:PWM1模式

PWM2 :PWM2模式

这是强制输出的两种模式。不过这两个参数不让我们初始化的时候使用。

我们主要用的是 PWM1:PWM1模式

关于Polarity的选择,这个可以根据你的电路需求来选择

后面依次找到每个变量的定义,找到我们要的参数 

 计算公式如下:比如产生频率1KHz,占空比为50%的PWM波形,分辨率为1%。代入公式,

ARR=100-1,PSC=720-1,CCR=50.

	//初始化输出比较单元
	TIM_OCInitTypeDef TIM_OCInitStructure;
	TIM_OCStructInit(&TIM_OCInitStructure);
	TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1 ;
	TIM_OCInitStructure.TIM_OCPolarity= TIM_OCPolarity_High;
	TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;
	TIM_OCInitStructure.TIM_Pulse=;//CCR   等下再算
	TIM_OC1Init(TIM2,&TIM_OCInitStructure);
	//通道已经初始化完成,在TIM2的OC1通道上就可以输出PWM波形了,这个波形借用GPIO口输出

通道已经初始化完成,在TIM2的OC1通道上就可以输出PWM波形了,这个波形借用GPIO口输出 

通道与GPIO口的对应关系:

 在这个TIM2的OC1通道是借了哪个GPIO口呢?

 这些其他外设也是同理,比如我们要是有SPI的MISO引脚,那就是PA6,如果要I2C2的SCL引脚,那就是PB10,这个关系是定死的,不能任意更改。不过STM32还是给了一次更改的机会的,这就是重定义,或者叫重映射,,比如你既要用USART2的TX引脚,又要用TIM2的CH3通道,它俩冲突了,没办法同时使用,那我们就可以在重映射的列表里找找,比如TIM2_CH3,那如TIM2_CH3就可以从原来的引脚,换到这里的引脚,这样就避免了两个外设引脚的冲突,如果这个重映射的列表里找不到,那外设复用的GPIO就不能挪位置。这就是重映射的功能,配置重映射是用AFIO来完成的。

这些就是外设引脚和GPIO引脚的复用关系和重映射的介绍。我们在使用外设的引脚时,需要多参考这个引脚定义表,找到默认复用功能和重定义功能,

要接GPIO口,我们把GPIO口配置一下,选择的是复用推挽输出,只有把GPIO设置为复用推挽输出,引脚的控制权才能交给片上外设,PWM波形才能通过引脚输出。可以参考一下8种输入模式。

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);

PWM.c

此时产生一个频率为1KHz的,占空比为50%的方波,

#include "stm32f10x.h"                  // Device header

void PWM_Init(void)
{
	//时基单元的代码写过,我们找之前定时中断的代码复制一下
	 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); //打开时钟、
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	
    TIM_InternalClockConfig(TIM2);//选择内部时钟
	
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;//初始化时基单元
	TIM_TimeBaseInitStructure.TIM_ClockDivision= TIM_CKD_DIV1 ;
	TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up ;
	TIM_TimeBaseInitStructure.TIM_Period=100-1;//ARR
	TIM_TimeBaseInitStructure.TIM_Prescaler=720-1;//PSC
    TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;
	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
	
	//初始化输出比较单元
	TIM_OCInitTypeDef TIM_OCInitStructure;
	TIM_OCStructInit(&TIM_OCInitStructure);
	TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1 ;
	TIM_OCInitStructure.TIM_OCPolarity= TIM_OCPolarity_High;
	TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;
	TIM_OCInitStructure.TIM_Pulse=50;//CCR   等下再算
	TIM_OC1Init(TIM2,&TIM_OCInitStructure);
	//通道已经初始化完成,在TIM2的OC1通道上就可以输出PWM波形了,这个波形借用GPIO口输出
	 
	 //初始化GPIO
	
	TIM_Cmd(TIM2,ENABLE);//启动定时器
}

为了实现呼吸灯,我们要一直改变占空比,也就是要一直改变CCR的值,

我们调用void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1);

这个函数是专门用来单独更改通道1的CCR值的。

注意,我们给了ARR=100,所以占空比直接由CRR决定,但是占空比是有两个共同决定的。

呼吸灯程序:

PWM.c

#include "stm32f10x.h"                  // Device header

void PWM_Init(void)
{
	//时基单元的代码写过,我们找之前定时中断的代码复制一下
	 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); //打开时钟、
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	
    TIM_InternalClockConfig(TIM2);//选择内部时钟
	
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;//初始化时基单元
	TIM_TimeBaseInitStructure.TIM_ClockDivision= TIM_CKD_DIV1 ;
	TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up ;
	TIM_TimeBaseInitStructure.TIM_Period=100-1;//ARR
	TIM_TimeBaseInitStructure.TIM_Prescaler=720-1;//PSC
    TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;
	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
	
	//初始化输出比较单元
	TIM_OCInitTypeDef TIM_OCInitStructure;
	TIM_OCStructInit(&TIM_OCInitStructure);
	TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1 ;
	TIM_OCInitStructure.TIM_OCPolarity= TIM_OCPolarity_High;
	TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;
	TIM_OCInitStructure.TIM_Pulse=0;//CCR   等下再算
	TIM_OC1Init(TIM2,&TIM_OCInitStructure);
	//通道已经初始化完成,在TIM2的OC1通道上就可以输出PWM波形了,这个波形借用GPIO口输出
	 
	 //初始化GPIO
	
	TIM_Cmd(TIM2,ENABLE);//启动定时器
}

void PWM_SetCompare1(uint16_t Compare)
{
	 TIM_SetCompare1(TIM2,Compare);
}

PWM.h

#ifndef __OLED_H
#define __OLED_H

void PWM_Init(void);
void PWM_SetCompare1(uint16_t Compare);
#endif

main.c

#include "stm32f10x.h"                  // Device header              
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"

uint8_t i;

int main()
{
	
	OLED_Init();
	PWM_Init();
	while(1)
	{
       for(i=0;i<=100;i++)//LED逐渐变亮
		{
			PWM_SetCompare1(i);
			Delay_ms(10);
		}
		 for(i=0;i<=100;i++)//LED逐渐变暗
		{
			PWM_SetCompare1(100-i);
			Delay_ms(10);
		}
	}
}

 最后,我们看看引脚重映射是怎么玩的,在PWM.c里面,加入下列代码

要使用AFIO,就要开启AFIO时钟。

RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);

 调用上图的引脚重映射的函数,可以看到这个函数的方式非常多。

每种对应的重映射关系是啥呢?查看一下手册,里面有四种重映射的方式,

如果要把PA0改到PA15,就可以选择部分重映射1,或者完全重映射,用下面语句就可以把PA0换到PA15了,不过还是有一个问题。

GPIO_PinRemapConfig(GPIO_PartialRemap2_TIM2,ENABLE);

 看到引脚定义表,因为引脚定义表会默认PA15上电后复用为调试端口JTDI,所以想让他作为普通GPIO或者复用定时器的通道,那先要调试端口的复用,

 同样也是GPIO_PinRemapConfig这个函数,找到下面三个定义的参数,就是解除调试端口复用的

 SWJ就是SWD和JTAG这两种调试方式。NOJTRST,就是解除JTRST的复用,

在引脚定义里看一下,NOJTRST就是PB4,如果使用这个参数,那么这个PB4就变为正常的GPIO口了,

SWJ_JTAGDisable  就是解除JTAG调试端口的复用,在引脚定义表里,就是PA15,PB3,PB4这三个端口变回GPIO,

SWJ_Disable这个就是把SWD和JTAG的调试端口全部解除,在引脚定义里。就是PA15,PA14,PA13,PB3,PB4这5个引脚全部变为普通的GPIO,没有调试功能了。但是这个参数千万不要随便调用,一旦你调用这个参数并且下载程序之后,那么你的调试端口就没有了,之后使用STLINK就下载不进去程序了。这时就只能使用串口下载,下载一个新的,没有解除调试端口的程序,这样才能把调试端口弄回来,所以使用这个参数要小心。

这些参数和GPIO能不能使用的情况,手册也写了,对应下图:

好,如果我们想使用PA15,PB3,PB4就是解除JTAG的复用,保留SWD的复用。

最后代码为

放入PWM.初始化里面

RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2,ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable ,ENABLE);

如下图所示。 也要记得把引脚改为15号引脚

 

 

  • 52
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值