一、直流有刷电机
直流有刷电机通常具有转速高、扭矩小的特点。
有刷直流电机的电枢绕有多组线圈,通电后产生较强磁场,该磁场与外部永磁体或励磁磁场相互作用,从而驱动电机旋转。**电机磁极方向的切换是通过固定位置的电刷与转子上的换向器接触实现的。**换向器由多个铜片组成,随转子转动;而电刷通常采用石墨材料制成,相对于铜等金属,石墨能在较大电流下避免因短路或启停过程中的电弧而熔焊到换向器上。此外,电刷通常由弹簧施加持续压力,确保与换向器保持稳定接触,从而可靠地向线圈供电。
当将电机的两根电源线与电池正确连接时,电机会按设定方向旋转;若需改变转向,只需将两根电源线反接即可。
二、控制电路原理
1. H桥电路


当 Q1 和 Q4 **导通时,**电流将经过 Q 1 从左往右流过电机,在经过 Q 4 流到电源负极,这时图中电机可以顺时针转动。
当 Q3 和 Q2 **导通时,**电流将经过 Q 3 从右往左流过电机,在经过 Q 2 流到电源负极,这时图中电机可以逆时针转动。
当同侧开关管Q1和Q2同时导通时,电流将从电源正极经Q1、Q2直接流回电源负极。该回路中除晶体管外无其他负载(未流经电机),电流可能达到最大值, 极易导致晶体管烧毁。同理,若Q3和Q4同时导通,也会发生相同情况。
2.改进驱动电路

与原有电路相比,改进方案中增加了两个非门和四个与门,并将Q1和Q3更换为NPN型晶体管。通过这样的逻辑组合,可使用同一信号控制同侧两个晶体管,并确保它们在任何时刻都不会同时导通,即同一侧始终仅有一个晶体管处于导通状态。
当ENABLE脚置高电平、IN1脚置高电平时,经第一个非门反相后,AND1的第2脚为低电平,因此AND1输出(第3脚)为低电平,Q1截止。而AND2的两个输入脚(1、2脚)均为高电平,故AND2输出为高电平,Q2导通。
当IN2脚置低电平时,同理可得Q3导通、Q4截止。
若IN1置低电平、IN2置高电平,则Q1与Q4导通,Q2与Q3截止。
当IN1与IN2同时为高电平或同时为低电平时,仅会导通上方(Q1、Q2)或下方(Q3、Q4)的两个晶体管,从而彻底避免同侧晶体管直通现象。
3. L298N驱动芯片



三、控制实现
1. 速度控制原理
**脉冲宽度调制(Pulse Width Modulation,PWM)**是一种通过调节脉冲信号的宽度来控制输出电压大小或频率的技术。它按照固定频率周期性地切换通断状态,通过改变每个周期内通电时间与断电时间的比例,来调节电路输出的平均功率。
在电机控制中,PWM常用于调节转速:在一个控制周期内,电机的通电时间越长,其转速一般越高。这一比例关系可用占空比来表示,即:
占空比 = 通电时间 / (通电时间 + 断电时间) × 100%
也就是高电平时间占整个信号周期的百分比。通过调整占空比,即可实现对电机转速的精确控制。
2. 硬件设计


2.1 驱动板H桥

**EG2104S 芯片主要具备以下功能:**逻辑信号输入处理、死区时间控制、电平转换、悬浮自举电源结构以及上下桥臂图腾柱输出。
其逻辑信号输入端的高电平阈值大于 2.5V,低电平阈值低于 1.0V,且对输入电流要求较低,因此可直接与 MCU 的逻辑输出引脚相连,无需额外驱动电路。
芯片设有SD关断(shutdown)引脚,该引脚低电平有效,可强制将 LO 和 HO 输出拉低。此引脚既可用于软件控制电机的启停,也可配合外部电路实现硬件过流保护。

**EG2104S 内部集成死区时间控制电路,典型死区时间(DT)为 640 ns。**此外,芯片采用自举悬浮驱动电源架构,仅需单路电源(VCC)即可同时驱动高端和低端两路 N 沟道 MOSFET,极大简化了驱动电源的设计。

