
TAMX这个名字乍一看像是个编程工具或命令行项目但它真正的定位是“Personal Tamagotchi”也就是个人电子宠物。简单说这类项目想解决的并不是吃饱穿暖的问题而是让你在桌面或手掌大小的设备上重新体验养一个虚拟小生命的感觉。它不是单纯模仿当年的拓麻歌子而是更偏向开发者自己可控、可改、可以让每个像素都按自己想法生长的个人项目。如果你看到 Show HN 上的 TAMX 后第一反应是“我能不能自己也复现一个”那这篇文章更适合按“从一个想法到能跑起来”的顺序来看。最值得关注的地方并不是画面多精致而是状态逻辑、交互反馈、断电保持和显示驱动这些环节是否真的能在低配置硬件上稳定工作。从我的实测经验看这种个人电子宠物项目最容易被低估的部分是状态管理。很多人以为画个像素动物、放几个表情就是电子宠物真正养起来才发现喂食、清洁、睡觉、心情、成长这些状态之间如果不同步宠物会变得像一段散装的动画而不是一个有生命感的系统。所以这篇文章会先说你该用什么环境复现再聊怎么设计状态机然后按“最小运行—通电—交互—保存”的顺序拆解最后补充我踩过的一些坑。1. 先确认它到底是一个“桌面宠物”还是“随身宠物”1.1 从标题能确认的信息和需要做的取舍TAMX 这个标题只暴露了两件事这是一个个人项目并且目标是做电子宠物。除此之外它采用什么形态、用什么硬件、是软件模拟还是实体设备原始材料里都没有明确说明。所以落地时你必须先做一次取舍而不是直接去搜一个成品 PCB 或整机方案。我的建议是把“桌面摆放”和“随身携带”当作两个不同的设计方向来处理。桌面摆放的设备供电可以走 USB屏幕可以稍大按键可以多一点外壳也更容易做。随身携带的设备就必须考虑电池续航、体积、掉电保存和误触问题难度会明显上升。TAMX 既然强调“Personal”个人化的感觉很重要但个人化不一定是越复杂越好很多时候一个能放进口袋、随时掏出来看状态的小设备反而比功能堆满的桌面闹钟更有宠物感。如果只是想快速验证项目思路先做桌面版本是更稳妥的路径。因为桌面版本可以忽略电池管理、充电电路、休眠唤醒这些复杂问题把注意力集中在“电子宠物是否真的像活着”这件事上。等状态逻辑稳定了再考虑缩小硬件、加电池变成随身设备。1.2 适合谁来复现这个项目TAMX 不是商业产品它更适合有三类需求的人嵌入式或硬件开发新手想做一个不大不小的完整项目既包含显示驱动又有交互逻辑和状态存储。想给孩子或自己做一个无网络、无社交依赖的小玩具不希望宠物活在云端而是活在本地硬件里。喜欢桌面小设备和像素艺术的人想把电子宠物当作一个长期可改的画布。第三种人往往被忽略。实际上这类项目最大的乐趣就是在不改变整体架构的前提下不断调整宠物的外观、名字、成长曲线和反馈音效。只要状态机设计得清楚这些调整都只是改配置和素材不需要重写代码。2. 玩这类项目前先把环境和硬件条件列清楚2.1 主控选型和屏幕选型个人电子宠物的实时性要求并不高画面也不需要很华丽所以主控不需要很强。常见环境里ESP32、树莓派 Pico、Arduino 系列都能胜任关键是看你需要多少 IO 口、是否需要 Wi-Fi、是否需要实时时钟。我实际跑过类似项目之后的感觉是ESP32 是一个比较均衡的选择。它性能足够GPIO 数量也够即使不做联网功能也能利用它内部的 RTC 来做时间管理。Arduino Uno 这类 8 位主控也不是不行但如果你想让宠物有较复杂的成长系统还要显示动画内存和 Flash 会比较吃紧。屏幕选型要注意三件事分辨率、驱动芯片和接口。常见的 0.96 寸 OLED 用的是 SSD1306 驱动芯片分辨率通常是 128x64。也有用 IPS 彩屏的比如 ST7789 驱动的 1.3 寸或 1.54 寸屏。不能只看尺寸因为驱动代码完全不同。屏幕类型常见驱动分辨率接口适合场景OLED 单色SSD1306128x64I2C / SPI低功耗、像素风、日志显示OLED 单色SH1106132x64I2C / SPI大尺寸单色屏LCD 彩色ST7789240x240 等SPI彩色宠物、动画丰富LCD 彩色ILI9341320x240SPI 并口大尺寸桌面面板从宠物陪伴感角度看彩色屏更容易把角色做得有情绪。但如果你更关注稳定性和低功耗单色 OLED 往往是更不容易出问题的选择。我建议第一次做 TAMX 复现时不要追求大屏先用 128x64 OLED 把逻辑跑通再决定要不要换彩屏。2.2 输入方式与交互逻辑电子宠物最常见的输入是按键。你需要考虑的不只是“有没有按键”而是按键数量够不够支撑交互。双键方案通常可以这样分配按键 A切换功能比如从“喂食”切到“清洁”再到“查看心情”。按键 B确认当前操作比如喂一颗食物、洗一次澡。如果需要更多操作可以加入长按、连续按、组合键。如果做的是随身设备我更推荐用 3 个按键而不是 5 个。因为按键越多外壳开孔越复杂误触概率也越高。三种操作通过“短按 A 切换菜单、短按 B 确认、长按 A 返回主界面”基本可以覆盖。如果你用的是触摸屏或支持触摸的彩屏交互逻辑可以更直接直接用坐标点击。但触摸屏会带来额外的校准、静电和功耗问题第一次复现时不是必须的。按键虽然是老派交互但对于电子宠物来说这种“按一下、反馈一次”的节奏反而更有仪式感。2.3 供电方式与便携性桌面版 TAMX 可以直接用 USB 供电不需要额外设计电池。这时你要重点考虑的是线材管理和长时间通电的稳定性。有一个细节容易被忽略某些 USB 线只支持充电、不支持数据传输如果固件需要串口烧录你就会发现设备能通电但烧录不进去换成带数据功能的线就好了。随身版就需要考虑充电和电池保护。常见的做法是采用锂电池供电用 TP4056 之类的充电模块。但要注意充电模块的输出必须稳定否则屏幕刷新时容易出现白屏或闪烁。如果你对电源设计不够熟悉第一次做时不要强行做随身版先固定在桌面跑一周确认长期稳定再移动电源部分。还有一个容易被忽视的点如果你把设备放在口袋里按键很容易被误触。随身版至少要在软件上做防误触设计比如连续按两下才唤醒或者用滑动开关做物理锁定。3. 让 TAMX 从一块屏幕变成能“养”的电子宠物最小运行流程3.1 初始化屏幕确认显示链路很多人在做电子宠物时上来就画角色动画结果一通电屏幕白屏就不知道该从哪查。我的习惯是先把显示链路打通只显示一行文字确认画面能刷新再叠加宠物图形。以常见的 ESP32 SSD1306 OLED 环境为例初始化流程一般是这样#include Wire.h #include Adafruit_GFX.h #include Adafruit_SSD1306.h #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 #define OLED_RESET -1 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, Wire, OLED_RESET); void setup() { Wire.begin(); if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { Serial.println(SSD1306 allocation failed); while (1); } display.clearDisplay(); display.setTextSize(2); display.setTextColor(SSD1306_WHITE); display.setCursor(10, 20); display.print(TAMX); display.display(); }这段示例代码的作用不是展示完整功能而是验证三件事I2C 地址是否正确。0x3C 是很常见的地址但有些屏是 0x3D。SDA 和 SCL 有没有接反或虚焊。屏幕电源是否稳定能不能完成初始化。如果屏幕能显示“TAMX”三个字说明显示链路正常可以继续往下做。这一步看起来很简单但能砍掉后面至少一半的定位时间。注意I2C 地址错误是最常见的白屏原因。如果显示初始化失败先用 I2C 扫描程序确认地址再改代码。3.2 设计宠物的状态机宠物不是“画在那里”就算完成了它必须有一组状态和一套状态转换规则。电子宠物领域最常见的状态有饥饿度数值越高代表越需要喂食。清洁度数值越低代表越脏。心情值越高越开心互动会提升。饱腹度刚刚喂食后会暂时上升但如果饱和度过高继续喂食可能产生负面效果。生命值低于某个阈值时宠物会生病或进入危险状态。成长阶段蛋、幼年、成年。这些状态不适合用一堆 if-else 直接写因为状态之间的耦合太强。更稳妥的做法是定义一个结构体来存储宠物状态把每个状态的取值范围固定下来。struct PetState { uint8_t hunger; // 0 - 100 uint8_t cleanliness; // 0 - 100 uint8_t mood; // 0 - 100 uint8_t satiety; // 0 - 100 uint8_t health; // 0 - 100 uint8_t lifeStage; // 0: egg, 1: baby, 2: adult uint32_t lastTickTime; };状态更新是周期性的而不是改一个变量就立刻刷一次屏幕。常见写法是每隔一定时间 tick 一次在 tick 里降低饥饿度、清洁度并让心情值随其他状态联动。时间间隔可以先用 1 秒方便观察效果确认逻辑后再改成真实场景里更合理的 30 秒或 1 分钟。一个常见错误是按键触发喂食后直接给 satiety 加数值却没有同步降低 hunger。这会导致屏幕上的数字相互矛盾比如宠物显示很饿但饱腹度已经爆表。设计状态机时每一步更新都要考虑“这个变化会影响到哪些下游状态”而不是只改当前面板上的数字。3.3 加入按键交互和状态刷新屏幕负责显示按键负责输入主循环负责把两者串起来。整体结构可以拆成这么几块读按键状态处理按下和释放事件。根据当前菜单和按键事件修改宠物状态。更新 UI绘制宠物形象和状态条。清空或者增量刷新屏幕避免闪烁。写代码时要注意不要把显示刷新放到每次按键处理之后都无条件执行否则连续按键会有明显的闪烁和卡顿。更合理的做法是设置一个“需要刷新”标志只有状态变化或周期到达时才调用display.display()。void loop() { handleButtons(); updatePetStateIfNeeded(); if (needDraw) { drawPet(); display.display(); needDraw false; } }这个最小主循环看起来很简单但它保证了事件处理、状态更新、UI 绘制三者解耦。后续无论加震动马达、加声音提示还是加一张新的宠物皮肤都不会把代码搅成一团。3.4 先用小样例验证运行不要一次性把喂食、洗澡、睡觉、成长动画全部写进去。我的建议是分四个小阶段验证第一阶段只显示宠物角色和三个状态条不做交互。第二阶段加一个按键按一下让饥饿度下降 5观察状态条变化。第三阶段加时间衰减逻辑每 10 秒饥饿度上涨 1观察宠物会不会随时间“变饿”。第四阶段加第二个按键切换不同状态页面比如第一页看状态第二页执行操作。每个阶段跑通后再进入下一阶段。这样你能清楚知道问题出在哪个环节。如果一上来就做全功能一个宠物动画异常、一个数值越界可能都要排查很久。4. 把“个人”两个字做扎实时间、存续和个性化4.1 时间感知喂食、睡眠和成长节奏电子宠物和普通动画最大的区别是它会随着真实时间发生变化。如果你只是按按键立刻改变状态那它本质上还是一个遥控玩具。要让 TAMX 有“活着”的感觉它必须具备时间感知。如果你用的是 ESP32 这类带 Wi-Fi 模块的主控可以通过网络获取时间。但如果不想依赖网络也可以用外部 RTC 模块比如 DS3231。还有一种更省钱的方式就是利用主控内部的延时统计或者看门狗计时器做相对时间但掉电后会丢失绝对时间。时间感知带来的典型效果是宠物到晚上会进入睡眠状态早上会醒来长期不喂食会从健康状态跌落到虚弱状态不同成长阶段之间状态衰减速度不一样。这些不一定都需要真实世界的小时来驱动你完全可以把时间系数调快让 10 分钟模拟一天方便测试。我说一个比较关键的经验不要把时间函数写在显示绘制的代码里。每次重新绘制时都去获取时间、计算衰减会让画面在状态变化时出现跳变。更好的做法是单独维护lastTickTime每次主循环先检查“这次循环和上次循环相差多久”达到阈值后才进行状态衰减。4.2 断电不丢状态持久化既然是个人电子宠物用户大概率希望关闭电源再打开宠物还能记得自己叫什么、长多大、上一次是否喂过饭。如果每次断电都恢复出厂状态这种宠物很难让人投入情感。对于 ESP32常见的做法是用 Preferences 库把状态写入 flash 分区。对于其他主控也可以用 EEPROM 模拟。保存时机要注意不必每秒钟都写一次 flash频繁写入会缩短 flash 寿命。更合理的策略是“重要变化时保存”比如喂食结束、清洁完成、成长阶段变化时保存另外每隔一段时间自动保存一次。void savePetState() { prefs.begin(tamx, false); prefs.putUChar(hunger, pet.hunger); prefs.putUChar(satiety, pet.satiety); prefs.putUChar(lifeStage, pet.lifeStage); prefs.putString(name, petName); prefs.end(); }这里最容易被忽视的是“断电时机”。如果用户正好在喂食动画进行时拔掉电源状态可能只保存了一半。要避免这种情况最好在每次状态写入后再写入一个“保存完成”的标志位。启动时先检查标志位如果发现上次没有正常保存就提示宠物状态可能回滚。但这种异常恢复逻辑对于个人项目来说可以先不做。如果只是自己玩每隔几分钟保存一次就够。但如果打算送给朋友或者放到社区里让别人复现保存完整性就值得认真处理。注意频繁调用 flash 写入会导致寿命下降。建议用“状态变化 定期保存”两种方式结合而不是每个 tick 都写一次。4.3 个性化宠物的名字、颜色和表现“Personal”这个词在 TAMX 里不应该只体现在硬件外观上软件的个性化也很重要。至少可以从这几个方向入手名字保存一个宠物名在开机画面和主界面显示。皮肤提供 2 到 3 套像素画风玩家通过组合键切换。背景色如果使用彩色屏可以给不同心情设置不同背景色。性格宠物的状态衰减曲线可以随性格不同而变化比如“贪吃型”饥饿得更快“安静型”心情更稳定。这些个性化项不需要做得很复杂关键是让用户感到“这只宠物是我定义的”。如果你让每个宠物长得完全一样状态逻辑再严谨陪伴感也会打折扣。我建议把这些个性化配置集中到一个配置结构中而不是分散在多个全局变量里。这样以后加“继承”或“多只宠物”功能时迁移成本会低很多。4.4 从单只宠物到多只宠物单只 TAMX 宠物跑稳定后很多开发者会想加入多只宠物。这个想法很好但复杂度是成倍上升的。你要考虑的不只是多几个状态结构体还包括如何切换当前查看的宠物。是否支持每只宠物独立成长。多只宠物之间要不要有互动。存储空间够不够放多份状态。如果只是为了自娱自乐我的建议是先做“可选宠物”而不是“同时养多只”。也就是说开机时可以选择使用哪一只宠物其他宠物的状态仍然保存在存储区里只是同一时间只运行一只。这样既满足了多宠物需求又不会让主循环变得很难维护。真要做一个宠物社交系统那就不是 TAMX 这个个人项目应该承担的复杂程度了。个人项目的好处就在于可以做减法把“一只宠物陪你足够久”这件事做透。5. 实测中常见的报错和排查顺序5.1 屏幕白屏、花屏、不刷新这是电子宠物项目里最让人容易怀疑人生的现象。同一套代码在家里好端端的换个地方、换根线就白屏。排查顺序应该按下面来先量电压。屏幕供电是否满足要求OLED 普遍需要 3.3V 供电如果从外部 5V 直接接错引脚轻则白屏重则烧屏。再查 I2C 地址。用 I2C 扫描工具确认实际地址不要默认所有屏都是 0x3C。再看接线。SDA、SCL 是否接对有没有虚焊。最后看代码初始化。显示库版本是否正确初始化是否发生在串口准备之前。屏幕不刷新还有一种容易被忽略的情况程序跑得比预期快display.display()没有持续调用或者主循环把代码卡在某个阻塞函数里。排查时可以在主循环里加一个调试计数器让 LED 闪烁或者串口输出数字确认主循环真的在跑。5.2 按键不灵敏或状态乱跳按键问题看起来是硬件问题很多时候是软件没有做消抖。机械按键在按下和释放的瞬间会产生抖动如果代码在上升沿或下降沿触发多次就会出现“按一下状态跳两格”的情况。最简单的消抖方式是检测确认后延时 20 到 50 毫秒再做下一次检测。更高阶的做法是用定时轮询和状态机记录按键状态只在“从释放变按下”的边沿触发事件。这里我建议用边沿触发而不是电平触发。因为电平触发会让按键一直响应特别是在长按组合键逻辑里会非常混乱。如果按键已经做消抖仍然跳变那就要检查公共地。按键、主控、屏幕如果使用不同电源供电没有共地就会产生逻辑电平漂移。这个现象在桌面版用 USB 供电时很少出现但用电池供电时偶尔会发生。5.3 重启后宠物状态丢失状态丢失通常有三类原因没有保存、保存了但没在启动时读取、读取的地址不对。排查时先看代码里有没有调用读取函数再看读取顺序是否在绘制界面之前。很多时候状态其实保存了但启动时先画 UI再读状态于是第一帧显示的是默认值看起来像丢失。还有一种情况是保存偏移重叠。比如你先保存了一个字符串再保存一个数值读取时顺序颠倒数值就会是乱码。做持久化前最好把数据格式和存储布局写在一段注释里防止排错时凭记忆猜测。5.4 设备发热、耗电快发热和耗电快通常不是处理器本身的问题而是外设和电源管理的问题。OLED 屏如果一直全屏显示白色会比显示黑色耗电高。彩屏功耗更高如果想省电可以在宠物进入睡眠状态后关闭背光。在软件上没有复杂逻辑的 TAMX 中主控 CPU 占用通常不高耗电主要来自屏幕、升压模块和电池自耗电。如果发现设备关机后电池仍然快速下降很可能是电源管理模块自身漏电或者开关没有真正切断整个电路。随身版做功耗调试时建议用带电流显示的 USB 测试表而不是凭感觉判断。6. 从演示项目变成稳定小设备还需要跨过的几个边界6.1 硬件上的边界TAMX 放到博客里展示是一回事每天带在身边又是另一回事。个人项目常见的问题是“能演示但不耐用”。在硬件上要注意几个地方电池连接器不要用杜邦线长期供电容易松动。按钮寿命便宜的轻触开关按几千次后可能失灵如果设备频繁交互换成寿命更长的贴片按键或锅仔片更稳妥。屏幕排线OLED 模块的排线比较脆弱贴在外壳内壁时最好用胶固定避免反复弯折。充电模块和主控之间的电流余量如果宠物加了震动马达和蜂鸣器瞬时电流可能拉低电压导致黑屏重启。这些边界不是一开始就要全部解决但如果你打算长期使用至少要预留外壳固定孔、电池仓和按键开孔的位置。很多开发者做完 PCB 后才发现结构装不起来原因就是没在早期考虑物理尺寸。6.2 软件上的边界软件上的最大边界不是功能不够而是定时器的准确性和任务优先级。电子宠物有动画、有状态衰减、有按键响应、有存储如果全部写在一个阻塞式主循环里一个函数耗时过长就会让其他模块卡顿。建议做三件事使用非阻塞的延时方式比如记录时间戳而不是调用delay。把存储写入放在低频定时器里避免高频触发。动画刷新和状态更新分离动画每 100 毫秒刷新一次状态每 10 秒更新一次。开发时可以在串口打时间戳看看每次主循环的耗时。日常 20 毫秒以内比较理想。如果单次循环超过 100 毫秒说明有阻塞函数需要优化。6.3 生产化和长期使用建议如果你想把 TAMX 做得更完整哪怕只是送给朋友也建议自己做几轮“无人值守测试”让设备连续运行 48 小时观察是否出现白屏、卡死、状态丢失、按键失灵。这类测试比手动按两下更能暴露问题。测试记录表可以简单按时间记录时间点现象是否重启备注0 小时正常显示按键响应正常否无24 小时状态条数值正常下降否屏幕无残影48 小时正常刷新否无异常如果在 48 小时里出现一次状态丢失不要急着改代码先复现问题确认是存储问题还是电源问题。很多时候你以为代码没问题实际是电池电压在峰值负载时跌破了主控最低工作电压。6.4 值得继续扩展的方向TAMX 这类项目可以一直往下做但要挑有价值的方向。我个人认为下面几个扩展最值得考虑离线语音反馈用蜂鸣器播放短旋律喂食成功时表示开心长时间没吃饭时表示委屈。这个功能成本很低但情绪反馈很强。低功耗睡眠宠物睡眠时主控进入休眠按键唤醒后快速恢复。这是从桌面玩具走向随身设备的必经之路。桌面看板模式不养宠物的时候让屏幕显示时间、温度和一条宠物心情短句。这样设备不会因为没有交互而变成一件摆设。像素画编辑器在 PC 上制作 16x16 或 32x32 的像素帧存成数组导到代码里修改宠物的外观就变成改数据而不是改代码。每个扩展都会引出一连串问题。比如蜂鸣器要注意音量不能刺耳低功耗睡眠要处理 RTC 唤醒桌面看板模式要考虑屏幕烧屏。但如果这些都能解决TAMX 就不再只是一个演示玩具而是一个真正有陪伴感、有使用寿命的个人设备。回到最开始的问题TAMX 值得复现吗如果你的目标是想了解显示驱动、状态机、按键交互、断电保存这些细节如何在一个小项目里协作那它非常值得。如果你只是想快速得到一个能每天依赖的完整产品可能还需要自己补上不少硬件、外壳和电源管理的工作。这个项目最大的价值不是“拿来即用”而是让你有机会从零开始把一个虚拟小生命真实地养在本地硬件上。真正落地时最该盯住的不是功能列表而是输入是否可靠、状态是否保存及时、长时间运行是否稳定。把这三点做扎实TAMX 就不会只是一个名字而是一个你愿意每天都在口袋里装着的宠物。