3个版本 (重大更新)
0.999.99 | 2024年1月18日 |
---|---|
0.99.9 | 2024年1月13日 |
0.9.9 | 2024年1月12日 |
#117 in 数据库实现
59KB
991 行
Erdnuss Comms
这是Erdnuss RS-485通信协议的核心引擎。
版本控制
我计划尽快将其升级到v1.0。这很可能会在我完成文档编写并可能更新几个依赖项版本后完成。
在此之前
- 重大变更将增加小版本号9,例如
0.9.x
->0.99.x
->0.999.x
- 非重大变更将增加一个9的简单版本号,例如
0.x.9
->0.x.99
。这可能不会在重大变更时重置,因为那样更有趣。
许可证
MPLv2.0
lib.rs
:
Erdnuss Comms
这是erdnuss项目的“网络栈”。它打算用于RS-485总线。目前,它只期望在裸机设备上以固定的网络速度7.812MHz工作。
这个网络栈打算用于半双工RS-485总线。
目前,单个总线上只支持32个设备。这也恰好是低成本硬件收发器支持的极限。
目前,总线上所有通信要么是控制器到单个目标,要么是单个目标到控制器。还没有提供目标到目标的消息传递功能。
实体
在这个网络栈中有两个角色
- 控制器(或CON)角色,负责指挥和管理分配给总线上所有其他设备的时间片段
- 目标(或TGT)角色,只响应控制器的命令。
这个术语与I3C和其他类似总线协议中使用相同术语的情况相匹配,其中只有一个设备“负责”驱动通信。
目前,这些角色预计在编译时永久分配。任何总线上必须有且只有一个控制器。
消息封装
公交车上的消息由换行符进行封装,通知它们达到“帧结束”状态。这是为了允许相对较低的CPU占用率,大多数节点在一个循环中
- 开始DMA接收,直到发生换行中断
- 离开并执行任何操作
- 一旦发生换行,检查接收到的消息是否有效,并且是否针对该节点。如果是:处理它并可能进行响应。如果不是:忽略该帧并返回到步骤1。
选择换行符是因为它在RP2040硬件UART实现中得到良好支持。
选择换行符而不是9位消息(其中msb用作地址/数据标志),因为RP2040不支持9位串行,并且没有地址匹配中断,这是STM32等设备通常具有的。
选择换行符而不是“空闲行”中断,因为虽然RP2040确实有空闲行中断,但在使用DMA时它不起作用,因为空闲中断仅在线路安静且接收FIFO中有数据时才会触发,而RX DMA在积极从接收FIFO中清除字节时不会发生这种情况。
时分复用
由于RS-485是一个半双工、共享介质的总线;必须协调所有发送者以避免消息冲突。
这是通过让控制器“负责”总线来实现的。控制器与目标之间的通信是基于轮询的,通常看起来像这样
- 线路空闲,控制器决定向特定的目标发送
- 控制器发送一个命令地址字节,包含控制器的ID
- 如果控制器有针对该目标的挂起消息,它随后发送该有效负载(零个或一个数据帧)
- 控制器发送完毕后,使用换行符表示“帧结束”,并开始监听1毫秒,或直到发生换行,以先到者为准。
- 目标注意到它已被指定,而所有其他未指定的目标返回到监听状态。
- 指定的目标发送一个带有其自身ID的响应地址字节
- 如果目标有针对CON的挂起消息,它随后发送该有效负载(零个或一个数据帧)
- 目标发送完毕后,使用换行符表示“帧结束”,并重新开始监听
- 控制器听到换行符,处理接收到的消息(如果有),然后返回到步骤1以处理下一个目标
自动逻辑寻址
所有设备都应具有全球唯一的64位硬件地址,类似于以太网/Wi-Fi设备上的MAC地址。对于基于RP2040的节点,这通常是通过使用QSPI闪存芯片的唯一序列号来实现的。
为了减少总线上的寻址开销,设备被动态分配一个5位地址(0..32)。
当目标首次启动时,它没有逻辑地址。控制器将定期提供未使用的地址,并且任何未分配地址的目标将随机决定是否声明该地址。
由于可能有多个目标同时尝试声明地址,分配地址的行为涉及多个步骤
- 控制器提供一个地址,并包含8字节的随机数据
- 没有地址的目标随机决定是否尝试声明该地址。这种随机机会旨在减少两个或多个节点尝试声明同一地址的冲突。
- 如果目标决定继续,它将取这8个随机字节,并将它们与自己的8字节唯一硬件ID进行XOR运算,并发送一个“声明”消息
- 如果控制器收到这个声明,它将取接收到的8个字节,并将它们与原始的8个随机字节进行XOR运算。如果没有发生冲突,它应该剩下新目标的MAC地址。控制器将该地址和唯一ID标记为“挂起”
- 稍后,控制器向逻辑地址发送一条消息,包含它认为在第4步中听到的MAC地址,并等待确认。
- 如果目标设备听到它声明的逻辑地址,并且唯一的ID与自己的唯一ID匹配,那么它会发送确认消息,并认为它“加入”了总线,独占该逻辑地址。
- 如果控制器收到确认,它会将该地址标记为完全分配。如果没有收到确认,它会将该地址从“待定”状态标记为“空闲”。
目前,第2步的随机机会是1/8,但将来可能会有所改变。
控制器“步骤”
到目前为止,我们已描述了单个CON/TGT通信的过程。这必须在总线上所有的TGT中进行。一般来说,控制器执行一个无休止的轮询循环,包括三个阶段
- 对于每个具有已分配逻辑地址的已知TGT
- 向TGT发送地址,另外发送0或1个数据帧
- 等待TGT做出响应。
- 如果它确实做出了响应,它将使用0或1个数据帧进行响应,并清除“失败计数器”。
- 如果它没有做出响应,我们将增加一个“失败计数器”。
- 对于“待定”阶段中的每个地址
- 我们发送确认消息(如上所述的第5步)
- 等待TGT做出响应或超时(如上所述的第7步)
- 如果我们有任何剩余的未分配逻辑地址
- 发送“提供消息”(如上所述的第1步)
- 等待TGT做出响应或超时(如上所述的第4步)
目前,应用程序负责决定“步骤”执行的频率。这可能是一直进行、每N毫秒一次,或其他某种指标。
每秒步骤越多意味着
- 在检查和响应消息上花费的CPU时间越多
- 从CON到TGT或从TGT到CON等待传输的消息的延迟越低
- 总线上的数据吞吐量更高
每秒步骤越少将意味着相反。将来,可能会有一种更智能地自适应轮询的更好方法。
移除不活跃的设备
由于所有目标设备都预期会迅速响应控制器的所有查询,控制器使用“三连败出局”规则来避免在无响应的目标设备上浪费总线时间。如果一个目标设备连续三次未做出响应,它将被移除,该地址将被标记为空闲。
依赖关系
~4MB
~79K SLoC