基于STM32F103的FreeRTOS系列(十)·消息队列的使用详细介绍以及代码编写

news2024/11/15 1:56:10

目录

1.  消息队列简介

1.1  概念

1.2  数据存储

1.3  阻塞机制

1.3.1  出队阻塞

1.3.2  入队阻塞

1.4  操作示意图

1.4.1  创建队列

1.4.2  向队列发送第一个消息

1.4.3  向队列发送第二个消息

1.4.4  从队列读取消息

1.5  消息队列的控制块

2.  常用API函数介绍

2.1  消息队列创建函数 xQueueCreate()

2.2  消息队列静态创建函数  xQueueCreateStatic()

2.3  消息队列删除函数  vQueueDelete()

2.4  消息队列的发送消息函数

2.4.1  xQueueSend()和xQueueSendToBack()

2.4.2  xQueueSendFromISR()与xQueueSendToBackFromISR()

2.4.3  xQueueSendToFront()

2.4.4  xQueueSendToFrontFromISR()

2.5  消息队列的读取消息函数

2.5.1  xQueueReceive()和xQueuePeek()

2.5.2  xQueueReceiveFromISR()和xQueuePeekFromISR()

3.  注意事项

4.  代码编写

4.1  应用任务创建

4.1.1  任务1函数

4.1.2  接收任务函数

4.1.3  发送任务函数

4.1.4  相关宏定义

4.2  开始任务的创建

4.3  主函数

4.4  完整代码


1.  消息队列简介

1.1  概念

        队列又称消息队列,是一种常用于任务间通信的数据结构,队列可以在任务与任务间、中断和任务间传递信息,实现了任务接收来自其他任务或中断的不固定长度的消息。

FreeRTOS 中使用队列数据结构实现任务异步通信工作,具有如下特性:

① 消息支持先进先出方式排队,支持异步读写工作方式。

② 读写队列均支持超时机制。

③ 消息支持后进先出方式排队,往队首发送消息(LIFO) 。

④ 可以允许不同长度(不超过队列节点最大值)的任意类型消息。

⑤ 一个任务能够从任意一个消息队列接收和发送消息。

⑥ 多个任务能够从同一个消息队列接收和发送消息。

⑦ 当队列使用结束后,可以通过删除队列函数进行删除。

1.2  数据存储

        通常队列采用先进先出(FIFO)的存储缓冲机制,也可以使用LIFO的存储缓冲,也就是后进先出。

        数据发送到队列中会导致数据拷贝,也就是将要发送的数据拷贝到队列中,这就意味着在队列中存储的是数据的原始值,而不是原数据的引用(即只传递数据的指针),这个也叫做值传递。

1.3  阻塞机制

1.3.1  出队阻塞

        当任务尝试从一个队列读取消息的时候可以指定一个阻塞时间,这个阻塞时间就是当任务从队列读取消息无效的时候任务阻塞的时间。

1.3.2  入队阻塞

        入队说的是向队列中发送消息,将消息加入到队列中,和出队阻塞一样,当一个任务向队列发送消息的话也可以设置阻塞时间。

1.4  操作示意图

1.4.1  创建队列

        首先创建两个任务A和B,任务A想向任务B发送消息,这个消息变量为x,我们在创建队列的时候需要创建其对列的长度以及每个消息的长度,消息的长度需要看好其数据类型,此时x的数据类型为int,占据四个字节,因此消息的长度为4:

1.4.2  向队列发送第一个消息

        任务 A 的变量 x 值为 10,将这个值发送到消息队列中。此时队列剩余长度就是 3 了。前面说了向队列中发送消息是采用拷贝的方式,所以一旦消息发送完成变量 x 就可以再次被使用,赋其他的值。

1.4.3  向队列发送第二个消息

        任务 A 又向队列发送了一个消息,即新的 x 的值,这里是 20。紧跟在上一个数据的后面,此时队列剩余长度为 2。

1.4.4  从队列读取消息

        任务 B 从队列中读取消息,并将读取到的消息值赋值给 y,这样 y 就等于 10 了。任务 B 从队列中读取消息完成以后可以选择清除掉这个消息或者不清除。当选择清除这个 消息的话其他任务或中断就不能获取这个消息了,而且队列剩余大小就会加一,变成 3。如果 不清除的话其他任务或中断也可以获取这个消息,而队列剩余大小依旧是 2。

