STM32L475脉冲宽度调制(PWM)实验

本文将使用PWM动态电机转速, 然后LED灯伴随电机转速进行闪烁, 电机转的越快, LED闪烁越快

使用CubeMX配置

首先配置定时器TIM2的硬件功能口为Channel 1和Channel 2两个口. 定时器的时钟源为内部时钟. 因为最终要输出PWM波形, 然后配置输出PWM频率为1kHz, 所以 1kHz = 80000kHz/(80*1000)

根据原理图, PA0和PA1为驱动电机的两个引脚, 所以在GPIO这里配置TIM2的硬件通道对应引脚为PA0和PA1

这里要注意的是, TIM2的中断要关闭掉, 因为控制电机PWM频率上到10kHz-20kHz时,

这里配置的TIM5是为了让LED灯闪烁, 闪烁频率为80,000,000/(8000*10000) = 1Hz, 闪烁周期为1s

使能TIM5的中断, 我要在中断中处理LED灯闪烁的逻辑

根据原理图增加按键中断配置

编写代码逻辑

tim.h

c 复制代码
#ifndef __TIM_H__
#define __TIM_H__

#ifdef __cplusplus
extern "C" {
#endif

#include "main.h"

extern TIM_HandleTypeDef htim2;

extern TIM_HandleTypeDef htim5;

void MX_TIM2_Init(void);
void MX_TIM5_Init(void);

void HAL_TIM_MspPostInit(TIM_HandleTypeDef *htim);

/* USER CODE BEGIN Prototypes */
#define MIN_MOTOR_SPEED 200
#define MAX_MOTOR_SPEED 1000
extern uint16_t g_motor_speed;  // 范围: 0-999
extern uint8_t g_motor_dir;     // 0=停止, 1=正转, 2=反转

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim);
void update_motor(void);
void update_led_frequency(void);

#ifdef __cplusplus
}
#endif

#endif /* __TIM_H__ */

tim.c

c 复制代码
#include "tim.h"

uint16_t g_motor_speed = 0; // 范围: 0-999
uint8_t g_motor_dir    = 0; // 0=停止, 1=正转, 2=反转

TIM_HandleTypeDef htim2;
TIM_HandleTypeDef htim5;

/* TIM2 init function */
void MX_TIM2_Init(void)
{
  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_OC_InitTypeDef sConfigOC = {0};

  htim2.Instance = TIM2;
  htim2.Init.Prescaler = 79;
  htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim2.Init.Period = 999;
  htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
  if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_PWM_Init(&htim2) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &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(&htim2, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_2) != HAL_OK)
  {
    Error_Handler();
  }
  HAL_TIM_MspPostInit(&htim2);

}
/* TIM5 init function */
void MX_TIM5_Init(void)
{
  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};

  htim5.Instance = TIM5;
  htim5.Init.Prescaler = 7999;
  htim5.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim5.Init.Period = 9999;
  htim5.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim5.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
  if (HAL_TIM_Base_Init(&htim5) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim5, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim5, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
}

void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
{

  if(tim_baseHandle->Instance==TIM2)
  {
    /* TIM2 clock enable */
    __HAL_RCC_TIM2_CLK_ENABLE();
  }
  else if(tim_baseHandle->Instance==TIM5)
  {
    /* TIM5 clock enable */
    __HAL_RCC_TIM5_CLK_ENABLE();

    /* TIM5 interrupt Init */
    HAL_NVIC_SetPriority(TIM5_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(TIM5_IRQn);
  }
}
void HAL_TIM_MspPostInit(TIM_HandleTypeDef* timHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(timHandle->Instance==TIM2)
  {
    __HAL_RCC_GPIOA_CLK_ENABLE();
    /**TIM2 GPIO Configuration
    PA0     ------> TIM2_CH1
    PA1     ------> TIM2_CH2
    */
    GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    GPIO_InitStruct.Alternate = GPIO_AF1_TIM2;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
  }

}

void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef* tim_baseHandle)
{

  if(tim_baseHandle->Instance==TIM2)
  {
    /* Peripheral clock disable */
    __HAL_RCC_TIM2_CLK_DISABLE();
  }
  else if(tim_baseHandle->Instance==TIM5)
  {
    __HAL_RCC_TIM5_CLK_DISABLE();
    HAL_NVIC_DisableIRQ(TIM5_IRQn);
  }
}

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
  if (htim->Instance == TIM5) {
    if (g_motor_dir == 1) {
      HAL_GPIO_WritePin(GPIOE, LED_R_Pin, GPIO_PIN_SET);
      HAL_GPIO_TogglePin(GPIOE, LED_B_Pin);
    } else if (g_motor_dir == 2) {
      HAL_GPIO_WritePin(GPIOE, LED_B_Pin, GPIO_PIN_SET);
      HAL_GPIO_TogglePin(GPIOE, LED_R_Pin);
    }
  }
}

