2019 · Microcontroller · Xueersi · KittenBot

ESP32 MeowBit (Xueersi / KittenBot)

The fourth Pocket Vapor target, verified on the physical board over UART.

Pocket Vapor targetPocket Vapor · AOTvapor/runtime/esp32/

The MeowBit is a card-sized educational handheld from KittenBot — a 1.8-inch 160×128 ST7735 TFT, six buttons, a microSD slot and a sensor set — sold in China as Xueersi's 小喵掌机. The ESP32 revision carries an ESP32-WROVER-B module: two Xtensa LX6 cores at 240 MHz, 520 KB of SRAM, 8 MB of PSRAM and 4 MB of flash, with a GD32F350 co-processor bridging USB serial.

It is Pocket Vapor's first board-as-data target: pins, panel and pad coverage live in vapor/boards/meowbit.json, and the compiler derives what the app demands and judges the board against it. A USB verifier replays the shared Vue-oracle tape against the physical board and compares all 360 logical cells after every press.

Processor
ESP32-D0WD · 2 × 240 MHz
Memory
520 KB SRAM + 8 MB PSRAM
Display
160 × 128 TFT
A KittenBot Meowbit handheld on a grey surface
A KittenBot Meowbit handheld on a grey surface. Photo: Xnou · CC BY-SA 4.0 · A KittenBot Meowbit is pictured; the Xueersi ESP32 revision shares the form factor.
01

Hardware

curated here · sources below

Compute

Module
ESP32-WROVER-B (ESP32-D0WD)
CPU
Dual-core Xtensa LX6, 240 MHz
RAM
520 KB SRAM + 8 MB PSRAM
Flash
4 MB SPI flash
Co-processor
GD32F350G8 — USB serial bridge, motor and LED control

Display & input

Display
1.8″ TFT, 160 × 128, ST7735 over SPI (write-only in the Vapor runtime)
Pocket grid
20 × 18 cells of 8 × 7 px — 160 × 126 content area
Input
Up, Down, Left, Right, A, B (active low; GPIO34/35 with external pull-ups)
Chords
A+B → Start, Left+Right → Select, Up+Down → R; L is absent
Sensors
MPU6050 IMU, light sensor, thermistor, passive buzzer

Connectivity & body

Wireless
Wi-Fi 802.11b/g/n, Bluetooth 4.2 (ESP32)
Ports
USB serial via the GD32, microSD, micro:bit-style edge connector
Released
Meowbit 2019; the ESP32 revision followed for the Xueersi programme
02

PocketJS on this machine

Pocket Vapor · AOT

What runs

bun run vapor:esp32 compiles the same todo component to C, generates an ESP-IDF v6.0.2 project in dist/vapor/gen-esp32/ from the board definitions, and produces an app-only todo.esp32.bin. The runtime rasterizes the 20×18 logical grid into RGB565 cells on the ST7735, latches button chords on release, and speaks a line-oriented receipt protocol over UART at 115200 baud: H for the hardware receipt, P <n> to press a button, D to dump the grid.

What is proven

Optional physical-board UART replay verifies the logical grid against the Vue oracle — 32 full-grid receipts, 23,040 cell comparisons, firmware identity hash-checked. It neither reads panel pixels nor actuates the GPIO buttons; those remain manual checks.

Record

  1. v0.7.0

    ESP32 MeowBit becomes the fourth Pocket Vapor target, with boards-as-data and a physical USB verifier.

Profile · as declared by the demo manifestsource ↗
Profile
esp32 · board meowbit (Pocket Vapor)

Acquisition reports

03

Example code

upstream source · highlighted at build time
vapor/boards/meowbit.jsonjson · 22 lines · @6c43f49

The devicetree of the board: panel, pins, pad coverage.

{
  "board": "meowbit",
  "title": "Xueersi/KittenBot MeowBit",
  "chip": "esp32",
  "lcd": {
    "controller": "st7735",
    "width": 160,
    "height": 128,
    "cell": [8, 7],
    "madctl": 96,
    "pins": { "sclk": 18, "mosi": 23, "cs": 5, "dc": 4, "rst": -1, "backlight": -1 }
  },
  "input": {
    "pins": { "up": 2, "down": 13, "left": 27, "right": 35, "a": 34, "b": 12 },
    "chorded": {
      "start": ["a", "b"],
      "select": ["left", "right"],
      "r": ["up", "down"]
    },
    "absent": ["l"]
  }
}
04

Bring-up guide

upstream documents · rendered verbatim

Toolchain, build, deploy and acceptance are owned by pocket-stack/pocketjs. The documents below are rendered from the pinned checkout without edits; relative links point back into the repository at the same revision.

Pocket Vapor ESP32 runtime

4 min read · 904 words

Board profile, chords, build/flash/verify with ESP-IDF, the UART receipt protocol.

rendered verbatim fromvapor/runtime/esp32/README.md@ 6c43f49raw ↗

This is the hardware half of Pocket Vapor's fourth AOT target. The input is the same real Vue component used by the console builds: vapor/examples/todo/todo.tsx, with ref, computed, JSX and keymaps. Pocket Vapor compiles it to gen_app.c; this directory supplies the ESP-IDF frame loop, fixed-memory Pocket runtime, RGB565 cell raster, button input and UART verification transport. No JavaScript engine runs on the device.

