Skip to content

Commit

Permalink
serial port support for GNU/Linux
Browse files Browse the repository at this point in the history
  • Loading branch information
Nikita Orlov committed Jul 21, 2024
1 parent ef0c05a commit 1b820ff
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 32 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
isp55e0
isp55e0.o
39 changes: 21 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ ISP-55e0 - An ISP flashing tool for the WCH CH55x, CH57x and CH32Fx

This tool is meant to flash the WinChipHead CH55x / CH57x / CH32Fx
series, such as the CH551, CH552, CH554, CH559 or CH579, through USB
on Linux.
or serial port on Linux.

When set in ISP mode, the chip creates a 4348:55e0 USB device, hence
the name for that project.
Expand All @@ -25,23 +25,25 @@ supported tool, written in rust, and with a license.

isp55e0 has been tested on:

- a CH549 with a bootloader version 2.4.0
- a CH551 with a bootloader version 2.3.1
- a CH552 with a bootloader version 2.3.1
- a CH552 with a bootloader version 2.4.0
- a CH554 with a bootloader version 2.4.0
- a CH559 with a bootloader version 2.4.0
- a CH573 with a bootloader version 2.8.0
- a CH579 with a bootloader version 2.8.0
- a CH582 with a bootloader version 2.4.0
- a CH583 with a bootloader version 2.4.0
- a CH32F103C8T6 with a bootloader version 2.3.1
- a CH32F103C8T6 with a bootloader version 2.5.0
- a CH32V103C8T6 with a bootloader version 2.6.0
- a CH32V203C8T6 with a bootloader version 2.7.0
- a CH32V203G8R6 with a bootloader version 2.6.0
- a CH32V307VCT6 with a bootloader version 2.9.0
- a CH32X035F8U6 with a bootloader version 2.6.0
|Chip |Bootloader|USB|Serial|
|------------|----------|---|------|
|CH549 |2.4.0 |+ | |
|CH551 |2.3.1 |+ | |
|CH552 |2.3.1 |+ | |
|CH552 |2.4.0 |+ | |
|CH554 |2.4.0 |+ | |
|CH559 |2.4.0 |+ | |
|CH573 |2.8.0 |+ | |
|CH579 |2.8.0 |+ | |
|CH582 |2.4.0 |+ |+ |
|CH583 |2.4.0 |+ | |
|CH32F103C8T6|2.3.1 |+ | |
|CH32F103C8T6|2.5.0 |+ | |
|CH32V103C8T6|2.6.0 |+ | |
|CH32V203C8T6|2.7.0 |+ | |
|CH32V203G8R6|2.6.0 |+ | |
|CH32V307VCT6|2.9.0 |+ | |
|CH32X035F8U6|2.6.0 |+ | |


Build
Expand Down Expand Up @@ -90,6 +92,7 @@ Help:
ISP programmer for some WinChipHead MCUs
Options:
--port, -p use serial port instead of usb
--code-flash, -f firmware to flash
--code-verify, -c verify existing firwmare
--data-flash, -k data to flash
Expand Down
165 changes: 152 additions & 13 deletions isp55e0.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@

#else
#include <err.h>
#include <termios.h>
#include <sys/ioctl.h>
#endif

#ifdef __APPLE__
Expand All @@ -59,13 +61,19 @@ static const struct option long_options[] = {
{ "data-flash", required_argument, 0, 'k' },
{ "data-verify", required_argument, 0, 'l' },
{ "data-dump", required_argument, 0, 'm' },
#ifndef WIN32
{ "port", required_argument, 0, 'p' },
#endif
{ 0, 0, 0, 0 }
};

static void usage(void)
{
printf("ISP programmer for some WinChipHead MCUs\n");
printf("Options:\n");
#ifndef WIN32
printf(" --port, -p use serial port instead of usb\n");
#endif
printf(" --code-flash, -f firmware to flash\n");
printf(" --code-verify, -c verify existing firwmare\n");
printf(" --data-flash, -k data to flash\n");
Expand All @@ -90,6 +98,80 @@ static void hexdump(const char *name, const void *data, int len)
printf("\n");
}

