對於燈等來說有很多種方法,前面介紹了一些基礎的點燈方法,比如直接點燈,按鍵控制點燈,按鍵中斷點燈,但都是比較簡單的一些方法也很基礎,要問我有沒有什麼高階點的點燈方法,答案是有的,在這我要介紹一種高階點燈的方法就是使用PWM進行點燈。
PWM是脈衝寬度調變,簡稱脈衝寬調。它利用微處理器的數位輸出來對類比電路進行控制的一種非常有效的技術。
PWM一般用在測量、通訊、功率控制雨轉換,電動機控制、調光、開關電源,但是在這我們只研究點燈,點燈才是重中之重。
PWM是基於定時器來進行實現的,但並不是所有的定時器都能實現PWM的,比如說基本定時器就沒有辦法實現PWM,所以下面的PWM是用通用定時器和高階定時器來進行實現的。
其實PWM的實現是非常的簡單,其實和定時器一樣,定時器到0或者是到設定的最大值就會觸發中斷,PWM也是,只不過它並不是到達最大值,也不觸發中斷,它是到達你設定的那個比較值後就進行翻轉電平,例如我的PWM的自動過載值為2000,比較值為1000
當計數到1000的時候PWM就會進行一次電平的翻轉,比如你一開始設定的電平是高電平,那等到到比較值的時候就會變成低電平。
前面說了,PWM和定時器一樣,所以開啟PWM的方式前面和開啟定時器的方式一樣,但是有一點不同的是PWM要使用到所對應的埠上,所以需要加上一般就是設定埠:
開啟定時器時鐘
設定埠
設定TIM定時器
使能TIM外設
按照上面的步驟完成後就得開始設定PWM了,設定的方法如下:
這樣就可以讓PWM進行使用了,現在來一個一個的介紹開啟的步驟。
開啟時鐘這個是必須要的內容,而在開啟時鐘的時候需要注意你使用的定時器,如果你使用的是通用定時器,一般都是開啟APB1的時鐘,而高階定時器一般是APB2,這個需要考慮一下。
如果我這是使用的TIM3的定時器,那麼開啟的時鐘程式碼如下:
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
如果這裡使用TIM1,程式碼如下:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
設定埠的步驟和之前設定埠的步驟一致,只不過需要注意一下這個埠的模式需要使用GPIO_Mode_AF_PP
複用推輓輸出的方式,因為有些埠是使用重對映到這個埠的,此時這個埠是屬於複用功能輸出,所以這裡需要使用到複用的方式。
那什麼時候需要使用到重對映呢?這裡需要知道一下,我們現在使用的PWM是PWM的輸出模式,所以需要知道開啟這個PWM定時器的輸出埠。
下面的表說明了通用定時器和高階定時器通道輸出所對應的埠:
TIM2定時器 | 未重對映 | 部分重對映 |
---|---|---|
TIM2_CH1 | PA0 | PA15 |
TIM2_CH2 | PA1 | PB3 |
TIM2_CH3 | PA2 | PB10 |
TIM2_CH4 | PA3 | PB11 |
TIM3定時器 | 未重對映 | 部分重對映 | 重對映 |
---|---|---|---|
TIM3_CH1 | PA6 | PB4 | PC6 |
TIM3_CH2 | PA7 | PB5 | PC7 |
TIM3_CH3 | PB0 | PB0 | PC8 |
TIM3_CH4 | PB1 | PB1 | PC9 |
TIM4定時器 | 未重對映 | 重對映 |
---|---|---|
TIM4_CH1 | PB6 | PD12 |
TIM4_CH2 | PB7 | PD13 |
TIM4_CH3 | PB8 | PD14 |
TIM4_CH4 | PB9 | PD15 |
TIM5定時器 | 未重對映 | 重對映 |
---|---|---|
TIM5_CH4 | PA3 | LSI內部時鐘連至TIM5_CH4的輸入作為校準使用 |
TIM1定時器 | 未重對映 | 部分重對映 | 重對映 |
---|---|---|---|
TIM1_ETR | PA12 | PA12 | PE7 |
TIM1_CH1 | PA8 | PA8 | PE9 |
TIM1_CH2 | PA9 | PA9 | PE11 |
TIM1_CH3 | PA10 | PA10 | PE13 |
TIM1_CH4 | PA11 | PA11 | PE14 |
上面只介紹了部分定時器輸出的重對映,更多的大家可以翻閱一下手冊,上面的只是一個簡單的參考。
知道了定時器輸出引腳後我們需要選擇定時器然後設定定時器所對應的輸出引腳。
這裡我使用的是PA6這個引腳,所以使用的是TIM3
這個定時器,設定的程式碼如下:
GPIO_InitTypeDef GPIO_InitStruct = {0};
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 開啟GPIOA的時鐘
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; // 設定複用推輓輸出
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
設定完埠後就可以開始設定定時器了,設定定時器的方法和之前開啟基本定時器的方法一樣。
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct = {0};
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CountterMode_Up; // 設定定時器計數模式為上拉
TIM_TimeBaseInitStruct.TIM_Period = 20000 - 1; // 自動重灌值為20000
TIM_TimeBaseInitStruct.TIM_Prescaler = 7200 - 1; // 預分系數,72MHz/7200
TIM_TimeBaseInitStruct.TIM_ClockDivision = 0; // 時鐘分割
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct);
前面設定完TIM3定時器了,現在就是想要設定一下PWM了。
TIM_OCInitTypeDef TIM_OCInitStruct = {0};
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1; // 開啟PWM通道1 因為這裡使用的是TIM3的通道一作為PWM
TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_ENABLE; // PWM輸出使能
TIM_OCInitStruct.TIM_OCPolarity = TIM_OCNPolarity_Low; // 一開始輸出的電平,如果為低,那到達比較值後就為高。
TIM_OC1Init(TIM3, &TIM_OCInitStruct); // 讓TIM3的通道1和設定的PWM進行繫結
TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_ENABLE); //使能TIM3在CCR1上的預重灌暫存器
TIM_SetCompare1(TIM3, 10000 - 1); // 設定比較值
TIM_Cmd(TIM3, ENABLE); // 使能TIM3外設
以上就是設定好PWM了,有幾個注意點,就是選擇的PWM的通道數,比如說我這要讓它開啟通道二,那在設定的時候需要加上:
TIM_OC2Init(TIM3, &TIM_OCInitStruct); // 讓TIM3的通道1和設定的PWM進行繫結
TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_ENABLE); //使能TIM3在CCR1上的預重灌暫存器
TIM_SetCompare2(TIM3, 10000 - 1); // 設定比較值
這樣也設定了通道2的,當然也可以讓PWM通道2的重灌值為其它的值,那就當設定完通道一後再設定一下PWM,再把設定好的PWM設定通道二。
下面展示一下完整的程式碼,大家可以拿下來直接使用,但是要注意這個是用TIM3,並且沒有重定向的。
void MX_PWMInit(void){
GPIO_InitTypeDef GPIO_InitStruct = {0};
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct = {0};
TIM_OCInitTypeDef TIM_OCInitStruct = {0};
// 開啟時鐘
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 設定埠
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// 設定通用定時器
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period = 20000 - 1;
TIM_TimeBaseInitStruct.TIM_Prescaler = 7200 - 1;
TIM_TimeBaseInitStruct.TIM_ClockDivision = 0;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct);
// 設定PWM
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1; // 開啟通道1
TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStruct.TIM_OCNPolarity = TIM_OCNPolarity_Low;
TIM_OC1Init(TIM3, &TIM_OCInitStruct);
TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);
TIM_SetCompare1(TIM3, 10000 - 1);
TIM_Cmd(TIM3, ENABLE);
}
其實高階定時器的實現步驟和上面一樣,只不過就是想要新增一條語句,為什麼不直接在通用定時器上寫呢?我也不知道,寫這個我都是隨性的。
當你設定完PWM後,想要新增下面的語句:
TIM_CtrlPWMOutputs(TIM1,ENABLE); //MOE 主輸出使能
因為這裡使用的是高階定時器TIM1,包括TIM8都需要新增這一句話,因為這一句話是讓MOE主輸出使能的,如果沒有這一句話,就算你設定好PWM,這個定時器也沒有辦法進行輸出,也就是讓TIM1或者TIM8為關閉狀態。
完整程式碼如下,你可以直接拿去使用,但是要注意我這個開啟的是TIM1定時器的PWM,而且是通道一,輸出的埠是PA8:
void MX_PWMInit(void){
GPIO_InitTypeDef GPIO_InitStruct = {0};
TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct = {0};
TIM_OCInitTypeDef TIM_OCInitStruct = {0};
// 開啟時鐘
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 設定GPIO模式和IO口
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// TIM1通用定時器初始化
// 因為要操作的PA8這個引腳的LED燈,但是隻有高階定時器TIM1才可以使用PWM進行控制這個埠
// 所以需要使用開啟高階定時器的方法來進行開啟
TIM_TimeBaseStruct.TIM_ClockDivision = 0;
TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up; // 向上計數
TIM_TimeBaseStruct.TIM_Period = 20000 - 1;
TIM_TimeBaseStruct.TIM_Prescaler = 7200 - 1;
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStruct);
// PWM初始化
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1; // 設定模式為PWM模式
TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable; // 設定比較輸出使能
TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High; // 設定極性為高
TIM_OC1Init(TIM1, &TIM_OCInitStruct);
TIM_CtrlPWMOutputs(TIM1,ENABLE); //MOE 主輸出使能
TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); // 使能TIM1在CCR1上的預裝載暫存器
TIM_ARRPreloadConfig(TIM1, ENABLE); //使能TIMx在ARR上的預裝載暫存器
TIM_SetCompare1(TIM1, 10000 - 1);
TIM_Cmd(TIM1, ENABLE);
}
如果你上面的步驟都跟上了,那麼現在恭喜你你可以點燈了,直接呼叫上面的初始化函數就可以直接開啟點燈了,效果就是亮滅亮滅的。
主函數:
#include "stm32f10x.h"
#include "pwm.h"
int main(void){
MX_PWMInit();
while(1)
{
}
}
這樣就可以了,你可以用TIM_SetCompare1(TIMx, value;
函數去修改一下每次的比較值,可以讓它亮得久或者亮得短。
你也可以通過修改TIM_OCInitStruct.TIM_OCPolarity
的值來改變一開始的電平。
點燈很容易,但是還是感覺比較低階,那麼在這我給大家介紹一下用PWM來點燈的高階點的內容----「PWM呼吸燈」,不知道的可以看完這個教學拿去跑一下就知道了。
呼吸燈其實就是改變每一次PWM的比較值,在感官上感覺就是LED像呼吸一樣。
這裡需要新增一下滴答定時器來延遲,所以需要新增一下下面的程式碼:
void delay_us(unsigned int time){
unsigned int temp;
SysTick->LOAD = 9 * time;
SysTick->CTRL = 0x01;
SysTick->VAL = 0;
do{
temp = SysTick->CTRL;
}while((temp&0x01)&&(!(temp&(1<<16))));
SysTick->VAL = 0;
SysTick->CTRL = 0x00;
}
void delay_ms(unsigned int time){
unsigned int temp;
SysTick->LOAD = 9000 * time;
SysTick->CTRL = 0x01;
SysTick->VAL = 0;
do{
temp = SysTick->CTRL;
}while((temp&0x01)&&(!(temp&(1<<16))));
SysTick->VAL = 0;
SysTick->CTRL = 0x00;
}
這個是之前說過的滴答定時器設定,我就懶得寫註釋了。
然後PWM需要將分頻變成0,因為使用滴答定時器來延遲,所以不需要分頻了(你分頻會出問題,不要分),然後預裝值改為900,其它都不變。
在主函數中的程式碼就如下:
int main(void){
unsigned char dir = 1; // 方向
unsigned int Duty = 0; // 佔空比
MX_PWMInit();
while(1)
{
delay_ms(10);
if (dir == 1){
Duty++;
if (Duty > 300){
dir = 0;
}
}
else{
Duty--;
if (Duty == 0){
dir = 1;
}
}
TIM_SetCompare1(TIM1, Duty);
}
}
上面的是你一開始的電平為:TIM_OCPolarity_Low
。
如果你一開始的電平為:TIM_OCPolarity_Hight
,那麼需要使用下面的程式碼:
int main(void){
unsigned char dir = 0; // 方向
unsigned int Duty = 0; // 佔空比
MX_PWMInit();
while(1)
{
delay_ms(10);
if (dir == 0){
Duty++;
if (Duty > 300){
dir = 1;
}
}
else{
Duty--;
if (Duty == 0){
dir = 0;
}
}
TIM_SetCompare1(TIM1, Duty);
}
}