Make the repo self-contained (no dependency on the parent firmware-tools/ checkout): copy in the patch/disasm/syx tools and the c2probe RP2040 C2 flash-reader/patcher firmware. - patch_usb1_to_cv.py : byte-level USB-1->CV patch (imported by roundtrip.py) - d8051.py : standalone 8051 disassembler - syx_extract.py : SysEx extractor - c2probe/ : RP2040 C2 flash reader + host scripts (c2probe.c, cdc/dump_flash/patch_c2/reflash_page/verify.py, CMake build) - RECOVERY.md : C2 flash recovery procedure - EFM8UB20_PINOUT.md : reverse-engineered QFP48 pinout + firmware pin usage - roundtrip.py : import local patch_usb1_to_cv (parent as fallback) - .gitignore : exclude c2probe/.venv, c2probe/build Verified: stock + patched round-trips still re-assemble byte-identical.
156 lines
7.2 KiB
Markdown
156 lines
7.2 KiB
Markdown
# EFM8UB20F64G (QFP48) — reverse-engineered pinout & firmware pin usage
|
||
|
||
QuNexus main MCU = **EFM8UB20F64G-B-QFP48** (48-pin TQFP). App firmware v2.2.1.
|
||
|
||
> There is **no QFN48** variant of the EFM8UB20F64G — the datasheet lists only
|
||
> QFP48, QFP32, and QFN32. The 48-pin part is therefore QFP48, and the QFP48
|
||
> ADC channel map is the one that matches the firmware.
|
||
|
||
All findings below are derived from the disassembly (`qunexus_v2.2.1.asm`) and
|
||
verified against the SiLabs header (SFR addresses) and the EFM8UB2 reference
|
||
manual (ADC channel→pin map). The pin *configuration* and *which pins are
|
||
actively toggled* are directly proven from the code; the identity of the
|
||
external device on the P2/P3/P4 bus is inferred from the bus structure.
|
||
|
||
## Port configuration
|
||
|
||
The entire crossbar/port setup is one block at `0xde6f`, written once
|
||
(firmware-wide — XBR / MDOUT / MDIN / SKIP are never written elsewhere):
|
||
|
||
| Register | Value | Meaning |
|
||
|----------|-------|---------|
|
||
| XBR0 | 0x01 | **UART0 only** — no SPI, no SMBus, no comparators routed |
|
||
| XBR1 | 0x43 | crossbar enabled (XBARE = 1) |
|
||
| XBR2 | 0x00 | default, never written — UART1/SMB1 off |
|
||
| P0SKIP | 0xCF | skip P0.0-3,6,7 → leave P0.4/P0.5 for UART0 |
|
||
| P1SKIP | 0x03 | skip P1.0/P1.1 |
|
||
| P2SKIP | 0x00 | default, never written |
|
||
| P3SKIP | 0x00 | default (P3SKIP = 0xDF, never written) |
|
||
| P0MDIN | 0x30 | P0.4/P0.5 digital; P0.0-3,6,7 **analog** |
|
||
| P1MDIN | 0x3F | P1.0-5 digital; P1.6/P1.7 **analog** |
|
||
| P2/P3/P4MDIN | 0xFF | all digital |
|
||
| P0MDOUT | 0x10 | P0.4 push-pull (TX); rest open-drain |
|
||
| P1MDOUT | 0x3F | P1.0-5 push-pull; P1.6/7 open-drain |
|
||
| P2MDOUT | 0xFF | all push-pull |
|
||
| P3MDOUT | 0xF0 | P3.4-7 push-pull; **P3.0-3 open-drain** |
|
||
| P4MDOUT | 0xFF | all push-pull |
|
||
|
||
> The P4 port latch is SFR **0xC7** — not 0xC0 (0xC0 is SMB0CN0, the SMBus
|
||
> control register). Easy to misidentify.
|
||
|
||
## Peripherals actually used
|
||
|
||
- **UART0** → P0.4 (TX) / P0.5 (RX) = the 5-pin DIN MIDI port. (USB-MIDI is a
|
||
separate class engine over D+/D−, not this UART.)
|
||
- **ADC0**: `AMX0P = 0x11` → **P0.3** (ADC0P.17 on QFP48), `AMX0N = 0x1f`
|
||
(GND, single-ended), `ADC0CN0 = 0x02` (enabled). AMX0P is written **exactly
|
||
once**, so the firmware reads a single fixed ADC channel. ADC0L/H (0xBD/0xBE)
|
||
are read at `0x8a37`, `0xa838`, `0xcadd`. → An **external analog mux**
|
||
(steered by the digital scan bus) feeds many sensors into P0.3.
|
||
- **No SPI0, no SMB0, no hardware EMIF** — none of their config registers are
|
||
ever written. The parallel bus below is **bit-banged**.
|
||
|
||
## Runtime GPIO — a bit-banged parallel bus to an external engine
|
||
|
||
The EFM8 is not driving the LEDs/touch directly; it talks to an external chip
|
||
(CPLD / expander / LED+touch controller) over a hand-strobed parallel bus, and
|
||
advances a select matrix on a timer tick:
|
||
|
||
- **P2.0–P2.7** (push-pull): written from a Timer ISR — `mov P2,a` at `0xc537`
|
||
+ `setb TR2`, ends `reti`. The `rlc a` / `djnz` / `cpl a` loop builds a
|
||
walking one-hot pattern → **scan / select bus**.
|
||
- **P3.0–P3.3** (open-drain): toggled with `orl/anl P3,#0x0f` (`0xc942`,
|
||
`0xc997`, `0xca89`, `0xcad1`, `0xcad8`, `0xd774`) → **control strobes**.
|
||
- **P4.0–P4.7** (push-pull, SFR 0xC7): `mov P4,a` (`0xc950`, `0xc999`, `0xd778`),
|
||
always paired with a P3 strobe → **8-bit data bus**.
|
||
- **P1.5** (push-pull): toggled (`clr P1.5` @ `0x860e`, `mov` @ `0xe38f`) →
|
||
GPIO output, function unknown. P1.0/P1.1 are skipped = reserved GPIO.
|
||
|
||
## Full QFP48 pin table (firmware function)
|
||
|
||
| Pin | Port | Firmware function | Used? |
|
||
|-----|------|--------------------|-------|
|
||
| 1 | P0.5 | UART0 RX — MIDI In | ✓ |
|
||
| 2 | P0.4 | UART0 TX — MIDI Out | ✓ |
|
||
| 3 | P0.3 | **ADC input** (single channel, ext. mux output) | ✓ |
|
||
| 4 | P0.2 | analog, no ADC/CMP fn | ○ unused |
|
||
| 5 | P0.1 | analog, no ADC/CMP fn | ○ unused |
|
||
| 6 | P0.0 | analog, no ADC/CMP fn | ○ unused |
|
||
| 7 | GND | ground | — |
|
||
| 8 | D+ | USB (USB-MIDI class) | ✓ |
|
||
| 9 | D− | USB | ✓ |
|
||
| 10 | VDD | supply / reg output | — |
|
||
| 11 | VREGIN | 5V reg input | — |
|
||
| 12 | VBUS | USB VBUS sense | ✓ |
|
||
| 13 | RST/C2CK | reset / C2 flash clock | ✓ |
|
||
| 14 | C2D | C2 flash data | ✓ |
|
||
| 15 | P4.7 | data bus D7 | ✓ |
|
||
| 16 | P4.6 | data bus D6 | ✓ |
|
||
| 17 | P4.5 | data bus D5 | ✓ |
|
||
| 18 | P4.4 | data bus D4 | ✓ |
|
||
| 19 | P4.3 | data bus D3 | ✓ |
|
||
| 20 | P4.2 | data bus D2 | ✓ |
|
||
| 21 | P4.1 | data bus D1 | ✓ |
|
||
| 22 | P4.0 | data bus D0 | ✓ |
|
||
| 23 | P3.7 | push-pull out, never written | ○ static |
|
||
| 24 | P3.6 | push-pull out, referenced once | ○ ~unused |
|
||
| 25 | P3.5 | push-pull out, never written | ○ static |
|
||
| 26 | P3.4 | push-pull out, never written | ○ static |
|
||
| 27 | P3.3 | open-drain **strobe** | ✓ |
|
||
| 28 | P3.2 | open-drain **strobe** | ✓ |
|
||
| 29 | P3.1 | open-drain **strobe** | ✓ |
|
||
| 30 | P3.0 | open-drain **strobe** | ✓ |
|
||
| 31 | P2.7 | **scan/select** (timer ISR) | ✓ |
|
||
| 32 | P2.6 | scan/select | ✓ |
|
||
| 33 | P2.5 | scan/select | ✓ |
|
||
| 34 | P2.4 | scan/select | ✓ |
|
||
| 35 | P2.3 | scan/select | ✓ |
|
||
| 36 | P2.2 | scan/select | ✓ |
|
||
| 37 | P2.1 | scan/select | ✓ |
|
||
| 38 | P2.0 | scan/select | ✓ |
|
||
| 39 | P1.7 | analog (EMIF /WR not used) | ○ unused |
|
||
| 40 | P1.6 | analog (EMIF /RD not used) | ○ unused |
|
||
| 41 | P1.5 | GPIO output (toggled) | ✓ |
|
||
| 42 | P1.4 | CNVSTR/GPIO (ADC is SW-triggered) | ○ ~unused |
|
||
| 43 | P1.3 | push-pull GPIO | ○ ~unused |
|
||
| 44 | P1.2 | push-pull GPIO | ○ ~unused |
|
||
| 45 | P1.1 | skipped GPIO | ○ ~unused |
|
||
| 46 | P1.0 | skipped GPIO | ○ ~unused |
|
||
| 47 | P0.7 | XTAL2 — internal oscillator | ○ unused |
|
||
| 48 | P0.6 | XTAL1 — internal oscillator | ○ unused |
|
||
|
||
Legend: ✓ actively driven/read by firmware · ○ configured but no active drive
|
||
found (likely unused/static) · — power/USB/debug (hardware).
|
||
|
||
## Architecture summary
|
||
|
||
The EFM8UB20 is essentially a **USB-MIDI class engine + DIN-MIDI UART + bus
|
||
master**, not the thing doing the LED/touch work:
|
||
|
||
- **USB** (pins 8/9/12) = USB-MIDI class traffic.
|
||
- **UART0** (pins 1/2) = 5-pin DIN MIDI in/out.
|
||
- **ADC** (pin 3, P0.3) = one analog channel reading an external analog mux.
|
||
- **Bit-banged parallel bus** to an external LED/touch engine: **P4 = 8-bit
|
||
data** (pins 15–22), **P3.0–3 = strobes** (pins 27–30), **P2 = scan/select**
|
||
(pins 31–38, advanced by a timer ISR).
|
||
- **C2** (pins 13/14) = how we flash/read it.
|
||
|
||
Used pins: **1, 2, 3** (MIDI + ADC), **8, 9, 11, 12** (USB), **13, 14** (C2
|
||
debug), **15–22** (P4 data), **27–30** (P3 strobes), **31–38** (P2 scan),
|
||
**41** (P1.5 GPIO). Everything else is configured but shows no active drive.
|
||
|
||
## Caveat
|
||
|
||
The "external LED/touch engine" on the P2/P3/P4 bus is an inference from the
|
||
bus structure and the walking-one scan pattern — the firmware's driving of the
|
||
bus is directly visible, but what sits on the other end can only be confirmed
|
||
from board photos or a schematic.
|
||
|
||
## Sources
|
||
|
||
- EFM8UB2 Reference Manual, Table 12.1 (AMX0P ADC channel→pin map):
|
||
https://www.silabs.com/documents/public/reference-manuals/efm8ub2-rm.pdf
|
||
- EFM8UB20F64G-B-QFP48 datasheet, Table 6.1 (QFP48 pin definitions):
|
||
https://resources.ampheo.com/static/datasheets/silicon-labs/efm8ub20f64g-b-qfp48.pdf
|
||
- si_efm8ub2_defs.h (SFR address verification):
|
||
https://www.keil.com/dd/docs/c51/silabs/efm8ub2/inc/si_efm8ub2_defs.h |