#ifndef WIN32
static void open_serial_device(struct device *dev, char *port)
{
int ret;
struct termios options;
int status;
speed_t baud = B115200;

if ((dev->fd = open(port, O_RDWR | O_NOCTTY)) == -1)
errx(EXIT_FAILURE, "Error occured while opening serial port '%s'", port);

ret = fcntl(dev->fd, F_SETFL, O_RDWR) ;
if (ret < 0)
goto fail;

ret = tcgetattr(dev->fd, &options);
if (ret < 0)
goto fail;

cfmakeraw(&options);

ret = cfsetispeed(&options, baud);
if (ret < 0)
goto fail;

ret = cfsetospeed(&options, baud);
if (ret < 0)
goto fail;

options.c_cflag |= (CLOCAL | CREAD) ;
options.c_cflag &= ~(PARENB | CSTOPB | CSIZE) ;
options.c_cflag |= CS8 ;
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG) ;
options.c_oflag &= ~OPOST ;
/* Turn off s/w flow ctrl */
options.c_iflag &= ~(IXON | IXOFF | IXANY);
/* Disable any special handling of received bytes */
options.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL);

options.c_cc [VMIN] = 0;
options.c_cc [VTIME] = SERIAL_TIMEOUT;

ret = tcsetattr(dev->fd, TCSANOW, &options) ;
if (ret < 0)
goto fail;

ret = ioctl(dev->fd, TIOCMGET, &status);
if (ret < 0)
goto fail;

status |= TIOCM_DTR;
status |= TIOCM_RTS;

ret = ioctl(dev->fd, TIOCMSET, &status);
if (ret < 0)
goto fail;

return;
fail:
errx(EXIT_FAILURE, "Error occured while configuring serial port");
}

static unsigned char serial_crc(unsigned char *req, int req_len)
{
unsigned char crc = 0;

for(int i=0;i<req_len;i++){
crc += req[i];
}

return crc;
}
#endif

