news 2026/9/3 6:42:22

STM32串口控制TB6612驱动直流电机转速实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32串口控制TB6612驱动直流电机转速实战

简介:基于STM32与TB6612的串口控制直流电机转速项目资料包,主要面向嵌入式初学者、电机控制方向的在校学生及电子竞赛备赛者。资源围绕UART串口通信与PWM调速技术展开,内容涵盖STM32F4标准外设库的初始化流程、直流电机转速与电压关系、TB6612驱动芯片的引脚时序,以及串口指令解析与控制策略。压缩包共249个文件,包含STM32F4系列定时器、复位、ADC等外设驱动源码,以及编译生成的.o/.axf/.hex文件、Keil MDK工程配置文件和PDF说明文档,整体仅1.39MB。已有751人学习下载,资料紧凑完整,可直接编译运行。工程中实现UART波特率、数据位、校验位等参数配置,并利用定时器产生PWM信号调节电机电压,通过串口发送命令即可改变转速。同时包含GPIO控制、故障保护等关键模块,适合作为课程设计、毕业设计或入门实战的完整参考。 先把这个项目定性:这是一个典型的嵌入式入门到进阶的“小闭环”实验——上位机通过串口发指令,单片机解析指令后,用PWM信号驱动TB6612模块,最终控制直流电机的转速。类似的项目在课设、竞赛、个人DIY里出镜率极高,但很多人在复现的时候卡在“能转”和“转得稳”之间,这篇文章就把从头到尾的坑和细节摊开讲清楚。

我拿到的是压缩包文件“8.串口控制直流电机转速(TB6612电机驱动).7z”,解压之后就是完整的工程源码、原理图还有使用说明,后面的所有内容都围绕这套工程展开。这套方案非常适合刚学完STM32基础外设、想串一个完整链路的同学,也适合做小车、做云台、做小型自动化设备时需要一套稳的调速方案的人。

1. 项目全景与技术选型

1.1 这个项目到底解决了什么问题

很多人第一次接触电机控制,是从“GPIO翻转电平让它转”开始的。但实际应用场景里,电机要么转要么停远远不够,你需要的是“让它以每小时多少转的速度稳定转”,或者“让它根据传感器数据实时调整速度”。这就引出了两个核心需求:一是要能人为设定目标转速,二是要能让转速尽量贴近目标值。

串口控制直流电机转速,解决的正是第一个需求——给用户一个方便、高效、可扩展的调速接口。你看市面上所有正经的电机控制器,无论是商业的驱动器还是开源的ODrive,默认标配都是串口/CAN通信。按键调速和电位器调速只适合现场手动操作,一旦你要做自动化,让上位机或者传感器根据条件自动调转速,串口几乎是性价比最高的选择。硬件上你只占一个UART外设,代码上只需要一个结构体存目标值,剩下的计算和处理全丢给单片机,这个思路会被后面所有的项目继续复用。

1.2 为什么选串口加TB6612这条技术路线

先看驱动芯片。市面上常用的电机驱动方案有三类:L298N、TB6612、分立MOS管H桥。L298N是老古董了,压降大(两个管压降加起来奔着2V以上去了),发热严重,而且逻辑电平还不兼容3.3V单片机,需要额外的电平转换。分立MOS管H桥性能最好,但需要自己搭栅极驱动、死区电路、防共态导通保护,对新手来说门槛太高,调试周期长。

TB6612就是那个“中间项”。它内部集成了两个完整的H桥,逻辑输入直接兼容3.3V,可以直接接STM32的GPIO,不需要电平转换芯片。输出电流在平均1.2A、峰值3.2A的水平,驱动常见的N20减速电机、370电机、TT马达都够用。更重要的是这个芯片自带续流二极管、过流保护电路,外围只需要几个滤波电容,不用自己折腾烧脑的保护电路。在这里选它,主要图的就是“开发效率优先,性能够用就好”。如果你后续要做大功率电机(几十瓦以上),再考虑换大电流驱动器或者分立MOS方案。

串口通信这块,UART是最经典的选择。I2C和SPI虽然速度更快,但四线制(SCL/SDA/CS等)接起来麻烦,调试也不直观。你写一个串口调试助手,发个字符串就完事了,逻辑非常透。USB转串口芯片用CH340就够了,Windows自动识别驱动,硬件成本几块钱,新手折腾起来没压力。

2. 硬件接线与TB6612驱动原理

2.1 TB6612模块的核心引脚与内部结构

TB6612模块拿出来,你第一眼看到的是一排排针。有些是逻辑引脚,有些是电源引脚,有些是电机引脚。如果你不完全清楚每个引脚是干什么的,后面接线一定会乱。先花两分钟把引脚功能理顺。

