// ============================================================
//  PCF8575 16路IO 批量检测
//  板子：ESP32-2432S028（2.8寸 ST7789 黄屏 / CYD）
//
//  I2C  ：SDA=27  SCL=22
//         —— CN1 排针（GND - IO22 - IO27 - 3V3），
//            和"万能IIC测试仪"扫描到 0x20 用的是同一组脚。
//  PCF8575 地址：0x20
//
//  流程：
//    启动 -> 检测 0x20
//      找不到：屏幕大字 0x20 FAIL，停机
//      找到  ：屏幕大字 0x20 PASS -> 进入 IO 测试
//    IO 测试：开机默认 16 路 LOW 并保持；按一次板载 BOOT 键，
//             在 0x0000(LOW) / 0xFFFF(HIGH) 之间切换一次并保持，
//             屏幕中央大字同步显示 LOW / HIGH，串口同步打印。
//    中途 I2C 出错：屏幕 I2C ERROR，串口打印错误码，停机。
// ============================================================

#include <Arduino.h>
#include <Wire.h>
#include "soc/gpio_struct.h"

// ---- 屏幕 ST7789 位带 SPI 引脚（与"万能IIC测试仪"一致）----
#define LCD_W     240
#define LCD_H     320
#define LCD_SCK   14   // 屏幕 SPI 时钟
#define LCD_MOSI  13   // 屏幕 SPI 数据（不是 I2C 的 SDA）
#define LCD_CS    15
#define LCD_DC    2
#define LCD_BLK   21   // 背光

// ---- I2C 引脚（扫描到 0x20 用的就是这两根）----
#define I2C_SDA   27
#define I2C_SCL   22

#define PCF8575_ADDR 0x20

// 板载 BOOT 按钮 = GPIO0，按下接地（低电平）
#define BOOT_BTN   0

// ---- RGB565 颜色（本驱动 MADCTL=0x00，颜色不反序）----
#define BLACK   0x0000
#define WHITE   0xFFFF
#define RED     0xF800
#define GREEN   0x07E0
#define BLUE    0x001F
#define CYAN    0x07FF
#define YELLOW  0xFFE0

// ---- 位带加速宏 ----
#define SCK_MASK  (1UL << LCD_SCK)
#define MOSI_MASK (1UL << LCD_MOSI)
#define CS_MASK   (1UL << LCD_CS)
#define DC_MASK   (1UL << LCD_DC)
#define BLK_MASK  (1UL << LCD_BLK)

#define FAST_HIGH(mask) (GPIO.out_w1ts = (mask))
#define FAST_LOW(mask)  (GPIO.out_w1tc = (mask))

#define SCK_0  FAST_LOW(SCK_MASK)
#define SCK_1  FAST_HIGH(SCK_MASK)
#define MOSI_0 FAST_LOW(MOSI_MASK)
#define MOSI_1 FAST_HIGH(MOSI_MASK)
#define CS_0   FAST_LOW(CS_MASK)
#define CS_1   FAST_HIGH(CS_MASK)
#define DC_0   FAST_LOW(DC_MASK)
#define DC_1   FAST_HIGH(DC_MASK)
#define BLK_1  FAST_HIGH(BLK_MASK)

