CH592 radio register reference

I've spent some time reverse engineering the radio peripheral of the CH592 microcontroller. The other WCH BLE microcontrollers are broadly similar. My long term goal is better support in the open source ch32fun library. There is some API design work still needed for that. In the meantime, here are my work-in-progress register level docs. These are undoubtedly both incomplete and in places incorrect. However, they are enough to implement the basics of BLE.

If you have questions about the WCH radios, the best place to ask is the ch32fun discord. The reverse engineering effort has been taking place there. Thanks to @beimster, @montemonte, and others there for their help with this.

Radio

The radio hardware is organized into 4 memory mapped peripheral blocks.

Register BlockAddress
BB_BASE0x4000C100
LL_BASE0x4000C200
AES_BASE0x4000C300
RFEND_BASE0x4000D000

The BB and LLE blocks have interrupt requests associated with them.

Interrupt lines: BB_IRQn = 20, LLE_IRQn = 21.

Notation

Register names match docs for the few that WCH has named publicly. Most are my own invention, matching ch32fun names when reasonable.

AccessMeaning
RRead only
R0Always reads 0
WWrite only
R/WRead / Write
R/W1CRead / Writing a 1 bit to this place clears it to 0
R/W0CRead / Writing a 0 bit to this place clears it to 0

BB (BB_BASE = 0x4000C100) -- BaseBand controller

CTRL_CFG (BB0) -- BaseBand Controller Config

BitAccessDescription
5:0R/WBLE channel
6R/WDisable whitening (untested)
7R/WPHY rate (clear -> 1M, set -> 2M)
9:8R/WRadio or BB enable? 2 for ON and 1 for OFF
12:10R/WUnknown
22:13R0Unused
23WSet when starting TX
25:24R0Unused
26R/WUnknown
27R0Unused
30:28R/WUnknown
31R0Unused

I think this register controls the RF frequency iff bit 1 of RF11 is cleared.

CRCINIT (BB1) -- Initial CRC register value

BitsAccessDescription
23:0R/WCRC register initialization value
31:24R0Unused

ACCESSADDR (BB2) -- Access Address

32-bit access address used for both TX and RX

BitsAccessDescription
31:0R/WAccess address

BB4 -- Unknown

BitsAccessDescription
5:0R/WCarrier frequency tolerance?
7:6R0Unused
10:8R/WUnknown Gain?
13:11R/WUnknown Gain?
15:14R0Unused
18:16R/WUnknown
23:19R0Unused
31:24R/WRX gain? Directly affects BBSTATUS[31:24]

Increasing [5:0] narrows the acceptable frequency offset. Reception breaks above 0x1c, the blob sets 0x0e.

BB6 -- Unknown

BitsAccessDescription
3:0R/WRX deviation window. Blob sets to 0x2 for 1M
9:4R/WUnknown. Must be 0x13
31:10R0Unused

BB9 -- Unknown

BitsAccessDescription
15R/WEnable continuous RSSI sampling

CTRL_TX (BB11) -- TX Control

BitsAccessDescription
0R/WUnknown. Must be cleared for TX to start
1R/W
2R0Unused
3R/WUnknown. Appends ~2 us of unmodulated carrier
4R/W16 symbol preamble when set (2M PHY untested)
24:5R0Unused
30:25R/WTransmit power setting
31R/W

RSSI (BB12) -- Signal Stats

Written repeatedly while demodulating a packet. Updates continuously in receive when BB9 bit 15 is set. Between updates it holds the last value.

BitsAccessDescription
14:0RCarrier frequency offset (15 bit signed)
23:15RRSSI (signed) dBm?
31:24R?Unknown

Uncertain if bit 0 is the LSB of the CFO, but I think it is. The carrier frequency offset scale is relative to the symbol rate: 19.3 Hz per count at 1M and 38.7 Hz per count at 2M.

BBINT_EN (BB13) -- Baseband Interrupt Enable

