基于STM32F103的FreeRTOS系列(十一)·信号量·二值信号量与计数信号量详细使用以及移植教程

news2024/11/25 2:43:16

目录

1.  信号量简介

1.1  同步和互斥

1.1.1  同步

1.1.2  互斥

1.1.3  总结

1.2  分类

1.2.1  二值信号量

1.2.2  计数信号量

1.2.3  互斥信号量

1.2.4  递归信号量

2.  信号量控制块

3.  常用信号量API函数

3.1  创建信号量函数

3.1.1  创建二值信号量  xSemaphoreCreateBinary() 

3.1.2  创建计数信号量  xSemaphoreCreateCounting()

3.1.3  信号量删除函数  vSemaphoreDelete()

3.2  信号量释放函数

3.2.1  普通释放函数  xSemaphoreGive()

3.2.2  中断释放函数  xSemaphoreGiveFromISR()

3.3  信号量获取函数

3.3.1  普通获取函数  xSemaphoreTake()

3.3.2  中断获取函数  xSemaphoreTakeFromISR()

4.  代码编写

4.1  二值信号量

4.1.1  应用任务创建

任务1函数:

接收任务函数:

发送任务函数:

相关宏定义:

4.1.2  开始任务的创建

4.1.3  主函数

4.1.4  完整代码

4.2  计数信号量

4.2.1  应用任务创建

任务1函数:

接收任务函数:

发送任务函数:

相关宏定义:

4.1.2  开始任务的创建

4.1.3  主函数

4.1.4  完整代码


1.  信号量简介

        信号量(Semaphore)是一种实现任务间通信的机制,可以实现任务之间同步临界资源的互斥访问,常用于协助一组相互竞争的任务来访问临界资源。在多任务系统中,各任务之间需要同步或互斥实现临界资源的保护,信号量功能可以为用户提供这方面的支持。

1.1  同步和互斥

1.1.1  同步

定义:同步是指在执行任务时,通过某种机制来协调不同任务的行为,确保它们以正确的顺序和条件进行。简单来说,就是使得一个任务的执行进度能够与另一个任务的状态保持一致。

例子:通俗点解释,如果你去包子店想买包子,但店里暂时没有包子,你就需要等待店主做包子。这种等待就是一种同步行为,确保你在包子准备好之前不能买到包子。

又例如,你需要进行传感器数据采集,一个采集数据的传感器任务,一个处理数据的任务,你想要处理数据,则需要等待传感器先去采用数据,这种等待的行为就称为同步行为。

1.1.2  互斥

定义:互斥是指确保在任何时刻只有一个任务能够访问共享资源。它防止了多个任务同时访问共享资源,从而避免数据冲突或资源争用的问题。

例子:厕所(临界资源或者说是共享资源),人上厕所(执行的任务)
        为了保证资源的合理使用,互斥机制确保在任何时刻只有一个人可以使用厕所。当一个人正在使用厕所时,其他人必须等待。

1.1.3  总结

        同步是一种更为复杂的互斥,而互斥是一种特殊的同步。

例子:还是以上厕所为例。

           厕所(临界资源或者说是共享资源),甲、乙、丙、丁四个人上厕所(执行的任务)

同步:相当于我给你规定了上厕所的顺序,甲、乙、丙、丁(任务)排队上厕所(共享资源),是有顺序的,例如甲执行完才是乙,乙执行完才是丙,丙执行完才是丁,你需要按照我给你排的顺序上厕所。

互斥:也是甲、乙、丙、丁(任务)排队上厕所(共享资源),不过并没有顺序,互斥就保证你们四个人就只能一个人到厕所里。但是你们谁先上我不管,我就保证你们在同一个时间内,只有一个人在厕所里(使用共享资源)。

1.2  分类

1.2.1  二值信号量

定义:二值信号量是一种特殊的信号量,其内部状态只有两种:被释放(释放状态)或被占用(占用状态)。二值信号量既可以用于临界资源访问,也可用于同步功能。

 

特点:只有两种状态(0 或 1),类似于一个长度为1的队列。

用途:同步,可以用来实现任务间的事件通知。

           互斥,用于保护临界区,但不支持优先级继承机制。

