/* ESP32-WROOM-32 DDR5 SPD Reader ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Minimal sketch для standalone DDR5 SPD readout. Реалізує MR11 init sequence на основі SPD-Reader-Writer firmware (1a2m3). Hardware: - ESP32 GPIO 21 (SDA) → DDR5 edge pin 5 (HSDA) - ESP32 GPIO 22 (SCL) → DDR5 edge pin 4 (HSCL) - ESP32 GND → common GND - Pull-up 4.7 kΩ: SDA → 1.8V, SCL → 1.8V (reference з 1.8V LDO) - DDR5 живиться через свій PMIC: 5V на pin 3, 3.3V на pin 151 (PWR_EN), pin 148 (HSA) → GND - НЕ підключати ESP32 3.3V до DDR5 hub напряму! Serial commands (Serial Monitor @ 115200 baud): s = scan I2C bus 0x08-0x77 d = detect DDR5 hub at 0x50 r = read full 1024-byte SPD dump p = probe PMIC at 0x48 h = help */ #include #define SDA_PIN 21 #define SCL_PIN 22 #define I2C_FREQ 100000 #define HUB_ADDR 0x50 // SPD5 Hub EEPROM/register slot 0 #define PMIC_ADDR 0x48 // PMIC management slot 0 // SPD5 hub register map #define MR0 0x00 // Device Type MSB → expect 0x51 #define MR1 0x01 // Device Type LSB → 0x18 (TS) or 0x08 (no TS) #define MR11 0x0B // I2C Legacy Mode Device Configuration #define MR48 0x30 // Device Status #define SPD_SIZE 1024 bool probeAddress(uint8_t addr) { Wire.beginTransmission(addr); return Wire.endTransmission() == 0; } bool writeReg(uint8_t addr, uint8_t reg, uint8_t val) { Wire.beginTransmission(addr); Wire.write(reg & 0x7F); // bit 7 = 0 → register access Wire.write(val); bool ok = (Wire.endTransmission() == 0); // MR11 не потребує delay, інші регістри — 10 ms if (reg != MR11) delay(10); return ok; } int16_t readReg(uint8_t addr, uint8_t reg) { Wire.beginTransmission(addr); Wire.write(reg & 0x7F); // bit 7 = 0 → register access if (Wire.endTransmission(false) != 0) return -1; Wire.requestFrom(addr, (uint8_t)1); uint32_t t0 = millis(); while (!Wire.available() && (millis() - t0) < 50) {} if (!Wire.available()) return -1; return Wire.read(); } int16_t readData(uint8_t addr, uint8_t pageOffset) { Wire.beginTransmission(addr); Wire.write(pageOffset | 0x80); // bit 7 = 1 → EEPROM data access if (Wire.endTransmission(false) != 0) return -1; Wire.requestFrom(addr, (uint8_t)1); uint32_t t0 = millis(); while (!Wire.available() && (millis() - t0) < 50) {} if (!Wire.available()) return -1; return Wire.read(); } void scanBus() { Serial.println("Scanning I2C bus 0x08-0x77..."); int count = 0; for (uint8_t addr = 0x08; addr <= 0x77; addr++) { if (probeAddress(addr)) { Serial.printf(" ACK at 0x%02X", addr); if (addr >= 0x48 && addr <= 0x4F) Serial.print(" (PMIC range)"); if (addr >= 0x50 && addr <= 0x57) Serial.print(" (SPD EEPROM/hub range)"); Serial.println(); count++; } } Serial.printf("Total devices found: %d\n", count); } bool detectDdr5(uint8_t addr) { Serial.printf("Detecting DDR5 hub at 0x%02X...\n", addr); if (!probeAddress(addr)) { Serial.println(" ✗ EEPROM/hub no ACK"); return false; } Serial.println(" ✓ EEPROM/hub ACK"); uint8_t pmic = (addr & 0b111) | 0x48; if (probeAddress(pmic)) { Serial.printf(" ✓ PMIC ACK at 0x%02X\n", pmic); } else { Serial.printf(" ⚠ PMIC no ACK at 0x%02X (continuing anyway)\n", pmic); } // Reset page to 0 + activate Legacy I2C mode if (!writeReg(addr, MR11, 0)) { Serial.println(" ✗ MR11 write failed"); return false; } Serial.println(" ✓ MR11 = 0 (page reset + Legacy mode)"); delay(2); int16_t mr0 = readReg(addr, MR0); int16_t mr1 = readReg(addr, MR1); Serial.printf(" MR0 = 0x%02X, MR1 = 0x%02X\n", mr0, mr1); if (mr0 == 0x51 && (mr1 == 0x18 || mr1 == 0x08)) { Serial.printf(" ✓ DDR5 SPD5 Hub: %s\n", mr1 == 0x18 ? "SPD5_TS (with temp sensor)" : "SPD5_NO"); return true; } Serial.println(" ✗ Not a recognized SPD5 hub signature"); return false; } void readFullSpd(uint8_t addr) { if (!detectDdr5(addr)) { Serial.println("Detection failed, aborting read."); return; } Serial.println("\n=== SPD DUMP (1024 bytes hex) ==="); uint8_t currentPage = 0xFF; uint16_t failCount = 0; for (uint16_t offset = 0; offset < SPD_SIZE; offset++) { uint8_t page = offset >> 7; // 128 bytes per page → 8 pages uint8_t pageOffset = offset & 0x7F; if (page != currentPage) { writeReg(addr, MR11, page); currentPage = page; delay(1); } int16_t val = readData(addr, pageOffset); if (val < 0) failCount++; if (offset % 16 == 0) { Serial.printf("\n%03X: ", offset); } Serial.printf("%02X ", val < 0 ? 0xFF : (uint8_t)val); } Serial.println("\n=== END OF DUMP ==="); Serial.printf("Read errors: %u / %u bytes\n", failCount, SPD_SIZE); // Reset page to 0 after read writeReg(addr, MR11, 0); } void probePmic(uint8_t addr) { Serial.printf("Probing PMIC at 0x%02X...\n", addr); if (probeAddress(addr)) { Serial.println(" ✓ ACK received — PMIC I2C management active"); // Try reading first register int16_t r0 = readReg(addr, 0x00); int16_t r1 = readReg(addr, 0x01); Serial.printf(" Reg 0x00 = 0x%02X, Reg 0x01 = 0x%02X\n", r0, r1); } else { Serial.println(" ✗ No ACK — PMIC not on I2C or not in management mode"); } } void printHelp() { Serial.println("Commands:"); Serial.println(" s = scan I2C bus 0x08-0x77"); Serial.println(" d = detect DDR5 hub at 0x50"); Serial.println(" p = probe PMIC at 0x48"); Serial.println(" r = read full 1024-byte SPD dump"); Serial.println(" h = this help"); } void setup() { Serial.begin(115200); delay(500); // Disable internal pull-ups so external 1.8V pull-up controls the line pinMode(SDA_PIN, INPUT); pinMode(SCL_PIN, INPUT); Wire.begin(SDA_PIN, SCL_PIN, I2C_FREQ); Serial.println("\n========================================"); Serial.println(" ESP32 DDR5 SPD Reader v1"); Serial.printf(" SDA = GPIO %d, SCL = GPIO %d\n", SDA_PIN, SCL_PIN); Serial.printf(" I2C freq = %d Hz\n", I2C_FREQ); Serial.println("========================================"); printHelp(); } void loop() { if (Serial.available()) { char c = Serial.read(); while (Serial.available()) Serial.read(); // flush remaining switch (c) { case 's': scanBus(); break; case 'd': detectDdr5(HUB_ADDR); break; case 'p': probePmic(PMIC_ADDR); break; case 'r': readFullSpd(HUB_ADDR); break; case 'h': printHelp(); break; case '\n': case '\r': break; default: Serial.printf("Unknown command: '%c'. Type 'h' for help.\n", c); } } delay(10); }