FreeRTOS实战项目CRTP协议移植(实现使用串口传输数据包)

一、CRTP 协议

为了和Crazyflie通信,Crazyflie飞控中提出一种高层次的协议叫做CRTP(Crazy RealTime Protocol)。这种简单的协议使用一些可以收发数据的双向目标端口,但是大都时候通信由主机发起。

①协议层次

在这里插入图片描述

②端口分配
端口目标用途
0Console读取使用consoleprintf打印到Crazyflie控制台上的控制台信息
2Parameters获取或设置Crazyflie参数.Crazyflie源码中宏定义参数
3Commander发送控制 滚转/俯仰/偏航/油门调节的设置点
4Memory access访问类似于单线或I2C总线的非易失性存储器 (只支持Crazyflie 2.0)
5Data logging设置包含需要在特殊周期发回Crazyflie的变量的日记块.
6Localization与定位相关的数据包
7Generic Setpoint允许发送设置点和控制模式
13Platform用于其它平台控制,例如调试和关机
14Client-side debugging调试界面并只存在于Crazyflie Python API,Crazyflie飞控本身不具备
15Link layer用于控制和询问通信连接

crazyflie-CRTP解析:https://www.ngui.cc/51cto/show-724815.html?action=onClick,端口解析可点击链接查看。
在这里插入图片描述

二、实现串口收发数据包

2.1 数据包格式:

在这里插入图片描述

大小是32字节,第0字节表示size,第1字节表示header,其它30字节表示Data,其中header包含channel,reserved,port.
在实际应用中channel表示功能,port表示发给谁,发给哪个子系统。

官网给出的解释:
在这里插入图片描述

2.2 如何传递数据包

传递数据包过程思路:
在这里插入图片描述

串口实现传递数据包流程图:
在这里插入图片描述

关键代码示例:

/* 发送数据包函数 */
static int uartlinkSendPacket(CRTPPacket *p)
{
	int i;
	
	/* 发出头部 0x55 */
	fputc(0x55, NULL);

	/* 发出size */
	fputc(p->size, NULL);

	/* 发出header */
	fputc(p->header, NULL);

	/* 发出data */
	for (i = 0; i < p->size; i++)
		fputc(p->data[i], NULL);

	return true;	
}

/* 解析数据包函数 */
static void UARTParserPacket( void * params)
{
	unsigned char c;
	CRTPPacket packet;
	enum recv_status {
		WAITING = 0,
		GET_SIZE,
		GET_HEADER,
		GET_DATA,
	} status;

	int data_cnt;

	//起始状态为等待
	status = WAITING;
	
	while (1)
	{
		/* 平时等待数据 */
		xQueueReceive(uartRecvQueue, &c, portMAX_DELAY);

		/* 解析出packet */
		switch (status)
		{
			case WAITING:
			{
				if (c != 0x55)
					status = GET_SIZE;
				break;
			}

			case GET_SIZE:
			{
				packet.size = c;
				status = GET_HEADER;
				break;
			}

			case GET_HEADER:
			{
				packet.header = c;
				data_cnt = 0;
				status = GET_DATA;
				break;
			}

			case GET_DATA:
			{
				packet.data[data_cnt++] = c;
				if (data_cnt == packet.size)
				{
					/* 把Packet写入队列 */
					xQueueSend(crtpPacketDelivery, &packet, 100);
					status = WAITING;
				}
				break;
			}

		}
		
	}

}

void StoreUARTDataInISR(unsigned char c)
{
	xQueueSendFromISR(uartRecvQueue, &c, NULL);
}

void uartlinkInit()
{
  if(isInit)
    return;
	
  crtpPacketDelivery = xQueueCreate(5, sizeof(CRTPPacket));
  // 串口接收队列,长度64 ,item1个字节
  uartRecvQueue = xQueueCreate(64, 1);

  // 创建一个"解析数据包的任务"
  
  xTaskCreate(UARTParserPacket, "ParserTask", 200, NULL, osPriorityNormal, &pxUARTParserTask);

  isInit = true;
}

三、CRTP处理数据过程

3.1 CRTP处理数据流程示例:

在这里插入图片描述