示例:一个任务用二值信号量通知另一个任务某个事件的发生。

xSemaphoreGive(xBinarySemaphore); // 释放信号量
xSemaphoreTake(xBinarySemaphore, portMAX_DELAY); // 等待信号量

1.2.2  计数信号量

定义:计数信号量是一种信号量,其内部状态可以是任意非负整数。 它可以被认为长度大于1的队列,信号量使用者依然不必关心存储在队列中的消息,只需要关心队列是否有消息即可。

 特点:可以有多个状态值,表示可用的资源数量。

用途:资源计数,用于表示可用资源的数量。例如,有限数量的资源池。

           任务同步,用于控制任务之间的协调。

示例:一个任务使用计数信号量来限制对共享资源的访问,例如限制最大并发线程数。

xSemaphoreGive(xCountingSemaphore); // 增加信号量计数
xSemaphoreTake(xCountingSemaphore, portMAX_DELAY); // 减少信号量计数

1.2.3  互斥信号量

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

特点:当一个低优先级的任务持有互斥信号量时,如果一个高优先级的任务请求该信号量,低优先级任务的优先级会临时提升,以避免优先级反转问题。

用途:专门用于保护临界区和同步任务。

示例:使用互斥信号量来保护一个共享资源,确保在任何时刻只有一个任务可以访问该资源。

xSemaphoreTake(xMutex, portMAX_DELAY); // 获取互斥信号量
// 访问共享资源
xSemaphoreGive(xMutex); // 释放互斥信号量

1.2.4  递归信号量

定义:递归信号量允许同一任务多次获取相同的信号量,而不需要释放每次获取时都释放。递归信号量的使用场景通常是需要递归调用的任务或函数。

特点:递归获取,同一任务可以多次获取递归信号量,每次获取时信号量计数会增加。

           递归释放,需要和获取次数相同次数的释放才能真正释放信号量,使得其他任务可以获取它。

用途:适用于递归函数或需要在同一任务中多次获取信号量的场景。

示例:一个任务递归调用函数时,使用递归信号量保护临界区。

xSemaphoreTakeRecursive(xRecursiveSemaphore, portMAX_DELAY); // 递归获取信号量
// 递归函数调用
xSemaphoreGiveRecursive(xRecursiveSemaphore); // 递归释放信号量

2.  信号量控制块

/*
 * 定义调度器使用的队列。
 * 项目通过复制而非引用排队。请参见以下链接了解详细原因:http://www.freertos.org/Embedded-RTOS-Queues.html
 */
typedef struct QueueDefinition
{
    int8_t *pcHead;                  /*< 指向队列存储区域的开始位置。 */
    int8_t *pcTail;                  /*< 指向队列存储区域末尾的字节。一次分配比实际需要的多一个字节,以作为标记。 */
    int8_t *pcWriteTo;              /*< 指向存储区域中下一个可写的位置。 */

    union                            /* 使用联合体是为了确保两个互斥的结构成员不会同时出现(避免浪费内存)。 */
    {
        int8_t *pcReadFrom;         /*< 当结构体被用作队列时,指向最后一个读取项目的位置。 */
        UBaseType_t uxRecursiveCallCount; /*< 当结构体用作互斥量时,维护递归获取互斥量的次数。 */
    } u;

    List_t xTasksWaitingToSend;       /*< 阻塞等待向此队列发送项目的任务列表,按优先级排序。 */
    List_t xTasksWaitingToReceive;    /*< 阻塞等待从此队列接收项目的任务列表,按优先级排序。 */

    volatile UBaseType_t uxMessagesWaiting; /*< 当前队列中项目的数量。 */
    UBaseType_t uxLength;             /*< 队列的长度,定义为队列可以容纳的项目数,而不是字节数。 */
    UBaseType_t uxItemSize;           /*< 队列将持有的每个项目的大小。 */

    volatile int8_t cRxLock;          /*< 存储在队列锁定时从队列中接收的项目数量(从队列中移除)。队列未锁定时设置为 queueUNLOCKED。 */
    volatile int8_t cTxLock;          /*< 存储在队列锁定时传输到队列的项目数量(添加到队列中)。队列未锁定时设置为 queueUNLOCKED。 */

    #if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
        uint8_t ucStaticallyAllocated; /*< 如果队列的内存是静态分配的,设置为 pdTRUE,以确保不尝试释放内存。 */
    #endif

    #if ( configUSE_QUEUE_SETS == 1 )
        struct QueueDefinition *pxQueueSetContainer; /*< 如果启用了队列集功能,指向包含此队列的队列集。 */
    #endif

    #if ( configUSE_TRACE_FACILITY == 1 )
        UBaseType_t uxQueueNumber;  /*< 队列编号,用于跟踪功能。 */
        uint8_t ucQueueType;        /*< 队列类型,用于跟踪功能。 */
    #endif

} xQUEUE;

