
如果第一次在 STM32F103 上做 CoreXY 机构的步进电机运动控制你很可能也会经历我最初踩过的那个坑接好驱动板写好两路电机各自的转动测试代码高高兴兴跑起来结果发现龙门架的运动方向完全不符合预期。翻来覆去检查接线、检查方向引脚配置一切都对但机构就是不走直线。问题的根源往往不在电机的转动逻辑而在运动学换算这一层。这次要聊的 Vibecode 项目核心就是基于 STM32F103 驱动 CoreXY 机构它真正值得拆开讲的地方不是“怎么让电机转”而是“怎么让两个固定电机协同输出把龙门架送到你想要的坐标位置”。先说一个我自己的判断这类项目的复杂度不在电机驱动本身而在两件事上——一是把目标坐标换算成两台电机的步进量也就是运动学解算二是用稳定可靠的脉冲时序把步进命令执行出去也就是定时器、PWM、DMA 和中断的配合。如果只追求电机能转GPIO 翻转就够但如果你想得到一个平滑、不丢步、可复用的运动控制流程就必须把机械联动和脉冲调度一起纳入设计。这也是 Vibecode 这一类项目真正的学习价值所在。1. 先看懂 CoreXY不是两个电机各转各的1.1 两个电机固定在机架上龙门架靠皮带联动CoreXY 是一种常见于高速 3D 打印机、激光雕刻机、写字绘图机的二维运动机构。它的显著特点是两个步进电机固定安装在机架上不随龙门架移动。X 方向和 Y 方向的运动是通过同步带的环形绕法和两个电机的旋转组合共同实现的。在典型的水平布局里电机 A 和电机 B 的转动会同时影响龙门架的 X、Y 位移。假设两台电机都朝某一方向旋转龙门架可能沿着 X 方向移动而如果两台电机一正一反旋转龙门架就沿着 Y 方向移动。如果两台电机的速度比例不同龙门架就会走出斜线甚至曲线。也就是说电机转动量和龙门架位移之间不是一对一的轴映射关系而是一个线性组合。这是 CoreXY 和传统十字滑台之间最本质的差别。很多现成的步进电机控制例程默认都是“一个电机管一根轴”比如串口收到 X 指令就让 X 电机走一定步数收到 Y 指令就让 Y 电机走一定步数。这种模型放在十字滑台上没问题因为 X 电机确实只带动 X 方向的负载。但放在 CoreXY 机构上直接套用这种思路就会出错你给 A 电机发了 100 步给 B 电机发了 0 步龙门架不会只走 X 方向而是会走一个斜向的合成位移。更麻烦的是如果两台电机输出脉冲时没有协调好速度比例皮带会被撕扯机构会抖动甚至出现皮带跳齿。所以在写运动控制代码之前必须先搞清楚你实际机构的皮带绕法然后把运动学公式确定下来。这一步不是可选项而是整个项目的地基。1.2 为什么“两轴独立”的思路会直接翻车假设你有现成的两轴独立控制代码直接把 CoreXY 的两台电机接到 X 和 Y 两个轴上会出现什么现象你想让龙门架沿 X 方向移动 100mm于是让 X 电机走对应步数Y 电机不动。结果龙门架既没有沿 X 走也没有沿 Y 走而是沿斜线移动。你想走一个矩形轨迹第一段沿 X、第二段沿 Y结果实际轨迹变成一个平行四边形。更隐蔽的问题是即使你手动修正了方向只要两路脉冲的时序没有对齐龙门架运动过程中就会出现速度抖动表面上看是直线放大看其实是锯齿。这些现象共同指向一个结论CoreXY 的控制固件必须内建运动学换算层而不是直接把外部坐标指令当作电机步进指令。换算层放在哪里、怎么实现、怎么测试正是这类项目最核心的工程内容。2. 用 STM32F103 做运动控制核心先想清楚三件事2.1 F103 为什么适合这个任务STM32F103 主频 72MHzFlash 从 64KB 到 512KB 不等RAM 从 20KB 到 64KB。从算力角度看它并不算强但步进电机运动控制恰恰不是高算力任务。步进电机的本质是“按固定频率产生脉冲”而不是像伺服电机那样做高带宽闭环调节。真正消耗 CPU 的是运动曲线的实时计算、脉冲频率的精确切换和多轴联动时的同步管理。F103 在这个场景里的角色更像一个“脉冲调度器”根据目标坐标、速度和加速度计算每一小段时间内应该输出多少个脉冲、频率是多少然后在正确的时间点上更新定时器参数让硬件自动输出 STEP 脉冲。从外设资源上看F103 也比较合适多个通用定时器可以分别产生多路 PWM 脉冲。DMA 控制器能够在 CPU 不介入的情况下更新定时器的频率参数。足够的 GPIO 用于方向、使能、限位和状态指示。如果你的目标只是驱动常见的 42 步进电机或 NEMA17 电机搭配 A4988、DRV8825、TMC2208 这类驱动器F103 的性能余量是足够的。这个前提下学习成本低、资料多、调试工具齐全的优势就体现出来了。2.2 驱动器接口STEP、DIR、EN 和电平匹配步进电机不能直接接 MCU 的 GPIO必须经由驱动器。常见的步进驱动器模块输入侧通常有这几根关键信号线STEP脉冲输入每来一个上升沿驱动器就让电机前进一步。DIR方向电平高/低电平代表两个旋转方向。EN使能信号一般低电平有效控制电机是否通电锁止。这里有一个非常容易被忽略的问题MCU 的 GPIO 是 3.3V 电平而不少步进驱动器模块工作在 5V 逻辑电平下。虽然 A4988、DRV8825 这类模块的 STEP 输入在很多情况下可以识别 3.3V 高电平但这不是一个可以拍胸脯保证的事。如果信号电平处于临界状态就会出现“有时候转、有时候不转”的随机问题排查起来非常费时间。最稳妥的做法有两种一是确认驱动器模块明确支持 3.3V 逻辑输入二是在 STEP、DIR、EN 信号线上加电平转换电路比如用 74LVC245、ULN2003 或者简单分压处理。这个细节看起来小但实际上会影响整个项目的稳定性。另外还要注意共地MCU 的 GND 和驱动器的 GND 必须可靠连在一起否则电平完全没有参考脉冲信号会乱掉。2.3 最小系统的关键点和引脚规划如果你是购买现成的 STM32F103 开发板可以跳过最小系统这一步。但如果你打算自己画板把核心板和控制板做在一起那么一个可用的最小系统至少需要3.3V 电源通常从 5V 用 AMS1117 稳压得到注意去耦电容要靠近芯片电源引脚。8MHz 外部晶振配合内部 PLL 倍频到 72MHz。复位电路、BOOT 引脚配置和 SWD 下载调试接口。关于晶振有一个常见的选择直接用内部 RC 振荡器省掉外部晶振。这在小规模验证、功能原型阶段可以接受但如果你的目标是稳定输出步进脉冲我建议还是使用外部晶振。原因很简单脉冲频率的精度直接由定时器的时钟精度决定内部 RC 振荡器的精度通常在 ±1% 到 ±2% 左右温度漂移也更大。在运动中1% 的频率偏差体现在速度上可能不明显但如果你之后要做长时间、高精度的轨迹控制这个偏差会累积成位置误差。引脚规划上至少需要电机 A 的 STEP接在某个定时器通道上例如 TIM2_CH1PA0。电机 A 的 DIR任意普通 GPIO。电机 B 的 STEP接在另一个定时器通道上例如 TIM3_CH1PA6。电机 B 的 DIR任意普通 GPIO。每轴至少一个限位开关接带外部中断的 GPIO并加上拉电阻。这里特别提醒STEP 引脚一定不要接在普通中断频繁、或者和串口共用的引脚上。STEP 信号是运动中产生频率最高的信号任何不稳定的因素都会被放大。3. 脉冲生成运动控制真正吃性能的地方3.1 用定时器 PWM 输出 STEP而不是 GPIO 翻转最原始的步进电机控制方式是在一个定时器中断里翻转 GPIO。这种方式在每秒几百个脉冲的低频场景下没有问题但一旦频率上到几千甚至上万问题就来了中断执行时间不稳定脉冲间隔抖动明显。两路电机同时运动时CPU 难以保证两路脉冲的严格同步。频率提高后中断来不及及时响应会导致步数计数错误。所以在实际项目中我更推荐让定时器硬件直接产生 STEP 脉冲。做法是把 STEP 引脚配置为定时器通道的 PWM 输出然后通过修改定时器的周期ARR来控制脉冲频率。定时器一旦启动脉冲就由硬件自动产生CPU 完全不用管每一拍脉冲的实现细节。这样做还有一个额外好处频率切换非常自然。你只需要在需要变速的时刻更新 ARR 的值定时器会在下一个周期自动按新频率工作。如果配合 DMA频率更新可以批量完成进一步降低 CPU 负担。3.2 频率、细分、速度之间的换算步进电机的步距角通常是 1.8°也就是 200 步/圈。驱动器的细分可以放大这个值比如 1/16 细分时每圈需要 3200 个脉冲。脉冲频率和电机转速之间的关系是电机转速(圈/秒) 脉冲频率(Hz) / (200 × 细分)如果不考虑任何机械传动损耗电机的转动会通过同步带轮转化为龙门架的直线位移。假设同步带轮的齿距是 2mm带轮 20 齿那么带轮转一圈皮带前进 40mm。于是龙门架移动 1mm 需要的脉冲数 (200 × 细分) / 40这个值就是通常说的steps_per_mm。它取决于电机的步距角、驱动器的细分倍数、带轮齿数和同步带节距。在 CoreXY 机构中由于两个电机同时参与运动这个参数还要结合运动学公式来确定实际意义。换句话说steps_per_mm不是一个凭空设置的参数而是由整套机械传动链决定的一个物理常数。实际调试中很少有人能在第一次就把这个值算得很准。正常的做法是先用估算值让机构移动一段已知距离然后用尺子测量实际位移再反推修正steps_per_mm。这个“测量-修正”的过程可能要重复两三次才能收敛。3.3 进阶用 DMA 批量更新频率曲线梯形加减速过程中脉冲频率需要持续变化。如果匀速段是 10000Hz加减速段每毫秒都要改一次频率那么在一个 0.5 秒的加速段里CPU 要更新 500 次定时器寄存器。如果每一步频率更新都靠中断完成中断开销叠加起来还是可观的。更高效的做法是用 DMA。你可以把加减速曲线预先计算成一张频率表表中每个元素对应一个时间片内的 ARR 值。然后让定时器的更新事件触发 DMADMA 自动把频率表里的值逐个写入 ARR。整段加速过程CPU 只需做一次 DMA 配置后面就由硬件自动完成。在 F103 上这个方案是可行的而且算是比较经典的用法。需要注意的关键点有三个DMA 通道的优先级要合理设置避免和其他外设抢占总线导致频率更新延迟。频率表的长度和加减速的时间片大小要匹配时间片太短会导致表过长太长会让速度曲线看起来粗糙。DMA 传输完成中断要用来判断整段速度曲线是否执行完毕而不是每一拍都进中断。如果你还在用“中断里翻转 GPIO”的写法可以先试着切换到“定时器 PWM 输出 中断里改 ARR”的第二阶段等调通之后再考虑“PWM DMA”的第三阶段。一步一步来每个阶段都能让你更清楚地理解步进控制的时序逻辑。4. CoreXY 运动学把目标坐标变成电机脉冲4.1 逆向运动学与正向运动学运动学要解决的核心问题是在“龙门架坐标系”和“电机坐标系”之间做换算。逆向运动学回答的是如果我想让龙门架移动到坐标X, Y两台电机各需要走多少步在常见的 CoreXY 标准绕线下这个关系可以写成MotorA X Y MotorB X - Y这里 X、Y 是龙门架在各自轴向上的位移量MotorA、MotorB 是两台电机的步进量。注意X 和 Y 的具体方向定义、以及 MotorB 前面的正负号取决于同步带的实际绕法和电机安装方向。不同的机械结构符号可能变成MotorA X - Y MotorB X Y或者MotorA -X - Y MotorB -X Y所以我不建议死记某一个公式。更可靠的做法是先按照你机构的设计意图选一个默认公式然后在样机上手动推动龙门架观察两台电机轴分别朝哪个方向旋转再回到公式里校准符号。正向运动学反过来从两台电机的步进量推算龙门架的位置X (MotorA MotorB) / 2 Y (MotorA - MotorB) / 2这个换算在调试和位置监控时很有用但需要注意在步进电机的开环控制体系里正向运动学计算出来的位置是“理论位置”不是“实测位置”。如果发生了丢步理论位置和实际位置就会产生偏差而这个偏差开环系统自己是发现不了的。4.2 固件里的实现结构在固件里运动学换算不建议放在每发一个脉冲就执行的路径上那样计算开销太大。更合理的做法是分层处理上层接收目标坐标单位可以是毫米也可以直接是步数。先把毫米坐标转换成 X 轴和 Y 轴各自的步数这一步要用到steps_per_mm。再通过逆向运动学把 X/Y 步数换算成 MotorA 和 MotorB 的目标步数。根据目标步数和当前位置步数计算出两台电机各自的方向和脉冲总数。启动定时器按预定的加减速曲线发送脉冲。用一段示意性的 C 代码来表达这个过程大概是这样的typedef struct { int32_t current_steps; // 当前绝对步数 int32_t target_steps; // 目标绝对步数 uint8_t direction; // 方向 } MotorState; void corexy_move_to(int32_t target_x_steps, int32_t target_y_steps, MotorState *motor_a, MotorState *motor_b) { int32_t a_steps target_x_steps target_y_steps; int32_t b_steps target_x_steps - target_y_steps; motor_a-direction (a_steps motor_a-current_steps) ? 1 : 0; motor_a-target_steps a_steps; motor_b-direction (b_steps motor_b-current_steps) ? 1 : 0; motor_b-target_steps b_steps; }这段代码只是示意真正的实现还要考虑方向引脚的物理极性、加速减速、限位以及异常中断。但核心思路是清晰的坐标换算在指令层完成脉冲生成在定时器层完成两者之间通过一个“目标步数”的接口连接。4.3 点到点运动和连续轨迹插补如果你只需要做点到点定位比如激光雕刻机从当前点移动到下一个加工点那么核心任务相对简单算好目标步数按“加速→匀速→减速”的曲线发脉冲结束后停下来。两点之间的实际路径是一条直线因为运动过程中 X 和 Y 的合成速度比例是恒定的。但如果你要做画图、写字、激光打标这类连续轨迹操作情况就不一样了。你需要把整条轨迹拆成很多小线段逐段执行并且尽量让段与段之间的速度连续避免频繁启停。这就是插补的范畴。在 F103 上做两轴直线插补常用的是时间分割法每个很短的周期比如 1ms内根据当前速度和加速度计算这一周期内应该前进的距离然后分解到 X 和 Y 方向再转换成两台电机的脉冲增量。由于 CoreXY 是联动机构这个计算过程发生在运动学换算之后本质上还是在做脉冲分配。插补是比点到点高一个台阶的内容。如果你第一次接触 CoreXY我建议先把点到点移动做扎实确认方向、步数、加减速都正确再考虑连续轨迹。否则一旦轨迹出现问题你很难分辨是运动学错了、脉冲时序不对还是插补算法本身有 bug。5. 加减速策略速度曲线才是稳定的关键5.1 梯形加减速为什么是起步标配步进电机不能直接以目标速度启动。原因是电机转矩在高速段会明显下降如果启动瞬间频率过高电机会因为跟不上脉冲指令而丢步。丢步一旦发生位置误差就产生了而且在开环系统里无法自动修正。所以运动控制必须引入加减速。最基础的模型是梯形速度曲线分为三个阶段加速段脉冲频率从启动频率线性上升到目标频率。匀速段保持目标频率稳定运行。减速段脉冲频率线性下降到停止频率然后停止。为什么要设置启动频率因为在低频段步进电机的转矩输出才足够大。经验上可靠启动频率通常在 500Hz 到 2kHz 之间具体取决于电机参数、驱动电压和负载惯量。如果你在调试中遇到“电机刚起步就卡住”第一件事不是调大目标速度而是检查启动频率是否太高或者加速度是否太大。5.2 丢步的排查顺序丢步是步进电机运动控制里最让人头疼的异常。它最直接的表现是“位置不对了”但原因可能有好几个层面加速度太大电机转子的转动惯量跟不上脉冲序列。目标速度超过电机在负载条件下的失步转矩对应的最大速度。脉冲频率抖动导致电机振动和瞬时失步。驱动器电流设置不当或者电流衰减模式与电机不匹配。机械问题皮带过松、滑块阻力大、导轨有异物。电源电压不足驱动器输入端电压跌落导致电流输出不稳定。排查丢步问题时我建议按从物理到软件的次序来先手动推动龙门架感受机械阻力是否均匀。如果有卡顿先解决机械问题。再检查驱动器的电流设置、细分倍数和供电电压。然后降低加速度看丢步是否消失。如果还有问题再降低最大速度。最后才考虑固件里的脉冲生成是否有缺陷。这个顺序很重要。很多人一遇到丢步就怀疑代码结果把驱动代码改了一遍又一遍最后发现是皮带太松或者电源功率不够。先物理、后电气、再软件能省下大量时间。5.3 参数调整的实操路径加减速参数没有一套公式可以适用于所有机构因为负载惯量、皮带张力、导轨阻力各不相同。但可以按下面的路径逐步逼近先把加速度设成一个保守值比如 500 steps/s²。设一条短距离直线运动比如 50mm逐步提高目标速度。找到在“不丢步”前提下能稳定达到的最大速度。固定这个速度再逐步提高加速度直到出现丢步然后往回退 20% 左右。每次修改参数后在机构上做标记跑固定距离确认没有累积偏差。在实际固件里梯形加减速需要频繁更新定时器的 ARR 值。一个常见的做法是在每个速度更新周期内根据当前速度、目标速度和加速度步长计算新的频率并写入定时器。还要注意一个特殊情况如果移动距离很短可能还没加速到目标速度就得开始减速这时候速度曲线是三角形。判断方法也很简单计算加速距离加上减速距离如果超过总距离就不要试图跑到最高速度直接采用三角形曲线。6. 从“能转”到“稳定用”调试路径和工程化补全6.1 四条验证台阶拿到板子和机械结构之后不建议直接烧录完整固件。我习惯把验证过程拆成四步每步都有明确的通过标准第一步验证单电机。只接电机 A发固定数量脉冲确认方向、电流、速度正常。然后单独测电机 B。第二步验证双电机同步。给两台电机发相同频率、相同方向的脉冲龙门架应该沿一个轴向移动。再改变其中一台的方向确认另一个轴向的运动。第三步验证运动学逆解。给定目标坐标X, Y先用上位机或手算得到 MotorA 和 MotorB 的目标步数再让固件执行对比实际落点。第四步验证加减速和连续轨迹。从短距离直线开始再到斜线、矩形、圆逐步提高速度。每一步都不要跳过。很多看似“神秘”的运动轨迹错误其实只要在第二步多花十分钟就能定位出来。6.2 常见问题排查链路调试过程中最常见的问题可以按下面的链路来排查电机不转先用示波器或逻辑分析仪量 STEP 引脚有没有波形。如果没波形查定时器是否启动、通道配置是否正确如果有波形但电机不动查驱动器的 EN 使能、VDD 电源和电机相线。电机转但方向反了改固件里对应轴的方向极性或者对调电机相线。注意如果两台电机的方向都反了不能简单把两台都调换要结合运动学公式统一修正。转到一半卡住先降低加速度再降低最大速度。如果降参数后仍然丢步重点检查机械阻力和驱动器供电。运动方向不对回到运动学公式确认 MotorA/MotorB 的符号和你实际机构的皮带绕法一致。速度不均匀、有抖动检查定时器配置和频率更新时间是否稳定确认是不是中断频繁导致的脉冲间隔抖动。一启动电机MCU 就复位大概率是电源问题。电机启动瞬间电流大会把系统电压拉低。重点检查 3.3V 稳压器是否余量不足驱动器的供电是否和 MCU 供电隔离共地参考是否干净。6.3 长期运行还要补哪些能力如果这个项目的目标不只是学习而是要稳定运行在某种设备里那么除了基本运动控制还要补几块“非运动”但决定工程可用性的能力上电回零用限位开关建立零点坐标否则每次上电都不知道龙门架在哪。软件限位和硬件限位双重保护防止运动越过机械边界。异常处理读取驱动器的 FAULT 信号、检测限位触发、处理通讯超时。参数持久化把速度、加速度、细分、steps_per_mm等参数保存到 Flash掉电不丢失。日志记录把运动指令、错误码和时间戳输出到串口方便后期分析。上位机协议通过串口或 CAN 接收 G 代码或者自定义指令把坐标指令解析成运动任务。这些能力看似和“步进电机控制”没有直接关系但恰恰是项目从原型走向产品的分水岭。还有一个常见的架构问题要不要上 FreeRTOS我的建议是如果项目比较简单可以不用 RTOS直接在定时器中断和主循环里完成任务调度。如果功能模块比较多比如同时要处理串口协议、按键显示、运动控制和限位监控那么可以引入 RTOS 来组织任务。但有一个原则要守住步进脉冲的生成和运动曲线的实时更新必须放在定时器中断或 DMA 处理里不能放在普通 RTOS 任务中。RTOS 任务的调度延迟和优先级切换无法保证脉冲时序的确定性。另外一个经常被问到的点是低功耗。对于需要电池供电或是桌面型便携设备可以考虑 STM32F103 的 STOP 模式来降低待机功耗。但要注意停机模式下定时器和 DMA 都会停止工作运动控制必须显式暂停并保存位置状态唤醒后重新初始化定时器。如果你只是做固定供电的设备这个需求可以放一放。回到最开始说的那个判断CoreXY 项目的难度在运动学换算和脉冲时序调度不在“电机转动”本身。当你把坐标变换、加减速曲线、定时器 PWM 和 DMA 这些模块理解透了你会发现这套方法不只是适用于 CoreXY也适用于三轴打印机、激光雕刻、绘图仪甚至更复杂的多轴联动设备。一个机构的硬件决定了它的极限而控制软件决定了它能不能稳定地接近这个极限。Vibecode 项目的价值