手把手教你FreeRTOS源码详解(三)——队列

2023-05-16

FreeRTOS源码解析集合(全网最详细)
手把手教你FreeRTOS源码解析(一)——内存管理
手把手教你FreeRTOS源码详解(二)——任务管理
手把手教你FreeRTOS源码详解(三)——队列
手把手教你FreeRTOS源码详解(四)——信号量、互斥量、递归互斥量

队列:

  • 1、队列结构体Queue_t
  • 2、创建队列xQueueGenericCreate
    • 2.1 详解 xQueueGenericCreate
    • 2.2 初始化队列prvInitialiseNewQueue
    • 2.3 复位队列xQueueGenericReset
  • 3、通用入队函数xQueueGenericSend
    • 3.1 prvCopyDataToQueue
    • 3.2 xQueueSend、xQueueSendToBack、xQueueSendToFront、xQueueOverwrite
  • 4、通用出队函数xQueueGenericReceive
    • 4.1 prvCopyDataFromQueue
    • 4.2 xQueueReceive、xQueuePeek

1、队列结构体Queue_t

队列与任务相同,同样也有一个结构体用来描述队列。删除部分宏定义后如下。

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*
} xQUEUE;
/*更名*/
typedef xQUEUE Queue_t;

2、创建队列xQueueGenericCreate

创建普通的消息队列、创建队列集、创建互斥信号量等,最后都是调用通用的队列创建函数xQueueGenericCreate。

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

其中:
uxQueueLength为队列长度
uxItemSize为队列项大小
ucQueueType 队列的类型,共有6种类型如下
queueQUEUE_TYPE_BASE
普通的消息队列
queueQUEUE_TYPE_SET 队列集
queueQUEUE_TYPE_MUTEX 互斥信号量
queueQUEUE_TYPE_COUNTING_SEMAPHORE 计数型信号量
queueQUEUE_TYPE_BINARY_SEMAPHORE 二值信号量
queueQUEUE_TYPE_RECURSIVE_MUTEX 递归互斥信号量

2.1 详解 xQueueGenericCreate

if( uxItemSize == ( UBaseType_t ) 0 )
		{
			/* 队列项大小为0,因此不需要存储区 */
			xQueueSizeInBytes = ( size_t ) 0;
		}
		else
		{
			/* 分配足够的存储区,以便于随时能够保存所有的消息
			   存储区=队列长度*队列项大小*/
			xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
		}

首先确定队列存储区的大小,如果队列项的大小为0,则队列不需要存储区,xQueueSizeInBytes 赋值为0;若队列项的大小不为0,则存储区的大小为队列长度*队列项大小

pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes );

计算队列总共需要的内存空间,需在存储区基础上再加上结构体Queue_t 大小

pucQueueStorage = ( ( uint8_t * ) pxNewQueue ) + sizeof( Queue_t );

pucQueueStorage 指向队列存储区的起始地址,在所申请的总内存空间的起始位置上偏移一个结构体Queue_t 大小
在这里插入图片描述

	#if( configSUPPORT_STATIC_ALLOCATION == 1 )
		{
			pxNewQueue->ucStaticallyAllocated = pdFALSE;
		}

如果队列是动态创建的,则将其ucStaticallyAllocated标志位置为pdFALSE 便于后续删除

prvInitialiseNewQueue( uxQueueLength, uxItemSize, pucQueueStorage, ucQueueType, pxNewQueue );

初始化队列

2.2 初始化队列prvInitialiseNewQueue

if( uxItemSize == ( UBaseType_t ) 0 )
{
	pxNewQueue->pcHead = ( int8_t * ) pxNewQueue;
}
else
{
	pxNewQueue->pcHead = ( int8_t * ) pucQueueStorage;
}

首先初始化队列存储区的起始地址,若队列长度为0(队列没有存储区),则指向队列的起始地址,若队列长度不为0,指向队列存储区的起始地址

pxNewQueue->uxLength = uxQueueLength;

