Marlin 中的 FTDI EVE 库深度解析:面向 FT800/FT810 的触摸屏 UI 框架与底层驱动实战指南
【免费下载链接】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
FTDI EVE 库是一套完全开源的、面向 FTDI FT800/FT810 图形处理器(EVE 芯片)的驱动程序与 UI 框架,最初由 Lulzbot(Aleph Objects)为 Marlin 固件的触摸屏界面开发,但设计上完全独立,可嵌入任何 Arduino 工程。本文将以仓库中的 ftdi_eve_lib/README.md 为骨架,结合ftdi_eve_lib目录下的真实源码,逐层拆解其"Basic API + Extended API"的双层架构:从 SPI 寄存器读写、命令 FIFO 与协处理器图形命令,到网格布局、屏幕导航、事件循环、显示列表缓存与声音播放,让你既能理解 Marlin 触摸屏背后的渲染原理,也能把这套库迁移到自己的 Arduino 项目中使用。
库的定位:为 EVE 图形芯片而生的双层 UI 架构
FTDI EVE(FT800/FT810)是一类"自带图形引擎"的显示控制芯片:主控 MCU 只需通过 SPI 向芯片的命令 FIFO 写入显示列表(Display List)和协处理器命令,芯片即可自行完成 2D 图形渲染、触摸检测、音效合成等繁重工作,从而把 8 位/32 位 MCU 从像素级渲染中解放出来。
README.md 明确指出,这个库刻意被拆分成两个层次:
- Basic API:对 FTDI 硬件与命令 FIFO 的浅封装,提供尽可能贴近 FTDI《Programmer's Guide》所描述的低层硬件访问接口;
- Extended API:在 Basic API 之上构建的 GUI 框架,用于解决构建可用图形界面时的常见难题。
从 ftdi_eve_lib.h 可以看到,库的总入口依次包含compat.h、basic/ftdi_basic.h与extended/ftdi_extended.h;而ftdi_basic.h中有一条关键逻辑:当未定义__MARLIN_FIRMWARE__(即作为独立 Arduino 库编译)时,会自动定义FTDI_BASIC/FTDI_EXTENDED宏并展开全部头文件;反之,在 Marlin 固件内部编译时则由 Marlin 自身的配置体系决定哪些模块被启用。这正是"既为 Marlin 而生、又可独立使用"设计意图的源码级体现。
在分层设计之下,Extended API 在 README 中承诺了以下 GUI 能力,后文将逐项对照源码展开:
- 基于网格的、与分辨率无关的控件布局宏;
- 基于类的 UI 屏幕,支持按下/松开触摸事件与触摸重复;
- 带按键去抖、按下视觉与听觉反馈的事件循环;
- 便捷的屏幕间导航,含用于"返回"的导航栈;
- 禁用/启用按钮的视觉区分与自定义按钮样式;
- 可播放单音符或完整声音序列的声音播放器;
- 显示列表缓存:把屏幕的静态背景元素存进 RAM_G。
第一层:Basic API —— 与 FT800/FT810 硬件直接对话
Basic API 位于 basic/ 目录,由 9 个头文件组成,职责划分非常清晰:
| 头文件 | 职责 |
|---|---|
registers_ft800.h/registers_ft810.h | 定义 FT800 与 FT810 的寄存器与内存映射 |
constants.h | 主机命令、协处理器命令、显示列表命令、音效与音符等全部常量 |
boards.h | 不同开发板/屏幕组合的引脚等适配 |
commands.h | CLCD类:SPI、寄存器读写、内存读写、命令 FIFO 封装 |
spi.h/spi.cpp | 底层 SPI 传输实现 |
display_list.h | 显示列表(Display List)相关封装 |
resolutions.h | 不同屏幕分辨率的时序参数与触摸变换矩阵 |
CLCD:寄存器与内存的统一访问入口
commands.h 开头以注释形式画出了完整的"功能地图",可以看作 Basic API 的使用手册:
- SPI 与电源控制:
spi_select()/spi_deselect()控制片选;reset()通过 Power Down 引脚复位芯片(50 ms);init()配置 FT800/810 寄存器;enable()/disable()开合 PCLK(像素时钟);set_backlight()设置背光亮度; - 内存读:
mem_read_addr()、mem_read_8/16/32()、mem_read_bulk(); - 内存写:
mem_write_addr()、mem_write_8/16/32()、mem_write_bulk()/mem_write_pgm()/mem_write_xbm()(支持从 RAM、PROGMEM 与 XBM 位图写入); - 主机命令:
host_cmd()发送 24 位主机命令; - 命令缓冲:
cmd()写入 32 位命令,str()以 32 位对齐方式发送文本。
CLCD还封装了若干高层查询:get_tag()读取当前触摸标签、is_touching()通过TOUCH_DIRECT_XY的符号位判断是否触屏按下、get_tracker()读取REG::TRACKER用于滑块等控件的拖拽跟踪。文件注释中还给出一张 RAM_G 图形内存分配表(例如0x8000挤出机位图、0x8100热床位图、0x8200风扇位图、0x35000/0xF5000分别为 FT800/FT810 的静态显示列表区),展示了库对片内 256 KB 图形内存的规划思路。
CommandFifo:驱动协处理器引擎的命令通道
CLCD::CommandFifo负责向芯片的命令 FIFO 写命令。构造即调用start()(等待协处理器空闲并定位写指针),execute()通过写REG_CMD_WRITE启动协处理器执行。它暴露了两大类接口:
- 状态控制:
reset()、is_processing()、has_fault(),以及dlstart()/swap()/coldstart()等命令快捷方式; - 协处理器图形命令:
fgcolor()/bgcolor()/gradcolor()、text()、button()、toggle()、keys()、slider()、progress()、scrollbar()、gauge()、dial()、clock()、spinner()、number()、sketch()、gradient()、track(),以及位图相关的loadimage()、getprops()、snapshot()等; - FT810 专属扩展:在
FTDI_API_LEVEL >= 810时追加setbitmap()、playvideo()、videostart()、romfont()、mediafifo()等视频与字体命令。
值得注意的细节:text/button/toggle/keys属于"复合命令",命令参数写入后还必须紧跟str()发送字符串数据——这正是 EVE 协处理器命令的固有协议。
constants.h:命令字与常量字典
constants.h 定义了与 FTDI Programmer's Guide 一一对应的常量体系,主要包括:
- 显示选项位:
OPT_CENTERX(0x0200)、OPT_CENTERY(0x0400)、OPT_RIGHTX(0x0800)、OPT_NOBACK(0x1000)、OPT_FLAT(0x0100) 等,用于控制text/button等命令的对齐与样式; - 主机命令:
ACTIVE(0x00)、STANDBY(0x41)、SLEEP(0x42)、PWRDOWN(0x50)、CLKEXT(0x44)、CLKINT(0x48)、CORESET(0x68),以及 FT800 的CLK48M/CLK36M时钟选择; - 显示列表命令:
CLEAR、COLOR_RGB、BITMAP_SOURCE、BEGIN、VERTEX2F、VERTEX2II、SAVE_CONTEXT等(FT800 与 FT810 版本分命名空间存放); - 协处理器命令:
CMD_DLSTART(0xFFFFFF00)、CMD_SWAP(0xFFFFFF01)、CMD_TEXT(0xFFFFFF0C)、CMD_BUTTON(0xFFFFFF0D)、CMD_LOADIMAGE(0xFFFFFF24)、CMD_INFLATE、CMD_CALIBRATE等; - 音效与音符:
effect_t枚举收录了SQUARE_WAVE、SINE_WAVE、BEEPING、ALARM、WARBLE、DTMF_0~9、HARP、XYLOPHONE、PIANO、BELL、CLICK、KICKDRUM等几十种合成音色;note_t则定义了从NOTE_C1到NOTE_B5的 MIDI 音符值,并含END_SONG(0xFF) 与REST(0x00) 两个控制码——它们是 Extended API 声音播放器的底层素材。
resolutions.h:三种屏幕分辨率的时序适配
resolutions.h 通过编译期宏选择屏幕参数,支持TOUCH_UI_320x240、TOUCH_UI_480x272、TOUCH_UI_800x480(含TOUCH_UI_800x480_GENERIC变体)三种规格,未匹配时报#error提示。
每种分辨率都定义了 PCLK 频率、像素时钟极性(Pclkpol)、有效分辨率(Hsize/Vsize),以及完整的行/场同步时序(Hsync0/1、Hoffset、Hcycle、Vsync0/1等)。对于 480x272 与 800x480,时序参数以"前后肩/消隐/脉宽"形式给出(如th、thfp、thb、thpw、tv、tvfp、tvb、tvpw),再通过COMPUTE_REGS_FROM_DATASHEET宏按 Bridgetek AN_336《Selecting an LCD Display》中的公式换算成寄存器值,并用static_assert在编译期校验thfp+thb+Hsize == th等一致性条件。每种分辨率还预置了一组default_transform_*触摸变换矩阵系数,用于把触摸坐标映射到屏幕坐标。
第二层:Extended API —— 完整的 GUI 框架
Extended API 位于 extended/ 目录,由 ftdi_extended.h 统一组织,包含 unicode 字体系统、grid_layout、dl_cache、event_loop、command_processor、screen_types、sound_player、text_box、adjuster_widget、circular_progress、poly_ui等模块。下面针对 README 承诺的每项能力逐一说明其实现。
分辨率无关的网格布局宏
grid_layout.h 实现了 README 所述"基于网格、与分辨率无关的控件放置"。其布局模型类似于 HTML TABLE:开发者以"网格单元"为单位描述控件位置,宏在编译期展开为常量坐标——注释明确说明这样可让"分辨率无关"的代价与硬编码坐标一样高效。
核心宏包括:
_GRID_X(x)/_GRID_Y(y):按GRID_COLS/GRID_ROWS计算网格分格位置;BOX_X/Y/W/H:计算某个网格单元的左上角与宽高;BTN_X/Y/W/H:在 BOX 基础上再扣除MARGIN_L/R/T/B边距,得到按钮的实际区域;BTN_POS/BTN_SIZE/BOX_POS/BOX_SIZE:一行源码定义控件位置/尺寸的缩写;SUB_GRID_*/SUB_*:在一个 BOX 内继续细分子网格;DRAW_LAYOUT_GRID:绘制参考网格线,方便排布调试。
边距宏也按分辨率自适应:800x480 屏幕默认边距 5 px,其余分辨率 3 px。同时通过TOUCH_UI_PORTRAIT宏决定display_width/display_height是否交换,以支持竖屏布局。
类化屏幕、触摸事件与导航栈
screen_types.h 是 GUI 框架的核心数据模型,围绕三件事展开:
1. 免虚表的"虚拟分发"(ScreenRef)。文件注释直言:真正的虚类在 Arduino 上极其昂贵,因为编译器会把虚函数表存进 RAM。为此库发明了ScreenRef类型:把屏幕的回调指针表放进 PROGMEM,通过整数 ID 间接调用。每个屏幕类需实现 9 个静态方法,并通过DECL_SCREEN(className)宏注册:
onStartup/onEntry/onExit:屏幕生命周期;onIdle:空闲时周期性回调;onRefresh:刷新整屏;onRedraw(draw_mode_t):按背景/前景模式重绘;onTouchStart/onTouchHeld/onTouchEnd:触摸按下/长按/松开事件,返回bool表示是否消费事件。
draw_mode_t定义了BACKGROUND、FOREGROUND、BOTH三种绘制模式,是显示列表缓存机制的基础。
2. 导航栈(ScreenStack)。为节省动态内存,栈被硬编码为 4 层(注释说明这允许最多 4 级菜单),提供push()(压栈进入)、pop()(返回并出栈)、goTo()(替换当前屏)、goBack()、forget()等操作。配合PUSH_SCREEN(screen)、GOTO_SCREEN(screen)、GOTO_PREVIOUS()、AT_SCREEN(screen)、IS_PARENT_SCREEN(screen)这些便捷宏,业务代码一行即可完成跳转或返回,这就是 README 所说的"简单屏幕间导航 + 返回导航栈"。
3. 基类 UIScreen 与缓存策略。UIScreen提供所有回调的默认空实现(onEntry默认触发一次onRefresh,onTouchStart/onTouchEnd默认返回true),新屏幕只需覆写关心的回调。同一文件中还给出了两种屏幕模板:
UncachedScreen:每次刷新都从CMD_DLSTART开始完整重建显示列表并CMD_SWAP提交;CachedScreen<DL_SLOT, DL_SIZE>:首次进入时将静态背景存入 RAM_G 缓存槽,之后刷新只回放缓存并重绘动态前景(FOREGROUND),缓存不足时还会在屏上绘制 "GFX MEM FULL" 错误提示。
事件循环:去抖与触摸反馈
event_loop.h 定义了FTDI::EventLoop与全局UIData。一组时间常量揭示了其调度策略:
| 常量 | 值 | 含义 |
|---|---|---|
STATUS_UPDATE_INTERVAL | 1000 ms | 状态信息刷新周期 |
TOUCH_UPDATE_INTERVAL | 50 ms | 触摸轮询周期(20 Hz) |
TOUCH_REPEATS_PER_SECOND | 4 | 长按重复触发频率 |
DEBOUNCE_PERIOD | 150 ms | 按键去抖窗口 |
EventLoop内部为按下/重复/松开分别配置了音效(CHACK/CHACK/POP),UIData用位域标志管理"触摸音效开关、动画开关、去抖状态、忽略松开事件、防止重入"等运行时状态,并支持把用户偏好写入持久化存储(get/set_persistent_data)。这正是 README 所述"按钮去抖 + 按压视觉与听觉反馈"的实现载体。
显示列表缓存:把静态背景存进 RAM_G
dl_cache.h 实现了 README 承诺的"显示列表缓存"。注释给出了标准用法:
DLCache dlcache(UNIQUE_ID); if (dlcache.has_data()) dlcache.append(); // 已有缓存:直接回放 else dlcache.store(); // 首次:把当前显示列表存入缓存槽其原理是把某个菜单对应的显示列表整体写入 RAM_G,之后每次刷新只需少量 SPI 流量回放缓存,动态内容(如数值指示)则不缓存、每帧重绘。缓存槽位信息(地址、大小、已用字节)通过load_slot/save_slot持久化保存,宏DL_CACHE_SLOTS 250定义了最多 250 个缓存槽——同一界面刷新时 SPI 流量的大幅下降,正是触摸屏流畅度的关键来源。
声音播放器:单音符与完整音序
sound_player.h 提供FTDI::SoundPlayer(全局实例sound),支持:
play(effect_t effect, note_t note):播放单个音效(可指定音符,默认NOTE_C4);play_tone(frequency_hz, duration_ms):按频率/时长播放提示音;play(const sound_t *seq, play_mode_t):播放完整音序,支持PLAY_ASYNCHRONOUS(异步,配合onIdle()驱动)与PLAY_SYNCHRONOUS(同步)两种模式;- 音量控制
set_volume()/get_volume()。
sound_t结构由"音色 effect + MIDI 音符 note + 时长 sixteenths(1/16 秒,0 表示播放到结束)"组成,WAIT = 0用于插入停顿。这与 constants.h 中几十种effect_t音色和NOTE_*音符枚举直接对应,Marlin 触摸屏的按键声、报警声、打印完成提示音都基于这套机制。
Unicode 与字体子系统
Extended API 还内置了一套独立的 unicode 字体系统(extended/unicode/),包含font_size_t、standard_char_set、western_char_set、cyrillic_char_set以及font_bitmaps。其中western_char_set_bitmap_31.h、cyrillic_char_set_bitmap_31.h等头文件存放按字符条带(bitmap strip)组织的字形数据(原始素材为同目录下的.pbm/.png/.svg)。当 Marlin 开启TOUCH_UI_USE_UTF8时,库会在刷新显示列表前通过load_utf8_bitmaps把 UTF-8 字符字形上传到芯片,从而支持英文、西文与西里尔字母等多语言显示。
配置与集成:从独立 Arduino 库到 Marlin 内置触摸 UI
README 对使用方式给出三点指引:查看examples目录中的 Arduino 示例、修改各示例的src/config.h适配自己的硬件、参考sample_configs中为 3D 打印机主板准备的样例配置。这是面向"独立库"用户的路径。
而在当前仓库中,这套库已整体嵌入 Marlin 的 FTDI EVE 触摸屏界面,位于 Marlin/src/lcd/extui/ftdi_eve_touch_ui/(ftdi_eve_lib目录内不含独立的 examples/sample_configs,它们属于上游独立发行版的内容)。此时配置入口从"库内 config.h"转移到 Marlin 的固件配置体系,由以TOUCH_UI_*开头的编译期宏驱动,例如:
- 分辨率选择:
TOUCH_UI_320x240、TOUCH_UI_480x272、TOUCH_UI_800x480(由 resolutions.h 在编译期展开时序参数); - 显示方向:
TOUCH_UI_PORTRAIT(交换宽高以支持竖屏); - 字体/调试:
TOUCH_UI_USE_UTF8(启用 UTF-8 字形上传)、TOUCH_UI_DEBUG(在 screen_types.h 的CachedScreen::onRefresh中打印每帧绘制耗时,便于性能调优)。
如果你要在非 Marlin 的 Arduino 工程中独立使用本库,直接包含 ftdi_eve_lib.h 即可——如前所述,未定义__MARLIN_FIRMWARE__时库会自动展开FTDI_BASIC/FTDI_EXTENDED全部模块。
此外,scripts/ 目录提供了 4 个资源转换脚本,负责把外部素材编译进固件:
file2cpp.py:把任意二进制文件转换为 C++ 字节数组;font2cpp.py:把字体文件转换为字形位图数组;img2cpp.py:把图片转换为位图数组;svg2cpp.py:把 SVG 矢量图转换为渲染数据。
这些脚本配合 unicode 子目录中"源素材(.png/.svg/.pbm)+ 生成产物(.h/.cpp)"共存的目录结构,构成了完整的"素材 → 代码"构建链。
小结
通过本次梳理可以看到,FTDI EVE 库在 README.md 中承诺的每一项能力,都能在源码中找到对应的实现模块:basic/层的CLCD与CommandFifo忠实复刻了 FTDI 官方协议,resolutions.h用编译期断言保证了屏幕时序的正确性;extended/层的grid_layout.h提供零运行时开销的响应式布局,screen_types.h用 PROGMEM 函数表在 AVR 上实现了廉价的"多态"与 4 层导航栈,dl_cache.h通过 RAM_G 缓存把静态背景与动态前景分离,sound_player.h配合 constants.h 中的音色/音符枚举实现了完整的声音反馈体系。
无论你是想在 Marlin 上定制 FTDI EVE 触摸屏界面,还是准备在自己的 Arduino 项目中复刻一套轻量级 GUI 框架,这份源码都是极佳的参考范本——它用实践证明了:即便在资源受限的 8 位 MCU 上,通过"芯片协处理器 + 编译期求值 + PROGMEM 数据"的组合,也能构建出流畅、可维护的图形触摸界面。
【免费下载链接】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),仅供参考