【FreeRTOS】信号量实验-控制车辆运行

news2024/9/21 9:47:18

目录

  • 0 前言
  • 1 控制车辆运行
  • 2 不使用信号量
  • 3 使用计数型信号量
    • 3.1 运行两辆车运行
    • 3.2 运行三辆车运行
  • 4 使用==二进制信号量==
  • 5 补充信号量知识
    • 5.1 两种信号量对比
    • 5.2 信号量函数
    • 5.3 创建
    • 5.4 删除
    • 5.5 Take / Give
      • 5.5.1 xSemaphoreGive
      • 5.5.2 pxHigherPriorityTaskWoken
      • 5.5.3 xSemaphoreTake
      • 5.5.4 xSemaphoreTakeFromISR


0 前言

学习视频:
【FreeRTOS入门与工程实践 --由浅入深带你学习FreeRTOS(FreeRTOS教程 基于STM32,以实际项目为导向)】 【精准空降到 00:28】 https://www.bilibili.com/video/BV1Jw411i7Fz/?p=40&share_source=copy_web&vd_source=8af85e60c2df9af1f0fd23935753a933&t=28

参考《FreeRTOS入门与工程实践(基于DshanMCU-103).pdf》


1 控制车辆运行

任务 :本节源码:在"17_queue_car_dispatch"的基础上,改出:

  • 18_semaphore_not_use:不使用信号量,3俩车都可以进城
  • 19_semaphore_count:使用计数型信号量,同时允许多辆车进城
  • 20_semaphore_binary:使用二进制信号量,同时只允许一辆车进城

17_queue_car_dispatch是使用遥控器数字键 1 2 3来控制3辆车的运行,现在18_semaphore_not_use:不使用信号量,3俩车都可以进城


2 不使用信号量

  • 18_semaphore_not_use:不使用信号量,3俩车都可以进城

修改game2.c中的CarTask任务函数,注释掉读取按键值和下面的if判断语句,加入延时,减小步进距离,现象更加明显了。

static void CarTask(void *params)
{
	struct car *pcar = params;
	struct ir_data idata;
	
	/* 创建自己的队列 */
	QueueHandle_t xQueueIR = xQueueCreate(10, sizeof(struct ir_data));
	
	/* 注册队列 */
	RegisterQueueHandle(xQueueIR);

	/* 显示汽车 */
	ShowCar(pcar);
	
	while (1)
	{
		/* 读取按键值:读队列 */
		// xQueueReceive(xQueueIR, &idata, portMAX_DELAY);
		
		/* 控制汽车往右移动 */
		// if (idata.val == pcar->control_key)
		{
			if (pcar->x < g_xres - CAR_LENGTH)
			{
				/* 隐藏汽车 */
				HideCar(pcar);
				
				/* 调整位置 */
				pcar->x += 3;
				if (pcar->x > g_xres - CAR_LENGTH)
				{
					pcar->x = g_xres - CAR_LENGTH;
				}
				
				/* 重新显示汽车 */
				ShowCar(pcar);
                
                vTaskDelay(50);
                if (pcar->x == g_xres - CAR_LENGTH) //移动到最右边
                    vTaskDelete(NULL);
			}
		}
	}
}

烧录程序之后能看到,三辆车基本是同时运行,同时抵达终点的!


3 使用计数型信号量

3.1 运行两辆车运行

在第18个程序的基础上修改代码

  • 19_semaphore_count:使用计数型信号量,同时允许多辆车进城

任务:
创建一个信号量:初始值为2,只能有两辆车运行到站。

使用信号量之前,得先创建信号量。

在 game2.c 中创建一个全局的信号量

static SemaphoreHandle_t g_xSemTicks;   //信号量

在CarTask任务里显示完汽车之后,获取一下信号量

/* 先获取信号量 */
    xSemaphoreTake(g_xSemTicks, portMAX_DELAY); //不成功就永远等待

完整的CarTask函数

static void CarTask(void *params)
{
	struct car *pcar = params;
	struct ir_data idata;
	
	/* 创建自己的队列 */
	QueueHandle_t xQueueIR = xQueueCreate(10, sizeof(struct ir_data));
	
	/* 注册队列 */
	RegisterQueueHandle(xQueueIR);

	/* 显示汽车 */
	ShowCar(pcar);
	
    /* 先获取信号量 */
    xSemaphoreTake(g_xSemTicks, portMAX_DELAY); //不成功就永远等待
    
	while (1)
	{
		/* 读取按键值:读队列 */
		// xQueueReceive(xQueueIR, &idata, portMAX_DELAY);
		
		/* 控制汽车往右移动 */
		// if (idata.val == pcar->control_key)
		{
			if (pcar->x < g_xres - CAR_LENGTH)
			{
				/* 隐藏汽车 */
				HideCar(pcar);
				
				/* 调整位置 */
				pcar->x += 3;
				if (pcar->x > g_xres - CAR_LENGTH)
				{
					pcar->x = g_xres - CAR_LENGTH;
				}
				
				/* 重新显示汽车 */
				ShowCar(pcar);
                
                vTaskDelay(50);
                if (pcar->x == g_xres - CAR_LENGTH) //移动到最右边
                    vTaskDelete(NULL);
			}
		}
	}
}

