☰
STM32直流电机PWM控制实战:Proteus仿真与L293D驱动全解析
2026/9/28 7:29:47 网站建设 项目流程

带过不少做嵌入式课设的同学之后,我发现一个特别普遍的现象:点灯、按键、串口这些实验都能顺利跑通,一旦进入"用STM32控制直流电机"这个项目,很快就卡住了。有人调了一下午,最后发现是L293D的使能引脚悬空;有人示波器上波形完全正确,电机却纹丝不动,一查是电机模型的额定电压和驱动电源不匹配。这类问题在Proteus仿真里其实很好排查,因为所有网络都是可视的,比实物排查直观得多。

这篇博文,我把自己在Proteus里跑通的一整套直流电机PWM控制方案完整复盘一遍:从PWM调速原理、仿真电路搭建、定时器配置、代码编写到烧录调试和排错,按实际操作顺序来。重点给出一份可以直接编译加载的STM32F103C8T6标准库代码,并把每个关键参数是怎么算出来的讲清楚。不管你是刚接触定时器输出PWM的新手,还是正在准备课程设计想找个参考工程,都可以照着这套流程在自己电脑上复现。

1. 先把原理讲透:PWM调速为什么能控制直流电机

1.1 电机控制前必须想明白的三个问题

动手接线之前,先回答三个问题,这三个问题直接决定了整个电路方案长什么样。

第一,STM32的GPIO引脚能不能直接驱动电机?不能。STM32F103C8T6的GPIO输出电流能力大概在20mA级别,而一个普通的直流电机正常工作电流轻松到几百毫安,启动瞬间甚至能到额定电流的好几倍。直接用引脚带电机,轻则转不动,重则把引脚烧掉。

第二,PWM到底是怎么实现调速的?电机两端加的不是可变的直流电压,而是一串固定频率、脉宽可变的高电平脉冲。电机依靠自身的电感和机械惯性,对脉冲进行"平均",得到等效的直流电压。占空比越大,平均电压越高,转速越快。

第三,方向怎么控制?单片机的GPIO只能输出高或者低,想让电机反转,需要让电机两端的电压极性反过来。这需要H桥电路,或者集成H桥的驱动芯片。

这三个问题想清楚了,后面的电路和代码就顺理成章。

1.2 占空比与平均电压:PWM调速的本质

PWM的英文是Pulse Width Modulation,脉冲宽度调制。一个周期内高电平持续的时间占整个周期的比例叫占空比。用公式表达就是:

duty = T_on / T_period

假设高电平电压是U_h,那么一个周期内的平均电压大概是:

U_avg = duty x U_h

举个例子,12V电源通过PWM驱动电机,占空比50%时,电机感受到的等效电压约6V。占空比25%,等效电压约3V。这个"等效"不是瞬时成立的,而是因为电机是感性负载,线圈中的电流不会突变,硬件上天然具备低通滤波的特性,会把高频脉冲平滑成近似直流的电流。

PWM调速相比串联电阻调速、线性稳压电源调速,最大的优势是效率高。功率管只工作在完全导通和完全关断两种状态,导通时损耗小,关断时没有电流,几乎不浪费能量。这也是为什么电机调速、LED调光、舵机控制这类场景全都在用PWM。

PWM频率的选择也值得多说一句。频率太低,比如低于几百赫兹,电机会出现明显的顿挫感甚至抖动,因为电流断续太严重。频率太高,开关损耗变大,驱动芯片发热明显。直流电机调速工程上常用10kHz到20kHz,大家熟知的"无刷电机油门信号"其实也是PWM类信号。Proteus仿真中取5kHz也足够,既能保证转速平滑,又方便示波器观察。

1.3 为什么3.3V的引脚不能直接驱动12V电机

电压和电流两个维度都不匹配。GPIO高电平是3.3V,而电机额定电压可能是6V、12V甚至更高,GPIO输出一个3.3V根本达不到电机的最低启动电压。再退一步说,就算电机额定电压正好是3.3V,驱动电流也完全不够,前面已经算过,引脚输出能力只有几十毫安。

所以整个系统需要分两部分:STM32输出的是控制信号,负责"决策";驱动芯片或者驱动电路负责"出力",把控制信号放大成功率信号。驱动芯片的输入侧接3.3V逻辑信号,输出侧接电机电源和电机绕组,输入输出之间通过内部逻辑完成信号搬移。

这也是为什么市面上直流电机驱动器几乎都有一个"逻辑电源"和一个"电机电源"引脚,两个电源分别供给逻辑部分和功率部分。

