九、任务通知
什么是任务通知?
FreeRTOS
从版本
V8.2.0
开始提供任务通知这个功能,每个任务都有一个
32
位的通知值。按照
FreeRTOS
官方的说法,使用消息通知比通过二进制信号量方式解除阻塞任务快
45%
, 并且更加
省内存(无需创建队列)。
在大多数情况下,任务通知可以替代二值信号量、计数信号量、事件标志组,可以替代长度为
1
的队列(可以保存一个
32
位整数或指针值),并且任务通知速度更快、使用的
RAM
更少!
任务通知值的更新方式
FreeRTOS
提供以下几种方式发送通知给任务 :
发送消息给任务,如果有通知未读, 不覆盖通知值
发送消息给任务,直接覆盖通知值
发送消息给任务,设置通知值的一个或者多个位
发送消息给任务,递增通知值
通过对以上方式的合理使用,可以在一定场合下替代原本的队列、信号量、事件标志组等。
任务通知的优势和劣势
任务通知的优势
1.
使用任务通知向任务发送事件或数据,比使用队列、事件标志组或信号量快得多。
2.
使用其他方法时都要先创建对应的结构体,使用任务通知时无需额外创建结构体
任务通知的劣势
1.
只有任务可以等待通知,中断服务函数中不可以,因为中断没有
TCB
。
2.
通知只能一对一,因为通知必须指定任务。
3.
等待通知的任务可以被阻塞, 但是发送消息的任务,任何情况下都不会被阻塞等待。
4.
任务通知是通过更新任务通知值来发送数据的,任务结构体中只有一个任务通知值,只能保
持一个数据。
函数
BaseType_t
xTaskNotify
(
TaskHandle_t xTaskToNotify
,
uint32_t
ulValue
,
eNotifyAction eAction
);
参数:
xTaskToNotify
:需要接收通知的任务句柄;
ulValue
:用于更新接收任务通知值, 具体如何更新由形参
eAction
决定;
eAction
:一个枚举,代表如何使用任务通知的值;
![](https://img-blog.csdnimg.cn/233b41380f1c4f5bb841c27d90871e6a.png)
返回值:
如果被通知任务还没取走上一个通知,又接收了一个通知,则这次通知值未能更新并返回
pdFALSE
, 而其他情况均返回
pdPASS
。
BaseType_t
xTaskNotifyAndQuery
(
TaskHandle_t xTaskToNotify
,
uint32_t
ulValue
,
eNotifyAction eAction
,
uint32_t
*
pulPreviousNotifyValue
);
参数:
xTaskToNotify
:需要接收通知的任务句柄;
ulValue
:用于更新接收任务通知值, 具体如何更新
由形参
eAction
决定;
eAction
:一个枚举,代表如何使用任务通知的值;
pulPreviousNotifyValue
:对象任务的上一个任务通知值,如果为
NULL
, 则不需要回传, 这个时
候就等价于函数
xTaskNotify()
。
返回值:
如果被通知任务还没取走上一个通知,又接收了一个通知,则这次通知值未能更新并返回
pdFALSE
, 而其他情况均返回
pdPASS
。
BaseType_t
xTaskNotifyGive
(
TaskHandle_t xTaskToNotify
);
参数:
xTaskToNotify
:接收通知的任务句柄, 并让其自身的任务通知值加
1
。
返回值:
总是返回
pdPASS
。
2.
等待通知
等待通知
API
函数只能用在任务,不可应用于中断中!
![](https://img-blog.csdnimg.cn/fd9cc138a86e4411abc45ad4e14465a5.png)
uint32_t ulTaskNotifyTake
(
BaseType_t xClearCountOnExit
,
TickType_t xTicksToWait
);
参数:
xClearCountOnExit
:指定在成功接收通知后,将通知值清零或减
1
,
pdTRUE
:把通知值清零(二
值信号量);
pdFALSE
:把通知值减一(计数型信号量);
xTicksToWait
:阻塞等待任务通知值
的最大时间;
返回值:
0
:接收失败 非
0
:接收成功,返回任务通知的通知值
BaseType_t
xTaskNotifyWait
(
uint32_t
ulBitsToClearOnEntry
,
uint32_t
ulBitsToClearOnExit
,
uint32_t
*
pulNotificationValue
,
TickType_t xTicksToWait
);
ulBitsToClearOnEntry
:函数执行前清零任务通知值那些位 。
ulBitsToClearOnExit
:表示在函数退
出前,清零任务通知值那些位,在清
0
前,接收到的任务通知值会先被保存到形参
*pulNotificationValue
中。
pulNotificationValue
:用于保存接收到的任务通知值。 如果 不需要使
用,则设置为
NULL
即可 。
xTicksToWait
:等待消息通知的最大等待时间。
实操
1.
模拟二值信号量
2.
模拟计数型信号量
3.
模拟事件标志组
4.
模拟邮箱
邮箱就是大小为1的队列
十、延时函数
相对延时:
vTaskDelay
绝对延时:
vTaskDelayUntil
vTaskDelay
作用是让任务阻塞,任务阻塞后,
RTOS
系统调用其它处于就绪状态的优先级最高的任
务来执行。
HAL_Delay
一直不停的调用获取系统时间的函数,直到指定的时间流逝然后退出,故其占用了全
部
CPU
时间。
十一、软件定时起
什么是定时器?
简单可以理解为闹钟,到达指定一段时间后,就会响铃。
STM32
芯片自带硬件定时器,精度较高,达到定时时间后会触发中断,也可以生成
PWM
、输入
捕获、输出比较,等等,功能强大,但是由于硬件的限制,个数有限。
软件定时器也可以实现定时功能,达到定时时间后可调用回调函数,可以在回调函数里处理信
息。
软件定时器优缺点
优点:
1.
简单、成本低;
2.
只要内存足够,可创建多个;
缺点:
精度较低,容易受中断影响。在大多数情况下够用,但对于精度要求比较高的场合不建议使用
软件定时器原理
定时器是一个可选的、不属于
FreeRTOS
内核的功能,它是由定时器服务任务来提供的。
在调用函数
vTaskStartScheduler()
开启任务调度器的时候,会创建一个用于管理软件定时器的任
务,这个任务就叫做软件定时器服务任务。
1.
负责软件定时器超时的逻辑判断
2.
调用超时软件定时器的超时回调函数
3.
处理软件定时器命令队列
FreeRTOS
提供了很多定时器有关的
API
函数,这些
API
函数大多都使用
FreeRTOS
的队列发送命令给
定时器服务任务。
这个队列叫做定时器命令队列
。定时器命令队列是提供给
FreeRTOS
的软件定时
器使用的,
用户不能直接访问
!
![](https://img-blog.csdnimg.cn/d21a59cd8fdf4b60a19f0160f662615c.png)
单次定时器和周期定时器
单次定时器:
只超时一次,调用一次回调函数。可手动再开启定时器;
周期定时器:
多次超时,多次调用回调函数。
函数
1.
创建软件定时器
TimerHandle_t
xTimerCreate
(
const char
*
const
pcTimerName
,
const
TickType_t xTimerPeriod
,
const
UBaseType_t uxAutoReload
,
void
*
const
pvTimerID
,
TimerCallbackFunction_t pxCallbackFunction
);
参数:
pcTimerName
:软件定时器名称
xTimerPeriodInTicks
:定时超时时间,单位:系统时钟节拍。宏
pdMS_TO_TICKS()
可用于将以毫秒为单位指定的时间转换为以
tick
为单位指定的时间。
uxAutoReload
:定时器模式,
pdTRUE
:周期定时器,
pdFALSE
:单次定时器
pvTimerID
:软件
定时器
ID
,用于多个软件定时器公用一个超时回调函数
pxCallbackFunction
:软件定时器超时回
调函数
返回值:
成功:定时器句柄
失败:
NULL
2.
开启软件定时器
BaseType_t
xTimerStart
(
TimerHandle_t xTimer
,
TickType_t xBlockTime
);
参数:
xTimer
:待开启的软件定时器的句柄
xTickToWait
:发送命令到软件定时器命令队列的最大等待时
间
返回值:
pdPASS
:开启成功
pdFAIL
:开启失败
3.
停止软件定时器
BaseType_t
xTimerStop
(
TimerHandle_t xTimer
,
TickType_t xBlockTime
);
参数与返回值同上。
4.
复位软件定时器
BaseType_t
xTimerReset
(
TimerHandle_t xTimer
,
TickType_t xBlockTime
);
参数与返回值同上。
该功能将使软件定时器的重新开启定时,复位后的软件定时器以复位时的时刻作为开启时刻重新
定时。
5.
更改软件定时器定时时间
BaseType_t
xTimerChangePeriod
(
TimerHandle_t xTimer
,
TickType_t xNewPeriod
,
TickType_t xBlockTime
);
xNewPeriod
:新的定时超时时间,单位:系统时钟节拍。
其余参数与返回值同上。
实操
实验需求
创建两个定时器:
定时器
1
,周期定时器,每
1
秒打印一次
liangxu shuai
定时器
2
,单次定时器,启动后
2
秒打印一次
laochen shuai
cubeMX
配置
![](https://img-blog.csdnimg.cn/d34eca9998a04f75954878a7d7d5f7d9.png)
void
StartDefaultTask
(
void const
*
argument
)
{
/* USER CODE BEGIN StartDefaultTask */
osTimerStart
(
myTimer01Handle
,
1000
);
// xTimerChangePeriod(myTimer01Handle, pdMS_TO_TICKS(1000), 0);
osTimerStart
(
myTimer02Handle
,
2000
);
/* Infinite loop */
for
(;;)
{
osDelay
(
1
);
}
系统有封装的定时器创建函数,直接调用即可
/* USER CODE END StartDefaultTask */
}
void
Callback01
(
void const
*
argument
)
{
/* USER CODE BEGIN Callback01 */
printf
(
"
周期定时器:
liangxu shuai\r\n"
);
/* USER CODE END Callback01 */
}
void
Callback02
(
void const
*
argument
)
{
/* USER CODE BEGIN Callback02 */
printf
(
"
单次定时器:
laochen shuai\r\n"
);
/* USER CODE END Callback02 */
}
十二、中断管理
中断优先级
任何中断的优先级都大于任务!
在我们的操作系统,中断同样是具有优先级的,并且我们也可以设置它的优先级,但是他的优先
级并不是从
015
,默认情况下它是从
515
,
0~4
这
5
个中断优先级不是
FreeRTOS
控制的(
5
是
取决于
configMAX_SYSCALL_INTERRUPT_PRIORITY
)。
相关注意
1在中断中必需使用中断相关的函数;
2.
中断服务函数运行时间越短越好。
实操
实验需求
创建一个队列及一个任务,按下按键
KEY1
触发中断,在中断服务函数里向队列里发送数据,任
务则阻塞接收队列数据。
![](https://img-blog.csdnimg.cn/69f3270998ef4932aa765a9004dc84ad.png)
代码实现
stm32f1xx_it.c
#
include
"cmsis_os.h" //系统配置的头文件,添加就好
extern
osMessageQId myQueue01Handle
;
void
HAL_GPIO_EXTI_Callback
(
uint16_t
GPIO_Pin
)
{
uint32_t
snd
=
1
;
xQueueSendFromISR
(
myQueue01Handle
,
&
snd
,
NULL
);
}
freertos.c
void
StartDefaultTask
(
void const
*
argument
)
{
/* USER CODE BEGIN StartDefaultTask */
uint32_t
rev
=
0
;
/* Infinite loop */
for
(;;)
{
if
(
xQueueReceive
(
myQueue01Handle
,
&
rev
,
portMAX_DELAY
)
==
pdTRUE
)
printf
(
"rev = %d\r\n"
,
rev
);
osDelay
(
1
);
}
/* USER CODE END StartDefaultTask */
}