【FreeRTOS学习 - 消息队列学习】

2023-05-16

跟着韦东山老师FreeRTOS教学资料的学习记录

FreeRTOS全部项目代码链接(更新中)

https://gitee.com/chenshao777/free-rtos_-study


本文章一共分为一下几个部分

1. 创建队列

2. 写队列

3. 读队列

4. 队列阻塞访问

5. 分辨数据源头

6. 传输大块数据


1. 创建队列

步骤: 包含队列头文件 -> 定义队列句柄全局变量 -> 在main函数中创建队列

#include "queue.h"  //首先包含一下队列的头文件

/* 定义 QueueHandle_t 类型变量,用于保存队列句柄 */
QueueHandle_t xQueue;
int main()
{
	/* 创建队列: 长度为5,数据大小为4字节(存放一个整数) */
    xQueue = xQueueCreate( 5, sizeof( int32_t ) );
    //.....
}

重点:
xQueueCreate 函数的第一个参数是 栈的长度,第二个参数是栈内每个数据的长度
该函数会返回一个队列句柄,写队列和读队列就是通过这个句柄来进行操作

--------主函数代码(对应 2. 写队列 和 3. 读队列 )--------

int main()
{
	SysTick_Init(72);
	USART1_Init(115200);
	printf("串口初始化成功!\r\n");

	/* ------主要是下面的代码,上面是初始化------ */

	/* 创建队列: 长度为5,数据大小为4字节(存放一个整数) */
    xQueue = xQueueCreate( 5, sizeof( int32_t ) );
    /* 创建三个任务写队列,并传递写入的参数 */
	xTaskCreate(TaskSend, "taskSend1", 200, (void*)100, 1, NULL);
	xTaskCreate(TaskSend, "taskSend2", 200, (void*)110, 1, NULL);
	xTaskCreate(TaskSend, "taskSend3", 200, (void*)120, 1, NULL);
	
	/* 创建一个任务读队列 */
	xTaskCreate(TaskRead, "TaskRead", 200, NULL, 2, NULL);

	/* 启动调度器,任务开始执行 */
	vTaskStartScheduler();          //开启任务调度
}


2. 写队列

写队列任务代码,使用的 xQueueSendToBack 函数,数据写入队列尾

/*
 * 写队列
*/
void TaskSend(void *pvParameters)
{
	int param;
	BaseType_t status;
	/* 将传递参数强转为 int 型 */
	param = (int)pvParameters;

	for(;;)
	{
		/* 写队列,参数:目标队列的句柄, 发送数据的指针, 阻塞超时时间*/
		status = xQueueSendToBack(Queue_t, &param, 0);
		/* 判断是否写入成功 */
		if(status == pdPASS){
			printf("write success!\r\n");
		}
		
		/* 允许其它发送任务执行。 taskYIELD()通知调度器现在就切换到其它任务,而不必等到本任务的时间片耗尽 */ 
		taskYIELD(); 
		
		/* 不能使用该延时函数,否则当队列满后,只有第一个写任务可以得到调度,后面的写任务会被饿死 */
		//vTaskDelay(1); 
	}
}

3. 读队列

读队列代码,使用的 xQueueReceive 函数,从队头读取一个数据,并将其删除

/*
 * 读队列
*/
void TaskRead(void *pvParameters)
{
	int read_data;   //定义一个int类型变量,用于读取队列后存放数据
	BaseType_t result;
	BaseType_t count;

	for(;;)
	{
		/* 查询队列中数据个数 */
		//count = uxQueueMessagesWaiting(Queue_t);
		//printf("c = %d\r\n", count);
		
		/* 读队列 */
		// 50表示当没有读到数据时,等待超时时间为50ms
		// 我的项目FreeRTOS默认一个节拍中断为1ms,可以通过portTICK_PERIOD_MS宏查看
		result = xQueueReceive(Queue_t, &read_data, 50);   
		if(result == pdPASS){
			printf("Read:  %d\r\n", read_data);
		}
		else
			printf("Read NULL\r\n");
	}
}

4. 队列阻塞访问

队列阻塞访问有两种情况

一、写队列时发现队列已经满了
二、读队列时发现队列是空的

我们发现 写队列函数读队列函数 的最后一个参数都可以传入一个 超时时间

情况一:

写队列时发现队列满了,如果设置了超时时间,则任务进入阻塞状态
-------->>>
在超时时间内,如果有 其他任务读取了队列,则会空出位置,那么会第一时间 唤醒正在阻塞的写队列任务
-------->>>
那么唤醒哪一个呢(唤醒优先级最高的、同优先级内唤醒等待时间最长的

情况二:

读队列时发现队列空,如果设置了超时时间,则任务进入阻塞状态
-------->>>
在超时时间内,如果有 其他任务写队列了,则队列里有数据了,那么会第一时间 唤醒正在阻塞的读任务
-------->>>
那么唤醒哪一个呢(唤醒优先级最高的、同优先级内唤醒等待时间最长的

情况三:

如果超时时间设置为0,则读不到数据(队列为空)或者写入失败(队列满),则立即返回不等待。

情况四:

如果把 xTicksToWait 设置为 portMAX_DELAY ,并且在FreeRTOSConig.h 中设定INCLUDE_vTaskSuspend 为 1,那么阻塞等待将没有超时限制,一直等。


5. 分辨数据源头

分辨数据源的方法: 使用结构体

/* 定义数据结构体:包含数据源ID & 数据 */
typedef struct{
	char id;       // 用于标识数据来源
	uint32_t data;
}SendStruct;

定义一个结构体,结构体成员包括 id 和 数据变量, 其中 id 用于标识数据来源

全部代码

#include "stm32f10x.h"
#include "led.h"
#include "usart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "SysTick.h"
#include "queue.h"
#include "string.h"


/* 定义数据结构体:包含数据源ID & 数据 */
typedef struct{
	char id;
	uint32_t data;
}SendStruct;

#define	 task1_id     1 
#define  task2_id     2 
#define  task1_data   10 
#define  task2_data   20 

/* 
*  任务发送到队列数据的结构体实例 
*  包括每个任务的id和数据
*/
const SendStruct xSend_Buff[2] = 
{
	{task1_id, task1_data},
	{task2_id, task2_data}
};

/* 创建队列返回句柄 */
QueueHandle_t Queue_t;

/*
* 写队列
*/
void TaskSend(void *pvParameters)
{
	BaseType_t status;

	for(;;)
	{
		/* 写队列,参数:目标队列的句柄, 发送数据的指针, 阻塞超时时间*/
		status = xQueueSendToBack(Queue_t, pvParameters, 10);
		
		/* 如果写队列失败,则打印信息 */
		if(status != pdPASS){
			printf("send fail %d \r\n", ((SendStruct *)pvParameters)->id);
		}
		/* 允许其它发送任务执行。 taskYIELD()通知调度器现在就切换到其它任务
		   而不必等到本任务的时间片耗尽 */ 
//		taskYIELD(); 
	}
}

/*
 * 读队列
*/
void TaskRead(void *pvParameters)
{
	SendStruct read_data;
	BaseType_t result;
	for(;;)
	{
		/* 读队列 */
		result = xQueueReceive(Queue_t, &read_data, 10);
		/* 读到了数据 */
		if(result == pdPASS){
			/* 输出数据来源 和 数据内容 */
			printf("ID: %d  DATA: %d\r\n", read_data.id, read_data.data);
		}else
			printf("Read NULL\r\n");
	}
}


int main()
{
	SysTick_Init(72);
	USART1_Init(115200);
	printf("串口初始化成功!\r\n");
	
	/*  创建队列, 用于保存最多5个值,每个数据单元都有足够的空间来存储一个 int 型变量  */
	Queue_t = xQueueCreate(5, sizeof(SendStruct));
	if(Queue_t != NULL)
		printf("队列创建成功!\r\n");
	
	/* 创建任务写队列 */
	xTaskCreate(TaskSend, "taskSend1", 200, (void *)&(xSend_Buff[0]), 3, NULL);
	xTaskCreate(TaskSend, "taskSend2", 200, (void *)&(xSend_Buff[1]), 3, NULL);
	
	/* 创建任务读队列 */
	xTaskCreate(TaskRead, "TaskRead", 200, NULL, 4, NULL);
	
	/* 启动调度器,任务开始执行 */
	vTaskStartScheduler();          //开启任务调度
}

6. 传输大块数据

传输大块数据的方法: 传输指针

定义一个大容量数组

写队列时只需写入一个指向该数组的指针的地址即可
这里注意,写入的是指向该数组的指针的地址
如果直接传入数组名(也就是数组的首地址)是不行的

举个例子
定义一个 sendBuff 数组,然后将该数组首地址写入队列,读队列时读取到的就是数组首地址了

QueueHandle_t xQueue;
char sendBuff[200];  // 全局缓冲区数组
......
......
void taskSend(void *pvParameters)
{
	static int count = 0;
	char *buff;
	for(;;)
	{
		/* 向缓冲区里放数据 */
		sprintf(sendBuff, "My name is ChenShao %d\r\n", count++);
		buff = sendBuff;  //应该加这一句,让一个指针指向该数组
		/* 写队列 */
		//xQueueSendToBack(xQueue, sendBuff, 0); //错误写法
		//xQueueSendToBack(xQueue, &sendBuff, 0); //错误写法
		//xQueueSendToBack(xQueue, buff, 0);    //错误写法
		xQueueSendToBack(xQueue, &buff, 0);     //正确写法
	}
}
......
......

以下这三种写法都不行

  1. xQueueSendToBack(xQueue, sendBuff, 0); //错误写法
  2. xQueueSendToBack(xQueue, &sendBuff, 0); //错误写法
  3. xQueueSendToBack(xQueue, buff, 0); //错误写法

只能这样写

  1. xQueueSendToBack(xQueue, &buff, 0); //正确写法

个人理解:
因为 xQueueSendToBack 函数的第二个参数需要传入一个指针

并且这个指针本身不存储数据,它只是指向一片存储数据的内存


从而可以从那片内存中拷贝数据到队列里

所以,如果按照前三种写法,实际上传入的是 sendBuff数组的 首地址,但是这个首地址 并不指向任何内存,它只是用来存储我们传输的数据的。

将这四个传输的地址打印出来分析

在这里插入图片描述
可以看到 图中1 2 3 打印出的都是 sendBuff 的首地址
此时给 xQueueSendToBack 函数的第二个参数是 &sendBuff ,可以看到程序只打印出 “read data :” 就没了
左边的报错是 “*** error 65: access violation at 0x6E20794D : no ‘read’ permission”
表示内存地址访问没有权限

但是如果 将第二个参数改为 &buff , 则可以顺利运行,如下图所示

在这里插入图片描述
&buff 是一个地址,这个地址上存储的是 0x20000164 这个值,也就是 sendBuff 数组的首地址
所以传入一个 指向数组首地址的指针的地址 才是正确的

============== 传输大块数据全部代码 ==============

#include "stm32f10x.h"
#include "led.h"
#include "usart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "SysTick.h"
#include "queue.h"
#include "string.h"
#include "stdlib.h"

/* 定义 QueueHandle_t 类型变量,用于保存队列句柄 */
QueueHandle_t xQueue;

char sendBuff[200];   // 大块数据缓冲区

/*
 * 写入队列大块数据 
 */
void taskSend(void *pvParameters)
{
	static int count = 0;
	BaseType_t res;
	char *buff;
	
	for(;;)
	{
		/* 向缓冲区里放数据 */
		sprintf(sendBuff, "My name is ChenShao %d\r\n", count++);
		buff = sendBuff;
		
		printf("sendBuff = 0x%016lx\r\n", (long unsigned int)sendBuff);
		printf("&sendBuff = 0x%016lx\r\n", (long unsigned int)&sendBuff);
		
		printf("buff = 0x%016lx\r\n", (long unsigned int)buff);
		printf("&buff = 0x%016lx\r\n", (long unsigned int)&buff);
		/*
		 * 这里不能传入 sendBuff、&sendBuff、buff ,因为这里需要传入一个指向一片内存的指针的地址
		 * 而sendBuff、&sendBuff、buff都是数组首地址,首地址没有指向任何内存
		*/
		res = xQueueSendToBack(xQueue, &buff, 0);  
		if(res != pdPASS){
			printf("write fail \r\n");
		}
	}
}

/*
 * 读取队列大块数据 
 */
void taskRead(void *pvParameters)
{
	const TickType_t delay = 100 / portTICK_PERIOD_MS;
	char *str;
	BaseType_t res;
	for(;;)
	{
		res = xQueueReceive(xQueue, &str, delay);
		if(res == pdPASS){
			printf("read data : %s\r\n", str);
		}else{
			printf("read null\r\n");
		}
	}
}

int main()
{
	SysTick_Init(72);
	USART1_Init(115200);
	printf("串口初始化成功!\r\n");
	
	/* 创建队列,1个字节,仅用于保存大块数据首地址 */
	xQueue = xQueueCreate(1, sizeof(char *));
	/* 写队列 */
	xTaskCreate(taskSend, "taskSend", 1000, NULL, 1, NULL);
	/* 读队列 */
	xTaskCreate(taskRead, "taskRead", 1000, NULL, 2, NULL);
	
	/* 启动调度器 */
	vTaskStartScheduler();
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【FreeRTOS学习 - 消息队列学习】 的相关文章

  • 基于HAL库的FREERTOS----------二.任务API函数

    任务API函数览概 CUBEMX对 做了API的封装 很多 的函数没有封装到位 可以用原函数调用 任务API函数分别介绍 1 uxTaskPriorityGet 此函数用来获取指定任务的优先级 要使用此函数的话宏 INCLUDE uxTas
  • FreeRTOS config开始的宏

    FreeRTOSConfig h系统配置文件中可以自定义 FreeRTOS h中定义默认值 configAPPLICATION ALLOCATED HEAP 默认情况下FreeRTOS的堆内存是由编译器来分配的 将宏configAPPLIC
  • FreeRTOS-内核控制函数

    FreeRTOS 内核控制函数 FreeRTOS中有一些内核函数 一般来说这些内核函数在应用层不会使用 但是内核控制函数是理解FreeRTOS中断的基础 接下来我们逐一分析这些内核函数 taskYIELD 该函数的作用是进行任务切换 这是一
  • FreeRTOS软件定时器创建、复位、开始和停止(备忘)

    目录 一 简介 1 1 开发环境 1 2 摘要 二 STM32CubeIDE配置 三 创建定时器 3 1 头文件声明 3 2 工程文件定义 3 3 创建定时器 3 4 开启 复位 和关闭定时器 四 定时器回调函数 一 简介 1 1 开发环境
  • 解决错误“ #error “include FreeRTOS.h“ must appear in source files before “include event_groups.““例子分享

    今天来给大家分享一下 关于之前自己在学习FreeRTOS过程中遇到的一个错误提示 话不多说 我们直接来看 错误分析 首先 我们看一下错误的提示 error 35 error directive include FreeRTOS h must
  • FreeRTOS学习(八) 延时函数

    声明及感谢 跟随正点原子资料学习 在此作为学习的记录和总结 环境 keil stm32f103 FreeRTOS延时函数有两个 分别是 vTaskDelay vTaskDelayUntil 1 vTaskDelay 任务相对延时 函数原型
  • FreeRTOS系列

    1 RTOS简介 RTOS全称为 Real Time Operation System 即实时操作系统 RTOS强调的是实时性 又分为硬实时和软实时 硬实时要求在规定的时间内必须完成操作 不允许超时 而软实时里对处理过程超时的要求则没有很严
  • STM32移植FreeRTOS的Tips

    转自 http bbs armfly com read php tid 7140 1 在FreeRTOS的demo文件夹中拷贝对应的FreeRTOSConfig h文件后 需要加入一行 define configUSE MUTEXES 1
  • 【FreeRTOS】队列的使用

    作者主页 凉开水白菜 作者简介 共同学习 互相监督 热于分享 多加讨论 一起进步 专栏资料 https pan baidu com s 1nc1rfyLiMyw6ZhxiZ1Cumg pwd free 点赞 收藏 再看 养成习惯 订阅的粉丝
  • FreeRTOS学习笔记<中断>

    中断概念 Cortex M的NVIC最多支持240个IRQ 中断请求 1个不可屏蔽中断 NMI 1个Systick 滴答定时器 定时器中断和多个系统异常 Cortex M处理器有多个用于管中断和异常的可编程寄存器 这些寄存器大多数都在 NV
  • Error: L6218E: Undefined symbol vApplicationGetIdleTaskMemory (referred from tasks.o).

    我用的是F103ZET6的板子 移植成功后 编译出现两个错误是关于stm32f10x it c 里 void SVC Handler void void PendSV Handler void 两个函数的占用问题 随后编译出现以下两个问题
  • 【FreeRTOS】任务通知的使用

    作者主页 凉开水白菜 作者简介 共同学习 互相监督 热于分享 多加讨论 一起进步 专栏资料 https pan baidu com s 1nc1rfyLiMyw6ZhxiZ1Cumg pwd free 点赞 收藏 再看 养成习惯 订阅的粉丝
  • FreeRTOS之软件定时器

    FreeRTOS之软件定时器 声明 本人按照正点原子的FreeRTOS例程进行学习的 欢迎各位大佬指责和批评 谢谢 include sys h include delay h include usart h include led h in
  • FreeRTOS学习(三)开关中断

    声明及感谢 跟随正点原子资料学习 在此作为学习的记录和总结 环境 keil stm32f103 背景知识 Cotex M3的NVIC最多支持240个IRQ 中断请求 1个不可屏蔽 NMI 1个Systick 滴答定时器 Cortex M处理
  • FreeRTOS死机原因

    1 中断回调函数中没有使用中断级API xxFromISR 函数 xSemaphoreGiveFromISR uart busy HighterTask 正确 xSemaphoreGive uart busy 错误 2 比configMAX
  • FreeRTOS笔记(二)

    FreeRTOS笔记 二 静态任务 文章目录 FreeRTOS笔记 二 静态任务 一 任务定义 二 任务创建 2 1 定义任务栈 2 2 定义任务函数 2 3 定义任务控制块 2 4 实现任务创建函数 三 实现就绪列表 3 1 定义就绪列表
  • FreeRTOS临界段

    1 临界段 在访问共享资源时不希望被其他任务或者中断打断的代码 这段要执行的代码称为临界段代码 2 设置临界段的目的 保护共享资源 例如 全局变量 公共函数 不可重入函数 函数里面使用 了一些静态全局变量 malloc 等 保护外设的实时性
  • C++ freeRTOS任务,非静态成员函数的无效使用

    哪里有问题 void MyClass task void pvParameter while 1 this gt update void MyClass startTask xTaskCreate this gt task Task 204
  • GNU Arm Cortex m4 上的 C++ 异常处理程序与 freertos

    2016 年 12 月更新现在还有一个关于此行为的最小示例 https community nxp com message 862676 https community nxp com message 862676 我正在使用带有 free
  • 哪些变量类型/大小在 STM32 微控制器上是原子的?

    以下是 STM32 微控制器上的数据类型 http www keil com support man docs armcc armcc chr1359125009502 htm http www keil com support man d

随机推荐

  • hualinux2.1 环境搭建:源码、二进制、yum/apt安装区别

    一 linux软件常用的安装方式 linux软件常见的安装方式一般分以下几种 xff1a 1 源码安装 xff1a 直接通过源代码安装 一般用make或cmake安装 2 二进制安装 xff1a 别人已经帮编译好了 xff0c 拿过来就可以
  • 百度试题---开发测试工程师

    一 问答题 说出常用的几种希哈函数 xff0c 其作用是什么 xff1f 描述OSI 的七层网络结构 xff0c HTTP 工作在哪一层 xff1f 描述一段C 语言代码程序能运行起来的代码要求和执行过程 二 算法设计 有一车苹果 xff0
  • ((硬件spi+dma)+模拟spi)驱动LCD5110

    span class hljs preprocessor ifndef spi dma h span span class hljs preprocessor define spi dma h span span class hljs pr
  • 《Python深度学习》第五章-2(Cats_vs_Dogs)读书笔记

    5 2 在小型数据集上从头开始训练一个卷积神经网络 当数据不够时 xff0c 有以下方法进行处理 xff0c 本节主要是介绍数据增强 5 2 1 深度学习与小数据问题的相关性 深度学习的一个基本特性就是能够独立地在训练数据中找到有趣的特征
  • 无人机原理图、pcb图下载地址

    原理图 pcb图下载地址 无人机pcb电路图原理图 智能家居文档类资源 CSDN文库
  • Win 10 系统下搭建 Python 编程环境,有手就行

    前言 想把一门编程语言学好 xff0c 会搭建编程环境无疑是有必要的 xff0c 纵然有线上集成的编程环境 xff0c 但那高度依赖于网络条件以及诸多情况 xff0c 这使得我们在编程的时候难以存储自己的资料 学习和使用 python 一年
  • Latex小白学习方法和实践

    1 了解简单语法 xff0c 读懂latex解决的问题 xff0c 和其核心方法论 知道latex其实不是万金油 xff0c 只是在熟悉的情况下 xff0c 让你更完美的排版 xff0c 省去可视化下不精确的问题 xff0c 其不会很快的完
  • PADS版本历史

    从加载文件的速度 xff0c 生成的文件大小 xff0c 画图的速度 xff0c 渲染的速度等多方面来说 xff0c 个人认为 PDAS 算是非常不错的一款软件 xff0c 值得学习和使用 大概把其光辉历史罗列一下 xff0c 作为一个回忆
  • 简述Z-Stack的基本工作原理与流程(OSAL操作系统)

    首先上图 xff0c 跟着图中的函数顺序来感受Z Stack的工作流程 xff1a Z Stack协议栈总的来说做了两件事 xff0c 系统的初始化和启动OSAL操作系统 系统初始化 xff1a 从main函数看 xff0c 首先是调用了o
  • 使用MaixPy IDE开发K210

    使用MaixPy IDE快速开发K210 距离我第一次使用MaixPy将近40天了 xff0c 大概花了26天 xff0c 完成了我的毕业设计并且进行了优化 后面我会花时间去和大家分享我的毕设 xff0c 也希望能得到各位码友的意见和建议
  • K210实现人脸识别(附代码解读)

    基于K210的人脸识别门禁 xff08 一 xff09 进入官网 xff08 首次登陆需要注册 xff09 获取人脸识别源码 https wiki sipeed com soft maixpy zh course ai image face
  • K210人脸识别+人脸信息存储

    在我的上一篇博客中已经介绍了如何使用K210实现基本的人脸识别功能 https blog csdn net HuangChen666 article details 113995079 spm 61 1001 2014 3001 5501
  • 旅行商问题--蚁群优化算法求解(matlab实现)

    今天给大家分享一下matlab实现蚁群优化算法 xff0c 解决旅行商问题 在上一篇博客中对蚁群优化算法做了较为详细的介绍 xff0c 有需要的小伙伴可以看一下 https blog csdn net HuangChen666 articl
  • 粒子群优化算法及MATLAB实现

    上一篇博客是关于蚁群优化算法的 xff0c 有兴趣的可以看下 https blog csdn net HuangChen666 article details 115913181 1 粒子群优化算法概述 2 粒子群优化算法求解 2 1 连续
  • A星(A*、A Star)路径规划算法详解(附MATLAB代码)

    首先看看运行效果 xff0c 分别有三种模式 xff0c 代码运行前需要通过鼠标点击设置起点和终点 第一种模式直接输出最短路径 第二种模式输出最短路径的生成过程 第三种模式输出最短路径的生成过程和详细探索的过程 代码获取 gitee链接 x
  • Ubuntu20.04+MAVROS+PX4+Gazebo保姆级安装教程

    Ubuntu20 04 43 MAVROS 43 PX4 43 Gazebo 安装PX4步骤安装MAVROS安装QGCPX4仿真 安装PX4步骤 从github上clone源码 span class token function git s
  • PX4+Offboard模式+代码控制无人机起飞(Gazebo)

    参考PX4自动驾驶用户指南 https docs px4 io main zh ros mavros offboard cpp html 我的另一篇博客写了 键盘控制PX4无人机飞行 PX4无人机 键盘控制飞行代码 可以先借鉴本篇博客 xf
  • 基于ESP32的小型四轴无人机

    粗糙版试飞成功 xff01 陀螺仪部分直接飞线飞了一个模块 xff0c 懒得焊了 不是很水平 xff0c 稳定性不是很好 因为滤波算法中加入的元素太少了 xff0c 目前也就MPU6050的输出数据加入了计算 xff0c 所以很多自稳定性飞
  • PX4无人机 - 键盘控制飞行代码

    PX4无人机 键盘控制飞行代码 仿真效果 实机效果 由于图片限制5M以内 xff0c 只能上传一小段了 xff0c 整段视频请点击链接 Pixhawk 6c 无人机 键盘控制无人机 Offboard模式 核心 xff1a 发布 mavros
  • 【FreeRTOS学习 - 消息队列学习】

    跟着韦东山老师FreeRTOS教学资料的学习记录 FreeRTOS全部项目代码链接 xff08 更新中 xff09 https gitee com chenshao777 free rtos study 本文章一共分为一下几个部分 1 创建