从功能上分,TB6612FNG芯片内部就是两个独立H桥(对应两颗电机),每个H桥需要三路信号控制:一条PWM调速信号(PWMA),两条方向信号(AIN1、AIN2)。还有一个STBY总使能引脚——这个极其关键,很多新手忘了拉高STBY,结果芯片完全不工作,PWM再怎么发都没用。

H桥的原理可以用一个很直观的比喻来理解,就像四个开关,把电机夹在中间。开关要么让电流从A端流到B端(正转),要么从B流到A端(反转)。控制H桥的导通方向,就控制了电机旋转方向。调节开关切换的速度(即PWM的占空比),就调节了平均电压,从而调节了转速。TB6612把这四个开关集成到一个芯片里,还用MOS管替代了传统H桥的三极管,压降小、发热少,效率高得多。

2.2 完整接线清单(以STM32F103C8T6为例)

下面给出我和这套工程采用的接线方式,你可以直接照抄。控制板用的是最常见的STM32F103C8T6“蓝丸”最小系统板,PWM选用定时器TIM3的通道1和通道2,串口选用USART1,具体引脚关系如下表:

功能TB6612引脚STM32引脚备注
电机A方向AIN1PA4普通GPIO输出,高低电平控制方向
电机A方向AIN2PA5普通GPIO输出,与AIN1配合
电机A调速PWMAPA6TIM3_CH1,输出PWM
电机B方向BIN1PA7普通GPIO输出
电机B方向BIN2PB0普通GPIO输出
电机B调速PWMBPB1TIM3_CH2,输出PWM
总使能STBYPB2必须拉高,否则模块不工作
逻辑电源VCC3.3V给芯片逻辑部分供电
电机电源VM5V电机供电,可从板子5V引脚取,或外接电源
公共地GNDGND必须共地,否则通信不工作

有两点特别提醒。第一,VM和VCC不是同一个电源,VM是电机电源,VCC是逻辑电源。很多模块上VCC支持2.7V到5.5V,可以直接接单片机的3.3V或5V;但VM要接到电机的供电轨上,一般5V到12V。如果你给电机用单独电源,电源的负极必须和单片机的GND连在一起,这是整个系统稳定的前提,不共地你会看到转速乱跳、串口乱码这些莫名其妙的问题。第二,如果使用外接电源给VM供电,建议在VM和GND之间并联一个100uF到470uF的电解电容,电机启动瞬间电流非常大,不加电容会把电源电压拉垮,严重的时候会直接让单片机复位。

2.3 电机方向和刹车逻辑

TB6612的控制逻辑说白了就是一个真值表。把AIN1和AIN2的状态整理一下,你会发现所有的可能性都涵盖了:

AIN1AIN2电机状态
00刹车(短接,惰性停止)
01反转
10正转
11停止(放开,惯性滑行)

重点说下“刹车”和“停止”的区别。AIN1=AIN2=0时,两个下管同时导通,电机两端直接短路,相当于用电磁力快速制动,适合需要急停的场景。AIN1=AIN2=1时,两个上管同时导通,电机两端都接正电源,电机会因为惯性继续转(滑行),慢慢停下来。这两个状态在PID调速闭环里很重要——当你需要快速响应目标转速变化时,可以用刹车状态辅助减速;平时不需要急停就用停止状态。工程代码里我封装了三个函数:Motor_SetSpeed、Motor_SetDirection、Motor_Brake,实际用起来非常顺手。

3. 软件设计与核心代码实现

3.1 串口通信协议怎么设计才不乱

串口通信本质上就是一个字节一个字节地传,但裸传字节没有边界,接收端怎么知道“A指令结束、B指令开始”?所以必须自己定义一套能区分边界的数据格式。这个工程里采用的就是最简单、最经典也最实用的帧格式:帧头+数据+校验+=+结尾。

我定义了一套ASCII字符串协议,原因是方便调试,直接用串口助手肉眼就能看出来问题。

数据段长度说明
帧头2字节固定为0xAA 0x55,用于检测一帧的开始
长度1字节后面数据的字节数
命令字1字节0x01=设置目标转速,0x02=设置方向,0x03=请求当前状态
数据N字节转速值的低字节、高字节,或其他参数
校验1字节前面所有字节的和校验(只保留低8位)
帧尾2字节固定为0x0D 0x0A,即\r\n,方便调试

