FreeRTOS_同步互斥与通信_环形buffer、队列_学习笔记

news2024/11/26 8:58:55

FreeRTOS_同步互斥与通信_概念_学习笔记
信号量、互斥量的本质是队列,队列的本质是加强版环形缓冲区

5 FreeRTOS数据传输的方法-环形buffer、队列

如果我有两个任务TaskA和TaskB,他俩可以同时运行。想要在他们之间传递数据,可以用一个全局变量实现。
但是用全局变量传递数据,一次只能传递1个数据,利用率很低,此外还可能出错

数据传输方法数据个数互斥措施阻塞-唤醒
全局变量1个
环形缓冲多个
队列多个

5.1 环形buffer概念

如果只需要沟通两个任务,且不考虑互斥和唤醒,就可以利用环形缓冲区
在这里插入图片描述
环形缓冲区就是个数组,需要指定读位置r和写位置w。
写时,如果w越界了,就要让他从0开始往后找

int buf[8];
int r=0, w=0; //r和w表示下一个读/写位置
//写操作:
if(w+1 != r)
{
	buf[w] = val;
	w++;
	if(w == 8){w = 0};
}
//读操作:
if(r != w)
{
	val = buf[r];
	r++;
	if(r == 8){r = 0};
}

能不能创建一个全局变量num来统计buf中的元素值,用num==8来判断是否满呢?这又涉及到全局变量的问题,有两个任务会对这个变量进行修改,有赋值过程中跳到别的任务的风险。
在上述方法中,写操作只能修改写位置,读操作只能修改读位置,没有任何一个变量能被多个任务修改,这就能避免出错。

5.2 队列概念与本质

队列中,数据读写的本质就是环形缓冲区,在此基础上增加了互斥机制、阻塞-环形机制。
如果队列不传输数据,只调整数据个数,他就是信号量(semaphore)
如果信号量中,限定数据个数最大为1,他就是互斥量(mutex)

举个例子:

流水线(环形buffer)两边有工人A和B,A(发送者)负责把产品放到流水线上,B(接收者)负责把产品从流水线上拿走进行下一步加工。
对于B,他执行的是读队列操作。如果流水线上没有产品,就睡一会(阻塞)。等到闹钟响了,或A写队列时(唤醒),他再起来继续工作。
对于A,他执行的是写队列操作。如果流水线放满了,就睡一会。等到闹钟响了,或B读队列时,他再起来继续工作

队列中有三个东西:

  1. 环形buffer(传送带)
  2. senderlist:想要发送但被阻塞的任务(睡着的A)
  3. receiverlist:想要接收但被阻塞的任务(睡着的B)

读队列的流程

创建任务B,他是就绪态,被放在在ReadyList中。B中执行读队列操作时,如果队列为空,任务B就会被阻塞,从ReadyList中删除,放到Queue.ReceiverList和DelayedLsit中。
即:一个就绪任务,如果读队列时阻塞了,会从就绪列表中删除,放到队列接收列表和等待列表里。
唤醒的情况1:
当任务A写队列时,会访问Queue.ReceiverList,如果非空,就会唤醒里面的第一个任务。将任务B从Queue.ReceiverList和DelayedLsit中删除,重新放入ReadyList中。
唤醒的情况2:
在每个tick中断,判断任务B是否超时(超时的时间由读操作的参数TickType_t指定),如果超时就唤醒。

5.3 队列函数

5.3.1 队列创建与删除

动态分配:

QueueHandle_t xQueueCreate(长度, 大小);

长度表示能放几个数据,大小表示每个数据的大小,以字节为单位。
创建成功返回句柄,失败返回NULL。

静态分配:

QueueHandel_t xQueueCreateStatic(长度, 大小, uint8_t数组, SQT结构体);

跟静态创建函数差不多。

复位:将队列回复为初始状态

xQueueReset(队列);

删除:

void vQueueDelete(队列);

只能删除动态创建的队列,并释放内存。

5.3.2 写队列

/* 等同于xQueueSendToBack
 * 往队列尾部写入数据,如果没有空间,阻塞时间为xTicksToWait
 */
BaseType_t xQueueSend(
	                      QueueHandle_t    xQueue,
	                      const void       *pvItemToQueue,
	                      TickType_t       xTicksToWait
	                  );

/* 
 * 往队列尾部写入数据,如果没有空间,阻塞时间为xTicksToWait
 */