BitsAccessDescription
15:0R/WInterrupt enable corresponding to BBSTATUS
31:16RUnused

BBSTATUS (BB14) -- Baseband Status

Bits [28:25] read 1 while the radio is idle.

BitsAccessDescription
0R/W1CTransmission complete
1R/W1CReception complete
2R/W1CPayload reception started
3R/W1CUnknown. Sets sporadically in RX
4R/W1CUnknown. Used in blob
5R/W1CCRC error
6R/W1CLength field complete. Written ~16 us later
7R/W1CCRC received
8R/W1CUnknown. Sets sporadically in RX
9R/W1CUnknown. Sets sporadically in RX
10R/W1CUnknown. Set at end of RX (trailer written?)
11?Never observed set
12?Never observed set
13?Never observed set
14?Never observed set
15?Never observed set
21:16RPacket-processing state
22RSet in transmit, clear in receive or at idle
23R8th state bit? Never observed set
31:24RAGC? Always 0x1e at idle

Packet-processing state [23:16] advances through a fixed sequence, 0x00 being idle.

Observed states:

DirectionSequence
Receive0x24 window open, 0x25 access address correlated, 0x26, 0x27, 0x28 payload start, 0x29 payload end, 0x2a
Transmit0x45 .. 0x4f setup and ramp, 0x50 payload sent, 0x54, 0x56

BB15 -- Unknown

Looks like four 5 bit fields?

BitsAccessDescription
4:0R/WUnknown. Blob sets 0x0c
7:5R0Unused (?)
12:8R/WUnknown. Blob sets 0x02
15:13R0Unused (?)
20:16R/WUnknown. Blob sets 0x02
23:21R0Unused (?)
28:24R/WUnknown. Blob sets 0x00
31:29R0Unused (?)

BB22, BB24 -- RSSI Sample Counters

Two independent 32-bit up-counters. Both increment at about 29.0 kHz, once for each RSSI sample.

CTE (BB26) -- Constant Tone Extension?

I think this is BB25 on the CH58x.

BitsAccessDescription
0(?)R/Wparam_1 (is this 1 bit or 2?)
1R/W
2R/WSet unconditionally
7:3R/Wparam_2
??:8R/Wparam_3
18R/Wparam_4
22:19R/W?

Bits 22:19 are 0x0010_0000 on CH59x and 0x0028_0000 on CH58x

Sets RB_RF_ANT_SW_EN in R16_PIN_ALTERNATE if param_1 != 0:

RF antenna switch control output enable:

1: Switch control output to PA[4]~PA[5], PA[12]~PA[15];

Write 1 for RX, 2 for TX. 8 is probably STOP, 4 might be SHUT (untested). STOP is intended to exit automatic mode from the ISR. SHUT resets the entire LLE state machine (c.f. CH579DS1).

BitAccessDescription
0R/WEnable auto mode if set
4:1R/WUnknown
7:5R0Unused
10:8R/WUnknown
15:11R0Unused
31:16R/WDelay after LL0 command

[31:16] is a 2 MHz counter, 0.5 us per count. It sets a delay between the LL0 command and the TX/RX in both manual and auto mode. Values above about 256 destabilise transmission?

Bits [25:0] are latched IRQ flags, cleared by writing 1. Bits [31:26] are read-only live state and clear themselves.

