最近两个月MBA美帝在职研的课程即将结束,经过个人的努力,目前两门课:全球商务、定量决策均分绩点如下,基本上逼近满分(凡尔赛了,哈哈哈),光鲜亮丽的背后,带领小组始终冲在了班级的最前面,同时也付出了不少精力和汗水,通宵了不知道多少个夜晚,交完学期项目以后就结课啦,开始进入下一门课的学习!所以开始有时间可以折腾新东西啦:(不少同学应该知道我开了个知识星球,专门分享这些内容,这里就不好放出来啦,有兴趣和我说即可)
Marlin相当庞大,自2.x版本开始,陆陆续续开始集成了32位机,主流的STM32、LPC等高性能MCU逐渐取代了以Arduino2560等8位机的3D打印机方案。Marlin,庞大到让每个初学者开始学习看代码都会觉得闻风丧胆,当然我刚开始接触的时候也不例外,但是不管是学什么东西,只要把大方向掌握了,那么细节的东西用到了再慢慢研究也不迟,同时,Marlin的文档非常少,对于初学者来说,是非常不友好的!以下这些介绍也是Marlin刚开放不久的说明文档,笔者英语水平有限,如有翻译失误,尽情纠正与谅解!
1、Marlin代码工程架构
以下是原版的Marlin固件解压下来的显示情况:
当使用 PlatformIO 构建Marlin时,它会在.pio此处创建一个文件夹,并且在shell中使用git的时候,这 是当前的工作目录,如下图所示:
1.2、 buildroot 文件夹
该目录包含开发人员的帮助脚本、字体工具、CI测试工具、Marlin图标和其它数据
1.3、 buildroot/share/PlatformIO 文件夹
电路板定义、环境变量、链接脚本和构建脚本会存放在这个地方,这个文件夹内的构建环境会经常引用 ini文件夹
1.4、 ini 文件夹
包含按 MCU 类型组织的所有 PlatformIO 环境设置, platformio.ini 文件将这些引入为 PlatformIO 提供构建/上传/调试设置,有一个文件特别有用:即是Marlin自定义构建脚本 features.ini 在构建开 始时用于根据启用的功能过滤源文件,因此构建能够完成得更快。
1.5、 Marlin 文件夹
构建Marlin时,配置文件就放在这个位置:
1.6、 Marlin/src 文件夹
包含Marlin应用程序,Marlin基于 Arduino 框架进行开发,所以它含有 Arduino 框架的setup()
和 loop()
功能。
2、Marlin应用源代码Marlin/src 文件夹
Marlin应用源代码主要由以上文件夹组成,核心的入口程序文件是 MarlinCore.cpp
,MarlinCore.h
会做一些宏和变量以及函数的声明,在这个文件中可以找到基于 Arduino
框架的 setup()
和 loop()
函 数,其余的文件夹包含的源码主要的作用如下:
- core文件夹
包含Marlin源文件需要的类型、宏、使用程序功能等等,大多数源文件只需要包含 inc/MarlinConfig.h
就可以确保这些文件按预期顺序包含在内。
- feature文件夹
包含一些可选功能的支持代码,当然这个文件夹下的代码有些功能是非常简单的,只需要添加一个 G代码或者在公共代码中插入更改即可完成。如果一个特性需要定义一个类或者一组函数,这些附 加的文件将放在这个位置。
- gcode 文件夹
包含GCodeParser
类的定义以及所有G代码命令的实现(有一些不在这里实现,但大部分是的),这 些功能都被包装在一个名为 GcodeSuite
的类里,G代码实现的文件被捆绑在多个类别的子文件夹 中,这些文件被命令为具体的G代码,因此可以使用 IDE 的查找功能找到它们。
- HAL文件夹
每个控制器系列都提供控制硬件的功能,但并非所有控制器系列都使用相同的接口, Marlin2.x
版本实现了硬件抽象层,更好的屏蔽了平台的差异性,使其它的平台更好的兼容 Arduino
框架。
- inc文件夹
包含了Marlin版本、配置条件等等的基本内容,请注意,每个HAL还包含自己的 Conditionals*.h
和 SanityCheck.h
文件 。
- lcd 文件夹
所有与 LCD
、TFT
、OLED
、编码器、按钮和串行控制器的相关代码都放在这里,语言翻译通常仅 适用于外部控制器,因此语言翻译也放在这里。
- libs文件夹
任何通用数据函数实现或者与硬件库代码都放在这里,如蜂鸣器代码、 CRC16 校验和实现、 3X3 矩阵、数字到字符串的转换功能、用于二进制传输的 HeatShrink ,甚至还有几个 EEPROM 。
- module文件夹
这里定义了机器的所有典型功能,包括 3D 打印机拥有的所有的组件,例如:加热器和传感器、热 床探测、路径规划算法、将命令转换为分段运动的高级运动功能、将毫米分段快速转换为步进块的 运动路径规划,以及将块段转换为中断时序和STEP信号的步进器 ISR 。
- pins文件夹
Marlin所有的板定义都在这个文件夹中,这里有不同的硬件架构,每个架构下的每块板都有自己独 特的引脚文件, pins.h
根据 MOTHERBOARD
设置进行包含,由于 pins.h
是 MarlinConfig.h
的包 含文件注意,因此它不会包含在其它的地方。
- sd 目录
在这里您可以找到所有实现实际文件和文件夹的高级文件系统代码。 CardReader
类是Marlin用 于导航目录、打开G-code
文件和从SD卡(或其他媒体)打印的主界面。自从Marlin 2.0.8
以来,所有 的媒体类型都派生自 DiskIODriver
抽象类。
3、Marlin的配置
马林是高度可配置的。您将在源代码的许多地方发现应用配置选项来打开和关闭代码、更改行为和提供 值。
- Marlin源文件如何获得它需要的所有配置值?
首先,当您构建一个c++程序时,第一阶段是分别构建所有的. C和 .cpp 文件。这些文件中的每一个都 必须是完整的,并且每个文件都负责包含它需要的头文件。在大型项目中,这可能会很乏味,所以很多 编译器都允许您创建“预编译头文件”( .PCH 文件),包括所有公共头文件。Marlin不使用 PCH 文件,而是 使用以类似方式工作的常规头文件。
在inc、core、HAL和pins文件夹中头文件的包含顺序很重要,因为每个头文件都是建立在其前身文件之 上的。为了确保始终遵循正确的包含顺序,任何需要配置和条件文件(直到conditionals_ad.h
)的代码都 必须包含 inc/ marlinconfigpreh
,而任何需要完全实现的硬件配置的代码都必须包含 inc/MarlinConfig.h
。
让我们仔细看看每个文件包含的头文件。
3.1、 MarlinConfigPre.h
3.2、 MarlinConfig.h
4、一个典型的源文件
将这些内容放在一起,典型的源文件至少将包含 marlinconfigpreh ,以便它可以预先检查一些配置 值。只有在需要时,才会包含其他头文件。有些源文件包含一些特性的头文件是很常见的。
/** * (c) 2021 Marlin Firmware * A typical Marlin source.cpp file. */ #include "inc/MarlinConfigPre.h" #if ENABLED(MY_COOL_FEATURE) #if ENABLED(EXTENSIBLE_UI) #include "lcd/extui/ui_api.h" #endif #endif // MY_COOL_FEATURE
5、典型的头文件
Marlin头文件不会像源文件一样使用 #if…#endif
来包装。相反,如果不需要头文件,那么它就不会被 包含。Marlin还避免使用c风格的 #ifdef
包装器,并且只在自己的头文件上使用 #pragma
一次。
/** * (c) 2021 Marlin Firmware * A typical Marlin header.h file. */ #pragma once extern int my_feature_var; void do_feature_stuff();
当该特性被禁用时,一些头文件将提供空函数。这使得在单个点关闭东西更容易,并且可以使其他地方 的代码更整洁。
/** * (c) 2021 Marlin Firmware * A typical Marlin header.h file. */ #pragma once #include "inc/MarlinConfigPre.h" #if ENABLED(MY_COOL_FEATURE) extern int my_feature_var; void do_feature_stuff(); #else inline void do_feature_stuff() {} #endif
6、Marlin的构建过程
一个Marlin式的结构可能需要一段时间,但它像其他任何草图一样工作。Marlin中的所有 .cpp 文件及 其依赖项都将被编译,以及它们包含的任何内容。使用 PlatformIO 的Marlin构建将使用 ini 文件夹中 的文件以及 buildroot/share/PlatformIO
中的脚本,根据您的配置过滤掉未使用的源文件,从而使 其更快。
7、程序和命令流程
Marlin程序的执行从 MarlinCore.cpp
开始,setup()
函数初始化,loop()
函数主循环,就像 Arduino 草 图一样。loop()
函数非常小,主要负责调用idle()
,然后在队列前面运行下一个G-code命令。
Marlin中的大多数任务都是通过空闲函数执行的,该函数调用 manage_inactivity
和 thermalManager.manage_heater
。您将看到许多对空闲的调用,因为所有等待循环都使用它来保持 机器运行。如果Marlin太长时间没有呼叫空闲,看门狗就会被触发,为了安全重新启动机器。
这个程序架构需要一些注意,因为我们不希望某个函数被idle本身调用,直到堆栈爆炸为止。在Marlin 只有少数的再入守卫,所以在实践中它工作得很好。
从空闲状态跟踪函数调用,可以很直观地看到Marlin是如何使所有设备和特性在程序上下文中运行的。一些特性一直在进行活动,但是Marlin所做的大部分工作都是由G-code命令发起的。
8、中断服务例程
Marlin定义了一些中断服务例程( ISRs ):
- 步进 ISR 反复运行,通过向步进电机的STEP和DIR引脚发送脉冲以高速移动规划队列和步进电机。这种中断的频率与移动速度有关。
- 温度 ISR 以接近 1KHz 的频率读取温度传感器,并在读数准备好时向主程序发送信号。它还管理不 需要非常高基频的加热器和风扇配置的软件/慢 PWM 。
- 终止 ISR 可以被激活,如果终止引脚是中断能力的。它只在结束引脚的输入状态改变时触发。
- Tone Timer由 Arduino 为某些平台定义,由Marlin为其他平台定义。它处理脉冲压电蜂鸣器来创 建音调,它运行的频率是当前音调的两倍。
- 伺服定时器提供了用于伺服系统的 PWM 信号。
平台还将为Serial UART和其他设备定义中断,所以Marlin必须小心选择它使用的中断和计时器。
9、G代码的处理
接下来,让我们看看如何处理 G 代码并遵循程序流程。
- 0.紧急解析器
当命令队列被阻塞,但是机器需要用户以某种形式输入时,您可以做什么?紧急解析器是一个简单的状态机,它在串行代码的低级别上运行,监视某些命令。当它看到 M108、M112 等时,它立即采取行动来处理代码。
- 1.从Serial和SD读取
manage_inactivity
函数调用queue.get_available_commands()
,该函数检查即时缓冲区,查询串行 端口,并读取活动的SD打印文件,将行复制到命令队列中,目的是使其充满。
- 2.弹出G代码
主循环()调用queue.advance()
获取队列前面的命令并立即运行它。在命令完成之前,Marlin不会返回到loop()
。注意```queue.advance() ````在队列之前运行内部命令,因此Marlin可以命令自身在常规命令流中 执行一些操作。
- 3.预扫描G代码
一旦queue.advance()
选择了下一个命令,它就调用解析器对G-code行进行预处理。预处理程序验证行号和校验和,如果一切正常,它会在调用特定的G-code处理程序之前对参数进行快速预扫描。
- 4.处理G代码
所有的G-code处理程序都封装在 GcodeSuite 类中(例如, GcodeSuite::G28()
),尽管有一些在其他地方实现。G-code处理程序是一个简单的没有返回值的void方法。它不是从函数调用中获取参数,而是查询 GCodeParser 类来检查参数并读取它们的值。例如,处理程序使用parser.seen('X')
来检查'X'参数是否存在,然后调用parser.value_float()
以浮点数的形式获取其数值。请参阅gcode/parser.h
获取所有可用的方法。
G-code处理程序几乎可以做任何事情,所以它们被分成单独的文件,每个文件只包含它需要的头文件。所有处理程序必须包括gcode.h
,这将包括parser.h
。
- 5.命令阻塞
当一个 G1 命令的移动队列被加入时,它就被认为是完成的,所以它可以立即返回。在实践中,特别是 在床水平启用时,每一次线性移动都有可能填满规划队列,并阻塞长时间等待空间打开。
当一个命令需要等待计划器或用户反馈中的空闲空间等内容时,它将调用空闲函数以保持机器的活动和 运行。idle函数甚至会将传入的命令读取到队列中,但是由于idle函数并不会分发g代码或推进队列,所 以在处理程序结束并返回之前,队列不能获得任何空的命令。
往期精彩
让野火F103开发板支持Marlin2.0固件是什么体验?3D打印主控板成员+1