在 Flipper Zero 上跑通拓麻歌子 P1 模拟器:从 ROM 部署到 4 位机模拟原理
在 Flipper Zero 上跑通拓麻歌子 P1 模拟器从 ROM 部署到 4 位机模拟原理【免费下载链接】FlipperPlayground (and dump) of stuff I make or modify for the Flipper Zero项目地址: https://gitcode.com/GitHub_Trending/fl/Flipper想在 Flipper Zero 的 128×64 屏幕上养一只初代拓麻歌子这个仓库里的 Tama P1 模拟器插件可以直接满足你它模拟 P1 初代的 4 位 CPU 与蜂鸣器三键即可游玩支持把宠物状态写进save.bin存档下次开机接着养。跟着本文走一遍你不仅能在自己的设备或 SD 卡上把它跑起来还会清楚它如何用一块硬件定时器驱动 64 kHz 的模拟时钟、又如何把 4096 个半字节的虚拟内存压进不到 500 字节的真实 RAM。它是什么一个外壳 模拟内核的插件应用Tama P1 是一个EXTERNAL类型的外部应用编译产物是.fap从 SD 卡加载源码位于 Applications/Official/source-OLDER/xMasterX/tama_p1/。它的架构可以一句话概括平台无关的模拟内核 TamaLIB 负责跑 CPUFlipper 侧的外壳代码只干三件事——接按键、喂时钟、画屏幕。文件职责tama_p1.c应用入口tama_p1_appROM 加载、事件循环、渲染回调、存档读写tama.h路径、缩放因子、状态结构等常量定义hal.c / hal_types.hTamaLIB 硬件抽象层HAL在 STM32WB 上的实现tamalib/模拟内核cpu.c指令集、hw.cLCD/蜂鸣器时序、tamalib.c调度icons/ compiled/8 个状态图标源文件及编译产物assets_icons.happlication.fam 里还有几个值得留意的身份参数入口tama_p1_app、依赖gui与storage两个系统服务、应用栈仅1 * 1024字节、发布到Games_Extra分类。从零跑起来放 ROM、编图标、一条命令上设备前置条件一张 microSD 卡、一份新版 Flipper 固件源码树用于构建。仓库若需获取git clone https://gitcode.com/GitHub_Trending/fl/Flipper放置 ROM在 SD 卡根目录建tama_p1文件夹把 P1 的 ROM 改名为rom.bin放进去。这个路径在 tama.h 中写死#define TAMA_ROM_PATH EXT_PATH(tama_p1/rom.bin)启动时先storage_common_stat检查文件是否存在缺 ROM 时屏幕只打印 No ROM。编译图标头文件应用引用的是compiled/assets_icons.h里的I_icon_0~I_icon_7需要先跑一次资源脚本把icons/下的 PNG 转成 C 头文件输出到compiled/目录scripts/assets.py icons applications/tama_p1/icons applications/tama_p1/compiled编译时若 TamaLIB 的cpu.c/hw.c报未使用参数告警严格告警配置下会被当错误在site_scons/cc.scons的CCFLAGS中追加-Wno-unused-parameter即可。编译并推送到设备在固件源码树根目录执行fbt它会完成编译、安装并立即在设备上启动应用Linux 下注意用正斜杠./fbt launch_app APPSRCapplications/plugins/tama_p1前提是把整个tama_p1目录放到固件树的applications/plugins/tama_p1位置——README 明确要求这一步。怎么玩按键映射、两个隐藏功能与存档版本差异基础操作只有三键源码 tama_p1.c 的输入分发里还藏着两个 README 没细说的功能按键行为说明左键拓麻歌子 A 键BTN_LEFTOKB 键BTN_MIDDLE右键C 键BTN_RIGHT上键静音同时向 TamaLIB 上报 AC 按下/释放下键短按立即存档调用tama_p1_save_state()返回短按立即存档同上返回长按存档并退出停掉 30 fps 定时器走退出流程README 与源码打架时以源码为准README.md 的 How to play 一节约 14 行写着 There is currently no saving但同一 README 的 Implemented 一节列着 Saving/Loading emulator state (stored in/ext/tama_p1/save.bin)。两处矛盾而源码给出的答案是存档早已实现——退出时返回长按写入TAMA_SAVE_PATH启动时tama_p1_worker里第一步就是tama_p1_load_state()。前文那句无存档属于过时描述。save.bin 的内部结构存档把整台机器冻结成一个定长二进制块字段大小说明Magic4 B固定TLST校验文件合法性Version1 B当前为 2PC / X / Y222 B程序计数器与各寄存器高字节按位宽掩码0x1F/0xFA / B / NP / SP / Flags各 1 B累加器、B 寄存器、页选择、栈指针、标志位tick / 定时器时间戳3×4 B 3 B时钟周期计数、两个定时器及使能位call_depth4 B调用栈深度interrupts16×3 B6 个中断槽 × (factor/mask/triggered)按INT_SLOT_NUM展开RAM640 B0x000~0x27F每半字节存 1 字节I/O128 B0xF00~0xF7F的 128 个半字节加载时先校验 magic 与版本号不匹配则打印FATAL日志并放弃恢复恢复完 RAM 与 I/O 后调用tamalib_refresh_hw()让内核重新同步显示和蜂鸣器状态。串口日志辅助定位应用内用FURI_LOG_D/FURI_LOG_E记录了 ROM 加载、存档读写TagTamaP1和 TamaLIB 层日志TagTamaLIB。配合 FlipperScripts 里的串口日志脚本即可边跑边看python .\serial_logger.py每次按键都会以sequence / key / type三要素打印验证按键映射和状态机行为时最有用。为什么能跑起来硬件时钟、半字节内存与 30 fps 渲染用一块硬件定时器当模拟时间源P1 初代基于 Epson E0C6S46 4 位 MCU晶振 32,768 HzLCD 为 32×16 黑白像素加 8 个状态图标网上流传的 ROM 据 TamaLIB 的说明是从芯片裸片照片光学识读出来的。要保真模拟模拟时钟的粒度必须够细。该应用的做法是把 STM32WB 的 TIM2 定时器当时间源LL_TIM_InitTypeDef tim_init { .Prescaler 999, .CounterMode LL_TIM_COUNTERMODE_UP, .Autoreload 0xFFFFFFFF, }; LL_TIM_Init(TIM2, tim_init);预分频 999 让计数器以 64 kHz 走数get_timestamp()直接返回LL_TIM_GetCounter(TIM2)。TamaLIB 初始化时传入的64000就是这个频率——它决定了时间戳的最小粒度。sleep_until()是节奏控制的关键模拟时间落后于目标时释放互斥锁、furi_delay_tick(1)让出 CPU再拿锁继续步进否则模拟线程会越跑越欠账。4096 个半字节如何塞进小内存被模拟 CPU 的地址空间共MEMORY_SIZE 40964096×4 bit区域地址半字节数RAM0x000~0x27F640显示区 10xE00~0xE4F80显示区 20xE80~0xECF80I/O0xF00~0xF7F128cpu.h 默认开启LOW_FOOTPRINT宏无效地址不占用缓冲四个有效区各两半字节打包进一个u8_t总缓冲只有 464 字节读写都靠SET_RAM_MEMORY/GET_RAM_MEMORY等位操作宏完成。STM32WB 的 RAM 紧张这一步压缩是它能在设备端常驻的前提。ROM 读进来后还要做一次半字节重排原因在于 P1 是 4 位机、程序按半字节组织而 SD 卡读到的是字节流// 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; }重排完成后调用tamalib_init((u12_t*)ctx-rom, NULL, 64000)把 ROM 交给内核。HAL约 146 行代码接通屏幕与蜂鸣器TamaLIB 通过hal_t结构体hal.h声明了全部平台依赖内存、halt、日志、时钟、屏幕、声音。hal.c 逐一对应实现其中三处值得细看static void tama_p1_hal_set_lcd_matrix(u8_t x, u8_t y, bool_t val) { if(val) g_ctx-framebuffer[y] | 1 x; else g_ctx-framebuffer[y] ~(1 x); }模拟器对每个像素的写入只是改帧缓冲里的一位——每行 32 个像素正好装进一个uint32_t共 16 行真正的绘制由 GUI 线程完成所以update_screen()是空实现。声音侧set_frequency把上报频率除以 10 换算成实际值play_frequency通过furi_hal_speaker_acquire/start/stop驱动内置蜂鸣器固定 0.5 音量因此音量调节至今是 To-do。线程模型模拟线程、GUI 线程与一把递归锁整体数据流是一条清晰的因果链独立线程栈 1024 字节持递归互斥锁g_state_mutex循环调用tamalib_step()手动步进 CPU按键事件经FuriMessageQueue深度 8送入 GUI 主循环分发到tamalib_set_button()30 fps 的周期定时器furi_kernel_get_tick_frequency() / 30触发重绘回调里先以 25 tick 超时抢锁再把帧缓冲按TAMA_SCREEN_SCALE_FACTOR 2放大成 64×32 的像素块画在屏幕正中上下各 4 个 14×14 的状态图标按g_ctx-icons的 8 个位逐一决定显隐。两个线程读写同一份帧缓冲全靠这把锁串行化模拟线程退出前 GUI 线程会furi_thread_flags_setfuri_thread_join干净收尾。TIM2、蜂鸣器、显示屏都是 STM32WB 的片上外设——上图可以帮你定位这颗主控理解模拟时间源实际落在哪块硬件上。边界与坑还没做到的事和容易踩的地方多存档槽Slots目前只有一个save.bin想多养几只宠物只能手动备份文件游戏内复位无法不退出应用就软复位宠物测试模式、音量调节都在 README 的 To-do 清单里蜂鸣器音量固定 0.5快进源码中fast_forward_done已被置为true上方留着// TODO: implement fast forwarding注释——加速功能尚未实现时间戳回绕hal_types.h 特意把timestamp_t定为无符号uint32_t并注释了原因以微秒计时时 32 位无符号数约 1 小时 11 分自然回绕换成有符号类型会让时间比较出错文档可信度遇到 README 与源码不一致如前文的无存档描述以源码为准。想继续深入tamalib/cpu.c 是指令集实现的最好入口hw.c里则是 LCD 与蜂鸣器时序的还原也可以翻 Applications/ 下其他插件看看同一套外壳 内核模式还有哪些玩法。【免费下载链接】FlipperPlayground (and dump) of stuff I make or modify for the Flipper Zero项目地址: https://gitcode.com/GitHub_Trending/fl/Flipper创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