初始化队列长度

pxNewQueue->uxItemSize = uxItemSize;

初始化队列每项大小

( void ) xQueueGenericReset( pxNewQueue, pdTRUE );

复位队列

2.3 复位队列xQueueGenericReset

我们首先来看一下xQueueGenericReset的参数

BaseType_t xQueueGenericReset( QueueHandle_t xQueue, 
		                        BaseType_t xNewQueue )

xQueue:需要复位或者初始化队列的句柄
xNewQueue :为pdFALSE,表明队列不是第一次初始化,只复位队列即可
为pdTRUE,表明队列是第一次初始化

taskENTER_CRITICAL();

首先进入临界区,防止队列初始化、复位过程被打断

pxQueue->pcTail = pxQueue->pcHead + ( pxQueue->uxLength * pxQueue->uxItemSize );

pcTail指向队列存储区的尾部

pxQueue->uxMessagesWaiting = ( UBaseType_t ) 0U;

队列刚创建好时,队列当前项数为0,即消息数为0

pxQueue->pcWriteTo = pxQueue->pcHead;

pcWriteTo指向队列存储区下一块空闲区域,初始创建队列时,下一块空闲区域即为pcHead

pxQueue->u.pcReadFrom = pxQueue->pcHead + ( ( pxQueue->uxLength - ( UBaseType_t ) 1U ) * pxQueue->uxItemSize );

pcReadFrom指向队列最后一项出队时的首地址

	pxQueue->cRxLock = queueUNLOCKED;
	pxQueue->cTxLock = queueUNLOCKED;

队列未上锁,cRxLock,cTxLock均为queueUNLOCKED

if( xNewQueue == pdFALSE )
		{
		/* 队列复位后为空,出队阻塞的任务仍然保存阻塞状态,但入队阻塞的任务不再阻塞,应该从对应的列表中删除*/
			if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
			{
				if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
				{
					queueYIELD_IF_USING_PREEMPTION();
				}
else
		{
			/* 初始化入队阻塞、出队阻塞列表 */
			vListInitialise( &( pxQueue->xTasksWaitingToSend ) );
			vListInitialise( &( pxQueue->xTasksWaitingToReceive ) );
		}

当xNewQueue为pdTRUE时,即该队列为第一次创建,因此需要初始化入队阻塞列表和出队阻塞列表
当xNewQueue为pdFALSE时,该队列已经初始化过了,只需要进行复位,队列复位后为空,出队阻塞的任务仍然保存阻塞状态,但入队阻塞的任务不再阻塞,应该从对应的列表中删除
普通的消息队列复位函数xQueueReset最终调用的函数仍是xQueueGenericReset,如下:

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

3、通用入队函数xQueueGenericSend

FreeRtos的队列有很多入队函数,但是这些入队函数最终调用的都是通用入队函数xQueueGenericSend

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

xQueue:队列句柄
pvItemToQueue:需要发送的消息(入队消息)
xTicksToWait:阻塞时间
xCopyPosition :入队的方式。
queueSEND TO_BACK:后向入队
queueSEND_TO_FRONT:前向入队
queueOVERWRITE:覆写入队

taskENTER_CRITICAL();

首先进入临界区

if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) )

判断队列是否还有剩余存储空间,如果采用覆写就不用在乎是否还有存储空间

xYieldRequired = prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );

若队列还有剩余存储空间,将入队消息复制到队列中
此处我们不考虑使用队列集的情况,将队列集部分的宏定义删除

if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )

消息入队以后,先检查是否有任务因等待读取消息而阻塞

if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
	{
					/* 解除阻塞的任务具有更高优先级,因此需要进行一次任务切换 */
					queueYIELD_IF_USING_PREEMPTION();
	}

