前言:
实际项目中,一个单片机经常要和另一个单片机通信,需要自定义一些通信协议,实现可靠通信,自己尝试写过一些,但没什么面向对象的精华,读RT-Thread简介看到了RT-Link,对于RT-Link个人总结如下。
每包发送数据为(4个字节的包头+4字节的帧数据信息+传输内容+4字节的crc),每包数据多12字节的传输开销,就可以实现数据重传、帧序号检查、状态同步等一系列能力,保证传输的稳定。一包数据的最大长度可配置,多个包数据可以自动组合成需要的一帧长数据。
发送结果,有回调通知,通知成功的话,能确保接收端收到了信息。
接收到数据,会处理成发送端的一帧有效传输内容,再回调通知。

RT-Link 介绍

初步看RT-Link的介绍,这个组件可以建立点对点的不同端口通信,一开始理解是能让单片机的进程和进程通信,无线射频设备可以建立不同的信道通信,但需要模组硬件跟着适配,但读了源码发现,这功能是可以建立不同硬件的点对点通信,一个主MCU/SOC可以和多个从MCU/SOC建立不同的串口通信或者无线设备通信,这就很nice,符合应用需求。这个理解误差也让我觉得读懂源码是挺好的学习,要实现对项目的具体功能扩展和使用,就需要对源码的机制了解,才会无bug。

开始记录自己读源码RT_Link V0.2.0的理解,首先是看官方使用例程,看应用层和要实现的接口是什么,然后是看最重要的头文件”rtlink.h“,理解有什么数据结构和协议。注解的包和帧说明,一帧可能是多包组合而成,也可能一包组成,每次硬件发送一次数据,称为一包。

目录

  • 1 应用层接口
    • 1.1 使用方式
    • 1.2 应用和驱动层要实现的接口
  • 2 配置使用
  • 3 RT-Link的协议处理
    • 3.1 RT-Link的发送处理
      • 3.1.1 rt_size_t rt_link_send(struct rt_link_service *service, const void *data, rt_size_t size)
      • 3.1.2 static void rt_link_send_ready(void)
      • 3.1.3 static rt_err_t rt_link_frame_send(rt_slist_t *slist)
      • 3.1.4 static int rt_link_command_frame_send(rt_uint8_t serv, rt_uint8_t sequence, rt_link_frame_attr_e attribute, rt_uint16_t parameter)
      • 3.1.5 static rt_size_t frame_send(struct rt_link_frame *frame)
      • 3.1.6 static rt_err_t rt_link_resend_handle(struct rt_link_frame *receive_frame)
    • 3.2 RT-Link的接收处理
      • 3.2.1 static void rt_link_frame_check(void)
      • 3.2.2 static rt_err_t rt_link_parse_frame(struct rt_link_frame *receive_frame)
      • 3.2.3 static void _long_handle_first(struct rt_link_frame *receive_frame)
      • 3.2.4 static void _long_handle_second(struct rt_link_frame *receive_frame)
  • 4 操作系统帮助处理的内容
  • 5 其他部分API解析
    • 5.1 关于握手的细节
  • 6 写在最后

1 应用层接口

1.1 使用方式

发送调用接口如下
rt_size_t rt_link_port_send(void *data, rt_size_t length);
接收会有一个回调函数入口设置。

1.2 应用和驱动层要实现的接口

/* 需要在传输端口中实现的功能 /
rt_err_t rt_link_port_init(void);//对通信的硬件初始化,uart、iic、spi等。
rt_err_t rt_link_port_deinit(void);//去初始化,如果用了动态内存初始化,需要释放
rt_err_t rt_link_port_reconnect(void);//重连,可以建立一个握手机制或者不需要
/
当通信的硬件接收到数据要写入这个API,一般是硬件中断接收完一包写入*/
rt_size_t rt_link_hw_write_cb(void data, rt_size_t length);
/
service 结构体对象初始化 */
rt_err_t rt_link_service_attach(struct rt_link_service *serv)

//service 结构体对象初始化,建立一个设备之间通信,需要一个变量,可以是全局的或者内存申请
static struct rt_link_service serv_socket;
//结构体分析如下
struct rt_link_service
{rt_int32_t timeout_tx;//发送超时时间void (*send_cb)(struct rt_link_service *service, void *buffer);//发送结果回调//接收回调void (*recv_cb)(struct rt_link_service *service, void *data, rt_size_t size);void *user_data;/*记录向上一层的数据结构地址(父亲结构体),源码用的是rt_link_device结构用于使用RTT的(rt_device)和(rt_slist_node)结构功能是继承的思想,以及将驱动接口的使用变成统一的设备操作接口,是抽象统一的思想 */rt_uint8_t flag;            /* Whether to use the CRC and ACK */rt_link_service_e service;  /* 端口号,可以理解是设备的硬件通信方式编号 */rt_link_linkstate_e state;  /* 源码处理使用,这个结构体只用了2种状态,2个api用到,RT_LINK_INIT在rt_link_service_attach和RT_LINK_DISCONN在rt_link_service_detach*/rt_link_err_e err;   /* 整个源码 通用错误类型 debug时候理解*/
};/*自定义一些通信类型,如socket、wifi、uart1、uart2、lora、zigbee,类似tcp/ip的端口号*/
typedef enum
{RT_LINK_SERVICE_RTLINK   = 0,RT_LINK_SERVICE_SOCKET   = 1,RT_LINK_SERVICE_WIFI     = 2,RT_LINK_SERVICE_MNGT     = 3,RT_LINK_SERVICE_MSHTOOLS = 4,/* Expandable to a maximum of 31,0到31共 32位,因为端口号在包头只分了5比特 */RT_LINK_SERVICE_MAX
} rt_link_service_e;
typedef enum//这个枚举类型是rt_link内部处理机制使用,无关通信协议
{RT_LINK_INIT    = 0,//初始化成功赋值这个状态RT_LINK_DISCONN = 1,//断开连接,赋值这个状态RT_LINK_CONNECT = 2,//握手赋值这个状态
} rt_link_linkstate_e;int rtlink_exinit(void)
{//不同类型,可实现建立不同硬件的点对点通信serv_socket.service = RT_LINK_SERVICE_SOCKET;serv_socket.timeout_tx = RT_WAITING_FOREVER;//发送超时等待serv_socket.flag = RT_LINK_FLAG_ACK | RT_LINK_FLAG_CRC;//是否要ack和crc校验serv_socket.recv_cb = recv_cb;//接收回调,有效数据,源码处理好的通信内容数据serv_socket.send_cb = send_cb;//发送结果回调,用于调试和可以记录成通信日志rt_link_service_attach(&serv_socket);//注册绑定serv_socket结构体,并发送一次握手包,2次握手即可。
}

理解实现以上接口,就能使用这套源码了,下面是对这套源码的配置使用。

2 配置使用

配置使用就在”rtlink.h“头文件,理解宏定义和源码的一些操作有利于改动配置

#ifndef __RT_LINK_H__
#define __RT_LINK_H__#include <rtdef.h>#define RT_LINK_VER     "0.2.0"#define RT_LINK_AUTO_INIT
/*
定义了会自动初始化,rtlink.c有以下代码
#ifdef RT_LINK_AUTO_INITINIT_ENV_EXPORT(rt_link_init);//RTT的environment初始化,也可以归类为组件初始化。
#endif
MSH_CMD_EXPORT(rt_link_init, rt link init);//导出到msh命令
*//* 下面4个宏,都只在rt_link_frame_init这个API使用,
其中RT_LINK_FRAME_HEAD_MASK这个掩码有大小端的细节, */
#define RT_LINK_FLAG_ACK            0x01
#define RT_LINK_FLAG_CRC            0x02#define RT_LINK_FRAME_HEAD          0x15
#define RT_LINK_FRAME_HEAD_MASK     0x1F

rt_link_frame_init这个API被3个API调用

函数原型如下,可以看到2个传的是空,rt_link_send传入的是rt_link_service_attach配置结构的标志,来确定协议是否要ack和crc标志。

static int rt_link_frame_init(struct rt_link_frame *frame, rt_uint8_t config)
{if (frame == RT_NULL){return -RT_ERROR;}/* set frame control information 先将帧头清空,帧头有4个字节,每包数据都会附带 */rt_memset(&frame->head, 0, sizeof(struct rt_link_frame_head));if (config & RT_LINK_FLAG_CRC){frame->head.crc = 1;}if (config & RT_LINK_FLAG_ACK){frame->head.ack = 1;}frame->head.magicid = RT_LINK_FRAME_HEAD;//帧逻辑头,在包的第一个字节的低5bit/* frame data information */rt_memset(&frame->extend, 0, sizeof(struct rt_link_extend));frame->crc = 0;//crc初始值要赋值0去计算frame->real_data = RT_NULL;//要发送真实数据的指向地址frame->data_len = 0;//此包真实数据的大小  frame->index = 0; //帧的包索引frame->total = 0; //一帧的总包数//默认帧类型,后续发送的时候会改动这个信息frame->attribute = RT_LINK_RESERVE_FRAME;//默认没有发送完帧frame->issent = RT_LINK_FRAME_NOSEND;//将改包指向的下一个包头信息为空rt_slist_init(&frame->slist);return RT_EOK;
}

