资讯动态

STM32F103C8T6驱动L298N控制直流电机正反转与停止实战

发布时间:2026/10/15 5:01:51 来源:尧图企业网站定制
简介这份资源面向STM32入门与电机控制初学者围绕STM32F103C8T6通过L298N双H桥驱动两个直流电机实现正转、反转与停止的完整方案展开包含硬件接线说明与可直接参考的软件代码适合课程设计、电子实训或自学嵌入式GPIO控制的读者。压缩包内共1个PDF文件约6.97MB以图文形式整理部件清单、L298N与STM32的引脚连接、电源与共地注意事项以及main.c、motor.c、motor.h、key.c等关键代码片段便于对照理解按键扫描与电机控制函数的实现逻辑。目前已有939人学习下载读者可从中获取引脚分配思路、正反转电平组合、5V供电与升压板使用的排错经验以及电源管理、中断优化、编码器反馈等扩展建议为后续调试与功能升级提供参考。1. 从一堆跳线到两个轮子转起来STM32F103C8T6 驱动 L298N 的真实门槛很多人第一次拿到 STM32F103C8T6 和 L298N 模块时以为最麻烦的是写代码。实际把线接完、上电、按键按下去电机纹丝不动或者只嗡嗡响不转才是最常见的翻车现场。这份资源解决的正是这个阶段的问题用一块 STM32F103C8T6 最小系统板通过 L298N 双 H 桥驱动模块控制两个直流电机正转、反转和停止按键直接挂在开发板上代码基于标准外设库结构清晰适合刚接触电机驱动的嵌入式新手也适合需要快速搭一个验证平台的熟手。它不涉及复杂算法重点在硬件连线逻辑和 GPIO 时序把这两块吃透后面加编码器、加 PWM 调速才有地基。2. 硬件连线L298N 与 STM32 的引脚映射和供电边界2.1 为什么选 L298N 而不是自己搭 H 桥L298N 是双 H 桥驱动芯片内部集成了两个完整的 H 桥电路每个 H 桥可以独立控制一个直流电机的正反转和停止。对于 STM32F103C8T6 这类 3.3V 逻辑电平的 MCU 来说L298N 的输入引脚兼容 5V TTL 电平STM32 的 GPIO 推挽输出可以直接驱动不需要额外加电平转换。这是它比分立 MOS 管方案省事的地方。从资源正文看作者用的是 L298N 模块上面有 OUT1、OUT2、OUT3、OUT4 四个输出端子IN1 到 IN4 四个控制输入以及 12V、GND、5V 三个电源端子。两个电机分别接 OUT1/OUT2 和 OUT3/OUT4。如果上电后发现某个电机转向和预期相反不需要改代码把对应那组输出线对调即可这是最省事的修正方式。选 L298N 的另一个理由是它自带续流二极管电机断电瞬间的反向电动势有泄放路径不会直接打坏驱动芯片。但要注意L298N 内部有约 1.4V 的压降12V 供电时电机实际拿到约 10.6V如果电机额定电压是 12V转速会略低这是正常现象不是故障。2.2 电源接法为什么 5V 端子不接以及升压板的使用资源正文里有一句很关键的提醒“5v端口不接不然很难转动”。这句话背后是 L298N 模块的供电逻辑。L298N 模块上通常有一个 5V 使能跳帽如果跳帽插着模块内部的 7805 稳压器会把 12V 降成 5V给芯片逻辑供电同时 5V 端子可以对外输出。但如果把外部 5V 直接接到 5V 端子同时 12V 也供电就会造成电源冲突模块工作异常电机表现为无力或抖动。作者因为手头只有 5V 电源用了升压板把 5V 升到 12V 给 L298N 的 12V 端子。这是可行的临时方案但升压板的输出电流能力必须足够。常见的小型升压模块输出电流在 1A 到 2A 之间而两个直流电机同时启动的瞬间电流可能超过 2A会导致升压板保护或电压跌落电机转一下停一下。如果要用升压板建议选输出电流 3A 以上的型号并且在 12V 输出端并一个 470uF 以上的电解电容缓冲。地线必须共地。L298N 中间的 GND 要引出两条线一条接 STM32 开发板的 GND一条接电源负极。不共地的话STM32 输出的高低电平在 L298N 看来没有参考点电机不会响应。这是新手最容易忽略的一步。2.3 控制引脚连接与电机对应关系根据资源正文IN1、IN2、IN3、IN4 分别接 STM32 的 PA2、PA3、PA4、PA5。这四个引脚在代码里被配置为通用推挽输出速度 50MHz。对应关系如下表L298N 引脚STM32 引脚控制对象功能IN1PA2电机 1方向控制位 1IN2PA3电机 1方向控制位 2IN3PA4电机 2方向控制位 1IN4PA5电机 2方向控制位 2OUT1/OUT2电机 1 两根线电机 1正反转OUT3/OUT4电机 2 两根线电机 2正反转按键部分资源里用了开发板上的三个按键KEY0 接 PE4KEY1 接 PE3WK_UP 接 PA0。KEY0 和 KEY1 配置为上拉输入按下时读到低电平WK_UP 配置为下拉输入按下时读到高电平。这三个按键分别对应停止、反转、正转。注意PA2、PA3、PA4、PA5 同时也是 STM32F103C8T6 的 USART2 引脚。如果后续要加串口调试需要把电机控制引脚换到其他 GPIO比如 PB0、PB1、PB10、PB11否则串口和电机控制会冲突。3. 代码拆解GPIO 初始化、按键扫描与电机状态机3.1 电机 GPIO 初始化与宏定义封装资源里的 motor.c 把四个控制引脚一次性初始化然后通过宏定义封装了电平操作。先看初始化函数void Motor_12_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_ResetBits(GPIOA, GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5); }这段代码做了三件事开启 GPIOA 时钟、把 PA2 到 PA5 配置为推挽输出、初始电平拉低。推挽输出意味着引脚可以主动输出高电平和低电平驱动能力比开漏强适合直接连 L298N 的输入。速度设为 50MHz 是标准库里的最高档对于电机方向控制这种低速信号其实用 2MHz 就够但设高了也不会出问题。宏定义部分在 motor.h 里#define High 1 #define Low 0 #define IN1(a) if (a) \ GPIO_SetBits(GPIOA, GPIO_Pin_2);\ else \ GPIO_ResetBits(GPIOA, GPIO_Pin_2)IN1 到 IN4 四个宏传入 High 就置高传入 Low 就置低。这种写法在标准库工程里很常见好处是调用时语义清晰坏处是宏没有类型检查传错参数编译不会报错。我一般会在宏外面再包一层 inline 函数或者至少把参数名写成(a)带括号防止展开时优先级问题。资源里的写法已经带了括号这点没问题。3.2 电机正反转与停止的逻辑表电机控制函数在 motor.c 里逻辑很直接void Motor_1_STOP(void) { IN1(High); IN2(High); } void Motor_1_PRun(void) { IN1(Low); IN2(High); } void Motor_1_NRun(void) { IN1(High); IN2(Low); } void Motor_2_STOP(void) { IN3(High); IN4(High); } void Motor_2_PRun(void) { IN3(Low); IN4(High); } void Motor_2_NRun(void) { IN3(High); IN4(Low); }L298N 每个 H 桥的两个输入引脚决定输出状态。当 IN1 和 IN2 同时为高或同时为低时H 桥上下臂全部导通或全部截止电机两端没有电压差处于停止状态。资源里停止用的是两个输入同时为高这会让 H 桥下臂导通电机两端被短路产生刹车效果比同时为低惯性滑行停得更快。如果希望停止时电机自由滑行把 STOP 函数改成两个低即可。正转和反转就是两个输入一高一低电流方向相反。这里有一个容易混淆的点正转和反转是相对的取决于电机线怎么接。如果按代码跑正转但轮子往后转把电机两根线对调就行不需要改代码。3.3 按键扫描与主循环调度按键部分在 key.c 里KEY_Scan 函数带一个 mode 参数mode 为 0 时不支持连续按mode 为 1 时支持连续按。资源里主循环调用的是KEY_Scan(0)也就是按一次只响应一次松开后才能再按。u8 KEY_Scan(u8 mode) { static u8 key_up 1; if (mode) key_up 1; if (key_up (KEY0 0 || KEY1 0 || WK_UP 1)) { delay_ms(10); key_up 0; if (KEY0 0) return KEY0_PRES; else if (KEY1 0) return KEY1_PRES; else if (WK_UP 1) return WKUP_PRES; } else if (KEY0 1 KEY1 1 WK_UP 0) key_up 1; return 0; }这段代码的优先级是 KEY0 大于 KEY1 大于 WK_UP。如果同时按下 KEY0 和 KEY1只会返回 KEY0 的值。10ms 的延时是软件去抖能滤掉大部分按键抖动。但要注意这个延时是阻塞的如果主循环里还有其他实时性要求高的任务按键扫描会拖慢响应。常见做法是把按键扫描放到定时器中断里每 10ms 或 20ms 执行一次主循环只读键值。主循环里的调度逻辑while (1) { key KEY_Scan(0); if (key) { switch (key) { case WKUP_PRES: Motor_1_PRun(); Motor_2_PRun(); delay_ms(1); break; case KEY1_PRES: Motor_1_NRun(); Motor_2_NRun(); delay_ms(1); break; case KEY0_PRES: Motor_1_STOP(); Motor_2_STOP(); delay_ms(1); break; } } else delay_ms(10); }这里每个 case 后面的delay_ms(1)其实没有太大必要因为电机状态会保持到下一次按键。真正需要延时的是 else 分支里的 10ms用来降低空循环的 CPU 占用。如果去掉这个延时主循环会全速跑功耗略高但功能不受影响。提示资源里 main 函数开头有一个delay_ms(10000)也就是上电后先等 10 秒再初始化按键。这个延时可能是作者调试时留下的实际使用时可以删掉或改成 100ms 以内否则上电后 10 秒内按键无响应容易误判为硬件故障。4. 避坑与排查电机不转、只响不转、按键无反应的现场记录4.1 电机完全不转L298N 指示灯亮但无输出现象上电后 L298N 电源灯亮按键按下后电机没有任何反应用手摸 L298N 芯片不发热。原因最常见的是 STM32 和 L298N 没有共地。STM32 输出的 3.3V 高电平在 L298N 的输入端没有参考地芯片识别不到有效电平。其次是 STM32 的 GPIO 没有正确初始化比如时钟没开或者引脚被复用功能占用。解决先用万用表测 STM32 的 GND 和 L298N 的 GND 之间是否导通不导通就补一根地线。然后测按键按下时 PA2 到 PA5 的电压正常应该在 0V 和 3.3V 之间切换。如果电压不变检查 Motor_12_Config 是否在 main 里被调用以及 RCC 时钟是否使能。4.2 电机嗡嗡响但不转或者转一下就停现象按键按下后电机发出嗡嗡声轴不转或者轻微抖动一下然后停止。原因供电电流不足。升压板输出电流不够或者电源线太细导致压降过大。L298N 的 12V 输入在电机启动瞬间被拉低到 6V 以下芯片欠压保护。解决用示波器或万用表测 L298N 12V 端子在电机启动瞬间的电压如果跌落超过 2V说明电源内阻太大。换用输出电流更大的电源或者在 12V 端子并一个 1000uF 以上的电解电容。另外检查 L298N 的 5V 跳帽是否插着如果插着且外部又接了 5V拔掉跳帽或断开外部 5V。4.3 按键按下后电机转向与预期相反现象按 WK_UP 预期正转实际反转按 KEY1 预期反转实际正转。原因电机两根线接反了或者 L298N 输出端子的顺序和代码里的定义不一致。解决不需要改代码把对应电机的两根输出线对调即可。如果是两个电机都反把两组输出线都对调。如果只有一个反只对调那一组。这是最省事的修正方式比改代码重新编译下载快得多。4.4 按键无反应但电机一直转或一直停现象上电后电机就朝一个方向转按键完全不起作用。原因GPIO 初始化失败或者按键引脚配置错误。资源里 KEY0 和 KEY1 用的是 PE3 和 PE4WK_UP 用的是 PA0。如果开发板的按键实际接在其他引脚代码里的宏定义就对不上。解决查开发板原理图确认按键对应的 GPIO。如果按键是接在 PE3 和 PE4检查 RCC 是否使能了 GPIOE 时钟。资源里的 KEY_Init 同时使能了 GPIOA 和 GPIOE这点是对的。另外注意 WK_UP 是下拉输入按下读到高电平如果开发板上的 WK_UP 是上拉接法代码里的判断条件要反过来。4.5 上电后 10 秒内按键无响应现象刚上电时按键怎么按都没反应等大约 10 秒后恢复正常。原因main 函数开头的delay_ms(10000)阻塞了程序按键初始化在延时之后才执行。解决把delay_ms(10000)改成delay_ms(100)或直接删掉。如果这个延时是为了等电源稳定100ms 到 500ms 足够了。10 秒的等待在实际使用中很容易被当成故障。5. 从按键控制到 PWM 调速一个可验证的进阶改法资源里的代码只做了正反转和停止电机速度是固定的。如果想调速最直接的办法是用 TIM 输出 PWM 到 L298N 的使能引脚。L298N 模块上通常有 ENA 和 ENB 两个使能跳帽拔掉跳帽后这两个引脚可以接 PWM 信号。ENA 控制电机 1 的速度ENB 控制电机 2 的速度。以 TIM3 的通道 1 为例PA6 可以复用为 TIM3_CH1输出 PWM 到 ENA。初始化代码如下void PWM_Init(u16 arr, u16 psc) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, 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_TimeBaseStructure.TIM_Period arr; TIM_TimeBaseStructure.TIM_Prescaler psc; TIM_TimeBaseStructure.TIM_ClockDivision 0; 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); }arr 是自动重装载值psc 是预分频系数。STM32F103C8T6 的 TIM3 挂在 APB1 总线上时钟频率 72MHz。如果 arr 设为 7199psc 设为 0PWM 频率就是 72MHz / 7200 10kHz这个频率在电机驱动里比较合适既不会听到啸叫也不会因为频率太低导致电机抖动。占空比通过 TIM_SetCompare1(TIM3, value) 设置value 范围 0 到 arr。验证方法先不接电机用示波器或逻辑分析仪测 PA6 引脚确认 PWM 波形频率和占空比正确。然后接上电机把占空比从 0 慢慢加到 50%观察电机是否从静止平滑加速到半速。如果电机在低占空比时不转说明启动扭矩不够需要把占空比下限设在 30% 左右或者换用减速比更大的电机。我自己的习惯是每次改完电机控制代码先不接电机用 LED 代替电机看 GPIO 翻转是否正确确认逻辑没问题再接电机。这样能避免因为代码问题反复拆线。从那以后我每次调电机驱动都强制走一遍“先 LED 后电机”的流程省了很多返工时间。希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