Skip to content

GPIO Mapping Session

The Carryout G2’s firmware has a gpio submenu. We didn’t expect it to be particularly interesting — maybe a few pins for LEDs, maybe a relay output. Then we ran gpio regs and got back 92 pins worth of state data across five I/O ports.

That was the start of a long session cross-referencing live GPIO readings with the K60 datasheet pin mux table, boot log output, and the A3981 motor driver datasheet. By the end, we had a functional map of how the MCU talks to every major peripheral on the board.

The GPIO submenu has four commands: dir <pin> (direction), r <pin> (read), w <pin> <val> (write), and regs (dump everything). The regs command is the one that matters:

TRK> gpio
GPIO> regs
Port A:
A0=0 A1=0 A2=0 A3=0 A4=0 A5=1 A6=0 A7=0
A8=0 A9=0 A10=0 A11=0 A12=1 A13=0 A14=0 A15=0
A16=1 A17=1 A18=0 A19=0
A24=0 A25=0 A26=0 A27=0 A28=0 A29=0
Port B:
B0=1 B1=1 B2=1 B3=1 B4=0 B5=0 B6=0 B7=0
B8=0 B9=0 B10=0 B11=1
B16=0 B17=0 B18=0 B19=0
B20=0 B21=0 B22=0 B23=0
Port C:
...

Five ports (A through E), with each pin showing its current logic level. The first thing we noticed: A20-A23 and B12-B15 are absent. Not zero — absent. The MK60DN512VLQ10 in the 144-LQFP package doesn’t bond those pins out. The firmware knows which pins exist on its own package variant.

Then there’s E29:

Port E:
...E28=1 Unknown bit E29

The firmware doesn’t know what to do with this one. It’s in the register space, but there’s no pin mux entry for it. The K60 reference manual shows E29 doesn’t exist on the 144-LQFP package — but the firmware still tries to read it and prints a confused message instead of silently skipping. A minor firmware bug, preserved in the GPIO register dump handler since 2013.

The regs dump gives you state but not direction. For that, you need gpio dir <pin> on each pin individually. We walked all 92:

GPIO> dir E0
E0: OUTPUT
GPIO> dir E1
E1: OUTPUT
GPIO> dir E2
E2: OUTPUT
GPIO> dir E3
E3: INPUT
GPIO> dir E4
E4: INPUT
GPIO> dir E5
E5: OUTPUT

Tedious, but necessary. The direction register tells you which pins the MCU is driving (OUTPUT) versus which it’s listening to (INPUT). Combined with the K60 datasheet’s pin mux table and the boot log, you can identify what each pin does.

The boot log gave us the first clue:

SPI1 init @ 4 MHz
Motor init: System=12Inch, master=40000 steps, slave=24960 steps, ratio=1.602564

SPI1 at 4 MHz, mode 0x03 (CPOL=1, CPHA=1). The K60 datasheet shows SPI1 on port E:

K60 PinGPIOAlt FunctionLive DirLive StateAssignment
PTE0E0SPI1_PCS1OUT1A3981 #2 chip select (EL motor)
PTE1E1SPI1_SOUTOUT1MOSI — MCU to A3981
PTE2E2SPI1_SCKOUT1SPI clock
PTE3E3SPI1_SININ0MISO — A3981 to MCU
PTE4E4SPI1_PCS0IN1A3981 #1 chip select (AZ motor)
PTE5E5SPI1_PCS2OUT1Possibly A3981 RESET or enable

E4 shows INPUT in the GPIO direction register, but it’s muxed to SPI1_PCS0 — the SPI controller manages chip select assertion directly, so the GPIO direction is meaningless here. The live state of 1 (high) on the chip select lines means both A3981s are deselected (active-low CS), which is the expected idle state.

The A3981 is an Allegro stepper motor driver. Two of them on SPI1 — one for azimuth (PCS0, 40000 steps/rev), one for elevation (PCS1, 24960 steps/rev). They support 1/16 microstepping in AUTO mode, which matches what the firmware’s a3981 ss command reports.

We could confirm this from the a3981 submenu:

A3981> cm
AZ Current Control Mode: AUTO
EL Current Control Mode: AUTO
A3981> sm
AZ Step Mode: AUTO
EL Step Mode: AUTO
A3981> diag
AZ DIAG: OK EL DIAG: OK

Both drivers healthy, both in AUTO mode. The DIAG pins on the A3981 are active-low open-drain — the “OK” reading means the GPIO pins reading the fault output are pulled high. The exact GPIO pins for DIAG are TBD (we haven’t isolated them from the regs dump yet), but they’re likely somewhere in the cluster of unidentified input pins.

SPI2 init @ 6.857 MHz
BCM4515 ID 0x4515 Rev B0, FW v113.37

