基于 STM32 HAL 库 + STM32CubeMX 开发的编码电机驱动,硬件表驱动、工程化、高解耦、开箱即用,无需大幅修改业务代码即可适配各类带编码器的直流减速电机与驱动板。


📋 目录

  1. 项目概述
  2. STM32CubeMX 配置指南
  3. 双方向反转机制与调试
  4. 核心数据结构体
  5. 三层解耦架构设计
  6. 核心 API 接口
  7. 快速使用流程 & 代码示例
  8. 常见问题排查
  9. 原代码附上

📌 项目概述

支持电机类型

适配所有带 AB 相正交编码器的直流减速电机,搭配主流 H 桥驱动板即可使用:

  • 电机:310 / 370 / 520 系列、JGB37-555、JGA25-370、N20 等编码减速电机
  • 驱动板:TB6612、L298N、DRV8833 等通用 H 桥模块

核心设计亮点

  1. 硬件表驱动:仅修改配置数组即可更换引脚,业务代码零改动
  2. 完全解耦:电机数组下标可随意调换,ID 自动匹配硬件
  3. 双反转校准:电机转向、编码器读数方向分开校准,仅改配置无需动代码
  4. 三运行模式:正常 PWM 输出、自由滑行、短接制动,一键切换
  5. 工程化接口:统一结构体 + 标准 API,跨项目通用
  6. NULL 安全:批量控制接口支持传入 NULL,跳过指定电机不报错
  7. CubeMX 深度兼容:自动读取 HAL 生成的定时器 / GPIO 句柄
  8. 一行初始化motor_init() 完成硬件绑定、定时器启动、状态清零
  9. 强大电机结构体:一站式管理电机数据,自动换算输出速度距离等物理量,自带滤波
  10. 支持PID扩展:引入其他任意PID代码可完成PID闭环
  11. 电机运行状态检测:online标志位,初始化后置1,堵转掉线为0,堵转保护

适用场景

  • 麦克纳姆轮全向底盘、差速两轮底盘、两轮自平衡小车
  • 机器人关节驱动、多电机闭环调速 / 调参项目
  • 所有「直流减速电机 + AB 相编码器」的嵌入式场景

性能指标

项目 参数说明
闭环控制周期 1ms(1000Hz PID 刷新)
PWM 分辨率 1000 级(默认 ARR=999,可自定义)
PWM 频率 10~20kHz(CubeMX 配置分频)
编码器精度 由电机线数 × 倍频 × 减速比决定,默认 4 倍频
反馈单位 rpm、rad/s、rad、m(自动换算输出这些物理量数据)
堵转检测 500ms 超时判定(阈值可配置)
RAM 占用 4 路电机合计约 304 字节
Flash 占用 配置表 + 代码合计约 2KB

⚙️ STM32CubeMX 配置指南

2.1 PWM 定时器配置(示例:TIM1,4 路 PWM 驱动 4 路电机)

  1. 进入 Pinout & Configuration → Timers → TIM1
  2. 通道配置:
    • Channel1:PWM Generation CH1 → 引脚 PE9
    • Channel2:PWM Generation CH2 → 引脚 PE11
    • Channel3:PWM Generation CH3 → 引脚 PE13
    • Channel4:PWM Generation CH4 → 引脚 PE14
  3. 参数配置(Parameter Settings):
    • Prescaler (PSC):根据主控主频计算
    • Counter Mode:Up(向上计数)
    • Counter Period(ARR):999(对应 PWM 最大量程 1000)
    • Auto-Reload Preload:Enable(开启自动重装载预装载)
  4. 单通道 PWM 配置:
    • PWM Mode:PWM Mode 1
    • Pulse:0(初始占空比为 0)
    • Output Compare Preload:Enable

提示:若修改 ARR,代码内 PWM_MAX_RANGE 需同步修改为 ARR+1

2.2 编码器定时器配置(每路电机独立定时器)

硬件引脚对应表:

根据自己实际接线调整

电机 定时器 A 相引脚 B 相引脚
左前 (LF) TIM8 PC6 PC7
右前 (RF) TIM3 PA6 PA7
左后 (LB) TIM4 PD12 PD13
右后 (RB) TIM5 PA0 PA1

单定时器统一配置(以 TIM8 为例)

  1. 通道模式:Combined Channels → Encoder Mode
  2. 编码器核心配置:
    • Mode:Encoder Mode(TI1+TI2 4 倍频,与代码宏 ENCODER_QUADRATURE_X4=4 对应)
    • Counter Period(ARR):65535(16 位满量程,溢出处理最稳定)
    • Prescaler(PSC):0(不分频,全脉冲计数)
    • Encoder Input Filter:根据现场噪声调整,噪声大建议开启滤波
  3. GPIO:默认 无上下拉(No pull),按硬件实际情况微调。

提示:若使用 1 倍频模式,CubeMX 选择 TI1 Only,同时修改代码倍频宏为 1

2.3 电机驱动 GPIO 配置(IN1/IN2 方向 + EN 使能)

引脚对应表:

根据自己实际接线调整