void update_motor(void) {
  if (g_motor_dir == 1) {
    __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, g_motor_speed);  // MOTOR_A
    __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, 0);              // MOTOR_B
  } else if (g_motor_dir == 2) {
    __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, g_motor_speed);  // MOTOR_B
    __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, 0);              // MOTOR_A
  } else { // 停止
    __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, 0);
    __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, 0);
  }
}

#define LED_PERIOD_MAX_MS   1000   // 最慢:1秒闪一次
#define LED_PERIOD_MIN_MS   50    // 最快:100ms闪一次

static uint32_t tim5_get_tick_hz(void)
{
    uint32_t pclk1 = HAL_RCC_GetPCLK1Freq();

    // 注意:APB1 分频不为1时,定时器时钟要×2
    if ((RCC->CFGR & RCC_CFGR_PPRE1) != RCC_CFGR_PPRE1_DIV1)
    {
        pclk1 *= 2;
    }

    uint32_t psc = htim5.Instance->PSC;

    return pclk1 / (psc + 1);
}

void led_set_blink_period_ms(uint32_t period_ms)
{
    if (period_ms < 1)
        period_ms = 1;

    uint32_t tick_hz = tim5_get_tick_hz();

    uint64_t ticks = (uint64_t)tick_hz * period_ms / 1000;

    if (ticks < 1)
        ticks = 1;

    uint32_t arr = (uint32_t)(ticks - 1);

    if (arr > 0xFFFF)
        arr = 0xFFFF;

    __HAL_TIM_SET_AUTORELOAD(&htim5, arr);
}

void update_led_frequency(void) {
    uint32_t speed = g_motor_speed;
    if (speed > MAX_MOTOR_SPEED)
        speed = MAX_MOTOR_SPEED;

    uint32_t period_ms =
        LED_PERIOD_MAX_MS
      - speed * (LED_PERIOD_MAX_MS - LED_PERIOD_MIN_MS)
        / MAX_MOTOR_SPEED;

    led_set_blink_period_ms(period_ms);
}

key.h

c 复制代码
#ifndef KEY_H
#define KEY_H

#include <stdint.h>
#include <stdbool.h>

typedef enum _KEY_PUSH_BUTTON_ {
    BTN_KEY_0,
    BTN_KEY_1,
    BTN_KEY_2,
    BTN_KEY_WK_UP,
} KEY_PUSH_BUTTON;

typedef struct _KeyPushEvent_ {
    KEY_PUSH_BUTTON btn;
} KeyPushEvent;

// ==================== 队列配置(根据需求修改) ====================
#define QUEUE_MAX_LEN    16      // 队列最大长度,建议2的幂,可根据需求调整
#define KEY_DATA_TYPE    KeyPushButton// 队列元素类型,存储按键按下的时间戳(如ms级)
// =================================================================

// 循环队列结构体
typedef struct {
    KeyPushEvent buffer[QUEUE_MAX_LEN];
    uint8_t front;
    uint8_t rear;
    uint8_t count;
} KeyEventQueue;

extern KeyEventQueue key_queue;

