FreeRTOS - 信号量

news2024/10/19 14:30:41

在学习FreeRTOS过程中,结合韦东山-FreeRTOS手册和视频、野火-FreeRTOS内核实现与应用开发、及网上查找的其他资源,整理了该篇文章。如有内容理解不正确之处,欢迎大家指出,共同进步。

队列用于传输数据:在任务之间、任务和中断之间。

消息队列用于传输多个数据,但是有时候我们只需要传递状态,这个状态值需要用一个数值表示。

信号量就是特殊的队列。队列里使用环形缓冲区存放数据,信号量里只记录计数值。

信号量是一种实现任务间通信的机制,可以实现任务之间同步或临界资源的互斥访问,常用于协助一组相互竞争的任务来访问临界资源。

1. 信号量的特性

1.1 信号量的常规操作:

信号量这个名字很恰当:

  • 信号:起通知作用
  • 量:还可以用来表示资源的数量
    • 当"量"没有限制时,它就是"计数型信号量"(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。

1.2 信号量和队列的对比

1.3 信号量
1.3.1 二值信号量

二值信号量既可以用于临界资源访问也可以用于同步功能。

二值信号量和互斥信号量(以下使用互斥量表示互斥信号量)非常相似,但是有一些细 微差别:

  • 互斥量有优先级继承机制,二值信号量则没有这个机制。
  • 这使得二值信号量更偏 向应用于同步功能(任务与任务间的同步或任务和中断间同步),而互斥量更偏向应用于 临界资源的访问。

二值信号量可以被认为是长度为 1 的队列,因此这个队列只能为空或满(因此称为二 值),我们在运用的时候只需要知道队列中是否有消息即可,而无需关注消息是什么。

1.3.2 如何使用二进制信号量来同步任务之间的操作?

用作同步时,信号量在创建后应被置为0,任务 1 获取信号量而进入阻塞,任务 2 在某种条件发生后,释放信号量,于是任务 1 获得信号量得以进入就绪态,如果任务 1 的优先级是最高的,那么就会立即切换任务,从而达到了两个任务间的同步。同样的,在中断服务函数中释放信号量,任务 1 也会得到信号量,从而达到任务与中断间的同步。

1.3.3 计数型信号量

二值信号量可以被认为是长度为 1 的队列,而计数信号量则可以被认为长度大于 1 的队列,信号量使用者依然不必关心存储在队列中的消息,只需关心队列是否有消息即可。

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

  • 计数:
    • 事件产生时释放"give"一个信号量,让计数值加1;处理事件时要先获取"take"一个信号量,就是获得信号量,让计数值减1。
    • 信号量的计数值表示还有多少个事件没被处理。
  • 资源管理:
    • 要想访问资源需要先"take"信号量,让计数值减1;用完资源后"give"信号量,让计数值加1。 信号量的"give"、"take"双方并不需要相同,可以用于生产者-消费者场合。
    • 信号量的计数值表示系统中可用的资源数目。
    • 任务必须先获取到信号量才能获取资源访问权,当信号量的计数值为零时表示系统 没有可用的资源,但是要注意,在使用完资源的时候必须归还信号量,否则当计数值为 0 的时候任务就无法访问该资源了。
1.3.4 互斥信号量

互斥信号量其实是特殊的二值信号量,由于其特有的优先级继承机制从而使它更适用 于简单互锁,也就是保护临界资源 。

用作互斥时,信号量创建后可用信号量个数应该是满的,任务在需要使用临界资源时, (临界资源是指任何时刻只能被一个任务访问的资源),先获取互斥信号量,使其变空, 这样其他任务需要使用临界资源时就会因为无法获取信号量而进入阻塞,从而保证了临界 资源的安全。

1.3.5 递归信号量

对于已经获取递归互斥量的 任务可以重复获取该递归互斥量,该任务拥有递归信号量的所有权。任务成功获取几次递 归互斥量,就要返还几次,在此之前递归互斥量都处于无效状态,其他任务无法获取,只 有持有递归信号量的任务才能获取与释放。

1.4 计数型和二进制信号量
1.4.1 计数型和二进制信号量的区别

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

1.4.2 二值信号量运作机制

1.4.3 计数信号量运作机制

1.5 释放信号量和获取信号量流程

释放信号量:give(要么成功,要么失败,不可能休眠)

  1. 关中断;
  2. count++;
  3. xTasksWaitingToReceive中是否有任务在等待信号量,如果有,将其唤醒
    • xTasksWaitingToReceive;中的第 1 个任务移除
    • 将其从 DelayList——> ReadList 中

获取信号量:Take

  1. 关中断;
  2. 判断 count > 0 ?
    • 否:
      • 返回 ERR(不等待)
      • 休眠
        • 放入xTasksWaitingToReceive
        • ReadList ——> DelayList中
  3. 是(count > 0):本来count > 0;休眠被唤醒(有任务释放了)
    • count --;
    • 有任务在等待释放信号量,唤醒它:
      • xTasksWaitingToSend移除
      • DelayList ——>ReadList
    • return ok;

2. 信号量函数

2.1 信号量控制块

信号量控制块结构体与消息队列结构体是一模一样的,只不过结构体中某些成员变量代表的含义不 一样而已

  • uxMessagesWaiting:
    • 用于消息队列: 用来记录当前消息队列的消息个数;
    • 用于信号量:这个值就表示有效信号量个数,
      • 二值信号量、互斥信号量:这个值是 1 则表示有可用信号量,如果 是 0 则表示没有可用信号量。
      • 计数信号量:这个值表示可用的信号量个数,在创建计数信号量的时候会被初始化一个可用信号量个数 uxInitialCount,最大不允许超过创建信号量的初始 值 uxMaxCount。
  • uxLength :
    • 用于消息队列: 表示队列的长度, 也就是能存放多少消息 ;
    • 用于信号量: 表示最大的信号量可用个数
      • 二值信号量、互斥信号量: uxLength 最大为 1,因为信号量要么是 有效的,要么是无效的。
      • 计数信号量: 这个值表示最大的信号量个数,在创建计数信号量的时候将 由用户指定这个值 uxMaxCount。
  • uxItemSize :
    • 用于消息队列:表示单个消息的大小;
    • 用于信号量无需存储空间,为 0 即可
2.2 创建二值信号量 xSemaphoreCreateBinary()
// 二进制型信号量 :初始值为0 
g_xSemTicks = xSemaphoreCreateBinary();
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define xSemaphoreCreateBinary() \
        xQueueGenericCreate( ( UBaseType_t ) 1, \
                            semSEMAPHORE_QUEUE_ITEM_LENGTH, \
                            queueQUEUE_TYPE_BINARY_SEMAPHORE )
#endif

创建函数xQueueGenericCreate也就是消息队列创建使用的函数,但参数不一样。

QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength, 
                                  const UBaseType_t uxItemSize, 
                                  const uint8_t ucQueueType )

  • 创建一个没有消息存储空间的队列,信号量用什么表示?
  • 其实二值信号量的释放和获取都是通过操作队列结控制块构体成员 uxMessageWaiting 来实现的, 它表示信号量中当前可用的信号量个数。在信号量创建之后,变量 uxMessageWaiting 的值 为 0,这说明当前信号量处于无效状态,此时的信号量是无法被获取的,在获取信号之前, 应先释放一个信号量。