BaseType_t xQueueSendToBack(
                                QueueHandle_t    xQueue,
                                const void       *pvItemToQueue,
                                TickType_t       xTicksToWait
                            );


/* 
 * 往队列尾部写入数据,此函数可以在中断函数中使用,不可阻塞
 */
BaseType_t xQueueSendToBackFromISR(
                                      QueueHandle_t xQueue,
                                      const void *pvItemToQueue,
                                      BaseType_t *pxHigherPriorityTaskWoken
                                   );

/* 
 * 往队列头部写入数据,如果没有空间,阻塞时间为xTicksToWait
 */
BaseType_t xQueueSendToFront(
                                QueueHandle_t    xQueue,
                                const void       *pvItemToQueue,
                                TickType_t       xTicksToWait
                            );

/* 
 * 往队列头部写入数据,此函数可以在中断函数中使用,不可阻塞
 */
BaseType_t xQueueSendToFrontFromISR(
                                      QueueHandle_t xQueue,
                                      const void *pvItemToQueue,
                                      BaseType_t *pxHigherPriorityTaskWoken
                                   );

一般记住
xQueueSendToFront(队列, 数据指针, 等待时间)
xQueueSendToBack(队列, 数据指针, 等待时间)
即可

5.3.3 读队列

读队列跟pop一样,读完了以后回吧这个数据移除

BaseType_t xQueueReceive( QueueHandle_t xQueue,
                          void * const pvBuffer,
                          TickType_t xTicksToWait );

BaseType_t xQueueReceiveFromISR(
                                    QueueHandle_t    xQueue,
                                    void             *pvBuffer,
                                    BaseType_t       *pxTaskWoken
                                );

一般记住
xQueueReceive(队列, 数据指针, 等待时间)

5.3.4 查询

/*
 * 返回队列中可用数据的个数
 */
UBaseType_t uxQueueMessagesWaiting( const QueueHandle_t xQueue );

/*
 * 返回队列中可用空间的个数
 */
UBaseType_t uxQueueSpacesAvailable( const QueueHandle_t xQueue );

5.3.5 窥视

会从队列中复制出数据,但是不移除数据。这也意味着,如果队列中没有数据,那么"偷看"时会导致阻塞;一旦队列中有数据,以后每次"偷看"都会成功。

/* 偷看队列
 * xQueue: 偷看哪个队列
 * pvItemToQueue: 数据地址, 用来保存复制出来的数据
 * xTicksToWait: 没有数据的话阻塞一会
 * 返回值: pdTRUE表示成功, pdFALSE表示失败
 */
BaseType_t xQueuePeek(
                          QueueHandle_t xQueue,
                          void * const pvBuffer,
                          TickType_t xTicksToWait
                      );

BaseType_t xQueuePeekFromISR(
                                 QueueHandle_t xQueue,
                                 void *pvBuffer,
                             );

5.3.6 覆盖

当队列长度为1时,可以覆盖数据

/* 覆盖队列
 * xQueue: 写哪个队列
 * pvItemToQueue: 数据地址
 * 返回值: pdTRUE表示成功, pdFALSE表示失败
 */
BaseType_t xQueueOverwrite(
                           QueueHandle_t xQueue,
                           const void * pvItemToQueue
                      );

BaseType_t xQueueOverwriteFromISR(
                           QueueHandle_t xQueue,
                           const void * pvItemToQueue,
                           BaseType_t *pxHigherPriorityTaskWoken
                      );

5.4 实验

5.4.1 原始程序

原始程序是一个利用红外遥控器控制挡球板的打砖块游戏,

    xTaskCreate(platform_task, "platform_task", 128, NULL, osPriorityNormal, NULL);

    while (1)
    {
        game1_draw();//让球运动到下一个位置,并判断是否发生触碰
        vTaskDelay(50);
    }

其中,挡球板任务如下:

