[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-1505":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":14,"contributorsCount":15,"subscribersCount":15,"size":15,"stars1d":16,"stars7d":17,"stars30d":18,"stars90d":15,"forks30d":15,"starsTrendScore":19,"compositeScore":20,"rankGlobal":10,"rankLanguage":10,"license":21,"archived":22,"fork":22,"defaultBranch":23,"hasWiki":24,"hasPages":24,"topics":25,"createdAt":10,"pushedAt":10,"updatedAt":26,"readmeContent":27,"aiSummary":28,"trendingCount":15,"starSnapshotCount":15,"syncStatus":29,"lastSyncTime":30,"discoverSource":31},1505,"waveshare-watch-rs","infinition\u002Fwaveshare-watch-rs","infinition","100% Rust `no_std` smartwatch firmware for the Waveshare ESP32-S3-Touch-AMOLED-2.06","https:\u002F\u002Finfinition.github.io\u002Fwaveshare-watch-rs\u002F",null,"Rust",331,39,3,0,5,8,72,15,4.81,"Other",false,"main",true,[],"2026-06-12 02:00:28","# waveshare-watch-rs\n\n[![oosmetrics](https:\u002F\u002Fapi.oosmetrics.com\u002Fapi\u002Fv1\u002Fbadge\u002Fachievement\u002Fd90d81a8-b5be-4e00-8418-c1e0b9321f57.svg)](https:\u002F\u002Foosmetrics.com\u002Frepo\u002Finfinition\u002Fwaveshare-watch-rs)\n\n\u003Cimg width=\"400\" height=\"270\" alt=\"image\" src=\"https:\u002F\u002Fgithub.com\u002Fuser-attachments\u002Fassets\u002F331e6778-bf67-4f5f-a753-b65d6e0f1d57\" \u002F>\n\n\n100% Rust `no_std` smartwatch firmware for the **Waveshare ESP32-S3-Touch-AMOLED-2.06**.\n\nComplete conversion of the original C\u002FC++ project (ESP-IDF + Arduino GFX + LVGL) to a single-binary Rust codebase relying on `esp-hal` 1.0, `esp-rtos`, Embassy, and custom drivers for each of the board's peripherals.\n\nThe firmware handles the 410×502 QSPI display in 80 MHz DMA, the I2S audio codec, 2.4 GHz WiFi with NTP sync, SD card, capacitive touch, gyroscope, hardware RTC, AXP2101 power management, a launcher with 5 mini-games, a T9 keyboard, an MP3 player (UI), a Smart Home app (HTTP), a sleep\u002Fwake mode with an Apple Watch style Always-On Display, and an event-driven main loop based on GPIO interrupts to leave the CPU asleep >99% of the time on the watchface.\n\n---\n\n## Hardware target\n\n| Component        | Reference                                        | Bus            |\n|------------------|--------------------------------------------------|----------------|\n| SoC              | ESP32-S3R8 (Xtensa LX7 dual-core, 8 MB PSRAM)    | —              |\n| Display          | CO5300 AMOLED 410×502, rounded edge              | QSPI 80 MHz    |\n| PMIC             | AXP2101 (charging, power rails)                  | I2C 400 kHz    |\n| Touch            | FT3168                                           | I2C + INT GPIO |\n| IMU              | QMI8658 (accelerometer + gyroscope + temp)       | I2C            |\n| RTC              | PCF85063A                                        | I2C            |\n| Audio codec      | ES8311 + amp                                     | I2C + I2S      |\n| Memory           | 32 MB flash, 8 MB octal PSRAM                    | —              |\n| SD Card          | SDHC SPI                                         | SPI3           |\n| WiFi + BLE       | Integrated 2.4 GHz                               | —              |\n| Tearing Effect   | GPIO13                                           | IRQ            |\n\nWaveshare Wiki reference: [https:\u002F\u002Fwww.waveshare.com\u002Fwiki\u002FESP32-S3-Touch-AMOLED-2.06](https:\u002F\u002Fwww.waveshare.com\u002Fwiki\u002FESP32-S3-Touch-AMOLED-2.06)\n\nFull pinout in `src\u002Fboard.rs`.\n\n---\n\n## Software stack\n\n| Layer        | Crate                           | Role                                                   |\n|--------------|---------------------------------|--------------------------------------------------------|\n| HAL          | `esp-hal` 1.0                   | Peripherals (GPIO, I2C, SPI, I2S, DMA, timers, PSRAM)  |\n| Runtime      | `esp-rtos` 0.2                  | Boot, executor, radio integration                      |\n| Async        | `embassy-executor` 0.9          | Cooperative scheduler, tasks, timers                   |\n|              | `embassy-time`, `embassy-futures` | `select`, `Timer::after`, `Duration`                   |\n|              | `embassy-net` 0.9               | smoltcp TCP\u002FIP stack (DHCPv4, TCP, UDP, DNS)           |\n| Radio        | `esp-radio` 0.17                | WiFi driver + Embassy interface                        |\n| Graphics     | `embedded-graphics` 0.8         | 2D primitives, fonts, text layout                      |\n| Storage      | `embedded-sdmmc` 0.8            | FAT32 on SD card                                       |\n| Codec        | `nanomp3` 0.1                   | `no_std` MP3 decoder (prepared, not wired)             |\n| Allocator    | `esp-alloc`                     | 64 KB SRAM heap + 8 MB PSRAM heap                      |\n| Panic        | `esp-backtrace`                 | Symbolic backtrace via `esp-println`                   |\n\nZero C code, zero ESP-IDF components compiled into the final target. The bootloader is `esp-bootloader-esp-idf` on the loader side only.\n\n---\n\n## Architecture\n\n```\nmain.rs\n│\n├── esp_hal::init(CpuClock::_160MHz)\n├── PSRAM allocator init\n├── esp_rtos::start(timer)         ← Embassy executor\n│\n├── [Peripherals init] ──── drivers\u002F + peripherals\u002F\n│   ├── Shared I2C bus (RefCell + RefCellDevice)\n│   ├── AXP2101 power rails + battery monitor\n│   ├── QSPI SPI2 80 MHz DMA 8 KB → Co5300Display\n│   ├── PSRAM Framebuffer double buffer (2 × 402 KB)\n│   ├── FT3168 touch + GPIO38 INT\n│   ├── PCF85063A RTC\n│   ├── QMI8658 IMU (power_down by default)\n│   ├── SD SPI3 4 MHz\n│   ├── ES8311 codec + I2S0 DMA\n│   └── WiFi esp-radio + embassy-net DHCPv4 + NTP sync\n│\n├── [Event-driven loop]\n│   │\n│   ├── select3(\n│   │       Timer::after(adaptive_tick),\n│   │       touch_int.wait_for_falling_edge(),\n│   │       boot_button.wait_for_falling_edge(),\n│   │     ).await\n│   │\n│   ├── Sensors I\u002FO (gated by screen_state + business need)\n│   ├── Touch poll I2C (only if finger is pressed or just lifted)\n│   ├── State machine sleep\u002Fwake (4 levels + AOD)\n│   ├── WiFi auto-disconnect idle >5min\n│   └── App state machine\n│       ├── Watchface (3 pages: Clock \u002F Sensors \u002F System)\n│       ├── Launcher\n│       ├── Snake, 2048, Tetris, Flappy, Maze\n│       ├── MP3 Player (UI)\n│       ├── Settings + T9 keyboard\n│       └── SmartHome (buttons → HTTP)\n```\n\n---\n\n## Module organization\n\n```\nsrc\u002F\n├── main.rs                 Hardware init + async main loop\n├── board.rs                Pinout + display dimensions\n│\n├── drivers\u002F                Low-level drivers (direct hardware)\n│   ├── qspi_bus.rs         QSPI bus quad-mode half-duplex, begin\u002Fstream\u002Fend\n│   ├── co5300.rs           CO5300 init sequence, addr window, set_brightness,\n│   │                       display_on\u002Foff (MIPI DCS), TEARON\n│   └── framebuffer.rs      410×502 RGB565 PSRAM FB, double buffer, flush_vsync\n│\n├── peripherals\u002F            High-level I2C \u002F SPI \u002F I2S drivers\n│   ├── power.rs            AXP2101: battery %, voltage, is_charging, power rails\n│   ├── touch.rs            FT3168: read, tracking swipe\u002Ftap, SwipeDirection\n│   ├── rtc.rs              PCF85063A: get_time, set_time, DateTime\n│   ├── imu.rs              QMI8658: read_accel\u002Fgyro\u002Ftemp, power_up\u002Fdown\n│   ├── audio.rs            ES8311: Waveshare registers init, mute\u002Funmute, beep\n│   ├── sdcard.rs           Stub wrapper around embedded-sdmmc\n│   ├── wifi.rs             WiFi types (scan stub)\n│   └── http.rs             HTTP GET\u002FPOST client via embassy-net TCP\n│\n├── ui\u002F                     UI components rendered on DrawTarget\u003CRgb565>\n│   ├── watchface.rs        Clock + gyro ball + battery + FR date + AOD\n│   ├── segments.rs         7-segment digits for time\n│   ├── pages.rs            Clock \u002F Sensors \u002F System pages\n│   ├── launcher.rs         App list, interpolated scroll\n│   └── t9_keyboard.rs      Alphanumeric T9 keyboard\n│\n└── apps\u002F                   Applications (implement the App trait)\n    ├── snake.rs            Snake with I2S beep on consume\n    ├── game2048.rs         2048 swipe merge\n    ├── tetris.rs           Tetris gyro + touch\n    ├── flappy.rs           Flappy Bird (direct rendering + framebuffer)\n    ├── maze.rs             Maze with IMU ball\n    ├── settings.rs         WiFi SSID\u002Fpassword + T9\n    ├── mp3player.rs        MP3 player UI (decoding to be wired)\n    └── smarthome.rs        Button grid → HTTP GET\u002FPOST\n```\n\n---\n\n## Power management\n\nThe firmware is designed to leave the CPU parked most of the time.\nThe Embassy executor only wakes the core on:\n- the GPIO38 touch interrupt (FT3168 in monitor mode)\n- the GPIO0 button interrupt\n- a periodic timer whose period depends on the current state\n\n### Screen states (4 levels + AOD)\n\n| State | Brightness | Idle trigger | Behavior                                                                         |\n|-------|------------|--------------|----------------------------------------------------------------------------------|\n| 3     | 0xD0       | —            | Normal interactive, full bright                                                  |\n| 2     | 0x40       | 20 s         | Dimming (transition), still interactive                                          |\n| 1     | 0x18       | 40 s         | **AOD**: minimal HH:MM, pure black background (AMOLED pixels OFF), 1 update\u002Fmin  |\n| 0     | DISPOFF    | 10 min in AOD| SLPIN panel, QSPI idle, only GPIO IRQ for wake                                   |\n\nOn wake via touch\u002Fbutton: immediate return to state 3, framebuffer forced into full redraw.\n\n### Adaptive main loop ticks\n\n| Context                            | Tick     | Effective frequency |\n|------------------------------------|----------|---------------------|\n| Screen OFF (state 0)               | 30 s     | 0.033 Hz            |\n| AOD (state 1)                      | 10 s     | 0.1 Hz              |\n| Watchface clock, gyro off          | 1 s      | 1 Hz                |\n| Watchface clock, gyro on           | 33 ms    | ~30 Hz              |\n| Sensors page                       | 100 ms   | 10 Hz               |\n| System page                        | 2 s      | 0.5 Hz              |\n| Launcher \u002F Settings \u002F MP3 \u002F SmartHome | 100 ms| 10 Hz               |\n| Snake \u002F 2048 \u002F Tetris \u002F Maze       | 16 ms    | ~60 Hz              |\n| Flappy                             | 8 ms     | 125 Hz              |\n| Finger held on the screen          | 16 ms    | 60 Hz (override)    |\n\n### Extra optimizations\n\n- **160 MHz CPU** by default (instead of 240 MHz), ~30% CPU power saving.\n- **IMU power-down**: `CTRL7 = 0x00` at boot, power-up only when requested by a consumer.\n- **Touch I2C** polled only when the finger is placed (GPIO38 LOW) or just lifted.\n- **RTC** polled at 1 Hz (instead of 5 Hz before optimization).\n- **Battery** polled at 1\u002F60 Hz (1\u002F300 Hz when the screen is off).\n- **Conditional Watchface flush**: the PSRAM FB is flushed only if `needs_render()` signals an actual change.\n- **WiFi auto-disconnect** after 5 mins of inactivity: `wifi_controller.disconnect_async()` — the 2.4 GHz radio is the biggest constant consumer. Automatic reconnect on next wake.\n- **TE VSync spin** limited to 400 iterations (instead of 2000) to avoid wasting cycles when TE doesn't pulse.\n- **Blocking delays** replaced by `Timer::after(...).await` so the CPU stays parked during button debounces.\n- **Audio PA amp** (GPIO46) held LOW at boot, codec muted (DAC power-down + HP drive off) immediately after init. The amp is pulled HIGH only while writing the beep via DMA, then pulled back down.\n- **AOD Anti burn-in**: the position of the HH:MM block in AOD is shifted by `(minutes % 9) - 4` pixels in X and Y, like Apple Watch.\n\n### Transition order\n\n```\ntouch\u002Fbutton       interaction +0s    state 3 (full)\n   └─ +20s idle    ────────────────→  state 2 (dim)\n   └─ +40s idle    ────────────────→  state 1 (AOD, if Clock page) or state 0 (otherwise)\n   └─ +300s idle   ────────────────→  WiFi disconnect\n   └─ +600s idle   ────────────────→  state 0 (full OFF)\n\ntouch or button GPIO IRQ             → immediate state 3, WiFi reconnect follows\n```\n\n---\n\n## Display pipeline\n\n```\nEmbedded-graphics draw calls\n         │\n         ▼\n410×502 u16 RGB565 PSRAM Framebuffer  (402 KB back buffer)\n         │\n         │  fb.flush() OR fb.flush_vsync(te_pin)\n         ▼\nCo5300Display::set_addr_window(...)\n         │\n         ▼\nQspiBus::write_pixels()\n         │\n         ▼\nesp-hal SPI2 half_duplex_write(\n    DataMode::Quad,             ← 4-bit QSPI mode\n    Command::_8Bit(0x12),       ← write memory\n    Address::_24Bit(0x003C00),\n    dummy = 0,\n    buffer,                      ← pixel data in quad mode\n)\n         │\n         ▼\nDMA_CH0 → GPIO SIO0..SIO3 @ 80 MHz\n```\n\n- `swap_and_flush()`: double buffer, for games (Flappy) — zero tearing.\n- `flush_vsync()`: single buffer, waits for a TE pulse (GPIO13) before sending pixels.\n- `flush_region(x, y, w, h)`: partial update, used by watchface partial updates.\n\n---\n\n## Build setup\n\n### Prerequisites\n\n- **Xtensa ESP Rust toolchain** (installed via [espup](https:\u002F\u002Fgithub.com\u002Fesp-rs\u002Fespup)):\n  ```bash\n  cargo install espup\n  espup install\n  ```\n  `rust-toolchain.toml` pins `channel = \"esp\"`.\n\n- **MSVC linker** (Windows): needed for host build scripts. Install \"Desktop development with C++\" via Visual Studio Installer. The `link.exe` must be in the PATH when running cargo. On this project we typically have:\n  ```bash\n  export PATH=\"\u002Fc\u002FProgram Files\u002FMicrosoft Visual Studio\u002F18\u002FCommunity\u002FVC\u002FTools\u002FMSVC\u002F14.50.35717\u002Fbin\u002FHostx64\u002Fx64:$PATH\"\n  ```\n\n- **espflash**:\n  ```bash\n  cargo install espflash\n  ```\n\n### WiFi credentials\n\nSSID and password are read at compile-time via `env!()`. They must be defined before building:\n\n```bash\n# Linux \u002F macOS \u002F Git Bash (Windows)\nexport WIFI_SSID=\"MyNetwork\"\nexport WIFI_PASS=\"MyPassword\"\n```\n\n```powershell\n# PowerShell\n$env:WIFI_SSID = \"MyNetwork\"\n$env:WIFI_PASS = \"MyPassword\"\n```\n\n### Build\n\n```bash\nWIFI_SSID=\"MyNetwork\" WIFI_PASS=\"MyPassword\" cargo build --release\n```\n\nThe final binary is around **579 KB** (full firmware with WiFi stack + games + UI).\n\n### Flash + serial monitor\n\n```bash\nespflash flash --port COM7 --monitor target\u002Fxtensa-esp32s3-none-elf\u002Frelease\u002Fwaveshare-watch-rs\n```\n\nOn Linux: `\u002Fdev\u002FttyACM0` or `\u002Fdev\u002FttyUSB0` depending on the USB bridge.\n\n### Build config\n\n- `opt-level = \"s\"` in dev AND release (size optimized).\n- `lto = true` in release (global inlining, reduces size by ~20%).\n- 64 KB SRAM heap (for the WiFi stack), 8 MB PSRAM heap (framebuffers + Vecs).\n\n---\n\n## Features\n\n### Integrated and working\n- 410×502 QSPI 80 MHz DMA double-buffer display\n- PSRAM framebuffer + DMA flush\n- Tearing Effect VSync anti-tearing\n- FT3168 touch + iOS-like swipe detection + tap + diagonal rejection\n- QMI8658 IMU accel + gyro + temperature with power management\n- PCF85063A RTC + NTP sync via embassy-net UDP\n- WiFi STA: DHCPv4, NTP, HTTP GET\u002FPOST, auto-disconnect idle\n- ES8311 audio + I2S DMA (Snake beep)\n- SD Card 4 GB detection (FAT32 \u002Fmp3 scan in place, stable mount if MBR is valid)\n- Watchface 3 pages: Clock (7-segment time + battery + FR date + gyro ball), Sensors, System\n- Launcher app list with smooth scroll\n- Games: Snake, 2048, Tetris, Flappy Bird, Maze (gyro)\n- Settings: WiFi SSID\u002Fpassword fields with T9 keyboard\n- SmartHome: configurable 6-button HTTP grid\n- MP3 Player: UI (play\u002Fpause, prev\u002Fnext, progress bar)\n- Screen sleep\u002Fwake 4 levels with minute-by-minute Always-On Display\n- Boot button = launcher, swipe up = launcher\n\n### Partially wired \u002F stubbed\n- **MP3 decoding**: `nanomp3` compiled and as a dependency, UI ready, SD → I2S stream to be wired.\n- **BLE**: `esp-radio` compiled with stub feature, init disabled due to a panic `btdm_controller_init -4` in coex with WiFi (requires additional `coex` config).\n- **WiFi scan list**: `ScanResult` types ready, Settings UI shows the field but without scan.\n- **USB Mass Storage**: not wired (copy-from-PC would require `usb-device` + `usbd-storage`).\n- **ESP deep sleep**: no `esp_hal::system::Sleep` — we stay in light sleep via the Embassy executor, sufficient for watch usage.\n\n---\n\n## Detailed custom drivers\n\n### `drivers\u002Fqspi_bus.rs` — QspiBus\n\nHalf-duplex quad-SPI bus for the CO5300. API:\n```rust\nfn write_command(&mut self, cmd: u8)\nfn write_c8d8(&mut self, cmd: u8, data: u8)\nfn write_pixels(&mut self, pixels: &[u16])\nfn begin_pixels(&mut self)\nfn stream_pixels(&mut self, pixels: &[u16])\nfn end_pixels(&mut self)\n```\n\nUses esp-hal `Spi::half_duplex_write()` with `Command::_8Bit` + `Address::_24Bit`. `DataMode::Single` is used for commands, `DataMode::Quad` for pixels.\n\n### `drivers\u002Fco5300.rs` — Co5300Display\n\nInit sequence faithful to the C Arduino Waveshare driver `Arduino_CO5300.cpp`:\n- Hardware reset (10ms low, 120ms high)\n- SLPOUT (0x11) + delay 120 ms\n- 0xFE 0x00 (vendor register access)\n- 0xC4 0x80 (SPI mode control)\n- 0x3A 0x55 (RGB565 pixel format)\n- 0x53 0x20 (write CTRL display)\n- 0x63 0xFF (HBM brightness)\n- DISPON (0x29)\n- 0x51 0xD0 (brightness)\n- 0x35 0x00 (TEARON VBlank only)\n\nFunctions: `init`, `set_addr_window`, `set_brightness`, `display_on`, `display_off`, `bus_mut`.\n\n### `peripherals\u002Faudio.rs` — Es8311\n\nES8311 init based on the Waveshare C driver. Critical registers missing in my first attempt:\n- `0x00 = 0x1F` (reset) → `0x00 = 0x00` → **`0x00 = 0x80`** (power-on command, initially forgotten)\n- Clock coefficients for 4.096 MHz MCLK @ 16 kHz sample rate\n- `0x0D = 0x01`, `0x0E = 0x02` (power up analog)\n- `0x12 = 0x00` (DAC power up), `0x13 = 0x10` (HP drive)\n- `0x32 = 0xD9` (volume 85%)\n\nAPI: `init`, `mute` (DAC power-down + HP off + vol 0), `unmute`, `set_volume`.\n\n### `peripherals\u002Fpower.rs` — Axp2101Power\n\nWrapper around `axp2101-embedded` for battery monitoring + power rails.\n\n### `peripherals\u002Ftouch.rs` — Ft3168Touch\n\n**Monitor** mode (`REG_POWER_MODE = 0x01`): the chip asserts GPIO38 only on a touch event. Internal state machine to distinguish tap \u002F swipe up\u002Fdown\u002Fleft\u002Fright with:\n- minimum 30 px threshold to qualify a swipe\n- 1.5× ratio on the dominant axis to reject diagonal swipes\n- tracking start\u002Fend coordinates\n\n### `peripherals\u002Fimu.rs` — Qmi8658Imu\n\nInit accel ±2g @ 500 Hz, gyro ±512 dps @ 119 Hz, LPF enabled.\n```rust\nfn read_accel() -> AccelData    \u002F\u002F m.x, y, z in g\nfn read_gyro() -> GyroData      \u002F\u002F °\u002Fs\nfn read_temperature() -> f32    \u002F\u002F °C\nfn power_up() \u002F power_down()    \u002F\u002F CTRL7 0x03 \u002F 0x00\n```\n\n### `peripherals\u002Frtc.rs` — Pcf85063aRtc\n\nBCD read\u002Fwrite of registers 0x04..0x0A. Auto conversion to `DateTime { year, month, day, hours, minutes, seconds }`.\n\n### `peripherals\u002Fhttp.rs` — http_get \u002F http_post\n\nMinimal HTTP client without external crate: parse URL, `TcpSocket::connect`, format request manually, custom `write_all` (handling partial writes), read until close, parse status code + body truncated to 128 bytes.\n\n---\n\n## Runtime data flow\n\n### On boot\n1. `esp_hal::init(CpuClock::_160MHz)`\n2. `esp_alloc::psram_allocator!` — 8 MB PSRAM heap\n3. `esp_rtos::start(timg0.timer0)` — Embassy executor\n4. Sequential init of all I2C\u002FSPI\u002FI2S drivers\n5. `wifi_controller.connect_async().await`\n6. `embassy_net::Stack` + `StackResources\u003C3>`, spawn `net_task` task\n7. Wait for DHCP IP\n8. `ntp_sync()` — UDP to 216.239.35.0:123, parse timestamp, `rtc.set_time()`\n9. Initial watchface render\n10. Enter main loop\n\n### In the loop\n```rust\nloop {\n    \u002F\u002F Choose tick based on state\n    let tick = match (screen_state, app_state, current_page, gyro_enabled) { ... };\n\n    \u002F\u002F Sleep until next event\n    select3(\n        Timer::after(tick),\n        touch_int.wait_for_falling_edge(),\n        boot_button.wait_for_falling_edge(),\n    ).await;\n\n    \u002F\u002F Sensors throttled by need + screen_state\n    if need_imu { imu.read_accel(); ... }\n    if screen_state >= 2 && now >= next_rtc { rtc.get_time(); ... }\n    if now >= next_battery { power.get_battery_percent(); ... }\n\n    \u002F\u002F Conditional touch poll (finger placed or just lifted)\n    if touch_active { touch.poll(); ... }\n\n    \u002F\u002F Sleep\u002Fwake state machine → transitions 3→2→1→0\n    \u002F\u002F WiFi auto-disconnect\n    \u002F\u002F AOD render path (1x\u002Fmin) → continue\n    \u002F\u002F Screen OFF → continue\n    \u002F\u002F App state machine → render + conditional flush\n}\n```\n\n---\n\n## Project metrics\n\n| Metric                 | Value      |\n|------------------------|------------|\n| Lines of Rust          | 5 545      |\n| Source files           | 23         |\n| Release binary         | 579 KB     |\n| Dependency crates      | ~35        |\n| Lines of C\u002FC++         | 0          |\n| SRAM Heap              | 64 KB      |\n| Allocated PSRAM        | ~1.2 MB    |\n| Framebuffers           | 2 x 402 KB |\n| Handwritten drivers    | 8 (QSPI, CO5300, AXP2101, FT3168, QMI8658, PCF85063A, ES8311, HTTP) |\n\n---\n\n## C++ vs Rust comparison\n\n| Aspect                     | C++ (ESP-IDF + Arduino)              | Rust (esp-hal + Embassy)                           |\n|----------------------------|--------------------------------------|----------------------------------------------------|\n| Runtime                    | FreeRTOS (preemptive, ~20 KB RAM)    | Embassy async (cooperative, ~0 KB overhead)        |\n| UI Stack                   | LVGL (C, ~100 KB RAM)               | embedded-graphics (Rust, zero alloc)               |\n| Display driver             | Arduino GFX (Arduino_CO5300.cpp)     | Custom driver qspi_bus.rs + co5300.rs              |\n| SPI Bus                    | ESP-IDF spi_device, polling          | esp-hal half_duplex_write, DMA 8 KB                |\n| Power management           | XPowersLib (C++)                     | Custom driver power.rs on embedded-hal I2C         |\n| Audio                      | ES8311 Arduino driver                | Custom driver audio.rs (registers faithful to C)   |\n| WiFi                       | ESP-IDF wifi_init + lwIP             | esp-radio + embassy-net (smoltcp)                  |\n| Sleep                      | Not implemented                      | 4 levels + AOD, event-driven select3               |\n| Build system               | PlatformIO \u002F Arduino IDE             | Cargo, Xtensa cross-compile via espup              |\n| Safety                     | Raw pointers, buffer overflows       | Ownership, borrow checker, no UB                   |\n| Firmware size              | ~1.2 MB (ESP-IDF + LVGL + WiFi)      | 579 KB (all included)                              |\n\n---\n\n## Conversion history (C++ to Rust)\n\nThe original C++ project used:\n- ESP-IDF + FreeRTOS\n- Arduino GFX for the CO5300\n- LVGL for the UI\n- ES8311 codec via Arduino driver\n- XPowersLib for the AXP2101\n\nMajor steps of the rewrite:\n\n1. **QSPI bus + CO5300** — The hardest part: discovering that esp-hal `half_duplex_write` supports `DataMode::Quad` via the `Command` + `Address` machinery. Initial bug: using `with_miso` (input) instead of `with_sio1` (output) caused SIO1 to float → all blacks appeared green.\n\n2. **AXP2101** — Activating the DC1 (3.3 V main) and ALDO1 (panel) rails via registers 0x80 and 0x92, otherwise the screen stays black even with the CO5300 correctly initialized.\n\n3. **PSRAM Framebuffer** — Alignment issue: the CO5300 is strict on even widths for partial writes. Added even-rounding logic in `flush_region`. The PSRAM allocator requires `features = [\"psram\"]` on `esp-hal` + `esp_alloc::psram_allocator!` macro after `esp_hal::init`.\n\n4. **ES8311 Audio** — 4 attempts before getting sound: the correct public method to play via I2S is **`write_dma()`** (not `write()` which is private). The init must exactly match the C sequence, particularly the `write_reg(0x00, 0x80)` after the reset, otherwise the codec stays in power-down.\n\n5. **Event-driven loop** — Converted from `loop { Timer::after(5ms).await; ... }` to `select3(Timer, touch_edge, button_edge)`. Gain: CPU wake-ups reduced by ~6000× in screen OFF and ~200× in idle watchface.\n\n6. **BLE** — Init attempt with `esp_radio::ble::BleConnector::new` → panic `btdm_controller_init returned -4`. BLE disabled in `Cargo.toml` features pending a correct coex configuration.\n\n7. **Sleep\u002Fwake** — Initial bug: the `display_on()` sequence did DISPON then SLPOUT (incorrect order), so DISPON happened while the panel was still in SLPIN. Fixed to SLPOUT (120 ms) → DISPON (20 ms), standard MIPI DCS order.\n\n---\n\n## License\n\nLicensed under either of\n\n- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or \u003Chttp:\u002F\u002Fwww.apache.org\u002Flicenses\u002FLICENSE-2.0>)\n- MIT License ([LICENSE-MIT](LICENSE-MIT) or \u003Chttp:\u002F\u002Fopensource.org\u002Flicenses\u002FMIT>)\n\nat your option.\n\nHardware drivers were written from scratch, informed by Waveshare's C\u002FC++ examples and the [esp-hal](https:\u002F\u002Fgithub.com\u002Fesp-rs\u002Fesp-hal) ecosystem.\n\n---\n\n## Resources\n\n- Waveshare Wiki: [https:\u002F\u002Fwww.waveshare.com\u002Fwiki\u002FESP32-S3-Touch-AMOLED-2.06](https:\u002F\u002Fwww.waveshare.com\u002Fwiki\u002FESP32-S3-Touch-AMOLED-2.06)\n- esp-hal: [https:\u002F\u002Fgithub.com\u002Fesp-rs\u002Fesp-hal](https:\u002F\u002Fgithub.com\u002Fesp-rs\u002Fesp-hal)\n- esp-rtos: [https:\u002F\u002Fgithub.com\u002Fesp-rs\u002Fesp-rtos](https:\u002F\u002Fgithub.com\u002Fesp-rs\u002Fesp-rtos)\n- Embassy: [https:\u002F\u002Fembassy.dev](https:\u002F\u002Fembassy.dev)\n- embedded-graphics: [https:\u002F\u002Fdocs.rs\u002Fembedded-graphics](https:\u002F\u002Fdocs.rs\u002Fembedded-graphics)\n- CO5300 datasheet: provided by Waveshare on the wiki\n- AXP2101 datasheet: X-Powers\n- ES8311 datasheet: Everest Semiconductor\n- QMI8658 datasheet: QST Corporation\n\n\n## Star History\n\n\u003Ca href=\"https:\u002F\u002Fwww.star-history.com\u002F?repos=infinition%2Fwaveshare-watch-rs&type=date&legend=top-left\">\n \u003Cpicture>\n   \u003Csource media=\"(prefers-color-scheme: dark)\" srcset=\"https:\u002F\u002Fapi.star-history.com\u002Fchart?repos=infinition\u002Fwaveshare-watch-rs&type=date&theme=dark&legend=top-left\" \u002F>\n   \u003Csource media=\"(prefers-color-scheme: light)\" srcset=\"https:\u002F\u002Fapi.star-history.com\u002Fchart?repos=infinition\u002Fwaveshare-watch-rs&type=date&legend=top-left\" \u002F>\n   \u003Cimg alt=\"Star History Chart\" src=\"https:\u002F\u002Fapi.star-history.com\u002Fchart?repos=infinition\u002Fwaveshare-watch-rs&type=date&legend=top-left\" \u002F>\n \u003C\u002Fpicture>\n\u003C\u002Fa>\n","该项目是为Waveshare ESP32-S3-Touch-AMOLED-2.06智能手表开发的100% Rust `no_std`固件。核心功能包括支持410×502 QSPI显示屏、I2S音频编解码器、WiFi NTP同步、SD卡读写、触摸屏操作、陀螺仪、硬件RTC以及AXP2101电源管理等，并集成了迷你游戏、T9键盘、MP3播放器界面和智能家居应用等功能。技术上，项目基于`esp-hal` 1.0、`esp-rtos`、Embassy框架及自定义驱动程序，实现了从C\u002FC++到Rust的完整转换。该固件适用于需要高性能、低功耗且具有丰富功能的手表型设备场景，特别是对系统稳定性与安全性有较高要求的应用场合。",2,"2026-06-11 02:44:22","CREATED_QUERY"]