FreeRTOS学习-队列管理

2023-05-16

1. 简介

在FreeRTOS中,提供了多种任务间通讯的机制,包括消息队列、信号量和互斥锁、事件组、任务通知,他们的总体特征如下图所示:

任务间通信机制总览

从图中可以看出,消息队列、信号量和互斥锁、事件组都是间接的任务间通信机制,而任务通知则是直接的通信方式,因此,任务通信的性能比其他几种间接的通信方式要高。

严格来说,互斥锁并不是一种任务间通信机制,将他们放在一块是因为互斥锁是基于信号量实现的,便于理解。在FreeRTOS中,信号量又是基于消息队列实现的,因此,先介绍消息队列,然后再介绍信号量和互斥锁。最后介绍事件组和任务通知。另外,由于FreeRTOSv10的实现更加易于理解并且具有更好的可维护性,因此这里将基于FreeRTOSv10的实现进行介绍。

消息队列(Queue)提供了任务与任务之间、任务与中断处理函数之间的交互机制。它是FreeRTOS中的基础功能,不能被裁剪。

2. 队列的特性

在FreeRTOS中,队列提供了任务间、任务和中断处理函数间的通信机制。

2.1.1. 数据存储方式

在FreeRTOS中,队列的长度指的是队列可以保存队列项(或称消息)的最大数量。

而每个队列项大小是在队列创建时设置的,且队列中所有队列项的大小一致

队列的实质就是一个FIFO,队列项从尾部写入,并从头部移除。另外,FreeRTOS的队列还支持从头部插入,也可以覆盖头部的数据。

需要注意的是,FreeRTOS在存储队列项时,采用的是副本方式而非引用,即原始数据入队需要一次拷贝动作。这么做的好处是:

  • 栈上的数据可以直接被入队;
  • 消息生产者可以继续使用已经发送的原始数据;
  • 这种方式依然可以当做引用方式来使用,即传入原始数据的指针即可;
  • 由FreeRTOS来管理消息队列的内存,对用户透明;
  • 由于某些支持内存保护的系统,使用引用的方式可能会导致两个任务之间无法正常的传递数据。

2.1.2. 被多个任务访问

一个队列可以被任意个任务和中断处理函数访问。

通常而言,一个队列有多个生产者,但只有一个消费者。

2.1.3. 阻塞读(Blocking on queue reads)

FreeRTOS的队列支持任务以阻塞的方式等待队列中的消息。并且支持多个任务同时等待同一个队列的消息,其阻塞和唤醒的方式如下:

  • 阻塞:当队列中的数据为空时,所有请求消息出队的任务会被阻塞,即变为Blocked状态。它们会从Ready任务队列迁移到Event任务队列。如果调用者还设置了等待时限,那任务还会被放入Delayed任务队列。
  • 唤醒:当数据就绪时,如果有多个任务在等待,那么只有最高优先级的任务会被唤醒,重新加入到Ready任务队列中。当优先级相同时,则等待时间最长的任务会被唤醒。如果设置了等待时限,等待超时也会将任务唤醒。

2.1.4. 阻塞写(Blocking on queue write)

与阻塞读类似,若队列已满,任何向该队列发送消息的任务都会被阻塞。

2.1.5. 多队列阻塞(Blocking on multiple queues)

有的时候,一个任务可能需要同时监控多个消息队列,那么可以将队列组织成队列组(Queue Set)。

与自行用多个队列来实现不同,它并不需要用户编写多个队列的轮询机制,因为这个队列组是由FreeRTOS来管理的。

3. 创建队列

动态创建队列的函数原型:需要设置configSUPPORT_DYNAMIC_ALLOCATION = 1

#define xQueueCreate( uxQueueLength, uxItemSize ) xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), ( queueQUEUE_TYPE_BASE ) )

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

该API用于创建一个新的队列实例,并返回该队列的句柄。在FreeRTOS的实现中,每当创建队列时,需要为该队列创建两块内存:一块用于存储队列的元信息;另一块用于存储队列接受的实际消息。

其中,uxQueueLength表示该队列的长度;uxItemSize表示队列项的大小。

另外,FreeRTOS还支持静态创建队列的方式,这里就不多做介绍了。

可以看到,队列的创建实际上调用的是一个通用的创建函数xQueueGenericCreate()。该函数通过参数ucQueueType来判断需要创建的队列是何种类型,这些类型包括:

  • 普通队列;
  • 队列组;
  • 互斥锁;
  • 计数信号量;
  • 二值信号量;
  • 递归互斥锁。

这些会在后续陆续介绍。

下面给出该函数的实现活动图:

创建队列活动图

  1. 申请队列所需内存:

    {
        参数检查:Assert `uxQueueLength > 0`。
    
        如果消息长度为0(`uxItemSize == 0`),则设置消息存储区域Bytes为0(xQueueSizeInBytes = 0)。
        否则,设置消息存储区域Bytes为队列长度*消息长度(`xQueueSizeInBytes = uxQueueLength * uxItemSize`)。
    
        申请队列所需的内存,长度为队列元信息结构体长度和消息存储区域长度的总和(`pvNewQueue = pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes )`)。
    
        如果申请成功(`pxNewQueue != NULL`),则跳转到第二步;
        否则直接返回`pxNewQueue`。
    }
    
  2. 初始化队列信息(static void prvInitialiseNewQueue( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, uint8_t *pucQueueStorage, const uint8_t ucQueueType, Queue_t *pxNewQueue )):如以下活动图所示

    初始化队列信息活动图
    复位队列请参看复位队列。

  3. 返回队列的句柄。

4. 队列控制

为了更好的说明队列控制API,我们假设抓取了某个队列在某个时刻的快照,它的存储方式如下图所示:

队列的快照
所有队列控制都是基于这个视图进行的。

4.1. 入队操作

FreeRTOS提供了多种消息入队操作,它们的函数原型如下:

#define xQueueSendToFront( xQueue, pvItemToQueue, xTicksToWait ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_FRONT )
#define xQueueSendToBack( xQueue, pvItemToQueue, xTicksToWait ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTickToWait ), queueSEND_TO_BACK )
#define xQueueSend( xQueue, pvItemToQueue, xTicksToWait ) xQueueGenericSend( ( xQueue ), ( pvItemToQueu ), ( xTicksToWait ), queueSEND_TO_BACK )
#define xQueueOverwrite( xQueue, pvItemToQueue ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), 0, queueOVERWRITE )

BaseType_t xQueueGenericSend( QueueHandle_t xQueue, const void * const pvItemToQueue, TickType_t xTicksToWait, const BaseType_t xCopyPosition ) PRIVILEGED_FUNCTION;

可以看到,FreeRTOS的队列可以支持头部、尾部入队,还能支持头部消息的重写。

他们的实现都是通过xQueueGenericSend()完成的,由参数来决定具体的行为。那我们便先来看看这个接口。它的主要职责是将一个消息放入队列中,如前文所述,这是通过内存拷贝来完成的,而不是引用。

pvItemToQueue便是需要入队的消息的指针,它的值会被拷贝到队列的消息存储区域。

前面提到,队列是可以实现阻塞写入的,而xTicksToWait便是阻塞的时间。特别地,如果xTicksToWait设置为0,且队列已满,则直接返回。

xCopyPosition表明了消息被插入的位置。可以用来实现消息优先级

