STM32 嵌入式学习入门(5)——PWM的实现

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)循环,否则,返回主调函数当前小车的状态。这段代码现在看写得不是特别好了,但是说明这个函数的用法还是没问题的。







你可能感兴趣的:(嵌入式STM32)