Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

wlink Documentation

This site is the documentation for wlink, the command-line flash and debug tool for WCH-Link probes and WCH RISC-V MCUs.

Its main part is a protocol reference for the WCH-LinkRV USB protocol — the private protocol spoken between the host and the WCH-Link probe. The old protocol.md at the repository root was written in 2023 and had stopped tracking the implementation; this reference is rebuilt from the source code so that the byte-level description and src/commands/ cannot silently diverge.

This is a reverse-engineered description, not an official WCH specification. WCH does not publish a protocol specification for the WCH-Link. Everything here comes from reading wlink’s implementation, from USB captures, and from other open-source implementations listed in External References. Entries marked unverified are observations only.

Where to start

If you want to…Read
Understand the wire formatUSB Transport, then Frame Format
Implement a commandCommand Reference and Control Subcommands
Understand how a flash operation is sequencedOperation Flows
Work with the RISC-V debug moduleDMI and the Debug Module
Look up an ID, flag or constantConstants and Chip Families
Find unimplemented or uncertain commandsUnverified Commands and Captures
Wire up a specific MCUThe Chip Notes section in the sidebar

Source of truth

Protocol knowledge is derived from these files in the repository. When the documentation and the code disagree, the code wins — please open a PR that corrects the documentation.

FileWhat it defines
src/commands/mod.rsFrame construction and parsing, command IDs, payload layouts
src/commands/control.rsThe complete 0x0d control subcommand set, firmware version encoding
src/probe.rsUSB IDs, endpoints, raw send/receive, mode switching
src/operations.rsCommand sequences for attach, erase, program, read, reset
src/chips.rsChip family IDs and the ChipID-to-part-name database
src/flash_op.rsPer-family flash algorithm blobs sent over the data endpoint
src/dmi.rs, src/regs.rsDMI operations and RISC-V debug register addresses

Building this site locally

The site is built with mdBook:

cargo install mdbook --locked
mdbook build          # output in book/
mdbook serve          # live preview at http://localhost:3000

The rendered site is published from the gh-pages branch; see .github/workflows/docs.yml. The intended public URL is https://ch32-rs.github.io/wlink/.

Overview and Status

The WCH-Link probe exposes a vendor-specific USB bulk protocol. There is no public specification from WCH; this reference documents the protocol as implemented by wlink, which was reconstructed from USB captures, WCH’s toolchain, and other open-source implementations.

Status of this document

This reference replaces the repository-root protocol.md. That file was last updated in November 2023 and had fallen behind the implementation — it had an empty 0x01 section, two empty DMI tables, a dozen ??? markers, listed 0x0c as unknown (it is SetSpeed), omitted 0x11 entirely, listed only 7 chip families (there are now 28), and stopped at firmware v2.8.

The tables in this reference are derived from the current source on main. Every command page names the Rust symbol that implements it. Anything that could not be confirmed from the implementation is collected in Unverified Commands and Captures and marked unverified rather than guessed.

If you find a mismatch between this document and the code, the code is correct and the document needs a PR.

Notation

ConventionMeaning
0x81 0d 01 01Hex bytes in transmission order
u32_be32-bit unsigned integer, most significant byte first
LENPayload byte count, one byte
()Command succeeds with no payload of interest
unverifiedSeen in a USB capture or an older note, not implemented/confirmed in wlink
riscvchipThe one-byte chip family code, see Constants

All multi-byte integers in command payloads are big-endian. Memory read data from the data endpoint is an exception: wlink reverses every 4-byte word after reading it (see Operation Flows).

Layer model

Host (wlink CLI)
  │
  ├─ Command endpoint (0x01 OUT / 0x81 IN)
  │    81 CMD LEN PAYLOAD   →   82 CMD LEN PAYLOAD
  │    all control, configuration and DMI operations
  │
  ├─ Data endpoint (0x02 OUT / 0x82 IN)
  │    unframed bulk stream: flash algorithm blobs, firmware bytes, memory dumps
  │
  └─ DMI operations (command 0x08)
        └─ RISC-V Debug Module registers (0x04 … 0x40)

Known limitations of the protocol itself

  • LEN is a single byte. wlink reads responses into a 64-byte buffer, so a response payload is limited to 61 bytes in practice; large transfers always go through the data endpoint.
  • The response parser does not check that the response CMD byte matches the request; it only distinguishes the 0x82 success marker from the 0x81 error marker.
  • Error frames carry an opaque one-byte REASON. Only 0x55 (failed to connect to the target) has a known meaning.
  • Some subcommands were observed only in captures from WCH’s Windows utility; they are listed separately as unverified.

Further reading

  • External References — WCH manuals, schematics, firmware archives and other FOSS implementations.
  • CHANGELOG.md in the repository records protocol-level fixes over time, for example the correction that 0x0c is a speed command rather than a verify command.

USB Transport

WCH-Link uses USB bulk transfers. There is no kernel driver in the loop on Linux/macOS: wlink opens the device with nusb, claims interface 0, and reads and writes bulk endpoints directly. On 32-bit Windows an optional native WCHLinkDLL.dll (CH375) backend is available, falling back to nusb.

Source: src/probe.rs (constants), src/usb_device.rs (backend implementations).

USB identifiers

ModeVIDPIDNotes
RV (RISC-V debug/flash)0x1a860x8010The mode wlink operates in
DAP (ARM SWD)0x1a860x8012wlink refuses to flash in this mode

The same physical probe appears under one PID or the other depending on its mode. wlink mode-switch flips between them.

Endpoints

EndpointDirectionPurpose
0x01OUTCommand frames (81 CMD LEN PAYLOAD)
0x81INCommand responses (82 … / 81 …)
0x02OUTData endpoint: flash algorithm blob, firmware chunks
0x82INData endpoint: memory dump, per-chunk write acknowledgement
0x02OUT (DAP mode only)Raw mode-switch frame

Command reads use a 64-byte buffer; a single bulk read is treated as one response frame. Data-endpoint transfers are streams, not frames: wlink loops over 64-byte bulk reads until the requested byte count is reached, and pads the final written chunk with 0xff to the family’s data packet size.

The nusb backend stores a 5-second default in NusbDevice::timeout; the current bulk read/write calls do not apply it and rely on nusb’s own blocking behaviour.

Selecting a probe

With several probes attached, enumeration order is not stable. wlink accepts either an index or a USB serial number:

wlink list
# <WCH-Link#0 nusb device> ID 1a86:8010 Serial F93D8F067E10 (USB-FS 12 Mbps) (RV mode)
# <WCH-Link#1 nusb device> ID 1a86:8010 Serial D7708F065C7F (USB-FS 12 Mbps) (RV mode)

wlink -d 1a86:8010:D7708F065C7F status   # probe-rs style VID:PID:SERIAL
wlink -d D7708F065C7F status             # serial alone
wlink -d 1 status                        # legacy index