Turborepo 缓存机制全解析:从缓存方程到 global.inputs 的哈希深入指南

Turborepo 缓存机制全解析:从缓存方程到 global.inputs 的哈希深入指南

Turborepo 缓存机制全解析:从缓存方程到 global.inputs 的哈希深入指南 【免费下载链接】turbo Build system optimized for JavaScript and TypeScript, written in Rust 项目地址: https://gitcode.com/gh_mirrors/tu/turbo Turborepo 是一个用 Rust 编写的…

2026/9/20 21:14:28 阅读更多 →
网盘直链下载工具完整教程:三步拿到直链,新手零门槛

网盘直链下载工具完整教程:三步拿到直链,新手零门槛

网盘直链下载工具完整教程:三步拿到直链,新手零门槛 【免费下载链接】Online-disk-direct-link-download-assistant 一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 ,支持 百度网盘 / 阿里云盘 / 中国移动云…

2026/9/20 21:14:28 阅读更多 →
enzyme ReactWrapper.closest(selector):向上遍历查找最近匹配祖先节点的权威指南

enzyme ReactWrapper.closest(selector):向上遍历查找最近匹配祖先节点的权威指南

测试前端 【免费下载链接】enzyme JavaScript Testing utilities for React 项目地址: https://gitcode.com/gh_mirrors/en/enzyme 点击查看 免费下载 导读 .closest(selector) 是 enzyme ReactWrapper(以及 ShallowWrapper)提供的祖先节点…

