使用定時器 4 通道 3 生成 PWM 波控制 LED1 ,實現呼吸燈效果。 頻率:2kHz,PSC=71,ARR=499
pwm.c:
#include "pwm.h" // 本模塊頭文件:應聲明 pwm_init/pwm_compare_set 等原型、并包含 HAL 頭//(示例未貼出 pwm.h,注意里頭應 #include "stm32f1xx_hal.h")TIM_HandleTypeDef pwm_handle = {0}; // 全局定時器句柄,供 HAL 例程與中斷共同訪問// init函數:配置 TIM4-CH3 為 PWM 輸出(PB8),頻率由 arr/psc 決定;默認給 50% 占空比
void pwm_init(uint16_t arr, uint16_t psc)
{TIM_OC_InitTypeDef pwm_config = {0}; // PWM 通道配置結構體(輸出比較/ PWM 專用)pwm_handle.Instance = TIM4; // 選擇定時器:TIM4(F103 默認 CH3 對應 PB8)pwm_handle.Init.Prescaler = psc; // 預分頻:計數時鐘 = TIM4CLK/(PSC+1)pwm_handle.Init.Period = arr; // 自動重裝載:計數滿 (ARR) 產生更新事件并回到 0pwm_handle.Init.CounterMode = TIM_COUNTERMODE_UP; // 向上計數(0 → ARR)HAL_TIM_PWM_Init(&pwm_handle); // 初始化 PWM 基本參數(會回調 HAL_TIM_PWM_MspInit)pwm_config.OCMode = TIM_OCMODE_PWM1; // PWM 模式1:CNT<CCR 時輸出為“有效電平”pwm_config.Pulse = arr/2; // 初始占空比:CCR = ARR/2 → 50%pwm_config.OCPolarity = TIM_OCPOLARITY_LOW; // 有效電平為“低”;常用于“下拉點亮/灌電流”接法// 可選:顯式關閉快速模式(F1 默認即可)// pwm_config.OCFastMode = TIM_OCFAST_DISABLE;HAL_TIM_PWM_ConfigChannel(&pwm_handle, &pwm_config, TIM_CHANNEL_3); // 配置通道3HAL_TIM_PWM_Start(&pwm_handle, TIM_CHANNEL_3); // 啟動通道3的 PWM 輸出
}// MSP(MCU Support Package)底層初始化:由 HAL_TIM_PWM_Init() 自動調用
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{if(htim->Instance == TIM4) // 僅處理 TIM4 的底層{GPIO_InitTypeDef gpio_initstruct;// 1) 打開外設時鐘__HAL_RCC_GPIOB_CLK_ENABLE(); // 使能 GPIOB 時鐘(PB8 用作復用推挽輸出)__HAL_RCC_TIM4_CLK_ENABLE(); // 使能 TIM4 時鐘// 2) 配置 PB8 為 TIM4_CH3 的復用功能gpio_initstruct.Pin = GPIO_PIN_8; // TIM4_CH3 → PB8(默認映射,無需重映射)gpio_initstruct.Mode = GPIO_MODE_AF_PP; // 復用推挽輸出(由定時器外設驅動)gpio_initstruct.Pull = GPIO_PULLUP; // 上拉(對輸出模式影響不大,可用 NOPULL)gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH; // 高速(F1 表示 50MHz 等效)HAL_GPIO_Init(GPIOB, &gpio_initstruct);}
}// 修改 CCR(占空比)的函數:val ∈ [0, ARR];決定“有效電平持續的計數值”
void pwm_compare_set(uint16_t val)
{__HAL_TIM_SET_COMPARE(&pwm_handle, TIM_CHANNEL_3, val); // 將 CCR3 設為 val// 注:本例 OCPolarity=LOW + PWM1 → CNT<CCR 期間輸出為低電平(有效電平=低),// 所以 val 表示“低電平持續的時間”;如要“高電平持續時間”,將極性改為 HIGH。
}
頻率驗證(給的參數):72 MHz 系統、APB1÷2 → TIM4CLK=72 MHz(F1 定時器×2 規則)。
PSC=71 ? 計數時鐘 72 MHz/(71+1)=1 MHz;ARR=499 ? 周期 (499+1)/1 MHz=0.5 ms ? 2 kHz。
main.c:
#include "sys.h" // 你的系統時鐘/板級支持
#include "delay.h" // 簡易延時(阻塞)
#include "led.h" // LED 初始化/翻轉(若 LED1 與 PWM 引腳同線,注意別沖突)
#include "pwm.h" // 本模塊頭:pwm_init/pwm_compare_setint main(void)
{HAL_Init(); /* 初始化 HAL 庫(中斷優先級分組、Systick 等) */stm32_clock_init(RCC_PLL_MUL9); /* HSE*9 → 72MHz,確保 TIM4 時鐘為 72MHz(見 F1×2 規則) */led_init(); /* 板載 LED 初始化(若是 PC13,不影響 PB8) */// 初始化 PWM:2kHz → PSC=71, ARR=499(注意傳參減1的寫法)pwm_init(500 - 1, 72 - 1); // ARR=499, PSC=71uint16_t i = 0;while(1){// 漸亮:CCR 從 0 → 299(最大低電平時間 300/500=60%)for(i = 0; i < 300; i++){pwm_compare_set(i); // 設置 CCR3 = i(決定“有效電平時寬”)delay_ms(10); // 粗糙的呼吸節奏(阻塞式,演示用)}// 漸暗:CCR 從 300 → 0for(i = 0; i < 300; i++){pwm_compare_set(300 - i);delay_ms(10);}// 注:若想“滿范圍”呼吸,可把上下限擴為 [0, ARR],并做非線性 Gamma 校正更符合人眼感知}
}
說明:當前把 CCR 只掃到 300(≤ARR=499),即“有效電平占比”最大約 60%(或在 HIGH 極性時為 60% 亮度)。
若 LED 是灌電流接法(LED 正端接 +3.3V,負端接 PB8),本例 OCPolarity=LOW 正合適(低電平點亮)。
若 LED 是拉電流接法(LED 負端接 GND,正端接 PB8),建議把 OCPolarity
改成 TIM_OCPOLARITY_HIGH
。
3) 字段與取值速查
A. 定時器基類(TIM_HandleTypeDef
的 Init
字段)
Instance
:定時器外設基址可選:
TIM1
(高級,APB2)、TIM2/3/4
(通用,APB1)。本例:
TIM4
(CH3→PB8)。
Prescaler
(PSC):uint16_t
,0~65535計數時鐘
CK_CNT = TIMxCLK / (PSC + 1)
;PSC 越大,計數越慢。
Period
(ARR):uint16_t
(F103 多數封裝當 16 位用)PWM 頻率
f_pwm = CK_CNT / (ARR + 1)
。
CounterMode
:計數模式TIM_COUNTERMODE_UP
(向上)TIM_COUNTERMODE_DOWN
(向下)TIM_COUNTERMODE_CENTERALIGNED1/2/3
(中心對齊:多用于對稱 PWM)
ClockDivision
(未在你代碼中設置,默認 DIV1):數字濾波分頻TIM_CLOCKDIVISION_DIV1 / DIV2 / DIV4
一般保持
DIV1
。
AutoReloadPreload
:ARR 預裝載TIM_AUTORELOAD_PRELOAD_DISABLE
:寫 ARR 立即生效TIM_AUTORELOAD_PRELOAD_ENABLE
:寫 ARR 進入影子,下次更新事件才生效(避免“撕裂”)
B. PWM 通道(TIM_OC_InitTypeDef
)
OCMode
:輸出比較模式(PWM 相關)TIM_OCMODE_PWM1
:CNT < CCR
時輸出有效電平,否則無效TIM_OCMODE_PWM2
:與 PWM1 反相(CNT < CCR
時輸出無效電平)
Pulse
:CCR
初值(占空比計數)取值范圍
[0, ARR]
,等于“有效電平持續的計數值”。
OCPolarity
:極性(有效電平)TIM_OCPOLARITY_HIGH
:有效電平=高(CNT<CCR
時輸出高)TIM_OCPOLARITY_LOW
:有效電平=低(CNT<CCR
時輸出低)和 LED 接法相關:灌電流(低電平點亮)用 LOW;拉電流(高電平點亮)用 HIGH。
(可選)
OCFastMode
:快速模式TIM_OCFAST_DISABLE/ENABLE
;一般保持關閉。
調用:
HAL_TIM_PWM_ConfigChannel(&htim, &cfg, TIM_CHANNEL_1/2/3/4)
本例:
TIM_CHANNEL_3
。
C. GPIO(GPIO_InitTypeDef
)
Pin
:GPIO_PIN_0 ... GPIO_PIN_15
本例:
GPIO_PIN_8
(PB8)
Mode
:工作模式GPIO_MODE_AF_PP
(復用推挽輸出,由外設驅動,PWM 必選)GPIO_MODE_AF_OD
(復用開漏)以及輸入/普通輸出模式(本例不適用)
Pull
:上下拉(對輸出影響不大)GPIO_NOPULL
/GPIO_PULLUP
/GPIO_PULLDOWN
Speed
:端口速度(F1 表示驅動能力/切換速度)GPIO_SPEED_FREQ_LOW/MEDIUM/HIGH
(HIGH≈50 MHz 級)
D. HAL 關鍵 API
HAL_TIM_PWM_Init(&htim)
初始化定時器為 PWM 功能,并回調
HAL_TIM_PWM_MspInit()
完成時鐘/GPIO。
HAL_TIM_PWM_ConfigChannel(&htim, &cfg, TIM_CHANNEL_x)
為指定通道寫入 OC/PWM 模式、極性、CCR 初值等。
HAL_TIM_PWM_Start(&htim, TIM_CHANNEL_x)
/HAL_TIM_PWM_Stop(...)
啟停 PWM 輸出(只影響該通道)。
__HAL_TIM_SET_COMPARE(&htim, TIM_CHANNEL_x, val)
運行時修改
CCR
(占空比):val ∈ [0, ARR]
。PWM1 + HIGH:
val
表示“高電平時間”;PWM1 + LOW:val
表示“低電平時間”。
E. 頻率/占空比關系(便于心算)
f_pwm = TIMxCLK / ((PSC+1)*(ARR+1))
duty = CCR / (ARR+1)
(以有效電平所占比例計)本例:
PSC=71, ARR=499 ? f_pwm=2 kHz
;CCR=250 ? 50% 占空比(有效電平占一半周期)
4) 小改進建議
范圍保護:在
pwm_compare_set()
里把val
限制到[0, ARR]
,避免越界:
if (val > __HAL_TIM_GET_AUTORELOAD(&pwm_handle)) val = __HAL_TIM_GET_AUTORELOAD(&pwm_handle);
__HAL_TIM_SET_COMPARE(&pwm_handle, TIM_CHANNEL_3, val);
視覺線性:人眼對亮度非線性,可用
val = (i*i)/k
或查表(Gamma≈2.2)做“呼吸更順滑”。非阻塞刷新:用定時器中斷/軟件定時器定期更新 CCR,替代
delay_ms(10)
,讓主循環可做別的事。
配置PWM步驟
一、步驟清單(從零到出波)
確認時鐘樹
目標:
SYSCLK=72 MHz
,APB1=36 MHz (÷2)
,APB2=72 MHz (÷1)
。F1 的“×2 規則”:APBx 分頻≠1 時,TIMxCLK = 2×PCLKx。
TIM2/3/4(APB1):若 APB1=36 MHz(÷2),則 TIM2/3/4CLK=72 MHz。
TIM1(APB2):本例不必,但規則相同。
選定定時器與通道、引腳
例:
TIM4_CH3 → PB8
(默認映射)。其他常見默認映射:
TIM2: CH1~4→PA0/PA1/PA2/PA3
TIM3: CH1~4→PA6/PA7/PB0/PB1
TIM4: CH1~4→PB6/PB7/PB8/PB9
TIM1: CH1~4→PA8/PA9/PA10/PA11(高級定時器)
開外設時鐘
__HAL_RCC_TIMx_CLK_ENABLE();
、__HAL_RCC_GPIOx_CLK_ENABLE();
(若 TIM1 還要 BDTR 的 MOE)。
配置引腳為復用推挽輸出
GPIO_MODE_AF_PP
、速度GPIO_SPEED_FREQ_HIGH
,引腳設為對應通道的管腳(如 PB8)。
計算頻率并設置 PSC/ARR
公式:
例:
TIMxCLK=72 MHz
、PSC=71
、ARR=499
→f=72e6/(72*500)=2 kHz
。
初始化定時器為 PWM 模式
htim.Instance = TIMx;
htim.Init.Prescaler = PSC;
、htim.Init.Period = ARR;
、CounterMode=UP;
HAL_TIM_PWM_Init(&htim);
(會回調 MSP 完成時鐘/GPIO)。
配置通道 PWM 參數
TIM_OC_InitTypeDef cfg;
cfg.OCMode = TIM_OCMODE_PWM1;
(或PWM2
)cfg.Pulse = CCR 初值
(占空比計數值,0~ARR)cfg.OCPolarity = TIM_OCPOLARITY_HIGH/LOW
(有效電平是高還是低)HAL_TIM_PWM_ConfigChannel(&htim, &cfg, TIM_CHANNEL_x);
啟動 PWM 輸出
HAL_TIM_PWM_Start(&htim, TIM_CHANNEL_x);
(僅該通道)TIM1(高級定時器)還需:
HAL_TIMEx_PWMN_Start(...)
(若用互補),并確保 MOE 置位(HAL 會在HAL_TIM_PWM_Start
內處理 BDTR)。
運行時改占空比
__HAL_TIM_SET_COMPARE(&htim, TIM_CHANNEL_x, val);
(0~ARR
)。
(可選)中斷/DMA 動態刷新
用
HAL_TIM_PWM_Start_DMA
或定時器中斷里遞增 CCR,可做“呼吸燈”等效果。
二、最小可跑模板(HAL,TIM4_CH3@PB8,2 kHz)
// pwm.h
#pragma once
#include "stm32f1xx_hal.h"
void PWM_Init_TIM4_CH3(uint16_t arr, uint16_t psc);
void PWM_SetCCR_TIM4_CH3(uint16_t val);
// pwm.c
#include "pwm.h"static TIM_HandleTypeDef htim4;void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{if (htim->Instance == TIM4) {__HAL_RCC_TIM4_CLK_ENABLE();__HAL_RCC_GPIOB_CLK_ENABLE();GPIO_InitTypeDef io = {0};io.Pin = GPIO_PIN_8; // TIM4_CH3 → PB8io.Mode = GPIO_MODE_AF_PP; // 復用推挽io.Pull = GPIO_NOPULL;io.Speed = GPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(GPIOB, &io);}
}void PWM_Init_TIM4_CH3(uint16_t arr, uint16_t psc)
{htim4.Instance = TIM4;htim4.Init.Prescaler = psc; // 例:71 → 72MHz/(71+1)=1MHzhtim4.Init.CounterMode = TIM_COUNTERMODE_UP;htim4.Init.Period = arr; // 例:499 → 1MHz/(499+1)=2kHzhtim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;htim4.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;HAL_TIM_PWM_Init(&htim4);TIM_OC_InitTypeDef oc = {0};oc.OCMode = TIM_OCMODE_PWM1; // PWM1:CNT<CCR 輸出“有效電平”oc.Pulse = arr/2; // 50% 占空oc.OCPolarity = TIM_OCPOLARITY_HIGH; // 有效電平=高(LED 拉電流點亮)HAL_TIM_PWM_ConfigChannel(&htim4, &oc, TIM_CHANNEL_3);HAL_TIM_PWM_Start(&htim4, TIM_CHANNEL_3); // 開始輸出
}void PWM_SetCCR_TIM4_CH3(uint16_t val)
{if (val > __HAL_TIM_GET_AUTORELOAD(&htim4)) val = __HAL_TIM_GET_AUTORELOAD(&htim4);__HAL_TIM_SET_COMPARE(&htim4, TIM_CHANNEL_3, val);
}
// main.c(節選)
PWM_Init_TIM4_CH3(499, 71); // 2kHz
PWM_SetCCR_TIM4_CH3(250); // 50% 占空
占空比換算:
有效電平=高(
OCPolarity=HIGH
,PWM1):duty = CCR/(ARR+1)
。有效電平=低(
OCPolarity=LOW
,PWM1):duty_low = CCR/(ARR+1)
(低電平占比)。
三、常用可選項與意義
htim.Instance = TIM1/TIM2/TIM3/TIM4;
→ 選擇定時器實例。htim.Init.Prescaler = 0~65535;
→ 預分頻(計數時鐘 = TIMxCLK/(PSC+1))。htim.Init.Period = 0~65535;
→ 自動重裝載(周期計數)。htim.Init.CounterMode = TIM_COUNTERMODE_UP/DOWN/CENTERALIGNED1/2/3;
htim.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1/DIV2/DIV4;
htim.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE/ENABLE;
(ARR 是否影子寄存器)oc.OCMode = TIM_OCMODE_PWM1/PWM2;
(PWM2 與 PWM1 波形相反)oc.Pulse = 0~ARR;
(CCR,占空比計數)oc.OCPolarity = TIM_OCPOLARITY_HIGH/LOW;
(有效電平是高還是低)oc.OCFastMode = TIM_OCFAST_DISABLE/ENABLE;
(一般關)HAL_TIM_PWM_ConfigChannel(..., TIM_CHANNEL_1/2/3/4);
(選擇通道)GPIO_InitTypeDef.Mode = GPIO_MODE_AF_PP/AF_OD/...
(PWM 必須 AF_PP)GPIO_InitTypeDef.Pull = GPIO_NOPULL/PULLUP/PULLDOWN;
GPIO_InitTypeDef.Speed = GPIO_SPEED_FREQ_LOW/MEDIUM/HIGH;
四、頻率/占空比/極性要點
頻率:
f_pwm = TIMxCLK / ((PSC+1)*(ARR+1))
。占空比:
PWM1 + HIGH:
高電平占比 = CCR/(ARR+1)
;PWM1 + LOW :
低電平占比 = CCR/(ARR+1)
;PWM2:與 PWM1 相反。
極性選擇(與 LED 接法相關):
拉電流(PBx→電阻→LED→GND,置高點亮):
OCPolarity=HIGH
。灌電流(+3V3→電阻→LED→PBx,下拉點亮):
OCPolarity=LOW
。
五、常見坑位與排查
無波形:
GPIO 不是
AF_PP
;PSC/ARR 不在范圍;
引腳占用(別和普通
GPIO_WritePin
沖突);選錯通道/定時器;
TIM1 忘了 MOE(HAL 一般處理了);
JTAG 復用影響(若用到 PB3/PB4/PA15 的重映射場景,需 AFIO 處理)。
頻率不對:
沒考慮 APB×2 規則;
系統時鐘非 72 MHz;
Cube 配置與手算不一致。
亮度“線性差”:
人眼非線性,做 gamma 校正或查表;
用 DMA/定時器中斷平滑更新 CCR,避免阻塞式
delay
。