Serial matching is case-insensitive and works in either RV or DAP mode; the VID/PID in the selector are validated but not used for matching.

Mode switching

Switching from RV to DAP mode sends a command frame on the normal command endpoint:

DirectionRaw bytesMeaning
RV → DAP81 ff 01 41RawCommand<0xff> with payload 0x41
DAP → RV81 ff 01 52written raw to endpoint 0x02 while in DAP mode

Both operations are experimental; the CH549-based first-generation probe can only switch modes by reflashing its firmware. Support is reported by WchLinkVariant::support_switch_mode in src/probe.rs.

Variants

CodeVariantHardwareCapabilities
0x01WchLinkVariant::Ch549WCH-Link (CH549)No CH32V00X, no mode switch, no power/SDI functions
0x02 / 0x12WchLinkVariant::ECh32v305WCH-LinkE (CH32V305)Full support, including SDI print
0x03WchLinkVariant::SCh32v203WCH-LinkS (CH32V203)Full chip support
0x05 / 0x85WchLinkVariant::WCh32v208WCH-LinkW (CH32V208)Wireless variant; no CH56X/CH57X/CH582/CH59X
other——Error::UnknownLinkVariant

See Probe Info and Firmware Version for how the variant byte is returned and how firmware versions are decoded.

Frame Format

Every command exchange uses the command endpoints (0x01 OUT / 0x81 IN). The data endpoints are a separate, unframed stream.

Source: Command::to_raw and Response::from_raw in src/commands/mod.rs, and WchLink::send_command in src/probe.rs.

Request

Byte 0Byte 1Byte 2Bytes 3 … n
0x81CMDLENPAYLOAD
#![allow(unused)]
fn main() {
let mut bytes = vec![0x81, Self::COMMAND_ID, 0x00];
bytes.extend(self.payload());
bytes[2] = bytes.len() as u8 - 3;   // LEN = PAYLOAD.len()
}

LEN is a single byte holding the payload length, so the format allows up to 255 payload bytes. In practice wlink reads responses into a 64-byte buffer, which caps a response payload at 61 bytes; all implemented commands use far smaller payloads.

Example — GetProbeInfo (0x0d, subcommand 0x01):

81 0d 01 01
^^ ^^ ^^ ^^
|  |  |  └─ PAYLOAD[0] = 0x01 (GetProbeInfo)
|  |  └──── LEN = 1
|  └─────── CMD = 0x0d (Control)
└────────── request marker

Success response

Byte 0Byte 1Byte 2Bytes 3 … n
0x82CMDLENPAYLOAD

The parser checks resp[0] == 0x82, verifies LEN == resp[3..].len(), then hands resp[3..] to the response type. It does not verify that CMD matches the request.

Example — GetProbeInfo responding with firmware 2.15 on a WCH-LinkE:

82 0d 03 02 0f 02
         ^^^^^^^^^^ PAYLOAD = [0x02, 0x0f, 0x02]

Error response

Byte 0Byte 1Byte 2Bytes 3 … n
0x81REASONLENPAYLOAD

An 0x81 first byte means failure regardless of CMD. wlink validates the length and surfaces Error::Protocol(reason, response_bytes).

REASONMeaning
0x55Failed to connect to the target RISC-V chip
otherOpaque; the full frame is preserved in the error

Response decoding by type

Rust typeRule
()Payload ignored, success is enough
u8Payload must be exactly 1 byte
boolPayload must be 1 byte; 0x01 means success (SetSpeed)
Vec<u8>Raw payload returned
AttachChipResponseExactly 5 bytes: family u8 + ChipID u32_be
DmiOpResponseExactly 6 bytes: addr u8 + data u32_be + op u8
ProbeInfoAt least 3 bytes: major, minor, optional variant
ESignatureNon-standard 20-byte raw response, see Chip Info
invalid markerError::InvalidPayload
length mismatchError::InvalidPayloadLength

Data endpoint stream

The data endpoints carry no 81/82 header. Two shapes occur:

  • Bulk output: the flash algorithm blob for the chip family, then firmware data split into write_pack_size chunks, each padded with 0xff to data_packet_size (see WchLink::write_data_with_progress).
  • Bulk input: read_data(n) accumulates 64-byte bulk reads until n bytes are collected. During a fast-program cycle, each written chunk is followed by a 4-byte acknowledgement on 0x82; wlink checks that the last byte is 0x04 (observed 41 01 01 04).

ProbeSession::read_memory reverses each 4-byte word of the returned memory buffer to compensate for the probe’s word order.

Command Reference

CMD is byte 1 of a request frame (see Frame Format). All payload integers are big-endian. Source links point at src/commands/mod.rs unless noted otherwise; 0x0d subcommands live in src/commands/control.rs and are expanded in Control Subcommands.

Command ID summary

CMDNamePayloadResponseStatus
0x01SetWriteMemoryRegion8 bytes: address, length()implemented
0x02Program1 byte: subcommandu8implemented
0x03SetReadMemoryRegion8 bytes: address, length()implemented
0x06ConfigChip1–8 bytes: protection/configu8partially implemented
0x08DmiOp6 bytes6 bytesimplemented
0x0bReset1 byte()implemented
0x0cSetSpeed2 bytes: riscvchip, speedboolimplemented
0x0dControlsubcommand + argsvariesimplemented — see Control Subcommands
0x0eDisableDebug1 byte 0x01()implemented
0x0fGet device mode1 byteunknownunverified
0x11GetChipInfo (ESIG)1 byte: 0x09 / 0x0620 raw bytesimplemented
0xfeDisable encryption1 byteunknownunverified
0xffMode switch1 byte 0x41—implemented (RV → DAP)

The old protocol.md listed 0x0c as ??? and 0x0f as “begin verify”. Both are wrong: 0x0c is SetSpeed (corrected in CHANGELOG for v0.0.6), and 0x0f only appears in a GetDeviceMode capture.


0x01 — SetWriteMemoryRegion

Sets the target address and byte count for the following write operation.

OffsetTypeField
0u32_bestart_addr (typically 0x08000000 code flash or 0x00000000 SRAM)
4u32_belen (firmware length in bytes)

Request: 81 01 08 <addr:4> <len:4>. Response: ().

Source: SetWriteMemoryRegion. Used at the start of ProbeSession::write_flash.

0x02 — Program

One-byte subcommand selector. Response is a single status byte; wlink checks the returned value for the 0x07 step during flashing.

SubRust variantMeaning
0x01EraseFlashErase code flash
0x02WriteFlashBegin fast-program (bulk data follows on the data endpoint)
0x04WriteFlashAndVerifyWrite flash with verification
0x05WriteFlashOPBegin writing the per-family flash algorithm blob
0x06PreparePrepare before setting the RAM address (not used by current flows)
0x07Unknown07AfterFlashOPWrittenCommit step after the flash blob was written; response must be 0x07
0x08EndEnd program
0x0bUnknown0BAfterFlashOPWrittenAlternative commit step for riscvchip == 1
0x0cReadMemoryStart a memory read on the data endpoint

