问题描述
我使用 Debian 9 LXDE 为基于 aarch64 的 SoC(Rockchip RK3399)编写了一些 C 代码,以从 GPS 模块接收数据。 GPS 模块连接到我的 SoC 中的 "ttyS4" 端口。我创建了一个 pthread 来接收来自 GPS 模块的数据。我正在使用 termios 库。所以我的流程如下:
- 初始化 UART 端口(波特率、奇偶校验、停止位等)。
- 创建一个线程来接收来自模块的数据。
现在我需要在从外部源接收波特率时更改 UART 的波特率。我能够接收新的波特率,我需要将其设置为端口。
如何为端口设置新的波特率?我应该pthread_exit()
接收线程,初始化UART端口,然后再次启动线程吗?
或者我应该关闭 fd 并使用新的波特率初始化 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() 例程“不工作” 对您而言的原因。