电机 GPIO 端口 IN1 引脚 IN2 引脚 EN 引脚
左前 (LF) GPIOE PE2 PE3 PE4
右前 (RF) GPIOC PC1 PC2 PC3
左后 (LB) GPIOB PB8 PB4 PB5
右后 (RB) GPIOD PD0 PD1 PD2

GPIO 统一配置

  • GPIO mode:Output Push Pull(推挽输出)
  • Pull-Up/Pull-Down:No pull
  • Maximum output speed:High
  • EN 引脚初始电平:Low(上电默认关闭驱动,软件控制使能)

2.4 填写硬件表

把硬件接线的实际引脚同步填入硬件配置表中,方便之后电机数组自动调用

static const motor_hw_config_t motor_hw_table[4] =
    {
        // 左前电机 LF
        {
            .motor_id = MOTOR_LF,
            .gpio_port = GPIOE,
            .in1_pin = GPIO_PIN_2,
            .in2_pin = GPIO_PIN_3,
            .en_pin = GPIO_PIN_4,
            .pwm_tim = &htim1,
            .pwm_channel = TIM_CHANNEL_1,
            .enc_tim = &htim8,
            .motor_dir_reverse = MOTOR_DIR_NORMAL,
            .enc_reverse = ENC_REVERSE},
        // 右前电机 RF
        {
            .motor_id = MOTOR_RF,
            .gpio_port = GPIOC,
            .in1_pin = GPIO_PIN_1,
            .in2_pin = GPIO_PIN_2,
            .en_pin = GPIO_PIN_3,
            .pwm_tim = &htim1,
            .pwm_channel = TIM_CHANNEL_2,
            .enc_tim = &htim3,
            .motor_dir_reverse = MOTOR_DIR_NORMAL,
            .enc_reverse = ENC_NORMAL},
            ......
};

🔄 双方向反转机制与调试

框架提供两套独立反转逻辑,分别控制「电机运动方向」和「编码器读数方向」,调试顺序:先调电机转向,再调编码器方向

3.1 电机运动方向反转 motor_dir_reverse

  • 作用:解决「PWM 为正,但电机实际反转」问题(电机接线、安装方向导致)
  • 枚举定义:
    • MOTOR_DIR_NORMAL:正常逻辑 → PWM>0 正转,PWM<0 反转
    • MOTOR_DIR_REVERSE:交换 IN1/IN2 电平,整体转向反转
  • 本质:软件模拟「物理调换电机动力线」。

3.2 编码器读数方向反转 enc_reverse

  • 作用:解决「电机正转,但编码器速度 / 角度读数为负」问题(AB 相接线反、编码器安装反向)
  • 枚举定义:
    • ENC_NORMAL:原始脉冲差值,不做处理
    • ENC_REVERSE:脉冲差值整体取反
  • 本质:软件模拟「物理调换编码器 AB 相线」。

3.3 两者区别对照表

表格

配置项 控制对象 影响范围 调试目标
motor_dir_reverse PWM → IN1/IN2 输出电平 电机实际转动方向 PWM>0 电机向前转
enc_reverse 编码器脉冲差值计算 闭环反馈数据(rpm / 角度) 电机正转 → 读数为正数

3.4 现场标准调试流程

  1. 前置准备
    • 所有电机 motor_dir_reverse = MOTOR_DIR_NORMALenc_reverse = ENC_NORMAL
    • 开启开环控制(暂时关闭 PID),将车轮悬空,保证安全
  2. 第一步:校准电机转向
    • 给所有电机赋值 pwm_output = 500(50% 占空比),执行输出
    • 向前转动:保持配置不变;向后转动:修改为 MOTOR_DIR_REVERSE
    • 重复测试,直到所有电机正 PWM 均向前转
  3. 第二步:校准编码器方向
    • 保持电机向前转动,循环调用 motor_update_feedback() 读取转速
    • 转速为正数:配置不变;转速为负数:修改为 ENC_REVERSE
    • 重复测试,直到电机正转时转速读数恒为正

3.5 参考默认配置(出厂接线)

表格

电机 motor_dir_reverse enc_reverse
左前 (LF) MOTOR_DIR_NORMAL ENC_REVERSE
右前 (RF) MOTOR_DIR_NORMAL ENC_NORMAL
左后 (LB) MOTOR_DIR_REVERSE ENC_REVERSE
右后 (RB) MOTOR_DIR_NORMAL ENC_NORMAL

提示:实际装车后需根据轮子安装位置重新校准。


🧱 核心数据结构体介绍

硬件配置表 motor_hw_table[] 与硬件配置结构体 motor_hw_config_t

硬件配置表 motor_hw_table[]是静态数组,集中管理所有电机的硬件配置,修改引脚仅修改此数组,业务代码无需改动。

硬件配置结构体 motor_hw_config_t是记录硬件信息的结构体,实际上是硬件配置表写入变量,通过电机数组传参,方便后续函数使用。