数据包占用了4字节,定义用了位域,其中magicid、extend 、crc 、ack合起来用了一个字节,sequence一个字节, service+length 2个字节

struct rt_link_frame_head
{rt_uint8_t magicid : 5;//数据包头标识 范围0x00-0x1Frt_uint8_t extend  : 1;//bit为1表示有额外的包头信息rt_uint8_t crc     : 1;//bit为1表示要crc校验rt_uint8_t ack     : 1;//bit为1表示要ackrt_uint8_t sequence;//发送数据帧的序列号rt_uint16_t service: 5;//端口号,5bit所以范围是0-31rt_uint16_t length : 11; /* range 0~2047 一次发送的数据包最大长度为2048*/
};

要梳理的是位域的存储,存储需要地址,其次是对应的RMA和ROM采用的是大端还是小段存储。
假设每个bit都有一个地址,实际上最小单位是每个字节才有一个地址,因为最小的指针是1字节类型,用位域去的定义,能访问到值的大小,但是不能访问位域名的地址,不然会报错。做了以下实验,这个实验平台的执行环境判断是小端存储。

在用位域定义后,赋值时候就会以多少个bit给到位域访问名称,截图用注释说明了实际存储的bit。重点留意11015535这个数据,即打印a结构体的内容。
我们会发现magicid存在0x404032这个地址的最低的5bit,extend 存在0x404032这个地址的最低的第6bit,0x404032地址字节的二进制就是00110101就是0x35(第一个bit是ack,第二个bit是crc,程序没有赋值,但是全局变量不赋值,定义在ZI-data数据段,当这个程序执行时,对应的启动内核一般将ZI-data数据段清零了,要是将a定义成局部变量,结果就不一定了,局部变量也是一个地址,地址不变,并且对这个地址的内容操作不变,结果就一定)

sequence存在0x404033这个地址,存储的是0x55。service是5bit,所以赋值1就是00001。
最终打印a结构变量值的时候,显示11015535,我们知道第一个字节地址0x404032存储的是0x35,第二个字节地址0x404033存储的是0x55,第二个字节地址比第一个字节地址高,第3个字节存储的是0x01,第4个字节存储的是0x11,所以a结构体内容为(11015535),表示一个数,大数写在最前面,也称高位,即低地址内容存在了低位,高地址内容存在了高位,属于小端存储。
如果访问a.length会发现是0x88,但不能访问a.length的地址,具体怎么能得到0x88的,和c库的位域源码处理相关,推测是取某个地址附近的11个比特,赋值到2个字节的RAM存储空间去运行。

以上是表诉可能有些抽象,主要是为了说明下面两个宏定义
#define RT_LINK_FRAME_HEAD 0x15
#define RT_LINK_FRAME_HEAD_MASK 0x1F
0x15属于0x00-0x1F的范围,可以定义为这个头标识,掩码是0x1F(000011111)的原因就是默认magicid这5个bit是小端存储。也就是这个协议包的发送和识别默认是小端存储,如果小端的数据包到了大端运行的电子设备里,需要做字节序调整,大部分嵌入式设备都是小端存储。

/* The maximum number of split frames for a long package */
#define RT_LINK_FRAMES_MAX          0x03
//定义一帧数据,最大分割为3包,分割包数为RT_LINK_MAX_DATA_LENGTH长度的1到n倍#define RT_LINK_MAX_FRAME_LENGTH    1024
/*定义单片机的发送buff的最大长度,在struct rt_link_session结构体用到,
rt_link_init初始化的时候会动态申请整个结构体,最大设置为2048+4+4+4,
因为11个bit最多表达的真实数据长度为2048,再加上每包数据都要补充4个字节的包头/帧信息/crc*//*2个功能,最大发送包数3次。
功能1,因为发送端,每发送一次,右移动一位,0x07(00000111),在rt_link_frame_send用到
如果改动了RT_LINK_FRAMES_MAX一帧最大不是3,也需改动rt_link_frame_send里面的send_max,这里设计开始以为有bug。
功能2,作为ack的掩码,在_long_handle_second中用到 */
#define RT_LINK_ACK_MAX             0x07
/*后续理解纠正,这个宏可以不配合RT_LINK_FRAMES_MAX一起改动,就默认值0x07,属于源码协议的细节,理解了就666,这个宏的意思是,发送多包为一帧的时候,最大多少包内就需要一个ack,保证传输的效率,不要一股脑的瞎发,中途丢包了还继续发后续内容,就会影响接收数据的效率。因为源码有滑动存储的机制,但是没有滑动的ack机制,所有长帧每3包内需要顺序接收,0x07的意思是,发长帧的时候,3包内需要一个ack*/#define RT_LINK_CRC_LENGTH          4//用的crc32,4字节
#define RT_LINK_HEAD_LENGTH         4//包头固定4字节#define RT_LINK_EXTEND_LENGTH       4//4字节帧信息,这4个字节数据比较灵活多变/*2个字节rt_link_frame_attr_e的类型,用于说明目前帧的情况,2个字节为参数,源码使用传入的是0或者接收的序列号,这4个字节是通信协议自身需要的内容*///最大发送一包真实数据的长度,为一包的缓存buff-4字节头-4字节额外的包头信息-4字节的crc
#define RT_LINK_MAX_DATA_LENGTH         (RT_LINK_MAX_FRAME_LENGTH - \RT_LINK_HEAD_LENGTH - \RT_LINK_EXTEND_LENGTH - \RT_LINK_CRC_LENGTH)/*有发送buff就有接收buff,最大长度为,
发送buff的长度乘最大的分包数+4字节头长度+4字节额外包头信息长度
在rt_link_hw_buffer_init里面会动态申请一个接收buff,用于应用层rt_link_hw_write_cb的写入,和fifo处理,这里定义的不大,如果发送方直接来一包最大发送帧,那整个fifo就满了,如果没处理完,硬件收到数据还调用写入,就会写到fifo满*/
#define RT_LINK_RECEIVE_BUFFER_LENGTH   (RT_LINK_MAX_FRAME_LENGTH * \RT_LINK_FRAMES_MAX + \RT_LINK_HEAD_LENGTH + \RT_LINK_EXTEND_LENGTH)

综合上面的信息,根据实际的硬件通信,去定义发送包最大,根据单片机自身RAM考虑接收fifo的大小,要配置的宏,初步看3个就够了。
是否要用RTT的自动初始化
#define RT_LINK_AUTO_INIT
一帧数据的最大分包数
#define RT_LINK_FRAMES_MAX 0x03
一包数据的最大长度
#define RT_LINK_MAX_FRAME_LENGTH 1024

考虑实际的应用,有些硬件设备,一包的数据最大长度往往不会很大,比如无线通信模组,发送的fifo字节可能不到128字节,如果不想用模组的发送中断,直接简单的填fifo就发送出去,那么RT_LINK_MAX_FRAME_LENGTH宏应该定义为小于通信模组的发送fifo,方便硬件接口的实现,RT_LINK_FRAMES_MAX 可以定义大一些。应用层能一次发送的最大buff为
RT_LINK_FRAMES_MAX * RT_LINK_MAX_DATA_LENGTH
如果不涉及模组的发送和接收fifo大小,用串口、485等硬件通信,考虑双方发送和接收能力,再去定义发送和接收的大小。

3 RT-Link的协议处理

3.1 RT-Link的发送处理

从应用层的rt_size_t rt_link_port_send(void *data, rt_size_t length);
向下追踪经过RTT的设备和驱动框架,调用的是下面的API

3.1.1 rt_size_t rt_link_send(struct rt_link_service *service, const void *data, rt_size_t size)

