我在(资源受限)嵌入式 Linux 平台(C 语言)中使用 Termios 来发送命令并从各种 tty 外设(CP2102 USB-UART)接收数据。显然,有多种方法可以做到这一点,但很多方法都是不正确的。我尝试了很少的成功,我遇到了一些有效的方法,但我不确定它是最佳的还是正确的:
#define BAUDRATE 115200
#define DEVICE "/dev/ttyUSB0"
#define DATA "BLK\r"
int handler(void){
char buf[255];
struct pollfd fds[1];
int fd, ret, res, retry = 0;
connect:
fd = open(DEVICE, O_RDWR | O_NOCTTY | O_NONBLOCK);
if (fd == 0){
perror(DEVICE);
printf("Failed to open %s\n",DEVICE);
sleepms(2000);
if(retry++<5) goto connect;
//exit(-1);
}
set_interface_attribs (fd, BAUDRATE, 0); // 8n1 no parity
set_blocking (fd, 0); // not blocking
fds[0].fd = fd; // streams
fds[0].events = POLLRDNORM;
for (;;)
{
int count = write(fd, DATA, strlen(DATA));
ret = poll(fds, 1, 1000);
if (ret > 0){
if (fds[0].revents & POLLHUP){ printf("Hangup\n"); close(fd); goto connect;}
if (fds[0].revents & POLLRDNORM){
res = read(fd,buf,255);
if(!res){close(fd); goto connect;}
buf[res-2]=0;
printf("Received %d bytes : %s",res,buf);
}
}
}
}
基本上发送命令,然后进行轮询,直到有数据到来或发生超时。 这工作了超过 24 小时,并且没有显示通信方面的问题,但是有一个问题:如果外围设备断开连接,那么我会收到“hangout”通知,但它永远不会重新连接,我希望它会重新连接,因为我关闭了文件并重试连接到接口.
此外,民意调查是最好的方法吗?我不想通过轮询将 CPU 时间花在纯粹的丢失上(除非轮询有某种机制将 CPU 时间释放给其他线程),但我仍然希望调用处于阻塞状态,直到发生超时或响应到达(即我不想发送命令并稍后通过回调获取响应)。来自外围设备的响应在几毫秒内。
n.b.我知道有些人因为 goto 语句而流血,不用担心,这里已经使用 goto 来快速测试“重新连接”方法(这没有用),但最终如果必须重新启动连接,它将在一个单独的函数,goto 在最终实现中永远不会被使用。
编辑:
重写后。它可以工作,但仍然存在问题,主要是当我断开外围设备时,Linux会随机保留端口名称或更改它。有时它会在多次连续断开连接时保留端口名称,有时它会重命名并在一段时间内保留新名称。所以我必须找到一种方法来识别 USB 外设并获取当前端口名称,无论它是什么。
断开连接时,它会从 tcgetattr 抛出错误 9(我认为来自 set_interface_attribs 或 set_blocking),但是一旦重新连接,并且如果 Linux 不更改端口名称,那么它会立即重新连接并重新启动以正常发送和接收,但是,当Linux 重命名端口然后失败。
int fd=-1,retry=0;
struct pollfd fds[1];
int connect(void){
if(fd==-1) fd = open(MODEMDEVICE, O_RDWR | O_NOCTTY | O_NONBLOCK);
else return 0; // already opened
printf("Connecting to fd #%d\n",fd);
if (fd==0){
perror(MODEMDEVICE);
printf("Failed to open %s\n",MODEMDEVICE);
retry++;
sleepms(2000);
return -1;
}
set_interface_attribs (fd, BAUDRATE, 0); // 8n1 no parity
set_blocking (fd, 0); // not blocking
fds[0].fd = fd; // streams
fds[0].events = POLLERR|POLLHUP|POLLRDNORM;
return 0;
}
int handlePoll(){
int ret=0,res=0;
char buf[255];
ret = poll(fds, 1, 1000); // 1000ms
if (ret > 0){
if (fds[0].revents & POLLERR){ close(fd); fd=-1; return -1; } // IO error
if (fds[0].revents & POLLHUP){ close(fd); fd=-1; return -2; } // interface closed
if (fds[0].revents & POLLRDNORM){
res = read(fd,buf,255);
if(!res){ close(fd); return -3; } // data receive error
buf[res-2]=0;
printf("Received %d bytes : %s\n",res,buf);
return 0;
}
return -4; // unknown error
}
return -5; // timeout
}
int sendCMD(char* cmd){
int res=1;
retry=0;
while(res && retry<5){ res=connect(); }
if(res) return -7;
if(retry>5) return -8;
int len = strlen(cmd);
int count = write(fd, cmd, len);
if(count<len){ return -6;}
return handlePoll();
}
int main(void){
while(1){
switch(sendCMD(DATA)){
case -1: printf("IO error\n"); break;
case -2: printf("Interface closed error\n"); break;
case -3: printf("data receive error\n"); break;
case -4: printf("Unknown error\n"); break;
case -5: printf("Timeout\n"); break;
case -6: printf("Command send error\n"); sleepms(200); break;
case -7: printf("Interface open error\n"); break;
case -8: printf("Cannot open interface after 5 try\n"); break;
default: break;
}
}
}
我认为应该有更好的方法来处理断开连接(Detecting if a character device has disconnected in Linux in with termios api (c++))
connect:
fd = open(DEVICE, O_RDWR | O_NOCTTY | O_NONBLOCK);
if (fd == 0){
perror(DEVICE);
如果出现错误,
open(2)
不会返回0
,而是返回-1
。 0
是一个有效的文件描述符——按照惯例是标准输入。
int count = write(fd, DATA, strlen(DATA));
ret = poll(fds, 1, 1000);
if (ret > 0){
为什么不检查
write()
的返回值?如果上面的open()
实际上失败并返回-1
,那么这个write()
也会失败,返回-1
并将errno
设置为EBADF
。
res = read(fd,buf,255);
if(!res){close(fd); goto connect;}
buf[res-2]=0;
同上,你不在乎
read()
是否失败,如果上面的 open()
无法打开文件,它肯定会这样做。就像 open()
、write()
和所有系统调用一样,如果出现错误,read()
将返回 -1
。
由于您的文件描述符是非阻塞的,因此请准备好单独处理诸如
EAGAIN
之类的瞬态错误 [2]。
在
res == -1
的情况下,buf[res-2]=0
会在buf
开始之前写入而破坏内存。
请注意,如果传递了无效的 fd,
poll()
将不会在 POLLERR
中设置
revents
——如果它是负数,则忽略(就像失败的
fd
返回的
open()
),或如果为正则设置
POLLNVAL
。
if (fds[0].revents & POLLHUP){ printf("Hangup\n"); close(fd); goto connect;}
if (fds[0].revents & POLLRDNORM){
POLLHUP
和
POLLRDNORM
[1] 可以在
revents
中一起返回,表示最后一次可能的读取,您的代码将错过该读取。我强烈建议您
strace(1)
您的程序,您可以在程序运行时使用
strace -p PID
进行操作。[1] 顺便说一句,
POLLRDNORM
相当于 Linux 中的
POLLIN
。[2] 和
EINTR
,如果您的程序或其使用的库正在设置任何信号处理程序。