在Link层中,从中断中写队列,唤醒“解析”数据包任务。CRTP层实现中转,实现从CPRT链路中接收数据包,并将它们发送到相应的队列或回调函数。

3.2 添加CRPT基本功能

①链路初始化函数
/* uart链路初始化 */
void uartlinkInit()
{
  if(isInit)
    return;

  crtpPacketDelivery = xQueueCreate(5, sizeof(CRTPPacket));
  
  uartRecvQueue = xQueueCreate(64, 1);

  // 创建一个"解析数据包的任务"
  
  xTaskCreate(UARTParserPacket, "ParserTask", 200, NULL, osPriorityNormal, &pxUARTParserTask);

  isInit = true;
}
/* 设置和操作crtp */
void crtpSetLink(struct crtpLinkOperations * lk)
{
  if(link)
    link->setEnable(false);

  if (lk)
    link = lk;
  else
    link = &nopLink;

  link->setEnable(true);
}

在main函数中调用:

  /* 链路层初始化 */
  uartlinkInit();
  crtpSetLink(uartlinkGetLink());
② CRTP层初始化
void crtpInit(void)
{
  if(isInit)
    return;

  txQueue = xQueueCreate(CRTP_TX_QUEUE_SIZE, sizeof(CRTPPacket));
  //DEBUG_QUEUE_MONITOR_REGISTER(txQueue);

  STATIC_MEM_TASK_CREATE(crtpTxTask, crtpTxTask, CRTP_TX_TASK_NAME, NULL, CRTP_TX_TASK_PRI);
  STATIC_MEM_TASK_CREATE(crtpRxTask, crtpRxTask, CRTP_RX_TASK_NAME, NULL, CRTP_RX_TASK_PRI);

  isInit = true;
}

在main函数中调用:

  /* CRTP层初始化(中转作用) */
  crtpInit();
③创建底层任务代码
void crtpInit(void)
{
  if(isInit)
    return;

  txQueue = xQueueCreate(CRTP_TX_QUEUE_SIZE, sizeof(CRTPPacket));
  //DEBUG_QUEUE_MONITOR_REGISTER(txQueue);

  STATIC_MEM_TASK_CREATE(crtpTxTask, crtpTxTask, CRTP_TX_TASK_NAME, NULL, CRTP_TX_TASK_PRI);
  STATIC_MEM_TASK_CREATE(crtpRxTask, crtpRxTask, CRTP_RX_TASK_NAME, NULL, CRTP_RX_TASK_PRI);

  isInit = true;
}
④ping服务

CRTP中有个ping服务,发送ping包会回应一个ping包:
在这里插入图片描述

创建crtpSrv就可以实现ping功能,代码示例:

void crtpserviceInit(void)
{
  if (isInit)
    return;

  //Start the task
  STATIC_MEM_TASK_CREATE(crtpSrvTask, crtpSrvTask, CRTP_SRV_TASK_NAME, NULL, CRTP_SRV_TASK_PRI);

  isInit = true;
}

⑤ 上机实验(发送数据包之后成功返回):

使用串口工具给开发发送PING包:

  • size:7
  • header:0xF0
    • port:CRTP_PORT_LINK,0x0F
    • channel:linkEcho,0x00
  • data:100ask,7个字符(0x31 0x30 0x30 0x61 0x73 0x6B 0x00)
55 07 F0 31 30 30 61 73 6B 00

在这里插入图片描述

3.3 移植CRTP

将下图的文件添加到工程项目下。
在这里插入图片描述

3.4 新的数据包

3.4.1 定义新的port/channel

port:

CRTP_PORT_LED         	 = 0x0E

channel:

typedef enum {
  ledSet   = 0x00,
  ledGet   = 0x01,  
} LinkNbr;

数据格式:

控制LED状态:data[0]表示哪个LED,data[1]表示什么状态
读取LED状态:
发来数据包:data[0]表示哪个LED
返回数据包:data[0]表示哪个LED,data[1]表示什么状态(1--亮,0--灭)
3.4.2 增加新的处理函数
#include <stdbool.h>
#include <string.h>

/* FreeRtos includes */
#include "FreeRTOS.h"
#include "task.h"
#include "cmsis_os.h"

