Read serial port with inter-character timeout for long streams - c

in my application i am connected to serial device which will send periodic data whose size is unknown. I do not want to detect incoming frame using StartofFrame and EndOfFrame keywords. Instead of this, i want to detect incoming frame using inter-character timeout. There always will be some duration between incoming data frames at least 100ms.
So, i can detect frames using this time interval. I am opening serial port using this function:
uint8_t open_serial_port(uart_device* dev)
{
dev->fd = open(dev->path, O_RDWR | O_NOCTTY);
if (dev->fd < 0)
{
syslog(LOG_CRIT, "Serial Port {%s}-{%s} couldn't opened", dev->name, dev->path);
return EXIT_FAILURE;
}
syslog(LOG_INFO, "Serial Port {%s}-{%s} opened with fd {%d}", dev->name, dev->path, dev->fd);
struct termios tty;
if (tcgetattr(dev->fd, &tty) < 0)
{
syslog(LOG_CRIT, "Serial Connection attributes get for fd {%d} failed with errno cause {%s}", dev->fd, strerror(errno));
return EXIT_FAILURE;
}
cfmakeraw(&tty);
cfsetospeed(&tty, (speed_t) dev->speed);
cfsetispeed(&tty, (speed_t) dev->speed);
tty.c_cc[VMIN] = 255; //! This is used to block to get at least VMIN characters
tty.c_cc[VTIME] = 10; //! used to block for tens of seconds timeout 10 -> 1 seconds timeout
tty.c_cflag |= CLOCAL;
tty.c_cflag &= ~CSIZE; //! Mask the character size bits
tty.c_cflag |= get_start_stop_opt(dev->data, dev->stop); //! Set data, stop bits
tty.c_cflag |= get_parity_opt(dev->parity); //! set parity configuration
tty.c_cflag |= get_hw_flow_control(dev->flow_control);
if (tcsetattr(dev->fd, TCSANOW, &tty))
{
syslog(LOG_CRIT, "Serial Connection attributes set for fd {%d} failed with errno cause {%s}", dev->fd, strerror(errno));
return EXIT_FAILURE;
}
syslog(LOG_INFO, "Serial Connection {%s}-{%s} established. speed: %d, parity: %s, data: %d, stop: %d, flw_ctrl: %d", dev->name, dev->path, dev->speed,
dev->parity, dev->data, dev->stop, dev->flow_control);
return EXIT_SUCCESS;
}
I am reading serial port in blocking mode:
readSize = read(dev->fd, &buf, UART_MAX_PAYLOAD);
if (readSize < 0)
{
syslog(LOG_CRIT, "UART Read failed while reading {%s}, error {%s}", dev->path, strerror(errno));
usleep(1000 * 1000);
continue;
}
syslog(LOG_INFO, "Uart read completed. readSize: %d", readSize);
Actually VMIN and VTIME parameters of termios is suitable for my situation but VMIN and VTIME is defined as 8 bit. So, i can state maximum of 255 characters to read.
So, when device sends 2048 bytes of data, i read this as :
Uart read completed. readSize: 496
Uart read completed. readSize: 496
Uart read completed. readSize: 496
Uart read completed. readSize: 496
Uart read completed. readSize: 64
Somehow, i can not read whole data at same time.
What is the problem about reading large data in blocking mode? How can i read large serial data in one time?
Best regards.

Related

How to change the baudrate of a serial port at runtime using C?

