【FreeRTOS 教程 四】队列创建与发布项目到队列
目录
一、FreeRTOS队列:
(1)队列介绍:
(2)用户模型说明:
(3)阻塞队列:
二、队列管理 API:
(1)uxQueueMessagesWaiting:
(2) uxQueueMessagesWaitingFromISR:
(3)uxQueueSpacesAvailable:
(4)vQueueDelete:
(5)xQueueReset:
(6)xQueueIsQueueEmptyFromISR:
(7)xQueueIsQueueFullFromISR:
三、队列的创建及使用:
(1)动态创建队列:
(2)静态创建队列:
(3)向队列中发布项目:
(4)项目发布到队列尾部:
(5)队列尾部入队数据项:
(6)项目发布到队列尾部:
(7)队列头部入队数据项:
(8)项目发布到队列头部:
四、队列示例程序:
(1)动态创建队列:
(2)静态创建队列:
五、队列创建时的常见问题:
(1)函数未定义:
(2)动态堆空间太大导致静态分配空间不足:
(3)队列创建后程序下载没反应:
六、FreeRTOS教程示例代码下载:
一、FreeRTOS队列:
(1)队列介绍:
队列是任务间通信的主要形式。它们可以用于在任务之间以及中断和任务之间发送消息。在大多数情况下,队列用作线程安全的 FIFO(先进先出)缓冲区, 新数据被发送到队列的后面,但也可以发送到前面。
向队列中写入和从队列中读取。此示例中创建队列来保存 5 个项目,并且队列永远不会满。
(2)用户模型说明:
FreeRTOS 队列使用模型既简单又灵活, 这两者通常是不可兼得的。消息通过队列以副本的方式发送, 这意味着数据(可以是更大的缓冲区的指针)本身被复制到队列中, 而不是队列始终只存储对数据的引用。这是最好的方法,因为:
- 已经包含在 C 语言变量(整数、 小结构体等)中的小消息可以直接送入队列。没有 必要为消息分配一个缓冲区, 然后将变量复制到分配的缓冲区中。同样,可以直接从队列中将消息读取到 C 变量中 。
- 此外,以这种方式向队列发送消息, 允许发送任务立即覆盖发送到队列的变量或缓冲区, 即使发送的消息仍在队列中。
- 由于变量中包含的数据已复制到队列中, 变量本身可以重复使用。不要求发送消息的任务 和接收消息的任务约定哪个任务拥有该消息, 以及哪个任务负责在不需要该消息时 将其清空。
- 使用通过复制传递数据的队列不会导致无法将队列 用于通过引用传递数据。当消息的大小达到一定程度, 将整条消息逐字节复制到队列中是不现实的, 此时可将消息定义为保存若干指针并复制消息的 一个指针至队列。
- 内核独自负责分配用于队列存储区的内存 。
- 可变大小的消息可以通过定义队列来保存结构体, 其中包含一个指向队列消息的成员, 以及另一个保存队列消息大小的成员。
- 单个队列可用于接收不同的消息类型, 以及来自多个地点的消息, 方法是将队列定义为保存一个结构体,该结构的一个成员持有消息类型, 另一个成员保存消息数据(或消息数据的一个指针)。如何解释数据 取决于消息类型。
- 正是使用这种方式,管理 FreeRTOS-Plus-TCP IP 堆栈的任务才能使用一个队列来接收 ARP 定时器事件、 从以太网硬件接收的数据包、 从应用程序接收的数据包、网络故障事件等的通知。
- 该实现适用于在内存保护环境中使用 。一个被限制在受保护的内存区域的任务可以将数据传递给一个被限制在不同的受保护内存区域的任务, 因为通过调用队列发送函数 来调用 RTOS 将提高微控制器的权限等级 。队列存储区 仅可由 RTOS 访问(具有完整权限)。
- 提供一个单独的 API 用于中断内部。将 RTOS 任务中使用的 API 与中断服务程序中使用的 API 分开, 意味着 RTOS API 函数的实现 不承担每次执行时检查其调用上下文的开销。 使用单独的中断 API 也意味着,在大多数情况下,创建 RTOS 感知的中断服务程序对终端用户而言更简单—— 与其他 RTOS 产品相比。
- 从任何意义上来说,API 都更加简单。
(3)阻塞队列:
- 队列 API 函数允许指定阻塞时间。
- 当一个任务试图从一个空队列中读取时,该队列将进入阻塞状态(因此它不会消耗任何 CPU 时间,且其他任务可以运行) 直到队列中的数据变得可用,或者阻塞时间过期。
- 当一个任务试图写入到一个满队列时,该队列将进入阻塞状态(因此它不会消耗任何 CPU 时间,且其他任务可以运行) 直到队列中出现可用空间,或者阻塞时间过期。
- 如果同一个队列上有多个处于阻塞状态的任务, 那么具有最高优先级的任务将最先解除阻塞。
- 请注意,中断只能使用以 "FromISR" 结尾的 API 函数。
二、队列管理 API:
(1)uxQueueMessagesWaiting
:
- 作用:返回队列中存储的消息数。
函数原型:
UBaseType_t uxQueueMessagesWaiting( QueueHandle_t xQueue );
参数/返回 | 描述 |
---|---|
xQueue | 正在查询的队列的句柄。 |
返回值 | 队列中可用的消息数。 |
(2) uxQueueMessagesWaitingFromISR
:
- 作用:
uxQueueMessagesWaiting()
的一个版本,可以从 ISR 中调用。返回队列中存储的消息数。
函数原型:
UBaseType_t uxQueueMessagesWaitingFromISR( QueueHandle_t xQueue );
参数/返回 | 描述 |
---|---|
xQueue | 正在查询的队列的句柄。 |
返回值 | 队列中可用的消息数。 |
(3)uxQueueSpacesAvailable
:
- 作用:返回队列中的可用空间数。
函数原型:
UBaseType_t uxQueueSpacesAvailable( QueueHandle_t xQueue );
参数/返回值 | 描述 |
---|---|
xQueue | 正在查询的队列的句柄。 |
返回值 | 队列中可用的可用空间数。 |
(4)vQueueDelete
:
- 作用:删除队列 — 释放分配用于存储放置在队列中的项目的所有内存。
函数原型:
void vQueueDelete( QueueHandle_t xQueue );
参数 | 描述 |
---|---|
xQueue | 要删除的队列的句柄。 |
(5)xQueueReset
:
- 作用:将队列重置为其原始的空状态。
函数原型:
BaseType_t xQueueReset( QueueHandle_t xQueue );
参数/返回值 | 描述 |
---|---|
xQueue | 正在重置的队列的句柄。 |
返回值 | 因为 FreeRTOS V7.2.0 中 xQueueReset() 总是返回 pdPASS 。 |
(6)xQueueIsQueueEmptyFromISR
:
- 作用:查询队列以确定队列是否为空。此函数只能用于 ISR。
函数原型:
BaseType_t xQueueIsQueueEmptyFromISR( const QueueHandle_t pxQueue );
参数/返回值 | 描述 |
---|---|
xQueue | 正在查询的队列的句柄。 |
返回值 | 如果队列不为空,则返回 pdFALSE ;如果队列为空,则返回 pdTRUE 。 |
(7)xQueueIsQueueFullFromISR
:
- 作用:查询队列以确定队列是否已满。此函数只能用于 ISR。
函数原型:
BaseType_t xQueueIsQueueFullFromISR( const QueueHandle_t pxQueue );
参数/返回 | 描述 |
---|---|
xQueue | 正在查询的队列的句柄。 |
返回值 | 如果队列未满,则返回 pdFALSE ;如果队列已满,则返回 pdTRUE 。 |
三、队列的创建及使用:
(1)动态创建队列:
函数原型:
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength,UBaseType_t uxItemSize );
- 该函数可以创建新队列并返回一个可以引用该队列的句柄。configSUPPORT_DYNAMIC_ALLOCATION 必须在 FreeRTOSConfig.h 中设置为 1,或保留为未定义状态(默认为 1), 才可使用此RTOS API 函数。
- 每个队列都需要 RAM 来保存队列状态 以及队列中包含的项目(队列存储区)。 如果使用
- xQueueCreate()创建队列,则所需的 RAM 会自动 从 FreeRTOS 堆中分配。 如果使用xQueueCreateStatic() 创建队列, 则 RAM 由应用程序编写者提供,这会产生更多的参数, 但这样能够在编译时静态分配 RAM 。
参数/返回值 | 描述 |
---|---|
uxQueueLength | 队列一次可存储的最大项目数。 |
uxItemSize | 存储队列中每个项目所需的大小(以字节为单位)。队列中的每个项目必须具有相同的大小。 |
返回值 | 如果队列创建成功,则返回所创建队列的句柄。如果创建队列所需的内存无法分配,则返回 NULL。 |
用法示例:
struct AMessage
{char ucMessageID; // 消息ID,使用1个字节的字符类型存储char ucData[20]; // 数据字段,使用20个字节的字符数组存储
};void vATask( void *pvParameters )
{QueueHandle_t xQueue1, xQueue2; // 定义两个队列句柄变量/* 创建一个能够存储10个无符号长整型(unsigned long)的队列 */xQueue1 = xQueueCreate( 10, sizeof( unsigned long ) );if( xQueue1 == NULL ) // 检查队列是否创建成功{/* 如果队列创建失败(内存分配失败),则不能使用这个队列 */}/* 创建一个能够存储10个指向AMessage结构体的指针的队列这些结构体通过指针入队,因为它们相对较大 */xQueue2 = xQueueCreate( 10, sizeof( struct AMessage * ) );if( xQueue2 == NULL ) // 检查队列是否创建成功{/* 如果队列创建失败(内存分配失败),则不能使用这个队列 */}/* ... 任务代码的其余部分 */
}
(2)静态创建队列:
函数原型:
QueueHandle_t xQueueCreateStatic(UBaseType_t uxQueueLength,UBaseType_t uxItemSize,uint8_t *pucQueueStorageBuffer,StaticQueue_t *pxQueueBuffer );
- 创建新队列并返回一个可以引用该队列的句柄。
configSUPPORT_STATIC_ALLOCATION
必须在FreeRTOSConfig.h
中设置为 1,才可使用此 RTOS API 函数。 - 每个队列都需要 RAM 来保存队列状态以及队列中包含的项目(队列存储区)。如果使用
xQueueCreate()
创建队列,则这部分 RAM 会自动从 FreeRTOS 堆中分配。如果使用xQueueCreateStatic()
创建队列,则 RAM 由应用程序编写者提供,这会产生更多的参数,但这样能够在编译时静态分配 RAM。
参数/描述 | 说明 |
---|---|
uxQueueLength | 队列一次可存储的最大项目数。 |
uxItemSize | 存储队列中每个项目所需的大小(以字节为单位)。项目通过复制而非引用的方式入队,因此该参数值是每个入队项目将复制的字节数。队列中的每个项目必须具有相同的大小。 |
pucQueueStorageBuffer | 如果 uxItemSize 不为零,则 pucQueueStorageBuffer 必须指向一个 uint8_t 数组,该数组的大小至少要能容纳队列中最多可能存在的项目的总字节数,即 (uxQueueLength * uxItemSize) 字节。如果 uxItemSize 为零,则 pucQueueStorageBuffer 可以为 NULL。 |
pxQueueBuffer | 必须指向 StaticQueue_t 类型的变量,该变量将用于保存队列的数据结构体。 |
返回 | 如果队列创建成功,则返回所创建队列的句柄。如果 pxQueueBuffer 为 NULL,则返回 NULL。 |
用法示例:
/* 定义队列的最大长度为10,即队列可以存储10个uint64_t类型的变量。 */
#define QUEUE_LENGTH 10/* 定义每个队列项的大小为uint64_t类型的大小。 */
#define ITEM_SIZE sizeof( uint64_t )/* 用于保存队列数据结构的静态变量。 */
static StaticQueue_t xStaticQueue;/* 定义队列的存储区域数组。该数组的大小必须至少为 uxQueueLength * uxItemSize 字节。 */
uint8_t ucQueueStorageArea[ QUEUE_LENGTH * ITEM_SIZE ];void vATask( void *pvParameters )
{QueueHandle_t xQueue; // 定义队列句柄变量/* 创建一个能够存储10个uint64_t值的静态队列。xQueueCreateStatic函数的参数分别为:- QUEUE_LENGTH:队列的最大长度。- ITEM_SIZE:每个队列项的大小。- ucQueueStorageArea:指向队列存储区域的指针。- &xStaticQueue:指向用于保存队列数据结构的静态变量的指针。 */xQueue = xQueueCreateStatic( QUEUE_LENGTH,ITEM_SIZE,ucQueueStorageArea,&xStaticQueue );/* 由于 pxQueueBuffer(即 &xStaticQueue)不为 NULL,因此 xQueue 不应为 NULL。使用 configASSERT 宏来断言 xQueue 不为 NULL,如果 xQueue 为 NULL,则触发断言失败。 */configASSERT( xQueue );
}
(3)向队列中发布项目:
函数原型:
BaseType_t xQueueSend(QueueHandle_t xQueue,const void * pvItemToQueue,TickType_t xTicksToWait);
- 此宏用于调用
xQueueGenericSend()
函数。之所以包含此宏,是为了向后兼容那些未提供xQueueSendToFront()
和xQueueSendToBack()
宏的 FreeRTOS 版本。其功能等同于xQueueSendToBack()
。 - 在队列中发布项目。项目通过复制而非引用的方式入队。不得从中断服务程序中调用此函数。请参阅
xQueueSendFromISR()
,这是一个可在 ISR 中使用的替代函数。
参数/返回值 | 描述 |
---|---|
xQueue | 要向其中发布项目的队列的句柄。 |
pvItemToQueue | 指向要放入队列中的项目的指针。队列能够存储的项目的大小在创建队列时即已定义,因此 pvItemToQueue 中的这些字节将复制到队列存储区中。 |
xTicksToWait | 队列已满的情况下,任务处于阻塞状态且愿意等待队列中出现可用空间的最长时间。如果队列已满且 xTicksToWait 设置为 0,则调用将立即返回。时间以滴答周期为单位定义,如果需要转换为实际时间,可以使用 portTICK_PERIOD_MS 常量。如果 INCLUDE_vTaskSuspend 设置为 1,则将阻塞时间指定为 portMAX_DELAY 会导致任务无限期地阻塞(没有超时限制)。 |
返回 | 如果成功发布项目,返回 pdTRUE ,否则返回 errQUEUE_FULL 。 |
用法示例:
struct AMessage
{char ucMessageID; // 消息ID,使用1个字节的字符类型存储char ucData[20]; // 数据字段,使用20个字节的字符数组存储
} xMessage; // 定义一个AMessage类型的变量xMessageunsigned long ulVar = 10UL; // 定义一个无符号长整型变量ulVar并初始化为10void vATask(void *pvParameters)
{QueueHandle_t xQueue1, xQueue2; // 定义两个队列句柄变量struct AMessage *pxMessage; // 定义一个指向AMessage结构体的指针变量/* 创建一个能够存储10个无符号长整型(unsigned long)的队列 */xQueue1 = xQueueCreate(10, sizeof(unsigned long));/* 创建一个能够存储10个指向AMessage结构体的指针的队列这些结构体通过指针入队,因为它们相对较大 */xQueue2 = xQueueCreate(10, sizeof(struct AMessage*));/* ... 其他任务代码 ... */if (xQueue1 != NULL) // 检查xQueue1是否创建成功{/* 向xQueue1发送一个无符号长整型变量。如果队列满,最多等待10个滴答周期 */if (xQueueSend(xQueue1, (void*)&ulVar, (TickType_t)10) != pdPASS){/* 发送失败,即使等待了10个滴答周期 */}}if (xQueue2 != NULL) // 检查xQueue2是否创建成功{/* 向xQueue2发送一个指向AMessage结构体的指针。如果队列满,不等待 */pxMessage = &xMessage; // 将pxMessage指向xMessagexQueueSend(xQueue2, (void*)&pxMessage, (TickType_t)0);}/* ... 任务代码的其余部分 */
}
(4)项目发布到队列尾部:
函数原型:
BaseType_t xQueueSendFromISR(QueueHandle_t xQueue,const void *pvItemToQueue,BaseType_t *pxHigherPriorityTaskWoken);
- 此宏用于调用
xQueueGenericSendFromISR()
函数。包含此宏是为了向后兼容某些版本的 FreeRTOS,这些版本未提供xQueueSendToBackFromISR()
和xQueueSendToFrontFromISR()
宏。 - 将项目发布到队列尾部。可以在中断服务程序中安全使用此函数。
- 项目通过复制而非引用的方式入队,因此最好只将较小的项目放入队列,特别是从 ISR 调用时。在大多数情况下,最好存储一个指向正在排队的项目的指针。
参数/返回值 | 描述 |
---|---|
xQueue | 要向其中发布项目的队列的句柄。 |
pvItemToQueue | 指向要放入队列中的项目的指针。队列能够存储的项目的大小在创建队列时即已定义,因此 pvItemToQueue 中的这些字节将复制到队列存储区中。 |
pxHigherPriorityTaskWoken | 如果发送到队列会导致任务解除阻塞,并且解除阻塞的任务的优先级高于当前正在运行的任务,则 xQueueSendFromISR() 会将 *pxHigherPriorityTaskWoken 设置为 pdTRUE 。如果 xQueueSendFromISR() 将此值设置为 pdTRUE ,则应在中断退出前请求上下文切换。从 FreeRTOS V7.3.0 开始,pxHigherPriorityTaskWoken 为可选参数,可设置为 NULL。 |
返回 | 如果数据成功发送至队列,则返回 pdTRUE ,否则返回 errQUEUE_FULL 。 |
用法示例:
缓冲 IO 的用法示例(每次调用时 ISR 可获得多个值):
void vBufferISR(void)
{char cIn; // 定义一个字符变量,用于存储从缓冲区读取的字节BaseType_t xHigherPriorityTaskWoken; // 定义一个变量,用于指示是否有更高优先级的任务被唤醒/* 在ISR开始时,我们还没有唤醒任何任务。 */xHigherPriorityTaskWoken = pdFALSE;/* 循环直到缓冲区为空。 */do{/* 从缓冲区获取一个字节。 */cIn = portINPUT_BYTE(RX_REGISTER_ADDRESS); // 从指定的寄存器地址读取一个字节/* 将字节发送到队列。 */xQueueSendFromISR(xRxQueue, &cIn, &xHigherPriorityTaskWoken); // 将读取的字节发送到队列,同时更新是否有更高优先级任务被唤醒} while(portINPUT_BYTE(BUFFER_COUNT)); // 检查缓冲区计数器,如果非零则继续循环/* 现在缓冲区为空,如果有必要,我们可以切换上下文。 */if(xHigherPriorityTaskWoken){/* 实际使用的宏是特定于端口的。 */taskYIELD_FROM_ISR(); // 如果有更高优先级的任务被唤醒,则请求上下文切换}
}
(5)队列尾部入队数据项:
函数原型:
BaseType_t xQueueSendToBack(QueueHandle_t xQueue,const void * pvItemToQueue,TickType_t xTicksToWait);
- 这是一个调用 xQueueGenericSend() 的宏。它等同于 xQueueSend()。
- 从队列尾部入队一个数据项。数据项通过复制而非引用入队。不得从中断服务程序 。可以参考 xQueueSendToBackFromISR (),获取可在 ISR 中使用的替代方案。
参数/返回值 | 描述 |
---|---|
xQueue | 要向其中添加数据项的队列的句柄。 |
pvItemToQueue | 指向待入队数据项的指针。创建队列时定义了队列将保留的项的大小,因此固定数量的字节将从 pvItemToQueue 复制到队列存储区。 |
xTicksToWait | 如果队列已满,则任务应进入阻塞态等待队列上出现可用空间的最大时间。如果设置为 0,调用将立即返回。时间以滴答周期为单位定义,因此如果需要,应使用常量 portTICK_PERIOD_MS 转换为实时。如果 INCLUDE_vTaskSuspend 设置为 "1",则将阻塞时间指定为 portMAX_DELAY 会导致任务无限期地阻塞(没有超时)。 |
返回 | 如果成功发布项目,返回 pdTRUE ,否则返回 errQUEUE_FULL 。 |
用法示例:
struct AMessage
{char ucMessageID; // 消息ID,使用1个字节的字符类型存储char ucData[20]; // 数据字段,使用20个字节的字符数组存储
} xMessage; // 定义一个AMessage类型的变量xMessageunsigned long ulVar = 10UL; // 定义一个无符号长整型变量ulVar并初始化为10void vATask(void *pvParameters)
{QueueHandle_t xQueue1, xQueue2; // 定义两个队列句柄变量struct AMessage *pxMessage; // 定义一个指向AMessage结构体的指针变量/* 创建一个能够存储10个无符号长整型(unsigned long)的队列 */xQueue1 = xQueueCreate(10, sizeof(unsigned long));/* 创建一个能够存储10个指向AMessage结构体的指针的队列这些结构体通过指针入队,因为它们相对较大 */xQueue2 = xQueueCreate(10, sizeof(struct AMessage*));/* ... 其他任务代码 ... */if (xQueue1 != NULL) // 检查xQueue1是否创建成功{/* 向xQueue1发送一个无符号长整型变量。如果队列满,最多等待10个滴答周期 */if (xQueueSendToBack(xQueue1, (void*)&ulVar, (TickType_t)10) != pdPASS){/* 发送失败,即使等待了10个滴答周期 */}}if (xQueue2 != NULL) // 检查xQueue2是否创建成功{/* 向xQueue2发送一个指向AMessage结构体的指针。如果队列满,不等待 */pxMessage = &xMessage; // 将pxMessage指向xMessagexQueueSendToBack(xQueue2, (void*)&pxMessage, (TickType_t)0);}/* ... 任务代码的其余部分 */
}
(6)项目发布到队列尾部:
函数原型:
BaseType_t xQueueSendToBackFromISR(QueueHandle_t xQueue,const void *pvItemToQueue,BaseType_t *pxHigherPriorityTaskWoken);
- 此宏用于调用 xQueueGenericSendFromISR() 函数。
- 将项目发布到队列尾部。可以在中断服务程序中安全使用此函数。
- 项目通过复制而非引用的方式入队,因此最好只将较小的项目放入队列,特别是从 ISR 调用时。
参数/返回值 | 描述 |
---|---|
xQueue | 要向其中发布项目的队列的句柄。 |
pvItemToQueue | 指向要放入队列中的项目的指针。队列能够存储的项目的大小在创建队列时即已定义,因此 pvItemToQueue 中的这些字节将复制到队列存储区中。 |
pxHigherPriorityTaskWoken | 如果发送到队列会导致任务解除阻塞,并且解除阻塞的任务的优先级高于当前正在运行的任务,则 xQueueSendToBackFromISR() 会将 *pxHigherPriorityTaskWoken 设置为 pdTRUE 。如果 xQueueSendToBackFromISR() 将此值设置为 pdTRUE ,则应在中断退出前请求上下文切换。从 FreeRTOS V7.3.0 开始,pxHigherPriorityTaskWoken 为可选参数,可设置为 NULL。 |
返回 | 如果成功发送至队列,则返回 pdPASS ,否则返回 errQUEUE_FULL 。 |
用法示例:
缓冲 IO 的用法示例(每次调用时 ISR 可获得多个值):
void vBufferISR(void)
{char cIn; // 定义一个字符变量cIn,用于临时存储从接收寄存器读取的字节BaseType_t xHigherPriorityTaskWoken; // 定义一个BaseType_t类型的变量,用于指示是否有更高优先级的任务被唤醒/* 在ISR开始时,我们还没有唤醒任何任务。 */xHigherPriorityTaskWoken = pdFALSE; // 初始化xHigherPriorityTaskWoken为pdFALSE/* 循环直到缓冲区为空。 */do{/* 从指定的接收寄存器地址RX_REGISTER_ADDRESS读取一个字节。 */cIn = portINPUT_BYTE(RX_REGISTER_ADDRESS); // 从接收寄存器读取一个字节到cIn/* 将读取的字节发送到队列xRxQueue的尾部。 */xQueueSendToBackFromISR(xRxQueue, &cIn, &xHigherPriorityTaskWoken); // 将cIn发送到队列,如果操作导致更高优先级任务被唤醒,xHigherPriorityTaskWoken会被设置为pdTRUE} while(portINPUT_BYTE(BUFFER_COUNT)); // 检查缓冲区计数器,如果非零则继续循环/* 现在缓冲区为空,如果有必要,我们可以切换上下文。 */if(xHigherPriorityTaskWoken) // 如果有更高优先级的任务被唤醒{/* 使用特定于端口的宏请求上下文切换。 */taskYIELD_FROM_ISR(); // 请求上下文切换,以运行更高优先级的任务}
}
(7)队列头部入队数据项:
函数原型:
BaseType_t xQueueSendToFront( QueueHandle_t xQueue,const void * pvItemToQueue,TickType_t xTicksToWait );
- 此宏用于调用 xQueueGenericSend()。
- 从队列头部入队一个数据项。数据项通过复制而非引用入队。不得从中断服务程序调用此函数。请参阅 xQueueSendToFrontFromISR() 了解 可在 ISR 中使用的替代方法。
参数/返回值 | 描述 |
---|---|
xQueue | 要向其中添加数据项的队列的句柄。 |
pvItemToQueue | 指向待入队数据项的指针。创建队列时定义了队列将保留的项的大小,因此固定数量的字节将从 pvItemToQueue 复制到队列存储区。 |
xTicksToWait | 如果队列已满,则任务应进入阻塞态等待队列上出现可用空间的最大时间。如果设置为 0,调用将立即返回。时间以滴答周期为单位定义,因此如果需要,应使用常量 portTICK_PERIOD_MS 转换为实时。如果 INCLUDE_vTaskSuspend 设置为 "1",则将阻塞时间指定为 portMAX_DELAY 会导致任务无限期地阻塞(没有超时)。 |
返回 | 如果成功发布项目,返回 pdTRUE ,否则返回 errQUEUE_FULL 。 |
用法示例:
struct AMessage
{char ucMessageID; // 消息ID,使用1个字节的字符类型存储char ucData[20]; // 数据字段,使用20个字节的字符数组存储
} xMessage; // 定义一个AMessage类型的变量xMessageunsigned long ulVar = 10UL; // 定义一个无符号长整型变量ulVar并初始化为10void vATask(void *pvParameters)
{QueueHandle_t xQueue1, xQueue2; // 定义两个队列句柄变量struct AMessage *pxMessage; // 定义一个指向AMessage结构体的指针变量/* 创建一个能够存储10个无符号长整型(unsigned long)的队列 */xQueue1 = xQueueCreate(10, sizeof(unsigned long));/* 创建一个能够存储10个指向AMessage结构体的指针的队列这些结构体通过指针入队,因为它们相对较大 */xQueue2 = xQueueCreate(10, sizeof(struct AMessage*));/* ... 其他任务代码 ... */if (xQueue1 != NULL) // 检查xQueue1是否创建成功{/* 向xQueue1发送一个无符号长整型变量。如果队列满,最多等待10个滴答周期 */if (xQueueSendToFront(xQueue1, (void*)&ulVar, (TickType_t)10) != pdPASS){/* 发送失败,即使等待了10个滴答周期 */}}if (xQueue2 != NULL) // 检查xQueue2是否创建成功{/* 向xQueue2发送一个指向AMessage结构体的指针。如果队列满,不等待 */pxMessage = &xMessage; // 将pxMessage指向xMessagexQueueSendToFront(xQueue2, (void*)&pxMessage, (TickType_t)0);}/* ... 任务代码的其余部分 */
}
(8)项目发布到队列头部:
函数原型:
BaseType_t xQueueSendToFrontFromISR(QueueHandle_t xQueue,const void *pvItemToQueue,BaseType_t *pxHigherPriorityTaskWoken);
- 此宏用于调用 xQueueGenericSendFromISR() 函数。
- 将项目发布到队列头部。可以在中断服务程序中安全使用此函数。
- 项目通过复制而非引用的方式入队,因此最好只发送较小的项目, 或者发送指向该项目的指针。
参数/返回值 | 描述 |
---|---|
xQueue | 要向其中发布项目的队列的句柄。 |
pvItemToQueue | 指向要放入队列中的项目的指针。队列能够存储的项目的大小在创建队列时即已定义,因此 pvItemToQueue 中的这些字节将复制到队列存储区中。 |
pxHigherPriorityTaskWoken | 如果发送到队列会导致任务解除阻塞,并且解除阻塞的任务的优先级高于当前正在运行的任务,则 xQueueSendToFrontFromISR() 会将 *pxHigherPriorityTaskWoken 设置为 pdTRUE 。如果 xQueueSendToFrontFromISR() 将此值设置为 pdTRUE ,则应在中断退出前请求上下文切换。从 FreeRTOS V7.3.0 开始,pxHigherPriorityTaskWoken 为可选参数,可设置为 NULL。 |
返回 | 如果数据成功发送至队列,则返回 pdPASS ,否则返回 errQUEUE_FULL 。 |
用法示例:
void vBufferISR(void)
{char cIn; // 定义一个字符变量cIn,用于临时存储从接收寄存器读取的字节BaseType_t xHigherPriorityTaskWoken; // 定义一个BaseType_t类型的变量,用于指示是否有更高优先级的任务被唤醒/* 在ISR开始时,我们还没有唤醒任何任务。 */xHigherPriorityTaskWoken = pdFALSE; // 初始化xHigherPriorityTaskWoken为pdFALSE/* 从指定的接收寄存器地址RX_REGISTER_ADDRESS读取一个字节。 */cIn = portINPUT_BYTE(RX_REGISTER_ADDRESS); // 从接收寄存器读取一个字节到cInif (cIn == EMERGENCY_MESSAGE) // 检查读取的字节是否为紧急消息{/* 如果是紧急消息,将字节发送到队列的前面。 */xQueueSendToFrontFromISR(xRxQueue, &cIn, &xHigherPriorityTaskWoken); // 将cIn发送到队列的前面,如果操作导致更高优先级任务被唤醒,xHigherPriorityTaskWoken会被设置为pdTRUE}else // 如果不是紧急消息{/* 将字节发送到队列的后面。 */xQueueSendToBackFromISR(xRxQueue, &cIn, &xHigherPriorityTaskWoken); // 将cIn发送到队列的后面,如果操作导致更高优先级任务被唤醒,xHigherPriorityTaskWoken会被设置为pdTRUE}/* 检查是否发送到队列导致更高优先级的任务被唤醒。 */if (xHigherPriorityTaskWoken) // 如果有更高优先级的任务被唤醒{/* 使用特定于端口的宏请求上下文切换。 */taskYIELD_FROM_ISR(); // 请求上下文切换,以运行更高优先级的任务}
}
四、队列示例程序:
(1)动态创建队列:
动态创建两个任务,一个用于发送消息到队列,另一个用于从队列接收消息并通过串口打印。
#include "stm32f10x.h" // 包含STM32F10x系列微控制器的头文件
#include "FreeRTOS.h"
#include "task.h" // 包含任务相关函数的头文件,用于任务创建和管理。
#include "queue.h"
#include "stdio.h"
#include "uart.h"/***********************************
* @method 创建两个任务,一个用于发送消息到队列,
* 另一个用于从队列接收消息并通过串口打印。
* @Platform CSDN
* @author The_xzs
* @date 2025.1.25
************************************/
// 定义一个消息结构体,用于存储消息ID和消息数据
struct AMessage
{char ucMessageID; // 消息ID,使用1个字节的字符类型存储char ucData[20]; // 数据字段,使用20个字节的字符数组存储
};QueueHandle_t xQueue1, xQueue2; // 定义两个队列句柄变量//发送任务函数
void vSenderTask(void *pvParameters){unsigned long ulValueToSend = 0; // 用于发送的无符号长整型变量struct AMessage *pxMessage; // 指向AMessage结构体的指针for( ;; ){ulValueToSend++; // 每次循环递增发送值pxMessage = (struct AMessage *) pvPortMalloc(sizeof(struct AMessage)); // 动态分配内存if(pxMessage != NULL) // 检查内存分配是否成功{pxMessage->ucMessageID = (char)ulValueToSend; // 设置消息IDsnprintf(pxMessage->ucData, 20, "Message %lu", ulValueToSend); // 格式化消息数据// 将无符号长整型值发送到队列xQueue1if( xQueueSend( xQueue1, &ulValueToSend, portMAX_DELAY ) != pdPASS ){// 如果发送失败,添加错误处理代码printf("xQueue1 failed to send\r\n");}// 将AMessage结构体的指针发送到队列xQueue2if( xQueueSend( xQueue2, &pxMessage, portMAX_DELAY ) != pdPASS ){printf("xQueue2 failed to send\r\n");}}vTaskDelay(1000 / portTICK_RATE_MS); // 延时1秒}
}// 接收任务函数
void vReceiverTask( void *pvParameters )
{unsigned long ulReceivedValue; // 用于接收的无符号长整型变量struct AMessage *pxReceivedMessage; // 指向AMessage结构体的指针for( ;; ) // 无限循环,任务持续运行{// 从队列xQueue1接收数据if( xQueueReceive( xQueue1, &ulReceivedValue, portMAX_DELAY ) == pdPASS ){// 如果接收成功,通过串口打印接收到的值printf("Received from xQueue1: %lu\r\n", ulReceivedValue);}// 从队列xQueue2接收数据if( xQueueReceive( xQueue2, &pxReceivedMessage, portMAX_DELAY ) == pdPASS ){// 如果接收成功,通过串口打印接收到的消息ID和数据printf("Received from xQueue2: ID = %d, Data = %s\r\n", pxReceivedMessage->ucMessageID, pxReceivedMessage->ucData);vPortFree(pxReceivedMessage); // 释放动态分配的内存}}
}// 主函数
int main(void)
{Uart_Init(115200); DMA1_Init(); /* 创建一个能够存储10个无符号长整型(unsigned long)的队列 */xQueue1 = xQueueCreate( 10, sizeof( unsigned long ) );if( xQueue1 == NULL ) // 检查队列是否创建成功{/* 如果队列创建失败(内存分配失败),则不能使用这个队列 */while(1); // 无限循环,停止程序运行}/* 创建一个能够存储10个指向AMessage结构体的指针的队列这些结构体通过指针入队,因为它们相对较大 */xQueue2 = xQueueCreate( 10, sizeof( struct AMessage * ) );if( xQueue2 == NULL ) // 检查队列是否创建成功{/* 如果队列创建失败(内存分配失败),则不能使用这个队列 */while(1); // 无限循环,停止程序运行}/* 创建发送和接收任务 */xTaskCreate( vSenderTask, "Sender", 128, NULL, 2, NULL ); // 创建发送任务xTaskCreate( vReceiverTask, "Receiver", 128, NULL, 2, NULL ); // 创建接收任务// 启动任务调度器,FreeRTOS开始执行任务的地方vTaskStartScheduler(); return 0; // 理论上不会到达这里
}
效果:
(2)静态创建队列:
静态创建两个任务,一个用于发送消息到队列,另一个用于从队列接收消息并通过串口打印。
#include "stm32f10x.h" // 包含STM32F10x系列微控制器的头文件
#include "FreeRTOS.h"
#include "task.h" // 包含任务相关函数的头文件,用于任务创建和管理。
#include "queue.h"
#include "stdio.h"
#include "uart.h"/***********************************
* @method 静态创建两个任务,一个用于发送消息到队列,
* 另一个用于从队列接收消息并通过串口打印。
* @Platform CSDN
* @author The_xzs
* @date 2025.1.25
************************************/
// 定义一个消息结构体,用于存储消息ID和消息数据
struct AMessage
{char ucMessageID; // 消息ID,使用1个字节的字符类型存储char ucData[20]; // 数据字段,使用20个字节的字符数组存储
};QueueHandle_t xQueue1, xQueue2; // 定义两个队列句柄变量// 发送任务函数
void vSenderTask(void *pvParameters)
{unsigned long ulValueToSend = 0; // 用于发送的无符号长整型变量struct AMessage *pxMessage; // 指向AMessage结构体的指针for( ;; ){ulValueToSend++; // 每次循环递增发送值pxMessage = (struct AMessage *) pvPortMalloc(sizeof(struct AMessage)); // 动态分配内存if(pxMessage != NULL) // 检查内存分配是否成功{pxMessage->ucMessageID = (char)ulValueToSend; // 设置消息IDsnprintf(pxMessage->ucData, 20, "Message %lu", ulValueToSend); // 格式化消息数据// 将无符号长整型值发送到队列xQueue1if( xQueueSend( xQueue1, &ulValueToSend, portMAX_DELAY ) != pdPASS ){// 如果发送失败,添加错误处理代码printf("xQueue1 failed to send\r\n");}// 将AMessage结构体的指针发送到队列xQueue2if( xQueueSend( xQueue2, &pxMessage, portMAX_DELAY ) != pdPASS ){printf("xQueue2 failed to send\r\n");}}vTaskDelay(1000 / portTICK_RATE_MS); // 延时1秒}
}// 接收任务函数
void vReceiverTask( void *pvParameters )
{unsigned long ulReceivedValue; // 用于接收的无符号长整型变量struct AMessage *pxReceivedMessage; // 指向AMessage结构体的指针for( ;; ) // 无限循环,任务持续运行{// 从队列xQueue1接收数据if( xQueueReceive( xQueue1, &ulReceivedValue, portMAX_DELAY ) == pdPASS ){// 如果接收成功,通过串口打印接收到的值printf("Received from xQueue1: %lu\r\n", ulReceivedValue);}// 从队列xQueue2接收数据if( xQueueReceive( xQueue2, &pxReceivedMessage, portMAX_DELAY ) == pdPASS ){// 如果接收成功,通过串口打印接收到的消息ID和数据printf("Received from xQueue2: ID = %d, Data = %s\r\n", pxReceivedMessage->ucMessageID, pxReceivedMessage->ucData);vPortFree(pxReceivedMessage); // 释放动态分配的内存}}
}/* 用于保存队列数据结构的静态变量。 */
static StaticQueue_t xStaticQueue;// 主函数
int main(void)
{Uart_Init(115200); DMA1_Init(); // 静态创建队列所需的存储空间static uint8_t ucQueueStorage1[10 * sizeof(unsigned long)];static uint8_t ucQueueStorage2[10 * sizeof(struct AMessage *)];/* 静态创建一个能够存储10个无符号长整型(unsigned long)的队列 */xQueue1 = xQueueCreateStatic(10, sizeof(unsigned long), ucQueueStorage1, &xStaticQueue);if( xQueue1 == NULL ) // 检查队列是否创建成功{/* 如果队列创建失败(内存分配失败),则不能使用这个队列 */while(1); // 无限循环,停止程序运行}/* 静态创建一个能够存储10个指向AMessage结构体的指针的队列这些结构体通过指针入队,因为它们相对较大 */xQueue2 = xQueueCreateStatic(10, sizeof(struct AMessage *), ucQueueStorage2, &xStaticQueue);if( xQueue2 == NULL ) // 检查队列是否创建成功{/* 如果队列创建失败(内存分配失败),则不能使用这个队列 */while(1); // 无限循环,停止程序运行}/* 创建发送和接收任务 */xTaskCreate(vSenderTask, "Sender", 64, NULL, 2, NULL); // 创建发送任务xTaskCreate(vReceiverTask, "Receiver", 64, NULL, 2, NULL); // 创建接收任务// 启动任务调度器,FreeRTOS开始执行任务的地方vTaskStartScheduler(); return 0; // 理论上不会到达这里
}
效果:
五、队列创建时的常见问题:
(1)函数未定义:
- 如果 configSUPPORT_STATIC_ALLOCATION设置为 1,则 RTOS 对象可以 通过应用程序编写者提供的 RAM 创建。
- 如果 configSUPPORT_STATIC_ALLOCATION设置为 0,则 RTOS 对象 只能通过从 FreeRTOS 堆中分配的 RAM 创建。
- 如果 configSUPPORT_STATIC_ALLOCATION未定义,则默认为 0。
- 如果 configSUPPORT_STATIC_ALLOCATION设置为 1,则应用程序编写者还必须提供两个回调 函数:vApplicationGetIdleTaskMemory(),为 RTOS 空闲任务提供内存;(如果 configUSE_TIMERS设置为 1)vApplicationGetTimerTaskMemory(),为 RTOS 守护进程/定时器服务任务提供内存。
在task.h头文件可以看到该函数的声明:
打开task.c文件打开找到该函数所在位置:
向上滑动添加上该函数实现即可,如下:
(2)动态堆空间太大导致静态分配空间不足:
如下所示:
原因:
在 FreeRTOS 中,configTOTAL_HEAP_SIZE
定义的堆空间是用于动态内存分配的区域。除了这个堆空间之外,系统中的其他内存空间可以用于静态内存分配。
默认定义的动态内存分配堆空间为17KB(c8t6):
只剩余3KB的内存空间用于静态内存分配导致空间不足。
解决办法减小,动态内存分配堆空间大小即可。
(3)队列创建后程序下载没反应:
静态创建队列中函数最后一个参数不能为NULL,且必须指向 StaticQueue_t
类型的变量因为该参数用于保存队列数据结构的静态变量。
六、FreeRTOS教程示例代码下载:
FreeRTOS教程示例代码将会持续更新...
通过网盘分享的文件:FreeRTOS教程示例代码
链接: https://pan.baidu.com/s/1363h7hHmf8u2pjauwKyhtw?pwd=mi98 提取码: mi98
相关文章:

【FreeRTOS 教程 四】队列创建与发布项目到队列
目录 一、FreeRTOS队列: (1)队列介绍: (2)用户模型说明: (3)阻塞队列: 二、队列管理 API: (1)uxQueueMessagesWaiti…...

STM32项目分享:智能厨房安全检测系统
目录 一、前言 二、项目简介 1.功能详解 2.主要器件 三、原理图设计 四、PCB硬件设计 PCB图 五、程序设计 六、实验效果 七、资料内容 项目分享 一、前言 项目成品图片: 哔哩哔哩视频链接: STM32智能厨房安全检测系统 (资料分…...
2025美赛数学建模MCM/ICM选题建议与分析,思路+模型+代码
2025美赛数学建模MCM/ICM选题建议与分析,思路模型代码,详细更新见文末名片 一、问题A:测试时间:楼梯的恒定磨损(Archaeological Modeling) 适合专业:考古学、历史学、数学、机械工程 难度:中等…...

高并发问题的多维度解决之道
…...

Ubuntu环境 nginx 源码 编译安装
ubuntu 终端 使用 wget 下载源码 sudo wget http://nginx.org/download/nginx-1.24.0.tar.gz解压刚下载的源码压缩包 nginx-1.24.0.tar.gz sudo tar -zxvf nginx-1.24.0.tar.gz 解压完成 产生 nginx-1.24.0 目录 进入该目录 cd ./nginx-1.24.0 目录下有一个可执行文件 con…...

K8S中的数据存储之基本存储
基本存储类型 EmptyDir 描述:当 Pod 被调度到节点上时,Kubernetes 会为 Pod 创建一个空目录,所有在该 Pod 中的容器都可以访问这个目录。特点: 生命周期与 Pod 绑定,Pod 删除时,数据也会丢失。适用于临时…...

编码器和扩散模型
目录 摘要abstract1.自动编码器2.变分编码器(VAE)3.论文阅读3.1 介绍3.2 方法3.3 结论 4.总结参考文献 摘要 本周学习了自动编码器(AE)和变分自动编码器(VAE)的基本原理与实现,分析其在数据降维…...

PAT甲级-1024 Palindromic Number
题目 题目大意 一个非回文数,加上它的翻转数所得的和,进行k次,有可能会得到一个回文数。给出一个数n,限制相加次数为k次,如果小于k次就得到回文数,那么输出该回文数和相加的次数;如果进行k次还…...

FS8405 Release FS0B
复位场景:FS8405正常工作后,RSTB后期产生拉低复位信号。 1 故障与PGOOD、RSTB和FS0B引脚的联系 FS8405出现故障时,会对PGOOD、RSTB和FS0B引脚产生不同的影响,具体影响如下: 橙色标记,反应是不可配置的。…...

IGBT的损耗计算的学习【2025/1/24】
可以通过示波器实测IGBT电压电流波形,然后通过示波器的math功能将电压电流波形乘积后积分求损耗。 软开管:给了导通信号,但是电流并没有从此IGBT流过 IGBT(绝缘栅双极晶体管)的损耗主要分为 导通损耗 和 开关损耗 两部…...

Unity|小游戏复刻|见缝插针1(C#)
准备 创建Scenes场景,Scripts脚本,Prefabs预制体文件夹 修改背景颜色 选中Main Camera 找到背景 选择颜色,一种白中透黄的颜色 创建小球 将文件夹里的Circle拖入层级里 选中Circle,位置为左右居中,偏上&…...

No.1|Godot|俄罗斯方块复刻|棋盘和初始方块的设置
删掉基础图标新建assets、scenes、scripts文件夹 俄罗斯方块的每种方块都是由四个小方块组成的,很适合放在网格地图中 比如网格地图是宽10列,高20行 要实现网格的对齐和下落 Node2D节点 新建一个Node2D 添加2个TileMapLayer 一个命名为Board&…...

SSM框架探秘:Spring 整合 SpringMVC 框架
搭建和测试 SpringMVC 的开发环境: web.xml 元素顺序: 在 web.xml 中配置 DisPatcherServlet 前端控制器: <!-- 配置前端控制器 --> <servlet><servlet-name>dispatcherServlet</servlet-name><servlet-class>…...

2025.1.20——二、buuctf BUU UPLOAD COURSE 1 1 文件上传
题目来源:buuctf BUU UPLOAD COURSE 1 1 目录 一、打开靶机,查看信息 二、解题思路 step 1:上传一句话木马.php文件康康回显 step 2:蚁剑连接 三、小结 一、打开靶机,查看信息 这里提示到了文件会被上传到./uplo…...

【架构面试】三、高可用高性能架构设计
高可用高性能架构设计 面试要点引入:架构原理、分布式技术等是面试必考领域,高可用高性能需求考察频繁。面试常通过询问系统架构设计来考察能力,讲解架构设计过程就是证明系统高可用的过程,其中涉及SLA指标。SLA指标详解 定义与衡…...
11.渲染管线——光栅化阶段
光栅化阶段是渲染管线中的一个关键步骤,负责将3D模型转换成屏幕上的2D像素。用通俗易懂的方式来解释: 通俗解释:光栅化就像把3D模型“投影”到2D屏幕上 想象你是一个画家,正在把3D场景画到2D画布上: 3D模型到2D屏幕的…...

【数据分享】1929-2024年全球站点的逐月平均能见度(Shp\Excel\免费获取)
气象数据是在各项研究中都经常使用的数据,气象指标包括气温、风速、降水、湿度等指标!说到气象数据,最详细的气象数据是具体到气象监测站点的数据! 有关气象指标的监测站点数据,之前我们分享过1929-2024年全球气象站点…...
二叉树的深度
二叉树深度的定义: 二叉树的深度(高度)是指从根节点到最远叶子节点的最长路径上的节点数。例如,一个只有根节点的二叉树,其深度为1;如果根节点有两个子节点,且每个子节点又分别有两个子节点&…...
MySQL命令及用法(精华版)
目录 DDL(数据定义语言) 数据库操作 表操作 DML(数据操作语言) DQL(数据查询语言) 基本查询 条件查询 聚合函数 分组查询 排序查询 分页查询 DCL(数据控制语言) 用户…...

R语言学习笔记之高效数据操作
一、概要 数据操作是R语言的一大优势,用户可以利用基本包或者拓展包在R语言中进行复杂的数据操作,包括排序、更新、分组汇总等。R数据操作包:data.table和tidyfst两个扩展包。 data.table是当前R中处理数据最快的工具,可以实现快…...
Oracle查询表空间大小
1 查询数据库中所有的表空间以及表空间所占空间的大小 SELECTtablespace_name,sum( bytes ) / 1024 / 1024 FROMdba_data_files GROUP BYtablespace_name; 2 Oracle查询表空间大小及每个表所占空间的大小 SELECTtablespace_name,file_id,file_name,round( bytes / ( 1024 …...

CMake基础:构建流程详解
目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...

深入浅出深度学习基础:从感知机到全连接神经网络的核心原理与应用
文章目录 前言一、感知机 (Perceptron)1.1 基础介绍1.1.1 感知机是什么?1.1.2 感知机的工作原理 1.2 感知机的简单应用:基本逻辑门1.2.1 逻辑与 (Logic AND)1.2.2 逻辑或 (Logic OR)1.2.3 逻辑与非 (Logic NAND) 1.3 感知机的实现1.3.1 简单实现 (基于阈…...

零知开源——STM32F103RBT6驱动 ICM20948 九轴传感器及 vofa + 上位机可视化教程
STM32F1 本教程使用零知标准板(STM32F103RBT6)通过I2C驱动ICM20948九轴传感器,实现姿态解算,并通过串口将数据实时发送至VOFA上位机进行3D可视化。代码基于开源库修改优化,适合嵌入式及物联网开发者。在基础驱动上新增…...
redis和redission的区别
Redis 和 Redisson 是两个密切相关但又本质不同的技术,它们扮演着完全不同的角色: Redis: 内存数据库/数据结构存储 本质: 它是一个开源的、高性能的、基于内存的 键值存储数据库。它也可以将数据持久化到磁盘。 核心功能: 提供丰…...

macOS 终端智能代理检测
🧠 终端智能代理检测:自动判断是否需要设置代理访问 GitHub 在开发中,使用 GitHub 是非常常见的需求。但有时候我们会发现某些命令失败、插件无法更新,例如: fatal: unable to access https://github.com/ohmyzsh/oh…...
JavaScript 标签加载
目录 JavaScript 标签加载script 标签的 async 和 defer 属性,分别代表什么,有什么区别1. 普通 script 标签2. async 属性3. defer 属性4. type"module"5. 各种加载方式的对比6. 使用建议 JavaScript 标签加载 script 标签的 async 和 defer …...
Java中栈的多种实现类详解
Java中栈的多种实现类详解:Stack、LinkedList与ArrayDeque全方位对比 前言一、Stack类——Java最早的栈实现1.1 Stack类简介1.2 常用方法1.3 优缺点分析 二、LinkedList类——灵活的双端链表2.1 LinkedList类简介2.2 常用方法2.3 优缺点分析 三、ArrayDeque类——高…...

五、jmeter脚本参数化
目录 1、脚本参数化 1.1 用户定义的变量 1.1.1 添加及引用方式 1.1.2 测试得出用户定义变量的特点 1.2 用户参数 1.2.1 概念 1.2.2 位置不同效果不同 1.2.3、用户参数的勾选框 - 每次迭代更新一次 总结用户定义的变量、用户参数 1.3 csv数据文件参数化 1、脚本参数化 …...