STM32嵌入式学习(5)——PWM的实现
上一篇博文介绍了定时器和PWM的基本的原理,本篇博文从代码层面来介绍PWM的具体实现。同样,还是以博主所用的开发板——正点原子开发板STM32F103ZET6为例。
一、基于STM32的PWM输出配置步骤(初始化操作):
1. 操作步骤(基于STM32固件库、使用定时器3的PWM功能):
(1)使能相关时钟(
定时器3和相关IO口时钟。
):
//要使用什么外设就要先使能相关外设所挂载的时钟,这些内容在最开始GPIO那块就有提到 STM32的GPIO介绍
①使能定时器3时钟:RCC_APB1PeriphClockCmd();
②使能GPIOB时钟:RCC_APB2PeriphClockCmd();
(2)初始化IO口为复用功能输出。函数:GPIO_Init();
//同样,IO口初始化的操作在STM32的GPIO介绍那篇文章里也说到了。
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
//这里我们是要把引脚用作定时器的PWM输出引脚,因此要重映射配置。所以需要开启AFIO时钟。同时设置重映射。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE);
(3)初始化定时器:ARR,PSC等:TIM_TimeBaseInit();
ARR寄存器在上一篇文章讲原理时候已经说到了。PWM原理
(4)初始化输出比较参数:TIM_OC2Init();
(5)使能预装载寄存器: TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);
(6)使能定时器。TIM_Cmd();
(7)不断改变比较值CCRx,达到不同的占空比效果:TIM_SetCompare2();
2.初始化源代码:
(1)以STM32F103ZET6为芯片的开发板的PWM初始化,这里只是初始化一个通道用作PWM输出
//TIM3 PWM部分初始化
//PWM输出初始化
//arr:自动重装值
//psc:时钟预分频数
void TIM3_PWM_Init(u16 arr,u16 psc)//STM32F103ZET6
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //使能定时器3时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO, ENABLE); //使能GPIO外设和AFIO复用功能模块时钟
GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); //Timer3部分重映射 TIM3_CH2->PB5
//设置该引脚为复用输出功能,输出TIM3 CH2的PWM脉冲波形 GPIOB.5
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //TIM_CH2
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIO
//初始化TIM3
TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
//初始化TIM3 CH2 PWM模式
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //选择定时器模式:TIM脉冲宽度调制模式2
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
TIM_OC2Init(TIM3, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM3 OC2
TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable); //使能TIM3在CCR2上的预装载寄存器
TIM_Cmd(TIM3, ENABLE); //使能TIM3
}
(2)下面的代码是博主做嵌入式循迹小车的项目中的PWM初始化,用了TIM1的两个通道(通道1和通道4)去分别控制两个驱动轮的转速,从而实现让小车转向的功能。和上面的初始化代码的不同主要在于小车项目中用了两个通道,同时项目使用的开发板的芯片是STM32F103RCT6。在这里贴出源代码,做一比较。
//PWM输出初始化
//arr:自动重装值
//psc:时钟预分频数
void TIM1_PWM_Init(u16 arr,u16 psc)//STM32F103RCT6
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //使能GPIOA外设时钟使能
//设置该引脚为复用输出功能,输出TIM1 CH1和CH4的PWM脉冲波形
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_11;//TIM_CH1
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值 80K
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值 不分频
TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM1,&TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //选择定时器模式:TIM脉冲宽度调制模式2
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
TIM_OCInitStructure.TIM_Pulse = 0; //设置待装入捕获比较寄存器的脉冲值
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
TIM_OC1Init(TIM1, &TIM_OCInitStructure); //根据TIM_OC1InitStruct中指定的参数初始化外设TIM1的通道1
TIM_OC4Init(TIM1, &TIM_OCInitStructure); //根据TIM_OC2InitStruct中指定的参数初始化外设TIM1的通道4
TIM_CtrlPWMOutputs(TIM1,ENABLE); //MOE 主输出使能
TIM_OC1PreloadConfig(TIM1,TIM_OCPreload_Enable); //CH1预装载使能 通道1
TIM_OC4PreloadConfig(TIM1,TIM_OCPreload_Enable); //CH4预装载使能 通道4
TIM_ARRPreloadConfig(TIM1,ENABLE); //使能TIMx在ARR上的预装载寄存器(x==1)
TIM_Cmd(TIM1,ENABLE); //使能TIM1
}
初始化函数在主函数的开头处调用,调用函数的两个参数根据自己需要去设定。
二、PWM的操作:
上面的初始化结束后就可以利用相关引脚输出PWM波了。这个输出过程简单说就是改变比较值CCRx的操作,就是往CCR寄存器里写值。这一点上一篇文章里有分析到。下面先介绍一下往CCRx寄存器里写值操作的函数(来自官方库函数):TIM_SetCompare1();
/**
* @brief Sets the TIMx Capture Compare1 Register value
* @param TIMx: where x can be 1 to 17 except 6 and 7 to select the TIM peripheral.
* @param Compare1: specifies the Capture Compare1 register new value.
* @retval None
*/
void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1)
{
/* Check the parameters */
assert_param(IS_TIM_LIST8_PERIPH(TIMx));//有效性判断
/* Set the Capture Compare1 Register value */
TIMx->CCR1 = Compare1;
}
说明:和这个函数类似的一共有四个:
TIM_SetCompare1(); TIM_SetCompare2(); TIM_SetCompare3(); TIM_SetCompare4();其实现框架都是一样的,四个函数分别对应四个通道,使用哪个通道调用哪个函数,要对应起来。
下面先介绍
还是以自己嵌入式循迹小车的项目为例,比如小车直行时就是在执行下面这个函数。
u8 Case_F_SIGN(void)//直行
{
while(1){
delay_ms(10);
pwmvalR=413;
pwmvalL=413;
TIM_SetCompare1(TIM1,pwmvalR);
TIM_SetCompare4(TIM1,pwmvalL);
t=SensorScan();
if(t!=F_SIGN)
return t;
}
}
pwmvalR和pwmvalL是两个全局变量,它们的值是根据项目需要和小车硬件情况实际测出来的,然后调用两个函数,写入相关寄存器,,下面是调用SensorScan();看小车是不是还沿着轨迹在行驶,如果不是,那么跳出while(1)循环,否则,返回主调函数当前小车的状态。这段代码现在看写得不是特别好了,但是说明这个函数的用法还是没问题的。