//传入初始化好的结构体信息,包含硬件所属的端口号,是否ack,crc等的信息
rt_size_t rt_link_send(struct rt_link_service *service, const void *data, rt_size_t size)
{RT_ASSERT(service != RT_NULL);rt_uint32_t recved = 0;rt_uint8_t total = 0; /* The total number of frames to send */rt_uint8_t index = 0; /* The index of the split packet */rt_size_t offset = 0; /* The offset of the send data */rt_size_t send_len = 0;struct rt_link_frame *send_frame = RT_NULL;//包协议流程默认赋值一包能发送完一帧rt_link_frame_attr_e attribute = RT_LINK_SHORT_DATA_FRAME;if ((size == 0) || (data == RT_NULL)){service->err = RT_LINK_ERR;goto __exit;}service->err = RT_LINK_EOK;//发送真实内容的长度(size )如果是RT_LINK_MAX_DATA_LENGTH的整数倍if (size % RT_LINK_MAX_DATA_LENGTH == 0){//发送包数将会大于1,并且刚好用整数包能发完total = size / RT_LINK_MAX_DATA_LENGTH;}else{//发送包数最小为1total = size / RT_LINK_MAX_DATA_LENGTH + 1;}//一帧需要的发送包数大于设定的一帧最大包就报错退出if (total > RT_LINK_FRAMES_MAX){service->err = RT_LINK_ENOMEM;goto __exit;}else if (total > 1){//发送的包数大于1,将协议归属长包类型,后续会写入协议头的,4字节的包协议流程的前2个字节attribute =  RT_LINK_LONG_DATA_FRAME;}do{send_frame = rt_malloc(sizeof(struct rt_link_frame));if (send_frame == RT_NULL){service->err = RT_LINK_ENOMEM;goto __exit;}//初始化申请到的发送帧,标志的service初始化时,是否要crc和ack,前面分析过这个apirt_link_frame_init(send_frame, service->flag);//将发送的序列号先自加1再赋值,序列号的初始值通过宏RT_LINK_INIT_FRAME_SEQENCE定义send_frame->head.sequence = ++rt_link_scb->tx_seq;send_frame->head.service = service->service;//端口号send_frame->real_data = (rt_uint8_t *)data + offset;//赋值真实数据地址send_frame->index = index;//发送包索引send_frame->total = total;//一帧需要发送的包数if (attribute == RT_LINK_LONG_DATA_FRAME){    //包数据信息为长数据帧send_frame->attribute = RT_LINK_LONG_DATA_FRAME;//偏移加数据最大长度大于要发送的长度if (offset + RT_LINK_MAX_DATA_LENGTH > size){//将帧头的数据长度,赋值剩余要发送的长度send_frame->data_len = size - offset;}else{//将帧头的数据长度,赋值真实数据的最大长度send_frame->data_len = RT_LINK_MAX_DATA_LENGTH;offset += RT_LINK_MAX_DATA_LENGTH;}//配置4字节的包协议信息,2字节为RT_LINK_LONG_DATA_FRAME,2字节为一帧数据的sizert_link_frame_extend_config(send_frame, RT_LINK_LONG_DATA_FRAME, size);/*static rt_err_t rt_link_frame_extend_config(struct rt_link_frame *frame,        rt_link_frame_attr_e attribute, rt_uint16_t parameter){frame->head.extend = 1;frame->extend.attribute = attribute;frame->extend.parameter = parameter;return RT_EOK;}*/}else{//包数据信息为短数据帧send_frame->attribute = RT_LINK_SHORT_DATA_FRAME;send_frame->data_len = size;//一包就能发送完一帧}/* append the frame on the tail of list */LOG_D("append send slist, seq(%d), len(%d)", send_frame->head.sequence, send_frame->data_len);//rt_link_scb->tx_data_slist是总的发送链表,入发送链表尾,操作系统后续会调用去发送rt_slist_append(&rt_link_scb->tx_data_slist, &send_frame->slist);index++;send_len += send_frame->data_len;//包索引和发送长度增加,不够发送完一帧的话继续申请内存,配置发送包}while(total > index);/* Notify the core thread to send packet *///发送一帧准备好了事件rt_event_send(&rt_link_scb->event, RT_LINK_SEND_READY_EVENT);if (service->timeout_tx != RT_WAITING_NO){//等待帧的发送结果/* Wait for the packet to send the result */rt_err_t ret = rt_event_recv(&rt_link_scb->sendevent, (0x01 << service->service),RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR,service->timeout_tx, &recved);if (ret == -RT_ETIMEOUT){service->err = RT_LINK_ETIMEOUT;send_len = 0;}}__exit:return send_len;
}

rt_event_send(&rt_link_scb->event, RT_LINK_SEND_READY_EVENT);这个事件处理,最终调用的是下面的API

3.1.2 static void rt_link_send_ready(void)

static void rt_link_send_ready(void)
{struct rt_link_frame *frame = RT_NULL;rt_uint8_t seq = rt_link_scb->tx_seq;/*获取发送数据帧的序列号rt_link_scb是一个全局的指针,机制处理使用,无关包协议,在rt_link_init时候申请了内存 rt_link_scb->tx_seq开始有个默认发送序列号为129rt_slist_init(&rt_link_scb->tx_data_slist);rt_link_scb->tx_seq = RT_LINK_INIT_FRAME_SEQENCE;
每次调用rt_link_send,rt_link_scb->tx_seq值会加1,所以要注意,一帧的发送结果没有返回前,不能再去调用rt_link_send*//* 补充  rt_link_scb->tx_seq发送的序列号初始化为129rt_slist_init(&rt_link_scb->tx_data_slist);rt_link_scb->tx_seq = RT_LINK_INIT_FRAME_SEQENCE;*/if (rt_slist_next(&rt_link_scb->tx_data_slist))//下一个要发送的{/*** 这个精彩寻址方式,操作系统的内核中广泛使用* 根据一个已知结构体成员的指针和变量名得出宿主结构体的地址的功能,而不需要传地址参数*/frame = rt_container_of(rt_slist_next(&rt_link_scb->tx_data_slist), struct rt_link_frame, slist);/*这句的意思rt_slist_next(&rt_link_scb->tx_data_slist)的地址是(struct rt_link_frame) 结构类型里面定义了一个名称为slist的结构体成功,如果对应的上会返回宿主结构体的地址,即rt_slist_next(&rt_link_scb->tx_data_slist)地址成员的struct rt_link_frame 结构的头地址,进而能根据frame的地址去名称访问struct rt_link_frame结构类型的所有成员。如果对应不上,编译器会报错,无法编译通过*/}//rt_link_scb->state这个状态初始化的时候是RT_LINK_INIT//当执行rt_link_service_attach的时候,最后发送握手包,如果握手成功会改变成RT_LINK_CONNECTif (rt_link_scb->state != RT_LINK_CONNECT){rt_link_scb->state = RT_LINK_DISCONN;/*发送握手包,参数端口号为0,发送序列号,帧数据信息为握手,接收序列号。rt_link_scb->rx_record.rx_seq初始化为255 在rt_link_short_handle和_long_handle_second这两个api会改变这个全局变量*/rt_link_command_frame_send(RT_LINK_SERVICE_RTLINK, seq,RT_LINK_HANDSHAKE_FRAME, rt_link_scb->rx_record.rx_seq);rt_int32_t timeout = 50;rt_timer_control(&rt_link_scb->sendtimer, RT_TIMER_CTRL_SET_TIME, &timeout);rt_timer_start(&rt_link_scb->sendtimer);/*设置和启动发送定时器,定时50个tick,超时会促发rt_link_sendtimer_callback,收到任何数据都会关闭这个定时器,在rt_link_confirm_handle*/}else{/* Avoid sending the first data frame multiple times 主要判断方式是frame->issent起作用,在调用一次rt_link_frame_send后,会将frame->issent = RT_LINK_FRAME_SENT;*/if ((frame != RT_NULL) && (frame->issent == RT_LINK_FRAME_NOSEND)){if (RT_EOK != rt_link_frame_send(&rt_link_scb->tx_data_slist)){rt_link_scb->state = RT_LINK_DISCONN;//报告发送错误,这个错误可能是底层的硬件发送失败rt_link_service_send_finish(RT_LINK_EIO);}}}
}

从rt_link_send_ready的调用,可以看到是调用了下面2个API
/* performs data transmission */
static rt_err_t rt_link_frame_send(rt_slist_t *slist)

//这个API是发送协议ack包,协议自身机制的东西,发送帧序号检查、状态同步等,保证传输的稳定;
static int rt_link_command_frame_send(rt_uint8_t serv, rt_uint8_t sequence, rt_link_frame_attr_e attribute, rt_uint16_t parameter)

3.1.3 static rt_err_t rt_link_frame_send(rt_slist_t *slist)

