文章目录
- 一、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)。这种简单的协议使用一些可以收发数据的双向目标端口,但是大都时候通信由主机发起。
①协议层次
②端口分配
端口 | 目标 | 用途 |
---|---|---|
0 | Console | 读取使用consoleprintf打印到Crazyflie控制台上的控制台信息 |
2 | Parameters | 获取或设置Crazyflie参数.Crazyflie源码中宏定义参数 |
3 | Commander | 发送控制 滚转/俯仰/偏航/油门调节的设置点 |
4 | Memory access | 访问类似于单线或I2C总线的非易失性存储器 (只支持Crazyflie 2.0) |
5 | Data logging | 设置包含需要在特殊周期发回Crazyflie的变量的日记块. |
6 | Localization | 与定位相关的数据包 |
7 | Generic Setpoint | 允许发送设置点和控制模式 |
13 | Platform | 用于其它平台控制,例如调试和关机 |
14 | Client-side debugging | 调试界面并只存在于Crazyflie Python API,Crazyflie飞控本身不具备 |
15 | Link 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下载。
本次项目使用的开发板是STM32F103最小系统板,完整参考代码可以通过Git下载。
git clone https://gitee.com/deng-bao/dshan-mcu-f103.git