尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

在 Flipper Zero 上复刻拓麻歌子:Tama P1 模拟器从 ROM 到源码的完整指南

发布时间:2026/9/20 12:58:07

资讯中心
01
ARTICLE

在 Flipper Zero 上复刻拓麻歌子:Tama P1 模拟器从 ROM 到源码的完整指南

在 Flipper Zero 上复刻拓麻歌子:Tama P1 模拟器从 ROM 到源码的完整指南
在 Flipper Zero 上复刻拓麻歌子Tama P1 模拟器从 ROM 到源码的完整指南【免费下载链接】FlipperPlayground (and dump) of stuff I make or modify for the Flipper Zero项目地址: https://gitcode.com/GitHub_Trending/fl/Flipper在一台口袋设备里复刻一台 32,768 Hz 的 4 位掌机这就是 Tama P1 模拟器干的事它把拓麻歌子初代的 ROM 从 SD 卡读进来模拟出那块 32×16 的液晶屏和蜂鸣器用 Flipper Zero 的三枚侧键完成喂食、清洁与游戏还自带存档。本文先拆原理再走部署最后落到排查手段。它是什么、由哪些件组成一句话定位这是一个编译成.fap的外部插件内核则是一个与平台无关的 TamaLIB 模拟库。整个目录 Applications/Official/source-OLDER/xMasterX/tama_p1 按谁负责什么分得很清楚文件 / 目录职责tama_p1.c应用主入口ROM 读取、事件循环、画面绘制、存档读写tama.h常量与结构体ROM/存档路径、缩放因子、TamaApp状态结构hal.c hal_types.hTamaLIB 的硬件抽象层在 STM32WB 上的落地实现tamalib/模拟内核cpu.c、hw.c、tamalib.c及头文件icons/ compiled/8 个 LCD 状态图标源图与编译产物assets_icons.happlication.fam构建元数据对照 application.fam 能确认几个影响部署的参数appid为TAMA_P1类型是EXTERNAL独立编成插件、从 SD 卡加载入口函数tama_p1_app依赖gui和storage两个系统服务应用主栈只有 1 KBCPU 步进跑在独立线程里发布到Games_Extra分类图标用tamaIcon.png。先把原理讲透模拟内核如何运转4 位 CPU 与内存模型把半字节内存打包进字节缓冲TamaLIB 是作者从零写成的 Tama P1 模拟库见 tamalib/README.md真机基于 Epson E0C6S46 4 位 MCU主频 32,768 Hz屏是 32×16 黑白点阵加 8 枚状态图标网上流传的 ROM 据称来自芯片裸片照片的光学识读。被模拟的 CPU 地址空间只有 4096 个 4 位单元cpu.h 里的MEMORY_SIZE 4096划分如下区域地址规模RAM0x000~0x27F640 个 4 位单元显示缓冲 10xE00~0xE4F80 个显示缓冲 20xE80~0xECF80 个I/O 寄存器0xF00~0xF7F128 个注意中间有大片地址根本不存在。为了不在 STM32WB 上白占 4 KB代码默认打开LOW_FOOTPRINT宏只把实际存在的 928 个半字节挤进一个 464 字节的缓冲区两个半字节塞进同一个u8_t读写全靠SET_RAM_MEMORY/GET_RAM_MEMORY这类宏做位运算。打个比方与其租一整层写字楼不如把四个工位拼成一张桌子。HAL 层如何接住宿主机TIM2 当模拟时钟源TamaLIB 本身不认识任何硬件它通过 hal.h 里的hal_t结构体把时钟、屏幕、声音、日志、halt 回调全部外包给宿主。本项目在 hal.c 里逐一填坑时钟直接用 STM32 硬件定时器 TIM2。初始化时Prescaler 999计数频率约为 64 kHzget_timestamp()就是读一下LL_TIM_GetCounter(TIM2)。sleep_until()是节奏关键——发现模拟时间落后目标时间戳时先放掉互斥锁、furi_delay_tick(1)让出 CPU再抢回锁继续跑否则模拟会越追越慢。屏幕set_lcd_matrix(x, y, val)把单个像素写进framebuffer[16]一行 32 像素恰好装进一个uint32_tset_lcd_icon(icon, val)写icons位图update_screen()留空因为真正的渲染交给 GUI 主循环。声音set_frequency把内核上报的频率除以 10.0 换算成实际值play_frequency走furi_hal_speaker_acquire/start/stop驱动内置蜂鸣器。halt模拟 CPU 停机时置位halted标志界面随即打出 Halted。还有一个容易被忽略的细节hal_types.h 里timestamp_t被注释为必须是无符号类型——按微秒计时时32 位无符号数大约 1 小时 11 分就自然回绕一次用有符号类型会让时间比较逻辑出错。32×16 画面怎么铺满屏幕两个线程怎么不打架主循环用 30 FPS 的周期定时器furi_kernel_get_tick_frequency() / 30不断投递EventTypeTick触发view_port_update()。绘制回调做的事很直接先抢互斥锁超时 25 tick抢不到就放弃这一帧然后把 32×16 像素矩阵按TAMA_SCREEN_SCALE_FACTOR 2放大、在画布中居中用canvas_draw_box逐点画出一个 64×32 的像素块再在矩阵上下两侧各排 4 枚 14×14 的状态图标按icons位图逐位决定显隐横向间距按矩阵宽度自动均分tama_p1.c 绘制分支。线程模型是一个名为 TamaLIB 的工作线程循环调用tamalib_step()推进 CPUGUI 线程负责输入与刷新两者共用一把递归互斥锁g_state_mutex保护帧缓冲事件则经过一个深度 8 的FuriMessageQueue传递EventTypeInput/EventTypeTick两类消息。之所以不用现成的tamalib_mainloop()而改用手动步进正是为了让 Flipper 的 GUI 事件循环掌握节奏。save.bin 存档结构存档落在/ext/tama_p1/save.bintama.h 的TAMA_SAVE_PATH由 tama_p1_save_state 写、tama_p1_load_state读二进制布局是顺序拼接的定长字段字段字节数说明Magic4固定TLST校验文件合法性Version1当前为 2不匹配直接放弃恢复PC2程序计数器高字节只取低 5 位掩码0x1FX / Y22通用寄存器高字节掩码0xFA / B / NP111累加器、B 寄存器各 4 位、页选择5 位SP / Flags11栈指针、标志寄存器tick_counter4时钟周期计数clk_timer_timestamp4时钟定时器时间戳prog_timer_timestamp4可编程定时器时间戳prog_timer_enabled / data / rld111定时器使能、数据、重载值call_depth4调用栈深度interrupts3×1616 个中断槽各存 factor/mask/triggeredRAM640每个半字节占 1 字节I/O1280xF00~0xF7F的全部 I/O 半字节加载时若 Magic 或版本不对只打FATAL错误日志、跳过恢复恢复完 RAM 与 I/O 后调tamalib_refresh_hw()让显示和蜂鸣器状态重新同步——所以关掉再开宠物接着上次活。从零到能玩部署全流程第一步备料ROM 准备与字节序重排 在 microSD 卡根目录建tama_p1文件夹放入名为rom.bin的 ROMREADME 提到网传资源里有个叫a的文件改名即可。路径常量TAMA_ROM_PATH即/ext/tama_p1/rom.bin。ROM 读入内存后并不是直接可用tama_p1_init 里紧接着做了一次字节序重排// Reorder endianess of ROM for(size_t i 0; i fi.size; i 2) { uint8_t b ctx-rom[i]; ctx-rom[i] ctx-rom[i 1]; ctx-rom[i 1] b 0xF; }原因是 P1 的程序存储按半字节组织每对相邻字节把后一个挪到前面、前一个只留低 4 位才还原出内核期望的顺序。完成后以tamalib_init((u12_t*)ctx-rom, NULL, 64000)初始化参数 64000 决定了时间戳的刻度。文件不存在时屏幕会显示 No ROM运行中 CPU 停机则显示 Halted。第二步编译fbt 一键编译部署 图标资源把整个tama_p1文件夹放到固件源码树的applications/plugins/tama_p1位置然后在仓库里执行./fbt launch_app APPSRCapplications/plugins/tama_p1launch_app目标会一次完成编译、串口部署并立即运行。原文档里APPSRC用的是 Windows 反斜杠写法在 Linux/macOS 的 Shell 中要换成斜杠如上面所示。应用还引用了 8 枚状态图标与启动图标需要先跑资源脚本把 PNG 编成 C 头文件scripts/assets.py icons applications/tama_p1/icons applications/tama_p1/compiled产物是compiled/assets_icons.h声明了I_icon_0~I_icon_7。 若编译时因未使用参数告警被当成错误按 README 的提示在site_cons/cc.scons的CCFLAGS里追加-Wno-unused-parameter即可压掉 TamaLIB 带来的该类告警。第三步上机运行后观察屏幕出现放大的宠物画面即成功出现 No ROM 检查 SD 卡路径与文件名出现 Halted 说明 ROM 内容或字节序有问题。怎么操作、怎么存档基础三键对应关系对照 tama_p1.c 输入处理Flipper 按键实际动作左键拓麻歌子 A 键OKB 键右键C 键长按返回保存并退出上键短按静音——同时按下 A 与 C下键短按/ 返回短按立即写一次存档最后两行是文档没细说、但代码里确实存在的行为InputKeyUp会同时向内核上报BTN_LEFT与BTN_RIGHT的按下/释放InputKeyDown短按与返回短按都会触发tama_p1_save_state()。至于能不能存档README 里自相矛盾正文写着目前不支持保存退出即重置下方 Implemented 清单却又列着保存/加载模拟器状态/ext/tama_p1/save.bin。对照源码可以下结论——前一句是过时信息保存/加载函数完整实现且已接入启动与退出路径存档功能实际可用。卡住了怎么查串口日志是最快的定位手段。参考 README 的调试一节用 FlipperScripts 里的serial_logger.py监听python serial_logger.py也可以编译部署和挂日志一起执行两条命令用分号串起来./fbt launch_app APPSRCapplications/plugins/tama_p1; python serial_logger.py日志分两个 TagTamaP1覆盖 ROM 加载、存档读写与输入事件TamaLIB覆盖内核层错误与状态信息。每条输入事件会打印sequence/key/type三要素排查按键映射或状态机行为时直接看这三个数即可。当前边界与可挖的方向README 的 To-do 清单标出了现有版本的边界对照源码大致是这些多存档槽Slots现在只有一个save.bin想扩展的话给TAMA_SAVE_PATH加参数化文件名即可游戏内重置目前只能退出重进来内核侧已有cpu_reset()接口cpu.h缺的是一个入口键测试模式拓麻歌子固件自带的自检入口尚未接通音量调节蜂鸣器响度写死在furi_hal_speaker_start的 0.5 参数里加速Fast-forwardfast_forward_done在初始化时就被置为true旁边挂着TODO: implement fast forwarding属于占位未完成。结语这套外部插件外壳 跨平台模拟内核的分工值得借鉴TamaLIB 管 4 位机指令与硬件状态机hal.c用不到两百行代码接上定时器、蜂鸣器与屏幕换个平台只需重写 HAL。想继续看同类玩法可以逛 Applications/Official/source-OLDER/xMasterX 下的其他游戏插件或深入 tamalib/ 的核心源码。【免费下载链接】FlipperPlayground (and dump) of stuff I make or modify for the Flipper Zero项目地址: https://gitcode.com/GitHub_Trending/fl/Flipper创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。