第12章 系统时钟节拍和时间管理
本章节为大家讲解RTX操作系统的时钟节拍和时间管理函数,其中时间管理函数是RTX的基本函数,初学者务必要掌握。
本章教程配套的例子含Cortex-M3内核的STM32F103和Cortex-M4内核的STM32F407。
12.1 RTX的时钟节拍
12.2 RTX的时间管理
12.3 实验例程说明
12.4 总结
任何操作系统都需要提供一个时钟节拍,以供系统处理诸如延时,超时等与时间相关的事件。
时钟节拍是特定的周期性中断。这个中断可以看做是系统心跳。中断之间的时间间隔取决于不同的应用,一般是1ms – 100ms。时钟的节拍中断使得内核可以将任务延迟若干个时钟节拍,以及当任务等待事件发生时,提供等待超时等依据。时钟节拍率越快,系统的额外开销就越大。
对于Cortex-M3内核的STM32F103和Cortex-M4内核的STM32F407,教程配套的例子都是用的滴答定时器来实现系统时钟节拍的。
滴答定时器Systick
SysTick定时器被捆绑在NVIC中,用于产生SysTick异常(异常号:15),滴答定时器是一个24位的递减计数器,支持中断。使用比较简单,专门用于给操作系统提供时钟节拍。
RTX的系统时钟节拍可以在配置向导里面设置:
Tick Timer Configuration
Hardware timer
CoreSysTick 表示选择系统滴答定时器,因为M3/M4内核带有滴答定时器,一般情况下都是选用滴答定时器作为系统时钟节拍。
PeripheralTimer 表示使用外设定时器。
Timer clock value
表示定时器主频,单位Hz。
Timer tick value
表示系统时钟节拍周期,单位us。
12.2 RTX的时间管理
时间管理功能是RTX操作系统里面比较基本的功能,同时也是非常有必要掌握好的。
12.2.1 时间延迟
RTX中的时间延迟函数主要有以下两个作用:
1. 为周期性执行的任务提供延迟。
2. 对于抢占式调度器,让高优先级任务可以通过时间延迟函数释放CPU使用权,从而让低优先级任务可以得到执行。
下面我们通过如下的框图来说明一下延迟函数对任务运行状态的影响,让大家有一个形象的认识。
运行条件:
1. 仅对任务Task1的运行状态做说明。
2. 调度器支持时间片调度和抢占式调度。
运行过程描述如下:
1. 起初任务Task1处于运行态,调用os_dly_wait函数后进入到挂起状态,也就是wait状态。
2. os_dly_wait函数设置的延迟时间到,由于任务Task1不是当前就绪的最高优先级任务,所以不能进入到运行状态,只能进入到就绪状态,也就是ready状态。
3. 一段时间后,调度器发现任务Task1是当前就绪的最高优先级任务,从而任务从就绪态切换到运行态。
4. 由于时间片调度,任务Task1由运行态切换到就绪态。
上面就是一个简单的任务运行状态的切换过程。
12.2.2 RTX的延迟函数
使用如下4个函数可以实现RTX的延迟:
os_dly_wait()
os_itv_set()
os_itv_wait()
os_time_get()
关于这4个函数的讲解及其使用方法可以看教程第3章3.3小节里面说的参考资料rlarm.chm文件
下面我们对这四个函数依次进行说明:
12.2.3 函数os_dly_wait
函数原型:
- void os_dly_wait (
- U16 delay_time ); /* 延迟时间长度 */
函数描述:
函数os_dly_wait用于任务的延迟。
参数delay_time用于设置延迟的时钟节拍个数,范围1-0xFFFE。
使用这个函数要注意以下几个问题
1. 同一个任务中os_dly_wait和os_itv_wait不能混合调用,只能选择其中一个。
使用举例:
- #include <rtl.h>
- __task void task1 (void) {
- ..
- os_dly_wait (20);
- ..
- }
12.2.4 函数os_itv_set
函数原型:
- void os_itv_set (
- U16 interval_time ); /* 周期唤醒的时间间隔 */
函数描述:
函数os_itv_set用于设置周期性延迟的时间间隔,此函数必须配合os_itv_wait函数一起使用。用户调用函数os_itv_set设置了周期性时间延迟的时间间隔后,然后调用函数os_itv_wait函数等待时间到。
参数interval_time用于设置周期性延迟的时间间隔,单位时钟节拍数,参数范围1-0x7FFF。
使用举例:
- #include <rtl.h>
- __task void task1 (void) {
- ..
- os_itv_set (20);
- ..
- }
12.2.5 函数os_itv_wait
函数原型:
- void os_itv_wait (void);
函数描述:
函数os_itv_wait函数用于等待周期性延迟时间到,此函数必须配合os_itv_set函数一起使用。用户调用函数os_itv_set设置了周期性时间延迟的时间间隔后,然后调用函数os_itv_wait函数等待时间到。
使用举例:
- #include <rtl.h>
-
- __task void task1 (void) {
- ..
- os_itv_set (20);
- for (;;) {
- os_itv_wait ();
- /* 添加功能 */
- }
- }
12.2.6 函数os_time_get
函数原型:
- void os_time_get (void);
函数描述:
函数os_time_get用于获取系统当前运行时钟节拍数。
使用举例:
- #include <rtl.h>
-
- BOOL init_io (void) {
- U32 ticks;
-
- ticks = os_time_get ();
- }
12.2.7 函数os_dly_wait和os_itv_wait的区别
函数os_dly_wait实现的是周期性延迟,而函数os_itv_wait实现的是相对性延迟,反映到实际应用上有什么区别呢,下面就给大家举一个简单的例子。
运行条件:
1. 有一个bsp_KeyScan函数,这个函数处理时间大概耗时2ms。
2. 有两个任务,一个任务Task1是用的os_dly_wait延迟,延迟10ms,另一个任务Task2是用的os_itv_wait延迟,延迟10ms。
3. 不考虑任务被抢占而造成的影响。
实际运行过程效果:
Task1:
bsp_KeyScan+ os_dly_wait(10) ---> bsp_KeyScan + os_dly_wait(10)
|----2ms + 10ms 为一个周期------| |----2ms + 10ms 为一个周期----|
这个就是相对性的含义
Task2:
bsp_KeyScan+ os_itv_wait ---------> bsp_KeyScan+ os_itv_wait
|----10ms为一个周期(2ms包含在10ms内)---| |----10ms为一个周期------|
这就是周期性的含义。
12.3 实验例程说明
12.3.1 STM32F103开发板实验
配套例子:
V4-408_RTX实验_时间管理
实验目的:
1. 学习RTX的时间延迟相关函数和系统时钟节拍计数的获取
2. 学习相对时间延迟和周期性时间延迟的实现
实验内容:
1. K1按键按下,串口打印。
2. K2按键按下,获取系统时钟节拍计数并打印。
3. 各个任务实现的功能如下:
AppTaskUserIF任务 :按键消息处理。
AppTaskLED任务 :LED闪烁。
AppTaskMsgPro任务 :消息处理,这里是用作LED闪烁。
AppTaskStart任务 :启动任务,也是最高优先级任务,这里实现按键扫描。
RTX配置:
RTX配置向导详情如下:
Task Configuration
Number of concurrent running tasks
允许创建4个任务,实际创建了如下四个任务
AppTaskUserIF任务 :按键消息处理。
AppTaskLED任务 :LED闪烁。
AppTaskMsgPro任务 :消息处理,这里是用作LED闪烁。
AppTaskStart任务 :启动任务,也是最高优先级任务,这里实现按键扫描。
Number of tasks with user-provided stack
创建的4个任务都是采用自定义堆栈方式。
RTX任务调试信息:
程序设计:
任务栈大小分配:
staticuint64_t AppTaskUserIFStk[512/8]; /* 任务栈 */
staticuint64_t AppTaskLEDStk[256/8]; /* 任务栈 */
staticuint64_t AppTaskMsgProStk[512/8]; /* 任务栈 */
staticuint64_t AppTaskStartStk[512/8]; /* 任务栈 */
将任务栈定义成uint64_t类型可以保证任务栈是8字节对齐的,8字节对齐的含义就是数组的首地址对8求余等于0。如果不做8字节对齐的话,部分C语言库函数,浮点运算和uint64_t类型数据运算会出问题。
系统栈大小分配:
RTX初始化:
- /*
- *********************************************************************************************************
- * 函 数 名: main
- * 功能说明: 标准c程序入口。
- * 形 参: 无
- * 返 回 值: 无
- *********************************************************************************************************
- */
- int main (void)
- {
- /* 初始化外设 */
- bsp_Init();
-
- /* 创建启动任务 */
- os_sys_init_user (AppTaskStart, /* 任务函数 */
- 4, /* 任务优先级 */
- &AppTaskStartStk, /* 任务栈 */
- sizeof(AppTaskStartStk)); /* 任务栈大小,单位字节数 */
- while(1);
- }
RTX任务创建:
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskCreate
- * 功能说明: 创建应用任务
- * 形 参: 无
- * 返 回 值: 无
- *********************************************************************************************************
- */
- static void AppTaskCreate (void)
- {
- HandleTaskUserIF = os_tsk_create_user(AppTaskUserIF, /* 任务函数 */
- 1, /* 任务优先级 */
- &AppTaskUserIFStk, /* 任务栈 */
- sizeof(AppTaskUserIFStk)); /* 任务栈大小,单位字节数 */
-
- HandleTaskLED = os_tsk_create_user(AppTaskLED, /* 任务函数 */
- 2, /* 任务优先级 */
- &AppTaskLEDStk, /* 任务栈 */
- sizeof(AppTaskLEDStk)); /* 任务栈大小,单位字节数 */
-
- HandleTaskMsgPro = os_tsk_create_user(AppTaskMsgPro, /* 任务函数 */
- 3, /* 任务优先级 */
- &AppTaskMsgProStk, /* 任务栈 */
- sizeof(AppTaskMsgProStk)); /* 任务栈大小,单位字节数 */
- }
四个RTX任务的实现:
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskUserIF
- * 功能说明: 按键消息处理
- * 形 参: 无
- * 返 回 值: 无
- * 优 先 级: 1 (数值越小优先级越低,这个跟uCOS相反)
- *********************************************************************************************************
- */
- __task void AppTaskUserIF(void)
- {
- uint32_t ulTicks;
- uint8_t ucKeyCode;
-
- while(1)
- {
- ucKeyCode = bsp_GetKey();
-
- if (ucKeyCode != KEY_NONE)
- {
- switch (ucKeyCode)
- {
- /* K1键按下,打印调试说明 */
- case KEY_DOWN_K1:
- printf("K1键按下,使用MDK中自带的RTX调试组件,请务必使用MDK4.74版本进行调试\r\n");
- break;
-
- /* K2键按下,打印系统时钟节拍计数 */
- case KEY_DOWN_K2:
- ulTicks = os_time_get();
- printf("K2键按下,当前系统时钟节拍 os_time_get = %d\r\n", ulTicks);
- break;
-
- /* 其他的键值不处理 */
- default:
- break;
- }
- }
-
- os_dly_wait(20);
- }
- }
-
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskLED
- * 功能说明: LED闪烁。
- * 形 参: 无
- * 返 回 值: 无
- * 优 先 级: 2
- *********************************************************************************************************
- */
- __task void AppTaskLED(void)
- {
- const uint16_t usFrequency = 200; /* 延迟周期 */
-
- /* 设置延迟周期 */
- os_itv_set(usFrequency);
-
- while(1)
- {
- bsp_LedToggle(2);
- bsp_LedToggle(3);
-
- /* os_itv_wait是周期性延迟,os_dly_wait是相对延迟。*/
- os_itv_wait();
- }
- }
-
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskMsgPro
- * 功能说明: 消息处理,这里是用作LED闪烁。
- * 形 参: 无
- * 返 回 值: 无
- * 优 先 级: 3
- *********************************************************************************************************
- */
- __task void AppTaskMsgPro(void)
- {
- while(1)
- {
- bsp_LedToggle(1);
- bsp_LedToggle(4);
- os_dly_wait(300);
- }
- }
-
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskStart
- * 功能说明: 启动任务,也就是最高优先级任务。这里实现按键扫描。
- * 形 参: 无
- * 返 回 值: 无
- * 优 先 级: 4
- *********************************************************************************************************
- */
- __task void AppTaskStart(void)
- {
- AppTaskCreate();
-
- while(1)
- {
- /* 按键扫描 */
- bsp_KeyScan();
- os_dly_wait(10);
- }
- }
12.3.2 STM32F407开发板实验
配套例子:
V4-408_RTX实验_时间管理
实验目的:
1. 学习RTX的时间延迟相关函数和系统时钟节拍计数的获取
2. 学习相对时间延迟和周期性时间延迟的实现
实验内容:
1. K1按键按下,串口打印。
2. K2按键按下,获取系统时钟节拍计数并打印。
3. 各个任务实现的功能如下:
AppTaskUserIF任务 :按键消息处理。
AppTaskLED任务 :LED闪烁。
AppTaskMsgPro任务 :消息处理,这里是用作LED闪烁。
AppTaskStart任务 :启动任务,也是最高优先级任务,这里实现按键扫描。
RTX配置:
RTX配置向导详情如下:
Task Configuration
Number of concurrent running tasks
允许创建4个任务,实际创建了如下四个任务
AppTaskUserIF任务 :按键消息处理。
AppTaskLED任务 :LED闪烁。
AppTaskMsgPro任务 :消息处理,这里是用作LED闪烁和串口打印任务正在运行。
AppTaskStart任务 :启动任务,也是最高优先级任务,这里实现按键扫描。
Number of tasks with user-provided stack
创建的4个任务都是采用自定义堆栈方式。
RTX任务调试信息:
程序设计:
任务栈大小分配:
staticuint64_t AppTaskUserIFStk[512/8]; /* 任务栈 */
staticuint64_t AppTaskLEDStk[256/8]; /* 任务栈 */
staticuint64_t AppTaskMsgProStk[512/8]; /* 任务栈 */
staticuint64_t AppTaskStartStk[512/8]; /* 任务栈 */
将任务栈定义成uint64_t类型可以保证任务栈是8字节对齐的,8字节对齐的含义就是数组的首地址对8求余等于0。如果不做8字节对齐的话,部分C语言库函数,浮点运算和uint64_t类型数据运算会出问题。
系统栈大小分配:
RTX初始化:
- /*
- *********************************************************************************************************
- * 函 数 名: main
- * 功能说明: 标准c程序入口。
- * 形 参: 无
- * 返 回 值: 无
- *********************************************************************************************************
- */
- int main (void)
- {
- /* 初始化外设 */
- bsp_Init();
-
- /* 创建启动任务 */
- os_sys_init_user (AppTaskStart, /* 任务函数 */
- 4, /* 任务优先级 */
- &AppTaskStartStk, /* 任务栈 */
- sizeof(AppTaskStartStk)); /* 任务栈大小,单位字节数 */
- while(1);
- }
RTX任务创建:
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskCreate
- * 功能说明: 创建应用任务
- * 形 参: 无
- * 返 回 值: 无
- *********************************************************************************************************
- */
- static void AppTaskCreate (void)
- {
- HandleTaskUserIF = os_tsk_create_user(AppTaskUserIF, /* 任务函数 */
- 1, /* 任务优先级 */
- &AppTaskUserIFStk, /* 任务栈 */
- sizeof(AppTaskUserIFStk)); /* 任务栈大小,单位字节数 */
-
- HandleTaskLED = os_tsk_create_user(AppTaskLED, /* 任务函数 */
- 2, /* 任务优先级 */
- &AppTaskLEDStk, /* 任务栈 */
- sizeof(AppTaskLEDStk)); /* 任务栈大小,单位字节数 */
-
- HandleTaskMsgPro = os_tsk_create_user(AppTaskMsgPro, /* 任务函数 */
- 3, /* 任务优先级 */
- &AppTaskMsgProStk, /* 任务栈 */
- sizeof(AppTaskMsgProStk)); /* 任务栈大小,单位字节数 */
- }
四个RTX任务的实现:
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskUserIF
- * 功能说明: 按键消息处理
- * 形 参: 无
- * 返 回 值: 无
- * 优 先 级: 1 (数值越小优先级越低,这个跟uCOS相反)
- *********************************************************************************************************
- */
- __task void AppTaskUserIF(void)
- {
- uint32_t ulTicks;
- uint8_t ucKeyCode;
-
- while(1)
- {
- ucKeyCode = bsp_GetKey();
-
- if (ucKeyCode != KEY_NONE)
- {
- switch (ucKeyCode)
- {
- /* K1键按下,打印调试说明 */
- case KEY_DOWN_K1:
- printf("K1键按下,使用MDK中自带的RTX调试组件,请务必使用MDK4.74版本进行调试\r\n");
- break;
-
- /* K2键按下,打印系统时钟节拍计数 */
- case KEY_DOWN_K2:
- ulTicks = os_time_get();
- printf("K2键按下,当前系统时钟节拍 os_time_get = %d\r\n", ulTicks);
- break;
-
- /* 其他的键值不处理 */
- default:
- break;
- }
- }
-
- os_dly_wait(20);
- }
- }
-
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskLED
- * 功能说明: LED闪烁。
- * 形 参: 无
- * 返 回 值: 无
- * 优 先 级: 2
- *********************************************************************************************************
- */
- __task void AppTaskLED(void)
- {
- const uint16_t usFrequency = 200; /* 延迟周期 */
-
- /* 设置延迟周期 */
- os_itv_set(usFrequency);
-
- while(1)
- {
- bsp_LedToggle(2);
- bsp_LedToggle(3);
-
- /* os_itv_wait是周期性延迟,os_dly_wait是相对延迟。*/
- os_itv_wait();
- }
- }
-
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskMsgPro
- * 功能说明: 消息处理,这里是用作LED闪烁。
- * 形 参: 无
- * 返 回 值: 无
- * 优 先 级: 3
- *********************************************************************************************************
- */
- __task void AppTaskMsgPro(void)
- {
- while(1)
- {
- bsp_LedToggle(1);
- bsp_LedToggle(4);
- os_dly_wait(300);
- }
- }
-
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskStart
- * 功能说明: 启动任务,也就是最高优先级任务。这里实现按键扫描。
- * 形 参: 无
- * 返 回 值: 无
- * 优 先 级: 4
- *********************************************************************************************************
- */
- __task void AppTaskStart(void)
- {
- AppTaskCreate();
-
- while(1)
- {
- /* 按键扫描 */
- bsp_KeyScan();
- os_dly_wait(10);
- }
- }
12.4 总结
本章节主要为大家讲解了RTX操作系统的时钟节拍和时间管理函数,其中时间管理函数是RTX的基本函数,初学者务必要掌握。