// ---- 6x8 点阵字体（仅本程序用到的字符）----
static const uint8_t FONT[][6] = {
  {' ', 0x00, 0x00, 0x00, 0x00, 0x00},
  {'0', 0x3E, 0x51, 0x49, 0x45, 0x3E},
  {'1', 0x00, 0x42, 0x7F, 0x40, 0x00},
  {'2', 0x42, 0x61, 0x51, 0x49, 0x46},
  {'3', 0x21, 0x41, 0x45, 0x4B, 0x31},
  {'4', 0x18, 0x14, 0x12, 0x7F, 0x10},
  {'5', 0x27, 0x45, 0x45, 0x45, 0x39},
  {'6', 0x3C, 0x4A, 0x49, 0x49, 0x30},
  {'7', 0x01, 0x71, 0x09, 0x05, 0x03},
  {'8', 0x36, 0x49, 0x49, 0x49, 0x36},
  {'9', 0x06, 0x49, 0x49, 0x29, 0x1E},
  {'A', 0x7E, 0x11, 0x11, 0x11, 0x7E},
  {'B', 0x7F, 0x49, 0x49, 0x49, 0x36},
  {'C', 0x3E, 0x41, 0x41, 0x41, 0x22},
  {'D', 0x7F, 0x41, 0x41, 0x22, 0x1C},
  {'E', 0x7F, 0x49, 0x49, 0x49, 0x41},
  {'F', 0x7F, 0x09, 0x09, 0x09, 0x01},
  {'G', 0x3E, 0x41, 0x49, 0x49, 0x7A},
  {'H', 0x7F, 0x08, 0x08, 0x08, 0x7F},
  {'I', 0x00, 0x41, 0x7F, 0x41, 0x00},
  {'K', 0x7F, 0x08, 0x14, 0x22, 0x41},
  {'L', 0x7F, 0x40, 0x40, 0x40, 0x40},
  {'M', 0x7F, 0x02, 0x0C, 0x02, 0x7F},
  {'N', 0x7F, 0x04, 0x08, 0x10, 0x7F},
  {'O', 0x3E, 0x41, 0x41, 0x41, 0x3E},
  {'P', 0x7F, 0x09, 0x09, 0x09, 0x06},
  {'R', 0x7F, 0x09, 0x19, 0x29, 0x46},
  {'S', 0x46, 0x49, 0x49, 0x49, 0x31},
  {'T', 0x01, 0x01, 0x7F, 0x01, 0x01},
  {'U', 0x3F, 0x40, 0x40, 0x40, 0x3F},
  {'V', 0x1F, 0x20, 0x40, 0x20, 0x1F},
  {'W', 0x3F, 0x40, 0x38, 0x40, 0x3F},
  {'X', 0x63, 0x14, 0x08, 0x14, 0x63},
  {'Y', 0x07, 0x08, 0x70, 0x08, 0x07},
  {'x', 0x44, 0x28, 0x10, 0x28, 0x44},
};

// ---- SPI 位带发送 ----
static void spiSendByte(uint8_t value) {
  for (uint8_t i = 0; i < 8; i++) {
    SCK_0;
    if (value & 0x80) MOSI_1; else MOSI_0;
    value <<= 1;
    SCK_1;
    SCK_0;
  }
}

static void sendCmd(uint8_t value)  { DC_0; spiSendByte(value); }
static void sendData(uint8_t value) { DC_1; spiSendByte(value); }

static void setWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) {
  sendCmd(0x2A);
  sendData(x0 >> 8); sendData(x0 & 0xFF);
  sendData(x1 >> 8); sendData(x1 & 0xFF);
  sendCmd(0x2B);
  sendData(y0 >> 8); sendData(y0 & 0xFF);
  sendData(y1 >> 8); sendData(y1 & 0xFF);
  sendCmd(0x2C);
}

static void fillRect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color) {
  if (x >= LCD_W || y >= LCD_H) return;
  if (x + w > LCD_W) w = LCD_W - x;
  if (y + h > LCD_H) h = LCD_H - y;
  setWindow(x, y, x + w - 1, y + h - 1);
  for (uint32_t i = 0; i < (uint32_t)w * h; i++) {
    sendData(color >> 8);
    sendData(color & 0xFF);
  }
}

static void fillScreen(uint16_t color) {
  setWindow(0, 0, LCD_W - 1, LCD_H - 1);
  for (uint32_t i = 0; i < (uint32_t)LCD_W * LCD_H; i++) {
    sendData(color >> 8);
    sendData(color & 0xFF);
  }
}

static const uint8_t *glyphFor(char c) {
  for (uint8_t i = 0; i < sizeof(FONT) / sizeof(FONT[0]); i++) {
    if (FONT[i][0] == c) return &FONT[i][1];
  }
  return &FONT[0][1];  // 未知字符退格为空格
}