/* 旧的 xQUEUE 名称在上面维护,然后重新定义为新的 Queue_t 名称,以便支持旧的内核调试工具。 */
typedef xQUEUE Queue_t;

3.  常用信号量API函数

3.1  创建信号量函数

3.1.1  创建二值信号量  xSemaphoreCreateBinary() 

#define xSemaphoreCreateBinary() 
    xQueueGenericCreate( ( UBaseType_t ) 1, 
                         semSEMAPHORE_QUEUE_ITEM_LENGTH, 
                         queueQUEUE_TYPE_BINARY_SEMAPHORE )

( UBaseType_t ) 1:这是传递给 xQueueGenericCreate 函数的第一个参数,表示队列(或信号量)可以容纳的最大项数。在创建二进制信号量时,这里设置为 1,因为二进制信号量只需要一个项目来表示“有信号”或“无信号”。

semSEMAPHORE_QUEUE_ITEM_LENGTH:这是第二个参数,指定队列项的长度,这里设置为 0。对于二进制信号量,这个值通常是sizeof(xSemaphoreHandle)或其他相应长度。它定义了队列中每个项的大小。在实际实现中,这个值通常是一个宏,确保与信号量的数据结构对齐。但是在二值信号量,我们不关注它的消息内容是什么,只关心它的里面有没有消息,因此这个值设为0。

queueQUEUE_TYPE_BINARY_SEMAPHORE:这是第三个参数,指定队列的类型。在这里,queueQUEUE_TYPE_BINARY_SEMAPHORE表示这是一个二进制信号量。这个宏在 FreeRTOS 的内部实现中定义了队列的具体类型,帮助系统正确管理不同类型的队列和信号量。

简单来说,就是创建一个没有消息存储空间的队列。

3.1.2  创建计数信号量  xSemaphoreCreateCounting()

函数原型

SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount,

                                                                                  UBaseType_tuxInitialCount);

功能创建一个计数信号量。
参数uxMaxCount计数信号量的最大值,当达到这个值的时候,信号量不能再被释放。
uxInitialCount创建计数信号量的初始值。
返回值如果创建成功则返回一个计数信号量句柄,用于访问创建的计数信号量。如果创建不成功则返回 NULL。

3.1.3  信号量删除函数  vSemaphoreDelete()

函数原型void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );
功能删除一个信号量
参数xSemaphore信号量句柄
返回值

3.2  信号量释放函数

3.2.1  普通释放函数  xSemaphoreGive()

#define xSemaphoreGive( xSemaphore )
        xQQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ),
                           NULL,
                           semGIVE_BLOCK_TIME, 
                           queueSEND_TO_BACK)

3.2.2  中断释放函数  xSemaphoreGiveFromISR()

#define xSemaphoreGiveFromISR( xSemaphore, pxHigherPriorityTaskWoken )

        xQueueGiveFromISR((QueueHandle_t)( xSemaphore ),
                          (pxHigherPriorityTaskWoken ))

3.3  信号量获取函数

3.3.1  普通获取函数  xSemaphoreTake()

函数原型

#define xSemaphoreTake( xSemaphore, xBlockTime)

             xQueueGenericReceive( (QueueHandle_t )(xSemaphore ),

                                                      NULL,

                                                      ( xBlockTime ),

                                                      pdFALSE)

功能获取一个信号量,可以是二值信号量、计数信号量、互斥量。
参数xSemaphore信号量句柄。
xBlockTime等待信号量可用的最大超时时间,单位为tick(即系统节拍周期)如果宏INCLUDE vTaskSuspend定义为1且形参xTicksToWait设置为portMAX_DELAY,则任务将一直阻塞在该信号量上(即没有超时时间)
返回值

