I'm having some issues getting data from a serial port using C on my Ubuntu 12 system.
I'm using open() and read(), and here is my code:
Fd = open("/dev/ttyUSB0", O_RDONLY | O_NOCTTY);
if (Fd == -1) {
printf("Could not open serial port: %s\n", strerror(errno));
return 1;
}
fcntl(Fd, F_SETFL, 0);
char buf;
while (1) {
read(Fd, &buf, 1);
printf("%c", buf);
}
However - my serial device is set to send "Boot.\r\n" followed by "To send: ", but when I attach the device and start the program, I only get the first line ("Boot.") and then no more. If I start gtkterm/picocom, I get both lines straight away.
I've also tried adding a signal handler for SIGTERM to get the port closed properly, using:
void signal_callback_handler(int signum) {
printf("Caught SIGTERM\n");
close(Fd);
exit(signum);
}
and
signal(SIGINT, signal_callback_handler);
Using this, I get the following when I press CTRL-C:
Boot.
^CTo send: Caught SIGTERM
I've also tried setting up the port first, using:
struct termios port_settings; // structure to store the port settings in
cfsetispeed(&port_settings, B115200); // set baud rates
cfsetospeed(&port_settings, B115200);
port_settings.c_cflag &= ~PARENB; // set no parity, stop bits, data bits
port_settings.c_cflag &= ~CSTOPB;
port_settings.c_cflag &= ~CSIZE;
port_settings.c_cflag |= CS8;
tcsetattr(Fd, TCSANOW, &port_settings);// apply the settings to the port
This only makes the situation worse - I get spammed with � :(
I'd be very appreciative of any help, thanks in advance!
Looks like your printf is just not being flushed until it hits a newline. That's why you get the first part of the output, but not the second. You could add fflush(stdout) after your printf to see the output immediately.
Related
I need to configure the UART settings such that the read() call remains blocked until a certain time before it's unblocked again if it didn't receive any data within the timeout. So if the timeout is 5 seconds, it remains blocked till 5 seconds max if it doesn't receive any byte and then unblocks...
I tried using VMIN which should block the read() call until no character is read within the time allowed, after which the call to read() returns 0, but that doesn't seem to be the case for me: the read() call remains blocked forever and as soon as I enter stuff in a minicom serial session, read() unblocks and then goes back to getting blocked again.
I'm not sure if it's taking into account VTIME setting, or maybe I'm misconfiguring it.
Rather, would select() with a timeout be a better approach?
#define SERIAL_PORT "/dev/ttyUSB4"
pthread_t td;
int fd;
int SerialOpen()
{
struct termios term;
fd = open(SERIAL_PORT, O_RDWR);
if (fd < 0)
{
perror ("failed to open");
return -1;
}
bzero(&term, sizeof(term));
cfmakeraw(&term);
term.c_cflag |= CREAD;
tcgetattr(fd, &term);
term.c_iflag &= ~ICRNL;
term.c_iflag &= ~INLCR;
term.c_iflag &= ~IGNBRK;
term.c_oflag &= ~OCRNL;
term.c_oflag &= ~ONLCR;
term.c_oflag &= ~OPOST;
term.c_lflag &= ~ICANON;
term.c_lflag &= ~ISIG;
term.c_lflag &= ~IEXTEN;
term.c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHONL|ECHOCTL|ECHOPRT|ECHOKE);
cfsetspeed(term, B115200); // set baud rate
term.c_cflag &= ~CSTOPB;
term.c_cflag |= CS8;
// disable flow control
term.c_cflag &= ~CRTSCTS;
term.c_iflag &= ~(IXON | IXOFF);
term.c_cflag &= ~PARENB; // no parity
term.c_cc[VTIME] = 50; // Wait for up to 5s (50 deciseconds), returning as soon as any data is received.
term.c_cc[VMIN] = 0;
if ( (tcsetattr(fd, TCSANOW, &term)) < 0)
{
perror ("Failed to set attr");
return -1;
}
return 1;
}
void *Rx(void *arg)
{
char buff[100] = {0};
while(1)
{
int sz = read(fd, buff, sizeof(buff)); // block until VTIME times out
if (sz < 0)
{
perror ("Read failed");
}
printf ("Received bytes %d: %s\n", sz, buff);
}
}
int main()
{
int ret = SerialOpen();
if (ret < 0)
{
return -1;
}
if (pthread_create(&td, NULL, Rx, NULL) != 0)
{
printf("Fail to create thread!\n");
}
pthread_join(td, 0);
return 0;
}
Caveat: Prefaced by the top comments.
Be sure you're applying the fixes I suggested in the comments above.
select may have the same issues as VMIN/VTIME. select operates at a level after the TTY layer [which is where VMIN/VTIME operate]. At a lower level is the USB driver. If the TTY layer isn't set up correctly, select may have the same issues as read (i.e. probably not the issue). Although, you might do: open(/dev/whatever,O_RDWR | O_NONBLOCK)
But, I suspect that the issue is at a more fundamental/lower level. I think the USB layer/level is part of the problem.
USB-to-RS232 cables are tricky. The cable may need active H/W flow control enabled to operate. So, just disabling H/W flow control via (e.g.) clearing CRTSCTS may not work too well.
Double check: Are you sure that you are getting all the way through the configuration/initialization of the device. Could the open be hanging? You could add debug printf statements throughout the code [easier than using gdb for this].
What is the specific manufacturer and device for the USB cable? What is the specific manufacturer/model of the end/UART device? Although the standard/generic USB serial driver (e.g. usbserial) should work, some vendors need a specific driver (e.g. FTDI has its own USB level driver). So, you may need to consult the manufacturer's website/datasheet for the specific devices in question.
Also, sometimes a USB cable shows up on two different /dev/tty* devices. You may have the wrong one. You may need ttyS* instead of ttyUSB* or the device can show up as (e.g.) ttyACM* !!! This can occur even if ttyS* or ttyUSB* are [also] present.
Look at:
ls -l /dev
/proc/devices
/sys/devices
lsmod
lsusb
lspci
dmesg [and, maybe, syslog output]
Did you [or the system] modprobe usbserial?
Specifically, the dmesg output should say which tty* the device is connected to.
Some resources (from a "all the words" websearch on linux /dev USB uart cable):
https://www.cyberciti.biz/faq/find-out-linux-serial-ports-with-setserial/
https://unix.stackexchange.com/questions/81754/how-to-match-a-ttyusbx-device-to-a-usb-serial-device
I am trying to send data over serial port programmatically. I have an embedded system which runs Linux. On the bootloader prompt, I intend to stop execution by pressing CTRL+C and modifying the boot args. I am able to send the CTRL+C character and the booting does stop. However when I try to send a big command over serial port using the write(), it doesn't seem to be succeeding. Any inputs on what could be wrong? Below is my code snippet:
int main() {
// Open the serial port. Change device path as needed (currently set to an standard FTDI USB-UART cable type device)
int serial_port = open("/dev/ttyUSB0", O_RDWR);
// Create new termios struc, we call it 'tty' for convention
struct termios tty;
char data = 0x03;
char data2[]={'r','u','n',' ', 'l','o','a','d','b','o','o','t','e','n','v','\r'};
char data4[]= "setenv mmcargs \'setenv bootargs console=ttyO0,115200n8 console=tty0 ${optargs}
root=/dev/mmcblk1p3 rootfstype=ext4 panic=1\';
// Read in existing settings, and handle any error
if(tcgetattr(serial_port, &tty) != 0) {
printf("Error %i from tcgetattr: %s\n", errno, strerror(errno));
return 1;
}
tty.c_cflag = CS8 | CLOCAL | CREAD;
tty.c_iflag = IGNBRK | IGNPAR;
tty.c_oflag = OPOST;
tty.c_lflag = 0;
tty.c_cc[VMIN] = 1;
tty.c_cc[VTIME] = 0;
tcflush(serial_port, TCIFLUSH);
// Set in/out baud rate to be 115200
cfsetispeed(&tty, B115200);
cfsetospeed(&tty, B115200);
// Save tty settings, also checking for error
if (tcsetattr(serial_port, TCSANOW, &tty) != 0) {
printf("Error %i from tcsetattr: %s\n", errno, strerror(errno));
return 1;
}
write(serial_port, &data, 1);
ret = write(serial_port, &data2, sizeof(data2));
ret = write(serial_port, &data4, sizeof(data4));
All the writes succeed (this i verified by returned value) but then the write corresponding to data4 doesn't succeed in generating a response. When I simultaneously run minicom, I see that only 27 bytes of the final write are actually written. Not sure what is wrong.
If I give the same command as in data4 using putty, it succeeds without any issue. The settings on putty were speed(baud): 115200
Databits: 8
Stopbits: 1
Parity: NONE
Flowcontrol: NONE
Can someone help me where I am going wrong?
Thanks!
I'm using a program for RS232 communication. The program works fine so far, but i need to start and quit minicom (and eventually switch off hardware flow control) before starting the program.
Below is the code I use to initialize the port. I already had a look at
http://www.cmrr.umn.edu/~strupp/serial.html
and tried different further flags/options, such as explicitly set hardware flow control off, but it didn't help.
int open_port(void)
{
int fd;
#ifdef DEBUG
printf("open port\n");
#endif
fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_NDELAY);
if (fd == -1)
{
perror("open_port: Unable to open /dev/ttyS0");
}
else
{
if (port_set==0){
port_set=1;
struct termios options;
tcgetattr(fd, &options);
cfsetispeed(&options, B9600);
cfsetospeed(&options, B9600);
options.c_cflag |= (CLOCAL | CREAD);
tcsetattr(fd, TCSANOW, &options);
}
}
return (fd);
}
For non-canonical mode, you need to use the macro cfmakeraw to simplify various settings required. Otherwise the program performs an blocking read until a terminating character like 0x0A is detected, which may never be sent. Insert this line
cfmakeraw(&options);// make raw
right after the line
tcgetattr(fd, &options);
I am reading data through a USB connection as a serial port with the PL2303 driver. It returns successfully when doing an open and when I set they TTY options and non blocking. When I try to close the connection, it hangs. In this state it reads "�" instead of characters.
I can connect to the device perfectly fine with cutecom. Here is the strange part:
If I first connect to the device via cutecom (a serial monitor), my program will connect and close perfectly fine every time afterwards. It reads the characters as I expect them to be read. (No �).
If I disconnect and reconnect the hardware, my program will hang again until I run cutecom.
Since it works after I use cutecom, it makes me think that I am missing something in my initial connection, or connection settings. Here's what I use to connect:
baud_rate = 38400;
fd = open (device_path, O_RDONLY | O_NOCTTY );
In my set_tty_options function:
struct termios tty_options;
memset (&tty_options, 0, sizeof(tty_options));
tcgetattr (fd, &tty_options);
cfsetispeed(&tty_options, baud_rate); // set baud rate
tty_options.c_cflag = (tty_options.c_cflag & ~CSIZE) | CS8; // 8 bit msgs
tty_options.c_cflag |= (CLOCAL | CREAD); // enable reading
tty_options.c_cflag &= ~(PARENB | PARODD); // shut off parity
tty_options.c_cflag |= parity;
tty_options.c_cflag &= ~CSTOPB;
tty_options.c_cflag &= ~CRTSCTS;
if (tcsetattr (fd, TCSANOW, &tty_options) != 0)
{
printf("error %d from tcsetattr\n", errno);
return TTY_ERROR;
}
In set_blocking function:
if (tcgetattr (fd, &tty) != 0)
{
printf("error %d from tggetattr", errno);
return FAILURE;
}
// 0 or 1 byte is enough to return from read
tty.c_cc[VMIN] = should_block ? 1 : 0;
tty.c_cc[VTIME] = 5; // 0.5 seconds read timeout
if (tcsetattr (fd, TCSANOW, &tty) != 0)
{
printf("error %d setting term attributes", errno);
return FAILURE;
}
I think you want to add | O_SYNC to the open flags to insist on synchronous i/o. I doubt that is causing a problem though.
However, I think you want to ignore the break signal, which is reported as a NUL character like you are getting:
tty_settings.c_iflag &= ~IGNBRK; // ignore break signal
Also, you want to be sure the input processing is completely turned off, so that receipt of a backspace, ^C, ^\, etc. aren't triggering any reaction:
tty_settings.c_lflag = 0; // no signaling chars, no echo,
// no canonical processing
It looks like you are already using my set_blocking() function, so that should be okay.
Here's what I ended up doing. I figured this out by basically copying and pasting parts from cutecom's source code.
When opening...
int fd, n;
fd = open (device_path, O_RDONLY | O_NOCTTY | O_NDELAY);
... error check fd ...
n = fcntl(ail_info->ail_serial_fd, F_GETFL, 0);
fcntl(fd, F_SETFL, n & ~O_NDELAY);
You cannot set the baud rate as I was doing. You have to use the defined B38400;
baud = B38400;
Then, I added wallyk's answer.
tty_settings.c_lflag = 0;
Edit: As per sawdust comment, I found a better way to set this to raw input.
tty_options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
And it works.
I am having some trouble reading some data from a serial port I opened the following way. I've used this instance of code plenty of times and all worked fine, but now, for some reason that I cant figure out, I am completely unable to read anything from the serial port.
I am able to write and all is correctly received on the other end, but the replies (which are correctly sent) are never received (No, the cables are all ok ;) )
The code I used to open the serial port is the following:
fd = open("/dev/ttyUSB0", O_RDWR | O_NONBLOCK | O_NOCTTY);
if (fd == -1)
{
Aviso("Unable to open port");
return (fd);
}
else
{
//Get the current options for the port...
bzero(&options, sizeof(options)); /* clear struct for new port settings */
tcgetattr(fd, &options);
/*-- Set baud rate -------------------------------------------------------*/
if (cfsetispeed(&options, SerialBaudInterp(BaudRate))==-1)
perror("On cfsetispeed:");
if (cfsetospeed(&options, SerialBaudInterp(BaudRate))==-1)
perror("On cfsetospeed:");
//Enable the receiver and set local mode...
options.c_cflag |= (CLOCAL | CREAD);
options.c_cflag &= ~PARENB; /* Parity disabled */
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CSIZE; /* Mask the character size bits */
options.c_cflag |= SerialDataBitsInterp(8); /* CS8 - Selects 8 data bits */
options.c_cflag &= ~CRTSCTS; // disable hardware flow control
options.c_iflag &= ~(IXON | IXOFF | IXANY); // disable XON XOFF (for transmit and receive)
options.c_cflag |= CRTSCTS; /* enable hardware flow control */
options.c_cc[VMIN] = 0; //min carachters to be read
options.c_cc[VTIME] = 0; //Time to wait for data (tenths of seconds)
//Set the new options for the port...
tcflush(fd, TCIFLUSH);
if (tcsetattr(fd, TCSANOW, &options)==-1)
{
perror("On tcsetattr:");
}
PortOpen[ComPort] = fd;
}
return PortOpen[ComPort];
After the port is initializeed I write some stuff to it through simple write command...
int nc = write(hCom, txchar, n);
where hCom is the file descriptor (and it's ok), and (as I said) this works. But... when I do a read afterwards, I get a "Resource Temporarily Unavailable" error from errno.
I tested select to see when the file descriptor had something t read... but it always times out!
I read data like this:
ret = read(hCom, rxchar, n);
and I always get an EAGAIN and I have no idea why.
Update:
The HW is working fine! I can see that there is inbound data on the serial port because I've made a debug cable to read whats going on on another terminal. So...
I know what nonblocking should do. My question is... why isn't anything getting read!. The same setup works fine on windows, so all hardware is working fine...
This is driving me nuts! I'm sure it's something simple as hell! I even tried getting rid of O_NONBLOCK to see when I would receive something... but nothing...
Read this.
EAGAIN Non-blocking I/O has been
selected using O_NONBLOCK and no data
was immediately available for reading.
You need to check the serial terminal settings first.
use command - stty -F /dev/ttyUSB0 -a
Check that ctsrts is selected as -ctsrts
and do the other required settings with stty utility and you are done.
EAGAIN with O_NONBLOCK means there's been no data received on the port. Check that the port and cable are working properly (using minicom or some other known-good program), and that the remote really is sending some data.
see my code samples, if EAGAIN, you'd try to read again:
...
options.c_cflag &= ~PARENB;
options.c_iflag &= ~INPCK;
...
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // input
options.c_oflag &= ~OPOST; // output
...
fd = open("/dev/ttyUSB0", O_RDWR | O_NDELY | O_NOCTTY);
fcntl(fd, F_SETFL, 0);
...
int nc = write(hCom, txchar, n);
msleep(500); // wait 500ms
fcntl(hCom, F_SETFL, FNDELAY); // don't block serial read
ret = read(hCom, rxchar, n);
if (ret > 0) {
here had read n bytes or just partial data, read again if partial.
}
if (ret < 0) {
if (EAGAIN == errno) {
not a real error, just read again.
} else {
oops, errors.
}
}
...