static rt_err_t rt_link_frame_send(rt_slist_t *slist)
{rt_uint8_t is_finish = RT_FALSE;struct rt_link_frame *frame = RT_NULL;rt_uint8_t send_max = RT_LINK_ACK_MAX;//最大发送3次,改动了一帧的最大包,这里需要改动/* if slist is tx_data_slist, we should send all data on the slist*///如果传入的&rt_link_scb->tx_data_slist地址等于&rt_link_scb->tx_data_slist//判断感觉有点多余,只有rt_link_send_ready调用了这个api,有判断会更安全的访问数据if (slist == &rt_link_scb->tx_data_slist){/*slist找下一个节点,因为链表的头内容初始化为空(rt_slist_init)在rt_link_send里面,将发送帧的序列号,不断的入尾部rt_slist_append(&rt_link_scb->tx_data_slist, &send_frame->slist);头的下一个节点,就是发送帧的入口地址后续会将在挂入的所有slist包发送出去*/slist = rt_slist_next(&rt_link_scb->tx_data_slist);}if (slist == RT_NULL){LOG_W("send data list NULL");return -RT_ERROR;}do{/* get frame for send *///精彩操作,前面解释过,取得宿主结构体的地址frame = rt_container_of(slist, struct rt_link_frame, slist);slist = rt_slist_next(slist);//传入帧结构,调用发送出去if (frame_send(frame) == 0){//硬件发送失败的话,返回EIO错误rt_link_scb->service[frame->head.service]->err = RT_LINK_EIO;goto __err;}frame->issent = RT_LINK_FRAME_SENT;//发送出去,这包的发送标志置为以发送if ((slist == RT_NULL) || (frame->index + 1 >= frame->total))//判断一帧是否发送完{send_max = 0;is_finish = RT_TRUE;}else{send_max >>= 1;//发送下一包,右移一位}}while (send_max);if ((is_finish) && (frame->head.ack == 0)){/* NACK frame send finish, remove after sending */rt_link_service_send_finish(RT_LINK_EOK);//不用ack,回调通知发送okif (slist != RT_NULL){LOG_D("Continue sending");rt_event_send(&rt_link_scb->event, RT_LINK_SEND_READY_EVENT);}}else{//开启发送帧定时,100个RTT的tick,没收到ack会回调rt_link_sendtimer_callbackrt_int32_t timeout = RT_LINK_SENT_FRAME_TIMEOUT;rt_timer_control(&rt_link_scb->sendtimer, RT_TIMER_CTRL_SET_TIME, &timeout);rt_timer_start(&rt_link_scb->sendtimer);}return RT_EOK;
__err:return -RT_ERROR;
}

从上面api发现还需要分析frame_send,3.1.5再分析。

下面API是发送协议ack包,协议自身机制的东西,发送帧序号检查、状态同步等,保证传输的稳定;

3.1.4 static int rt_link_command_frame_send(rt_uint8_t serv, rt_uint8_t sequence, rt_link_frame_attr_e attribute, rt_uint16_t parameter)

static int rt_link_command_frame_send(rt_uint8_t serv, rt_uint8_t sequence, rt_link_frame_attr_e attribute, rt_uint16_t parameter)
{struct rt_link_frame command_frame = {0};rt_uint8_t data[sizeof(command_frame.head) + sizeof(command_frame.extend)] = {0};rt_uint8_t data_len = 0;/* command frame don't need crc and ack ability 不需要crc和ack,详细看后续注释源码 */rt_link_frame_init(&command_frame, RT_NULL);/*static int rt_link_frame_init(struct rt_link_frame *frame, rt_uint8_t config){if (frame == RT_NULL){return -RT_ERROR;}rt_memset(&frame->head, 0, sizeof(struct rt_link_frame_head));if (config & RT_LINK_FLAG_CRC){frame->head.crc = 1;}if (config & RT_LINK_FLAG_ACK){frame->head.ack = 1;}frame->head.magicid = RT_LINK_FRAME_HEAD;rt_memset(&frame->extend, 0, sizeof(struct rt_link_extend));frame->crc = 0;frame->real_data = RT_NULL;frame->data_len = 0;frame->index = 0;frame->total = 0;//此时frame->attribute = RT_LINK_RESERVE_FRAME,一个默认值,后面会改frame->attribute = RT_LINK_RESERVE_FRAME;frame->issent = RT_LINK_FRAME_NOSEND;rt_slist_init(&frame->slist);        return RT_EOK;}*/data_len += sizeof(command_frame.head);//配置4字节帧数据信息,将4字节帧头的extend置为1//attribute为2字节帧处理流程//parameter为2字节的接收序列号或NULLrt_link_frame_extend_config(&command_frame, attribute, parameter);rt_memcpy(data + data_len, &command_frame.extend, sizeof(command_frame.extend));data_len += sizeof(command_frame.extend);command_frame.head.sequence = sequence;//配置发送序列号command_frame.head.service = serv;//配置端口号rt_memcpy(data, &command_frame.head, sizeof(command_frame.head));rt_link_hw_send(data, data_len);return RT_EOK;
}

rt_link_command_frame_send的调用主要是要知道传入的参数,发现有很多地方调用了这个api,如下图,attribute是帧处理流程,parameter用于说明发送和接收的序列号或者是发送长帧数据的时候,说明整帧的字节长度,最大一帧65535字节。
参与了下面流程的处理,比较多就暂不展开具体说明,看完后续的接收分析,找到对应API名称展开阅读,应该比较容易理解其功能作用。

frame_send这个API就是发送一帧数据,其中的数据包,都是内存申请的,在收到对应的发送序列ack,才去释放内存。

向上追踪发现有2个函数调用了frame_send,一起分析这两个api

static rt_err_t rt_link_frame_send(rt_slist_t *slist)//前面分析过了,第一次发送内容使用

static rt_err_t rt_link_resend_handle(struct rt_link_frame *receive_frame)//重发数据用

3.1.5 static rt_size_t frame_send(struct rt_link_frame *frame)

static rt_size_t frame_send(struct rt_link_frame *frame)
{rt_size_t length = 0;rt_uint8_t *data = RT_NULL;//将发送buff清零,buff的大小就是宏RT_LINK_MAX_FRAME_LENGTH  rt_memset(rt_link_scb->sendbuffer, 0, sizeof(rt_link_scb->sendbuffer));data = rt_link_scb->sendbuffer;//指向发送buff的地址length = RT_LINK_HEAD_LENGTH;//发送长度等于4字节的协议包头if (frame->head.crc){length += RT_LINK_CRC_LENGTH;//协议头要加crc的话,发送长度加4字节的crc长度}if (frame->head.extend){length += RT_LINK_EXTEND_LENGTH;//协议头有扩展说明的话,发送长度加4字节的扩展长度}length += frame->data_len;//发送长度加传输的数据内容长度frame->head.length = frame->data_len;//将传输的数据内容长度赋值到协议头的长度rt_memcpy(data, &frame->head, RT_LINK_HEAD_LENGTH);//将4字节的协议头,拷贝到发送datadata = data + RT_LINK_HEAD_LENGTH;//data指向的地址加4字节if (frame->head.extend){//如果包头需要帧的信息,拷贝到包头的后面rt_memcpy(data, &frame->extend, RT_LINK_EXTEND_LENGTH);      data = data + RT_LINK_EXTEND_LENGTH;//data指向的地址加4字节}if (frame->attribute == RT_LINK_SHORT_DATA_FRAME || frame->attribute == RT_LINK_LONG_DATA_FRAME){//如果这帧数据的处理流程是一包能发完的短数据或者是发送长帧数据,将内容拷贝到datart_memcpy(data, frame->real_data, frame->data_len);data = data + frame->data_len;}if (frame->head.crc){//如果需要crc,计算crc的结果再拷贝到发送数据的最后frame->crc = rt_link_scb->calculate_crc(RT_FALSE, rt_link_scb->sendbuffer, length - RT_LINK_CRC_LENGTH);rt_memcpy(data, &frame->crc, RT_LINK_CRC_LENGTH);}//打印发送序列号 发送长度 帧的流程 crcLOG_D("frame send seq(%d) len(%d) attr:(%d), crc:(0x%08x).", frame->head.sequence, length, frame->attribute, frame->crc);//底层硬件发送,需实现接口和返回错误return rt_link_hw_send(rt_link_scb->sendbuffer, length);
}

3.1.6 static rt_err_t rt_link_resend_handle(struct rt_link_frame *receive_frame)

//这个接口在attribute为RT_LINK_RESEND_FRAME时会执行
static rt_err_t rt_link_resend_handle(struct rt_link_frame *receive_frame)
{struct rt_link_frame *find_frame = RT_NULL;rt_slist_t *tem_list = RT_NULL;//取发送链表头tem_list = rt_slist_first(&rt_link_scb->tx_data_slist);while (tem_list != RT_NULL){       /*找到宿主结构体的地址,rt_link_send_ready解释过,附上宏定义#define rt_container_of(ptr, type, member) \((type *)((char *)(ptr) - (unsigned long)(&((type *)0)->member)))*/        find_frame = rt_container_of(tem_list, struct rt_link_frame, slist);//包头的发送的序列号如果等于接收到需要重发的序列号if (find_frame->head.sequence == receive_frame->head.sequence){LOG_D("resend frame(%d)", find_frame->head.sequence);frame_send(find_frame);//发送这包break;}//查找下一个发送包,是否有收到的请求序列号,直到为空退出tem_list = tem_list->next;}//发送数据包指针为空,说明请求的那包数据收到被释放了,或者收到的请求有误。if (tem_list == RT_NULL){//这个应该改为报错LOG_E,可能是释放了包内存,被请求。//或者更深层原因,变量被错误逻辑修改、任务之间资源访问没保护号,内存被越界修改等LOG_D("frame resent failed, can't find(%d).", receive_frame->head.sequence);/*RT_LINK_SESSION_END  The retring failed to end the session告诉没有这个序列号了*/rt_link_command_frame_send(receive_frame->head.service,receive_frame->head.sequence,RT_LINK_SESSION_END, RT_NULL);}return RT_EOK;
}

