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闪烁频率

相关推荐
悠哉悠哉愿意8 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
Lester_11018 天前
STM32霍尔传感器输入口设置为复用功能输入口时,还能用GPIO函数直接读取IO的状态吗
stm32·单片机·嵌入式硬件·电机控制
LCG元8 天前
低功耗显示方案:STM32L0驱动OLED,动态波形绘制与优化
stm32·嵌入式硬件·信息可视化
三佛科技-187366133978 天前
120W小体积碳化硅电源方案(LP8841SC极简方案12V10A/24V5A输出)
单片机·嵌入式硬件
z20348315208 天前
STM32F103系列单片机定时器介绍(二)
stm32·单片机·嵌入式硬件
古译汉书8 天前
【IoT死磕系列】Day 7:只传8字节怎么控机械臂?学习工业控制 CANopen 的“对象字典”(附企业级源码)
数据结构·stm32·物联网·http
Alaso_shuang8 天前
STM32 核心输入、输出模式
stm32·单片机·嵌入式硬件
脚后跟8 天前
AI助力嵌入式物联网项目全栈开发
嵌入式硬件·物联网·ai编程
2501_918126918 天前
stm32死锁是怎么实现的
stm32·单片机·嵌入式硬件·学习·个人开发
z20348315208 天前
STM32F103系列单片机定时器介绍(一)
stm32·单片机