BitAccessDescription
0R/W1CRX command complete (LL0 = 1 sequence finished, including an auto-mode turnaround TX)
1R/W1CTX command complete (LL0 = 2 sequence finished, including an auto-mode turnaround RX)
2R/W1CAuto-mode RX phase complete: packet received, turnaround pending
3R/W1CAuto-mode TX phase complete: packet sent, turnaround pending
4R/W1CUnknown event. Sets with bit 29 when the second phase of an auto-mode sequence whose first phase is an RX engages. With a TX second phase, at the end of the LL7 interval
5R/W1CUnknown event. Sets when the sequencer re-engages after an RX-initiated sequence completes
6R/W1CUnknown event. Sets when the second phase of an auto-mode TX sequence engages
7R/W1CNever observed set
8R/W1CUnknown event. Sets at the midpoint of the turnaround following an auto-mode RX phase. With a TX second phase, at the end of the LL5 interval, where the LL7 interval begins. Also sets 67 us after a manual-mode RX command completes
9R/W1CUnknown event. Sets 71 us after bit 0 in an auto-mode RX sequence
10R/W1CPost-transmit guard elapsed, 240 us after a TX completes
11R/W1CNever observed set
12R/W1CUnknown event. Sets with bit 8 in auto mode
13R/W1CUnknown event. Sets with bit 9 in auto mode
14R/W1CUnknown event. Sets with bit 10 in auto mode
15R/W1CNever observed set
16R/W1CTMR0 (LL24) expired
17R/W1CTMR1 (LL25) expired
18R/W1CTMR2 (LL26) expired
19R/W1CTMR3 (LL27) expired
20R/W1CTMR4 (LL28) expired
21R/W1CTMR5 (LL29) expired
22R/W1CUnknown event. Sets with bit 0 on a manual-mode RX completion
23R/W1CUnknown event. Sets with bit 1 on a manual-mode TX completion
24R/W1CUnknown event. Set when RX is cancelled?
25R/W1CNever observed set
26RLive state. Set while the radio is engaged (CTRL_MOD bit 3 = 1). Clears with bit 8 at the turnaround midpoint
27RLive state. Set when a command is written to LL0, and with bit 8 at the turnaround midpoint, where bit 26 clears
28RLive state. Set during an auto-mode turnaround
29RLive state. Set during an auto-mode turnaround. Sets with bit 4 when the second phase engages
30RLive state. Set while the radio is engaged (CTRL_MOD bit 3 = 1)
31RLive state. Set when a command is written to LL0
BitsAccessDescription
25:0R/WInterrupt enable corresponding to LLSTATUS
31:26R0Unused

Auto Mode Delay Pairs (LL5/LL7 - LL17/LL19)

Four pairs of 32-bit timing registers. In auto mode, the spacing between the two phases is determined by one of these four pairs. Which pair is used seems to match the choice of IRQs in 7:4 and 11:8. The values work out to either 129 us or 125 us. I suspect this is so the RX starts a little early?

RegisterBlob Valuetime
LL50x8c70 us
LL70x7659 us
LL90x8c70 us
LL110x6e55 us
LL130x8c70 us
LL150x6e55 us
LL170x8c70 us
LL190x7659 us

The even indexed LL registers in-between (LL6 - LL18) appear unused?

CTRL_MOD (LL20) -- LLE Module Control?

ModeBlob value
TX0x30258
RX0x30158
TX (AES)0x30278
RX (AES)0x30178
STOP0x30078
BitsAccessDescription
2:0R0Unused
3?Unknown, maybe a READY bit? HW changes this
4R/WUnknown, always set to 1
5R/WRoutes packet data through AES module when set
6R/WUnknown, always set to 1
7WWritten before AES config. AES block reset?
10:8R/WRadio mode? 0 stop, 1 RX, 2 TX, 5 tune
15:11R0Unused
19:16R/WUnknown, always set to 3
31:20R0Unused

LL21 -- Unknown

BitsAccessDescription
15:0R/WUnknown, set to 0x14 in lle.o:LLE_DevInit
25:16R0Unused
30:26R/WUnknown
31R/WUnknown, set in lle.o:LLE_DevInit

LLTMRn (LL24-LL29) -- LLE Timers

Six independent 32-bit count down timers. Value decrements every 0.5 us (2 MHz) until it reaches 0. The decrement of the count from 1 to 0 triggers the corresponding LL IRQ (16 - 21).