发送先分析到这里,还差调用rt_link_command_frame_send相关的api没梳理完全,先分析接收,后续再看要不要分析。

3.2 RT-Link的接收处理

从应用层的rt_size_t rt_link_hw_write_cb(void *data, rt_size_t length);
向下追踪经过ringbuff和事件通知处理,调用的是下面的API,帧检查

3.2.1 static void rt_link_frame_check(void)

static void rt_link_frame_check(void)
{/*下面的变量,全程只在这个api使用,这种技巧可以学习下,避免定义成全局的变量,影响其他代码理解避免不了也应该封装成结构体,不偷懒*/static struct rt_link_frame receive_frame = {0}; static rt_link_frame_parse_t analysis_status = FIND_FRAME_HEAD;static rt_uint8_t *data = RT_NULL;//buff_len 作用是和fifo的最大长度对比,不能小于协议需要的字节,小于的话结束分析,等收完再来分析static rt_uint16_t buff_len = RT_LINK_HEAD_LENGTH;struct rt_link_frame *send_frame = RT_NULL;rt_tick_t timeout = 0;rt_uint32_t temporary_crc = 0;rt_uint8_t offset = 0;//读出fifo里面的长度,有协议在,用rt_link_hw_write_cb写入多包都可以分析rt_size_t recv_len = rt_link_hw_recv_len(rt_link_scb->rx_buffer);while (recv_len > 0){switch (analysis_status){case FIND_FRAME_HEAD://分析状态默认是这个,找包头{/* if we can't find frame head, throw that data *///前面配置使用,提到过头掩码为什么是0x1F,小端存储//如果fifo的读指针指向的内容,是定义的RT_LINK_FRAME_HEAD头标识if ((*rt_link_scb->rx_buffer->read_point & RT_LINK_FRAME_HEAD_MASK) == RT_LINK_FRAME_HEAD){//分析状态为解析包头analysis_status = PARSE_FRAME_HEAD;break;}//将fifo的读指针前移1字节rt_link_hw_buffer_point_shift(&rt_link_scb->rx_buffer->read_point, 1);break;}case PARSE_FRAME_HEAD://解析包头{//fifo里面的长度,小于头长度,结束帧检查apiif (recv_len < buff_len){LOG_D("HEAD: actual: %d, need: %d.", recv_len, buff_len);return ;}/* Data is an offset address */data = rt_link_scb->rx_buffer->read_point;//初始化接收帧rt_link_frame_init(&receive_frame, RT_NULL);//从fifo的读指针开始,拷贝4字节的包头信息到接收帧的包头rt_link_hw_copy((rt_uint8_t *)&receive_frame.head, data, sizeof(struct rt_link_frame_head));//将data指针,向前偏移4字节,不直接加4字节,要用api,是避免从fifo的尾巴错误加过去了rt_link_hw_buffer_point_shift(&data, sizeof(struct rt_link_frame_head));LOG_D("HEAD: seq(%d) serv(%d) ack(%d) crc(%d) ext(%d) len(%d) attr(%d)(0x%x)", receive_frame.head.sequence, receive_frame.head.service, receive_frame.head.ack, receive_frame.head.crc, receive_frame.head.extend, receive_frame.head.length, receive_frame.extend.attribute, receive_frame.extend.parameter);receive_frame.data_len = receive_frame.head.length;//如果端口号大于等于枚举的最大端口号,说明是错误头if (receive_frame.head.service >= RT_LINK_SERVICE_MAX){//fifo读指针偏移1rt_link_hw_buffer_point_shift(&rt_link_scb->rx_buffer->read_point, 1);goto __find_head;}//如果包头有帧信息if (receive_frame.head.extend){buff_len += RT_LINK_EXTEND_LENGTH;//4+4,PARSE_FRAME_EXTEND流程用到analysis_status = PARSE_FRAME_EXTEND;//分析状态为有帧信息}else{receive_frame.attribute = RT_LINK_SHORT_DATA_FRAME;//一包就是一帧格式analysis_status = PARSE_FRAME_SEQ;//分析包序列号流程}}case PARSE_FRAME_EXTEND://解析帧信息{if (receive_frame.head.extend){  //fifo的长度小于 8字节,说明没有后续的内容,结束解析if (recv_len < buff_len){LOG_D("EXTEND: actual: %d, need: %d.", recv_len, buff_len);/* should set timer, control receive frame timeout, one shot */timeout = 50;rt_timer_control(&rt_link_scb->recvtimer, RT_TIMER_CTRL_SET_TIME, &timeout);rt_timer_start(&rt_link_scb->recvtimer);//定时操作的作用是,收到了包头,但是后续内容没有发过来,定时一下接收超时回调return;}rt_timer_stop(&rt_link_scb->recvtimer);//从fifo拷贝一下4字节的帧信息rt_link_hw_copy((rt_uint8_t *)&receive_frame.extend, data, sizeof(struct rt_link_extend));//指针后移4rt_link_hw_buffer_point_shift(&data, sizeof(struct rt_link_extend));//帧的流程是否小于默认的流程,枚举的流程,最后是RT_LINK_RESERVE_FRAMEif (receive_frame.extend.attribute < RT_LINK_RESERVE_FRAME){receive_frame.attribute = receive_frame.extend.attribute;}else{   //发送过来的帧流程有问题,没有初始化发送帧可能,fifo读指针后移1,去找头LOG_D("EXTEND: attr(%d) err", receive_frame.extend.attribute);rt_link_hw_buffer_point_shift(&rt_link_scb->rx_buffer->read_point, 1);goto __find_head;}}analysis_status = PARSE_FRAME_SEQ;//分析包序列号}case PARSE_FRAME_SEQ:{switch (receive_frame.attribute){case RT_LINK_CONFIRM_FRAME:case RT_LINK_RESEND_FRAME://确定帧和响应帧{/* Check the send sequence *///返回接收到的帧序列号和在发送的序列号的偏移offset = rt_link_check_seq(receive_frame.head.sequence, rt_link_scb->tx_seq);//如果发送的链表不为空if (rt_slist_first(&rt_link_scb->tx_data_slist) != RT_NULL){//找到成员rt_link_scb->tx_data_slist.next的宿主结构体头指针send_frame = rt_container_of(rt_link_scb->tx_data_slist.next, struct rt_link_frame, slist);/*如果偏移大于了发送链表记录的包信息的总包数,说明此包头信息不符合发送链表的包头                 信息,举例,此发送包的序列号是5,总包数是3.分析接收到的包信息序列号是10,说明不是我序列号5要的数据包。因为序列号在发送端都是顺序递增1的,到255递增为0.rt_link_check_seq处理好了偏移计算。*/if (offset > send_frame->total){/* exceptional frame, ignore it */LOG_D("seq (%d) failed, tx_seq (%d).offset=(%d) total= (%d)", receive_frame.head.sequence, rt_link_scb->tx_seq, offset, send_frame->total);rt_link_hw_buffer_point_shift(&rt_link_scb->rx_buffer->read_point, 1);goto __find_head;}}break;}//默认发送的帧信息都是长包帧信息或短包帧信息case RT_LINK_LONG_DATA_FRAME:case RT_LINK_SHORT_DATA_FRAME:/*还记得发送时候,帧信息什么时候为RT_LINK_SESSION_END吗?当找不到请求的序列号的时候,此时传入的序列号为接收的请求序列号。*/case RT_LINK_SESSION_END:{/* Check the receive sequence *///检查此包的序列号和接收请求的序列号偏移offset = rt_link_check_seq(receive_frame.head.sequence, rt_link_scb->rx_record.rx_seq) - 1;//是否偏移大于一帧的最大包数if (offset > RT_LINK_FRAMES_MAX){/* exceptional frame, ignore it */LOG_D("seq (%d) failed, rx_seq (%d) offset=(%d) attr= (%d) status (%d)", receive_frame.head.sequence, rt_link_scb->rx_record.rx_seq, offset, receive_frame.attribute, rt_link_scb->state);rt_link_hw_buffer_point_shift(&rt_link_scb->rx_buffer->read_point, 1);                goto __find_head;}}case RT_LINK_HANDSHAKE_FRAME:case RT_LINK_DETACH_FRAME:analysis_status = HEADLE_FRAME_DATA;break;default:LOG_D("quick filter error frame.");rt_link_hw_buffer_point_shift(&rt_link_scb->rx_buffer->read_point, 1);goto __find_head;}//正确解析了包头,加上真实数据长度buff_len += receive_frame.data_len;if (receive_frame.head.crc){buff_len += RT_LINK_CRC_LENGTH;analysis_status = CHECK_FRAME_CRC;//如果协议要crc,去检查crc}else{analysis_status = HEADLE_FRAME_DATA;}/* fill real data point */receive_frame.real_data = data;}//将整个包内容(头+帧信息+数据),crc计算,不包括最后的4字节crccase CHECK_FRAME_CRC:{if (receive_frame.head.crc){if (recv_len < buff_len){LOG_D("CRC: actual: %d, need: %d.", recv_len, buff_len);/* should set timer, control receive frame timeout, one shot */timeout = 50;rt_timer_control(&rt_link_scb->recvtimer, RT_TIMER_CTRL_SET_TIME, &timeout);rt_timer_start(&rt_link_scb->recvtimer);return;}rt_timer_stop(&rt_link_scb->recvtimer);rt_link_hw_buffer_point_shift(&data, receive_frame.data_len);rt_link_hw_copy((rt_uint8_t *)&receive_frame.crc, data, RT_LINK_CRC_LENGTH);temporary_crc = rt_link_scb->calculate_crc(RT_TRUE, rt_link_scb->rx_buffer->read_point, buff_len - RT_LINK_CRC_LENGTH);if (receive_frame.crc != temporary_crc){/* check failed. ready resent */LOG_D("CRC: calc:(0x%08x) ,recv:(0x%08x).", temporary_crc, receive_frame.crc);rt_link_hw_buffer_point_shift(&rt_link_scb->rx_buffer->read_point, 1);goto __find_head;}}analysis_status = HEADLE_FRAME_DATA;}case HEADLE_FRAME_DATA:{if (recv_len < buff_len)//fifo长度是否小于,分析协议需要的最小长度{LOG_D("PARSE: actual: %d, need: %d.", recv_len, buff_len);/* should set timer, control receive frame timeout, one shot */timeout = 50;rt_timer_control(&rt_link_scb->recvtimer, RT_TIMER_CTRL_SET_TIME, &timeout);rt_timer_start(&rt_link_scb->recvtimer);//结束,等收多点数据,再分析return;}LOG_D("PARSE: buff_len (%d) r (0x%p)  w (0x%p)", buff_len, rt_link_scb->rx_buffer->read_point, rt_link_scb->rx_buffer->write_point);rt_timer_stop(&rt_link_scb->recvtimer);rt_link_hw_buffer_point_shift(&rt_link_scb->rx_buffer->read_point, buff_len);//帧信息和收到的数据都检查正常,去对于的接收帧信息处理句柄,处理数据rt_link_parse_frame(&receive_frame);data = RT_NULL;buff_len = RT_LINK_HEAD_LENGTH;//回到最小需要4字节头才能分析状态analysis_status = FIND_FRAME_HEAD;//回到默认找头状态break;}default://帧信息的流程不对,回到最初状态
__find_head:LOG_D("to find head (%d)", analysis_status);rt_link_frame_stop_receive(&receive_frame);buff_len = RT_LINK_HEAD_LENGTH;analysis_status = FIND_FRAME_HEAD;break;}//前面有因为fifo长度不够,无法继续检查跳出swtich,继续读现在fifo的长度recv_len = rt_link_hw_recv_len(rt_link_scb->rx_buffer);}
}