如果有任务因等待读取消息而阻塞,则将该任务从阻塞列表中移除,因解除阻塞的任务具有更高优先级,因此需要进行一次任务切换
若队列已满,没有剩余的存储空间:

				if( xTicksToWait == ( TickType_t ) 0 )
				{
					taskEXIT_CRITICAL();
					return errQUEUE_FULL;
				}

若不阻塞,则直接退出临界区,并返回errQUEUE_FULL

			else if( xEntryTimeSet == pdFALSE )
			{
				vTaskSetTimeOutState( &xTimeOut );
				xEntryTimeSet = pdTRUE;
			}

若阻塞时间不为0,则初始化超时结构体,并将超时标志位xEntryTimeSet 设为pdTRUE,表明超时结构体已经初始化了

taskEXIT_CRITICAL();

退出临界区

vTaskSuspendAll();
prvLockQueue( pxQueue );

挂起任务调度器,并将队列上锁

if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )

更新超时状态,并判断时间是否已到

if( prvIsQueueFull( pxQueue ) != pdFALSE )

如果超时时间未到,判断队列是否已满

				vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToSend ), xTicksToWait );
				prvUnlockQueue( pxQueue );
				if( xTaskResumeAll() == pdFALSE )
				{
					portYIELD_WITHIN_API();
				}

如果超时时间未到,且队列已满,则将入队任务置于对应的阻塞列表中,然后恢复任务调度器

			prvUnlockQueue( pxQueue );
			( void ) xTaskResumeAll();

如果超时时间未到,队列不满,则解锁队列,恢复任务调度器,重新执行通用入队任务一次

			prvUnlockQueue( pxQueue );
			( void ) xTaskResumeAll();
			return errQUEUE_FULL;

若超时时间已到,则恢复任务调度器,返回errQUEUE_FULL,表明入队失败

3.1 prvCopyDataToQueue

uxMessagesWaiting = pxQueue->uxMessagesWaiting;

首先获取当前队列中的消息数目
在这里插入图片描述
一个队列初始化完成后,大致如上图所示;数据复制采用的是c/c++中的函数memcpy,其原型如下:

void *memcpy(void *destin, void *source, unsigned n);

其作用是以source指向的地址为起点,将连续的n个字节数据,复制到以destin指向的地址为起点的内存中,注意n的单位是字节
我们去掉互斥锁相关宏定义

else if( xPosition == queueSEND_TO_BACK )

采用后向入队方式:

( void ) memcpy( ( void * ) pxQueue->pcWriteTo, pvItemToQueue, ( size_t ) pxQueue->uxItemSize ); 

队列首次初始化完成后,第一个消息的入队起始地址为pcHead(pcWriteTo),将消息pvItemToQueue复制到对应的区域

pxQueue->pcWriteTo += pxQueue->uxItemSize;

pcWriteTo指向下一个消息入队的起始地址(下一块空闲区域)

		if( pxQueue->pcWriteTo >= pxQueue->pcTail ) /*lint !e946 MISRA exception justified as comparison of pointers is the cleanest solution. */
		{
			pxQueue->pcWriteTo = pxQueue->pcHead;
		}

如果消息已经写至队尾pcTail ,则pcWriteTo 重新指向队列头部pcHead

采用前向入队方式:

( void ) memcpy( ( void * ) pxQueue->u.pcReadFrom, pvItemToQueue, ( size_t ) pxQueue->uxItemSize );

第一个消息的入队起始地址为pcReadFrom,将消息pvItemToQueue复制到对应的区域

pxQueue->u.pcReadFrom -= pxQueue->uxItemSize;

pcReadFrom 指向下一个消息入队的起始地址

	if( pxQueue->u.pcReadFrom < pxQueue->pcHead ) 
	{
		pxQueue->u.pcReadFrom = ( pxQueue->pcTail - pxQueue->uxItemSize );
	}

如果消息已经写至队首pcHead ,则pcReadFrom 重新指向最后一个出队的队列项首地址
采用覆写入队方式
需要注意的是采用覆写入队的队列长度必须为1

	if( xPosition == queueOVERWRITE )
	{
		if( uxMessagesWaiting > ( UBaseType_t ) 0 )
		{
			--uxMessagesWaiting;
		}
	}