/* Open and claim the USB device */
static void open_usb_device(struct device *dev)
{
Expand Down Expand Up @@ -123,22 +205,62 @@ static int transfer(struct device *dev, void *req, int req_len,
{
int len;
int ret;
#ifndef WIN32
int serial_transmitted;
unsigned char req_serial_prefix[2] = {SERIAL_REQ_MAGIC1, SERIAL_REQ_MAGIC2};
unsigned char req_serial_crc[1] = {serial_crc(req, req_len)};
unsigned char resp_serial_prefix[2];
unsigned char resp_serial_crc[1];

if (dev->fd) {
/* Serial port case */
serial_transmitted = write(dev->fd, req_serial_prefix, sizeof(req_serial_prefix));
serial_transmitted += write(dev->fd, req, req_len);
serial_transmitted += write(dev->fd, req_serial_crc, sizeof(req_serial_crc));

if (serial_transmitted != (sizeof(req_serial_prefix)+req_len+sizeof(req_serial_crc)))
errx(EXIT_FAILURE, "Serial port write error");

if (dev->debug)
hexdump("request", req, req_len);

serial_transmitted = read(dev->fd, resp_serial_prefix, sizeof(resp_serial_prefix));
if (serial_transmitted != sizeof(resp_serial_prefix) || resp_serial_prefix[0] != SERIAL_RESP_MAGIC1 || resp_serial_prefix[1] != SERIAL_RESP_MAGIC2)
errx(EXIT_FAILURE, "Serial port response magic read error");

serial_transmitted = read(dev->fd, resp, resp_len);
if (serial_transmitted != resp_len)
errx(EXIT_FAILURE, "Serial port response read error");

serial_transmitted = read(dev->fd, resp_serial_crc, sizeof(resp_serial_crc));
if (serial_transmitted != sizeof(resp_serial_crc) || resp_serial_crc[0] != serial_crc(resp, resp_len))
errx(EXIT_FAILURE, "Serial port response crc read error");

ret = libusb_bulk_transfer(dev->usb_h, EP_OUT, req, req_len,
if (dev->debug)
hexdump("response", resp, resp_len);

} else {
#endif
/* USB case */
ret = libusb_bulk_transfer(dev->usb_h, EP_OUT, req, req_len,
&len, USB_TIMEOUT);
if (ret)
return -EIO;
if (ret)
return -EIO;

if (dev->debug)
hexdump("request", req, len);
if (dev->debug)
hexdump("request", req, len);

ret = libusb_bulk_transfer(dev->usb_h, EP_IN, resp, resp_len,
ret = libusb_bulk_transfer(dev->usb_h, EP_IN, resp, resp_len,
&len, USB_TIMEOUT);
if (ret)
return -EIO;
if (ret)
return -EIO;

if (dev->debug)
hexdump("response", resp, len);
if (dev->debug)
hexdump("response", resp, len);

#ifndef WIN32
}
#endif

return 0;
}
Expand Down Expand Up @@ -584,12 +706,18 @@ int main(int argc, char *argv[])
bool do_data_dump = false;
int c;
int i;
#ifndef WIN32
char *port = NULL;
#endif

while (1) {
int option_index = 0;

c = getopt_long(argc, argv, "c:df:hk:l:m:",
long_options, &option_index);
c = getopt_long(argc, argv, "c:df:hk:l:m:"
#ifndef WIN32
"p:"
#endif
, long_options, &option_index);
if (c == -1)
break;

Expand Down Expand Up @@ -625,6 +753,11 @@ int main(int argc, char *argv[])
dev.data_dump.filename = optarg;
do_data_dump = true;
break;
#ifndef WIN32
case 'p':
port = optarg;
break;
#endif
case 'h':
usage();
return EXIT_SUCCESS;
Expand All @@ -636,7 +769,13 @@ int main(int argc, char *argv[])
if (optind < argc)
errx(EXIT_FAILURE, "Extra argument: %s", argv[optind]);

open_usb_device(&dev);
#ifndef WIN32
if (port)
open_serial_device(&dev, port);
else
#endif
open_usb_device(&dev);

read_chip_type(&dev);
printf("Found device %s\n", dev.profile->name);

Expand Down
12 changes: 11 additions & 1 deletion isp55e0.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@

#define XOR_KEY_LEN 8

/* Serial port wrapper magics */
#define SERIAL_REQ_MAGIC1 (0x57)
#define SERIAL_REQ_MAGIC2 (0xAB)
#define SERIAL_RESP_MAGIC1 (0x55)
#define SERIAL_RESP_MAGIC2 (0xAA)

/* Profile of a specific CH device */
struct ch_profile {
const char *name;
Expand Down Expand Up @@ -43,10 +49,14 @@ struct device {
uint8_t config_data[12];
uint8_t xor_key[XOR_KEY_LEN];
bool wait_reboot_resp; /* wait for reboot command response */
#ifndef WIN32
int fd; /* serial port descriptor */
#endif
};

/* Enough to erase the flash. */
#define USB_TIMEOUT 5000
#define USB_TIMEOUT 5000 // milliseconds
#define SERIAL_TIMEOUT 50 // deciseconds

#define CMD_CHIP_TYPE 0xa1
#define CMD_REBOOT 0xa2
Expand Down
10 changes: 10 additions & 0 deletions protocol.txt
Original file line number Diff line number Diff line change
Expand Up @@ -221,3 +221,13 @@ Similar to above, except for a few things.
- After flashing the last 56-byte block (or less than), the bootloader
must be sent a write with no data. Failing to do so results in a
failed firmware check.





WCH chips expose similar protocol via UART1.
The only difference is prefix and crc:
0x57 0xAB for request
0x55 0xAA for response
CRC is just 8bit all bytes sum except prefix.

0 comments on commit 1b820ff

Please sign in to comment.