2026/9/20 21:14:28 阅读更多 →

最新新闻

3个坑让仙台地图渲染崩盘?这份保姆级教程救你

3个坑让仙台地图渲染崩盘?这份保姆级教程救你

3个坑让仙台地图渲染崩盘?这份保姆级教程救你 上周给一个医疗SaaS项目做区域数据可视化,客户点名要集成“仙台地图”组件。我信心满满,结果第一版代码跑起来,控制台直接炸出一屏红字,StackTrace 长得像天书,滚动条都拉不到底。…

2026/9/22 1:02:19 阅读更多 →
3个新手避坑点:北京积分落户新政策源码级拆解与帧对比选型

3个新手避坑点:北京积分落户新政策源码级拆解与帧对比选型

3个新手避坑点:北京积分落户新政策源码级拆解与帧对比选型 看了一堆教程还是不会写项目?别怪自己笨,是你没搞懂底层逻辑。北京积分落户新政策的核心其实就是一本动态账本,很多新手在报名材料清单整理时栽跟头,不是因为材料不全,而是因为没看懂“加权逻…

2026/9/22 1:02:19 阅读更多 →
自动重拨最佳实践

自动重拨最佳实践

3个坑让你告别手动重拨:新手避坑指南 学会语法却不知怎么搭项目,是很多刚入行同学的通病。特别是处理网络不稳定场景时,盯着报错日志发呆,只会手动刷新页面。自动重拨机制看似简单,实则暗藏玄机,稍不留神就陷入死循环。 入口定位:为什么你需要它…

