Victorlsx · 1 天前

【Mini-F5375-OB开发板评测】轮趣直流有刷减速电机驱动和控制

本文介绍了Mini-F5375-OB开发板实现轮趣TB6612驱动板的使用,驱动直流有刷减速电机转动方向、旋转速度的精准控制。


项目介绍

  • 底盘模型: 轮趣R3全向轮+两轮差速模型。
  • 驱动与供电: 轮趣TB6612FNG双路直流电机驱动模块。
  • 软件设计: 参考官方TIM1_8_PWM_Output例程,进行了分层设计,项目软件结构简单的分为了驱动层与设备层,最后在main.c文件中进行了电机驱动测试。

电机与底盘模型

 title=
电机采用的是轮趣的MG513P30_12V直流有刷减速电机,与R3底盘进行了适配,安装方便,可直接使用。
底盘模型则是采用了轮趣R3系列差速底盘,后轮差速驱动,前轮配备全向轮用于从动。
详细参考: 轮趣R3系列智能小车底盘

驱动器

 title=
驱动直流有刷电机的经典芯片为TB6612,轮趣的TB6612FNG双路直流电机驱动模块包含了驱动与稳压输出,可直接驱动两路直流电机。

驱动方式

速度控制: 根据PWM的占空比控制速度
方向控制: 轮趣的驱动板使用额外两个引脚控制转动方向与制动刹车功能,下面是TB6612真值表。

功能制动停止正传反转制动停止停止
PWM11110
IN10101x
IN20111x

硬件连接

MM32F5375TB6612FNG驱动板
B0AIN1
B1AIN2
C0BIN1
C1BIN2
A8PWMA
A9PWMB
5V5V
GNDGND

电机直接用配套的电机线连接到驱动板即可。

工程创建

参考官方Demo例程TIM1_8_PWM_Output,在同一文件夹下拷贝,改名为TIM1_8_DRIVER_MOTOR,在文件夹下创建Devices/inc/motor.hDevices/src/motor.cDrivers/inc/pwm.hDrivers/src/pwm.c

代码分析

驱动层代码主要为PWM初始化与设置PWM占空比功能

pwm.h

#ifndef __PWM_H__
#define __PWM_H__

#include <stdint.h>

void User_PWM_Init(void);
void User_PWM_SetDuty(uint8_t channel, uint16_t duty); // channel: 1=CH1, 2=CH2, duty: 0~Period

#endif // __PWM_H__ 

pwm.c

#include "pwm.h"
#include "platform.h"

#define PWM_PERIOD  1000 // PWM周期,duty最大值为1000,对应100%占空比

void User_PWM_Init(void)
{
    GPIO_InitTypeDef        GPIO_InitStruct;
    TIM_OCInitTypeDef       TIM_OCInitStruct;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;

    // 1. 使能时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);

    // 2. 配置GPIO为复用推挽
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource8, GPIO_AF_1);    // TIM1_CH1
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_1);    // TIM1_CH2

    GPIO_StructInit(&GPIO_InitStruct);
    GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_8 | GPIO_Pin_9;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_High;
    GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStruct);

    // 3. 配置定时器
    TIM_TimeBaseStructInit(&TIM_TimeBaseStruct);
    TIM_TimeBaseStruct.TIM_Prescaler         = 0;
    TIM_TimeBaseStruct.TIM_CounterMode       = TIM_CounterMode_Up;
    TIM_TimeBaseStruct.TIM_Period            = PWM_PERIOD;
    TIM_TimeBaseStruct.TIM_ClockDivision     = TIM_CKD_Div1;
    TIM_TimeBaseStruct.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStruct);

    // 4. 配置PWM输出
    TIM_OCStructInit(&TIM_OCInitStruct);
    TIM_OCInitStruct.TIM_OCMode       = TIM_OCMode_PWM1;
    TIM_OCInitStruct.TIM_OutputState  = TIM_OutputState_Enable;
    TIM_OCInitStruct.TIM_Pulse        = 0; // 初始占空比0
    TIM_OCInitStruct.TIM_OCPolarity   = TIM_OCPolarity_High;
    TIM_OCInitStruct.TIM_OCIdleState  = TIM_OCIdleState_Set;

    // CH1
    TIM_OC1Init(TIM1, &TIM_OCInitStruct);
    // CH2
    TIM_OC2Init(TIM1, &TIM_OCInitStruct);

    // 5. 使能定时器和PWM输出
    TIM_Cmd(TIM1, ENABLE);
    TIM_CtrlPWMOutputs(TIM1, ENABLE);
}

// 设置指定通道的占空比
void User_PWM_SetDuty(uint8_t channel, uint16_t duty)
{
    if (channel == 1)
    {
        TIM_SetCompare1(TIM1, duty);
    }
    else if (channel == 2)
    {
        TIM_SetCompare2(TIM1, duty);
    }
} 

motor.h

#ifndef __MOTOR_H__
#define __MOTOR_H__

#include <stdint.h>

typedef enum {
    MOTOR_STOP = 0,
    MOTOR_FORWARD,
    MOTOR_BACKWARD,
    MOTOR_BRAKE
} MotorDir_t;

void Motor_Init(void);
void Motor_SetDuty(uint16_t duty_ch1, uint16_t duty_ch2);
void Motor_SetDir(uint8_t motor_id, MotorDir_t dir); // motor_id: 1=左轮, 2=右轮

