简介:这份资源面向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 引脚 | 控制对象 | 功能 |
|---|---|---|---|
| IN1 | PA2 | 电机 1 | 方向控制位 1 |
| IN2 | PA3 | 电机 1 | 方向控制位 2 |
| IN3 | PA4 | 电机 2 | 方向控制位 1 |
| IN4 | PA5 | 电机 2 | 方向控制位 2 |
| OUT1/OUT2 | 电机 1 两根线 | 电机 1 | 正反转 |
| OUT3/OUT4 | 电机 2 两根线 | 电机 2 | 正反转 |
按键部分,资源里用了开发板上的三个按键:KEY0 接 PE4,KEY1 接 PE3,WK_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 和 PE4,WK_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 设为 7199,psc 设为 0,PWM 频率就是 72MHz / 7200 = 10kHz,这个频率在电机驱动里比较合适,既不会听到啸叫,也不会因为频率太低导致电机抖动。占空比通过 TIM_SetCompare1(TIM3, value) 设置,value 范围 0 到 arr。
验证方法:先不接电机,用示波器或逻辑分析仪测 PA6 引脚,确认 PWM 波形频率和占空比正确。然后接上电机,把占空比从 0 慢慢加到 50%,观察电机是否从静止平滑加速到半速。如果电机在低占空比时不转,说明启动扭矩不够,需要把占空比下限设在 30% 左右,或者换用减速比更大的电机。
我自己的习惯是,每次改完电机控制代码,先不接电机,用 LED 代替电机看 GPIO 翻转是否正确,确认逻辑没问题再接电机。这样能避免因为代码问题反复拆线。从那以后我每次调电机驱动都强制走一遍“先 LED 后电机”的流程,省了很多返工时间。希望帮到你。
本文还有配套的精品资源,点击获取