/* * PCF8574 I/O expander @ 0x21. * * The PCF8574 has no direction register. Every pin is quasi-bidirectional: * writing 1 releases the pin to a weak pull-up (and lets it be read as an * input), writing 0 drives it hard low. So "output high" and "input" are the * same electrical state, and there is no way to read back what you last wrote * — reading returns the actual pin level. * * That is why this driver keeps a shadow byte. Without it, a read-modify-write * to set the touch reset line would sample the encoder button and the relay * line and write those levels back, which on the relay is a genuine hazard. * The shadow starts at 0xFF, matching the chip's power-on state (all pins * released high) — which is also the relay-off state for an active-low module. */ #pragma once #include #include #include "esp_err.h" #define PCF8574_I2C_ADDR 0x21 /* Named per docs/HARDWARE.md. */ #define PCF_PIN_TOUCH_RST 0 #define PCF_PIN_RELAY 1 /* this project — heat call, active low */ #define PCF_PIN_TOUCH_INT 2 #define PCF_PIN_LCD_POWER 3 #define PCF_PIN_LCD_RST 4 #define PCF_PIN_ENCODER_SW 5 /* P6, P7 free */ esp_err_t pcf8574_init(void); /* True once init has succeeded. relay.c checks this before trusting writes. */ bool pcf8574_is_ready(void); /* Drive one pin. level=false pulls low; level=true releases to the pull-up. */ esp_err_t pcf8574_write(uint8_t pin, bool level); /* Read the actual level of one pin. */ esp_err_t pcf8574_read(uint8_t pin, bool *level); /* Write the whole port at once. Updates the shadow. */ esp_err_t pcf8574_write_port(uint8_t value); /* Last value written, not the pin levels. */ uint8_t pcf8574_shadow(void);