From 99a9a606080652a9d676df07be15e95359792a10 Mon Sep 17 00:00:00 2001 From: Hans Date: Sun, 15 Oct 2023 17:42:52 +0200 Subject: [PATCH] Support for serial port "touch" functionality using libserialport (#1507) This makes it possible to upload to boards like the Arduino Leonardo and Arduino Nano Every wwithout using an external tool to enter bootloader mode Co-authored-by: Stefan Rueger --- src/avrdude.1 | 9 ++++++ src/doc/avrdude.texi | 9 ++++++ src/libavrdude.h | 3 ++ src/main.c | 37 +++++++++++++++------- src/ser_avrdoper.c | 1 + src/ser_posix.c | 7 ++++- src/ser_win32.c | 1 + src/serialadapter.c | 74 +++++++++++++++++++++++++++++++++++++++----- src/usb_hidapi.c | 1 + src/usb_libusb.c | 2 ++ src/xbee.c | 1 + 11 files changed, 124 insertions(+), 21 deletions(-) diff --git a/src/avrdude.1 b/src/avrdude.1 index f1e352913..0be3994df 100644 --- a/src/avrdude.1 +++ b/src/avrdude.1 @@ -29,6 +29,7 @@ .Nm .Fl p Ar partno .Op Fl b Ar baudrate +.Op Fl r .Op Fl B Ar bitclock .Op Fl c Ar programmer-id .Op Fl C Ar config-file @@ -365,6 +366,14 @@ for more information run avrdude -p x/h. .It Fl b Ar baudrate Override the RS-232 connection baud rate specified in the respective programmer's entry of the configuration file. +.It Fl r +Opens the serial port at 1200 baud and immediately closes it, waits 400 ms +for each -r on the command line and then establishes communication with +the programmer. This is commonly known as a "1200bps touch", and is used +to trigger programming mode for certain boards like Arduino Leonardo, +Arduino Micro/Pro Micro and the Arduino Nano Every. Longer waits, and +therefore multiple -r options, are sometimes needed for slower, less +powerful hosts. .It Fl B Ar bitclock Specify the bit clock period for the JTAG, PDI, TPI, UPDI, or ISP interface. The value is a floating-point number in microseconds. diff --git a/src/doc/avrdude.texi b/src/doc/avrdude.texi index f71598ba2..60d5c6e42 100644 --- a/src/doc/avrdude.texi +++ b/src/doc/avrdude.texi @@ -469,6 +469,15 @@ ATmega328P MCU properties; for more information run @code{avrdude -p x/h}. Override the RS-232 connection baud rate specified in the respective programmer's entry of the configuration file. +@item -r +Opens the serial port at 1200 baud and immediately closes it, waits 400 ms +for each @code{-r} on the command line and then establishes communication +with the programmer. This is commonly known as a "1200bps touch", and is +used to trigger programming mode for certain boards like Arduino Leonardo, +Arduino Micro/Pro Micro and the Arduino Nano Every. Longer waits, and +therefore multiple @code{-r} options, are sometimes needed for slower, less +powerful hosts. + @item -B @var{bitclock} Specify the bit clock period for the JTAG, PDI, TPI, UPDI, or ISP interface. The value is a floating-point number in microseconds. diff --git a/src/libavrdude.h b/src/libavrdude.h index 0f9d59380..85d1e59cd 100644 --- a/src/libavrdude.h +++ b/src/libavrdude.h @@ -668,6 +668,7 @@ struct serial_device { int (*open)(const char *port, union pinfo pinfo, union filedescriptor *fd); int (*setparams)(const union filedescriptor *fd, long baud, unsigned long cflags); void (*close)(union filedescriptor *fd); + void (*rawclose)(union filedescriptor *fd); // Don't restore terminal attributes (Linux) int (*send)(const union filedescriptor *fd, const unsigned char * buf, size_t buflen); int (*recv)(const union filedescriptor *fd, unsigned char * buf, size_t buflen); @@ -691,6 +692,7 @@ extern struct serial_device usbhid_serdev; #define serial_open (serdev->open) #define serial_setparams (serdev->setparams) #define serial_close (serdev->close) +#define serial_rawclose (serdev->rawclose) #define serial_send (serdev->send) #define serial_recv (serdev->recv) #define serial_drain (serdev->drain) @@ -1252,6 +1254,7 @@ extern "C" { int setport_from_serialadapter(char **portp, const SERIALADAPTER *ser, const char *sernum); int setport_from_vid_pid(char **portp, int vid, int pid, const char *sernum); int list_available_serialports(LISTID programmers); +int touch_serialport(char **portp, int baudrate, int nwaits); int str_starts(const char *str, const char *starts); int str_eq(const char *str1, const char *str2); diff --git a/src/main.c b/src/main.c index 5b5eb6995..21dccd287 100644 --- a/src/main.c +++ b/src/main.c @@ -237,6 +237,8 @@ static void usage(void) " -D Disable auto erase for flash memory; implies -A\n" " -i ISP Clock Delay [in microseconds]\n" " -P Connection; -P ?s or -P ?sa lists serial ones\n" + " -r Reconnect to -P port after \"touching\" it; wait\n" + " 400 ms for each -r; needed for some USB boards\n" " -F Override invalid signature or initial checks\n" " -e Perform a chip erase\n" " -O Perform RC oscillator calibration (see AVR053)\n" @@ -526,6 +528,7 @@ int main(int argc, char * argv []) char * e; /* for strtod() error checking */ const char *errstr; /* for str_int() error checking */ int baudrate; /* override default programmer baud rate */ + int touch_1200bps; /* "touch" serial port prior to programming */ double bitclock; /* Specify programmer bit clock (JTAG ICE) */ int ispdelay; /* Specify the delay for ISP clock */ int init_ok; /* Device initialization worked well */ @@ -612,6 +615,7 @@ int main(int argc, char * argv []) explicit_e = 0; verbose = 0; baudrate = 0; + touch_1200bps = 0; bitclock = 0.0; ispdelay = 0; is_open = 0; @@ -635,7 +639,7 @@ int main(int argc, char * argv []) /* * process command line arguments */ - while ((ch = getopt(argc,argv,"?Ab:B:c:C:DeE:Fi:l:np:OP:qstT:U:uvVx:yY:")) != -1) { + while ((ch = getopt(argc,argv,"?Ab:B:c:C:DeE:Fi:l:np:OP:qrstT:U:uvVx:yY:")) != -1) { switch (ch) { case 'b': /* override default programmer baud rate */ @@ -766,6 +770,10 @@ int main(int argc, char * argv []) quell_progress++ ; break; + case 'r' : + touch_1200bps++; + break; + case 't': /* enter terminal mode */ ladd(updates, cmd_update("interactive terminal")); break; @@ -1175,6 +1183,13 @@ int main(int argc, char * argv []) } } + if(port[0] == 0 || str_eq(port, "unknown")) { + msg_error("\n"); + pmsg_error("no port has been specified on the command line or in the config file\n"); + imsg_error("specify a port using the -P option and try again\n\n"); + exit(1); + } + /* * Divide a serialadapter port string into tokens separated by colons. * There are two ways such a port string can be presented: @@ -1229,18 +1244,12 @@ int main(int argc, char * argv []) } for (int i = 0; i < 4; i++) free(port_tok[i]); + if(touch_1200bps && touch_serialport(&port, 1200, touch_1200bps) < 0) + goto skipopen; } - /* - * open the programmer - */ - if (port[0] == 0) { - msg_error("\n"); - pmsg_error("no port has been specified on the command line or in the config file\n"); - imsg_error("specify a port using the -P option and try again\n\n"); - exit(1); - } + // Open the programmer if (verbose > 0) { imsg_notice("Using Port : %s\n", port); imsg_notice("Using Programmer : %s\n", pgmid); @@ -1266,9 +1275,13 @@ int main(int argc, char * argv []) rc = pgm->open(pgm, port); if (rc < 0) { - pmsg_error("unable to open programmer %s on port %s\n", pgmid, port); - if (print_ports && pgm->conntype == CONNTYPE_SERIAL) + pmsg_error("unable to open port %s for programmer %s\n", port, pgmid); +skipopen: + if (print_ports && pgm->conntype == CONNTYPE_SERIAL) { list_available_serialports(programmers); + if(touch_1200bps == 1) + pmsg_info("alternatively, try -rr or -rrr for longer delays\n"); + } exitrc = 1; pgm->ppidata = 0; /* clear all bits at exit */ goto main_exit; diff --git a/src/ser_avrdoper.c b/src/ser_avrdoper.c index ca46ca58e..937909a47 100644 --- a/src/ser_avrdoper.c +++ b/src/ser_avrdoper.c @@ -352,6 +352,7 @@ struct serial_device avrdoper_serdev = { .open = avrdoper_open, .close = avrdoper_close, + .rawclose = avrdoper_close, .send = avrdoper_send, .recv = avrdoper_recv, .drain = avrdoper_drain, diff --git a/src/ser_posix.c b/src/ser_posix.c index b0bab284d..aec8ba5c5 100644 --- a/src/ser_posix.c +++ b/src/ser_posix.c @@ -352,7 +352,6 @@ static int net_open(const char *port, union filedescriptor *fdp) { return ret; } - static int ser_set_dtr_rts(const union filedescriptor *fdp, int is_on) { unsigned int ctl; int r; @@ -431,6 +430,11 @@ static void ser_close(union filedescriptor *fd) { close(fd->ifd); } +// Close but don't restore attributes +static void ser_rawclose(union filedescriptor *fd) { + saved_original_termios = 0; + close(fd->ifd); +} static int ser_send(const union filedescriptor *fd, const unsigned char * buf, size_t buflen) { int rc; @@ -598,6 +602,7 @@ struct serial_device serial_serdev = .open = ser_open, .setparams = ser_setparams, .close = ser_close, + .rawclose = ser_rawclose, .send = ser_send, .recv = ser_recv, .drain = ser_drain, diff --git a/src/ser_win32.c b/src/ser_win32.c index fc59b9caa..b7480ac2f 100644 --- a/src/ser_win32.c +++ b/src/ser_win32.c @@ -756,6 +756,7 @@ struct serial_device serial_serdev = .open = ser_open, .setparams = ser_setparams, .close = ser_close, + .rawclose = ser_close, .send = ser_send, .recv = ser_recv, .drain = ser_drain, diff --git a/src/serialadapter.c b/src/serialadapter.c index b57f679ec..9f785458e 100644 --- a/src/serialadapter.c +++ b/src/serialadapter.c @@ -21,6 +21,7 @@ #include #include #include +#include #include "avrdude.h" #include "libavrdude.h" @@ -130,6 +131,13 @@ static SERPORT *get_libserialport_data(int *np) { return sp; } +// Free memory allocated from get_libserialport_data() +static void free_libserialport_data(SERPORT *sp, int n) { + for(int k = 0; sp && k < n; k++) + free(sp[k].sernum), free(sp[k].port); + free(sp); +} + // Returns a NULL-terminated malloc'd list of items in SERPORT list spa that are not in spb SERPORT **sa_spa_not_spb(SERPORT *spa, int na, SERPORT *spb, int nb) { SERPORT **ret = cfg_malloc(__func__, (na+1)*sizeof*ret); @@ -266,10 +274,7 @@ int setport_from_serialadapter(char **portp, const SERIALADAPTER *sea, const cha if(m == 0 || sa_num_matches_by_sea(sea, sernum, sp+i, 1) == 1) sa_print_specs(sp, n, i); } - - for(int k = 0; k < n; k++) - free(sp[k].sernum), free(sp[k].port); - free(sp); + free_libserialport_data(sp, n); return rv; } @@ -294,14 +299,62 @@ int setport_from_vid_pid(char **portp, int vid, int pid, const char *sernum) { if(m == 0 || sa_num_matches_by_ids(vid, pid, sernum, sp+i, 1) == 1) sa_print_specs(sp, n, i); } - - for(int k = 0; k < n; k++) - free(sp[k].sernum), free(sp[k].port); - free(sp); + free_libserialport_data(sp, n); return rv; } +// Potentially change port *portp after opening & closing it with baudrate +int touch_serialport(char **portp, int baudrate, int nwaits) { + int i, n1, n2; + SERPORT *sp1, *sp2, **diff; + sp1 = get_libserialport_data(&n1); + if(!sp1 || n1 <= 0 || !portp) + return -1; + + pmsg_info("touching serial port %s at %d baud\n", *portp, baudrate); + + union pinfo pinfo; + union filedescriptor fd; + pinfo.serialinfo.baud = baudrate; + pinfo.serialinfo.cflags = SERIAL_8N1; + if(serial_open(*portp, pinfo, &fd) == -1) { + pmsg_error("%s() failed to open port %s at %d baud\n", __func__, *portp, baudrate); + return -1; + } + serial_set_dtr_rts(&fd, 1); + usleep(100); + serial_set_dtr_rts(&fd, 0); + serial_rawclose(&fd); + + const int nloops = 32, nap = 50; +#if (defined(__arm__) || defined(__aarch64__)) && !defined(__APPLE__) + nwaits += 2; +#endif + pmsg_info("waiting for new port..."); + usleep(400*1000*nwaits); + for(i = nloops; i > 0; i--) { + usleep(nap*1000); + if((sp2 = get_libserialport_data(&n2))) { + diff = sa_spa_not_spb(sp2, n2, sp1, n1); + if(*diff && diff[0]->port && !diff[1]) { // Exactly one new port sprung up + pmsg_notice("new port %s discovered\n", (*diff)->port); + if(*portp) + free(*portp); + *portp = cfg_strdup(__func__, (*diff)->port); + msg_info(" %d ms:", (nloops-i+1)*nap + nwaits*400); + i = -1; // Leave loop + } + free(diff); + free_libserialport_data(sp2, n2); + } + } + free_libserialport_data(sp1, n1); + msg_info(" using %s port %s\n", i<0? "new": "same", *portp); + + return 0; +} + // List available serial ports int list_available_serialports(LISTID programmers) { // Get serial port information from libserialport @@ -344,6 +397,11 @@ int list_available_serialports(LISTID programmers) { return -1; } +int touch_serialport(char **portp, int baudrate, int nwaits) { + pmsg_error("avrdude built without libserialport support; please compile again with libserialport installed\n"); + return -1; +} + #endif void list_serialadapters(FILE *fp, const char *prefix, LISTID programmers) { diff --git a/src/usb_hidapi.c b/src/usb_hidapi.c index 34d768223..c7021608a 100644 --- a/src/usb_hidapi.c +++ b/src/usb_hidapi.c @@ -318,6 +318,7 @@ static int usbhid_drain(const union filedescriptor *fd, int display) { struct serial_device usbhid_serdev = { .open = usbhid_open, .close = usbhid_close, + .rawclose = usbhid_close, .send = usbhid_send, .recv = usbhid_recv, .drain = usbhid_drain, diff --git a/src/usb_libusb.c b/src/usb_libusb.c index a7847b357..20ef605ed 100644 --- a/src/usb_libusb.c +++ b/src/usb_libusb.c @@ -578,6 +578,7 @@ struct serial_device usb_serdev = { .open = usbdev_open, .close = usbdev_close, + .rawclose = usbdev_close, .send = usbdev_send, .recv = usbdev_recv, .drain = usbdev_drain, @@ -591,6 +592,7 @@ struct serial_device usb_serdev_frame = { .open = usbdev_open, .close = usbdev_close, + .rawclose = usbdev_close, .send = usbdev_send, .recv = usbdev_recv_frame, .drain = usbdev_drain, diff --git a/src/xbee.c b/src/xbee.c index 0aaf04693..52b3c5df9 100644 --- a/src/xbee.c +++ b/src/xbee.c @@ -1505,6 +1505,7 @@ static int xbeedev_set_dtr_rts(const union filedescriptor *fdp, int is_on) static struct serial_device xbee_serdev_frame = { .open = xbeedev_open, .close = xbeedev_close, + .rawclose = xbeedev_close, .send = xbeedev_send, .recv = xbeedev_recv, .drain = xbeedev_drain,