基于STM32的PWM控制与多任务调度系统设计与实战

PWM(脉冲宽度调制)技术详解

1. PWM基本概念

1.1 什么是PWM

PWM(Pulse Width Modulation,脉冲宽度调制)是一种通过数字信号模拟模拟信号的技术。它通过改变脉冲的宽度(占空比)来控制平均电压值,而不改变脉冲的频率

1.2 PWM关键参数

  • 频率(Frequency):PWM信号完整周期的重复速率
  • 周期(Period):一个完整PWM波形的时间长度
  • 占空比(Duty Cycle):高电平时间占整个周期的百分比
  • 脉冲宽度(Pulse Width):高电平持续的时间

2. PWM工作原理

2.1 基本原理

一个PWM周期:
┌─────────────────┐
│                 │
│   高电平时间    │   低电平时间
│   (Ton)         │   (Toff)
└─────────────────┘
←───── 周期(T) ────→

占空比 = (Ton / T) × 100%

2.2 PWM控制模拟量原理

通过改变占空比,可以控制负载上的平均电压:

  • 占空比0%:平均电压为0V
  • 占空比50%:平均电压为电源电压的一半
  • 占空比100%:平均电压等于电源电压

3. STM32中的PWM实现

3.1 STM32定时器PWM模式

STM32的通用定时器(TIM2-TIM5)和高级定时器都支持PWM输出。

3.2 关键寄存器

  • ARR(Auto-Reload Register):决定PWM周期
  • CCR(Capture/Compare Register):决定PWM占空比
  • PSC(Prescaler):时钟预分频器

3.3 PWM频率计算

定时器时钟 = 系统时钟 / (PSC + 1)
PWM频率 = 定时器时钟 / (ARR + 1)

在我们的代码中:

  • 系统时钟 = 72MHz
  • PSC = 71 → 定时器时钟 = 72MHz / 72 = 1MHz
  • ARR = 999 → PWM频率 = 1MHz / 1000 = 1kHz

3.4 PWM占空比计算

占空比 = (CCR / (ARR + 1)) × 100%

在我们的代码中,ARR=999,所以:

  • CCR = 0 → 占空比0%
  • CCR = 500 → 占空比50%
  • CCR = 999 → 占空比100%

4. STM32CubeMX中PWM配置详解

4.1 定时器配置步骤

  1. 选择定时器:TIM3用于PA1,TIM2/TIM3备用
  2. 时钟源:内部时钟
  3. 通道配置:PWM Generation CH2
  4. 参数设置
    • Prescaler:71
    • Counter Period:999
    • Pulse:初始占空比
    • CH Polarity:High/Low
      在这里插入图片描述
  5. 选择定时器:TIM3用于PA1,TIM2/TIM3备用
  6. 时钟源:内部时钟
  7. 通道配置:PWM Generation CH3
  8. 参数设置
    • Prescaler:71
    • Counter Period:999
    • Pulse:初始占空比
    • CH Polarity:High/Low

在这里插入图片描述

4.2 引脚配置

  • PA1:TIM3_CH1,支持硬件PWM
  • PC13:通常不支持硬件PWM,需要软件模拟

5. 硬件PWM vs 软件PWM

5.1 硬件PWM(TIM3控制PA1)

优点:

  • 精度高,稳定性好
  • 不占用CPU资源
  • 频率和占空比精确可控

实现方式:

HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, pulse_value);

5.2 软件PWM(控制PC13)

优点:

  • 可在任何GPIO上实现
  • 配置简单

缺点:

  • 占用CPU资源
  • 精度和稳定性较差

实现方式:

// 软件PWM计数器
static uint32_t pwm_counter = 0;

// 在主循环中
if(pwm_counter < target_pulse) {
    HAL_GPIO_WritePin(GPIOx, GPIO_PIN_x, LED_ON);
} else {
    HAL_GPIO_WritePin(GPIOx, GPIO_PIN_x, LED_OFF);
}
pwm_counter = (pwm_counter + 1) % PWM_PERIOD;

6. 呼吸灯算法实现

6.1 线性呼吸算法

// 呼吸灯核心算法
pulse += direction * step;

if(pulse >= MAX_VALUE) {
    pulse = MAX_VALUE;
    direction = -1;  // 改为递减
} else if(pulse <= MIN_VALUE) {
    pulse = MIN_VALUE;
    direction = 1;   // 改为递增
}

6.2 呼吸灯效果调节

  • 步长(step):控制亮度变化速度
  • 延时时间:控制呼吸周期
  • 最大/最小值:控制亮度范围

结果展示

视频

mmexport1760942956673