static const motor_hw_config_t motor_hw_table[4] =
    {
        // 左前电机 LF
        {
            .motor_id = MOTOR_LF,
            .gpio_port = GPIOE,
            .in1_pin = GPIO_PIN_2,
            .in2_pin = GPIO_PIN_3,
            .en_pin = GPIO_PIN_4,
            .pwm_tim = &htim1,
            .pwm_channel = TIM_CHANNEL_1,
            .enc_tim = &htim8,
            .motor_dir_reverse = MOTOR_DIR_NORMAL,
            .enc_reverse = ENC_REVERSE},
        // 右前电机 RF
        {
            .motor_id = MOTOR_RF,
            .gpio_port = GPIOC,
            .in1_pin = GPIO_PIN_1,
            .in2_pin = GPIO_PIN_2,
            .en_pin = GPIO_PIN_3,
            .pwm_tim = &htim1,
            .pwm_channel = TIM_CHANNEL_2,
            .enc_tim = &htim3,
            .motor_dir_reverse = MOTOR_DIR_NORMAL,
            .enc_reverse = ENC_NORMAL},
            ......
};
typedef struct
{
    uint8_t motor_id;                // 电机唯一标识ID
    GPIO_TypeDef *gpio_port;         // IN1/IN2/EN 所在GPIO端口
    uint16_t in1_pin;                // 方向引脚IN1
    uint16_t in2_pin;                // 方向引脚IN2
    uint16_t en_pin;                 // 驱动板使能引脚EN
    TIM_HandleTypeDef *pwm_tim;      // PWM输出定时器句柄
    uint8_t pwm_channel;             // PWM通道号
    TIM_HandleTypeDef *enc_tim;      // 编码器计数定时器句柄
    uint8_t motor_dir_reverse;       // 电机转向反转配置
    uint8_t enc_reverse;             // 编码器读数反转配置
} motor_hw_config_t;

电机结构体 motor_t

每路电机独立实例,包括硬件配置情况,存储控制参数、反馈数据、滤波数据、原始数据、状态信息,方便debug。参数分区如下:

  1. 硬件绑定区:硬件配置结构体motor_hw_config_tmotor_idreduction_ratio 减速比
  2. 控制输入区:目标位置 / 速度、PWM 输出、运行模式
  3. 反馈输出区:转速 (rpm)、角速度 (rad/s)、角度 (rad)、行驶距离 (m)
  4. 滤波数据区:一阶低通滤波后转速 / 速度、滤波系数
  5. 编码器原始数据:当前计数值、历史值、总脉冲、总圈数
  6. 状态监控区:在线状态、堵转计时
typedef struct
{
    // 硬件配置参数
    motor_hw_config_t hw;
    uint8_t motor_id;
    float reduction_ratio;

    // 控制模式
    motor_control_mode_e control_mode;

    // 运行状态监控
    uint8_t online;
    uint32_t stall_ms;

    // 电机控制参数
    float p_ref;
    float v_ref;
    int16_t pwm_output;

    // 一阶低通滤波数据
    float filter_alpha;
    float f_rpm;
    float f_vel;
    float last_f_rpm;
    float last_f_vel;

    // 电机反馈数据(国际标准单位)
    float rpm;
    float angle;
    float velocity;
    float distance;

    // 编码器原始数据
    uint16_t ecd;
    uint16_t last_ecd;
    int32_t total_pulse;
    int32_t round_cnt;

} motor_t;

🧩 三层解耦架构设计

框架核心优势,实现「硬件配置、软件逻辑、电机实例」完全解耦:

  1. 第一层:宏定义层(电机标签)纯枚举 ID:MOTOR_LF(0x01) / MOTOR_RF(0x04) / MOTOR_LB(0x02) / MOTOR_RB(0x03)

  2. 第二层:硬件表层(引脚配置)motor_hw_table[] 硬件表数组

  3. 第三层:运行实例层(软件状态)motor_t 结构体存放电机运行数据,其中motor_hw_config_t专门存放这个电机数组绑定的引脚传参调用

绑定示例

// 将 motor[0] 绑定到 左前电机(LF)
motor_init(&motor[0], MOTOR_LF);

绑定原理与实现流程

  1. define左前电机ID 0x01
  2. 填写左前电机硬件表(左前电机id与左前电机引脚在motor_hw_table中任意位置)
  3. motor_init()传入的左前电机 ID 0x01与打算绑定的电机数组 0 (第一二层在此手动绑定)
  4. 代码将自动遍历硬件表,找到硬件表 motor_id == 0x01 的引脚配置写入motor_hw_config_t(第一三层在此自动绑定)
  5. motor_hw_config_t是电机数组motor_t的一部分将一起传参进函数以供调用
  6. 函数调用传入结构体中的引脚设置完成应用功能实现

解耦优势

  • 电机数组下标可任意调换,硬件匹配不受数组顺序影响
  • 更换引脚、新增电机,仅修改 motor_hw_table[],上层逻辑零改动

🛠️ 核心 API 接口

6.1 void motor_init(motor_t *motor, uint8_t motor_id)

功能:电机初始化 + 硬件绑定

  • 参数:
    • motor:电机状态结构体指针
    • motor_id:电机 ID(MOTOR_LF/MOTOR_RF 等)
  • 执行动作:匹配硬件表、清零状态、启动 PWM / 编码器、使能驱动、编码器归零。

6.2 void motor_update_feedback(motor_t *motor, uint32_t interval_ms)