static void platform_task(void *params)
{
    byte platformXtmp = platformX;    
    uint8_t dev, data, last_data;

    // Draw platform
    draw_bitmap(platformXtmp, g_yres - 8, platform, 12, 8, NOINVERT, 0);
    draw_flushArea(platformXtmp, g_yres - 8, 12, 8);
    
    while (1)
    {
        /* 读取红外遥控器 */
		if (0 == IRReceiver_Read(&dev, &data))
		{
            if (data == 0x00)
            {
                data = last_data;
            }
            
            if (data == 0xe0) /* Left */
            {
                btnLeft(); //左移
            }

            if (data == 0x90)  /* Right */
            {
                btnRight(); //右移
            }
            last_data = data;

            
            // Hide platform
            draw_bitmap(platformXtmp, g_yres - 8, clearImg, 12, 8, NOINVERT, 0);
            draw_flushArea(platformXtmp, g_yres - 8, 12, 8);
            
            // Move platform 根据移动情况改变x坐标
            if(uptMove == UPT_MOVE_RIGHT)
                platformXtmp += 3;
            else if(uptMove == UPT_MOVE_LEFT)
                platformXtmp -= 3;
            uptMove = UPT_MOVE_NONE;
            
            // Make sure platform stays on screen 
            if(platformXtmp > 250)
                platformXtmp = 0;
            else if(platformXtmp > g_xres - PLATFORM_WIDTH)
                platformXtmp = g_xres - PLATFORM_WIDTH;
            
            // Draw platform 并且重新绘制挡球板
            draw_bitmap(platformXtmp, g_yres - 8, platform, 12, 8, NOINVERT, 0);
            draw_flushArea(platformXtmp, g_yres - 8, 12, 8);
            
            platformX = platformXtmp;
            
		}
    }
}

现在获取红外遥控的值是通过IRReceiver_Read实现,其读环形缓冲区:

int IRReceiver_Read(uint8_t *pDev, uint8_t *pData)
{
    if (isKeysBufEmpty())
        return -1;
    
    *pDev  = GetKeyFromBuf();
    *pData = GetKeyFromBuf();
    return 0;
}

而写环形缓冲区则由IRReceiver_IRQ_Callback中断服务程序实现

void IRReceiver_IRQ_Callback(void)
{
    uint64_t time;
    static uint64_t pre_time = 0;

        
	/* 1. 记录中断发生的时刻 */	
	time = system_get_ns();
    
    /* 一次按键的最长数据 = 引导码 + 32个数据"1" = 9+4.5+2.25*32 = 85.5ms
     * 如果当前中断的时刻, 举例上次中断的时刻超过这个时间, 以前的数据就抛弃
     */
    if (time - pre_time > 100000000) 
    {
        g_IRReceiverIRQ_Cnt = 0;
    }
    pre_time = time;
    
	g_IRReceiverIRQ_Timers[g_IRReceiverIRQ_Cnt] = time;

	/* 2. 累计中断次数 */
	g_IRReceiverIRQ_Cnt++;

	/* 3. 次数达标后, 解析数据, 放入buffer */
	if (g_IRReceiverIRQ_Cnt == 4)
	{
		/* 是否重复码 */
		if (isRepeatedKey())
		{
			/* device: 0, val: 0, 表示重复码 */
			PutKeyToBuf(0);
			PutKeyToBuf(0);
			g_IRReceiverIRQ_Cnt = 0;
		}
	}
	if (g_IRReceiverIRQ_Cnt == 68)
	{
		IRReceiver_IRQTimes_Parse();
		g_IRReceiverIRQ_Cnt = 0;
	}
}

上述读写环形缓冲区的操作,一直尝试读取红外接收器的信号,效率很低。
因此,对红外遥控器的控制进行改进:

  1. 创建队列
  2. 在挡球板platform_task任务中读队列
  3. 在红外遥控器的中断IRISR中写队列

5.4.2 改进需求:读环形缓冲区->读队列

将读写环形缓冲区改成读写队列。