如果采用覆写入队,直接将队列消息数目减1,在任务结尾会重新将队列消息数目加1,这样队列中的消息数目会一直为0,队列不会进行阻塞,在下一次入队时会直接将前面的数据进行覆盖

pxQueue->uxMessagesWaiting = uxMessagesWaiting + 1;

将队列消息数目增1

3.2 xQueueSend、xQueueSendToBack、xQueueSendToFront、xQueueOverwrite

我们来看一下常用的几种入队函数的封装:
xQueueSend和xQueueSendToBack:
xQueueSend和xQueueSendToBack的封装完全一样:

#define xQueueSend( xQueue, pvItemToQueue, xTicksToWait ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )
#define xQueueSendToBack( xQueue, pvItemToQueue, xTicksToWait ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )

xQueueSendToFront:

#define xQueueSendToFront( xQueue, pvItemToQueue, xTicksToWait ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_FRONT )

xQueueOverwrite:

#define xQueueOverwrite( xQueue, pvItemToQueue ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), 0, queueOVERWRITE )

上述几种封装都类似,仅仅对通用入队函数xQueueGenericSend的参数xCopyPosition进行了不同选择

4、通用出队函数xQueueGenericReceive

taskENTER_CRITICAL();

首先进入临界区

const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;

获取队列中的消息数目,保存至uxMessagesWaiting

if( uxMessagesWaiting > ( UBaseType_t ) 0 )

首先判断队列中是否有消息可读取,若队列中的消息不为空:

pcOriginalReadPosition = pxQueue->u.pcReadFrom;

记录当前读取消息的地址位置,用于实现“偷看”,即只读取消息,不删除消息

prvCopyDataFromQueue( pxQueue, pvBuffer );

将需要读取的消息复制至缓存区pvBuffer
如果消息出队,不是“偷看消息”:

pxQueue->uxMessagesWaiting = uxMessagesWaiting - 1;

消息出队,消息数减一

					if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
					{
						/*将任务从入队阻塞列表中移除*/
						if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
						{
							queueYIELD_IF_USING_PREEMPTION();
						}
						}

消息出队后,队列就不满了,一些在入队阻塞列表中的任务就可以解除阻塞状态了,上述代码即判断入队阻塞列表是否为空,不为空则将任务从阻塞列表中移除,再进行一次任务切换
“偷看”消息:

pxQueue->u.pcReadFrom = pcOriginalReadPosition;

重新赋值下次读取消息的地址位置,这样读取消息后而不会删除队列消息

				if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
				{
					if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
					{
						/* The task waiting has a higher priority than this task. */
						queueYIELD_IF_USING_PREEMPTION();
					}
					}

判断是否有任务因等待消息而阻塞,如果有,则将任务从出队阻塞列表中删除,并进行一次任务切换
队列中的消息为空:

			if( xTicksToWait == ( TickType_t ) 0 )
			{
				taskEXIT_CRITICAL();
				traceQUEUE_RECEIVE_FAILED( pxQueue );
				return errQUEUE_EMPTY;
			}

未设置阻塞时间,则直接退出临界区,返回errQUEUE_EMPTY

			else if( xEntryTimeSet == pdFALSE )
			{
				vTaskSetTimeOutState( &xTimeOut );
				xEntryTimeSet = pdTRUE;
			}

设置了阻塞时间,即阻塞时间不为0,初始化超时结构体,并将超时标志位xEntryTimeSet 置为pdTRUE

	taskEXIT_CRITICAL();
	vTaskSuspendAll();
	prvLockQueue( pxQueue );

退出临界区,挂起任务调度器,将队列上锁

if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )

更新超时状态,并判断超时时间是否结束

if( prvIsQueueEmpty( pxQueue ) != pdFALSE )

判断队列是否为空

vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );
prvUnlockQueue( pxQueue );

若超时时间未结束且队列为空,则直接将出队任务挂载至出队阻塞列表,再重新将任务解锁

prvUnlockQueue( pxQueue );
( void ) xTaskResumeAll();

若超时时间未结束,队列不为空,则将任务解锁,恢复任务调度器,重新执行一次出队任务

		prvUnlockQueue( pxQueue );
		( void ) xTaskResumeAll();
		if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
		{
			traceQUEUE_RECEIVE_FAILED( pxQueue );
			return errQUEUE_EMPTY;
		}

若超时时间结束,则将队列解锁,恢复任务调度器,并判断队列是否为空,若队列此时仍为空,表明无消息可读,返回errQUEUE_EMPTY;若队列不为空,则再次执行该函数进行消息读取

4.1 prvCopyDataFromQueue

队列通常采用的是先进先出(FIFO)缓冲机制,当队列入队采用后向入队时(往队列发送数据时,发送到队列尾部,从队列读取数据时,从队列头部读取)

假设队列采用后向入队,入队了两个消息,项目1先入队,项目2随后入队,则此时队列结构如下图所示(注意pcWriteTo和pcReadFrom指向
在这里插入图片描述

按照出队函数的流程:

if( pxQueue->uxItemSize != ( UBaseType_t ) 0 )

首先判断队列项是否不为0

pxQueue->u.pcReadFrom += pxQueue->uxItemSize;

然后将pcReadFrom 向后偏移一个队列项大小,偏移后如下图所示:
在这里插入图片描述

		if( pxQueue->u.pcReadFrom >= pxQueue->pcTail ) 
		{
			pxQueue->u.pcReadFrom = pxQueue->pcHead;
		}

按上图所示pcReadFrom 指向了队列尾pcTail ,应重新将pcReadFrom 指向队列头pcHead,如下图所示:
在这里插入图片描述

( void ) memcpy( ( void * ) pvBuffer, ( void * ) pxQueue->u.pcReadFrom, ( size_t ) pxQueue->uxItemSize );

最后调用memcpy函数,将以pcReadFrom为起始地址,向后uxItemSize个字节的内容复制到缓冲区pvBuffer中,即实现了先入队的消息先出队FIFO

假设队列采用前向入队,入队了两个消息,则此时队列结构如下图所示(注意pcWriteTo和pcReadFrom指向
在这里插入图片描述
其中第一个入队的消息为项目1,第二个入队的消息为项目2
同样,我们按照出队函数的流程:

pxQueue->u.pcReadFrom += pxQueue->uxItemSize;

然后将pcReadFrom 向后偏移一个队列项大小,偏移后如下图所示:
在这里插入图片描述

	if( pxQueue->u.pcReadFrom >= pxQueue->pcTail ) 
	{
		pxQueue->u.pcReadFrom = pxQueue->pcHead;
	}

判断是否到队列尾部pcTail,目前还未到队列尾部,忽略该段代码

( void ) memcpy( ( void * ) pvBuffer, ( void * ) pxQueue->u.pcReadFrom, ( size_t ) pxQueue->uxItemSize );

最后调用memcpy函数,将以pcReadFrom为起始地址,向后uxItemSize个字节的内容复制到缓冲区pvBuffer中,优先将后入队的项目2先读取,即实现了后入队先出队LIFO

4.2 xQueueReceive、xQueuePeek

最后我们来看一下两个常用出队函数的封装
xQueueReceive:

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

该函数直接将“偷看”置为pdFALSE
xQueuePeek:

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

相反,peek函数直接将“偷看”标志位置为pdTRUE

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

手把手教你FreeRTOS源码详解(三)——队列 的相关文章

  • 【FreeRTOS 信号量】互斥信号量

    互斥信号量与二值信号量类似 但是互斥信号量可以解决二值信号量出现的优先级翻转问题 解决办法就是优先级继承 普通互斥信号量创建及运行 参阅安富莱电子demo 互斥信号量句柄 static SemaphoreHandle t xMutex NU
  • FreeRTOS软件定时器创建、复位、开始和停止(备忘)

    目录 一 简介 1 1 开发环境 1 2 摘要 二 STM32CubeIDE配置 三 创建定时器 3 1 头文件声明 3 2 工程文件定义 3 3 创建定时器 3 4 开启 复位 和关闭定时器 四 定时器回调函数 一 简介 1 1 开发环境
  • 解决错误“ #error “include FreeRTOS.h“ must appear in source files before “include event_groups.““例子分享

    今天来给大家分享一下 关于之前自己在学习FreeRTOS过程中遇到的一个错误提示 话不多说 我们直接来看 错误分析 首先 我们看一下错误的提示 error 35 error directive include FreeRTOS h must
  • FreeRTOS简述和移植文档

    FreeRTOS简述和移植文档 文章目录 FreeRTOS简述和移植文档 1 前言 2 FreeRTOS简述 1 概述 2 实现 3 主要特色 4 支持平台 3 移植FreeRTOS 4 最后 1 前言 目前由于IOT的飞速发展 针对单片机
  • FreeRTOS系列

    1 RTOS简介 RTOS全称为 Real Time Operation System 即实时操作系统 RTOS强调的是实时性 又分为硬实时和软实时 硬实时要求在规定的时间内必须完成操作 不允许超时 而软实时里对处理过程超时的要求则没有很严
  • freeRTOS手册 第六章 . 中断管理

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

    FreeROTS 就是一个免费的 RTOS 类系统 这里要注意 RTOS 不是指某一个确定的系统 而是指一类系统 比如 UCOS FreeRTOS RTX RT Thread 等这些都是 RTOS 类操作系统 FreeRTOS 是 RTOS
  • FreeRTOS:中断配置

    目录 一 Cortex M 中断 1 1中断简介 1 2中断管理简介 1 3优先级分组定义 1 4优先级设置 1 5用于中断屏蔽的特殊寄存器 1 5 1PRIMASK 和 FAULTMASK 寄存器 1 5 2BASEPRI 寄存器 二 F
  • 基于HAL库的FREERTOS-----------三.队列

    一 队列简介 在实际的应用中 常常会遇到一个任务或者中断服务需要和另外一个任务进行 沟通交流 这个 沟通交流 的过程其实就是消息传递的过程 在没有操作系统的时候两个应用程序进行消息传递一般使用全局变量的方式 但是如果在使用操作系统的应用中用
  • FreeRTOS临界段和开关中断

    http blog sina com cn s blog 98ee3a930102wg5u html 本章教程为大家讲解两个重要的概念 FreeRTOS的临界段和开关中断 本章教程配套的例子含Cortex M3内核的STM32F103和Co
  • stm32f103zet6移植标准库的sdio驱动

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

    声明及感谢 跟随正点原子资料学习 在此作为学习的记录和总结 环境 keil stm32f103 背景知识 Cotex M3的NVIC最多支持240个IRQ 中断请求 1个不可屏蔽 NMI 1个Systick 滴答定时器 Cortex M处理
  • FreeRTOS死机原因

    1 中断回调函数中没有使用中断级API xxFromISR 函数 xSemaphoreGiveFromISR uart busy HighterTask 正确 xSemaphoreGive uart busy 错误 2 比configMAX
  • FreeRTOS学习---“定时器”篇

    总目录 FreeRTOS学习 任务 篇 FreeRTOS学习 消息队列 篇 FreeRTOS学习 信号量 篇 FreeRTOS学习 事件组 篇 FreeRTOS学习 定时器 篇 FreeRTOS提供了一种软件定时器 用来快速实现一些周期性的
  • FreeRTOS轻量级同步--任务通知

    1 简介 在FreeRTOS的配置参数中的configUSE TASK NOTIFICATIONS宏打开 一般RTOS会默认打开 如图1所示 图1 notify宏开关 RTOS在创建任务时 会创建一个32位的通知值ulNotifiedVal
  • STM32 Freertos 添加 外部sram heap_5.c

    1 添加外部SRAM 初始化 2 添加heap 5 c 3 初始化heap 5 c 外部堆栈 Define the start address and size of the two RAM regions not used by the
  • 当一个任务写入变量而其他任务读取该变量时,我们是否需要信号量?

    我正在研究 freeRtos 并且我有一个名为 x 的变量 现在 每秒只有一个任务正在写入该变量 而其他任务正在读取该变量值 我需要用互斥锁来保护变量吗 如果变量为 32 位或更小 并且其值是独立的并且不与任何其他变量一起解释 则不需要互斥
  • 如何将 void* 转换为函数指针?

    我在 FreeRTOS 中使用 xTaskCreate 其第四个参数 void const 是传递给新线程调用的函数的参数 void connect to foo void const task params void on connect
  • FreeRTOS 匈牙利表示法 [重复]

    这个问题在这里已经有答案了 我是 RTOS 和 C 编程的新手 而且我仍在习惯 C 的良好实践 因此 我打开了一个使用 FreeRTOS 的项目 我注意到操作系统文件使用匈牙利表示法 我知道一点符号 但面临一些新的 标准 FreeRTOS
  • C++ freeRTOS任务,非静态成员函数的无效使用

    哪里有问题 void MyClass task void pvParameter while 1 this gt update void MyClass startTask xTaskCreate this gt task Task 204

随机推荐

  • LeetCode560:和为K的子数组

    要求 给你一个整数数组 nums 和一个整数 k xff0c 请你统计并返回 该数组中和为 k 的连续子数组的个数 思路 方法一 xff1a 暴力解 先固定左边界 xff0c 再去枚举右边 span class token keyword
  • LeetCode581:最短无序连续子数组

    要求 给你一个整数数组 nums xff0c 你需要找出一个 连续子数组 xff0c 如果对这个子数组进行升序排序 xff0c 那么整个数组都会变为升序排序 请你找出符合题意的 最短 子数组 xff0c 并输出它的长度 思路 我们可以假设把
  • LeetCoed617:合并二叉树

    要求 给你两棵二叉树 xff1a root1 和 root2 想象一下 xff0c 当你将其中一棵覆盖到另一棵之上时 xff0c 两棵树上的一些节点将会重叠 xff08 而另一些不会 xff09 你需要将这两棵树合并成一棵新二叉树 合并的规
  • LeetCode621:任务调度器

    要求 给你一个用字符数组 tasks 表示的 CPU 需要执行的任务列表 其中每个字母表示一种不同种类的任务 任务可以以任意顺序执行 xff0c 并且每个任务都可以在 1 个单位时间内执行完 在任何一个单位时间 xff0c CPU 可以完成
  • LeetCode647:回文子串

    要求 给你一个字符串 s xff0c 请你统计并返回这个字符串中 回文子串 的数目 回文字符串 是正着读和倒过来读一样的字符串 子字符串 是字符串中的由连续字符组成的一个序列 具有不同开始位置或结束位置的子串 xff0c 即使是由相同的字符
  • LeetCode739:每日温度

    要求 给定一个整数数组 temperatures xff0c 表示每天的温度 xff0c 返回一个数组 answer xff0c 其中 answer i 是指对于第 i 天 xff0c 下一个更高温度出现在几天后 如果气温在这之后都不会升高
  • 八大排序算法

    介绍 排序也称排序算法 Sort Algorithm xff0c 排序是将一组数据 xff0c 依指定的顺序进行排列的过程 排序分类 1 内部排序 指将需要处理的所有数据都加载到内部存储器 内存 中进行排序 2 外部排序法 数据量过大 xf
  • 时间复杂度和空间复杂度详解及排序算法复杂度

    时间复杂度 度量一个程序 算法 执行时间的两种方法 1 事前估算法 通过分析某个算法的时间复杂度来判断哪个算法更优 2 事后统计法 这种方法可行 xff0c 但是有两个问题 xff1a 一是要想对设计的算法的运行性能进行评测 xff0c 需
  • Java反编译工具JAD的安装与简单使用

    jad介绍 jad 是一个使用非常广泛的 Java 反编译工具 可以将java编译后的class文件反编译成对应的java文件 下载地址 JAD下载地址 xff0c 点击跳转https varaneckas com jad 按照自己的需求
  • python Counter() 函数

    目录 1 以统计列表中的词频为例 2 寻找出现次数最多的 k 个数 刷 leetcode 时发现了可以很方便地统计词频的 Counter 函数 xff0c 有了这个函数就不用手动的使用 for 循环来手动统计词频啦 xff01 Counte
  • C++11 新特性简介

    1 auto auto是旧关键字 xff0c 在C 43 43 11之前 xff0c auto用来声明自动变量 xff0c 表明变量存储在栈 xff0c 很少使用 在C 43 43 11中被赋予了新的含义和作用 xff0c 用于类型推断 a
  • Java反射(Reflex)机制

    反射概述 Reflection 反射 是Java被视为动态语言的关键 xff0c 反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息 xff0c 并能直接操作任意对象的内部属性及方法 加载完类之后 xff0c 在
  • 偏航角、俯仰角、滚动角

    偏航角 俯仰角 滚动角 在姿态估计中 xff0c 常常会提到三个概念 xff0c 就是偏航角 俯仰角和滚动角 姿态估计是物体在三维空间内方向的表征 通常描述物体姿态以大地作为参考系 xff08 标准坐标系 xff09 将世界坐标系 xff0
  • 定时器与计数器的区别

    曾经我在北方工业大学复试的时候 xff0c 有个老师问了我个问题 他说同学你好 xff0c 我看你前面一直都在说深度学习的问题 xff0c 请问你对单片机了解吗 xff1f xff0c 请问定时器与计数器的区别是什么 xff1f 我告诉他
  • 编译make px4_fmu-v2_default upload时,报错ERROR Board can accept larger flash images (2080768 bytes)

    编译make px4 fmu v2 default upload时 xff0c 报错ERROR Board can accept larger flash images 2080768 bytes than board config 103
  • vue中实现axios封装

    vue中实现axios封装 为什么要进行axios封装vue项目安装axios封装前的get和post请求封装后的get和post请求 为什么要进行axios封装 实际开发过程中可能有几种环境 xff0c 开发环境 xff0c 测试环境 x
  • 项目实训—场景划分(一)

    1 什么是场景 xff08 Scene xff09 场景作为电影讲故事的关键单元 xff0c 包含了演员的复杂活动及其在物理位置上的互动 识别场景的组成和语义是视觉理解复杂长视频 xff08 如电影 电视剧 娱乐节目和综艺节目 xff09
  • 手把手教你FreeRTOS源码解析(一)——内存管理

    FreeRTOS中一共有5种内存分配的方法 xff0c 分别在文件heap 1 c heap 2 c heap 3 c heap 4 c heap 5 c种 虽然标准C库中的 malloc 和 free 也可以实现动态内存管理 xff0c
  • 手把手教你FreeRTOS源码详解(二)——任务管理

    FreeRTOS源码解析集合 xff08 全网最详细 xff09 手把手教你FreeRTOS源码解析 xff08 一 xff09 内存管理 手把手教你FreeRTOS源码详解 xff08 二 xff09 任务管理 手把手教你FreeRTOS
  • 手把手教你FreeRTOS源码详解(三)——队列

    FreeRTOS源码解析集合 xff08 全网最详细 xff09 手把手教你FreeRTOS源码解析 xff08 一 xff09 内存管理 手把手教你FreeRTOS源码详解 xff08 二 xff09 任务管理 手把手教你FreeRTOS