如果在预设时间xTicksToWait内完成了入队,则返回pdTRUE,否则返回errQUEUE_FULL

在介绍具体实现之前,需要明确的是,FreeRTOS的互斥锁,信号量都是通过队列来实现的,而互斥锁的解锁以及信号量的Give都是通过这个消息入队函数来完成的。而这里只关注消息队列实现的细节,与信号量和互斥锁相关的细节会在其他相应的小节中介绍。因为入队操作涉及到任务状态切换等行为,需要先理解几个关键的概念和行为:

关键概念:

  • 队列的计数锁(读计数锁cRxLock和写计数锁cTxLock):这是为了保证消息队列中的事件任务队列不会因为并行访问而导致其一致性被破坏。并且这些锁是可以计数的,它表示在加锁状态下,入队和出队的消息数量。例如读计数锁的数值表示:在读加锁状态下,出队消息的数量;而写计数锁的数值则表示:在写加锁状态下,入队消息的数量。在加锁的情况下,消息可以入队或出队,但是事件任务队列不可以被更新。加锁和解锁的实现在关键行为中介绍。
  • Timeout信息:记录了系统当前Tick溢出的次数,以及当前的系统Tick。这个信息用于判断等待队列消息的任务是否发生了超时。它通过vTaskSetTimeOutState()vTaskInternalSetTimeOutState()获取当前系统Tick信息作为准备进入Delayed状态的时刻,在xTaskCheckForTimeOut()中用于检查是否超时。

关键行为:

  • 将任务加入到事件任务队列(void vTaskPlaceOnEventList( List_t * const pxEventList, const TickType_t xTicksToWait )):这实际上是任务管理的接口,但只能被FreeRTOS API的实现调用,这在任务管理章节中介绍过。该函数只能在中断屏蔽、或调度器被挂起且队列加锁的情况下调用。因为xEventListItemxItemValue默认值是portMAX_PRIORITIES减任务的优先级,因此队列中的任务按照任务优先级排序。

    {
        参数检查:Assert `pxEventList != NULL`。
    
        将任务加入到事件任务队列(`vListInsert( pxEventList, &( pxCurrentTCB->xEventListItem )`)。
    
        并且将任务加入Delayed任务队列(`prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE )`)。
    }
    
  • 将任务从事件任务队列中唤醒(BaseType_t xTaskRemoveFromEventList( const List * const pxEventList ) PRIVILEGED_FUNCTION):这也是任务管理的接口,但它只能被FreeRTOS API的实现调用。该接口假设事件任务队列中的任务是按照优先级来排序的。如果有更高优先级的任务被它唤醒,则返回pdTRUE

    {
        从队首获取一个任务(`pxUnblockedTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxEventList )`)。
        将该任务从事件任务队列中删除(`uxListRemove( &( pxUnblockedTCB->xEventListItem ) )`)。
    
        如果调度器未挂起(`uxSchedulerSuspended == pdFALSE`):
        {
            将任务从当前的状态(Blocked)任务队列中删除(`uxListRemove( &( pxUnblockedTCB->xStateListItem ) )`)。
            将其添加到Ready任务队列中(`prvAddTaskToReadyList( pxUnblockedTCB )`)。
        }
        否则,将任务加入到Pended任务队列中。
    
        如果唤醒的任务优先级比当前运行的优先级高(`pxUnblockedTCB->uxPriority > pxCurrentTCB->uxPriority`),设置调度标志`xYieldPending = pdTRUE`并设置返回值为`pdTRUE`(`xReturn = pdTRUE`);否则设置返回值为`pdFALSE`。
    
        (仅开启USE_TICKLESS_IDLE)正常来说,系统下一次唤醒任务时刻`xNextTaskUnblockTime`会在更新系统tick时自动更新;但为了Tickless idle能够更快的进入低功耗模式,所以在这里就更新了系统唤醒任务的时刻(`prvResetNextTaskUnblockTime()`)。
    
        返回`xReturn`。
    }
    
  • 检查超时状态(BaseType_t xTaskCheckForTimeOut( TimeOut_t * const pxTimeOut, TickType_t * const pxTicksToWait )):检查任务是否超时或被中止等待,如果是则返回pdTRUE,否则返回pdFALSE

    {
        参数检查:Assert `pxTimeOut != NULL`,`pxTicksToWait > 0`
    
        进入临界区(`taskENTER_CRITICAL()`)。
    
        获取系统Tick(`xConstTickCount = xTickCount`)。
        获取自上一次时间以来的时间流逝(`xElapsedTime = xConstTickCount - pxTimeOut->xTimeOnEntering`)。
    
        (仅开启xTaskAbortDelay)如果用户中止了延迟(`pxCurrentTCB->ucDelayAborted != pdFALSE`):
        {
            清除延迟中止标志(`pxCurrentTCB->ucDelayAborted = pdFALSE`)。
            返回值为超时(`xReturn = pdTRUE`)。
        }
    
        (仅开启vTaskSuspend)如果等待时限为无限时长(`*pxTicksToWait == portMAX_DELAY`),则应该返回`pdFALSE`(`xReturn = pdFALSE`)。
    
        如果发生了Tick溢出且当前系统Tick大于Timeout的tick(`( xNumOfOverflows != pxTimeOut->xOverflowCount ) && ( xConstTickCount >= pxTimeOut->xTImeOnEntering )`),此时已经溢出`xTickToWait`所能表示的数量了,因此肯定已经超时,设置`xReturn = pdTRUE`。
        否则,如果`xElapsedTime < *pxTicksToWait`:(如果`xConstTickCount < pxTimeOut->xTimeOnEntering`,那么`xElapsedTime`必定会大于`*pxTicksToWait`,所以不会选择这条分支)
        {
            对于这种情况,一定是没有发生Tick溢出(否则该条件不会成立)。
            此时没发生溢出,调整等待时间`*pxTicksToWait -= xElapsedTime`。
            重新获取Timeout信息(`vTaskInternalSetTimeOutState( pxTimeOut )`*)
    
            返回未超时(`xReturn = pdFALSE`)。
        }
        否则,都是属于超时的情况:
        {
            设置等待时间为0(`*pxTicksToWait = 0`)。
            返回超时(`xReturn = pdTRUE`)。
        }
    
        退出临界区(`taskEXIT_CRITICAL()`)。
    
        返回`xReturn`。
    }
    
  • 将消息拷贝到队列中(static BaseType_t prvCopyDataToQueue( Queue_t * const pxQueue, const void *pvItemToQueue, const BaseType_t xPosition )):该函数只能在临界区中被调用。对于普通队列,这是一个将消息拷贝到队列的操作;对于互斥锁而言,这是一个释放锁的操作。返回值表示是否需要发起一次调度。活动图如下:

    将消息拷贝到队列中的活动图

  • 队列消息空、满状态判断(static BaseType_t prvIsQueueEmpty( const Queue_t *pxQueue )static BaseType_t prvIsQueueFull( const Queue_t * pxQueue )):

    队列空:
    {
        进入临界区(`taskENTER_CRITICAL()`)。
        如果消息未空,即`pxQueue->uxMessagesWaiting == 0`,设置`xReturn = pdTRUE`。
        否则设置`xReturn = pdFALSE`。
    
        退出临界区(`taskEXIT_CRITICAL()`)。
    
        返回`xReturn`。
    }
    
    队列满:
    {
        进入临界区(`taskENTER_CRITICAL()`)。
        如果消息满,即`pxQueue->uxMessageWaiting == pxQueue->uxLength`,设置`xReturn = pdTRUE`。
        否则设置`xReturn = pdFALSE`。
    
        退出临界区(`taskEXIT_CRITICAL()`)。
    }
    
  • 队列加锁(#define prvLockQueue( pxQueue ))和解锁(static void prvUnlockQueue( Queue_t * const pxQueue )):prvLockQueue()宏会进入临界区完成队列的读/写加锁;重点看看解锁操作:解锁时,调度器必须被挂起。

    {1. 处理队列的写计数锁)
        进入临界区(`taskENTER_CRITICAL()`)。
        获取队列的写计数锁(`cTxLock = pxQueue->cTxLock`)。
        循环处理队列写计数锁加锁时入队的消息(`while ( cTxLock > queueLOCKED_UNMODIFIED )`):
        {
            (仅开启USE_QUEUE_SETS):
            {
                如果队列在队列组中(`pxQueue->pxQueueSetContainer != NULL`):
                {
                    将消息通知到队列组,如果发现高优先级任务被唤醒(`prvNotifyQueueSetContainer( pxQueue, queueSEND_TO_BACK ) != pdFALSE`),则设置调度标志(`vTaskMissedYield()`),使得在下一次调度时进行任务切换。
                }
                否则,即队列不在任何队列组中:
                {
                    如果该队列的阻塞读队列不为空(`listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE`):
                    {
                        将任务从事件任务队列中删除,如果有高优先任务被唤醒(`xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE`),则设置调度标志(`vTaskMissedYield()`),使得在下一次调度时进行任务切换。
                    }
                    否则,即等待队列为空,则跳出循环。
                }
            }
    
            队列写计数锁减一(`--cTxLock`)。
        }
    
        释放队列写计数锁(`pxQueue->cTxLock = queueUNLOCKED`)。
    
        退出临界区(`taskEXIT_CRITICAL()`)。
    
        (2. 处理队列的读计数锁)
        进入临界区(`taskENTER_CRITICAL()`)。
        获取队列的读计数锁(`cRxLock = pxQueue->cRxLock`)。
    
        循环处理队列读计数锁加锁时出队的消息(`while ( cRxLock > queueLOCKED_UNMODIFIED )`):
        {
            如果队列的阻塞写队列不为空(`listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE`):
            {
                将任务从事件任务队列中删除,如果有更高优先级的任务被唤醒(`xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE`),则设置调度标志(`vTaskMiessdYield()`)。
    
                队列读计数锁减一(`--cRxLock`)。
            }
            否则,即阻塞写队列为空,则跳出循环。
        }
    
        释放队列读计数锁(`pxQueue->cRxLock = queueUNLOCKED`)。
    
        退出临界区(`taskEXIT_CRITICAL()`)。
    }
    

