尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
STM32F103C8T6 控制 L298N 驱动直流电机:从接线到PWM调速与闭环避坑指南
简介这份资源面向STM32入门与电机控制初学者围绕STM32F103C8T6通过L298N双H桥驱动两个直流电机实现正转、反转与停止的完整方案展开配套可直接参考的工程代码帮助读者快速理解GPIO输出控制与电机驱动之间的配合逻辑。资源包共1个文件为PDF格式大小约6.97MB内容涵盖硬件连接说明、main.c与motor.c等关键代码片段及优化建议便于对照学习引脚配置与函数调用关系。目前已有939人学习下载说明其在同类入门资料中具有一定参考价值。读者可从中获取L298N与STM32的接线思路、按键扫描控制电机的程序结构、电机正反转与停止的电平设定方法以及电源管理与中断优化等改进方向适合作为课程设计、电子DIY或嵌入式入门练习的参考资料。1. STM32F103C8T6 控制 L298N 驱动直流电机从最小系统到能跑起来很多人第一次拿到 STM32F103C8T6 和 L298N 模块接线图看了十几遍代码也照着抄了电机就是不转或者只会单向疯转、PWM 调速完全没反应。问题往往不在代码本身而在三个地方共地没做、使能跳线帽没拔、PWM 定时器通道选错了引脚。这篇笔记就按“最小系统能转 → 能调速 → 能正反转 → 能稳定跑”的顺序把 STM32F103C8T6 控制 L298N 驱动直流电机这件事拆开讲清楚。适合刚上手 STM32 的嵌入式新手也适合从 51 单片机转过来、第一次用 HAL 库配 PWM 的开发者。整套方案成本很低一块最小系统板、一个 L298N 模块、一节 18650 或 12V 电源、两个直流电机就能复现代码基于 STM32CubeMX 生成的 HAL 工程逻辑清晰改起来也方便。2. 硬件选型与接线L298N 到底该怎么和 STM32F103C8T6 接2.1 为什么是 L298N而不是 TB6612 或 DRV8833L298N 是老牌双 H 桥驱动芯片最大特点是皮实、便宜、资料多能同时驱动两路直流电机或一个步进电机。它的逻辑供电和电机供电是分开的逻辑 5V 可以来自 STM32 板上的 5V 输出电机供电 7V 到 12V 单独接。缺点是内部压降大典型每路约 1.4V 到 2V大电流下发热明显效率不如 TB6612 或 DRV8833。但如果你只是做小车、云台、简单传动L298N 完全够用而且它对 3.3V 逻辑的兼容性比很多人想象的好STM32F103C8T6 的 GPIO 输出 3.3V 高电平L298N 的输入高电平门槛典型在 2.3V 左右直接接就能识别。选型上我一般这样判断电流小于 1A、对体积没要求、预算极低选 L298N电流 1A 到 3A、要求效率高、板子空间小选 TB6612需要更精细的电流控制或静音选 DRV8833。这篇只讲 L298N因为标题锁死了而且它最容易踩坑把它的坑踩完换别的驱动基本是降维。2.2 接线表STM32F103C8T6 与 L298N 的引脚对应先给一张我实际用的接线表基于 STM32F103C8T6 最小系统板和常见红色 L298N 模块。注意 L298N 模块上通常有 5V 使能跳线帽如果你用外部 5V 给逻辑供电必须把跳线帽拔掉否则会倒灌。STM32F103C8T6 引脚L298N 引脚作用备注PA0ENA电机 A 使能/PWM必须接定时器通道PA1IN1电机 A 方向 1普通 GPIOPA2IN2电机 A 方向 2普通 GPIOPA3ENB电机 B 使能/PWM必须接定时器通道PA4IN3电机 B 方向 1普通 GPIOPA5IN4电机 B 方向 2普通 GPIO5V5V逻辑供电跳线帽拔掉时接GNDGND共地必须和电源地接一起12V 电源正12V电机供电7V 到 12V12V 电源负GND电机地和 STM32 地共地这里有个关键点ENA 和 ENB 必须接在 STM32 有定时器通道的引脚上否则没法输出 PWM。PA0 对应 TIM2_CH1PA3 对应 TIM2_CH4这两个正好在 TIM2 上配置起来方便。如果你用 PA8 和 PA9那就分别是 TIM1_CH1 和 TIM1_CH2也可以但 TIM1 是高级定时器配置稍复杂。新手建议直接用 TIM2。2.3 共地一个被说烂但每天都有人翻车的地方L298N 的 GND 必须和 STM32F103C8T6 的 GND 接在一起同时还要和电机电源的负极接在一起。很多人用两路电源一路给 STM32 供电一路给 L298N 电机供电然后只把 L298N 的 GND 和电机电源负极接了忘了和 STM32 共地结果就是电机乱转、PWM 无效、芯片发烫。血泪经验只要看到电机行为诡异先拿万用表蜂鸣档测 STM32 的 GND 和 L298N 的 GND 是不是通的不通就补线。3. 用 STM32CubeMX 配 TIM2 输出 PWM参数怎么算、怎么填3.1 时钟树与定时器频率72MHz 怎么分到 1kHz PWMSTM32F103C8T6 最高主频 72MHzTIM2 挂在 APB1 总线上APB1 预分频系数不为 1 时定时器时钟是 APB1 时钟的 2 倍。CubeMX 默认配置下APB1 是 36MHzTIM2 时钟就是 72MHz。我们要生成 1kHz 的 PWM方便驱动普通直流电机频率太低电机会有啸叫太高 L298N 响应跟不上。计算公式PWM 频率 定时器时钟 / ((Prescaler 1) * (Counter Period 1))。取 Prescaler 71则计数时钟为 72MHz / 72 1MHz。要 1kHz则 Counter Period 1000 - 1 999。这样每个计数周期 1us1000 个计数就是 1ms即 1kHz。占空比分辨率是 1000 级足够调速。在 CubeMX 里这样填Timers → TIM2 → Clock Source 选 Internal ClockChannel1 选 PWM Generation CH1Channel4 选 PWM Generation CH4Prescaler 填 71Counter Period 填 999PWM Generation CH1 的 Pulse 先填 0后面代码里改GPIO 里 PA1、PA2、PA4、PA5 设为 Output Push PullLow 速度即可生成代码后HAL 会自动初始化 TIM2 和 GPIO。3.2 代码启动 PWM 并封装电机控制函数下面是我常用的电机控制封装基于 HAL 库放在 main.c 的 USER CODE 区域。先启动 PWM再封装正转、反转、停止、调速四个动作。/* 启动 TIM2 的 PWM 输出必须在 while(1) 之前调用 */ HAL_TIM_PWM_Start(htim2, TIM_CHANNEL_1); // ENA HAL_TIM_PWM_Start(htim2, TIM_CHANNEL_4); // ENB /* 电机 A 正转速度 0 到 1000 */ void MotorA_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); // 改占空比 } /* 电机 A 反转速度 0 到 1000 */ void MotorA_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); } /* 电机 A 停止IN1 和 IN2 都低PWM 给 0 */ void MotorA_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); }逻辑说明L298N 的方向由 IN1 和 IN2 决定IN1 高 IN2 低是正转反过来是反转两个都低是停止两个都高是刹车。使能端 ENA 的 PWM 占空比决定速度占空比越大转速越高。__HAL_TIM_SET_COMPARE直接改比较寄存器不影响定时器运行适合实时调速。参数说明speed 范围 0 到 999对应 0% 到 99.9% 占空比。不要填 1000因为计数器从 0 到 999填 1000 会一直满占空比虽然也能转但边界不干净。实际使用中低于 200 的占空比很多电机启动不了会听到嗡嗡声但不转这是正常现象不是代码问题。3.3 主循环里怎么调用一个简单的往返测试在 while(1) 里写一个正转两秒、停一秒、反转两秒的循环用来验证接线和代码。while (1) { MotorA_Forward(700); // 70% 速度正转 HAL_Delay(2000); MotorA_Stop(); HAL_Delay(1000); MotorA_Backward(700); // 70% 速度反转 HAL_Delay(2000); MotorA_Stop(); HAL_Delay(1000); }这段代码能跑通说明 PWM、GPIO、L298N 和电机基本都正常。如果正转和反转方向反了把 IN1 和 IN2 的接线对调或者代码里对调 SET 和 RESET 即可。如果电机不转但 L298N 发烫先拔掉电机测 ENA 对 GND 电压正常应该是 1.65V 左右70% 占空比如果一直是 0 或 3.3V说明 PWM 没起来回去检查HAL_TIM_PWM_Start有没有调用。4. 避坑与排查电机不转、调速失效、芯片发烫的 5 个真实原因4.1 现象电机完全不转L298N 指示灯亮但无输出原因最常见的是 ENA 或 ENB 的跳线帽没拔。很多 L298N 模块出厂时 5V 使能跳线帽是插上的此时 ENA 被强制拉高STM32 的 PWM 根本进不去电机要么全速转要么不转。另一个原因是共地没做STM32 的 GND 和 L298N 的 GND 之间没有低阻通路PWM 信号没有参考地L298N 识别不到。解决拔掉 5V 使能跳线帽用杜邦线把 STM32 的 5V 接到 L298N 的 5VGND 接 GND。用万用表测 ENA 对 GND 的电压如果随占空比变化说明 PWM 正常如果一直是 0检查HAL_TIM_PWM_Start是否在 while(1) 之前调用以及 CubeMX 里通道是否选对。4.2 现象电机只能单向转换向无效原因IN1 和 IN2 接反了或者其中一个 GPIO 没有正确初始化。还有一种情况是代码里只改了 PWM没改方向引脚比如正转和反转都只调了__HAL_TIM_SET_COMPAREIN1 和 IN2 一直保持同一状态。解决先确认 IN1 和 IN2 分别接在 PA1 和 PA2代码里正转时 PA1 高 PA2 低反转时反过来。如果接线没错用万用表测 PA1 和 PA2 在正反转时的电平正常应该一个高一个低。如果两个都低检查 GPIO 初始化代码确认HAL_GPIO_Init被调用。4.3 现象PWM 调速没反应电机要么全速要么不转原因PWM 频率太低比如低于 100Hz电机会听到明显抖动调速线性度差或者占空比设置不对比如__HAL_TIM_SET_COMPARE填了 1000 以上导致比较寄存器溢出。另一个常见原因是把 ENA 接在了普通 GPIO 上而不是定时器通道这样只能开关不能调速。解决确认 ENA 接在 PA0TIM2_CH1或 PA3TIM2_CH4CubeMX 里对应通道选 PWM Generation。PWM 频率设 1kHz 到 20kHz 之间我一般用 1kHz兼顾 L298N 响应和电机噪音。占空比范围控制在 0 到 999低于 200 启动不了就适当提高下限。4.4 现象L298N 发热严重电机转速慢原因L298N 内部压降大12V 供电时电机实际只能得到 10V 左右电流大时压降更明显芯片温度能到 60 度以上。如果电机堵转或负载太重电流超过 2AL298N 会触发过热保护输出断续。解决加散热片或者换 TB6612。如果必须用 L298N电机供电可以提高到 12V 到 15V但不要超过 35V。堵转测试不要超过几秒否则芯片容易损坏。实际项目中我会在 L298N 的 12V 和 GND 之间并一个 100uF 电解电容吸收电机启停时的电压波动。4.5 现象电机启动时 STM32 复位或程序跑飞原因电机启动瞬间电流很大如果 STM32 和 L298N 共用一路电源电压会被拉低导致 STM32 欠压复位。另一个原因是电机电源线离 STM32 太近干扰了晶振或复位电路。解决STM32 和 L298N 分开供电STM32 用 USB 或单独 3.3VL298N 用 12V 电池。如果必须共电源在 STM32 的 3.3V 输入端并一个 100uF 和 0.1uF 电容电机电源线上串一个磁环。代码里可以在启动电机前先拉低使能延时 100ms 再给 PWM减少冲击。5. 进阶用编码器做闭环调速和串口实时调参5.1 编码器测速TIM 编码器模式读脉冲开环调速在负载变化时转速会漂加上编码器就能做闭环。常见做法是用 TIM3 或 TIM4 的编码器模式读电机轴上的增量式编码器。以 TIM3 为例PA6 和 PA7 分别接编码器 A 相和 B 相CubeMX 里 TIM3 的 Combined Channels 选 Encoder ModeCounter Period 填 65535其他默认。代码里每 100ms 读一次计数算出转速。int16_t last_cnt 0; int16_t curr_cnt 0; float speed_rpm 0; /* 每 100ms 调用一次 */ void Encoder_Update(void) { curr_cnt (int16_t)__HAL_TIM_GET_COUNTER(htim3); int16_t diff curr_cnt - last_cnt; last_cnt curr_cnt; /* 假设编码器 11 线四倍频后 44 脉冲每转减速比 30 */ speed_rpm (float)diff * 600.0f / (44.0f * 30.0f); }逻辑说明__HAL_TIM_GET_COUNTER读当前计数值两次差值就是这段时间的脉冲数。乘以 600 是因为 100ms 换算成分钟是 600 倍。44 是四倍频后的每转脉冲数30 是减速比这两个参数根据实际电机填。算出的 speed_rpm 就是输出轴转速。参数说明编码器模式要设置好极性如果读出来方向反了把其中一个通道的极性取反。Counter Period 填 65535 是为了避免频繁溢出100ms 内一般不会溢出。5.2 简单 PI 闭环让转速稳住有了转速反馈就可以写一个简单的 PI 控制器输出 PWM 占空比。下面是一个位置式 PIKp 和 Ki 需要根据电机调。float Kp 2.0f; float Ki 0.5f; float target_rpm 100.0f; float integral 0; float output 0; void PI_Control(float actual_rpm) { float error target_rpm - actual_rpm; integral error * 0.1f; // 0.1s 周期 if (integral 500) integral 500; // 积分限幅 if (integral -500) integral -500; output Kp * error Ki * integral; if (output 999) output 999; if (output 0) output 0; __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_1, (uint16_t)output); }逻辑说明误差是目标转速减实际转速积分项累加误差输出是比例加积分。积分限幅防止启动时积分饱和输出限幅保证占空比在 0 到 999。每 100ms 调用一次和编码器更新同周期。参数说明Kp 太大会震荡太小响应慢Ki 太大会超调太小消除静差慢。我一般先调 Kp 到电机开始轻微震荡再退回 60%然后加 Ki 到静差消失。这套参数没有万能值和电机、负载、编码器线数都有关。5.3 串口调参不用反复烧录就能改目标转速调试时最烦的是改一个参数就要重新编译烧录。用串口接收目标转速就能实时调。在 CubeMX 里开 USART1波特率 115200中断接收。代码里解析简单协议比如收到S500就把目标转速设为 500。uint8_t rx_byte; char rx_buf[16]; uint8_t rx_idx 0; void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart-Instance USART1) { if (rx_byte \n || rx_idx 15) { rx_buf[rx_idx] \0; if (rx_buf[0] S) { target_rpm atof(rx_buf[1]); } rx_idx 0; } else { rx_buf[rx_idx] rx_byte; } HAL_UART_Receive_IT(huart1, rx_byte, 1); } }逻辑说明每收到一个字节进一次中断遇到换行或缓冲区满就解析。如果首字符是 S就把后面的数字转成浮点赋给 target_rpm。这样在串口助手里发S300就能把目标转速改成 300。参数说明波特率 115200 足够缓冲区 16 字节够用。atof需要包含stdlib.h。注意中断里不要做太多事解析完就退出实际控制放在主循环。5.4 一个我常犯的错误刚开始做闭环时我把 PI 计算放在中断里结果中断频率太高主循环被拖慢串口都来不及收。后来改成定时器中断置标志位主循环里查标志再算 PI就稳了。还有一次编码器计数溢出没处理电机转了几圈后速度突然变负查了半天才发现是int16_t溢出。现在我会在每次读计数后判断差值是否超过 30000超过就认为溢出手动修正。这些坑不写出来下次还会踩。希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

