Marlin SAMD21 HAL 深入解析:架构、构建验证与移植避坑指南
【免费下载链接】MarlinMarlin is a firmware for RepRap 3D printers optimized for both 8 and 32 bit microcontrollers. Marlin supports all common platforms. Many commercial 3D printers come with Marlin installed. Check with your vendor if you need source code for your specific machine.项目地址: https://gitcode.com/GitHub_Trending/ma/Marlin
本篇技术指南以Marlin/src/HAL/SAMD21/AGENTS.md为核心,系统讲解 Marlin 固件中 SAMD21(Atmel SAMD21J18,ARM Cortex-M0+)硬件抽象层(HAL)的整体架构、构建与测试流程、框架集成方式,以及源码中不易察觉的七类关键"陷阱"。读者将掌握 SAMD21 HAL 的串口/SPI/ADC/EEPROM 实现原理、mftest验证命令的正确用法,以及新增 SAMD21 板卡环境时必须满足的编译前提,可直接用于二次开发与移植实践。
一、SAMD21 HAL 概览与仓库布局
SAMD21 HAL 位于仓库 Marlin/src/HAL/SAMD21/,由 Bart Meijer(brupje)开发,基于 Giuliano Zaro 的 SAMD51 HAL 改写而来(见 HAL.h)。该目录承担了 Marlin 在 SAMD21 平台上的全部底层能力,包括:
- 串口:原生 USB-CDC 与硬件 UART(SERCOM)封装;
- SPI:基于上游
SPIClass的硬件 SPI 驱动(MarlinSPI.h、HAL_SPI.cpp); - ADC:12 位 ADC 自由运行扫描与中断采集(HAL.cpp);
- 定时器/伺服/引脚:timers.cpp、Servo.cpp、fastio.h;
- EEPROM 后端:eeprom/ 下的
eeprom_wired.cpp、eeprom_flash.cpp、eeprom_qspi.cpp三种实现; - u8g LCD 驱动:u8g/ 下的 I2C/硬件 SPI 例程。
与 HAL 配套的板级引脚文件位于 Marlin/src/pins/samd/,目前包含四块板卡:
| 引脚文件 | 对应板卡 |
|---|---|
pins_MINITRONICS20.h | ReprapWorld Minitronics v2.0(SAMD21J18) |
pins_BRICOLEMON_V1_0.h | Bricolemon V1.0 |
pins_BRICOLEMON_LITE_V1_0.h | Bricolemon Lite V1.0 |
pins_RAMPS_144.h | RAMPS 1.4.4 适配 |
构建环境的接入点则分散在两处:ini/samd21.ini定义平台环境,buildroot/share/PlatformIO/scripts/SAMD21_minitronics20.py提供按环境定制的构建脚本。
二、构建与测试:用mftest验证 SAMD21 目标
SAMD21 家族目前只有一个真实测试目标SAMD21_minitronics20,它是 SAMD21 最严格(也是唯一)的约束条件——只要它构建通过并保持绿色,就代表该家族整体得到覆盖。
2.1 执行验证命令
仓库推荐使用mftest进行验证,因为它会按目标重新生成Marlin/Configuration.h,而裸执行pio run -e无法可靠地做到这一点:
cd "$(git rev-parse --show-toplevel)" buildroot/bin/mftest -t SAMD21_minitronics20 -n1 -y # ReprapWorld Minitronics 2.0, SAMD21J18其中-t指定测试目标,-n1表示仅执行一次,-y自动确认。测试目标的真实配置位于 buildroot/tests/SAMD21_minitronics20/config-01.ini,其[config:base]段落给出了该板卡的参考配置,例如:
motherboard = BOARD_MINITRONICS20 serial_port = -1 x_driver_type = DRV8825 y_driver_type = DRV8825 z_driver_type = DRV8825 e0_driver_type = DRV8825 bltouch = on endstop_interrupts_feature = on filament_runout_sensor = on lin_advance = on eeprom_settings = on reprapworld_graphical_lcd = on该配置开启了 BLTouch、限位中断、断料检测、线性提前、EEPROM 设置、图形 LCD 等一揽子特性,是 SAMD21 上"功能全开"的构建压力测试。
注意:不要把
SAMD51_grandcentral_m4(buildroot/tests/SAMD51_grandcentral_m4/config-01.ini)与 SAMD21 混淆,前者属于 SAMD51 家族,两者架构并不相同。
2.2 清理与重建规则
禁止使用rm -rf .pio/build/...强制重建——交叉配置的写保护会拦截该操作,且本身不安全。正确做法是:
pio run -e SAMD21_minitronics20 -t clean或者直接让mftest自行触发重建。
2.3 环境配置要点
SAMD21_minitronics20是 SAMD21 唯一的 PlatformIO 环境,定义于 ini/samd21.ini:
[env:SAMD21_minitronics20] platform = atmelsam board = minitronics20 build_flags = ${common.build_flags} -std=gnu++17 -DUSBCON -DUSBD_USE_CDC -D__SAMD21__ -DARDUINO_SAMD_MINITRONICS20 -Wno-deprecated-declarations -DDEBUG -IMarlin/src/HAL/SAMD21/u8g build_unflags = -std=gnu++11 build_src_filter = ${common.default_src_filter} +<src/HAL/SAMD21> extra_scripts = ${common.extra_scripts} pre:buildroot/share/PlatformIO/scripts/SAMD21_minitronics20.py debug_tool = atmel-icebuildroot/share/PlatformIO/scripts/SAMD21_minitronics20.py是 per-env 额外脚本,其作用仅是根据MARLIN_FEATURES中的RX_BUFFER_SIZE/TX_BUFFER_SIZE调整串口缓冲区BUILD_FLAGS(取两者与 350 的最大值),不涉及其他逻辑改动。
三、框架与平台集成:上游atmelsam,并非内置副本
与 AT32 等平台不同,SAMD21 的 Arduino 核心是PlatformIO 上游atmelsam平台(framework-arduino-samd-*,例如framework-arduino-samd-reprap/...-adafruit),仓库内没有Marlin 自带的 vendored 副本(buildroot/share/PlatformIO/下不存在)。因此:
- Marlin 特有的胶水代码分布在三处:
- Marlin/src/HAL/SAMD21/——HAL 本体;
- Marlin/src/pins/samd/——板级引脚定义;
- buildroot/share/PlatformIO/scripts/SAMD21_minitronics20.py 与 ini/samd21.ini——构建胶水。
- 因为框架来自上游,所以不存在"mirror-to-installed-package"(镜像到已安装包)警告——该警告只适用于 vendored 的 AT32 核心。
四、源码中不明显的七个"陷阱"(Gotchas)
这是 AGENTS.md 的核心实战部分,以下逐条结合源码印证。
4.1 编译器宏是__SAMD21__,由构建参数注入,而非核心定义
HAL 的所有源码都通过#ifdef __SAMD21__门控编译,包括 HAL.cpp、HAL_SPI.cpp 以及eeprom/下的各实现。但上游 Arduino SAMD 核心定义的宏是ARDUINO_ARCH_SAMD,并不定义__SAMD21__。正是 ini/samd21.ini 通过-D__SAMD21__注入该宏。
移植铁律:新增任何 SAMD21 板卡环境,必须在
build_flags中包含-D__SAMD21__,否则该 HAL 目录下的一切代码都不会参与编译。
4.2 主串口是原生 USB-CDC,而非 UART
HAL.h 中:
typedef ForwardSerial1Class< decltype(SerialUSB) > DefaultSerial1; extern DefaultSerial1 MSerialUSB; typedef ForwardSerial1Class< decltype(Serial1) > DefaultSerial2; typedef ForwardSerial1Class< decltype(Serial2) > DefaultSerial3; extern DefaultSerial2 MSerial0; extern DefaultSerial3 MSerial1; ... #define USB_SERIAL_PORT(...) MSerialUSBDefaultSerial1绑定SerialUSB(即MSerialUSB),而Serial1/Serial2通过 SERCOM 映射到硬件 UART(MSerial0/MSerial1)。构建参数中的-DUSBCON -DUSBD_USE_CDC(ini/samd21.ini)使SerialUSB得以存在。
因此测试配置 config-01.ini 中serial_port = -1表示放弃 UART、完全依赖 USB。若某块板卡没有原生 USB,必须重新指定串口端口,否则无法与主机通信。
4.3 软件 SPI 是硬性#error
HAL_SPI.cpp 中:
#if ANY(SOFTWARE_SPI, FORCE_SOFT_SPI) #error "Software SPI not supported for SAMD21. Use Hardware SPI." #else即启用SOFTWARE_SPI或FORCE_SOFT_SPI会直接编译失败。MarlinSPI只是SPIClass的别名(MarlinSPI.h),即上游 SAMD SERCOM SPI 驱动。spiSendBlock()(HAL_SPI.cpp)注释中声称"Uses DMA",但实际调用的是阻塞式SPI.transfer()——这在功能上没有问题,只是不要指望 SD 卡路径获得 DMA 卸载。
硬件 SPI 的速率映射(HAL_SPI.cpp):
| Marlin 速率常量 | 实际时钟 |
|---|---|
SPI_FULL_SPEED | 8 MHz |
SPI_HALF_SPEED | 4 MHz |
SPI_QUARTER_SPEED | 2 MHz |
SPI_EIGHTH_SPEED | 1 MHz |
SPI_SIXTEENTH_SPEED | 500 kHz |
SPI_SPEED_5 | 250 kHz |
SPI_SPEED_6 | 125 kHz |
| 默认 | 4 MHz |
4.4 EEPROM 后端按板卡选择,不自动探测
SAMD21 HAL 的 eeprom/ 目录提供三种PersistentStore实现:
- I2C EEPROM(eeprom_wired.cpp):经
USE_WIRED_EEPROM使能,但对 SAMD21 而言该宏是显式#error("USE_WIRED_EEPROM emulation Not implemented on SAMD21"); - Flash 模拟 EEPROM(eeprom_flash.cpp):经
FLASH_EEPROM_EMULATION使能,通过 NVMCTRL 按 ROW 擦除、按页写入,数据保存在__attribute__((__aligned__(256)))的flashdata静态数组中,容量由MARLIN_EEPROM_SIZE决定; - QSPI EEPROM(eeprom_qspi.cpp + QSPIFlash.cpp):经
QSPI_EEPROM使能,代码已写好但同样以#error "QSPI_EEPROM emulation Not implemented on SAMD21"拒绝(eeprom_qspi.cpp),当前没有任何板卡选用。
各板卡的实际默认(见 pins_MINITRONICS20.h):
//#define FLASH_EEPROM_EMULATION //#define I2C_EEPROM // EEPROM on I2C-0 #define MARLIN_EEPROM_SIZE 500U // 4000 bytesBRICOLEMON_*与RAMPS_144默认I2C_EEPROM;MINITRONICS20保持FLASH_EEPROM_EMULATION注释状态,并定义MARLIN_EEPROM_SIZE 500U(注意:注释中"4000 bytes"的表述与 500U 存在出入,实际容量以MARLIN_EEPROM_SIZE为准);QSPI_EEPROM已编码但当前无板卡启用。
4.5WDT宏与 CMSIS 结构体字段冲突
在 HAL.cpp 中,get_reset_source()的函数体被特意包裹在宏推送/弹出中:
#pragma push_macro("WDT") #undef WDT // Required to be able to use '.bit.WDT'. Compiler wrongly replace struct field with WDT define uint8_t MarlinHAL::get_reset_source() { return 0; } #pragma pop_macro("WDT")原因在于REG_WDT_CTRL会解析为.bit.WDT,而预处理器会错误地把WDT宏替换进结构体字段。不要试图通过重命名字段来"修复"此问题——必须保留宏 push/pop 结构。
4.6 ADC 是自由运行的 INPUTSCAN 扫描,传感器顺序被重映射
HAL.h 定义:
#define HAL_ADC_FILTERED 1 // Disable Marlin's oversampling. The HAL filters ADC values. #define HAL_ADC_VREF_MV 3300 #define HAL_ADC_RESOLUTION 12 #define HAL_ADC_AIN_START ADC_INPUTCTRL_MUXPOS_PIN3 #define HAL_ADC_AIN_NUM_SENSORS 3adc_init()(HAL.cpp)将 12 位 ADC 配置为 16 位均值结果(32 次采样累加、ADJRES(4)、GAIN_DIV2)、自由运行模式,并扫描 3 个连续 AIN(ADC_INPUTCTRL_INPUTSCAN(HAL_ADC_AIN_LEN))。SAMD21 的 ADC 只能扫描连续的 AIN,因此:
ADC_Handler()通过ADC->INPUTCTRL.bit.INPUTOFFSET将结果写入adc_results[pos]数组(HAL.cpp);adc_start()在应用层把引脚的 AIN 重新映射回数组索引(HAL.cpp):pos = PIN_TO_INPUTCTRL(pin) - HAL_ADC_AIN_START + 1,当pos等于传感器数时归零。
此外HAL_ADC_FILTERED 1意味着Marlin 自身的过采样被关闭——HAL 层已经完成了滤波(32 次平均),上层无需重复处理。
4.7 48 MHz Cortex-M0+,无 FPU
SAMD21 最高主频 48 MHz,且没有硬件浮点单元。在热路径(如运动规划、温度 PID)中应尽量使用整数/定点运算。另外,freeMemory()(HAL.cpp)实现为基于_sbrk/__bss_end__的堆尾估算:
int freeMemory() { int free_memory, heap_end = (int)_sbrk(0); return (int)&free_memory - (heap_end ?: (int)&__bss_end__); }这只是一个粗略的堆尾 hack,并非真正的栈检查,不应据此判断栈溢出风险。
五、SAMD21 代码约定(Conventions)
- 所有 HAL 源码均由
#ifdef __SAMD21__门控——新增源文件或新板卡环境时务必注入-D__SAMD21__(见 4.1)。 - 修改边界清晰:HAL 改动放入 Marlin/src/HAL/SAMD21/,板级引脚改动放入 Marlin/src/pins/samd/,构建胶水放入 buildroot/share/PlatformIO/scripts/(per-env)或 ini/samd21.ini。
- 原生 USB CDC 是预期的主机链路:默认将
SerialUSB/MSerialUSB视为主端口,除非板卡显式迁移到 UART。
六、看门狗与复位语义(补充实现细节)
虽然 AGENTS.md 未展开,但 HAL.cpp 中的看门狗实现值得了解:watchdog_init()将 GCLK2 配置为 1.024 kHz(对 32.768 kHz OSCULP32K 分频),喂给 WDT,并设置 4 秒(WATCHDOG_DURATION_8S时 8 秒)复位超时;watchdog_refresh()通过写入WDT_CLEAR_CLEAR_KEY清狗。注意:初始化后必须至少每 4 秒刷新一次,否则 SAMD21 会进入紧急复位流程。MarlinHAL::reboot()直接调用NVIC_SystemReset()重启固件。
七、与共享 HAL 的协作关系
SAMD21 HAL 复用了 Marlin/src/HAL/shared/ 中的共享 API(eeprom_api、SPI 辅助函数等),例如PersistentStore的eeprom_exclude_size语义、HAL_SPI.h的接口声明都来自共享层。在阅读 SAMD21 特定实现时,需要与共享目录对照理解接口约定。
结语
SAMD21 HAL 是一个"麻雀虽小、五脏俱全"的移植范本:上游框架 + 少量胶水 + 严格宏门控是其骨架,USB-CDC 主串口、SERCOM SPI、自由运行 ADC、按板卡选择的 EEPROM 后端是其血肉,而__SAMD21__宏注入、WDT宏冲突规避、软件 SPI 拒绝等约定则是所有后续移植者必须遵守的边界条件。以mftest -t SAMD21_minitronics20为唯一验证闸门,即可在新增板卡时快速确认家族完整性。
【免费下载链接】MarlinMarlin is a firmware for RepRap 3D printers optimized for both 8 and 32 bit microcontrollers. Marlin supports all common platforms. Many commercial 3D printers come with Marlin installed. Check with your vendor if you need source code for your specific machine.项目地址: https://gitcode.com/GitHub_Trending/ma/Marlin
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考