FreeRTOS源码分析-7 消息队列
目录
1 消息队列的概念和作用
2 应用
2.1功能需求
2.2接口函数API
2.3 功能实现
3 消息队列源码分析
3.1消息队列控制块
3.2消息队列创建
3.3消息队列删除
3.4消息队列在任务中发送
3.5消息队列在中断中发送
3.6消息队列在任务中接收
3.7消息队列在中断中接收
1 消息队列的概念和作用
消息队列(queue),可以在任务与任务间、中断和任务间传递消息实现任务接收来自其他任务或中断的不固定长度的消息。后面的二值信号量、互斥信号量等都是基于消息队列衍生出来的。

队列是什么?

解决三个问题:
- 排队无序的问题(先入先出,有序)
- 不可能插队的问题(在一端插入)
- 高效的问题(只要入队都能买到票)
FreeRTOS程序中的消息队列
中断和任务不断的发送消息
在固定时间内等待(Timeout,相当于osdelay挂起)消息,没有消息的时候把cpu交给其他任务。


“深度”。在队列创建时需要设定其深度和每个单元的大小。
通常情况下,队列被作为 FIFO(先进先出)使用,即数据由队列尾写入,从队列首读出。当然,由队列首写人也是有可能的。
向队列写入数据是通过字节拷贝把数据存储到队列中,从队列读出数据使得把队列中的数据拷贝删除。
在FreeROTS中,操作消息队列控制块,只要对头有消息,就会取,直到去完为止。
2 应用
2.1功能需求
- 1、使用消息队列检测串口输入
- 2、通过串口发送字符串openled6,openled7,openled8,openled9,分别打开板载led6,led7,led8, led9
- 3、通过串口发送字符串closeled6,closeled7,closeled8,closeled9,分别关闭板载led6,led7,led8, led9
2.2接口函数API
一般在调度器开启之前创建

可以发送在队头、队尾,一般先入先出,有紧急消息可以队头插队,常用的是Send和SendtoBack

启动调度器之前是不能调用此函数的,因为是在中断中触发的,在中断中不能阻塞。第三个参数NULL,已经不用了。

第三个参数NULL,已经不用了。 
2.3 功能实现
CubeMX功能配置
led端口配置、usart1中断配置、创建消息队列