Vue3+Vant4移动端部门树实战:递归组件与懒加载性能优化解析

Vue3+Vant4移动端部门树实战:递归组件与懒加载性能优化解析

简介:面向移动端开发者的H5部门树展示示例包,定位在企业内部管理系统、协作平台等场景下的组织架构与员工层级呈现,帮助前端工程师快速实现跨手机、平板的树形列表交互。压缩包共含十一个文件,其中包括一个HTML入口、两个JavaScri…

📅 2026/10/15 20:15:17
干扰消除实战指南:从物理根源到算法落地的全栈工程方法

干扰消除实战指南:从物理根源到算法落地的全栈工程方法

1. 项目概述:为什么“干扰消除”不是锦上添花,而是信号链的生死线 你有没有遇到过这样的场景:实验室里刚搭好的射频接收前端,示波器上信号波形清晰漂亮,但一接入ADC做频谱分析,底噪突然抬高15dB&#xff0c…

📅 2026/10/15 20:15:17
2026跨境电商英语口语APP哪个好?天花板级「咕噜口语」,一个音差一个价

2026跨境电商英语口语APP哪个好?天花板级「咕噜口语」,一个音差一个价

本文为【跨境电商英语口语App】专项实测内容。本次测评综合参考发音纠音精度、对话响应速度、场景与跨境业务贴合度、跟读体验等维度,实测综合推荐 TOP1 为【咕噜口语 SpeakGuru】,综合推荐度【9.6/10(★★★★★)】,核…

