如何在运行时使用 C 更改串口的波特率?

问题描述

我使用 Debian 9 LXDE 为基于 aarch64 的 SoC(Rockchip RK3399)编写了一些 C 代码,以从 GPS 模块接收数据。 GPS 模块连接到我的 SoC 中的 "ttyS4" 端口。我创建了一个 pthread 来接收来自 GPS 模块的数据。我正在使用 termios 库。所以我的流程如下:

  • 初始化 UART 端口(波特率、奇偶校验、停止位等)。
  • 创建一个线程来接收来自模块的数据。

现在我需要在从外部源接收波特率时更改 UART 的波特率。我能够接收新的波特率,我需要将其设置为端口。

如何为端口设置新的波特率?我应该pthread_exit()接收线程,初始化UART端口,然后再次启动线程吗?

或者我应该关闭 fd 并使用新的波特率初始化 UART 端口而不退出线程?

或者有没有其他简单的方法函数来设置UART到端口?

我的初始化代码

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);
}

这是下面建议的波特率变化函数

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,&Gps_termios) != 0) {
        printf("Error from tcsetattr: %s\n",strerror(errno));
        return -1;
    }
    tcflush(Gpsfd,TCIOFLUSH);  /* discard buffers */

    return 0;
}

我使用 GpsPortinit 初始化 UART 一次,之后如果我收到任何更改波特率的请求,我会使用 set_baudrate 进行更改。

解决方法

如何为端口设置新的波特率?

在 Linux 中,用户空间无法直接访问 UART 等硬件,因此您的程序只能使用串行终端和 termios API。
您使用 /dev/ttyS4(而不是名为 /dev/uart4 的设备节点)证实了这一点。

我是否应该 pthread_exit() 接收线程,初始化 UART 端口,然后再次启动线程?

那应该没关系。
pthread 只是从 termios 缓冲区“读取”,而不是直接访问任何硬件。
但是,您的程序需要健壮并能够处理可能的错误消息。

或者我应该关闭 fd 并使用新的波特率初始化 UART 端口而不退出线程?

关闭文件描述符将拒绝您的程序进一步访问串行终端,因此没有意义。
用户空间无法直接访问 UART 等硬件,因此您的程序只能使用 termios API。

或者有没有其他简单的方法或函数来设置UART到端口?

使用termios API改变串口终端的波特率,例如:

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;
}

以上应该类似于您用于“初始化 UART 端口(波特率、奇偶校验、停止位等)...使用 termios 库”的初始化。
即上面的代码遵循Setting Terminal Modes Properly .


请注意,tcsetattr() 调用可以使用参数 TCSAFLUSH 而不是 TCSANOW。根据{{​​3}},这将“在输出耗尽时更改属性;同时刷新挂起的输入”。
然而等待输出耗尽似乎没有必要,因为改变波特率意味着当前波特率与串行链路的另一端不兼容。 IOW垃圾可能正在传输,垃圾可能正在接收。
所以立即改变波特率(即使用TCSANOW),然后丢弃接收缓冲区的内容。


附录:审核您的初始化代码

您的初始化代码质量低,不可移植,并且可能无法可靠地设置串行终端。
从清零的 termios 结构(而不​​是现有值)开始与 termios.h man page 中所述的推荐做法相反 和Setting Terminal Modes Properly

请参阅 Serial Programming Guide for POSIX Operating Systems 以获取有关正确串行终端初始化以阻止非规范模式的简洁代码。

这是下面建议的波特率变化函数:

请注意,我的回答中已更新该例程以增强错误报告。
你忽略了你是如何使用/调用这个程序的。

我使用 GpsPortInit 初始化 UART 一次,之后如果我收到任何更改波特率的请求,我会使用 set_baudrate 更改它。

您如何协调串行链路另一端的任何波特率变化?

您还忽略了描述您的测试程序、您使用的波特率、另一端的设置方式。
这些遗漏中的任何一个都可能揭示为什么这个 set_baudrate() 例程“不工作” 对您而言的原因。