Skip to content

Commit

Permalink
Support for serial port "touch" functionality using libserialport (#1507
Browse files Browse the repository at this point in the history
)

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 <[email protected]>
  • Loading branch information
MCUdude and stefanrueger authored Oct 15, 2023
1 parent 331d46f commit 99a9a60
Show file tree
Hide file tree
Showing 11 changed files with 124 additions and 21 deletions.
9 changes: 9 additions & 0 deletions src/avrdude.1
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
9 changes: 9 additions & 0 deletions src/doc/avrdude.texi
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 3 additions & 0 deletions src/libavrdude.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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)
Expand Down Expand Up @@ -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);
Expand Down
37 changes: 25 additions & 12 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,8 @@ static void usage(void)
" -D Disable auto erase for flash memory; implies -A\n"
" -i <delay> ISP Clock Delay [in microseconds]\n"
" -P <port> 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"
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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;
Expand All @@ -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 */
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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);
Expand All @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/ser_avrdoper.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
7 changes: 6 additions & 1 deletion src/ser_posix.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions src/ser_win32.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
74 changes: 66 additions & 8 deletions src/serialadapter.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include "avrdude.h"
#include "libavrdude.h"
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}
Expand All @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions src/usb_hidapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions src/usb_libusb.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down
1 change: 1 addition & 0 deletions src/xbee.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit 99a9a60

Please sign in to comment.