• 【FreeRTOS】队列的使用


    ❤️作者主页:凉开水白菜
    ❤️作者简介:共同学习,互相监督,热于分享,多加讨论,一起进步!
    ❤️专栏资料:https://pan.baidu.com/s/1nc1rfyLiMyw6ZhxiZ1Cumg?pwd=free
    ❤️点赞 👍 收藏 ⭐再看,养成习惯

    订阅的粉丝可通过PC端左侧加我微信,可对文章的内容进行一对一答疑!



    前言

    基于 FreeRTOS 的应用程序由一组独立的任务构成——每个任务都是具有独立权限的小程序。这些独立的任务之间很可能会通过相互通信以提供有用的系统功能。FreeRTOS 中所有的通信与同步机制都是基于队列实现的;

    创建队列

    队列是具有自己独立权限的内核对象,并不属于或赋予任何任务。所有任务都可以向同一队列写入和读出。一个队列由多方写入是经常的事,但由多方读出倒是很少遇到。

    xQueueHandle xQueueCreate( unsigned portBASE_TYPE uxQueueLength, 
    						   unsigned portBASE_TYPE uxItemSize );
    
    • 1
    • 2

    unsigned portBASE_TYPE uxQueueLength:指定队列的长度,即最多可以存放的元素个数

    unsigned portBASE_TYPE uxItemSize:队列中存储的元素的大小(占用的字节数)

    返回值:返回NULL代表创建失败,没有足够的堆空间来创建当前队列;创建成功则返回队列的句柄

    发送数据

    xQueueSendToBack()用于将数据发送到队列尾;而 xQueueSendToFront()用于将数据发送到队列首,这两个函数在中断中不可使用,在中断中应该采用xQueueSendToFrontFromISR()和xQueueSendToBackFromISR()他们可以在中断中实现和前面两个函数相同的功能(中断章节中断再说明);

    portBASE_TYPE xQueueSendToFront( xQueueHandle xQueue, 
    								const void * pvItemToQueue, 
    								portTickType xTicksToWait );
    								
    portBASE_TYPE xQueueSendToBack( xQueueHandle xQueue, 
    								const void * pvItemToQueue, 
    								portTickType xTicksToWait );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    xQueueHandle xQueue:目标队列的句柄。

    const void * pvItemToQueue:入队元素的指针。队列将存储此指针指向的数据的备份。

    portTickType xTicksToWait:指定等待队列有空间可以容纳新元素入队的最长等待(阻塞)时间,这种情况发生在队列已经满了的时候。如果需要等待,则任务会因为调用这个函数而进入阻塞状态,直到队列非满而能让这个任务写入数据或者指定的阻塞时间过期,才会转变为就绪态。如果此参数使用0,则当队列已经满了的时候,此函数立即返回而不阻塞。使用portMAX_DELAY作为参数将使得等待时间为无限长,直到队列非满而能让这个任务写入数据,注意如果要使用portMAX_DELAY为参数,则必须在FreeRTOSConfig.h将INCLUDE_vTaskSuspend定义为1。

    返回值
    pdPASS,当元素成功入队时返回pdPASS。例如,在限定超时时间(xTicksToWait非0)过期前成功入队。

    errQUEUE_FULL,因为队列满而无法入队时返回errQUEUE_FULL。例如,在限定超时时间(xTicksToWait非0)过期后,队列一直为满而无法入队新元素。

    由于队列可以被多个任务写入,所以对单个队列而言,也可能有多个任务处于阻塞状态以等待队列空间有效。这种情况下,一旦队列空间有效,只会有一个任务会被解除阻塞,这个任务就是所有等待任务中优先级最高的任务。而如果所有等待任务的优先级相同,那么被解除阻塞的任务将是等待最久的任务;

    接收数据

    xQueueReceive()用于从队列中接收(读取)数据单元。接收到的单元同时会从队列中删除。xQueuePeek()也是从从队列中接收数据单元,不同的是并不从队列中删出接收到的单元。xQueuePeek()从队列首接收到数据后,不会修改队列中的数据,也不会改变数据在队列中的存储序顺。
    在中断中应该采用数 xQueueReceiveFromISR()(中断章节再说明);

    portBASE_TYPE xQueueReceive( xQueueHandle xQueue, 
    							const void * pvBuffer, 
    							portTickType xTicksToWait );
    
    portBASE_TYPE xQueuePeek( xQueueHandle xQueue, 
    							const void * pvBuffer, 
    							portTickType xTicksToWait );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    xQueueHandle xQueue:目标队列的句柄。

    const void * pvBuffer:用于存放读取到的队列元素的缓冲区,队列将把出队的元素拷贝到此缓冲区中。

    portTickType xTicksToWait:参见xQueueSendToBack()函数的解释。

    返回值
    pdPASS,当成功从队列读取到元素时返回pdPASS。例如,在限定超时时间(xTicksToWait非0)过期前,成功读取到元素。

    errQUEUE_EMPTY,因为队列空而无法出队时返回errQUEUE_EMPTY。例如,在限定超时时间(xTicksToWait非0)过期后,队列一直为空而无法读取元素。

    由于队列可以被多个任务读取,所以对单个队列而言,也可能有多个任务处于阻塞状态以等待队列数据有效。这种情况下,一旦队列数据有效,只会有一个任务会被解除阻塞,这个任务就是所有等待任务中优先级最高的任务。而如果所有等待任务的优先级相同,那么被解除阻塞的任务将是等待最久的任务;

    简单来说就是写队列和读队列是有先后顺序的,优先级高的先读写,优先级相同的情况下根据等待时间来判断,等待时间长的先读写;

    查询队列数据个数

    uxQueueMessagesWaiting()用于查询队列中当前有效数据单元个数,中断中使用uxQueueMessagesWaitingFromISR()替代;

    unsigned portBASE_TYPE uxQueueMessagesWaiting( xQueueHandle xQueue );
    
    • 1

    xQueueHandle xQueue:队列的句柄
    返回值:队列中的元素个数,返回0代表队列为空

    使用示例

    创建两个线程

    创建一个入队的线程和一个出队的线程

        xTaskCreate((TaskFunction_t )SenderTask,  		/* 任务入口函数 */
                  (const char*    )"send",			/* 任务名字 */
                  (uint16_t       )512,  			/* 任务栈大小 */
                  (void*          )NULL,			/* 任务入口函数参数 */
                  (UBaseType_t    )10, 				/* 任务的优先级 */
                   NULL);	                        /* 任务控制块指针 */ 
                  
                  
        xTaskCreate((TaskFunction_t )RevcerTask,  		/* 任务入口函数 */
                  (const char*    )"revc",			/* 任务名字 */
                  (uint16_t       )512,  			/* 任务栈大小 */
                  (void*          )NULL,			/* 任务入口函数参数 */
                  (UBaseType_t    )10, 				/* 任务的优先级 */
                   NULL);	                        /* 任务控制块指针 */ 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    配置按键驱动

    打开cubemx工程
    在这里插入图片描述
    配置引脚重新生成工程,这里需要注意生成完工程我们工程中前面的更改就需要重新做了,比如讲__NVIC_PRIO_BITS的4U改成4,屏蔽三个中断服务函数;
    在这里插入图片描述
    还需要注意的是配置正点原子的板子按键引脚需要上拉;

    编写按键发送子函数

    static void SenderTask(void *par)
    {
        uint8_t key_state;
        BaseType_t err;
        while(1)
        {
            if((queue!=NULL)&&(HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == 0)) 
            {
                while(HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == 0); // 按键弹起
                key_state = KEY1_PRESS;
                err = xQueueSend(queue,&key_state,10);
                if(err==errQUEUE_FULL)   	//发送按键值
                {
                    printf("数据发送失败\r\n");
                }
            }    
            if((queue!=NULL)&&(HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin) == 0)) 
            {
                while(HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin) == 0);
                key_state = KEY2_PRESS;
                err = xQueueSend(queue,&key_state,10);
                if(err==errQUEUE_FULL)   	//发送按键值
                {
                    printf("数据发送失败\r\n");
                }
            }    
            vTaskDelay(100); // 去抖       
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    编写按键读取子函数

    static void RevcerTask(void *par)
    {
        uint8_t key = 0;
        while(1)
        {
            if(queue!=NULL)
            {
                if(xQueueReceive(queue,&key,portMAX_DELAY))//请求消息Key_Queue
                {
                    switch(key)
                    {
                        case KEY1_PRESS:	
                            printf("KEY1_PRESS\r\n");
                            break;
                        case KEY2_PRESS:		
                            printf("KEY2_PRESS\r\n");
                            break;
                        default:break;
                    }
                }
            }
            vTaskDelay(10);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    实验效果
    在这里插入图片描述

    后面的资源都会在这个连接更新

    链接:https://pan.baidu.com/s/1nc1rfyLiMyw6ZhxiZ1Cumg?pwd=free
    提取码:free

    结尾

    我是凉开水白菜,我们下文见~

  • 相关阅读:
    面试题整理:防抖函数的应用场景和实现方式?
    关于重写equals方法引发的不一致
    代理模式(静态代理、JDK代理、CGLIB代理)
    网页前端设计-作业四(HTML5)
    Spring事务
    各个段位 毕业要准备几篇论文,毕业太难了。
    设计模式-访问者模式
    云安全之访问控制的常见攻击及防御
    10 个用图表解释JavaScript 闭包的面试题
    华为十年大佬带你开启springboot实战之旅,从源码到项目,一步到位!
  • 原文地址:https://blog.csdn.net/qq_43581670/article/details/127668786