1.5  消息队列的控制块

        在queue.c文件找到如下函数,做一个了解:

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;

2.  常用API函数介绍

2.1  消息队列创建函数 xQueueCreate()

函数原型QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_tuxItemSize);
功能用于创建一个新的队列。
参数uxQueueLength队列能够存储的最大消息单元数目,即队列长度。
uxltemSize队列中消息单元的大小,以字节为单位。
返回值如果创建成功则返回一个队列句柄,用于访问创建的队列。如果创建不成功则返回NULL,可能原因是创建队列需要的RAM无法分配成功。

        xQueueCreate()用于创建一个新的队列并返回可用于访问这个队列的队列句柄。队列句柄其实就是一个指向队列数据结构类型的指针。 队列就是一个数据结构,用于任务间的数据的传递。每创建一个新的队列都需要为其分配 RAM,一部分用于存储队列的状态,剩下的作为队列消息的存储区域。

        消息队列创建函数默认的是动态创建的方法。通常情况下,在 FreeRTOS 中,凡是创建任务,队列, 信号量和互斥量等内核对象都需要使用动态内存分配,使用静态创建消息队列函数创建队列时需要的形参更多,需要的内存 由编译的时候预先分配好,一般很少使用这种方法。

2.2  消息队列静态创建函数  xQueueCreateStatic()

函数原型

QueueHandle_t xQueueCreateStatic(UBaseType_tuxQueueLength,

                                                            UBaseType_t uxltemSize,

                                                            uint8_t *pucQueueStorageBuffer,

                                                            StaticQueue_t *pxQueueBuffer);

功能用于创建一个新的队列。
参数uxQueueLength队列能够存储的最大单元数目,即队列深度。
uxItemSize队列中数据单元的长度,以字节为单位。
pucQueueStorageBuffer指针,指向一个uint8_t类型的数组,数组的大小至少有uxQueueLength* uxltemSize个字节。当uxItemSize为0时,pucQueueStorageBuffer可以为NULL.
pxQueueBuffer指针,指向 StaticQueue_t 类型的变量,该变量用于存储队列的数据结构。
返回值如果创建成功则返回一个队列句柄,用于访问创建的队列。如果创建不成功则返回NULL,可能原因是创建队列需要的RAM无法分配成功。

        相较于动态方法,静态方法的功能是一样的,只是静态方法需要自己手动分配内存,比较麻烦一些。

2.3  消息队列删除函数  vQueueDelete()

        队列删除函数是根据消息队列句柄直接删除的,删除之后这个消息队列的所有信息都会被系统回收清空,而且不能再次使用这个消息队列了,但是需要注意的是,如果某个消息队列没有被创建,那也是无法被删除的,因为没创建的东西就不存在, 怎么可能被删除。

函数原型

void vQueueDelete(QueueHandle_t xQueue)

2.4  消息队列的发送消息函数