你可能觉得这个协议有点“过度设计”,但实际真不是。如果你只用单个字节传转速,比如发送0x80表示“转128”,那你想传255是没问题,可如果串口多了一个字节(比如静电干扰、接触不良),整个数据流就会错位,后面所有指令全乱了。带帧头帧尾加校验的协议虽然多几个字节,但可靠性提升了一个数量级,而且这套协议模板以后做任何串口项目都能直接套用,属于一次投资终身受益。

3.2 STM32串口接收解析的状态机

串口数据接收,核心问题是“什么时候知道一帧数据收完了”。这里有两种思路,一种是定长接收,一种是可变长+超时判断。我用的是比较通用的状态机法,每收到一个字节就根据当前状态去判断它属于哪一段,代码结构清晰,而且不会因为阻塞在某个while(等待接收完成)里把系统拖死。

状态机有四个状态:寻找帧头、接收长度、接收数据、接收校验。收到一个字节就切一个状态,逻辑严谨。下面是核心代码(完整工程里已在STM32F103上验证):

typedef enum { STATE_FRAME_HEADER1 = 0x00, STATE_FRAME_HEADER2, STATE_FRAME_LENGTH, STATE_FRAME_CMD, STATE_FRAME_DATA, STATE_FRAME_CHECK, } UART_RX_STATE; uint8_t rx_buf[64]; uint8_t rx_len = 0; uint8_t rx_index = 0; uint8_t rx_checksum = 0; void UART1_IRQHandler(void) { uint8_t ch = 0; if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { ch = USART_ReceiveData(USART1); rx_state_machine(ch); } } void rx_state_machine(uint8_t ch) { static uint8_t state = STATE_FRAME_HEADER1; switch (state) { case STATE_FRAME_HEADER1: if (ch == 0xAA) state = STATE_FRAME_HEADER2; else state = STATE_FRAME_HEADER1; // 没有帧头就继续找 break; case STATE_FRAME_HEADER2: if (ch == 0x55) { state = STATE_FRAME_LENGTH; rx_checksum = 0xAA + 0x55; } else { state = STATE_FRAME_HEADER1; } break; case STATE_FRAME_LENGTH: rx_len = ch; rx_checksum += ch; rx_index = 0; if (rx_len > 0 && rx_len <= 60) { state = STATE_FRAME_CMD; } else { state = STATE_FRAME_HEADER1; } break; case STATE_FRAME_CMD: rx_buf[rx_index++] = ch; rx_checksum += ch; state = STATE_FRAME_DATA; break; case STATE_FRAME_DATA: rx_buf[rx_index++] = ch; rx_checksum += ch; if (rx_index >= rx_len - 1) { // 减1是因为包含命令字本身 state = STATE_FRAME_CHECK; } break; case STATE_FRAME_CHECK: if (ch == rx_checksum) { // 校验通过,解析命令 parse_cmd(rx_buf, rx_len); } state = STATE_FRAME_HEADER1; break; default: state = STATE_FRAME_HEADER1; break; } }

注意,UART1的中断优先级建议设置为最低或次低,因为状态机很轻量,不会阻塞太长的时间。如果你同时跑着PWM中断和串口中断,一定要理清优先级,避免PWM更新频率被串口干扰。另外,如果你的单片机主频高,还可以用串口DMA+空闲中断的收包方式,性能更好,参数也更好看。但对于这个项目,中断收字节完全够用,不必为了炫技把复杂度拉上去。

3.3 PWM调速与方向控制的定时器配置

到了PWM环节,需要讲点配置原理。PWM调速的本质是“斩波”调压——频率固定,把高电平占一个周期的时间比例调高或调低,电机的平均电压就变了,转速也就跟着变。真正需要决定的是两个数值:频率和占空比。

先说占空比,这是转速的直接映射。我在代码里把PWM周期设为999(ARR=999),这样占空比计算特别方便,CCR的值直接就是“千分比”。比如你要设置50%转速,CCR就填500,非常直观。再说频率,我在工程里用的是10kHz。这个频率选择是有讲究的:低于5kHz会有可闻的电机啸叫,高于20kHz会显著增加MOS管的开关损耗。10kHz到20kHz之间是比较甜点的区间,既能避开人耳敏感频段,又能平衡开关损耗。

下面是TIM3输出PWM的配置代码(生成两个通道的PWM,分别控制两个电机):

void PWM_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; // 使能时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE); // PA6、PA7 复用为TIM3_CH1、TIM3_CH2 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); TIM_TimeBaseStructure.TIM_Prescaler = 72 - 1; // 72MHz / 72 = 1MHz TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseStructure.TIM_Period = 1000 - 1; // 1MHz / 1000 = 1kHz,即PWM频率1kHz TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse = 0; TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OC1Init(TIM3, &TIM_OCInitStructure); TIM_OC2Init(TIM3, &TIM_OCInitStructure); TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable); TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable); TIM_Cmd(TIM3, ENABLE); }

上面这段是基础版,实际工程里频率用10kHz,你把Prescaler改成8-1、Period设为899(即9MHz/900=10kHz)就行。我把Prescaler和Period的计算方式再拆解一下:系统主时钟72MHz,分频到9MHz(预分频8分频),再计数到899(900个计数周期),每个计数周期发出一个PWM脉冲,最终频率就是10kHz。如果你要改频率,记得公式:PWM频率 = 主时钟频率 / (Prescaler+1) / (Period+1),不用背,现场算就行。

方向和转速的联动逻辑我写成一个函数:

void Motor_SetSpeed(uint8_t motor_id, int16_t speed) { if (motor_id == MOTOR_A) { if (speed > 0) { AIN1_Level = 1; AIN2_Level = 0; TIM_SetCompare1(TIM3, speed > 1000 ? 1000 : speed); } else if (speed < 0) { AIN1_Level = 0; AIN2_Level = 1; TIM_SetCompare1(TIM3, (-speed) > 1000 ? 1000 : (-speed)); } else { AIN1_Level = 0; AIN2_Level = 0; TIM_SetCompare1(TIM3, 0); } } else { // 同理处理电机B } }

为了让代码更健壮,我给speed加了限幅,保证TIM_SetCompare1的输入值不会超过ARR的值。否则CCR大于ARR时,PWM会一直输出高电平,看起来是转疯了,其实是配置出了问题。

3.4 进阶扩展:从开环调速到PID闭环控制

工程里默认是开环控制——也就是说,你发指令设定一个转速,单片机就输出对应的PWM占空比,电机实际转多快不反馈、不修正。这在空载、轻载条件下基本够用,但只要你给电机加上负载(比如小车走在粗糙地面上),转速就会往下掉,开环控制就“失准”了。

这时候就需要闭环控制。最简单也最有效的方案是PID。在一个电机调速系统里,PID的三个参数各有分工:比例项Kp直接反映当前误差,误差大就输出大;积分项Ki负责消除静态误差,让转速最终能精确贴合目标值;微分项Kd负责抑制超调,让转速不会在目标值附近激烈震荡。

硬件上你需要一个转速反馈源,最常用的是霍尔编码器。编码器每转一圈输出一定数量的脉冲(比如13线编码器通过倍频后能得到52个脉冲/圈),单片机通过定时器捕获模式计算单位时间内的脉冲数,就换算出实时转速。然后PID算法根据“目标转速”和“当前转速”的差值,实时调整PWM占空比。实际做下来,编码器加速度环PID,转速稳定度能控制在±1%以内,这在小车控制和云台控制里已经非常能打了。

PID调参的核心口诀是:先调Kp,让系统响应变快但不震荡;再加Ki,消除静态误差;最后加Kd,压住超调。如果你是从零开始,建议在串口助手里打印出目标值和实时值,做成曲线图,边看边调,效率比自己瞎猜高得多。Windows下用SerialPlot这类免费的串口绘图工具,比用眼睛看数字直观太多了,强烈推荐。

4. 实操调试与常见问题排查

4.1 从烧录到跑通的完整流程

拿到这个工程后,最快跑通的步骤是这样的:

  1. 安装CH340驱动,用USB转TTL模块连接STM32的USART1(PA9为TX、PA10为RX,交叉连接),打开设备管理器确认端口号。
  2. 打开串口调试助手,波特率设成115200,8位数据、1位停止位、无校验(这个要和代码里的USART_Init配置完全一致,不然收到的是乱码)。
  3. 把STM32和TB6612模块按上面表格接好线,电机接在模块的A路输出上。
  4. 给板子供电,下载程序(可以通过串口ISP下载或者ST-Link SWD下载)。
  5. 在串口助手发送一帧数据:AA 55 03 01 D0 07 08 0D 0A。这帧数据的含义是:设置电机A的正转,目标转速0x07D0(即2000,转速单位自行定义)。

如果一切正常,电机应该开始转动,转速逐渐升高并稳定在2000附近。你可以试着改变转速值再发一次,观察电机的响应。

首次跑通时建议把所有指示灯都利用起来:程序上电时点亮板载LED表示系统初始化成功,串口收到一帧有效指令时翻转一次LED状态,这样你调试的时候肉眼就能看到串口数据是否被正确接收,而不是电机不转但不知道是没收到指令还是指令解析失败。

4.2 常见问题速查表

我在调试过程中把过去踩过、朋友们踩过的坑都整理了一遍,汇总成下面的速查表,你遇到问题先对号入座:

现象可能原因排查方法
电机完全不动STBY没拉高检查STBY引脚是否接PB2,并拉高到3.3V
电机完全不动VM没供电压测量VM引脚和GND之间是否有5-12V
电机完全不动串口没收到数据用USB转TTL直接发数据,看板载LED是否闪烁
电机只朝一个方向转AIN1/AIN2逻辑接反互换两个方向引脚,或调整代码里的方向配置
电机抖动、转速不稳PWM频率过低检查定时器配置,把PWM频率提高到10kHz以上
串口显示乱码波特率不匹配检查代码里的波特率和串口助手是否一致
串口显示乱码接线太长/干扰缩短杜邦线,或用屏蔽线
电机转速达不到最大值电源功率不足换大电流电源,或用外接电源给VM供电
模块发烫电机电流超额定减轻负载或用更大驱动能力的驱动器
一启动电机单片机就复位电源被拉垮在VM加电解电容,或独立给电机供电

4.3 几个值得记住的调试技巧

最后分享几个我在实际调试中总结的小技巧。

第一个,串口助手发送字符串和发送HEX的区别要搞清楚。如果你定义的是二进制协议,就必须用HEX模式发送,AA和55不能打成ASCII字符“AA55”。字符串协议虽然直观,但解析效率稍微低一点点,且传中文不方便;二进制协议省带宽、速度快,但调试时眼睛不那么友好。建议学有余力的话,两种协议都实现一下,这对你理解数据通信的本质帮助很大。实际工程里,我一般会用二进制协议传输实时数据(如转速、电流采样),用字符串协议传输调试命令,两者共存、互不干扰。

第二个,调试PWM时,一定先把方向调到正转,确认电机正常转起来,再去调速度和PID,不要一上来就全上。因为你没法确定是方向问题还是速度问题,一次性暴露的变量越多,越难定位故障。

第三个,硬件上给模块的电源和信号线不要走同一束杜邦线,特别是电机电流比较大时,会产生电磁干扰,影响PWM信号的波形质量。如果你的电机是碳刷电机,建议在电机两端并联一个小容量瓷片电容(104),能明显减少换向火花产生的EMI,串口乱码的几率也会小很多。

第四个,PCB布局的排针定义千万别想当然。市面上TB6612模块虽然引脚标注基本一致,但有的兼容板会把VM和VCC的顺序颠倒,拿到模块第一步是拿万用表通断档打一遍,确认每个引脚连接正确再上电,不然可能烧板子。

这个项目看似简单的“串口控制调速”,实际上已经把嵌入式开发里最经典的“上位机通信+外设驱动+PWM调速”整条链路走了一遍。你把这条链路吃透了,后面做PID闭环、做无线遥控、做上位机可视化监控,都只是在当前基础上加零件、加模块,总体的思路和套路是完全一致的。我自己当年从这条链路走过来,最大的感受就是:串口没通之前,所有调试都是空中楼阁;串口一通,整个系统就像通了气一样,后面每一步都有反馈、都看得见摸得着。希望这篇笔记能帮你尽快走到“通了气”的那一步。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/1 9:59:51

Excel多条件筛选进阶:用COUNTIF数组技巧解决复杂或与非逻辑

你是不是也遇到过这样的场景&#xff1a;领导丢给你一份密密麻麻的销售数据表&#xff0c;要求你“把华东区和华北区&#xff0c;并且销售额大于10万的订单找出来”&#xff0c;或者更刁钻一点&#xff0c;“找出所有既不是A供应商也不是B供应商的采购记录”。你熟练地打开了“…

作者头像 李华
网站建设 2026/9/1 9:59:23

3 条命令搞定 PDF 智能翻译:PDFMathTranslate 保留排版翻译完整指南

3 条命令搞定 PDF 智能翻译:PDFMathTranslate 保留排版翻译完整指南 【免费下载链接】PDFMathTranslate [EMNLP 2025 Demo] PDF scientific paper translation with preserved formats - 基于 AI 完整保留排版的 PDF 文档全文双语翻译&#xff0c;支持 Google/DeepL/Ollama/Ope…

作者头像 李华
网站建设 2026/9/1 9:58:25

小米测试开发笔试复盘:考点拆解与备考路线

2022年秋季那场小米秋招测试开发笔试&#xff0c;我到现在还记得很清楚。那时候我在宿舍里刷完了一套往年的算法题&#xff0c;自信满满地打开笔试链接&#xff0c;结果第一道选择题就差点把我看懵——不是题目本身多难&#xff0c;而是出题方式跟普通刷题网站完全不是一个路子…

作者头像 李华