Older notes listed 0x03 “execute ram”, 0x05 “begin bulk transfer”, 0x07 “verify”, 0x09 “bulk transfer ends”, 0x0a “verify” and 0x0b “= verify for riscvchip 1”. Those labels are unverified; see Unverified Commands.

0x03 — SetReadMemoryRegion

Identical layout to 0x01: start_addr and len, both u32_be. Response (). Followed by 0x02 / 0x0c (ReadMemory) and a bulk read on endpoint 0x82.

Source: SetReadMemoryRegion, ProbeSession::read_memory.

0x06 — ConfigChip

Flash read/write protection and option-byte configuration. Response is a status u8 for the query variants.

PAYLOAD[0]Rust variantPayloadMeaning / response
0x01CheckReadProtect1 byte0x01 protected, 0x02 unprotected
0x02Unprotect1 byteRemove read protection (mass-erases option bytes)
0x03Protect1 byteSet read protection
0x04CheckReadProtectEx1 byte0x11 write-protected, 0x00 unprotected
0x02 + argUnprotectEx(b)8 bytes: 02 b ff ff ff ff ff ffExtended unprotect; b is 0xff or 0xbf
0x03 + argProtectEx(b)8 bytes: 03 b ff ff ff ff ff ffExtended protect
—Config { data, wrp }8 bytesUnimplemented in wlink (todo!()); capture: 81 06 08 02 3f 00 00 ff ff ff ff

Flags (ConfigChip associated constants): FLAG_READ_PROTECTED = 0x01, FLAG_READ_UNPROTECTED = 0x02, FLAG_WRITE_PROTECTED = 0x11, FLAG_WRITE_UNPROTECTED = 0x00.

Changing protection requires a fresh 0x0d / 0x02 attach afterwards. wlink skips Unprotect when flash is already unprotected, because the probe firmware mass-erases the option-byte page (USER/Data/WRPR).

0x08 — DmiOp

Six-byte payload, six-byte response. See DMI and the Debug Module for register addresses and status handling.

OffsetSizeRequest fieldResponse field
01addr (DMI register)addr
1–44data (u32_be)data (u32_be)
51op: 0 NOP, 1 READ, 2 WRITEop: 0x00 success, 0x02 failed, 0x03 busy

Example — read DMSTATUS (0x11): 81 08 06 11 00 00 00 00 01.

0x0b — Reset

PAYLOAD[0]Rust variantMeaning
0x01Reset::SoftQuit reset / reset and run (most common); wlink waits ~300 ms afterwards
0x02Reset::ChipChip reset (memory is not reset)
0x03Reset::NormalNormal reset

Response: (). Source: Reset, ProbeSession::soft_reset.

0x0c — SetSpeed

OffsetFieldValues
0riscvchipChip family code, see Constants
1speed0x01 High (6000 kHz, default), 0x02 Medium (4000 kHz), 0x03 Low (400 kHz)

Response payload 0x01 means success (Response for bool). wlink sends this before attaching and again after auto-detection. Source: SetSpeed, Speed.

0x0d — Control

The multi-purpose control command. Its first payload byte selects a subcommand; the remainder depends on it. See Control Subcommands for the complete table.

Quick reference: 0x01 probe info, 0x02 attach chip, 0x04/0x05 ROM/RAM split, 0x08 erase by RST pin, 0x09–0x0c power output, 0x0e RST pin, 0x0f erase by power-off, 0xee SDI print, 0xff detach.

0x0e — DisableDebug

Payload 0x01, response (). Only used for specific riscvchip families (older notes mention families 2 and 3). Frame: 81 0e 01 01.

Source: DisableDebug.

0x11 — GetChipInfo / ESignature

PAYLOAD[0]Rust variantNotes
0x09GetChipInfo::V1Used with firmware older than v2.9
0x06GetChipInfo::V2Introduced in firmware v2.9

This command does not use the standard response frame — see Chip Info and ESIG for the raw 20-byte layout. Source: GetChipInfo, ESignature.

0xff — Mode switch

RawCommand<0xff> with payload 0x41 (81 ff 01 41) switches an RV-mode probe into DAP mode. The reverse direction writes 81 ff 01 52 raw to endpoint 0x02 while the probe is in DAP mode. See USB Transport.

Control Subcommands (0x0d)

0x0d is a multiplexed command: PAYLOAD[0] selects an operation and the remaining bytes are its arguments. Its Rust implementation is src/commands/control.rs.

Frame shape: 81 0d LEN SUB [ARGS…].

Complete subcommand table

SUBRust symbolRequest payloadResponseNotes
0x01GetProbeInfo01≥3 bytesFirmware version and variant; see Probe Info
0x02AttachChip025 bytesAuto-detect chip family + ChipID; see Chip Info
0x03———unverified, seen in older notes as a post-attach stage for riscvchip 1
0x04GetChipRomRamSplit04u8SRAM/CODE split mode 0..3; CH32V2/V3, CH56X
0x05SetChipRomRamSplit(mode)05 mode()Set split mode 0..3
0x06—06—unverified, capture labels it CheckQE
0x07—07—unverified, capture labels it EnableQE
0x08EraseCodeFlash::ByPinRST08 riscvchip()Erase via the nRST line; requires an RST pin connection
0x09SetPower::Enable3v309()Enable 3.3 V output
0x0aSetPower::Disable3v30a()Disable 3.3 V output
0x0bSetPower::Enable5v0b()Enable 5 V output
0x0cSetPower::Disable5v0c()Disable 5 V output
0x0eSetRSTPin0e 13 / 0e 14 / 0e 15()Drive RST low / high / floating
0x0fEraseCodeFlash::ByPowerOff0f riscvchip()Power-cycle erase; see the length caveat below
0x11—11 …—unverified, capture labels it SetAccessAddress
0xeeSetSdiPrintEnabledee 00 (on) / ee 01 (off)u80x00 success, 0xff unsupported
0xffOptEndff()Detach / end the session

0x09–0x0c require a probe with power functions (WchLinkVariant::ECh32v305 or WCh32v208). 0xee requires WCH-LinkE with firmware ≥ 2.10.

Length caveat for 0x0f

The captured frame from WCH’s utility is:

81 0d 01 0f 09
   ^^ ^^ ^^ ^^
   │  │  │  └─ chip family (0x09 = CH32V003)
   │  │  └──── SUB = 0x0f
   │  └─────── LEN = 1   ← inconsistent: two payload bytes follow
   └────────── CMD = 0x0d

LEN is wrong in the capture. wlink assembles the frame from its own to_raw, producing 81 0d 02 0f 09 with the correct length. Treat the captured length byte as a firmware quirk, not as the protocol rule.

