资讯动态

STM32F103 PWM直流电机调速Proteus仿真与L298N驱动详解

发布时间:2026/9/8 17:28:24 来源:尧图企业网站定制
简介基于STM32F103与L298N的直流电机PWM调速Proteus仿真项目资源面向嵌入式初学者、课程设计与工程实践人员解决从PWM调速原理到实际驱动电路仿真验证的完整学习链路。资源共138个文件压缩包仅2.62MB包含34个头文件与33个C源码构成的Keil工程、HEX固件、Proteus仿真工程pdsprj/pdsbak及工程配置和编译辅助文件可对照源码与电路同步学习。目前已有12336人浏览学习这一项目适用于STM32入门、直流电机控制或Proteus仿真练习。读者可依据资源中的源码与仿真工程理解PWM占空比与电机转速的关系掌握STM32F103的GPIO和定时器PWM输出配置学会L298N双H桥驱动器的EN/IN引脚控制方法并能加载HEX到Proteus电机模型中运行仿真快速验证硬件逻辑节省实物联调时间。 用STM32F103做直流电机的PWM调速再放到Proteus里仿真验证驱动芯片选L298N——这套方案我猜很多人都不陌生。课程设计、毕业设计、电子竞赛热身只要是跟直流电机控制沾边的项目十有八九会碰到。它不像直接用开发板点灯那么单调也不像纯理论分析占空比那样摸不着头脑刚好卡在“能看懂原理”和“能动手验证”之间。这篇文章就从一个实际能跑通的项目出发把电路连接、定时器配置、占空比计算、代码实现和排查经验完整过一遍。不管你是刚学STM32的在校生还是想快速验证调速方案的开发者只要照着这条流程走基本就能在Proteus里把电机调速的效果跑出来。我会把每一步为什么这么做的原因也讲清楚这样换一颗芯片、换一个电机你也能照样推出来。1. 项目定位与整体设计思路1.1 为什么用PWM而不是直接调电压直流电机的转速本质上是靠电枢两端的平均电压决定的。最简单粗暴的办法是串电阻调压或者用线性稳压器调压但这两个方案的问题都很明显串电阻会浪费大量功率电阻发热严重线性稳压器同样是把多余的能量以热的形式消耗掉。而且电机在不同负载下电流变化很大电阻分压的结果让转速根本不是线性的控制起来很别扭。PWM调速就不一样了。它用固定频率的高电平脉冲去驱动电机通过改变脉冲的宽度也就是占空比来改变平均电压。占空比50%平均电压就是电源电压的一半占空比80%平均电压就是电源电压的80%。实际加到电机两端的是方波但电机绕组有电感电流会被电感“抹平”所以电机感受到的等效电压是平滑的。这个方案效率极高开关管要么全导通、要么全关断损耗很小而且占空比和速度之间近似线性控制起来直观。STM32F103做PWM的优势在于它的定时器本身就是硬件PWM发生器。CPU只需要往比较寄存器里写一个数引脚就会自动输出对应占空比的波形不需要软件去翻转IO口不占用CPU时间精度也高。这正是电机调速、呼吸灯、舵机控制这些场景最需要的特性。1.2 STM32F103、L298N、Proteus三者如何配合STM32F103在这个项目里承担的任务是“大脑”负责生成PWM信号和控制方向信号。它本身输出的电流很小只有几毫安直接驱动直流电机根本带不动所以中间必须加驱动芯片。L298N就是最常见的选择之一。它内部是两个完整的H桥可以控制两个直流电机的正反转和速度。每个H桥的4个开关管由逻辑输入电平控制而电机的供电来自外部电源不经过单片机。这就把“弱电控制”和“强电驱动”隔离开了逻辑上非常清晰。Proteus在这里扮演的是“虚拟试验台”。在没有实物板子的情况下它可以把STM32、L298N、直流电机、示波器全部放到一张图纸上跑仿真。尤其是Proteus 8以上的版本对STM32的支持已经比较完整可以加载编译生成的hex文件直接观察电机转速变化还能用虚拟示波器看PWM引脚的实际波形。这对于验证控制逻辑、排查接线错误效率比反复烧写实物高不少。这套组合还有一个好处从仿真到实物迁移的代价很小。Proteus里验证过的引脚分配和逻辑拿到真实STM32F103最小系统板上只要接线一致代码基本不用大改。这也是我推荐入门者先跑仿真、再上手实物的原因。2. Proteus仿真电路搭建从最小系统到电机驱动2.1 仿真环境准备与芯片放置Proteus仿真STM32F103用的是它自带的STM32模型库。打开Proteus后在元件搜索框里输入“STM32F103C8”或者“STM32F103C8T6”就能找到对应的芯片模型。这里我用的芯片是STM32F103C8T648引脚封装资源足够也是目前最常见的型号之一。如果你用的版本里搜不到注意检查软件版本Proteus 8.9以后的STM32器件库都比较完整老版本可能需要手动导入器件库文件这一点网上资料很多但本质上就是换新版本的事。放置芯片后建议先把电源引脚处理清楚。STM32F103C8T6在Proteus里默认接好VDD和VSS但为了仿真稳定还是要确认一下有没有额外的VDDA、VSSA引脚需要接电源。放置L298N芯片时元件搜索框输入“L298”就能找到。Proteus的库里面就写着L298它和实物L298N模块的引脚定义是兼容的放心用。此外还需要放置一个直流电机模型搜索“MOTOR-DC”或者“MOTOR”就能找到。为了能看到转速变化我建议再放一个虚拟示波器方便观察PA6引脚上的PWM波形。2.2 L298N引脚功能与电路连接L298N的引脚看起来多但其实每个功能都很清晰。以控制电机A这一路为例涉及的核心引脚是VSS、VS、GND、ENA、IN1、IN2、OUT1、OUT2。VSS是逻辑电源引脚接5V给L298N内部的逻辑电路供电这个必须接不接的话芯片内部逻辑不工作VS是电机电源引脚接电机供电的正极仿真里我用12V做演示GND必须把逻辑电源和电机电源的地连在一起同时和STM32的地也要连在一起这一点最容易出错地线不共地PWM信号的电平基准就不一致电机会出现完全不转或者乱转的情况。IN1和IN2是方向控制输入接STM32的两个普通GPIO比如PB0和PB1。ENA是使能引脚接STM32的PWM输出脚比如PA6。L298N内部逻辑是ENA为高电平时OUT1和OUT2才跟随IN1、IN2的电平变化ENA为低电平时电机两端被切断处于自由停车状态。把ENA接PWM信号就等于用PWM占空比控制有效导通时间达到调速目的。OUT1和OUT2接电机两端。这里需要注意OUT1和OUT2的电压是随IN1、IN2的状态变化的电机正转时OUT1为高OUT2为低反转时反过来。所以电机两端不要接反但即使接反了也只是电机翻转方向反了不会烧东西调试时灵活处理就行。2.3 电机、电源、地线的布置细节在Proteus里画电路时最容易踩的坑是电源和地的画法。STM32仿真模型和L298N模型都需要明确的电位参考不能只靠默认的VCC端子。我的做法是用Proteus侧边栏的“终端模式”放置电源端子5V端接L298N的VSS12V端接L298N的VSGND端子接到芯片对应的GND引脚上。电机两端我习惯接一个反向并联的二极管仿真里这个二极管的作用是模拟真实电路中的续流保护。虽然Proteus仿真不一定会因为没接二极管而出错但从养成好习惯的角度讲接了更接近实物电路。真实电机是感性负载关断瞬间会产生反向电动势如果没有续流回路容易击穿驱动芯片。Proteus里看不太明显但实物调试时这个问题很致命。还有一个细节STM32的PA6作为PWM输出在Proteus里不需要额外配置复用功能但真实芯片上必须设置为复用推挽输出模式。这也是后面代码里要做的重要初始化。仿真模型对GPIO模式的约束没那么严格但代码不能因此省略。3. 定时器PWM配置与参数计算3.1 PWM波形产生的原理STM32的通用定时器TIM3产生PWM靠的是内部计数器和比较寄存器之间的碰撞。定时器不断从0向上计数到自动重装载值ARR计数过程中当计数值到达比较寄存器CCR时输出引脚的电平会翻转一次计数到ARR后回到0电平再翻转一次。这样在引脚上就形成了一个周期性的方波高电平宽度由CCR决定周期由ARR决定。占空比的计算公式是占空比 CCR / (ARR 1) × 100%。只要CCR往大了写高电平时间就长平均电压就高电机转速就快。往小了写转速会降下来。整个过程中定时器的硬件自动完成计数和比较CPU不需要频繁干预这是硬件PWM和软件延时翻转IO口的根本区别。3.2 定时器时钟来源与分频计算TIM3挂在APB1总线上STM32F103的APB1最高时钟是36MHz但定时器时钟有个倍频机制当APB1分频系数不为1时定时器时钟自动翻倍到72MHz。系统上电后默认使用的是内部8MHz HSI但实际工程里标准做法是使用外部8MHz晶振配合PLL倍频到72MHz系统主频。Proteus仿真里如果烧写的是标准库工程生成的hex文件时钟初始化时会自动完成这个配置。TIM3最终工作的时钟频率是72MHz。为了得到想要的PWM频率需要设置预分频器PSC和自动重装载值ARR。推导公式是PWM频率 72MHz / (PSC 1) / (ARR 1)直流电机调速常用的PWM频率是10kHz到20kHz。低于10kHz电机会有可闻噪声转速也会不稳高于20kHz进入超声波频段噪声消失但对开关管的开关损耗要求更高。这里我选20kHz作为目标频率这样既安静参数也比较好算。取PSC 35那么 (PSC1) 3672MHz / 36 2MHz。再取ARR 99那么 (ARR1) 1002MHz / 100 20kHz。这组参数的好处在于PWM的分辨率是1%也就是CCR从0到99变化时占空比能精确对应0%到99%非常直观。3.3 TIM3_CH1输出PWM的实际配置代码标准库V3.5的代码风格虽然看着啰嗦但结构清晰很适合用来理解原理。整个输出PWM的初始化可以分为三步开启TIM3和GPIO的时钟、配置GPIO为复用推挽输出、配置定时器时基和PWM输出通道。void TIM3_PWM_Init(u16 arr, u16 psc) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; TIM_OCInitTypeDef TIM_OCInitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); TIM_TimeBaseInitStructure.TIM_Period arr; TIM_TimeBaseInitStructure.TIM_Prescaler psc; TIM_TimeBaseInitStructure.TIM_ClockDivision 0; TIM_TimeBaseInitStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM3, TIM_TimeBaseInitStructure); 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); }这段代码有几个点值得注意。PA6默认就是TIM3的通道1输出引脚不需要重映射。如果哪天PA6被其他功能占用需要把TIM3_CH1重映射到PB4那么还要开启AFIO时钟并调用GPIO_PinRemapConfig(GPIO_Remap_TIM3, ENABLE)。TIM_OCMode_InitStructure.TIM_OCMode先选PWM1模式还是PWM2模式差别只是有效电平的极性PWM1配合TIM_OCPolarity_High是最常用的组合。TIM_Pulse初始化为0意味着上电瞬间占空比是0%电机不会突然全速转动这个细节在实物调试时能避免很多麻烦。4. 调速控制逻辑与完整实现4.1 方向控制信号逻辑L298N的方向控制逻辑很直白。以电机A这一路为例当ENA为高电平时如果IN11、IN20OUT1输出高电平、OUT2输出低电平电机正转反过来IN10、IN21电机反转如果IN1IN2电机不转。实际工程里为了保证安全最好在切换方向前先把占空比降到0避免电机带着惯性突然反向产生很大的冲击电流。方向控制的GPIO初始化也很简单选PB0和PB1作为两个方向引脚配置成通用推挽输出void Motor_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); 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); } void Motor_SetDirection(u8 dir) { if(dir FORWARD) { GPIO_SetBits(GPIOB, GPIO_Pin_0); GPIO_ResetBits(GPIOB, GPIO_Pin_1); } else { GPIO_ResetBits(GPIOB, GPIO_Pin_0); GPIO_SetBits(GPIOB, GPIO_Pin_1); } }这里我特意把方向引脚放在PB0和PB1和PWM引脚PA6分开避免在调试时混淆。选引脚时要注意避开JTAG下载占用的一些引脚比如PA13、PA14、PB3、PB4。虽然Proteus仿真时无所谓但实物调试时这几个默认被调试接口占用容易出奇怪问题。4.2 占空比调节的几种实现方式占空比调节核心就一句话调用TIM_SetCompare1(TIM3, 值)往通道1的比较寄存器里写目标值。这个值范围是0到99因为是100级分辨率。最简单的演示方式是让占空比自动循环变化从0加到99再减回来模拟呼吸灯一样的效果。实现如下int main(void) { u8 duty 0; TIM3_PWM_Init(99, 35); Motor_GPIO_Init(); Motor_SetDirection(FORWARD); while(1) { for(duty 0; duty 99; duty duty 5) { TIM_SetCompare1(TIM3, duty); delay_ms(200); } for(duty 99; duty 0; duty duty - 5) { TIM_SetCompare1(TIM3, duty); delay_ms(200); } } }每次增加5%的占空比电机转速会看到明显阶梯式变化。如果你想让调速更细腻可以把循环里的步进改成1%但为了让Proteus仿真里肉眼看到变化过程5%的步进更直观。delay_ms可以用简单的软件延时函数实现也可以用SysTick定时器来做精确延时。仿真时软件延时就够了不用太纠结精度。更接近实际应用的做法是用按键调速按一下按键占空比加10%再按另一个按键减10%。这个方法适合做交互演示代码无非是在while循环里加按键扫描逻辑。再进一步还可以接一个电位器通过ADC读取模拟量映射到占空比上实现连续调压调速。这样整个系统就从单纯的PWM扩展到ADC采集和数据处理适合作为扩展学习方向。4.3 保护逻辑与故障处理思路电机驱动里“PWM故障保护”是一个容易被新手忽略但在实物中极其重要的点。你想想如果电机堵转电流会飙升这时候如果不能及时切断PWM输出驱动芯片很可能直接烧掉。L298N本身有过热保护但标准型号没有集成电流检测需要自己在外部电路上加采样电阻。STM32F103的高级定时器TIM1和TIM8带有刹车功能当刹车输入引脚接收到有效电平PWM输出会被硬件强制关闭响应速度是纳秒级根本不需要软件介入。但TIM3是通用定时器没有刹车功能。所以如果你的项目对安全性要求高建议把PWM通道从TIM3换到TIM1或TIM8的某个通道上。仿真阶段虽然不需要考虑过流烧板子但代码里可以预留软件保护思路比如定义了一个motor_error标志位检测到异常时把占空比清零并让电机进入自由停车状态。或者在切换方向时先调用TIM_SetCompare1(TIM3, 0)延迟一小段时间再改IN1、IN2的电平。这些细节写进代码里以后迁移到实物项目时能省下不少调试时间。5. 常见问题与排查技巧5.1 电机不转的检查清单Proteus仿真里电机纹丝不动是出现频率最高的问题。我总结下来排在前三的原因基本都是接线和使能问题。第一L298N的VSS没接5V。很多新手只接了VS电机电源觉得芯片有电了就行但L298N的逻辑部分需要单独的5V供电VSS不接内部逻辑根本不工作IN1、IN2给再多电平也没反应。第二GND没有共地。这是Proteus仿真里最容易忽略的STM32的GND、L298N的VSS地、VS地必须连到同一个网络否则PWM信号没有统一的电平参考。第三ENA没有拉高或者没有接PWM。如果ENA接到固定高电平电机只会全速转或者不转没有调速效果如果ENA悬空它内部是下拉的默认使能不了电机当然不转。排查顺序我建议是先看L298N的VSS和GND再看ENA是不是接到了PA6最后用虚拟示波器看PA6有没有波形输出。从“电源→逻辑→波形”这个顺序走基本很快能定位问题。5.2 转速调节不平滑或者没有明显变化如果在Proteus里观察电机觉得转速好像一直很快减不下来先检查ARR和CCR的数值关系。占空比 CCR / (ARR 1)如果ARR设成了9999而CCR只在0到99之间变那占空比最大也才1%电机当然看不出明显变化。另一种情况是占空比寄存器更新了但电机转速一直不变。这种问题在真实芯片上往往是预装载没开导致TIM_OC1PreloadConfig这个函数的调用很重要。预装载的意思是寄存器里的新值要等当前PWM周期结束才生效防止在更新瞬间产生毛刺波形。这个问题在Proteus仿真里不太容易暴露但实物调试时很典型。还有一点Proteus里的直流电机模型和真实电机一样占空比很低时转矩很小电机可能停在原地不转。当你看到占空比低于10%电机没动静不一定是程序错了先把占空比加到30%以上再观察这才是正常的工作区。想要更平滑的调速效果可以把PWM分辨率从100级提升到1000级甚至更高也就是把ARR调大同时同步调大PSC保证频率不变。5.3 虚拟示波器显示异常的处理Proteus的虚拟示波器是最直观的调试工具我建议每次调PWM都把示波器挂到PA6引脚上观察频率和占空比是否符合预期。如果示波器上看到的不是方波而是奇怪的毛刺或低电平先别急着怀疑代码检查一下定时器时钟有没有正常使能RCC_APB1PeriphClockCmd那行注释掉的话TIM3整个不工作输出自然异常。另外示波器探头所接的网络名要和PA6引脚匹配。Proteus里如果从PA6引出一条线线上标了别的网络标号示波器就直接显示那条网络名的信号了。这种低级错误最容易让人一脸懵排查方法就是双击示波器输入端的连线确认网络标签真的是PA6。还有一个经验是Proteus仿真时把PWM频率设得太高比如超过100kHz虚拟示波器的采样率可能跟不上波形显示会失真或者出现锯齿。20kHz这个参数在仿真和实物里都很合适属于怎么调都不会出问题的安全区间。5.4 代码编译时的常见坑这个项目用标准库V3.5写代码最容易出的编译错误有两类。第一类是只添加了stm32f10x_gpio.o和stm32f10x_tim.o源文件但忘了在stm32f10x_conf.h里注释掉对应的头文件包含或者使能对应的模块宏。标准库的文件组织比较死板缺一个依赖就编译报错而且报错信息还不直观。第二类是启动文件选错。STM32F103C8T6是中等容量Medium Density芯片必须使用startup_stm32f10x_md.s启动文件如果选了hd高容量的启动文件程序下载到Proteus里可能能跑但实际用在大容量芯片上时机正确仿真时可能出现中断不进、延时不准的情况。这种问题卡住人一整天都很正常。编译生成的hex文件要在Proteus里“双击芯片”打开属性窗口在Program File那一栏指定hex路径。同时把Crystal Frequency改成8MHz和代码里的外部晶振频率保持一致。这一步不做仿真时时钟不对PWM频率自然也不对。写在最后个人实践体会这套STM32F103 L298N PWM调速的仿真项目每一次重新做都能复习一遍好几门课的知识STM32定时器的时基、PWM模式、GPIO复用功能、H桥驱动逻辑还有Proteus仿真的信号分析技巧。我个人的建议是先把代码里TIM_SetCompare1的值改成固定数比如50用示波器确认PA6确实输出了50%占空比的20kHz方波然后再写循环调速的逻辑。这样从“稳定输出”到“动态调节”一步一个脚印排查起来最省时间。如果你在Proteus里把这一整套流程跑通了我强烈建议找一块真实的STM32F103最小系统板和L298N模块按照同样的接线方式焊一套实物出来。你会发现仿真里忽略的很多细节比如L298N的发热、电机启动瞬间的电流冲击、电源纹波对转速的影响在实物里都会被放大。到那时候你再回头看PWM调速的原理理解会完全上另一个台阶。这也是仿真和实物搭配学习最有意思的地方。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价