一、PWM簡介
脈沖寬度調制(PWM)蹬昌,是英文“Pulse Width Modulation”的縮寫混驰,簡稱脈寬調試。是利用微處理器的數(shù)字輸出來對模擬電路進行控制的一種非常有效的技術皂贩。廣泛應用在從測量栖榨、通信到功率控制與變換的許多領域中。
例如上圖中明刷,圖b)是微處理輸出的數(shù)字信號婴栽,實際上他接到電機等功率設備上時,效果相當于圖a)辈末。這就是PWM調制愚争。例如輸出占空比為50%,頻率為10Hz的脈沖挤聘,高電平為3.3V.則其輸出的模擬效果相當于輸出一個1.65V的高電平轰枝。脈沖調制有兩個重要的參數(shù),第一個就是輸出頻率组去,頻率越高鞍陨,則模擬的效果越好。第二個就是占空比从隆。占空比就是改變輸出模擬效果的電壓大小诚撵。占空比越大則模擬出的電壓越大。
二广料、定時器簡介
STM32F1 系列中砾脑,除了互聯(lián)型的產品,共有 8
個定時器艾杏,分為基本定時器韧衣,通用定時器和高級定時器。
基本定時器 TIM6
和 TIM7
是一個 16 位的只能向上計數(shù)的定時器,只能定時畅铭,沒有外部 IO氏淑。
通用定時器 TIM2/3/4/5
是一個 16 位的可以向上/下計數(shù)的定時器,可以定時硕噩,可以輸出比較假残,可以輸入捕捉,每個定時器有四個外部 IO炉擅。
高級定時器 TIM1/8
是一個 16 位的可以向上/下計數(shù)的定時器辉懒,可以定時,可以輸出比較谍失,可以輸入捕捉眶俩,還可以有三相電機互補輸出信號,每個定時器有 8 個外部 IO快鱼。
STM32 的定時器除了 TIM6
和 TIM7
(基本定時器)颠印。其他的定時器都可以用來產生 PWM 輸出。其中高級定時器 TIM1
和 TIM8
可以同時產生多達 7
路的 PWM 輸出抹竹。而通用定時器 TIM2/3/4/5
也能同時產生多達 4
路的 PWM 輸出线罕。這樣,STM32 最多可以同時產生 30 路 PWM 輸出窃判。
每個定時器有四個通道钞楼,每一個通道都有一個捕獲比較寄存器,將寄存器值和計數(shù)器值比較兢孝,通過比較結果輸出高低電平窿凤,便可以實現(xiàn)脈沖寬度調制模式(PWM信號)。
三跨蟹、新建工程
1. 打開 STM32CubeMX 軟件雳殊,點擊“新建工程”
2. 選擇 MCU 和封裝
3. 配置時鐘
RCC 設置,選擇 HSE(外部高速時鐘) 為 Crystal/Ceramic Resonator(晶振/陶瓷諧振器)
選擇 Clock Configuration窗轩,配置系統(tǒng)時鐘 SYSCLK 為 72MHz
修改 HCLK 的值為 72 后夯秃,輸入回車,軟件會自動修改所有配置
4. 配置調試模式
非常重要的一步痢艺,否則會造成第一次燒錄程序后續(xù)無法識別調試器
SYS 設置仓洼,選擇 Debug 為 Serial Wire
四、TIM3通用定時器
4.1 選擇定時器
由于 LED 燈所在引腳為 PB1
堤舒,在右邊圖中找到 LED 燈對應引腳色建,選擇 TIM3_CH4
4.2 參數(shù)配置
在 Timers
中選擇 TIM3
設置,指定時鐘源為 Internal Clock
內部時鐘舌缤,通道4選擇 PWM Generation CH4
PWM輸出箕戳。
在 Parameter Settings
進行具體參數(shù)配置某残。
- Counter setting
- Prescaler(時鐘預分頻數(shù)):1024-1
- Counter Mode(計數(shù)模式):Up(向上計數(shù)模式)
- Counter Period(自動重裝載值):200-1
- Internal Clock Division(CKD)(時鐘分頻因子):No Division(不分頻)
- auto-reload preload(自動重裝載):Enable(使能)
- TRGO Output (TRGO) Parameters
TRGO:在定時器的定時時間到達的時候輸出一個信號(如:定時器更新產生TRGO信號來觸發(fā)ADC的同步轉換)
- Master/Slave Mode(MSM bit):不使能
- Trigger Event Selection:Reset(UG bit from TIMx_EGR)
- PWM Generation Channel 4
- Mode(定時模式):PWM mode 1
設置定時器計數(shù)器與比較值相等時輸出引腳的狀態(tài)
- Pulse(計數(shù)比較值):0
這里建議設置為0,在中斷中改變比較寄存器的值
- Output compare preload(輸出比較預加載):Enable(使能)
作用和 auto-reload preload 類似
- Fast Mode(脈沖快速模式):Disable(不使能)
與我們配置無關不使能
- CH Polarity(輸出極性):Low
當定時器計數(shù)值小于 CCR1_Val 時陵吸,輸出低電平
4.2 配置GPIO
在 GPIO Settings
配置速度為高速玻墅。
4.3 配置NVIC
使能定時器中斷
4.4 生成代碼
輸入項目名和項目路徑
選擇應用的 IDE 開發(fā)環(huán)境 MDK-ARM V5
每個外設生成獨立的
’.c/.h’
文件不勾:所有初始化代碼都生成在 main.c
勾選:初始化代碼生成在對應的外設文件。 如 GPIO 初始化代碼生成在 gpio.c 中壮虫。
點擊 GENERATE CODE 生成代碼
4.5 添加全局變量
在 main.c
頭部添加 PWM 表
/* Private variables ---------------------------------------------------------*/
TIM_HandleTypeDef htim3;
/* USER CODE BEGIN PV */
uint16_t indexWave[] = {
1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 4,
4, 5, 5, 6, 7, 8, 9, 10, 11, 13,
15, 17, 19, 22, 25, 28, 32, 36,
41, 47, 53, 61, 69, 79, 89, 102,
116, 131, 149, 170, 193, 219, 250,
284, 323, 367, 417, 474, 539, 613,
697, 792, 901, 1024, 1024, 901, 792,
697, 613, 539, 474, 417, 367, 323,
284, 250, 219, 193, 170, 149, 131,
116, 102, 89, 79, 69, 61, 53, 47, 41,
36, 32, 28, 25, 22, 19, 17, 15, 13,
11, 10, 9, 8, 7, 6, 5, 5, 4, 4, 3, 3,
2, 2, 2, 2, 1, 1, 1, 1
};
uint16_t POINT_NUM = sizeof(indexWave)/sizeof(indexWave[0]);
/* USER CODE END PV */
并在 stm32f1xx_it.c
中聲明
/* External variables --------------------------------------------------------*/
extern TIM_HandleTypeDef htim3;
/* USER CODE BEGIN EV */
extern uint16_t indexWave[];
extern uint16_t POINT_NUM; /*PWM表中的點數(shù)*/
/* USER CODE END EV */
4.6 修改中斷回調函數(shù)
打開 stm32f1xx_it.c
中斷服務函數(shù)文件澳厢,找到 TIM3 中斷的服務函數(shù) TIM3_IRQHandler()
中斷服務函數(shù)里面就調用了定時器中斷處理函數(shù) HAL_TIM_IRQHandler()
打開 stm32f1xx_hal_tim.c
文件,找到定時器中斷處理函數(shù)原型 HAL_TIM_IRQHandler()
囚似,其主要作用就是判斷是哪個定時器產生哪種事件中斷剩拢,清除中斷標識位,然后調用中斷回調函數(shù) HAL_TIM_PeriodElapsedCallback()
饶唤。
/* NOTE: This function Should not be modified, when the callback is needed,
the HAL_GPIO_EXTI_Callback could be implemented in the user file
*/
這個函數(shù)不應該被改變裸扶,如果需要使用回調函數(shù),請重新在用戶文件中實現(xiàn)該函數(shù)搬素。
HAL_TIM_PeriodElapsedCallback()
按照官方提示我們應該再次定義該函數(shù),__weak
是一個弱化標識魏保,帶有這個的函數(shù)就是一個弱化函數(shù)熬尺,就是你可以在其他地方寫一個名稱和參數(shù)都一模一樣的函數(shù),編譯器就會忽略這一個函數(shù)谓罗,而去執(zhí)行你寫的那個函數(shù)粱哼;而 UNUSED(htim)
,這就是一個防報錯的定義檩咱,當傳進來的定時器號沒有做任何處理的時候揭措,編譯器也不會報出警告。其實我們在開發(fā)的時候已經不需要去理會中斷服務函數(shù)了刻蚯,只需要找到這個中斷回調函數(shù)并將其重寫即可而這個回調函數(shù)還有一點非常便利的地方這里沒有體現(xiàn)出來绊含,就是當同時有多個中斷使能的時候,STM32CubeMX會自動地將幾個中斷的服務函數(shù)規(guī)整到一起并調用一個回調函數(shù)炊汹,也就是無論幾個中斷躬充,我們只需要重寫一個回調函并判斷傳進來的定時器號即可。
接下來我們就在 stm32f1xx_it.c
這個文件的最下面添加 HAL_TIM_PeriodElapsedCallback()
/* USER CODE BEGIN 1 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
static uint8_t pwm_index = 1; /* 用于PWM查表 */
static uint8_t period_cnt = 0; /* 用于計算周期數(shù) */
period_cnt++;
/* 若輸出的周期數(shù)大于20讨便,輸出下一種脈沖寬的PWM波 */
if(period_cnt >= 20)
{
/* 根據(jù)PWM表修改定時器的比較寄存器值 */
__HAL_TIM_SET_COMPARE(htim, TIM_CHANNEL_4, indexWave[pwm_index]);
/* 標志PWM表的下一個元素 */
pwm_index++;
/* 若PWM脈沖表已經輸出完成一遍充甚,重置PWM查表標志 */
if( pwm_index >= POINT_NUM)
{
pwm_index=0;
}
/* 重置周期計數(shù)標志 */
period_cnt=0;
}
}
/* USER CODE END 1 */
其中 pwm_index 比較容易理解,它用于指示當前要使用 PWM 表中的哪個元素霸褒,從而 在“BRE_TIMx->BRE_CCRx = indexWave[pwm_index];‖語句中可以給 CCRx 賦予正確的數(shù)值伴找,而且當 PWM 表中的數(shù)據(jù)都使用一遍時,pwm_index 將重新指向 PWM 表的開頭废菱,開始下一次呼吸循環(huán)技矮。在本實驗的單次呼吸循環(huán)中抖誉,每個 PWM 表元素都會使用 20 次,代碼中利用 period_cnt 變量指示當前使用的次數(shù)穆役,當 period_cnt> period_class 時(即period_cnt>10 時)寸五,pwm_index 才會指向下一個元素。每個 PWM 表元素使用多次耿币,主要是為了在 TIMPeriod梳杏、PWM 表的點數(shù)、TIM_Prescaler 都固定的情況下淹接,通過調整每個元素的重復次數(shù)可以調整整個擬合波形的周期十性。如把代碼中的比較值 period_class 改為 200,每個 PWM 表遍歷一次的時間就變?yōu)樵瓉砼渲玫?10 倍塑悼,其擬合的呼吸周期也就相應地改變了劲适。圖 40-7 說明了 period =3 和 period=1 時輸出的 PWM 波形佣耐。
4.7 添加定時器啟動函數(shù)
現(xiàn)在進入 main 函數(shù)并在 while 循環(huán)前加入開啟定時器函數(shù) HAL_TIM_Base_Start_IT()
和 PWM 開啟函數(shù) HAL_TIM_PWM_Start()
狡孔,這里所傳入的 htim3 就是剛剛定時器初始化后的結構體悠反。
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_TIM3_Init();
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim3);
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_4);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
4.8 HAL庫與標準庫代碼比較
STM32CubeMX 使用 HAL 庫生成的代碼:
/**
* @brief TIM3 Initialization Function
* @param None
* @retval None
*/
static void MX_TIM3_Init(void)
{
/* USER CODE BEGIN TIM3_Init 0 */
/* USER CODE END TIM3_Init 0 */
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_OC_InitTypeDef sConfigOC = {0};
/* USER CODE BEGIN TIM3_Init 1 */
/* USER CODE END TIM3_Init 1 */
htim3.Instance = TIM3;
htim3.Init.Prescaler = 1023;
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 199;
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_PWM_Init(&htim3) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 0;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
if (HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_4) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM3_Init 2 */
/* USER CODE END TIM3_Init 2 */
HAL_TIM_MspPostInit(&htim3);
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
static uint8_t pwm_index = 1; /* 用于PWM查表 */
static uint8_t period_cnt = 0; /* 用于計算周期數(shù) */
period_cnt++;
/* 若輸出的周期數(shù)大于20盐固,輸出下一種脈沖寬的PWM波 */
if(period_cnt >= 20)
{
/* 根據(jù)PWM表修改定時器的比較寄存器值 */
__HAL_TIM_SET_COMPARE(htim,TIM_CHANNEL_4,indexWave[pwm_index]);
/* 標志PWM表的下一個元素 */
pwm_index++;
/* 若PWM脈沖表已經輸出完成一遍薛窥,重置PWM查表標志 */
if( pwm_index >= POINT_NUM)
{
pwm_index=0;
}
/* 重置周期計數(shù)標志 */
period_cnt=0;
}
}
HAL_TIM_Base_Start_IT(&htim3);
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_4);
使用 STM32 標準庫的代碼:
/**
* @brief 配置TIM復用輸出PWM時用到的I/O
* @param 無
* @retval 無
*/
static void TIMx_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* clock enable */
RCC_APB2PeriphClockCmd(BRE_TIM_GPIO_CLK, ENABLE);
BRE_TIM_GPIO_APBxClock_FUN ( BRE_TIM_GPIO_CLK, ENABLE );
BRE_GPIO_REMAP_FUN();
/* 配置呼吸燈用到的引腳 */
GPIO_InitStructure.GPIO_Pin = BRE_TIM_LED_PIN ;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 復用推挽輸出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init( BRE_TIM_LED_PORT, &GPIO_InitStructure );
}
/**
* @brief 配置嵌套向量中斷控制器NVIC
* @param 無
* @retval 無
*/
static void NVIC_Config_PWM(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
/* Configure one bit for preemption priority */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
/* 配置TIM3_IRQ中斷為中斷源 */
NVIC_InitStructure.NVIC_IRQChannel = BRE_TIMx_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
/**
* @brief 配置TIM輸出的PWM信號的模式眯停,如周期潦嘶、極性
* @param 無
* @retval 無
*/
static void TIMx_Mode_Config(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
/* 設置TIM3CLK 時鐘 */
BRE_TIM_APBxClock_FUN ( BRE_TIM_CLK, ENABLE );
/* 基本定時器配置 ,配合PWM表點數(shù)贰锁、中斷服務函數(shù)中的period_cnt循環(huán)次數(shù)設置*/
/* 設置使得整個呼吸過程為3秒左右即可達到很好的效果 */
//要求:
//TIM_Period:與PWM表中數(shù)值范圍一致
//TIM_Prescaler:越小越好巷屿,可減輕閃爍現(xiàn)象
//PERIOD_CLASS:中斷服務函數(shù)中控制單個點循環(huán)的次數(shù)固以,調整它可控制擬合曲線的周期
//POINT_NUM:PWM表的元素,它是PWM擬合曲線的采樣點數(shù)
/*************本實驗中的配置***************/
/***********************************************
#python計算腳本 count.py
#PWM點數(shù)
POINT_NUM = 110
#周期倍數(shù)
PERIOD_CLASS = 10
#定時器定時周期
TIMER_TIM_Period = 2**10
#定時器分頻
TIMER_TIM_Prescaler = 200
#STM32系統(tǒng)時鐘頻率和周期
f_pclk = 72000000
t_pclk = 1/f_pclk
#定時器update事件周期
t_timer = t_pclk*TIMER_TIM_Prescaler*TIMER_TIM_Period
#每個PWM點的時間
T_Point = t_timer * PERIOD_CLASS
#整個呼吸周期
T_Up_Down_Cycle = T_Point * POINT_NUM
print ("呼吸周期:",T_Up_Down_Cycle)
#運行結果:
呼吸周期:3.12888
************************************************************/
/* 基本定時器配置 */
TIM_TimeBaseStructure.TIM_Period = (1024-1);; //當定時器從0計數(shù)到 TIM_Period+1 嘱巾,為一個定時周期
TIM_TimeBaseStructure.TIM_Prescaler = (200-1); //設置預分頻
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1 ; //設置時鐘分頻系數(shù):不分頻(這里用不到)
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上計數(shù)模式
TIM_TimeBaseInit(BRE_TIMx, &TIM_TimeBaseStructure);
/* PWM模式配置 */
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //配置為PWM模式1
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //使能輸出
TIM_OCInitStructure.TIM_Pulse = 0; //設置初始PWM脈沖寬度為0
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //當定時器計數(shù)值小于CCR1_Val時為低電平
BRE_TIM_OCxInit ( BRE_TIMx, &TIM_OCInitStructure ); //使能通道
BRE_TIM_OCxPreloadConfig ( BRE_TIMx, TIM_OCPreload_Enable ); //使能預裝載
TIM_ARRPreloadConfig(BRE_TIMx, ENABLE); //使能TIM重載寄存器ARR
/* TIM3 enable counter */
TIM_Cmd(BRE_TIMx, ENABLE); //使能定時器
TIM_ITConfig(BRE_TIMx, TIM_IT_Update, ENABLE); //使能update中斷
NVIC_Config_PWM(); //配置中斷優(yōu)先級
}
void BRE_TIMx_IRQHandler(void)
{
static uint16_t pwm_index = 0; //用于PWM查表
static uint16_t period_cnt = 0; //用于計算周期數(shù)
if (TIM_GetITStatus(BRE_TIMx, TIM_IT_Update) != RESET) //TIM_IT_Update
{
period_cnt++;
BRE_TIMx->BRE_CCRx = indexWave[pwm_index]; //根據(jù)PWM表修改定時器的比較寄存器值
//每個PWM表中的每個元素使用period_class次
if(period_cnt > period_class)
{
pwm_index++; //標志PWM表指向下一個元素
//若PWM表已到達結尾憨琳,重新指向表頭
if( pwm_index >= POINT_NUM)
{
pwm_index=0;
}
period_cnt=0; //重置周期計數(shù)標志
}
else
{
}
TIM_ClearITPendingBit (BRE_TIMx, TIM_IT_Update); //必須要清除中斷標志位
}
}
MX_TIM3_Init();
對應 TIMx_GPIO_Config();NVIC_Config_PWM();TIMx_Mode_Config();
HAL_TIM_PWM_Init(&htim3)
對應 TIM_TimeBaseInit(BRE_TIMx, &TIM_TimeBaseStructure)
HAL_TIM_Base_Start_IT(&htim3);HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_4);
對應 TIM_Cmd(BRE_TIMx, ENABLE);TIM_ITConfig(BRE_TIMx, TIM_IT_Update, ENABLE);
五、注意事項
用戶代碼要加在 USER CODE BEGIN N
和 USER CODE END N
之間旬昭,否則下次使用 STM32CubeMX 重新生成代碼后篙螟,會被刪除。
? 由 Leung 寫于 2021 年 2 月 3 日
? 參考:STM32CubeMX系列教程4:PWM
《嵌入式-STM32開發(fā)指南》第二部分 基礎篇 - 第5章 PWM(HAL庫)
STM32CubeMX實戰(zhàn)教程(五)——通用定時器(PWM輸出)
【STM32】HAL庫 STM32CubeMX教程七---PWM輸出(呼吸燈)