Special-erase operations

0x08 and 0x0f bypass chip attach entirely: the probe performs the erase without a debug connection, which is useful when read protection or a broken auto-detect prevents attaching. Because no detection happens, the caller must supply the chip family:

wlink erase --method power-off --chip CH32H41X
wlink erase --method pin-rst   --chip CH32V003

The riscvchip argument is the one-byte family code from Constants.

SDI print

0xee enables the probe’s virtual serial port used by WCH’s SDI print feature. Captures also show 81 0d 02 ee 00 / 02 / 03 labelled SetSDLineMode; this three-value form is unverified and not implemented by wlink. wlink only uses ee 00 (enable) and ee 01 (disable).

Session lifecycle

A typical session opens with 0x01 (probe info), then 0x02 (attach), runs operations, and ends with 0xff (OptEnd). wlink sends OptEnd on detach unless --no-detach is given; commands that leave the MCU halted deliberately skip detach so the halt is preserved.

Probe Info and Firmware Version

Source: GetProbeInfo, ProbeInfo and the version helpers in src/commands/control.rs; WchLinkVariant in src/probe.rs.

0x0d / 0x01 — GetProbeInfo

Request: 81 0d 01 01.

Response payload (ProbeInfo::from_payload requires at least 3 bytes):

OffsetFieldNotes
0major_versionRaw major byte as reported by the probe
1minor_versionRaw minor byte (may exceed 15 — see below)
2variantOnly interpreted when the payload is 4 bytes long; otherwise wlink assumes Ch549
3—Present in the 4-byte form; meaning unverified

Example: 82 0d 04 02 0f 02 00 → major 0x02, minor 0x0f, variant 0x02 (WCH-LinkE-CH32V305), with a fourth payload byte of unknown meaning. A 3-byte payload carries no variant and makes wlink assume Ch549.

Firmware version encoding

The probe does not report a decimal version. Its two bytes are a packed value, and WCH’s tooling displays versions as major * 10 + minor. This is why README says “firmware version 2.15 is actually v35”.

For raw bytes (major, minor):

packed        = 16 * major + minor
version_code  = if packed < 0x30 { 10 * major + minor } else { packed - 12 }
display       = (packed >> 4, packed & 0x0f)

version_code is monotonic and is what wlink uses for all comparisons. display_version reconstructs the human-readable version by treating the packed value as two nibbles, so an “impossible” minor carries into the major.

Raw bytespackedversion_codeDisplayed asNote
02 080x28282.8Older firmware
02 0f0x2f352.15 (v35)Current baseline in README
02 160x36423.6wlink source spells newer builds as 2.22
02 0a0x2a302.10 (v30)First firmware with SDI print
ConstantValuePurpose
MIN_FW_VERSION_CHIP_INFO_V229 (v2.9)Use GetChipInfo::V2 (0x11 / 0x06) instead of V1
MIN_FW_VERSION_CH32V20542 (v3.6, sometimes written 2.22)First firmware that reports the new-generation CH32V205 family (0xce) correctly

Older firmware misdetects CH32V205 as CH32V20X with garbage ChipID/ESIG; wlink emits a warning but continues.

Variant codes and capabilities

WchLinkVariant::try_from_u8 accepts:

ByteVariant
0x01Ch549
0x02, 0x12ECh32v305
0x03SCh32v203
0x05, 0x85WCh32v208
otherError::UnknownLinkVariant
VariantMode switchPower outputSDI printChip restrictions
Ch549nononoNo CH32V003 / CH32X035 / CH643
ECh32v305yesyesyesnone
SCh32v203yesnononone
WCh32v208yesyesnoNo CH56X / CH57X / CH582 / CH59X

Hardware variants (historical table)

The old protocol.md listed these; they are retained here for orientation but have not been re-verified against current hardware.

Probe MCUVariantNote
CH549WCH-Link-R1-1v1Switch mode by reflashing firmware
CH32V305FWCH-LinkE-R0-1v3Switch mode by button or EEPROM setting
CH32V203WCH-LinkS-CH32V203—
unverifiedWCH-LinkBHardware MCU unknown

See USB Transport for VID/PID and mode-switch frames.

Chip Info and ESIG

Two commands identify the attached MCU: 0x0d / 0x02 (attach) returns the chip family and ChipID, while 0x11 returns the “ESIG” block with flash size, UID and a second ChipID field.

Sources: AttachChipResponse in src/commands/control.rs, GetChipInfo / ESignature in src/commands/mod.rs, and src/chips.rs for the ChipID database.

0x0d / 0x02 — AttachChip

Request: 81 0d 01 02.

Response payload must be exactly 5 bytes:

OffsetSizeField
01riscvchip — chip family code, decoded via RiscvChip::try_from_u8
1–44chip_id (u32_be), 0 when not reported

Example: 82 0d 05 05 20 36 05 10 → family 0x05 (CH32V20X), ChipID 0x20360510.

ProbeSession::attach retries this command up to 3 times with a 100 ms delay because a freshly powered target can fail the first attach. When --chip is given, the returned family must match or wlink raises ChipMismatch.

The ChipID-to-part-name table lives in src/chips.rs (chip_id_to_chip_name); the per-family tables in the Chip Notes section of this site list the known part numbers.

0x11 — GetChipInfo / ESignature

PAYLOAD[0]VariantWhen used
0x09GetChipInfo::V1Firmware older than v2.9 (version_code < 29)
0x06GetChipInfo::V2Firmware v2.9 and newer

This command does not follow the standard 82 CMD LEN PAYLOAD response format. ESignature overrides Response::from_raw and reads a raw 20-byte buffer directly, with no 0x82/0x81 marker validation. This is tracked in wlink#58.

Observed raw response (from a source comment in src/commands/mod.rs):

ffff 0020 aeb4abcd 16c6bc45 e339e339 20360510
^^^^ ^^^^ ^^^^^^^^ ^^^^^^^^ ^^^^^^^^ ^^^^^^^^
 │    │    │        │        │        └─ ChipID (0x20360510)
 │    │    │        │        └────────── erased-flash pattern 0xe339e339
 │    │    │        └─────────────────── UID words 1 and 2 (see byte order below)
 │    │    └─────────────────────────── flash size in KB (0x0020 = 32 KB)
 │    └──────────────────────────────── prefix / header, meaning unverified
 └───────────────────────────────────── prefix / header, meaning unverified

ESignature::from_raw only parses the first 12 bytes:

OffsetSizeField
2–32flash_size_kb (u16_be)
4–74UID word 1 (u32_be)
8–114UID word 2 (u32_be)

The trailing 8 bytes (erased-flash marker and ChipID) are observed but not parsed by wlink; their semantics are unverified.

UID byte order

wlink stores the two u32_be words and reinterprets them as native-endian bytes for display, producing the same order as wchisp:

