资讯详情

Marlin 固件 TEENSY31_32 HAL 深入解析:MK20DX256 平台适配与开发约定

📅 2026/9/14 5:51:11 | 华诺云谱 👁 阅读
Marlin 固件 TEENSY31_32 HAL 深入解析:MK20DX256 平台适配与开发约定
Marlin 固件 TEENSY31_32 HAL 深入解析MK20DX256 平台适配与开发约定【免费下载链接】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导读本文基于 TEENSY31_32 HAL 工作笔记 展开系统讲解 Marlin 固件在 PJRC Teensy 3.1 / 3.2 板卡NXP/Freescale MK20DX256ARM Cortex-M472 MHz256 KB Flash上的硬件抽象层实现。文中将完整继承原文档的构建测试流程、框架集成方式、源码级陷阱清单与开发约定并深入对应源码文件验证每一步实现帮助读者掌握如何为 Teensy 3.1/3.2 构建与回归测试 Marlin、该 HAL 各模块EEPROM、定时器、SPI、舵机、ADC、快速 IO、串口的真实实现机理以及为这一平台贡献代码时必须遵守的边界与约束。平台概览TEENSY31_32 HAL 是什么Marlin/src/HAL/TEENSY31_32/是 Marlin 面向Teensy 3.1 / 3.2两块板卡的硬件抽象层。两者共用同一颗 MK20DX256 芯片ARM Cortex-M4 内核、72 MHz 主频、256 KB Flash、64 KB RAM并拥有 2 KB 真正的片上 EEPROM。与其他一些 HAL 最大的不同点在于Teensy 的 Arduino 核心core并非随仓库 vendored内置而是由 PlatformIO 的teensy平台在构建时拉取的上游 Teensyduino/Arduino 核心。这意味着Marlin/src/HAL/TEENSY31_32/只包含 Marlin 与上游核心之间的胶水适配代码改动范围被严格限制在本 HAL 目录与引脚映射文件内。目录结构如下Marlin/src/HAL/TEENSY31_32/ ├── AGENTS.md # 本文所依据的工作笔记 ├── HAL.cpp / HAL.h # 平台核心抽象复位、看门狗、ADC、串口声明 ├── timers.cpp / timers.h # FTM0/FTM1 定时器步进电机 温度采样 ├── eeprom.cpp # 有线 EEPROM 持久化存储后端 ├── HAL_SPI.cpp # 基于 Teensyduino SPI 库的 SPI 封装 ├── MarlinSPI.h # MarlinSPI 类型别名 ├── Servo.cpp / Servo.h # 舵机库封装 ├── fastio.h # 基于 bitbanding 的快速 GPIO ├── endstop_interrupts.h # 限位开关中断 ├── spi_pins.h / pinsDebug.h └── inc/ # LCD 条件编译与 SanityCheck构建与测试用 mftest 验证 Teensy 3.1/3.2 固件为什么用mftest而不是裸pio run原文档强调验证 TEENSY31_32 HAL 的标准方式是mftest因为它会按目标重新生成Marlin/Configuration.h而直接pio run -e无法可靠地做到这一点。构建命令如下cd $(git rev-parse --show-toplevel) buildroot/bin/mftest -t teensy31 -n1 -y # BOARD_TEENSY31_32, MK20DX256该命令会以teensy31环境为目标执行完整构建流程对应平台配置在 ini/teensy.ini 中定义[teensy_arm] platform teensy~4.12.0 build_src_filter ${common.default_src_filter} lib_ignore NativeEthernet [env:teensy31] extends teensy_arm board teensy31 build_src_filter ${teensy_arm.build_src_filter} src/HAL/TEENSY31_32关键点build_src_filter在通用源文件基础上追加了src/HAL/TEENSY31_32目录因此新增 HAL 源文件会被自动纳入构建无需修改 ini 文件。三套测试配置与通过标准buildroot/tests/teensy31/下存放三套测试配置全部指向同一块主板BOARD_TEENSY31_32配置说明config-01.ini默认配置config-02.ini零限位开关zero-endstops配置config-03.ini功能最全many-features压力最大其中config-03 是最紧的约束——它以完整功能集构建用来压测 256 KB Flash 的容量极限。从其内容可见功能密度极高双温度传感器、16×16 双线性自动调平、断电续打eeprom_settings、断料检测filament_width_sensor、弧线/贝塞尔插补、babystepping、喷嘴清洁/停靠、主机动作命令等一应俱全。通过标准三套配置必须全部构建绿色才能认为 Teensy 3.1/3.2 相关工作完成。清理构建产物的正确姿势原文档特别警告不要用rm -rf .pio/build/...强制重建——跨 profile 的写入保护会阻止该操作且本身不安全。正确做法是pio run -e teensy31 -t clean # 只删除构建产物或者干脆让mftest自行重建。框架集成上游 Teensyduino 核心而非 vendored核心来源与安装位置Teensy Arduino 核心来自上游 Teensyduino 包framework-arduinoteensy平台版本teensy~4.12.0见 ini/teensy.ini通常安装于~/.platformio/packages/framework-arduinoteensy*。它不在buildroot/share/PlatformIO/下AT32 才是例外情况。因此不存在镜像到已安装包的步骤Marlin 侧的改动只存在于本 HAL 文件夹核心直接从 PlatformIO 注册表拉取。HAL 目录选择由编译器宏驱动Marlin/src/HAL/platforms.h中的HAL_PATH宏负责按芯片宏选择 HAL 目录#elif defined(__MK20DX256__) #define HAL_PATH(PATH, NAME) XSTR(PATH/HAL/TEENSY31_32/NAME)也就是说选择本 HAL 的守护宏是__MK20DX256__而不是ARDUINO_ARCH_TEENSY。文件夹内每个.cpp都以#ifdef __MK20DX256__包裹例如 HAL.cpp、timers.cpp、eeprom.cpp。因为 Teensy 3.5/3.6 与 4.0/4.1 的 HAL 共享同一套teensy_arm平台与teensy工具链一旦漏写该守护宏文件会被编译进错误的 MCU 目标。这是所有新增源文件都必须遵守的第一约定。在 HAL.h 中还定义了IS_TEENSY_31_32、IS_TEENSY31/IS_TEENSY32等平台宏供引脚映射等条件编译使用。源码级陷阱清单逐一验证原文档列出六条仅看源码不容易发现的坑以下结合源码逐一展开验证这也是本 HAL 开发中最容易踩雷的地方。陷阱 1编译器守护宏是__MK20DX256__如上一节所述platforms.h 用__MK20DX256__选择 TEENSY31_32 目录。Teensy 3.5/3.6__MK64FX512__/__MK66FX1M0__与 4.0/4.1__IMXRT1062__各走各的 HAL共享工具链但共享不了源文件。始终保留#ifdef __MK20DX256__守护是贡献者最需要记住的一条。陷阱 2EEPROM 是有线的不是 SD/Flash 模拟MK20DX256 芯片自带 2 KB 真实 EEPROM因此本 HAL 使用有线wiredEEPROM直接调用 AVR 风格的avr/eeprom.h接口见 eeprom.cpp#if USE_WIRED_EEPROM #include ../shared/eeprom_api.h #include avr/eeprom.h #ifndef MARLIN_EEPROM_SIZE #define MARLIN_EEPROM_SIZE size_t(E2END 1) #endif size_t PersistentStore::capacity() { return MARLIN_EEPROM_SIZE - eeprom_exclude_size; }写入时通过REAL_EEPROM_ADDR(pos)计算真实地址并做了两个工程优化只写变化的字节if (v ! eeprom_read_byte(p))—— 因为 EEPROM 寿命约 10 万次擦写避免无谓磨损长写入期间喂狗if (written 0x7F) delay(2); else safe_delay(2);防止批量写 EEPROM 时触发看门狗复位。因此不要为此平台添加SDCARD_EEPROM_EMULATION或任何 SPI/I2C EEPROM 后端——芯片上有真实 EEPROMUSE_WIRED_EEPROM已经覆盖。注意MARLIN_EEPROM_SIZE的默认值基于E2END 1即 2 KB 的容量。陷阱 3定时器硬编码假设 60 MHz 总线时钟timer.h 中FTM 定时器的计数率直接由总线时钟推算#define FTM0_TIMER_PRESCALE 8 #define FTM1_TIMER_PRESCALE 4 #define FTM0_TIMER_PRESCALE_BITS 0b011 #define FTM1_TIMER_PRESCALE_BITS 0b010 #define FTM0_TIMER_RATE (F_BUS / (FTM0_TIMER_PRESCALE)) // 60MHz / 8 7.5MHz #define FTM1_TIMER_RATE (F_BUS / (FTM1_TIMER_PRESCALE)) // 60MHz / 4 15MHz而 timers.cpp 的HAL_timer_start()据此计算 FTM 比较值case MF_TIMER_STEP: FTM0_MODE FTM_MODE_WPDIS | FTM_MODE_FTMEN; FTM0_SC 0x00; FTM0_CNT 0x0000; FTM0_MOD 0xFFFF; FTM0_C0V (FTM0_TIMER_RATE) / frequency; FTM0_SC (FTM_SC_CLKS(0b1) FTM_SC_CLKS_MASK) | (FTM_SC_PS(FTM0_TIMER_PRESCALE_BITS) FTM_SC_PS_MASK); // Bus clock 60MHz / 8关键假设F_BUS必须为 60 MHz。teensy31板卡 JSON 的F_CPU与之一致若该值将来改变步进电机与温度定时器会以错误速率运行导致运动控制与温控失真。因此修改时钟相关配置必须极为谨慎。另一个与温度定时器相关的细节adc_init()中执行NVIC_ENABLE_IRQ(IRQ_FTM1)见 HAL.cpp即温度定时器 FTM1 的中断必须在此处使能共享温度 ISRftm1_isr见 timers.h才会触发。MF_TIMER_STEP0、MF_TIMER_TEMP1步进与温度各占一个 FTM 通道。中断使能/禁用在 timers.cpp 中实现禁用路径特意加入了内存屏障__DSB()/__ISB()确保中断在 Cortex-M 上被真正禁用。陷阱 4SPI 包装的是 TeensyduinoSPI库不是自研驱动HAL_SPI.cpp 的所有收发路径最终都落到上游SPI.h的SPI.begin()/SPI.transfer()uint8_t spiRec() { SPI.beginTransaction(spiConfig); const uint8_t returnByte SPI.transfer(0xFF); SPI.endTransaction(); return returnByte; }速率映射由 Marlin 的标准速率常量给出见spiInitspiRate时钟SPI_FULL_SPEED10 MHzSPI_HALF_SPEED5 MHzSPI_QUARTER_SPEED2.5 MHzSPI_EIGHTH_SPEED1.25 MHzSPI_SPEED_5625 kHzSPI_SPEED_6312.5 kHz默认4 MHz同时 MarlinSPI.h 简单地将MarlinSPI定义为SPIClass的别名#include SPI.h using MarlinSPI SPIClass;注意事项spiSendBlock()中仍残留着 AVR 风格的SPDR/SPSR寄存器操作代码见 HAL_SPI.cpp但该路径实际是死代码——活跃路径走SPI.transfer。不要被误导去把它修复成寄存器驱动当前实现才是正确语义。陷阱 5舵机move()里的attach(0)是占位符不是 0 号引脚Servo.h 中libServo继承官方Servo库并扩展了attach/move。真正的引脚保存在servoPin[MAX_SERVOS]数组中Servo.cpp 的实现为int8_t libServo::attach(const int inPin) { if (servoIndex MAX_SERVOS) return -1; if (inPin 0) servoPin[servoIndex] inPin; // 真实引脚只在此处记录 return super::attach(servoPin[servoIndex]); } void libServo::move(const int value) { constexpr uint16_t servo_delay[] SERVO_DELAY; static_assert(COUNT(servo_delay) NUM_SERVOS, SERVO_DELAY must be an array NUM_SERVOS long.); if (attach(0) 0) { // 0 是占位符不覆盖 servoPin仅重挂载已存引脚 write(value); safe_delay(servo_delay[servoIndex]); TERN_(DEACTIVATE_SERVOS_AFTER_MOVE, detach()); } }attach(0)传入 0 时由于inPin 0不成立servoPin[servoIndex]保持原值从而解析到之前记录的真实引脚。不要改成实际引脚号否则move()每次都会覆盖正确的引脚记录。move()还带有SERVO_DELAY驱动的安全延时并支持DEACTIVATE_SERVOS_AFTER_MOVE配置在移动后自动detach()。陷阱 6ADC 走固定查表pin2sc1a[]不是analogReadHAL.cpp 的adc_start()使用一张手工构建的查表把 Marlin 引脚号映射为 MK20DX256 的 ADC0 SC1A 通道号void MarlinHAL::adc_start(const pin_t pin) { static const uint8_t pin2sc1a[] { 5, 14, 8, 9, 13, 12, 6, 7, 15, 4, 0, 19, 3, 31, // 0-13, 视为 A0-A13 5, 14, 8, 9, 13, 12, 6, 7, 15, 4, // 14-23 (A0-A9) 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, // 24-33 064, 1964, 364, 3164, // 34-37 (A10-A13) 26, 22, 23, 27, 29, 30 // 38-43: 温度传感器、VREF_OUT、A14、bandgap、VREFH、VREFL }; ADC0_SC1A pin2sc1a[pin]; } uint16_t MarlinHAL::adc_value() { return ADC0_RA; }采样流程是adc_start()写通道 →ADC0_RA读结果adc_value()由温度中断驱动。而 HAL.h 中的analogInputToDigitalPin宏只覆盖前 12 个 A 引脚(p 12U) ? (p) 54U : -1。因此模拟采样必须走hal.adc_start()/adc_value()绝不要用analogRead()。ADC 参考电压为 3.3 V、10 位分辨率HAL_ADC_VREF_MV 3300、HAL_ADC_RESOLUTION 10。adc_init()中先analog_init()并等待校准完成再使能 FTM1 中断见 HAL.cpp。开发约定与代码边界目录职责划分HAL 改动只允许落在Marlin/src/HAL/TEENSY31_32/板级引脚改动只允许落在Marlin/src/pins/teensy3/pins_TEENSY31_32.h例如该文件将 X/Y/Z 限位开关映射到引脚 3/4/5、步进使能共用引脚 2 等见 pins_TEENSY31_32.h核心与 PlatformIO 集成属于上游——这里没有可编辑的 vendored 框架不要试图镜像或修改已安装的上游核心。fastio.h位带bitbanding快速 GPIO 与一个已知怪癖fastio.h 的头部注释写着Teensy 3.5 and 3.6但实际是 3.1/3.2 的实现——这是一个历史遗留的注释错误实现本身通过 Cortex-M 位带操作直接操纵端口寄存器#define GPIO_BITBAND_ADDR(reg, bit) (((uint32_t)(reg) - 0x40000000) * 32 (bit) * 4 0x42000000) #define GPIO_BITBAND(reg, bit) (*(uint32_t *)GPIO_BITBAND_ADDR((reg), (bit))) #define _WRITE(P,V) do{ \ if (V) CORE_PIN ## P ## _PORTSET CORE_PIN ## P ## _BITMASK; \ else CORE_PIN ## P ## _PORTCLEAR CORE_PIN ## P ## _BITMASK; \ }while(0)它基于 Teensy 核心提供的CORE_PINxx_PORTSET/PORTCLEAR符号实现原子置位/清零。原文档指出一个已知怪癖_IS_OUTPUT与_IS_INPUT定义完全相同都检查方向寄存器的 0 值即输出方向实际上没有被区分——目前无害但不要新增依赖输出方向判断的代码。串口体系Serial0 别名与多端口转发HAL.h 定义了完整的串口抽象Serial0直接别名核心的SerialUSBSerial包装SerialUSBForwardSerial1Classdecltype(SerialUSB)多端口DefaultSerialX对象转发到对应的SerialXForwardSerial1Classdecltype(Serial##X)SERIAL_INDEX_MIN/MAX为 0..3即支持最多 4 个串口索引。HAL.cpp 根据SERIAL_PORT/SERIAL_PORT_2/SERIAL_PORT_3/MMU_SERIAL_PORT/LCD_SERIAL_PORT等配置实例化对应端口对象并与 shared/serial_ports.h 协同完成按SERIAL_PORT选择默认串口的逻辑。其余模块速览看门狗HAL.cppUSE_WATCHDOG启用时配置 WDOG 超时 4 s 或 8 sWATCHDOG_DURATION_8S刷新序列为经典0xA602/0xB480复位源HAL.cpp通过RCM_SRS0区分上电/外部/看门狗复位限位中断endstop_interrupts.h所有限位引脚attachInterrupt(..., CHANGE)汇入单一endstop_ISR()仅在状态变化时调用endstops.update()省去温度中断里的轮询开销自由内存HAL.cpp基于__bss_end/__heap_start/__brkval计算堆剩余。关联资源导航共享 HAL APIeeprom_api、SPI 辅助Marlin/src/HAL/shared/板级引脚映射Marlin/src/pins/teensy3/pins_TEENSY31_32.h构建定义ini/teensy.ini回归测试配置buildroot/tests/teensy31/平台选择逻辑Marlin/src/HAL/platforms.h小结TEENSY31_32 HAL 是 Marlin 硬件抽象体系中一个上游核心 薄胶水层的典型代表全部源码受__MK20DX256__守护EEPROM 使用芯片自带 2 KB 有线存储定时器建立在 60 MHz 总线时钟假设之上SPI/舵机/ADC 分别包装或绕过上游库的特定行为。本文的六条陷阱清单直接来自维护者的开发笔记并经源码逐条印证——对任何准备为 Teensy 3.1/3.2 提交代码、排查构建或运行时问题的开发者而言遵循这些约定尤其是守护宏、EEPROM 后端选择、attach(0)占位符语义与 ADC 查表路径都能显著降低踩坑概率。【免费下载链接】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),仅供参考
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。