2. 仿真电路搭建:元件、选型与完整连线

2.1 元件清单和Proteus搜索方法

Proteus中每个元件都有对应的库内关键词,直接搜索最快。本项目的元件清单如下:

元件Proteus搜索关键词数量作用
STM32F103C8T6STM32F103C8T61主控
L293DL293D1电机驱动
DC电机DC MOTOR1被控对象
按键BUTTON4调速、换向、启停
电解电容CAP-ELEC3电源滤波
电阻RES若干上拉/复位

Proteus中放置元件后,双击元件可以修改参数。DC Motor元件需要根据实际电源设置额定电压,这个细节后面会专门讲,也是很多人电机不转的隐藏原因。

2.2 驱动器选型:L293D比ULN2003强在哪

很多教材上喜欢用ULN2003驱动电机,但ULN2003是达林顿管阵列,内部是集电极开路结构,只能把负载一端接到电源、另一端通过管子导通到地。也就是说它只能做单向开关,没法让电机反转。用它做直流电机调速只能调速度,不能换方向。

L293D是双H桥驱动芯片,一路就是一个完整的H桥。所谓H桥,就是四个开关管接成H形,负载电机横跨中间。通过控制两组对角线上的开关管分别导通,可以让电机两端电压极性互换,从而实现正反转。H桥还有一个好处:当上下桥臂同时导通时,电机两端短接,实现刹车;当所有管子关断时,电机进入惰性滑行状态。

L293D每个通道的输出电流是600mA,峰值1.2A,驱动小型直流电机绰绰有余。最常用的控制关系如下:

1,2ENIN1IN2OUT1OUT2电机状态
PWMHL跟随PWML正转调速
PWMLHL跟随PWM反转调速
LXXLL停止
HHHHH刹车

注意看,EN使能脚接PWM信号,IN1和IN2接方向控制信号。这个接法非常重要,后面排错章节会专门展开。只要掌握了这张表,L293D的用法就吃透了。

2.3 关键连线:电源、共地、复位与时钟

电路接线的第一个原则是电源分三路,共地必须贯通。

STM32F103C8T6的VDD引脚全部接3.3V,VDDA也接3.3V,BOOT0接GND,NRST通过10k电阻上拉至3.3V,并加一个0.1uF电容到GND。L293D的VSS接5V,作为逻辑电源;VS接电机电源,我用12V。两个电源的GND全部连接在一起,同时连接到STM32的GND。如果逻辑电源和电机电源不共地,L293D内部的信号参考电位就没有建立,输出根本没法工作。

第二个原则是晶振频率要和代码匹配。Proteus中放置STM32F103C8T6后,双击芯片,把Crystal Frequency设置为8MHz,与标准库中HSE_VALUE的默认值一致。这个设置非常容易被忽略,如果你代码里按8MHz算好定时器频率,但仿真芯片实际用的是12MHz晶振配置,所有时序全部偏移,PWM频率也会对不上。

去耦电容在仿真中看着不起眼,但加上更接近真实习惯。VS和VSS引脚各加一个0.1uF和10uF电容到GND,可以明显减小电源纹波,避免仿真中出现莫名其妙的电平抖动。

2.4 引脚分配方案

我的引脚分配是这样的:

引脚功能连接目标
PA6TIM3_CH1,PWM输出L293D的1,2EN
PA7TIM3_CH2,可扩展第二路PWM空闲或备用
PB0方向控制AL293D的1A
PB1方向控制BL293D的2A
PA0加速按键按键另一端接GND
PA1减速按键按键另一端接GND
PA2正反转切换按键另一端接GND
PA3启停控制按键另一端接GND

L293D的OUT1、OUT2接电机两端。L293D的3,4EN和3A、4A这组暂不使用,把3,4EN接地,防止干扰。整个项目只用一片L293D的其中一路,另一路还能留着扩展第二台电机。

2.5 电路检查清单

连线完成后,建议按下面这个清单过一遍再进调试阶段:

  • BOOT0是否接GND,NRST是否上拉
  • L293D的VS、VSS是否都供电,GND是否所有模块共地
  • 电机是否接在OUT1和OUT2之间
  • PA6到1,2EN之间有没有断线或错误网络
  • 晶振频率是否在芯片属性中设置成8MHz
  • 4个按键是否一端接PA口、另一端接GND

这个清单能在仿真运行之前过滤掉超过一半的低级错误。

