具有自动重传问题的强健串行协议

时间:2017-02-14 23:52:11

标签: c embedded uart usart

长期读者,第一次海报。

我通过蓝牙在传感器和基站之间建立了串行链路。蓝牙链接非常不可靠,丢弃的数据包就像你不相信的那样。我正在使用它作为一个积极的,并将设计一个强大的串行协议,可以在一个糟糕的链接中存活。

我想从人们那里反省一些想法,因为我是办公室里唯一的嵌入式开发人员。

计划是使用字节填充来创建具有开始(STX)和结束(ETX)字节,索引号和CRC的数据包。计划在STX和ETX和DLE字符出现时使用转义字符(DLE)。这一部分都非常清楚,这里是代码,应该这样做

static void SendCommand(struct usart_module * const usart_module, uint16_t cmd,
        uint8_t *data, uint8_t len)
{
    //[STX] ( { [IDX] [CMD_H] [CMD_L] [LEN] ...[DATA]... } [CRC] ) [ETX] // Data in {} brackets is XORed together for the CRC. // Data in () is the packet length
    static uint8_t idx;
    uint8_t packet[256];
    uint8_t crc, packetLength, i;

    if (len > 250)
        return;

    packetLength = len + 5;

    packet[0] = idx++;
    packet[1] = (cmd >> 8);
    packet[2] = (cmd & 0x00FF);
    packet[3] = packetLength;

    crc = 0;
    for (i = 0; i <= 3; i++)
    {
        crc ^= packet[i];
    }

    for (i = 0; i < len; i++)
    {
        packet[4 + i] = data[i];
        crc ^= data[i];
    }

    packet[4 + len] = crc;

    // Send Packet
    uart_putc(usart_module, STX);
    for (i = 0; i < packetLength; i++)
    {
        if ((packet[i] == STX) || (packet[i] == ETX) || (packet[i] == DLE))
        {
            uart_putc(usart_module, DLE);
        }
        uart_putc(usart_module, packet[i]);
    }
    uart_putc(usart_module, ETX);
}

因此,这将发送一个数据包,但现在我需要添加一些代码来跟踪数据包并自动重新发送失败的数据包,这就是我需要一些帮助的一些想法。

我拥有的夫妻选择; - 最简单的,分配一个256个数据包的巨大阵列,每个数据包都有足够的空间来存储数据包,在发送数据包之后,将其放入缓冲区,如果我在x个时间后没有收到ACK,则再次传输。然后,如果我收到一个ACK,从数组中删除该记录,以便输入为空,所以我知道它收到了很好。

问题是,如果我使用256字节的最坏情况数据包大小,以及它们的256个实例,那就是64K的RAM而我没有那个(即使我这样做,那也是一个可怕的想法)

- 接下来的想法,创建所有数据包的链接列表,并使用malloc命令等动态分配内存,并删除已确认的数据包,并保留那些不知道哪些数据包,以便在x时间之后重新传输。< / p>

我遇到的问题是整个malloc的想法。说实话,我从来没有使用它,我不喜欢在内存有限的嵌入式环境中使用它的想法。也许那只是我很傻,但我觉得这样就可以打开一大堆我不需要的其他问题。

- 潜在的解决方案,为上面提到的所有数据包创建链接列表,但是在fifo中创建它们,并移动所有记录以将它们保存在fifo中。

e.g。 发送数据包1,将数据包放入fifo中 发送数据包2,将数据包放入fifo中 收到数据包1的NACK,什么都不做 发送数据包3,将数据包放入fifo中 接收到数据包2的ACK,将memset数据包2接收到FIFO中的0x00 接收到数据包3的ACK,将memset数据包2接收到FIFO中的0x00 发送数据包4,将数据包放入fifo中 发送数据包5,将数据包放入fifo中 在FIFO中没有更多的空间,通过并将所有内容随机播放到前面,因为数据包2和3现在是空的。

可以让他们实时进行洗牌,但是我们需要在收到每个数据包之后将整个fifo洗牌,这似乎是很多不必要的工作?

我正在寻找的是你们其中一个人说“Jeez Ned,那不错,但如果你只是做xyz,那么可以节省你的工作量或RAM或复杂性”等等。

只是希望有几个人能够真正反省您的想法,因为您通常会获得最佳解决方案。我喜欢我的解决方案,但我觉得我错过了一些东西,或者我可能过于复杂吗?不确定...我只是不觉得这个想法100%满意我不这么认为,但只是想不出更好的方法。

2 个答案:

答案 0 :(得分:0)

您不需要使用malloc。只是静态地分配所有数据包,可能是一个struct数组。但是不要在运行时使用数组迭代数据包。相反,扩展您的链接列表的想法。创建两个列表,一个用于等待ACK的数据包,另一个用于免费(即可用)数据包。在启动时,将每个数据包添加到空闲列表。发送数据包时,将其从空闲列表中删除并将其添加到等待确认列表中。使等待确认列表双向链接,以便您可以从列表中间删除数据包并将其返回到空闲列表。

如果您的数据包大小变化很大并且您希望支持更多内存较少的数据包,那么您可以为不同大小的数据包创建多个空闲列表。例如,max-size-free-list保存最大的数据包,而economic-size-free-list保存较小的数据包。只要可以知道将数据包返回到哪个空闲列表,等待确认列表就可以保持两种大小。这可以通过在数据包结构中添加一个标志来实现。

typedef enum PacketSizeType
  PACKET_SIZE_MAX = 0,
  PACKET_SIZE_ECONOMY
} PacketSizeType;

typedef struct PacketBase{
  PacketBase * next;
  PacketBase * prev;
  PacketSizeType type;
  uint8_t data[1];  // a place holder (must be last)
} PacketBase;

typedef struct PacketMax
{
  PacketBase base;  // inherit from PacketBase (must be first)
  uint8_t data[255];
} PacketMax;

typedef struct PacketEconomy
{
  PacketBase base;  // inherit from PacketBase (must be first)
  uint8_t data[30];
} PacketEconomy;

PacketMax MaxSizedPackets[100];
PacketEconomy EconomySizedPackets[100];
Packet *free_list_max;
Packet *free_list_economy;
Packet *awaiting_ack_list;

初始化代码应遍历两个数组,将base.type成员设置为MAXECONOMY,并将数据包添加到相应的空闲列表中。发送代码从适当大小的空闲列表中获取数据包,并将其移至等待确认列表。等待确认列表可以包含两种类型的数据包,因为它们都从PacketBase继承。 ACK处理程序代码应检查base.type以将数据包返回到适当的空闲列表。

答案 1 :(得分:0)

一些注释。

  • 您需要考虑订购。是否可以进行无序交付?
  • 你关心重复的数据包吗?
  • 必须在超时时检测到丢失的ACK。

我的猜测是你关心数据包传送顺序而你不想要重复数据包。

基本方法是发送带有序列号的数据包,在一段时间内等待ACK。如果收到ACK,请按顺序发送下一个数据包。如果您没有收到ACK(考虑超时和NAK等效),则重新传输。重复。

数据结构问题随后会在您的应用程序中排队,而且还有一个未完成的数据包。

如果你想要进入多个未完成的数据包,你需要一个更复杂的数据结构和一种做出选择性ACK的方法。那里有很多文献,但一个好的起点是&#34;计算机网络&#34;作者:Tannenbaum。