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

18 KiB
Raw Blame History

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++)

#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

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


Document Version: 3.0
Last Updated: April 2026