功能:读取编码器、计算物理量、滤波、堵转检测

  • 调用时机:1ms 定时器中断中循环调用
  • 参数:
    • motor:电机结构体指针
    • interval_ms:调用周期(固定填 1)
  • 输出:更新转速、角度、距离、滤波值、堵转状态等所有反馈数据。

6.3 void motor_control_all(m1, m2, m3, m4)

功能:批量输出 PWM 与 GPIO 控制电机运转

  • 参数:4 路电机指针,支持传入 NULL(跳过对应电机)
  • 模式逻辑:
    • MOTOR_RUN(默认):按 pwm_output 输出 PWM,正负控制转向
    • MOTOR_COAST(滑行):H 桥断开,电机惯性转动,无视 PWM
    • MOTOR_BRAKE(制动):H 桥短接,反电动势急停,无视 PWM

6.4 void motor_set_mode(m1, m2, m3, m4, mode)

功能:批量修改电机运行模式

  • 参数:4 路电机指针 (可 NULL) + 模式枚举
  • 作用:统一切换 正常 / 滑行 / 制动 模式。

6.5 void motor_reset_encoder(motor_t *motor)

功能:编码器硬件计数器 + 软件累计数据清零

  • 适用场景:坐标零点校准、复位里程 / 角度。
/**
 * @brief  初始化电机:绑定ID到硬件表,启动PWM和编码器
 * @param  motor: motor_t结构体指针
 * @param  motor_id: 电机ID (MOTOR_LF/RF/LB/RB)
 */
void motor_init(motor_t *motor, uint8_t motor_id);

/**
 * @brief  读取编码器并转换为国际标准单位(rpm/rad/s/rad/米)
 * @param  motor: 电机指针
 * @param  interval_ms: 调用间隔(ms),通常填1
 */
void motor_update_feedback(motor_t *motor, uint32_t interval_ms);

/**
 * @brief  执行电机控制(方向+PWM),支持传NULL跳过
 * @param  motor1~motor4: 电机指针,传NULL则跳过该电机
 */
void motor_control_all(motor_t *motor1, motor_t *motor2, motor_t *motor3, motor_t *motor4);

/**
 * @brief  清零编码器累计数据
 * @param  motor: 电机指针
 */
void motor_reset_encoder(motor_t *motor);

/**
 * @brief  批量切换控制模式,支持传NULL跳过
 * @param  motor1~motor4: 电机指针,传NULL则跳过该电机
 * @param  mode: MOTOR_RUN / MOTOR_COAST / MOTOR_BRAKE
 */
void motor_set_mode(motor_t *motor1, motor_t *motor2, motor_t *motor3, motor_t *motor4, motor_control_mode_e mode);

🚀 快速使用流程 & 代码示例

7.1 系统全局定义 & 初始化

#include "drv_motor.h"

// 定义4路电机实例
motor_t motor[4];

// 系统电机初始化
void Motor_System_Init(void)
{
    // 绑定电机ID与实例,顺序可任意调换
    motor_init(&motor[0], MOTOR_LF);  // 左前
    motor_init(&motor[1], MOTOR_RF);  // 右前
    motor_init(&motor[2], MOTOR_LB);  // 左后
    motor_init(&motor[3], MOTOR_RB);  // 右后

    // 配置一阶低通滤波系数(推荐 0.7 ~ 0.9)
    for(int i = 0; i < 4; i++)
    {
        motor[i].filter_alpha = 0.85f;
    }
}

7.2 1ms 定时中断(PID闭环时用)

略作修改可用串级位置环

// 1ms定时器中断回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    // 替换为你项目中1ms定时器句柄
    if(htim->Instance == YOUR_1MS_TIMER)
    {
        // 1. 更新所有电机编码器反馈
        motor_update_feedback(&motor[0], 1);
        motor_update_feedback(&motor[1], 1);
        motor_update_feedback(&motor[2], 1);
        motor_update_feedback(&motor[3], 1);

        // 2. 速度PID计算(示例)
        motor[0].pwm_output = PID_Calc(&pid_vel[0], motor[0].f_rpm, 200.0f);
        motor[1].pwm_output = PID_Calc(&pid_vel[1], motor[1].f_rpm, 200.0f);
        motor[2].pwm_output = PID_Calc(&pid_vel[2], motor[2].f_rpm, 200.0f);
        motor[3].pwm_output = PID_Calc(&pid_vel[3], motor[3].f_rpm, 200.0f);

        // 3. 执行电机输出
        motor_control_all(&motor[0], &motor[1], &motor[2], &motor[3]);
    }
}

7.3 其他常用业务场景代码

// 场景1:全车开环匀速前进
for(int i = 0; i < 4; i++) motor[i].pwm_output = 500;
motor_control_all(&motor[0], &motor[1], &motor[2], &motor[3]);

// 场景2:原地差速转向(左右轮反向)
motor[0].pwm_output =  400;
motor[1].pwm_output = -400;
motor[2].pwm_output =  400;
motor[3].pwm_output = -400;
motor_control_all(&motor[0], &motor[1], &motor[2], &motor[3]);

// 场景3:全车急停(制动模式)
motor_set_mode(&motor[0], &motor[1], &motor[2], &motor[3], MOTOR_BRAKE);