words:  aeb4abcd  16c6bc45
bytes:  cd ab b4 ae 45 bc c6 16
UID:    cd-ab-b4-ae-45-bc-c6-16

Erased flash-size field

0xE339 (per halfword) is what erased flash reads back on these MCUs, not 0xFFFF. When the flash-size field equals ESignature::ERASED_FLASH_SIZE (0xE339), wlink substitutes the family’s fixed flash size (RiscvChip::fixed_flash_size_kb). A programmed value always wins.

Example from a test in src/commands/mod.rs (CH32V407VET): ffff 0240 50ad061a ab33546c e339e339 46700000 → 576 KB, ChipID 0x46700000.

Chip family codes

The one-byte riscvchip code is required by SetSpeed (0x0c), by the special erase subcommands (0x0d / 0x08, 0x0d / 0x0f) and by several per-family behaviours. The complete 28-entry table is in Constants and Chip Families.

Operation Flows

This page describes the order in which wlink issues commands. Implementations live in src/operations.rs and src/probe.rs. Command byte layouts are in Command Reference.

Attach (start of a session)

ProbeSession::attach:

  1. Check that the probe variant supports the requested chip. Without --chip, CH32V103 (0x01) is used as the provisional family.
  2. Loop (up to 3 retries, 100 ms apart):
    • 0x0c SetSpeed with the provisional riscvchip and the selected speed;
    • 0x0d / 0x02 AttachChip — break on success.
  3. If --chip was given, verify the detected family matches; otherwise issue 0x0c SetSpeed again using the detected family.
  4. Run the family-specific post-init hook (RiscvChip::do_post_init).
81 0c 02 01 01      SetSpeed(riscvchip=0x01, High)
81 0d 01 02          AttachChip
82 0d 05 05 20 36 05 10   → CH32V20X, ChipID 0x20360510
81 0c 02 05 01      SetSpeed again with the detected family

The CH32V205 family requires firmware v3.6 (version_code >= 42); older firmware misdetects it as CH32V20X and wlink warns.

Status / chip info

ProbeSession::dump_info:

  1. 0x11 / 0x06 GetChipInfo::V2 when version_code >= 29, else 0x11 / 0x09 GetChipInfo::V1 — see Chip Info.
  2. 0x06 / 0x01 CheckReadProtect → report whether flash is read-protected.
  3. If the family supports it, 0x0d / 0x04 GetChipRomRamSplit → SRAM/CODE split mode.

Querying info halts the MCU. wlink re-enables execution on detach.

Erase (default method)

ProbeSession::erase_flash:

  1. If the family supports flash protection:
    • 0x06 / 0x01 CheckReadProtect; protected → unprotect_flash();
    • if unprotected, 0x06 / 0x04 CheckReadProtectEx; write-protected → unprotect_flash().
  2. 0x02 / 0x01 Program::EraseFlash.
  3. 0x0d / 0x02 AttachChip again to re-establish the session.

Unprotect

ProbeSession::unprotect_flash starts with a fresh attach (0x0d / 0xff OptEnd then 0x0d / 0x02), then:

  1. 0x06 / 0x01 CheckReadProtect; if 0x01, 0x06 / 0x02 Unprotect, reattach, re-check.
  2. 0x06 / 0x04 CheckReadProtectEx; if 0x11, 0x06 02 ff ff ff ff ff ff ff (UnprotectEx(0xff)), reattach, re-check.

wlink deliberately skips Unprotect when flash is already unprotected: the probe firmware mass-erases the option-byte page, destroying USER/Data/WRPR.

Flash programming

ProbeSession::write_flash(data, address):

 1  (optional) unprotect_flash()
 2  0x01 SetWriteMemoryRegion(address, len)
 3  0x02 / 0x05 Program::WriteFlashOP
 4  data EP 0x02: write the family's flash algorithm blob
                  (padded to data_packet_size, see src/flash_op.rs)
 5  0x02 / 0x07 Program::Unknown07AfterFlashOPWritten
    → response must be 0x07
 6  0x02 / 0x02 Program::WriteFlash
 7  for each write_pack_size chunk:
       data EP 0x02: write chunk (0xff-padded to data_packet_size)
       data EP 0x82: read 4-byte ack, last byte must be 0x04
                     (observed 41 01 01 04)
 8  0x02 / 0x08 Program::End

data_packet_size and write_pack_size are per-family; see RiscvChip::data_packet_size / write_pack_size in src/lib.rs.

Memory read

ProbeSession::read_memory(address, length):

  1. Round length up to a multiple of 4.
  2. 0x03 SetReadMemoryRegion(address, len).
  3. 0x02 / 0x0c Program::ReadMemory.
  4. 0x82 data endpoint: read len bytes in 64-byte bulk chunks.
  5. Reverse each 4-byte word to fix the probe’s word order.

A leading A9 BD F9 F3 sequence indicates a read from an invalid location or a chip that has just entered debug mode; wlink logs a warning.

Special erase (power-off / RST pin)

erase --method power-off|pin-rst --chip <FAMILY> bypasses attach entirely:

 0x0c SetSpeed(riscvchip=<--chip>, High)
 0x0d / 0x0f EraseCodeFlash::ByPowerOff    → 81 0d 02 0f <riscvchip>
   or 0x0d / 0x08 EraseCodeFlash::ByPinRST  → 81 0d 02 08 <riscvchip>

Both require a probe with power functions and a chip family whose support_special_erase() is true. Because there is no auto-detection, --chip is mandatory. The RST-pin variant additionally needs the nRST line wired to the target.

Reset and detach

CLICommands
wlink reset (quit)0x0b / 0x01 Reset::Soft, then ~300 ms delay
wlink reset run / haltDMI halt/resume via 0x08 — see DMI
end of most commands0x0d / 0xff OptEnd (skipped with --no-detach, or when the MCU must stay halted)
wlink sdi-print enable0x0d / 0xee 00
wlink set-power0x0d / 0x09…0x0c

DMI and the Debug Module

The Debug Module Interface (DMI) is a separate layer above the WCH-Link command protocol: a single 0x08 command carries one DMI transaction. The register addresses and semantics come from the RISC-V Debug Specification 0.13.2 and WCH’s QingKe debug manuals.

Sources: DmiOp / DmiOpResponse in src/commands/mod.rs, src/dmi.rs, src/regs.rs.

0x08 transaction layout

Request payload (6 bytes):

OffsetSizeField
01addr — DMI register address
1–44data (u32_be), ignored for NOP/READ
51op — 0x00 NOP, 0x01 READ, 0x02 WRITE

Response payload uses the same 6-byte shape; op is now a status:

opMeaningDmiOpResponse helper
0x00Successis_success()
0x02Failedis_failed()
0x03Busy / failedis_busy(), also counted by is_failed()

Example — read DMSTATUS (0x11):