static void drawChar(uint16_t x, uint16_t y, char c, uint16_t color, uint16_t bg, uint8_t scale) {
  const uint8_t *g = glyphFor(c);
  uint16_t w = 6 * scale;
  uint16_t h = 8 * scale;
  setWindow(x, y, x + w - 1, y + h - 1);
  for (uint8_t py = 0; py < h; py++) {
    uint8_t row = py / scale;
    for (uint8_t px = 0; px < w; px++) {
      uint8_t col = px / scale;
      bool on = (col < 5 && row < 7) ? (g[col] & (1 << row)) : false;
      uint16_t out = on ? color : bg;
      sendData(out >> 8);
      sendData(out & 0xFF);
    }
  }
}

static void drawText(uint16_t x, uint16_t y, const char *text, uint16_t color, uint16_t bg, uint8_t scale) {
  while (*text) {
    drawChar(x, y, *text++, color, bg, scale);
    x += 6 * scale;
  }
}

static void drawCentered(uint16_t y, const char *text, uint16_t color, uint16_t bg, uint8_t scale) {
  uint16_t w = strlen(text) * 6 * scale;
  uint16_t x = (w >= LCD_W) ? 0 : (LCD_W - w) / 2;
  drawText(x, y, text, color, bg, scale);
}

static void tftInit() {
  pinMode(LCD_SCK, OUTPUT);
  pinMode(LCD_MOSI, OUTPUT);
  pinMode(LCD_CS, OUTPUT);
  pinMode(LCD_DC, OUTPUT);
  pinMode(LCD_BLK, OUTPUT);

  SCK_1; CS_1; BLK_1;
  delay(100);
  CS_0;

  sendCmd(0x11); delay(120);
  sendCmd(0x3A); sendData(0x05);
  sendCmd(0xC5); sendData(0x1A);
  sendCmd(0x36); sendData(0x00);
  sendCmd(0xB2); sendData(0x05); sendData(0x05); sendData(0x00); sendData(0x33); sendData(0x33);
  sendCmd(0xB7); sendData(0x05);
  sendCmd(0xBB); sendData(0x3F);
  sendCmd(0xC0); sendData(0x2C);
  sendCmd(0xC2); sendData(0x01);
  sendCmd(0xC3); sendData(0x0F);
  sendCmd(0xC4); sendData(0x20);
  sendCmd(0xC6); sendData(0x01);
  sendCmd(0xD0); sendData(0xA4); sendData(0xA1);
  sendCmd(0xE8); sendData(0x03);
  sendCmd(0xE9); sendData(0x09); sendData(0x09); sendData(0x08);

  sendCmd(0xE0);
  sendData(0xD0); sendData(0x05); sendData(0x09); sendData(0x09);
  sendData(0x08); sendData(0x14); sendData(0x28); sendData(0x33);
  sendData(0x3F); sendData(0x07); sendData(0x13); sendData(0x14);
  sendData(0x28); sendData(0x30);

  sendCmd(0xE1);
  sendData(0xD0); sendData(0x05); sendData(0x09); sendData(0x09);
  sendData(0x08); sendData(0x03); sendData(0x24); sendData(0x32);
  sendData(0x32); sendData(0x3B); sendData(0x14); sendData(0x13);
  sendData(0x28); sendData(0x2F);

  // 这块 2.8 寸 ST7789 用 INVON 会反色，这里不启用
  sendCmd(0x20);
  sendCmd(0x29);
  CS_0;
}

// ---- PCF8575 写 16 位 ----
// 先低 8 位（P00~P07），再高 8 位（P10~P17），共 16 路全部控制。
// PCF8575 是准双向(quasi-bidirectional)IO：写 1 = 释放为高，写 0 = 拉低。
// 返回值是 Wire.endTransmission() 的错误码：0 = 成功。
static byte pcfWrite(uint16_t value) {
  Wire.beginTransmission(PCF8575_ADDR);
  Wire.write(value & 0xFF);          // P00~P07 低8位
  Wire.write((value >> 8) & 0xFF);   // P10~P17 高8位
  return Wire.endTransmission();
}

