IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> rt-link源码笔记,适用于自定义点对点的通信协议 -> 正文阅读

[嵌入式]rt-link源码笔记,适用于自定义点对点的通信协议

前言:
实际项目中,一个单片机经常要和另一个单片机通信,需要自定义一些通信协议,实现可靠通信,自己尝试写过一些,但没什么面向对象的精华,读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 使用方式

发送调用接口如下
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 配置使用

配置使用就在”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_INIT
    INIT_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-0x1F
    rt_uint8_t extend  : 1;//bit为1表示有额外的包头信息
    rt_uint8_t crc     : 1;//bit为1表示要crc校验
    rt_uint8_t ack     : 1;//bit为1表示要ack

    rt_uint8_t sequence;//发送数据帧的序列号
    rt_uint16_t service: 5;//端口号,5bit所以范围是0-31
    rt_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
    {
        //发送包数最小为1
        total = 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,前面分析过这个api
        rt_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字节为一帧数据的size
            rt_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开始有个默认发送序列号为129
    rt_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发送的序列号初始化为129
    rt_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_CONNECT
    if (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,回调通知发送ok
        if (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_callback
        rt_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字节的接收序列号或NULL
    rt_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字节的协议头,拷贝到发送data
    data = 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)
    {
   		 //如果这帧数据的处理流程是一包能发完的短数据或者是发送长帧数据,将内容拷贝到data
        rt_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);
    }
	//打印发送序列号 发送长度 帧的流程 crc
    LOG_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里面的长度,小于头长度,结束帧检查api
            if (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读指针偏移1
                rt_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));
                //指针后移4
                rt_link_hw_buffer_point_shift(&data, sizeof(struct rt_link_extend));
                //帧的流程是否小于默认的流程,枚举的流程,最后是RT_LINK_RESERVE_FRAME
                if (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字节crc
        case 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_mask
            else 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的话,会扩大成0x3F
                ack_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解析

待更新

6 写在最后

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

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2022-08-19 19:22:29  更:2022-08-19 19:22:53 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/18 12:07:31-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码