#endif // __MOTOR_H__ 

motor.c

#include "motor.h"
#include "pwm.h"
#include "platform.h"

#define MOTOR1_IN1_GPIO   GPIOB
#define MOTOR1_IN1_PIN    GPIO_Pin_0
#define MOTOR1_IN2_GPIO   GPIOB
#define MOTOR1_IN2_PIN    GPIO_Pin_1
#define MOTOR2_IN1_GPIO   GPIOC
#define MOTOR2_IN1_PIN    GPIO_Pin_0
#define MOTOR2_IN2_GPIO   GPIOC
#define MOTOR2_IN2_PIN    GPIO_Pin_1

static void Motor_GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOC, ENABLE);

    GPIO_StructInit(&GPIO_InitStruct);
    GPIO_InitStruct.GPIO_Pin = MOTOR1_IN1_PIN | MOTOR1_IN2_PIN;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_High;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_Init(MOTOR1_IN1_GPIO, &GPIO_InitStruct);

    GPIO_StructInit(&GPIO_InitStruct);
    GPIO_InitStruct.GPIO_Pin = MOTOR2_IN1_PIN | MOTOR2_IN2_PIN;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_High;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_Init(MOTOR2_IN1_GPIO, &GPIO_InitStruct);
}

void Motor_Init(void)
{
    User_PWM_Init();
    Motor_GPIO_Init();
}

void Motor_SetDuty(uint16_t duty_ch1, uint16_t duty_ch2)
{
    User_PWM_SetDuty(1, duty_ch1);
    User_PWM_SetDuty(2, duty_ch2);
}

void Motor_SetDir(uint8_t motor_id, MotorDir_t dir)
{
    GPIO_TypeDef* IN1_GPIO;
    uint16_t IN1_PIN;
    GPIO_TypeDef* IN2_GPIO;
    uint16_t IN2_PIN;

    if (motor_id == 1) {
        IN1_GPIO = MOTOR1_IN1_GPIO;
        IN1_PIN  = MOTOR1_IN1_PIN;
        IN2_GPIO = MOTOR1_IN2_GPIO;
        IN2_PIN  = MOTOR1_IN2_PIN;
    } else if (motor_id == 2) {
        IN1_GPIO = MOTOR2_IN1_GPIO;
        IN1_PIN  = MOTOR2_IN1_PIN;
        IN2_GPIO = MOTOR2_IN2_GPIO;
        IN2_PIN  = MOTOR2_IN2_PIN;
    } else {
        return;
    }

    switch (dir) {
        case MOTOR_FORWARD:
            GPIO_SetBits(IN1_GPIO, IN1_PIN);
            GPIO_ResetBits(IN2_GPIO, IN2_PIN);
            break;
        case MOTOR_BACKWARD:
            GPIO_ResetBits(IN1_GPIO, IN1_PIN);
            GPIO_SetBits(IN2_GPIO, IN2_PIN);
            break;
        case MOTOR_BRAKE:
            GPIO_SetBits(IN1_GPIO, IN1_PIN);
            GPIO_SetBits(IN2_GPIO, IN2_PIN);
            break;
        case MOTOR_STOP:
        default:
            GPIO_ResetBits(IN1_GPIO, IN1_PIN);
            GPIO_ResetBits(IN2_GPIO, IN2_PIN);
            break;
    }
} 

main.c中的main函数

int main(void)
{
    PLATFORM_Init();
    Motor_Init();
    uint32_t TimerPeriod1 = 0;
    TimerPeriod1 = TIM_GetTIMxClock(TIM1) / 100000;
    printf("TimerPeriod1: %d\r\n", TimerPeriod1);

    while (1)
    {
        // 左轮正转,右轮反转,50%占空比
        Motor_SetDir(1, MOTOR_FORWARD);
        Motor_SetDir(2, MOTOR_BACKWARD);
        Motor_SetDuty(500, 500); // 50%占空比
        PLATFORM_DelayMS(2000);

        // 左右轮刹车
        Motor_SetDir(1, MOTOR_BRAKE);
        Motor_SetDir(2, MOTOR_BRAKE);
        PLATFORM_DelayMS(1000);

        // 左轮反转,右轮正转,80%占空比
        Motor_SetDir(1, MOTOR_BACKWARD);
        Motor_SetDir(2, MOTOR_FORWARD);
        Motor_SetDuty(800, 800); // 80%占空比
        PLATFORM_DelayMS(2000);

        // 左右轮停止
        Motor_SetDir(1, MOTOR_STOP);
        Motor_SetDir(2, MOTOR_STOP);
        PLATFORM_DelayMS(1000);
    }
}

这里代码并没有严格的进行抽象设计,可以进一步设计枚举等让工程代码更合理,这里就抛砖引玉。

测试效果

测试用例为小车原地旋转,演示视频过长无法上传,下图为轮胎旋转效果
2025-06-27T08_04_13.295Z-727809.gif

问题总结

在一开始使用例程查看单片机信息时发现平台化里面初始化的是串口1,需要使用USB转TTL才能使用平台化文件里面的InfoPrint
在设置PWM初始化的函数时,注意PWM_Init函数,在开发完成驱动层后才发现这个函数在hal_mindpwm.c中定义了,由于无法了解库函数有哪些,导致全部重写了。大家可以先去了解官方的hal库中有哪些功能,避免二次开发。

推荐阅读
关注数
0
文章数
1
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息