gpt4 book ai didi

c - 具有自动重传问题的强大串行协议(protocol)

转载 作者:行者123 更新时间:2023-11-30 16:50:20 25 4
gpt4 key购买 nike

关闭。这个问题是opinion-based .它目前不接受答案。












想改进这个问题?更新问题,以便 editing this post 可以用事实和引用来回答它.

5年前关闭。




Improve this question




长期读者,第一次海报。

我通过蓝牙在传感器和基站之间建立了串行链路。蓝牙链接非常不可靠,并且会像您不相信的那样丢弃数据包。我将此用作积极因素,并打算设计一个健壮的串行协议(protocol),该协议(protocol)可以在垃圾链接中幸存下来。

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

计划是使用字节填充来创建具有开始 (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 次后重新传输哪个数据包。

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

- 潜在的解决方案,像上面提到的那样为所有数据包创建一个链表,但在 fifo 中创建它们,然后移动所有记录以将它们保留在 fifo 中。

例如
发送数据包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% 不满意,但只是想不出更好的方法。

最佳答案

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

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

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成为 MAX 的成员或 ECONOMY ,并将数据包添加到适当的空闲列表中。发送代码从适当大小的空闲列表中获取一个数据包并将其移动到等待确认列表中。 awaiting-ACK 列表可以包含这两种类型的数据包,因为它们都继承自 PacketBase。 . ACK 处理程序代码应检查 base.type将数据包返回到适当的空闲列表。

关于c - 具有自动重传问题的强大串行协议(protocol),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42238384/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com