【STM32】HAL库中的实现(三):PWM(脉冲宽度调制)
🔧 HAL库中的实现:PWM(脉冲宽度调制)
PWM(Pulse Width Modulation)是基于定时器(TIM)产生的周期性脉冲信号,广泛应用于:① 电机调速;② LED 亮度控制;③ 蜂鸣器频率控制;④ 模拟信号模拟(DAC)等等。
PWM 本质原理(基于定时器)
PWM 信号是 定时器的输出比较功能(Output Compare) 的一种模式,核心逻辑:
参数 | 含义 |
---|---|
PSC(Prescaler) | 预分频器,降低计数频率 |
ARR(Auto Reload Register) | 自动重装值,决定周期 |
CCR(Capture Compare Register) | 比较值,决定高电平时间 |
占空比 = CCR / ARR
PWM的初始化流程( STM32CubeMX )
总体实现思路:(使用 STM32CubeMX 配置)1.启用一个支持 PWM 的定时器(如 TIM3)2.设置为 PWM Generation CHx3.选择输出引脚(TIM3_CH1 → PA6 等)4.设置频率、占空比、极性等参数HAL中的常用API:
函数 作用
HAL_TIM_PWM_Init() 初始化定时器为 PWM 模式
HAL_TIM_PWM_ConfigChannel() 配置 PWM 输出通道
HAL_TIM_PWM_Start() 启动 PWM 输出
HAL_TIM_PWM_Stop() 停止 PWM 输出
__HAL_TIM_SET_COMPARE() 动态设置占空比
__HAL_TIM_SET_AUTORELOAD() 动态修改周期
一般通过 STM32CubeMX 配置,自动生成如下代码:
TIM_HandleTypeDef htim1;void MX_TIM1_Init(void)
{htim1.Instance = TIM1;htim1.Init.Prescaler = 71; // 72MHz / (71+1) = 1MHzhtim1.Init.Period = 999; // 每 1000 次递增,周期为 1mshtim1.Init.CounterMode = TIM_COUNTERMODE_UP;HAL_TIM_PWM_Init(&htim1);
}
然后配置 PWM 通道:
TIM_OC_InitTypeDef sConfigOC = {0};
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 500; // 占空比 = 500 / 1000 = 50%
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1);
HAL 启动 PWM 输出
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
该函数会设置:启动计数器(CEN 位)使能输出通道(CC1E 位)
动态修改占空比
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, new_value);
作用:修改 CCRx(比较寄存器)值控制 PWM 高电平时间长度
例如:使用 TIM1 通道1 产生 1KHz、占空比 25% 的 PWM
Prescaler = 71; // f = 1 MHz
Period = 999; // T = 1ms (1kHz)
Pulse = 250; // 占空比 25%
PWM 频率与占空比计算公式:
设系统时钟f_clk = 72MHz
PWM频率 =f_clk / ((PSC + 1) * (ARR + 1))
占空比 =CCR / (ARR + 1)
PWM 模式图示(PWM1):
↑ ↑ ↑
__|¯¯¯¯¯¯¯|________|¯¯¯¯¯¯¯|________|¯¯¯¯¯¯¯|<-- T --> (周期)
<--> <-- (高电平时间 = CCRx)
PWM 实现呼吸灯(提供STM32F1 系列中断服务函数的实现代码)
目标:使用 定时器 PWM 输出,并通过周期性改变占空比 使 LED 灯呈现亮 → 暗 → 亮的效果 ,就像“呼吸”一样。
呼吸灯原理简介
使用 PWM 控制 LED 的亮度,通过改变 PWM 的占空比(CCR 值)实现 LED 的亮/暗过渡:- 占空比从 0% 增加到 100%:LED 从暗变亮- 占空比从 100% 减小到 0%:LED 从亮变暗- 循环往复,形成呼吸灯效果
实现结构:
1. 使用一个定时器(如 TIM2)生成 PWM
2. 使用另一个定时器(如 TIM3)定时改变占空比
也可以使用中断,在 stm32f1xx_it.c
中启用 SysTick
和 TIM3
的中断,也能用它们来实现呼吸灯。
实现流程:
- 配置 TIM2 生成 PWM(在
MX_TIM2_Init()
中)
htim2.Instance = TIM2;
htim2.Init.Prescaler = 71; // 72MHz / (71+1) = 1MHz
htim2.Init.Period = 999; // 1kHz PWM
HAL_TIM_PWM_Init(&htim2);sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 0; // 初始占空比为 0%
HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1);HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1); // 启动 PWM
- 使用 TIM3 定时改变 PWM 占空比
启用 TIM3 中断,每隔 1ms 调整一次占空比:
HAL_TIM_Base_Start_IT(&htim3);
- 修改
stm32f1xx_it.c
中的中断回调实现为呼吸灯逻辑:
在 /* USER CODE BEGIN 1 */
区域添加以下内容:
extern TIM_HandleTypeDef htim2;uint16_t pwm_val = 0;
int8_t step = 1;void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{if(htim->Instance == TIM3){// 修改占空比__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, pwm_val);pwm_val += step;if(pwm_val >= 999)step = -1;else if(pwm_val <= 0)step = 1;}
}
编程实现思路
[TIM3 中断] ---> 每 1ms 触发一次↓
[改变 pwm_val] ---> 0~999~0 来回↓
[设置 TIM2 的 CCR1] ---> 改变占空比↓
[LED 呼吸效果] ---> 更改 step 步长:控制呼吸快慢;更改 TIM3 定时时间:调节呼吸频率加入非线性变化(如正弦表):更自然的呼吸效果CubeMX配置:
TIM2 ---> PWM Mode, 通道1启用,频率设置合适(如 1kHz)
TIM3 ---> 基础定时器,启用中断,周期设置为 1ms
GPIO ---> TIM2_CH1 绑定的引脚(如 PA0)设置为 AF 推挽
NVIC ---> TIM3 中断启用,优先级配置合理LED 呼吸周期为 2 秒:pwm_val += step: 每次变化 1,共 1000 步每步 1ms → 1 秒变亮,1 秒变暗总共:2 秒一个完整呼吸周期作用:
PWM 定时器 ---> 产生恒定频率、可调占空比的信号
中断定时器 ---> 每 1ms 改变一次占空比,实现呼吸变化
HAL 接口 ---> __HAL_TIM_SET_COMPARE 实时控制亮度
优化呼吸灯频率 ---> 可使用 DMA / LUT 表优化呼吸曲线
完整代码
📄 main.c
/* USER CODE BEGIN Header */
/********************************************************************************* @file : main.c* @brief : Main program body******************************************************************************* @attention** Copyright (c) 2025 STMicroelectronics.* All rights reserved.** This software is licensed under terms that can be found in the LICENSE file* in the root directory of this software component.* If no LICENSE file comes with this software, it is provided AS-IS.********************************************************************************/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "iwdg.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "string.h"/* USER CODE END Includes *//* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
uint8_t U1SendData[] = {"hello world!"};
/* USER CODE END PTD *//* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD *//* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM *//* USER CODE END PM *//* Private variables ---------------------------------------------------------*//* USER CODE BEGIN PV *//* USER CODE END PV *//* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP *//* USER CODE END PFP *//* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 *//* USER CODE END 0 *//*** @brief The application entry point.* @retval int*/
int main(void)
{/* USER CODE BEGIN 1 */uint16_t pwmVal = 0; //占空比/* 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_USART1_UART_Init();MX_IWDG_Init();MX_TIM3_Init();/* USER CODE BEGIN 2 */HAL_UARTEx_ReceiveToIdle_IT(&huart1, U1RxData, U1RxDataSize); //使能空闲中断HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_3); //PWM初始化完毕之后,找到对应PWM的启动函数/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */HAL_UART_Transmit(&huart1, U1SendData, sizeof(U1SendData), 0xff); //启动串口空闲中断的发送while (1){while(pwmVal < 500){HAL_IWDG_Refresh(&hiwdg); //喂狗pwmVal++;//让LED灯以呼吸灯的频率闪烁__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_3, pwmVal);printf("pwmVal++: %d \r\n",pwmVal);HAL_Delay(1);}while(pwmVal){HAL_IWDG_Refresh(&hiwdg); //喂狗pwmVal--;//让LED灯以呼吸灯的频率闪烁__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_3, pwmVal);printf("pwmVal--: %d \r\n",pwmVal);HAL_Delay(1);}/* USER CODE END WHILE *//* USER CODE BEGIN 3 */}/* USER CODE END 3 */
}/*** @brief System Clock Configuration* @retval None*/
void SystemClock_Config(void)
{RCC_OscInitTypeDef RCC_OscInitStruct = {0};RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};/** Initializes the RCC Oscillators according to the specified parameters* in the RCC_OscInitTypeDef structure.*/RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSI|RCC_OSCILLATORTYPE_HSE;RCC_OscInitStruct.HSEState = RCC_HSE_ON;RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;RCC_OscInitStruct.HSIState = RCC_HSI_ON;RCC_OscInitStruct.LSIState = RCC_LSI_ON;RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK){Error_Handler();}/** Initializes the CPU, AHB and APB buses clocks*/RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK){Error_Handler();}
}/* USER CODE BEGIN 4 *//* USER CODE END 4 *//*** @brief This function is executed in case of error occurrence.* @retval None*/
void Error_Handler(void)
{/* USER CODE BEGIN Error_Handler_Debug *//* User can add his own implementation to report the HAL error return state */__disable_irq();while (1){}/* USER CODE END Error_Handler_Debug */
}#ifdef USE_FULL_ASSERT
/*** @brief Reports the name of the source file and the source line number* where the assert_param error has occurred.* @param file: pointer to the source file name* @param line: assert_param error line source number* @retval None*/
void assert_failed(uint8_t *file, uint32_t line)
{/* USER CODE BEGIN 6 *//* User can add his own implementation to report the file name and line number,ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) *//* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
📄 stm32f1xx_it.c
/* USER CODE BEGIN Header */
/********************************************************************************* @file stm32f1xx_it.c* @brief Interrupt Service Routines.******************************************************************************* @attention** Copyright (c) 2025 STMicroelectronics.* All rights reserved.** This software is licensed under terms that can be found in the LICENSE file* in the root directory of this software component.* If no LICENSE file comes with this software, it is provided AS-IS.********************************************************************************/
/* USER CODE END Header *//* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "stm32f1xx_it.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "usart.h"
#include "iwdg.h"
/* USER CODE END Includes *//* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN TD *//* USER CODE END TD *//* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD *//* USER CODE END PD *//* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM *//* USER CODE END PM *//* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV *//* USER CODE END PV *//* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN PFP *//* USER CODE END PFP *//* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 *//* USER CODE END 0 *//* External variables --------------------------------------------------------*/
extern UART_HandleTypeDef huart1;
/* USER CODE BEGIN EV *//* USER CODE END EV *//******************************************************************************/
/* Cortex-M3 Processor Interruption and Exception Handlers */
/******************************************************************************/
/*** @brief This function handles Non maskable interrupt.*/
void NMI_Handler(void)
{/* USER CODE BEGIN NonMaskableInt_IRQn 0 *//* USER CODE END NonMaskableInt_IRQn 0 *//* USER CODE BEGIN NonMaskableInt_IRQn 1 */while (1){}/* USER CODE END NonMaskableInt_IRQn 1 */
}/*** @brief This function handles Hard fault interrupt.*/
void HardFault_Handler(void)
{/* USER CODE BEGIN HardFault_IRQn 0 *//* USER CODE END HardFault_IRQn 0 */while (1){/* USER CODE BEGIN W1_HardFault_IRQn 0 *//* USER CODE END W1_HardFault_IRQn 0 */}
}/*** @brief This function handles Memory management fault.*/
void MemManage_Handler(void)
{/* USER CODE BEGIN MemoryManagement_IRQn 0 *//* USER CODE END MemoryManagement_IRQn 0 */while (1){/* USER CODE BEGIN W1_MemoryManagement_IRQn 0 *//* USER CODE END W1_MemoryManagement_IRQn 0 */}
}/*** @brief This function handles Prefetch fault, memory access fault.*/
void BusFault_Handler(void)
{/* USER CODE BEGIN BusFault_IRQn 0 *//* USER CODE END BusFault_IRQn 0 */while (1){/* USER CODE BEGIN W1_BusFault_IRQn 0 *//* USER CODE END W1_BusFault_IRQn 0 */}
}/*** @brief This function handles Undefined instruction or illegal state.*/
void UsageFault_Handler(void)
{/* USER CODE BEGIN UsageFault_IRQn 0 *//* USER CODE END UsageFault_IRQn 0 */while (1){/* USER CODE BEGIN W1_UsageFault_IRQn 0 *//* USER CODE END W1_UsageFault_IRQn 0 */}
}/*** @brief This function handles System service call via SWI instruction.*/
void SVC_Handler(void)
{/* USER CODE BEGIN SVCall_IRQn 0 *//* USER CODE END SVCall_IRQn 0 *//* USER CODE BEGIN SVCall_IRQn 1 *//* USER CODE END SVCall_IRQn 1 */
}/*** @brief This function handles Debug monitor.*/
void DebugMon_Handler(void)
{/* USER CODE BEGIN DebugMonitor_IRQn 0 *//* USER CODE END DebugMonitor_IRQn 0 *//* USER CODE BEGIN DebugMonitor_IRQn 1 *//* USER CODE END DebugMonitor_IRQn 1 */
}/*** @brief This function handles Pendable request for system service.*/
void PendSV_Handler(void)
{/* USER CODE BEGIN PendSV_IRQn 0 *//* USER CODE END PendSV_IRQn 0 *//* USER CODE BEGIN PendSV_IRQn 1 *//* USER CODE END PendSV_IRQn 1 */
}/*** @brief This function handles System tick timer.*/
void SysTick_Handler(void)
{/* USER CODE BEGIN SysTick_IRQn 0 *//* USER CODE END SysTick_IRQn 0 */HAL_IncTick();/* USER CODE BEGIN SysTick_IRQn 1 *//* USER CODE END SysTick_IRQn 1 */
}/******************************************************************************/
/* STM32F1xx Peripheral Interrupt Handlers */
/* Add here the Interrupt Handlers for the used peripherals. */
/* For the available peripheral interrupt handler names, */
/* please refer to the startup file (startup_stm32f1xx.s). */
/******************************************************************************//*** @brief This function handles EXTI line0 interrupt.*/
void EXTI0_IRQHandler(void)
{/* USER CODE BEGIN EXTI0_IRQn 0 *//* USER CODE END EXTI0_IRQn 0 */HAL_GPIO_EXTI_IRQHandler(PA0_Key_Pin);/* USER CODE BEGIN EXTI0_IRQn 1 *//* USER CODE END EXTI0_IRQn 1 */
}/*** @brief This function handles USART1 global interrupt.*/
void USART1_IRQHandler(void)
{/* USER CODE BEGIN USART1_IRQn 0 *//* USER CODE END USART1_IRQn 0 */HAL_UART_IRQHandler(&huart1);/* USER CODE BEGIN USART1_IRQn 1 *//* USER CODE END USART1_IRQn 1 */
}/* USER CODE BEGIN 1 */void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{if(GPIO_Pin == PA0_Key_Pin){if( HAL_GPIO_ReadPin(PA0_Key_GPIO_Port, PA0_Key_Pin) == GPIO_PIN_RESET){HAL_GPIO_TogglePin(GPIOC, LED_G_Pin); //红灯的状态翻转
// HAL_IWDG_Refresh(&hiwdg); //进行喂狗
// printf("Iwdg Count = %d \r\n", Count++);}}
}void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{U1RxLen = Size;HAL_UARTEx_ReceiveToIdle_IT(&huart1, U1RxData, U1RxDataSize); //启动串口空闲中断的接收U1RxFlag = 1;
}/*
//TIM的中断回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
// static uint32_t Count = 0;if(htim->Instance == TIM3) //判断产生中断的哪一个中断回调函数{HAL_GPIO_TogglePin(LED_G_GPIO_Port, LED_G_Pin); //绿灯的状态翻转// HAL_IWDG_Refresh(&hiwdg); //进行喂狗
// printf("Iwdg Count = %d \r\n", Count++);}
}
*//* USER CODE END 1 */
以上。STM32 HAL 库通过对定时器的封装,提供了标准化的 PWM 控制方式,适用于各种电平控制、电机驱动、LED 等场景,配合 CubeMX 可以快速开发出稳定可靠的 PWM 应用。
以上,欢迎有从事同行业的电子信息工程、互联网通信、嵌入式开发的朋友共同探讨与提问,我可以提供实战演示或模板库。希望内容能够对你产生帮助!