# 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