/* 等同于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
                                   );
 
#define QueueSendFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken ) xQueueGenericSendFromISR( ( xQueue ), ( pvItemToQueue ), ( pxHigherPriorityTaskWoken ), queueSEND_TO_BACK )

2.4.1  xQueueSend()和xQueueSendToBack()

函数原型

BaseType_t xQueueSend(QueueHandle_t xQueue,

                                           const void * pvltemToQueue,

                                           TickType_txTicksToWait);

功能用于向队列尾部发送一个队列消息。
参数xQueue队列句柄。
pvItemToQueue指针,指向要发送到队列尾部的队列消息。
xTicksToWait队列满时,等待队列空闲的最大超时时间。如果队列满并且xTicksToWait被设置成0,函数立刻返回。超时时间的单位为系统节拍周期,常量portTICK-PERIOD-MS用于辅助计算真实的时间,单位为ms。如果INCLUDE_vTaskSuspend设置成1,并且指定延时为 portMAX_DELAY 将导致任务挂起(没有超时)。
返回值消息发送成功成功返回pdTRUE,否则返回errQUEUE-FULL。

2.4.2  xQueueSendFromISR()与xQueueSendToBackFromISR()

函数原型

BaseType_t xQueueSendFromISR(QueueHandle_txQueue,

                                                         const void *pvItemToQueue,

                                                         BaseType_t*pxHigherPriority TaskWoken);

功能在中断服务程序中用于向队列尾部发送一个消息。
参数xQueue队列句柄。
pvltemToQueue指针,指向要发送到队列尾部的消息。
pxHigherPriority TaskWoken如果入队导致一个任务解锁,并且解锁的任务优先级高于当前被中断的任务,则将*pxHigherPriorityTaskWoken设置成 pdTRUE,然后在中断退出前需要进行一次上下文切换,去执行被唤醒的优先级更高的任务。从FreeRTOS V7.3.0 起, pxHigherPriorityTaskWoken作为一个可选参数,可以设置为 NULL。
返回值消息发送成功返回pdTRUE,否则返回errQUEUE-FULL。

2.4.3  xQueueSendToFront()

函数原型

BaseType_t xQueueSendToFront(QueueHandle_t xQueue,

                                                       const void * pvItemToQueue,

                                                       TickType_t xTicksToWait);

功能于向队列队首发送一个消息。
参数xQueue队列句柄。
pvltemToQueue指针,指向要发送到队首的消息。
xTicksToWait队列满时,等待队列空闲的最大超时时间。如果队列满并且xTicksToWait被设置成0,函数立刻返回。超时时间的单位为系统节拍周期,常量 portTICK_PERIOD_MS 用于辅助计算真实的时间,单位为 ms。如果 INCLUDE_vTaskSuspend 设置成 1,并且指定延时为 portMAX_DELAY 将导致任务无限阻塞(没有超时)。
返回值发送消息成功返回pdTRUE,否则返回errQUEUE-FULL。

2.4.4  xQueueSendToFrontFromISR()

函数原型

BaseType_t xQueueSendToFrontFromISR(QueueHandle_t xQueue,

                                                                     const void *pvItemToQueue,

                                                                     BaseType_t*pxHigherPriorityTaskWoken);

功能在中断服务程序中向消息队列队首发送一个消息。
参数xQueue队列句柄。
pvltemToQueue指针,指向要发送到队首的消息。
pxHigherPriority TaskWoken如果入队导致一个任务解锁,并且解锁的任务优先级高于当前被中断的任务,则将*pxHigherPriorityTaskWoken设置成 pdTRUE,然后在中断退出前需要进行一次上下文切换,去执行被唤醒的优先级更高的任务。从FreeRTOS V7.3.0 起, pxHigherPriorityTaskWoken作为一个可选参数,可以设置为 NULL。
返回值队列项投递成功返回pdTRUE,否则返回errQUEUE_FULL。

2.5  消息队列的读取消息函数

2.5.1  xQueueReceive()和xQueuePeek()

#define xQueueReceive( xQueue, pvBuffer, xTicksToWait ) xQueueGenericReceive( ( xQueue ), ( pvBuffer ), ( xTicksToWait ), pdFALSE )


#define xQueuePeek( xQueue, pvBuffer, xTicksToWait ) xQueueGenericReceive( ( xQueue ), ( pvBuffer ), ( xTicksToWait ), pdTRUE )
函数原型

BaseType_t xQueueReceive(QueueHandle_t xQueue,

                                              void *pvBuffer,

                                              TickType_txTicksToWait);

功能用于从一个队列中接收消息,并把接收的消息从队列中删除。
参数xQueue队列句柄。
pvBuffer指针,指向接收到要保存的数据。
xTicksToWait队列空时,阻塞超时的最大时间。如果该参数设置为0,函数立刻返回。超时时间的单位为系统节拍周期,常量portTICK_PERIOD_MS用于辅助计算真实的时间,单位为ms。如果INCLUDE_vTaskSuspend设置成1,并且指定延时为portMAX_DELAY将导致任务无限阻塞(没有超时)。
返回值

队列项接收成功返回pdTRUE,否则返回pdFALSE。

        xQueuePeek功能上和xQueueReceive是相似的,但是xQueuePeek不会把接收的消息从队列中删除。

2.5.2  xQueueReceiveFromISR()和xQueuePeekFromISR()

函数原型

BaseType_t xQueueReceiveFromISR(QueueHandle_txQueue,

                                                              void *pvBuffer,

                                                              BaseType_t*pxHigherPriorityTaskWoken);

功能在中断中从一个队列中接收消息,并从队列中删除该消息。
参数xQueue队列句柄。
pvBuffer指针,指向接收到要保存的数据。
pxHigherPriority TaskWoken任务在往队列投递信息时,如果队列满,则任务将阻塞在该队列上。如果 xQueueReceiveFromISR()到账了一个任务解锁了则将*pxHigherPriorityTaskWoken设置为pdTRUE, 否则*pxHigherPriorityTaskWoken的值将不变。从FreeRTOS V7.3.0起, pxHigherPriorityTaskWoken作为一个可选参数,可以设置为NULL。
返回值队列项接收成功返回pdTRUE,否则返回pdFALSE。
函数原型

BaseType_t xQueuePeekFromISR(QueueHandle_t xQueue,

                                                         void *pvBuffer);

功能在中断中从一个队列中接收消息,但并不会把消息从该队列中移除。
参数xQueue队列句柄。
pvBuffer指针,指向接收到要保存的数据。
返回值队列项接收(peek)成功返回pdTRUE,否则返回pdFALSE。

3.  注意事项

① 使用 xQueueSend()、xQueueSendFromISR()、xQueueReceive()等这些函数之前应先创建需消息队列,并根据队列句柄进行操作。

② 队列读取采用的是先进先出(FIFO)模式,会先读取先存储在队列中的数据。当然也FreeRTOS也支持后进先出(LIFO)模式,那么读取的时候就会读取到后进队列的数据。

③ 在获取队列中的消息时候,我们必须要定义一个存储读取数据的地方,并且该数据区域大小不小于消息大小,否则,很可能引发地址非法的错误。

④ 无论是发送或者是接收消息都是以拷贝的方式进行,如果消息过于庞大,可以将消息的地址作为消息进行发送、接收。

⑤ 队列是具有自己独立权限的内核对象,并不属于任何任务。所有任务都可以向同一队列写入和读出。一个队列由多任务或中断写入是经常的事,但由多个任务读出倒是用的比较少。

4.  代码编写

        找到一个动态方法的代码:

基于STM32F103的FreeRTOS系列(九)·任务创建函数的使用·静态方法和动态方法-CSDN博客

4.1  应用任务创建

4.1.1  任务1函数

        实现LED灯的闪烁:

//任务1函数
void led1_task(void *pvParameters)
{
    while(1)
    {
        led1_on();
        vTaskDelay(200);
        led1_off();
        vTaskDelay(800);
    }
}

4.1.2  接收任务函数

        用于接收消息变量:

//接收任务函数
void receive_task(void *pvParameters)
{
    BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为pdTRUE */
	uint32_t r_queue;	/* 定义一个接收消息的变量 */
	
	while(1)
    {
        xReturn = xQueueReceive( Test_Queue,    /* 消息队列的句柄 */
								&r_queue,      /* 发送的消息内容 */
								portMAX_DELAY); /* 等待时间 一直等 */
		if(pdTRUE == xReturn)
			printf("本次接收到的数据是%d\r\n",r_queue);
		else
			printf("数据接收出错,错误代码0x%lx\r\n",xReturn);
    }
}

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

        xReturn = xQueueReceive( Test_Queue,    /* 消息队列的句柄 */
								&r_queue,      /* 发送的消息内容 */
								portMAX_DELAY); /* 等待时间 一直等 */

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