获取成功则返回pTRUE,在指定的超时时间中没有获取成功则返回

errQUEUE_EMPTY。

3.3.2  中断获取函数  xSemaphoreTakeFromISR()

函数原型

xSemaphoreTakeFromISR(SemahoreHale_t xmar,

                                            signed BaseType_t pxHigherPriorityTaskWoken)

功能

在中断中获一个信号量(其实很少在中断中获取信号量)。可以是二值信号量、计数信号量。

参数xSemaphore信号量句柄。
pxHigherPriority TaskWoken一个或者多个任务有可能阻塞在同一个信号量上,调用函数SemaphoreTakeFromISRO会唤醒阻塞在该信号量上优先级最高的信号量入队任务,如果被唤醒的任务的优先级大于或者等于被中断的任务的优先级,那么形参pxHigherPriorityTaskWoken就会被设置为pdTRUE.然后在中断退出前执行一次上下文切换,中断退出后则直接返回刚刚被唤醒的高优先级的任务。从FreeRTOS V7.3.0版本开始, pxHigherPriorityTaskWoken是一个可选的参数,可以设置为 NULL。
返回值获取成功则返回pdTRUE,没有获取成功则返回errQUEUE_EMPTY,没有获取成功是因为信号量不可用。

4.  代码编写

4.1  二值信号量

        首先,找到一个工程,这里我们以动态任务为例:

基于STM32F103C8T6的FreeRTOS任务创建·动态任务资源-CSDN文库

4.1.1  应用任务创建

任务1函数:

        实现LED灯的闪烁:

//任务1函数
void led1_task(void *pvParameters)
{
    while(1)
    {
        LED1=0;
        vTaskDelay(200);
        LED1=1;
        vTaskDelay(800);
    }
}
接收任务函数:

        用于接收消息变量,当接受到发送任务函数发送的BinarySem_Handle,串口打印BinarySem_Handle二值信号量获取成功!,并实现led2的翻转:

//接收任务函数
void receive_task(void *pvParameters)
{
   BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
	
	while(1)
    {
        //获取二值信号量 xSemaphore,没获取到则一直等待
		xReturn = xSemaphoreTake(BinarySem_Handle,/* 二值信号量句柄 */
								portMAX_DELAY); /* 等待时间 */
		if(pdTRUE == xReturn)
			printf("BinarySem_Handle二值信号量获取成功!\n\n");
		 LED2=!LED2;
    }
}

        这里的任务不需要再调用vTaskDelay();进行延时阻塞让出CPU的资源,因为在: 

        //获取二值信号量 xSemaphore,没获取到则一直等待
		xReturn = xSemaphoreTake(BinarySem_Handle,/* 二值信号量句柄 */
								portMAX_DELAY); /* 等待时间 */

         portMAX_DELAY表示无限等待,直到有消息可接收,已经进行了阻塞。

发送任务函数:

        用于发送消息,当按键PB1按下给出二值信号量BinarySem_Handle,当按键PB11按下给出二值信号量BinarySem_Handle:

//发送任务函数
void send_task(void *pvParameters)
{
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
	uint8_t KeyNum;
	
	while(1)
    {
		KeyNum=Key_GetNum();
		if(KeyNum==1)
		{
			xReturn = xSemaphoreGive( BinarySem_Handle );//给出二值信号量
			if( xReturn == pdTRUE )
				printf("BinarySem_Handle二值信号量释放成功,按键1!\r\n");
			else
				printf("BinarySem_Handle二值信号量释放失败,按键1!\r\n");
		}
		else if(KeyNum==2)
		{
			xReturn = xSemaphoreGive( BinarySem_Handle );//给出二值信号量
			if( xReturn == pdTRUE )
				printf("BinarySem_Handle二值信号量释放成功,按键2!\r\n");
			else
				printf("BinarySem_Handle二值信号量释放失败,按键2!\r\n");
		}
		vTaskDelay(20);
    }
}
相关宏定义:
//任务优先级
#define LED1_TASK_PRIO		2
//任务堆栈大小	
#define LED1_STK_SIZE 		50  
//任务句柄
TaskHandle_t LED1Task_Handler;
//任务函数
void led1_task(void *pvParameters);