下图所示为 EG2104S 的自举电路结构。通过外接一个自举二极管和一个自举电容,即可实现自举升压功能。其工作原理如下:假设在下管导通、上管关断期间,自举电容 C 已充电至足够电压(Vc ≈ VCC);随后当 HO 输出高电平、上管导通而下管关断时,电容 C 上的电压将作为内部驱动电路(VB 与 VS 之间)的电源,从而完成对高端 N 沟道 MOSFET 的驱动。

**自举电路:**又称升压电路,是一种通过二极管、电容与电感等元件组合实现电压升高的电路结构。其核心原理基于电容储能与电压叠加机制,电容充电时储存能量,放电时将电压与电源叠加,部分电路可输出数倍于输入电压值。参考下面这篇博客

**ENABLEA 和 ENABLEB 为使能输入引脚:**ENABLEA 用于控制 A 桥,可连接至单片机引脚进行控制,也可直接用跳线帽连接至 5V 电平;ENABLEB 用于控制 B 桥,其控制方式与 ENABLEA 相同。
INPUT1 和 INPUT2 为 A 桥的控制信号输入,INPUT3 和 INPUT4 为 B 桥的控制信号输入。这些引脚可接入 PWM 信号,用于控制电机的转速与转向。
OUTPUT1 和 OUTPUT2 是 A 桥的输出接口,OUTPUT3 和 OUTPUT4 是 B 桥的输出接口,分别用于连接电机。
**电路中的 8 个二极管主要用于防止电机反电动势损坏 L298N。**以其中一组为例:当输出端 E 点因反电动势产生高于电源电压(Vcc)+0.7V 的正向电压时,上方的二极管导通,将输出电位钳位在 Vcc+0.7V 以内;当 E 点电压低于 -0.7V 时,下方二极管导通,将输出电位限制在 -0.7V 以上。这样,输出线上的电压就被限制在 -0.7V 至 Vcc+0.7V 之间。其余二极管的作用与此相同,共同实现对输出端的电压钳位保护。
2.2 隔离电路

与门的作用是可以使单片机和过流保护电路共同控制 SD 脚。

