[TOC] # PWM脈衝寬調點燈 ## 前言 對於燈等來說有很多種方法,前面介紹了一些基礎的點燈方法,比如直接點燈,按鍵控制點燈,按鍵中斷點燈,但都是比較簡單的一些方法也很基礎,要問我有沒有什麼高級點的點燈方法,答案是有的,在這我要介紹一種高級點燈的方法就是使用PWM進行點燈。 ## 1.什麼是P ...
目錄
PWM脈衝寬調點燈
前言
對於燈等來說有很多種方法,前面介紹了一些基礎的點燈方法,比如直接點燈,按鍵控制點燈,按鍵中斷點燈,但都是比較簡單的一些方法也很基礎,要問我有沒有什麼高級點的點燈方法,答案是有的,在這我要介紹一種高級點燈的方法就是使用PWM進行點燈。
1.什麼是PWM
PWM是脈衝寬度調製,簡稱脈衝寬調。它利用微處理器的數字輸出來對模擬電路進行控制的一種非常有效的技術。
PWM一般用在測量、通訊、功率控制雨轉換,電動機控制、調光、開關電源,但是在這我們只研究點燈,點燈才是重中之重。
2.PWM的實現
PWM是基於定時器來進行實現的,但並不是所有的定時器都能實現PWM的,比如說基本定時器就沒有辦法實現PWM,所以下麵的PWM是用通用定時器和高級定時器來進行實現的。
其實PWM的實現是非常的簡單,其實和定時器一樣,定時器到0或者是到設置的最大值就會觸發中斷,PWM也是,只不過它並不是到達最大值,也不觸發中斷,它是到達你設置的那個比較值後就進行翻轉電平,例如我的PWM的自動重載值為2000,比較值為1000
當計數到1000的時候PWM就會進行一次電平的翻轉,比如你一開始設置的電平是高電平,那等到到比較值的時候就會變成低電平。
3.PWM實現步驟(通用定時器)
前面說了,PWM和定時器一樣,所以開啟PWM的方式前面和打開定時器的方式一樣,但是有一點不同的是PWM要使用到所對應的埠上,所以需要加上一般就是設置埠:
-
打開定時器時鐘
-
配置埠
-
配置TIM定時器
-
使能TIM外設
按照上面的步驟完成後就得開始配置PWM了,配置的方法如下:
- 配置PWM
- 使能預裝寄存器
這樣就可以讓PWM進行使用了,現在來一個一個的介紹打開的步驟。
3.1 打開定時器的時鐘
打開時鐘這個是必須要的內容,而在打開時鐘的時候需要註意你使用的定時器,如果你使用的是通用定時器,一般都是打開APB1的時鐘,而高級定時器一般是APB2,這個需要考慮一下。
如果我這是使用的TIM3的定時器,那麼打開的時鐘代碼如下:
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
如果這裡使用TIM1,代碼如下:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
3.2 配置埠
配置埠的步驟和之前配置埠的步驟一致,只不過需要註意一下這個埠的模式需要使用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);
3.3 設置定時器
設置完埠後就可以開始設置定時器了,設置定時器的方法和之前打開基本定時器的方法一樣。
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);
3.4 設置PWM
前面設置完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設置通道二。
3.5 完整代碼
下麵展示一下完整的代碼,大家可以拿下來直接使用,但是要註意這個是用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);
}
4.PWM實現步驟(高級定時器)
其實高級定時器的實現步驟和上面一樣,只不過就是想要添加一條語句,為什麼不直接在通用定時器上寫呢?我也不知道,寫這個我都是隨性的。
當你設置完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);
}
5.開始點燈
如果你上面的步驟都跟上了,那麼現在恭喜你你可以點燈了,直接調用上面的初始化函數就可以直接開啟點燈了,效果就是亮滅亮滅的。
主函數:
#include "stm32f10x.h"
#include "pwm.h"
int main(void){
MX_PWMInit();
while(1)
{
}
}
這樣就可以了,你可以用TIM_SetCompare1(TIMx, value;
函數去修改一下每次的比較值,可以讓它亮得久或者亮得短。
你也可以通過修改TIM_OCInitStruct.TIM_OCPolarity
的值來改變一開始的電平。
6.PWM呼吸燈
點燈很容易,但是還是感覺比較低級,那麼在這我給大家介紹一下用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);
}
}