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

相关推荐
不做无法实现的梦~19 小时前
ros2实现路径规划---nav2部分
linux·stm32·嵌入式硬件·机器人·自动驾驶
熊猫_豆豆1 天前
同步整流 Buck 降压变换器
单片机·嵌入式硬件·matlab
chenchen000000001 天前
49元能否买到四核性能?HZ-RK3506G2_MiniEVM开发板评测:MCU+三核CPU带来的超高性价比
单片机·嵌入式硬件
孤芳剑影1 天前
反馈环路设计总结
嵌入式硬件·学习
dump linux1 天前
设备树子系统与驱动开发入门
linux·驱动开发·嵌入式硬件
专注VB编程开发20年1 天前
简易虚拟 PLC 服务器-流水线自动化,上位机程序维护升级,西门子PLC仿真
服务器·单片机·自动化·上位机·plc·流水线·工控
LeoZY_1 天前
CH347/339W开源项目:集SPI、I2C、JTAG、SWD、UART、GPIO多功能为一体(3)
stm32·单片机·嵌入式硬件·mcu·开源
chenchen000000001 天前
国产显示芯势力新篇章:内置DDR+四核A35!MY-SSD2351-MINI开发板深度评测
驱动开发·嵌入式硬件
BackCatK Chen1 天前
第13篇:TMC2240 StallGuard4失速检测|寄存器配置+状态读取(保姆级)
单片机·嵌入式硬件·tmc2240·stm32实战·stallguard4·失速检测·电机故障识别
Hello_Embed1 天前
libmodbus STM32 板载串口实验(双串口主从通信)
笔记·stm32·单片机·学习·modbus