3. 定时器PWM配置:寄存器逻辑与代码实现

3.1 为什么是TIM3_CH1/CH2

STM32F103C8T6有TIM1、TIM2、TIM3、TIM4,外加两个基本定时器TIM6和TIM7。TIM1是高级定时器,带互补输出和刹车功能,适合电机控制里的三相无刷驱动器,但对单台直流电机来说资源过剩。通用定时器TIM2、TIM3、TIM4足够。

我选了TIM3,原因是它的通道1和通道2默认映射在PA6和PA7,与按键使用的PA0到PA3不冲突,以后扩展双路电机控制很方便。TIM2的CH1和CH2也支持PWM,但PA0和PA1在后续还要做按键输入,如果把它们用作PWM,按键就得挪位置,没必要给自己添麻烦。

另外要提醒一个STM32F103的老坑:APB1预分频为2时,APB1总线时钟是36MHz,但挂在APB1上的通用定时器时钟不是36MHz,而是72MHz。原因是STM32F1系列在APB1预分频系数大于1时,定时器时钟会自动倍频到APB1的2倍。很多教程里只给了72MHz这个结果,没解释原因,导致新手一算PWM频率怎么算都对不上,实际上就是在这里被绕晕了。

3.2 PWM频率与占空比的计算方法

定时器产生PWM的核心寄存器有三个:TIM_Prescaler、TIM_Period和TIM_Pulse。

  • TIM_Prescaler分频计数器,内部计数时钟先经过它分频
  • TIM_Period是自动重装载值,计数器从0数到它然后重新开始
  • TIM_Pulse是捕获比较值,计数器数到这个值触发电平翻转

PWM频率的计算公式:

F_PWM = 定时器时钟 / ((PSC + 1) x (ARR + 1))

占空比的计算公式:

duty = CCR / (ARR + 1)

我用的是PSC=14、ARR=999,定时器时钟72MHz,代入后:

F_PWM = 72MHz / (15 x 1000) = 4800Hz

占空比就直接由CCR决定,例如CCR=500时占空比约50%,CCR=0时占空比0,CCR=999时接近100%。

为什么选这个组合?4800Hz对直流电机来说不会一顿一顿,同时示波器能清楚看到完整的PWM周期,仿真体验好。如果想贴近实物习惯,可以把PSC改成9、ARR设为719,得到10kHz,公式一样,读者可以自己验算一遍。

3.3 完整代码:初始化、调速、正反转与控制逻辑

代码基于标准外设库3.5,工程创建好后只需要把下面的main.c替换进去,并在Options for Target里面勾选Create HEX File。需要确保工程中加入了stm32f10x_gpio.c、stm32f10x_tim.c、stm32f10x_rcc.c这三个外设库文件。

#include "stm32f10x.h" #include "stm32f10x_gpio.h" #include "stm32f10x_tim.h" #include "stm32f10x_rcc.h" #define PWM_ARR 999 #define PWM_PSC 14 void Delay_Ms(uint16_t ms) { volatile uint16_t i, j; for (i = 0; i < ms; i++) for (j = 0; j < 7200; j++) ; } void RCC_Config(void) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE); } void GPIO_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; /* PA6/PA7:TIM3_CH1/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); /* PB0/PB1:方向控制,普通推挽输出 */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); /* PA0~PA3:按键输入,上拉输入,按下读到低 */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(GPIOA, &GPIO_InitStructure); } void TIM3_PWM_Config(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; TIM_TimeBaseStructure.TIM_Period = PWM_ARR; TIM_TimeBaseStructure.TIM_Prescaler = PWM_PSC; TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; 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_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable); TIM_Cmd(TIM3, ENABLE); } void Motor_Forward(void) { GPIO_SetBits(GPIOB, GPIO_Pin_0); GPIO_ResetBits(GPIOB, GPIO_Pin_1); } void Motor_Reverse(void) { GPIO_ResetBits(GPIOB, GPIO_Pin_0); GPIO_SetBits(GPIOB, GPIO_Pin_1); } void Motor_Brake(void) { GPIO_SetBits(GPIOB, GPIO_Pin_0); GPIO_SetBits(GPIOB, GPIO_Pin_1); } void Motor_Stop(void) { GPIO_ResetBits(GPIOB, GPIO_Pin_0); GPIO_ResetBits(GPIOB, GPIO_Pin_1); } int main(void) { uint16_t speed = 0; uint8_t dir = 1; uint8_t run = 0; RCC_Config(); GPIO_Config(); TIM3_PWM_Config(); Motor_Stop(); while (1) { if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == RESET) /* 加速 */ { if (speed < PWM_ARR) speed += 50; if (speed > PWM_ARR) speed = PWM_ARR; TIM_SetCompare1(TIM3, speed); while (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == RESET); } if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1) == RESET) /* 减速 */ { if (speed >= 50) speed -= 50; else speed = 0; TIM_SetCompare1(TIM3, speed); while (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1) == RESET); } if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_2) == RESET) /* 换向 */ { speed = 0; TIM_SetCompare1(TIM3, speed); Delay_Ms(20); /* 先让电机停下来再换向 */ if (dir == 1) { dir = 0; Motor_Reverse(); } else { dir = 1; Motor_Forward(); } while (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_2) == RESET); } if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_3) == RESET) /* 启停 */ { run = !run; if (run) { if (dir == 1) Motor_Forward(); else Motor_Reverse(); TIM_SetCompare1(TIM3, speed); } else { speed = 0; TIM_SetCompare1(TIM3, speed); Motor_Stop(); } while (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_3) == RESET); } } }

