Files
deskscreen/FW/ESPHOME_WITH_ATTINY85.md
T
2026-05-13 11:45:58 +02:00

481 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ESPHome Standing Desk Display Component
## ATmega328P Proxy Architecture
**Version:** 3.1
**Date:** April 2026
**Platform:** ESP32-S3 + ATmega328P proxy
**Protocol:** I2C (ATmega328P slave at 0x50)
---
## Executive Summary
This project uses an **ATmega328P microcontroller (28-pin) as a protocol proxy** to bridge the original desk controller's AiP650E display communication and memory keys to an ESP32-S3 running ESPHome.
**Architecture:**
- **Original Desk Controller** → Sends CLK/DIO protocol + memory key signals
- **ATmega328P Proxy** → Listens to CLK/DIO, reads memory keys, exposes I2C registers
- **ESP32-S3** → Reads I2C registers, renders touchscreen UI, connects to Home Assistant
**Benefits:**
- ✅ Timing-critical protocol capture isolated from ESP32 (no WiFi jitter)
- ✅ Handles all 3 memory keys + common line (no ESP32 GPIO needed)
- ✅ Serial debugging via UART (faster development)
- ✅ Easy reprogramming via bootloader (after initial ISP)
- ✅ Hardware I2C support (not bit-banged)
- ✅ Minimal wiring (CLK/DIO + 3 keys + I2C + power)
- ✅ Original motor control completely unaffected
---
## 1. Hardware Setup
### 1.1 ATmega328P Pin Assignment (28-pin QFN)
```
┌─────────────┐
PC6 ├───●───────┤ AVCC (pin 20)
PD0 ├───────────┤ GND (pin 22)
PD1 ├───────────┤ PC5
PD2 ├───────────┤ PC4
PD3 ├───────────┤ PC3
PD4 ├───────────┤ PC2
PD5 ├───────────┤ PC1
PD6 ├───────────┤ PC0
PD7 ├───────────┤ GND
PB0 ├───────────┤ VCC
PB1 ├───────────┤ PB2
PB3 ├───────────┤ PB4
PB5 ├───────────┤ PB6
└─────────────┘
```
| Pin # | Port | Function | Connect To | Notes |
|-------|------|----------|-----------|-------|
| 1 | PC6 | RESET | ISP Header (or button) | ISP programming reset |
| 2 | PD0 | RXD | USB-UART TX (optional) | Serial debugging input |
| 3 | PD1 | TXD | USB-UART RX (optional) | Serial debugging output |
| 4 | PD2 | (spare) | — | Available for future use |
| 5 | PD3 | (spare) | — | Available for future use |
| 6 | PD4 | KEY_1 Input | Display Pin 13 | Memory key 1 input |
| 7 | VCC | Power | ESP32 3.3V | Main power supply |
| 8 | GND | Ground | Common GND | Ground reference |
| 11 | PD5 | KEY_2 Input | Display Pin 9 | Memory key 2 input |
| 12 | PD6 | KEY_3 Input | Display Pin 8 | Memory key 3 input |
| 13 | PD7 | KEY_COMMON | Display Pin 7 | Memory key common line |
| 14 | PB0 | SCL (I2C) | ESP32 GPIO21 | I2C Clock (4.7k pull-up to 3.3V) |
| 15 | PB1 | SDA (I2C) | ESP32 GPIO20 | I2C Data (4.7k pull-up to 3.3V) |
| 16 | PB2 | CLK Input | Display Pin 2 | Display protocol clock |
| 17 | PB3 | MOSI (ISP) | ISP Header Pin 1 | SPI Data In (ISP only) |
| 18 | PB4 | MISO (ISP) | ISP Header Pin 5 | SPI Data Out (ISP only) |
| 19 | PB5 | SCK (ISP) | ISP Header Pin 3 | SPI Clock (ISP only) |
| 20 | AVCC | Analog Ref | VCC + 0.1µF cap | Analog voltage reference |
| 21 | PC1 | LED Output | LED Anode | Activity LED indicator (active HIGH) |
| 22 | GND | Ground | Common GND | Ground reference |
| 23 | PC0 | DIO Input | Display Pin 3 | Display protocol data |
| 24 | PC2 | UP Button | Button to GND | UP button input (pull-up) |
| 25 | PC3 | DOWN Button | Button to GND | DOWN button input (pull-up) |
**Key Layout Advantages:**
- ✅ Protocol pins (CLK/DIO) on PB2/PC0 (safe from ISP)
- ✅ Memory keys on PD4/5/6/7 (safe from ISP)
- ✅ ISP pins (PB3/4/5) isolated and only used during programming
- ✅ Serial debug on PD0/1 (useful for development)
### 1.2 Wiring Diagram
```
┌──────────────────────────────┐ ┌──────────────────┐ ┌──────────────┐
│ Display Driver Board │ │ ATmega328P │ │ ESP32-S3 │
├──────────────────────────────┤ ├──────────────────┤ ├──────────────┤
│ Pin 2 (CLK) ────────────────→├─→ │ Pin 16 PB2 │ │ │
│ Pin 3 (DIO) ────────────────→├─→ │ Pin 23 PC0 │ │ │
│ Pin 7 (KEY_COMMON) ─────────→├─→ │ Pin 13 PD7 │ │ │
│ Pin 8 (KEY_3) ──────────────→├─→ │ Pin 12 PD6 │ │ │
│ Pin 9 (KEY_2) ──────────────→├─→ │ Pin 11 PD5 │ │ │
│ Pin 13 (KEY_1) ─────────────→├─→ │ Pin 6 PD4 │ │ │
│ GND ────────────────────────→├─→ │ Pin 8 GND ──┬───→├──→ GND │
│ │ │ Pin 21 PC1 ─┼──┐ │ │
│ │ │ (LED → R ├─→└─┼→ +3.3V via R │
│ │ │ Pin 24 PC2 ─┼──→├──→ UP Button │
│ │ │ Pin 25 PC3 ─┼──→├──→ DOWN Button │
│ │ │ Pin 14 PB0 ─┼──→├──→ GPIO21 (SCL) │
│ │ │ (4.7k pull) │ │ │
│ │ │ Pin 15 PB1 ─┼──→├──→ GPIO20 (SDA) │
│ │ │ (4.7k pull) │ │ │
│ │ │ Pin 7 VCC ◄─┴──→├──← GPIO 3.3V │
│ │ │ │ │
│ │ │ [ISP Header] │ │
│ │ │ Pin 17 (MOSI) │ │
│ │ │ Pin 18 (MISO) │ │
│ │ │ Pin 19 (SCK) ────→ ISP Programmer │
│ │ │ Pin 1 (RESET) │ │
└──────────────────────────────┘ └──────────────────┘ └──────────────┘
```
### 1.3 Bill of Materials
#### For Breadboard Prototype:
| Part | Value | Qty | Cost | Notes |
|------|-------|-----|------|-------|
| ATmega328P | 28-pin QFN | 1 | $2-3 | Microchip or compatible |
| Resistor | 4.7k | 2 | $0.20 | I2C pull-ups (3.3V side) |
| Resistor | 330Ω | 1 | $0.05 | LED current limiting |
| Resistor | 10k | 2 | $0.10 | Button pull-ups (optional) |
| Capacitor | 100nF | 1 | $0.10 | Decoupling on VCC |
| LED | Any color | 1 | $0.10 | Activity indicator |
| Button | Tactile 6mm | 2 | $0.20 | UP and DOWN buttons |
| **Total** | | | **$2.70-3.75** | Breadboard prototype |
| Breadboard | — | 1 | $2-5 | For prototyping |
| **Total** | | | **$5-15** | Estimated cost |
#### For Custom PCB Interposer:
| Part | Value | Qty | Cost | Notes |
|------|-------|-----|------|-------|
| ATmega328P | 28-pin DIP/TQFP | 1 | $2-3 | Better for production |
| Resistor | 4.7k | 2 | $0.10 | I2C pull-ups |
| Resistor | 10k | 2 | $0.10 | LED resistors (optional) |
| Capacitor | 100nF | 2 | $0.20 | Decoupling + AVCC filter |
| **Connectors:** | | | | |
| JST-SH4 (QWIIC) | — | 1 | $0.50 | I2C port (standard) |
| 2×3 pin header | — | 1 | $0.20 | ISP programming header |
| 1×6 pin header | — | 1 | $0.20 | Serial debug (optional) |
| **Level Shifter** | TXB0104 | 1 | $0.80 | Shift 5V ↔ 3.3V (if needed) |
| PCB | — | 1 | $5-15 | Small custom PCB |
| **Total** | | | **$12-25** | Estimated cost |
**Notes:**
- ISP header is required for initial firmware programming
- Serial header enables easy reprogramming (after bootloader installed)
- Level shifter needed only if running ATmega at 5V with 3.3V ESP32
- QWIIC port provides standardized I2C connector
### 1.4 Interposer PCB Design Checklist
When designing the custom PCB, include:
-**Main Components:**
- [ ] ATmega328P in 28-pin DIP or TQFP-32 package
- [ ] 100nF decoupling capacitor across VCC/GND
- [ ] 4.7k pull-up resistors on I2C lines (if not on ESP32 board)
-**Programming Headers:**
- [ ] 6-pin ISP header (2×3 pin spacing, labeled clearly)
- [ ] 6-pin serial FTDI header (for bootloader reprogramming)
- [ ] Label each header with pin numbers (1-6)
-**External Connectors:**
- [ ] JST-SH4 connector for QWIIC I2C (front-facing)
- [ ] 4-pin header for CLK/DIO from original micro
- [ ] 4-pin header for UP/DOWN buttons (optional)
-**Layout:**
- [ ] ISP header on edge of board for programmer access
- [ ] Serial header nearby ISP for easy access
- [ ] QWIIC port on opposite edge from ISP
- [ ] Clear silk-screen labeling
-**Routing:**
- [ ] Keep ISP traces short (< 1 inch)
- [ ] 4.7k pull-ups on I2C, close to connector
- [ ] Ground plane if possible (connects all GNDs together)
- [ ] No high-speed traces near ISP/serial headers
---
## 2. I2C Protocol (ATtiny85 → ESP32-S3)
### 2.1 Register Map
**Address:** 0x50 (1010_0000 in 8-bit format)
| Offset | Name | Type | Access | Description |
|--------|------|------|--------|-------------|
| 0x00 | DIG1 | uint8 | R | Digit 1 (hundreds): 0-9 or 0xFF |
| 0x01 | DIG2 | uint8 | R | Digit 2 (tens): 0-9 or 0xFF |
| 0x02 | DIG3 | uint8 | R | Digit 3 (ones): 0-9 or 0xFF |
| 0x03 | STAT | uint8 | R | Display status (power, brightness, mode) |
| 0x10 | VERSION | uint8 | R | Firmware version (0x10 = v1.0) |
| 0x11 | ERROR | uint8 | R | Error flags |
### 2.2 Register Details
**DIG1, DIG2, DIG3 (0x00, 0x01, 0x02) - Shadow RAM:**
- **Range:** 0-9 (valid digit) or 0xFF (invalid/not available)
- **Combined Value:** `display_mm = DIG1*100 + DIG2*10 + DIG3`
- **Example:** If DIG1=2, DIG2=5, DIG3=0 → display shows "250"
⚠️ **Important: Shadow RAM Behavior**
These registers contain **persistent** display values, maintained by the ATtiny85 even when the original desk display blanks (power saving mode). This means:
- **Display is awake:** Registers show current desk height
- **Display goes to sleep (blanks after timeout):** Registers continue showing last known height
- Original display might show brightness=0 or blank segments
- ESP32 still reads the height value (e.g., "250")
- This prevents "NaN" or error readings on your touchscreen UI
- **Display wakes up:** Registers update with any new height changes
**Example Timeline:**
```
Time 0: Desk at 250mm → DIG1=2, DIG2=5, DIG3=0
Time 60s: Sleep timeout → Original display blanks
Time 60s: ESP32 reads DIG1/2/3 → Still gets "250" (shadow RAM)
Time 120s: User presses UP button → Display wakes to 275mm
Time 120s: DIG1=2, DIG2=7, DIG3=5 (updated)
```
**Benefits of Shadow RAM:**
- ✅ No stale data on ESP32 touchscreen
- ✅ Display blanking doesn't disrupt UI
- ✅ Seamless height display during sleep mode
**STAT Byte (0x03):**
```
Bit 7 (MSB): Display Power (1=on, 0=off)
Bit 6-4: Brightness Level (0-7, 7=brightest)
Bit 3: SEG Mode (1=7-segment, 0=8-segment)
Bit 2: Sleep Mode (1=enabled, 0=disabled)
Bit 1-0: Reserved (always 0)
```
**VERSION Byte (0x10):**
- `0x10` = ATtiny85 firmware v1.0
**ERROR Byte (0x11):**
```
Bit 7: I2C Communication Error (1=error, 0=ok)
Bit 6: Frame Timeout (1=no CLK >1sec, 0=ok)
Bit 5: Malformed Protocol Frame (1=error, 0=ok)
Bit 4: Segment Data Corruption (1=error, 0=ok)
Bit 3-0: Reserved
```
### 2.3 Example I2C Read (C++)
```cpp
#include <Wire.h>
#define PROXY_ADDR 0x50
void setup() {
Wire.begin();
Serial.begin(115200);
}
void loop() {
uint8_t dig1, dig2, dig3, stat, err;
// Read digit 1
Wire.beginTransmission(PROXY_ADDR);
Wire.write(0x00);
Wire.endTransmission();
Wire.requestFrom(PROXY_ADDR, 1);
dig1 = Wire.read();
// Read digit 2
Wire.beginTransmission(PROXY_ADDR);
Wire.write(0x01);
Wire.endTransmission();
Wire.requestFrom(PROXY_ADDR, 1);
dig2 = Wire.read();
// Read digit 3
Wire.beginTransmission(PROXY_ADDR);
Wire.write(0x02);
Wire.endTransmission();
Wire.requestFrom(PROXY_ADDR, 1);
dig3 = Wire.read();
// Combine into height value
if (dig1 != 0xFF && dig2 != 0xFF && dig3 != 0xFF) {
uint16_t height = dig1 * 100 + dig2 * 10 + dig3;
Serial.printf("Desk Height: %d mm\n", height);
}
delay(100); // Poll every 100ms
}
```
### 2.4 Example ESPHome Config
```yaml
esphome:
name: standing-desk
esp32_s3:
board: esp32-s3-devkitc-1
i2c:
sda: GPIO20
scl: GPIO21
frequency: 100kHz
sensor:
- platform: i2c
name: "Desk Height (from proxy)"
address: 0x50
register: 0x00
value_type: U8
id: proxy_dig1
unit_of_measurement: ""
- platform: i2c
name: "Desk Height Digit 2"
address: 0x50
register: 0x01
value_type: U8
id: proxy_dig2
- platform: i2c
name: "Desk Height Digit 3"
address: 0x50
register: 0x02
value_type: U8
id: proxy_dig3
- platform: template
name: "Desk Height (mm)"
unit_of_measurement: "mm"
icon: "mdi:ruler"
update_interval: 100ms
lambda: |-
uint16_t d1 = (uint8_t)id(proxy_dig1).state;
uint16_t d2 = (uint8_t)id(proxy_dig2).state;
uint16_t d3 = (uint8_t)id(proxy_dig3).state;
if (d1 != 0xFF && d2 != 0xFF && d3 != 0xFF) {
return d1 * 100 + d2 * 10 + d3;
}
return {};
binary_sensor:
- platform: gpio
name: "UP Button"
pin: GPIO14
icon: "mdi:arrow-up"
- platform: gpio
name: "DOWN Button"
pin: GPIO13
icon: "mdi:arrow-down"
```
---
## 3. ATtiny85 Firmware
See: **ATTINY85_PROXY_FIRMWARE.md** for complete firmware source code
**Key functions:**
- CLK interrupt handler: Capture protocol bits
- Frame parser: Decode AiP650E commands
- I2C slave: Expose registers 0x00-0x11
- Timeout detection: Set error flags on bus inactivity
**Compilation:**
- Arduino IDE + ATtinyCore
- 8 MHz internal clock
- 3-4 KB flash used
---
## 4. Expected Display Values
The ATtiny85 decodes AiP650E segment data and converts to digits.
### 4.1 7-Segment Character Mapping
| Digit | Hex | Segments | Pattern |
|-------|-----|----------|---------|
| 0 | 0x3F | A,B,C,D,E,F | `█████▓` |
| 1 | 0x06 | B,C | `░░█████` |
| 2 | 0x5B | A,B,D,E,G | `█▓█▓██` |
| 3 | 0x4F | A,B,C,D,G | `█▓███▓` |
| 4 | 0x66 | B,C,F,G | `░░██████` |
| 5 | 0x6D | A,C,D,F,G | `██▓███` |
| 6 | 0x7D | A,C,D,E,F,G | `██▓███` |
| 7 | 0x07 | A,B,C | `░░█████` |
| 8 | 0x7F | A,B,C,D,E,F,G | `███████` |
| 9 | 0x6F | A,B,C,D,F,G | `██████▓` |
### 4.2 Typical Display Values
- **Minimum height:** 000 (all zeros: 0 mm)
- **Normal range:** 600-1200 (standing desk typical)
- **Maximum height:** 999 (3 digits max)
---
## 5. Troubleshooting
### I2C Issues
| Problem | Cause | Solution |
|---------|-------|----------|
| I2C not responding | Wrong address or not started | Check address 0x50, verify Wire.begin() in ESP32 sketch |
| Reads return 0xFF | Protocol not capturing | Check CLK/DIO connections, verify ATtiny85 power |
| Intermittent I2C | Weak pull-ups | Use external 4.7k resistors on SCL/SDA |
| ATtiny85 won't program | ISP speed too fast | Reduce ISP clock to <500kHz in programmer |
### Protocol Issues
| Problem | Cause | Solution |
|---------|-------|----------|
| Display values always 0xFF | CLK interrupt not firing | Check PB3 connected to original pin 16 |
| Display values wrong | Segment decoder bug | Check 7-segment lookup table in firmware |
| ERROR byte shows bit 6 (timeout) | Original micro not running | Power-cycle original control board |
### Expected Behavior - Display Blanking
This is **not** a problem - it's expected behavior!
**Scenario:** Your desk display goes dark after timeout (sleep mode)
- **Original desk display:** Blank/dark (brightness=0)
- **ESP32 touchscreen:** Still shows the height value (e.g., "250mm")
- **Explanation:** Shadow RAM keeps the last valid value even when the physical display blanks
**Why this is good:**
- ✅ Your touchscreen UI remains responsive and shows current height
- ✅ No "NaN" or error messages on screen
- ✅ User always knows the desk position, even at night with display off
- ✅ When desk wakes up, both displays sync automatically
**If you DON'T see height on touchscreen while original display is blank:**
- Check I2C pull-ups (4.7k resistors on SCL/SDA)
- Verify ESP32 is polling DIG1/DIG2/DIG3 registers
- Check ATtiny85 power supply (should be 3.3V)
---
## 6. Performance
- **I2C Read Time:** ~5-10ms per register
- **Display Update Rate:** 10-100ms (based on original micro)
- **ESP32 Poll Interval:** 100ms recommended
- **Power Consumption (ATtiny85):** ~10-50mA depending on load
---
## 7. Future Enhancements
- [ ] Decode memory buttons from AiP650E key matrix
- [ ] Add CRC8 checksum for data validation
- [ ] Support dual-display setup (multiple ATtiny85 proxies)
- [ ] Energy monitoring (motor current sense)
- [ ] Watchdog timer for crash recovery
---
## 8. References
- **ATtiny85 Datasheet:** Microchip ATtiny85 (8-bit AVR)
- **AiP650E Datasheet:** Wuxi I-CORE Electronics (attached in spec)
- **ESPHome Documentation:** https://esphome.io
- **Arduino ATtinyCore:** https://github.com/SpenceKonde/ATTinyCore
---
**Document Version:** 3.0
**Last Updated:** April 2026