下面的API没什么好解释的,每包有4字节的帧数据信息,有个帧信息流程2个字节,去对于的句柄处理,后面文字简单一起解释下。

3.2.2 static rt_err_t rt_link_parse_frame(struct rt_link_frame *receive_frame)

/* Discriminate frame type */
static rt_err_t rt_link_parse_frame(struct rt_link_frame *receive_frame)
{switch (receive_frame->attribute){case RT_LINK_RESEND_FRAME:rt_link_resend_handle(receive_frame);break;case RT_LINK_CONFIRM_FRAME:rt_link_confirm_handle(receive_frame);break;case RT_LINK_HANDSHAKE_FRAME:rt_link_handshake_handle(receive_frame);break;case RT_LINK_SESSION_END:rt_link_session_end_handle(receive_frame);break;case RT_LINK_DETACH_FRAME:rt_link_detach_handle(receive_frame);break;case RT_LINK_SHORT_DATA_FRAME:rt_link_short_handle(receive_frame);break;case RT_LINK_LONG_DATA_FRAME:rt_link_long_handle(receive_frame);break;default:return -RT_ERROR;}return RT_EOK;
}

rt_link_resend_handle,重发句柄3.1.6具体解释过,收到的包信息里面,有个序列号,去检查发送的链表里面有没有这个序列号,有就重发一次。


rt_link_confirm_handle,确认句柄,检查收到的接收序列号,和发送的序列号是否相同,如果相同,说明收到了ack, rt_link_service_send_finish(RT_LINK_EOK);回调通知发送成功,并判断发送链表如果有数据,继续发送下一帧数据。


rt_link_handshake_handle,握手回馈句柄,发送的是收到的序列号和RT_LINK_CONFIRM_FRAME帧流程。请求握手方法参考rt_link_service_attach里面调用的rt_link_command_frame_send所用参数。


rt_link_session_end_handle,对应序列号关闭句柄,没有对方需要的序列号数据的时候,发送RT_LINK_SESSION_END帧信息,说明关闭这个序列回话。


rt_link_detach_handle,这个和握手相反,解除通信连接。


rt_link_short_handle,一包就是一帧数据句柄,申请该数据帧需要的长度内存,如果需要ack,先进行ack,帧流程是RT_LINK_CONFIRM_FRAME,序列号为此包的接收序列号。然后从fifo里面拷贝真实数据,并发送一个接收回调通知 rt_link_recv_finish(receive_frame->head.service, rt_link_scb->rx_record.dataspace, receive_frame->data_len);//回调里处理完这包数据后要主动释放


rt_link_long_handle,多包组合成一帧数据句柄,这个处理机制较复杂些和巧妙,展开分析学习一下。

static rt_err_t rt_link_long_handle(struct rt_link_frame *receive_frame)
{//这个变量默认就是0,中途停止接收,接收完置为默认if (rt_link_scb->rx_record.long_count == 0){/* Receive this long package for the first time:* calculates the total number of frames,* requests space, and turns on the receive timer */_long_handle_first(receive_frame);}//等计算好需要收几包和申请到接收缓存的内存,进行长包接收if (rt_link_scb->rx_record.total > 0){/* Intermediate frame processing:* serial number repeated check,* receive completion check, reply to ACK */_long_handle_second(receive_frame);}receive_frame->real_data = RT_NULL;return RT_EOK;
}

3.2.3 static void _long_handle_first(struct rt_link_frame *receive_frame)

static void _long_handle_first(struct rt_link_frame *receive_frame)
{//是否帧信息里面说明的帧长度参数,是最大数据包长度的整数倍,用于计算需要收几包,和发送的分包对应if (receive_frame->extend.parameter % RT_LINK_MAX_DATA_LENGTH == 0){receive_frame->total = receive_frame->extend.parameter / RT_LINK_MAX_DATA_LENGTH;}else{receive_frame->total = receive_frame->extend.parameter / RT_LINK_MAX_DATA_LENGTH + 1;}//记录需要接收几包rt_link_scb->rx_record.total = receive_frame->total;//申请一个帧的缓存通信内容长度的buff,用于缓存收到的数据rt_link_scb->rx_record.dataspace = rt_malloc(receive_frame->extend.parameter);if (rt_link_scb->rx_record.dataspace == RT_NULL){LOG_W("long data (%dB) alloc failed.", receive_frame->extend.parameter);}
}

3.2.4 static void _long_handle_second(struct rt_link_frame *receive_frame)

