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

news2024/9/21 16:24:33

文章目录

    • 一、CRTP 协议
        • ①协议层次
        • ②端口分配
    • 二、实现串口收发数据包
      • 2.1 数据包格式:
      • 2.2 如何传递数据包
    • 三、CRTP处理数据过程
      • 3.1 CRTP处理数据流程示例:
      • 3.2 添加CRPT基本功能
        • ①链路初始化函数
        • ② CRTP层初始化
        • ③创建底层任务代码
        • ④ping服务
        • ⑤ 上机实验(发送数据包之后成功返回):
      • 3.3 移植CRTP
      • 3.4 新的数据包
        • 3.4.1 定义新的port/channel
        • 3.4.2 增加新的处理函数
        • 3.4.3 上机测试
        • 3.5 实现回调函数
    • 四、项目实现遇到的错误
      • 1、FreeRTOS的中断优先级
      • 2、编译遇错:
      • 3、使用printf()函数需要重写串口重定向函数
      • 5、串口调试工具没有打印信息问题

一、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
image-20240827200600429

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下载。

本次项目使用的开发板是STM32F103最小系统板,完整参考代码可以通过Git下载。

git clone https://gitee.com/deng-bao/dshan-mcu-f103.git

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2082150.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

宝兰德多款仓颉开源项目获GitCode官方G-Star毕业认证,释放开发效率新动能

近日&#xff0c;由宝兰德基于仓颉编程语言开发的项目「Cangjie-TPC/redis-sdk」「Cangjie-TPC/hyperion」入驻GitCode平台&#xff0c;并获得GitCode官方优秀毕业项目认证&#xff0c;成为G-Star计划的一员&#xff0c;标志着宝兰德与GitCode将携手开启软件研发新纪元&#xf…

变频器会干扰补偿电容器正常工作吗

变频器&#xff08;VFD&#xff0c;Variable Frequency Drive&#xff09;可能会对补偿电容器的正常工作产生干扰。变频器工作时产生的谐波、快速开关操作和高频噪声都可能影响电容器的性能。 一、以下是变频器对补偿电容器可能产生的一些影响&#xff1a; 1、谐波干扰 谐波生…

JS基础之【基本数据类型与类型间的隐式显示转换】

&#x1f680; 个人简介&#xff1a;某大型国企高级前端开发工程师&#xff0c;7年研发经验&#xff0c;信息系统项目管理师、CSDN优质创作者、阿里云专家博主&#xff0c;华为云云享专家&#xff0c;分享前端后端相关技术与工作常见问题~ &#x1f49f; 作 者&#xff1a;码…

【Linux】初步识操作系统

linux专栏&#xff1a;《Linux入门系列》 系列文章&#xff1a;gdb-调试器初入门&#xff08;简单版使用&#xff09; 编辑器vim入门&#xff08;概念模式转换技巧&#xff09; 目录 1. 概念 2. 设计操作系统的目的 3. 定位&#xff1a;操作系统负责管理 4. 如何理解管理 …

Linux 数据结构 链表

1.段错误调试方法&#xff1a; 1.按照网上的方法配置Ubuntu&#xff0c;允许生成core文件 2.重新编译代码并加入-g选项(允许进行GDB调试) 3.ulimit -c unlimited 不限制core文件的生成的大小 4.执行代码,复现段错误,产生包含出错信息的core文件(检查cor…

呼叫系统怎么使用提高安全性

呼叫中心提高使用的安全性是一个综合性的任务&#xff0c;涉及技术、管理、人员等多个方面。以下是一些关键措施&#xff0c;旨在提升呼叫中心的安全性&#xff1a; 一、加强技术防护 1.数据加密&#xff1a;采用先进的加密技术对通话内容和数据传输进行加密&#xff0c;如使用…

浙大联合港中深发布AI医疗最新报告,全面审视「虚拟现实+人工智能」

想象一下&#xff0c;医生在手术前&#xff0c;通过虚拟现实技术&#xff0c;能够身临其境地预演手术过程&#xff0c;精确到每一个细节&#xff1b;患者则可以在VR环境中进行心理治疗&#xff0c;减轻焦虑与恐惧。 这一切&#xff0c;都得益于AI与VR的强强联手。 医学视觉增强…

今日分享丨微服务架构下查询数据缓存策略

引言 随着企业业务规模的扩大和复杂度的提升&#xff0c;微服务架构因其高可用性、可扩展性和易于维护的特性&#xff0c;逐渐成为现代软件开发的首选架构模式。然而&#xff0c;微服务架构带来的分布式特性也增加了数据访问的复杂性和延迟。特别是业务查询领域&#xff0c;一…

HIS系统成品|HIS系统搭建|医院HIS系统开发

