STM32F103C8T6控制L298N驱动直流电机:PWM调速与正反转实战
发布时间:2026/10/15 22:08:30 锦皓数字建站

简介这份资源面向STM32入门与电机控制初学者围绕STM32F103C8T6微控制器配合L298N双H桥驱动模块实现两个直流电机的正转、反转与停止控制并附有可直接参考的完整代码。内容涵盖硬件连接思路与软件编程方法包括L298N的OUT1至OUT4与电机的对应接法、IN1至IN4分别连接PA2至PA5的控制信号定义、电源与共地处理以及5V供电需借助升压板的注意事项代码部分给出main.c主循环按键扫描逻辑、motor.c中电机初始化与正反转停止函数、motor.h宏定义及key.c按键初始化便于读者理解GPIO推挽输出与电平组合控制电机的原理。资源包共1个PDF文件约6.97MB以图文与代码清单形式呈现结构紧凑适合作为课程设计、电子竞赛或自学项目的参考文档。目前已有939人学习可帮助读者快速掌握STM32驱动直流电机的基础方案并在此基础上扩展中断响应、编码器测速与电源优化等进阶功能。1. STM32F103C8T6 控制 L298N 驱动直流电机从最小系统到能跑起来手头有一块 STM32F103C8T6 最小系统板一个 L298N 红色驱动模块一台 12V 直流减速电机接线照着网上图连了代码烧进去电机却纹丝不动或者只会单向狂转、调速没反应——这是绝大多数人第一次做 STM32F103C8T6 控制 L298N 驱动直流电机时会遇到的场景。问题往往不在芯片而在三处共地没做、PWM 定时器通道选错、L298N 的 ENA 跳线帽没拔。这篇笔记就按“能复现”的标准把硬件接线、CubeMX 配置、PWM 调速代码、正反转逻辑和排错顺序一次讲透。适合刚上手 STM32 的嵌入式新手也适合从 51 单片机转过来、第一次用 HAL 库驱动电机的开发者。整套方案成本不到三十块是理解“MCU 输出逻辑电平 → 驱动芯片放大电流 → 电机执行”这条链路最划算的练手项目。2. 先搞懂 L298N 和 STM32 的分工为什么不能拿 IO 口直接怼电机2.1 驱动芯片到底在替 MCU 扛什么STM32F103C8T6 的 GPIO 单个引脚输出电流典型值只有 20mA 左右而一台常见的 12V 直流减速电机堵转电流轻松上到 1A 甚至更高。直接把电机接在 IO 口上轻则引脚拉低电压导致单片机复位重则烧掉端口驱动电路。L298N 是一颗双 H 桥驱动芯片内部用功率 MOSFET 搭成 H 型桥臂能承受最高 46V 电压、单通道 2A 持续电流峰值 3A它的作用就是把 MCU 送来的 3.3V 逻辑信号“翻译”成能驱动电机的功率输出。理解 H 桥是理解正反转的关键。H 桥由四个开关组成电机接在桥臂中间。当左上和右下导通电流从左往右流过电机电机正转当右上和左下导通电流反向电机反转四个开关全关电机两端悬空自由滑行上桥臂全开或下桥臂全开电机两端被短接进入刹车状态。L298N 模块把这两个 H 桥和外围续流二极管、稳压电路都集成好了你只需要给它三根控制线使能端 ENA 和两个方向端 IN1、IN2。2.2 三根控制线的逻辑真值表L298N 每个通道的控制逻辑非常固定ENA 是使能IN1/IN2 决定电流方向。把这张表记住后面写代码就是照表翻译ENAPWMIN1IN2电机状态高电平 / PWM高低正转高电平 / PWM低高反转高电平 / PWM低低停止自由滑行高电平 / PWM高高刹车短接制动低电平任意任意停止使能关闭这里有个新手最容易忽略的点调速靠的是 ENA 上的 PWM 占空比而不是 IN1/IN2。IN1/IN2 只负责方向给它们 PWM 是没用的因为方向端内部是比较器逻辑不是功率调制端。我见过有人把 PWM 接到 IN1 上结果电机只有“转”和“不转”两种状态还以为是定时器配置错了其实是接错了引脚。2.3 为什么选 TIM 通道而不是软件延时用 GPIO 翻转加delay也能产生方波但那样 CPU 全程被占用没法同时干别的事而且频率和占空比都不稳。STM32F103C8T6 有 4 个通用定时器其中 TIM1 是高级定时器TIM2/3/4 是通用定时器每个都能输出 4 路独立 PWM。用硬件定时器产生 PWMCPU 只在初始化时配置一次之后波形由硬件自动维持占空比改一个寄存器就行。常见做法是用 TIM2 或 TIM3 的通道 1 到 4因为它们挂载在 APB1 总线上配置简单不涉及高级定时器的刹车和死区那些复杂功能。3. 接线与 CubeMX 配置把 PWM 通道和方向脚定下来3.1 硬件接线清单与共地这个死穴先把线接对再谈代码。以 L298N 通道 A 驱动一台电机为例接线如下STM32F103C8T6L298N说明PA0TIM2_CH1ENAPWM 调速必须拔掉 ENA 跳线帽PA1IN1方向控制 1PA2IN2方向控制 2GNDGND共地必须接5V 或外部 12V12V 输入电机电源视模块供电方式—OUT1/OUT2接电机两根线L298N 模块上有个 5V 使能跳线帽如果板上稳压芯片给逻辑电路供电跳线帽保留如果外部给 12V 且模块自带 7805跳线帽保留时 5V 排针可输出 5V 给单片机。但注意STM32 和 L298N 必须共地否则 PWM 信号没有参考电平电机要么不转要么随机乱转。这是血泪经验里排第一的坑。提示ENA 和 ENB 上的跳线帽出厂默认短接意味着使能端一直被拉高。你要用 PWM 调速必须把跳线帽拔掉否则 PWM 信号被硬拉高调速完全失效。3.2 CubeMX 里 TIM2 的 PWM 参数怎么填打开 CubeMX选好 STM32F103C8T6进入 Pinout 视图。把 PA0 配置为TIM2_CH1PA1、PA2 配置为GPIO_Output。然后进 Clock Configuration确保系统时钟 72MHz。接着进 TIM2 配置Clock SourceInternal ClockChannel1PWM Generation CH1Prescaler预分频71Counter Period自动重装值 ARR999Pulse初始占空比0PWM Generation CH1 的 ModePWM mode 1CH PolarityHigh算一下72MHz / (711) 1MHz 计数频率再除以 (9991) 1000得到 1kHz 的 PWM 频率。1kHz 对大多数直流电机足够太低会有可听啸叫太高则 L298N 内部开关损耗增大、发热明显。我一般把频率放在 1kHz 到 20kHz 之间1kHz 是稳妥起点。3.3 生成代码前必须检查的三个地方第一GPIO 输出模式。PA1、PA2 默认是推挽输出、无上下拉、低速这个默认值可以直接用。第二检查 TIM2 的 GPIO 是否自动分配到了 PA0有时候 CubeMX 会把它映射到 PA15 等重映射引脚需要手动改回。第三Project Manager 里把 Toolchain 选成 MDK-ARM 或 STM32CubeIDECode Generator 勾选“Generate peripheral initialization as a pair of .c/.h files”这样代码结构清晰方便后面加自己的逻辑。4. 代码落地PWM 调速、正反转和软启动怎么写4.1 启动 PWM 和设置占空比的核心函数CubeMX 生成的代码里TIM2 初始化已经在MX_TIM2_Init()里完成。你要做的第一件事是在main()里启动 PWM 通道/* 在 main() 的初始化区域MX_TIM2_Init() 之后调用 */ HAL_TIM_PWM_Start(htim2, TIM_CHANNEL_1); /* 设置占空比参数范围 0~1000对应 0%~100% */ __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_1, 500); // 50% 占空比HAL_TIM_PWM_Start负责打开定时器通道的输出使能没有这一句比较寄存器写了也没波形。__HAL_TIM_SET_COMPARE是宏直接写 TIM2 的 CCR1 寄存器比调HAL_TIM_PWM_ConfigChannel再启动要轻量得多适合在循环里频繁调速。参数 500 对应 50%因为 ARR 设的是 999占空比 CCR / (ARR1)。4.2 正反转和停止的封装把方向控制封装成三个函数代码可读性会好很多/* 电机正转speed 范围 0~1000 */ void Motor_Forward(uint16_t speed) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET); // IN1 高 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_RESET); // IN2 低 __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_1, speed); } /* 电机反转 */ void Motor_Backward(uint16_t speed) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_SET); __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_1, speed); } /* 电机停止自由滑行 */ void Motor_Stop(void) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_RESET); __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_1, 0); }注意Motor_Stop里先把 IN1、IN2 拉低再关 PWM顺序反了也没事但养成先断方向再断使能的习惯能避免某些工况下的瞬间反向电流。如果要做刹车而不是滑行把 IN1、IN2 同时拉高再保持 ENA 为高即可但刹车电流较大L298N 会发热不建议长时间用。4.3 软启动别让电机一上来就满速直接给 100% 占空比电机会“哐”一下冲出去机械冲击大电流冲击也大。加一个简单的斜坡/* 从 0 缓慢加速到目标速度step 为每步增量delay_ms 为步间延时 */ void Motor_SoftStart(uint16_t target, uint16_t step, uint32_t delay_ms) { for (uint16_t s 0; s target; s step) { __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_1, s); HAL_Delay(delay_ms); } }调用Motor_SoftStart(800, 20, 10)表示从 0 到 80% 占空比每步加 20每步停 10ms总共约 400ms 加速完成。step越小加速越平滑但循环次数多delay_ms用HAL_Delay会阻塞 CPU如果项目里还有别的任务建议改成定时器中断里更新 CCR。这个函数放在Motor_Forward之后调用方向先定好再加速。4.4 主循环里怎么组织测试逻辑一个最小可跑的主循环可以这样写int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_TIM2_Init(); HAL_TIM_PWM_Start(htim2, TIM_CHANNEL_1); while (1) { Motor_Forward(0); Motor_SoftStart(700, 20, 10); // 正转加速到 70% HAL_Delay(2000); Motor_Stop(); HAL_Delay(1000); Motor_Backward(0); Motor_SoftStart(700, 20, 10); // 反转加速到 70% HAL_Delay(2000); Motor_Stop(); HAL_Delay(1000); } }这段代码烧进去电机会正转两秒、停一秒、反转两秒、停一秒循环往复。如果方向反了把 IN1 和 IN2 的接线对调或者把Motor_Forward里的电平反过来即可不用改硬件。5. 避坑与排查电机不转、只单向转、调速失灵怎么查5.1 电机完全不转万用表量 ENA 没有波形现象代码烧进去电机一点反应没有L298N 电源灯亮。原因最常见是 ENA 跳线帽没拔PWM 被硬拉高但如果你连 IN1/IN2 也没给对电平电机照样不转其次是 STM32 和 L298N 没共地PWM 信号没有回路。解决先拔掉 ENA 跳线帽用万用表直流档量 PA0 对 GND 的电压调速时应该在 0 到 3.3V 之间变化。如果没有变化检查HAL_TIM_PWM_Start是否调用、TIM2 时钟是否使能。再量 L298N 的 GND 和 STM32 的 GND 是否导通。5.2 电机只会单向转反向没反应现象正转正常反转指令下去电机不动。原因IN1 或 IN2 其中一个引脚没配置成输出或者被别的外设复用了。比如 PA1 如果同时被配置成 ADC 或串口GPIO 输出就失效。解决在 CubeMX 里检查 PA1、PA2 的引脚状态确保是GPIO_Output且没有被其他外设占用。也可以在代码里直接翻转电平测试HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_1); HAL_Delay(500);如果 LED 能闪但电机不反向那就是 L298N 通道坏了换通道 B 试试。5.3 调速时电机有异响、低速抖动现象占空比低于 30% 时电机嗡嗡响但不转或者转速忽快忽慢。原因PWM 频率太低电机在低频下转矩脉动大或者占空比太低L298N 内部达林顿管压降导致电机端电压不足以克服静摩擦。解决把 PWM 频率提高到 8kHz 以上改 Prescaler 和 ARR 的组合比如 Prescaler 设 8、ARR 设 999得到 72MHz/9/1000 8kHz。同时给一个最低启动占空比比如 200低于这个值直接关输出避免电机半死不活地抖。5.4 L298N 发热严重甚至烫手现象跑几分钟后 L298N 散热片烫得摸不得。原因L298N 是双极型工艺内部达林顿管饱和压降约 1.4V 到 2V2A 电流下功耗就有 3W 到 4W不热才怪。解决第一确认电机电流没超过 2A堵转电流大的电机要换驱动方案第二加散热片必要时加小风扇第三PWM 频率不要超过 20kHz开关损耗会随频率上升第四如果只是做小功率实验可以在 OUT 端并联续流二极管进一步降低尖峰。这个发热是 L298N 的固有特性不是代码问题选型时就要有预期。5.5 电机一转单片机就复位现象电机启动瞬间STM32 重新跑初始化串口打印乱码。原因电机启动电流拉低电源电压导致单片机欠压复位或者电机换向火花通过共地线串扰。解决电机电源和单片机电源分开供电共地但各自独立稳压在电机两端并联 0.1uF 陶瓷电容和 100uF 电解电容吸收尖峰L298N 的 12V 输入端并一个大电容。如果还不行给单片机供电的 3.3V 稳压前级加二极管和电容做隔离。6. 进阶技巧用编码器闭环把“能转”变成“转得准”开环 PWM 调速只能保证“大概多快”负载一变转速就飘。要让转速稳定得加编码器做闭环。常见做法是在电机轴上装增量式编码器A/B 相接到 STM32 的 TIM 编码器接口模式比如 TIM3 的 CH1 和 CH2。CubeMX 里把 TIM3 配置为 Encoder Mode计数方向自动跟随转向读 CNT 寄存器就能得到位置增量。速度环用定时器中断每 10ms 读一次编码器差值算出实际转速和设定值比较用增量式 PI 调节器更新 PWM 占空比。核心代码结构如下/* 每 10ms 在定时器中断里调用 */ void Speed_Loop(void) { static int16_t last_cnt 0; int16_t now_cnt (int16_t)__HAL_TIM_GET_COUNTER(htim3); int16_t delta now_cnt - last_cnt; last_cnt now_cnt; float actual delta * 100.0f; // 换算成每秒计数系数按编码器线数定 float error target_speed - actual; integral error * 0.01f; // 积分项0.01 是 10ms 周期 float output Kp * error Ki * integral; if (output 1000) output 1000; if (output 0) output 0; __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_1, (uint16_t)output); }Kp和Ki要现场调先只加Kp从小往大加直到转速接近目标但略有静差再加Ki消除静差。Ki太大会振荡表现为转速周期性上下摆。编码器线数、减速比、轮径这些参数决定了actual的换算系数别照抄自己算一遍。验证闭环有没有生效最简单的办法是用串口把target_speed和actual同时打印出来用手捏住电机轴加负载看actual掉下去之后 PWM 有没有自动往上顶。如果负载加上去转速纹丝不动说明闭环在工作如果转速直接掉到零说明积分饱和或者 PWM 上限设低了。我自己调这个环的时候习惯先把Ki置零只用Kp把系统调到临界振荡再把Kp减半、慢慢加Ki这样比一上来就双参数乱试快得多。还有一点编码器线数别贪高几百线足够线数太高在 10ms 周期里计数溢出反而麻烦。这套开环到闭环的路径是我从玩具车做到小型云台一直沿用的顺序先把开环跑通再上闭环别跳步。希望帮到你。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。