【发布时间】:2019-05-25 03:00:30
【问题描述】:
我在(资源受限)嵌入式 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++))
【问题讨论】:
-
除非轮询有某种机制可以将 CPU tiem 释放到其他线程 - 这正是
poll()调用的目的,为其他进程(和线程)释放 cpu。主要有两所学校 -poll()或select()。除了在for{if{if{内部跳出的可怕 goto 和一些缩进主观标记之外,这是很好的代码。最好设置fds[0].events = POLLERR|POLLHUP|POLLRDNORM并处理 POLLERR。如果你使用poll(),你可以只使用set_blocking(, 1)。 -
哦,太好了,谢谢,我没有意识到,来自裸机嵌入式系统,轮询周期非常昂贵(除非有 RTOS)。好的,我会检查 fds[0].events = POLLERR|POLLHUP|POLLRDNORM,处理 POLLERR,然后正确处理断开/重新连接。
-
您标记了
linux,所以poll()可能是用GNU libcglibc实现的,并通过poll()系统调用转发给kenrel。然后内核将您的进程设置为阻塞状态,因此它没有执行,它正在等待 I/O。这段代码有效,这里真的没有问题,我投票作为stackoverflow的题外话。它可能更适合codereview.stackexchange.com -
是的,它是在 GNU libc 中实现的,感谢您的审查!
-
抱歉,但是您的第二版仍然存在与第一版相同的问题(也许更多!)并且只能靠侥幸工作。