在了解了以上的关键概念和关键行为之后,就可以来看看他的具体实现了。其实现活动图如下:

消息入队的实现

可以看到,这个函数的实现是目前看到的最复杂的函数实现了(互斥锁的实现会更复杂一些),这是因为消息入队涉及到了临界区的操作,调度器的挂起和恢复,队列的加锁和解锁,任务队列的操作等。

需要注意的是,这个接口只能在任务上下文中调用,如果在中断上下文中,则需要调用ISR的版本:

#define xQueueSendToFrontFromISR( xQueue, pvItemToQueue, phHigherPriorityTaskWoken ) xQueueGenericSendFromISR( ( xQueue ), ( pvItemToQueue ), ( pxHigherPriorityTaskWoken ), queueSEND_TO_FRONT )
#define xQueueSendToBackFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken ) xQueueGenericSendFromISR( ( xQueue ), ( pvItemToQueue ), ( pxHigherPriorityTaskWoken ), queueSEND_TO_BACK )
#define xQueueSendFromISR( xQueue, pvItemToQueue, phHigherPriorityTaskWoken ) xQueueGenericSendFromISR( ( xQueue ), ( pvItemToQueue ), ( pxHigherPriorityTaskWoken ), queueSEND_TO_BACK )
#define xQueueOverwriteFromISR( xQueue, pvItemToQueue), pxHigherPriorityTaskWoken ) xQueueGenericSendFromISR( ( xQueue ), ( pvItemToQueue ), ( pxHigherPriorityTaskWoken ), queueOVERWRITE )

BaseType_t xQueueGenericSendFromISR( QueueHandle_t xQueue, const void * const pvItemToQueue, BaseType_t * const pxHigherPriorityTaskWoken, const BaseType_t xCopyPosition ) PRIVILEGED_FUNCTION;

中断版本通常更简洁,并输出pxHigherPriorityTaskWoken用于反馈任务唤醒信息,详情可参看在ISR中使用FreeRTOS API。

来看看中断版本的实现:与任务上下文的版本不同的是,它不会被阻塞,且不会在内部触发调度。另外,该实现仅用于普通队列的消息入队,而信号量和互斥锁使用的是xQueueGiveFromISR(),详情在归还信号量小节中介绍。

{
    参数检查:Assert `pxQueue != NULL`,`!( ( pvItemToQueue == NULL) && ( pxQueue->uxItemSize != 0 ) )`,`!( ( xCopyPosition == queueOVERWRITE ) && ( pxQueue->uxLength != 1 ) )`

    检查优先级是否满足需求(`portASSERT_IF_INTERRUPT_PRIORITY_INVALID()`)。

    进入临界区并保存当前中断优先级屏蔽值(`uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR()`)。

    如果队列没满(`pxQueue->uxMessagesWaiting < pxQueue->uxLength`)或入队方式为重写(`xCopyPosition == queueOVERWRITE`):
    {
        获取队列计数写锁(`cTxLock = pxQueue->cTxLock`)。

        (由于信号量和互斥锁的解锁不会调用该接口,所以这里只考虑队列的情况)将消息入队(`prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition )`)。

        如果队列没有加计数写锁(`cTxLock == queueUNLOCKED`):
        {
            (仅开启USE_QUEUE_SETS):
            {
                如果队列在某个队列组内(`pxQUeue->pxQueueSetContainer != NULL`):
                {
                    将消息给到对应的队列组,如果有高优先级的任务被唤醒(`prvNotifyQueueSetContainer( pxQueue, xCopyPosition ) != pdFALSE`),则设置`*pxHigherPriorityTaskWoken = pdTRUE`。
                }
                否则:
                {
                    如果有任务在等待该队列的消息(`listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE`):
                    {
                        将任务从事件任务队列中移除,如果唤醒了更高优先级的任务(`xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE`),设置`*pxHigherPriorityTaskWoken = pdTRUE`。
                    }
                }
            }
        }
        否则,即队列被加了计数写锁:
        {
            将计数写锁増1(`pxQueue->cTxLock = cTxLock + 1`)。
        }

        设置返回值`xReturn = pdPASS`。
    }
    否则,队列已满:
    {
        设置返回值`xReturn = errQUEUE_FULL`。
    }

    退出临界区(`portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus )`)。

    返回`xReturn`。
}

4.2. 出队操作

在FreeRTOSv9时,出队相关操作的函数原型:

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

