Skip to content

Instantly share code, notes, and snippets.

@981213
Last active February 18, 2023 07:02
Show Gist options
  • Select an option

  • Save 981213/19c716df4348e889fd21a9e8a8be2451 to your computer and use it in GitHub Desktop.

Select an option

Save 981213/19c716df4348e889fd21a9e8a8be2451 to your computer and use it in GitHub Desktop.
CH347 SPI library implemented with libusb
// SPDX-License-Identifier: BSD-1-Clause
/*
* Copyright (C) 2022 Chuanhong Guo <[email protected]>
*
* CH347 SPI library using libusb. Protocol reverse-engineered from WCH linux library.
* FIXME: Every numbers used in the USB protocol should be little-endian.
*/
#include <ch347.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int ch347_spi_write_packet(struct ch347_priv *priv, uint8_t cmd, const void *tx, int len) {
uint8_t *ptr;
int cur_len;
int err, transferred;
if (len > CH347_SPI_MAX_TRX)
return -EINVAL;
priv->tmpbuf[0] = cmd;
priv->tmpbuf[1] = len & 0xff;
priv->tmpbuf[2] = len >> 8;
cur_len = sizeof(priv->tmpbuf) - 3;
if (len < cur_len)
cur_len = len;
memcpy(priv->tmpbuf + 3, tx, cur_len);
err = libusb_bulk_transfer(priv->handle, CH347_EPOUT, priv->tmpbuf, cur_len + 3, &transferred, 1000);
if (err) {
fprintf(stderr, "ch347: libusb: failed to send packet: %d\n", err);
return err;
}
if (cur_len < len) {
/* This discards the const qualifier. However, libusb won't be writing to it. */
ptr = (uint8_t *) (tx + cur_len);
err = libusb_bulk_transfer(priv->handle, CH347_EPOUT, ptr, len - cur_len, &transferred, 1000);
if (err) {
fprintf(stderr, "ch347: libusb: failed to send packet: %d\n", err);
return err;
}
}
return 0;
}
int ch347_spi_read_packet(struct ch347_priv *priv, uint8_t cmd, void *rx, int len, int *actual_len) {
int cur_len, rxlen, rx_received;
int err, transferred;
err = libusb_bulk_transfer(priv->handle, CH347_EPIN, priv->tmpbuf, sizeof(priv->tmpbuf), &transferred, 1000);
if (err) {
fprintf(stderr, "ch347: libusb: failed to receive packet: %d\n", err);
return err;
}
if (priv->tmpbuf[0] != cmd) {
fprintf(stderr, "ch347: unexpected packet cmd: expecting 0x%02x but we got 0x%02x.\n", cmd, priv->tmpbuf[0]);
return -EINVAL;
}
rxlen = priv->tmpbuf[1] | priv->tmpbuf[2] << 8;
if (rxlen > len) {
fprintf(stderr, "ch347: packet too big.\n");
return -EINVAL;
}
cur_len = transferred - 3;
if (rxlen < cur_len)
cur_len = rxlen;
memcpy(rx, priv->tmpbuf + 3, cur_len);
rx_received = cur_len;
while (rx_received < rxlen) {
err = libusb_bulk_transfer(priv->handle, CH347_EPIN, priv->tmpbuf, sizeof(priv->tmpbuf), &transferred, 1000);
if (err) {
fprintf(stderr, "ch347: libusb: failed to receive packet: %d\n", err);
return err;
}
memcpy(rx + rx_received, priv->tmpbuf, transferred);
rx_received += transferred;
}
*actual_len = rx_received;
return 0;
}
int ch347_get_hw_config(struct ch347_priv *priv) {
int err, transferred;
uint8_t unknown_data = 0x01;
err = ch347_spi_write_packet(priv, CH347_CMD_INFO_RD, &unknown_data, 1);
if (err)
return err;
err = ch347_spi_read_packet(priv, CH347_CMD_INFO_RD, &priv->cfg, sizeof(priv->cfg), &transferred);
if (err)
return err;
if (transferred != sizeof(priv->cfg)) {
fprintf(stderr, "ch347: config returned isn't long enough.\n");
return -EINVAL;
}
return 0;
}
int ch347_commit_settings(struct ch347_priv *priv) {
int err, transferred;
uint8_t unknown_data;
err = ch347_spi_write_packet(priv, CH347_CMD_SPI_INIT, &priv->cfg, sizeof(priv->cfg));
if (err)
return err;
return ch347_spi_read_packet(priv, CH347_CMD_SPI_INIT, &unknown_data, 1, &transferred);
}
int ch347_set_cs(struct ch347_priv *priv, int cs, int val) {
uint8_t pkt_val = val ? 0xc0 : 0x80;
uint8_t buf[10] = {};
if (cs)
buf[5] = pkt_val;
else
buf[0] = pkt_val;
return ch347_spi_write_packet(priv, CH347_CMD_SPI_CONTROL, buf, 10);
}
int ch347_set_spi_freq(struct ch347_priv *priv, int *clk_khz) {
int freq = CH347_SPI_MAX_FREQ;
int prescaler;
for (prescaler = 0; prescaler < CH347_SPI_MAX_PRESCALER; prescaler++) {
if (freq <= *clk_khz)
break;
freq /= 2;
}
if (freq > *clk_khz)
return -EINVAL;
priv->cfg.SPI_BaudRatePrescaler = prescaler * 8;
*clk_khz = freq;
return ch347_commit_settings(priv);
}
int ch347_setup_spi(struct ch347_priv *priv, int spi_mode, bool lsb_first, bool cs0_active_high, bool cs1_active_high) {
priv->cfg.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
priv->cfg.SPI_Mode = SPI_Mode_Master;
priv->cfg.SPI_DataSize = SPI_DataSize_8b;
priv->cfg.SPI_CPOL = (spi_mode & 2) ? SPI_CPOL_High : SPI_CPOL_Low;
priv->cfg.SPI_CPHA = (spi_mode & 1) ? SPI_CPHA_2Edge : SPI_CPHA_1Edge;
priv->cfg.SPI_NSS = SPI_NSS_Software;
priv->cfg.SPI_FirstBit = lsb_first ? SPI_FirstBit_LSB : SPI_FirstBit_MSB;
priv->cfg.SPI_WriteReadInterval = 0;
priv->cfg.SPI_OutDefaultData = 0;
if (cs0_active_high)
priv->cfg.OtherCfg |= 0x80;
else
priv->cfg.OtherCfg &= 0x7f;
if (cs1_active_high)
priv->cfg.OtherCfg |= 0x40;
else
priv->cfg.OtherCfg &= 0xbf;
return ch347_commit_settings(priv);
}
static int ch347_spi_trx_full_duplex_one(struct ch347_priv *priv, void *buf, uint32_t len) {
int err, transferred;
err = ch347_spi_write_packet(priv, CH347_CMD_SPI_RD_WR, buf, len);
if (err)
return err;
err = ch347_spi_read_packet(priv, CH347_CMD_SPI_RD_WR, buf, len, &transferred);
if (err)
return err;
if (transferred != len) {
fprintf(stderr, "ch347: not enough data received.");
return -EINVAL;
}
return 0;
}
int ch347_spi_trx_full_duplex(struct ch347_priv *priv, void *buf, uint32_t len) {
int err;
while (len > CH347_SPI_MAX_TRX) {
err = ch347_spi_trx_full_duplex_one(priv, buf, CH347_SPI_MAX_TRX);
if (err)
return err;
len -= CH347_SPI_MAX_TRX;
}
return ch347_spi_trx_full_duplex_one(priv, buf, len);
}
int ch347_spi_tx(struct ch347_priv *priv, const void *tx, uint32_t len) {
int err, transferred;
uint8_t unknown_data;
const void *ptr = tx;
while (len) {
int cur_len = len > CH347_SPI_MAX_TRX ? CH347_SPI_MAX_TRX : len;
err = ch347_spi_write_packet(priv, CH347_CMD_SPI_BLCK_WR, ptr, cur_len);
if (err)
return err;
err = ch347_spi_read_packet(priv, CH347_CMD_SPI_BLCK_WR, &unknown_data, 1, &transferred);
if (err)
return err;
ptr += cur_len;
len -= cur_len;
}
return 0;
}
int ch347_spi_rx(struct ch347_priv *priv, void *rx, uint32_t len) {
int err, transferred;
void *ptr = rx;
uint32_t rxlen = 0;
/* FIXME: len should be little endian! */
err = ch347_spi_write_packet(priv, CH347_CMD_SPI_BLCK_RD, &len, sizeof(len));
if (err)
return err;
while(rxlen < len) {
uint32_t cur_rx = len - rxlen;
if(cur_rx > CH347_SPI_MAX_TRX)
cur_rx = CH347_SPI_MAX_TRX;
err = ch347_spi_read_packet(priv, CH347_CMD_SPI_BLCK_RD, ptr, (int)cur_rx, &transferred);
if (err)
return err;
rxlen += transferred;
ptr += transferred;
}
return 0;
}
struct ch347_priv *ch347_open() {
struct ch347_priv *priv = calloc(1, sizeof(struct ch347_priv));
int ret;
if (!priv) {
fprintf(stderr, "ch347: faied to allocate memory.\n");
return NULL;
}
ret = libusb_init(&priv->ctx);
if (ret < 0) {
perror("ch347: libusb: init");
goto ERR_0;
}
libusb_set_option(priv->ctx, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_INFO);
priv->handle = libusb_open_device_with_vid_pid(priv->ctx, CH347_SPI_VID, CH347_SPI_PID);
if (!priv->handle) {
perror("ch347: libusb: open");
goto ERR_1;
}
libusb_set_auto_detach_kernel_driver(priv->handle, 1);
ret = libusb_claim_interface(priv->handle, CH347_SPI_IF);
if (ret < 0) {
perror("ch347: libusb: claim_if");
goto ERR_2;
}
if (ch347_get_hw_config(priv))
goto ERR_3;
return priv;
ERR_3:
libusb_release_interface(priv->handle, CH347_SPI_IF);
ERR_2:
libusb_close(priv->handle);
ERR_1:
libusb_exit(priv->ctx);
ERR_0:
free(priv);
return NULL;
}
void ch347_close(struct ch347_priv *priv) {
libusb_release_interface(priv->handle, CH347_SPI_IF);
libusb_close(priv->handle);
libusb_exit(priv->ctx);
free(priv);
}
// SPDX-License-Identifier: BSD-1-Clause
/*
* Copyright (C) 2022 Chuanhong Guo <[email protected]>
*
* CH347 SPI library using libusb. Protocol reverse-engineered from WCH linux library.
* FIXME: Every numbers used in the USB protocol should be little-endian.
*/
#ifndef CH347_TEST_CH347_H
#define CH347_TEST_CH347_H
#include <endian.h>
#include <stdint.h>
#include <stdbool.h>
#include <libusb-1.0/libusb.h>
#define CH347_SPI_VID 0x1a86
#define CH347_SPI_PID 0x55db
#define CH347_SPI_IF 2
#define CH347_EPOUT (6 | LIBUSB_ENDPOINT_OUT)
#define CH347_EPIN (6 | LIBUSB_ENDPOINT_IN)
#define CH347_SPI_MAX_FREQ 60000
#define CH347_SPI_MAX_PRESCALER 7
#define CH347_SPI_MAX_TRX 4096
/* SPI_data_direction */
#define SPI_Direction_2Lines_FullDuplex 0x0000
#define SPI_Direction_2Lines_RxOnly 0x0400
#define SPI_Direction_1Line_Rx 0x8000
#define SPI_Direction_1Line_Tx 0xC000
/* SPI_mode */
#define SPI_Mode_Master 0x0104
#define SPI_Mode_Slave 0x0000
/* SPI_data_size */
#define SPI_DataSize_16b 0x0800
#define SPI_DataSize_8b 0x0000
/* SPI_Clock_Polarity */
#define SPI_CPOL_Low 0x0000
#define SPI_CPOL_High 0x0002
/* SPI_Clock_Phase */
#define SPI_CPHA_1Edge 0x0000
#define SPI_CPHA_2Edge 0x0001
/* SPI_Slave_Select_management */
#define SPI_NSS_Software 0x0200
#define SPI_NSS_Hardware 0x0000
/* SPI_MSB_LSB_transmission */
#define SPI_FirstBit_MSB 0x0000
#define SPI_FirstBit_LSB 0x0080
/* CH347 commands */
#define CH347_CMD_SPI_INIT 0xC0
#define CH347_CMD_SPI_CONTROL 0xC1
#define CH347_CMD_SPI_RD_WR 0xC2
#define CH347_CMD_SPI_BLCK_RD 0xC3
#define CH347_CMD_SPI_BLCK_WR 0xC4
#define CH347_CMD_INFO_RD 0xCA
struct ch347_spi_hw_config {
uint16_t SPI_Direction;
uint16_t SPI_Mode;
uint16_t SPI_DataSize;
uint16_t SPI_CPOL;
uint16_t SPI_CPHA;
uint16_t SPI_NSS; /* hardware or software managed CS */
uint16_t SPI_BaudRatePrescaler; /* prescaler = x * 8. x: 0=60MHz, 1=30MHz, 2=15MHz, 3=7.5MHz, 4=3.75MHz, 5=1.875MHz, 6=937.5KHz,7=468.75KHz */
uint16_t SPI_FirstBit; /* MSB or LSB first */
uint16_t SPI_CRCPolynomial; /* polynomial used for the CRC calculation. */
uint16_t SPI_WriteReadInterval; /* No idea what this is... Original comment from WCH: SPI接口常规读取写入数据命令(DEF_CMD_SPI_RD_WR)),单位为uS */
uint8_t SPI_OutDefaultData; /* Data to output on MOSI during SPI reading */
/*
* Miscellaneous settings:
* Bit 7: CS0 polarity
* Bit 6: CS1 polarity
* Bit 5: Enable I2C clock stretching
* Bit 4: NACK on last I2C reading
* Bit 3-0: reserved
*/
uint8_t OtherCfg;
uint8_t Reserved[4];
};
struct ch347_priv {
struct ch347_spi_hw_config cfg;
libusb_context *ctx;
libusb_device_handle *handle;
uint8_t tmpbuf[512];
};
struct ch347_priv *ch347_open();
void ch347_close(struct ch347_priv *priv);
int ch347_commit_settings(struct ch347_priv *priv);
int ch347_set_cs(struct ch347_priv *priv, int cs, int val);
int ch347_set_spi_freq(struct ch347_priv *priv, int *clk_khz);
int ch347_setup_spi(struct ch347_priv *priv, int spi_mode, bool lsb_first, bool cs0_active_high, bool cs1_active_high);
int ch347_spi_trx_full_duplex(struct ch347_priv *priv, void *buf, uint32_t len);
int ch347_spi_tx(struct ch347_priv *priv, const void *tx, uint32_t len);
int ch347_spi_rx(struct ch347_priv *priv, void *rx, uint32_t len);
#endif //CH347_TEST_CH347_H
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment