Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

serial port support for GNU/Linux #15

Merged
merged 1 commit into from
Jul 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer something less verbose:
c = getopt_long(argc, argv, "c:df:hk:l:m:"
#ifndef WIN32
"p:"
#endif
, long_options .....

, 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.