# 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