// 场景4:全车平缓停车(自由滑行)
motor_set_mode(&motor[0], &motor[1], &motor[2], &motor[3], MOTOR_COAST);

// 场景5:检测单电机堵转
if(motor[0].online == 0)
{
    motor_set_mode(&motor[0], NULL, NULL, NULL, MOTOR_COAST);
    // 自定义报警/保护逻辑
}

// 场景6:仅控制左前电机,其余电机不动作
motor_control_all(&motor[0], NULL, NULL, NULL);

❓ 常见问题排查

  1. Q:电机转向和预期相反?A:修改硬件表中对应电机的 motor_dir_reverse 配置,按「先调转向、再调编码器」流程校准。

  2. Q:PID 闭环正负相反,正目标速度电机反转?A:编码器方向配置错误,修改 enc_reverse;两个反转配置相互独立,不要混淆。

  3. Q:编码器读数乱跳、噪声大?A:CubeMX 中调大 Encoder Input Filter;确认编码器倍频宏与配置一致。

  4. Q:motor_update_feedback 中脉冲差值 delta 恒为 0?A:检查 HAL_TIM_Encoder_Start() 是否执行、编码器 AB 相接线是否导通、定时器配置是否正常。

  5. Q:电机正常转动,但 online 变为 0(堵转报警)?A:堵转阈值过于灵敏,调大 STALL_PWM_THRESHOLD / STALL_TIMEOUT_MS;或检查电机是否物理卡死。

  6. Q:想增加第 5/6 路电机?A:① 新增电机 ID 宏 ② motor_hw_table[] 追加配置项 ③ 放大 motor[] 数组 ④ 调用 motor_init() 绑定,原有逻辑无需修改。

  7. Q:滤波后转速依旧抖动?A:减小 filter_alpha 滤波系数(取值 0.5~0.8),或增强编码器硬件滤波。

原代码附上

#ifndef __DRV_MOTOR_H
#define __DRV_MOTOR_H

/**
 * @file    drv_motor.h
 * @brief   Universal DC Encoder Motor Driver for STM32 HAL
 * @version 1.0.0
 * @author  Sakila-la
 * @date    2025
 * @license MIT — see LICENSE or https://opensource.org/licenses/MIT
 *
 * Hardware table-driven driver for DC geared motors with AB-phase encoders.
 * Compatible with 310/370/520, JGB37, JGA25-370, N20, etc.
 * Works with TB6612 / L298N / DRV8833 H-bridge drivers.
 *
 * @note    Hardware pin configuration is in motor_hw_table (drv_motor.c).
 *          Modify it to match your actual wiring before use.
 * @see     README.md for full documentation and quick start guide.
 */