编译烧录之后,只有1、3两辆车可以到达终点,结合之前的内容,现象是正确的。
后创建的任务先执行,然后执行第一个创建的任务。

3.2 运行三辆车运行

这里操作是假设有一辆车到站了,走到头了,就释放信号量,这样第三辆车就可以运行了

只修改CarTask里的最后一部分,添加释放信号量的操作!

/* 重新显示汽车 */
ShowCar(pcar);

vTaskDelay(50);
if (pcar->x == g_xres - CAR_LENGTH) //移动到最右边
{
	/* 释放信号量 */
	xSemaphoreGive(g_xSemTicks);
	vTaskDelete(NULL);
}

4 使用二进制信号量

在第19个程序的基础上修改代码

  • 20_semaphore_binary:使用二进制信号量,同时只允许一辆车进城
g_xSemTicks = xSemaphoreCreateCounting(3, 1); //创建信号量,最大值3,初始值1

将信号量改为1,每次只能有一辆车运行,车辆运行的顺序是3、1、2

现在就出现了唤醒的顺序问题,为什么是312的顺序呢?

如果有任务想获得信号量,获取不成功的话,并且愿意等待的话,会被放入等待的链表里,再链表里是怎么排序的呢?

  • 高优先级任务,排在前面。
  • 同等优先级的任务,按调用函数的先后时刻排序。

当有信号量被释放,就会到这个链表里找到第一个任务来执行!

在这里插入图片描述

我们这里任务3最后创建,任务最先执行,其次是任务1,再次是任务2,这里可以看以前的文章,有详细介绍。

创建二进制信号量的方法,有两种,区别就在于初始值,xSemaphoreCreateBinary的初始值为0且不能修改,xSemaphoreCreateCounting的初始值可以指定。

    g_xSemTicks = xSemaphoreCreateCounting(1, 1); //创建二进制信号量,最大值1,初始值1
    g_xSemTicks = xSemaphoreCreateBinary();//创建二进制信号量,初始值为0且不能修改

使用xSemaphoreCreateBinary创建信号量,会发现三辆车都不能移动了,因为初值是0,所以不能移动!
解决方法:

    g_xSemTicks = xSemaphoreCreateBinary();//创建二进制信号量,初始值为0且不能修改
    xSemaphoreGive(g_xSemTicks);   //增加一个信号量
    xSemaphoreGive(g_xSemTicks);   //对于二进制信号量,最大值为1,后面这几次Give都无效,只有第一次有效
    xSemaphoreGive(g_xSemTicks);   
    xSemaphoreGive(g_xSemTicks);   

现在就能正常运行汽车任务了。


5 补充信号量知识

信号量这个名字很恰当:

  • 信号:起通知作用
  • :还可以用来表示资源的数量
    • 当"量"没有限制时,它就是"计数型信号量"(Counting Semaphores)
    • 当"量"只有0、1两个取值时,它就是"二进制信号量"(Binary Semaphores)
  • 支持的动作:"give"给出资源,计数值加1;"take"获得资源,计数值减1

计数型信号量的典型场景是:

  • 计数:事件产生时"give"信号量,让计数值加1;处理事件时要先"take"信号量,就是获得信号量,让计数值减1。
  • 资源管理:要想访问资源需要先"take"信号量,让计数值减1;用完资源后"give"信号量,让计数值加1。 信号量的"give"、"take"双方并不需要相同,可以用于生产者-消费者场合:
  • 生产者为任务A、B,消费者为任务C、D
  • 一开始信号量的计数值为0,如果任务C、D想获得信号量,会有两种结果:
    • 阻塞:买不到东西咱就等等吧,可以定个闹钟(超时时间)
    • 即刻返回失败:不等
  • 任务A、B可以生产资源,就是让信号量的计数值增加1,并且把等待这个资源的顾客唤醒
  • 唤醒谁?谁优先级高就唤醒谁,如果大家优先级一样就唤醒等待时间最长的人

二进制信号量跟计数型的唯一差别,就是计数值的最大值被限定为1。
在这里插入图片描述

