Answer, thanks to commenters:
There are some terminal settings that default to values not consistent with raw, unfiltered serial transfer. Adding the code below fixed the problem. I'm sure I'm setting/clearing more things than I need.
struct termios options;
tcgetattr(fd, &options);
cfsetispeed(&options, B115200);
cfsetospeed(&options, B115200);
options.c_iflag &= ~(IXOFF | IXANY | BRKINT | ICRNL | INLCR | PARMRK | INPCK | ISTRIP | IXON);
options.c_cflag &= ~(CRTSCTS | CLOCAL | CSIZE | PARENB | CSTOPB);
options.c_cflag |= CREAD | CS8;
options.c_lflag &= ~(ICANON | ISIG | ECHO | ECHOE | ECHONL | IEXTEN);
options.c_oflag &= ~(OPOST);
options.c_cc[VMIN] = 1;
options.c_cc[VTIME] = 0;
tcsetattr(fd, TCSANOW, &options);
Original question:
I have a device that I want to connect to my Nvidia Jetson Orin Nano, which is running Ubuntu 20.04. It requires TTL-level serial communication, and I'm using an appropriate USB-to-serial cable. I also have my user added to the appropriate group for access to serial devices.
To start with, I can successfully access the serial device using cu. This command works:
cu -f -s 115200 -l /dev/ttyUSB0
So next I'm tying to write my own C program to access the same device.
#include <iostream>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <stdio.h>
int main() {
int r;
int fd = open("/dev/ttyUSB0", O_RDWR | O_NOCTTY);
if (fd == -1) {
perror("tty ");
return 1;
}
// Get current serial port options
struct termios options;
r = tcgetattr(fd, &options);
if (r < 0) {
perror("tcgetattr ");
return 1;
}
// Set baud rate to 115200
cfsetispeed(&options, B115200);
cfsetospeed(&options, B115200);
// Disable flow control (CTS and RTS)
options.c_cflag &= ~CRTSCTS;
// Set new serial port options
r = tcsetattr(fd, TCSANOW, &options);
if (r < 0) {
perror("tcsetattr ");
return 1;
}
char buf;
while (1) {
r = read(fd, &buf, 1);
if (r < 0) {
perror("read ");
return 1;
}
printf("%02x\n", 255 & buf);
}
close(fd);
return 0;
}
Unfortunately, this program doesn't work. The open succeeds. The tcgetattr and tcsetattr succeed. But the read just sits there and does nothing.
I ran an strace on cu to see what was different (see paste of part of the trace below), but aside from checking for a lock file (which doesn't exist), I can't tell what could possibly be relevant here.
Anyone have any ideas as to what I'm doing wrong?
Thanks!
[Mostly what precedes this is things like dynamically linking shared object libraries, setting signal handlers, closing already-closed fd's, and trying to access various files that don't exist.]
getpid() = 17612
openat(AT_FDCWD, "/var/lock/TMP00000044cc", O_WRONLY|O_CREAT|O_TRUNC, 0644) = 3
write(3, " 17612\n", 11) = 11
close(3) = 0
linkat(AT_FDCWD, "/var/lock/TMP00000044cc", AT_FDCWD, "/var/lock/LCK..ttyUSB0", 0) = 0
unlinkat(AT_FDCWD, "/var/lock/TMP00000044cc", 0) = 0
geteuid() = 1000
getuid() = 1000
getegid() = 1000
getgid() = 1000
setregid(1000, 1000) = 0
setreuid(1000, 1000) = 0
openat(AT_FDCWD, "/dev/ttyUSB0", O_RDWR|O_NONBLOCK) = 3
geteuid() = 1000
getuid() = 1000
getegid() = 1000
getgid() = 1000
setregid(1000, 1000) = 0
setreuid(1000, 1000) = 0
fcntl(3, F_GETFD) = 0
fcntl(3, F_SETFD, FD_CLOEXEC) = 0
faccessat(AT_FDCWD, "/dev/ttyUSB0", R_OK|W_OK) = 0
fcntl(3, F_GETFL) = 0x20802 (flags O_RDWR|O_NONBLOCK|O_LARGEFILE)
fcntl(3, F_SETFL, O_RDWR|O_LARGEFILE) = 0
ioctl(3, TCGETS, {B115200 -opost -isig -icanon -echo ...}) = 0
ioctl(3, TCFLSH, TCIFLUSH) = 0
ioctl(3, TCGETS, {B115200 -opost -isig -icanon -echo ...}) = 0
ioctl(3, SNDCTL_TMR_START or TCSETS, {B115200 -opost -isig -icanon -echo ...}) = 0
ioctl(3, TCGETS, {B115200 -opost -isig -icanon -echo ...}) = 0
ioctl(3, TIOCSCTTY, 0) = -1 EPERM (Operation not permitted)
ioctl(3, TCGETS, {B115200 -opost -isig -icanon -echo ...}) = 0
ioctl(3, SNDCTL_TMR_START or TCSETS, {B115200 -opost -isig -icanon -echo ...}) = 0
ioctl(3, TCGETS, {B115200 -opost -isig -icanon -echo ...}) = 0
ioctl(3, TCGETS, {B115200 -opost -isig -icanon -echo ...}) = 0
ioctl(3, SNDCTL_TMR_STOP or TCSETSW, {B115200 -opost -isig -icanon -echo ...}) = 0
ioctl(3, TCGETS, {B115200 -opost -isig -icanon -echo ...}) = 0
faccessat(AT_FDCWD, "/dev/ttyUSB0", R_OK|W_OK) = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x2), ...}) = 0
write(1, "\7Connected.\n", 12) = 12
ioctl(0, TCGETS, {B38400 opost isig icanon echo ...}) = 0
ioctl(0, TCGETS, {B38400 opost isig icanon echo ...}) = 0
ioctl(0, SNDCTL_TMR_START or TCSETS, {B38400 -opost -isig -icanon -echo ...}) = 0
ioctl(0, TCGETS, {B38400 -opost -isig -icanon -echo ...}) = 0
pipe2([4, 5], 0) = 0
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0xffffa25c8d00) = 17613
close(5) = 0
read(0, 0xffffe67adad6, 1) = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGTERM {si_signo=SIGTERM, si_code=SI_USER, si_pid=17614, si_uid=1000} ---
rt_sigreturn({mask=[]}) = -1 EINTR (Interrupted system call)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=17613, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
ioctl(0, TCGETS, {B38400 -opost -isig -icanon -echo ...}) = 0
ioctl(0, SNDCTL_TMR_START or TCSETS, {B38400 opost isig icanon echo ...}) = 0
ioctl(0, TCGETS, {B38400 opost isig icanon echo ...}) = 0
write(2, "cu: Got termination signal\n", 27) = 27
ioctl(0, TCGETS, {B38400 opost isig icanon echo ...}) = 0
ioctl(0, TCGETS, {B38400 opost isig icanon echo ...}) = 0
ioctl(0, SNDCTL_TMR_START or TCSETS, {B38400 -opost -isig -icanon -echo ...}) = 0
ioctl(0, TCGETS, {B38400 -opost -isig -icanon -echo ...}) = 0
close(4) = 0