BaseType_t xQueueGenericReceive( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait, const BaseType_t xJustPeek ) PRIVILEGED_FUNCTION;

与入队不同的是,消息永远只能从队列的首部出队。出队的接口方式与入队类似,都是通过宏定义来决定出队的方式,最终调用xQueueGenericReceive()接口实现。下面便介绍一下该接口。

该接口的主要职责是从队列中获取一个消息。这个获取行为也需要一次内存拷贝

pvBuffer是指向接受消息的缓存的指针,接收缓存的内存由调用者管理。

如前面提到,FreeRTOS的队列还支持阻塞读,而xTicksToWait便是本次读取动作的等待时限。特别地,如果xTicksToWait为0,且队列为空,则直接返回。

xJustPeek如果为pdTRUE,表示本次读取操作仅仅是消息拷贝,并不会导致消息从队列中出队;否则需要将消息从队列中移除。

如果在预设的时限内成功获取到消息,则返回pdTRUE,否则返回pdFALSE

FreeRTOSv10后,xQueueGenericReceive()已经被拆分为两个单独的接口,函数原型如下:

BaseType_t xQueueReceive( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait ) PRIVILEGED_FUNCTION;
BaseType_t xQueuePeek( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait ) PRIVILEGED_FUNCTION;

在介绍具体的实现之前,需要了解一下关键的行为:

  • 将队列中的数据出队(static void prvCopyDataFromQueue( Queue_t * const pxQueue, void * const pvBuffer )):先移动队首位置,再将数据拷贝出来。因此,复位队列时,队首的位置指向最后一个消息。

    {
        如果消息长度不为0(`pxQUeue->uxItemSize != 0`):
        {
            移动队首到下一个消息位置(`pxQueue->u.pcReadFrom += pxQueue->uxItemSize`)。
    
            如果队首已经溢出(`pxQueue->u.pcReadFrom >= pxQueue->pcTail`),则重置到队首(`pxQueue->u.pcReadFrom = pxQueue->pcHead`)。
    
            拷贝数据(`memcpy( ( void * ) pvBuffer, ( void * ) pxQueue->u.pcReadFrom, ( size_t ) pxQueue->uxItemSize )`)。
        }
    }
    

下面看看xQueueReceive()函数的具体实现:用活动图表示如下

出队的活动图
需要注意的是,该接口也只能在任务上下文中调用,如果在中断上下文中,则需要调用ISR的版本:

BaseType_t xQueuePeekFromISR( QueueHandle_t xQueue, void * const pvBuffer ) PRIVILEGED_FUNCTION;

BaseType_t xQueueReceiveFromISR( QueueHandle_t xQueue, void * const pvBuffer, BaseType_t * const pxHigherPriorityTaskWoken ) PRIVILEGED_FUNCTION;

下面看看xQueuePeekFromISR()具体的实现:

{
    初始化`pxQueue = ( Queue_t * ) xQueue`。

    参数校验:Assert `pxQueue != NULL`,`!( ( pvBuffer == NULL ) && ( pxQueue->uxItemSize != 0 ) )`,`pxQueue->uxItemSize != 0`(因为不能Peek信号量和互斥锁)

    检查中断优先级适合符合要求(`portASSERT_IF_INTERRUPT_PRIORITY_INVALID()`)。

    进入临界区,并保存原有的中断屏蔽优先级(`uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR()`)。
    {
        如果队列中的消息不为空(`pxQueue->uxMessagesWaiting > 0`):
        {
            记录当前的队首位置(`pcOriginalReadPosition = pxQueue->u.pcReadFrom`)。
            将消息拷出队列(`prvCopyDataFromQueue( pxQueue, pvBuffer )`)。
            恢复队首位置(`pxQueue->u.pcReadFrom = pcOriginalReadPosition`)。

            设置返回值`xReturn = pdPASS`。
        }
        否则,即消息队列为空:设置返回值`xReturn = pdFAIL`。
    }
    退出临界区,恢复原因的中断屏蔽优先级(`portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus )`)。

    返回`xReturn`。
}

可以看到,这个实现是任务上下文中的简化版本,并且只考虑了Peek的情况。

下面看看xQueueReceiveFromISR()的实现:

{
    初始化`pxQueue = ( Queue_t * ) xQueue`。

    参数校验:Assert `pxQueue != NULL`,`!( ( pvBuffer == NULL ) && ( pxQueue->uxItemSize != 0 ) )`。

    检查中断优先级是否符合要求(`portASSERT_IF_INTERRUPT_PRIORITY_INVALID()`)。

    进入临界区,并保存中断优先级屏蔽状态(`portSET_INTERRUPT_MASK_FROM_ISR()`)。
    {
        获取队列的消息数量(`uxMessagesWaiting = pxQueue->uxMessagesWaiting`)。

        如果队列的存在消息(`uxMessagesWaiting > 0`):
        {
            获取队列的计数读锁(`cRxLock = pxQueue->cRxLock`)。

            将数据从队列中拷出(`prvCopyDataFromQueue( pxQueue, pvBuffer )`)。
            将队列的消息计数减一(`pxQueue->uxMessagesWaiting = uxMessagesWaiting - 1`)。

            如果队列处于未加锁状态(`cRxLock == queueUNLOCKed`);
            {
                如果有任务正在等待入队消息(`listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE`):
                {
                    将当前任务从事件任务队列中唤醒,如果唤醒了高优先级的任务(`xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE`),则设置高优先级任务唤醒标志`*pxHigherPriorityTaskWoken = pdTRUE`。
                }
            }
            否则,即队列已加锁:
            {
                读计数锁加一(`pxQueue->cRxLock = cRxLock + 1`)。
            }

            设置返回`xReturn = pdPASS`。
        }
        否则,即队列无消息:
        {
            设置返回`xReturn = pdFAIL`。
        }
    }

    退出临界区,并恢复中断优先级屏蔽状态(`portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus )`)。

    返回`xReturn`。
}

可以看到,中断实现比任务上下文的实现更加简洁,因为不需要考虑阻塞的状态。

4.3. 查询队列状态

FreeRTOS的队列支持查询队列中消息的数量,以及剩余空间,函数原型如下:

UBaseType_t uxQueueMessagesWaiting( const QueueHandle_t xQueue ) PRIVILEGED_FUNCTION;

UBaseType_t uxQueueSpacesAvailable( const QueueHandle_t xQueue ) PRIVILEGED_FUNCTION;

其中,uxQueueMessagesWaiting()将返回队列中等待获取的消息数量;uxQueueSpacesAvailable()则返回队列中剩余的空闲槽数量。在具体实现时,这两个函数需进入临界区读取任务的消息计数进而计算出结果。另外,还提供了ISR的版本:

BaseType_t xQueueIsQueueEmptyFromISR( const QueueHandle_t xQueue ) PRIVILEGED_FUNCTION;
BaseType_t xQueueIsQueueFullFromISR( const QueueHandle_t xQueue ) PRIVILEGED_FUNCTION;
UBaseType_t uxQueueMessagesWaitingFromISR( const QueueHandle_t xQueue ) PRIVILEGED_FUNCTION;

这三个接口只能在ISR或者临界区使用。

4.4. 复位队列

FreeRTOS支持队列的一键复位,函数原型:

#define xQueueReset( xQueue ) xQueueGenericReset( xQueue, pdFALSE )