5.1 两种信号量对比

信号量的计数值都有限制:限定了最大值。如果最大值被限定为1,那么它就是二进制信号量;如果最大值不是1,它就是计数型信号量。

差别列表如下:

二进制信号量计术型信号量
被创建时初始值为0被创建时初始值可以设定
其他操作是一样的其他操作是一样的

5.2 信号量函数

使用信号量时,先创建、然后去添加资源、获得资源。使用句柄来表示一个信号量。

5.3 创建

使用信号量之前,要先创建,得到一个句柄;使用信号量时,要使用句柄来表明使用哪个信号量。 对于二进制信号量、计数型信号量,它们的创建函数不一样:

二进制信号量计数型信号量
动态创建xSemaphoreCreateBinary计数值初始值为0
vSemaphoreCreateBinary(过时了) 计数值初始值为1
静态创建xSemaphoreCreateBinaryStaticxSemaphoreCreateCountingStatic

创建二进制信号量的函数原型如下:

/* 创建一个二进制信号量,返回它的句柄。
 * 此函数内部会分配信号量结构体 
 * 返回值: 返回句柄,非NULL表示成功
 */
SemaphoreHandle_t xSemaphoreCreateBinary( void );

/* 创建一个二进制信号量,返回它的句柄。
 * 此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t结构体,并传入它的指针
 * 返回值: 返回句柄,非NULL表示成功
 */
SemaphoreHandle_t xSemaphoreCreateBinaryStatic( StaticSemaphore_t *pxSemaphoreBuffer );

创建计数型信号量的函数原型如下:

/* 创建一个计数型信号量,返回它的句柄。
 * 此函数内部会分配信号量结构体 
 * uxMaxCount: 最大计数值
 * uxInitialCount: 初始计数值
 * 返回值: 返回句柄,非NULL表示成功
 */
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount);

/* 创建一个计数型信号量,返回它的句柄。
 * 此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t结构体,并传入它的指针
 * uxMaxCount: 最大计数值
 * uxInitialCount: 初始计数值
 * pxSemaphoreBuffer: StaticSemaphore_t结构体指针
 * 返回值: 返回句柄,非NULL表示成功
 */
SemaphoreHandle_t xSemaphoreCreateCountingStatic( UBaseType_t uxMaxCount, 
                                                 UBaseType_t uxInitialCount, 
                                                 StaticSemaphore_t *pxSemaphoreBuffer );

5.4 删除

对于动态创建的信号量,不再需要它们时,可以删除它们以回收内存。

vSemaphoreDelete可以用来删除二进制信号量、计数型信号量,函数原型如下:

/*
 * xSemaphore: 信号量句柄,你要删除哪个信号量
 */
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );

5.5 Take / Give

二进制信号量、计数型信号量的give、take操作函数是一样的。这些函数也分为2个版本:给任务使用,给ISR使用。列表如下:

在任务中使用在ISR中使用
givexSemaphoreGivexSemaphoreGiveFromISR
takexSemaphoreTakexSemaphoreTakeFromISR

5.5.1 xSemaphoreGive

xSemaphoreGive的函数原型如下:

BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );

xSemaphoreGive函数的参数与返回值列表如下:

参数说明
xSemaphore信号量句柄,释放哪个信号量
返回值pdTRUE表示成功, 如果二进制信号量的计数值已经是1,再次调用此函数则返回失败; 如果计数型信号量的计数值已经是最大值,再次调用此函数则返回失败

5.5.2 pxHigherPriorityTaskWoken

pxHigherPriorityTaskWoken的函数原型如下:

BaseType_t xSemaphoreGiveFromISR(
                        SemaphoreHandle_t xSemaphore,
                        BaseType_t *pxHigherPriorityTaskWoken
                    );

xSemaphoreGiveFromISR函数的参数与返回值列表如下:

参数说明
xSemaphore信号量句柄,释放哪个信号量
pxHigherPriorityTaskWoken如果释放信号量导致更高优先级的任务变为了就绪态, 则*pxHigherPriorityTaskWoken = pdTRUE
返回值pdTRUE表示成功, 如果二进制信号量的计数值已经是1,再次调用此函数则返回失败; 如果计数型信号量的计数值已经是最大值,再次调用此函数则返回失败

5.5.3 xSemaphoreTake

xSemaphoreTake的函数原型如下:

BaseType_t xSemaphoreTake(
                   SemaphoreHandle_t xSemaphore,
                   TickType_t xTicksToWait
               );

xSemaphoreTake函数的参数与返回值列表如下:

参数说明
xSemaphore信号量句柄,获取哪个信号量
xTicksToWait如果无法马上获得信号量,阻塞一会: 0:不阻塞,马上返回 portMAX_DELAY: 一直阻塞直到成功 其他值: 阻塞的Tick个数,可以使用*pdMS_TO_TICKS()*来指定阻塞时间为若干ms
返回值pdTRUE表示成功

5.5.4 xSemaphoreTakeFromISR

xSemaphoreTakeFromISR的函数原型如下:

BaseType_t xSemaphoreTakeFromISR(
                        SemaphoreHandle_t xSemaphore,
                        BaseType_t *pxHigherPriorityTaskWoken
                    );

xSemaphoreTakeFromISR函数的参数与返回值列表如下:

参数说明
xSemaphore信号量句柄,获取哪个信号量
pxHigherPriorityTaskWoken如果获取信号量导致更高优先级的任务变为了就绪态, 则*pxHigherPriorityTaskWoken = pdTRUE
返回值pdTRUE表示成功

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

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

相关文章

库存零件耗尽 任天堂宣布停止New 3DS的维修服务

由于库存零件已耗尽&#xff0c;任天堂宣布自8月28日起停止接受New 3DS游戏机的维修服务。在今年3月份时&#xff0c;任天堂就宣布过2DS、New 3DS、New 3DS LL的维修服务将在库存零件耗尽后终止&#xff0c;目前来看2DS和New 3DS LL的维修服务还将继续&#xff0c;直到零件耗尽…

算法中常用的排序

1.概念 排序是将一组数据,依指定的顺序进行排列的过程. 2.排序的分类 (1).内部排序 指将需要处理的所有数据都加载到内部存储器中进行排序.包括:交换式排序法,选择式排序法和插入式排序法 (2).外部排序 数据量过大,无法全部加载到内存中,需要借助外部存储进行排序.包括:合并排序…

FTP主动与被动模式

文件传送协议FTP&#xff1a; 提供交互式访问FTP屏蔽了各计算机系统的细节&#xff0c;因⽽适合于在异构⽹络中任意计算机之间传送⽂件。 传统FTP默认不加密 工作模式&#xff1a;&#xff08;站在服务器的角度&#xff09; 主动模式&#xff1a;服务器主动使用TCP20端口发起数…

群晖NAS配置SFTP服务并结合内网穿透工具实现无公网IP远程传输文件

文章目录 前言1. 开启群晖SFTP连接2. 群晖安装Cpolar工具3. 创建SFTP公网地址4. 群晖SFTP远程连接5. 固定SFTP公网地址6. SFTP固定地址连接 前言 本文主要介绍如何将在群晖NAS中开启SFTP服务&#xff0c;并安装cpolar内网穿透工具配置公网地址&#xff0c;轻松打造一套高效、安…

LRN正则化是什么?

LRN正则化&#xff0c;全称为Local Response Normalization&#xff08;局部响应归一化&#xff09;&#xff0c;是一种在深度学习&#xff0c;特别是在卷积神经网络&#xff08;CNN&#xff09;中常用的正则化技术。该技术旨在通过模拟生物视觉系统中的侧抑制现象&#xff0c;…

【第0003页 · 递归】N皇后问题

【前言】本文以及之后的一些题解都会陆续整理到目录中&#xff0c;若想了解全部题解整理&#xff0c;请看这里&#xff1a; 第0003页 N皇后问题 今天我们来看一个著名的问题&#xff1a;N皇后问题。在此之前&#xff0c;我们先温习一下递归的思想。当然&#xff0c;温习的方式…

阅读笔记:明朝那些事儿人间再无魏忠贤

持续了10多天时间&#xff0c;明朝那些事儿第八部人间再无魏忠贤截止到今天凌晨0&#xff1a;58分看完了&#xff0c;给我印象比较深刻的人物杨涟&#xff0c;努尔哈赤&#xff0c;孙承宗&#xff0c;袁崇焕&#xff0c;魏忠贤&#xff0c;皇太极&#xff0c;熊廷弼&#xff0c…

C#MDI子窗体通过TabControl列表显示的控制实现过程

类似excel表格中各个表单sheet的切换效果&#xff0c;使用tabcontrol控件实现类似的功能。效果如下&#xff1a; 过程涉及父窗体MDIParent1、子窗体main、自定义基础功能类MdiChildBase。 基础功能类MdiChildBase继承自Form创建&#xff0c;定义了一个委托SetTabControlDelega…

项目:基于TCP的文件传输系统

