单片机学习!
目录
[2.1 RCC开启时钟](#2.1 RCC开启时钟)
[2.2 配置时基单元](#2.2 配置时基单元)
[2.3 配置输出比较单元](#2.3 配置输出比较单元)
[2.4 配置GPIO](#2.4 配置GPIO)
[2.5 运行控制](#2.5 运行控制)
前言
PWM本质是利用面积等效原理来改变波形的有效值。
一、PWM驱动配置步骤
第一步、RCC开启时钟,将需要的TIM外设和GPIO外设的时钟打开。
第二步、配置时基单元,包括时钟源选择的配置。
第三步、配置输出比较单元,包括CCR的值、输出比较模式、极性选择、输出使能这些参数。(在库函数中也是用结构体统一来配置)。
第四步、配置GPIO,把PWM对应的GPIO口初始化为复用推挽输出的配置。
第五步、运行控制,启动计数器就可以输出PWM了。
二、代码示例及注意事项
2.1 RCC开启时钟
将需要的TIM外设 和GPIO外设的时钟打开。打开时钟后定时器的基准时钟和整个外设的工作时钟就会同时打开。
代码示例:
cpp
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);//使用APB1的开启时钟函数,因为TIM2是APB1总线的外设。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
2.2 配置时基单元
配置时基单元,包括时钟源选择的配置。
- 时基单元的选择时钟源。对于定时中断可选择内部时钟源。
- 配置时基单元,包括预分频器、自动重装器、计数模式等,这些参数可用结构体配置。
代码示例:
cpp
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_ClockDivision:指定时钟分频,用于信号经过滤波器时的滤波采样频率。
- TIM_CounterMode:计数器模式,这里选择向上计数模式TIM_CounterMode_Up。
- TIM_Period :ARR自动重装器的值。
- TIM_Prescaler:PSC预分频器的值。
- TIM_RepetitionCounter:重复计数器的值,只有高级定时器才有,本文初始化通用寄存器,所以值给0。
时基单元中关键寄存器参数ARR、PSC都有设置,但是这里没有CNT计数器的参数,CNT参数的配置可根据需要在函数 TIM_SetCounter 和函数 TIM_GetCounter 中操作。
决定定时时间的参数是 TIM_Period 和 TIM_Prescaler 。定时时间可用计数器溢出频率公式计算,定时频率=72M/(PSC+1)/(ARR+1)。
2.3 配置输出比较单元
配置输出比较单元,包括CCR的值、输出比较模式、极性选择、输出使能这些参数。(在库函数中也是用结构体统一来配置)。
cpp
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);
这里4个初始化函数对应4个输出比较通道(单元),不同的通道对应的GPIO口也不一样,函数的选择需要根据GPIO口的配置选择。代码示例中使用PA0口,对应第一个输出比较通道,选择TIM_OC1Init函数。
代码示例:
cpp
TIM_OCInitTypeDef TIM_OCInitStructture;
TIM_OCStructInit(&TIM_OCInitStructture);
TIM_OCInitStructture.TIM_OCMode=TIM_OCMode_PWM1; //设置输出比较的模式。
TIM_OCInitStructture.TIM_OCPolarity=TIM_OCPolarity_High; //设置输出比较的极性
TIM_OCInitStructture.TIM_OutputState=TIM_OutputState_Enable; //设置输出使能
TIM_OCInitStructture.TIM_Pulse=50; //设置CCR
TIM_OC1Init(TIM2,&TIM_OCInitStructture);
代码示例中的程序并没有给结构体的所有成员赋值,而是用TIM_OCStructInit函数先给结构体成员赋一个初始值,再修改部分的结构体成员。这个结构体变量是局部变量,若不给成员赋初始值,它成员的值就是不确定的,这会导致一些问题。比如当你想把高级定时器当作通用定时器输出PWM时,只配置了通用定时器需要的结构体成员,剩下这些没赋值成员就会导致高级定时器输出PWM出现一些奇怪的问题。
TIM_OCStructInit函数需要把结构体变量的地址传进去,才能给结构体赋初始值。
- TIM_OCMode设置输出比较的模式。
TIM_OCMode输出比较模式参数对应含义:
- TIM_OCMode_Timing 冻结模式
- TIM_OCMode_Active 相等时置有效电平
- TIM_OCMode_Inactive 相等时置无效电平
- TIM_OCMode_Toggle 相等时电平翻转
- TIM_OCMode_PWM1 PWM模式1
- TIM_OCMode_PWM2 PWM模式2
- TIM_ForcedAction_Active 强制输出有效电平
- TIM_ForcedAction_InActive 强制输出无效电平
注:强制输出两种模式的参数不可以在初始化时使用
- TIM_OCPolarity设置输出比较的极性。
- TIM_OCPolarity_High 高级性,是极性不翻转,REF波形直接输出。意思是有效电平是高电平,REF有效时,输出高电平。
- TIM_OCPolarity_Low 低级性,REF电平取反,意思是有效电平为低电平。
- TIM_OutputState设置输出使能。
4.TIM_Pulse置CCR。 ARR,PSC,CCR共同决定PWM的周期和占空比
以上配置已经把输出比较通道初始化好了,在TIM2的OC1通道上就可以输出PWM波形了,最终这个波形需要通过GPIO口才可以输出。那TIM2的OC1通道是借用哪个GPIO口呢?下文来选择并配置GPIO。
注:TIM_OC1Init是通道1的初始化函数,若通道1、2、3、4都需要的话可以直接在后面加TIM_OC2Init、TIM_OC3Init、TIM_OC4Init,这样就可以同时使用4个通道来输出4个PWM了。
代码示例:
cpp
TIM_OCInitTypeDef TIM_OCInitStructture;
TIM_OCStructInit(&TIM_OCInitStructture);
TIM_OCInitStructture.TIM_OCMode=TIM_OCMode_PWM1; //设置输出比较的模式。
TIM_OCInitStructture.TIM_OCPolarity=TIM_OCPolarity_High; //设置输出比较的极性
TIM_OCInitStructture.TIM_OutputState=TIM_OutputState_Enable; //设置输出使能
TIM_OCInitStructture.TIM_Pulse=50; //设置CCR
TIM_OC1Init(TIM2,&TIM_OCInitStructture);
TIM_OC2Init(TIM2,&TIM_OCInitStructture);
TIM_OC3Init(TIM2,&TIM_OCInitStructture);
TIM_OC4Init(TIM2,&TIM_OCInitStructture);
同一个定时器的不同通道输出的PWM,因为不同通道是共用一个计数器的,所以它们的频率必须是一样的;它们的占空比由各自的CCR决定,所以占空比可以各自设定;他们的相位由于计数器更新,所有PWM同时跳变,所以它们的相位是同步的。
这就是同一个定时器不同通道输出PWM的特点。如果使用多个设备如电机或者舵机,那使用同一个定时器不同通道的PWM就完全可以。
2.4 配置GPIO
输出比较通道借用GPIO口可以在引脚定义表中查看:
默认复用功能这一列就是片上外设的端口和GPIO的连接关系。可以找到 TIM2_CH1_ETR 在 PA0 这一行,这说明 TIM2 的 ETR 引脚和通道1的引脚都是借用了 PA0 这个引脚位置,就是TIM2的引脚复用在了PA0引脚上。所以使用TIM2的OC1通道也就是CH1通道输出PWM,就只能在PA0引脚上输出,而不能任意选择引脚输出。
同理:
- 使用TIM2_CH2只能在PA1端口输出
- 使用TIM2_CH3只能在PA2端口输出
- 使用TIM2_CH4只能在PA3端口输出
表中其他外设也是同理:如使用SPI1_MISO只能在PA6端口输出。虽然引脚与外设都是规定好的,但是还可以根据情况做一次改动。在引脚定义表重定义(重映射)这一列,还可以对应更改。如既要使用 USART2_TX 引脚又要使用 TIM2_CH3 通道,但是它俩都在PA2端口输出,这就冲突了没法同时使用。这时可以在重定义列表里找一下,有 TIM2_CH3 ,那么 TIM2_CH3 就可以从PA2端口输出换为从PB10端口输出。这样就避免了两个外设引脚的冲突。但是如果重映射的列表里找不到,那外设复用的GPIO就不能挪位置。配置重映射需要用AFIO来完成。
配置GPIO,把PWM对应的GPIO口初始化为复用推挽输出的配置。
代码示例:
cpp
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode= GPIO_Mode_AF_PP;//这里选择复用推挽输出。
GPIO_InitStruct.GPIO_Pin= GPIO_Pin_0;
GPIO_InitStruct.GPIO_Speed= GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
这里选择复用推挽输出。
原因:对于普通的开漏/推挽输出,引脚的控制权是来自于输出数据寄存器的,定时器控制引脚需要使用复用开漏/推挽输出的模式,复用开漏/推挽输出模式中输出数据寄存器将被断开,输出控制权将转移给片上外设,通过引脚定义表可知,这里片上外设引脚连接的就是TIM2的CH1通道。所以只有把GPIO设置为复用推挽输出,引脚的控制权才能交给片上外设,PWM波形才能通过引脚输出。
2.5 运行控制
运行控制:整个模块配置完成后,还需要使能一下计数器,PWM波形就能通过PA0输出了。
代码示例:
cpp
TIM_Cmd(TIM2,ENABLE);
三、PWM周期和占空比计算
计算公式:
- PWM频率: Freq = CK_PSC / (PSC + 1) / (ARR + 1) PWM频率等于计数器更新频率。
- PWM占空比: Duty = CCR / (ARR + 1)
- PWM分辨率: Reso = 1 / (ARR + 1)
ARR、PSC、CCR共同决定PWM的周期和占空比
代码示例中产生的是一个频率为1KHHz,占空比为50%,分辨率为1%的PWM波形。
代入公式:
- 72M/(PSC+1)/(ARR+1)=1000
- CCR/(ARR+1)=50%
- 1/(ARR+1)=1%
得出:
- (ARR+1)=100
- CCR=50
- (PSC+1)=720
对应代码中:
- ARR 给 100-1;
- PSC 给 720-1;
- CCR 给 50。
代码运行时,PWM占空比也可更改,可选择使用TIM_SetCompare1、TIM_SetCompare2、TIM_SetCompare3、TIM_SetCompare4 函数改变CCR寄存器的值来调节PWM占空比。但是PWM的占空比是由ARR、PSC、CCR共同决定。
总结
以上就是今天要讲的内容,本文仅仅简单介绍了PWM驱动配置。