TinyFrame是一个简单的库,用于通过串行界面(例如UART,TELNET,插座)发送的构建和解析数据帧。该代码是为了与之构建 --std=gnu99并大多与之兼容的代码--std=gnu89。
该库提供了一个高级接口,用于在两个对等方之间传递消息。库的多消息会话,响应听众,校验和超时。
TinyFrame适用于广泛的应用程序,包括Microcontroller通信,作为基于FTDI的PC应用程序的协议或通过UDP数据包发送的协议。
该库使您可以注册侦听器(回调功能)等待(1)任何帧,(2)特定的帧类型或(3)特定的消息ID。这个高级 API 足够通用,可以实现大多数通信模式。
TinyFrame 是可重入的,支持创建多个实例,但它们的结构(字段大小和校验和类型)相同。支持使用互斥体向共享实例添加多线程访问。
TinyFrame 还带有(可选)帮助函数,用于构建和解析消息有效负载,这些函数在utils/文件夹中提供。
端口
TinyFrame 已被移植到多种语言:
参考 C 实现在这个 repo 中
Python 端口 - MightyPork/PonyFrame
Rust 端口 - cpsdqs/tinyframe-rs
JavaScript 端口 - cpsdqs/tinyframe-js
请注意,大多数端口都是实验性的,可能会表现出各种错误或缺失功能。欢迎测试人员:)
功能概览
TinyFrame 的基本功能在这里解释。对于诸如API函数之类的Pracelars,建议在标题文件中读取DOC注释。
框架的结构
每个框架由标头和有效载荷组成。这两个部分都可以通过校验和保护,以确保具有畸形的标头(例如,长度损坏的字段)或损坏的有效载荷被拒绝。
帧头包含帧 ID 和消息类型。每条新消息都会增加帧 ID。ID 字段的最高位固定为两个对等体的 1 和 0,避免冲突。
框架ID可以在响应中重复使用,以将两条消息联系在一起。类型字段的值是用户定义的。
帧中的所有字段都具有可配置的大小。通过更改配置文件中的字段,例如TF_LEN_BYTES(1、2 或 4),库可以无缝地在 和 之间切换,uint8_t以便 所有使用该字段的函数。uint16_tuint32_t
,
| SOF | ID | LEN | TYPE | HEAD_CKSUM | DATA | DATA_CKSUM |
| 0-1 | 1-4 | 1-4 | 1-4 | 0-4 | ... | 0-4 | <- size (bytes)
'
SOF ......... start of frame, usually 0x01 (optional, configurable)
ID ......... the frame ID (MSb is the peer bit)
LEN ......... number of data bytes in the frame
TYPE ........ message type (used to run Type Listeners, pick any values you like)
HEAD_CKSUM .. header checksum
DATA ........ LEN bytes of data
DATA_CKSUM .. data checksum (left out if LEN is 0)
消息监听器
TinyFrame基于消息听众的概念。侦听器是等待接收特定消息类型或 ID 的回调函数。
有3种侦听器类型,按优先顺序:
ID听众- 等待回复
类型侦听器 - 等待给定类型字段的消息
通用侦听器- 后备
ID监听器可以在发送消息时自动注册。所有侦听器也可以手动注册和删除。
ID 侦听器用于接收对请求的响应。注册 ID 侦听器时,可以将自定义用户数据附加到侦听器回调中。此数据 ( void *) 可以是任何类型的应用程序上下文变量。
可以为 ID 侦听器分配超时。当侦听器过期时,在它被删除之前,回调会使用 NULL 有效负载数据触发,以便让用户获得free()任何附加的用户数据。仅当用户数据不为 NULL 时才会发生这种情况。
侦听器回调返回TF_Result枚举的值:
TF_CLOSE- 消息接受,移除监听器
TF_STAY- 消息接受,保持注册
TF_RENEW-sameas TF_STAY,但 ID 侦听器的超时更新
TF_NEXT- 不接受消息,保留侦听器并将消息传递给能够处理它的下一个侦听器。
数据缓冲区,多部分帧
TinyFrame 使用两个数据缓冲区:一个小的发送缓冲区和一个较大的接收缓冲区。发送缓冲区用于准备要发送的字节,或者一次全部发送,或者如果缓冲区不够大,则以循环方式发送。缓冲区必须只包含整个帧头,因此例如 32 个字节对于短消息应该是足够的。
使用*_Multipart()发送函数,还可以将帧头和有效负载拆分为多个函数调用,从而允许应用程序即时生成有效负载。
与发送缓冲区相比,接收缓冲区必须足够大以包含整个帧。这是因为必须在处理帧之前验证最终校验和。
如果需要大于可能的接收缓冲区大小的帧(例如在具有小 RAM 的嵌入式系统中),建议在更高级别实现多消息传输机制并以块的形式发送数据。
使用提示
所有 TinyFrame 函数、typedef 和宏都以前缀开头TF_。
两个对等点都必须包含具有相同配置参数的库
请参阅如何配置和集成库以供参考TF_Integration.example.c。TF_Config.example.c
如果可能,请勿修改库文件。这使得升级变得容易。
首先调用或作为参数TF_Init()。这将创建一个句柄。用于避免使用 malloc()。TF_MASTERTF_SLAVETF_InitStatic()
如果使用多个实例,您可以使用tf.userdata/tf.usertag字段标记它们。
实现TF_WriteImpl()- 在头文件的底部声明为extern. 此函数用于TF_Send()和其他人将字节写入您的 UART(或其他物理层)。一个帧可以整体发送,也可以分多个部分发送,具体取决于其大小。
使用 TF_AcceptChar(tf, byte) 将读取的数据提供给 TF。TF_Accept(tf, bytes, count) 将接受多个字节。
如果您希望使用超时,请定期调用TF_Tick(). 调用周期决定了 1 个刻度的长度。这用于在解析器陷入错误状态(例如接收部分帧)时使解析器超时,并且还可以使 ID 侦听器超时。
TF_AddTypeListener()使用或绑定类型或通用侦听器TF_AddGenericListener()。
TF_Send()使用, TF_Query(), TF_SendSimple(),发送消息TF_QuerySimple()。查询函数接受一个侦听器回调(函数指针),它将作为 ID 侦听器添加并等待响应。
对在多个函数调用中生成的有效负载使用*_Multipart()上述发送函数的变体。之后通过调用发送有效载荷,并由TF_Multipart_Payload() 关闭帧TF_Multipart_Close()。
如果需要自定义校验和实现,请选择TF_CKSUM_CUSTOM8、16 或 32 并实现三个校验和功能。
要回复消息(当您的侦听器被调用时),请使用TF_Respond() 您收到的 msg 对象,将data指针 (and len) 替换为响应。
您可以随时使用 手动重置消息解析器TF_ResetParser()。它也可以在配置文件中配置的超时后自动重置。
需要注意的问题
如果任何 userdata 附加到 ID 侦听器超时,当侦听器超时时,它将以 NULL 调用msg->data以让用户释放 userdata。因此,msg->data在继续处理消息之前需要进行检查。
如果正在发送多部分帧,则库的 Tx 部分将被锁定以防止并发访问。在尝试发送其他任何内容之前,必须完全发送并关闭该帧。
如果使用多个线程,不要忘记实现互斥回调以避免并发访问 Tx 函数。默认实现不是完全线程安全的,因为它不能依赖于平台特定的资源,如互斥锁或原子访问。TF_USE_MUTEX在1配置文件中设置。
例子
您将在demo/文件夹中找到各种示例。每个示例都有自己的 Makefile,阅读它以查看可用的选项。
演示是为Linux编写的,有些是使用插座和clone()背景处理的。他们试图在具有异步RX和TX的嵌入式系统中模拟真实的小框架行为。如果您无法运行演示,源文件仍然可以作为示例。
原作者:Ondřej Hruška