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


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表示成功
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

北国无红豆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值