//任务优先级
#define RECEIVE_TASK_PRIO		3
//任务堆栈大小	
#define RECEIVE_STK_SIZE 		50  
//任务句柄
TaskHandle_t ReceiveTask_Handler;
//任务函数
void receive_task(void *pvParameters);



//任务优先级
#define SEND_TASK_PRIO		4
//任务堆栈大小	
#define SEND_STK_SIZE 		50  
//任务句柄
TaskHandle_t SendTask_Handler;
//任务函数
void send_task(void *pvParameters);

4.1.2  开始任务的创建

        之前讲过,开始任务的作用就是将,应用任务的三个任务完整的建立起来,通过调用临界区的API,规避中断带来的风险,创建完后删除自身,只进行应用任务的调度:

//开始任务任务函数
void start_task(void *pvParameters)
{
    taskENTER_CRITICAL();           //进入临界区

	/* 创建 BinarySem */
	BinarySem_Handle = xSemaphoreCreateBinary();	 
	
    //创建LED1任务
    xTaskCreate((TaskFunction_t )led1_task,     
                (const char*    )"led1_task",   
                (uint16_t       )LED1_STK_SIZE, 
                (void*          )NULL,
                (UBaseType_t    )LED1_TASK_PRIO,
                (TaskHandle_t*  )&LED1Task_Handler); 
				
	//创建接收任务
    xTaskCreate((TaskFunction_t )receive_task,     
                (const char*    )"receive_task",   
                (uint16_t       )RECEIVE_STK_SIZE, 
                (void*          )NULL,
                (UBaseType_t    )RECEIVE_TASK_PRIO,
                (TaskHandle_t*  )&ReceiveTask_Handler);

	//创建发送任务
    xTaskCreate((TaskFunction_t )send_task,     
                (const char*    )"send_task",   
                (uint16_t       )SEND_STK_SIZE, 
                (void*          )NULL,
                (UBaseType_t    )SEND_TASK_PRIO,
                (TaskHandle_t*  )&SendTask_Handler);								
				
    vTaskDelete(StartTask_Handler); //删除开始任务
    taskEXIT_CRITICAL();            //退出临界区
} 

        消息队列的创建以及开始任务相关宏定义:

//任务优先级
#define START_TASK_PRIO		1
//任务堆栈大小	
#define START_STK_SIZE 		128  
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);

SemaphoreHandle_t BinarySem_Handle =NULL;

4.1.3  主函数

        创建开始任务开始调用:

int main()
{
	SysTick_Init(72);
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
	LED_Init();
	KEY_Init();
	USART1_Init(115200);
	printf("FreeRTOS二值信号量实验\r\n");
	printf("按下KEY_UP或者KEY1进行任务与任务间的同步\r\n");
	printf("Receive任务接收到消息在串口回显\r\n");
	
	//创建开始任务
    xTaskCreate((TaskFunction_t )start_task,            //任务函数
                (const char*    )"start_task",          //任务名称
                (uint16_t       )START_STK_SIZE,        //任务堆栈大小
                (void*          )NULL,                  //传递给任务函数的参数
                (UBaseType_t    )START_TASK_PRIO,       //任务优先级
                (TaskHandle_t*  )&StartTask_Handler);   //任务句柄              
    vTaskStartScheduler();          //开启任务调度
}

4.1.4  完整代码

基于STM32F103C8T6的FreeRTOS的二值信号量资源-CSDN文库

4.2  计数信号量

4.2.1  应用任务创建

任务1函数:

        实现LED灯的闪烁:

//任务1函数
void led1_task(void *pvParameters)
{
    while(1)
    {
        LED1=0;
        vTaskDelay(200);
        LED1=1;
        vTaskDelay(800);
    }
}
接收任务函数:

        用于接收消息变量,当按键1按下,释放一个空间(停车位):