static void _long_handle_second(struct rt_link_frame *receive_frame)
{static rt_uint8_t ack_mask = RT_LINK_ACK_MAX;rt_size_t offset = 0; /* offset, count from 0 *///计算收到的包序列号和“记录的接收序列号”的差值,为索引/*具体说明,rt_link_scb->rx_record.rx_seq是“记录的接收序列号”,一般是上一包的接收到信息的序列号,即发送端的发送序列号,一定会有,因为初始化通信需要握手绑定。rt_link_scb->rx_record.rx_seq,在短包的接收中,会赋值为包头的序列号,在长包的处理中,在长包接收完成后才改变,直接增加rt_link_scb->rx_record.total个序列号举例:发送第一个长包的序列号为5,之前的通信没有丢包的话rt_link_scb->rx_record.rx_seq一定为4.丢包后需要重新握手,记录最后通信的序列号。这样计算的索引就为0当第二个长包发送过来,计算索引就为1...*/receive_frame->index = rt_link_check_seq(receive_frame->head.sequence, rt_link_scb->rx_record.rx_seq) - 1;LOG_D("seq(%d), rxseq(%d), index(%d), total(%d), count(0x%x)", receive_frame->head.sequence, rt_link_scb->rx_record.rx_seq, receive_frame->index, receive_frame->total, rt_link_scb->rx_record.long_count);//是否 索引大于最大一帧的分包数 或者 (接收长包的计数 与 0x01左移索引值)为真//第一个逻辑就是当前的收到序列号不能大于我记录的接收序列号+RT_LINK_FRAMES_MAX//第二个逻辑就是这个长包得顺序接收,因为拷贝数据时候的地址直接递增,没有什么滑动串口的ack机制if ((receive_frame->index > RT_LINK_FRAMES_MAX) || (rt_link_scb->rx_record.long_count & (0x01 << receive_frame->index))){LOG_D("ERR:index %d, rx_seq %d", receive_frame->index, rt_link_scb->rx_record.rx_seq);}else if (rt_link_scb->rx_record.dataspace != RT_NULL)//是否申请的内存不为空{//收到的计数 或等于 0x01左移索引/*可以理解为rt_link_scb->rx_record.long_count哪个bit为1,说明收到了那一包数据,0标识第一包 */rt_link_scb->rx_record.long_count |= (0x01 << receive_frame->index);/*偏移为一包真实数据最大长度 乘上 索引,乘RT_LINK_MAX_DATA_LENGTH就可以计算出该存放的偏移地址。可以实现的原因是,发送的时候是按前面以最大包的方式拆包下去,比如定义一包最大为20.发送50字节,就会拆成3包,20,20,10,不能拆成,10,20,20。*/offset = RT_LINK_MAX_DATA_LENGTH * receive_frame->index;//拷贝真实的数据rt_link_hw_copy(rt_link_scb->rx_record.dataspace + offset, receive_frame->real_data, receive_frame->data_len);if (receive_frame->head.ack){/*rt_link_scb->rx_record.long_count每有一个bit说明收到一包数句,rt_link_utils_num1是计算rt_link_scb->rx_record.long_count有多少个bit 1*/if (rt_link_utils_num1(rt_link_scb->rx_record.long_count) == rt_link_scb->rx_record.total){//说明收到了所有包,发送记录的序列号加总报数,就是最后一包序列号的ack数字了。rt_link_command_frame_send(receive_frame->head.service,(rt_link_scb->rx_record.rx_seq + rt_link_scb->rx_record.total),RT_LINK_CONFIRM_FRAME, RT_NULL);}//是否rt_link_scb->rx_record.long_count等于ack_maskelse if ((rt_link_scb->rx_record.long_count & ack_mask) == ack_mask){/*发送ack序列号 = 记录的序列号 + ack_mask有几个bit 1 ,0x07的话有3个.*/rt_link_command_frame_send(receive_frame->head.service,(rt_link_scb->rx_record.rx_seq + rt_link_utils_num1(ack_mask)),RT_LINK_CONFIRM_FRAME, RT_NULL);//扩大最大的ack数,因为定义的ack序列号不够,0x07的话,会扩大成0x3Fack_mask |= ack_mask << rt_link_utils_num1(RT_LINK_ACK_MAX);//大于6包后还会继续扩大,也就是9包,最多扩大到255包。}/*有没有人奇怪为什么没有多一个判断,去ack长包里面的中间序列号,目前只ack了长包的最后一包以及大于最大ack_mask数的时候,ack了一包,为什么其他都不ack呢?我个人思考明白的逻辑就是,这是协议中的机制,发送端只要收到最后一包的ack,就认为发送成功了。在调用发送的时候,入发送链表会将一个个的发送序列号进入发送链表。进入以后,再通知一起发送出去,发送端记录需要ack是最后一个发送序列号。而接收端这边,需要确保协议里说的一共有几包,都收到了才去ack最后一包,中途丢了一包都不ack,会有超时接收,然后置为重新开始接收长帧,发送端重新发送多包组成的一帧数据。中途ack的rt_link_scb->rx_record.rx_seq + rt_link_utils_num1(ack_mask)序号,不会是最后一包的ack,我一开始以为不需要发送去发这个ack,后来我想明白了,源码没有bug。rt_link_frame_send里面用到了RT_LINK_ACK_MAX,即便是通知一次发送出去,也只会发送3包,所以最后的序列号变成了,长包开始的序列号加3,这个时候接收端是需要ack这包的,不然会促发重发机制,造成不会继续发送这个长帧的内容,知道收到ack才去继续将发送链表的内容发送出去。这个逻辑设计是优秀的,就是不太好理解。*/}//是否rt_link_scb->rx_record.long_count里面的bit 1个数等于包数    /* receive a complete package */if (rt_link_utils_num1(rt_link_scb->rx_record.long_count) == rt_link_scb->rx_record.total){rt_timer_stop(&rt_link_scb->longframetimer);//停止长包接收计时//回调收完了一帧的长包,处理完这包数据后要主动释放rt_link_recv_finish(receive_frame->head.service, rt_link_scb->rx_record.dataspace, receive_frame->extend.parameter);//关中断,避免申请的rt_link_scb指向的内存,在别的任务错误操作了这个变量rt_enter_critical();/* empty  rx_record 回到最初的接收长包状态*/rt_link_scb->rx_record.rx_seq += rt_link_scb->rx_record.total;rt_link_scb->rx_record.dataspace = RT_NULL;rt_link_scb->rx_record.long_count = 0;rt_link_scb->rx_record.total = 0;ack_mask = RT_LINK_ACK_MAX;rt_exit_critical();//开中断}//是否fifo里面的长度 小于 此接收包的数据长度else if (rt_link_hw_recv_len(rt_link_scb->rx_buffer) < (receive_frame->data_len % RT_LINK_MAX_DATA_LENGTH)){//开启长接收的超时计时,超时后停止接收这个长数据包rt_int32_t timeout = RT_LINK_LONG_FRAME_TIMEOUT;rt_timer_control(&rt_link_scb->longframetimer, RT_TIMER_CTRL_SET_TIME, &timeout);rt_timer_start(&rt_link_scb->longframetimer);}}
}

至此源码的初始化和使用配置都梳理完了,解释了下发送和接收的机制。目前分析认为没有bug,传输效率和稳定性是有的,目前梳理了一段时间,具体其他细节处理,后续再更新。

4 操作系统帮助处理的内容

待更新

5 其他部分API解析

5.1 关于握手的细节

首先想握手方,先调用下面的语句。
rt_link_command_frame_send(serv->service, seq, RT_LINK_HANDSHAKE_FRAME, rt_link_scb->rx_record.rx_seq);

  1. serv->service 是对应的service 硬件结构体资源
  2. seq 是 rt_link_session 结构体里面,用发送数据链表的(tx_data_slist)记录的一个发送序列号。
    ,发送序列号初始化为宏RT_LINK_INIT_FRAME_SEQENCE(129)
  3. RT_LINK_HANDSHAKE_FRAME 是rt_link_frame_attr_e帧格式类型
  4. rt_link_scb->rx_record.rx_seq 是rt_link_record结构体里面记录的接收序列号,在rt_link_init里面默认将接收序列号初始化为255

然后是分析握手包的处理api,如下