代码逻辑拆开说几段。GPIO_Config里PA6和PA7配置成复用推挽输出,这样才能把定时器内部产生的波形送到引脚上,普通推挽输出是引不出来PWM的。PB0和PB1配置成普通推挽输出,用来输出方向电平。PA0到PA3配置成上拉输入,按键按下时引脚读到低电平,和我的按键接线方式呼应。

TIM3_PWM_Config里的关键是PWM模式选择。我用的PWM1模式,在这个模式下,计数器CNT小于CCR时输出有效电平,大于CCR时输出无效电平,配合TIM_OCPolarity_High就得到正占空比波形。理解这个对应关系,后面示波器上看到波形就能立刻判断配置是否正确。

主循环里的逻辑很直白,但有一个细节值得注意:换向操作时,先把speed清零,再延时,最后才切换方向。这是模拟实物操作时的规范流程,因为电机带着转速突然反接电源,冲击电流很大,对驱动芯片和机械结构都不友好。仿真中体现不出来,但习惯应该从仿真就开始养成。

4. 烧录仿真:Hex加载、波形验证与运行判断

4.1 Keil侧生成Hex的准备工作

代码写好后,打开Options for Target,在Output标签页勾选Create HEX File。这一步不勾,Proteus就没有程序文件可以加载。

同时确认一下Device标签页选的芯片是STM32F103C8Tx,或者至少是同系列的C8T6。选错芯片型号会导致链接脚本和Flash容量不对,虽然PWM功能不太受影响,但可能编译出奇怪的问题。

编译时如果报错缺某个外设库文件,回到工程组列表,把stm32f10x_gpio.c、stm32f10x_tim.c、stm32f10x_rcc.c添加进去。如果是在原工程上替换的代码,记得把标准库头文件路径配置对。

编译通过后,会在工程目录下的Output文件夹里生成同名.hex文件。记住一个容易踩的坑:这个hex文件的路径最好全英文。路径里只要出现中文,很多情况下Proteus加载时会静默失败,现象就是点击运行后芯片纹丝不动。

4.2 Proteus侧加载Hex和时钟设置

回到Proteus,双击原理图中的STM32F103C8T6芯片,弹出属性对话框。在Program File一栏选择刚才生成的hex文件。这一栏如果为空或者路径不对,仿真时芯片就是空的,什么都不会执行。

同时把Crystal Frequency设置为8MHz。这一步和第2.3节强调的一样,必须和代码中的HSE_VALUE匹配。标准库system_stm32f10x.c默认HSE_VALUE是8000000,也就是8MHz。如果这里设置成12MHz,PLL倍频后主频会变成108MHz,超出STM32F103的72MHz额定范围,仿真模型可能会给出警告,而且所有外设时序全部乱套。

加载完成后,点击左下角运行按钮。如果一切正常,能看到DEMO程序在控制台显示运行状态,电机模型会开始转动。如果没反应,进入下一节按顺序排查。

4.3 用示波器看PWM波形

Proteus左侧工具栏找到Virtual Instruments,添加Virtual Oscilloscope,把通道1的探头接到PA6引脚,地接GND。点击运行,再按一下加速按键,示波器上应该能看到幅值3.3V左右的方波。调节右下角的Time/Div到200us左右,可以清晰看到完整脉冲周期。

