心智圖資源庫 事件组多任务同步
在事件組多工同步中,任務要求程式使用事件組進行同步。 雖然可以通過先後使用函數xEventGroupGetBits和xEventGroupWaitBits來實現這個操作,但這種操作不是原子性的。 為此,我們可以使用函數xEventGroupSync來替代這兩個函數,實現原子操作,從而確保多工同步的準確性和高效性。 這種方法簡化了程式碼邏輯,提高了系統的穩定性和響應速度。
編輯於2024-09-15 02:51:14启用FreeRTOS
步骤
步骤:
开启
RCC 中 设置LSE(低速时钟):外部晶振(Crystal/Ceramic Resonator)
SYS 中 设置Timebase Source(时基): TIM6
使用USART1 向 PC上传字符串信息
作为互斥性访问的资源
启用一步模式Mode:Asynchronous
波特率:576500(同书上一样)
只做串口的发送,不作接收的解析,所以不需要打开中断
启用FreeRTOS
设置Interface(接口): CMSIS_V2
配置要求的外设
例子: 设置RTC实时时钟,在Timers选项卡中设置RTC参数
用到了RTC唤醒中断
在此中断返回函数中,读取RTC的当前时间,并显示在LCD上
RTC模式(RTC Mode )
启用时钟源、启用日历
AlarmA:闹钟A AlarmB:闹钟B
Internal AlarmX 内部闹钟功能 Routed to OUT 闹钟事件信号输出到复用引脚RTC_AF1
Wake UP:周期唤醒
Internal Wake UP 内部周期唤醒 Routed to OUT 周期唤醒事件信号输出到复用引脚RTC_AF1
闹钟A/闹钟B/周期唤醒都可以产生中断 且中断事件信号都能输出到服用引脚RTC_AF1 但是只能选择一个进行复用输出。
RTC配置(Configuration)
(General) 通用
小时格式
异步预分频器值
同步预分频器值
(Calendar Time)日历时间
(Calendar Date)日历日期
(Wake UP) 周期唤醒设置
Wake UP Clock (周期唤醒时钟源)
Wake UP Counter(周期唤醒计数器)
0:每个时钟周期中断1次
1:当时钟源为1Hz时每2秒发生一次唤醒中断
NVIC中断管理
由于启用FreeRTOS,系统会对NVIC自动生成一些设置
RTC的唤醒中断,是用户程序需要使用的中断,优先级是可以设置的,先将优先级设置成1,这样它就是FreeRTOS的不可屏蔽中断了
生成代码
在FreeRTOS中设计了一个任务LED1
遇到的问题
下载不进去
主题
主题
Free RTOS
第三章 Free RTOS的中断管理
搞清楚2个概念
概念1
Free RTOS 的任务有 任务优先级
纯软件的概念,与硬件无关。
优先级是编程人员在软件中设置的
任务优先级数字越低,表示优先级越低。 任务优先级最低为 0
Free RTOS中的(任务调度算法)决定 哪个任务处于运行状态
任务只有在没有ISR运行的时候才能执行,即使优先级最低的中断也能抢占高优先级的任务执行,而任务不能抢占ISR运行。
总之ISR执行时就不能执行任务函数,ISR的执行时间越短越好
概念2
MCU的“硬件” 有 中断优先级
中断是MCU的 硬件特性 由CubeMAX 里面的NVIC页面管理中断
中断是由 硬件事件 或 软件信号 引起的,运行哪个ISR是由硬件决定的
硬件中断优先级数字越小 , 优先级越高。 最高优先级为 0
每个硬件中断都有一个中断服务例程 ISR
Free RTOS的上下文切换就是在PendVS中断里进行的
启用FreeRTOS以后 系统会自动设置NVIC分组策略 4bit抢占式优先级(编号0-15)
Free RTOS中与中断相关的2个参数
Free RTOS中与中断相关的2个参数 中断最低优先级 15 中断最高优先级 5 (绝对不能设置成0)
只有在中断优先级>=5的中断ISR函数里, 才可以调用 FreeRTOS的中断 API函数(带FromISR后缀)
NVIC管理中断的特点: 同等抢占优先级的中断,是不能发生抢占的。
Time 6 的优先级要自己设置成0 这样的话FreeRTOS就不能屏蔽掉HAL的基础时钟中断
概要
HAL的基础时钟(Timer 6)的中断优先级是 0
PendSV和SysTick的中断优先级为 15, 所以,只有在没有其他中断需要处理的情况下才会发生任务切换
中断分为2组,高优先级的一组中断不受FreeRTOS的管理称为FreeRTOS不可屏蔽中断,低优先级的一组是FreeRTOS可屏蔽中断
概要
在实际软件设计中
一般要简化ISR的功能,使其尽量少占用CPU的时间,
一般的硬件中断都是处理一些数据的 接收 发送
比如: 采用中断方式进行ADC数据采集时,只需要在ADC的中断 里将数据读取到缓冲区,而对数据进行滤波、频计算等耗时间的操作就转移到函数里处理。
这个例子还涉及到 中断ISR和任务函数之间的同步问题,这就是进程间通讯
第四章:进程间通讯
中断屏蔽和临界代码段
一个任务在执行的时候会被 其他高优先级的任务 或者 任何一个中断的ISR 抢占CPU
中断屏蔽
屏蔽部分中断
taskDISABLE_INTERRUPTS()
解除中断屏蔽
taskENABLE_INTERRUPTS()
只能屏蔽可屏蔽的低优先级中断不能嵌套使用
临界代码段
开始临界代码段
taskKENTER_CRITICAL()
taskKENTER_CRITICAL_FROM_ISR()
结束临界代码段
taskEXIT_CRITICAL()
taskEXIT_CRITICAL_FROM_ISR()
在此代码段内,FreeRTOS会暂停任务调度,保证不会被抢占,保证代码连续性可以嵌套使用
规定
在使用(中断屏蔽)和(临界代码段)内,不能使用触发任务调度的函数 如延时函数vTaskDelay()、申请信号量等进行进程间同步的函数。
因为发生任务调度时就会打开中断,从而失去了中断屏蔽代码段或临界代码段的意义
在中断ISR中使用FreeRTOS的API函数
FreeRTOS的API函数2个版本
任务级:普通名称的API函数
中断级:带后缀FromISR(中断安全API函数)
在ISR(可屏蔽+不可屏蔽)中绝对不能使用任务级API函数,但是在任务函数中可以使用中断级API函数。在不可屏蔽的ISR中不能使用中断级API函数
中断优先级及其ISR程序设计原则
根据终端的重要性和功能,为期设置合适的中断优先级,使其成为FreeRTOS的可屏蔽中断或不可屏蔽中断
中断ISR的代码应该简短,耗时的功能在任务函数中实现
在可屏蔽中断的ISR中能调用中断级的API函数,绝对不用普通的API函数。
在不可屏蔽中断中,不能调用任何API函数
实验;任务和中断程序设计示例
用到了RTC唤醒中断
此中断中,读取RTC的当前时间,并显示在LCD上
在FreeRTOS中设计了一个任务LED1
步骤:
设置RTC
RCC 中
设置LSE(低速时钟):外部晶振(Crystal/Ceramic Resonator)
SYS 中
设置Debug:
设置Timebase Source(时基): TIM6
Clock Configuration
72Mhz
将外部时钟32.768的LSE--设成--To RTC--的时钟源
启用FreeRTOS
设置Interface(接口): CMSIS_V2
Timers 中 设置RTC参数
RTC模式(RTC Mode )
启用时钟源、启用日历
AlarmA:闹钟A AlarmB:闹钟B
Internal AlarmX 内部闹钟功能 Routed to OUT 闹钟事件信号输出到复用引脚RTC_AF1
Wake UP:周期唤醒
Internal Wake UP 内部周期唤醒 Routed to OUT 周期唤醒事件信号输出到复用引脚RTC_AF1
闹钟A/闹钟B/周期唤醒都可以产生中断 且中断事件信号都能输出到服用引脚RTC_AF1 但是只能选择一个进行复用输出。
RTC配置(Configuration)
(General) 通用
小时格式
异步预分频器值
同步预分频器值
(Calendar Time)日历时间
(Calendar Date)日历日期
(Wake UP) 周期唤醒设置
Wake UP Clock (周期唤醒时钟源)
1HZ
Wake UP Counter(周期唤醒计数器)
0:每个时钟周期中断1次
1:当时钟源为1Hz时每2秒发生一次唤醒中断
NVIC中断管理
由于启用FreeRTOS,系统会对NVIC自动做一些设置
RTC的唤醒中断,是用户程序需要使用的中断,优先级是可以设置的,先将优先级设置成1,这样它就是FreeRTOS的不可屏蔽中断了
生成代码
第四章 进程间通信(简单概念) 与 消息队列(队列写/读数据)
进程间通信 与 消息队列
消息队列(基础)
功能:将进程间需要传递的数据存在其中
信号量
更适用于进程间同步
互斥量
更适用于共享资源的互斥性访问
事件组
任务通知
流缓冲区和消息缓冲区
4.1 进程间通信IPC
任务与任务之间,或任务与ISR(中断)之间,有时需要进行通信或同步
消息队列(基础)
一般采用FIFO
队列不属于某个任务,任何任务和中断都可以向队列发送/读取消息
LIFO
消息队列(基础)
信号量(第五章)
二值信号量
1.用于进程间同步
2.无优先级继承机制,可能出现优先级翻转问题
计数信号量
用于共享资源管理
互斥量(第六章)
互斥量
递归互斥量
概要
用于互斥性共享资源访问
有优先级继承机制,可减轻优先级翻转问题
任务通知
1.不用创建中间对象
2.直接从任务向任务,或从ISR向任务发送通知,传递通知值。
3.可以模拟二值信号量、计数信号量、长度为1的消息队列
4.使用任务通知,效率高,消耗内存少
事件组
用于多个事件触发一个或多个任务的运行
实现事件的广播,实现多个任务同步运行
流缓冲区和消息缓冲区
1.是优化的进程间通信机制
2.专门用于一个写入者,一个读取者的场景
3.还用于多核CPU的两个内核只讲的高效率传输
4.2 队列的特点和基本操作
4.2.1 队列的创建和存储 CubeIDE在图形化界面创建
创建
动态创建:xQueueCreate(队列长度、队列大小)
示例:Queue_KeyHandle = xQueueCreate( 5, sizeof(uint16_t) );
返回值:NULL:创建失败 其他值:创建成功,返回队列句柄
如果传递的数据较大,可以在存储单元里存储需要传递的数据的指针。
静态创建:xQueueCreateStatic()
存储
队列中可以存储数量有限、大小固定的数据(自己设置)
队列中的每一个数据叫做 “队列项目”
队列能够存储“队列项目”的最大数量成为队列的长度
4.2.2 向队列写入数据(发送消息、入队)
xQueueSendToBack(要写入数据的队列句柄,要写入队列的项,pdMS_TO_TICKS(X)) : 向队列后端写入数据(FIFO模式) 队列句柄在创建队列的那里(Queue_KeyHandle), , pdMS_TO_TICKS(毫秒) 示例:xQueueSendToBack(Queue_KeyHandle,&key_pressed,pdMS_TO_TICKS(50)
xQueueSend()同上面的作用一样
xQueueSendToFront() : 向队列前端写入数据(LIFO模式)
返回值类型 BaseType_t
返回值 pdTRUE: 队列未满,队列写入成功
返回值 errQUEUE_FULL:队列已满,队列写入失败,不能写入数据
xQueueOverwrite() : 只能向长度为1的队列写入数据
概要
队列已满,覆盖队列原来数据
数据传递方式
实际值传递:将实际值拷贝到队列中进行传递
指针传递:传递大数据的时候采用
4.2.3 从队列读取数据(接收消息)
任务级
xQueueReceive(队列句柄,信息读取缓冲区,等待节拍数);
总是从队列首端读取,读取后删除这个单元的数据,未读数据依次向首端移动
等待节拍数 = 0:不等待
等待节拍数 = X:等待X节拍,读不到就返回值pdFALSE
等待节拍数 = portMAX_DELAY:一直等待
xQueuePeek(队列句柄,信息读取缓冲区,等待节拍数);
总是从队列首端读取,只是不删除队列中的数据。
概要
返回值pdTRUE:读取成功
返回值pdFALSE:读取失败。超过节拍数队列还没有数据,退出阻塞态,进入就绪态
ISR中断级
xQueueReceiveFromISR ( );
总是从队列首端读取,读取后删除这个单元的数据,未读数据依次向首端移动
xQueuePeekFromISR();
总是从队列首端读取,只是不删除队列中的数据。
4.2.4 队列操作相关函数
任务中操作队列
ISR中操作队列:必须使用相应的中断级函数FromISR
正点原子实验
学习 FreeRTOS 的队列相关API函数的使用,实现任务的入队出队操作
正点原子的Start_task任务在CubeIDE中图形配置界面创建好了
所以CubeIDE实验设计:三个任务
用到队列操作要在freertos.c中包含队列头文件 #include "queue.h" //队列相关的功能的头文件 (#include "semphr.h"//信号量和互斥量等同步原语的头文件)
task1
按键key1或key2按下,将键值(实际值)拷贝(发送)到队列key_queue(入队)
按键key_up按下,将传输“大数据”,这里拷贝(大数据地址)到队列big_date_queue中
task2
读取队列key_queue中的消息(出队),打印出接收到的键值
task3
从队列big_date_queue读取大数据地址,通过地址访问大数据
步骤
RCC 中 设置LSE(低速时钟):外部晶振(Crystal/Ceramic Resonator)
SYS 中 设置Timebase Source(时基): TIM6
启用FreeRTOS
设置Interface(接口): CMSIS_V2
编辑任务(Tasks and Queues)
创建任务
Task1
Task2,同上
Task3,同上
创建队列
队列长度
因为有两个按键Key1 / Key2,所以队列长度为2。
队列项目大小
这里就是按键键值的大小
查看按键扫描函数返回值是 uint8_t key_scan (void)
生成代码 忽略警告点Yes
编辑代码
定义一个数组buff
char buff[100] = {"我是一个大数组,大大的数组 1223311215 ndjlandj"};
在Start_Task_1中编写key按下,通过API函数---xQueueSendToBack()--写入“对应”队列的代码
在Start_Task_2中编写,通过API---读取队列key_queue中的消息(出队),打印出接收到的键值
第五章:信号量
信号量和互斥量(都是基于队列)
信号量
更适用于进程间同步
互斥量
更适用于共享资源的互斥性访问
”队列“派生出来的信号量和互斥量分类
信号量组
二值信号量:更适用于进程间同步
1.只有一个项的队列
2.队列要么空0、要么满1,两种状态
3.就像一个标志,适用于进程间同步的通讯
4.使用二值信号量时,可能会出现优先级翻转问题,使系统的实时性变差
例子:更适用于进程间同步
进程间同步:一个进程只负责释放(Give)信号量(二值信号量变得有效说明转换区存入了新的转换数据)。另一个任务总是获取(Take)二值信号量,如果二值信号量无效,则这个任务进入阻塞状态,一直等待,也可以设置等待超时时间,如果二值信号量变得有效,则这个任务立刻退出阻塞状态,进入运行态,之后可以读取缓冲区状态。
如果不使用二值信号量,而是使用自定义标志变量,则这个任务需要不断的查询标志变量的值,不像使用二值信号量,可以进入阻塞状态,所以使用二值信号量进行进程间同步效率更高。
计数信号量
1.就是有固定长度的队列
2.队列的每一个项就是一个标志
3.通常用于对多个共享资源的访问进行控制
计数信号量初值为4,如果计数信号量的值为0,再有客人想进店,就需要等待,在任务中申请信号量时可以设置等待超时时间,在等待时任务就进入了阻塞状态。当一个客人用餐结束就释放(Give)计数信号量,计数信号量的值+1,此时可以供新的任务或者ISR使用
例如:ADC连续数据采集时一般使用双缓冲区,就可以使用计数信号量来管理
互斥量组
互斥量:更适用于共享资源的互斥性访问
1.互斥量是对二值信号量的一种改进
2.互斥量引入了优先级继承机制,可以减缓优先级翻转问题,但是不能消除。
3.互斥量不能在ISR中使用,因为互斥量具有任务的优先级的继承机制,而ISR不是任务,且ISR不可以设置阻塞时间,获取互斥量时,经常需要等待的。P88
4.同一个任务在获取互斥量后就只能释放互斥量,不能再次获得互斥量。其他的任务只能等待此任务释放互斥量
共享资源的互斥访问和二值信号量的区别:
进程间同步
一个只负责释放信号量,一个只负责获取信号量
共享资源的互斥访问
一个任务对互斥量既有获取,也有释放操作
例子:更适用于共享资源的互斥性访问
共享资源互斥访问:一个任务对互斥量既有获取操作,也有释放操作
递归互斥量
1.是一种特殊的互斥量,可用于需要递归调用的函数中
2.一个任务获得递归互斥量后,可以再次获得此递归互斥量, 当然获取必须与释放配对使用
3.递归互斥量不能在ISR中使用。P88
相关函数
创建与删除
创建 1.图形化界面创建 2.用户代码区创建
创建二值信号量
动态创建
xSemaphoreCreateBinary
静态创建
xSemaphoreCreateBinaryStatic
(静态分配内存)
创建计数信号量
动态创建
xSemaphoreCreateCounting(最大计数值, 初始计数值)
返回值类型 QueueHandle_t
它是创建的技术信号量的句柄
一般 最大计数值5 == 初始计数值5,表示有5个资源可用
静态创建
xSemaphoreCreateCountingStatic
(静态分配内存)
创建互斥信号量
动态创建
xSemaphoreCreateMutex
静态创建
xSemaphoreCreateMutexStatic
(静态分配内存)
创建递归互斥信号量
动态创建
xSemaphoreCreateRecursiveMutex
静态创建
xSemaphoreCreateRecursiveMutexStatic
(静态分配内存)
删除
删除这4种信号量和互斥量
vSemaphoreDelete
获取与释放
获取
二值信号量
计数信号量
ISR版本
xSemaphoreTakeFromISR(2中对象信号量句柄,返回值pdTRUE / pdFALSE)
返回值表示:是否要在 退出ISR前进行任务调度
portYIELD_FROM_ISR( )-----来申请任务调度
互斥量
不能在ISR中使用
任务版本
xSemaphoreTake(3中对象信号量句柄,阻塞等待节拍数)
等待时间:
返回值 pdTRUE: 获取信号量成功
返回值 pdFALSE:获取信号量失败
递归互斥量
任务版本:xSemaphoreTakeRecursive
不能在ISR中使用
释放
二值信号量
计数信号量
ISR版本
xSemaphoreGiveFromISR(信号量句柄,返回值是pdTRUE / pdFALSE) 根据第二个参数的返回值判断是否申请任务调度,如果释放信号量导致了一个任务解锁,而解锁的任务比当前的任务优先级高,这里就会返回pdTRUE,
返回pdTRUE,就需要再退出ISR之前申请进行一次任务调度,以便及时解锁高优先级的任务可以使用 portYIELD_FROM_ISR( )-----来申请任务调度
返回pdFALSE,
互斥量
不能在ISR中使用
释放
xSemaphoreGive(二值信号量的句柄,写入队列的方向queueSEND_TO_BACK)
返回值:pdTRUE 成功释放二值量
返回值 : pdFALSE 释放二值信号量失败
递归互斥量
xSemaphoreGiveRecursive
不能在ISR中使用
其他操作
返回计数信号量当前值剩余可用个数123...
返回二值信号量当前值0/1
返回当前值
uxSemaphoreGetCount
返回互斥量当前持有者
xSemaphoreGetMutexHolder
用于确定当前任务是不是某个互斥量的持有者
xSemaphoreGetMutexHolderFromISR
Kevin_WWW 二值信号量实验
学习 FreeRTOS 的队列相关API函数的使用,实现任务的入队出队操作
正点原子的Start_task任务在CubeIDE中图形配置界面创建好了
CubeIDE实验设计流程
用到信号量和互斥量操作要在freertos.c中包含队列头文件 #include "semphr.h"//信号量和互斥量等同步原语的头文件
任务
在一个定时器的中断服务里,进行周期为500ms的ADC数据采集
1.启用一个定时器TIM,根据定时器所在APB的时钟频率,设置预分频系数和自动重装值,以及是否动态修改自动重装值。
2.当达到自动冲装置时
在ISR中将转换结果写入缓存变量并释放信号量
任务中总是去尝试获取信号量,在获取到信号量后读取ADC的缓存结果变量
然后在LCD上显示
步骤
RCC 中 设置LSE(低速时钟):外部晶振(Crystal/Ceramic Resonator)
SYS 中 设置Timebase Source(时基): TIM6
启用FreeRTOS
设置Interface(接口): CMSIS_V2
参数暂时保持默认
设置一个定时器TIM
TIM3:时钟源设置成--内部时钟(Internal Clock)
设置分频系数(Prescaler) 设成7200
根据TIM3挂载总线APB1 -- 72MHz,希望分成1000Hz,需要将分频系数设成72000,而分频系数最大65535。我们设成7200,得到10000Hz,也就是0.1ms我们希望获得的周期是500ms,则计数周期(Counter Period)就可以设置成5000-1
计数周期(Counter Period)可以设置成5000-1
触发事件选择(TRGOTrigger Event Selection TRGO)
更新一个事件(Update Event)
这个事件连接到ADC采集
设置ADC(adc1_in2)
外部触发转换源(External Trigger Conversion Source)
定时器3触发事件(Timer 3 Trigger Out event)
Rank
采样周期Sampling Time 19.5
NVIC
打开ADC的NVIC
编辑任务(Tasks and Queues)
创建任务
改名Task_Show,其它参数保持默认
计时器和信号量面板(Timers and Semaphores)可以设置
创建二值信号量:名称BinSem_Data_Ready,其他默认
NVIC设置优先级
Timebase 设为0(总是这样)
TIM3全局中断无需启用×
ADC全局中断打开√
并且稍后要在ADC的中断回调函数ISR中调用FreeRTOS里的函数,所以不能设置优先级高于5(01234)
生成代码 忽略警告点Yes
编辑代码
main.c
检查是否main.h包含所有的头文件
lcd_init();
HAL_ADC_Start_IT(&hadc1); 以-------中断------的方式来启动-------ADC1,传入ADC1的地址----&hadc1
HAL_TIM_Base_Start(&htim3); 启动定时器TIM3,传入TIM3的地址----&htim3, 这样ADC1就在TIM3的触发下,进行500ms的周期性转换了
freertos.c
#include "semphr.h" //信号量和互斥量等同步原语的头文件
uint32_t adc_value; //定义ADC1全局变量adc_value用来存储ADC1的值
Start_Task_Show
if(xSemaphoreTake(BinSem_Data_ReadyHandle,portMAX_DELAY) == pdTRUE)//判断获取二值信号量成功吗? { printf("ADC_Value = %ld mV\r\n",(adc_value*3300) >> 12); }
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) //最后用户代码区写ADC回调函数,500ms进入一次ADC1中断回调函数 if(hadc->Instance == ADC1) // 判断传入的参数是不是ADC1 { adc_value = HAL_ADC_GetValue(hadc); //获取ADC1的值存到全局变量adc_value 读到ADC数了以后,就可以释放二值信号量了 if(BinSem_Data_ReadyHandle != NULL) 考虑释放的信号量没有被任务获取的情况,判断这信号量不是空的,说明已经被获取过了 { BaseType_t highTaskWoken = pdFALSE; //定义一个变量存取返回值,用于判断是否进行任务调度的变量,默认pdFALSE(不调度) xSemaphoreGiveFromISR ( BinSem_Data_ReadyHandle , &highTaskWoken ) ; portYIELD_FROM_ISR(highTaskWoken); //根据传入的返回值决定是否进行一次高优先级的任务调度 } }
信号量
发送状态
0 1 二值信号量
0 1 2 3....计数信号量
队列
发送数据
Kevin_WWW 计数信号量实验
学习 FreeRTOS 的计数信号量相关API函数的使用,实现任务的入队出队操作
使用到的API函数
创建计数信号量
动态创建
xSemaphoreCreateCounting(最大计数值, 初始计数值)
返回值类型 QueueHandle_t
它是创建的技术信号量的句柄
一般 最大计数值5 == 初始计数值5,表示有5个资源可用
静态创建
xSemaphoreCreateCountingStatic
(静态分配内存)
删除(计数信号量)
vSemaphoreDelete
删除这4种信号量和互斥量
获取(Take) 计数信号量
任务版本
xSemaphoreTake(3中对象信号量句柄,阻塞等待节拍数)
等待时间:
返回值 pdTRUE: 获取信号量成功
返回值 pdFALSE:获取信号量失败
ISR版本
xSemaphoreGiveFromISR(信号量句柄,返回值是pdTRUE / pdFALSE)
根据"第二个参数"的返回值判断是否申请任务调度,如果释放信号量导致了一个任务解锁,而解锁的任务比当前的任务优先级高,这里就会返回pdTRUE,
返回pdTRUE,就需要再退出ISR之前申请进行一次任务调度,以便及时解锁高优先级的任务可以使用 portYIELD_FROM_ISR( )-----来申请任务调度
返回pdFALSE,
概要
释放(Give) 技术信号量
任务版本
xSemaphoreGive(信号量的句柄,写入队列的方向queueSEND_TO_BACK)
返回值:pdTRUE 成功释放二值量
返回值 : pdFALSE 释放二值信号量失败
ISR版本
xSemaphoreGiveFromISR(信号量句柄,返回值是pdTRUE / pdFALSE)
根据第二个参数的返回值判断是否申请任务调度,如果释放信号量导致了一个任务解锁,而解锁的任务比当前的任务优先级高,这里就会返回pdTRUE,
返回pdTRUE,就需要再退出ISR之前申请进行一次任务调度,以便及时解锁高优先级的任务可以使用 portYIELD_FROM_ISR( )-----来申请任务调度
返回pdFALSE,
这个参数就是为了解决优先级翻转问题
概要
获取信号量当前可用资源的数量
uxSemaphoreGetCount(信号量的句柄);
用于获取信号量(Semaphore)当前的计数值。这个计数值表示当前信号量所允许的最大可用资源数。
1.Start_task任务在CubeIDE中图形配置界面创建好了 2.可以自己在用户代码区创建
CubeIDE实验设计流程
用到信号量和互斥量操作要在freertos.c中包含队列头文件 #include "semphr.h"//信号量和互斥量等同步原语的头文件
任务
设计一个技术信号量,最大值为5 ,初始值为5(5张餐桌)
使用RTC的唤醒中断,中断周期为3s。在RTC唤醒中断中Give(释放)信号量 比作客人离开了饭店,资源+1。
使用一个任务检测按键的状态,按键被按下就Take(获取)一个信号量模拟客人进店资源-1
步骤
RCC 中 设置LSE(低速时钟):外部晶振(Crystal/Ceramic Resonator)
SYS 中 设置Timebase Source(时基): TIM6
启用FreeRTOS
设置Interface(接口): CMSIS_V2
参数暂时保持默认
Clock Configuration
72Mhz
将外部时钟32.768的LSE--设成--To RTC--的时钟源
Timers 中 设置RTC参数
RTC模式(RTC Mode )
启用时钟源 启用日历
AlarmA:闹钟A AlarmB:闹钟B
Internal AlarmX 内部闹钟功能 Routed to OUT 闹钟事件信号输出到复用引脚RTC_AF1
Wake UP:周期唤醒
Internal Wake UP 内部周期唤醒 Routed to OUT 周期唤醒事件信号输出到复用引脚RTC_AF1
闹钟A/闹钟B/周期唤醒都可以产生中断 且中断事件信号都能输出到服用引脚RTC_AF1 但是只能选择一个进行复用输出。
RTC配置(Configuration)
(General) 通用
小时格式
异步预分频器值
同步预分频器值
(Calendar Time)日历时间
(Calendar Date)日历日期
(Wake UP) 周期唤醒设置
Wake UP Clock (周期唤醒时钟源)
1Hz
Wake UP Counter(周期唤醒计数器0-65535)
0:每个时钟周期中断1次
1:当时钟源为1Hz时每2秒发生一次唤醒中断
2:当时钟源为2Hz时每3秒发生一次唤醒中断
NVIC中断管理
由于启用FreeRTOS,系统会对NVIC自动做一些设置
RTC的唤醒中断,是用户程序需要使用的中断,优先级是可以设置的,先将优先级设置成1,这样它就是FreeRTOS的不可屏蔽中断了
稍后要在RTC的中断回调函数ISR中调用FreeRTOS里的函数xSemaphoreGiveFromISR(信号量句柄,返回值是pdTRUE / pdFALSE),释放信号量 所以不能设置优先级高于5(01234)
生成代码
设置一个定时器TIM
TIM3:时钟源设置成--内部时钟(Internal Clock)
设置分频系数(Prescaler) 设成7200
根据TIM3挂载总线APB1 -- 72MHz,希望分成1000Hz,需要将分频系数设成72000,而分频系数最大65535。我们设成7200,得到10000Hz,也就是0.1ms我们希望获得的周期是500ms,则计数周期(Counter Period)就可以设置成5000-1
计数周期(Counter Period)可以设置成5000-1
触发事件选择(TRGOTrigger Event Selection TRGO)
更新一个事件(Update Event)
这个事件连接到ADC采集
设置ADC(adc1_in2)
外部触发转换源(External Trigger Conversion Source)
定时器3触发事件(Timer 3 Trigger Out event)
Rank
采样周期Sampling Time 19.5
NVIC
打开ADC的NVIC
编辑任务(Tasks and Queues)
创建任务
改名Task_Checkin, 入口函数名字AppTask_Checkin, 其它参数保持默认
计时器和信号量面板(Timers and Semaphores)可以设置
创建计数信号量:名称CountingSem_Tables,最大值5,初始值5.其他默认
NVIC设置优先级
Timebase 设为0(总是这样)
TIM3全局中断无需启用×
ADC全局中断打开√
并且稍后要在ADC的中断回调函数ISR中调用FreeRTOS里的函数,所以不能设置优先级高于5(01234)
稍后要在RTC的中断回调函数ISR中调用FreeRTOS里的函数xSemaphoreGiveFromISR(信号量句柄,返回值是pdTRUE / pdFALSE),释放信号量 所以RTC唤醒中断不能设置优先级高于5(01234)
生成代码 忽略警告点Yes
编辑代码
main.c
检查是否main.h包含所有的头文件驱动
lcd_init();
HAL_ADC_Start_IT(&hadc1);
以-------中断------的方式来启动-------ADC1,传入ADC1的地址----&hadc1
HAL_TIM_Base_Start(&htim3);
启动定时器TIM3,传入TIM3的地址----&htim3, 这样ADC1就在TIM3的触发下,进行500ms的周期性转换了
freertos.c
#include "semphr.h" //信号量和互斥量等同步原语的头文件
Start_Task_Checkin
printf("Total_tables = %d\r\n" , uxSemaphoreGetCount(CountingSem_TablesHandle));//打印现有资源数量 for(;;) { uint8_t key_pressed = key_scan(); // 扫描按键 switch (key_pressed) { case 1: if ( xSemaphoreTake (CountingSem_TablesHandle , pdMS_TO_TICKS(100) ) == pdTRUE) { printf("Take in OK!\r\n"); }else { printf("Take in fail!\r\n"); }; break; } printf("Total_tables = %d\r\n",uxSemaphoreGetCount(CountingSem_TablesHandle)); //打印现有资源数量 vTaskDelay(10); //进入阻塞态10ms }
最后的用户代码区
中断回调函数
ADC1中断回调函数
RTC中断回调函数(3s进一次)
void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc) { BaseType_t highTaskWoken = pdFALSE;//定义存取返回值用于判断是否进行任务调度的变量,默认pdFALSE if(CountingSem_TablesHandle != NULL)//如果信号量不是空的就执行释放(清空)操作,如果 == NULL,就不需要操作 { xSemaphoreGiveFromISR(CountingSem_TablesHandle,&highTaskWoken); portYIELD_FROM_ISR(highTaskWoken);//根据传入的返回值决定是否进行一次高优先级的任务调度 } }
3s释放一次计数信号量资源
释放(Give)一个计数信号量,就是释放一个资源,释放成功后计数信号量的值 + 1。
获取(Take)计数信号量,就是申请一个资源,申请成功后计数信号量的值 - 1。 计数信号量的值 = 0,表示没有资源可以被申请。再去申请资源的信号量任务就需要等待
互斥量Mutexes
队列
信号量组
二值信号量:更适用于进程间同步
计数信号量
互斥量组
互斥量:更适用于共享资源的互斥性访问
1.互斥量是对二值信号量的一种改进
2.互斥量引入了优先级继承机制,可以减缓优先级翻转问题,但是不能消除。
3.互斥量不能在ISR中使用,因为互斥量具有任务的优先级的继承机制,而ISR不是任务,且ISR不可以设置阻塞时间,获取互斥量时,经常需要等待的。P88
4.同一个任务在获取互斥量后就只能释放互斥量,不能再次获得互斥量。其他的任务只能等待此任务释放互斥量
共享资源的互斥访问和二值信号量的区别:
进程间同步
一个只负责释放信号量,一个只负责获取信号量
共享资源的互斥访问
一个任务对互斥量既有获取,也有释放操作
例子:更适用于共享资源的互斥性访问
共享资源互斥访问:一个任务对互斥量既有获取操作,也有释放操作
递归互斥量
1.是一种特殊的互斥量,可用于需要递归调用的函数中
2.一个任务获得递归互斥量后,可以再次获得此递归互斥量, 当然获取必须与释放配对使用
3.递归互斥量不能在ISR中使用。P88
二值信号量有优先级翻转问题
看视频解释的更清楚
任务的优先级继承机制
互斥量相关的API函数
创建
动态创建
xSemaphoreCreateMutex();
静态创建
xSemaphoreCreateMutexStatic( pxMutexBuffer )
返回值 队列句柄:QueueHandle_t
获取
二值信号量
计数信号量
互斥量
因为互斥量使用了优先级继承机制 所以不能在ISR中使用
获取任务版本
xSemaphoreTake(3中对象信号量句柄,阻塞等待节拍数)
等待时间:
返回值 pdTRUE: 获取信号量成功
返回值 pdFALSE:获取信号量失败
递归互斥量
任务版本:xSemaphoreTakeRecursive
不能在ISR中使用
释放
二值信号量
计数信号量
互斥量
因为互斥量使用了优先级继承机制 所以不能在ISR中使用
释放任务版本
xSemaphoreGive(二值信号量的句柄,写入队列的方向queueSEND_TO_BACK)
返回值:pdTRUE 成功释放二值量
返回值 : pdFALSE 释放二值信号量失败
递归互斥量
xSemaphoreGiveRecursive
不能在ISR中使用
Kevin_WWW互斥信号量 任务优先级继承实验
开启
RCC 中 设置LSE(低速时钟):外部晶振(Crystal/Ceramic Resonator)
SYS 中 设置Timebase Source(时基): TIM6
使用USART1 向 PC上传字符串信息
作为互斥性访问的资源
启用一步模式Mode:Asynchronous
波特率:576500(同书上一样)
只做串口的发送,不作接收的解析,所以不需要打开中断
启用FreeRTOS
设置Interface(接口): CMSIS_V2
创建任务
编辑任务(Tasks and Queues)
改默认任务改名Task_High, 入口函数名字AppTask_High, 优先级:osPrionityHigh高优先级 其它参数保持默认
任务2改名Task_Middle, 入口函数名字AppTask_Middle, 优先级:osPrionityNomal中优先级 其它参数保持默认
任务3改名Task_Low, 入口函数名字AppTask_Low, 优先级:osPrionityLow低优先级 其它参数保持默认
互斥量面板(Mutexes)可以设置
创建互斥信号量:名称token.其他默认
生成代码 忽略警告点Yes
计时器和信号量面板(Timers and Semaphores)可以设置
创建二值信号量:名称token.其他默认
生成代码 忽略警告点Yes
编辑代码
main.c
检查是否main.h包含所有的头文件驱动
lcd_init();
HAL_ADC_Start_IT(&hadc1);
以-------中断------的方式来启动-------ADC1,传入ADC1的地址----&hadc1
HAL_TIM_Base_Start(&htim3);
启动定时器TIM3,传入TIM3的地址----&htim3, 这样ADC1就在TIM3的触发下,进行500ms的周期性转换了
freertos.c
#include "semphr.h" //信号量和互斥量等同步原语的头文件
AppTask_Low
Task_Middle
Task_Low
最后的用户代码区
中断回调函数
ADC1中断回调函数
RTC中断回调函数(3s进一次)
void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc) { BaseType_t highTaskWoken = pdFALSE;//定义存取返回值用于判断是否进行任务调度的变量,默认pdFALSE if(CountingSem_TablesHandle != NULL)//如果信号量不是空的就执行释放(清空)操作,如果 == NULL,就不需要操作 { xSemaphoreGiveFromISR(CountingSem_TablesHandle,&highTaskWoken); portYIELD_FROM_ISR(highTaskWoken);//根据传入的返回值决定是否进行一次高优先级的任务调度 } }
3s释放一次计数信号量资源
事件组
事件组适用于多个事件触发一个或多个任务可以实现事件的广播, 还可以实现多个任务的同步运行运行
事件组功能特点
事件组允许任务等待一个或多个事件的组合。
例:先后按下KeyLeft键和KeyRight键, 或只按下其中一个键。
事件组会解除所有等待同一事件的任务的阻塞状态。
例:TaskA使用LED1闪烁报警,TaskB 使用蜂鸣器报警, 当报警事件发生时,两个任务同时解除阻塞状态, 两个任务都开始运行。
事件组适用场景
任务等待一组事件中的某个事件发生后做出响应(或运算关系),
任务等待一组事件都发生后做出响应(与运算关系);
将事件广播给多个任务;
多个任务之间的同步。
事件组工作原理
预备知识
使用前需要适用API函数创建事件组对象
xEventGroupCreate( void )
xEventGroupCreateStatic( StaticEventGroup_t *pxEventGroupBuffer )
一个事件组有一个内部变量用来存储很多个事件标志,
这个内部变量的位数与参数ConfigUSE_16BIT_TICKS有关
ConfigUSE_16BIT_TICKS = 1
用来存储事件标志的内部变量是16位
ConfigUSE_16BIT_TICKS = 0
用来存储事件标志的内部变量是32位
STM32 MCU是32位的,所以事件组内部变量是32位
事件标志只能是 0 / 1
用单独的一个位来存储一个事件标志,一个事件又被称为“事件位”
一个事件位被置位 1 表示事件发生了
一个事件位被置位 0 表示事件没发生
一个事件组中所有的标志保存在一个EventBits_t类型的变量里面
工作原理
例1:多个事件触发任务运行的原理
例2:事件组还可以使多个任务同步运行
事件组相关函数
事件组操作
创建事件组
xEventGroupCreate(不需要参数)
动态
xEventGroupCreateStatic(不需要参数)
静态
返回值类型 EventGroupHandle_t
返回值是所创建实践组的句柄变量-----是一个指针变量
删除事件组
vEventGroupDelete()
设置组编号
vEventGroupSetNumber()
编号作用用户自己定义
读取组编号
uxEventGroupGetNumber()
事件位操作
任务中置位
xEventGroupSetBits(事件组句柄,置位掩码 )
返回值类型 EventBits_t
返回的是置位成功后,事件组当前的值
中断中置位
xEventGroupSetBitsFromISR(事件组句柄,置位掩码,用于返回一个掩码)
有两个ISR版本 当configUSE_TRACE_FACILITY == 1
xEventGroupSetBitsFromISR(事件组句柄,置位掩码,用于返回一个掩码)
对事件组进行置位操作不是一个确定性操作,因为可能有其他任务也在设置事件置位FreeRTOS不允许中断或临界代码段进行不确定的操作,所以在ISR中对事件组进行置位操作时,FreeRTOS实际上是向定时器守护任务发送了一个消息,将事件组置位操作延后到定时器守护任务里去执行,返回的第三个参数,如果定时器守护任务的优先级高于当前执行任务,第三个参数就返回pdTRUE,使用返回的pdTRUE在后续代码中手动申请一次上下文切换
返回值类型 BaseType_t
返回值 pdTRUE: 延后处理的消息成功的发动给了定时器守护任务
返回值 pdFALSE:当定时器守护任务的队列消息满时,函数会无法接收到新的消息,返回值就是pdFALSE
任务中清零
xEventGroupClearBits(事件组句柄,需要清零的置位掩码)
返回值类型 EventBits_t
返回值是事件位被清零之前的事件组的值
中断中清零
xEventGroupClearBitsFromISR()
有两个ISR版本 当configUSE_TRACE_FACILITY == 1
xEventGroupSetBitsFromISR(事件组句柄,需要清零的事件位句柄)
返回值类型 BaseType_t
返回值 pdTRUE: 延后处理的消息成功的发动给了定时器守护任务
返回值 pdFALSE:当定时器守护任务的队列消息满时,函数会无法接收到新的消息,返回值就是pdFALSE
任务中返回组的当前值
xEventGroupGetBits(事件组句柄)
中断中返回组的当前值
xEventGroupGetBitsFromISR(事件组句柄)
返回值类型 EventBits_t
返回值是事件位被清零之前的事件组的值
调用的是这个函数xEventGroupClearBits(事件组句柄,0)
等待事件
使当前任务进入阻塞状态,然后等待事件组中多个事件位表示的事件成立(事件组成立的条件可以是多个事件位被置位,也可以是一个事件位被置位)然后解除阻塞态
xEventGroupWaitBits(句柄,所等待事件位的掩码,xClearOnExit,xWaitForAllBits,xTicksToWait)
句柄
需要等待哪个事件位就将掩码中相应的位置1
xClearOnExit---可以设置成---pdTRUE / pdFALSE
pdTRUE
函数在事件组条件成立而退出阻塞态时会将掩码中置顶的所有位全部清零
如果函数是因为超时而退出阻塞状态,即使设为pdTRUE也不会对事件位清零
pdFALSE
xWaitForAllBits---可以设置成---pdTRUE / pdFALSE
pdTRUE
表示需要将掩码中所有的位都置1,事件条件才会成立(逻辑与运算)
pdFALSE
表示需要将掩码中某一位置1,事件条件就成立(逻辑或运算)
当事件条件成立时函数就会退出,任务就退出了阻塞态
xTicksToWait
当前任务进入阻塞状态等待事件成立的节拍数
等待时间:
当事件组条件成立时,任务会提前退出阻塞态
也可以使任务进入阻塞状态等待事件组条件成立,主要用于某个同步点多任务进行同步,
xEventGroupSync()
从事件组的事件表示特点,以及xEventGroupWaitBits()的参数设置可知 1.事件组可以等待多个事件发生后作出响应,而队列/信号量只能对一个事件作出响应。 2.使用事件组时可以有多个任务执行xEventGroupWaitBits()等待同一个事件组的同一个条件成立,当事件组条件成立时多个任务都可以解除阻塞状态起到事件广播的作用
事件组是FreeRTOS中另外一种进程间通讯技术 与队列、信号量等进程间通讯技术相比,具有不同的特点
7-2事件组使用示例
任务
创建1个事件组和3个任务。
任务1
任务 Task ScanKeys中, 1.分别检测KeyLeft 和KeyRight 两个按键是否按下 2.按下时,将事件组中对应的事件位置位。 3.检测到KeyDown键按下时,将事件组清零。
任务2
条件成立时, 任务 Task LED 使LED1闪烁几次
任务3
条件成立时, 任务 Task Buzzer 使蜂鸣器响几次
事件
动态添加Add(Event flage Name)事件标志名称
生成代码
代码中要包含
#include "event_groups.h" //事件组相关函数头文件
#define BITMASK_KEY_LEFT (0b00000001<<2) #define BITMASK_KEY_RIGHT (0b00000001<<0)
按键扫描代码
void Start_Task_Task_ScanKeys(void *argument) { /* USER CODE BEGIN Start_Task_Task_ScanKeys */ // printf("Current event bit = %02lx\r\n", xEventGroupGetBits(Event_GroupHandle));//显示当前事件组的事件位的值 /* Infinite loop */ for(;;) { uint8_t key_pressed = key_scan(); // 扫描按键 printf("Current event bit = %02lx\r\n", xEventGroupGetBits(Event_GroupHandle));//显示当前事件组的事件位的值 switch (key_pressed) { case KEY1_PRES: xEventGroupSetBits(Event_GroupHandle, BITMASK_KEY_LEFT);break;//按键1按下设置事件2 bit位为1 case KEY2_PRES: xEventGroupSetBits(Event_GroupHandle, BITMASK_KEY_RIGHT);break;//按键2按下设置事件0 bit位为1 case KEY_UP_PRES: xEventGroupClearBits(Event_GroupHandle, BITMASK_KEY_LEFT | BITMASK_KEY_RIGHT);break;//按键KEY_UP按下清除2个事件位 } vTaskDelay(10);//进入阻塞态10ms }
蜂鸣器代码
void Start_Task_Buzzer(void *argument) { for(;;) { xEventGroupWaitBits(Event_GroupHandle, BITMASK_KEY_LEFT | BITMASK_KEY_RIGHT, pdTRUE, pdTRUE,portMAX_DELAY); //这个函数是让这段代码直接进入到阻塞状态,不往下执行,一直等到条件成立才向下执行 for(int i = 0; i < 10; i++) { Buzzer_TOGGLE(); vTaskDelay(pdMS_TO_TICKS(500)); } } /* USER CODE END Start_Task_Buzzer */ }
LED代码
void Start_Task_LED(void *argument) { for(;;) { printf("进入LED1阻塞\r\n");//阻塞结束后LED闪烁开始执行 xEventGroupWaitBits(Event_GroupHandle, BITMASK_KEY_LEFT | BITMASK_KEY_RIGHT, pdTRUE, pdTRUE, portMAX_DELAY); printf("LED1阻塞结束\r\n");//阻塞结束后LED闪烁开始执行 for(int i = 0; i < 10; i++) { LED1_TOGGLE(); vTaskDelay(pdMS_TO_TICKS(500)); } printf("LED1结束任务\r\n");//阻塞结束后蜂鸣器开始执行 } }
两个任务同时解除阻塞状态,虽然两个任务都调用 xEventGroupWaitBits()函数,参数 clearOnExit 的值都设置为了pdTRUE
事件组多任务同步
任务要求
其实可以通过先后使用函数 xEventGroupGetBits和xEventGroupWaitBits 实现这个操作,但是这种操作不是原子操作 我们可以使用函数xEventGroupSync替代这两个函数, 实现原子操作,实现多任务同步
函数xEventGroupSync()
xEventGroupSync( xEventGroup, uxBitsToSet, uxBitsToWaitFor, TicksToWait )
xEventGroup:所操作的事件组对象
uxBitsToSet:需要置位的事件位掩码
uxBitsToWaitFor:需要等待的同步条件(Bit2 Bit1 Bit0都被置位时0x07,任务接触阻塞,开始同步运行)
TicksToWait:等待的节拍数
返回值类型 EventBits_t
返回值是函数退出时事件组的值
退出
可以满足条件时退出 或者超时等待后退出
主题