Add bit-probe results, decimal point/percentage bit map, and working temperature+humidity display

- bitprobe.c: interactive single-bit probe that discovered the display glyph
  layout and the decimal point / percent sign bit positions
- temphummon.c: render top line '188.8°C' and bottom '88.8%' using the
  verified decimal points (f[4], f[8] bit5) and percent sign (f[10] bit5)
- PROJECT_MEMORY.md: document full frame layout + empirical bit map
- README: add wiring table and project memory pointer
- add Waveshare reference zip and flake.lock
This commit is contained in:
temphummon dev 2026-09-24 17:33:12 +00:00
commit ebbeffa432
8 changed files with 809 additions and 4 deletions

292
PROJECT_MEMORY.md Normal file
View file

@ -0,0 +1,292 @@
# Project Memory — temphummon
Temperature and humidity monitor using Matter over Thread.
Based on the **ESP32-H2 Zero** and **Waveshare 1.9" Segment E-Paper** display.
---
## Quick Reference
| Item | Value |
|---|---|
| MCU | ESP32-H2 (RISC-V, IEEE 802.15.4 / Thread) |
| IDF version | v6.0.1 |
| Chip target | `esp32h2` |
| Build tool | `idf.py` (via Nix dev shell) |
| Main source | `firmware/main/temphummon.c` |
| sdkconfig | `firmware/sdkconfig` |
| Datasheets | `datasheets/` |
| CAD files | `cad/` |
| BOM | See `README.md` |
| Reference code | `E-Paper-Segment-Code.zip` (Waveshare, in project root) |
---
## Wiring (ESP32-H2-Zero ↔ E-Paper Module)
Fixed and tested — display does NOT overheat with correct wiring.
| ESP32-H2-Zero | E-Paper Module | Wire color |
|:---|:---|:---:|
| GPIO4 | SDA | 🟢 Green |
| GPIO5 | SCL | 🟡 Yellow |
| GPIO10 | RST | 🟠 Orange |
| GPIO11 | BUSY | 🔵 Blue |
| 3V3 | VCC | 🔴 Red |
| GND | GND | ⚫ Black |
**Note:** The module version has its own level shifters and LDO — safe to connect directly. The bare panel (without PCB) has different pinout.
---
## Important: Display Interface = I²C, not SPI
The IST7134 driver IC on the 1.9" Segment E-Paper uses **I²C** communication, NOT SPI. The Waveshare reference code confirms this.
### I²C Protocol Details
| Parameter | Value |
|:---|:---:|
| Command address | `0x3C` (A0=0, 7-bit) |
| Data address | `0x3D` (A0=1, 7-bit) |
| Speed | 100 kHz |
| Pull-ups | Internal (enabled in firmware) |
The A0 bit is encoded in the I²C address itself: `0x3C` for commands, `0x3D` for data. This is handled by using two separate `i2c_master_dev_handle_t` handles in the firmware.
### Display Data Format
- 15 bytes per frame
- 91 segment outputs + 2 background + 1 VCOM (94 bits ≈ 12 bytes, padded to 15)
- Digit patterns (0-9) from Waveshare reference code
- Two SRAM banks used: first for image data, second for control
### Frame Layout (EMPIRICALLY VERIFIED with bit-probe)
The digits are **dot-matrix style** (each bit = one dot / short stroke of a column),
NOT classic one-bit-per-7-segment. Each digit glyph maps to a **byte pair**. The two
lines interleave: top-tens, top-ones, bottom-tens, bottom-ones, bottom-tenths, top-tenths.
| Frame byte(s) | Display location | Position label |
|:---|:---|:---|
| `f[0]` bits 0-4 | Top line, **leading "1"** (hundreds) | — |
| `f[1], f[2]` | Top line, **tens digit** | pos 1 |
| `f[3], f[4]` | Top line, **ones digit** | pos 2 |
| `f[5], f[6]` | **Bottom** line, **tens digit** | pos 3 |
| `f[7], f[8]` | Bottom line, **ones digit** | pos 4 |
| `f[9], f[10]` | Bottom line, **tenths digit** | pos 5 |
| `f[11], f[12]` | Top line, **tenths digit** | pos 6 |
| `f[13]` | °C / °F letter (0x05 = °C) | — |
| `f[14]` | Padding / unknown | — |
**→ Top line renders as `[1][tens][ones].[tenths] °C`** e.g. `188.8 °C`
**→ Bottom line renders as `[tens][ones].[tenths] %`** e.g. `88.8 %`
### Decimal point & % (THE missing piece — now found! 🎯)
All three are **bit 5 (0x20) of the SECOND byte of the pair**, matching the manual:
"decimal point and % are the 5th place in the 4th, 8th, and 10th positions."
| Symbol | Frame byte | Bit | Where it appears |
|:---|:---|:---|:---|
| **Decimal point (top)** | `f[4]` | bit 5 (0x20) | after top-line **ones** → `188.8` |
| **Decimal point (bottom)** | `f[8]` | bit 5 (0x20) | after bottom-line **ones** → `88.8` |
| **% sign (humidity)** | `f[10]` | bit 5 (0x20) | after bottom-line **tenths** → `88.8%` |
### Verified glyph segment map (from probing)
For a digit byte-pair, the **first byte** = left column + horizontal bars, the
**second byte** = right column (bits 0-3) + decimal/control (bit 5+).
Example (pos 1, top tens): byte1 bit5 = top bar, bit6 = middle bar, bit7 = bottom
bar; byte2 bits 0-3 = right-side dots/bars.
⚠️ **Control bits** (probed): second-byte bit 4 of some pairs and byte13 bit1
flip the whole panel BLACK and wedge the driver until a hard re-init (RST pulse).
Avoid setting them, or re-init after. (bitprobe had to power-cycle the board.)
### Initialization Sequence
```
1. Hardware reset (RST pin: high → low → high with delays)
2. POWER_ON command (0x2B)
3. Boost + TSON (0xA7, 0xE0)
4. Temperature compensation based on ambient temp
```
### Display Refresh Sequence
```
1. Wake from sleep (0xAC)
2. Power on (0x2B)
3. Set RAM address (0x40)
4. Open first SRAM (0xA9), close (0xA8)
5. Write 15 bytes of image data
6. Write extra byte (0x00 or 0x03)
7. Open second SRAM (0xAB), close (0xAA)
8. Display on (0xAF)
9. Wait for BUSY pin
10. Display off (0xAE), HV off (0x28), sleep (0xAD)
```
### Waveform LUTs
| Function | Bytes sent | Use case |
|:---|:---|:---|
| `epd_lut_DU_WB()` | `82 80 00 C0 80 80 62` | Partial update (white extinction + black out) |
| `epd_lut_GC()` | `82 20 00 A0 80 40 63` | Full global refresh |
| `epd_lut_5S()` | `82 28 20 A8 A0 50 65` | Boot waveform (better ghosting) |
---
## Nix Development Shell
The project uses **Nix flakes** to provision the ESP-IDF toolchain.
### How to enter the dev shell
```bash
export PATH="/root/.nix-profile/bin:$PATH"
cd /src && nix develop --command bash
```
### One-off commands (no interactive shell needed)
```bash
cd /src/firmware && nix develop /src --no-write-lock-file --command idf.py build
```
### Nix details
- **Flake**: `/src/flake.nix`
- **Lock**: `/src/flake.lock`
- **Nix binary**: `/root/.nix-profile/bin/nix` (also at `/nix/store/irfrbndi76zhkvqsfhmsn4a99iafck29-nix-2.35.2/bin/nix`)
- **Nix config**: `/etc/nix/nix.conf` (has `experimental-features = nix-command flakes`)
- **Dev shell inputs**: `esp-idf`, `git`, `minicom`, `usbutils`, `gawk`, `coreutils`, `cmake`, `ninja`
### ⚠️ Note: Custom nixpkgs-esp-dev fork
The flake uses a **custom fork** of `nixpkgs-esp-dev`:
```
nixpkgs-esp-dev.url = "github:dvdvgt/nixpkgs-esp-dev/update-v6.0.1";
```
This fork has **few/no cached binary substitutes**, so `nix develop` compiles the entire ESP-IDF toolchain from source. This takes **a long time** on first run but we're keeping it for now since mainline `mirrexagon/nixpkgs-esp-dev` doesn't support ESP-IDF v6.0.1 yet.
---
## Build State
### ✅ Successful build
The firmware compiles and links successfully for ESP32-H2 (RISC-V target). Size: `0x2b1b0` bytes (83% free).
### Firmware behavior
Current firmware (`main/temphummon.c`):
- Initializes GPIOs (RST output, BUSY input) and I²C bus
- Initializes display (power on, boost, temperature compensation)
- Clears display (full refresh cycle)
- Reads internal CPU temperature, renders on top line as `188.8°C` with
correct **decimal point** (`f[4]` bit5) and leading "1" (`f[0]`)
- Renders fixed humidity sample on bottom line as `88.8%` (tens/ones/tenths
+ decimal `f[8]` bit5 + **percent sign** `f[10]` bit5), updates every 1s
- Prints status via ESP_LOG
### ✅ Hardware bringup (confirmed working)
- **ESP32-H2 Zero** detected as rev v1.2 at 96 MHz
- **Flashing** via USB-Serial/JTAG works at 460800 baud
- **Display driver** (IST7134 via I²C at 0x3C/0x3D) works:
- I²C communication succeeds, BUSY handshake works
- Display clears properly
- Digits 0–9 show correctly in sequence
- All-black font pattern renders
- Deep sleep mode works without errors
- **No hardware issues** — wiring, level shifting, LDO all correct
### Flashing (requires serial connected)
```bash
cd /src/firmware && nix develop /src --no-write-lock-file --command idf.py -p <PORT> flash monitor
```
Or via the flake app:
```bash
nix run .#flash
```
---
## Hardware Info
### ESP32-H2 Zero (Waveshare)
- **SoC**: ESP32-H2 (single-core RISC-V 32-bit, up to 96 MHz)
- **Radio**: IEEE 802.15.4 (Thread, Zigbee) + Bluetooth 5 (LE)
- **Flash**: 4 MB
- **PSRAM**: Not present on this board
- **Schematic**: `datasheets/ESP32-H2-Zero-Schematic.pdf`
- **Datasheet**: `datasheets/Esp32-h2_datasheet_en.pdf`
- **TRM**: `datasheets/Esp32-h2_technical_reference_manual_en.pdf`
- **Wiki**: https://docs.waveshare.com/ESP32-H2-Zero
### 1.9" Segment E-Paper Module (Waveshare)
- **Driver IC**: IST7134 (datasheet: `datasheets/IST7134.pdf`)
- **Interface**: I²C
- **Module schematic**: `datasheets/1.9inch_Segment_e-Paper_Module01.pdf`
- **Display manual**: `datasheets/1.9inch Segment e-Paper V1.1.pdf`
- **Module wiki**: https://www.waveshare.com/wiki/1.9inch_Segment_e-Paper_Module_Manual
- **Reference code**: `E-Paper-Segment-Code.zip` (in project root)
#### Module board features
- **NDC7002N** dual N-channel MOSFETs for level shifting
- **RT9193-33** LDO (3.3V regulator)
- **AO3401** P-MOSFET power switch (on by default without external EN)
- Two **6-pin male headers**: SDA, SCL, RST, BUSY, VCC, GND
- Supports 3.3V or 5V input
### Other components
- **WS2812B RGB LED**: `datasheets/XL-0807RGBC-WS2812B.pdf` (on-board, on GPIO8)
---
## Next Steps (TODO)
- [x] **Board bringup** — flashed and tested the display driver on real hardware
- [x] **Port Waveshare digit lookup table** — all 10 digits (0–9) verified working on the display
- [x] **Internal temperature sensor** — reads ESP32-H2 internal temp sensor, shows on display line 1, updates every 1s
- [x] **Find decimal point & % bits** — probing session mapped them: `f[4]/f[8]` bit5 = decimals, `f[10]` bit5 = %
- [ ] **Add humidity sensor** — wire up an external I²C sensor (SHT40, BME280, etc.) and show on display line 2
- [ ] **Configure Matter/Thread** — enable OpenThread / Matter components in `sdkconfig`
- [ ] **Implement Matter cluster** — temperature and humidity measurement clusters
- [ ] **Implement low-power operation** — deep sleep between measurements
---
## Flake Nix Apps
The flake defines these convenience apps (`nix run .#<name>`):
| App | Description |
|---|---|
| `build` | Build firmware (`idf.py build`) |
| `flash` | Flash to auto-detected serial port |
| `monitor` | Open serial monitor |
| `menuconfig` | Open `idf.py menuconfig` |
| `clean` | `idf.py clean` |
| `full` | Build → Flash → Monitor pipeline |
| `run-agent` | Run pi agent container |
| `build-agent` | Build pi agent container image |
---
## Environment Notes
- Container: Debian-based (from `Containerfile`), runs as root
- Working directory: `/src` (mounted volume)
- Nix is installed but NOT in default PATH — must use `/root/.nix-profile/bin/nix`
- `.envrc` contains `use flake` (for direnv, not active in this environment)
- `direnv` state: `.direnv/` directory exists