标题STM32多任务并发实现方案

1. 项目概述

本项目基于STM32微控制器,使用硬件定时器实现多任务并发功能,同时完成两个周期性任务:

  • 任务1:每隔5秒从串口发送"hello windows!"
  • 任务2:每隔2秒控制LED灯周期性闪烁

2. STM32CubeMX详细配置步骤

2.1 创建新项目

  1. 打开STM32CubeMX,点击"New Project"
  2. 选择对应的STM32系列芯片(如STM32F103C8T6)
  3. 在Pinout视图中开始配置引脚

2.2 系统核心配置

  1. SYS配置
    • Debug: Serial Wire(用于SWD调试)
    • Timebase Source: SysTick(保持默认)

在这里插入图片描述

  1. RCC配置
    • High Speed Clock (HSE): Crystal/Ceramic Resonator
    • 启用外部高速时钟

在这里插入图片描述

  1. 时钟树配置
    • HSE输入频率:8MHz
    • PLL倍频设置:×9
    • 系统时钟:72MHz
    • AHB预分频器:1
    • APB1预分频器:2(36MHz)
    • APB2预分频器:1(72MHz)
      在这里插入图片描述

2.3 外设配置

GPIO配置
  1. 找到PA1引脚(或其他可用GPIO)我选择的是PA1
  2. 设置为GPIO_Output
  3. 配置如下:
    • GPIO output level: Low
    • GPIO mode: Output Push Pull
    • GPIO Pull-up/Pull-down: No pull-up and no pull-down
    • Maximum output speed: Low

在这里插入图片描述

USART配置
  1. 选择USART1
  2. 模式:Asynchronous
  3. 基本参数:
    • Baud Rate: 115200
    • Word Length: 8 Bits
    • Parity: None
    • Stop Bits: 1
    • Over Sampling: 16 Samples
      在这里插入图片描述
TIM2配置(5秒定时器)
  1. 选择TIM2

  2. 配置参数:

    • Clock Source: Internal Clock
    • Prescaler: 7199
    • Counter Period: 4999
    • auto-reload preload: Enable
    • 计算公式:72MHz / (7199+1) = 10kHz,10kHz / 5000 = 2Hz(0.5秒周期)
    • 实际使用软件计数实现5秒
      在这里插入图片描述
  3. NVIC设置:

    • TIM2 global interrupt: Enable
      在这里插入图片描述
TIM3配置(2秒定时器)
  1. 选择TIM3
  2. 配置参数:
    • Clock Source: Internal Clock
    • Prescaler: 7199
    • Counter Period: 1999
    • auto-reload preload: Enable
    • 计算公式:72MHz / (7199+1) = 10kHz,10kHz / 2000 = 5Hz(0.2秒周期)
    • 实际使用软件计数实现2秒

在这里插入图片描述

  1. NVIC设置:
    • TIM3 global interrupt: Enable
      在这里插入图片描述
      在这里插入图片描述

2.4 项目生成设置

  1. Project Manager

    • Project Name: MultiTask_Timer
    • Project Location: 选择合适路径
    • Toolchain/IDE: MDK-ARM V5
  2. Code Generator

    • 选择"Copy only necessary library files"
    • 选择"Generate peripheral initialization as pair of ‘.c/.h’"
    • 选择"Set all free pins as analog"
  3. 点击"GENERATE CODE"生成项目

在这里插入图片描述

3. 硬件定时器中断方案

3.1 核心特性

  • 技术基础:使用STM32的TIM2和TIM3硬件定时器
  • 中断机制:基于1ms定时器中断进行时间累积
  • 任务触发:通过标志位在主循环中处理任务
  • 精度:高精度,不受主循环执行时间影响

3.2 代码架构

宏定义和变量声明
/* USER CODE BEGIN PD */
// 定义LED引脚
#define LED_PIN GPIO_PIN_1
#define LED_PORT GPIOA

// 定时目标值 (基于1ms中断)
#define TIMER_5S_TARGET 5000  // 5秒 = 5000 × 1ms
#define TIMER_2S_TARGET 2000  // 2秒 = 2000 × 1ms
/* USER CODE END PD */

/* USER CODE BEGIN PV */
// 定时器相关变量
volatile uint32_t timer2_counter = 0;
volatile uint32_t timer3_counter = 0;
volatile uint8_t timer2_flag = 0;
volatile uint8_t timer3_flag = 0;

