Workshop: repairs and experiments

Reading a DDR5 module’s SPD: Windows, CH341A and ESP32 — and why the dumps differ

Published 06.11.2026 · Updated 07.11.2026

This is part of repairing a Corsair Vengeance DDR5 module — main article. Here: how we read the SPD in three ways, what came out, why the file from Windows turned out incomplete, and how to build a reader on an ESP32 — wiring diagram, firmware and our dumps for comparison.

What the SPD is on DDR5

The SPD is the module’s “passport”: size, speeds and timings, the XMP profile, the manufacturer. On DDR5 it lives in a hub chip (SPD5118 or similar), takes 1024 bytes and is split into 8 pages of 128 bytes. Only one page is visible at a time; which one is chosen by the hub register MR11. Anything that cannot switch pages sees only the first one — that is the key to everything below.

Three methods we tried

MethodWhat you needResult
Windows + Thaiphoon Burnera working PC, module in a slot1024 bytes, but actually the first page repeated 8 times; checksum does not match
CH341A programmer + AsProgrammerprogrammer, 1.8 V adapter, wires to the edgeno address answered — neither on the working nor on the dead module
ESP32 + our own firmwarean ESP32 board, 2 resistors, a 5 V supplyboth modules, 1024 bytes, 0 read errors, valid checksum

1. Windows and Thaiphoon Burner

The easiest way: put the module in a PC and read it with a program. The downside is obvious — only the working module can be read this way: the dead one keeps the PC from booting. Thaiphoon saves the result in its own .thp format (1029 bytes, encoded data) — useless for a byte-by-byte comparison. But next to the program it keeps a backup file where the same 1024 bytes are stored raw (after 8 header bytes). That raw array is what we compared.

2. The CH341A programmer

An ordinary EEPROM programmer does not read DDR5. The DDR5 hub is not a simple EEPROM like a 24C02: before reading it has to be put into the right mode by a command and pages have to be switched through register MR11, and the module itself must be powered through its own power controller (PMIC). AsProgrammer does none of that. We tried addresses 0x50–0x53, 0x18, 0x36, 0x37 — no answer on either module. Conclusion: the tool, not the modules.

3. ESP32 with our own firmware — it worked

An ESP32 board can do everything needed: talk to the hub over I²C, switch pages and print the dump to the serial monitor. We took the init sequence from the open-source SPD-Reader-Writer project (by 1a2m3): check that the hub (0x50) and the PMIC (0x48) answer, write MR11 = 0 (page 0, plain I²C mode), read the device type (MR0 = 0x51, MR1 = 0x18 — hub with a temperature sensor), then 8 pages of 128 bytes.

Our dumps

FileWhat it isSize
ddr5_module1_not_working_esp32.binmodule 1 (dead), ESP32 — raw 1024 bytes1024 B
ddr5_module2_working_esp32.binmodule 2 (working), ESP32 — raw 1024 bytes1024 B
ddr5_module1_not_working_esp32_serial.txtmodule 1: Serial Monitor output as is3607 B
ddr5_module2_working_esp32_serial.txtmodule 2: Serial Monitor output as is3607 B
ddr5_windows_thaiphoon_raw.binWindows, Thaiphoon Burner — raw array from its backup copy1024 B
ddr5_windows_thaiphoon.thpWindows, Thaiphoon Burner — file in the program’s own format1029 B
esp32_ddr5_spd_reader.inoESP32 firmware (Arduino)6776 B

The ESP32 dumps of both modules are identical to the byte. The base block checksum (CRC-16, bytes 0–509) computes to A7AB for both — exactly what is stored in the SPD itself. So the dead module’s description is intact: the fault is not in the SPD, and rewriting it is pointless.

AddressValueMeaning
0x00212memory type: DDR5
0x00302module type: UDIMM
0x1FE–0x1FFAB A7base block checksum (A7AB)
0x200–0x20102 9Emodule manufacturer: Corsair
0x205–0x20800 00 00 00module serial number: not written on our modules
0x209–0x21ACMH32GX5M2E6000C36part number (ASCII)
0x228–0x22980 2CDRAM manufacturer: Micron
0x2800C …start of the XMP 3.0 profile

Before publishing your own dump, look at bytes 0x205–0x208: that is the module serial number. Ours are zeros, so the dumps are published as they are. If yours are not zeros, replace those four bytes with zeros before publishing (the checksum at 0x1FE covers bytes 0–509 only, so it is unaffected), or do not publish the dump at all.

Why the Windows dump differs from the ESP32 dump

436 of 1024 bytes differ. But that is not “different module contents” — it is an incomplete read. Evidence:

  • Each of the 8 pages in the Windows file is the first SPD page: 1021 of 1024 bytes match (the three that differ are in one copy — looks like a read glitch on the shared bus).
  • The checksum of such a file does not match: it computes to 9778, and where it should be stored there are zeros.
  • At address 0x200, where the manufacturer code lives, the Windows file has 30 10 — the first two SPD bytes. That is why Thaiphoon named the file “Unknown (JEDEC ID 3010h)”: it read the “manufacturer” from a repeat of the first page.

Thaiphoon itself describes the cause in its readme: on Intel platforms, if the “SPD Write Disable” protection is enabled in the BIOS, the program cannot access the whole 1024-byte array and the decoded data may look corrupted. Most likely the protection keeps the program from changing the hub’s page register, so every request returns the first page — which is exactly what our file looks like. The ESP32 talks to the module directly, with no BIOS in between, and switches pages itself — so it reads everything.