判断波形是否正确的三个点:

  • 频率对不对。4.8kHz的方波,一个周期约208us,在200us一格的时间轴上应该看到差不多一格一个周期
  • 幅值够不够。STM32引脚输出高电平是3.3V,波形顶部应该是平坦的3.3V,不会出现圆角或严重跌落
  • 占空比是否随按键变化。按加速键,高电平宽度变宽;按减速键,高电平宽度变窄。如果这两点做不到,说明PWM配置有问题,或者按键根本没被扫描到

示波器在这个项目里是最核心的调试工具。我调试这类仿真时,第一件事永远是先看信号,再看动画。很多排错只要一上示波器,问题原因就自己浮出来了。

4.4 电机转没转怎么判断

电机转没转,不能只看动画。Proteus里的DC Motor模型在低速或者占空比极低时,动画看上去像没转,容易误判。我通常用三个信号综合判断:

第一,旋转动画。电机轴上如果有转动箭头或者标记,说明在转。第二,输出波形。把示波器通道接到L293D的OUT1脚,能看到跟随PWM输入的方波,幅值接近VS电压。第三,电流表。在VS电源线上串一个电流表,能看到电流值随占空比变化而变化。三个都正确,系统才算真正通了。

电流这一点特别容易被忽略。电机堵转、负载变化、占空比变化,电流都会有明显反应。Proteus仿真里加一个电流表非常方便,能帮你建立对"电机电流"的直觉,这对以后做实物大有帮助。

5. 电机不转?按这条链路逐个排查

5.1 第一步:确认芯片本身在运行

每次排障都从最简单的地方开始。先别管PWM,写个最朴素的GPIO点灯程序,把一个引脚输出高电平接个LED,如果LED亮了,说明芯片、时钟、hex加载链路全部正常。如果LED不亮,问题基本出在Proteus配置层面:hex路径有中文、Crystal Frequency不对、芯片供电没接全、NRST一直处于复位状态。

这个"先证明芯片能跑"的习惯,能把从源头上就错误的情况和后面复杂问题快速区分开。很多人一上来就抱着一大段PWM代码查寄存器配置,查了半天,其实芯片根本没运行。

5.2 第二步:确认PWM波形真的到达驱动器

芯片没问题,接着用示波器接PA6引脚。这里要特别提醒一个细节:程序启动后speed初始值是0,占空比是0%,PA6输出一直是低电平,示波器上看起来就是一条0V直线。这不一定是故障。你需要按一下加速按键,再观察波形。

如果按了加速键还是没有波形,常见的原因有:

  • TIM3没有使能。检查有没有调用TIM_Cmd(TIM3, ENABLE)
  • GPIO没有配置成复用功能。PA6还是普通推挽输出时,定时器波形出不来
  • TIM_OC1Init没调用。通道1没有做比较输出初始化
  • 代码里改了CCR但定时器没配置预装载,导致影子寄存器没有更新

从波形入手,一查一个准。

5.3 第三步:L293D周边常见接线错误

波形到了1,2EN引脚,电机还是不转,问题大概率在L293D周边。我见过最多的错误是:把PWM信号接到了IN1或者IN2,而EN引脚直接接VCC。这个接法下,电机要么全速转,要么不转,按键调速完全无效。原因就是本章前面那张真值表:EN决定输出是否跟随,如果EN固定为高,那输出就是恒定的高电平或者低电平,PWM根本没起作用。

另一个高频错误是1,2EN悬空。L293D的使能脚悬空时输入状态不确定,输出处于高阻或者不定状态,电机当然不会正常转。仿真里悬空尤其隐蔽,因为Proteus不会报错,只会表现为"莫名其妙不工作"。

排查时用两个电压探针,分别测OUT1和OUT2对GND的电压。正常正转调速时,OUT1应该是跟随PWM的高电平变化,OUT2应该是低电平,两者之间才有电压差,电机两端才有电流。如果OUT1和OUT2都是0V,检查VS有没有供12V;如果OUT1和OUT2都是高电平或者都是低电平,检查IN1和IN2的组合是否符合预期。

5.4 第四步:电机模型与电源匹配问题

排除前面所有问题后,再双击DC Motor元件检查属性。电机模型有一个额定电压参数,如果额定电压和目标供电电压差距太大,Proteus模型的转速计算公式会给出一个很不合理的值。比如供电12V但电机模型额定电压设成3V,仿真中电机可能会直接转飞或者表现异常。

我的做法是,DC Motor的额定电压设成12V,和VS保持一致。这样占空比50%时电机两端等效电压6V,模型计算出来的转速更符合物理直觉。