BitsAccessDescription
31:0R/WTimer count down value

TXBUF (LL30) -- Transmit data buffer

Pointer to the transmit data, formatted as a PDU including header PDU (plaintext) length is determined from TXBUF[1]. When hardware packet encryption is enabled the on-air packet includes four additional MIC bytes.

Address must be in RAM and word aligned. The value in the register is masked by 0x00007ffc.

BitsAccessDescription
1:0R0Fixed zero (word aligned)
14:2R/WBuffer RAM address
31:15R0Implied 0x2000_0000, reads zero

RXBUF (LL31) -- Receive data buffer

Pointer to the receive buffer. Will be filled with received PDU including header. PDU (plaintext) length is in RXBUF[1]. When hardware packet encryption is enabled the length in the buffer does not include the four additional MIC bytes.

Address must be in RAM and word aligned. The value in the register is masked by 0x00007ffc.

BitsAccessDescription
1:0R0Fixed zero (word aligned)
14:2R/WBuffer RAM address
31:15R0Implied 0x2000_0000, reads zero

Buffer contents

The hardware appends four status bytes to the end of the RX buffer. Here n is the length on-air, which is BUF[1]+4 when AES is enabled.

ByteDescription
0PDU, as received on air
1Length, plaintext length when AES is enabled
2..n+1Payload data
n+2..n+3center frequency offset (see RSSI reg BB12)
n+4Packet RSSI (see RSSI reg BB12)
n+5Status bits: 4->CRCFAIL, 7->MICFAIL

When AES CCM is enabled, RXBUF[LENGTH + 9] bit 7 set indicates MIC failure.

AES (AES_BASE = 0x4000C300) -- AES Accelerator

128-bit AES engine with two modes selected by CTRL bit 7: ECB block mode over the register file, and CCM packet mode inline in the radio datapath. Register addresses are the same in both modes; the roles of AES2-AES5, DATA and KEY differ.

Clocked by the BLE clock (R8_SLP_CLK_OFF1.RB_SLP_CLK_BLE). Serviced by BB_IRQn; there is no dedicated AES interrupt line.

CTRL (AES0) -- AES Control

BitsAccessDescription
0R/WECB mode: start. Self-clearing
1R/WECB mode: decrypt if set, encrypt if clear
2R/WUnknown
4:3R/WKey length: 0 (or 3) -> 128 bit, 1 -> 192, 2 -> 256
5R/WCCM mode: nonce direction bit
6R/WCCM mode: arm. Clears when a packet is processed
7R/WMode select (clear -> ECB, set -> CCM)
31:8R0Unused

STAT (AES1) -- AES Status

BitsAccessDescription
0R/WCompletion BB_IRQn interrupt enable
1R/W0COperation complete, cleared by writing 0
31:2R0Unused

When bit 0 is set, AES completion triggers a BB interrupt.

AES2 -- CCM Nonce IV Low

BitsAccessDescription
31:0R/WCCM: low bytes of the 8-byte nonce IV (IVm)

AES3 -- CCM Nonce IV High

BitsAccessDescription
31:0R/WCCM: high bytes of the 8-byte nonce IV (IVs)

AES4 -- CCM Packet Counter Low

BitsAccessDescription
31:0R/WCCM mode: packet counter bits [31:0]

AES5 -- CCM Packet Counter High

BitsAccessDescription
6:0R/WCCM mode: packet counter bits [38:32]
31:7R0Unused

DATA (AES6-AES9) -- Data Block / CCM SKD

Four 32-bit words, DATA[0] at offset 0x18. Words are packed little-endian: the byte at DATA[0] bits [7:0] is FIPS-197 block byte 0.

ECB mode: the input block. The result overwrites it in place, and is valid once STAT bit 1 is set.

CCM mode: the 16-byte session key diversifier (SKD). Not modified by the engine.

BitsAccessDescription
31:0R/WBlock word