I have written some C code for aarch64-based SoC (Rockchip RK3399) with Debian 9 LXDE, to receive data from a GPS module. The GPS module is connected to "ttyS4" port in my SoC. I have created a pthread to receive data from the GPS module. I'm using the termios library. So my flow goes as follows:
Initialize the UART port (baud rate, parity, stop bits etc.).
Create a thread to receive the data from the module.
Now I need to change the baud rate of the UART upon receiving the baud rate from a external source. I am able to receive the new baud rate which I need to set it to the port.
How do I set the new baud rate to the port? Should I pthread_exit() the receiving thread, initialize the UART port and then start the thread again?
Or should I just close the fd and initialize the UART port with the new baud rate without exiting from the thread?
Or is there any other simple way or function to set the UART to the port ?
My Initialization code:
int Gpsfd;
struct termios Gps_termios, Gps_old;
void GpsPortInit(void)
{
char path[12] = "/dev/ttyS4";
//open GSM_termios for tx/rx
Gpsfd = open(path, O_RDWR | O_NOCTTY);
if (Gpsfd < 0)
printf("port failed to open\n");
//save current attributes
tcgetattr(Gpsfd, &Gps_old);
bzero(&Gps_termios, sizeof(Gps_termios));
Gps_termios.c_cflag = CLOCAL | CREAD | CS8;
if (!strcmp(g_sParameters.RS232BaudRate, "9600"))
{
Gps_termios.c_cflag |= B9600;
}
else if (!strcmp(g_sParameters.RS232BaudRate, "19200"))
{
Gps_termios.c_cflag |= B19200;
}
else if (!strcmp(g_sParameters.RS232BaudRate, "57600"))
{
Gps_termios.c_cflag |= B57600;
}
else if (!strcmp(g_sParameters.RS232BaudRate, "115200"))
{
Gps_termios.c_cflag |= B115200;
}
Gps_termios.c_iflag = IGNPAR;
Gps_termios.c_oflag = 0;
Gps_termios.c_lflag = 0;
Gps_termios.c_cc[VTIME] = 0;
Gps_termios.c_cc[VMIN] = 1;
//clean the line and set the attributes
tcflush(Gpsfd, TCIFLUSH);
tcsetattr(Gpsfd, TCSANOW, &Gps_termios);
}
This is the baudrate change function suggested below:
int set_baudrate(speed_t speed)
{
//struct termios tty;
if (tcgetattr(Gpsfd, &Gps_termios) < 0) {
printf("Error from tcgetattr1: %s\n", strerror(errno));
return -1;
}
cfsetospeed(&Gps_termios, speed);
cfsetispeed(&Gps_termios, speed);
if (tcsetattr(Gpsfd, TCSANOW, &Gps_termios) != 0) {
printf("Error from tcsetattr: %s\n", strerror(errno));
return -1;
}
tcflush(Gpsfd, TCIOFLUSH); /* discard buffers */
return 0;
}
I am Initializing the UART once with GpsPortInit and after that if I get any request to change the baudrate I change it with set_baudrate.
How do I set the new baud rate to the port?
In Linux, userspace does not have direct access to hardware such as a UART, so your program is constrained to use the serial terminal and the termios API.
That is confirmed by your use of /dev/ttyS4 (rather than a device node named /dev/uart4).
Should I pthread_exit() the receiving thread, initialize the UART port and then start the thread again?
That should not matter.
The pthread is merely "reading" from the termios buffer, rather than directly accessing any hardware.
However your program needs to be robust and able to cope with possible mangled messages.
Or should I just close the fd and initialize the UART port with the new baud rate without exiting from the thread?
Closing the file descriptor would deny your program further access to the serial terminal, so that does not make sense.
Userspace does not have direct access to hardware such as a UART, so your program is constrained to use the termios API.
Or is there any other simple way or function to set the UART to the port ?
Use the termios API to change the baudrate of the serial terminal, e.g.:
int set_baudrate(int fd, speed_t speed)
{
struct termios tty;
int rc1, rc2;
if (tcgetattr(fd, &tty) < 0) {
printf("Error from tcgetattr: %s\n", strerror(errno));
return -1;
}
rc1 = cfsetospeed(&tty, speed);
rc2 = cfsetispeed(&tty, speed);
if ((rc1 | rc2) != 0 ) {
printf("Error from cfsetxspeed: %s\n", strerror(errno));
return -1;
}
if (tcsetattr(fd, TCSANOW, &tty) != 0) {
printf("Error from tcsetattr: %s\n", strerror(errno));
return -1;
}
tcflush(fd, TCIOFLUSH); /* discard buffers */
return 0;
}
The above should be similar to the initialization that you used to "initialize the UART port (baud rate, parity, stop bits etc.) ... using the termios library".
That is, the code above adheres to Setting Terminal Modes Properly
.
Note that the tcsetattr() call could use the argument TCSAFLUSH instead of TCSANOW. That would "change attributes when output has drained; also flush pending input" according to the termios.h man page.
However waiting for the output to drain seems unnecessary, as changing the baudrate implies the current baudrate is incompatible with the other end of the serial link. IOW garbage is presumably being transmitted and garbage is presumably being received.
So immediately change the baudrate (i.e. use TCSANOW), and then discard the contents of the receive buffer.
Addendum: review of your initialization code
Your initialization code is low quality, is not portable, and may not reliably setup the serial terminal.
Starting with a zeroed-out termios structure (instead of the existing values) is contrary to recommended practice as described in Setting Terminal Modes Properly
and Serial Programming Guide for POSIX Operating Systems.
See this answer for concise code for proper serial terminal initialization to blocking non-canonical mode.
This is the baudrate change function suggested below:
Note that the routine has been updated in my answer to enhance error reporting.
You neglect to show exactly how you use/call this routine.
I am Initializing the UART once with GpsPortInit and after that if I get any request to change the baudrate I change it with set_baudrate.
How do you coordinate any baudrate changes with the other end of the serial link?
You also neglect to describe your test procedures, what baudrates you are using, how the other end is setup.
Any of these omissions could reveal the reason(s) why this set_baudrate() routine "is not working" for you.

Sending data over serial port

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!

Setting Port for rs485 comunication linux