//接收任务函数
void receive_task(void *pvParameters)
{
   BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS,值为1 */
	 uint8_t KeyNum;
	
	while(1)
    {
			KeyNum=Key_GetNum();
			if(KeyNum==1)
			{
				xReturn = xSemaphoreTake( CountSem_Handle,0 );//获取计数信号量
				if( xReturn == pdTRUE )
					printf( "KEY1被按下,释放1个停车位。\r\n" );
				else
					printf( "KEY1被按下,但已无车位可以释放!\r\n" );
			}
			vTaskDelay(20);
    }
}
发送任务函数:

        用于发送消息,当按键2按下,申请占用一个空间(停车位):

//发送任务函数
void send_task(void *pvParameters)
{
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
	uint8_t KeyNum;
	
	while(1)
    {
		KeyNum=Key_GetNum();
		if(KeyNum==2)
		{
			xReturn = xSemaphoreGive( CountSem_Handle );//给出计数信号量
			if( xReturn == pdTRUE )
				printf("KEY_UP被按下,成功申请到停车位。\r\n");
			else
				printf("KEY_UP被按下,不好意思,现在停车场已满!\r\n");
		}
		vTaskDelay(20);
    }
}
相关宏定义:
//任务优先级
#define LED1_TASK_PRIO		2
//任务堆栈大小	
#define LED1_STK_SIZE 		50  
//任务句柄
TaskHandle_t LED1Task_Handler;
//任务函数
void led1_task(void *pvParameters);


//任务优先级
#define RECEIVE_TASK_PRIO		3
//任务堆栈大小	
#define RECEIVE_STK_SIZE 		50  
//任务句柄
TaskHandle_t ReceiveTask_Handler;
//任务函数
void receive_task(void *pvParameters);



//任务优先级
#define SEND_TASK_PRIO		4
//任务堆栈大小	
#define SEND_STK_SIZE 		50  
//任务句柄
TaskHandle_t SendTask_Handler;
//任务函数
void send_task(void *pvParameters);

4.1.2  开始任务的创建

        之前讲过,开始任务的作用就是将,应用任务的三个任务完整的建立起来,通过调用临界区的API,规避中断带来的风险,创建完后删除自身,只进行应用任务的调度:

//开始任务任务函数
void start_task(void *pvParameters)
{
    taskENTER_CRITICAL();           //进入临界区
     
	/* 创建 CountSem */
	CountSem_Handle = xSemaphoreCreateCounting(5,5); 
	
    //创建LED1任务
    xTaskCreate((TaskFunction_t )led1_task,     
                (const char*    )"led1_task",   
                (uint16_t       )LED1_STK_SIZE, 
                (void*          )NULL,
                (UBaseType_t    )LED1_TASK_PRIO,
                (TaskHandle_t*  )&LED1Task_Handler); 
				
	//创建接收任务
    xTaskCreate((TaskFunction_t )receive_task,     
                (const char*    )"receive_task",   
                (uint16_t       )RECEIVE_STK_SIZE, 
                (void*          )NULL,
                (UBaseType_t    )RECEIVE_TASK_PRIO,
                (TaskHandle_t*  )&ReceiveTask_Handler);

	//创建发送任务
    xTaskCreate((TaskFunction_t )send_task,     
                (const char*    )"send_task",   
                (uint16_t       )SEND_STK_SIZE, 
                (void*          )NULL,
                (UBaseType_t    )SEND_TASK_PRIO,
                (TaskHandle_t*  )&SendTask_Handler);
				
    vTaskDelete(StartTask_Handler); //删除开始任务
    taskEXIT_CRITICAL();            //退出临界区
} 

        消息队列的创建以及开始任务相关宏定义:

//任务优先级
#define START_TASK_PRIO		1
//任务堆栈大小	
#define START_STK_SIZE 		128  
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);


SemaphoreHandle_t CountSem_Handle =NULL;

4.1.3  主函数

        创建开始任务开始调用:

int main()
{
	SysTick_Init(72);
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
	LED_Init();
	KEY_Init();
	USART1_Init(115200);
	printf("FreeRTOS计数信号量实验\r\n");
	printf("车位默认值为5个,按下KEY_UP申请车位,按下KEY1释放车位\r\n");
	//创建开始任务
    xTaskCreate((TaskFunction_t )start_task,            //任务函数
                (const char*    )"start_task",          //任务名称
                (uint16_t       )START_STK_SIZE,        //任务堆栈大小
                (void*          )NULL,                  //传递给任务函数的参数
                (UBaseType_t    )START_TASK_PRIO,       //任务优先级
                (TaskHandle_t*  )&StartTask_Handler);   //任务句柄              
    vTaskStartScheduler();          //开启任务调度
}