在医疗信息化的浪潮中&#xff0c;医院信息系统&#xff08;HIS&#xff09;的开发功能分析成为关键。本文将探讨如何通过功能分析构建一个高效、智能的HIS系统&#xff0c;以满足现代医院的需求&#xff0c;并提升医疗服务质量。 1、HIS系统功能分析的必要性 在医疗行业中&am…

基于java的进销存管理系统设计与实现

需求分析 进销存主要是帮助商业企业全面有效管理采购、销售和库存&#xff0c;软件适用于需要进行采购管理&#xff0c;销售管理以及库存管理的所有商业企业。 进销存系统的产生和发展情况 进销存软件是一款通用性极强的商业企业进销存管理系统&#xff0c;软件囊括了商业企业…

第六届机器人与智能制造技术国际会议 (ISRIMT 2024)

重要信息 大会官网&#xff1a;www.isrimt.org&#xff08;点击了解大会&#xff0c;参会&#xff0c;投稿等信息&#xff09; 大会时间&#xff1a;2024年9月20-22日 大会地点&#xff1a;中国-江苏常州 收录检索&#xff1a;IEEE Xplore, EI Compendex, Scopus 大会简介…

航空公司名字趣史:看看有趣又有意义的命名背后有什么玄机

上周“东海航空”事件引发了东方航空在社交媒体上的一系列被迫营业&#xff0c;因为媒体的乌龙报道误将“东海航空”简称为“东航”&#xff0c;甚至直接用错了图片。众号&#xff1a;标猿公司起名 给公司起个好名字 其实除了大部分以地域、国家命名的航空公司&#xff0c;还…

Java 8 Optional用法【总结记录】

一、前言 这里引用书中描述来介绍Optional类&#xff1a; Optional是为核心类库设计的一个数据类型&#xff0c;用来替换null值。人们对原有的null值有很多抱怨&#xff0c;甚至连发明这一概念的Tony Hoare也是如此&#xff0c;他曾说这是自己的一个“价值连城的错误”。作为一…

cad导出图片格式怎么导出?5个软件帮助你快速转换文件格式

cad导出图片格式怎么导出&#xff1f;5个软件帮助你快速转换文件格式 将CAD文件导出为图片格式可以帮助你更方便地展示、分享或打印设计图纸。CAD&#xff08;Computer-Aided Design&#xff09;文件通常以DWG或DXF格式保存&#xff0c;而要将它们转换为常见的图片格式&#x…

NodeJS “次元高校”社团管理系统 ---附源码94897

摘要 计算机科学技术的飞速发展也更好地促进了高校信息化建设。为了适应新形势下更好地培养人才&#xff0c;高校在发展的过程中开始推进信息系统的建设。随着我国教育模式的不断改革和发展&#xff0c;越来越多的高校正在开展校园信息工程建设&#xff0c;以更好地提高高校的各…

C / C++内存管理

内存分布 1. 栈又叫堆栈--非静态局部变量/函数参数/返回值等等&#xff0c;栈是向下增长的。 2. 内存映射段是高效的I/O映射方式&#xff0c;用于装载一个共享的动态内存库。用户可使用系统接口创建共享共 享内存&#xff0c;做进程间通信&#xff0c;子进程堆区的开辟。 3. …

文心快码帮你解大厂面试题:TCP关闭连接的过程,为什么要4次挥手,为什么最大等待时间是2*MSL?

&#x1f50d;【大厂面试真题】系列&#xff0c;带你攻克大厂面试真题&#xff0c;秒变offer收割机&#xff01; ❓今日问题&#xff1a;在8g内存的机器&#xff0c;能否启动一个7G堆大小的java进程&#xff1f; ❤️一起看看文心快码Baidu Comate给出的答案吧&#xff01;如…

Oracle RAC 修改系统时区避坑指南(深挖篇)

大家好&#xff0c;这里是 Lucifer三思而后行&#xff0c;专注于提升数据库运维效率。 目录 前言环境安装问题重现时区检查修改时区问题分析问题解决 深究根源问题一问题二问题三 写在最后往期精彩文章推荐 前言 昨天遇到一个问题&#xff0c;Oracle RAC 安装完之后&#xff0…

bitsandbytes使用错误:CUDA Setup failed despite GPU being available

参考:https://huggingface.co/docs/bitsandbytes/main/en/installation 报错信息 ======================

【JavaEE精炼宝库】网络原理基础——网络层 | IP协议

文章目录 一、IP 协议的格式二、IP 地址的数量限制三、私有 IP 地址和公网 IP 地址3.1 私有 IP 地址和公网 IP 地址的基本知识&#xff1a;3.2 内网 IP 设备访问外网 IP 设备的过程&#xff08;NAT 机制&#xff09;&#xff1a; 四、地址管理4.1 网段划分&#xff1a;4.1.1 网…