2.3 创建计数信号量 xSemaphoreCreateCounting()
// 计数型信号量 :最大值为3,初始值为2 
g_xSemTicks = xSemaphoreCreateCounting(3, 2);
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
	#define xSemaphoreCreateCounting( uxMaxCount, uxInitialCount )\
            xQueueCreateCountingSemaphore( ( uxMaxCount ), \
                                          ( uxInitialCount ) )
#endif
  • uxMaxCount: 计数信号量的最大值,当达到这个值的时候,信号量不能再被释放。
  • uxInitialCount: 创建计数信号量的初始值。

#if( ( configUSE_COUNTING_SEMAPHORES == 1 ) && 
   ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )

QueueHandle_t xQueueCreateCountingSemaphore( const UBaseType_t uxMaxCount, 
                                            const UBaseType_t uxInitialCount )
{
QueueHandle_t xHandle;

    configASSERT( uxMaxCount != 0 );
    configASSERT( uxInitialCount <= uxMaxCount );

    xHandle = xQueueGenericCreate( uxMaxCount, 
                                  queueSEMAPHORE_QUEUE_ITEM_LENGTH, 
                                  queueQUEUE_TYPE_COUNTING_SEMAPHORE );

    if( xHandle != NULL )
    {
        ( ( Queue_t * ) xHandle )->uxMessagesWaiting = uxInitialCount;

        traceCREATE_COUNTING_SEMAPHORE();
    }else{
        traceCREATE_COUNTING_SEMAPHORE_FAILED();
    }

    return xHandle;
}