#include "crtp.h"
#include "ledservice.h"
#include "static_mem.h"
#include "param.h"


typedef enum {
  ledSet   = 0x00,
  ledGet = 0x01,
} LinkNbr;

static int led0_status;
static bool isInit=false;

static void ledSrvTask(void* params)
{
  static CRTPPacket p;

  crtpInitTaskQueue(CRTP_PORT_LED);

  while(1) {
	crtpReceivePacketBlock(CRTP_PORT_LED, &p);

	switch (p.channel)
	{
	  case ledSet:
	  	/* 根据参数控制LED */
		led0_status = p.data[1];
	    printf("set led %d as %s\r\n", p.data[0], led0_status ? "on" : "off");
		break;
	  case ledGet:
	  	if (p.data[0] == 0)
	  	{
			p.size  = 2;			
			p.data[1] = led0_status;
			crtpSendPacketBlock(&p);
	  	}
		break;
	  default:
		break;
	}
  }
}


void ledserviceInit(void)
{
  if (isInit)
    return;

  //Start the task
  xTaskCreate(ledSrvTask, "ledSrvTask", 200, NULL, osPriorityNormal, NULL);

  isInit = true;
}

在main函数中调用

  /* 上层 */
  ledserviceInit();
3.4.3 上机测试
亮灯 55 02 e0 00 01
灭灯 55 02 e0 00 00

在这里插入图片描述
在这里插入图片描述

读取LED状态:发包

55 01 e1 00

在这里插入图片描述

3.5 实现回调函数

创建任务会浪费CPU资源,使用回调函数的方式可以解决。回调函数适合处理比较简单的事情,如果需求较为复杂的情况下,容易造成丢失数据包的问题。

static void LedPortCallback(CRTPPacket *p)
{
	switch (p->channel)
	{
	  case ledSet:
	  	/* 根据参数控制LED */
		led0_status = p->data[1];
	    printf("set led %d as %s\r\n", p->data[0], led0_status ? "on" : "off");
		break;
	  case ledGet:
	  	if (p->data[0] == 0)
	  	{
			p->size  = 2;			
			p->data[1] = led0_status;
			crtpSendPacketBlock(&p);
	  	}
		break;
	  default:
		break;
	}
}

void ledserviceInit(void)
{
  if (isInit)
    return;
#if 0
 /* 省略 */
#else
    /* 将LedPortCallback回调函数注册到CRTP协议的LED端口上 */
	crtpRegisterPortCB(CRTP_PORT_LED,LedPortCallback);
#endif
  isInit = true;
}

四、项目实现遇到的错误

1、FreeRTOS的中断优先级

FreeRTOS中高优先级任务会导致FreeRTOS长时间关中断,导致程序异常。
在这里插入图片描述

该项目中解决方案,将串口的中断优先级提高,如下图:
在这里插入图片描述

2、编译遇错:

在这里插入图片描述

修改:
在这里插入图片描述

3、使用printf()函数需要重写串口重定向函数

int fputc(int ch, FILE *f)
{
#if 0	
    txcplt_flag = 0;
    HAL_UART_Transmit_IT(&huart1, (uint8_t*)&ch, 1);
    while(txcplt_flag==0);
#else
	HAL_UART_Transmit(&huart1, (uint8_t*)&ch, 1, HAL_MAX_DELAY);
#endif
	return 0;
}

int fgetc(FILE *f)
{
    char c = 0;
//    while(ring_buffer_read((unsigned char *)&c, &test_buffer) != 0);
    return c;
}

在keil中 要勾选此配置:
在这里插入图片描述

4、堆栈大小的问题

在FreeRTOS中,堆栈不够会导致创建任务或者队列失败,需要在FreeRTOSConfig.h文件中修改堆大小,如下图:
在这里插入图片描述

5、串口调试工具没有打印信息问题

勾选HEX发送:
在这里插入图片描述

资源参考:百问网嵌入式专家-韦东山嵌入式专注于嵌入式课程及硬件研发 (100ask.net)

视频开发板使用的是百问网STM32F103Pro,需要购买的可以登录百问网官网进行购买。视频代码可以通过gitee下载。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值