本文将使用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闪烁频率