梁山派GD32F470驱动TB6612FNG电机模块:PWM调速与双路控制实战
梁山派GD32F470驱动TB6612FNG电机模块PWM调速与双路控制实战最近在做一个智能小车项目需要同时控制两个直流电机既要能正反转还得能精确调速。选来选去最终锁定了TB6612FNG这款电机驱动模块。它比经典的L298N效率高、发热小体积也小巧非常适合集成到小车上。不过怎么用咱们手头的梁山派GD32F470开发板去驱动它实现PWM调速呢今天我就把整个从硬件连接到软件驱动的实战过程手把手分享给大家。这篇文章就是为正在用或打算用梁山派做电机控制项目比如智能小车、机器人手臂的朋友准备的。我会详细拆解TB6612的工作原理一步步教你在GD32F470上配置定时器产生PWM信号并提供一个完整、可以直接拿来用的驱动代码。学完你就能轻松让电机转起来并且想快就快想慢就慢想正转就正转想反转就反转。1. 认识你的装备TB6612FNG模块与梁山派在动手接线和写代码之前咱们先得把手里两件“兵器”摸清楚。1.1 TB6612FNG模块小而强的电机驱动器TB6612FNG是一个双路直流电机驱动芯片。所谓“双路”就是它能独立控制两个电机。它最大的优点就是效率高在额定电流内工作几乎不发热这意味着更省电也更稳定。但它的引脚比较密集接线时需要格外细心正负极千万不能接反。它的核心参数如下接线前务必确认你的电源符合要求参数项规格说明VM电机电源 12V接电机的工作电源比如7.4V锂电池。VCC逻辑电源2.7V ~ 5.5V接芯片本身的逻辑电源我们接梁山派的5V引脚。输出电流连续1A每个通道最大持续输出电流瞬间峰值可达3A。控制方式PWM使用PWM脉冲信号来控制电机速度。模块上关键的引脚就这几类电源类VM,VCC,GND电机输出类AO1/AO2控制电机A,BO1/BO2控制电机B控制信号类STBY,AIN1/AIN2,BIN1/BIN2,PWMA,PWMB1.2 控制逻辑让电机听指挥的关键TB6612的控制逻辑很清晰咱们把它拆开看第一关总开关STBY引脚这个引脚就像是整个驱动芯片的总闸。只有给STBY引脚输入高电平接3.3V或5V芯片才进入工作状态。如果给它低电平接GND那么无论其他引脚怎么设置两个电机都会立刻停止。在咱们的教程里为了简化操作我们直接把STBY接到梁山派的3.3V上让它一直处于“开机”状态。实际项目中你也可以用一个GPIO口来控制它实现紧急制动功能。第二关方向控制AIN1/AIN2, BIN1/BIN2芯片工作后具体让电机正转还是反转就由这两组引脚来决定。每一组控制一个电机。它的逻辑关系由一个“真值表”定义咱们以A电机为例AIN1AIN2电机A状态00停止刹车01正转10反转11停止刹车注意这里的“0”代表低电平GND“1”代表高电平3.3V。B电机的控制逻辑BIN1/BIN2和A电机完全一样。第三关速度控制PWMA, PWMB方向定了速度怎么调就靠这两个引脚了。我们需要给PWMA和PWMB输入PWM信号。PWM信号的占空比高电平时间占整个周期的比例决定了电机的平均电压从而控制转速。占空比越大电机转得越快。2. 硬件连接把梁山派和TB6612“手牵手”理解了原理现在开始动手接线。请对照下面的表格将梁山派和TB6612模块用杜邦线连接起来。TB6612FNG 引脚梁山派 GD32F470 引脚说明GNDGND共地必须接VCC5V模块逻辑电源接5V输出。VM外部电源正极接你的电机电源如电池正极范围3.7V-12V。PWMAPA5A电机PWM速度控制复用为TIMER1_CH0。AIN1PC1A电机方向控制信号1。AIN2PC2A电机方向控制信号2。STBY3V3使能引脚接3.3V使其常启用。PWMBPA6B电机PWM速度控制复用为TIMER2_CH1。BIN1PC3B电机方向控制信号1。BIN2PF8B电机方向控制信号2。AO1/AO2电机A的两根线接你要控制的第一个电机。BO1/BO2电机B的两根线接你要控制的第二个电机。重要提示电机电源VM和逻辑电源VCC一定要分开供电。VM接动力电池VCC接梁山派的5V。千万不要只接一个接线时务必断电操作确认无误后再上电。电机接口AO1/AO2, BO1/BO2没有正负之分交换接线只会改变电机默认的“正转”方向。3. 软件驱动编写从零构建控制程序硬件连好了接下来就是让梁山派“发号施令”。我们要创建一个电机驱动的板级支持包BSP。3.1 创建头文件定义引脚和函数首先我们创建bsp_tb6612.h文件。这个文件的主要作用是进行宏定义把上面表格里的硬件连接关系用代码固定下来并声明我们即将要用的函数。#ifndef _BSP_TB6612_H #define _BSP_TB6612_H #include gd32f4xx.h #include systick.h /* A电机方向控制引脚定义 */ #define RCU_AIN1 RCU_GPIOC #define PORT_AIN1 GPIOC #define GPIO_AIN1 GPIO_PIN_1 #define RCU_AIN2 RCU_GPIOC #define PORT_AIN2 GPIOC #define GPIO_AIN2 GPIO_PIN_2 /* B电机方向控制引脚定义 */ #define RCU_BIN1 RCU_GPIOC #define PORT_BIN1 GPIOC #define GPIO_BIN1 GPIO_PIN_3 #define RCU_BIN2 RCU_GPIOF #define PORT_BIN2 GPIOF #define GPIO_BIN2 GPIO_PIN_8 /* A电机PWM引脚定义 (PA5 - TIMER1_CH0) */ #define RCU_PWMA RCU_GPIOA #define PORT_PWMA GPIOA #define GPIO_PWMA GPIO_PIN_5 #define AF_PWMA GPIO_AF_1 // PA5的复用功能AF1对应TIMER1 #define RCU_PWMA_TIMER RCU_TIMER1 #define BSP_PWMA_TIMER TIMER1 #define BSP_PWMA_CHANNEL TIMER_CH_0 /* B电机PWM引脚定义 (PA6 - TIMER2_CH1) */ #define RCU_PWMB RCU_GPIOA #define PORT_PWMB GPIOA #define GPIO_PWMB GPIO_PIN_6 #define AF_PWMB GPIO_AF_2 // PA6的复用功能AF2对应TIMER2 #define RCU_PWMB_TIMER RCU_TIMER2 #define BSP_PWMB_TIMER TIMER2 #define BSP_PWMB_CHANNEL TIMER_CH_1 /* 方向控制引脚输出宏函数方便使用 */ #define AIN1_OUT(X) gpio_bit_write(PORT_AIN1, GPIO_AIN1, X?SET:RESET) #define AIN2_OUT(X) gpio_bit_write(PORT_AIN2, GPIO_AIN2, X?SET:RESET) #define BIN1_OUT(X) gpio_bit_write(PORT_BIN1, GPIO_BIN1, X?SET:RESET) #define BIN2_OUT(X) gpio_bit_write(PORT_BIN2, GPIO_BIN2, X?SET:RESET) /* 函数声明 */ void TB6612_Init(uint16_t pre, uint16_t per); void AO_Control(uint8_t dir, uint32_t speed); void BO_Control(uint8_t dir, uint32_t speed); #endif /* _BSP_TB6612_H */3.2 编写初始化函数配置GPIO和定时器这是最核心的部分在bsp_tb6612.c文件中实现。TB6612_Init函数负责初始化所有用到的GPIO和定时器以产生PWM信号。#include bsp_tb6612.h #include systick.h /** * brief TB6612模块初始化 * param pre: 定时器预分频值 * param per: 定时器周期值 * note PWM频率计算公式 200MHz / ((pre1) * (per1)) * 例如 pre9, per19999则频率 200,000,000 / (10*20000) 1kHz */ void TB6612_Init(uint16_t pre, uint16_t per) { timer_parameter_struct timer_initpara {0}; // 定时器基本参数结构体 timer_oc_parameter_struct timer_ocinitpara {0}; // 定时器输出比较参数结构体 /* 1. 开启相关外设时钟 */ rcu_periph_clock_enable(RCU_PWMA_TIMER); // 开启TIMER1时钟 rcu_periph_clock_enable(RCU_PWMB_TIMER); // 开启TIMER2时钟 rcu_periph_clock_enable(RCU_AIN1); // 开启GPIOC时钟AIN1, AIN2, BIN1 rcu_periph_clock_enable(RCU_BIN2); // 开启GPIOF时钟BIN2 // 注意RCU_AIN1等宏已定义为RCU_GPIOC所以AIN1/AIN2/BIN1时钟已开启 // BIN2在PF8需要单独开启GPIOF时钟这里用RCU_BIN2即RCU_GPIOF /* 配置定时器时钟源为系统时钟4分频 */ rcu_timer_clock_prescaler_config(RCU_TIMER_PSC_MUL4); /* 2. 配置PWM引脚为复用推挽输出模式 */ // 配置PA5 (PWMA) 为复用功能高速输出 gpio_mode_set(PORT_PWMA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PWMA); gpio_output_options_set(PORT_PWMA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PWMA); gpio_af_set(PORT_PWMA, AF_PWMA, GPIO_PWMA); // 复用为TIMER1 // 配置PA6 (PWMB) 为复用功能高速输出 gpio_mode_set(PORT_PWMB, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PWMB); gpio_output_options_set(PORT_PWMB, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PWMB); gpio_af_set(PORT_PWMB, AF_PWMB, GPIO_PWMB); // 复用为TIMER2 /* 3. 配置方向控制引脚为普通推挽输出模式 */ gpio_mode_set(PORT_AIN1, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_AIN1); gpio_output_options_set(PORT_AIN1, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_AIN1); gpio_mode_set(PORT_AIN2, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_AIN2); gpio_output_options_set(PORT_AIN2, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_AIN2); gpio_mode_set(PORT_BIN1, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_BIN1); gpio_output_options_set(PORT_BIN1, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_BIN1); gpio_mode_set(PORT_BIN2, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_BIN2); gpio_output_options_set(PORT_BIN2, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_BIN2); /* 4. 配置定时器基础参数 (TIMER1 TIMER2) */ timer_deinit(BSP_PWMA_TIMER); // 复位TIMER1 timer_deinit(BSP_PWMB_TIMER); // 复位TIMER2 timer_initpara.prescaler pre - 1; // 预分频值 timer_initpara.alignedmode TIMER_COUNTER_EDGE; // 边沿对齐模式 timer_initpara.counterdirection TIMER_COUNTER_UP; // 向上计数 timer_initpara.period per - 1; // 自动重装载值周期 timer_initpara.clockdivision TIMER_CKDIV_DIV1; // 时钟分频 timer_initpara.repetitioncounter 0; // 重复计数器高级定时器用 timer_init(BSP_PWMA_TIMER, timer_initpara); timer_init(BSP_PWMB_TIMER, timer_initpara); /* 5. 配置定时器输出比较参数PWM模式 */ timer_ocinitpara.ocpolarity TIMER_OC_POLARITY_HIGH; // 输出极性高 timer_ocinitpara.outputstate TIMER_CCX_ENABLE; // 使能通道输出 // 以下参数主要针对高级定时器的互补通道基础定时器配置即可 timer_ocinitpara.ocnpolarity TIMER_OCN_POLARITY_HIGH; timer_ocinitpara.outputnstate TIMER_CCXN_DISABLE; timer_ocinitpara.ocidlestate TIMER_OC_IDLE_STATE_LOW; timer_ocinitpara.ocnidlestate TIMER_OCN_IDLE_STATE_LOW; timer_channel_output_config(BSP_PWMA_TIMER, BSP_PWMA_CHANNEL, timer_ocinitpara); timer_channel_output_config(BSP_PWMB_TIMER, BSP_PWMB_CHANNEL, timer_ocinitpara); /* 6. 设置初始占空比为0电机不转 */ timer_channel_output_pulse_value_config(BSP_PWMA_TIMER, BSP_PWMA_CHANNEL, 0); timer_channel_output_pulse_value_config(BSP_PWMB_TIMER, BSP_PWMB_CHANNEL, 0); /* 7. 配置PWM模式 */ timer_channel_output_mode_config(BSP_PWMA_TIMER, BSP_PWMA_CHANNEL, TIMER_OC_MODE_PWM0); timer_channel_output_mode_config(BSP_PWMB_TIMER, BSP_PWMB_CHANNEL, TIMER_OC_MODE_PWM0); /* 8. 使能定时器自动重载影子寄存器高级定时器相关 */ timer_auto_reload_shadow_enable(BSP_PWMA_TIMER); timer_auto_reload_shadow_enable(BSP_PWMB_TIMER); /* 9. 主输出使能高级定时器必需基础定时器配置也无妨 */ timer_primary_output_config(BSP_PWMA_TIMER, ENABLE); timer_primary_output_config(BSP_PWMB_TIMER, ENABLE); /* 10. 最后使能定时器开始产生PWM波形 */ timer_enable(BSP_PWMA_TIMER); timer_enable(BSP_PWMB_TIMER); }这个函数看起来长其实逻辑很清晰开时钟 - 配引脚 - 设定时器 - 开PWM。其中pre和per两个参数决定了PWM的频率你可以根据电机特性调整。3.3 编写电机控制函数让电机动起来初始化完成后我们需要一个简单的函数来控制单个电机的转向和速度。/** * brief 控制A端口电机 * param dir: 方向1正转0反转 * param speed: 速度范围 0 ~ (per-1)。0最慢停止(per-1)最快。 */ void AO_Control(uint8_t dir, uint32_t speed) { if(dir 1) // 正转 { AIN1_OUT(0); // AIN1 0 AIN2_OUT(1); // AIN2 1 } else // 反转 { AIN1_OUT(1); // AIN1 1 AIN2_OUT(0); // AIN2 0 } // 更新PWM占空比控制速度 timer_channel_output_pulse_value_config(BSP_PWMA_TIMER, BSP_PWMA_CHANNEL, speed); } /** * brief 控制B端口电机 * param dir: 方向1正转0反转 * param speed: 速度范围 0 ~ (per-1) */ void BO_Control(uint8_t dir, uint32_t speed) { if(dir 1) // 正转 { BIN1_OUT(0); // BIN1 0 BIN2_OUT(1); // BIN2 1 } else // 反转 { BIN1_OUT(1); // BIN1 1 BIN2_OUT(0); // BIN2 0 } // 更新PWM占空比控制速度 timer_channel_output_pulse_value_config(BSP_PWMB_TIMER, BSP_PWMB_CHANNEL, speed); }这两个函数非常直观先根据dir参数设置方向控制引脚的电平组合参照之前的真值表然后通过timer_channel_output_pulse_value_config函数设置PWM的脉冲值这个值就是占空比。speed的值越大占空比越高电机转速越快。4. 实战测试让电机转起来驱动代码写好了最后一步就是在主函数里调用它们看看电机能不能听话。#include gd32f4xx.h #include systick.h #include bsp_tb6612.h int main(void) { /* 系统初始化 */ nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); // 设置中断优先级分组 systick_config(); // 初始化系统滴答定时器用于delay_ms函数 /* 初始化TB6612驱动 * 参数 pre10, per8000 * PWM频率 200MHz / ((101)*(80001)) ≈ 2272 Hz * 速度范围0 ~ 7999 */ TB6612_Init(10, 8000); while(1) { /* 示例1A电机加速正转 */ for(int speed 0; speed 8000; speed 100) { AO_Control(1, speed); // 正转速度递增 delay_1ms(50); // 延时50ms让速度变化可见 } /* 示例2A电机减速反转 */ for(int speed 8000; speed 0; speed - 100) { AO_Control(0, speed); // 反转速度递减 delay_1ms(50); } // 你可以在这里添加B电机的控制代码让两个电机协同工作 // BO_Control(1, 4000); // 例如B电机以一半速度正转 } }把代码编译下载到梁山派开发板上电后连接到TB6612模块A通道的电机就应该开始旋转了。它会先慢慢加速正转达到最高速后再慢慢减速反转如此循环。如果电机不转别着急按这个顺序排查查电源VM和VCC都接对了吗用万用表量一下电压。查接线对照引脚连接表一根一根线检查特别是GND。查代码确认PWM引脚PA5, PA6和方向引脚PC1, PC2等的定义和初始化代码是否正确。查电机直接把电机接到电池上看电机本身是不是好的。到这里你已经成功用梁山派GD32F470驱动了TB6612电机模块。这套驱动代码结构清晰你可以轻松地把它用到你的智能小车或机器人项目中通过调整AO_Control和BO_Control函数的参数实现复杂的运动控制。