→ 81 08 06 11 00 00 00 00 01
← 82 08 06 11 00 00 00 00 00

dmi_read polls on busy (up to 100 retries with 10 ms sleeps) and raises Error::NotAttached when it sees op == 0x03 && data == 0xffffffff && addr == 0x7d.

DMI register addresses

From src/regs.rs:

AddressNameNotes
0x04DMDATA0Abstract command data
0x05DMDATA1
0x10DMCONTROLHalt/resume, dmactive
0x11DMSTATUSallhalted, anyhalted, havereset
0x12DMHARTINFO
0x16DMABSTRACTCScmderr, busy
0x17DMCOMMANDAbstract command register
0x18DMABSTRACTAUTONot available on QingKeV2 / QingKeV3
0x20–0x27DMPROGBUF0–7Program buffer
0x40DMHALTSUM0

CSR addresses used with abstract commands (16-bit command.regno): 0x300 MSTATUS, 0x301 MISA, 0x305 MTVEC, 0x340 MSCRATCH, 0x341 MEPC, 0x342 MCAUSE, 0x343 MTVAL, 0x7b1 DPC, 0xf12 MARCHID, 0xf13 MIMPID. General-purpose registers x0–x31 occupy 0x1000–0x101f.

Abstract command errors

AbstractcsCmdErr::try_from_cmderr maps the cmderr field of DMABSTRACTCS:

ValueMeaning
0No error
1Busy (register access during an abstract command)
2Command not supported
3Exception during execution
4Hart in wrong state (not halted/running)
5Bus error (alignment, size, timeout)
6Parity error (WCH extension)
7Other

ProbeSession::clear_abstractcs_cmderr writes 0b111 to the cmderr field to clear it (W1C), and clear_dmstatus_havereset acknowledges havereset.

Flash/option unlock keys

src/dmi.rs defines two keys used when unlocking flash/option-byte writes through the probe’s debug interface:

KEY1 = 0x45670123
KEY2 = 0xCDEF89AB

Higher-level operations

DebugModuleInterface (implemented for WchLink) provides dmi_nop, dmi_read, dmi_write and typed register helpers. ProbeSession builds on them for:

  • ensure_mcu_halt / ensure_mcu_resume — write 0x80000001 to DMCONTROL (0x10) and poll DMSTATUS;
  • register and memory inspection via abstract commands and the program buffer;
  • dump_regs, dump_core_csrs, dump_pmp_csrs, dump_dmi.

The RISC-V Debug Specification details are out of scope for this project documentation. See External References for the QingKeV2 debug manual and other implementations.

Constants and Chip Families

riscvchip — chip family codes

Source: RiscvChip in src/lib.rs. The one-byte value is returned by AttachChip and is passed to SetSpeed and to the special-erase subcommands.

CodeFamilySeries / notes
0x01CH32V103RISC-V3A
0x02CH57XCH571/CH573, RISC-V3A BLE 4.2
0x03CH56XCH565/CH569, RISC-V3A
0x04CH32F10XCortex-M (not a RISC-V target)
0x05CH32V20XRISC-V4B/V4C
0x06CH32V30XRISC-V4C/V4F, shares type 5 traits
0x07CH582CH581/CH582/CH583, RISC-V4A BLE 5.3
0x08CH32F20XCortex-M (not a RISC-V target)
0x09CH32V003RISC-V2A
0x0aCH8571Undocumented RISC-V EC controller
0x0bCH59XCH59x RISC-V4C BLE 5.4, falls back as CH58X
0x0cCH643RISC-V4C, RGB display driver
0x0dCH32X035CH32X035/033, RISC-V4C USB-PD
0x0eCH32L103RISC-V4C low-power USB-PD
0x0fCH564RISC-V4J
0x46CH645CH645/CH653, RISC-V4C
0x49CH641RISC-V2A USB-PD, falls back as CH32V003
0x4bCH585CH585/CH584, RISC-V3C BLE 5.4 / NFC / USB HS
0x4eCH32V00XCH32V002/4/5/6/7, CH32M007
0x86CH32V317RISC-V4
0x8bCH570CH570/CH572, RISC-V BLE
0x8eCH32M030RISC-V motor control
0xa6CH32V4X7CH32V407/CH32V467
0xabCH586CH586/CH587, RISC-V BLE 5.4
0xc6CH32H41XCH32H415/416/417, RISC-V5F + RISC-V3F
0xceCH32V205New-generation CH32V205/CH32V203 (QingKe V3B), unrelated to CH32V20X
0xe6CH32X3X5CH32X315/CH32X305, RISC-V3F

RiscvChip also implements clap’s ValueEnum, so --chip accepts the family name plus part-number aliases such as CH32H417QEU or CH32X315MCU6. The ChipID-to-part-name database is chip_id_to_chip_name in src/chips.rs.

Speed codes (0x0c SetSpeed)

CodeSpeed variantClock
0x01High (default)6000 kHz
0x02Medium4000 kHz
0x03Low400 kHz

Protection flags (0x06 ConfigChip)

ConstantValue
FLAG_READ_PROTECTED0x01
FLAG_READ_UNPROTECTED0x02
FLAG_WRITE_PROTECTED0x11
FLAG_WRITE_UNPROTECTED0x00

CheckReadProtect returns the read flags; CheckReadProtectEx returns the write flags.

Error reasons

REASONMeaning
0x55Failed to connect to the target RISC-V chip
otherUnknown; wlink preserves the whole frame in Error::Protocol(reason, bytes)

USB constants

ConstantValue
VENDOR_ID0x1a86
PRODUCT_ID (RV)0x8010
PRODUCT_ID_DAP0x8012
ENDPOINT_OUT / ENDPOINT_IN0x01 / 0x81
DATA_ENDPOINT_OUT / DATA_ENDPOINT_IN0x02 / 0x82
ENDPOINT_OUT_DAP0x02

Flash algorithm blobs

Each supported family has a binary “flash OP” blob in src/flash_op.rs, sent over the data endpoint after 0x02 / 0x05 Program::WriteFlashOP. They are opaque byte arrays selected by RiscvChip::get_flash_op, for example CH32V307: [u8; 446], CH32V003: [u8; 498], CH32H417: [u8; 618], CH564: [u8; 1532]. Some families have an _ALT variant. Packet and write chunk sizes come from RiscvChip::data_packet_size and RiscvChip::write_pack_size in src/lib.rs.

Debug interface keys

ConstantValue
KEY10x45670123
KEY20xCDEF89AB

Used to unlock flash/option-byte writes through the debug interface; see DMI.

Unverified Commands and Captures

This page collects protocol knowledge that wlink does not implement and cannot confirm. It exists so that the old protocol.md’s ??? markers are not silently dropped: they are either resolved (and documented on other pages) or listed here as observations.

unverified means: seen in a USB capture from WCH’s utility, or written in an older note, without a confirmed implementation in wlink. Do not rely on these byte sequences in new code without re-capturing.