消息队列接收和发送功能开发
串口中断使能
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{GPIO_InitTypeDef GPIO_InitStruct = {0};if(uartHandle->Instance==USART1){/* USER CODE BEGIN USART1_MspInit 0 *//* USER CODE END USART1_MspInit 0 *//* USART1 clock enable */__HAL_RCC_USART1_CLK_ENABLE();__HAL_RCC_GPIOA_CLK_ENABLE();/**USART1 GPIO Configuration PA9 ------> USART1_TXPA10 ------> USART1_RX */GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10;GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;GPIO_InitStruct.Pull = GPIO_PULLUP;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;GPIO_InitStruct.Alternate = GPIO_AF7_USART1;HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);/* USART1 interrupt Init */HAL_NVIC_SetPriority(USART1_IRQn, 5, 0);HAL_NVIC_EnableIRQ(USART1_IRQn);/* USER CODE BEGIN USART1_MspInit 1 *///开启接收中断__HAL_UART_ENABLE_IT(uartHandle,UART_IT_RXNE);/* USER CODE END USART1_MspInit 1 */}
}
串口中断服务函数入队操作
void USART1_IRQHandler(void)
{uint8_t u8Data;//接收中断标志位if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_RXNE) == SET){//读取接收寄存器u8Data = huart1.Instance->DR;//进行入队操作xQueueSendFromISR(CmdQueueHandle,&u8Data,NULL);}HAL_UART_IRQHandler(&huart1);
}
从消息队列出队一直等待,当接收到第一个消息循环从消息队列出队,阻塞等待50ms,完成消息接收
uint8_t u8CmdBuff[20]; //全局变量void Usart_Task(void const * argument)
{uint8_t u8Index;for(;;){//每次读取消息之前,把索引初始化为0u8Index = 0;//1、一直等待接收消息,第一个消息应该放在消息缓冲区的第一个元素上if(xQueueReceive(CmdQueueHandle,&u8CmdBuff[u8Index++],portMAX_DELAY)==pdPASS){while(xQueueReceive(CmdQueueHandle,&u8CmdBuff[u8Index++],50)){}u8CmdBuff[u8Index] = '\0';//保证一包完成字符串信息vParseString(u8CmdBuff);//完成解析以后,要清空接收缓冲区memset(u8CmdBuff,0,20);}}
}
消息解析控制功能开发
根据板载LED端口数量循环遍历,openledX字符串进行比较,如果字符串比较成功打开相关led端口
根据板载LED端口数量循环遍历,closeledX字符串进行比较,如果字符串比较成功打开相关led端口
uint8_t *OpenString[LED_NUM] = {"openled6",
"openled7",
"openled8",
"openled9",};uint8_t *CloseString[LED_NUM] = {"closeled6",
"closeled7",
"closeled8",
"closeled9",};void vParseString(uint8_t *buff){uint8_t i;for(i=0;i<LED_NUM;i++){if(strcmp((char const*)buff,(char const*)OpenString[i]) == 0){HAL_GPIO_WritePin(LedPort[i], LedPin[i], GPIO_PIN_RESET);printf("Cmd is %s\n",OpenString[i]);return;}}for(i=0;i<LED_NUM;i++){if(strcmp((char const*)buff,(char const*)CloseString[i]) == 0){HAL_GPIO_WritePin(LedPort[i], LedPin[i], GPIO_PIN_SET);printf("Cmd is %s\n",CloseString[i]);return;}}}
3 消息队列源码分析
3.1消息队列控制块

3.2消息队列创建

//queue.h//队列实现,实际是xQueueGenericCreate实现的
#define xQueueCreate( uxQueueLength, uxItemSize ) xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), ( queueQUEUE_TYPE_BASE ) )//队列的创建类型
#define queueQUEUE_TYPE_BASE ( ( uint8_t ) 0U ) //基本类型
#define queueQUEUE_TYPE_SET ( ( uint8_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 ) //递归互斥锁/*参数:uxQueueLength 队列长度uxItemSize 队列项大小ucQueueType 队列类型返回值:QueueHandle_t 队列的句柄 其实就是队列控制块地址
*/
QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, const uint8_t ucQueueType )
{Queue_t *pxNewQueue;size_t xQueueSizeInBytes;uint8_t *pucQueueStorage;//队列内存空间为空if( uxItemSize == ( UBaseType_t ) 0 ){/*队列字节大小赋值为0 */xQueueSizeInBytes = ( size_t ) 0;}else{/* 队列字节大小赋值为 长度*每个队列项大小 */xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize ); }// 申请内存空间 消息队列控制块大小+消息队列空间大小pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes );if( pxNewQueue != NULL ){/* 找到消息队列操作空间的首地址 */pucQueueStorage = ( ( uint8_t * ) pxNewQueue ) + sizeof( Queue_t );//初始化一个新的消息队列prvInitialiseNewQueue( uxQueueLength, uxItemSize, pucQueueStorage, ucQueueType, pxNewQueue );}return pxNewQueue;
}/*参数:uxQueueLengthuxItemSizepucQueueStorage:队列操作空间的首地址ucQueueType:队列类型pxNewQueue:队列的句柄
*/
static void prvInitialiseNewQueue( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, uint8_t *pucQueueStorage, const uint8_t ucQueueType, Queue_t *pxNewQueue )
{( void ) ucQueueType;if( uxItemSize == ( UBaseType_t ) 0 )//是否有队列空间{/* 把队列控制块首地址赋值到队列头指针 ??这是互斥信号使用,后面分析*/pxNewQueue->pcHead = ( int8_t * ) pxNewQueue;}else{/* 把队列空间的首地址赋值给队列头指针 */pxNewQueue->pcHead = ( int8_t * ) pucQueueStorage;}/* 长度、单元大小 */pxNewQueue->uxLength = uxQueueLength;pxNewQueue->uxItemSize = uxItemSize;//队列重置函数( void ) xQueueGenericReset( pxNewQueue, pdTRUE );}/*参数:xQueue 队列句柄xNewQueue 操作队列的状态是什么,新建pdTRUE还是已经创建好了返回值:*/
BaseType_t xQueueGenericReset( QueueHandle_t xQueue, BaseType_t xNewQueue )
{Queue_t * const pxQueue = ( Queue_t * ) xQueue;//进入临界段,这时候操作队列控制块,不允许打断taskENTER_CRITICAL();{/*1、头地址赋值2、等待处理的消息个数为03、写入指针赋值为队列头指针4、读出指针写入最后一个可用消息5、赋值队列锁位解锁状态*/pxQueue->pcTail = pxQueue->pcHead + ( pxQueue->uxLength * pxQueue->uxItemSize );pxQueue->uxMessagesWaiting = ( UBaseType_t ) 0U;pxQueue->pcWriteTo = pxQueue->pcHead;pxQueue->u.pcReadFrom = pxQueue->pcHead + ( ( pxQueue->uxLength - ( UBaseType_t ) 1U ) * pxQueue->uxItemSize );pxQueue->cRxLock = queueUNLOCKED;pxQueue->cTxLock = queueUNLOCKED;//判断是否位新建队列if( xNewQueue == pdFALSE )//不是新任务{/* 判断发送等待列表里面是否有任务 */if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE ){// 移除事件列表中的任务,如延时任务if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE ){//进行上下文切换queueYIELD_IF_USING_PREEMPTION();}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}}else//新建队列,直接初始化 发送和接收 列表项{/* Ensure the event queues start in the correct state. */vListInitialise( &( pxQueue->xTasksWaitingToSend ) );vListInitialise( &( pxQueue->xTasksWaitingToReceive ) );}}taskEXIT_CRITICAL();/* A value is returned for calling semantic consistency with previousversions. */return pdPASS;
}
3.3消息队列删除
void vQueueDelete( QueueHandle_t xQueue )
{Queue_t * const pxQueue = ( Queue_t * ) xQueue;#if ( configQUEUE_REGISTRY_SIZE > 0 ){vQueueUnregisterQueue( pxQueue );}#endif#if( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 0 ) ){/* 释放消息队列的内存空间 */vPortFree( pxQueue );}#elif( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 1 ) ){/* 释放消息队列的内存空间 */if( pxQueue->ucStaticallyAllocated == ( uint8_t ) pdFALSE ){vPortFree( pxQueue );}else{mtCOVERAGE_TEST_MARKER();}}#else{( void ) pxQueue;}#endif /* configSUPPORT_DYNAMIC_ALLOCATION */
}
3.4消息队列在任务中发送

分支一:
- 队列未满或覆盖入队,那么看下是否有接收任务,有的话解除一个接收任务,退出临界段。
- 这队列已满,不需要等待的话直接退出临界段。如果需要等待,初始化超时结构体。最后退出临界段挂起调度器。
分支二:
- 锁定队列,判断超时等待是否溢出,溢出解锁跌了恢复调度。
- 没有溢出,再次判断一下队列是否满,没满解锁队列恢复调度器,如果满了将任务插入等待发送列表,解锁队列,恢复调度。
问:为什么在进入临界段?
因为操作的消息是共享资源,可以被多个任务或中断接收和发送,那么操作的时候是不希望被别的任务打断的。
问:为什么要挂起调度器?
不让内核调度,不让其他任务打断下面程序的处理。
问:为什么要锁定队列?
因为消息队列是可以从任务中发送的,锁定队列是告诉任务不要打断下面程序的查询队列和插入队列,否则整个程序会打乱。
/*queueSEND_TO_BACK ?由于信号量都是有消息队列实现的,这个时候,操作系统,定义了消息队列的类型类型:#define queueSEND_TO_BACK ( ( BaseType_t ) 0 ) 1从队尾加入#define queueSEND_TO_FRONT ( ( BaseType_t ) 1 ) 2从对头加入#define queueOVERWRITE ( ( BaseType_t ) 2 ) 3覆盖入队
*/
#define xQueueSend( xQueue, pvItemToQueue, xTicksToWait ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )/*最终实现消息队列发送的是xQueueGenericSend接口参数:xQueue 消息队列句柄pvItemToQueue 要发送的消息的地址xTicksToWait 超时时间xCopyPosition 队列操作类型返回值:BaseTyp_t*/
BaseType_t xQueueGenericSend( QueueHandle_t xQueue, const void * const pvItemToQueue, TickType_t xTicksToWait, const BaseType_t xCopyPosition )
{BaseType_t xEntryTimeSet = pdFALSE, xYieldRequired;TimeOut_t xTimeOut;Queue_t * const pxQueue = ( Queue_t * ) xQueue;/* 使用for循环的目的:为了快速处理消息拷贝,消息处理功能*/for( ;; ){//进入临界段taskENTER_CRITICAL();{/* 1、判断消息队列是否满了2、判断是否允许覆盖入队任何一个成立,执行入队操作*/if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) ){//拷贝数据到队列操作空间内xYieldRequired = prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );#if ( configUSE_QUEUE_SETS == 1 ){//宏定义不看}#else /* configUSE_QUEUE_SETS */{/* 判断等待接收的列表是否为空. */if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE ){//移除等待接收任务的列表,改变等待接收任务的状态为就绪态if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE ){/* 操作成功,进行上下文切换,让任务赶紧处理 */queueYIELD_IF_USING_PREEMPTION();}else{mtCOVERAGE_TEST_MARKER();}}//如果在拷贝数据的时候提示需要调度else if( xYieldRequired != pdFALSE ){/* 再次调度,进行上下文切换*/queueYIELD_IF_USING_PREEMPTION();}else{mtCOVERAGE_TEST_MARKER();}}#endif /* configUSE_QUEUE_SETS *///退出临界段,返回成功taskEXIT_CRITICAL();return pdPASS;}else //如果不允许入队{//是否需要阻塞if( xTicksToWait == ( TickType_t ) 0 ){/* 不需要阻塞,退出临界段,之后返回队列队满 */taskEXIT_CRITICAL();traceQUEUE_SEND_FAILED( pxQueue );return errQUEUE_FULL;}//超时结构体是否操作过else if( xEntryTimeSet == pdFALSE ){/* 超时结构体初始化 */vTaskSetTimeOutState( &xTimeOut );xEntryTimeSet = pdTRUE;}else{/* Entry time was already set. */mtCOVERAGE_TEST_MARKER();}}}//退出临界段taskEXIT_CRITICAL();/* 后面的代码都是允许阻塞处理 *//* 1、挂起了调度器 ----不让其他任务打断2、队列上锁------不让中断打断 (因为之前已经退出临界段了)*/vTaskSuspendAll();prvLockQueue( pxQueue );/* 判断阻塞时间是否超时了 */if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE ){//判断队列是否满if( prvIsQueueFull( pxQueue ) != pdFALSE ){//队满,把当前任务,添加到等待发送的事件列表中,内部还把任务添加到延时列表中去traceBLOCKING_ON_QUEUE_SEND( pxQueue );vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToSend ), xTicksToWait );/* 解锁*/prvUnlockQueue( pxQueue );/* 恢复调度器 */if( xTaskResumeAll() == pdFALSE ){//进行上下文切换portYIELD_WITHIN_API();}}else{//队列未满 解锁,恢复调度器,重新进行入队操作/* Try again. */prvUnlockQueue( pxQueue );( void ) xTaskResumeAll();}}////已经超时了,解锁,开始调度器 返回队满else{/* The timeout has expired. */prvUnlockQueue( pxQueue );( void ) xTaskResumeAll();traceQUEUE_SEND_FAILED( pxQueue );return errQUEUE_FULL;}}
}
3.5消息队列在中断中发送

问:上锁的目的是什么?
如果上锁了,在中断中不会处理,上面3.5在任务中发送的功能。如果中断能打断,那么中断也能操作队列、中断也能操作队列,那么优先级会出现混乱。没有上锁,那么可以操作。
/*最终调用发送消息接口是xQueueGenericSendFromISR???为什么由于信号量都是有消息队列实现的,这个时候,操作系统,定义了消息队列的类型类型:#define queueSEND_TO_BACK ( ( BaseType_t ) 0 ) 1、从队尾加入#define queueSEND_TO_FRONT ( ( BaseType_t ) 1 ) 2、从队头加入#define queueOVERWRITE ( ( BaseType_t ) 2 ) 3、覆盖入队
*/
#define xQueueSendFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken )
xQueueGenericSendFromISR( ( xQueue ), ( pvItemToQueue ), ( pxHigherPriorityTaskWoken ), queueSEND_TO_BACK )
/*参数:xQueuepvItemToQueueNULLqueueSEND_TO_BACK返回值:BaseType_t */
BaseType_t xQueueGenericSendFromISR( QueueHandle_t xQueue, const void * const pvItemToQueue, BaseType_t * const pxHigherPriorityTaskWoken, const BaseType_t xCopyPosition )
{
BaseType_t xReturn;
UBaseType_t uxSavedInterruptStatus;
Queue_t * const pxQueue = ( Queue_t * ) xQueue;/*带返回值的关闭中断,需要保存上次关闭中断的状态值,恢复时候,写入 */uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();{//队满?覆盖入队?if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) ){//获取了队列发送锁的状态值const int8_t cTxLock = pxQueue->cTxLock;//拷贝数据到队列操作空间内( void ) prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );/* 判断队列是否上锁 *//*#define queueUNLOCKED ( ( int8_t ) -1 ) 解锁状态#define queueLOCKED_UNMODIFIED ( ( int8_t ) 0 ) 上锁状态初值*/if( cTxLock == queueUNLOCKED ){#if ( configUSE_QUEUE_SETS == 1 )#else /* configUSE_QUEUE_SETS */{//恢复等待消息任务,中断内没有进行上下文切换,会在开启调度器的时候进行if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE ){if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE ){/* The task waiting has a higher priority so record that acontext switch is required. */if( pxHigherPriorityTaskWoken != NULL ){*pxHigherPriorityTaskWoken = pdTRUE;}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}}#endif /* configUSE_QUEUE_SETS */}else //队列已经上锁{/* 发送锁加一 */pxQueue->cTxLock = ( int8_t ) ( cTxLock + 1 );}//返回成功xReturn = pdPASS;}else{ //返回队满traceQUEUE_SEND_FROM_ISR_FAILED( pxQueue );xReturn = errQUEUE_FULL;}}//开启中断,保存上次状态值portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );return xReturn;
}
//队列上锁
把发送和接受锁都赋值为上锁的初始值
#define prvLockQueue( pxQueue ) \taskENTER_CRITICAL(); \{ \if( ( pxQueue )->cRxLock == queueUNLOCKED ) \{ \( pxQueue )->cRxLock = queueLOCKED_UNMODIFIED; \} \if( ( pxQueue )->cTxLock == queueUNLOCKED ) \{ \( pxQueue )->cTxLock = queueLOCKED_UNMODIFIED; \} \} \taskEXIT_CRITICAL()/*队列解锁参数:消息队列句柄
*/
static void prvUnlockQueue( Queue_t * const pxQueue )
{/* THIS FUNCTION MUST BE CALLED WITH THE SCHEDULER SUSPENDED. *//* 进入临界段 */taskENTER_CRITICAL();{//获取发送锁的状态值int8_t cTxLock = pxQueue->cTxLock;/* 遍历解锁 直到解锁为止 */while( cTxLock > queueLOCKED_UNMODIFIED ){/* Data was posted while the queue was locked. Are any tasksblocked waiting for data to become available? */#if ( configUSE_QUEUE_SETS == 1 )#else /* configUSE_QUEUE_SETS */{/* 解除等待消息任务,进行上下文切换 */if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE ){if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE ){/* The task waiting has a higher priority so record thata context switch is required. */vTaskMissedYield();}else{mtCOVERAGE_TEST_MARKER();}}else{break;}}#endif /* configUSE_QUEUE_SETS *///队列发送锁减一--cTxLock;}//最后,解除发送锁pxQueue->cTxLock = queueUNLOCKED;}//退出临界段taskEXIT_CRITICAL();/* Do the same for the Rx lock. 接收锁也是一样的 */taskENTER_CRITICAL();{int8_t cRxLock = pxQueue->cRxLock;while( cRxLock > queueLOCKED_UNMODIFIED ){if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE ){if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE ){vTaskMissedYield();}else{mtCOVERAGE_TEST_MARKER();}--cRxLock;}else{break;}}pxQueue->cRxLock = queueUNLOCKED;}taskEXIT_CRITICAL();
}
3.6消息队列在任务中接收

分支一:
是出队删除,还是出队不删除,不删除是留个多个任务读取一个消息的情况使用的。
分支二:
接收消息为空,需要判断是否允许阻塞
/*最终调用发送消息接口是xQueueGenericReceive???为什么队列出队,有两种模式一种是:出队后,删除已经读取到队列项或者消息空间另一种是:出队后,不删除,然后恢复出队记录地址,让其他任务或者中断,继续读取使用类型:pdFALSE 删除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 xJustPeeking )//重点分析这一个,其他流程和发送流程差不多//判断是否删除已经接收到的消息空间if( xJustPeeking == pdFALSE ){traceQUEUE_RECEIVE( pxQueue );//更新消息等待的记录值,让它减一pxQueue->uxMessagesWaiting = uxMessagesWaiting - 1;}else{//不删除 就重新赋值未读取消息之前的地址,到出队指针pxQueue->u.pcReadFrom = pcOriginalReadPosition;}
3.7消息队列在中断中接收

源码分析,参考3.5中断中发送最后一部分。
相关文章:
FreeRTOS源码分析-7 消息队列
目录 1 消息队列的概念和作用 2 应用 2.1功能需求 2.2接口函数API 2.3 功能实现 3 消息队列源码分析 3.1消息队列控制块 3.2消息队列创建 3.3消息队列删除 3.4消息队列在任务中发送 3.5消息队列在中断中发送 3.6消息队列在任务中接收 3.7消息队列在中断中接收 1 消…...
机器学习深度学习——权重衰减
👨🎓作者简介:一位即将上大四,正专攻机器学习的保研er 🌌上期文章:机器学习&&深度学习——模型选择、欠拟合和过拟合 📚订阅专栏:机器学习&&深度学习 希望文章对你…...
【Linux】线程互斥 -- 互斥锁 | 死锁 | 线程安全
引入互斥初识锁互斥量mutex锁原理解析 可重入VS线程安全STL中的容器是否是线程安全的? 死锁 引入 我们写一个多线程同时访问一个全局变量的情况(抢票系统),看看会出什么bug: // 共享资源, 火车票 int tickets 10000; //新线程执行方法 vo…...
【vue-pdf】PDF文件预览插件
1 插件安装 npm install vue-pdf vue-pdf GitHub:https://github.com/FranckFreiburger/vue-pdf#readme 参考文档:https://www.cnblogs.com/steamed-twisted-roll/p/9648255.html catch报错:vue-pdf组件报错vue-pdf Cannot read properti…...
Flink集群运行模式--Standalone运行模式
Flink集群运行模式--Standalone运行模式 一、实验目的二、实验内容三、实验原理四、实验环境五、实验步骤5.1 部署模式5.1.1 会话模式(Session Mode)5.1.2 单作业模式(Per-Job Mode)5.1.3 应用模式(Application Mode&a…...
Spring整合JUnit实现单元测试
Spring整合JUnit实现单元测试 一、引言 在软件开发过程中,单元测试是保证代码质量和稳定性的重要手段。JUnit是一个流行的Java单元测试框架,而Spring是一个广泛应用于Java企业级开发的框架。本文将介绍如何使用Spring整合JUnit实现单元测试,…...
Spring Boot学习路线1
Spring Boot是什么? Spring Boot是基于Spring Framework构建应用程序的框架,Spring Framework是一个广泛使用的用于构建基于Java的企业应用程序的开源框架。Spring Boot旨在使创建独立的、生产级别的Spring应用程序变得容易,您可以"只是…...
管理类联考——写作——论说文——实战篇——标题篇
角度3——4种材料类型、4个立意对象、5种写作态度 经过审题立意后,我们要根据我们的立意,确定一个主题,这个主题必须通过文章的标题直接表达出来。 标题的基本要求 主题清晰,态度明确 第一,阅卷人看到一篇论说文的标…...
idea中设置maven本地仓库和自动下载依赖jar包
1.下载maven 地址:maven3.6.3 解压缩在D:\apache-maven-3.6.3-bin\apache-maven-3.6.3\目录下新建文件夹repository打开apache-maven-3.6.3-bin\apache-maven-3.6.3\conf文件中的settings.xml编辑:新增本地仓库路径 <localRepository>D:\apache-…...
前缀和差分
前缀和 前缀和:一段序列里的前n项和 给出n个数,在给出q次问询,每次问询给出L、R,快速求出每组数组中一段L至R区间的和 给出一段数组,每次问询为求出l到r区间的和 普通方法:L到R进行遍历,那么…...
Golang GORM 模型定义
模型定义 参考文档:https://gorm.io/zh_CN/docs/models.html 模型一般都是普通的 Golang 的结构体,Go的基本数据类型,或者指针。 模型是标准的struct,由Go的基本数据类型、实现了Scanner和Valuer接口的自定义类型及其指针或别名组成&#x…...
微服务的各种边界在架构演进中的作用
演进式架构 在微服务设计和实施的过程中,很多人认为:“将单体拆分成多少个微服务,是微服务的设计重点。”可事实真的是这样吗?其实并非如此! Martin Fowler 在提出微服务时,他提到了微服务的一个重要特征—…...
使用 docker-compose 一键部署多个 redis 实例
目录 1. 前期准备 2. 导入镜像 3. 部署redis master脚本 4. 部署redis slave脚本 5. 模板文件 6. 部署redis 7. 基本维护 1. 前期准备 新部署前可以从仓库(repository)下载 redis 镜像,或者从已有部署中的镜像生成文件: …...
14-测试分类
1.按照测试对象划分 ①界面测试 软件只是一种工具,软件与人的信息交流是通过界面来进行的,界面是软件与用户交流的最直接的一层,界面的设计决定了用户对设计的软件的第一印象。界面如同人的面孔,具有吸引用户的直接优势…...
打开域名跳转其他网站,官网被黑解决方案(Linux)
某天打开网站,发现进入首页,马上挑战到其他赌博网站。 事不宜迟,不能让客户发现,得马上解决 我的网站跳转到这个域名了 例如网站跳转到 k77.cc 就在你们部署的代码的当前文件夹下面,执行下如下命令 find -type …...
redis总结
1.redis redis高性能的key-value数据库,支持持久化,不仅仅支持简单的key-value,还提供了list,set,zset,hash等数据结构的存储,支持数据的备份(master-slave模式) redis&…...
现代C++中的从头开始深度学习:激活函数
一、说明 让我们通过在C中实现激活函数来获得乐趣。人工神经网络是生物启发模型的一个例子。在人工神经网络中,称为神经元的处理单元被分组在计算层中,通常用于执行模式识别任务。 在这个模型中,我们通常更喜欢控制每一层的输出以服从一些约束…...
python怎么实现tcp和udp连接
目录 什么是tcp连接 什么是udp连接 python怎么实现tcp和udp连接 什么是tcp连接 TCP(Transmission Control Protocol)连接是一种网络连接,它提供了可靠的、面向连接的数据传输服务。 在TCP连接中,通信的两端(客户端和…...
java设计模式-观察者模式(jdk内置)
上一篇我们学习了 观察者模式。 观察者和被观察者接口都是我们自己定义的,整个设计模式我们从无到有都是自己设计的,其实,java已经内置了这个设计模式,我们只需要定义实现类即可。 下面我们不多说明,直接示例代码&am…...
秒级体验本地调试远程 k8s 中的服务
点击上方蓝色字体,选择“设为星标” 回复”云原生“获取基础架构实践 背景 在这个以k8s为云os的时代,程序员在日常的开发过程中,肯定会遇到各种问题,比如:本地开发完,需要部署到远程k8s集群,本地…...
IDEA运行Tomcat出现乱码问题解决汇总
最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…...
uniapp 对接腾讯云IM群组成员管理(增删改查)
UniApp 实战:腾讯云IM群组成员管理(增删改查) 一、前言 在社交类App开发中,群组成员管理是核心功能之一。本文将基于UniApp框架,结合腾讯云IM SDK,详细讲解如何实现群组成员的增删改查全流程。 权限校验…...
(十)学生端搭建
本次旨在将之前的已完成的部分功能进行拼装到学生端,同时完善学生端的构建。本次工作主要包括: 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...
.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...
【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力
引言: 在人工智能快速发展的浪潮中,快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型(LLM)。该模型代表着该领域的重大突破,通过独特方式融合思考与非思考…...
DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”
目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...
音视频——I2S 协议详解
I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议,专门用于在数字音频设备之间传输数字音频数据。它由飞利浦(Philips)公司开发,以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...
高抗扰度汽车光耦合器的特性
晶台光电推出的125℃光耦合器系列产品(包括KL357NU、KL3H7U和KL817U),专为高温环境下的汽车应用设计,具备以下核心优势和技术特点: 一、技术特性分析 高温稳定性 采用先进的LED技术和优化的IC设计,确保在…...
【Java多线程从青铜到王者】单例设计模式(八)
wait和sleep的区别 我们的wait也是提供了一个还有超时时间的版本,sleep也是可以指定时间的,也就是说时间一到就会解除阻塞,继续执行 wait和sleep都能被提前唤醒(虽然时间还没有到也可以提前唤醒),wait能被notify提前唤醒…...
【Java】Ajax 技术详解
文章目录 1. Filter 过滤器1.1 Filter 概述1.2 Filter 快速入门开发步骤:1.3 Filter 执行流程1.4 Filter 拦截路径配置1.5 过滤器链2. Listener 监听器2.1 Listener 概述2.2 ServletContextListener3. Ajax 技术3.1 Ajax 概述3.2 Ajax 快速入门服务端实现:客户端实现:4. Axi…...
