1
votes

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.

1
I doubt your processor has a serial port. A microcontroller is however very likely to have one. But since you speak of pthreads, it would seem that you rather have some manner of Linux PC. If you told us what system you are using, I wouldn't have to guess.Lundin
It is a aarch64 based processor(rockchip rk3399) with debian 9 LXDEVibhu
Generally you have to close down all communication if you need to change baudrate. Is it the termios lib you are using or something else?Lundin
By all communication you mean the file descriptor ? Yes, I am using termios lib.Vibhu
Been forever since I used that lib, but now with the clarifications, someone else should be able to give an answer. I took the liberty to add the details to the question with an edit.Lundin

1 Answers

2
votes

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.