#ifdef __cplusplus
extern "C"
{
#endif

#include "main.h"
#include "tim.h"
#include "gpio.h"
#include <stdlib.h>
#include <string.h>
#include <math.h>

#ifndef PI
#define PI 3.14159265358979323846f
#endif

// ===================== 电机ID定义 =====================
#define MOTOR_LF 0x01 // 左前电机
#define MOTOR_RF 0x04 // 右前电机
#define MOTOR_LB 0x02 // 左后电机
#define MOTOR_RB 0x03 // 右后电机

// ===================== 电机参数宏定义 =====================
#define PWM_MAX_RANGE 1000         // PWM最大占空比
#define ENCODER_LINES 11           // 编码器物理线数(PPR)
#define ENCODER_QUADRATURE_X4 4    // 正交解码倍频 (TI1+TI2 4倍频)
#define GEAR_REDUCTION_RATIO 30.0f // 减速比
#define WHEEL_DIAMETER 0.097f      // 轮子直径(米)

// 编码器定时器溢出处理宏(16位定时器专用/32位需手动修改)
#define ENC_TIM_HALF_RANGE 32768   // 半量程阈值(溢出判断用)
#define ENC_TIM_OVERFLOW_VAL 65536 // 溢出补偿值

// 失速检测阈值(可按电机型号调整)
#define STALL_PWM_THRESHOLD 200 // PWM超过此值才算"用力了"
#define STALL_TIMEOUT_MS 500    // 堵转累计超过此时间(ms)则online=0报警
#define STALL_MIN_DELTA 2       // 脉冲增量低于此值视为"几乎没转"

// 单圈总脉冲数 = 编码器线数 × 倍频 × 减速比
#define ENCODER_TOTAL_PULSE (ENCODER_LINES * ENCODER_QUADRATURE_X4 * GEAR_REDUCTION_RATIO)
// 轮子周长
#define WHEEL_CIRCUMFERENCE (PI * WHEEL_DIAMETER)

// 电机物理运动方向反转枚举
typedef enum
{
    MOTOR_DIR_NORMAL = 0, // 不反转
    MOTOR_DIR_REVERSE = 1 // 反转物理运动方向
} MotorDirReverseType;

// 编码器读数方向反转枚举
typedef enum
{
    ENC_NORMAL = 0, // 不反转
    ENC_REVERSE = 1 // 反转编码器反馈方向
} EncReverseType;

// 电机控制模式枚举
typedef enum
{
    MOTOR_RUN = 0,   // 正常运行 — PWM+方向控制,pwm_output=0时滑行停止
    MOTOR_COAST = 1, // 自由滑行 — 无视pwm_output,H桥断开,惯性自由转动
    MOTOR_BRAKE = 2, // 急停制动 — 无视pwm_output,H桥短接,反电动势制动
} motor_control_mode_e;

// ===================== 电机硬件配置结构体 =====================
typedef struct
{
    uint8_t motor_id;

    // 电机驱动GPIO端口
    GPIO_TypeDef *gpio_port;
    uint16_t in1_pin;
    uint16_t in2_pin;
    uint16_t en_pin;

    // PWM定时器与通道
    TIM_HandleTypeDef *pwm_tim;
    uint32_t pwm_channel;

    // 编码器定时器与方向反转
    TIM_HandleTypeDef *enc_tim;
    MotorDirReverseType motor_dir_reverse;
    EncReverseType enc_reverse;
} motor_hw_config_t;

// ===================== 电机运行时数据体 =====================
typedef struct
{
    // 硬件配置参数
    motor_hw_config_t hw;
    uint8_t motor_id;
    float reduction_ratio;

    // 控制模式
    motor_control_mode_e control_mode;

    // 运行状态监控
    uint8_t online;
    uint32_t stall_ms;

    // 电机控制参数
    float p_ref;
    float v_ref;
    int16_t pwm_output;

    // 一阶低通滤波数据
    float filter_alpha;
    float f_rpm;
    float f_vel;
    float last_f_rpm;
    float last_f_vel;

    // 电机反馈数据(国际标准单位)
    float rpm;
    float angle;
    float velocity;
    float distance;

    // 编码器原始数据
    uint16_t ecd;
    uint16_t last_ecd;
    int32_t total_pulse;
    int32_t round_cnt;

} motor_t;

extern motor_t motor[4];

/**
 * @brief  初始化电机:绑定ID到硬件表,启动PWM和编码器
 * @param  motor: motor_t结构体指针
 * @param  motor_id: 电机ID (MOTOR_LF/RF/LB/RB)
 */
void motor_init(motor_t *motor, uint8_t motor_id);

/**
 * @brief  读取编码器并转换为国际标准单位(rpm/rad/s/rad/米)
 * @param  motor: 电机指针
 * @param  interval_ms: 调用间隔(ms),通常填1
 */
void motor_update_feedback(motor_t *motor, uint32_t interval_ms);

/**
 * @brief  执行电机控制(方向+PWM),支持传NULL跳过
 * @param  motor1~motor4: 电机指针,传NULL则跳过该电机
 */
void motor_control_all(motor_t *motor1, motor_t *motor2, motor_t *motor3, motor_t *motor4);

/**
 * @brief  清零编码器累计数据
 * @param  motor: 电机指针
 */
void motor_reset_encoder(motor_t *motor);

/**
 * @brief  批量切换控制模式,支持传NULL跳过
 * @param  motor1~motor4: 电机指针,传NULL则跳过该电机
 * @param  mode: MOTOR_RUN / MOTOR_COAST / MOTOR_BRAKE
 */
void motor_set_mode(motor_t *motor1, motor_t *motor2, motor_t *motor3, motor_t *motor4, motor_control_mode_e mode);

#endif

#ifdef __cplusplus
}
#endif
/**
 * @file    drv_motor.c
 * @brief   Universal DC Encoder Motor Driver — hardware table & implementation
 * @license MIT — see LICENSE or https://opensource.org/licenses/MIT
 */

#include "drv_motor.h"

motor_t motor[4];

/**
 * @brief 电机硬件参数表
 * @note  以下为示例配置,请根据你的实际电路修改引脚/定时器设置
 */
static const motor_hw_config_t motor_hw_table[4] =
    {
        // 左前电机 LF
        {
            .motor_id = MOTOR_LF,
            .gpio_port = GPIOE,
            .in1_pin = GPIO_PIN_2,
            .in2_pin = GPIO_PIN_3,
            .en_pin = GPIO_PIN_4,
            .pwm_tim = &htim1,
            .pwm_channel = TIM_CHANNEL_1,
            .enc_tim = &htim8,
            .motor_dir_reverse = MOTOR_DIR_NORMAL,
            .enc_reverse = ENC_REVERSE},
        // 右前电机 RF
        {
            .motor_id = MOTOR_RF,
            .gpio_port = GPIOC,
            .in1_pin = GPIO_PIN_1,
            .in2_pin = GPIO_PIN_2,
            .en_pin = GPIO_PIN_3,
            .pwm_tim = &htim1,
            .pwm_channel = TIM_CHANNEL_2,
            .enc_tim = &htim3,
            .motor_dir_reverse = MOTOR_DIR_NORMAL,
            .enc_reverse = ENC_NORMAL},
        // 左后电机 LB
        {
            .motor_id = MOTOR_LB,
            .gpio_port = GPIOB,
            .in1_pin = GPIO_PIN_8,
            .in2_pin = GPIO_PIN_4,
            .en_pin = GPIO_PIN_5,
            .pwm_tim = &htim1,
            .pwm_channel = TIM_CHANNEL_3,
            .enc_tim = &htim4,
            .motor_dir_reverse = MOTOR_DIR_REVERSE,
            .enc_reverse = ENC_REVERSE},
        // 右后电机 RB
        {
            .motor_id = MOTOR_RB,
            .gpio_port = GPIOD,
            .in1_pin = GPIO_PIN_0,
            .in2_pin = GPIO_PIN_1,
            .en_pin = GPIO_PIN_2,
            .pwm_tim = &htim1,
            .pwm_channel = TIM_CHANNEL_4,
            .enc_tim = &htim5,
            .motor_dir_reverse = MOTOR_DIR_NORMAL,
            .enc_reverse = ENC_NORMAL},
};