// 初始化按键事件队列
void key_event_queue_init(void);
// 获取按键事件
bool key_event_queue_pop(KeyPushEvent *data);

// BUTTON KEY 0 按下事件处理
void button_key_0_pressed(void);
// BUTTON KEY 1 按下事件处理
void button_key_1_pressed(void);
// BUTTON KEY 2 按下事件处理
void button_key_2_pressed(void);
// BUTTON KEY WK_UP 按下事件处理
void button_key_wk_up_pressed(void);
#endif

key.c

c 复制代码
#include "key.h"
#include "gpio.h"
#include "main.h"
#include "trace.h"
#include "tim.h"
#include "delay.h"
#include "health.h"
#include "stm32l4xx_it.h"

// 全局队列实例(用于按键中断和主循环共享)
static KeyEventQueue  key_queue;

/**
 * @brief 初始化循环队列
 */
void key_event_queue_init(void) {
    key_queue.front = 0;
    key_queue.rear = 0;
    key_queue.count = 0;
}

/**
 * @brief 判断队列是否为空
 * @return true-空,false-非空
 */
bool key_event_queue_is_empty(void) {
    return (key_queue.count == 0);
}

/**
 * @brief 判断队列是否为满
 * @return true-满,false-未满
 */
bool key_event_queue_is_full(void) {
    return (key_queue.count == QUEUE_MAX_LEN);
}

/**
 * @brief 队列入队(中断中调用)
 * @param data 要入队的按键时间戳
 * @return true-入队成功,false-队列满失败
 */
bool key_event_queue_push(KeyPushEvent data) {
    // 进入临界区:关闭总中断,防止中断与主循环同时操作队列
    __disable_irq();

    bool ret = false;
    if (!key_event_queue_is_full()) {
        key_queue.buffer[key_queue.rear] = data;
        key_queue.rear = (key_queue.rear + 1) % QUEUE_MAX_LEN;
        key_queue.count++;
        ret = true;
    }

    // 退出临界区:开启总中断
    __enable_irq();
    return ret;
}

/**
 * @brief 队列出队(主循环中调用)
 * @param data 出队数据的存储地址
 * @return true-出队成功,false-队列空失败
 */
bool key_event_queue_pop(KeyPushEvent *data) {
    if (data == NULL) return false;

    // 进入临界区:关闭总中断(防止出队时中断写入)
    __disable_irq();

    bool ret = false;
    if (!key_event_queue_is_empty()) {
        *data = key_queue.buffer[key_queue.front];
        key_queue.front = (key_queue.front + 1) % QUEUE_MAX_LEN;
        key_queue.count--;
        ret = true;
    }

    // 退出临界区:开启总中断
    __enable_irq();
    return ret;
}

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    static uint32_t prev_tick = 0;
    uint32_t cur_tick = HAL_GetTick();

    if (cur_tick - prev_tick < 50) {
        return;
    }
    prev_tick = cur_tick;

    KeyPushEvent k_event = {.btn = BTN_KEY_0};

    switch (GPIO_Pin)
    {
    case KEY0_Pin:
        k_event.btn = BTN_KEY_0;
        break;
    case KEY1_Pin:
        k_event.btn = BTN_KEY_1;
        break;
    case KEY2_Pin:
        k_event.btn = BTN_KEY_2;
        break;
    case WK_UP_Pin:
        k_event.btn = BTN_KEY_WK_UP;
    default:
        break;
    }

    key_event_queue_push(k_event);
}

/**
 * @brief BUTTON KEY 0 按下后, 耗时操作
 */
void button_key_0_pressed() {
    uint16_t pre_speed = g_motor_speed;
    if (g_motor_speed <= MIN_MOTOR_SPEED) {
        g_motor_speed = MIN_MOTOR_SPEED;
    }
    g_motor_speed += 10;
    if (g_motor_speed >= MAX_MOTOR_SPEED) {
        g_motor_speed = MAX_MOTOR_SPEED;
    }
    TRACE_INFO("motor speed: %d -> %d", pre_speed, g_motor_speed);
    update_motor();
    update_led_frequency();
}

