1

我正在尝试在 Linux(Ubuntu 12.04)中读取和写入串行端口,其中另一端的微控制器在完成特定任务时会爆炸 1 或 3 个字节。我能够成功读取和写入设备,但问题是我的读取现在有点“危险”:

do
{
    nbytes = read(fd, buffer, sizeof(buffer));
    usleep(50000);
} while(nbytes == -1);

即简单地监视设备发送给我的内容,我每半秒轮询一次缓冲区。如果为空,则在此循环中空闲。如果它收到一些东西或错误,它就会退出。然后一些逻辑处理 1 或 3 个数据包并将其打印到终端。半秒通常是一个足够长的窗口,可以让某些东西完全出现在缓冲区中,但对于最终会看到它的人来说,它足够快,不会认为它很慢。

“通常”是关键词。如果我读取中间的缓冲区爆破 3 个字节。我会读得很糟糕;缓冲区中将包含 1 或 2 个字节,它会在数据包处理中被拒绝(如果我捕获 3 字节数据包中的第一个,它不会是有目的地发送一个字节的值)。

我考虑过/尝试过的解决方案:

  1. 我想过一次简单地读取一个字节,如果它是 3 字节传输的一部分,则输入额外的字节。但是,这会产生一些丑陋的循环(因为 read() 只返回仅返回最近一次读取的字节数),如果可以的话,我想避免这些循环

  2. 我试图读取 0 个字节(例如 nbytes = read(fd, buffer, 0);)只是为了查看缓冲区中当前有多少字节,然后再尝试将其加载到我自己的缓冲区中,但正如我所怀疑的那样返回 0。

如果我可以在将端口缓冲区的内容加载到我自己的缓冲区之前查看端口缓冲区的内容,那么我的很多问题似乎都会很容易解决。但是 read() 的破坏性取决于您告诉它读取的字节数。

我怎样才能从这个缓冲区中读取数据,这样我就不会在接收传输的过程中这样做,但要足够快以至于不会让用户觉得慢?我的串行信使分为发送者和接收者线程,所以我不必担心我的程序循环阻塞某处而忽略另一半。

谢谢你的帮助。

4

3 回答 3

3

修复您的数据包处理。对于这样的实例,我总是最终使用状态机,所以如果我收到部分消息,我会记得(有状态的)我停止处理的地方,并且可以在其余数据包到达时恢复。

通常我必须在数据包末尾验证校验和,然后再进行其他处理,所以“我停止处理的地方”总是“等待校验和”。但是我存储了部分数据包,以便在更多数据到达时使用。

Even though you can't peek into the driver buffer, you can load all those bytes into your own buffer (in C++ a deque is a good choice) and peek into that all you want.

于 2013-03-30T16:11:23.433 回答
2

您需要知道要发送的消息有多大。有几种方法可以做到这一点:

  1. 使用消息的长度为消息添加前缀。
  2. 有一个消息终止符,一个不能成为消息一部分的字节(或字节序列)。
  3. 使用“命令”来计算长度,即当您读取命令字节时,您知道应该遵循多少数据,因此请读取该数量。

第二种方法最适合您可能不同步的情况,因为然后读取直到您获得消息终止序列并且您确定下一个字节将是一条新消息。

你当然可以结合这些方法。

于 2013-03-30T15:02:43.090 回答
1

要轮询设备,您最好使用像poll(2)这样的多路复用系统调用,当某些数据可用于从该设备读取时,它会成功。请注意,这poll是多路复用:您可以一次轮询多个文件描述符,并且poll只要一个(任何)文件描述符可读POLLIN(或可写,如果用POLLOUT等询问)就会成功。

一旦poll成功,fdPOLLIN可以从中读取(2)fd

当然,您需要了解硬件设备使用的关于其消息的约定。请注意,单个read消息可能会收到多条消息,或者只是一条(或多条)消息的一部分。没有办法阻止读取部分消息(或“数据包”)——可能是因为您的 PC 串行 I/O 比微控制器内部的串行 I/O 快得多。您应该忍受这一点,通过了解定义消息的约定(如果您可以更改微控制器内部的软件,请为此定义一个简单的约定)并实施适当的状态机和缓冲等......

注意:还有用于多路复用的较旧的select(2)系统调用,它具有与 C10K 问题相关的限制。我建议poll不要select使用新代码。

于 2013-03-30T15:57:34.753 回答