Raw captures recorded in the source

Copied verbatim from the comment block at the end of src/commands/mod.rs:

81 0D 05 11 SetAccessAddress
81 0F 01 02 GetDeviceMode
81 0D 01 07 EnableQE
81 0D 01 06 CheckQE
81 FE 01 00 DisEncrypt
81 0D 01 0F ClearCodeFlashB
81 0D 02 08 xx ClearCodeFlash
81 11 01 0D unknown in query info, before GetChipRomRamSplit
81 0D 02 EE 00/02/03 SetSDLineMode
81 0F 01 01 SetIAPMode

Observations:

  • 0x0f appears as a command ID in GetDeviceMode / SetIAPMode captures, not as a “begin verify” 0x02 subcommand as the old document claimed.
  • 0xfe appears once as DisEncrypt.
  • 81 0D 01 0F (ClearCodeFlashB) has LEN = 1 while two payload bytes follow — see the discussion of the analogous 0x0f length quirk in Control Subcommands.
  • 81 11 01 0D is a 0x11 variant not used by wlink, which only sends 0x09 (V1) and 0x06 (V2).
  • 81 0D 02 EE 00/02/03 suggests additional SDI line modes; wlink only uses ee 00 / ee 01.
CMD / SUBCapture labelStatus
0x0d / 0x03“stage after connect chip” (old note, riscvchip 1)unverified
0x0d / 0x06CheckQEunverified
0x0d / 0x07EnableQEunverified
0x0d / 0x11SetAccessAddressunverified
0x0f / 0x01SetIAPModeunverified
0x0f / 0x02GetDeviceModeunverified
0xfe / 0x00DisEncryptunverified
0x06 Config { data, wrp }option-byte config flagsknown capture 81 06 08 02 3f 00 00 ff ff ff ff, but todo!() in wlink

Old protocol.md questions and their resolution

Old entryResolution
0x01 - Set RAM address and size (empty section)SetWriteMemoryRegion, 8-byte payload — Command Reference
0x02 subcommand guesses (0x03 execute ram, 0x05 begin bulk transfer, 0x07 verify, 0x09 bulk transfer ends, 0x0a verify, 0x0b verify for riscvchip 1)Replaced by the implemented Program enum; unconfirmed labels are unverified
0x08 - DMI OP (two empty tables)Filled in — DMI
0x0c - ???SetSpeed; see also the v0.0.6 CHANGELOG correction
0x0d - Info in the command table vs 0x0d - Control as a headingBoth refer to the Control command — Control Subcommands
0x0e (unnamed)DisableDebug
0x0f - ? begin verifyunverified; captures point at GetDeviceMode / SetIAPMode
0x03 Reset, normal?Reset::Normal (0x03)
0x03 ? stage after connect chipStill unverified
Error Reason: 0x55 onlyStill the only reason with a known meaning
RiscvChip with 7 families28 entries — Constants
Firmware Versions: v2.8Current baseline v2.15 (v35); encoding explained in Probe Info
Variants: ?? / WCH-LinkBStill unverified
beigin, buck, Swith typosCorrected

Contributing a correction

  1. Capture the exchange with wlink -vv (trace logs print send and recv hex for every frame) or with a USB sniffer.
  2. Identify the Rust symbol in src/commands/ that produces or parses it.
  3. Update the relevant page here, and move the entry out of this page if it is now confirmed. If the implementation changes, update the code and the documentation in the same PR.

CH32V003

RISC-V2A, 16KB Flash, 2KB SRAM, 48MHz.

Note CH32V003 is a riscv32ec core, which is not supported by ofiicial Rust yet. ch32-rs team maintains a fork of Rust at https://github.com/ch32-rs/rust. You can check Noxim’s Blog for riscv32ec support.

Debug support

1-wire debug.

SWDIO <-> D1/DIO@MCU, PD1
GND   <-> GND@MCU
3V3   <-> 3V3@MCU (No need if you connect your MCU board with USB-C power supply)
  • Flash support
  • Memory dump support
  • Reset / Resume
  • Reg read/write support
> wlink -v flash ./firmware.bin
13:41:49 [INFO] WCH-Link v2.8 (WCH-LinkE-CH32V305)
13:41:49 [INFO] attached chip: CH32V003(0x00300500)
13:41:49 [DEBUG] (1) wlink::operations: Chip UID: cd-ab-80-17-48-bc-95-7f
13:41:49 [DEBUG] (1) wlink::operations: flash protected: false
13:41:49 [DEBUG] (1) wlink::operations: SRAM CODE mode: 3
13:41:49 [DEBUG] (1) wlink::operations: already halted
13:41:49 [DEBUG] (1) wlink::operations: RISC-V core version: Some("WCH-V2A")
13:41:49 [INFO] flash 860 bytes
13:41:49 [DEBUG] (1) wlink::operations: Code start address 0x08000000
13:41:49 [DEBUG] (1) wlink::operations: flash op written
13:41:49 [DEBUG] (1) wlink::operations: fastprogram done
13:41:49 [INFO] flash done
13:41:50 [INFO] now reset...
13:41:51 [INFO] resume executing...
13:41:51 [WARN] resume fails

Chips

 * CH32V003F4P6-0x003005x0
 * CH32V003F4U6-0x003105x0
 * CH32V003A4M6-0x003205x0
 * CH32V003J4M6-0x003305x0

CH32V103

SWDIO - PA13 @ MCU SWCLK - PA14 @ MCU

“Deprotect” or “接触代码保护” must be run before using wlin.(use WCHISPTool or wchisp).

USBISP

BOOT0 to VCC

Chips

 * CH32F103C8T6-0x20004102
 * CH32F103R8T6-0x2000410F

CH32V20x

  • PA13 SWDIO
  • PA14 SWCLK

Dev Boards

Notes

  • Erased bytes 39 e3 39 e3

Chips

 * CH32V203C8U6-0x203005x0
 * CH32V203C8T6-0x203105x0
 * CH32V203K8T6-0x203205x0
 * CH32V203C6T6-0x203305x0
 * CH32V203K6T6-0x203505x0
 * CH32V203G6U6-0x203605x0
 * CH32V203G8R6-0x203B05x0
 * CH32V203F8U6-0x203E05x0
 * CH32V203F6P6-0x203705x0-0x203905x0
 * CH32V203F8P6-0x203A05x0
 * CH32V203RBT6-0x203405xC
 * CH32V208WBU6-0x208005xC
 * CH32V208RBT6-0x208105xC
 * CH32V208CBU6-0x208205xC
 * CH32V208GBU6-0x208305xC

CH32V30x

  • CH32V303
  • CH32V305
  • CH32V307
