\n\r 作为停止字节的故障安全性如何?

电器工程 UART 沟通 协议
2022-01-13 18:28:45

在我的 UART 通信中,我需要知道所发送消息的起始字节和停止字节。开始字节很容易,但停止字节就不那么容易了。我在消息末尾实现了两个停止字节,即 \n 和 \r(十进制的 10 和 13)。UART 仅适用于字节 0-255 的值,那么它的故障安全性如何?我可以想象,虽然概率很低,但当它们不是停止字节时,我的消息可能会包含一个接一个的值“10 和 13”。

有没有更好的方法来实现这一点?

4个回答

有不同的方法可以防止这种情况:

  • 确保您永远不会在常规消息中发送 10/13 组合(因此仅作为停止字节)。例如发送 20 21 22 23 24 25:

20 21 22 23 24 25 10 13

  • 转义 10 和 13 (或所有带有转义字符的非 ASCII 字符,例如 。所以要发送 20 21 10 13 25 26 发送:(见评论/信用:DanW)

20 21 1b 10 1b 13 25 26

  • 发送消息时定义一个数据包。例如,如果您想发送消息 20 21 22 23 24 25 而不是添加要发送的字节数,那么包是:

<nr_of_data_bytes > <数据>

如果您的消息最大为 256 字节,请发送:

06 20 21 22 23 24 25

所以你知道在收到 6 个数据字节后就结束了;之后您不必发送 10 13 。您可以在消息中发送 10 13 。如果您的消息可以更长,您可以使用 2 个字节作为数据大小。

更新 1:另一种定义数据包的方法

另一种选择是发送具有特定长度并且可以有很多变化的命令,例如

10 20 30(命令 10 总是有 2 个数据字节)

11 30 40 50(命令 11 始终具有 3 个数据字节)

12 06 10 11 12 13 14 15(命令 12 + 1 字节用于后面的数据字节数)

13 01 02 01 02 03 ...(命令 13 + 2 个字节(01 02 表示 256 + 2 = 258 个数据字节)

14 80 90 10 13(命令 14 后跟以 10 13 结尾的 ASCII 字符串)

更新 2:连接错误/字节丢失

以上所有方法仅在 UART 线路正确发送字节时有效。如果您想使用更可靠的发送方式,也有很多可能性。以下是一些:

  1. 在包中发送校验和(检查谷歌的 CRC:循环冗余校验)。如果 CRC 正确,则接收方知道消息已正确发送(很有可能)。
  2. 如果您需要重新发送消息,则需要使用确认(ACK/回复)机制(例如,发送方发送某些内容,接收方接收到损坏的数据,发送 NACK(未确认),然后发送方可以再次发送。
  3. 超时:如果接收方没有及时收到 ACK 或 NACK,则需要重新发送消息。

请注意,上述所有机制可以简单,也可以根据您的需要(或需要)复杂。在重新发送消息的情况下,还需要一种识别消息的机制(例如,将序列号添加到包中)。

\n\r 作为停止字节的故障安全性如何?

如果您发送任意数据 -> 可能不够安全。

一个常见的解决方案是使用转义:

让我们定义字符 0x02(STX - 帧开始)和 0x03(ETX - 帧结束)在传输的数据流中必须是唯一的。这样可以安全地检测消息的开始和结束。

如果应在消息帧中发送这些字符之一,则将其替换为前缀转义字符 (ESC = 0x1b) 并将 0x20 添加到原始字符。

原始字符替换为

0x02 -> 0x1b 0x22  
0x03 -> 0x1b 0x23  
0x1b -> 0x1b 0x3b  

接收者反转这个过程:任何时候他收到一个转义字符,这个字符被丢弃,下一个字符被减去 0x20。

这只会增加一些处理开销,但 100% 可靠(假设没有发生传输错误,您可以/应该通过额外实施校验和机制来验证)。

你知道,ASCII 已经有这些函数的字节。

  • 0x01 : 航向开始——起始字节
  • 0x02 : 文本开始——结束标头,开始有效负载
  • 0x03 : 文本结尾——结束有效载荷
  • 0x04:传输结束——停止字节
  • 0x17:传输块结束——消息在下一个块继续

它还在有效载荷内具有各种用途的代码。

  • 0x1b :转义(转义下一个字符——在有效负载中使用以指示下一个字符不是协议中使用的描述代码的结构之一)
  • 0x1c、0x1d、0x1e、0x1f:分别是文件、组、记录和单位分隔符——用作部分分层数据的同时停止和开始字节

您的协议应指定 ACK (0x06) 和 NAK (0x15) 的最细粒度,以便可以重新传输否定的确认数据。在最精细的粒度下,明智的做法是在任何(未转义的)开始指示符之后立即有一个长度字段,并且(如其他答案中所述)在任何(未转义的)停止指示符之后使用 CRC 是明智的。

UART 本质上并不是故障安全的——我们在这里谈论的是 1960 年代的技术。

问题的根源在于 UART 每 10 位仅同步一次,从而允许在这些同步周期之间传递大量乱码。与例如 CAN 不同,它对每个单独的位进行多次采样。

数据内部发生的任何双位错误都会破坏 UART 帧,并且不会被检测到。开始/停止位中的位错误可能会或可能不会以溢出错误的形式被检测到。

因此,无论使用原始数据还是数据包,EMI 引起的位翻转总是有可能导致意外数据。

有许多“传统的 UART 骗子”方法可以稍微改善这种情况。您可以添加同步字节、同步位、奇偶校验、双停止位。您可以添加校验和来计算所有字节的总和(然后将其反转 - 因为为什么不),或者您可以将二进制数作为校验和来计算。所有这些都被广泛使用,非常不科学,并且很可能会丢失错误。但这就是人们从 1960 年代到 1990 年代所做的事情,以及许多像今天这样的奇怪事情。

处理 UART 安全传输的最专业方法是在数据包末尾添加 16 位 CRC 校验和。其他一切都不是很安全,并且很可能会丢失错误。

然后在硬件层面,您可以使用差分 RS-422/RS-485 来大幅提高传输的耐用性。这是长距离安全传输的必要条件。TTL 电平 UART 只能用于板载通信。RS-232 不应用于任何其他目的,但应与旧东西向后兼容。

总的来说,你的错误检测机制越接近硬件,它就越有效。在有效性方面,差分信号增加最多,其次是检查帧/溢出等错误。CRC16 增加了一些,然后“传统的 UART quackery”增加了一点。