📅 2026/10/15 20:15:17
MORE NEWS

更多资讯

📰

C语言printf中i++求值次序与未定义行为详解

1. 从一个让人抓狂的输出结果说起如果你写过一段时间的C语言&#xff0c;大概率遇到过下面这种让人怀疑编译器是不是坏了的代码&#xff1a;#include <stdio.h>int main(void) {int i 5;printf("%d %d %d\n", i, i, i);return 0; }这段代码在不同编译器、不同…

📰

ThinkJS 2.x 分页组件 think-pagination 使用指南:从 countSelect 到模板渲染的完整实践

后端Web框架 【免费下载链接】thinkjs Use full ES2015 features to develop Node.js applications, Support TypeScript. 项目地址&#xff1a; https://gitcode.com/gh_mirrors/thi/thinkjs 点击查看 免费下载 本指南以 packages/think-pagination/README_v2.md 为骨架&…

📰

从App开发到Unity游戏开发:认知转型与AI辅助学习路径

1. 从界面思维到空间思维&#xff1a;一次被迫的认知重启做了五年移动端应用开发&#xff0c;我一度以为自己对“软件”这件事已经摸得差不多了。页面、路由、状态管理、网络请求、数据持久化&#xff0c;这套东西闭着眼都能搭出来。直到去年接手一个轻量级3D互动项目&#xff…