4.1.4  完整代码

基于STM32F103C8T6的FreeRTOS的计数信号量资源-CSDN文库

FreeRTOS_时光の尘的博客-CSDN博客

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

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

相关文章

实验七:独立按键实验

硬件电路图和题目; LED1-LD8是 P2口8个管脚 mian.c #include<reg52.h>sbit But1=P3^1 ; sbit But2=P3^0 ; sbit But3=P3^2 ; sbit But4=P3^3 ;sbit LED1 =P2^0 ; sbit LED2 =P2^1 ; sbit LED3 =P2^2 ; sbit LED4 =P2^3 ;#define PRESS_1 1 #define PRESS_…

数据库多表设计:深入理解一对多、一对一、多对多关系 【后端 12】

数据库多表设计&#xff1a;深入理解一对多、一对一、多对多关系 在数据库设计中&#xff0c;表之间的关系决定了如何组织和存储数据。常见的表关系包括一对多、一对一和多对多。在不同的业务场景下&#xff0c;我们会选择不同的关系模式进行数据库设计。本文将通过具体案例介绍…

linux Qt QkeyEvent及驱动键盘按键捕获

基于正点原子 QT中有专门的类处理键盘事件的类QKeyEvent 1.include “QKeyEvent” 查看它的说明中的描述 也就是说接受按键事件在keyPressEvent和keyReleaseEvent这两个函数&#xff0c;继续查看 重构这个函数 查看输入的QKeyEvent类&#xff0c;发现有一个方法key返回哪一个按…

MinerU pdf文档解析markdown格式、内容提取

参考&#xff1a; https://github.com/opendatalab/MinerU/blob/master/README_zh-CN.md demo在线网址&#xff1a; https://opendatalab.com/OpenSourceTools/Extractor/PDF/detail

Robot Operating System——创建动态链接文件项目的步骤

大纲 初始化环境创建Package代码添加依赖&#xff08;package.xml&#xff09;修改编译描述find_package寻找依赖库指定代码路径和编译类型&#xff08;动态库&#xff09;设置头文件路径链接依赖的库 编译测试参考资料 在 《Robot Operating System——创建可执行文件项目的步…

大数据-93 Spark 集群 Spark SQL 概述 基本概念 SparkSQL对比 架构 抽象

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

VMware虚拟机nat无法联通主机

VMware在nat模式下主机无法ping通虚拟机 原因&#xff1a; 虚拟机和对应的网卡不在一个网段 虚拟机开启了防火墙 解决方法: 首先判断虚拟机的网络ip是否和网卡在一个网段上 判断虚拟机使用的网卡 nat模式在VMware虚拟机中一般只有一个对应的网卡 如图笔者的nat网卡为VM…

基于机器学习的二手房房价数据分析与价格预测模型

有需要本项目的可以私信博主&#xff0c;提供远程部署讲解 本研究聚焦重庆二手房市场&#xff0c;通过创新的数据采集和分析方法&#xff0c;深入探讨影响房价的关键因素&#xff0c;并开发了预测模型。 我们首先利用Python编写的爬虫程序&#xff0c;巧妙规避了链家网站的反…

ClickHouse实时探索与实践 京东云

1 前言 京喜达技术部在社区团购场景下采用JDQFlinkElasticsearch架构来打造实时数据报表。随着业务的发展 Elasticsearch开始暴露出一些弊端&#xff0c;不适合大批量的数据查询&#xff0c;高频次深度分页导出导致ES宕机、不能精确去重统计&#xff0c;多个字段聚合计算时性能…

初识Linux · 权限

目录 前言&#xff1a; 1 预备知识 2 权限 2.1 文件的基本权限 2.2 修改权限的第一种做法 2.3 修改权限的第二种做法 2.4 权限的对比 2.5 文件类型 前言&#xff1a; 继上文我们将常用的指令介绍的七七八八了&#xff0c;本文着重探索Linux文件中的权限部分&#xff0…