// 调试变量
uint32_t last_debug_time = 0;
uint32_t debug_counter = 0;
/* USER CODE END PV */
主函数初始化
int main(void)
{
  /* MCU初始化 */
  HAL_Init();
  SystemClock_Config();
  
  /* 外设初始化 */
  MX_GPIO_Init();
  MX_TIM2_Init();
  MX_TIM3_Init();
  MX_USART1_UART_Init();
  MX_NVIC_Init();
  
  /* USER CODE BEGIN 2 */
  // 启动定时器
  HAL_TIM_Base_Start_IT(&htim2);  // TIM2用于5秒定时
  HAL_TIM_Base_Start_IT(&htim3);  // TIM3用于2秒定时
  
  // 初始化LED为关闭状态
  HAL_GPIO_WritePin(LED_PORT, LED_PIN, GPIO_PIN_RESET);
  
  // LED硬件测试
  char startup_msg[] = "系统启动,开始LED硬件测试...\r\n";
  HAL_UART_Transmit(&huart1, (uint8_t*)startup_msg, strlen(startup_msg), HAL_MAX_DELAY);
  
  // 快速闪烁测试LED
  for(int i = 0; i < 6; i++) {
    HAL_GPIO_TogglePin(LED_PORT, LED_PIN);
    HAL_Delay(200);
  }
  
  char test_complete_msg[] = "LED硬件测试完成,开始定时器任务\r\n";
  HAL_UART_Transmit(&huart1, (uint8_t*)test_complete_msg, strlen(test_complete_msg), HAL_MAX_DELAY);
  /* USER CODE END 2 */
主循环任务处理
while (1)
{
  uint32_t current_time = HAL_GetTick();
  
  // 每秒输出调试信息
  if (current_time - last_debug_time >= 1000) {
    last_debug_time = current_time;
    debug_counter++;
    
    char debug_msg[128];
    sprintf(debug_msg, "[%lu秒] T2计数:%lu T3计数:%lu T2标志:%d T3标志:%d 循环:%lu\r\n", 
            current_time/1000, timer2_counter, timer3_counter, timer2_flag, timer3_flag, debug_counter);
    HAL_UART_Transmit(&huart1, (uint8_t*)debug_msg, strlen(debug_msg), HAL_MAX_DELAY);
  }
  
  // 处理5秒定时任务 - 串口发送
  if (timer2_flag) {
    timer2_flag = 0;
    char hello_msg[] = "=== hello windows! ===\r\n";
    HAL_UART_Transmit(&huart1, (uint8_t*)hello_msg, strlen(hello_msg), HAL_MAX_DELAY);
  }
  
  // 处理2秒定时任务 - LED闪烁
  if (timer3_flag) {
    timer3_flag = 0;
    HAL_GPIO_TogglePin(LED_PORT, LED_PIN);
    char led_msg[50];
    sprintf(led_msg, "LED状态: %s\r\n", HAL_GPIO_ReadPin(LED_PORT, LED_PIN) ? "ON" : "OFF");
    HAL_UART_Transmit(&huart1, (uint8_t*)led_msg, strlen(led_msg), HAL_MAX_DELAY);
  }
  
  // 这里可以添加其他后台任务
  HAL_Delay(10); // 小延时,让出CPU时间处理其他任务
}
定时器中断回调函数
/* USER CODE BEGIN 4 */
/**
  * @brief 定时器中断回调函数
  */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if (htim->Instance == TIM2) {
    // 5秒定时器 - 累积计数
    timer2_counter++;
    if (timer2_counter >= TIMER_5S_TARGET) {
      timer2_counter = 0;
      timer2_flag = 1;
    }
  }
  else if (htim->Instance == TIM3) {
    // 2秒定时器 - 累积计数
    timer3_counter++;
    if (timer3_counter >= TIMER_2S_TARGET) {
      timer3_counter = 0;
      timer3_flag = 1;
    }
  }
}
/* USER CODE END 4 */

3.3 系统时钟配置

void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_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();
  }
}

3.4 NVIC中断配置

