资讯动态

STM32F103C8T6驱动L298N实现直流电机正反转控制

发布时间:2026/11/6 5:46:32 来源:尧图企业网站定制
简介围绕STM32F103C8T6与L298N展开的直流电机控制实践资料面向嵌入式入门学习者及需要完成电机正反转实验的开发者重点解决双直流电机正转、反转与停止的硬件接线和GPIO驱动问题。内容以PDF形式呈现结合按键扫描与电机控制函数梳理PA2至PA5分别连接IN1至IN4、OUT1至OUT4接电机、外部电源与共地处理以及5V供电需升压、L298N的5V端口不宜接入等易错点。压缩包共1个PDF文件大小约6.97MB篇幅集中便于在电脑或移动端直接查阅。已有939人学习说明其对入门实验具有参考价值。资料还给出main.c、motor.c、motor.h与key.c的关键代码片段包含Motor_12_Config、Motor_1_PRun、Motor_1_NRun、Motor_1_STOP等函数及WKUP、KEY1、KEY0按键对应关系并附电源管理、中断优化、编码器测速与错误处理等扩展建议可帮助读者快速搭建实验框架并排查接线与方向设置问题。1. 一台只会原地打转的小车卡住的往往不是代码某开发者拿着一块 STM32F103C8T6 最小系统板、一块 L298N 双 H 桥模块和两个直流电机想让轮子正转、反转、停下结果上电以后电机一声不吭示波器一量 PA2 到 PA5 电平跳得挺正常。这类现场问题我在群里见过太多次九成不是 C 代码写错而是 ENA/ENB 跳线帽没插、模块 GND 没跟板子共地、或者 5V 端子被外部电源顶住了。这份资源就是一份把链路做完整的最小例程STM32F103C8T6 用四个 GPIO 输出高低电平去控制 L298N 的 IN1~IN4按键扫描决定两个直流电机正转、反转还是停止代码分了 main、motor、key、delay 四个文件硬件接线也一并写清楚。适合刚上手标准外设库、想把「单片机引脚 → 驱动芯片 → 电机」这条链跑通的读者也适合拿它当底盘控制的第一版骨架。2. 硬件接线与供电四根信号线背后的共地与跳线帽2.1 器件清单与引脚分工这套东西的物料很少但每一项都有它的脾气。主控是 STM32F103C8T648 脚封装64KB Flash、20KB RAM属于中容量产品线驱动是 L298N 模块内部两个独立 H 桥能同时带两路直流电机或者一个四线步进电机执行端是两个普通的减速直流电机剩下的就是电源和升压板这类配角。原例程的按键没有外接键盘直接借用了开发板上自带的三个按键这也是很多人复现时第一个对不上的地方——换了块没有板载按键的板子KEY0、KEY1、WK_UP 三个宏就全废了得自己补按键电路。引脚分配是这样的信号STM32 引脚L298N 端子说明电机 A 方向 1PA2IN1推挽输出控制 H 桥左半电机 A 方向 2PA3IN2推挽输出与 IN1 反相即反转电机 B 方向 1PA4IN3推挽输出电机 B 方向 2PA5IN4推挽输出电机 A 输出—OUT1、OUT2接第一个电机的两根线电机 B 输出—OUT3、OUT4接第二个电机的两根线地GND中间 GND必须连否则信号没有参考逻辑电源—5V 端子本例程不接见 2.2电机电源升压板输出正12V 端子电机功率来源这里有个细节值得单独说一句L298N 的输入是 TTL 电平兼容的内部靠晶体管做电平判断高电平阈值大约在 2.3V 上下。STM32 的 GPIO 推挽输出高电平是 3.3V直连过去完全够用不需要额外的电平转换。很多人一看到「5V 驱动」就下意识加一片 74HC245其实没必要加了反而多一层延时和故障点。2.2 供电链路5V 端子为什么不能接原例程里最值得记的一句是「5v 端口不接不然很难转动」。这句话背后是 L298N 模块的板载稳压结构模块上那颗 7805 三端稳压器输入接在 12V 端子上输出就引到了 5V 端子正常用法是「12V 进、5V 出」5V 端子可以作为输出给单片机供电。如果你一边从 12V 端子灌入升压后的电压一边又把外部 5V 电源怼到 5V 端子上两个电源就在这条线上顶牛了稳压器输出被抬高或者被拉低逻辑侧电压不稳H 桥的开关状态跟着抖表现出来就是电机哼哼唧唧转不动。我自己搭这套东西时会按下面的顺序接接完再上电先断电把两个电机分别接到 OUT1/OUT2 和 OUT3/OUT4线序先随便插。把 L298N 中间的 GND 引出两条线一条到 STM32 的 GND一条到电源负极。这一步是共地别省。L298N 的 12V 端子接升压板输出正极升压板输入接 5V 电源。ENA、ENB 两个使能端上的跳线帽保持插着。这一步最容易漏拔掉跳线帽等于把 H 桥的使能拉低IN1~IN4 怎么翻都不会有输出。最后才给 STM32 上电再给升压板上电。顺序上把「强电」放最后是为了避免电机启动瞬间的浪涌把单片机的复位电路带翻。直流电机堵转电流能到额定值的三五倍共地线又细地弹一起MCU 直接复位现象就是程序跑着跑着从头开始非常像「玄学」。2.3 上电前的三个静态验证在烧代码之前有三件事可以用万用表做完做完能省掉后面一大半返工。第一断电状态下量 L298N 的 12V 端子和 GND 之间是不是短路模块上的滤波电容击穿很常见第二给逻辑侧上电量 STM32 的 GND 和 L298N 的 GND 之间电阻应该接近 0 欧超过几欧就说明共地线虚接第三把 PA2~PA5 配成输出后用万用表直流档逐根量对 GND 的电压正转时应该是 0V、3.3V、0V、3.3V 这样的组合如果四根全是 0V问题在初始化不在驱动。另外一点经验如果电机转向和你预期的相反不要去改代码里的 High/Low直接把接在该通道上的电机两根线对调就行。代码是逻辑接线是物理物理层能解决的事不要动逻辑层否则后面接第二台电机、第三台电机的时候方向定义会乱成一锅粥。3. 工程骨架从标准外设库模板里挑出必要的那几个文件3.1 最小工程的文件夹构成原例程是基于标准外设库SPL的不是 HAL。这意味着它需要一份现成的工程模板做底子然后往里塞自己的文件。如果你手上已经有一份能点亮 LED 的标准库工程那么这次只需要新增 motor.c / motor.h替换 main.c、key.c、key.hdelay 相关文件沿用模板里的即可。整个目录大致是这么组织的目录/文件作用是否可以复用模板main.c主循环、按键分发、调用电机动作需要替换motor.c / motor.hGPIO 初始化、六个动作函数、引脚宏新增key.c / key.h按键 GPIO 初始化、扫描状态机需要替换或改写delay.c / delay.h毫秒级延时一般基于 SysTick直接复用stm32f10x_*.c官方外设库源码复用startup_stm32f10x_*.s启动文件与芯片容量对应需按型号选对system_stm32f10x.c系统时钟配置复用确认晶振频率启动文件这一项要格外注意。STM32F103C8T6 是 64KB Flash 的中容量器件对应 startup_stm32f10x_md.s而原例程正文里提到的另一颗 zet6 是 512KB 大容量对应 startup_stm32f10x_hd.s。两者中断向量表长度不一样选错了编译能过、下载也能进但一进中断就跑飞排查起来非常折磨。我一般会打开工程选项里的 Device 页确认芯片型号和启动文件后缀一致再看 C/C 页的预处理宏是不是 STM32F10X_MD 加 USE_STDPERIPH_DRIVER这两个宏决定了库函数里走哪条分支。3.2 三层分工应用层、驱动层、板级支持代码虽然只有四个文件但分层是清楚的。main.c 属于应用层只关心「按了哪个键、该调哪个动作」不碰任何寄存器motor.c 属于驱动层把 L298N 的四种输入组合封装成 PRun、NRun、STOP 六个语义化函数key.c 属于板级支持负责把物理按键翻译成 KEY0_PRES 这类事件码delay.c 是基础服务。这种切法的好处是换驱动芯片的时候只需要重写 motor.cmain.c 一行不用动。提示如果你后面要改成 HAL 库main.c 里的 switch 结构可以直接搬motor.c 里把 GPIO_SetBits 换成 HAL_GPIO_WritePin 就行分层的价值在这时候就体现出来了。3.3 编译与下载环节的几个开关工程建好之后还有三处配置容易翻车。第一处是调试方式C8T6 最小系统板一般用 SWD 四线下载工程里 Debug 页要选对应的调试器Port 选 SW如果选成 JTAG 会连不上。第二处是 Flash 算法Options → Utilities → Settings 里要确认添加了 STM32F10x Med-density Flash 的编程算法否则会出现「能连上但下载失败」。第三处是时钟system_stm32f10x.c 里默认按 8MHz 外部晶振起 PLL 到 72MHz如果你板子上焊的是 12MHz 晶振串口波特率和延时全都会偏表现出来是延时短了一截、按键像失灵。编译产物建议打开 Create HEX File这样即使换一台没装调试器驱动的电脑也能用串口 ISP 烧进去。C8T6 的 BOOT0 拉高进 ISP、拉低进用户 Flash这个跳线在最小系统板上通常是个两针排针别小看它很多人烧完程序忘了把 BOOT0 拨回去然后疑惑「为什么程序不跑」。4. 核心代码拆解GPIO 初始化、宏封装与按键状态机4.1 Motor_12_Config四路推挽输出的正确打开方式初始化函数是整个驱动层的地基它做的事情非常朴素开时钟、配模式、给初值。#include motor.h #include stm32f10x.h #include stm32f10x_rcc.h void Motor_12_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; // 局部结构体用完即弃 // 使能 GPIOA 的 APB2 时钟这一步漏了后面全白干 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 一次配置 PA2~PA5 四根引脚 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); // 上电默认全部拉低H 桥两输入同为低电平电机处于刹车状态 GPIO_ResetBits(GPIOA, GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5); }RCC_APB2PeriphClockCmd这一行是必须的STM32 的外设时钟默认关闭不给时钟写寄存器没有任何反应这是新手最常见的第一个坑。GPIO_Mode_Out_PP选推挽而不是开漏是因为推挽能主动输出高电平开漏只能拉低、高电平要靠外部上拉接 L298N 这种需要明确高电平的输入会很不稳。GPIO_Speed_50MHz这个参数控制的是输出驱动器的翻转速率不是「引脚跑多快」。对电机方向控制这种几赫兹到几十赫兹的信号选 2MHz 都绰绰有余50MHz 反而会带来更陡的边沿和更多的电磁干扰。原例程选 50MHz 没什么问题但如果后面要做 PWM 调速边沿太陡加上电机线长容易在 PA 口上耦合出尖峰我一般会降到 2MHz 或者 10MHz。最后那行GPIO_ResetBits给初值很重要。如果省掉上电瞬间四个引脚处于浮空输入态L298N 的输入端电压不确定可能出现上电自己转一下的怪现象。4.2 motor.h 里的宏语法糖背后的坑原例程用了一层宏把置位/复位包起来让动作函数看起来更干净#define High 1 #define Low 0 // 注意这是原例程的写法if/else 没有用 do{}while(0) 包裹 #define IN1(a) if (a) \ GPIO_SetBits(GPIOA, GPIO_Pin_2);\ else \ GPIO_ResetBits(GPIOA, GPIO_Pin_2) #define IN2(a) if (a) \ GPIO_SetBits(GPIOA, GPIO_Pin_3);\ else \ GPIO_ResetBits(GPIOA, GPIO_Pin_3) #define IN3(a) if (a) \ GPIO_SetBits(GPIOA, GPIO_Pin_4);\ else \ GPIO_ResetBits(GPIOA, GPIO_Pin_4) #define IN4(a) if (a) \ GPIO_SetBits(GPIOA, GPIO_Pin_5);\ else \ GPIO_ResetBits(GPIOA, GPIO_Pin_5)这层封装的好处是把「哪根引脚对应哪个 IN 通道」这件事集中在头文件里改板子只需要改这里的引脚号motor.c 里的六个动作函数原样不动。代价是宏没有用do { ... } while(0)包起来一旦写成if (cond) IN1(High); else ...展开后 else 会挂到宏内部的 if 上逻辑直接错位。这是个经典的血泪经验我现在的习惯是只要宏里有分号一律裹do {} while(0)。另外在 C 语言里High和Low这两个名字过于通用和别的头文件撞名的概率不低工程一大就容易翻车。稳妥做法是加个前缀比如MOTOR_HIGH、MOTOR_LOW。4.3 六个动作函数与 H 桥真值表L298N 单个 H 桥的两个输入决定了输出状态这个真值表必须刻在脑子里IN1IN2输出状态对应函数00刹车输出端短接到地—10正转OUT1 高、OUT2 低Motor_1_NRun01反转OUT1 低、OUT2 高Motor_1_PRun11停止两输出同时拉低自由停车Motor_1_STOP对应到代码void Motor_1_STOP(void) { IN1(High); // 两输入同为高H 桥下管全开电机自由停车 IN2(High); } void Motor_1_PRun(void) { IN1(Low); IN2(High); // 电流 OUT2 → OUT1 } void Motor_1_NRun(void) { IN1(High); IN2(Low); // 电流 OUT1 → OUT2方向相反 } 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 来说同高时两个下管导通电机两端被短接属于自由停车转子靠惯性滑行同低时两个上管导通同样是把电机短接效果类似。真正的「刹车」需要让电流反向流动L298N 做不到主动制动只能靠短接绕组消耗动能。如果你需要快速停住得靠 PWM 反接一段时间这是后面二次开发要处理的事。还要注意Motor_1_PRun这个命名和实际转向的对应关系完全是约定出来的。原例程里 PRun 是 IN1 低、IN2 高你要是把电机线反过来插同一个函数就变成反转了。所以我一般会在 motor.h 里补一行注释写明「PRun 对应 OUT1 相对 OUT2 为负」避免过两个月回来自己也看不懂。4.4 key.c10ms 去抖加松手检测的状态机按键这块的逻辑比电机复杂因为它要处理抖动和长按。void KEY_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; // 按键接在 GPIOE 和 GPIOA 上两个端口的时钟都要开 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOE, ENABLE); // KEY0 接 PE4KEY1 接 PE3配置为上拉输入按下读到低电平 GPIO_InitStructure.GPIO_Pin GPIO_Pin_4 | GPIO_Pin_3; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU; GPIO_Init(GPIOE, GPIO_InitStructure); // WK_UP 接 PA0下拉输入按下读到高电平 GPIO_InitStructure.GPIO_Pin GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPD; GPIO_Init(GPIOA, GPIO_InitStructure); }上拉输入的意思是引脚内部通过一个几十千欧的电阻接到 VCC按键没按时读到 1按下后被拉到 GND 读到 0。下拉输入反过来。这样按键就不需要外部电阻硬件上省事。原例程在 GPIOE 那段代码后面还有一行GPIO_InitStructure.GPIO_Pin GPIO_Pin_5;是复制粘贴留下的残影会把前面Pin_4|Pin_3的赋值覆盖掉——不过这行如果后面没有紧跟GPIO_Init其实不生效属于「看着吓人但不致命」的那种。你在自己工程里抄的时候直接删掉别留着过年。扫描函数是典型的「松手检测」状态机u8 KEY_Scan(u8 mode) { static u8 key_up 1; // 按键松开标志静态变量跨调用保持 if (mode) key_up 1; // mode1 时支持连续按 if (key_up (KEY0 0 || KEY1 0 || WK_UP 1)) { delay_ms(10); // 10ms 去抖避开机械触点弹跳 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; // 无按键 }static u8 key_up是这个函数的核心它把「已经处理过这次按下」这个状态保存在静态区函数返回后不丢失。没有它按住不放会每隔 10ms 触发一次电机就会不停地被重复置位。delay_ms(10)是软件去抖机械按键的弹跳时间一般在 5~20ms取 10ms 是折中值。这个延时是阻塞的按下期间主循环停在这里 10ms对电机控制来说完全无感但如果后面加了串口通信或者传感器采样这 10ms 就会变成丢包的元凶。mode参数决定支不支持连按。main.c 里传的是 0也就是不支持连按——按一下正转松开再按才反转中间必须经过松手。这是为电机场景特意选的避免手一抖就被识别成连击。4.5 main.c一个极简的事件分发循环主函数把上面这些串起来#include stm32f10x.h #include motor.h #include delay.h #include key.h int main(void) { vu8 key 0; Motor_12_Config(); // 先把四路 GPIO 配好防止上电乱转 delay_init(); // SysTick 初始化延时函数的前提 delay_ms(10000); // 上电等待 10 秒给电源和调试器留时间 KEY_Init(); // 按键引脚初始化 while (1) { key KEY_Scan(0); // 非阻塞式取键值无键返回 0 if (key) { switch (key) { case WKUP_PRES: // WK_UP 按下 Motor_1_PRun(); Motor_2_PRun(); delay_ms(1); break; case KEY1_PRES: // KEY1 按下 Motor_1_NRun(); Motor_2_NRun(); delay_ms(1); break; case KEY0_PRES: // KEY0 按下 Motor_1_STOP(); Motor_2_STOP(); delay_ms(1); break; } } else { delay_ms(10); // 空闲时也让出一点时间降低无效轮询 } } }结构非常清楚初始化、取键值、分发、回到循环。三个 case 分别对应两个电机正转、两个电机反转、两个电机停止。delay_ms(1)放在 case 末尾其实没什么实际作用因为下一次循环本来就会去扫描按键删掉也不影响功能。真正需要留意的是delay_ms(10000)这一行。很多标准库延时实现里单次延时的上限受 SysTick 重装载寄存器位数限制常见的说法是单次最大约 1864ms传 10000 进去要么被截断成小数要么内部循环次数溢出。原例程在这里放 10 秒多半是为了留时间给电源稳定和串口打印但这个写法本身就埋了个雷。我一般改成循环调用 10 次delay_ms(1000)或者干脆删掉改成「按键按下才启动」的显式确认调试体验反而更好。还有一点vu8 key用了 volatile 修饰这里其实没必要——它不是被中断修改的变量只是一个普通的局部量。加上 volatile 会阻止编译器优化虽然不影响正确性但属于习惯性滥用。5. 避坑与排查不转、单边转、复位按这个顺序查5.1 电机完全不动但引脚电压正常现象万用表量 PA2~PA5正转时确实是 0V、3.3V、0V、3.3V可电机就是不吭声。 原因八成是 ENA/ENB 跳线帽被拔了。L298N 模块上 ENA、ENB 各有一个跳线帽插着的时候使能端被拉到高电平H 桥才工作拔掉是为了外接 PWM 调速。原例程没有用 PWM所以跳线帽必须插上。 解决把两个跳线帽插回去再上电试。如果还是不转量 12V 端子和 GND 之间有没有电压没有就是升压板没工作或者保险丝断了。5.2 只有一个轮子转另一个纹丝不动现象两个电机的代码一模一样接法也一样但只有 OUT1/OUT2 那一路有反应。 原因要么是另一路电机线虚接要么是 IN3/IN4 对应的 PA4/PA5 在初始化时被别的外设占用了。开发板上 PA4/PA5 经常被复用成 SPI1 的 NSS/SCK如果工程里开了 SPI 或者用了外部 Flash这两根脚就被抢走了。 解决先对调两个电机的插头确认是电机问题还是通道问题如果是通道问题检查工程里有没有其他地方调用了 GPIO_Pin_4 / GPIO_Pin_5 的初始化或者直接换到 PB0/PB1 这类空闲脚改 motor.h 里的宏即可。5.3 上电后程序像「死」了十几秒才响应按键现象烧完程序板子上电按键按下去没反应等十来秒才正常。 原因main.c 里那句delay_ms(10000)。如果延时库单次上限不够或者内部实现有溢出实际等待时间会变得不可预测即使延时正常这十秒里主循环根本没跑起来当然不响应。 解决把那句删掉或者改成for (int i 0; i 10; i) delay_ms(1000);。调试阶段我建议直接删上电即响应看现象更直接。5.4 按键按了没反应但直接在 main 里调动作函数电机能转现象电机和驱动都没问题就是按键读不到。 原因KEY_Init 里 GPIOE 那段有引脚覆盖或者按键引脚定义和你手上的板子对不上。原例程的 KEY0/KEY1 在 PE4/PE3WK_UP 在 PA0这是某款常见开发板的固定布局换成最小系统板就得自己接。另外 WK_UP 是下拉输入、按下为高KEY0/KEY1 是上拉输入、按下为低两者电平逻辑相反宏写反了就会一直读到「按下」。 解决先用万用表量按键按下前后引脚电平有没有变化确认硬件通路再检查 key.h 里三个宏读的端口和引脚号。实在不行在KEY_Scan里加一句串口打印把三个宏的值实时打出来比猜快得多。5.5 电机一转单片机就复位或者调试器掉线现象不接电机时一切正常一驱动电机程序从头开始跑或者调试会话断开。 原因电机启动瞬间的大电流在共地线上产生地弹把 MCU 的复位引脚拉低或者电机电源和逻辑电源共用一条线负载变化直接反映到 MCU 供电上。 解决三件事一起做——共地线加粗、逻辑侧和功率侧的电源分开走升压板输入从电源端单独引、在电机两端并一个 0.1μF 陶瓷电容加一个续流二极管吸收反电动势。如果还不行把复位引脚对 VCC 接一个 100nF 电容提高抗干扰能力。6. 二次开发从「只会正反转」到能调速、能走直线的底盘原例程只做了方向控制电机要么全速要么停离能跑的小车还差一层。改造的入口在 ENA/ENB把这两个使能端从跳线帽上拔下来接到两个带 PWM 输出的定时器通道上比如 TIM3 的 CH1、CH2或者 TIM2 的 CH3、CH4。方向仍然由 IN1~IN4 决定转速由占空比决定两者正交互不干扰。// TIM3_CH1(PA6) 与 TIM3_CH2(PA7) 输出 PWM频率 10kHz void Motor_PWM_Init(u16 arr, u16 psc) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); // PA6/PA7 复用推挽交给定时器驱动 GPIO_InitStructure.GPIO_Pin GPIO_Pin_6 | GPIO_Pin_7; 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_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; // 初始占空比 0 TIM_OCInitStructure.TIM_OCPolarity TIM_OCPolarity_High; TIM_OC1Init(TIM3, TIM_OCInitStructure); TIM_OC2Init(TIM3, TIM_OCInitStructure); TIM_Cmd(TIM3, ENABLE); }arr和psc的取值要一起算72MHz 主频除以(psc1)得到计数频率再除以(arr1)得到 PWM 频率。想要 10kHz、分辨率 100 档就取psc71、arr99。占空比通过TIM_SetCompare1(TIM3, duty)改duty 范围 0~99。第二处改造是把delay_ms(10)的空闲等待换成定时器中断或者状态机让主循环不再被阻塞。做法很简单SysTick 每 1ms 置一个标志主循环里检查标志再调KEY_Scan按键去抖用计数器实现而不是死等。这样主循环就能腾出手来处理串口指令、读编码器。验证改造是否成功我一般分三步走第一步占空比给 30%、50%、80%听电机声音有没有明显变化同时用万用表量 ENA 对地电压应该跟着占空比线性变化第二步两个电机给同样的占空比看小车能不能走直线走偏了就说明两个电机的死区电压不一致需要单独标定第三步连续正反切换 50 次观察有没有丢步或者复位这一步能暴露电源和散热的余量。改到这里这套代码才算真正能拿去当底盘用。我第一次搭这类东西的时候图省事把 PWM 和方向控制写在同一个函数里结果每次调速都顺带把方向重置一遍车在原地抽搐了半小时才反应过来。从那以后我每次加新外设都强制先把「引脚归谁管」列一张表贴在显示器边上确认没有第二个人碰同一根脚再动代码。希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