A C++ shared-library plugin that exposes the WCH CH347 Hi-Speed USB adapter through a unified command dispatcher. The CH347 is a single-chip USB 2.0 Hi-Speed bridge that simultaneously provides SPI, I2C, GPIO, and JTAG interfaces over one USB device file descriptor. Unlike serial-port-based adapters, the CH347 communicates directly through the WCH native USB driver library.
Version: 1.0.0.0
Requires: C++20
- Overview
- Project Structure
- Architecture
- Building
- Platform Notes
- Command Reference
- SPI Clock Reference
- I2C Speed Reference
- Script Files
- Fault-Tolerant and Dry-Run Modes
- Error Handling and Return Values
The plugin loads as a dynamic shared library (.so / .dll). The host application calls the exported C entry points pluginEntry() / pluginExit() to create and destroy the plugin instance. Once loaded, settings are pushed via setParams(), the plugin is initialized with doInit(), enabled with doEnable(), and commands are dispatched with doDispatch().
All commands follow the pattern:
<PLUGIN>.<COMMAND> [subcommand] [arguments]
For example:
CH347.SPI open clock=15000000 mode=0
CH347.SPI wrrd 9F:3
CH347.I2C scan
CH347.GPIO set pins=0x01
CH347.JTAG write ir FF
A key difference from the BusPirate plugin is that each interface (SPI, I2C, GPIO, JTAG) must be explicitly opened and closed — there is no mode-switch step. All four interfaces can be open simultaneously.
ch347_plugin/
├── CMakeLists.txt # Build definition (shared library, C++20)
├── inc/
│ ├── ch347_plugin.hpp # Main class + command tables + pending config structs
│ ├── ch347_generic.hpp # Generic template helpers + clock index helpers
│ └── private/
│ ├── spi_config.hpp # SPI_COMMANDS_CONFIG_TABLE + SPI_SPEED_CONFIG_TABLE
│ ├── i2c_config.hpp # I2C_COMMANDS_CONFIG_TABLE + I2C_SPEED_CONFIG_TABLE
│ ├── gpio_config.hpp # GPIO_COMMANDS_CONFIG_TABLE
│ └── jtag_config.hpp # JTAG_COMMANDS_CONFIG_TABLE + JTAG_RATE_CONFIG_TABLE
└── src/
├── ch347_plugin.cpp # Entry points, init/cleanup, INFO, setParams, parse helpers
├── ch347_spi.cpp # SPI sub-command implementations
├── ch347_i2c.cpp # I2C sub-command implementations
├── ch347_gpio.cpp # GPIO sub-command implementations
└── ch347_jtag.cpp # JTAG sub-command implementations
Each protocol lives in its own .cpp file. The command/speed tables are defined at the bottom of each config header using X-macros, mirroring the same pattern as the BusPirate plugin.
pluginEntry() → creates CH347Plugin instance
setParams() → loads INI values (device path, speeds, address, timeouts…)
doInit() → propagates INI defaults into pending config structs; marks initialized
doEnable() → enables real execution (without this, commands only validate args)
doDispatch(cmd, args) → routes to the correct top-level or module handler
CH347.SPI open ... → opens SPI driver (CH347SPI), validates hardware connection
CH347.SPI close → closes SPI driver
(similar for I2C / GPIO / JTAG)
doCleanup() → closes all four drivers, resets state
pluginExit(ptr) → deletes the CH347Plugin instance
doEnable() controls a dry-run / argument-validation mode: when not enabled, commands parse their arguments and return true without touching the hardware. This is used by test frameworks to verify command syntax before the device is connected.
doInit() does not open any hardware interface. Opening happens explicitly via the per-module open sub-command. This allows lazy initialization and supports opening only the interface(s) needed for a given test sequence.
The dispatch model uses two layers of std::map:
- Top-level map (
m_mapCmds): maps command names (INFO,SPI,I2C,GPIO,JTAG) to member-function pointers onCH347Plugin. - Module-level maps (
m_mapCmds_SPI,m_mapCmds_I2C,m_mapCmds_GPIO,m_mapCmds_JTAG): each module owns a map of sub-command name → handler pointer.
A meta-map (m_mapCommandsMaps) maps module names to their sub-maps, so the generic dispatcher can locate any sub-command dynamically without any switch statements.
Command registration is entirely driven by X-macros in the *_config.hpp headers:
// In spi_config.hpp:
#define SPI_COMMANDS_CONFIG_TABLE \
SPI_CMD_RECORD( open ) \
SPI_CMD_RECORD( close ) \
SPI_CMD_RECORD( cfg ) \
...
// In the constructor (ch347_plugin.hpp):
#define SPI_CMD_RECORD(a) \
m_mapCmds_SPI.insert({#a, &CH347Plugin::m_handle_spi_##a});
SPI_COMMANDS_CONFIG_TABLE
#undef SPI_CMD_RECORDAdding a new sub-command requires only one line in the config table and one handler function.
ch347_generic.hpp provides stateless template functions shared by all modules:
| Function | Purpose |
|---|---|
generic_module_dispatch<T>() |
Splits "subcmd args" and routes to the correct module handler |
generic_module_set_speed<T>() |
Looks up a speed label (or raw Hz value) and calls setModuleSpeed() |
generic_write_data<T>() |
Unhexlifies a hex string and calls a write callback (up to 4096 bytes) |
generic_write_read_data<T>() |
Parses HEXDATA:rdlen and calls a write-then-read callback |
generic_write_read_file<T>() |
Reads write data from a binary file in ARTEFACTS_PATH, streams results |
generic_execute_script<T>() |
Runs a CommScriptClient script on an open driver |
generic_module_list_commands<T>() |
Logs all registered sub-command names (used by help) |
Two inline helper functions manage the SPI clock index encoding:
spiHzToClockIndex(uint32_t hz) → uint8_t // maps Hz to CH347 clock register index 0-7
spiClockIndexToHz(uint8_t idx) → uint32_t // reverse mapping for displayEach module maintains a "pending configuration" struct that is updated by open and cfg commands and applied to the live driver if it is already open:
| Struct | Fields | Default |
|---|---|---|
SpiPendingCfg |
cfg.iClock, cfg.iMode, cfg.iByteOrder, xferOpts.chipSelect, cfgDirty |
clock=1MHz, mode=0, MSB, CS1 |
I2cPendingCfg |
speed, address |
400 kHz, 0x50 |
GpioPendingCfg |
enableMask, dirMask, dataValue |
all inputs, all 0 |
JtagPendingCfg |
clockRate, lastReg |
rate=2, DR |
The lastReg field in JtagPendingCfg remembers the last IR/DR register used so subsequent write, read, and wrrd commands can omit the ir/dr prefix.
| Key | Type | Default | Description |
|---|---|---|---|
DEVICE_PATH |
string | /dev/ch34xpis0 (Linux) / 0 (Windows) |
USB device node or index |
ARTEFACTS_PATH |
string | "" |
Base directory for script and binary data files |
SPI_CLOCK |
uint32 (Hz) | 1000000 |
Initial SPI clock frequency |
I2C_SPEED |
string/uint8 | 400kHz / fast |
Initial I2C speed preset |
I2C_ADDRESS |
uint8 (hex) | 0x50 |
Default I2C target device address |
JTAG_CLOCK_RATE |
uint8 | 2 |
Initial JTAG clock rate (0–5) |
READ_TIMEOUT |
uint32 (ms) | 5000 |
Per-operation read timeout |
SCRIPT_DELAY |
uint32 (ms) | 0 |
Inter-command delay during script execution |
mkdir build && cd build
cmake ..
make ch347_pluginThe output is libch347_plugin.so (Linux) or ch347_plugin.dll (Windows).
Required libraries (must be present in the CMake build tree):
uCH347— WCH CH347 USB driver abstraction (CH347SPI, CH347I2C, CH347GPIO, CH347JTAG)uPluginOps,uIPlugin,uSharedConfig— plugin frameworkuICoreScript,uCommScriptClient,uCommScriptCommandInterpreter,uScriptReader— scripting engineuICommDriver,uUtils— communication driver base and utilities
Linux:
- The default device path is
/dev/ch34xpis0. Additional CH347 devices appear as/dev/ch34xpis1,/dev/ch34xpis2, etc. - The WCH Linux kernel driver or userspace library must be installed.
- Override per-command with
device=/dev/ch34xpis1in theopensub-command.
Windows:
- The default device path is
"0"— a decimal index passed toCH347OpenDevice(). - Multiple devices use
"1","2", etc. - The WCH Windows DLL (
CH347DLL.DLL) must be in the system path. - Override with
device=1in theopensub-command.
Prints version, device path, and a complete command reference. Takes no arguments and works even before doInit().
CH347.INFO
Example output (abbreviated):
CH347 | Vers: 1.0.0.0
CH347 | Description: WCH CH347 Hi-Speed USB adapter (SPI/I2C/GPIO/JTAG)
CH347 | Device: /dev/ch34xpis0
...
Full-duplex SPI bus master. Each module must be explicitly opened before use.
CH347.SPI <subcommand> [arguments]
Opens the CH347 SPI interface. All optional parameters follow the key=value format and may be combined freely. The driver is closed and re-opened if called again while already open.
CH347.SPI open [clock=N] [mode=0-3] [order=msb|lsb] [cs=cs1|cs2|none] [device=PATH]
| Parameter | Values | Default | Description |
|---|---|---|---|
clock |
468750 – 60000000 (Hz) | from INI / 1 MHz | SPI clock frequency in Hz |
mode |
0, 1, 2, 3 | 0 |
SPI mode (CPOL/CPHA combination) |
order |
msb, lsb |
msb |
Bit order |
cs |
cs1, cs2, none |
cs1 |
Chip-select pin selection |
device |
path string | from INI | Override device path for this session |
SPI mode definitions:
| Mode | CPOL | CPHA | Clock idle | Data sampled |
|---|---|---|---|---|
| 0 | 0 | 0 | Low | Rising edge |
| 1 | 0 | 1 | Low | Falling edge |
| 2 | 1 | 0 | High | Falling edge |
| 3 | 1 | 1 | High | Rising edge |
# Open at 15 MHz, mode 0, MSB-first, CS1 (typical flash memory)
CH347.SPI open clock=15000000 mode=0
# Open at 1 MHz, SPI mode 3, LSB-first, using CS2
CH347.SPI open clock=1000000 mode=3 order=lsb cs=cs2
# Open on an alternate device
CH347.SPI open device=/dev/ch34xpis1 clock=8000000
# No CS management (CS driven externally or via GPIO)
CH347.SPI open clock=4000000 cs=none
# Windows: second CH347 device
CH347.SPI open device=1 clock=15000000
CH347.SPI close
Updates pending SPI parameters and applies them to the open driver (if currently open). Use ? to print the current configuration.
CH347.SPI cfg [clock=N] [mode=0-3] [order=msb|lsb] [cs=cs1|cs2|none]
CH347.SPI cfg ?
# Change clock to 7.5 MHz, switch to mode 1
CH347.SPI cfg clock=7500000 mode=1
# Switch to MSB-first
CH347.SPI cfg order=msb
# Print current pending configuration
CH347.SPI cfg ?
# Change CS to CS2
CH347.SPI cfg cs=cs2
Clock preset labels (can be used as values for clock=):
| Label | Frequency |
|---|---|
468kHz |
468,750 Hz |
937kHz |
937,500 Hz |
1.875MHz |
1,875,000 Hz |
3.75MHz |
3,750,000 Hz |
7.5MHz |
7,500,000 Hz |
15MHz |
15,000,000 Hz |
30MHz |
30,000,000 Hz |
60MHz |
60,000,000 Hz |
CS is also automatically asserted/deasserted per transfer. Use this for manual control of the CS line outside of a transfer.
CH347.SPI cs <en|dis>
| Argument | Effect |
|---|---|
en or 1 |
Assert CS (drive low) |
dis or 0 |
Deassert CS (drive high / HiZ) |
CH347.SPI cs en
CH347.SPI cs dis
Sends bytes to MOSI. MISO data is received but discarded.
CH347.SPI write <HEXDATA>
# Send a Write Enable command (0x06) to a flash
CH347.SPI write 06
# Send a 4-byte command
CH347.SPI write 03000000
# Send 8 bytes of data
CH347.SPI write 0102030405060708
Performs a full-duplex transfer, clocking 0x00 on MOSI and capturing MISO. The received bytes are printed as a hex dump.
CH347.SPI read <N>
# Read 4 bytes
CH347.SPI read 4
# Read 256 bytes (e.g., one page from flash)
CH347.SPI read 256
# Read a single status byte
CH347.SPI read 1
Sends MOSI data while simultaneously capturing MISO. Both sides are the same length. The MISO bytes are printed as a hex dump.
CH347.SPI xfer <HEXDATA>
# Send 4 bytes, capture 4 bytes of MISO simultaneously
CH347.SPI xfer DEADBEEF
# Send command + dummy bytes, capture response
CH347.SPI xfer 9F000000
# Single-byte full-duplex exchange
CH347.SPI xfer FF
Writes bytes, then reads back a specified number of bytes in one CS-asserted transaction. Internally this uses tout_xfer with a combined buffer (write bytes + zero padding for the read phase).
CH347.SPI wrrd <HEXDATA>:<rdlen>
HEXDATA— hex string for the write phase (may be empty for read-only::N)rdlen— number of bytes to read back
# JEDEC ID: send 0x9F, read 3 bytes (manufacturer, type, capacity)
CH347.SPI wrrd 9F:3
# Status register read (0x05), read 1 byte
CH347.SPI wrrd 05:1
# Read 256 bytes from address 0x000000 (READ command 0x03)
CH347.SPI wrrd 03000000:256
# Write Enable (0x06) — write only, no read
CH347.SPI wrrd 06:0
# Read-only: clock 4 dummy bytes, capture MISO
CH347.SPI wrrd :4
# Write 4 bytes, read back 4 bytes
CH347.SPI wrrd DEADBEEF:4
Same as wrrd but write data is loaded from a binary file in ARTEFACTS_PATH. Read data is printed as it arrives. Optional chunk size parameters control how the file is split.
CH347.SPI wrrdf <filename>[:<wrchunk>][:<rdchunk>]
CH347.SPI wrrdf flash_program.bin
CH347.SPI wrrdf page_write.bin:256:0
CH347.SPI wrrdf firmware.bin:512:512
Runs a CommScriptClient script from ARTEFACTS_PATH. SPI must be open before calling this.
CH347.SPI script <filename>
CH347.SPI script read_flash.txt
CH347.SPI script erase_chip.txt
CH347.SPI script program_sector.txt
I2C bus master. Must call open before any transfer.
CH347.I2C <subcommand> [arguments]
CH347.I2C open [speed=PRESET] [addr=0xNN] [device=PATH]
| Parameter | Values | Default | Description |
|---|---|---|---|
speed |
see table below | from INI / fast (400 kHz) |
I2C clock speed |
addr |
0x00–0x7F |
from INI / 0x50 |
Default target device address for read and wrrd |
device |
path string | from INI | Override device path |
Speed presets accept both frequency labels and word aliases:
| Label | Alias | Frequency |
|---|---|---|
20kHz |
low |
20 kHz |
50kHz |
std50 |
50 kHz |
100kHz |
standard |
100 kHz |
200kHz |
std200 |
200 kHz |
400kHz |
fast |
400 kHz (default) |
750kHz |
high |
750 kHz |
1MHz |
fast1m |
1 MHz |
Raw enum integers 0–6 are also accepted.
# Open at 400 kHz targeting device at 0x50
CH347.I2C open speed=400kHz addr=0x50
# Open at 100 kHz using the "standard" alias
CH347.I2C open speed=standard addr=0x68
# Open at 1 MHz on an alternate device
CH347.I2C open speed=1MHz device=/dev/ch34xpis1
# Open at low speed (20 kHz) for long cables
CH347.I2C open speed=low
CH347.I2C close
CH347.I2C cfg [speed=PRESET] [addr=0xNN]
CH347.I2C cfg ?
# Change target address to TMP102 temperature sensor
CH347.I2C cfg addr=0x48
# Switch to fast mode
CH347.I2C cfg speed=400kHz
# Change both at once
CH347.I2C cfg speed=100kHz addr=0x68
# Print current config
CH347.I2C cfg ?
Sends a complete START + address+W + data + STOP sequence. The first byte of the data buffer is the device address with the write bit set (devAddr << 1), followed by register address and payload.
CH347.I2C write <HEXDATA>
# Write to device 0x50 (addr byte = 0xA0), register 0x00, data 0xFF
CH347.I2C write A000FF
# Write to device 0x68 (addr = 0xD0), register 0x07, value 0x10
CH347.I2C write D00710
# Write-only to configure a register on device 0x48
CH347.I2C write 9000
Reads N bytes from the device address configured via open or cfg. Performs START + addr+R + N bytes (ACK each except last NACK) + STOP.
CH347.I2C read <N>
# Read 2 bytes (e.g., a 16-bit sensor register)
CH347.I2C read 2
# Read a single byte
CH347.I2C read 1
# Read 8 bytes
CH347.I2C read 8
Performs a write phase, then a read phase. The read uses the device address from open/cfg. The write bytes are sent first; the read phase immediately follows. Read bytes are printed as a hex dump.
CH347.I2C wrrd <HEXDATA>:<rdlen>
# Write 0x50 (device addr+W) + reg 0x00, read 2 bytes
CH347.I2C wrrd A000:2
# TMP102 temperature read: write device addr+W + reg 0x00, read 2 bytes
CH347.I2C wrrd 9000:2
# Read-only: skip the write phase
CH347.I2C wrrd :4
# Write 3 bytes of config, read 1 byte status
CH347.I2C wrrd D007103A:1
CH347.I2C wrrdf <filename>[:<wrchunk>][:<rdchunk>]
CH347.I2C wrrdf i2c_init_sequence.bin
CH347.I2C wrrdf eeprom_write.bin:16:0
Probes every address in the standard 7-bit range 0x08–0x77 by sending a zero-length write and checking for ACK. A temporary driver is opened automatically if the I2C interface is not already open. Prints the address of every responding device.
CH347.I2C scan
CH347.I2C open speed=100kHz
CH347.I2C scan
# Output example:
# CH347_I2C | Found device at 0x48
# CH347_I2C | Found device at 0x50
# CH347_I2C | Found device at 0x68
High-level EEPROM access using the CH347I2C library's built-in read_eeprom() / write_eeprom() calls. Supports the full 24Cxx family.
CH347.I2C eeprom read <TYPE> <ADDR> <N>
CH347.I2C eeprom write <TYPE> <ADDR> <HEXDATA>
| TYPE index | EEPROM |
|---|---|
| 0 | 24C01 (128 bytes) |
| 1 | 24C02 (256 bytes) |
| 2 | 24C04 (512 bytes) |
| 3 | 24C08 (1 KB) |
| 4 | 24C16 (2 KB) |
| 5 | 24C32 (4 KB) |
| 6 | 24C64 (8 KB) |
| 7 | 24C128 (16 KB) |
| 8 | 24C256 (32 KB) |
# Read 16 bytes from a 24C04 (type 2) starting at address 0
CH347.I2C eeprom read 2 0 16
# Read the first 256 bytes of a 24C64 (type 6)
CH347.I2C eeprom read 6 0 256
# Write 4 bytes to a 24C02 (type 1) at address 0
CH347.I2C eeprom write 1 0 DEADBEEF
# Write a calibration constant to a 24C256 at offset 0x0100
CH347.I2C eeprom write 8 256 A5B6C7D8
# Write a string "HELLO" (ASCII hex) to 24C32 at address 0
CH347.I2C eeprom write 5 0 48454C4C4F
I2C must be open first.
CH347.I2C script <filename>
CH347.I2C script sensor_init.txt
CH347.I2C script eeprom_test.txt
8-pin GPIO interface (GPIO0–GPIO7). All commands use bitmasks where bit N corresponds to pin GPION. Must call open before use.
CH347.GPIO <subcommand> [arguments]
Opens the GPIO interface. All 8 pins default to inputs.
CH347.GPIO open [device=PATH]
CH347.GPIO open
CH347.GPIO open device=/dev/ch34xpis1
CH347.GPIO close
Sets which pins are outputs vs inputs. Bit N = 1 means pin GPION is an output.
CH347.GPIO dir output=0xNN
Also accepts a bare hex value.
# Set GPIO0–GPIO3 as outputs, GPIO4–GPIO7 as inputs
CH347.GPIO dir output=0x0F
# All 8 pins as outputs
CH347.GPIO dir output=0xFF
# All 8 pins as inputs (default state after open)
CH347.GPIO dir output=0x00
# GPIO0 and GPIO7 as outputs
CH347.GPIO dir output=0x81
# Bare form
CH347.GPIO dir 0x0F
Sets the levels of a masked set of pins using pins= (which pins to affect) and levels= (their desired state). The internal cached value is updated for subsequent toggle operations.
CH347.GPIO write pins=0xNN levels=0xNN
Also accepts a bare hex value to set all pins at once.
# Set GPIO0 and GPIO2 HIGH, GPIO1 and GPIO3 LOW (pins 0-3 affected)
CH347.GPIO write pins=0x0F levels=0x05
# Set all output pins to 0xAA (alternating high/low)
CH347.GPIO write pins=0xFF levels=0xAA
# Drive all pins LOW
CH347.GPIO write pins=0xFF levels=0x00
# Bare form: set all pins to 0x55
CH347.GPIO write 0x55
Drives the specified pins to logic high. Other pins are unchanged.
CH347.GPIO set pins=0xNN
Also accepts a bare hex value.
# Set GPIO0 high
CH347.GPIO set pins=0x01
# Set GPIO0–GPIO3 high
CH347.GPIO set pins=0x0F
# Set all pins high
CH347.GPIO set 0xFF
# Set GPIO7 high (e.g., LED on)
CH347.GPIO set pins=0x80
Drives the specified pins to logic low. Other pins are unchanged.
CH347.GPIO clear pins=0xNN
Also accepts a bare hex value.
# Clear GPIO0 low
CH347.GPIO clear pins=0x01
# Clear GPIO4–GPIO7 low
CH347.GPIO clear pins=0xF0
# Clear all pins
CH347.GPIO clear 0xFF
# Clear GPIO7 low (e.g., LED off)
CH347.GPIO clear pins=0x80
Inverts the state of the specified pins using a read-modify-write on the internally cached data value. No hardware read is performed.
CH347.GPIO toggle pins=0xNN
Also accepts a bare hex value.
# Toggle GPIO0
CH347.GPIO toggle pins=0x01
# Toggle all 8 output pins
CH347.GPIO toggle 0xFF
# Toggle GPIO0 and GPIO1 (blink two LEDs)
CH347.GPIO toggle pins=0x03
# Toggle GPIO7
CH347.GPIO toggle pins=0x80
Reads the current direction and data values from the hardware and prints them as hex plus a binary representation.
CH347.GPIO read
Output format:
CH347_GPIO | GPIO state: dir=0x0F data=0x05 [00000101]
dir— direction bitmask (1 = output)data— current pin levels (1 = high), both inputs and outputs[BBBBBBBB]— binary representation, bit 7 on the left (GPIO7), bit 0 on the right (GPIO0)
CH347.GPIO open
CH347.GPIO dir output=0x0F
CH347.GPIO set pins=0x05
CH347.GPIO read
# → dir=0x0F data=0x05 [00000101]
JTAG TAP interface. Must call open before any operation.
CH347.JTAG <subcommand> [arguments]
CH347.JTAG open [rate=0-5] [device=PATH]
| Parameter | Values | Default | Description |
|---|---|---|---|
rate |
0–5 | from INI / 2 |
Clock rate: 0 = slowest, 5 = fastest |
device |
path string | from INI | Override device path |
# Open at default rate (2)
CH347.JTAG open
# Open at high speed
CH347.JTAG open rate=5
# Open at slow speed for long cables or slow devices
CH347.JTAG open rate=0
# Open on alternate device at rate 4
CH347.JTAG open rate=4 device=/dev/ch34xpis1
CH347.JTAG close
CH347.JTAG cfg rate=0-5
CH347.JTAG cfg ?
CH347.JTAG cfg rate=3
CH347.JTAG cfg ?
Resets the JTAG TAP state machine. Either via a TMS sequence (5× TMS=1) or via the physical TRST pin.
CH347.JTAG reset
CH347.JTAG reset trst
| Argument | Mechanism |
|---|---|
| (none) | TAP reset via TMS sequence |
trst |
Assert TRST pin |
# Standard TAP reset (always safe to call before any JTAG sequence)
CH347.JTAG reset
# Assert TRST pin if connected
CH347.JTAG reset trst
Shifts data into the Instruction Register or Data Register. The IR/DR selection is optional and is remembered between calls (lastReg defaults to DR).
CH347.JTAG write [ir|dr] <HEXDATA>
# Load 0xFF into the Instruction Register
CH347.JTAG write ir FF
# Shift 4 bytes into the Data Register
CH347.JTAG write dr DEADBEEF
# Shift into DR again (omit register, uses last-used: DR)
CH347.JTAG write CAFEBABE
# Load a BYPASS instruction (all ones) into IR
CH347.JTAG write ir FFFFFFFF
# Load IDCODE instruction (0x01) into a 4-bit IR
CH347.JTAG write ir 01
Shifts N bytes out of the selected register while shifting in zeros. The received bytes are printed as a hex dump.
CH347.JTAG read [ir|dr] <N>
# Read 4 bytes from the Data Register (e.g., IDCODE)
CH347.JTAG read dr 4
# Read 1 byte from the Instruction Register
CH347.JTAG read ir 1
# Read using last-used register (defaults to DR)
CH347.JTAG read 4
Writes to the selected register and simultaneously (or sequentially) reads back data.
CH347.JTAG wrrd [ir|dr] <HEXDATA>:<rdlen>
# Write to DR and read back 4 bytes
CH347.JTAG wrrd dr DEADBEEF:4
# Load IDCODE instruction into IR and read 1 byte back
CH347.JTAG wrrd ir FF:1
# Write 2 bytes to DR, read back 2 bytes
CH347.JTAG wrrd dr A5B6:2
# Without register specifier (uses last-used)
CH347.JTAG wrrd DEADBEEF:4
JTAG must be open first.
CH347.JTAG script <filename>
CH347.JTAG script jtag_identify.txt
CH347.JTAG script boundary_scan.txt
CH347.JTAG script jtag_prog.txt
The CH347 SPI clock is set by a 3-bit index value (0 = fastest):
| Index | Frequency | Preset label |
|---|---|---|
| 0 | 60.000 MHz | 60MHz |
| 1 | 30.000 MHz | 30MHz |
| 2 | 15.000 MHz | 15MHz |
| 3 | 7.500 MHz | 7.5MHz |
| 4 | 3.750 MHz | 3.75MHz |
| 5 | 1.875 MHz | 1.875MHz |
| 6 | 937.500 kHz | 937kHz |
| 7 | 468.750 kHz | 468kHz (minimum) |
The spiHzToClockIndex() helper selects the fastest clock that does not exceed the requested frequency. For example, requesting clock=10000000 (10 MHz) selects index 2 (15 MHz would be too fast, so 7.5 MHz is actually chosen — the mapping rounds down to the next available preset).
| Enum value | Label | Alias | Frequency |
|---|---|---|---|
| 0 | 20kHz |
low |
20 kHz |
| 1 | 50kHz |
std50 |
50 kHz |
| 2 | 100kHz |
standard |
100 kHz |
| 3 | 200kHz |
std200 |
200 kHz |
| 4 | 400kHz |
fast |
400 kHz (power-on default) |
| 5 | 750kHz |
high |
750 kHz |
| 6 | 1MHz |
fast1m |
1 MHz |
Raw enum integers 0–6 are also accepted.
Script files are plain text files located under ARTEFACTS_PATH. They are executed by the CommScriptClient engine, which reads each line and performs send/receive/expect operations. The SCRIPT_DELAY INI key inserts a per-command delay in milliseconds.
Important: The corresponding interface must be open before calling script. Unlike the BusPirate plugin which re-opens the UART per script execution, this plugin passes the already-open driver handle directly to CommScriptClient.
CH347.SPI open clock=15000000
CH347.SPI script flash_read_id.txt
CH347.I2C open speed=400kHz addr=0x50
CH347.I2C script eeprom_dump.txt
CH347.JTAG open rate=2
CH347.JTAG script boundary_scan.txt
-
Dry-run mode: when
doEnable()has not been called, every command validates its arguments and returnstruewithout touching hardware. The generic dispatcher detectsisEnabled() == falseand returns early. This is used by test framework validators to check syntax before a live run. -
Fault-tolerant mode (
setFaultTolerant()/isFaultTolerant()): when set, the plugin framework can be configured to continue execution past command failures. Useful in production test scripts where a non-critical probe failure should not abort a longer sequence. -
Privileged mode (
isPrivileged()): always returnsfalse; reserved for future framework use.
Every handler returns bool:
true— success (or argument validation passed in disabled mode).false— argument validation failed, unknown sub-command, driver open failed, hardware operation returned an error status, or file not found.
All four module drivers return a typed Status enum (SUCCESS, and various error values). The plugin checks these and logs an error then returns false on any non-SUCCESS status.
Diagnostic messages are emitted via LOG_PRINT at several severity levels:
| Level | Usage |
|---|---|
LOG_ERROR |
Command failed, invalid argument, hardware error |
LOG_WARNING |
Non-fatal issue (e.g., closing a port that was not open) |
LOG_INFO |
Successful operations (bytes written, device opened, etc.) |
LOG_DEBUG |
Internal state changes |
LOG_VERBOSE |
INI parameter loading details |
LOG_FIXED |
Help text output |
Log verbosity is controlled by the host application via the shared uLogger configuration.