2026/9/22 1:02:19 阅读更多 →
商标宝注册全流程解析与避坑最佳实践

商标宝注册全流程解析与避坑最佳实践

商标宝注册全流程解析与避坑最佳实践 刚拿到商标宝查询结果,或者在提交注册时看到那一长串红色的 StackTrace 报错,是不是瞬间大脑宕机?很多人以为这是系统崩溃,其实是你的申请文件触发了审查系统的硬性拦截。别慌,这行干久了就知道,报错不…

2026/9/22 1:02:19 阅读更多 →
3个实战项目拆解ustcmail,彻底搞懂USTC邮件系统

3个实战项目拆解ustcmail,彻底搞懂USTC邮件系统

3个实战项目拆解ustcmail,彻底搞懂USTC邮件系统 看了一堆教程还是不会写项目?这是大多数应届生在准备大厂面试时的真实困境。你背了无数八股文,刷了上百道算法题,但一旦面试官问起“你做过什么实战项目”,你的大脑瞬间空白。特别是当涉及到…

2026/9/22 1:02:19 阅读更多 →
高速工具钢源码解析: 3步搞定版本API变更坑

高速工具钢源码解析: 3步搞定版本API变更坑

高速工具钢源码解析: 3步搞定版本API变更坑 版本升级后 API 全变了,这是转岗工程师最崩溃的瞬间。你刚把旧版逻辑跑通,新版文档却换了天,报错堆栈像天书。别慌,我们直接拆解 高速工具钢 相关的底层逻辑,通过 源码解析 找到不变的内核。…

2026/9/22 1:01:18 阅读更多 →

日新闻

3台商务办公笔记本实测:手写实现环境配置,告别卡半天

3台商务办公笔记本实测:手写实现环境配置,告别卡半天

3台商务办公笔记本实测:手写实现环境配置,告别卡半天 配置环境就卡半天?别怪机器慢,多半是你没选对工具链。在Java、Go或Python的项目现场, 手写实现…

2026/9/22 0:00:41 阅读更多 →
剑帝加点速查手册:3分钟搞懂核心逻辑

剑帝加点速查手册:3分钟搞懂核心逻辑

剑帝加点速查手册:3分钟搞懂核心逻辑 面试被问原理答不上来,是不是常态?别慌。很多开发者对着 GitHub 开源仓库里的代码发呆,看似简单实则暗藏玄机。今天这份【剑帝加点】速查手册,直接带你拆解核心实现,把面试必考的原理讲透。…

2026/9/22 0:00:41 阅读更多 →
手写实现图片压缩网站核心:搞定WebP转换与质量调优

手写实现图片压缩网站核心:搞定WebP转换与质量调优

手写实现图片压缩网站核心:搞定WebP转换与质量调优 复制来的代码跑不通不知道怎么调?别慌,这种“复制粘贴地狱”在开发圈太常见了。尤其是做 图片压缩网站…

2026/9/22 0:00:41 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/21 3:13:20 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/21 2:19:36 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/21 4:51:05 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/21 15:36:51 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/21 15:36:51 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/19 23:35:34 阅读更多 →