【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 CHx
	3.选择输出引脚(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 配置,自动生成如下代码:

c 复制代码
TIM_HandleTypeDef htim1;

void MX_TIM1_Init(void)
{
    htim1.Instance = TIM1;
    htim1.Init.Prescaler = 71;         // 72MHz / (71+1) = 1MHz
    htim1.Init.Period = 999;           // 每 1000 次递增,周期为 1ms
    htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
    HAL_TIM_PWM_Init(&htim1);
}

然后配置 PWM 通道:

c 复制代码
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 输出
c 复制代码
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
复制代码
该函数会设置:
	启动计数器(CEN 位)
	使能输出通道(CC1E 位)
动态修改占空比
c 复制代码
__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):

handlebars 复制代码
↑       ↑       ↑
__|¯¯¯¯¯¯¯|________|¯¯¯¯¯¯¯|________|¯¯¯¯¯¯¯|

<--  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 中启用 SysTickTIM3 的中断,也能用它们来实现呼吸灯。

实现流程:
  1. 配置 TIM2 生成 PWM (在 MX_TIM2_Init() 中)
c 复制代码
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
  1. 使用 TIM3 定时改变 PWM 占空比

启用 TIM3 中断,每隔 1ms 调整一次占空比:

c 复制代码
HAL_TIM_Base_Start_IT(&htim3);
  1. 修改 stm32f1xx_it.c 中的中断回调实现为呼吸灯逻辑:

/* USER CODE BEGIN 1 */ 区域添加以下内容:

c 复制代码
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;
    }
}

编程实现思路
bash 复制代码
[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

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

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 应用。

以上,欢迎有从事同行业的电子信息工程、互联网通信、嵌入式开发的朋友共同探讨与提问,我可以提供实战演示或模板库。希望内容能够对你产生帮助!

相关推荐
sayang_shao1 小时前
STM32H7+FreeRTOS+LwIP移植EtherCAT开源主站SOEM
stm32·soem·ethercat·canopen
源远流长jerry1 小时前
电路基础相关知识
stm32·单片机·嵌入式硬件
1+2单片机电子设计2 小时前
基于STM32的数控机床物联网改造研究
stm32·单片机·嵌入式硬件·51单片机
国科安芯3 小时前
ASP3605I同步降压调节器的高频化设计与多相扩展技术优化方案
网络·单片机·嵌入式硬件·硬件架构
许野平4 小时前
Rust:如何开发Windows 动态链接库 DLL
windows·单片机·rust·dll·动态链接库
嵌入式×边缘AI:打怪升级日志5 小时前
韦东山STM32_HAl库入门教程(SPI)学习笔记[09]内容
stm32·嵌入式硬件·microsoft
yiqiqukanhaiba6 小时前
江协科技STM32学习笔记1
科技·stm32·学习
码小文9 小时前
Altium Designer 22使用笔记(4)---添加封装、ERC检查、PDF文档与BOM生成
笔记·嵌入式硬件·硬件工程·学习方法·硬件经验
猫猫的小茶馆9 小时前
【STM32】HAL库中的实现(四):RTC (实时时钟)
stm32·单片机·嵌入式硬件·mcu·51单片机·实时音视频·pcb工艺