void game1_task(void *params)
{...
	/* 创建队列:平台任务从里面读到红外数据,... */
	g_xQueuePlatform = xQueueCreate(10, sizeof(struct input_data));
    xTaskCreate(platform_task, "platform_task", 128, NULL, osPriorityNormal, NULL);

    while (1)
    {
        game1_draw();//让球运动到下一个位置,并判断是否发生触碰
        vTaskDelay(50);
    }
...}
QueueHandle_t g_xQueuePlatform; /* 挡球板队列 */
static void platform_task(void *params)
{
    byte platformXtmp = platformX;    
    uint8_t dev, data, last_data;
	struct input_data idata; //用来存放数据

    // Draw platform
    draw_bitmap(platformXtmp, g_yres - 8, platform, 12, 8, NOINVERT, 0);
    draw_flushArea(platformXtmp, g_yres - 8, 12, 8);
    
    while (1)
    {
        /* 读取红外遥控器 */
		//if (0 == IRReceiver_Read(&dev, &data))
		if (pdPASS == xQueueReceive(g_xQueuePlatform, &idata, portMAX_DELAY))
		{
			data = idata.val;
            if (data == 0x00)
            {
                data = last_data;
            }

g_xQueuePlatform = xQueueCreate(10, sizeof(struct input_data))创建了一个队列,并在挡球板任务中使用xQueueReceive读队列。

5.4.3 改进需求:写环形缓冲区->写队列

extern QueueHandle_t g_xQueuePlatform; // 挡球板队列 声明外部变量
void IRReceiver_IRQ_Callback(void)
{
    uint64_t time;
    static uint64_t pre_time = 0;
	struct input_data idata; // 定义输入数据

        
	/* 1. 记录中断发生的时刻 */	
	time = system_get_ns();
    
    /* 一次按键的最长数据 = 引导码 + 32个数据"1" = 9+4.5+2.25*32 = 85.5ms
     * 如果当前中断的时刻, 举例上次中断的时刻超过这个时间, 以前的数据就抛弃
     */
    if (time - pre_time > 100000000) 
    {
        g_IRReceiverIRQ_Cnt = 0;
    }
    pre_time = time;
    
	g_IRReceiverIRQ_Timers[g_IRReceiverIRQ_Cnt] = time;

	/* 2. 累计中断次数 */
	g_IRReceiverIRQ_Cnt++;

	/* 3. 次数达标后, 解析数据, 放入buffer */
	if (g_IRReceiverIRQ_Cnt == 4)
	{
		/* 是否重复码 */
		if (isRepeatedKey())
		{
			/* device: 0, val: 0, 表示重复码 */
			//PutKeyToBuf(0);
			//PutKeyToBuf(0);
			/* 写队列 */
			idata.dev = 0;
			idata.val = 0;
			xQueueSendToBackFromISR(g_xQueuePlatform, &idata, NULL);//中断里不允许等待
			g_IRReceiverIRQ_Cnt = 0;
		}
	}
	if (g_IRReceiverIRQ_Cnt == 68)
	{
		IRReceiver_IRQTimes_Parse();
		g_IRReceiverIRQ_Cnt = 0;
	}
}

使用xQueueSendToBackFromISR(g_xQueuePlatform, &idata, NULL)实现写队列

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

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

相关文章

深入解析kube-scheduler的算法自定义插件

目录 ​编辑 一、问题引入 二、自定义步骤 三、最佳实践考虑 一、问题引入 当涉及到 Kubernetes 集群的调度和资源分配时,kube-scheduler 是一个关键组件。kube-scheduler 负责根据集群的调度策略,将 Pod 分配到适当的节点上。kube-scheduler 默认使…

YTM32的flash应用答疑-详解写保护功能

YTM32的flash应用答疑-详解写保护功能 文章目录 YTM32的flash应用答疑-详解写保护功能IntroductionPrincipleOperation & DemonstrationDemo #1 验证基本的写保护功能Demo #2 编程CUS_NVR设定EFM_ADDR_PROT初值Demo #3 启用写保护后试试块擦除操作 Conclusion Introduction…

HarmonyOS之ArkUI布局设计常见细节

这里写目录标题 1. Button设置带有渐变色的背景图片无效1.1 问题分析1.2 成功案例 2. 路由跳转失败2.1 问题分析 1. Button设置带有渐变色的背景图片无效 1.1 问题分析 说明:设置颜色渐变需先设置backgroundColor为透明色。 Button($r(app.string.login), { type…

python实现对应分析的随笔记

文档来源: Correspondence analysis 1 对应分析 参考: SPSS(十二)SPSS对应分析(图文数据集)案例6:SPSS–对应分析10 对应分析 对应分析的实质(理论很复杂,但是结果很明…

春秋CVE-2022-23906

简介 CMS Made Simple v2.2.15 被发现包含通过上传图片功能的远程命令执行 (RCE) 漏洞。此漏洞通过精心制作的图像文件被利用。 正文 1.进入靶场2.进入登录界面,弱口令admin/123456 3.进入后台,文件上传点 4.上传一句话木马图片 5.复制图片&#xf…

爬虫基础1

一、爬虫的基本概念 1.什么是爬虫? 请求网站并提取数据的自动化程序 2.爬虫的分类 2.1 通用爬虫(大而全) 功能强大,采集面广,通常用于搜索引擎:百度,360,谷歌 2.2 聚焦爬虫&#x…

人工智能应用-实验4-蚁群算法求解 TSP

文章目录 🧡🧡实验内容🧡🧡🧡🧡代码🧡🧡🧡🧡分析结果🧡🧡🧡🧡实验总结🧡🧡 &#x1f9…

【GO基础】1. Go语言环境搭建

Go语言环境搭建 Go的三种安装方式Go标准包安装Windows 安装验证是否安装成功 4.Go的第一个程序 Hello World.go Go的三种安装方式 Go有多种安装方式,可以选择自己适合的。这里介绍三种最常见的安装方式: Go源码安装:这是一种标准的软件安装…

【NumPy】NumPy实战入门:统计与聚合(histogram、percentile、corrcoef、cov)详解

🧑 博主简介:阿里巴巴嵌入式技术专家,深耕嵌入式人工智能领域,具备多年的嵌入式硬件产品研发管理经验。 📒 博客介绍:分享嵌入式开发领域的相关知识、经验、思考和感悟,欢迎关注。提供嵌入式方向…

如何解决vcruntime140.dll丢失问题,详细介绍5种靠谱的解决方法

vcruntime140.dll是Microsoft Visual C Redistributable Package的一部分,它为使用Visual C编译器开发的应用程序提供必要的运行时环境。该DLL文件包含了大量应用程序运行时需要调用的库函数,这些函数是实现C标准库、异常处理机制、RTTI(运行…

IO端口编址

统一编址 特点 独立编址 特点 内存地址分配 区别 应用 IO端口地址译码 硬件上的实现 示例1: 示例2: IO指令 软件上的实现 示例

golang通过go-aci适配神通数据库

1. go-aci简介 go-aci是神通数据库基于ACI(兼容Oracle的OCI)开发的go语言开发接口,因此运行时需要依赖ACI驱动和ACI库的头文件。支持各种数据类型的读写、支持参数绑定、支持游标范围等操作。 2. Linux部署步骤 2.1. Go安装: 版本:1.9以上…

CleanMyMac X2024垃圾清理神器,让你的Mac保持飞速运行

在数字时代的浪潮中,我们的苹果电脑扮演了至关重要的角色。然而,随着数据的增长和存储需求的不断上升,不合理的文件管理往往会导致系统性能逐渐下降,影响我们的工作效率。为了有效应对这一挑战,许多用户转向使用专为Ma…

抖音运营_抖音电商介绍

截止20年8月,抖音的日活跃数高达6亿。 20年6月,上线抖店 (抖音官方电商) 一 抖店的定位和特色 1 一站式经营 帮助商家进行 商品交易、店铺管理、客户服务 等全链路的生意经营 2 多渠道拓展 抖音、今日头条、西瓜、抖音火山版…

MyBatisPlus使用流程

引入依赖 <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.4</version> </dependency> 版本号根据需要选取 在实体类上加注解声明&#xff0c;表信息 根据数…

7、按钮无法点击

不能点击&#xff0c;打开f12&#xff0c;删除disabled

AIGC绘画设计基础-建筑设计应用

一、AI及AIGC 对于AI大家都不陌生&#xff0c;但是AIGC这个概念好多人其实不大清楚。“AI”是指人工智能技术本身&#xff0c;而“AIGC”是指基于人工智能技术而生成的内容。 生成式人工智能——AIGC&#xff08;Artificial Intelligence Generated Content&#xff09;&…

Pod进阶——资源限制以及探针检查

目录 一、资源限制 1、资源限制定义&#xff1a; 2、资源限制request和limit资源约束 3、Pod和容器的资源请求和限制 4、官方文档示例 5、CPU资源单位 6、内存资源单位 7、资源限制实例 ①编写yaml资源配置清单 ②释放内存&#xff08;node节点&#xff0c;以node01为…

阴影映射(线段树)

实时阴影是电子游戏中最为重要的画面效果之一。在计算机图形学中&#xff0c;通常使用阴影映射方法来实现实时阴影。 游戏开发部正在开发一款 2D 游戏&#xff0c;同时希望能够在 2D 游戏中模仿 3D 游戏的光影效果&#xff0c;请帮帮游戏开发部&#xff01; 给定 x-y 平面上的…

深度学习模型keras第二十一讲:使用BaseImageAugmentationLayer进行自定义图像增强

1、自定义图像增强技术概述 1.1自定义图像增强概念 深度学习的自定义图像增强技术是一种通过自定义的算法和策略对图像进行变换&#xff0c;以增加模型泛化能力和提高训练效果的技术。这些增强技术可以应用于各种深度学习任务&#xff0c;如图像分类、目标检测、图像分割等。…