docker部署postgresSQL 并做持久化

先安装docker&#xff0c;安装docker 方法自行寻找方法 然后安装pgsql 拉取镜像 docker pull registry.cn-hangzhou.aliyuncs.com/qiluo-images/postgres:latest运行容器 docker run -it --name postgres --privileged --restart always -e POSTGRES_PASSWORDZ6n8g4zJzC3mr…

手动与自动修复mfc140u.dll丢失的解决方法,mfc140u.dll在电脑中是什么样的存在

当您遇到“mfc140u.dll丢失”的错误时&#xff0c;通常意味着计算机上缺少Microsoft Foundation Class (MFC) 库的特定版本&#xff0c;该库是Visual Studio 2015的一部分。这种问题往往在启动某些应用程序或游戏时出现&#xff0c;并显示如“无法启动该程序&#xff0c;因为计…

可变参数模板(C++11)

这篇文章讲解的是C11的特性之一——可变参数模板&#xff0c;适合有一定基础的同学学习&#xff0c;如果是刚入门的同学可以看我过往的文章&#xff1a;C基础入门 可变参数模板&#xff08;Variadic Templates&#xff09;是C的一种高级特性&#xff0c;它允许你编写接受任意数…

8.20T3 无损加密(线性代数转LGV+状压dp+高维前缀和)

http://cplusoj.com/d/senior/p/NODSX2301C 对于式子&#xff1a; 这个神秘的线性代数形式比较难处理&#xff0c;但我们可以考虑其组合意义。行列式现存的可用组合意义之一就是LGV&#xff08;矩阵式不太可用&#xff09; 先把原先的矩阵转化为一个有向图。现在我们要构造一…

笔记本电脑无线网卡突然没有了

目录 笔记本电脑无线网卡突然没有了最优解决方案 笔记本电脑无线网卡突然没有了 记录一次笔记本无线网卡突然没有了的解决方案 显示黄色感叹号&#xff0c;试了几个安装驱动的软件都不行 最优解决方案 找到网卡的厂商官网&#xff0c;官网上下载驱动 比如我的无线网卡是Int…

【Hot100】LeetCode—146. LRU 缓存

目录 1-思路1-1 LRU知识点1-2 实现思路LRU的子数据结构① 双向链表 DLinkedNode 结点定义② 其他字段 LRU实现的方法① 初始化——LRUCache中初始化② public int get(int key) 取元素方法③ public void put(int key, int value) 存元素方法 2-实现⭐146. LRU 缓存——题解思路…

rufus制作ubantu的U盘安装介质时,rufus界面上的分区类型选什么?

rufus制作ubantu的U盘安装介质时&#xff0c;rufus软件界面上的分区类型选什么(如下图&#xff09;&#xff1f; 在使用Rufus制作Ubuntu的U盘安装介质时&#xff0c;分区类型的选择取决于我们的计算机的引导方式。 以下是具体的选择建议&#xff1a; 1、查看计算机的引导方式…

JAVA设计模式之【单例模式】

1 类图 2 饿汉式单例 例如&#xff1a;静态块、静态成员 2.1 概念 类加载的时候就立即初始化&#xff0c;并且创建单例对象 2.2 优点 没有加任何的锁、执行效率比较高 2.3 缺点 类加载的时候就初始化&#xff0c;不管用与不用都占着空间&#xff0c;浪费了内存。 3 懒汉…

Java之迭代器的使用

Java之迭代器的使用 摘要基础知识List迭代器Map迭代器 摘要 本博客主要讲解容器的迭代器的使用&#xff0c;包括List、Set和Map等容器 基础知识 这是类的继承关系图 迭代器的原理(一开始迭代器并不指向任何有效元素)&#xff1a; List迭代器 public class TestIterator …

VMware vSphere Client无法访问和连接ESXi虚拟主机解决思路

文章目录 前言1. 问题现象2. 问题原因3. 解决方法4. 参考文章 前言 注意 : 可以先看看参考文章那里&#xff0c;在回过来看 1 、 2 、3 1. 问题现象 版本&#xff1a;VMware vCenter Server 5.5.0 build-2442329 问题描述&#xff1a;用VMware vSphere Client 登录ESXI主机出…