How to read it fully in Windows. On Intel boards the BIOS option is often called “SPD Write Disable”: set it to FALSE (Disabled) for the read, read, then set it back. The option disables SPD write protection — it should not be left off.

ESP32 wiring

Wiring an ESP32 to a DDR5 module to read the SPDESP32 (DevKit, ESP32-WROOM-32)3V3GNDGPIO21 · SDAGPIO22 · SCLUSB → computer (Serial 115200)DDR5 UDIMM module3 · 5 V (PMIC supply)151 · PWR_EN ← 3.3 V148 · HSA → ground150 · ground (GND)5 · HSDA (data)4 · HSCL (clock)module 1.8 V:capacitor next to the SPD hub5 V power supply(1 A or more)+−R1R2R1, R2: 4.7–5.1 kΩ⚠ SDA/SCL get only pull-ups to the module’s 1.8 V. Never put 5 V or 3.3 V on these lines.⚠ Before applying 5 V, check with a meter that pin 3 connects to 145/146 on your module.
How we wired the ESP32 to the module. Wire colour = signal; R1, R2 pull the I²C lines up to the module’s own 1.8 V.
ESP32 / sourceModule pinSignalPurpose
GPIO21 (SDA)5HSDAI²C data + pull-up to the module’s 1.8 V
GPIO22 (SCL)4HSCLI²C clock + pull-up to the module’s 1.8 V
GND150Vsscommon ground: ESP32, 5 V supply, module
— (to ground)148HSAaddress 0 and hub service mode
3V3151PWR_ENPMIC enable
5 V supply “+”3VIN_BULKmodule PMIC supply
R1, R2 4.7–5.1 kΩ—1.8 Vfrom SDA and SCL to the 1.8 V capacitor next to the SPD hub

What matters:

  • Power the module through its PMIC, as a PC does: 5 V on the supply input, 3.3 V on the PWR_EN enable signal. The PMIC makes all internal voltages itself, including 1.8 V for the hub.
  • Pull-ups go to the module’s own 1.8 V: the easiest point is the capacitor next to the SPD hub chip (it appears as soon as the PMIC starts; check with a meter). No separate 1.8 V source is needed.
  • Pin 148 (HSA) to ground: that is address 0 and the hub service mode.
  • Pin 3: we applied 5 V to exactly this pin (as described in the Bus Pirate docs), and the PMIC started. The UniIC datasheet lists the supply on pins 1, 145, 146 and pin 3 as reserved. So before applying 5 V, check on your module that pin 3 connects to 145/146.
  • ESP32 internal pull-ups: the Wire library enables its own weak pull-ups (tens of kΩ) to 3.3 V. With our 4.7–5.1 kΩ to 1.8 V the idle line sits slightly above 1.8 V by calculation (≈1.9 V). It worked for us; if you want exactly 1.8 V, disable the internal pull-ups after Wire.begin().
  • Reaching the edge contacts: either thin wires carefully attached to the gold contacts you need, or a DDR5 slot from a spare board as a “socket”. The main thing is not to short neighbouring contacts: the pitch is under a millimetre.

Laptop memory and SPD write protection

A few more useful facts — from the Bus Pirate DDR5 documentation (we have not verified them on our own equipment).

SignalUDIMM (desktop)SO-DIMM (laptop)
HSDA (I²C data)56
HSCL (I²C clock)44
PWR_EN (PMIC enable, 3.3 V)1518
HSA (address / service mode)1482
PWR_GOOD (power OK, output)1477
BULK_VIN (5 V)31
ground1509
  • Write protection. The 1024 SPD bytes are split into 16 blocks of 64 bytes; which ones are protected is set by bits in hub registers MR12 and MR13. Protection can be set at any time, but cleared only in service mode (HSA to ground) — exactly the wiring we read with. Our firmware only reads, but any write to the SPD can leave the module unable to boot: save a dump first.
  • Hub status — register MR48: separate bits show “write in progress” and “service mode”.
  • The hub has a temperature sensor (registers 49–50) — handy for watching the module warm up.

ESP32 firmware

The file esp32_ddr5_spd_reader.ino is a sketch for the Arduino IDE (board “ESP32 Dev Module”). After uploading, open the Serial Monitor at 115200 baud and type commands:

CommandWhat it does
sscans the I²C bus (our module answered at 0x18, 0x48 — PMIC — and 0x50 — SPD hub)
dchecks the hub: expect MR0 = 0x51, MR1 = 0x18 (hub with temperature sensor)
pchecks the PMIC at 0x48
rreads all 1024 bytes and prints the dump; at the end — “Read errors: 0 / 1024 bytes”
hhelp

To turn the printed dump into a .bin file (save the monitor output as dump.txt):

# Linux / Git Bash
grep -E '^[0-9A-F]{3}:' dump.txt | cut -d: -f2 | tr -d ' \r\n' | xxd -r -p > dump.bin

# Python (any OS)
python -c "import re,sys; d=bytes(int(b,16) for l in open(sys.argv[1]) if re.match(r'^[0-9A-F]{3}:',l) for b in l.split(':')[1].split()); open(sys.argv[2],'wb').write(d)" dump.txt dump.bin

The result must be exactly 1024 bytes. Compare two dumps with cmp a.bin b.bin (empty output — identical files) or fc /b a.bin b.bin on Windows.

Article changelog

DateWhat changed
2026-10-30SO-DIMM pins, SPD write protection, status and temperature registers (per the Bus Pirate docs). First version: three read methods, dumps, explanation of the Windows difference, ESP32 wiring and firmware.