3. 软件设计
3.1 实现功能
按下 KEY1 后,使能 PWM 输出;
按下 KEY2 后,失能 PWM 输出;
按下 KEY3 可以增加 CH1 通道的占空比;
按下 KEY4 可以减小占空比;
按下 KEY5 可以交换 PWM 输出。
3.2 所需资源
GPIO: 按键控制、PWM输出通道
**TIM:**PWM输出
3.3 设计实现
3.3.1 按键控制GPIO初始化
相关定义
#ifndef __KEY_H
#define __KEY_H
#include "stm32f4xx.h"
#include "main.h"
/* 按键引脚宏定义 */
/*******************************************************/
/* 按键1定义:PA0引脚 */
#define KEY1_PIN GPIO_PIN_0
#define KEY1_GPIO_PORT GPIOA
#define KEY1_GPIO_CLK_ENABLE() __GPIOA_CLK_ENABLE()
/* 按键2定义:PG2引脚 */
#define KEY2_PIN GPIO_PIN_2
#define KEY2_GPIO_PORT GPIOG
#define KEY2_GPIO_CLK_ENABLE() __GPIOG_CLK_ENABLE()
/* 按键3定义:PC13引脚 */
#define KEY3_PIN GPIO_PIN_13
#define KEY3_GPIO_PORT GPIOC
#define KEY3_GPIO_CLK_ENABLE() __GPIOC_CLK_ENABLE()
/* 按键4定义:PG3引脚 */
#define KEY4_PIN GPIO_PIN_3
#define KEY4_GPIO_PORT GPIOG
#define KEY4_GPIO_CLK_ENABLE() __GPIOG_CLK_ENABLE()
/* 按键5定义:PG4引脚 */
#define KEY5_PIN GPIO_PIN_4
#define KEY5_GPIO_PORT GPIOG
#define KEY5_GPIO_CLK_ENABLE() __GPIOG_CLK_ENABLE()
/*******************************************************/
/* 按键状态宏定义 */
/**
* @brief 按键按下状态定义
* @note 根据实际硬件连接调整:
* 按键按下为高电平时,设置 KEY_ON=1,KEY_OFF=0
* 按键按下为低电平时,设置 KEY_ON=0,KEY_OFF=1
*/
#define KEY_ON 1 /* 按键按下状态 */
#define KEY_OFF 0 /* 按键释放状态 */
/* 函数声明 */
void Key_GPIO_Config(void); /* 按键GPIO初始化函数 */
uint8_t Key_Scan(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); /* 按键扫描函数 */
#endif /* __KEY_H */
按键配置为输入模式,不上拉也不下拉
/**
* @brief 按键GPIO初始化配置
* @note 配置所有按键引脚为输入模式,使用内部下拉电阻
* 按键默认为低电平,按下时变为高电平
*/
void Key_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure = {0};
/* 使能所有按键GPIO端口的时钟 */
KEY1_GPIO_CLK_ENABLE();
KEY2_GPIO_CLK_ENABLE();
KEY3_GPIO_CLK_ENABLE();
KEY4_GPIO_CLK_ENABLE();
KEY5_GPIO_CLK_ENABLE();
/* 配置按键1引脚 */
GPIO_InitStructure.Pin = KEY1_PIN; /* 选择引脚 */
GPIO_InitStructure.Mode = GPIO_MODE_INPUT; /* 设置为输入模式 */
GPIO_InitStructure.Pull = GPIO_PULLDOWN; /* 启用内部下拉电阻 */
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW; /* 低速模式(可选) */
HAL_GPIO_Init(KEY1_GPIO_PORT, &GPIO_InitStructure);
/* 配置按键2引脚 */
GPIO_InitStructure.Pin = KEY2_PIN;
HAL_GPIO_Init(KEY2_GPIO_PORT, &GPIO_InitStructure);
/* 配置按键3引脚 */
GPIO_InitStructure.Pin = KEY3_PIN;
HAL_GPIO_Init(KEY3_GPIO_PORT, &GPIO_InitStructure);
/* 配置按键4引脚 */
GPIO_InitStructure.Pin = KEY4_PIN;
HAL_GPIO_Init(KEY4_GPIO_PORT, &GPIO_InitStructure);
/* 配置按键5引脚 */
GPIO_InitStructure.Pin = KEY5_PIN;
HAL_GPIO_Init(KEY5_GPIO_PORT, &GPIO_InitStructure);
}
按键软件消抖
/**
* @brief 按键扫描函数
* @param GPIOx: GPIO端口,范围GPIOA-GPIOG
* @param GPIO_Pin: 具体的引脚编号,范围GPIO_PIN_0~GPIO_PIN_15
* @retval 按键状态
* 返回值: KEY_ON - 按键被按下
* KEY_OFF - 按键未被按下
* @note 此函数采用简单的延时消抖和松手检测:
* 1. 检测到按键按下后等待按键释放
* 2. 防止按键抖动引起的误触发
* 3. 只能检测单个按键的按下事件
*/
uint8_t Key_Scan(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
/* 检测指定引脚是否为高电平(按键按下) */
if (HAL_GPIO_ReadPin(GPIOx, GPIO_Pin) == KEY_ON)
{
/* 等待按键释放,避免连续触发 */
while (HAL_GPIO_ReadPin(GPIOx, GPIO_Pin) == KEY_ON);
/* 返回按键按下状态 */
return KEY_ON;
}
else
{
/* 返回按键释放状态 */
return KEY_OFF;
}
}
3.3.2 TIM PWM配置
相关定义
#ifndef __BSP_MOTOR_TIM_H
#define __BSP_MOTOR_TIM_H
#include "stm32f4xx.h"
/* 定时器宏定义 */
#define PWM_TIM TIM1 /* 使用定时器1产生PWM */
#define PWM_TIM_GPIO_AF GPIO_AF1_TIM1 /* 定时器1的复用功能 */
#define PWM_TIM_CLK_ENABLE() __TIM1_CLK_ENABLE() /* 使能定时器1时钟 */
/* PWM通道定义 */
#define PWM_CHANNEL_1 TIM_CHANNEL_1 /* 通道1 */
#define PWM_CHANNEL_2 TIM_CHANNEL_2 /* 通道2 */
/* PWM周期定义
PWM频率 = 定时器时钟频率 / ((预分频值 + 1) * (自动重装载值 + 1))
定时器时钟频率 = 168MHz(假设HCLK=168MHz)
预分频值 = PWM_PRESCALER_COUNT - 1
自动重装载值 = PWM_PERIOD_COUNT - 1
*/
#define PWM_PERIOD_COUNT (5600) /* 自动重装载值,决定PWM频率 */
/* 预分频值定义
定时器时钟分频系数 = PWM_PRESCALER_COUNT
实际预分频寄存器值 = PWM_PRESCALER_COUNT - 1
*/
#define PWM_PRESCALER_COUNT (2) /* 预分频值,用于分频定时器时钟 */
/* 最大比较值定义
防止占空比设置过大,超出周期值
*/
#define PWM_MAX_PERIOD_COUNT (PWM_PERIOD_COUNT - 100) /* 最大比较值,避免占空比过大 */
/* PWM输出引脚定义 */
#define PWM_TIM_CH1_GPIO_PORT GPIOA /* 通道1输出引脚端口 */
#define PWM_TIM_CH1_PIN GPIO_PIN_8 /* 通道1输出引脚 */
#define PWM_TIM_CH2_GPIO_PORT GPIOA /* 通道2输出引脚端口 */
#define PWM_TIM_CH2_PIN GPIO_PIN_9 /* 通道2输出引脚 */
#define PWM_TIM_CH3_GPIO_PORT GPIOA /* 通道3输出引脚端口 */
#define PWM_TIM_CH3_PIN GPIO_PIN_10 /* 通道3输出引脚 */
/* 全局变量声明 */
extern TIM_HandleTypeDef TIM_TimeBaseStructure; /* 定时器时基结构体 */
/* 函数声明 */
extern void TIMx_Configuration(void); /* 定时器配置初始化函数 */
extern void TIM1_SetPWM_pulse(uint32_t channel, int compare); /* 设置指定通道的PWM占空比 */
#endif /* __BSP_MOTOR_TIM_H */
配置PWM输出相关GPIO引脚,共使用TIM1两个通道:TIM_CHANNEL_1、TIM_CHANNEL_2
/**
* @brief 配置定时器PWM输出所需的GPIO引脚
* @note 将相关引脚配置为复用推挽输出模式,并连接到定时器通道
*/
static void TIMx_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* 使能GPIOA时钟(PWM输出引脚所在端口) */
__HAL_RCC_GPIOA_CLK_ENABLE();
/* 配置GPIO引脚为定时器复用功能 */
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; /* 复用推挽输出 */
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速模式 */
GPIO_InitStruct.Alternate = PWM_TIM_GPIO_AF; /* 复用为定时器功能 */
GPIO_InitStruct.Pull = GPIO_NOPULL; /* 不上拉也不下拉 */
/* 初始化通道1对应引脚(PA8) */
GPIO_InitStruct.Pin = PWM_TIM_CH1_PIN;
HAL_GPIO_Init(PWM_TIM_CH1_GPIO_PORT, &GPIO_InitStruct);
/* 初始化通道2对应引脚(PA9) */
GPIO_InitStruct.Pin = PWM_TIM_CH2_PIN;
HAL_GPIO_Init(PWM_TIM_CH2_GPIO_PORT, &GPIO_InitStruct);
}
自动重装载值: 决定PWM波形的周期,设置定时器从0开始计数到该值后重新开始,控制PWM的频率。
预分频值: 对定时器时钟源进行分频,降低计数频率,从而调整PWM频率的分辨率。
计数模式: 设定定时器计数的方向(向上/向下/中心对齐),影响PWM波形的生成方式。
时钟分频: 调整定时器内部时钟与外部输入信号的同步关系,通常用于滤波和采样。
输出极性: 定义PWM有效电平是高电平还是低电平,决定占空比对应的高电平部分。
互补输出极性: 定义互补输出通道的有效电平,用于电机控制等需要互补PWM的场景。
空闲状态下主输出电平: 设置定时器不工作时主输出通道的电平状态,确保系统安全。
**空闲状态下互补输出电平:**设置定时器不工作时互补输出通道的电平状态,防止电机等设备误动作。
/**
* @brief 配置定时器PWM输出模式
* @note 配置定时器的时基参数和PWM输出通道,并启动PWM输出
*/
static void TIM_PWMOUTPUT_Config(void)
{
TIM_OC_InitTypeDef TIM_OCInitStructure = {0};
/* 使能定时器时钟 */
PWM_TIM_CLK_ENABLE();
/* 配置定时器时基参数 */
TIM_TimeBaseStructure.Instance = PWM_TIM; /* 选择定时器1 */
TIM_TimeBaseStructure.Init.Period = PWM_PERIOD_COUNT - 1; /* 自动重装载值,决定PWM频率 */
TIM_TimeBaseStructure.Init.Prescaler = PWM_PRESCALER_COUNT - 1; /* 预分频值,分频定时器时钟 */
TIM_TimeBaseStructure.Init.CounterMode = TIM_COUNTERMODE_UP; /* 向上计数模式 */
TIM_TimeBaseStructure.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; /* 时钟分频,不分频 */
TIM_TimeBaseStructure.Init.RepetitionCounter = 0; /* 重复计数器值(高级定时器特有) */
TIM_TimeBaseStructure.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; /* 使能自动重装载预装载 */
/* 初始化定时器时基 */
HAL_TIM_PWM_Init(&TIM_TimeBaseStructure);
/* 配置PWM输出通道参数 */
TIM_OCInitStructure.OCMode = TIM_OCMODE_PWM1; /* PWM模式1 */
TIM_OCInitStructure.Pulse = 0; /* 初始占空比为0% */
TIM_OCInitStructure.OCPolarity = TIM_OCPOLARITY_HIGH; /* 输出极性:高电平有效 */
TIM_OCInitStructure.OCNPolarity = TIM_OCPOLARITY_HIGH; /* 互补输出极性:高电平有效 */
TIM_OCInitStructure.OCIdleState = TIM_OCIDLESTATE_SET; /* 空闲状态下主输出为高电平 */
TIM_OCInitStructure.OCNIdleState = TIM_OCNIDLESTATE_RESET; /* 空闲状态下互补输出为低电平 */
TIM_OCInitStructure.OCFastMode = TIM_OCFAST_DISABLE; /* 快速模式禁用 */
/* 配置通道1并启动PWM输出 */
HAL_TIM_PWM_ConfigChannel(&TIM_TimeBaseStructure, &TIM_OCInitStructure, PWM_CHANNEL_1);
HAL_TIM_PWM_Start(&TIM_TimeBaseStructure, PWM_CHANNEL_1);
/* 配置通道2并启动PWM输出(注意:这里占空比仍为0,实际可根据需要修改) */
HAL_TIM_PWM_ConfigChannel(&TIM_TimeBaseStructure, &TIM_OCInitStructure, PWM_CHANNEL_2);
HAL_TIM_PWM_Start(&TIM_TimeBaseStructure, PWM_CHANNEL_2);
/* 注意:通道3和通道4的配置未启用,但硬件引脚已初始化,可根据需要添加 */
}
使用__HAL_TIM_SET_COMPARE(HANDLE, CHANNEL, COMPARE)实现各通道占空比的配置
/**
* @brief 设置指定通道的PWM占空比
* @param channel: 通道编号,可选 TIM_CHANNEL_1, TIM_CHANNEL_2, TIM_CHANNEL_3, TIM_CHANNEL_4
* @param compare: 比较值,即PWM脉冲宽度,范围为0到PWM_PERIOD_COUNT-1
* @note 占空比 = (compare + 1) / (PWM_PERIOD_COUNT) * 100%
*/
void TIM1_SetPWM_pulse(uint32_t channel, int compare)
{
switch (channel)
{
case TIM_CHANNEL_1:
__HAL_TIM_SET_COMPARE(&TIM_TimeBaseStructure, TIM_CHANNEL_1, compare);
break;
case TIM_CHANNEL_2:
__HAL_TIM_SET_COMPARE(&TIM_TimeBaseStructure, TIM_CHANNEL_2, compare);
break;
case TIM_CHANNEL_3:
__HAL_TIM_SET_COMPARE(&TIM_TimeBaseStructure, TIM_CHANNEL_3, compare);
break;
case TIM_CHANNEL_4:
__HAL_TIM_SET_COMPARE(&TIM_TimeBaseStructure, TIM_CHANNEL_4, compare);
break;
default:
/* 可选:错误处理 */
break;
}
}
PWM输出初始化
/**
* @brief 定时器PWM输出初始化
* @note 依次初始化GPIO和定时器PWM输出
*/
void TIMx_Configuration(void)
{
TIMx_GPIO_Config(); /* 配置PWM输出引脚 */
TIM_PWMOUTPUT_Config(); /* 配置定时器PWM输出模式 */
}
3.3.3 电机控制函数
相关定义
#ifndef __BSP_MOTOR_CONTROL_H
#define __BSP_MOTOR_CONTROL_H
#include "stm32f4xx.h"
#include "./tim/bsp_motor_tim.h"
/* 硬件引脚定义 */
/*******************************************************/
/* 电机使能/关断控制引脚定义(连接到MOS管驱动板的SD脚或L298N的EN脚) */
#define SHUTDOWN_PIN GPIO_PIN_12 /* 使能控制引脚编号 */
#define SHUTDOWN_GPIO_PORT GPIOG /* 使能控制引脚端口 */
#define SHUTDOWN_GPIO_CLK_ENABLE() __GPIOG_CLK_ENABLE() /* 使能端口时钟宏 */
/*******************************************************/
/* 电机使能/禁用控制宏 */
#define MOTOR_ENABLE_SD() HAL_GPIO_WritePin(SHUTDOWN_GPIO_PORT, SHUTDOWN_PIN, GPIO_PIN_SET) /* 使能电机(高电平有效) */
#define MOTOR_DISABLE_SD() HAL_GPIO_WritePin(SHUTDOWN_GPIO_PORT, SHUTDOWN_PIN, GPIO_PIN_RESET) /* 禁用电机(低电平有效) */
/* 电机方向控制枚举 */
typedef enum
{
MOTOR_FWD = 0, /* 正转方向 */
MOTOR_REV, /* 反转方向 */
} motor_dir_t;
/* 设置电机速度的宏(通过设置PWM占空比) */
#define SET_FWD_COMPAER(ChannelPulse) TIM1_SetPWM_pulse(PWM_CHANNEL_1, ChannelPulse) /* 设置正转通道占空比 */
#define SET_REV_COMPAER(ChannelPulse) TIM1_SetPWM_pulse(PWM_CHANNEL_2, ChannelPulse) /* 设置反转通道占空比 */
/* PWM通道使能控制宏 */
#define MOTOR_FWD_ENABLE() HAL_TIM_PWM_Start(&TIM_TimeBaseStructure, PWM_CHANNEL_1) /* 使能正转PWM输出 */
#define MOTOR_REV_ENABLE() HAL_TIM_PWM_Start(&TIM_TimeBaseStructure, PWM_CHANNEL_2) /* 使能反转PWM输出 */
/* PWM通道禁用控制宏 */
#define MOTOR_FWD_DISABLE() HAL_TIM_PWM_Stop(&TIM_TimeBaseStructure, PWM_CHANNEL_1) /* 禁用正转PWM输出 */
#define MOTOR_REV_DISABLE() HAL_TIM_PWM_Stop(&TIM_TimeBaseStructure, PWM_CHANNEL_2) /* 禁用反转PWM输出 */
/* 函数声明 */
void motor_init(void); /* 电机初始化函数 */
void set_motor_speed(uint16_t v); /* 设置电机速度函数 */
void set_motor_direction(motor_dir_t dir); /* 设置电机方向函数 */
void set_motor_enable(void); /* 使能电机函数 */
void set_motor_disable(void); /* 禁用电机函数 */
#endif /* __BSP_MOTOR_CONTROL_H */
初始化电机使能/关断控制引脚
/**
* @brief 电机使能/关断控制引脚的GPIO配置
* @note 配置PG12引脚为推挽输出模式,用于控制电机驱动芯片的使能端
*/
static void sd_gpio_config(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* 使能GPIOG端口时钟 */
SHUTDOWN_GPIO_CLK_ENABLE();
/* 配置引脚为推挽输出模式 */
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; /* 推挽输出模式 */
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速模式 */
GPIO_InitStruct.Pin = SHUTDOWN_PIN; /* 选择控制引脚 */
GPIO_InitStruct.Pull = GPIO_NOPULL; /* 不上拉也不下拉 */
/* 初始化GPIO */
HAL_GPIO_Init(SHUTDOWN_GPIO_PORT, &GPIO_InitStruct);
}
电机系统初始化,主要包括初始化TIM和使能控制引脚,并禁用电机
/**
* @brief 电机系统初始化
* @note 初始化定时器PWM输出和使能控制引脚,完成电机控制系统的准备工作
*/
void motor_init(void)
{
TIMx_Configuration(); /* 初始化定时器PWM输出功能 */
sd_gpio_config(); /* 配置电机使能控制引脚 */
/* 可选:初始化后默认禁用电机,确保安全 */
set_motor_disable();
}
使用 TIM1_SetPWM_pulse函数配置占空比的大小,进而设置电机的运行速度,包括正反转
/**
* @brief 设置电机运行速度
* @param v: 速度值,对应PWM占空比,范围为0到PWM_MAX_PERIOD_COUNT
* @note 占空比越大,电机转速越快;占空比为0时电机停止
* 根据当前方向设置对应通道的PWM占空比
*/
void set_motor_speed(uint16_t v)
{
dutyfactor = v; /* 更新当前占空比值 */
/* 根据当前方向设置对应的PWM通道 */
if (direction == MOTOR_FWD)
{
SET_FWD_COMPAER(dutyfactor); /* 设置正转通道占空比 */
}
else
{
SET_REV_COMPAER(dutyfactor); /* 设置反转通道占空比 */
}
}
设置电机的运行方向,即打开一个通道,关闭另外一个通道,自动继承占空比
/**
* @brief 设置电机运行方向
* @param dir: 方向枚举值,MOTOR_FWD为正转,MOTOR_REV为反转
* @note 切换方向时会自动将当前方向的通道设置为存储的占空比,
* 另一方向的通道设置为0,避免两个通道同时有输出
*/
void set_motor_direction(motor_dir_t dir)
{
direction = dir; /* 更新方向变量 */
/* 根据新方向重新配置两个PWM通道的占空比 */
if (direction == MOTOR_FWD)
{
/* 正转模式:正转通道输出当前占空比,反转通道关闭 */
SET_FWD_COMPAER(dutyfactor);
SET_REV_COMPAER(0);
}
else
{
/* 反转模式:反转通道输出当前占空比,正转通道关闭 */
SET_FWD_COMPAER(0);
SET_REV_COMPAER(dutyfactor);
}
}
使能电机运行,使用HAL_StatusTypeDef HAL_TIM_PWM_Start(TIM_HandleTypeDef *htim, uint32_t Channel)函数使能PWM输出
/**
* @brief 使能电机运行
* @note 同时使能使能控制引脚和两个PWM通道的输出
*/
void set_motor_enable(void)
{
MOTOR_ENABLE_SD(); /* 拉高使能引脚,使能驱动芯片 */
MOTOR_FWD_ENABLE(); /* 使能正转PWM输出通道 */
MOTOR_REV_ENABLE(); /* 使能反转PWM输出通道 */
}
禁用电机运行,使用HAL_TIM_PWM_Stop(TIM_HandleTypeDef *htim, uint32_t Channel)函数禁用PWM输出
/**
* @brief 禁用电机运行
* @note 同时禁用使能控制引脚和两个PWM通道的输出,使电机完全停止
*/
void set_motor_disable(void)
{
MOTOR_DISABLE_SD(); /* 拉低使能引脚,禁用驱动芯片 */
MOTOR_FWD_DISABLE(); /* 禁用正转PWM输出通道 */
MOTOR_REV_DISABLE(); /* 禁用反转PWM输出通道 */
}
3.3.4 主函数
通过定时器PWM信号控制电机速度,主要功能包括:初始化系统时钟为168MHz,配置按键输入和电机控制硬件,通过5个按键实现电机的启停控制、正反转切换以及速度调节(每次加减10%最大速度),同时确保电机初始状态为禁用以保证安全性,并在主循环中持续检测按键事件以实时控制电机的运行状态、方向和转速。
int pulse_num = 0; /* 全局脉冲计数变量,可用于记录电机脉冲数或运行状态 */
/**
* @brief 简单延时函数
* @param nCount: 延时计数值,值越大延时时间越长
* @note 基于空循环实现的简单延时,精确度不高但适用于非精确延时场景
*/
void Delay(__IO uint32_t nCount)
{
/* 通过递减计数实现延时,循环结束即延时完成 */
for (; nCount != 0; nCount--);
}
/**
* @brief 主函数
* @note 程序入口,完成初始化后进入主循环处理按键事件并控制电机
* @retval 程序执行状态码(通常为0表示正常结束)
*/
int main(void)
{
__IO uint16_t ChannelPulse = PWM_MAX_PERIOD_COUNT / 2; /* 初始PWM占空比为最大值的50% */
uint8_t i = 0; /* 方向切换计数器 */
/* 初始化系统时钟为168MHz */
SystemClock_Config();
/* 初始化按键GPIO引脚 */
Key_GPIO_Config();
/* 初始化电机控制系统 */
motor_init();
/* 启动前确保电机处于禁用状态,提高安全性 */
set_motor_disable();
/* 设置初始电机速度 */
set_motor_speed(ChannelPulse);
/* 主循环,持续检测按键事件并执行相应操作 */
while (1)
{
/* 检测KEY1按键:使能电机 */
if (Key_Scan(KEY1_GPIO_PORT, KEY1_PIN) == KEY_ON)
{
/* 使能电机驱动和PWM输出 */
set_motor_enable();
}
/* 检测KEY2按键:禁用电机 */
if (Key_Scan(KEY2_GPIO_PORT, KEY2_PIN) == KEY_ON)
{
/* 禁用电机驱动和PWM输出,使电机停止 */
set_motor_disable();
}
/* 检测KEY3按键:增大电机速度 */
if (Key_Scan(KEY3_GPIO_PORT, KEY3_PIN) == KEY_ON)
{
/* 将当前占空比增加最大值的10%(即速度增加10%) */
ChannelPulse += PWM_MAX_PERIOD_COUNT / 10;
/* 防止占空比超过最大值,确保不超过硬件允许范围 */
if (ChannelPulse > PWM_MAX_PERIOD_COUNT)
ChannelPulse = PWM_MAX_PERIOD_COUNT;
/* 应用新的速度设置 */
set_motor_speed(ChannelPulse);
}
/* 检测KEY4按键:减小电机速度 */
if (Key_Scan(KEY4_GPIO_PORT, KEY4_PIN) == KEY_ON)
{
/* 如果当前占空比小于最大值的10%,则直接设为0(停止) */
if (ChannelPulse < PWM_MAX_PERIOD_COUNT / 10)
ChannelPulse = 0;
else
/* 否则将当前占空比减少最大值的10% */
ChannelPulse -= PWM_MAX_PERIOD_COUNT / 10;
/* 应用新的速度设置 */
set_motor_speed(ChannelPulse);
}
/* 检测KEY5按键:切换电机方向 */
if (Key_Scan(KEY5_GPIO_PORT, KEY5_PIN) == KEY_ON)
{
/* 每次按下切换方向:i递增后对2取余,奇数时正转,偶数时反转 */
set_motor_direction((++i % 2) ? MOTOR_FWD : MOTOR_REV);
}
/* 可选:添加短延时防止按键过于灵敏,平衡CPU使用率 */
Delay(1000);
}
}