4.1.3  发送任务函数

        用于发送消息,当按键PB1按下发送&send_data1的内容,当按键PB2按下发送&send_data2的内容:

//发送任务函数
void send_task(void *pvParameters)
{
    BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
	uint32_t send_data1 = 1;
	uint32_t send_data2 = 2;
	uint8_t KeyNum;
	
	while(1)
    {
		KeyNum=Key_GetNum();
		if(KeyNum==1)
		{
			printf("发送消息send_data1!\r\n");
			xReturn = xQueueSend( Test_Queue, /* 消息队列的句柄 */
								&send_data1,/* 发送的消息内容 */
								0 );        /* 等待时间 0 */
			if(pdPASS == xReturn)
				printf("消息send_data1发送成功!\r\n");
		}
		else if(KeyNum==2)
		{
			printf("发送消息send_data2!\r\n");
			xReturn = xQueueSend( Test_Queue, /* 消息队列的句柄 */
								&send_data2,/* 发送的消息内容 */
								0 );        /* 等待时间 0 */
			if(pdPASS == xReturn)
				printf("消息send_data2发送成功!\r\n");
		}
		vTaskDelay(20);
    }
}

4.1.4  相关宏定义

//任务优先级
#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.2  开始任务的创建

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

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

	/* 创建Test_Queue */
	Test_Queue = xQueueCreate((UBaseType_t ) QUEUE_LEN,/* 消息队列的长度 */
                            (UBaseType_t ) QUEUE_SIZE);/* 消息的大小 */
	
    //创建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);


