【FreeRTOS】【STM32】08 FreeRTOS 消息队列
简单来说
消息队列是一种数据结构
任务操作队列的基本描述
1.如果队列未满
或者允许覆盖入队
,FreeRTOS会将任务需要发送的消息添加到队列尾
。
2.如果队列满
,任务会阻塞(等待)。
3.用户可以指定等待时间。
4.当其它任务从其等待的队列中读取入了数据(这时候队列未满了)
5.该任务(等待发送数据到队列的任务)将自动由阻塞态转移为就绪态。
6.超过了等待时间,即使队列还不允许访问,任务也会自动从阻塞态转移为就绪态,此时发送消息的任务或者中断程序会收到一个错误码 errQUEUE_FULL。
消息队列使用注意事项
1、发送,接收消息前需要定义一个消息队列,并根据队列句柄进行操作
2、队列读取采用的是先进先出(FIFO)模式,会先读取先存储在队列中的据。当然也 FreeRTOS 也支持后进先出(LIFO)模式。
3、读取数据前需要定义读取buffer。
4、无论是发送或者是接收消息都是以拷贝的方式进行,如果消息过于庞大,可以将消息的地址作为消息进行发送、接收。
5、 队列是具有自己独立权限的内核对象,并不属于任何任务。所有任务都可以向同
一队列写入和读出。一个队列由多任务或中断写入是经常的事,但由多个任务读
出倒是用的比较少。
消息队列创建函数 xQueueCreate()
1.xQueueCreate
函数是基于xQueueGenericCreate
进行创建
2.使用xQueueCreate()创建队列时,使用的是动态内存分配,所以要想使用该函数必须在FreeRTOSConfig.h 中把 configSUPPORT_DYNAMIC_ALLOCATION 定义为 1 来使能。FreeRTOSConfig.h 中这个宏定义默认1.
3.消息量API函数也是基于xQueueGenericCreate
进行创建的
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )#define xQueueCreate( uxQueueLength, uxItemSize ) \ xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), ( queueQUEUE_TYPE_BASE ) ) #endif
函数原型
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength,UBaseType_t uxItemSize );
参数:
uxQueueLength
:队列能够存储的最大消息单元数目,即队列长度。
uxItemSize
:队列中消息单元的大小,以字节为单位
返回一个句柄,句柄是一个指向队列数据结构类型的指针,RTOS中对函数以及数据结构的操作都是通过句柄。
使用示例:
QueueHandle_t Test_Queue =NULL;//队列句柄#define QUEUE_LEN 4 /* 队列的长度,最大可包含多少个消息 */
#define QUEUE_SIZE 4 /* 队列中每个消息大小(字节) */BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */taskENTER_CRITICAL(); //进入临界区/* 创建 Test_Queue */
Test_Queue = xQueueCreate((UBaseType_t ) QUEUE_LEN,/* 消息队列的长度 */ (UBaseType_t ) QUEUE_SIZE);/* 消息的大小 */ if (NULL != Test_Queue) printf("创建 Test_Queue 消息队列成功!\r\n");taskEXIT_CRITICAL(); //退出临界区
消息队列静态创建函数 xQueueCreateStatic()
与静态创建任务一样,静态创建消息队列,也需要自定义一块内存
函数原型
QueueHandle_t xQueueCreateStatic(UBaseType_t uxQueueLength,UBaseType_t uxItemSize,uint8_t *pucQueueStorageBuffer,StaticQueue_t *pxQueueBuffer );
参数
uxQueueLength
:队列能够存储的最大单元数目,即队列深度
uxItemSize
:队列中数据单元的长度,以字节为单位。
pucQueueStorageBuffer
:指针,指向一个 uint8_t 类型的数组,数组的大小至少有uxQueueLength* uxItemSize 个字节。当 uxItemSize 为 0 时,pucQueueStorageBuffer 可以为 NULL。
pxQueueBuffer
:指针,指向 StaticQueue_t 类型的变量,该变量用于存储队列的数据结构。
如果创建成功则返回一个队列句柄,用于访问创建的队列。如果创建不成功则返回NULL,可能原因是创建队列需要的 RAM 无法分配成功。
使用示例
/* 创建一个可以最多可以存储 10 个 64 位变量的队列 */
#define QUEUE_LENGTH 10
#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;/* 创建一个队列 */ xQueue = xQueueCreateStatic( QUEUE_LENGTH, /* 队列深度 */ ITEM_SIZE, /* 队列数据单元的单位 */ ucQueueStorageArea,/* 队列的存储区域 */ &xStaticQueue ); /* 队列的数据结构 */ /* 剩下的其他代码 */}
消息队列删除函数 vQueueDelete()
传入形参是消息队列的队柄
使用示例
#define QUEUE_LENGTH 5
#define QUEUE_ITEM_SIZE 4int main( void )
{QueueHandle_t xQueue;/* 创建消息队列 */xQueue = xQueueCreate( QUEUE_LENGTH, QUEUE_ITEM_SIZE );if ( xQueue == NULL ) {/* 消息队列创建失败 */} else {/* 删除已创建的消息队列 */ vQueueDelete( xQueue ); }}
向消息队列发送消息函数
任务
或者中断服务程序
都可以给消息队列发送消息.
所有的发送消息函数都是基于xQueueGenericSend
.
xQueueSend()与 xQueueSendToBack() 用于非中断程序
函数原型:
BaseType_t xQueueSend(QueueHandle_t xQueue,const void * pvItemToQueue,TickType_t xTicksToWait);
参数
:
xQueue
:目标队列句柄
pvItemToQueue
:指针,指向要发送到队列尾部的队列消息
xTicksToWait
:队列满时,等待队列空闲的最大超时时间。如果队列满并 且xTicksToWait 被设置成 0,函数立刻返回。超时时间的单位为系统节拍周期,常量 portTICK_PERIOD_MS 用于辅助计算真实的时间,单位为 ms。如果 INCLUDE_vTaskSuspend 设置成 1,并且指定延时为 portMAX_DELAY 将导致任务挂起(没有超时)。
返回值
:消息发送成功成功返回 pdTRUE,否则返回 errQUEUE_FULL。
宏定义
#define xQueueSend( xQueue, pvItemToQueue, xTicksToWait ) \xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), \( xTicksToWait ), queueSEND_TO_BACK )
可以看出来xQueueSend()
是一个宏,宏展开是调用函数 xQueueGenericSend()
.
#define xQueueSendToBack( xQueue, pvItemToQueue, xTicksToWait ) \xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), \( xTicksToWait ), queueSEND_TO_BACK )
可以看出来,xQueueSendToBack
宏展开是调用函数 xQueueGenericSend()
.
xQueueSend() 等同于xQueueSendToBack()。
xQueueSend()用于向队列尾部发送一个队列消息。消息以拷贝的形式入队,而不是以引用的形式。该函数绝对不能在中断服务程序里面被调用,中断中必须使用带有中断保护功能的 xQueueSendFromISR()来代替。
使用实例
Send_Task
作为一个任务函数,功能是检测到按键之后发送消息到队列
这个实际任务也是任务的一个形参。 任务创建
static void Send_Task(void* parameter)
{BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */uint32_t send_data1 = 1;uint32_t send_data2 = 2;while (1) {if ( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON ) {/* K1 被按下 */printf("发送消息 send_data1!\n"); xReturn = xQueueSend( Test_Queue, /* 消息队列的句柄 */ &send_data1,/* 发送的消息内容 */ 0 ); /* 等待时间 0 */ if (pdPASS == xReturn) printf("消息 send_data1 发送成功!\n\n"); }if ( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON ) {/* K2 被按下 */printf("发送消息 send_data2!\n"); xReturn = xQueueSend( Test_Queue, /* 消息队列的句柄 */ &send_data2,/* 发送的消息内容 */ 0 ); /* 等待时间 0 */ if (pdPASS == xReturn) printf("消息 send_data2 发送成功!\n\n"); }vTaskDelay(20);/* 延时 20 个 tick */}}
xQueueSendFromISR()与 xQueueSendToBackFromISR() 中断服务函数中发送消息
函数原型:
BaseType_t xQueueSendFromISR(QueueHandle_t xQueue,const void *pvItemToQueue,BaseType_t *pxHigherPriorityTaskWoken);
参数
:
xQueue
:目标队列句柄
pvItemToQueue
:指针,指向要发送到队列尾部的消息
pxHigherPriorityTaskWoken
:如果入队导致一个任务解锁,并且解锁的任务优先级高于当前被中断的任务,则将*pxHigherPriorityTaskWoken设置成 pdTRUE,然后在中断退出前需要进行一次上下文切换, 去执行被唤醒的优先级更高的任务 。从FreeRTOS V7.3.0 起pxHigherPriorityTaskWoken 作为一个可选参数,可以设置为 NULL。
返回值
:
消息发送成功返回 pdTRUE,否则返回 errQUEUE_FULL。
与xQueueSend
函数相同,xQueueSendFromISR()
与 xQueueSendToBackFromISR()
也是宏定义。
xQueueSendFromISR():
#define xQueueSendToFrontFromISR(xQueue,pvItemToQueue,pxHigherPriorityTaskWoken) \xQueueGenericSendFromISR( ( xQueue ), ( pvItemToQueue ),\( pxHigherPriorityTaskWoken ), queueSEND_TO_FRONT )
xQueueSendToBackFromISR 等同于 xQueueSendFromISR ()。
xQueueSendToBackFromISR():
#define xQueueSendToBackFromISR(xQueue,pvItemToQueue,pxHigherPriorityTaskWoken) \xQueueGenericSendFromISR( ( xQueue ), ( pvItemToQueue ), \( pxHigherPriorityTaskWoken ), queueSEND_TO_BACK )
使用实例
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 ) );/* 这时候 buffer 已经为空,如果需要则进行上下文切换 */ if ( xHigherPriorityTaskWoken ) { /* 上下文切换,这是一个宏,不同的处理器,具体的方法不一样 */ taskYIELD_FROM_ISR (); } }
我如何知道中断服务函数中有没有唤醒任务?
向队列首发送消息
xQueueSendToFront()
用法与xQueueSend()一致。用于task,中断中使用有中断保护的xQueueSendToFrontFromISR (),类似的xQueueSendToFrontFromISR
也是宏定义,基于xQueueGenericSendFromISR
。
函数原型
BaseType_t xQueueSendToFront( QueueHandle_t xQueue,const void * pvItemToQueue,TickType_t xTicksToWait );
参数
:
xQueue
:队列句柄
pvItemToQueue
:指针,指向要发送到队首的消息。
xTicksToWait
:队列满时,等待队列空闲的最大超时时间。如果队列满并 且xTicksToWait 被设置成 0,函数立刻返回。超时时间的单位为系统节拍周期,常量 portTICK_PERIOD_MS 用于辅助计算真实的时间,单位为 ms。如果 INCLUDE_vTaskSuspend 设置成 1,并且指定延时为 portMAX_DELAY 将导致任务无限阻塞(没有超时).
xQueueSendToFrontFromISR()
函数原型
BaseType_t xQueueSendToFrontFromISR(QueueHandle_t xQueue,const void *pvItemToQueue,BaseType_t *pxHigherPriorityTaskWoken);
参数:
xQueue
:队列句柄
pvItemToQueue
:指针,指向要发送到队首的消息
pxHigherPriorityTaskWoken
:如果入队导致一个任务解锁,并且解锁的任务优先高于当前被中断的任务,则将*pxHigherPriorityTaskWoken设置成 pdTRUE,然后在中断退出前需要进行一次上下文切换, 去执行被唤醒的优先级更高的任务 。从FreeRTOS V7.3.0 起,pxHigherPriorityTaskWoken 作为一个可选参数,可以设置为 NULL。
返回值:队列项投递成功返回 pdTRUE,否则返回 errQUEUE_FULL。
从消息队列读取消息函数
xQueueReceive() 接收并从消息队列里删除数据
xQueueReceive()
是一个宏 , 宏展开是调用函数xQueueGenericReceive()
。xQueueReceive()用于从一个队列中接收消息并把消息从队列中删除。
接收的消息是以拷贝的形式进行的,所以我们必须提供一个足够大空间的缓冲区。
类似发送消息到队列,这个函数用于普通Task,中断服务函数中使用带有中断保护功能的 xQueueReceiveFromISR
。
函数原型
BaseType_t xQueueReceive(QueueHandle_t xQueue,void *pvBuffer,TickType_t xTicksToWait);
参数:
xQueue
:队列句柄
pvBuffer
:指针,指向接收到要保存的数据
xTicksToWait
:队列空时,阻塞超时的最大时间。如果该参数设置为 0,函数立刻返回。超时时间的单位为系统节拍周期,常量 portTICK_PERIOD_MS 用于辅助计算真实的时间,单位为 ms。如果 INCLUDE_vTaskSuspend 设置成 1,并且指定延时为 portMAX_DELAY 将导致任务无限阻塞(没有超时)。
返回值:队列项接收成功返回 pdTRUE,否则返回 pdFALSE。
xQueueReceive函数使用实例
Receive_Task是一个任务函数。
static void Receive_Task(void* parameter)
{BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为 pdPASS */uint32_t r_queue; /* 定义一个接收消息的变量 */while (1) {xReturn = xQueueReceive( Test_Queue, /* 消息队列的句柄 */ &r_queue, /* 接受的数据 */ portMAX_DELAY); /* 等待时间 一直等 */ if (pdTRUE== xReturn) printf("本次接收到的数据是:%d\n\n",r_queue); else printf("数据接收出错,错误代码: 0x%lx\n",xReturn); }}
xQueuePeek() 接收但不从消息队列里删除数据
xQueuePeek使用方法与xQueueReceive一致,但是不会从消息队列里删除数据。
xQueueReceiveFromISR()与 xQueuePeekFromISR()中断服务函数的接收消息版本
从队列中获取消息的中断服务函数版本。
xQueueReceiveFromISR函数原型
BaseType_t xQueueReceiveFromISR(QueueHandle_t xQueue,void *pvBuffer,BaseType_t *pxHigherPriorityTaskWoken);
参数:
xQueue
:队列句柄
pvBuffer
:指针,指向存放接收到的数据buffer
pxHigherPriorityTaskWoken
:任务在往队列投递信息时,如果队列满,则任务将阻塞在该队列上。如果 xQueueReceiveFromISR引起了一个TASK被唤醒则将 *pxHigherPriorityTaskWoken 设置为pdTRUE
//---------------------------------------------------------------------------------------------------
pxHigherPriorityTaskWoken
对于这个参数的理解,联系xQueueReceiveFromISR()
与 xQueuePeekFromISR()
函数的区别,一个函数是可以从队列中删除数据,一个函数是不可以从队列中删除数据。删除了队列中的数据可能导致一个task被唤醒,中断服务函数在Task中中断执行的,如果引起了一个别的TASK,那么下一步如何执行这两个TASK呢?
//---------------------------------------------------------------------------------------------------
xQueuePeekFromISR函数原型
BaseType_t xQueuePeekFromISR(QueueHandle_t xQueue,void *pvBuffer);
参数:
xQueue
:队列句柄。
pvBuffer
:指针,指向接收到要保存的数据。
xQueueReceiveFromISR使用范例
QueueHandle_t xQueue;/* 创建一个队列,并往队列里面发送一些数据 */
void vAFunction( void *pvParameters )
{char cValueToPost;const TickType_t xTicksToWait = ( TickType_t )0xff;/* 创建一个可以容纳 10 个字符的队列 */xQueue = xQueueCreate( 10, sizeof( char ) );if ( xQueue == 0 ) {/* 队列创建失败 */}/* ... 任务其他代码 *//* 往队列里面发送两个字符 如果队列满了则等待 xTicksToWait 个系统节拍周期*/cValueToPost = 'a';xQueueSend( xQueue, ( void * ) &cValueToPost, xTicksToWait );cValueToPost = 'b';xQueueSend( xQueue, ( void * ) &cValueToPost, xTicksToWait );/* 继续往队列里面发送字符当队列满的时候该任务将被阻塞*/cValueToPost = 'c';xQueueSend( xQueue, ( void * ) &cValueToPost, xTicksToWait );}/* 中断服务程序:输出所有从队列中接收到的字符 */void vISR_Routine( void ){BaseType_t xTaskWokenByReceive = pdFALSE;char cRxedChar;while ( xQueueReceiveFromISR( xQueue, ( void * ) &cRxedChar, &xTaskWokenByReceive) ){ /* 接收到一个字符,然后输出这个字符 */vOutputCharacter( cRxedChar );/* 如果从队列移除一个字符串后唤醒了向此队列投递字符的任务,那么参数 xTaskWokenByReceive 将会设置成 pdTRUE,这个循环无论重复 多少次,仅会有一个任务被唤醒 */}if ( xTaskWokenByReceive != pdFALSE ) { /* 我们应该进行一次上下文切换,当 ISR 返回的时候则执行另外一个任务 */ /* 这是一个上下文切换的宏,不同的处理器,具体处理的方式不一样 */ taskYIELD (); } }
消息队列实例
/* ************************************************************************** 包含的头文件**************************************************************************//* FreeRTOS 头文件 */#include "FreeRTOS.h"#include "task.h"#include "queue.h"/* 开发板硬件 bsp 头文件 */#include "bsp_led.h"#include "bsp_usart.h"#include "bsp_key.h"/**************************** 任务句柄 ********************************//** 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄* 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么* 这个句柄可以为 NULL。*/static TaskHandle_t AppTaskCreate_Handle = NULL;/* 创建任务句柄 */static TaskHandle_t Receive_Task_Handle = NULL;/* LED 任务句柄 */static TaskHandle_t Send_Task_Handle = NULL;/* KEY 任务句柄 *//***************************** 内核对象句柄 *****************************//** 信号量,消息队列,事件标志组,软件定时器这些都属于内核的对象,要想使用这些内核* 对象,必须先创建,创建成功之后会返回一个相应的句柄。实际上就是一个指针,后续我* 们就可以通过这个句柄操作这些内核对象。** * 内核对象说白了就是一种全局的数据结构,通过这些数据结构我们可以实现任务间的通信,* 任务间的事件同步等各种功能。至于这些功能的实现我们是通过调用这些内核对象的函数* 来完成的**/QueueHandle_t Test_Queue =NULL;/*************************** 全局变量声明 *******************************//** 当我们在写应用程序的时候,可能需要用到一些全局变量。*//*************************** 宏定义 ************************************//** 当我们在写应用程序的时候,可能需要用到一些宏定义。*/#define QUEUE_LEN 4 /* 队列的长度,最大可包含多少个消息 */ #define QUEUE_SIZE 4 /* 队列中每个消息大小(字节) */ /*************************************************************************** 函数声明**************************************************************************/static void AppTaskCreate(void);/* 用于创建任务 */static void Receive_Task(void* pvParameters);/* Receive_Task 任务实现 */static void Send_Task(void* pvParameters);/* Send_Task 任务实现 */static void BSP_Init(void);/* 用于初始化板载相关资源 *//****************************************************************** @brief 主函数* @param 无* @retval 无* @note 第一步:开发板硬件初始化第二步:创建 APP 应用任务第三步:启动 FreeRTOS,开始多任务调度****************************************************************/int main(void){BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS *//* 开发板硬件初始化 */BSP_Init();printf("按下 KEY1 或者 KEY2 发送队列消息\n");printf("Receive 任务接收到消息在串口回显\n\n");/* 创建 AppTaskCreate 任务 */xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate, /* 任务入口函数 */(const char* )"AppTaskCreate",/* 任务名字 */(uint16_t )512, /* 任务栈大小 */(void* )NULL,/* 任务入口函数参数 */(UBaseType_t )1, /* 任务的优先级 */(TaskHandle_t* )&AppTaskCreate_Handle);/* 任务控制块指*//* 启动任务调度 */if (pdPASS == xReturn)vTaskStartScheduler(); /* 启动任务,开启调度 */elsereturn -1;
while (1); /* 正常不会执行到这里 */}/************************************************************************ @ 函数名 : AppTaskCreate* @ 功能说明: 为了方便管理,所有的任务创建函数都放在这个函数里面* @ 参数 : 无* @ 返回值 : 无********************************************************************/static void AppTaskCreate(void){BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */taskENTER_CRITICAL(); //进入临界区/* 创建 Test_Queue */ Test_Queue = xQueueCreate((UBaseType_t ) QUEUE_LEN,/* 消息队列的长度 */ (UBaseType_t ) QUEUE_SIZE);/* 消息的大小 */ if (NULL != Test_Queue) printf("创建 Test_Queue 消息队列成功!\r\n"); /* 创建 Receive_Task 任务 */xReturn = xTaskCreate((TaskFunction_t )Receive_Task,/* 任务入口函数 */(const char* )"Receive_Task",/* 任务名字 */(uint16_t )512, /* 任务栈大小 */(void* )NULL, /* 任务入口函数参数 */(UBaseType_t )2, /* 任务的优先级 */(TaskHandle_t* )&Receive_Task_Handle);/*任务控制块指针*/if (pdPASS == xReturn)printf("创建 Receive_Task 任务成功!\r\n");/* 创建 Send_Task 任务 */xReturn = xTaskCreate((TaskFunction_t )Send_Task, /* 任务入口函数 */(const char* )"Send_Task",/* 任务名字 */(uint16_t )512, /* 任务栈大小 */(void* )NULL,/* 任务入口函数参数 */(UBaseType_t )3, /* 任务的优先级 */(TaskHandle_t* )&Send_Task_Handle);/*任务控制块指针 */if (pdPASS == xReturn)printf("创建 Send_Task 任务成功!\n\n");vTaskDelete(AppTaskCreate_Handle); //删除 AppTaskCreate 任务 taskEXIT_CRITICAL(); //退出临界区}/*********************************************************************** @ 函数名 : Receive_Task* @ 功能说明: Receive_Task 任务主体* @ 参数 :* @ 返回值 : 无********************************************************************/static void Receive_Task(void* parameter) { BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为 pdTRUE */ uint32_t r_queue; /* 定义一个接收消息的变量 */ while (1) { xReturn = xQueueReceive( Test_Queue, /* 消息队列的句柄 */ &r_queue, /* 发送的消息内容 */ portMAX_DELAY); /* 等待时间 一直等 */ if (pdTRUE == xReturn) printf("本次接收到的数据是%d\n\n",r_queue); else printf("数据接收出错,错误代码: 0x%lx\n",xReturn); } } /*********************************************************************** @ 函数名 : Send_Task* @ 功能说明: Send_Task 任务主体* @ 参数 :* @ 返回值 : 无********************************************************************/static void Send_Task(void* parameter) { BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */ uint32_t send_data1 = 1; uint32_t send_data2 = 2; while (1) { if ( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON ) { /* KEY1 被按下 */ printf("发送消息 send_data1!\n"); xReturn = xQueueSend( Test_Queue, /* 消息队列的句柄 */ &send_data1,/* 发送的消息内容 */ 0 ); /* 等待时间 0 */ if (pdPASS == xReturn) printf("消息 send_data1 发送成功!\n\n"); } if ( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON ) { /* KEY2 被按下 */ printf("发送消息 send_data2!\n"); xReturn = xQueueSend( Test_Queue, /* 消息队列的句柄 */ &send_data2,/* 发送的消息内容 */ 0 ); /* 等待时间 0 */ if (pdPASS == xReturn) printf("消息 send_data2 发送成功!\n\n"); } vTaskDelay(20);/* 延时 20 个 tick */ } } /************************************************************************ @ 函数名 : BSP_Init* @ 功能说明: 板级外设初始化,所有板子上的初始化均可放在这个函数里面* @ 参数 :* @ 返回值 : 无*********************************************************************/static void BSP_Init(void){/** STM32 中断优先级分组为 4,即 4bit 都用来表示抢占优先级,范围为:0~15* 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,* 都统一用这个优先级分组,千万不要再分组,切忌。*/NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);/* LED 初始化 */LED_GPIO_Config();/* 串口初始化 */USART_Config();/* 按键初始化 */Key_GPIO_Config();}/*******************************END OF FILE****************************/
相关文章:
【FreeRTOS】【STM32】08 FreeRTOS 消息队列
简单来说 消息队列是一种数据结构 任务操作队列的基本描述 1.如果队列未满或者允许覆盖入队,FreeRTOS会将任务需要发送的消息添加到队列尾。 2.如果队列满,任务会阻塞(等待)。 3.用户可以指定等待时间。 4.当其它任务从其等待的队列中读取入了数据(这时候队列未满…...

【计算机组成原理】CPU的工作原理
一.CPU的组成结构 CPU主要有运算器、控制器、寄存器和内部总线等组成,其大概的样子长这样: 看不懂没关系,我们将采用自顶而下的方法来讲解CPU的具体工作原理,我们首先来说一下什么叫寄存器,顾名思义,寄存器…...

部署ELK
一、elasticsearch #拉取镜像 docker pull elasticsearch:7.12.1 #创建ELK docker网络 docker network create elk #启动ELK docker run -d --name es --net elk -P -e "discovery.typesingle-node" elasticsearch:7.12.1 #拷贝配置文件 docker cp es:/usr/share/el…...
纯前端实现图片验证码
前言 之前业务系统中验证码一直是由后端返回base64与一个验证码的字符串来实现的,想了下,前端其实可以直接canvas实现,减轻服务器压力。 实现 子组件,允许自定义图片尺寸(默认尺寸为100 * 40)与验证码刷新时间(默认时间为60秒)…...
#django基本常识01#
1、manage.py 所有子命令的入口,比如: python3 manage.py runserver 启动服务 python3 manage.py startapp 创建应用 python3 manage.py migrate 数据库迁移 直接执行python3 manage.py 可显示所有子命令...

什么是物流RPA?物流RPA解决什么问题?物流RPA实施难点在哪里?
RPA指的是机器人流程自动化,它是一套模拟人类在计算机、平板电脑、移动设备等界面执行任务的软件。通过RPA,可以自动完成重复性、繁琐的工作,提高工作效率和质量,降低人力成本。RPA适用于各种行业和场景,例如财务、人力…...

乐鑫工程部署过程记录
一、获取编译环境 1、下载sdk,ESP-IDF 这里有很多发布版本,当前我选择的是4.4.6,可以选择下载压缩包,也可以git直接clone 2、配置编译环境 我选择的是Linux Ubuntu下部署开发环境 查看入门指南 选择对应的芯片,我…...
to 后接ing形式的情况
look forward to seeing you. (期待着见到你) She admitted to making a mistake. (承认犯了个错误) He is accustomed to working long hours. (习惯于长时间工作)...

我做云原生的那几年
背景介绍 在2020年6月,我加入了一家拥有超过500人的企业。彼时,前端团队人数众多,有二三十名成员。在这样的大团队中,每个人都要寻找自己的独特之处和核心竞争力。否则,你可能会沉没于常规的增删改查工作中࿰…...
@EventListener注解使用说明
在Java的Spring框架中,EventListener注解用于监听和处理应用程序中的各种事件。通过使用EventListener注解,开发人员可以方便地实现事件驱动的编程模型,提高代码的灵活性和可维护性。本文将详细探讨EventListener注解的使用方法和作用&#x…...

算法通关村第五关-白银挑战实现队列
大纲 队列基础队列的基本概念和基本特征实现队列队列的基本操作Java中的队列 队列基础 队列的基本概念和基本特征 队列的特点是节点的排队次序和出队次序按入队时间先后确定,即先入队者先出队,后入队者后出队,即我们常说的FIFO(first in fi…...

协力共创智能未来:乐鑫 ESP RainMaker 云方案线下研讨会圆满落幕
近日,乐鑫 ESP RainMaker 云方案线下研讨会(深圳)在亚马逊云科技与合作伙伴嘉宾的支持下成功举办,吸引了众多来自智能家电、照明电工、能源和宠物等行业的品牌客户、方案商和制造商。研讨会围绕如何基于乐鑫 ESP RainMaker 硬件连…...

读取谷歌地球的kml文件中的经纬度坐标
最近我在B站上传了如何获取研究边界的视频,下面分享一个可以读取kml中经纬度的matlab函数,如此一来就可以获取任意区域的经纬度坐标了。 1.谷歌地球中划分区域 2.matlab读取kml文件 function [sname,lon,lat] kml2xy(ip_kml) % ip_kml ocean_distubu…...

1深度学习李宏毅
目录 机器学习三件事:分类,预测和结构化生成 2、一般会有经常提到什么是标签label,label就是预测值,在机器学习领域的残差就是e和loss编辑3、一些计算loss的方法:编辑编辑 4、可以设置不同的b和w从而控制loss的…...

Flask_Login使用与源码解读
一、前言 用户登录后,验证状态需要记录在会话中,这样浏览不同页面时才能记住这个状态,Flask_Login是Flask的扩展,专门用于管理用户身份验证系统中的验证状态。 注:Flask是一个微框架,仅提供包含基本服务的…...

利用Graviton2和S3免费套餐搭建私人网盘
网盘是一种在线存储服务,提供文件存储,访问,备份,贡献等功能,是我们日常中不可或缺的一种服务。很多互联网公司都为个人和企业提供免费的网盘服务。但这些免费服务都有一些限制,比如限制下载速度࿰…...
跟着GPT学设计模式之单例模式
单例设计模式(Singleton Design Pattern)一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。 单例有几种经典的实现方式,…...
【MySQL索引与优化篇】数据库调优策略
数据库调优策略 文章目录 数据库调优策略1. 数据库调优的措施1.1 调优目标1.2 如何定位调优问题1.3 调优的维度和步骤第1步:选择合适的DBMS第2步:优化表设计第3步:优化逻辑查询第4步:优化物理查询第5步:使用 Redis 或 …...
基于BP神经网络的风险等级预测,BP神经网络的详细原理,
目录 背影 BP神经网络的原理 BP神经网络的定义 BP神经网络的基本结构 BP神经网络的神经元 BP神经网络的激活函数, BP神经网络的传递函数 代码链接:基于BP神经网络的风险等级评价,基于BP神经网络的风险等级预测(代码完整,数据齐全)资源-CSDN文库 https://download.csdn.n…...

最新Ai智能创作系统源码V3.0,AI绘画系统/支持GPT联网提问/支持Prompt应用+搭建部署教程
一、AI创作系统 SparkAi创作系统是基于OpenAI很火的ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统,支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美,可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如…...
题解:洛谷 P12672 「LAOI-8」近期我们注意到有网站混淆视听
设 LGR 存在数量为 x x x,CSP 存在数量为 y y y。 很明显,我们只需要将其中数量较小的一方改没就行了(一个巴掌拍不响)。 每两个字符串可同意进行一次更改,答案为: ⌈ min ( x , y ) 2 ⌉ \left\lce…...
全面解析:npm 命令、package.json 结构与 Vite 详解
全面解析:npm 命令、package.json 结构与 Vite 详解 一、npm run dev 和 npm run build 命令解析 1. npm run dev 作用:启动开发服务器,用于本地开发原理: 启动 Vite 开发服务器提供实时热更新(HMR)功能…...

堆与堆排序及 Top-K 问题解析:从原理到实践
一、堆的本质与核心特性 堆是一种基于完全二叉树的数据结构,其核心特性为父节点与子节点的数值关系,分为大堆和小堆两类: 大堆:每个父节点的值均大于或等于其子节点的值,堆顶元素为最大值。如: 小堆:每个…...

IEEE P370:用于高达 50 GHz 互连的夹具设计和数据质量公制标准
大多数高频仪器,如矢量网络分析仪 (VNA) 和时域反射仪 (TDR),都可以在同轴接口的末端进行非常好的测量。然而,复杂系统中使用的互连很少具有同轴接口。用于表征这些设备的夹具的设计和实施会对测…...
ipv6与p2p的关系
在PCDN(P2P内容分发网络)领域,IPv6与PCDN盒子的关系紧密且相互影响,主要体现在以下几个方面: 一、IPv6的部署推动PCDN盒子普及 地址资源充足 IPv6采用128位地址,解决了IPv4地址枯竭的问题,为PC…...
pytest 常见问题解答 (FAQ)
pytest 常见问题解答 (FAQ) 1. 基础问题 Q1: 如何让 pytest 发现我的测试文件? 测试文件命名需符合 test_*.py 或 *_test.py 模式测试函数/方法需以 test_ 开头测试类需以 Test 开头(且不能有__init__方法) Q2: 如何运行特定测试? pytest path/to/t…...
SQL Server 事务详解:概念、特性、隔离级别与实践
一、事务的基本概念 事务(Transaction)是数据库操作的基本单位,它是由一组SQL语句组成的逻辑工作单元。事务具有以下关键特性,通常被称为ACID特性: 原子性(Atomicity):事务…...

力扣每日一题——找到离给定两个节点最近的节点
目录 题目链接:2359. 找到离给定两个节点最近的节点 - 力扣(LeetCode) 题目描述 解法一:双指针路径交汇法 基本思路 关键步骤 为什么这样可行呢我请问了? 举个例子 特殊情况 Java写法: C写法&a…...
微信小程序返回上一页监听
本文实现的是微信小程序在返回上一页时获取通知并自定义业务。 最简单的实现: 使用 wx.enableAlertBeforeUnload() 优点:快速接入 缺点:手势不能识别、无法自定义弹窗内容(仅询问) 方法二: page-conta…...
Python爬虫实战:研究CherryPy库相关技术
1. 引言 1.1 研究背景与意义 随着互联网信息的爆炸式增长,如何高效地获取、组织和利用网络信息成为重要研究方向。网络爬虫作为自动采集网页内容的关键技术,被广泛应用于搜索引擎构建、市场调研、数据挖掘等领域。同时,将采集到的数据以 Web 服务的形式提供,能够为用户提…...