The current and only board profile is the Xueersi/KittenBot ESP32 MeowBit. It uses an ESP32-D0WD and a 160×128 ST7735 panel. The logical Pocket screen is 20×18; each character is rasterized into an 8×7 RGB565 cell, leaving one physical row above and below the 160×126 content area.

Board profiles are data, not code: pins, panel and pad coverage live in vapor/boards/meowbit.json, validated and turned into compile definitions by vapor/compiler/boards.ts. The build and verify commands take --board <name> (default meowbit). See vapor/BOARDS.md for the design. The tables below describe the MeowBit values.

Board profile

functionGPIOelectrical behavior
TFT SCLK18SPI clock
TFT MOSI23SPI data
SD MISO19LCD runtime does not use this pin
TFT CS5active low
TFT DC4command/data select
Up2active low
Down13active low
Left27active low
Right35active low, external pull-up
A34active low, external pull-up
B12active low

GPIO34 and GPIO35 do not have usable internal pull-ups on the ESP32. The MeowBit board supplies external pull-ups, so the firmware treats those inputs as active-low without enabling an internal pull.

The panel is reset with the ST7735 SWRESET command. The LCD SPI bus is write-only in this runtime, so GPIO19 remains unused and the optional hardware-reset pin is set to -1.

The board profile and ST7735 sequence are grounded in the device-specific xueersi-idf implementation; the independent Reversing-Meowbit-v1 probes corroborate the display and six button pins.

All physical actions dispatch once on release. The six buttons map directly to their Pocket equivalents, while three release-latched pairs expose actions for which the board has no dedicated button:

physical inputPocket button
Up / Down / Left / Right / A / Bsame named button
A+BStart
Left+RightSelect
Up+DownR

The chord is emitted once when the pair is released; its two constituent button presses are suppressed. This keeps editing and list actions deterministic.

Build, flash and verify

Use ESP-IDF v6.0.2 and bun. The builder uses IDF_PATH and IDF_TOOLS_PATH when set; otherwise it checks the cached macOS toolchain layout used by this repository and the standard ~/esp/esp-idf plus ~/.espressif layout:

export IDF_PATH="$HOME/esp/esp-idf"       # omit when auto-discovery works
export IDF_TOOLS_PATH="$HOME/.espressif"  # omit when auto-discovery works

bun run vapor:esp32
# dist/vapor/todo.esp32.bin
# dist/vapor/gen-esp32/

Flashing replaces the board's factory application. Before the first flash, make a full 4 MiB backup and keep its checksum next to it:

PORT=/dev/cu.usbmodem2101
uvx --from esptool esptool --chip esp32 --port "$PORT" \
  read-flash 0x0 0x400000 meowbit-factory-4mb.bin
shasum -a 256 meowbit-factory-4mb.bin \
  > meowbit-factory-4mb.bin.sha256

Keep that file outside dist/; generated output can be removed at any time. To restore the original image, verify the checksum first, then write the whole backup at offset zero:

shasum -a 256 -c meowbit-factory-4mb.bin.sha256
uvx --from esptool esptool --chip esp32 --port "$PORT" \
  write-flash 0x0 meowbit-factory-4mb.bin

Do not restore a backup captured from a different board: it may contain device-specific factory data.

Both commands below write the connected board. In particular, verify builds and flashes before it runs parity:

bun run vapor:esp32:flash
bun run vapor:esp32:verify

dist/vapor/gen-esp32 is the generated ESP-IDF project. dist/vapor/todo.esp32.bin is the application-only image; if it is written manually, its required flash offset is 0x10000, never zero. The normal flash command is preferred: it uses ESP-IDF's segmented operation to write the bootloader at 0x1000, partition table at 0x8000, and app at 0x10000 without filling the NVS/PHY gaps with 0xff. The verifier repeats that build/flash, then talks to the running firmware over USB serial.

To check an already-flashed image without writing flash, opt in explicitly:

bun vapor/scripts/esp32.ts verify --no-flash --port /dev/cu.usbmodem2101

The source-derived build ID in PVREADY must match, so --no-flash fails instead of silently accepting an older firmware. If more than one USB serial device is attached, pass --port /dev/cu.usbmodem....

UART receipt protocol

The verification protocol is line-oriented at 115200 baud:

  • H returns the firmware and hardware receipt.
  • R resets the Todo app in-process.
  • P <0..9> dispatches one Pocket button.
  • D returns the 20×18 logical character and palette grids as hex.

bun run vapor:esp32:verify boots the real Vue build as the oracle, resets the device, replays the same full button trajectory over UART, and requests a receipt after every press. It compares both character and palette values for all 360 logical cells at every step. This proves generated-app behavior and the LCD commit path's logical-grid parity; it does not read pixels back from the panel or electrically press the GPIO buttons. Panel appearance and physical button/chord behavior remain manual hardware checks. Merely completing a flash is not equivalent to passing the verifier.

Photo

Wikimedia Commons · Kittenbot meowbit.jpg — Xnou, CC BY-SA 4.0. A KittenBot Meowbit is pictured; the Xueersi ESP32 revision shares the form factor.