项目介绍: 模拟FTP原理&#xff1a;客户端连接服务器后&#xff0c;向服务器发送一个文件。文件名可以通过参数指定&#xff0c;服务器端接收客户端传来的文件&#xff08;文件名随意&#xff09;&#xff0c;如果文件不存在自动创建文件&#xff0c;如果文件存在&#xff0c;…

2024年整理的自动化测试面试题及答案

selenium中如何判断元素是否存在&#xff1f; 没有提供原生的方法判断元素是否存在&#xff0c;一般我们可以通过定位元素异常捕获的方式判断selenium中hidden或者是display &#xff1d; none的元素是否可以定位到&#xff1f;不可以&#xff0c;想点击的话&#xff0c;可以用…

C# 爬虫技术:京东视频内容抓取的实战案例分析

摘要 随着互联网技术的飞速发展&#xff0c;数据的获取和分析变得愈发重要。爬虫技术作为数据获取的重要手段之一&#xff0c;广泛应用于各个领域。本文将重点探讨C#语言在京东视频抓取中的实现过程&#xff0c;分析其技术细节&#xff0c;并提供相应的代码实现。 引言 京东…

python学习之路 - 面向对象编程

目录 一、面向对象编程1、成员方法a、类的定义和使用b、案例 2、类和对象3、构造方法4、其他内置方法&#xff08;魔术方法&#xff09;5、面向对象三大特性——封装a、介绍&#xff1a;b、表现形式&#xff1a;私有成员变量与私有成员方法c、作用 6、面向对象三大特性——继承…

iview Cascader 组件动态数据回显

在使用Cascader组件动态加载数据后&#xff0c;编辑的时候回显会有问题 问题如下&#xff1a;回显的时候&#xff0c;如果是多级&#xff0c;只显示了一级且&#xff0c;中间会闪一下 经过多方查找资料发现&#xff0c;是callback造成的。给组件增加on-visible-change事件监听…

如何下载淘宝的主图视频

目录&#xff1a; 1、通过插件插件下载短视频 1&#xff09;获取“Microsoft Edge扩展” 2&#xff09;搜索“aix智能下载器” 3&#xff09;将插件钉在浏览器上 4&#xff09;嗅控并下载视频 2、从其他来源安装插件 1、通过插件插件下载短视频 1&#xff09;获取“M…

孙宇晨:以区块链科技为翼,青年企业家引领社会进步新航向

​ 孙宇晨&#xff0c;作为区块链领域的一位青年企业家&#xff0c;以其大胆的创新精神和卓越的远见&#xff0c;正在用区块链技术推动社会的进步。他不仅在加密货币和区块链技术领域取得了令人瞩目的成就&#xff0c;还通过不断的努力&#xff0c;致力于将这些技术应用…

FreeRTOS 列表 List 源码解析

目录 一、链表及链表项的定义1、链表节点数据结构 xList_ITEM2、链表精简节点结构 xMINI_LIST_ITEM3、链表根节点结构 xLIST 二、链表的相关操作1、初始化1.1 链表节点初始化1.2 链表根节点初始化 2、插入2.1 将节点插入到链表的尾部2.2 将节点按照升序排列插入到链表 3、删除4…

(go)线性表的顺序存储

闲来无事&#xff0c;更新一下&#xff0c;线性表的顺序存储&#xff0c;go语言版本&#xff0c;效果都已经测试过&#xff0c;下面给出各部分细节 文章目录 1、生成一个线性表2、查找3、插入4、求长度5、改值6、删除7、遍历8、测试程序9、完整代码总结 package mainimport &q…

VBA技术资料MF195:屏蔽工作表中的粘贴输入

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。“VBA语言専攻”提供的教程一共九套&#xff0c;分为初级、中级、高级三大部分&#xff0c;教程是对VBA的系统讲解&#…

关于测试工程师在性能测试工具jmeter的熟悉和精通

经过一周的jmeter接口编写&#xff0c;不不不&#xff0c;是一年1-2次的jmeterd 使用&#xff0c;每次都是新的发现&#xff0c;新的起点&#xff01;&#xff01; 去年10月学习过的东西&#xff0c;现在还是不记得当时怎么这么聪明&#xff0c;于是&#xff0c;每次都是0基础…

笔试训练,牛客.合唱团牛客.kannan与高音牛客.拜访(BFS)牛客.买卖股票的最好时机(四)

目录 牛客.合唱团 牛客.kannan与高音 牛客.拜访&#xff08;BFS&#xff09; 牛客.买卖股票的最好时机(四) 牛客.合唱团 dp[i][j]:从1到i,中挑选最大乘积是多少&#xff0c;但是我们会发现状态转移方程推不出来&#xff0c;我们不知道如何推导的任意两个人&#xff0c; 从[…