I have a problem with set up a good parameters for my rs485 transmission. Already i am working on this 4th day and i have no idea why it doesn't work.
I am writting a program, which will be communication with other device via rs485. I have a minicomputer with i.MX6 and Linux Linaro. When I try to send special frame running my program on my minicomp it send but, my device doesn't responsible. Furthermore when i send echo from my PC with the same correct frame like in my program device responds. So problem is correct configuration my UART port /dev/ttyUSB.
I need baud rate 115200 and frame 8 bit and 1 stop bit.
void SetUARTPort()
{
int errnum;
ctrl485.flags |= SER_RS485_ENABLED;
ctrl485.flags |= SER_RS485_RX_DURING_TX;
ctrl485.delay_rts_before_send = 0;
ctrl485.delay_rts_after_send = 0;
status = ioctl(fd, TIOCSRS485, &ctrl485);
if (status <0 )
{
printf("%s: Unable to configure port in 485 mode, status (%i)\n", dev, status);
errnum = errno;
fprintf(stderr, "Value of errno: %d\n", errno);
fprintf(stderr, "Error opening file: %s\n", strerror( errnum ));
}
option.c_cflag = B115200 | CS8 | CSTOPB | CLOCAL ;
option.c_iflag = 0;
option.c_oflag = 0;
option.c_lflag = 0;
option.c_iflag = IGNPAR | IGNBRK;
speed = B115200 ;
tcgetattr(fd, &option);
cfsetospeed(&option, speed); //TX baude rate
cfsetispeed(&option, speed); // RX baude rate
tcsetattr(fd, TCSANOW, &option); //set new serial config}
The last things is that ioctl problem. When i start program i had aN error like:
/dev/ttyUSB0: Unable to configure port in 485 mode, status (-1)
Value of errno:25. Inappropriate ioctl for device
I think i try a lot of thing but i still don't have no idea why it doesn't work.
Anybody can help me?
cvanny.

Unable to Read From Serial Device After Unplugging and Replugging Connector

I'm have a Linux application that is supposed to read from serial device /dev/ttyS0. The serial device is opened in the following manner:
// Open the serial port
if((serial_device = open("/dev/ttyS0", O_RDWR | O_NOCTTY)) < 0){
fprintf(stderr, "ERROR: Open\n");
exit(EXIT_FAILURE);
}
// Get serial device attributes
if(tcgetattr(serial_device,&options)){
fprintf(stderr, "ERROR: Terminal Get Attributes\n");
exit(EXIT_FAILURE);
}
cfsetspeed(&options,speed); // Set I/O baud rates
cfmakeraw(&options); // Set options to transceive raw data
options.c_cflag |= (CLOCAL | CREAD); // Enable the receiver and set local mode
options.c_cflag &= ~CSTOPB; // 1 stop bit
options.c_cflag &= ~CRTSCTS; // Disable hardware flow control
options.c_cc[VMIN] = 1; // Minimum number of characters to read
options.c_cc[VTIME] = 10; // One second timeout
// Set the new serial device attributes
if(tcsetattr(serial_device, TCSANOW, &options)){
fprintf(stderr, "ERROR: Terminal Set Attributes\n");
exit(EXIT_FAILURE);
}
I then use the select function to try and read from the serial device:
// Flush I/O Bffer
if(tcflush(serial_device,TCIOFLUSH)){
fprintf(stderr, "ERROR: I/O Flush\n");
exit(EXIT_FAILURE);
}
// Write message to board
if(write(serial_device,msg, strlen(msg)) != (int)strlen(msg)){
fprintf(stderr, "ERROR: Write\n");
exit(EXIT_FAILURE);
}
switch(select(serial_device+1, &set, NULL, NULL, &timeout)){
// Error
case -1:
fprintf(stderr, "ERROR: Select\n");
exit(EXIT_FAILURE);
// Timeout
case 0:
success = false;
break;
// Input ready
default:
// Try to read a character
switch(read(serial_device, &c, 1)){
// Error (miss)
case -1:
success = false;
break;
// Got a character
default:
msg[i++] = c;
break;
}
break;
}
// Set 200ms timeout
this->timeout.tv_sec = 0;
this->timeout.tv_usec = 200000;
}
I've tried reopening the port by determining if the read was not successful:
if(!success)
close(serial_device);
openPort(); // Same as above
}
However, the act of physically unplugging the serial connector will result in the application being unable to read anything further, and select will do nothing but time out. Plugging the connector back in while the application is running will not fix the issue, and select will continue to detect nothing.
The only way to successfully read from the serial port again is to restart the application. I'm wondering why this is, and how I can recover from the serial connector being unplugged at runtime.
The use of select() with just one file descriptor is unusual. It also adds a level of complexity.
Since the serial port is configured for non-canonical input, with proper selection of VMIN and VTIME, you might be able to accomplish the read of a char at a time with simpler code. E.G. try VMIN = 1 and VTIME = 10*timeout.tv_sec
However as you figured out, and if you are willing to handle (or want) a timeout rather than wait for at least one character to arrive, then VMIN = 0 will emulate your original code with the select().
VMIN = 0 and VTIME > 0
This is a pure timed read. If data are available in the input queue, it's transferred to the caller's buffer up to a maximum of nbytes, and returned immediately to the caller. Otherwise the driver blocks until data arrives, or when VTIME tenths expire from the start of the call. If the timer expires without data, zero is returned. A single byte is sufficient to satisfy this read call, but if more is available in the input queue, it's returned to the caller. Note that this is an overall timer, not an intercharacter one.
However (like the OP) I'm baffled as to why reconnecting the port connector should disrupt any reads or select monitoring, and have never encountered such an issue.

C: read() hangs without getting input from serial port on unix

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.

Resources