Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96089c168f | ||
|
|
094ff08877 | ||
|
|
b6a7e082b1 | ||
|
|
b5cf59b39a | ||
|
|
d9bac7cf28 | ||
|
|
ebbeffa432 |
8 changed files with 925 additions and 4 deletions
BIN
E-Paper-Segment-Code.zip
Normal file
BIN
E-Paper-Segment-Code.zip
Normal file
Binary file not shown.
391
PROJECT_MEMORY.md
Normal file
391
PROJECT_MEMORY.md
Normal file
|
|
@ -0,0 +1,391 @@
|
|||
# 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%` |
|
||||
|
||||
### Bluetooth / power icons (FOUND! 🎯 — in f[13], not a special byte)
|
||||
|
||||
All status icons live in **`f[13]`**, the same byte as the °C/°F letter. The
|
||||
manual's "3rd and 4th position on the 13th bit" = bits 3 and 4 of `f[13]`.
|
||||
Probing on a blank panel confirmed each bit, and combos compose cleanly.
|
||||
|
||||
| f[13] value | Bits | Displays |
|
||||
|:---|:---|:---|
|
||||
| `0x01` | bit0 | part of C letter (standalone = black-control risk) |
|
||||
| `0x02` | bit1 | degree sign ° line |
|
||||
| `0x04` | bit2 | C/F letter strokes |
|
||||
| **`0x05`** | bits0+2 | **°C** (degree + C) |
|
||||
| **`0x06`** | bits1+2 | **°F** (per manual) |
|
||||
| **`0x08`** | bit3 | **Bluetooth icon** |
|
||||
| **`0x10`** | bit4 | **Battery/power icon** |
|
||||
| **`0x0D`** | 0x05+0x08 | °C + Bluetooth |
|
||||
| **`0x15`** | 0x05+0x10 | °C + battery |
|
||||
| **`0x1D`** | 0x05+0x08+0x10 | **°C + Bluetooth + battery** |
|
||||
| bits 5-7 | — | unused |
|
||||
|
||||
All compositions verified live on the panel. (The earlier "no icon found"
|
||||
conclusion was wrong - the icons were in f[13] bits 3/4, which the first
|
||||
probe had not isolated.)
|
||||
|
||||
### Planned status-icon behavior (for future battery + Matter work)
|
||||
|
||||
- **Battery icon** (`f[13]` bit4, 0x10): to turn ON when the device battery
|
||||
is **nearly empty** (low-battery warning). Requires adding a battery/gas-
|
||||
gauge circuit (e.g. ADC divider on VBAT, or an I²C fuel gauge) later.
|
||||
- **Bluetooth icon** (`f[13]` bit3, 0x08): to turn ON when the **Matter/
|
||||
Thread network join/connection is successful**, OFF when not connected.
|
||||
Requires the Matter cluster + OpenThread bring-up (see TODO).
|
||||
- Both compose with °C via OR: `f[13] = ICON_DEGC | (low_bat ? ICON_BAT : 0) |
|
||||
(matter_ok ? ICON_BT : 0)` once those signals exist.
|
||||
|
||||
### 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.
|
||||
|
||||
### ⚙️ Two firmware builds: app vs. bit-probe
|
||||
|
||||
`firmware/main/CMakeLists.txt` selects which source compiles. To switch:
|
||||
- **App** (`temphummon.c`): `SRCS "temphummon.c"` — the normal temperature/humidity display
|
||||
- **Probe** (`bitprobe.c`): `SRCS "bitprobe.c"` — interactive bit-field explorer (BOOT-button
|
||||
advance). Files `dpfinder`/`iconfinder` were folded into `bitprobe.c` across sessions.
|
||||
|
||||
The project name is always `temphummon` (even when bitprobe is compiled), so the ELF/bin are
|
||||
named `temphummon.*` regardless. See the serial-monitor workflow below for how to watch output.
|
||||
|
||||
### 🖥️ Serial monitor workflow (headless container)
|
||||
|
||||
`idf.py monitor` needs a real TTY and fails in this container. Use a detached `tmux`
|
||||
session instead (tmux was installed:
|
||||
`apt-get install -y -qq tmux`):
|
||||
|
||||
```bash
|
||||
PYENV=/nix/store/h1az2v5l5jx81jdqfzk5q6wnv40cgq4y-python3-3.13.12-env
|
||||
IDF_MON=/nix/store/4d83mx8ff4ffpcj0x8r4l2x3qd8z3z8k-source/tools/idf_monitor.py
|
||||
tmux new-session -d -s mon "cd /src/firmware && $PYENV/bin/python $IDF_MON \
|
||||
-p /dev/ttyACM0 -b 115200 --toolchain-prefix riscv32-esp-elf- --target esp32h2 \
|
||||
--revision 0 --decode-panic backtrace build/temphummon.elf build/bootloader/bootloader.elf \
|
||||
2>&1 | tee /tmp/monitor.log"
|
||||
# read live log: tr -d '\000' < /tmp/monitor.log | tail -20
|
||||
# stop: tmux kill-session -t mon
|
||||
```
|
||||
|
||||
To hard-reset the board (re-init a wedged panel) without reflashing:
|
||||
```bash
|
||||
$PYENV/bin/python -m esptool --chip esp32h2 -p /dev/ttyACM0 \
|
||||
--before=default-reset --after=hard-reset chip-id
|
||||
```
|
||||
|
||||
The flash tool needs the port free — kill the tmux monitor first (`tmux kill-session -t mon`).
|
||||
|
||||
### ⚠️ Black-control bits (panel wedging) & I²C timeout
|
||||
|
||||
Probing found several bits that flip the whole panel BLACK and wedge it until a hard
|
||||
re-init (RST pulse / board power-cycle): `f[2]` b4, `f[4]` b7, `f[13]` b1 (and `f[8]` b4
|
||||
caused an I²C hardware timeout). Avoid touching these when composing frames. A single
|
||||
`epd_write()` does not always clear the wedge — use `epd_lut_GC()` clear or reset the board.
|
||||
|
||||
---
|
||||
|
||||
## Build State
|
||||
|
||||
### ✅ Successful build
|
||||
|
||||
The firmware compiles and links successfully for ESP32-H2 (RISC-V target).
|
||||
Current `temphummon.c` build size: `0x2c580` bytes (83% free).
|
||||
|
||||
### Firmware behavior
|
||||
|
||||
Current firmware (`main/temphummon.c`):
|
||||
- Initializes GPIOs (RST output, BUSY input) and I²C bus
|
||||
- Initializes display (RST pulse → `0x2B 0xA7 0xE0` power/boost/TSON → temp comp)
|
||||
- Clears display with **GC (full global refresh) LUT**, then updates with
|
||||
**DU_WB (partial) LUT** every second
|
||||
- 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
|
||||
(`render_temp()` / `render_humidity()` helpers)
|
||||
- Prints status via ESP_LOG (`temphummon: T: 27.6°C` every 1s)
|
||||
- Bit 1 of `f[13]` (`0x02`) is NOT used for °C (leaves the degree circle incomplete)
|
||||
— `0x05` = °C (bits 0+2) is the correct full-degree rendering
|
||||
|
||||
### Internal temp sensor — accuracy/resolution notes (verified)
|
||||
|
||||
The internal CPU die sensor is **live** (a CPU-stress test during bring-up
|
||||
raised its reading 27.6 → 28.6°C and held it, confirming the die heats). It
|
||||
measures the **silicon die**, NOT ambient air — blowing on it barely changes it.
|
||||
The readout is **coarsely quantized with a per-chip calibration offset**: values
|
||||
appear stuck in ~1°C steps and always end in `.6` (e.g. 27.6 / 28.6). This is
|
||||
an inherent limitation of the ESP32-H2 internal sensor, not a firmware bug.
|
||||
For a real ambient thermometer, wire an **external I²C sensor** (BME280/SHT40)
|
||||
instead — see TODO.
|
||||
|
||||
⚠️ The display uses a dot-matrix glyph style. A lone bit does not always
|
||||
produce a clean segment; the erase flash (white frame) is normal before each
|
||||
partial update.
|
||||
|
||||
### ✅ 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 = %
|
||||
- [x] **Find Bluetooth & battery icons** — `f[13]` bit3 = Bluetooth, bit4 = battery; combos with °C work (0x0D/0x15/0x1D)
|
||||
- [ ] **Add humidity sensor** — wire up an external I²C sensor (SHT40, BME280, etc.) and show on display line 2
|
||||
- [ ] **Add battery + low-battery icon** — battery circuit/gauge; drive `f[13]` bit4 (battery icon) when nearly empty
|
||||
- [ ] **Configure Matter/Thread** — enable OpenThread / Matter components in `sdkconfig`; drive `f[13]` bit3 (Bluetooth icon) on successful join
|
||||
- [ ] **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
|
||||
14
README.md
14
README.md
|
|
@ -12,3 +12,17 @@ Based on the ESP32-H2 and Waveshare Segment E-Paper display.
|
|||
| [1.9inch Segment E-Paper Module](https://www.waveshare.com/product/displays/e-paper/1.9inch-segment-e-paper-module.htm) | 9.47 € | [wiki](https://www.waveshare.com/wiki/1.9inch_Segment_e-Paper_Module_Manual) |
|
||||
|
||||
Matter over Thread Temperature and humidity monitor
|
||||
|
||||
## 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 |
|
||||
|
||||
> 📖 **Project Memory**: See [`PROJECT_MEMORY.md`](PROJECT_MEMORY.md) for environment setup,
|
||||
> Nix dev shell usage, build status, hardware details, and next steps.
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -1,2 +1,3 @@
|
|||
idf_component_register(SRCS "temphummon.c"
|
||||
INCLUDE_DIRS ".")
|
||||
INCLUDE_DIRS "."
|
||||
PRIV_REQUIRES esp_driver_tsens esp_driver_i2c esp_driver_gpio)
|
||||
|
|
|
|||
218
firmware/main/bitprobe.c
Normal file
218
firmware/main/bitprobe.c
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
/**
|
||||
* iconfinder2 — ESP32-H2 Zero + 1.9" Segment E-Paper (IST7134 via I²C)
|
||||
*
|
||||
* Hunt the BLUETOOTH and BATTERY icons specifically.
|
||||
* Per user: they are at the "3rd and 4th position on the 13th bit" —
|
||||
* i.e. frame byte 13 (the °C/°F byte), bits 3 and 4.
|
||||
*
|
||||
* We probe byte 13 bits 0..7 one at a time on a BLANK panel so any icon is
|
||||
* unmistakable. (We already know: bit0+bit2 = °C letter; bit1 = black
|
||||
* control, so it's guarded.)
|
||||
*
|
||||
* Wiring (GPIO4→SDA, GPIO5→SCL, GPIO10→RST, GPIO11→BUSY, 3V3→VCC, GND→GND)
|
||||
*/
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/i2c_master.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_timer.h"
|
||||
|
||||
#define PIN_SDA GPIO_NUM_4
|
||||
#define PIN_SCL GPIO_NUM_5
|
||||
#define PIN_RST GPIO_NUM_10
|
||||
#define PIN_BUSY GPIO_NUM_11
|
||||
#define PIN_BOOT GPIO_NUM_9
|
||||
|
||||
#define FRAME_LEN 15
|
||||
#define ADVANCE_MODE 1
|
||||
#define AUTO_HOLD_MS 3000
|
||||
#define BOOT_TIMEOUT_MS 0
|
||||
|
||||
static i2c_master_bus_handle_t bus;
|
||||
static i2c_master_dev_handle_t cmd_dev, dat_dev;
|
||||
|
||||
static void epd_reset(void)
|
||||
{
|
||||
gpio_set_level(PIN_RST, 1); vTaskDelay(pdMS_TO_TICKS(200));
|
||||
gpio_set_level(PIN_RST, 0); vTaskDelay(pdMS_TO_TICKS(20));
|
||||
gpio_set_level(PIN_RST, 1); vTaskDelay(pdMS_TO_TICKS(200));
|
||||
}
|
||||
|
||||
static void epd_wait_busy(void)
|
||||
{
|
||||
while (gpio_get_level(PIN_BUSY) == 0) vTaskDelay(pdMS_TO_TICKS(1));
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
|
||||
static void epd_lut_GC(void)
|
||||
{
|
||||
uint8_t lut[] = {0x82, 0x20, 0x00, 0xA0, 0x80, 0x40, 0x63};
|
||||
i2c_master_transmit(cmd_dev, lut, sizeof(lut), pdMS_TO_TICKS(100));
|
||||
}
|
||||
static void epd_lut_DU_WB(void)
|
||||
{
|
||||
uint8_t lut[] = {0x82, 0x80, 0x00, 0xC0, 0x80, 0x80, 0x62};
|
||||
i2c_master_transmit(cmd_dev, lut, sizeof(lut), pdMS_TO_TICKS(100));
|
||||
}
|
||||
|
||||
static void epd_temperature(void)
|
||||
{
|
||||
uint8_t a[] = {0x7E, 0x81, 0xB4};
|
||||
i2c_master_transmit(cmd_dev, a, sizeof(a), pdMS_TO_TICKS(100));
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
uint8_t b[] = {0xE7, 0x0E};
|
||||
i2c_master_transmit(cmd_dev, b, sizeof(b), pdMS_TO_TICKS(100));
|
||||
}
|
||||
|
||||
static void epd_init(void)
|
||||
{
|
||||
epd_reset();
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
uint8_t p[] = {0x2B, 0xA7, 0xE0};
|
||||
i2c_master_transmit(cmd_dev, p, sizeof(p), pdMS_TO_TICKS(100));
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
epd_temperature();
|
||||
}
|
||||
|
||||
static void epd_write(const uint8_t *frame, uint8_t extra_byte)
|
||||
{
|
||||
uint8_t w[] = {0xAC, 0x2B, 0x40, 0xA9, 0xA8};
|
||||
i2c_master_transmit(cmd_dev, w, sizeof(w), pdMS_TO_TICKS(100));
|
||||
i2c_master_transmit(dat_dev, frame, FRAME_LEN, pdMS_TO_TICKS(100));
|
||||
i2c_master_transmit(dat_dev, &extra_byte, 1, pdMS_TO_TICKS(100));
|
||||
uint8_t d[] = {0xAB, 0xAA, 0xAF};
|
||||
i2c_master_transmit(cmd_dev, d, sizeof(d), pdMS_TO_TICKS(100));
|
||||
epd_wait_busy();
|
||||
uint8_t s[] = {0xAE, 0x28, 0xAD};
|
||||
i2c_master_transmit(cmd_dev, s, sizeof(s), pdMS_TO_TICKS(100));
|
||||
}
|
||||
|
||||
/* Show byte13 with ONLY the given bit set (blank elsewhere). */
|
||||
static void show_b13(uint8_t value, int tag, const char *note)
|
||||
{
|
||||
uint8_t f[FRAME_LEN];
|
||||
memset(f, 0, FRAME_LEN);
|
||||
f[13] = value;
|
||||
ESP_LOGI("icon2", "T%d: f[13]=0x%02x (%s)", tag, value, note);
|
||||
|
||||
uint8_t off[FRAME_LEN];
|
||||
memset(off, 0, FRAME_LEN);
|
||||
epd_lut_GC();
|
||||
epd_write(off, 0x00);
|
||||
epd_lut_DU_WB();
|
||||
epd_write(f, 0x00);
|
||||
}
|
||||
|
||||
static void boot_init(void)
|
||||
{
|
||||
gpio_config_t bc = {
|
||||
.pin_bit_mask = (1ULL << PIN_BOOT), .mode = GPIO_MODE_INPUT,
|
||||
.pull_up_en = GPIO_PULLUP_ENABLE, .pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
};
|
||||
gpio_config(&bc);
|
||||
}
|
||||
|
||||
static void wait_for_advance(void)
|
||||
{
|
||||
if (ADVANCE_MODE == 0) { vTaskDelay(pdMS_TO_TICKS(AUTO_HOLD_MS)); return; }
|
||||
ESP_LOGI("icon2", "press BOOT to advance next...");
|
||||
int64_t t_start = esp_timer_get_time();
|
||||
for (;;) {
|
||||
if (gpio_get_level(PIN_BOOT) == 0) {
|
||||
vTaskDelay(pdMS_TO_TICKS(30));
|
||||
while (gpio_get_level(PIN_BOOT) == 0) vTaskDelay(pdMS_TO_TICKS(10));
|
||||
vTaskDelay(pdMS_TO_TICKS(30));
|
||||
break;
|
||||
}
|
||||
if (BOOT_TIMEOUT_MS > 0 &&
|
||||
esp_timer_get_time() - t_start > BOOT_TIMEOUT_MS * 1000LL) {
|
||||
ESP_LOGW("icon2", "no BOOT press in %d ms", BOOT_TIMEOUT_MS);
|
||||
break;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(20));
|
||||
}
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
i2c_master_bus_config_t bc = {
|
||||
.i2c_port = -1, .sda_io_num = PIN_SDA, .scl_io_num = PIN_SCL,
|
||||
.clk_source = I2C_CLK_SRC_DEFAULT, .glitch_ignore_cnt = 7,
|
||||
.flags.enable_internal_pullup = 1,
|
||||
};
|
||||
ESP_ERROR_CHECK(i2c_new_master_bus(&bc, &bus));
|
||||
i2c_device_config_t cc = {
|
||||
.dev_addr_length = I2C_ADDR_BIT_LEN_7, .device_address = 0x3C, .scl_speed_hz = 100000,
|
||||
};
|
||||
i2c_device_config_t dc = {
|
||||
.dev_addr_length = I2C_ADDR_BIT_LEN_7, .device_address = 0x3D, .scl_speed_hz = 100000,
|
||||
};
|
||||
ESP_ERROR_CHECK(i2c_master_bus_add_device(bus, &cc, &cmd_dev));
|
||||
ESP_ERROR_CHECK(i2c_master_bus_add_device(bus, &dc, &dat_dev));
|
||||
|
||||
gpio_config_t rc = {
|
||||
.pin_bit_mask = (1ULL << PIN_RST), .mode = GPIO_MODE_OUTPUT,
|
||||
.pull_up_en = GPIO_PULLUP_DISABLE, .pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
};
|
||||
gpio_config(&rc); gpio_set_level(PIN_RST, 1);
|
||||
gpio_config_t busy_cfg = {
|
||||
.pin_bit_mask = (1ULL << PIN_BUSY), .mode = GPIO_MODE_INPUT,
|
||||
.pull_up_en = GPIO_PULLUP_DISABLE, .pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
};
|
||||
gpio_config(&busy_cfg);
|
||||
|
||||
boot_init();
|
||||
epd_init();
|
||||
|
||||
ESP_LOGI("icon2", "== Probe f[13] bits for Bluetooth (3) & power (4) icons ==");
|
||||
|
||||
/* Single bits of byte 13 (skip bit1 = known black control). */
|
||||
show_b13(0x01, 1, "bit0 (C stroke)");
|
||||
wait_for_advance();
|
||||
|
||||
show_b13(0x02, 2, "bit1 (SKIPPED-guard, black ctrl)");
|
||||
wait_for_advance(); // just wait, we already know it blackens
|
||||
|
||||
show_b13(0x04, 3, "bit2 (C stroke)");
|
||||
wait_for_advance();
|
||||
|
||||
show_b13(0x08, 4, "bit3 = BLUETOOTH?");
|
||||
wait_for_advance();
|
||||
|
||||
show_b13(0x10, 5, "bit4 = POWER/BATTERY?");
|
||||
wait_for_advance();
|
||||
|
||||
show_b13(0x20, 6, "bit5");
|
||||
wait_for_advance();
|
||||
|
||||
show_b13(0x40, 7, "bit6");
|
||||
wait_for_advance();
|
||||
|
||||
show_b13(0x80, 8, "bit7");
|
||||
wait_for_advance();
|
||||
|
||||
/* Combo reference: full °C (0x05) and, if icons exist, try adding them. */
|
||||
show_b13(0x05, 9, "0x05 = °C full");
|
||||
wait_for_advance();
|
||||
|
||||
show_b13(0x05 | 0x08, 10, "0x0D = °C + BT?");
|
||||
wait_for_advance();
|
||||
|
||||
show_b13(0x05 | 0x10, 11, "0x15 = °C + bat?");
|
||||
wait_for_advance();
|
||||
|
||||
show_b13(0x05 | 0x08 | 0x10, 12, "0x1D = °C + BT + bat?");
|
||||
wait_for_advance();
|
||||
|
||||
ESP_LOGI("icon2", "== Done. ==");
|
||||
uint8_t off[FRAME_LEN];
|
||||
memset(off, 0, FRAME_LEN);
|
||||
epd_lut_DU_WB();
|
||||
epd_write(off, 0x00);
|
||||
while (1) vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
|
|
@ -1,6 +1,217 @@
|
|||
#include <stdio.h>
|
||||
/**
|
||||
* temphummon — ESP32-H2 + 1.9" Segment E-Paper (IST7134 via I²C)
|
||||
* Shows internal CPU temperature on top line + fixed humidity on bottom.
|
||||
*
|
||||
* Frame layout (empirically verified, see PROJECT_MEMORY.md):
|
||||
* f[0] = leading "1" glyph (bits 0-4)
|
||||
* f[1],f[2] = top-line tens digit
|
||||
* f[3],f[4] = top-line ones digit
|
||||
* f[5],f[6] = bottom-line tens digit
|
||||
* f[7],f[8] = bottom-line ones digit
|
||||
* f[9],f[10] = bottom-line tenths digit
|
||||
* f[11],f[12] = top-line tenths digit
|
||||
* f[13] = °C (0x05) / °F (0x06) letter
|
||||
*
|
||||
* DECIMAL POINT / % (the hard-won bits!):
|
||||
* f[4] bit5 = top decimal (after top ones) -> "188.8"
|
||||
* f[8] bit5 = bottom decimal (after bottom ones) -> "88.8"
|
||||
* f[10] bit5 = percent sign (after bottom tenths) -> "88.8%"
|
||||
*
|
||||
* Wiring: GPIO4→SDA, GPIO5→SCL, GPIO10→RST, GPIO11→BUSY, 3V3→VCC, GND→GND
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/i2c_master.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/temperature_sensor.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
#define PIN_SDA GPIO_NUM_4
|
||||
#define PIN_SCL GPIO_NUM_5
|
||||
#define PIN_RST GPIO_NUM_10
|
||||
#define PIN_BUSY GPIO_NUM_11
|
||||
|
||||
static i2c_master_bus_handle_t bus;
|
||||
static i2c_master_dev_handle_t cmd_dev, dat_dev;
|
||||
static temperature_sensor_handle_t tsens;
|
||||
|
||||
/* Digit glyphs from Waveshare reference (byte pair per digit). */
|
||||
static const uint8_t DIG[10][2] = {
|
||||
{0xbf,0x1f},{0x1f,0x00},{0xfd,0x17},{0xf5,0x1f},{0x47,0x1f},
|
||||
{0xf7,0x1d},{0xff,0x1d},{0x21,0x1f},{0xff,0x1f},{0xf7,0x1f},
|
||||
};
|
||||
static const uint8_t DIG_L[10][2] = {
|
||||
{0xbf,0x1f},{0x1f,0x00},{0xfd,0x37},{0xf5,0x1f},{0x47,0x1f},
|
||||
{0xf7,0x1d},{0xff,0x3d},{0x21,0x1f},{0xff,0x3f},{0xf7,0x1f},
|
||||
};
|
||||
|
||||
/* Decimal point & % bits (0x20) */
|
||||
#define BIT_DP_TOP 0x20 /* f[4] */
|
||||
#define BIT_DP_BOT 0x20 /* f[8] */
|
||||
#define BIT_PERCENT 0x20 /* f[10] */
|
||||
|
||||
/* Status icons in f[13] (empirically verified, see PROJECT_MEMORY.md) */
|
||||
#define ICON_DEGC 0x05 /* °C (degree + C) */
|
||||
#define ICON_DEGF 0x06 /* °F */
|
||||
#define ICON_BT 0x08 /* Bluetooth */
|
||||
#define ICON_BAT 0x10 /* battery/power */
|
||||
#define ICON_BT_BAT 0x1D /* °C + Bluetooth + battery */
|
||||
|
||||
/* Debug: burn CPU for a while to stress the die and verify the internal
|
||||
* temp sensor responds. Set to 1 to spin in a busy loop for STRESS_MS
|
||||
* after each read (should raise/perturb the die temp).
|
||||
* Verified: die rises 27.6 -> 28.6°C under load (sensor is live). */
|
||||
#define CPU_STRESS_TEST 0
|
||||
#define STRESS_MS 3000
|
||||
|
||||
static void epd_wait_busy(void) {
|
||||
/* BUSY_N: LOW = busy, HIGH = ready */
|
||||
while (gpio_get_level(PIN_BUSY) == 0) vTaskDelay(1);
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
|
||||
static void epd_lut_GC(void) {
|
||||
uint8_t lut[] = {0x82,0x20,0x00,0xA0,0x80,0x40,0x63};
|
||||
i2c_master_transmit(cmd_dev, lut, sizeof(lut), pdMS_TO_TICKS(100));
|
||||
}
|
||||
static void epd_lut_DU_WB(void) {
|
||||
uint8_t lut[] = {0x82,0x80,0x00,0xC0,0x80,0x80,0x62};
|
||||
i2c_master_transmit(cmd_dev, lut, sizeof(lut), pdMS_TO_TICKS(100));
|
||||
}
|
||||
|
||||
static void epd_temperature(void) {
|
||||
uint8_t a[] = {0x7E,0x81,0xB4};
|
||||
i2c_master_transmit(cmd_dev, a, sizeof(a), pdMS_TO_TICKS(100));
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
uint8_t b[] = {0xE7,0x0E};
|
||||
i2c_master_transmit(cmd_dev, b, sizeof(b), pdMS_TO_TICKS(100));
|
||||
}
|
||||
|
||||
static void epd_write(const uint8_t *frame, bool clear) {
|
||||
uint8_t w[] = {0xAC,0x2B,0x40,0xA9,0xA8};
|
||||
i2c_master_transmit(cmd_dev, w, 5, pdMS_TO_TICKS(100));
|
||||
i2c_master_transmit(dat_dev, frame, 15, pdMS_TO_TICKS(100));
|
||||
uint8_t e = clear ? 0x03 : 0x00;
|
||||
i2c_master_transmit(dat_dev, &e, 1, pdMS_TO_TICKS(100));
|
||||
uint8_t d[] = {0xAB,0xAA,0xAF};
|
||||
i2c_master_transmit(cmd_dev, d, 3, pdMS_TO_TICKS(100));
|
||||
epd_wait_busy();
|
||||
uint8_t s[] = {0xAE,0x28,0xAD};
|
||||
i2c_master_transmit(cmd_dev, s, 3, pdMS_TO_TICKS(100));
|
||||
}
|
||||
|
||||
static void epd_init(void) {
|
||||
// Reset
|
||||
gpio_set_level(PIN_RST, 1); vTaskDelay(pdMS_TO_TICKS(200));
|
||||
gpio_set_level(PIN_RST, 0); vTaskDelay(pdMS_TO_TICKS(20));
|
||||
gpio_set_level(PIN_RST, 1); vTaskDelay(pdMS_TO_TICKS(200));
|
||||
// Power on
|
||||
uint8_t p[] = {0x2B,0xA7,0xE0};
|
||||
i2c_master_transmit(cmd_dev, p, 3, pdMS_TO_TICKS(100));
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
epd_temperature();
|
||||
}
|
||||
|
||||
/* Render a value (0-199.9) to the top line: [lead1 ][tens][ones].[tenths] */
|
||||
static void render_temp(uint8_t *f, float t) {
|
||||
if (t < 0) t = 0;
|
||||
if (t > 199.9) t = 199.9;
|
||||
|
||||
int hi = (int)t / 100;
|
||||
int te = ((int)t / 10) % 10;
|
||||
int on = (int)t % 10;
|
||||
int tn = (int)(t * 10) % 10;
|
||||
|
||||
// tens (top)
|
||||
if (hi || te) { f[1] = DIG[te][0]; f[2] = DIG[te][1]; }
|
||||
// ones (top) + DECIMAL (f[4] bit5)
|
||||
f[3] = DIG[on][0];
|
||||
f[4] = DIG[on][1] | BIT_DP_TOP;
|
||||
// tenths (top)
|
||||
f[11] = DIG_L[tn][0];
|
||||
f[12] = DIG_L[tn][1];
|
||||
// leading "1"
|
||||
if (hi) {
|
||||
f[0] |= 0x1f; /* left column of the leading "1" glyph */
|
||||
}
|
||||
// °C
|
||||
f[13] = ICON_DEGC;
|
||||
}
|
||||
|
||||
/* Render a value (0-99.9) to the bottom line: [tens][ones].[tenths]% */
|
||||
static void render_humidity(uint8_t *f, float h) {
|
||||
if (h < 0) h = 0;
|
||||
if (h > 99.9) h = 99.9;
|
||||
|
||||
int te = ((int)h / 10) % 10;
|
||||
int on = (int)h % 10;
|
||||
int tn = (int)(h * 10) % 10;
|
||||
|
||||
// tens (bottom)
|
||||
if (te) { f[5] = DIG[te][0]; f[6] = DIG[te][1]; }
|
||||
// ones (bottom) + DECIMAL (f[8] bit5)
|
||||
f[7] = DIG[on][0];
|
||||
f[8] = DIG[on][1] | BIT_DP_BOT;
|
||||
// tenths (bottom) + PERCENT (f[10] bit5)
|
||||
f[9] = DIG_L[tn][0];
|
||||
f[10] = DIG_L[tn][1] | BIT_PERCENT;
|
||||
}
|
||||
|
||||
void app_main(void) {
|
||||
// Init I²C
|
||||
i2c_master_bus_config_t bc = {.i2c_port=-1,.sda_io_num=PIN_SDA,.scl_io_num=PIN_SCL,
|
||||
.clk_source=I2C_CLK_SRC_DEFAULT,.glitch_ignore_cnt=7,.flags.enable_internal_pullup=1};
|
||||
ESP_ERROR_CHECK(i2c_new_master_bus(&bc, &bus));
|
||||
i2c_device_config_t cc = {.dev_addr_length=I2C_ADDR_BIT_LEN_7,.device_address=0x3C,.scl_speed_hz=100000};
|
||||
i2c_device_config_t dc = {.dev_addr_length=I2C_ADDR_BIT_LEN_7,.device_address=0x3D,.scl_speed_hz=100000};
|
||||
ESP_ERROR_CHECK(i2c_master_bus_add_device(bus, &cc, &cmd_dev));
|
||||
ESP_ERROR_CHECK(i2c_master_bus_add_device(bus, &dc, &dat_dev));
|
||||
|
||||
// Init GPIO
|
||||
gpio_config_t rc = {.pin_bit_mask=(1ULL<<PIN_RST),.mode=GPIO_MODE_OUTPUT,
|
||||
.pull_up_en=GPIO_PULLUP_DISABLE,.pull_down_en=GPIO_PULLDOWN_DISABLE,.intr_type=GPIO_INTR_DISABLE};
|
||||
gpio_config(&rc); gpio_set_level(PIN_RST, 1);
|
||||
gpio_config_t bc2 = {.pin_bit_mask=(1ULL<<PIN_BUSY),.mode=GPIO_MODE_INPUT,
|
||||
.pull_up_en=GPIO_PULLUP_DISABLE,.pull_down_en=GPIO_PULLDOWN_DISABLE,.intr_type=GPIO_INTR_DISABLE};
|
||||
gpio_config(&bc2);
|
||||
|
||||
// Init temp sensor
|
||||
temperature_sensor_config_t tc = TEMPERATURE_SENSOR_CONFIG_DEFAULT(-10, 60);
|
||||
ESP_ERROR_CHECK(temperature_sensor_install(&tc, &tsens));
|
||||
ESP_ERROR_CHECK(temperature_sensor_enable(tsens));
|
||||
|
||||
// Init display
|
||||
epd_init();
|
||||
|
||||
// Clear
|
||||
uint8_t off[15]; memset(off, 0, 15);
|
||||
epd_lut_GC();
|
||||
epd_write(off, 0); vTaskDelay(pdMS_TO_TICKS(500));
|
||||
|
||||
float t;
|
||||
while (1) {
|
||||
ESP_ERROR_CHECK(temperature_sensor_get_celsius(tsens, &t));
|
||||
ESP_LOGI("temphummon", "T: %.1f°C", (double)t);
|
||||
|
||||
#if CPU_STRESS_TEST
|
||||
/* Burn CPU hard for STRESS_MS to heat the die. */
|
||||
uint32_t ticks = pdMS_TO_TICKS(STRESS_MS);
|
||||
uint32_t start = xTaskGetTickCount();
|
||||
volatile uint32_t sink = 1;
|
||||
while ((xTaskGetTickCount() - start) < ticks) {
|
||||
for (uint32_t i = 0; i < 100000; i++) sink = sink * 131 + i; /* ~busy */
|
||||
}
|
||||
(void)sink;
|
||||
#endif
|
||||
|
||||
uint8_t f[15]; memset(f, 0, 15);
|
||||
render_temp(f, t);
|
||||
render_humidity(f, 88.8f); /* fixed sample until a humidity sensor is wired */
|
||||
|
||||
epd_lut_DU_WB();
|
||||
epd_write(f, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
86
flake.lock
generated
Normal file
86
flake.lock
generated
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1782847189,
|
||||
"narHash": "sha256-twXPFqFsrrY5r28Zh7Homgcp2gUMBgQ6WDS98Q/3xFI=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "b6018f87da91d19d0ab4cf979885689b469cdd41",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "nixos-25.11",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-esp-dev": {
|
||||
"inputs": {
|
||||
"flake-utils": [
|
||||
"flake-utils"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1777553768,
|
||||
"narHash": "sha256-2J1ZbfbLUGwpXfLpDTCEpiR4wZD1lVFWiup67DSJRuw=",
|
||||
"owner": "dvdvgt",
|
||||
"repo": "nixpkgs-esp-dev",
|
||||
"rev": "f9b1e211262a4cc9c1a265b227def56ef01c2d56",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "dvdvgt",
|
||||
"ref": "update-v6.0.1",
|
||||
"repo": "nixpkgs-esp-dev",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"nixpkgs-esp-dev": "nixpkgs-esp-dev"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
Loading…
Reference in a new issue