BaseType_t xQueueGenericReset( QueueHandle_t xQueue, BaseType_t xNewQueue ) PRIVILEGED_FUNCTION;

所谓复位,指的是将队列恢复到创建时的初始状态。

xQueueReset()实际上通过xQueueGenericReset()实现。

其中xNewQueue指示该队列是否是一个新创建的队列。该函数永远返回pdPASS

下面来看看其实现:用活动图表示如下所示

复位队列的活动图

4.5. 调试相关

FreeRTOS提供了内核调试相关的接口,例如队列名注册和注销,获取队列名等。函数原型:需要设置configQUEUE_REGISTRY_SIZE > 0

void vQueueAddToRegistry( QueueHandle_t xQueue, const char *pcName ) PRIVILEGED_FUNCTION;
void vQueueUnregisterQueue( QueueHandle_t xQueue ) PRIVILEGED_FUNCTION;

const char *pcQueueGetName( QueueHandle_t xQueue ) PRIVILEGED_FUNCTION;

将队列加入到注册表可以让内核调试工具感知到队列(或信号量,互斥锁)的存在,并且可以为队列赋予名字pcName。需要注意的是,注册表只记录了名字字符串的指针,因此需要注意传入的参数不能是栈上的变量。

将队列注册到队列注册表后,就可以通过pcQueueGetName()获取该队列的名字,否则会返回NULL

队列注册表的实现很简单,只是单纯的数组xQueueRegistry的操作。

5. 删除队列

FreeRTOS支持动态删除队列,函数原型如下:

void vQueueDelete( QueueHandle_t xQueue ) PRIVILEGED_FUNCTION;

在删除队列时,FreeRTOS会将该队列所使用的两块内存全部释放,即元信息和队列的数据。

下面来看看具体的实现:

{
    初始化`pxQueue = ( Queue_t * ) xQueue`。

    参数检查:Assert `pxQueue != NULL`

    (仅开启configQUEUE_REGISTRY_SIZE > 0)将队列从注册表中注销(`vQueueUnregisterQueue( pxQueue )`)。

    如果队列是动态申请的(`pxQueue->ucStaticallyAllocated == pdFALSE`),释放内存(`vPortFree( pxQueue )`)。
}

6. 队列的应用

对于消息的内存占用较大的情况,推荐使用引用的方式,即通过传递消息的指针来进行通信。

对于消息类型不固定的情况,需要结合结构体与引用的方式,由使用者自身来确定如何解析数据。

7. 队列组

如前文所说,FreeRTOS的队列支持队列组的功能,从而使得任务对于这个队列组进行阻塞读/写操作。本小节则介绍这个队列组的使用。

队列组的使用需要注意一下几点问题:

  • 除非是非常有必要,很少会使用到这个功能;
  • 对于在组内使用了互斥锁的情况,并不会导致优先级的继承;
  • 队列组的长度为所有队列的长度之和,因此不适用于最大值很大的计数信号量;
  • 除非是通过xQueueSelectFromSet()获取的句柄,不应该单独对某个队列进行读取操作。

7.1. 创建队列组

函数原型:

QueueSetHandle_t xQueueCreateSet( const UBaseType_t uxEventQueueLength ) PRIVILEGED_FUNCTION;

在创建了队列组后,便可以往该组里添加队列(或信号量、互斥锁)。

其中,uxEventQueueLength指的是队列组中所有队列的长度的总和,其中二值信号量和互斥锁的长度为1,计数信号量的长度为最大计数值。

下面来看看其具体的实现:

{
    创建Set类型的队列(`pxQueue = xQueueGenericCreate( uxEventQueueLength, sizeof( Queue_t * ), queueQUEUE_TYPE_SET )`。

    返回`pxQueue`。
}

可以看到,它的实现只是简单地调用了创建队列的方法,只是类型为queueQUEUE_TYPE_SET。另外,与创建队列一样,队列组还支持静态的创建,这里就不介绍了。

7.2. 添加/删除队列组成员

添加到队列组的函数原型:

BaseType_t xQueueAddToSet( QueueSetMemberHandle_t xQueueOrSemaphore, QueueSetHandle_t xQueueSet ) PRIVILEGED_FUNCTION;

该函数将队列(或信号量,互斥锁)xQueueOrSemaphore加入到指定的队列xQueueSet中。如果成功则返回pdPASS;如果由于队列已经在其他队列组中,则返回pdFAIL

下面来看看其具体实现:

{
    进入临界区(`taskENTER_CRITICAL()`)。

    如果该队列已经在其他队列组中(`( ( Queue_t * ) xQueueOrSemaphore )->pxQueueSetContainer != NULL`),则设置返回值`xReturn = pdFAIL`。
    否则,如果队列中已经存在消息(`( ( Queue_t * ) xQueueOrSemaphore )->uxMessagesWaiting != 0`),则设置返回值`xReturn = pdFAIL`。
    否则,表明队列可以加入到队列组,将其加入到队列组中(`( ( Queue_t * ) xQueueOrSemaphore )->pxQueueSetContainer = xQueueSet`),并设置返回值`xReturn = pdPASS。

    退出临界区(`taskEXIT_CRITICAL()`)。

    返回`xReturn`。
}

将队列从队列组中删除的函数原型:

BaseType_t xQueueRemoveFromSet( QueueSetMemberHandle_t xQueueOrSemaphore, QueueSetHandle_t xQueueSet) PRIVILEGED_FUNCTION;

该函数从队列组xQueueSet中删除队列(或信号量、互斥锁)xQueueOrSemaphore。需要注意的是,只有当被删除的队列(或信号量、互斥锁)为空时,才能将其从队列组中删除。如果删除成功,则返回pdPASS;如果队列不在队列组中,或不为空,则返回pdFAIL

下面来看看具体的实现:

{
    初始化`pxQueueOrSemaphore = ( Queue_t * ) xQueueOrSemaphore`。

    如果队列不在该队列组中(`pxQueueOrSemaphore->pxQueueSetContainer != xQueueSet`),则设置返回值`xReturn = pdFAIL`。
    否则,如果该队列中的还有消息(`pxQueueOrSemaphore->uxMessagesWaiting != 0`),则设置返回值`xReturn = pdFAIL`。
    否则,可以将其从队列组中剔除:
    {
        进入临界区(`taskENTER_CRITICAL()`)。
        将队列从队列组中移除(`pxQueueOrSemaphore->pxQueueSetContainer = NULL`)。
        退出临界区(`taskEXIT_CRITICAL()`)。
        设置返回值`xReturn = pdPASS`。
    }

    返回`xReturn`。
}

7.3. 从队列组中获取消息

函数原型:

QueueSetMemberHandle_t xQueueSelectFromSet( QueueSetHandle_t xQueueSet, const TickType_t xTicksToWait ) PRIVILEGED_FUNCTION;

该函数中队列组中的所有成员中选择一个消息非空的成员,并返回其句柄给调用者。该函数也支持阻塞的等待,时限为xTicksToWait。如果在预设的时间内没有消息抵达,则返回NULL

在获取到了成员后,便可以对其进行进一步消息获取操作。

下面来看看其具体的实现:

{
    通过调用`xQueueGenericReceive( ( QueueHandle_t ) xQueueSet, &xReturn, xTicksToWait, pdFALSE )`实现。
    返回`xReturn`。
}

可以看到,队列组的消息实际上是队列组中的队列句柄。

这里还需要详细介绍一下在xQueueGenericReceive()的实现中,将入队消息通知到队列组的行为(static BaseType_t prvNotifyQueueSetContainer( const Queue_t * const pxQueue, const BaseType_t xCopyPostition )):该函数必须在临界区内调用。返回指表示是否唤醒了高优先级的任务。

将入队事件通知至队列组的活动图

该函数的ISR版本:

QueueSetMemberHandle_t xQueueSelectFromSetFromISR( QueueSetHandle_t xQueueSet ) PRIVILEGED_FUNCTION;

其实现也很简单:

{
    通过调用`xQueueReceiveFromISR( ( QueueHandle_t ) xQueueSet, &xReturn, NULL )`实现。

    返回`xReturn`。
}

8. 队列的实现细节

8.1. 队列的接口

8.1.1. 队列的数据结构接口

8.1.1.1. 队列句柄

队列句柄是指向一个队列的指针,在创建队列时返回的类型,是所有队列操作的API所需的参数。

typedef void * QueueHandle_t;
8.1.1.2. 队列组(Queue Set)句柄

队列组句柄是指向一个队列组的指针,在创建队列组返回的类型,是所有队列组操作的API所需的参数。

typedef void *QueueSetHandle_t;
8.1.1.3. 队列组成员(Queue Set Member)句柄

由于队列组中可以包含队列和信号量,该类型代表两者中的任意一个。

typedef void * QueueSetMemberHandle_t;

8.2. 队列的内涵

8.2.1. 队列依赖的头文件

C标准库依赖头文件:

#include <stdlib.h>
#include <string.h>

FreeRTOS依赖头文件:

#define MPU_WRAPPERS_INCLUDED_FROM_API_FILE

#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"

#if ( configUSE_CO_ROUTINES == 1 )
    #include "croutine.h"
#endif

#undef MPU_WRAPPERS_INCLUDED_FROM_API_FILE

8.2.2. 队列的私有宏

8.2.2.1. 队列的私有常量

表示队列的访问方式:

#define queueSEND_TO_BACK       ( ( BaseType_t ) 0 )
#define queueSEND_TO_FRONT      ( ( BaseType_t ) 1 )
#define queueOVERWRITE          ( ( BaseType_t ) 2 )

表示队列的类型:

#define queueQUEUE_TYPE_BASE                ( ( uint8_t ) 0U )
#define queueQUEUE_TYPE_SET                 ( ( uitt8_t ) 0U )
#define queueQUEUE_TYPE_MUTEX               ( ( uint8_t ) 1U )
#define queueQUEUE_TYPE_COUNTING_SEMAPHORE  ( ( uint8_t ) 2U )
#define queueQUEUE_TYPE_BINARY_SEMAPHORE    ( ( uint8_t ) 3U )
#define queueQUEUE_TYPE_RECURSIVE_MUTEX     ( ( uint8_t ) 4U )

可以看出队列可以被实现为6种类型。

队列结构体的cRxLock和cTxLock成员的可取值的定义:

#define queueUNLOCKED               ( ( uint8_t ) -1 )
#define queueLOCKED_UNMODIFIED      ( ( uint8_t ) 0 )

信号量和互斥锁的消息长度:

#define queueSEMAPHORE_QUEUE_ITEM_LENGTH    ( ( UBaseType_t ) 0 )
#define queueMUTEX_GIVE_BLOCK_ITEM          ( ( TickType_t ) 0U )

因为信号量和互斥锁并不真的存储消息,因此他们的消息长度都为0。

8.2.2.2. 队列的私有宏函数

调度相关的宏函数:

#if ( configUSE_PREEMPTION == 0 )
    #define queueYIELD_IF_USING_PREEMTION()
#else
    #define queueYIELD_IF_USING_PREEMTION() portYIELD_WITHIN_API()
#endif

8.2.3. 队列的私有数据结构

8.2.3.1. 队列的元信息结构体

队列的元信息是队列实现的最重要的数据结构,其定义如下:

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;        /* 存储了当队列处于Locked状态时,出队的消息数量。如果队列未被锁时,设置为queueUNLOCKED */
    volatile int8_t cTxLock;        /* 存储了当队列处于Locked状态时,入队的消息数量。如果队列未被锁时,设置为queueUNLOCKED */

    #if ( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
        uint8_t ucStaticallyAllocated;  /* 如果设置为pdTRUE,表示队列使用的内存是静态申请,不需要FreeRTOS来删除 */
    #endif

    #if ( configUSE_QUEUE_SETS == 1 )
        struct QueueDefinition *pxQueueSetContainer;
    #endif

    #if ( configUSE_TRACE_FACILITY == 1 )
        UBaseType_t uxQueueNumber;
        uint8_t ucQueueType;
    #endif
} xQUEUE;

typedef xQUEUE Queue_t;

在队列的实现中,普通队列、信号量和互斥锁都是使用该数据结构存储元信息。我们来仔细看看队列数据结构中的一些成员。

对于pcHeadpcTail成员,根据队列的实现类型不同,主要有以下几种情况:

  • 如果Queue_t用于表示基本的队列,那么其pcHeadpcTail都作为指针使用,指向队列的消息。
  • 如果Queue_t用于表示互斥锁,那么pcHeadpcTail不会作为消息指针使用,为了更好的可读性,所以将pcHead通过宏定义重命名为uxQueueType。同时设置pcHead = queueQUEUE_IS_MUTEX,因此可通过uxQueueType来判断该队列是不是被实现为了互斥锁。而pcTail则用于表示互斥锁的所有者,将其重命名为pxMutexHolder

这里不使用联合体的原因是这将会破坏命名规范,宏定义如下:

#define pxMutexHolder           pcTail
#define uxQueueType             pcHead

#define queueQUEUE_IS_MUTEX     NULL

同样地,在pcReadFromuxRecursiveCallCount之间也有类似的关系。

xTasksWaitingToSendxTasksWaitingToReceive是非常重要的成员。它们与TCB_t中的xEventListItem互相关联,因为支持阻塞的读/写,通常还会与Delayed任务队列一同操作。

cRxLockcTxLock是队列的出队和入队计数锁。用于防止队列被并行访问而破坏上述两个队列的一致性,同时它们还记录加锁时队列的入队和出队消息的次数。

如果队列在某个队列组中,pxQueueSetContainer记录了该队列的所属队列组。

uxQueueNumberucQueueType用于第三方的Profiling工具。

8.2.3.2. 队列的注册表

队列注册表为内核调试工具定位某个队列的提供了方法。这是可选的功能。

定义如下:需要设置configQUEUE_REGISTRY_SIZE > 0

typedef struct QUEUE_REGISTRY_ITEM
{
    const char *pcQueueName;
    QueueHandle_t xHandle;
} xQueueRegistryItem;

typedef xQueueRegistryItem QueueRegistryItem_t;

PRIVILEGED_DATA QueueRegistryItem_t xQueueRegistry[ configQUEUE_REGISTRY_SIZE ];

队列的注册表是一个简单的数组。

8.2.4. 队列的私有函数

在私有函数中,比较重要的是为信号量和互斥锁预留的内部接口:

QueueHandle_t xQueueCreateMutex( const uint8_t ucQueueType ) PRIVILEGED_FUNCTION;
QueueHandle_t xQueueCreateMutexStatic( const uint8_t ucQueueType, StaticQueue_t *pxStaticQueue ) PRIVILEGED_FUNCTION;

QueueHandle_t xQueueCreateCountingSemaphore( const UBaseType_t uxMaxCount, const UBaseType_t uxInitialCount ) PRIVILEGED_FUNCTION;
QueueHandle_t xQueueCreateCountingSemaphoreStatic( const UBaseType_ uxMaxCount, const UBaseType_t uxInitialCount, StaticQueue_t *pxStaticQueue ) PRIVILEGED_FUNCTION;

void *xQueueGetMutexHolder( QueueHandle_t xSemaphore ) PRIVILEGED_FUNCTION;

BaseType_t xQueueTakeMutexRecursive( QueueHandle_t xMutex, TickType_t xTicksToWait ) PRIVILEGED_FUNCTION;
BaseType_t xQueueGiveMutexRecursive( QueueHandle_t xMutex ) PRIVILEGED_FUNCTION;

FreeRTOS专用的接口:

void vQueueWaitForMessageRestricted( QueueHandle_t xQueue, TickType_t xTicksToWait, const BaseType_t xWaitIndefinitely) PRIVILEGED_FUNCTION;

vQueueWaitForMessageRestricted()是专门为软件定时器的实现而提供的函数。用于等待Timer命令队列中的消息。

与调试相关的接口:

void vQueueSetQueueNumber( QueueHandle_t xQueue, UBaseType_t xuQueueNumber ) PRIVILEGED_FUNCTION;
UBaseType_t uxQueueGetQueueNumber( QueueHandle_t xQueue ) PRIVILEGED_FUNCTION;
uint8_t ucQueueGetQueueType( QueueHandle_t xQueue ) PRIVILEGED_FUNCTION;

这些接口用于第三方的调试工具。功能如其名,无需过多解释。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

FreeRTOS学习-队列管理 的相关文章

  • FreeRTOS系列

    1 多任务系统 1 1 前后台系统 单片机裸机开发时 一般都是在main函数里面用while 1 做一个大循环来完成所有的处理 循环中调用相应的函数完成所需的处理 有时也需要在中断中完成一些处理 相对于多任务系统而言 这就是单人单任务系统也
  • 详解FreeRTOS中的软件定时器

    软件定时器用于让某个任务定时执行 或者周期性执行 比如设定某个时间后执行某个函数 或者每隔一段时间执行某个函数 由软件定时器执行的函数称为软件定时器的回调函数 参考资料 Mastering the FreeRTOS Real Time Ke
  • FreeRTOS config开始的宏

    FreeRTOSConfig h系统配置文件中可以自定义 FreeRTOS h中定义默认值 configAPPLICATION ALLOCATED HEAP 默认情况下FreeRTOS的堆内存是由编译器来分配的 将宏configAPPLIC
  • 【FreeRTOS 信号量】互斥信号量

    互斥信号量与二值信号量类似 但是互斥信号量可以解决二值信号量出现的优先级翻转问题 解决办法就是优先级继承 普通互斥信号量创建及运行 参阅安富莱电子demo 互斥信号量句柄 static SemaphoreHandle t xMutex NU
  • 解决错误“ #error “include FreeRTOS.h“ must appear in source files before “include event_groups.““例子分享

    今天来给大家分享一下 关于之前自己在学习FreeRTOS过程中遇到的一个错误提示 话不多说 我们直接来看 错误分析 首先 我们看一下错误的提示 error 35 error directive include FreeRTOS h must
  • 一文教你学会keil软件仿真

    仿真在我们调试代码中是非常重要的 通过仿真 我们可以快速定位到错误代码 或者错误逻辑的地方 这里我就以上一篇博客为例 教大家如何软件仿真 软件仿真不需要单片机 直接通过keil软件进行代码调试 一 打开工具 二 选择软件仿真 三 开始仿真
  • Freertos中vTaskDelay()是怎么用的

    1 常见的使用场景 void vLED Task void pvParameters while 1 Heartbeat LED vTaskDelay 1000 portTICK RATE MS 说明 上面这段代码的意思是 led翻转后经过
  • ZYNQ中FreeRTOS中使用定时器

    使用普通的Timer中断方式时 Timer中断可以正常运行 但是UDP通信进程无法启动 其中TimerIntrHandler是中断服务程序 打印程序运行时间与从BRAM中读取的数据 void SetupInterruptSystem XSc
  • FreeRTOS ------- 任务(task)

    在学习RTOS的时候 个人觉得带着问题去学习 会了解到更多 1 什么是任务 在FreeRTOS中 每个执行线程都被称为 任务 每个任务都是在自己权限范围内的一个小程序 其具有程序入口每个任务都是在自己权限范围内的一个小程序 其具有程序入口通
  • freeRTOS手册 第六章 . 中断管理

    如果我对本翻译内容享有所有权 允许任何人复制使用本文章 不会收取任何费用 如有平台向你收取费用与本人无任何关系 第六章 中断管理 章节介绍和范围 事件 嵌入式实时系统必需对环境中的事件做出响应 比如 外部网络设备收到一个发送给TCP IP栈
  • FreeRTOS临界区

    FreeRTOS临界区是指那些必须完整运行 不能被打断的代码段 比如有的外设的初始化需要严格的时序 初始化过程中不能被打断 FreeRTOS 在进入临界区代码的时候需要关闭中断 当处理完临界区代码以后再打开中断 FreeRTOS 系统本身就
  • FreeRTOS记录(九、一个裸机工程转FreeRTOS的实例)

    记录一下一个实际项目由裸机程序改成FreeRTOS 以前产品的平台还是C8051单片机上面的程序 硬件平台改成了STM32L051 同时使用STM32CubeMX生成的工程 使用FreeRTOS系统 EEPROM数据存储读取函数修改更新 2
  • Error: L6218E: Undefined symbol vApplicationGetIdleTaskMemory (referred from tasks.o).

    我用的是F103ZET6的板子 移植成功后 编译出现两个错误是关于stm32f10x it c 里 void SVC Handler void void PendSV Handler void 两个函数的占用问题 随后编译出现以下两个问题
  • stm32f103zet6移植标准库的sdio驱动

    sdio移植 st官网给的标准库有给一个用于st出的评估板的sdio外设实现 但一是文件结构有点复杂 二是相比于国内正点原子和野火的板子也有点不同 因此还是需要移植下才能使用 当然也可以直接使用正点原子或野火提供的实例 但为了熟悉下sdio
  • FreeRTOS之软件定时器

    FreeRTOS之软件定时器 声明 本人按照正点原子的FreeRTOS例程进行学习的 欢迎各位大佬指责和批评 谢谢 include sys h include delay h include usart h include led h in
  • FreeRTOS学习---“定时器”篇

    总目录 FreeRTOS学习 任务 篇 FreeRTOS学习 消息队列 篇 FreeRTOS学习 信号量 篇 FreeRTOS学习 事件组 篇 FreeRTOS学习 定时器 篇 FreeRTOS提供了一种软件定时器 用来快速实现一些周期性的
  • FreeRTOS笔记(二)

    FreeRTOS笔记 二 静态任务 文章目录 FreeRTOS笔记 二 静态任务 一 任务定义 二 任务创建 2 1 定义任务栈 2 2 定义任务函数 2 3 定义任务控制块 2 4 实现任务创建函数 三 实现就绪列表 3 1 定义就绪列表
  • 再论FreeRTOS中的configTOTAL_HEAP_SIZE

    关于任务栈和系统栈的基础知识 可以参考之前的随笔 FreeRTOS 任务栈大小确定及其溢出检测 这里再次说明 define configTOTAL HEAP SIZE size t 17 1024 这个宏 官方文档解释 configTOTA
  • FreeRTOS 配置TICK_RATE_HZ

    我使用的是带有 5 4 版 FreeRTOS 的 MSP430f5438 我有一个有趣的问题 我无法弄清楚 基本上 当我将 configTICK RATE HZ 设置为不同的值时 LED 闪烁得更快或更慢 它应该保持相同的速率 我将 con
  • 如何将 void* 转换为函数指针?

    我在 FreeRTOS 中使用 xTaskCreate 其第四个参数 void const 是传递给新线程调用的函数的参数 void connect to foo void const task params void on connect

随机推荐

  • [Android Studio]Android Studio 三种添加插件的方式

    何给Android Studio添加插件 添加插件的路径有三种 xff0c 我把他们分类如下 xff1a 点击设置小按钮 点击 xff3b Plugins xff3d 这里展示的是你已经安装的插件 xff0c 我们可以点击插件名称 xff0
  • Gerrit 服务器插件安装-示例插件delete project

    gerrit2 X 中没法直接删除一个项目 xff0c 之前需要手工删除 xff0c 后来社区提供了一个插件delete project来搞定这个事 xff0c 安装方法如下 xff1a 到 gerritforge xff0c 找到对应的
  • Windows平台下载Android源码(整理)

    Google官方下载源码使用的系统Ubuntu系统 xff0c 不过现在我们需要在Windows系统中下载Android源码文件 网站的地址是 xff1a https android googlesource com 里面包括Android
  • Ubuntu 16.04 文件服务器--samba的安装和配置

    Samba是在Linux系统上实现的SMB xff08 Server Messages Block xff0c 信息服务块 xff09 协议的一款免费软件 它实现在局域网内共享文件和打印机 xff0c 是一个客户机 服务器型协议 客户机通过
  • 深入剖析Android音频之AudioTrack

    播放声音能够用MediaPlayer和AudioTrack xff0c 两者都提供了java API供应用开发人员使用 尽管都能够播放声音 但两者还是有非常大的差别的 当中最大的差别是MediaPlayer能够播放多种格式的声音文件 比如M
  • 树莓派4 运行 Tensorflow Lite

    树莓派4 运行 Tensorflow Lite 1 更新树莓派 span class token function sudo span apt update 2 下载安装脚本 span class token function git sp
  • 操作系统进程进行系统调用详细过程

    翻阅很多资料 xff0c 综合了各处所述进程在进行系统调用之后的状态会如何的解答 xff0c 以下是我个人理解 xff0c 欢迎各位读者纠错 PS 特别感谢以下这个帖子 xff0c 看完他们的讨论我才茅塞顿开 xff0c 非常感谢 xff0
  • 解决Ubuntu 找不到ARM64 的源的问题(转)

    Ubuntu 安装了NVIDIA的驱动还有DriveWokrs之后 xff0c 好像把系统添加了arm64的架构 xff0c 因此 xff0c 在源更新的时候 xff0c 也会更新arm64相关的源 xff0c 但是问题在于 xff0c 用
  • asp.net 实现打开文件所在的文件夹, 本地可以打开,发布后点击按钮没有反应的解决办法

    此类情况大概是安全范畴的问题 确定上传文件夹的共享 xff0c iis 以及电脑帐户 xff0c 以及aspnet 等是否有对应的相关权限 1 确认ASPNET 账户属于管理员级别 2 在 服务 里面找到 IIS Admin xff0c 双
  • numpy 和 tensor 的区别

    关系 xff1a 两者共享内存 xff0c 转换方便 xff0c 没有额外的开销 区别 xff1a 1 数据类型上面的区别 xff1a numpy 默认类型是 float64 int32 tensor 默认类型是float32 int64
  • 关于docker无法apt-get update的问题

    在看这篇文章https www jianshu com p 21d66ca6115e 有一个部分是 但是发现自己的 Node 没有ping命令 想着去apt get update 但是出现如下错误 只要在命令签名加上 sudo 就行
  • ubuntu下安装zip unzip

    安装命令 apt get install zip unzip 执行命令常见错误 xff1a 1 unable to locate package 解决办法 xff1a 执行sudo apt get update命令后再执行安装命令就可以了
  • 平衡小车卡尔曼滤波算法

    最近研究STM32的自平衡小车 xff0c 发现有两座必过的大山 xff0c 一为卡尔曼滤波 xff0c 二为PID算法 网上看了很多关于卡尔曼滤波的代码 xff0c 感觉写得真不咋地 一怒之下 xff0c 自己重写 xff0c 不废话 x
  • FreeRTOS学习-前言与FreeRTOS发行版

    1 前言 因为工作的需要 xff0c 学习FreeRTOS已经有一段时间了 接下来一段时间会定期更新本人学习FreeRTOS的系列笔记 系列笔记主要参考了官方的说明手册和FreeRTOS的源代码 其主要思想是先了解FreeRTOS的对外接口
  • FreeRTOS学习-内存管理

    1 动态内存分配与FreeRTOS 从v9 0 0后 xff0c FreeRTOS开始支持内核对象的静态分配方式 xff0c 因此 xff0c 内存管理库可以被裁剪 但在大多数嵌入式应用中 xff0c 堆的使用还是非常常见的 因此 xff0
  • FreeRTOS学习-任务管理(Task管理)(1)

    1 简介 任务管理 xff08 或称进程管理 xff09 是所有操作系统内核的最基本组成模块之一 xff0c FreeRTOS也不例外 想要了解一个操作系统 xff0c 不得不理解其任务管理的设计和实现 任务管理的介绍由两篇文章组成 xff
  • Java基础之Java枚举

    絮叨 昨天刚好有遇到一个枚举的小问题 xff0c 然后发现自己并不是那么熟悉它 xff0c 然后在开发中 xff0c 枚举用的特别多 xff0c 所以有了今天的文章 什么是枚举 Java中的枚举是一种类型 xff0c 顾名思义 xff1a
  • C++ STL 移动一个vector的元素到另一个vector

    1 背景 有的时候 xff0c 我们需要提取某个现有的vector中的元素到另一个vector中 xff0c 或者对多维的vector进行纬度的转换 在这种场景下 xff0c 往往原始的vector中的数据可能并不需要了 xff0c 为了节
  • Qt/C++ 临时屏蔽控件信号(signal)的实用方法

    1 背景 在使用Qt的控件时 xff0c 我们大概率会使用Qt的信号与槽 xff08 signal slot xff09 的机制来实现自己的UI交互逻辑 由于Qt内置控件的信号种类是有限的 xff0c 我们常常会遇到如下窘境 xff1a 以
  • FreeRTOS学习-队列管理

    1 简介 在FreeRTOS中 xff0c 提供了多种任务间通讯的机制 xff0c 包括消息队列 信号量和互斥锁 事件组 任务通知 xff0c 他们的总体特征如下图所示 xff1a 从图中可以看出 xff0c 消息队列 信号量和互斥锁 事件