📰

C语言printf多参数自增自减为何结果不确定?求值次序与序列点解析

1. 从一个让人抓狂的输出结果说起如果你写过一段时间的C语言&#xff0c;大概率遇到过下面这种让人怀疑编译器是不是坏了的场景&#xff1a;明明变量在脑子里推演得好好的&#xff0c;结果printf一跑出来&#xff0c;数字跟预期完全对不上。尤其是当表达式里同时出现自增和自减…

📰

汇编语言上机四阶段:从.asm源文件到GDB调试的完整流程

汇编语言与计算机系统结构这门课&#xff0c;真正让人卡住的往往不是指令语法&#xff0c;而是第一次上机时对完整流程没有概念。我见过不少同学&#xff0c;在编辑器里写好了汇编源文件&#xff0c;却不知道接下来该敲什么命令&#xff0c;也不知道每个命令到底做了什么。这篇…

📰

PyTorch同维度张量matmul规则详解:批量矩阵乘与广播机制

1. 从一次矩阵乘法报错说起前几天有个做模型压缩的朋友发来一段代码&#xff0c;说他在实现一个注意力模块的简化版本时&#xff0c;被torch.matmul的维度规则卡了整整一个下午。代码大概长这样&#xff1a;两个形状都是(8, 16, 32)的张量&#xff0c;他理所当然地认为“同维度…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