SPI2 at 6.857 MHz, same mode 0x03. On port D:

K60 PinGPIOAlt FunctionLive DirLive StateAssignment
PTD11D11SPI2_PCS0OUT1BCM4515 chip select
PTD12D12SPI2_SCKIN1SPI clock
PTD13D13SPI2_SOUTIN1MOSI — MCU to BCM4515
PTD14D14SPI2_SIN0MISO — BCM4515 to MCU
PTD15D15SPI2_PCS10Secondary chip select (unused?)

D12 and D13 show INPUT in the GPIO register despite being SPI clock and MOSI — again, the peripheral mux overrides GPIO direction. D15 is a secondary chip select that’s held low; likely unused (the BCM4515 only needs one CS).

The BCM4515 is a Broadcom DVB-S2 demodulator. It handles satellite signal reception — carrier tracking, forward error correction, NID (Network ID) detection. The firmware talks to it over SPI at nearly 7 MHz, which is fast enough for real-time signal monitoring (RSSI, AGC, SNR readings).

UART4 — the console we’re talking through

Section titled “UART4 — the console we’re talking through”
GPIO> dir E24
E24: OUTPUT
GPIO> dir E25
E25: INPUT
GPIO> dir E26
E26: INPUT

Port E pins 24-28:

K60 PinGPIOAlt FunctionLive DirLive StateNotes
PTE24E24UART4_TXOUT1Console TX (to computer RX pair)
PTE25E25UART4_RXIN1Console RX (from computer TX pair)
PTE26E26UART4_CTSIN1Hardware flow control (idle high)
PTE27E27GPIO?IN1Unknown — RTS, or just pulled up
PTE28E28GPIO?IN1Unknown

This is the RS-422 console port — the one we’re using to send all these queries. UART4_TX on E24 drives one differential pair of the RS-422 transceiver (which connects to pin 4/5 on the RJ-12, our RX pair). UART4_RX on E25 receives from the other pair (pin 2/3, our TX pair). CTS on E26 is idle high, meaning the firmware is ready to receive.

The K60 has five UART peripherals (UART0-4). UART4 is the last one, and it’s the debug console. The firmware probably uses UART0 or UART1 for the DVB tuner’s serial interface (some BCM4515 configurations use SPI + UART), but we haven’t confirmed that yet.

After mapping the three major peripherals (SPI1, SPI2, UART4), we still had a bunch of pins in known states that we couldn’t attribute to specific functions:

GPIODirStateBest guess
D10OUT1BCM4515 reset or power enable — it’s adjacent to the SPI2 cluster
B0-B31Contiguous high block — possibly SPI0 or I2C0, both available on port B
B111Isolated high pin — status LED or peripheral enable
C10-C131Four contiguous pins, all high — could be a bus interface or DIP switch reads
C181Single pin — LNB voltage control relay? The lnbdc odu command switches 13V/18V

The DIP switch reads are particularly interesting. The dipswitch submenu returns val:ffffff01 — a 32-bit register read. The 0xffffff01 pattern (bits 1-24 all high, bit 0 high) suggests GPIO pins with internal pullups and all switches in the OFF/up position. Port C has enough pins in the right state to be candidates, but without being able to flip individual switches during a GPIO read, we can’t confirm the mapping.

The functional pin map isn’t just an academic exercise. It tells us what’s possible:

Motor control is SPI-based, not bit-banged. The A3981 drivers are on a proper SPI bus with dedicated chip selects. The MCU can read back driver status (fault flags, step mode, current mode) in real time. This is why the a3981 diag command works — it’s doing an SPI read of the DIAG register, not just checking a GPIO fault pin.

The DVB tuner has a high-bandwidth connection. SPI2 at 6.857 MHz means the MCU can stream signal data from the BCM4515 fast enough for real-time RSSI and AGC monitoring. The dvb agc streaming command works because the bus can sustain the data rate.

The UART has hardware flow control available. CTS is wired (E26). If we ever need to send large data blocks to the firmware (firmware updates, configuration dumps), we have flow control to prevent buffer overruns. Currently unused by our code since command/response at 115200 never overruns.

There are unaccounted pins. Port B0-B3, C10-C13, and several others are in definite states but unmapped. These could be additional peripherals (I2C EEPROM? second UART? temperature sensor?) or just board-level power control. A board-level reverse engineering session (tracing PCB traces under a microscope) would resolve these, but we’d need to open the dish enclosure for that.

The GPIO map is a snapshot — it captures the board’s state at idle, after boot, with the tracker disabled. Different states during motor movement or DVB tuning would show different patterns (chip select toggling, SPI activity, motor driver current modes changing from LOW to HIGH torque). But as a static reference for “what’s connected to what,” it’s the most detailed view we have without physical board access.