static rt_err_t rt_link_handshake_handle(struct rt_link_frame *receive_frame)
{//receive_frame->head.sequence对应发送参数的seq //receive_frame->extend.parameter对应发送参数的rt_link_scb->rx_record.rx_seqLOG_D("HANDSHAKE: seq(%d) param(%d)", receive_frame->head.sequence, receive_frame->extend.parameter);rt_link_scb->state = RT_LINK_CONNECT;//收到一包握手包,将状态改为连接/*sync 和seq 对应tcp的3次握手来理解好些,RT-Link是主动方发一次握手包,被动方发一次确认包,也不管主动方收到没有。rt_link_confirm_handle这个API是处理确认帧的细节,后面分析。这种握手方式,我称为2次握手,为了交换发送和接收的序列号。此api解释后面,有牛客网摘抄的tcp3次握手过程。配合理解机制,有利于记忆握手的逻辑。*//* sync requester tx seq, responder rx seq = requester tx seq */rt_link_scb->rx_record.rx_seq = receive_frame->head.sequence;/* sync requester rx seq, responder tx seq = requester rx seq */rt_link_scb->tx_seq = receive_frame->extend.parameter;//是否这包握手帧数据里面的service下标(即receive_frame->head.service),初始化赋值了地址/*rt_link_scb->service[receive_frame->head.service] 是指向rt_link_session结构体里面的struct rt_link_service *service[RT_LINK_SERVICE_MAX]内容rt_link_scb->service[RT_LINK_SERVICE_MAX]里面的内容默认都清为零,rt_link_service_attach里面有下面语句进行赋值,
rt_link_scb->service[serv->service] = serv;
将应用层定义的struct rt_link_service变量的地址,赋值给serv->service对应变量的地址,serv->service是rt_link_service_e里面的枚举端口类型。
*/if (rt_link_scb->service[receive_frame->head.service] != RT_NULL){/*下面这个语句,初步分析是有数组越界被赋值的风险,发送方端口号发了一个很大的值,刚好这个值对应接收方rt_link_scb->service[receive_frame->head.service]内容的地址不为空。再次分析,其实是遗漏了前面接收包的一个处理细节,即下面语句,rt_link_frame_check里面有if (receive_frame.head.service >= RT_LINK_SERVICE_MAX)这里可以得出的信息是协议两边的rt_link_service_e枚举必须相同,要注意这个细节*/rt_link_scb->service[receive_frame->head.service]->state = RT_LINK_CONNECT;//发生确认帧数据rt_link_command_frame_send(receive_frame->head.service,receive_frame->head.sequence,RT_LINK_CONFIRM_FRAME, RT_NULL);}else{//如果接收方找不到这个初始化的端口号,(即接收方没有rtlink_exinit)发送断开帧数据。rt_link_command_frame_send(receive_frame->head.service,receive_frame->head.sequence,RT_LINK_DETACH_FRAME, RT_NULL);}return RT_EOK;
}

简述TCP三次握手的过程:
第一次握手:客户端创建传输控制块,然后向服务器发出连接请求报文(将标志位SYN置1,随机产生一个序列号seq=x),接着进入SYN-SENT状态。 第二次握手:服务器收到请求报文后由SYN=1得到客户端请求建立连接,回复一个确认报文(将标志位SYN和ACK都置1,ack=x+1,随机产生一个序列号seq=y),接着进入SYN-RCVD状态。此时操作系统为该TCP连接分配TCP缓存和变量。 第三次握手:客户端收到确认报文后,检查ack是否为x+1,ACK是否为1,是则发送确认报文(将标志位ACK置1,ack=y+1,序列号seq=x+1),此时操作系统为该TCP连接分配TCP缓存和变量。服务器收到确认报文并检查无误后则连接建立成功,两者都进入ESTABLISHED状态,完成三次握手。

下面是确认帧的api处理

static rt_err_t rt_link_confirm_handle(struct rt_link_frame *receive_frame)
{static struct rt_link_frame *send_frame = RT_NULL;rt_slist_t *tem_list = RT_NULL;rt_uint16_t seq_offset = 0;LOG_D("confirm seq(%d) frame", receive_frame->head.sequence);rt_timer_stop(&rt_link_scb->sendtimer);//收到确认帧,将状态改为连接,这里不会有数组越界访问的风险,分析包的时候处理过了。if (rt_link_scb->service[receive_frame->head.service] != RT_NULL){rt_link_scb->service[receive_frame->head.service]->state = RT_LINK_CONNECT;}if (rt_link_scb->state != RT_LINK_CONNECT){   //如果状态不是连接,说明握手成功,重新启动发送事件处理。/* The handshake success and resends the data frame */rt_link_scb->state = RT_LINK_CONNECT;if (rt_slist_first(&rt_link_scb->tx_data_slist)){rt_event_send(&rt_link_scb->event, RT_LINK_SEND_READY_EVENT);}return RT_EOK;}//检查这个确认帧是不是发送帧链表里的起始包发送序列号/* Check to see if the frame is send for confirm */tem_list = rt_slist_first(&rt_link_scb->tx_data_slist);if (tem_list == RT_NULL){return -RT_ERROR;}send_frame = rt_container_of(tem_list, struct rt_link_frame, slist);seq_offset = rt_link_check_seq(receive_frame->head.sequence, send_frame->head.sequence);if (seq_offset <= send_frame->total){rt_link_service_send_finish(RT_LINK_EOK);rt_link_scb->state = RT_LINK_CONNECT;tem_list = rt_slist_first(&rt_link_scb->tx_data_slist);if (tem_list != RT_NULL){LOG_D("Continue sending");rt_event_send(&rt_link_scb->event, RT_LINK_SEND_READY_EVENT);}}return RT_EOK;
}

6 写在最后

希望上面的一些信息,有利于将RT-Link移植到实际的项目中,将RT-Link用起来。
个人分析难免有理解不到位的地方,如阅读有发现疑点或错误,请留言。

rt-link源码笔记,适用于自定义点对点的通信协议相关推荐

  1. 数据结构源码笔记(C语言):Josephus问题之循环链接表

    /*josephus_clist.c*/ /*Josephus问题:循环链接表实现*/#include <stdio.h> #include <stdlib.h>#define ...

  2. 数据结构源码笔记(C语言):链接队列

    /* LinkQueue.c*/ /*链接队列:函数实现*/#include <stdio.h> #include <stdlib.h>typedef int DataType ...

  3. 数据结构源码笔记(C语言):链接栈

    /* 链接栈:类型和界面函数声明 */ /*链接栈:函数实现*/#include <stdio.h> #include <stdlib.h>typedef int DataTy ...

  4. 数据结构源码笔记(C语言):线性表的单链表示

    /* LinkList.c*/ /*线性表的单链表示:函数实现*/#include <stdio.h> #include <stdlib.h> //#include " ...

  5. 数据结构源码笔记(C语言):分块法查找

    //实现分块法查找的算法#include<stdio.h> #include<malloc.h> #include<malloc.h>#define MAXL 10 ...

  6. Dubbo-go 源码笔记(二)客户端调用过程

    作者 | 李志信 导读:有了上一篇文章<Dubbo-go 源码笔记(一)Server 端开启服务过程>的铺垫,可以类比客户端启动于服务端的启动过程.其中最大的区别是服务端通过 zk 注册服 ...

  7. SpringBoot源码笔记分析

    SpringBoot源码笔记分析 1.基础 1.配置SpringBoot热部署 1.引入依赖 <dependency><groupId>org.springframework. ...

  8. phpcms 指定id范围 调用_Dubbogo 源码笔记(二)客户端调用过程

    作者 | 李志信 导读:有了上一篇文章<Dubbo-go 源码笔记(一)Server 端开启服务过程>的铺垫,可以类比客户端启动于服务端的启动过程.其中最大的区别是服务端通过 zk 注册服 ...

  9. Web前端全栈开发_node源码笔记【爱创课堂】

    一.NodeJS简单复习 NodeJS是模块化开发的,有许多内置模块.HTTP模块用于搭建服务器.FS模块用于操作文件和文件夹.URL模块用于URL字符串和URL对象的转换.QueryStrings模 ...

最新文章

  1. thinkphp 查找表并返回结果
  2. 【错误记录】编译 Linux 内核报错 ( /bin/sh: 1: bison: not found )
  3. 一个复杂系统的拆分改造实践
  4. .NET 6新特性试用 | 异步流
  5. 大学python笔记_Python 上手笔记
  6. python中的fstring的 !r,!a,!s
  7. 英雄联盟修复返回服务器异常,玩LOL英雄联盟在XP中服务器连接异常退出的恢复步骤...
  8. 云计算设计模式(二十四)——仆人键模式
  9. Dw cs6的详细下载安装教程对网页设计需要cs6的同学
  10. 利用ffmpeg将H264解码为RGB
  11. UMD算法讲义——Lecture 2:算法设计:稳定婚姻问题
  12. 单例模式如何确保线程安全
  13. 普渡大学计算机科学系可以转到计算机工程吗,2019美国普渡大学计算机专业研究生申请条件...
  14. MYSQL常见命令-Java学习之数据库学习
  15. Spring Security认证_内存认证
  16. gradle优化之 总体配置优化
  17. swagger(三):统一返回结果不显示字段说明
  18. 九方财富更新招股书:上半年营收9亿 冲刺港股一年未果
  19. 大小写字母表(大小写字母表)
  20. 华为Android 10手机微信小程序无法调起的问题解决办法

热门文章

  1. Centos7安装go1.14.4超级详细(两种安装方式)
  2. Python面向对象编程---多态
  3. Android中R文件ID值
  4. STM32的DS18B20驱动
  5. android 编译 icu,使用NDK构建ICU
  6. 实时编译、动态执行C/C++源码函数
  7. 保姆级 | ChatGPT接入微信教程
  8. 张小龙、周鸿祎、傅盛都认同的架构设计思维
  9. 香港进入5G时代!多功能智能灯柱试验计划为5G建设作配合
  10. 疫情传播SEIR模型(python)