1. 程式人生 > >STM32 嵌入式學習入門(5)——PWM的實現

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)迴圈,否則,返回主調函式當前小車的狀態。這段程式碼現在看寫得不是特別好了,但是說明這個函式的用法還是沒問題的。