另外检查一下L293D的VS和VSS是不是都接了。VS是电机电源,VSS是逻辑电源。很多初学者只接VS,觉得"电机有电了"就行,但L293D内部逻辑部分没有VSS供电,输入信号全部无效,输出自然没有反应。

6. 从仿真走向实物:驱动方案与工程扩展

6.1 仿真和实物的真实差距

Proteus仿真能帮你验证逻辑正确性,但它呈现的是一个偏理想化的世界。仿真中不会有接触电阻,不会有电源压降,不会有大电流时地线上的干扰电压,也不会有电机换向时产生的反电动势尖峰。

实物中最需要注意的差距有三点:

第一,电机是感性负载,PWM关闭的瞬间,绕组电流不能突变,会产生反向电动势。这个尖峰可能达到电源电压的数倍。用L293D这类的集成芯片,内部已经集成续流二极管,问题不大;如果用分离MOS管搭H桥,必须在每个开关管两端并联续流二极管,否则MOS管很容易被打穿。

第二,电源的瞬时响应能力。电机启动电流远大于额定电流,如果电源余量不足,一上电电压就被拖垮,STM32可能复位,直接导致"一开电机单片机就重启"的诡异现象。

第三,逻辑电平和驱动芯片的匹配。STM32的GPIO高电平是3.3V,L293D的逻辑部分用5V供电,3.3V高电平可以满足TTL高电平阈值,可以直接驱动。但有些驱动芯片要求5V逻辑电平,直接接3.3V会识别不到高电平,这时需要电平转换电路。选驱动方案之前一定要查数据手册中的逻辑电平规格。

6.2 不同功率下的驱动芯片选型建议

L293D适合学习和小功率应用,驱动能力600mA,不能做大功率。实际项目里根据功率选择驱动方案,我的建议参考这张表:

方案驱动能力能否正反转适合场景
L293D600mA,峰值1.2A能学习、小功率课设
TB6612FNG1.2A,峰值3.2A能3.3V逻辑直驱,机器人小车
ULN2003500mA/通道不能单向调速、步进电机
分离MOS管H桥取决于MOS管能10A以上大功率电机

TB6612FNG我实际用过不少,体积小,逻辑高电平兼容3.3V,性能比L293D好,做小型智能车项目非常合适。大功率场景下,IR2104驱动半桥加MOS管,或者直接买集成驱动模块,效率更高,但调试复杂度也上升。作为学习者,先用L293D把PWM控制、正反转、H桥原理搞明白,后面换更高性能的芯片就是看数据手册、改改代码的事。

6.3 基于这个工程的三个扩展方向

这个工程本身是个很完整的基础框架,往上扩展很容易。

第一个方向是双路电机控制。TIM3的CH2也就是PA7引脚还有一路PWM输出,接L293D的3,4EN和3A、4A,就可以驱动两台电机,配合方向控制逻辑就能做一个简易差速小车平台。代码上只需要把TIM_OC1Init复制一份改成TIM_OC2Init,加两个方向引脚就行。

第二个方向是测速闭环。把普通电机换成带编码器输出的直流电机,Proteus库里有MOTOR-ENCODER模型,可以输出频率与转速成正比的脉冲。用STM32的TIM2编码器模式测量脉冲频率和方向,再和当前CCR值做PID运算,就能实现恒速控制。这是机器人项目里最常用的算法之一,直接从PWM开环跳进PID闭环,能理解一堆自动控制的概念。

第三个方向是把调试数据通过串口发出来。在代码中加个UART发送,每隔几百毫秒把当前速度、方向、占空比通过串口打印,Proteus的Virtual Terminal可以直接连接串口观察输出。这能帮你建立"程序里到底在发生什么"的可视化,也顺便把串口调试的基本功练了一遍。

这个项目再往后就是完整的电机驱动库,把PWM配置、方向控制、速度设置封装成几个函数,不管是做小车还是做机械臂,都能直接复用。

最后说一下我自己的习惯。每次搭这类仿真,我都是把"芯片能否运行、PWM波形是否正确、驱动输出是否有电压差"这三步当作固定流程,任何一步不明确,绝不往下走。这种办法看起来笨,实际最省时间。特别是Proteus里所有信号都能直接看,如果连示波器都不肯用,把波形确认清楚,等到了实物阶段排查成本会高出好几倍。这套流程跑通之后,你手里的不只是一个仿真工程,还有一套完整的电机控制调试方法论,做实物的时候会轻松很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询