static void MX_NVIC_Init(void)
{
  /* TIM2_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(TIM2_IRQn);
  /* TIM3_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(TIM3_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(TIM3_IRQn);
}

4. 定时器配置原理

4.1 定时器计算公式

定时器频率 = 系统时钟 / (预分频器 + 1)
中断周期 = (计数器周期 + 1) / 定时器频率

4.2 TIM2配置(5秒定时基础)

  • 系统时钟:72MHz
  • 预分频器:7199
  • 定时器频率:72MHz / 7200 = 10kHz
  • 中断周期:5000 / 10kHz = 0.5秒
  • 软件计数:10次中断 = 5秒

4.3 TIM3配置(2秒定时基础)

  • 系统时钟:72MHz
  • 预分频器:7199
  • 定时器频率:72MHz / 7200 = 10kHz
  • 中断周期:2000 / 10kHz = 0.2秒
  • 软件计数:10次中断 = 2秒

5. 性能特点

5.1 优势

  • 高精度定时:硬件定时器提供精确的时间基准
  • 实时响应:中断机制确保及时响应
  • 低CPU占用:主循环可处理其他任务
  • 可扩展性:易于添加更多定时任务
  • 稳定性:不受主循环执行时间影响

5.2 技术要点

  1. 中断优先级:合理设置NVIC优先级避免冲突
  2. 标志位处理:使用volatile变量确保数据一致性
  3. 资源管理:合理配置定时器参数避免资源浪费
  4. 错误处理:包含完整的错误处理机制

6. 测试验证

6.1 功能测试项目

  1. 系统启动测试:验证硬件初始化和LED测试
  2. 定时精度测试:检查5秒和2秒定时的准确性
  3. 多任务并发测试:验证两个任务能否同时正常运行
  4. 长时间运行测试:检查系统稳定性

6.2 预期输出结果

系统启动,开始LED硬件测试...
LED硬件测试完成,开始定时器任务
[1秒] T2计数:1000 T3计数:1000 T2标志:0 T3标志:0 循环:1
LED状态: ON
[2秒] T2计数:2000 T3计数:0 T2标志:0 T3标志:1 循环:2
[3秒] T2计数:3000 T3计数:1000 T2标志:0 T3标志:0 循环:3
LED状态: OFF
[4秒] T2计数:4000 T3计数:0 T2标志:0 T3标志:1 循环:4
=== hello windows! ===
[5秒] T2计数:0 T3计数:1000 T2标志:1 T3标志:0 循环:5

6.3测试结果

在这里插入图片描述
视频

1.LED

mmexport1760930152907

2.串口表现

mmexport1760930217986

7. 应用扩展建议

7.1 功能扩展

  1. 增加更多定时任务:使用其他定时器或优化现有定时器
  2. 实现任务优先级:在中断处理中添加优先级机制
  3. 添加参数配置:通过串口动态修改定时参数

7.2 优化方向

  1. 功耗优化:在空闲任务中进入低功耗模式
  2. 通信优化:使用DMA进行串口数据传输
  3. 可靠性提升:添加看门狗和异常恢复机制

8. 总结

综上所述,本报告系统地阐述了STM32嵌入式系统中两项核心技术的原理与实现:PWM(脉冲宽度调制)基于硬件定时器的多任务并发

通过PWM技术,我们实现了对输出信号的精确模拟控制,从呼吸灯这一经典应用入手,深入剖析了其背后的数学模型、STM32的硬件配置方法以及关键参数的计算,展现了如何利用微控制器的定时器外设,以数字之道实现模拟控制之效。

进而,通过多任务并发项目,我们演示了如何利用STM32的硬件定时器中断,构建一个稳定、精准且低CPU占用的多任务调度系统。该方案成功实现了“5秒串口通信”与“2秒LED闪烁”任务的并行不悖,其设计思想——包括中断服务例程的轻量化、标志位通信机制以及主循环的任务分发——为开发更复杂的实时嵌入式应用奠定了坚实的基础。

从精确的PWM波形控制到可靠的多任务调度,本报告所呈现的不仅是具体的技术实现,更是一种嵌入式系统的设计哲学:高效地利用硬件资源,通过中断与主循环的协同,在有限的资源下构建出稳定、响应及时且易于维护的应用系统。

展望未来,在此基础之上,我们可以进一步集成更多的功能模块,如传感器数据采集、电机伺服控制、通信协议栈(如MQTT、Modbus)等,并引入实时操作系统(RTOS)来管理更复杂的任务调度与资源共享,从而将系统扩展至物联网节点、工业控制器、智能设备等更广阔的应用领域。

至此,STM32的探索之旅告一段落,但它所开启的嵌入式世界大门已然敞开。掌握了这些核心基础,开发者便拥有了将创新想法转化为现实产品的钥匙,能够在嵌入式开发的广阔天地中,构建出更多智能、高效的电子系统。


感谢友情链接
https://blog.csdn.net/weixin_64559251/article/details/127561710

https://blog.csdn.net/weixin_64559251/article/details/127581276

http://www.mcublog.cn/stm32/2021_01/stm32cubemx-dingshiqi-led/

http://www.mcublog.cn/stm32/2021_01/stm32cubemx-pwm-huxideng/

https://blog.csdn.net/zmhDD/article/details/111942507

https://blog.csdn.net/qq_45237293/article/details/111997424

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