QueueHandle_t Test_Queue =NULL;

#define  QUEUE_LEN    4   /* 队列的长度,最大可包含多少个消息 */
#define  QUEUE_SIZE   4   /* 队列中每个消息大小(字节) */

4.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.4  完整代码

基于STM32F103C8T6的FreeRTOS的消息队列的收发资源-CSDN文库

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

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

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

相关文章

android13顶部状态栏里面调节背光,不隐藏状态栏面板

总纲 android13 rom 开发总纲说明 目录 1.前言 2.代码分析 3.修改方法 4.编译运行 5.彩蛋 1.前言 android13顶部状态栏里面调节背光,这个时候状态栏面板会被隐藏掉,有些需求就需要不隐藏这个面板。 2.代码分析 查找亮度条属性 id/brightness_slider ./frameworks/b…

Vue 3 + 天地图 + D3.js 绘制行政区划

​&#x1f308;个人主页&#xff1a;前端青山 &#x1f525;系列专栏&#xff1a;组件封装篇 &#x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来组件封装篇专栏内容:组件封装-天地图 目录 引入天地图 API 初始化地图 引入 D3.js 加载行政区划数据 添…

k8s高版本(1,28)部署NodePort模式下的ingress-nginx的详细过程及应用案例

文章目录 前言环境ingress安装应用案例(ingress-http案例&#xff1a; 基于名称的负载均衡) 前言 这个是nodeport模式下的&#xff0c;如果需要loadbalancer模式下的&#xff0c;看看博主下面以前的博客 链接: k8s学习–负载均衡器matelLB的详细解释与安装 链接: k8s学习–ing…

机器学习 之 使用逻辑回归 进行银行贷款预测(请帮忙点点赞谢谢,这对我很重要)

目录 一、逻辑回归简介 逻辑回归的基本原理 线性组合&#xff1a; Sigmoid函数&#xff1a; 二、实战案例 1.导入数据 2.准备环境 混淆矩阵的基本概念 混淆矩阵的作用 3.加载数据 4.数据预处理 什么是标准化&#xff1f; 标准化的计算公式 划分数据集 5.逻辑回归模…

19.缓存的认识和基本使用

缓存介绍 缓存是数据交换的缓冲区Cache&#xff0c;是临时存储数据的地方&#xff0c;一般读写性能较高。 数据库的缓存就是建立索引。 缓存的作用 1.降低后端负载。 2.提高读写效率&#xff0c;降低响应时间。 缓存的问题 1.保证数据的一致性。 2.增加代码维护成本。解…

Kafka运行机制(二):消息确认,消息日志的存储和回收

前置知识 Kafka基本概念https://blog.csdn.net/dxh9231028/article/details/141270920?spm1001.2014.3001.5501Kafka运行机制&#xff08;一&#xff09;&#xff1a;Kafka集群启动&#xff0c;controller选举&#xff0c;生产消费流程https://blog.csdn.net/dxh9231028/arti…

Qt 0816作业

一、思维导图 二、将day1做的登录界面升级优化【资源文件的添加】 三、在登录界面的登录取消按钮进行一下设置 使用手动连接&#xff0c;将登录框中的取消按钮使用qt4版本的连接到自定义的槽函数中&#xff0c;在自定义的槽函数中调用关闭函数 将登录按钮使用qt5版本的连接到…

C++ | Leetcode C++题解之第350题两个数组的交集II

题目&#xff1a; 题解&#xff1a; class Solution { public:vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {sort(nums1.begin(), nums1.end());sort(nums2.begin(), nums2.end());int length1 nums1.size(), length2 nums2…

函数递归VS操作符深入?

1>>前言 函数递归函数递归&#xff0c;当小白听到这样的词会感到无比陌生&#xff0c;请不要惊慌&#xff0c;这是正常的&#xff0c;以至于都不是很经常用到&#xff0c;但是它的算法&#xff0c;它的思想是值得我们深入思考的。还有一些复杂操作符&#xff0c;如按位与…

【原创】java+swing+mysql共享充电宝管理系统设计与实现

个人主页&#xff1a;程序员杨工 个人简介&#xff1a;从事软件开发多年&#xff0c;前后端均有涉猎&#xff0c;具有丰富的开发经验 博客内容&#xff1a;全栈开发&#xff0c;分享Java、Python、Php、小程序、前后端、数据库经验和实战 文末有本人名片&#xff0c;希望和大家…

PyTorch之TensorBoard使用

接回上一篇&#xff1a;PyTorch深度学习框架-CSDN博客 在学习这篇之前建议先按照上一篇搭建好整个PyTorch环境 然后这一篇讲怎么用TensorBoard&#xff0c;这个玩意是Tensorflow官方推出的一个可视化工具&#xff0c;当使用Tensorflow训练大量深层的神经网络时&#xff0c;我们…

全局锁、表级锁、行级锁

锁的作用和特点 WHY&#xff1a;锁的出现是为了解决并发场景下不同用户同时对共享资源进行操作&#xff0c;而可能引发的并发问题。 HOW&#xff1a;控制不同线程对资源访问的规则。 全局锁 顾名思义&#xff0c;全局锁就是对整个数据库实例加锁。一般在进行全库备份的时候…

prometheus + grafana + 告警

配置环境 准备三台主机&#xff0c;将三台主机的信息分别写入/etc/hosts文件中 192.168.100.115 server.example.com server 192.168.100.116 agent1.example.com agent1 192.168.100.117 grafana.example.com grafana [rootserver ~]# cat /etc/hosts 127.0.0.1 localhos…

【MySQL 08】内置函数 (带思维导图)

文章目录 &#x1f308; 一、日期函数⭐ 1. 常见日期函数⭐ 2. 日期函数使用示例⭐ 3. 日期函数综合案例 &#x1f308; 二、字符串函数⭐ 1. 常见字符串函数⭐ 2. 字符串函数使用示例⭐ 3. 字符串函数综合案例 &#x1f308; 三、数值函数⭐ 1. 常见数值函数⭐ 2. 数值函数使用…

探索GitHub的无限可能:从注册到Linux环境下的库分支链接

在这个数字化时代&#xff0c;GitHub已成为开发者们不可或缺的宝藏库。无论你是编程新手还是资深开发者&#xff0c;GitHub都能为你打开一扇通往无限创意与协作的大门。今天&#xff0c;就让我们一起踏上这段探索之旅&#xff0c;从GitHub的注册开始&#xff0c;再到如何在Linu…

google transalte api的使用,V2服务账户方式(google-cloud-java)

Google Cloud Translation API 有几个不同的使用方式&#xff0c;其中之一是使用最新的 Google Cloud Client Library。这些库提供了简化的 API&#xff0c;使得与 Google Cloud 服务的交互变得更加容易。 对于gcp平台的创建方式&#xff0c;我记得得绑定真信用卡了&#xff0c…

Debug-021-el-table实现分页多选的效果(切换分页,仍可以保持前一页的选中效果)

前情提要&#xff1a; 这个功能实现很久了&#xff0c;但是一直没有留意如何实现&#xff0c;今天想分享一下。具体就是我们展示table数据的时候&#xff0c;表格中的数据多数情况是分页展示&#xff0c;毕竟数据量太多&#xff0c;分页的确是有必要的。那么我们有业务需要给表…

portswigger的Exploiting DOM clobbering to enable XSS

目录 尝试一下看看可不可以XSS DOM破坏 查看源码确定DOM破坏漏洞点以及代码分析 首先查看/resources/labheader/js/labHeader.js&#xff0c;没有什么作用 然后domPurify这东西是一个过滤框架也没啥子用 看/resources/js/loadCommentsWithDomClobbering.js尝试分析代码(对…

使用Poi-tl对word模板生成动态报告

一、pom依赖问题&#xff1a; <dependency> <groupId>com.deepoove</groupId> <artifactId>poi-tl</artifactId> <version>1.12.2</version> </dependency> 使用 poi-tl 的 1.12.2版本&#xff0c;如果使用了poi依赖&#x…

【编程之路:在 Bug 的迷宫中寻找出口】

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…