本文记录液体处理机器人中的 Marlin 固件改造部分。
Marlin 本来是一套面向 3D 打印机的开源固件,常见在笛卡尔结构的 FDM 设备上。它负责的事情很底层,包括 G-code 解析、运动规划、步进驱动、限位、串口通信、屏幕和 SD 卡这些基础功能。对这种“底层本质上还是一台多轴运动平台”的改造项目来说,它已经提供了不少可以直接接着用的控制基础。
这套系统的底层硬件是改造后的 3D 打印机运动平台,控制板换成了 SKR Mini E3 V3.0,并在原本 XYZ 的基础上增加了一根驱动注射器活塞的 U 轴。固件层没有从零重写运动控制系统,而是基于 Marlin 现成的运动规划、G-code 执行和 TMC 驱动支持进行适配。
内容主要包括轴语义修改、板级引脚映射、运动参数重定义,以及编译后刷回控制板的流程。
这份固件修改是为下面这个移液机器人项目服务的。整机的机械结构、上位机和实验结果都放在项目页里。
基于 3D 打印机改造的低成本自动化液体处理系统
为什么选择 Marlin
对于实验原型而非通用控制器开发,Marlin 已经提供了多项可复用能力:
- 基本稳定的直角坐标运动规划
- 成熟的
G0/G1/G28这类底层控制接口 - 对
TMC2209这类驱动的现成支持 - EEPROM、串口、LCD、SD 和 endstop 相关的基础设施
因此固件改造的重点不是补齐底层基础设施,而是移除 Marlin 默认属于 3D 打印机的假设,并把现有能力重新映射到液体处理平台的控制链路上。
第一步:将机器语义改成 XYZU
Marlin 默认最熟悉的机器模型是 XYZ + E。但对这台平台来说,原本的 E0 不再是挤出机,而是一根推动注射器的线性轴。所以最先要改的不是速度,而是“这台机器到底是什么”。
在 Configuration.h 中启用额外驱动轴,并将其对外显示成 U 轴:
#define X_DRIVER_TYPE TMC2209
#define Y_DRIVER_TYPE TMC2209
#define Z_DRIVER_TYPE TMC2209
#define I_DRIVER_TYPE TMC2209
#ifdef I_DRIVER_TYPE
#define AXIS4_NAME 'U'
#endif
#define EXTRUDERS 0
这里的 TMC2209 指的是步进电机驱动芯片类型。Marlin 里的 X_DRIVER_TYPE、Y_DRIVER_TYPE 这些定义,本质上是在告诉固件每根轴接的是什么驱动。如果控制板使用的不是 TMC2209,而是 A4988、DRV8825 或其他 TMC 型号,这里也需要改成对应的驱动类型,后面的 UART、电流和归零配置才会匹配。
其中有两个关键点。
第一,Marlin 内部额外轴的命名是 I/J/K/U/V/W 这套扩展轴体系,不一定直接等于最终对外暴露的字母。所以这里实际上是启用了 I_DRIVER_TYPE,再把 AXIS4_NAME 改成 'U',这样上层可以用 U 来理解这根轴,但底层还是沿着 Marlin 的额外轴机制工作。
第二,EXTRUDERS 被改成了 0。这一步很重要,因为它等于明确告诉固件:这台机器已经不是“有热端、有耗材、有挤出”的打印机。很多后续配置,包括温度、挤出保护和动作语义,都是在这个前提上继续调整的。
对应地,热端和热床传感器也需要关闭,避免固件继续带着打印机的加热假设:
#define TEMP_SENSOR_0 0
#define TEMP_SENSOR_BED 0
#define EXTRUDE_MINTEMP 0
#define EXTRUDE_MAXLENGTH 600
EXTRUDE_MINTEMP 0 和 EXTRUDE_MAXLENGTH 600 这两项看起来还带着“extrude”这个词,但本质上是在清掉 Marlin 对挤出动作的保护假设,避免它误把 U 轴动作当作热端未升温下的非法挤出。
第二步:把 E0 驱动真正复用成 U 轴
语义改完之后,真正决定能不能跑起来的,是板级引脚映射。
SKR Mini E3 V3.0 本身是一块典型的打印机控制板,默认提供 X/Y/Z/E0 四个驱动位。在不更换更大控制板的前提下增加一根执行轴,直接方案是复用 E0 驱动资源。
项目中最核心的一段改动位于板级 pins 文件:
#define E0_STOP_PIN PC15
#ifndef I_MIN_PIN
#define I_MIN_PIN E0_STOP_PIN
#endif
#ifndef I_STEP_PIN
#define I_STEP_PIN E0_STEP_PIN
#endif
#ifndef I_DIR_PIN
#define I_DIR_PIN E0_DIR_PIN
#endif
#ifndef I_ENABLE_PIN
#define I_ENABLE_PIN E0_ENABLE_PIN
#endif
#ifndef I_SERIAL_TX_PIN
#define I_SERIAL_TX_PIN E0_SERIAL_TX_PIN
#endif
#ifndef I_SERIAL_RX_PIN
#define I_SERIAL_RX_PIN E0_SERIAL_RX_PIN
#endif
#ifndef I_CS_PIN
#define I_CS_PIN E0_CS_PIN
#endif
#ifndef I_HARDWARE_SERIAL
#define I_HARDWARE_SERIAL E0_HARDWARE_SERIAL
#endif
这段映射把 Marlin 眼里的 I 轴,直接接到板卡原本给 E0 预留的步进、方向、使能、UART 和 endstop 资源上。这样一来,控制板不需要换,驱动芯片也不需要换,但在固件层它已经不再被当成挤出机,而是一根额外的线性轴。
这类改法的好处是资源利用率高,缺点是语义层和板级层必须同时理顺。只改 Configuration.h 不够,只改 pins 也不够,必须两边一起改,否则会出现“逻辑上有 U 轴、硬件上映射不到”或者“硬件上映射了、上层还把它当 E 轴”的错位。
第三步:重新定义行程、步数、速度和归零
把轴接出来以后,下一步就是让这台机器按液体处理平台的方式去动,而不是按打印机的方式去动。
最终保留的几组核心参数如下:
#define USE_IMIN_PLUG
#define DEFAULT_AXIS_STEPS_PER_UNIT { 80, 80, 800, 4000 }
#define DEFAULT_MAX_FEEDRATE { 500, 500, 500, 500 }
#define DEFAULT_MAX_ACCELERATION { 500, 500, 100, 100 }
#define DEFAULT_ACCELERATION 500
#define DEFAULT_RETRACT_ACCELERATION 500
#define DEFAULT_TRAVEL_ACCELERATION 500
#define I_HOME_DIR -1
#define I_MIN_POS 0
#define I_MAX_POS 100
#define X_BED_SIZE 305
#define Y_BED_SIZE 305
#define Z_MAX_POS 605
#define HOMING_FEEDRATE_MM_M { (20*60), (20*60), (4*60), (2*60) }
需要重点关注的参数包括:
DEFAULT_AXIS_STEPS_PER_UNIT这组值决定了“发出 1 mm 指令时电机到底走多少步”。其中U轴设到4000,本质上是把丝杆、细分和注射器推进行程一起换算进去了。DEFAULT_MAX_FEEDRATE它定义了各轴允许的最高速度上限。对液体处理来说,U轴不一定能像空载 XY 那样跑得很激进,因为它背后是真正在推液体。DEFAULT_MAX_ACCELERATION这一组很影响系统有没有机械冲击。特别是带注射器和枪头的结构,一味把加速度拉高通常没有意义。HOMING_FEEDRATE_MM_M归零速度和普通移动速度不应该混在一起看。这个平台中U轴的归零速度被设置得更低,优先保证动作可重复。I_MIN_POS / I_MAX_POS这类边界值看起来普通,但很重要。它是软件限位的最后一道保险,能避免调试时一条错误指令把注射器机构直接顶死。
除了速度和行程之外,方向也要重新对齐。配置中对 Y/Z/I 的方向进行了重新定义:
#define INVERT_Y_DIR false
#define INVERT_Z_DIR true
#define INVERT_I_DIR true
#define I_HOME_DIR -1
这类值没有固定标准答案,需要结合实际机械装配方向、限位位置和移动结果逐项校准。更稳妥的方式是一次只放开一根轴,确认方向、步数和限位都正确后,再继续向下调试。
第四步:把 TMC2209 和驱动参数调顺
在这套改造里,驱动层的工作量不算小,因为它不只是“能转”就结束了,还要考虑 UART 地址、电流、无感归零和步进表现。
最终保留的几组驱动配置如下:
#define X_CURRENT 580
#define Y_CURRENT 580
#define Z_CURRENT 750
#define I_CURRENT 750
#define X_SLAVE_ADDRESS 0
#define Y_SLAVE_ADDRESS 2
#define Z_SLAVE_ADDRESS 1
#define I_SLAVE_ADDRESS 3
#define CHOPPER_TIMING CHOPPER_DEFAULT_24V
#define SENSORLESS_HOMING
#define X_STALL_SENSITIVITY 75
#define Y_STALL_SENSITIVITY 50
#define I_STALL_SENSITIVITY 75
#define IMPROVE_HOMING_RELIABILITY
#define SQUARE_WAVE_STEPPING
这部分里最容易踩坑的通常是三类值:
*_CURRENT直接决定驱动电流。太低可能丢步,太高又会发热甚至让机构不必要地受力。这里X/Y、Z和U的值不同,反映的就是三根轴机械负载本来就不一样。*_SLAVE_ADDRESS既然走的是TMC2209的 UART 配置,地址一定要和板上跳线、驱动位置对应起来。否则看似在调整 U 轴,实际可能是在给其他轴写寄存器。*_STALL_SENSITIVITY这是无感归零里最需要试出来的一组值。太敏感会误触发,太迟钝又撞上去了还不回报。它基本不可能靠“抄一个通用参数”解决,只能结合具体机构去试。
sensorless homing 在资料中通常看起来很直接,但落到具体机器上仍然需要逐轴调试。不同的皮带张力、负载、导轨摩擦和供电条件,都会让这一组参数发生明显变化。
第五步:把构建裁成只保留需要的部分
platformio.ini 的裁剪也是本次改造中的一项重要工作。
构建目标没有直接沿用整套 Marlin 默认模块,而是压缩成一个更接近“运动控制器”的集合。核心配置思路如下:
[platformio]
default_envs = STM32G0B1RE_btt
[common]
default_src_filter = +<src/*> -<src/config> -<src/tests>
-<src/feature>
-<src/gcode/bedlevel>
-<src/gcode/probe>
-<src/gcode/sd>
-<src/module>
-<src/module/stepper>
+<src/gcode/calibrate/G28.cpp>
+<src/gcode/config/M92.cpp>
+<src/gcode/motion/G0_G1.cpp>
+<src/module/endstops.cpp>
+<src/module/motion.cpp>
+<src/module/planner.cpp>
+<src/module/stepper.cpp>
+<src/module/temperature.cpp>
这里的思路不是“追求极端瘦身”,而是让编译结果更贴近这台机器真正会用到的能力。
这样做的作用包括:
- 更容易确认固件到底依赖了哪些 G-code 和模块
- 调试时少一些无关特性带来的干扰
- 从项目维护角度看,固件职责会更清楚
这类裁剪并不是越狠越好。更稳妥的方式是先让完整链路跑通,再回头删功能。否则一开始就大面积裁模块,最后遇到问题时很难判断是参数错误,还是依赖文件被一起删掉。
还有一些配套修改
前面几节主要是控制链路本身。除此之外,还调整了几组更偏“配套”的设置,主要和串口、界面、状态上报以及风扇管理有关。
#define SHOW_CUSTOM_BOOTSCREEN
#define CUSTOM_STATUS_SCREEN_IMAGE
#define SERIAL_PORT 2
#define BAUDRATE 115200
#define SERIAL_PORT_2 -1
#define ENDSTOP_INTERRUPTS_FEATURE
#define EEPROM_SETTINGS
#define SDSUPPORT
#define INDIVIDUAL_AXIS_HOMING_MENU
#define CR10_STOCKDISPLAY
这几项里比较实际的有几类:
SERIAL_PORT 2和BAUDRATE 115200用于和上位机通信的串口配置。SHOW_CUSTOM_BOOTSCREEN和CUSTOM_STATUS_SCREEN_IMAGE这两项让启动界面和状态页显示自定义位图。虽然不影响运动控制,但能更直观地区分这块板现在刷的是哪份固件。EEPROM_SETTINGS用来保存一些运行后确认过的设置,避免每次上电都从头再配。SDSUPPORT、CR10_STOCKDISPLAY、INDIVIDUAL_AXIS_HOMING_MENU这些主要是为了保留本地调试和界面操作能力,方便在不连上位机的时候做基础检查。ENDSTOP_INTERRUPTS_FEATURE用中断方式处理限位触发,能让归零和边界检测更直接一些。
在 Configuration_adv.h 里还有一组和散热、上报有关的配置:
#define USE_CONTROLLER_FAN
#define E0_AUTO_FAN_PIN FAN1_PIN
#define AUTO_REPORT_POSITION
#define M114_DETAIL
#define HOST_ACTION_COMMANDS
USE_CONTROLLER_FAN板子风扇跟着驱动工作状态走,长时间运行会更稳一些。E0_AUTO_FAN_PIN FAN1_PIN这里虽然还是沿用E0这个名字,但实际是把对应风扇输出也接进了现在这套控制结构里。AUTO_REPORT_POSITION和M114_DETAIL调试时更方便看当前位置和规划状态。HOST_ACTION_COMMANDS让上位机侧更容易接收暂停、继续之类的状态变化。
编译与刷写
编译与刷写是验证配置修改的最后步骤。前面的固件配置需要生成实际固件并刷回控制板,才能进入硬件验证。
platformio.ini 中的目标环境设为 STM32G0B1RE_btt,编译也围绕这个环境生成固件:
[platformio]
default_envs = STM32G0B1RE_btt
如果直接用 PlatformIO,命令大致就是:
pio run -e STM32G0B1RE_btt
编译完成之后会生成 firmware.bin。对 SKR Mini E3 V3.0 这种板子,比较直接的刷写方式就是:
- 把
firmware.bin拷到 SD 卡根目录 - 断电状态下把 SD 卡插回控制板
- 重新上电,让板载 bootloader 自动读取并刷写
通常刷写完成后,这个文件会被改名成 FIRMWARE.CUR,或者不再保持原来的 firmware.bin 文件名。这个流程不需要额外下载器,适合配置修改后的快速验证。
小结
这次修改里真正重要的部分,主要还是这几件事:把 E0 资源改造成 U 轴、把机器语义从 XYZ + E 改成 XYZU、重新定义运动和归零参数、把 TMC2209 的 UART 与无感归零调通,再把最终固件编成 firmware.bin 刷回控制板。
在这个项目中,Marlin 更像是一个已经成熟的底层执行框架。本次修改的主要工作,是把它从打印机场景重新适配到实验移液自动化平台真正需要的那一组功能上。