#endif

如果我们创建一个最大计数值uxMaxCount为 5,并且默认有效的可用信号量个数uxInitialCount为 5 的计数信号量:

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

删除信号量过程其实就是删除消息队列过程,因为信号量其实就是消息队列,只不过是无法存储消息的队列而已。

#define vSemaphoreDelete( xSemaphore ) \
        vQueueDelete( ( QueueHandle_t ) ( xSemaphore ) )
2.5 信号量释放函数

SemaphoreHandle_t g_xSemTicks; // 信号量

xSemaphoreGive(g_xSemTicks);

xSemaphoreTake(g_xSemTicks, portMAX_DELAY);
2.5.1 xSemaphoreGive()(任务)

释放的信号量对象必须是已 经被创建的,可以用于二值信号量、计数信号量、互斥量的释放,但不能释放由函数 xSemaphoreCreateRecursiveMutex()创建的递归互斥量。此外该函数不能在中断中使用。

#define xSemaphoreGive( xSemaphore )\
        xQueueGenericSend( ( QueueHandle_t )( xSemaphore ),\
                          NULL,\
                          semGIVE_BLOCK_TIME,\
                          queueSEND_TO_BACK )
#define semGIVE_BLOCK_TIME	( ( TickType_t ) 0U )
BaseType_t xQueueGenericSend( QueueHandle_t xQueue, 
                             const void * const pvItemToQueue, 
                             TickType_t xTicksToWait, 
                             const BaseType_t xCopyPosition )

释放信号量实际上是一次入队操作,并且是不允许入队阻塞,因 为阻塞时间为 semGIVE_BLOCK_TIME,该宏的值为 0。

如果信号量未满,控制块结构体成员 uxMessageWaiting 就会加 1,然后判断是否有阻塞的任务,如果有的话就会恢复阻塞的任务,然后返回成功信息(pdPASS);如果信号量已满,则返回错误代码(err_QUEUE_FULL),

2.5.2 xSemaphoreGiveFromISR()(中断)
#define xSemaphoreGiveFromISR(xSemaphore, pxHigherPriorityTaskWoken)\
    xQueueGiveFromISR( ( QueueHandle_t ) ( xSemaphore ),\
                      ( pxHigherPriorityTaskWoken ) )


如果可用信号量未满,控制块结构体成员 uxMessageWaiting 就会加 1,然后判断是否有阻塞的任务,如果有的话就会恢复阻塞的任务,然后返回成功信息(pdPASS),如果恢复的任务优先级比当前任务优先级高,那么在退出中断要进行任务切换一次;如果信号量满,则返回错误代码(err_QUEUE_FULL),表示信号量满。

一个或者多个任务有可能阻塞在同一个信号量上,调用函数 xSemaphoreGiveFromISR()可能会唤醒阻塞在该信号量上的任务,如果被唤醒的任务的优先级大于当前任务的优先级,那么形参 pxHigherPriorityTaskWoken 就会被设置为 pdTRUE,然后在中断退出前执行一次 上下文切换。

2.6 信号量获取函数
2.6.1 xSemaphoreTake()(任务)

用于获取信号量,不带中断保护。获取的信号量对象可以是二值信号量、计数信号量和互斥量,但是递归互斥量并不能使用这个 API 函数获取。

#define xSemaphoreTake( xSemaphore, xBlockTime )\
        xQueueSemaphoreTake( ( xSemaphore ), \
                             ( xBlockTime ) )
