【北京迅为】《STM32MP157开发板使用手册》- 第三十九章 消息队列实验
iTOP-STM32MP157开发板采用ST推出的双核cortex-A7+单核cortex-M4异构处理器,既可用Linux、又可以用于STM32单片机开发。开发板采用核心板+底板结构,主频650M、1G内存、8G存储,核心板采用工业级板对板连接器,高可靠,牢固耐用,可满足高速信号环境下使用。共240PIN,CPU功能全部引出:底板扩展接口丰富底板板载4G接口(选配)、千兆以太网、WIFI蓝牙模块HDMI、CAN、RS485、LVDS接口、温湿度传感器(选配)光环境传感器、六轴传感器、2路USB OTG、3路串口,CAMERA接口、ADC电位器、SPDIF、SDIO接口等
第三十九章 消息队列实验
39.1 消息队列的基本概念
基于 FreeRTOS 的应用程序由一组独立的任务构成——每个任务都是具有独立权限的程序。这些独立的任务之间的通讯与同步一般都是基于操作系统提供的IPC通讯机制,而FreeRTOS 中所有的通信与同步机制都是基于队列实现的。
消息队列是一种常用于任务间通信的数据结构,队列可以在任务与任务间、中断和任务间传送信息,实现了任务接收来自其他任务或中断的不固定长度的消息。任务能够从队列里面读取消息,当队列中的消息是空时,挂起读取任务,用户还可以指定挂起的任务时间;当队列中有新消息时,挂起的读取任务被唤醒并处理新消息,消息队列是一种异步的通信方式
具有如下特性:
• 消息支持先进先出方式排队,支持异步读写工作方式。
• 读写队列均支持超时机制。
• 消息支持后进先出方式排队,往队首发送消息(LIFO)。
• 可以允许不同长度(不超过队列节点最大值)的任意类型消息。
• 一个任务能够从任意一个消息队列接收和发送消息。
• 多个任务能够从同一个消息队列接收和发送消息。
• 当队列使用结束后,可以通过删除队列函数进行删除。
39.2 消息队列的运作机制
创建消息队列时 FreeRTOS 会先给消息队列分配一块内存空间,这块内存的大小等于消息队列控制块大小加上(单个消息空间大小与消息队列长度的乘积),接着再初始化消息队列,此时消息队列为空。FreeRTOS 的消息队列控制块由多个元素组成,当消息队列被创建时,系统会为控制块分配对应的内存空间,用于保存消息队列的一些信息如消息的存储位置,头指针pcHead、尾指针 pcTail、消息大小 uxItemSize 以及队列长度 uxLength 等。同时每个消息队列都与消息空间在同一段连续的内存空间中,在创建成功的时候,这些内存就被占用了,只有删除了消息队列的时候,这段内存才会被释放掉,创建成功的时候就已经分配好每个消息空间与消息队列的容量,无法更改,每个消息空间可以存放不大于消息大小 uxItemSize 的任意类型的数据,所有消息队列中的消息空间总数即是消息队列的长度,这个长度可在消息队列创建时指定。 任务或者中断服务程序都可以给消息队列发送消息,当发送消息时,如果队列未满或者允许覆盖入队,FreeRTOS 会将消息拷贝到消息队列队尾,否则,会根据用户指定的阻塞超时时间进行阻塞,在这段时间中,如果队列一直不允许入队,该任务将保持阻塞状态以等待队列允许入队。当其他任务从其等待的队列中读取入了数据(队列未满),该任务将自动由阻塞态转移为就绪态。当等待的时间超过了指定的阻塞时间,即使队列中还不允许入队,任务也会自动从阻塞态转移为就绪态,此时发送消息的任务或者中断程序会收到一个错误码。
当其他任务或中断服务程序往其等待的队列中写入了数据,该任务将自动由阻塞态转移为就绪态。当等待的时间超过了指定的阻塞时间,即使队列中尚无有效数据,任务也会自动从阻塞态转移为就绪态。 当消息队列不再被使用时,应该删除它以释放系统资源,一旦操作完成,消息队列将被永久性的删除。
消息队列的运作过程具体见下图:
39.3 实验目的
1)STM32CubeIDE工具软件建立freertos工程
2)学习消息队列相关的知识与初步使用
我们的实验任务为通过三个按键使用队列的方式分别来控制LED2、LED3和蜂鸣器状态的反转。
首先对实验进行分析,总共需要有两个任务,分别为SEND和RECEIVE两个任务。所实现的功能如下:
SEND任务:通过三个按键,分别为 VOL-UP、VOL-DN、BACK,三个按键可以分别用来通过队列,发送不同的值。
RECEIVE任务:通过接收队列中的信息,根据对应的信息实现不同的功能。
本章节完成的实验存放位置为“iTOP-STM32MP157开发板\iTOP-STM32MP157开发板网盘资料汇总\08_freertos实验例程\03_队列实验.zip”
39.4 实验步骤
39.4.1建立freertos_Queue工程
首先我们打开STM32CubeIDE软件,进入软件界面之后,我们点击File属性,选择NEW下的STM32 Project的选项,如下图所示:
然后我们会进入下图所示界面:在Part Number选择框输入STM32MP157A,然后在右边的选择界面选择STM32MP157AAA,然后点击Next选项
在Project Name框中输入工程名字freertos_Queue,然后点击Finish选项即可,如下图所示:
等待工程创建完毕,会询问我们是否要安装OpenSTLinux ,由于我们是在windows环境下,所以我们不需要安装,点击NO即可
至此我们的工程创建完毕,进入工程界面如下图所示界面:
39.4.2输出引脚的配置(LED和蜂鸣器)
首先我们在下面的搜索框之中输入我们要配置的引脚,我们在这里以PE1为例进行搜索,输入名称之后,对应的引脚在工程中会闪烁,如下图所示:
然后我们使用鼠标左键点击对应的引脚会弹出PE1的复用功能选择,我们在这里选择复用为GPIO_Output功能,如下图所示:
配置完复用功能之后,我们还要配置 Pin Reserved 选项,如果不配置此项,在生成工程代码的时候将不会看到有关这个 Pin 的初始化代码。继续选中 PE1,右键弹出设置项,我们选择Pin ReservedàCortex-M4。如下图所示:
第二个LED的控制管脚PE14按同样的方法进行配置。
配置完成之后打开左侧菜单的 System CoreàGPIO 进入 GPIO 模式配置界面:如下图所示:
点击对应的引脚配置之后会弹出右下方的管脚配置界面,如上图所示:
在下方会列出要配置选项的具体说明和我们要进行的配置。
1)选项 GPIO output level 用来设置IO口的输出电平的高低,这这里我们选择LOW
2)选项 GPIO mode 用来设置 IO 口输出模式为 Output Push Pull(推挽)还是 Output Open Drain(开漏)。本实验我们设置为推挽输出 Output Push Pull。
3)选项 GPIO Pull-up/Pull-down 用来设置 IO 口是上拉/下拉/没有上下拉。本实验我们设置为上拉(Pull-up)。
4)选项 Mzximum ouput speed 用来设置 IO 口输出速度为低速(Low)/中速(Medium)/高速 (Hign)/快速(Very High)。我们设置为高速 High 。
5)选项 User Label 是用来设置初始化的 IO 口 Pin 值为我们自定义的宏,这里我们填写为 LED3。按照如上要求设置后的界面如下(由于PE14的配置相同,只是最后的Label值不同,也在下方列了出来):
39.4.3输入引脚的配置(按键)
首先我们在下面的搜索框之中输入我们要配置的引脚,我们在这里以PI2为例进行搜索(由于三个按键的配置相同,在这里我们只是列出了BACK按键的配置步骤),输入名称之后,对应的引脚在工程中会闪烁,如下图所示:
然后我们使用鼠标左键点击对应的引脚会弹出PI2的复用功能选择,我们在这里选择复用为GPIO_Input功能,如下图所示:
配置完复用功能之后,我们还要配置 Pin Reserved 选项,如果不配置此项,在生成工程代码的时候将不会看到有关这个 Pin 的初始化代码。继续选中 PI2,右键弹出设置项,我们选择Pin ReservedàCortex-M4。如下图所示:
VOL-UP和VOL-DN对应的PI3和PI1引脚按同样的方法进行配置。在此就不一一展示。
配置完成之后打开左侧菜单的 System CoreàGPIO 进入 GPIO 模式配置界面:如下图所示:
选项 GPIO Pull-up/Pull-down 用来设置 IO 口是上拉/下拉/没有上下拉。本实验我们设置为上拉(Pull-up)。
选项 User Label 是用来设置初始化的 IO 口 Pin 值为我们自定义的宏,这里我们填写为VOL-DN。按照如上要求设置后的界面如下(由于PI2和PI3的配置相同,只是最后的Label值不同,也在下方列了出来):
39.4.4时钟的配置
我们本次实验所采用的时钟为外部时钟HSE,所以我们要在左侧属性栏中的System Core 属性下找到RCC将High Speed Clock选择为Crystal/Ceramic Resonator(晶体/陶瓷晶振)。如下图所示:
然后在Clock Configuration里我们选择 HSE,作为锁相环 PLL3P 的时钟源,在 MCU 子系统时钟里输入 209 并回车,软件会自动设置相应的倍频和分频,如下图所示:
设置完成之后,如下图所示,然后再手动配置 APB1DIV、APB2DIV 和 APB3DIV的分频值为 2。当 APB1DIV 的分频数大于 1 的时候,基本定时器的倍频器倍频值始终为 2,所以基本定时器的时钟频率为 209MHz。
39.4.5 配置 FreeRTOS
时钟配置完成之后,我们要在左侧属性栏中的Middleware属性下找到FREERTOS将Interface函数接口选择 CMSIS_V2,选择完成如下图所示:
每个功能窗口对应的功能如下:
窗口 | 对应的功能 |
Mutexes | 互斥量 |
Events | 事件 |
FreeRTOS Heap Usage | 堆情况使用 |
User Constants | 常量的定义 |
Tasks and Queues | 任务和消息队列 |
Timers and Semaphores | 软件定时器和信号 |
Config parameters | 配置参数 |
Inclued parameters | 头文件配置 |
Advanced settings | 高级设置 |
然后我们进入到Tasks and Queues任务和消息队列窗口,如下图所示:
随后我们点击default默认创建的任务,将任务名字修改为SEND,然后修改优先级为最低osPriorityLow如下图所示:
我们只需要修改任务名称和设置线程函数名即可,修改完成之后点击OK按钮,随后我们以同样的方式,创建任务名字为RECEIVE的任务,创建完成如下图所示:
在同一界面的下方,就是我们的队列串口,如下图所示,然后我们点击Add添加按钮,
弹出队列的添加窗口之后,对默认的Queue Name进行修改,修改为Test_Queue,然后我们点击确定按钮,如下图所示:
弹出队列的添加窗口之后,对默认的Queue Name进行修改,修改为Test_Queue,然后我们点击确定按钮,如下图所示:
配置完成之后我们需要在Project Manage下的Code Generator选项下勾选 Generate peripheral initialization as a pair of ".c/.h' files per peripheral 选项,这样可以独立生成对应外设的初始化.h 和.c 文件(方便配置的查看),如下图所示:
39.4.6工程的生成与完善
在上述的步骤完成之后,按下键盘的“Ctrl+S”组合键保存保存 freertos_Queue.ioc 文件,系统开始生成初始化代码,此处会弹出一个警告,提示我们 Systick 定时器已被 HAL 库占用,在 STM32MP157 Cortex-M4 内核上我们更换不了其他的定时器,选择 Yes 继续生成代码即可。
工程生成之后如下图所示:
然后我们进行工程的完善,以及添加对应的逻辑代码。
39.4.6.1 对应文件与文件夹的添加
由于我们在裸机章节已经完善了对应的LED,BEEP和KEY文件,所以我们将iTOP-STM32MP157开发板网盘资料汇总\06_Cortex-M4实验例程\03_KEY\KEY\CM4\Core\BSP文件拷贝到当前工程对应的位置,拷贝完成如下下图所示:
39.4.6.2 app_freertos.c文件的完善
我们要修改的 app_freertos.c文件路径如下图所示:
打开app_freertos.c文件,我们首先在/* USER CODE BEGIN Includes */和/* USER CODE END Includes */中间添加以下内容,将led、beep和key的头文件进行添加。
#include "../BSP/Include/led.h"
#include "../BSP/Include/beep.h"
#include "../BSP/Include/key.h"
添加完成如下图所示:
然后我们来到文件的底部可以看到我们创建的RECEIVE_Task和SEND_Task任务
修改RECEIVE_Task 任务的for循环中的内容,修改内容如下:
uint32_t r_queue;
for(;;)
{
osMessageQueueGet( Test_QueueHandle,&r_queue,0, osWaitForever);
if(r_queue == 1)
LED2_TOGGLE();
else if (r_queue == 2)
LED3_TOGGLE();
else if (r_queue == 3)
BEEP_TOGGLE();
}
修改完成如下图所示:
这里的osMessageQueueGet (osMessageQueueId_t mq_id, void *msg_ptr, uint8_t *msg_prio, uint32_t timeout)API函数的四个参数分别的意义如下:
• mq_id :消息队列 ID(消息队列句柄)
• msg_ptr :接收消息的地址
• msg_prio :发送优先级,在源码中可看到该参数被忽略并不生效。
• timeout :线程等待时间
随后我们修改SEND_Task 任务的内容,修改内容如下:
uint8_t key;
uint32_t send_data;
for(;;)
{
key = key_scan();
if (key)
{
switch (key)
{
case VOL_UP_PRES:
send_data=1;
osMessageQueuePut( Test_QueueHandle,&send_data,0,0 );
break;
case VOL_DN_PRES:
send_data=2;
osMessageQueuePut( Test_QueueHandle,&send_data,0,0 );
break;
case BACK_PRES:
send_data=3;
osMessageQueuePut( Test_QueueHandle,&send_data,0,0 );
break;
}
}
}
osDelay(20);
这里的osMessageQueuePut (osMessageQueueId_t mq_id, const void *msg_ptr, uint8_t msg_prio, uint32_t timeout) API函数的四个参数分别的意义如下:
• mq_id :消息队列 ID(消息队列句柄)
• msg_ptr :发送消息的地址
• msg_prio :发送优先级,在源码中可看到该参数被忽略并不生效。
• timeout :线程等待时间
至此,我们的内容就添加完成了。之后进行编译烧写,当按下VOL_UP按键时会发送内容为1的队列,当按下VOL_DN按键时会发送内容为2的队列、当按下BACK按键时会发送内容为3的队列,当接收端收到队列的内容为1时,LED2状态翻转,当接收端收到队列的内容为2时,LED3状态翻转,当接收端收到队列的内容为3时,BEEP状态翻转,至此,我们的消息队列实验就完成了。
原文地址:https://blog.csdn.net/BeiJingXunWei/article/details/142364736
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!