本文介绍了Mini-F5375-OB
开发板实现轮趣TB6612
驱动板的使用,驱动直流有刷减速电机转动方向、旋转速度的精准控制。
项目介绍
- 底盘模型: 轮趣R3全向轮+两轮差速模型。
- 驱动与供电: 轮趣TB6612FNG双路直流电机驱动模块。
- 软件设计: 参考官方
TIM1_8_PWM_Output
例程,进行了分层设计,项目软件结构简单的分为了驱动层与设备层,最后在main.c
文件中进行了电机驱动测试。
电机与底盘模型
电机采用的是轮趣的MG513P30_12V
直流有刷减速电机,与R3
底盘进行了适配,安装方便,可直接使用。
底盘模型则是采用了轮趣R3
系列差速底盘,后轮差速驱动,前轮配备全向轮用于从动。
详细参考: 轮趣R3系列智能小车底盘
驱动器
驱动直流有刷电机的经典芯片为TB6612
,轮趣的TB6612FNG双路直流电机驱动模块
包含了驱动与稳压输出,可直接驱动两路直流电机。
驱动方式
速度控制: 根据PWM
的占空比控制速度
方向控制: 轮趣的驱动板使用额外两个引脚控制转动方向与制动刹车功能,下面是TB6612真值表。
功能 | 制动停止 | 正传 | 反转 | 制动停止 | 停止 |
---|---|---|---|---|---|
PWM | 1 | 1 | 1 | 1 | 0 |
IN1 | 0 | 1 | 0 | 1 | x |
IN2 | 0 | 1 | 1 | 1 | x |
硬件连接
MM32F5375 | TB6612FNG驱动板 |
---|---|
B0 | AIN1 |
B1 | AIN2 |
C0 | BIN1 |
C1 | BIN2 |
A8 | PWMA |
A9 | PWMB |
5V | 5V |
GND | GND |
电机直接用配套的电机线连接到驱动板即可。
工程创建
参考官方Demo例程TIM1_8_PWM_Output
,在同一文件夹下拷贝,改名为TIM1_8_DRIVER_MOTOR
,在文件夹下创建Devices/inc/motor.h
、Devices/src/motor.c
、Drivers/inc/pwm.h
、Drivers/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);
}
}
这里代码并没有严格的进行抽象设计,可以进一步设计枚举等让工程代码更合理,这里就抛砖引玉。
测试效果
测试用例为小车原地旋转,演示视频过长无法上传,下图为轮胎旋转效果
问题总结
在一开始使用例程查看单片机信息时发现平台化里面初始化的是串口1,需要使用USB转TTL才能使用平台化文件里面的InfoPrint
;
在设置PWM初始化的函数时,注意PWM_Init
函数,在开发完成驱动层后才发现这个函数在hal_mindpwm.c
中定义了,由于无法了解库函数有哪些,导致全部重写了。大家可以先去了解官方的hal
库中有哪些功能,避免二次开发。