// ---- 界面 ----
static void drawTopBar(const char *text, uint16_t bg, uint16_t fg) {
  fillRect(0, 0, LCD_W, 38, bg);
  drawCentered(8, text, fg, bg, 2);
}

// 启动时的 PASS / FAIL 大字
static void drawStartup(bool pass) {
  uint16_t color = pass ? GREEN : RED;
  const char *status = pass ? "0x20 PASS" : "0x20 FAIL";
  fillScreen(BLACK);
  drawCentered(80, "PCF8575", WHITE, BLACK, 4);
  drawCentered(150, status, color, BLACK, 4);
}

// 进入测试后：顶部固定状态栏 + "16 IO"（只画一次）
static void drawTestHeader() {
  fillScreen(BLACK);
  drawTopBar("PCF8575 0x20 PASS", GREEN, BLACK);
  drawCentered(64, "16 IO", WHITE, BLACK, 4);
}

// 只重绘中央大字 LOW / HIGH，避免整屏闪烁
static void drawState(const char *label, uint16_t color) {
  fillRect(0, 110, LCD_W, 170, BLACK);
  drawCentered(140, label, color, BLACK, 7);
}

static void showError(byte err) {
  fillScreen(BLACK);
  drawTopBar("PCF8575 TEST", RED, WHITE);
  drawCentered(110, "I2C ERROR", RED, BLACK, 4);
  char code[12];
  snprintf(code, sizeof(code), "CODE %d", err);
  drawCentered(200, code, YELLOW, BLACK, 3);
}

static bool running = false;
static bool highState = false;       // false=LOW, true=HIGH；开机默认 LOW
static bool btnWasPressed = false;   // BOOT 键下降沿检测
static uint32_t lastBtnMs = 0;       // BOOT 键消抖

void setup() {
  Serial.begin(115200);
  delay(500);
  Serial.println("PCF8575 TEST");

  tftInit();
  pinMode(BOOT_BTN, INPUT_PULLUP);
  Wire.begin(I2C_SDA, I2C_SCL);
  Wire.setClock(100000);
  delay(100);

  // 检测 0x20
  Wire.beginTransmission(PCF8575_ADDR);
  byte err = Wire.endTransmission();

  if (err == 0) {
    Serial.println("Address: 0x20 PASS");
    drawStartup(true);
    delay(1500);              // 给用户看清 PASS
    drawTestHeader();
    // 开机默认 16 路全部 LOW 并保持，等 BOOT 键切换
    byte w = pcfWrite(0x0000);
    if (w != 0) {
      showError(w);
      Serial.print("I2C ERROR code=");
      Serial.println(w);
      return;               // running 保持 false，屏幕停在 FAIL
    }
    drawState("LOW", RED);
    Serial.println("16 IO = LOW");
    running = true;
  } else {
    Serial.print("Address: 0x20 FAIL code=");
    Serial.println(err);
    drawStartup(false);
    // running 保持 false，loop() 里不动作，屏幕停在 FAIL
  }
}

void loop() {
  if (!running) {
    delay(100);
    return;
  }

  // BOOT 键（GPIO0）按下为低电平，抓下降沿，按一次切换一次并保持
  bool pressed = (digitalRead(BOOT_BTN) == LOW);
  uint32_t now = millis();

  if (pressed && !btnWasPressed && (now - lastBtnMs) > 200) {
    lastBtnMs = now;
    highState = !highState;

    uint16_t value = highState ? 0xFFFF : 0x0000;
    byte err = pcfWrite(value);
    if (err != 0) {
      showError(err);
      Serial.print("I2C ERROR code=");
      Serial.println(err);
      running = false;
      return;
    }
    drawState(highState ? "HIGH" : "LOW", highState ? GREEN : RED);
    Serial.println(highState ? "16 IO = HIGH" : "16 IO = LOW");
  }
  btnWasPressed = pressed;
}
