Last active
February 18, 2023 07:02
-
-
Save 981213/19c716df4348e889fd21a9e8a8be2451 to your computer and use it in GitHub Desktop.
CH347 SPI library implemented with libusb
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 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); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 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