/**
 * @brief  根据电机ID查找对应的硬件配置
 * @param  id: 电机ID (MOTOR_LF/RF/LB/RB)
 * @return 找到返回硬件指针,失败返回NULL
 */
static const motor_hw_config_t *motor_hw_find(uint8_t id)
{
    for (int i = 0; i < sizeof(motor_hw_table) / sizeof(motor_hw_config_t); i++)
    {
        if (motor_hw_table[i].motor_id == id)
        {
            return &motor_hw_table[i];
        }
    }
    return NULL;
}

static void motor_control(motor_t *motor);

/**
 * @brief  初始化电机:绑定ID到硬件表,启动PWM和编码器
 * @param  motor: motor_t结构体指针
 * @param  motor_id: 电机ID (MOTOR_LF/RF/LB/RB)
 */
void motor_init(motor_t *motor, uint8_t motor_id)
{
    if (motor == NULL)
        return;

    // 查找硬件配置
    const motor_hw_config_t *hw = motor_hw_find(motor_id);
    if (hw == NULL)
        return;

    // 清空结构体
    memset(motor, 0, sizeof(motor_t));

    // 绑定硬件参数
    motor->hw = *hw;
    motor->motor_id = motor_id;
    motor->reduction_ratio = GEAR_REDUCTION_RATIO;

    // 初始化滤波与控制参数
    motor->filter_alpha = 1.0f;
    motor->control_mode = MOTOR_RUN;
    motor->stall_ms = 0;
    motor->online = 1;

    // 启动外设
    HAL_TIM_PWM_Start(motor->hw.pwm_tim, motor->hw.pwm_channel);
    HAL_TIM_Encoder_Start(motor->hw.enc_tim, TIM_CHANNEL_ALL);
    HAL_GPIO_WritePin(motor->hw.gpio_port, motor->hw.en_pin, GPIO_PIN_SET);

    // 初始状态:零占空比 + 编码器归零
    motor->pwm_output = 0;
    motor_control(motor);
    motor_reset_encoder(motor);
}

/**
 * @brief  计算编码器脉冲差值(处理溢出+方向反转)
 * @param  motor: 电机结构体指针
 * @return 脉冲增量值(已处理溢出和方向反转)
 */
static int32_t motor_calc_ecd_delta(motor_t *motor)
{
    uint16_t curr_ecd = __HAL_TIM_GET_COUNTER(motor->hw.enc_tim);
    int32_t delta = (int32_t)curr_ecd - (int32_t)motor->last_ecd;

    // 溢出处理(16位定时器半量程法)
    if (delta < -ENC_TIM_HALF_RANGE)
        delta += ENC_TIM_OVERFLOW_VAL;
    if (delta > ENC_TIM_HALF_RANGE - 1)
        delta -= ENC_TIM_OVERFLOW_VAL;

    // 编码器方向反转
    if (motor->hw.enc_reverse)
        delta = -delta;

    motor->last_ecd = curr_ecd;
    motor->ecd = curr_ecd;
    return delta;
}

/**
 * @brief  读取编码器并转换为国际标准单位(rpm/rad/s/rad/米)
 * @param  motor: 电机指针
 * @param  interval_ms: 调用间隔(ms),通常填1
 */
void motor_update_feedback(motor_t *motor, uint32_t interval_ms)
{
    if (motor == NULL || interval_ms == 0)
        return;

    // 编码器脉冲增量
    int32_t delta = motor_calc_ecd_delta(motor);
    motor->total_pulse += delta;

    // 圈数
    motor->round_cnt = motor->total_pulse / ENCODER_TOTAL_PULSE;

    // 单位转换(输出轴层面,已含减速比)
    motor->rpm = (float)delta * 60000.0f / (float)ENCODER_TOTAL_PULSE / (float)interval_ms;
    motor->angle = 2.0f * PI * motor->total_pulse / ENCODER_TOTAL_PULSE;
    motor->velocity = 2.0f * PI * (delta / (float)interval_ms * 1000.0f) / ENCODER_TOTAL_PULSE;
    motor->distance = motor->total_pulse * (WHEEL_CIRCUMFERENCE / ENCODER_TOTAL_PULSE);

    // 首帧用实际值初始化,避免滤波跳变
    if (motor->last_f_rpm == 0.0f && motor->f_rpm == 0.0f)
    {
        motor->last_f_rpm = motor->rpm;
        motor->f_rpm = motor->rpm;
    }
    if (motor->last_f_vel == 0.0f && motor->f_vel == 0.0f)
    {
        motor->last_f_vel = motor->velocity;
        motor->f_vel = motor->velocity;
    }

    // 一阶低通滤波
    motor->f_rpm = motor->filter_alpha * motor->rpm + (1.0f - motor->filter_alpha) * motor->last_f_rpm;
    motor->f_vel = motor->filter_alpha * motor->velocity + (1.0f - motor->filter_alpha) * motor->last_f_vel;

    motor->last_f_rpm = motor->f_rpm;
    motor->last_f_vel = motor->f_vel;

    // 失速检测:PWM很大但编码器几乎没动
    if (abs(motor->pwm_output) > STALL_PWM_THRESHOLD && abs(delta) < STALL_MIN_DELTA)
    {
        motor->stall_ms += interval_ms;
        if (motor->stall_ms > STALL_TIMEOUT_MS)
        {
            motor->online = 0;
        }
    }
    else
    {
        motor->stall_ms = 0;
        motor->online = 1;
    }
}

/**
 * @brief  单电机执行输出(方向+PWM,支持RUN/COAST/BRAKE三种模式)
 */
static void motor_control(motor_t *motor)
{
    if (motor == NULL)
        return;

    int16_t out = motor->pwm_output;
    if (out > PWM_MAX_RANGE)
        out = PWM_MAX_RANGE;
    if (out < -PWM_MAX_RANGE)
        out = -PWM_MAX_RANGE;

    switch (motor->control_mode)
    {
    case MOTOR_COAST:
        // 自由滑行:H桥断开,惯性转动
        HAL_GPIO_WritePin(motor->hw.gpio_port, motor->hw.in1_pin, GPIO_PIN_RESET);
        HAL_GPIO_WritePin(motor->hw.gpio_port, motor->hw.in2_pin, GPIO_PIN_RESET);
        __HAL_TIM_SET_COMPARE(motor->hw.pwm_tim, motor->hw.pwm_channel, 0);
        break;

    case MOTOR_BRAKE:
        // 急停制动:H桥短接,反电动势制动
        HAL_GPIO_WritePin(motor->hw.gpio_port, motor->hw.in1_pin, GPIO_PIN_SET);
        HAL_GPIO_WritePin(motor->hw.gpio_port, motor->hw.in2_pin, GPIO_PIN_SET);
        __HAL_TIM_SET_COMPARE(motor->hw.pwm_tim, motor->hw.pwm_channel, 0);
        break;

    case MOTOR_RUN:
    default:
        if (out != 0)
        {
            // PWM控制:方向由IN1/IN2决定,占空比决定速度
            uint8_t dir = (out > 0) ? 1 : 0;
            if (motor->hw.motor_dir_reverse)
                dir = !dir;
            HAL_GPIO_WritePin(motor->hw.gpio_port, motor->hw.in1_pin, dir ? GPIO_PIN_SET : GPIO_PIN_RESET);
            HAL_GPIO_WritePin(motor->hw.gpio_port, motor->hw.in2_pin, (!dir) ? GPIO_PIN_SET : GPIO_PIN_RESET);
            __HAL_TIM_SET_COMPARE(motor->hw.pwm_tim, motor->hw.pwm_channel, abs(out));
        }
        else
        {
            // pwm_output=0 → 自然滑行
            HAL_GPIO_WritePin(motor->hw.gpio_port, motor->hw.in1_pin, GPIO_PIN_RESET);
            HAL_GPIO_WritePin(motor->hw.gpio_port, motor->hw.in2_pin, GPIO_PIN_RESET);
            __HAL_TIM_SET_COMPARE(motor->hw.pwm_tim, motor->hw.pwm_channel, 0);
        }
        break;
    }
}

/**
 * @brief  执行电机控制(方向+PWM),支持传NULL跳过
 * @param  motor1~motor4: 电机指针,传NULL则跳过该电机
 */
void motor_control_all(motor_t *motor1, motor_t *motor2, motor_t *motor3, motor_t *motor4)
{
    motor_control(motor1);
    motor_control(motor2);
    motor_control(motor3);
    motor_control(motor4);
}

/**
 * @brief  清零编码器累计数据
 * @param  motor: 电机指针
 */
void motor_reset_encoder(motor_t *motor)
{
    if (motor == NULL)
        return;

    __HAL_TIM_SET_COUNTER(motor->hw.enc_tim, 0);
    motor->ecd = 0;
    motor->last_ecd = 0;
    motor->total_pulse = 0;
    motor->round_cnt = 0;
    motor->rpm = 0;
    motor->angle = 0;
    motor->velocity = 0;
    motor->distance = 0;
}

/**
 * @brief  批量切换控制模式,支持传NULL跳过
 * @param  motor1~motor4: 电机指针,传NULL则跳过该电机
 * @param  mode: MOTOR_RUN / MOTOR_COAST / MOTOR_BRAKE
 */
void motor_set_mode(motor_t *motor1, motor_t *motor2, motor_t *motor3, motor_t *motor4, motor_control_mode_e mode)
{
    if (motor1 != NULL)
        motor1->control_mode = mode;
    if (motor2 != NULL)
        motor2->control_mode = mode;
    if (motor3 != NULL)
        motor3->control_mode = mode;
    if (motor4 != NULL)
        motor4->control_mode = mode;
}

测试可能不够全面充分,有问题欢迎留言。

 

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