BaseType_t xQueueSemaphoreTake( QueueHandle_t xQueue, 
                               TickType_t xTicksToWait )
{
BaseType_t xEntryTimeSet = pdFALSE;
TimeOut_t xTimeOut;
Queue_t * const pxQueue = ( Queue_t * ) xQueue;

	for( ;; )
	{
		taskENTER_CRITICAL();
		{
			/* 信号量数= uxMessagesWaiting*/
			const UBaseType_t uxSemaphoreCount = pxQueue->uxMessagesWaiting;

			/* 看看是否可以获取信号量 */
			if( uxSemaphoreCount > ( UBaseType_t ) 0 )
			{
				traceQUEUE_RECEIVE( pxQueue );

				/* 可以获取信号量,信号量值减一 */
				pxQueue->uxMessagesWaiting = uxSemaphoreCount - ( UBaseType_t ) 1;

				#if ( configUSE_MUTEXES == 1 )
				{ /* 如果是互斥量的话 */
					if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
					{
						/* 当前互斥量 > 0,所以当前任务可以获得互斥量,
                           因此当前任务成为互斥量的持有者 */
						pxQueue->pxMutexHolder = ( int8_t * ) pvTaskIncrementMutexHeldCount(); /*lint !e961 Cast is not redundant as TaskHandle_t is a typedef. */
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}
				}
				#endif /* configUSE_MUTEXES */

				/* 看是否有任务在等待 唤醒 */
				if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
				{
                    /* 将事件链表中第一个任务(优先级最高)移除,从阻塞链表中移除,
                       放入就绪链表*/
					if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
					{
                        /* 若恢复任务的优先级大于当前任务,则进行任务切换*/
						queueYIELD_IF_USING_PREEMPTION();
					}else{
						mtCOVERAGE_TEST_MARKER();
					}
				}else{
					mtCOVERAGE_TEST_MARKER();
				}

				taskEXIT_CRITICAL();
				return pdPASS;
			}
			else // 信号量数 < 0,进入阻塞
			{	
				if( xTicksToWait == ( TickType_t ) 0 )/* 不等待 */
				{
					
					#if( configUSE_MUTEXES == 1 )
					{
						configASSERT( xInheritanceOccurred == pdFALSE );
					}
					#endif /* configUSE_MUTEXES */

					/* 直接返回 */
					taskEXIT_CRITICAL();
					traceQUEUE_RECEIVE_FAILED( pxQueue );
					return errQUEUE_EMPTY;
				}
				else if( xEntryTimeSet == pdFALSE )
				{
					/* 要等待,初始化阻塞超时结构体变量,初始化进入阻塞的时间 xTickCount
                    和溢出次数 xNumOfOverflows */
					vTaskInternalSetTimeOutState( &xTimeOut );
					xEntryTimeSet = pdTRUE;
				}else{
					/* Entry time was already set. */
					mtCOVERAGE_TEST_MARKER();
				}
			}
		}
		taskEXIT_CRITICAL();

		/* Interrupts and other tasks can give to and take from the semaphore
		now the critical section has been exited. */

		vTaskSuspendAll();
		prvLockQueue( pxQueue );

		/* 看阻塞时间是否超时 */
		if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )
		{ /* 没超时 */
			
			if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
			{/* 信号量数为 0 */
				traceBLOCKING_ON_QUEUE_RECEIVE( pxQueue );

				#if ( configUSE_MUTEXES == 1 )
				{
					if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
					{
						taskENTER_CRITICAL();
						{   /* 互斥量无效(不可获取,被别的任务持有),
                            判断互斥量的持有者的优先级是否小于当前任务的优先级,
                            若是,则进行优先级继承 */
							xInheritanceOccurred = xTaskPriorityInherit( ( void * ) pxQueue->pxMutexHolder );
						}
						taskEXIT_CRITICAL();
					}else{
						mtCOVERAGE_TEST_MARKER();
					}
				}
				#endif
                /* 将其放入链表xTasksWaitingToReceive */
				vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );
				prvUnlockQueue( pxQueue );
				if( xTaskResumeAll() == pdFALSE )
				{
					portYIELD_WITHIN_API();
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
			else /* 信号量不为 0 */
			{
				/* 没超时,信号量不为0,尝试再次获取信号量 */
				prvUnlockQueue( pxQueue );
				( void ) xTaskResumeAll();
			}
		}
		else
		{
			/* 超时 */
			prvUnlockQueue( pxQueue );
			( void ) xTaskResumeAll();

			/* 信号量数为 0 ,且 超时 */
			if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
			{
				#if ( configUSE_MUTEXES == 1 )
				{
					/* 优先级继承*/
					if( xInheritanceOccurred != pdFALSE )
					{
						taskENTER_CRITICAL();
						{
							UBaseType_t uxHighestWaitingPriority;

							/* This task blocking on the mutex caused another
							task to inherit this task's priority.  Now this task
							has timed out the priority should be disinherited
							again, but only as low as the next highest priority
							task that is waiting for the same mutex. */
							uxHighestWaitingPriority = prvGetDisinheritPriorityAfterTimeout( pxQueue );
							vTaskPriorityDisinheritAfterTimeout( ( void * ) pxQueue->pxMutexHolder, uxHighestWaitingPriority );
						}
						taskEXIT_CRITICAL();
					}
				}
				#endif /* configUSE_MUTEXES */

				traceQUEUE_RECEIVE_FAILED( pxQueue );
				return errQUEUE_EMPTY;
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
	}
}

2.6.2 xSemaphoreTakeFromISR()(中断)

用于获取信号量,是 一个不带阻塞机制获取信号量的函数,获取对象必须由是已经创建的信号量,信号量类型可以是二值信号量和计数信号量,它与 xSemaphoreTake()函数不同,它不能用于获取互斥 量,因为互斥量不可以在中断中使用,并且互斥量特有的优先级继承机制只能在任务中起 作用,而在中断中毫无意义。

3. 信号量实验-控制车辆运行

3.1 计数型信号量实验

有3个car,但只有两张票,前2个car获取信号量( take)先跑到右边后,释放信号量(give)后,第3个car运行

static uint8_t *g_framebuffer;

SemaphoreHandle_t g_xSemTicks;

static void CarTask(void *params)
{
	/* 获取信号量:获取到票后才能进城 */
	xSemaphoreTake(g_xSemTicks, portMAX_DELAY);
	
	while(1)
	{	
            vTaskDelay(50);
            if (pcar->x == g_xres - CAR_LENGTH)
            {
                /* 释放信号量 */
                xSemaphoreGive(g_xSemTicks);
                vTaskDelete(NULL);
            }
	}
}


void car_game(void)
{
	//int x;
	int i,j;
	g_framebuffer = LCD_GetFrameBuffer(&g_xres, &g_yres, &g_bpp);
    draw_init();
    draw_end();
	
	/* 计数型信号量 :最大值为3,初始值为2 */
	g_xSemTicks = xSemaphoreCreateCounting(3, 2);
	
    xTaskCreate(CarTask, "car1", 128, &g_cars[0], osPriorityNormal, NULL);
    xTaskCreate(CarTask, "car2", 128, &g_cars[1], osPriorityNormal, NULL);
    xTaskCreate(CarTask, "car3", 128, &g_cars[2], osPriorityNormal, NULL);	
	
}


3.2 二进制信号量
  • 对于二进制信号量,它的最大值是1,开始时第 1 个car获取信号量(take);
  • 第 1 个car跑到最右边后,释放信号量(give);
  • 第 2 个car 获取信号量(take),跑到最右边后,释放信号量(give);
  • 第 3 个car 获取信号量(take),跑到最右边后,释放信号量(give);
static void CarTask(void *params)
{
	/* 获取信号量:获取到票后才能进城 */
	xSemaphoreTake(g_xSemTicks, portMAX_DELAY);
	
	while(1)
	{
				vTaskDelay(50);
				if (pcar->x == g_xres - CAR_LENGTH)
				{
					/* 释放信号量 */
					xSemaphoreGive(g_xSemTicks);
					vTaskDelete(NULL);
				}	
	}
}


void car_game(void)
{
	//int x;
	int i,j;
	g_framebuffer = LCD_GetFrameBuffer(&g_xres, &g_yres, &g_bpp);
    draw_init();
    draw_end();
	
	/* 计数型信号量 :最大值为3,初始值为2 
	g_xSemTicks = xSemaphoreCreateCounting(3, 2);
	*/
	/* 二进制型信号量 :初始值为0 	*/
	g_xSemTicks = xSemaphoreCreateBinary();
	xSemaphoreGive(g_xSemTicks);/* 对于二进制信号量,它的最大值是1,后面两次give无效*/
	xSemaphoreGive(g_xSemTicks);
	xSemaphoreGive(g_xSemTicks);	

    xTaskCreate(CarTask, "car1", 128, &g_cars[0], osPriorityNormal, NULL);
    xTaskCreate(CarTask, "car2", 128, &g_cars[1], osPriorityNormal, NULL);
    xTaskCreate(CarTask, "car3", 128, &g_cars[2], osPriorityNormal, NULL);	
	
}

3.3 信号量-优先级反转

car1:优先级为 0;

car2:优先级为 2;

car3:优先级为 3;

car1:获取了信号量(take),释放信号量(give)

car2:延迟1s(即进入阻塞)后再运行,无获取信号量(take),无释放信号量(give)

car3:延迟2s(即进入阻塞),获取信号量(take),释放信号量(give)

car1先获得信号量,所以car1先运行,跑了1s后,car2运行,过了一会car3任务启动,但它无法获得信号量而阻塞。当car2跑到最右边后,car1继续运行,跑到最右边后,释放信号量(give),car3获得信号量,跑到最右边,释放信号量

static void Car1Task(void *params)
{
	
	/* 获取信号量:获取到票后才能进城 */
	xSemaphoreTake(g_xSemTicks, portMAX_DELAY);
	
	while(1)
	{
				vTaskDelay(50);
				if (pcar->x == g_xres - CAR_LENGTH)
				{
					/* 释放信号量 */
					xSemaphoreGive(g_xSemTicks);
					vTaskDelete(NULL);
				}	
	}
}

static void Car2Task(void *params)
{
	/* 故意延迟1000,即1s后再让它运行 */
	vTaskDelay(1000);
	
	/* 获取信号量:获取到票后才能进城 */
	//xSemaphoreTake(g_xSemTicks, portMAX_DELAY);
	
	while(1)
	{
				//vTaskDelay(50);
				mdelay(50);// 不会进入阻塞状态
				if (pcar->x == g_xres - CAR_LENGTH)
				{
					/* 释放信号量 */
					//xSemaphoreGive(g_xSemTicks);
					vTaskDelete(NULL);
				}	
	}
}
static void Car3Task(void *params)
{
	
	/* 延迟2000,即2s*/
	vTaskDelay(2000);
	
	/* 获取信号量:获取到票后才能进城 */
	xSemaphoreTake(g_xSemTicks, portMAX_DELAY);
	
	while(1)
	{
				//vTaskDelay(50);
				mdelay(50);// 不会进入阻塞状态
				if (pcar->x == g_xres - CAR_LENGTH)
				{
					/* 释放信号量 */
					xSemaphoreGive(g_xSemTicks);
					vTaskDelete(NULL);
				}	
	}
}

void car_game(void)
{
	//int x;
	int i,j;
	g_framebuffer = LCD_GetFrameBuffer(&g_xres, &g_yres, &g_bpp);
    draw_init();
    draw_end();
	
	/* 计数型信号量 :最大值为3,初始值为2 
	g_xSemTicks = xSemaphoreCreateCounting(3, 2);
	*/
	/* 二进制型信号量 :初始值为0 	*/
	g_xSemTicks = xSemaphoreCreateBinary();
	xSemaphoreGive(g_xSemTicks);/* 对于二进制信号量,它的最大值是1,后面两次give无效*/
	xSemaphoreGive(g_xSemTicks);
	xSemaphoreGive(g_xSemTicks);	

    xTaskCreate(Car1Task, "car1", 128, &g_cars[0], osPriorityNormal, NULL);
    xTaskCreate(Car2Task, "car2", 128, &g_cars[1], osPriorityNormal+2, NULL);
    xTaskCreate(Car3Task, "car3", 128, &g_cars[2], osPriorityNormal+3, NULL);	
	
}

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

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

相关文章

冻干咖啡市场洞察:销售额占比背后的消费密码

冻干咖啡销售额前十占比分析 一、概述 本报告基于从淘宝商品搜索接口和淘宝精确月销量接口中提取的数据&#xff0c;分析了前十个品牌在销售额上的占比情况。分析涵盖了销售额和占比的数据&#xff0c;为决策提供了依据。&#xff08;数据获取时间&#xff1a;2024.09.20&…

音乐播放器项目专栏介绍​

1.简介 本专栏使用Qt QWidget作为显示界面&#xff0c;你将会学习到以下内容&#xff1a; 1.大量ui美化的实例。 2.各种复杂ui布局。 3.常见显示效果实现。 4.大量QSS实例。 5.Qt音频播放&#xff0c;音乐歌词文件加载&#xff0c;展示。 6.播放器界面换肤。 相信学习了本专栏…

C++【内存管理】(超详细讲解C++内存管理以及new与delete的使用和原理)

文章目录 1.C/C内存分布2.C语言中动态内存管理方式3.C内存管理方式3.1 new/delete操作内置类型3. 2new/delete操作自定义类型 4. operator new与operator delete函数&#xff08;重点&#xff09;5. new和delete的实现原理5.1 内置类型5.2 自定义类型5.2.1 自定义类型调用new[]…

【机器学习】特征降维|低方差过滤|主成分分析PCA|相关系数法|皮尔逊相关系数|斯皮尔曼相关系数

特征降维 特征降维 为什么要进行特征降维? 特征对训练模型非常重要,当用于训练的数据集包涵一些不重要的特征时,可能会导致模型泛化性能不加 eg&#xff1a;某些特征的取值较为接近&#xff0c;其包含的信息较少eg&#xff1a;希望特征独立存在对预测产生影响&#xff0c;两…

Unity使用TriangleNet参考

TriangleNet下载如下&#xff1a; TriangleNet 效果如下&#xff1a; 代码参考如下&#xff1a; using System.Collections.Generic; using UnityEngine; using TriangleNet.Geometry;public class TestTriangleNet : MonoBehaviour {[SerializeField]Material material;voi…

2024下半年,国内大模型六小虎最新发展情况怎么样了?

最近大模型圈有点冷,ChatGPT访问量下降,英伟达的股价都跟着跌,更是有人开始唱衰大模型。这感觉就像经历了一场盛夏酷暑后的骤然降温,资本市场也开始理性回归。但与此同时,国内的"六小虎"却展现出另一番景象——热火朝天!这真是冰火两重天啊! 在这看似矛盾的局…

CNStream流处理多路并发Pipeline框架相关问题整理:Pipeline整体流程、数据传输、多路并发

目录 1 CNStream之前博客汇总 1.1 Pipeline中的EventBus 1.2 Pipeline中的内存池 1.3 Pipeline中的视频解码流程分析 1.4 Pipeline中的视频编码流程分析 1.5 Pipeline中的反射机制 1.6 Pipeline中的单例模式代码 1.7 怎么将CNStream适配到NVIDIA Jetson Orin 2 构建Pi…

EI收录检索报告是什么样的?怎么出具?一文了解!

一、EI检索报告是什么 EI(Engineering Village)数据库是全球最全面的工程检索二次文献数据库&#xff0c;它收录了7,000,000多篇论文的参考文献和摘要。这些论文出自5,000多种工程类期刊、会议论文集和技术报告。EI收录的文献涵盖了所有的工程领域&#xff0c;其中大约22%为会…

聊聊零基础如何开始学习鸿蒙开发技术

鸿蒙系统是一款分布式操作系统&#xff0c;其适用范围非常广泛&#xff0c;从智能手机到家用电器&#xff0c;再到工业设备&#xff0c;都能找到应用场景。特别是在智能家居领域&#xff0c;鸿蒙系统可以实现不同设备之间的无缝连接和协同工作&#xff0c;提供更加智能和便利的…

建筑工程管理软件推荐,2024年最佳选择

建筑工程管理软件助力项目全周期管理&#xff0c;包括规划、监控、资源成本控制等。类型多样&#xff0c;选购需考虑需求匹配、便捷性、集成能力、灵活性和安全性。软件优化流程、提高效率、监控进展、优化资源配置、提升协作水平。 一、建筑工程管理软件到底是什么&#xff1f…

浮点数二进制制科学计数法理解

Owed by: 春夜喜雨 http://blog.csdn.net/chunyexiyu 1. 引言 对于浮点数&#xff0c;主要是单精度-float、双精度-double两种类型。 对于浮点类型&#xff0c;我们知道其采用科学计数法&#xff0c;准确来说应该是二进制科学计数法。 为什么准确说是是二进制科学计数法&…

机器学习笔记20241017

文章目录 torchvisiondataloadernn.module卷积非线性激活模型选择训练误差泛化误差 正则化权重衰退的基本概念数学表示权重衰退的效果物理解释 数值稳定性&#xff08;Gradient Vanishing&#xff09;梯度消失原因解决方法 梯度爆炸&#xff08;Gradient Explosion&#xff09;…

linux anconda下基础环境配置(torch、opencv等)

1、torch安装&#xff08;GPU&#xff09; 下载链接&#xff1a;https://pytorch.org/ 根据配置下载对应版本&#xff0c;CUDA11.4 可用11.3下的安装包 conda install pytorch1.12.0 torchvision0.13.0 torchaudio0.12.0 cudatoolkit11.3 -c pytorch错误解决&#xff1a; 安…

好用的python相关的AI工具Bito介绍

插件名称&#xff1a;Bito 好用的python相关的AI工具Bito介绍 step 1:点插件step 2&#xff1a;搜索bito并安装step3 &#xff1a;需要登录&#xff0c;要有真实邮箱&#xff0c;按步骤走就行&#xff0c;完后就可以使用 step 1:点插件 step 2&#xff1a;搜索bito并安装 step3…

基于PHP考研互助系统【附源码】

基于PHP考研互助系统 效果如下&#xff1a; 系统首页界面 用户注册界面 考研论坛页面 每日打卡页面 管理员登录主页面 管理员主界面 用户管理界面 备考经验界面 研究背景 近些年&#xff0c;随着中国经济发展&#xff0c;人民的生活质量逐渐提高&#xff0c;对网络的依赖性越…

Q2=10 and Q2=1--PLB(Fig.4)

&#xff08;个人学习笔记&#xff0c;仅供参考&#xff09; import numpy as np from scipy.special import kv, erfc from scipy.integrate import dblquad import matplotlib.pyplot as plt import scipy.integrate as spi# Constants w 0.6198 g0_sq 21.5989 rho 0.782…

【UML】一个UML学习的还不错的几个帖子

https://segmentfault.com/a/1190000042775634 寂然解读设计模式 - UML类图&类的六大关系-阿里云开发者社区

【工具变量】上市公司企业广告支出数据(2007-2023年)

一、测算方式&#xff1a;具体而言&#xff0c;参照 Lu 等&#xff08;2022&#xff09;的研究&#xff0c;本文通过上市公司财务报表附注获取每家上市公司每年销售费用明细项目&#xff0c;筛选出广告费、广告宣传费、广告推广费、广告策划费、广告展览费等与广告支出相关的项…

CodeActAgent :Executable Code Actions Elicit Better LLM Agents解读

论文地址 https://arxiv.org/pdf/2402.01030.pdf 项目地址 https://github.com/svjack/CodeActAgent-Gradio/blob/main/README.md 代码智能体的优势 选择代码智能体有以下几个关键原因&#xff0c;它们相较于使用类似JSON的字典输出具有显著优势&#xff1a; 1. 代码的高…

软件分享|Microsoft To Do 任务管理

Microsoft To Do 是一款流行的待办事项和任务管理应用&#xff0c;它帮助用户通过创建任务、清单和安排日程来提高生产力。以下是 Microsoft To Do 的一些关键特性&#xff1a; &#x1f511; 核心特性 任务和清单&#xff1a;创建任务并将其组织到不同的清单中&#xff0c;…