wlink -v dump 0x08000000 0x10`
14:40:22 [INFO] WCH-Link v2.8 (WCH-LinkE-CH32V305)
14:40:22 [INFO] attached chip: CH32V30X(0x30700518)
14:40:22 [DEBUG] (1) wlink::operations: Chip UID: 30-78-3e-26-3b-38-a9-d6
14:40:22 [DEBUG] (1) wlink::operations: flash protected: false
14:40:22 [DEBUG] (1) wlink::operations: SRAM CODE mode: 3
14:40:22 [DEBUG] (1) wlink::operations: already halted
14:40:22 [DEBUG] (1) wlink::operations: RISC-V core version: Some("WCH-V4A")
14:40:22 [INFO] Read memory from 0x08000000 to 0x08000010
08000000:   b7 00 00 08  67 80 80 00  73 50 40 30  73 50 40 34   ×00•g××0sP@0sP@4

Notes

  • erased flash 39 e3 39 e3

Chips

 * CH32V303CBT6-0x303305x4
 * CH32V303RBT6-0x303205x4
 * CH32V303RCT6-0x303105x4
 * CH32V303VCT6-0x303005x4
 * CH32V305FBP6-0x305205x8
 * CH32V305RBT6-0x305005x8
 * CH32V305GBU6-0x305B05x8
 * CH32V307WCU6-0x307305x8
 * CH32V307FBP6-0x307205x8
 * CH32V307RCT6-0x307105x8
 * CH32V307VCT6-0x307005x8

CH32X035/CH32X033

RISC-V4C, 62KB Flash, 20KB SRAM.

OPA/PGA/CMP, USB-PD/Type-C.

Debug Pins

  • SWDIO - PC18/DIO @ MCU
  • SWCLK - PC19/DCK @ MCU

PC17 to High, enable USB ISP DOWNLOAD MODE

Boards

Official CH32X035EVT

  • CH32X035F8U6-R0
  • CH32X035G8U6-R0
  • CH32X035C8T6-R0

LED1, LED2 blue LED, active low, open, connected to open jumper

Chips

CH32X035R8T6-0x035006x1
CH32X035C8T6-0x035106x1
CH32X035F8U6-0x035E06x1
CH32X035G8U6-0x035606x1
CH32X035G8R6-0x035B06x1
CH32X035F7P6-0x035706x1
CH32X033F8P6-0x035A06x1

CH32L103

RISC-V4C, 96MHz, 64KB Flash, 20KB SRAM.

RTC, ADC/TKey, CAN, OPA, CMP, USB-PD/Type-C.

Chips

* CH32L103C8U6-0x103007x0
* CH32L103C8T6-0x103107x0
* CH32L103F8P6-0x103A07x0
* CH32L103G8R6-0x103B07x0
* CH32L103K8U6-0x103207x0
* CH32L103F8U6-0x103D07x0
* CH32L103F7P6-0x103707x0

CH569/CH565

RISC-V3A, 448KB Code Flash, 32KB Data Flash, 32/64/96KB SRAM(RAMX).

BUS8, USB-SS, ETH, EMMC, SerDes

  • CH569: HSPI
  • CH565: DVP

Debug Pins

  • TCK=HTACK=PA10
  • TIO=HTCLK=PA11

Boards

  • CH569W-R0-1v0

CH57X & CH58X & CH59X

BLE MCUs. They are sharing almost the same preipheral set

Debug Pins

  • PB15 TCK
  • PB14 TIO

Notes

  • Enable debug using WCHISPTool
  • Always specify chip type using --chip xxx parameter during connect, auto-detection mechanism does not work on these targets
  • Similiarly, use -c "chip_id CH59x" while using official OpenOCD release from MRS, as there’s no way to know the chip type before connecting to it
  • After debug enabled, flash should be prgrammed at lease once
  • a9 bd f9 f3 means erased flash or protected flash. The detailed mechanism is unknown

CH641

RISC-V2A, 48MHz, 16KB Flash, 2KB SRAM.

USB-PD/Type-C, BC1.2, HV DCP. ISP/ISN, QII.

Note CH641 is a riscv32ec core, which is not supported by ofiicial Rust yet. ch32-rs team maintains a fork of Rust at https://github.com/ch32-rs/rust. You can check Noxim’s Blog for riscv32ec support.

Chips

* CH641F-0x641005x0
* CH641D-0x641105x0
* CH641U-0x641505x0
* CH641P-0x641605x0

CH643

RISC-V4C, 62KB Flash, 20KB SRAM.

OPA/PGA/CMP, LEDPWM, USB-PD/Type-C.

Chips

* CH643W-0x64300601
* CH643Q-0x64310601
* CH643L-0x64330601
* CH643U-0x64340601

References

Real Shot

Feature matrix

FeatureWCH-LinkWCH-LinkEWCH-LinkWWCH-DAPLink
RISC-V mode✓✓✓
ARM-SWD mode (HID device)✓
ARM-SWD mode (WinUSB device)✓✓✓✓
ARM-JTAG mode (HID device)✓
ARM-JTAG mode (WinUSB device)✓✓✓
ModeS button✓✓✓
DFU via 2-wire✓
DFU via serial-port✓
DFU via USB✓✓✓
Power Supply (3.3v)✓✓✓✓
Power Supply (5.0v)✓✓✓✓
Power Supply (Configurable)✓✓✓
USB 2.0 to JTAG✓
Wireless mode✓
Download via MounRiver Studio✓✓✓✓
Download via WCH-LinkUtility✓✓✓
Download via Keil≥ v5.25≥ v5.25≥ v5.25✓

Supported Chip matrix

ChipWCH-LinkWCH-LinkEWCH-LinkWWCH-DAPLink
JTAG interface✓✓✓
SWD interface✓✓✓✓
CH32F10x✓✓✓✓
CH32F20x✓✓✓✓
CH32V003✓✓
CH32V10x✓✓✓
CH32V20X✓✓✓
CH32V30X✓✓✓
CH569✓✓
CH573✓✓
CH579✓✓✓✓
CH583✓✓

Supported Baud matrix

BaudWCH-LinkWCH-LinkEWCH-LinkWWCH-DAPLink
1200✓✓✓✓
2400✓✓✓✓
4800✓✓✓✓
9600✓✓✓✓
14400✓✓✓✓
19200✓✓✓✓
38400✓✓✓✓
57600✓✓✓✓
115200✓✓✓✓
230400✓✓✓✓
460800✓✓✓
921600✓✓✓

SWD PIN matrix

ChipSWDIOSWCLK
CH32F10xPA13PA14
CH32F20xPA13PA14
CH32V003PD1
CH32V10xPA13PA14
CH32V20XPA13PA14
CH32V30XPA13PA14
CH32X035PC18PC19
CH569PA11PA10
CH573PB14PB15
CH579PB16PB17
CH583PB14PB15
CH59xPB14PB15
CH643PC18PC19

Documentation

ISP

Firmware

Other FOSS implementation