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接口等
第四十章 二值信号量实验
40.1 信号量基本概念
信号量(Semaphore)是一种实现任务间通信的机制,可以实现任务之间同步或临界资源的互斥访问,常用于协助一组相互竞争的任务来访问临界资源。在多任务系统中,各任务之间需要同步或互斥实现临界资源的保护,信号量功能可以为用户提供这方面的支持。
40.2 二值信号量的基本概念
二值信号量既可以用于临界资源访问也可以用于同步功能。
二值信号量和互斥信号量(以下使用互斥量表示互斥信号量)非常相似,但是有一些细微差别: 互斥量有优先级继承机制,二值信号量则没有这个机制。这使得二值信号量更偏向应用于同步功能,而互斥量更偏向应用于临界资源的访问。
可以将二值信号量看作只有一个项目(item)的队列,因此这个队列只能为空或满(因此称为二值)。任务和中断使用队列无需关注谁控制队列---只需要知道队列是空还是满。利用这个机制可以在任务和中断之间同步。
考虑这样一种情况,一个任务用来维护外设。使用轮询的方法会浪费CPU资源并且妨碍其它任务执行。更好的做法是任务的大部分时间处于阻塞状态(允许其它任务执行),直到某些事件发生该任务才执行。可以使用二进制信号量实现这种应用:当任务取信号量时,因为此时尚未发生特定事件,信号量为空,任务会进入阻塞状态;当外设需要维护时,触发一个中断服务例程,该中断服务仅仅给出信号量(向队列写数据)。任务只是取信号,并不需要归还,中断服务只是给信号。
在嵌入式操作系统中二值信号量是任务间、任务与中断间同步的重要手段,信号量使用最多的一般都是二值信号量与互斥信号量(互斥信号量在下一章讲解)。为什么叫二值信号量呢?因为信号量资源被获取了,信号量值就是 0,信号量资源被释放,信号量值就是 1,把这种只有 0 和 1两种情况的信号量称之为二值信号量。
40.3 实验目的
1)STM32CubeIDE工具软件建立freertos工程
2)学习二值信号量的相关知识与初步使用
我们的实验任务为通过三个按键使用二值信号量的方式分别来控制LED2、LED3和蜂鸣器状态的反转。
首先对实验进行分析,其实本实验和上一章节的队列实验完成的现象相同,但是实现的方式不同,总共需要有四个任务,分别为RECEIVELED2、RECEIVELED3、RECEIVEBEEP三个信号量接收任务和SEND信号量发送任务。所实现的功能如下:
SEND任务:通过三个按键,分别为 VOL-UP、VOL-DN、BACK,三个按键可以分别用来传递LED2Binary、LED3Binary、BEEPBinary三个二值信号量。
RECEIVELED2任务:若是接收到LED2Binary二值信号量,则LED2的状态进行反转。
RECEIVELED3任务:若是接收到LED3Binary二值信号量,则LED3的状态进行反转。
RECEIVEBEEP任务:若是接收到BEEPBinary二值信号量,则BEEP的状态进行反转。
本章节完成的实验存放位置为“iTOP-STM32MP157开发板\iTOP-STM32MP157开发板网盘资料汇总\08_freertos实验例程\04_二值信号量实验.zip”
40.4 二值信号量实验
40.4.1建立freertos_Binary工程
首先我们打开STM32CubeIDE软件,进入软件界面之后,我们点击File属性,选择NEW下的STM32 Project的选项,如下图所示:
然后我们会进入下图所示界面:在Part Number选择框输入STM32MP157A,然后在右边的选择界面选择STM32MP157AAA,然后点击Next选项
在Project Name框中输入工程名字freertos_Binary,然后点击Finish选项即可,如下图所示:
等待工程创建完毕,会询问我们是否要安装OpenSTLinux ,由于我们是在windows环境下,所以我们不需要安装,点击NO即可
至此我们的工程创建完毕,进入工程界面如下图所示界面:
40.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值不同,也在下方列了出来):
40.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值不同,也在下方列了出来):
40.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。
40.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按钮,随后我们以同样的方式,创建任务名字为RECEIVELED2、RECEIVELED3、RECEIVEBEEP的任务,创建完成如下图所示:
随后我们来到Timers and Semaphores选项界面(即软件定时器和信号界面),在该页面中总共有三个任务栏,分别为定时器、二值信号量、计数信号量,如下图所示,然后我们点击中间二值信号量的Add添加按钮如下图所示:
弹出队列的添加窗口之后,对默认的Semaphore Name进行修改,修改为LED2Binary,然后我们点击确定按钮,如下图所示:
用同样的方法创建LED3Binary、BEEPBinary两个二值信号量,创建完成如下图所示:
配置完成之后我们需要在Project Manage下的Code Generator选项下勾选 Generate peripheral initialization as a pair of ".c/.h' files per peripheral 选项,这样可以独立生成对应外设的初始化.h 和.c 文件(方便配置的查看),如下图所示:
40.4.6工程的生成与完善
在上述的步骤完成之后,按下键盘的“Ctrl+S”组合键保存保存 LED.ioc 文件,系统开始生成初始化代码,此处会弹出一个警告,提示我们 Systick 定时器已被 HAL 库占用,在 STM32MP157 Cortex-M4 内核上我们更换不了其他的定时器,选择 Yes 继续生成代码即可。
工程生成之后如下图所示:
然后我们进行工程的完善,以及添加对应的逻辑代码。
40.4.6.1 对应文件与文件夹的添加
由于我们在裸机章节已经完善了对应的LED,BEEP和KEY文件,所以我们将iTOP-STM32MP157开发板网盘资料汇总\06_Cortex-M4实验例程\03_KEY\KEY\CM4\Core\BSP文件拷贝到当前工程对应的位置,拷贝完成如下下图所示:
40.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"
添加完成如下图所示:
然后我们来到文件的底部可以看到我们创建的RECEIVELED2_Task、RECEIVELED3_Task、RECEIVEBEEP_Task和SEND_Task 任务
修改RECEIVELED2_Task任务的for循环中的内容,修改内容如下:
osStatus_t xReturn = osOK ;
for(;;)
{
xReturn = osSemaphoreAcquire(LED2BinaryHandle,osWaitForever);
if(osOK == xReturn)
{
LED2_TOGGLE();
}
}
修改完成如下图所示:
修改RECEIVELED3_Task任务的for循环中的内容,修改内容如下:
osStatus_t xReturn = osOK ;
for(;;)
{
xReturn = osSemaphoreAcquire(LED3BinaryHandle,osWaitForever);
if(osOK == xReturn)
{
LED3_TOGGLE();
}
}
修改完成如下图所示:
修改RECEIVEBEEP_Task任务的for循环中的内容,修改内容如下:
osStatus_t xReturn = osOK ;
for(;;)
{
xReturn = osSemaphoreAcquire(BEEPBinaryHandle,osWaitForever);
if(osOK == xReturn)
{
BEEP_TOGGLE();
}
}
修改完成如下图所示:
随后我们修改SEND_Task 任务的内容,修改内容如下:
uint8_t key;
for(;;)
{
key = key_scan();
if (key)
{
switch (key)
{
case VOL_UP_PRES:
osSemaphoreRelease( LED2BinaryHandle );
break;
case VOL_DN_PRES:
osSemaphoreRelease( LED3BinaryHandle );
break;
case BACK_PRES:
osSemaphoreRelease( BEEPBinaryHandle );
break;
}
}
}
osDelay(20);
至此,我们的内容就添加完成了。之后进行编译烧写,当按下VOL_UP按键时会释放LED2Binary信号量,接收函数接收到该信号之后LED2状态翻转,当按下VOL_DN按键时会释放LED3Binary信号量,接收函数接收到该信号之后LED3状态翻转,当按下BACK按键时会释放BEEPBinary信号量,接收函数接收到该信号之后BEEP状态翻转至此,我们的挂起和恢复任务实验就完成了。