KEY (AES10-AES17) -- Key / CCM LTK

Four, six, or eight 32-bit words, KEY[0] at offset 0x28. Pack like DATA: the byte at KEY[0] bits [7:0] is FIPS-197 key byte 0. Retained across operations.

ECB mode: the 128-bit / 192-bit / 256-bit key. Length determined by CTRL.

CCM mode: the long term key (LTK). The engine derives the session key internally as AES-128-ECB(KEY, DATA). The derived key is not written back to any register.

BitsAccessDescription
31:0R/WKey word

AES18 - AES21 -- Scratch

BitsAccessDescription
31:0R/WUnknown

Updated by AES operations but has no visible impact.

ECB operation

Write KEY, write CTRL with bit 1 selecting the direction, write DATA, then set CTRL bit 0. Then wait for STAT bit 1 to set. DATA read back before STAT bit 1 sets is stale. One block takes approximately 39 core cycles.

CCM operation

Write CTRL with bit 7 set and bit 5 selecting the direction, write AES4, AES5, DATA, KEY, AES2 and AES3, then set CTRL bit 6. The engine then waits for one packet in one direction. LL->CTRL_MOD bit 5 must be set for that packet to be routed through the engine. Completion is indicated by CTRL bit 6 clearing and STAT bit 1 setting. Raises no interrupt of its own, unclear if a BB interrupt is tied to this.

LENGTH is the plaintext length in both directions. The additional authenticated data is the PDU header octet with NESN, SN and MD masked to zero. Data is encrypted/decrypted on the fly. TXBUF is not modified. On reception the plaintext is written to RXBUF whether or not authentication succeeded. A status byte is appended at RXBUF[RXBUF[1] + 9] with bit 7 set on MIC failure.

RFEND (RFEND_BASE = 0x4000D000) -- RF FrontEND

RF11

BitsAccessDescription
1R/WFreq from RF17 if set, BB->CTRL_MOD if clear

RF17 -- Manual Tune

F_hz = COARSE * 64 MHz + FINE * 244.140625 Hz and 244.140625 Hz = 64 MHz / 2^18

I suspect the demodulator expects offset tuning by data rate, e.g. tune to 2401 MHz to receive 2402 MHz in 1M PHY mode.

BitsAccessDescription
17:0R/WCoarse tune (freq / 64 MHz)
19:18R0Unused
24:20R/WFine tune (244.140625 Hz steps)
31:25R0Unused
if (tune-by-channel) {
    RFEND->RF11 &= ~(1 << 1);
    BB->CTRL_CFG = (BB->CTRL_CFG & ~0x7f) | chan & 0x7f;
} else {
    // Probably needed for whitening sequence
    BB->CTRL_CFG = (BB->CTRL_CFG & ~0x7f) | chan & 0x7f;
    if (tune-by-channel) {
        if (2M PHY) {
            freq = freq - 2000;
        }
        else {
            freq = freq - 1000;
        }
    }
    RFEND->RF11 |= (1 << 1);
    RFEND->RF17 = (RFEND->RF17 & 0xfe0fffff) | (freq / 64000 & 0x1f) << 20;
    RFEND->RF17 = (RFEND->RF17 & 0xfffc0000) | (freq % 64000 << 10) / 250 & 0x3ffff;
}

RF20 -- Receive Settings

POR value 0x00017010. Writable mask 0x1031f71f.

BitsAccessDescription
4:0R/WReceive filter corner trim
7:5R0Unused
10:8R/WUnknown
11R0Unused
16:12R/WUnknown. Default is 7
16:12R/WIgnores 4:0 filter trim when set
19:17R0Unused
21:20R/WNegate I / Q
27:22R0Unused
28R/WUnknown
31:29R0Unused

Increasing 4:0 lowers the upper edge of the channel filter. Setting 20 / 21 negates I / Q, mirroring the signal. Can't determine which is I vs Q, doesn't really matter.