/**
 * @brief BUTTON KEY 1 按下后, 耗时操作
 */
void button_key_1_pressed() {
    uint16_t pre_motor_speed = g_motor_speed;
    if (g_motor_dir != 1) {
        g_motor_speed   = 0;
        g_motor_dir     = 0;
        update_motor();

        delay_ms(500);

    } else {
        TRACE_INFO("motor turn nothing.");
        return;
    }

    g_motor_dir = 1;
    g_motor_speed = pre_motor_speed;
    update_motor();
    TRACE_INFO("motor turn forward.");
}

/**
 * @brief BUTTON KEY 2 按下后, 耗时操作
 */
void button_key_2_pressed() {
    uint16_t pre_speed = g_motor_speed;
    g_motor_speed -= 10;
    if (g_motor_speed <= MIN_MOTOR_SPEED | g_motor_speed > MAX_MOTOR_SPEED) {
        g_motor_speed = MIN_MOTOR_SPEED;
    }
    TRACE_INFO("motor speed: %d -> %d", pre_speed, g_motor_speed);
    update_motor();
    update_led_frequency();
}

void button_key_wk_up_pressed() {
    uint16_t pre_motor_speed = g_motor_speed;
    if (g_motor_dir != 2) {
        g_motor_speed   = 0;
        g_motor_dir     = 0;
        update_motor();

        delay_ms(500);
    } else {
        TRACE_INFO("motor turn nothing.");
        return;
    }

    g_motor_dir = 2;
    g_motor_speed = pre_motor_speed;
    update_motor();
    TRACE_INFO("motor turn back.");
}

main.c

c 复制代码
int main(void)
{
  MX_TIM2_Init();
  MX_TIM5_Init();

  HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1); // 开启 PA0 控制
  HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2); // 开启 PA1 控制

  HAL_TIM_Base_Start_IT(&htim5); // 启动 tim 5 的时钟中断
  while (1)
  {

    if(key_event_queue_pop(&k_event)) {
      switch (k_event.btn)
      {
      case BTN_KEY_0:
        button_key_0_pressed();
        break;
      case BTN_KEY_1:
        button_key_1_pressed();
        break;
      case BTN_KEY_2:
        button_key_2_pressed();
        break;
      case BTN_KEY_WK_UP:
        button_key_wk_up_pressed();
        break;
      default:
        break;
      }
    }
  }
}

实验效果:

PWM输出控制电机转速同步LED闪烁频率

相关推荐
水云桐程序员1 天前
用C语言写LED灯嵌入式系统案例|STM32 LED控制与按键输入系统
c语言·stm32·单片机
电子工程师成长日记-C511 天前
51单片机电子打铃系统
单片机·嵌入式硬件·51单片机
iCxhust1 天前
Keil µVision 调试指南---UART#1 模拟/调试窗口 完全使用教程
stm32·单片机·嵌入式硬件
iCxhust1 天前
51单片机引脚 ALE EA PSEN的用途
单片机·嵌入式硬件·51单片机
碎像1 天前
51单片机创建项目
单片机·嵌入式硬件·51单片机
木白CPP1 天前
MCU 进程内存布局详解(.text, .rodata, .data, .bss, 堆, 栈)
单片机·嵌入式硬件
Lugas Luo1 天前
车载录像存储性能模拟测试工具设计
linux·嵌入式硬件·测试工具
v132665623681 天前
BK7258 wifi6音视频soc芯片应用分析
嵌入式硬件·物联网·音视频·iot·wifi6
風清掦1 天前
【江科大STM32学习笔记-10】I2C通信协议 - 10.2 硬件 I2C 读写MPU6050
笔记·stm32·单片机·嵌入式硬件·学习
ALINX技术博客1 天前
【黑金云课堂】FPGA技术教程Vitis开发:RTC中断讲解
单片机·嵌入式硬件·fpga开发