STM32理论 —— μCOS-Ⅲ(1/2):移植、任务创建&删除&挂起&恢复、中断管理、临界段代码保护及任务调度锁、任务调度器
任务调度器就是决定当前执行哪个任务;μCos-Ⅲ 支持2种任务调度方式抢占式调度:针对优先级不同的任务,优先级高的任务可抢占优先级低的任务;时间片调度:针对优先级相同的任务,当多个任务优先级相同且就绪时,调度器会根据用户设置的时间片轮流运行这些任务。时间片以一次系统时钟节拍为单位(滴答定时器的中断频率),µC/OS-III 默认设置的任务时间片为 100,则 µC/OS-III 会在当前任务运行
文章目录
-
μCos-Ⅲ全称是Micro C OS Ⅲ,由Micriμm 公司发布的一个基于C 语言编写的第三代小型实时操作系统(RTOS);
-
RTOS 与裸机相比最大的优势在于多任务管理与实时性,它提供了多任务管理和任务间通信的功能;
-
μCOS-Ⅲ与FreeRTOS的区别:μCOS-III 的源码可读性比较强,代码写的非常规范。国内资料较多;
-
RTOS 的任务调度结构:如下图,高优先级任务能抢占低优先级任务,而中断能打断任意任务,每个任务都有自己的任务堆栈,用于保存任务的寄存器值;除非高优先级任务挂起,否则在一直运行高优先级任务过程中,低优先级任务无法被运行;

-
μCos-Ⅲ官方文档:https://micrium.atlassian.net/wiki/spaces、https://docs.silabs.com/micrium/latest/micrium-common-api/
1. 任务调度器简介
任务调度器就是决定当前执行哪个任务;
μCos-Ⅲ 支持2种任务调度方式:
- 抢占式调度:针对优先级不同的任务,优先级高的任务可抢占优先级低的任务;
- 时间片调度:针对优先级相同的任务,当多个任务优先级相同且就绪时,调度器会根据用户设置的时间片轮流运行这些任务。时间片以一次系统时钟节拍为单位(滴答定时器的中断频率),µC/OS-III 默认设置的任务时间片为 100,则 µC/OS-III 会在当前任务运行 100 次系统时钟节拍的时间后,切换到另一个相同任务优先级的任务中运行。
1.1 抢占式调度
- 创建3个任务;
- 任务1、任务2、任务3的优先级分别设置为3、2、1(数字越小,优先级越高);
- 任务1先运行,过程中任务2就绪,在抢占式调度器作用下任务2抢占运行,任务1进入就绪态;
- 任务2运行中任务3就绪,在抢占式调度器作用下任务3抢占运行,任务2进入就绪态;
- 任务3运行中被挂起(系统延时或等待信号量等),此时处于就绪态中优先级最高的任务2运行;
- 任务3阻塞解除(延时时间到货接收到信号量),任务3恢复到就绪态,在抢占式调度器作用下任务3抢占运行,任务2进入就绪态;

1.2 时间片调度
- 创建3个任务;
- 3个任务的优先级同等;
- 3个任务的时间片默认设置为100;
- 任务1运行完100个时间片后,任务2运行;
- 任务2运行完100个时间片后,任务3运行;
- 任务3运行过程中(100个时间片未运行完)被挂起(系统延时或等待信号量等),此时直接切换到下一个任务,即任务1;
- 任务1运行完100个时间片后,任务2运行,以此循环;

一个时间片运行时间,取决于滴答定时器中断频率;
更多详见8.3 时间片调度;
1.3 任务状态
- 运行态:即正在运行的任务,STM32是单核CPU,故同一时间只能运行一个任务;
- 就绪态:即任务能被执行但还没被执行;
- 挂起态:任务在运行中因延时或等待某一事件发生时被挂起;
- 休眠态:任务被创建在内存中,但未在任务调度器中(任务被删除);
- 中断态:任务在运行中被中断任务打断,CPU 跳转去执行中断服务函数,直到中断结束;
上诉5种任务状态并非可互相转换,转换关系如下图:
1.4 任务列表
-
就绪列表:存放就绪态任务的列表,
OSRdyList[x],其中x表示任务优先级值,一般范围值为0~ 31;任务调度器总是在就绪列表中,选择最高优先级的任务来执行;如下图,bit0 ~ bit31 为OSPrioTbl[x]代表对应就绪列表中是否有就绪任务,有则为1,否则为0;
-
Tick 列表:存放正在等待延时超时或正在等待挂起超时的任务的列表,
OSTickList; -
挂起列表:存放等待信号量、事件的任务的列表,
PendList;
2. UCOSIII 移植过程
2.1 源码获取与源码介绍
- 源码获取途径:
- UCOSSIII 官网下载:
- 正点原子 - 软件资料 - ucos-III 学习资料 - ucos-III 及其相关组件源代码.zip
源代码包含:uC-OS3、uC-CPU、uC-LIB
- uC-OS3源码文件简介:
Cfg文件夹: µC/OS-III 配置文件的模板文件;Ports文件夹:接口文件,与硬件相关的移植文件;Source文件夹:µC/OS-III 的源码文;Template文件夹:与动态 Tick 管理相关的文件;
- uC-CPU 源码文件简介:STM32主要看
ARM Cortex-M,内含与 ARM Cortex-M 内核的 CPU 相关的移植文件;
- uC-LIB 源码文件简介:官方提供的ASCII 字符操作、数学、内存管理、字符串操作的库;

2.2 开始移植
- 代码准备:
参考:《UCOS-III 开发指南》 - 第二章 µC/OS-III 移植
- uC-OSIII 源码:正点原子精英V2资料\6,软件资料\2,UCOS学习资料\UCOSIII资料\UCOS-III及其相关组件源代码
- 基础工程:正点原子精英V2资料\4,程序源码\2,标准例程-HAL库版本\2,标准例程-HAL库版本\实验33 内存管理实验
-
在基础工程的 Middlewares 文件夹中新建一个 uC-OS3 子文件夹
-
将uCOSIII 源代码的3个文件夹都拷贝到该文件夹中

-
打开基础工程,新建4个分组
Middlewares/uC-OS3/BSP、Middlewares/uC-OS3/CPU、Middlewares/uC-OS3/LIB 和 Middlewares/uC-OS3/OS3;
-
在分组Middlewares/uC-OS3/BSP 中添加以下源文件

路径分别为:- bsp_cpu.c:uC-CPU-1.32.01\BSP\Template
- bsp_os_dt.c:uC-OS3-3.08.01\Template
-
在分组Middlewares/uC-OS3/CPU 中添加以下源文件

路径分别为:
- cpu_a.asm:uC-CPU-1.32.01\ARM-Cortex-M\ARMv7-M\ARM
- cpu_c.c:uC-CPU-1.32.01\ARM-Cortex-M\ARMv7-M
- cpu_core.c:uC-CPU-1.32.01
- 在分组Middlewares/uC-OS3/LIB 中添加以下源文件

路径都为:uC-LIB-1.39.01
- 在分组Middlewares/uC-OS3/OS3中添加以下源文件

路径分别为:
- os_app_hooks.c:uC-OS3-3.08.01\Cfg\Template
- os_cpu_a.asm:uC-OS3-3.08.01\Ports\ARM-Cortex-M\ARMv7-M\ARM
- os_cpu_c.c:uC-OS3-3.08.01\Ports\ARM-Cortex-M\ARMv7-M
- 其他源文件:uC-OS3-3.08.01\Source
-
包含头文件路径

-
修改System 文件中的sys.h、usart.c、delay.c
- 在sys.h 中将宏定义
#define SYS_SUPPORT_OS改为1; - 在usart.c 中:修改头文件包含
/* 如果使用os,则包括下面的头文件即可. */
#if SYS_SUPPORT_OS
//#include "includes.h" /* os 使用 */
#include "os.h" /* os 使用 */
#endif
- delay.c:删除或注释下面代码
///* 定义g_fac_ms变量, 表示ms延时的倍乘数, 代表每个节拍的ms数, (仅在使能os的时候,需要用到) */
//static uint16_t g_fac_ms = 0;
///*
// * 当delay_us/delay_ms需要支持OS的时候需要三个与OS相关的宏定义和函数来支持
// * 首先是3个宏定义:
// * delay_osrunning :用于表示OS当前是否正在运行,以决定是否可以使用相关函数
// * delay_ostickspersec:用于表示OS设定的时钟节拍,delay_init将根据这个参数来初始化systick
// * delay_osintnesting :用于表示OS中断嵌套级别,因为中断里面不可以调度,delay_ms使用该参数来决定如何运行
// * 然后是3个函数:
// * delay_osschedlock :用于锁定OS任务调度,禁止调度
// * delay_osschedunlock:用于解锁OS任务调度,重新开启调度
// * delay_ostimedly :用于OS延时,可以引起任务调度.
// *
// * 本例程仅作UCOSII和UCOSIII的支持,其他OS,请自行参考着移植
// */
///* 支持UCOSII */
//#ifdef OS_CRITICAL_METHOD /* OS_CRITICAL_METHOD定义了,说明要支持UCOSII */
//#define delay_osrunning OSRunning /* OS是否运行标记,0,不运行;1,在运行 */
//#define delay_ostickspersec OS_TICKS_PER_SEC /* OS时钟节拍,即每秒调度次数 */
//#define delay_osintnesting OSIntNesting /* 中断嵌套级别,即中断嵌套次数 */
//#endif
///* 支持UCOSIII */
//#ifdef CPU_CFG_CRITICAL_METHOD /* CPU_CFG_CRITICAL_METHOD定义了,说明要支持UCOSIII */
//#define delay_osrunning OSRunning /* OS是否运行标记,0,不运行;1,在运行 */
//#define delay_ostickspersec OSCfg_TickRate_Hz /* OS时钟节拍,即每秒调度次数 */
//#define delay_osintnesting OSIntNestingCtr /* 中断嵌套级别,即中断嵌套次数 */
//#endif
///**
// * @brief us级延时时,关闭任务调度(防止打断us级延迟)
// * @param 无
// * @retval 无
// */
//static void delay_osschedlock(void)
//{
//#ifdef CPU_CFG_CRITICAL_METHOD /* 使用UCOSIII */
// OS_ERR err;
// OSSchedLock(&err); /* UCOSIII的方式,禁止调度,防止打断us延时 */
//#else /* 否则UCOSII */
// OSSchedLock(); /* UCOSII的方式,禁止调度,防止打断us延时 */
//#endif
//}
///**
// * @brief us级延时时,恢复任务调度
// * @param 无
// * @retval 无
// */
//static void delay_osschedunlock(void)
//{
//#ifdef CPU_CFG_CRITICAL_METHOD /* 使用UCOSIII */
// OS_ERR err;
// OSSchedUnlock(&err); /* UCOSIII的方式,恢复调度 */
//#else /* 否则UCOSII */
// OSSchedUnlock(); /* UCOSII的方式,恢复调度 */
//#endif
//}
///**
// * @brief us级延时时,恢复任务调度
// * @param ticks: 延时的节拍数
// * @retval 无
// */
//static void delay_ostimedly(uint32_t ticks)
//{
//#ifdef CPU_CFG_CRITICAL_METHOD
// OS_ERR err;
// OSTimeDly(ticks, OS_OPT_TIME_PERIODIC, &err); /* UCOSIII延时采用周期模式 */
//#else
// OSTimeDly(ticks); /* UCOSII延时 */
//#endif
//}
注释或直接修改下面SysTick_Handler滴答定时器中断服务函数代码:
/**
* @brief systick中断服务函数,使用OS时用到
* @param ticks: 延时的节拍数
* @retval 无
*/
//void SysTick_Handler(void)
//{
// if (delay_osrunning == 1) /* OS开始跑了,才执行正常的调度处理 */
// {
// OSIntEnter(); /* 进入中断 */
// OSTimeTick(); /* 调用ucos的时钟服务程序 */
// OSIntExit(); /* 触发任务切换软中断 */
// }
// HAL_IncTick();
//}
void SysTick_Handler(void)
{
/* OS 开始跑了,才执行正常的调度处理 */
if (OSRunning == OS_STATE_OS_RUNNING)
{
/* 调用 uC/OS-III 的 SysTick 中断服务函数 */
OS_CPU_SysTickHandler();
}
HAL_IncTick();
}
注释或直接修改下面delay_init函数代码:
/**
* @brief 初始化延迟函数
* @param sysclk: 系统时钟频率, 即CPU频率(HCLK)
* @retval 无
*/
//void delay_init(uint16_t sysclk)
//{
//#if SYS_SUPPORT_OS /* 如果需要支持OS. */
// uint32_t reload;
//#endif
// SysTick->CTRL = 0; /* 清Systick状态,以便下一步重设,如果这里开了中断会关闭其中断 */
// HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK_DIV8); /* SYSTICK使用内核时钟源8分频,因systick的计数器最大值只有2^24 */
// g_fac_us = sysclk / 8; /* 不论是否使用OS,g_fac_us都需要使用,作为1us的基础时基 */
//#if SYS_SUPPORT_OS /* 如果需要支持OS. */
// reload = sysclk / 8; /* 每秒钟的计数次数 单位为M */
// reload *= 1000000 / delay_ostickspersec; /* 根据delay_ostickspersec设定溢出时间
// * reload为24位寄存器,最大值:16777216,在9M下,约合1.86s左右
// */
// g_fac_ms = 1000 / delay_ostickspersec; /* 代表OS可以延时的最少单位 */
// SysTick->CTRL |= 1 << 1; /* 开启SYSTICK中断 */
// SysTick->LOAD = reload; /* 每1/delay_ostickspersec秒中断一次 */
// SysTick->CTRL |= 1 << 0; /* 开启SYSTICK */
//#endif
//}
void delay_init(uint16_t sysclk)
{
#if SYS_SUPPORT_OS
uint32_t reload;
#endif
SysTick->CTRL = 0;
HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);
g_fac_us = sysclk;
#if SYS_SUPPORT_OS
reload = sysclk;
reload *= 1000000 / OSCfg_TickRate_Hz;
SysTick->CTRL |= 1 << 1;
SysTick->LOAD = reload;
SysTick->CTRL |= 1 << 0;
#endif
}
注释或直接修改下面delay_us函数代码:
/**
* @brief 延时nus
* @param nus: 要延时的us数.
* @note nus取值范围: 0 ~ 477218588(最大值即2^32 / g_fac_us @g_fac_us = 9)
* @retval 无
*/
//void delay_us(uint32_t nus)
//{
// uint32_t ticks;
// uint32_t told, tnow, tcnt = 0;
// uint32_t reload;
// reload = SysTick->LOAD; /* LOAD的值 */
// ticks = nus * g_fac_us; /* 需要的节拍数 */
// delay_osschedlock(); /* 阻止OS调度,防止打断us延时 */
// told = SysTick->VAL; /* 刚进入时的计数器值 */
// while (1)
// {
// tnow = SysTick->VAL;
// if (tnow != told)
// {
// if (tnow < told)
// {
// tcnt += told - tnow; /* 这里注意一下SYSTICK是一个递减的计数器就可以了. */
// }
// else
// {
// tcnt += reload - tnow + told;
// }
// told = tnow;
// if (tcnt >= ticks) break; /* 时间超过/等于要延迟的时间,则退出. */
// }
// }
// delay_osschedunlock(); /* 恢复OS调度 */
//}
void delay_us(uint32_t nus)
{
uint32_t ticks;
uint32_t told, tnow, tcnt = 0;
uint32_t reload;
/* 定义用于接收错误代码的变量 */
OS_ERR err;
reload = SysTick->LOAD;
ticks = nus * g_fac_us;
/* 锁定 uC/OS-III 的任务调度器 */
OSSchedLock(&err);
told = SysTick->VAL;
while (1)
{
tnow = SysTick->VAL;
if (tnow != told)
{
if (tnow < told)
{
tcnt += told - tnow;
}
else
{
tcnt += reload - tnow + told;
}
told = tnow;
if (tcnt >= ticks) break;
}
}
/* 恢复 uC/OS-III 的任务调度器 */
OSSchedUnlock(&err);
}
注释或直接修改下面delay_ms函数代码:
/**
* @brief 延时nms
* @param nms: 要延时的ms数 (0< nms <= 65535)
* @retval 无
*/
//void delay_ms(uint16_t nms)
//{
// if (delay_osrunning && delay_osintnesting == 0) /* 如果OS已经在跑了,并且不是在中断里面(中断里面不能任务调度) */
// {
// if (nms >= g_fac_ms) /* 延时的时间大于OS的最少时间周期 */
// {
// delay_ostimedly(nms / g_fac_ms); /* OS延时 */
// }
// nms %= g_fac_ms; /* OS已经无法提供这么小的延时了,采用普通方式延时 */
// }
// delay_us((uint32_t)(nms * 1000)); /* 普通方式延时 */
//}
void delay_ms(uint16_t nms)
{
uint32_t i;
for (i=0; i<nms; i++)
{
delay_us(1000);
}
}
- 在
stm32f1xx_it.c和stm32f1xx_it.h中注释下面代码,避免中断服务函数定义重复异常
///**
// * @brief This function handles PendSVC exception.
// * @param None
// * @retval None
// */
//void PendSV_Handler(void)
//{
//}
///**
// * @brief This function handles SysTick Handler.
// * @param None
// * @retval None
// */
//void SysTick_Handler(void)
//{
// HAL_IncTick();
//}
//void PendSV_Handler(void);
//void SysTick_Handler(void);
- 在启动文件
startup_stm32f103xe.s中用UCOSIII 的PendSV 中断服务函数名覆盖原PendSV 中断服务函数名(一共3处)
DCD OS_CPU_PendSVHandler ; PendSV Handler
...
OS_CPU_PendSVHandler PROC
EXPORT OS_CPU_PendSVHandler [WEAK]
- 在头文件
cpu_cfg_h中修改宏定义
#if 1 // 此处0改为1
#define CPU_CFG_NVIC_PRIO_BITS 4u
#endif
- 添加4个配置文件
- 在已经移植好的工程文件中找到这4个头文件,并覆盖到基础工程中,他们分别是:
- cpu_cfg.h:uC-OS3\uC-CPU-1.32.01\Cfg\Template
- lib_cfg.h:uC-OS3\uC-LIB-1.39.01\Cfg\Template
- os_cfg.h:uC-OS3\uC-OS3-3.08.01\Cfg\Template
- os_cfg_app.h:uC-OS3\uC-OS3-3.08.01\Cfg\Template(与os_cfg.h 同一路径)
工程路径:正点原子精英V2资料\精英STM32F103开发板 V2-资料盘(A盘)\4,程序源码\3,扩展例程\3,扩展例程\1,uC-OS3例程\1,uC-OS3例程\UCOS-III实验例程2 UCOS-III移植实验
- 最后工作:
收尾
-
修改工程名称:

-
移除USMART 组件(如果没有用到)

在main函数中,删除头文件包含语句:#include "./USMART/usmart.h",删除初始化函数语句usmart_dev.init(72); /* 初始化USMART */, -
添加uCOSIII 任务,验证是否成功移植
-
在已经移植好的工程文件中找到这源码与对应头文件,粘贴到基础工程中:
- 工程路径 - User 文件夹中的
uc-os3_demo.c、uc-os3_demo.h;
- 工程路径 - User 文件夹中的
-
在基础工程中添加源代码与包含头文件路径;

-
删除
main函数中的while(1)循环,并在main函数最后,调用uCOSIII 的入口函数uc_os3_demo();,并包含其头文件#include "uc-os3_demo.h"; -
烧录到开发板中,查看是否正常运行;
原移植后工程路径:正点原子精英V2资料\精英STM32F103开发板 V2-资料盘(A盘)\4,程序源码\3,扩展例程\3,扩展例程\1,uC-OS3例程\1,uC-OS3例程\UCOS-III实验例程2 UCOS-III移植实验
ZLBF:6xT2bE7sH5kP9dF1jR3vM8zC
3. 系统配置文件说明
简单了解即可,主要配置文件有以下4个:
《UCOS-III开发指南_V1.5.pdf》 - 第三章 µC/OS-III 配置项,有关于上述4个头文件的宏定义的详细讲解;
如:头文件
os_cfg.h中,宏定义OS_CFG_ARG_CHK_EN的意思如下:
3.1 os.cfg.h 重点宏定义
主要用于配置 µC/OS-III 内核的一些功能,这里重点讲解几个重要的宏定义:
OS_CFG_PRIO_MAX:定义任务优先级的最大数值。系统中最高任务优先级的数值为 0,最低任务优先级的数值为OS_CFG_PRIO_MAX-1,默认值为32;OS_CFG_SCHED_ROUND_ROBIN_EN:配置使能或禁用时间片调度功能。当此宏配置为 1 时,则使能时间片调度功能;OS_CFG_FLAG_EN、OS_CFG_FLAG_DEL_EN、OS_CFG_FLAG_MODE_CLR_EN、OS_CFG_FLAG_PEND_ABORT_EN:事件标志是能或禁用;
3.2 os.cfg_app.h 重点宏定义
主要用于配置于应用程序相关的 µC/OS-III 内核配置,这里重点讲解几个重要的宏定义:
OS_CFG_ISR_STK_SIZE:定义用于异常的 Main Stack 栈的大小,默认单位为字(由变量类型 CPU_STK 决定),如配置为128u即Main Stack 栈的大小为128字;OS_CFG_IDLE_TASK_STK_SIZE:定义空闲任务任务栈的大小,默认单位为字(由变量类型 CPU_STK 决定);OS_CFG_TICK_RATE_HZ:定义系统时钟节拍的频率,单位:赫兹,如配置为1000u即滴答定时器的中断频率为1000us=1ms;
3.3 cpu_cfg.h 重点宏定义
主要用于配置 µC/CPU 组件,配置一些于 CPU 硬件相关的配置项,这里重点讲解几个重要的宏定义:
CPU_CFG_TS_32_EN、CPU_CFG_TS_64_EN:32/64位硬件定时器作为时间戳功能的时基定时器,STM32硬件定时器为32位;CPU_CFG_LEAD_ZEROS_ASM_PRESENT:用于指示 CPU 具有硬件计算前导零的指令,如果硬件支持,则必须打开,否则失能该宏定义,则使用软件实现;
3.4 lib_cfg.h 重点宏定义
主要用于配置 µC/LIB 组件,这里重点讲解几个重要的宏定义:
LIB_MEM_CFG_HEAP_SIZE:定义用于内存库管理的内存堆的大小,单位:字节;对于内存较小的cpu,简易直接定义成0;
4. 任务创建与删除
在调用任何UCOS-III 函数之前,必须先初始化UCOS-III,即调用函数OSInit();
在任务被创建之后,需开启任务调度器,任务才能被任务调度器调度运行,即调用OSStart();
- 任务创建函数
OSTaskCreat():任务的任务控制块由用户手动分配,当任务被创建成功后,任务就处于就绪态; - 任务删除函数
OSTaskDel():任务被删除后,其内存不会被释放,只代表该任务的代码和任务栈不再由UCOS-III 内核管理,即不再能被UCOS-III 所调用;
4.1 任务创建函数OSTaskCreat()
函数原型输入形参:
void OSTaskCreate (OS_TCB *p_tcb, // 指向任务控制块的指针,一个结构体
CPU_CHAR *p_name, // 指向作为任务名的 ASCII 字符串的指针
OS_TASK_PTR p_task, // 指向任务函数的指针
void *p_arg, // 指向任务函数参数的指针,一般写0
OS_PRIO prio, // 任务的任务优先级,数字越小,优先级越高
CPU_STK *p_stk_base, // 指向任务栈起始地址的指针
CPU_STK_SIZE stk_limit, // 任务栈的使用警戒线,一般为任务栈大小的10%
CPU_STK_SIZE stk_size, // 任务的任务栈大小
OS_MSG_QTY q_size, // 任务内嵌消息队列的大小,一般写0
OS_TICK time_quanta, // 任务的时间片,不使用时写0
void *p_ext, // 指向用户扩展内存的指针,任务栈的扩展,很少用
OS_OPT opt, // 任务选项,共5个
OS_ERR *p_err) // 指向接收错误代码变量的指针
- 任务控制块的结构体成员介绍:任务控制块的结构体成员有很多,这里只挑几个常用的讲解:
struct os_tcb {
CPU_STK *StkPtr; // 任务栈栈顶,必须为TCB 的第一个成员,在任务切换与任务的上下文保存、恢复相关
CPU_STK *StkLimitPtr; // 指向任务栈使用警戒线的指针
OS_TCB *NextPtr; // 指向任务链表中下一个任务控制块的指针
OS_TCB *PrevPtr; // 指向任务链表中上一个任务控制块的指针
OS_TCB *TickNextPtr; // 指向 Tick 任务链表中下一个任务控制块的指针
OS_TCB *TickPrevPtr; // 指向 Tick 任务链表中上一个任务控制块的指针
OS_PRIO Prio; // 任务优先级,数字越小,优先级越高
OS_TICK TickRemain; // 任务延时的剩余时钟节拍数
OS_TICK TimeQuanta; // 任务时间片
OS_TICK TimeQuantaCtr; // 任务剩余时间片
...
- 任务选项:
OS_OPT_TASK_NONE // 没有选项,一般不要
OS_OPT_TASK_STK_CHK // 是否允许对任务进行堆栈检查,检查堆栈是否安全/正常,一般需要
OS_OPT_TASK_STK_CLR // 是否需要清除任务堆栈,一般需要
OS_OPT_TASK_SAVE_FP // 是否保存浮点寄存器,一般不要
OS_OPT_TASK_NO_TLS // 不需要对正在创建的任务提供TLS(线程本地存储)支持,一般不要
- 函数错误代码:
OS_ERR_NONE // 任务创建成功
OS_ERR_ILLEGAL_CREATE_RUN_TIME // 定义了 OS_SAFETY_CRITICAL_IEC61508,且在 OSStart()之后非法地创建内核对象
OS_ERR_PRIO_INVALID // 非法的任务优先级数值
OS_ERR_STAT_STK_SIZE_INVALID // 任务栈在初始化期间溢出
OS_ERR_STK_INVALID // 指向任务栈起始地址的指针为空
OS_ERR_STK_SIZE_INVALID // 任务栈小于配置项 OS_CFG_STK_SIZE_MIN
OS_ERR_STK_LIMIT_INVALID // 任务栈“水位”限制大小大于或等于任务栈大小
OS_ERR_TASK_CREATE_ISR // 在中断中非法地创建任务,即任务创建函数不能用在中断服务函数中
OS_ERR_TASK_INVALID // 指向任务函数的指针为空
OS_ERR_TCB_INVALID // 指向任务控制块的指针为空
4.2 任务删除函数OSTaskDel()
函数原型输入形参:
void OSTaskDel (OS_TCB *p_tcb, // 指向任务控制块的指针
OS_ERR *p_err) // 接受错误代码变量的指针
如果p_tcb 传入的参数为NULL,代表删除当前任务本身
- 函数错误代码:
OS_ERR_NONE // 任务删除成功
OS_ERR_ILLEGAL_DEL_RUN_TIME // 定义了 OS_SAFETY_CRITICAL_IEC61508,且在 OSStart()之后非法地删除内核对象
OS_ERR_OS_NOT_RUNING µC/OS-III // 内核还未运行
OS_ERR_STATE_INVALID // 任务处于无效状态
OS_ERR_TASK_DEL_IDLE // 非法删除空闲任务
OS_ERR_TASK_DEL_INVALID // 非法删除 µC/OS-III 的中断服务任务
OS_ERR_TASK_DEL_ISR // 在中断中非法地删除任务,即任务删除函数不能用在中断服务函数中
4.3 任务创建与删除实验
任务创建流程如下:
- 定义函数入口参数(任务堆栈、任务优先级…);
- 调用 创建任务API 函数;
- 实现任务函数功能;
上述3步后,任务即被创建,之后,任务立即进入就绪态,由任务调度器调度运行;
- 实验目的:完成以下4个任务:
- start_task:初始化cup 库,配置Systick 中断及优先级,创建其他3个任务;
- task1:实现LED0每500ms 闪烁一次;
- task2:实现LED1每500ms 闪烁一次;
- task3:判断按键KEY0是否按下,按下则删除task1;
- 实验过程:
- 在移植好UCOS-III 的工程中,创建一个
uc-os3_demo.c的源文件和uc-os3_demo.h的头文件,其初始内容分别如下:
// 源文件
#include "uc-os3_demo.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./MALLOC/malloc.h"
#include "./BSP/KEY/key.h"
/*uC/OS-III*********************************************************************************************/
#include "os.h"
#include "cpu.h"
/******************************************************************************************************/
/*uC/OS-III配置*/
/* START_TASK 任务 配置
* 包括: 任务优先级 任务栈大小 任务控制块 任务栈 任务函数
*/
/**
* @brief uC/OS-III例程入口函数
* @param 无
* @retval 无
*/
void uc_os3_demo(void)
{
OS_ERR err;
/* 初始化uC/OS-III ,在调用任何UCOS-III 函数之前,必须先调用它*/
OSInit(&err);
/* 开始任务调度 */
OSStart(&err);
}
// 头文件
#ifndef __UC_OS3_DEMO_H
#define __UC_OS3_DEMO_H
void uc_os3_demo(void);
#endif
- 在UCOS-III 初始化函数
OSInit(&err);后面创建一个任务:
OSTaskCreate ( (OS_TCB*) p_tcb,
(CPU_CHAR*) p_name,
(OS_TASK_PTR) p_task,
(void*) p_arg,
(OS_PRIO) prio,
(CPU_STK*) p_stk_base,
(CPU_STK_SIZE) stk_limit,
(CPU_STK_SIZE) stk_size,
(OS_MSG_QTY) q_size,
(OS_TICK) time_quanta,
(void*) p_ext,
(OS_OPT) opt,
(OS_ERR*) p_err);
- 定义任务控制块结构体:
OS_TCB start_task_tcb
- 定义并编写start_task 任务函数:
void start_task(void *p_arg)
{
OS_ERR err;
OSTaskDel((OS_TCB*)0,&err); // 由于任务start_task 只执行一次,故在其最后添加自我删除的函数;
}
- 定义任务优先级:
#define START_TASK_PRIO 5 - 定义任务堆栈:
#define START_TASK_STACK_SIZE 256
CPU_STK start_task_stack[START_TASK_STACK_SIZE];
- 同样的方式,定义并编写task1·、task2、task3任务函数:
最后源文件代码如下:
#include "uc-os3_demo.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./MALLOC/malloc.h"
#include "./BSP/KEY/key.h"
/*uC/OS-III*********************************************************************************************/
#include "os.h"
#include "cpu.h"
/******************************************************************************************************/
/*uC/OS-III配置*/
/* START_TASK 任务 配置
* 包括: 任务优先级 任务栈大小 任务控制块 任务栈 任务函数
*/
#define START_TASK_PRIO 5
#define START_TASK_STACK_SIZE 256
CPU_STK start_task_stack[START_TASK_STACK_SIZE];
OS_TCB start_task_tcb;
void start_task(void *p_arg);
/* TASK1 任务 配置
* 包括: 任务优先级 任务栈大小 任务控制块 任务栈 任务函数
*/
#define TASK1_PRIO 4
#define TASK1_STACK_SIZE 256
CPU_STK* task1_stack; // 申请一个内存给task1 的任务堆栈
OS_TCB task1_tcb;
void task1(void *p_arg);
/* TASK2 任务 配置
* 包括: 任务优先级 任务栈大小 任务控制块 任务栈 任务函数
*/
#define TASK2_PRIO 3
#define TASK2_STACK_SIZE 256
CPU_STK* task2_stack; // 申请一个内存给task1 的任务堆栈
OS_TCB task2_tcb;
void task2(void *p_arg);
/* TASK3 任务 配置
* 包括: 任务优先级 任务栈大小 任务控制块 任务栈 任务函数
*/
#define TASK3_PRIO 2
#define TASK3_STACK_SIZE 256
CPU_STK* task3_stack; // 申请一个内存给task1 的任务堆栈
OS_TCB task3_tcb;
void task3(void *p_arg);
/**
* @brief uC/OS-III例程入口函数
* @param 无
* @retval 无
*/
void uc_os3_demo(void)
{
OS_ERR err;
/* 关闭所有中断,防止中断打断UCOS-III 的初始化*/
CPU_IntDis();
/* 初始化uC/OS-III */
OSInit(&err);
/* 创建start_task 任务 */
OSTaskCreate ( (OS_TCB*) &start_task_tcb,
(CPU_CHAR*) "start_task",
(OS_TASK_PTR) start_task,
(void*) 0,
(OS_PRIO) START_TASK_PRIO,
(CPU_STK*) &start_task_stack[0],// 以数组方式提供任务堆栈
(CPU_STK_SIZE) START_TASK_STACK_SIZE/10,
(CPU_STK_SIZE) START_TASK_STACK_SIZE,
(OS_MSG_QTY) 0,
(OS_TICK) 0,
(void*) 0,
(OS_OPT) (OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR),
(OS_ERR*) &err);
/* 开始任务调度 */
OSStart(&err);
}
void start_task(void *p_arg)
{
OS_ERR err;
CPU_INT32U cnts = 0;
CPU_Init(); // 初始化cpu 库
cnts = HAL_RCC_GetSysClockFreq()/OS_CFG_TICK_RATE_HZ;// 这里用的STM32 的主频为168MHz,使用HAL_RCC_GetSysClockFreq() 来获取芯片主频,
OS_CPU_SysTickInit(cnts);// 初始化滴答定时器
/* 创建task1 */
task1_stack = (CPU_STK *)mymalloc(SRAMIN,TASK1_STACK_SIZE*sizeof(CPU_STK)); // 向系统申请内存
OSTaskCreate ((OS_TCB*) &task1_tcb,
(CPU_CHAR*) "task1",
(OS_TASK_PTR) task1,
(void*) 0,
(OS_PRIO) TASK1_PRIO,
(CPU_STK*) task1_stack,// 以申请内存方式提供任务堆栈
(CPU_STK_SIZE) TASK1_STACK_SIZE/10,
(CPU_STK_SIZE) TASK1_STACK_SIZE,
(OS_MSG_QTY) 0,
(OS_TICK) 0,
(void*) 0,
(OS_OPT) (OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR),
(OS_ERR*) &err);
/* 创建task2 */
task2_stack = (CPU_STK *)mymalloc(SRAMIN,TASK2_STACK_SIZE*sizeof(CPU_STK)); // 向系统申请内存
OSTaskCreate ((OS_TCB*) &task2_tcb,
(CPU_CHAR*) "task2",
(OS_TASK_PTR) task2,
(void*) 0,
(OS_PRIO) TASK2_PRIO,
(CPU_STK*) task2_stack,// 以申请内存方式提供任务堆栈
(CPU_STK_SIZE) TASK2_STACK_SIZE/10,
(CPU_STK_SIZE) TASK2_STACK_SIZE,
(OS_MSG_QTY) 0,
(OS_TICK) 0,
(void*) 0,
(OS_OPT) (OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR),
(OS_ERR*) &err);
/* 创建task3 */
task3_stack = (CPU_STK *)mymalloc(SRAMIN,TASK3_STACK_SIZE*sizeof(CPU_STK)); // 向系统申请内存
OSTaskCreate ((OS_TCB*) &task3_tcb,
(CPU_CHAR*) "task3",
(OS_TASK_PTR) task3,
(void*) 0,
(OS_PRIO) TASK3_PRIO,
(CPU_STK*) task3_stack,// 以申请内存方式提供任务堆栈
(CPU_STK_SIZE) TASK3_STACK_SIZE/10,
(CPU_STK_SIZE) TASK3_STACK_SIZE,
(OS_MSG_QTY) 0,
(OS_TICK) 0,
(void*) 0,
(OS_OPT) (OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR),
(OS_ERR*) &err);
OSTaskDel((OS_TCB*)0,&err);// 由于任务start_task 只执行一次,故在其最后添加自我删除的函数;
}
/* 实现LED0每500ms闪烁一次 */
void task1(void *p_arg)
{
OS_ERR err;
while(1)
{
printf("task1正在运行\r\n");
LED0_TOGGLE();
OSTimeDly(500,OS_OPT_TIME_DLY,&err); // UCOS-III 延时
}
}
/* 实现LED1每500ms闪烁一次 */
void task2(void *p_arg)
{
OS_ERR err;
while(1)
{
printf("task2正在运行\r\n");
LED1_TOGGLE();
OSTimeDly(500,OS_OPT_TIME_DLY,&err); // UCOS-III 延时
}
}
/* 判断按键KEY0 是否按下,按下则删除task1 */
void task3(void *p_arg)
{
OS_ERR err;
uint8_t key = 0;
while(1)
{
printf("task3正在运行\r\n");
key = key_scan(0);
if(key == KEY0_PRES)
{
printf("删除task1\r\n");
OSTaskDel(&task1_tcb,&err);
}
OSTimeDly(500,OS_OPT_TIME_DLY,&err); // UCOS-III 延时
}
}
代码运行结果如下:
代码运行逻辑如下:
- start_task 被创建并开始执行,task1在start_task 内被创建,由于优先级比start_task 高,故抢占运行;
- 在task1 进入延时时,返回start_task,此时task2在start_task 内被创建,由于优先级比start_task 和task1高,故抢占运行;
- 在task2 进入延时时,返回start_task,此时task3在start_task 内被创建,由于优先级比start_task 、task1和task2都高,故抢占运行;
- 此后,start_task 被删除,task1、task2、task3 循环运行

若在start_task 任务创建过程中,关闭任务调度器功能,即不允许task1、task2、task3开始执行,可使用临界区的方法实现(详见第7.1节):
CPU_CRITICAL_ENTER(); // 进入临界区
/* 创建task1 */
...
/* 创建task2 */
...
/* 创建task3 */
...
CPU_CRITICAL_EXIT(); // 退出临界区()
代码运行结果如下:
此时task1、task2、task3循环运行;
5. 任务挂起与恢复
任务的挂起和恢复都不允许在中断中调用;
同一任务被挂起多少次,想要该任务恢复,就要重新恢复多少次;
不能挂起空闲任务;
- 任务挂起函数
OSTaskSuspend():任务挂起类似于暂停,可恢复;任务删除则无法恢复,只能重新创建;该函数无条件地挂起任务,被挂起的任务不再参与任务调度,即将任务从就绪列表中移除; - 任务恢复函数
OSTaskResume():恢复被挂起的任务;
5.1 任务挂起函数OSTaskSuspend()
函数原型输入形参:
void OSTaskSuspend (OS_TCB *p_tcb,
OS_ERR *p_err)
- p_tcb:指向任务控制块的指针,该参数传入
0则代表挂起当前任务; - p_err:指向接收错误代码变量的指针;
5.2 任务恢复函数OSTaskResume()
函数原型输入形参:
void OSTaskResume (OS_TCB *p_tcb,
OS_ERR *p_err)
- p_tcb:指向任务控制块的指针;
- p_err:指向接收错误代码变量的指针;
5.3 任务挂起与恢复实验
- 实验目的:完成以下4个任务:
- start_task:初始化CPU,配置Systick 中断及优先级,创建其他3个任务
- task1:实现LED0每500ms 闪烁一次;
- task2:实现LED1每500ms 闪烁一次;
- task3:判断按键KEY0是否按下,按下则挂起task1,判断按键KEY1是否按下,按下则恢复task1;
- 实验过程:
- 在“4.3 任务创建与删除实验”工程中基础上,修改task3 任务函数即可:
/* 判断按键KEY0是否按下,按下则挂起task1,判断按键KEY1是否按下,按下则恢复task1 */
void task3(void *p_arg)
{
OS_ERR err;
uint8_t key = 0;
while(1)
{
// printf("task3正在运行\r\n");
key = key_scan(0);
if(key == KEY0_PRES)
{
printf("挂起task1\r\n");
OSTaskSuspend(&task1_tcb,&err);
}
else if(key == KEY1_PRES)
{
printf("恢复task1\r\n");
OSTaskResume(&task1_tcb,&err);
}
OSTimeDly(500,OS_OPT_TIME_DLY,&err); // UCOS-III 延时
}
}
代码运行结果如下:

6. 中断管理
中断:让CPU 打断正常运行的程序,转而去处理紧急事件;
中断执行机制:
- 中断请求:外设产生中断请求(GPIO 外部中断、定时器中断等);
- 响应中断:CPU 停止执行当前程序,转而去执行中断服务函数程序(ISR);
- 退出中断:中断执行完毕,返回被打断的程序处,继续往下执行;
中断优先级分组设置:ARM Cortex-M 使用8位宽的寄存器来配置中断的优先级等级;但STM32只用了中断优先级配置寄存器的高4位[7:4],所以STM32提供了最大16级(2的4次方)的中断优先级;
抢占优先级与子优先级:
- 抢占优先级:抢占优先级高的中断可打断正在执行但抢占优先级低的中断;
- 子优先级:当同时发生具有抢占优先级相同的两个中断时,子优先级数值小的优先执行;
中断优先级分组:在UCOS-III 中一般使用“分组4”;
在HAL 库中,执行函数void HAL_NVIC_SetPriorityGrouping(uint32_t PriorityGroup)进行中断优先级分组,该函数集成在HAL_Init()中;
中断优先级分组设置特点:
- UCOS-III 中断管理范围,通过宏
CPU_CFG_KA_IPL_BOUNDARY设置,一般默认设置为4,即管理中断优先级范围为4 ~ 15; - 中断优先级分组设置为分组4;
中断相关寄存器:SHPR1、SHPR2、SHPR3,下表截自《Cortex M3权威指南(中文)》第286页;
SHPR1寄存器起始地址:0xE000ED18;SHPR2寄存器起始地址:0xE000ED1C;SHPR3寄存器起始地址:0xE000ED20;
其中,PendSV 设置为最低优先级,SysTick 设置为UCOS-III 所管理的最高优先级;- 3个中断屏蔽寄存器:
PRIMASK、FAULTMASK、BASEPRI:
在UCOS-III 中,中断屏蔽主要用到BASEPRI(关闭优先级高于设定阈值的中断)和PRIMASK(关闭所有中断);
在进入、退出临界区,实际操作的就是BASEPRI寄存器;
如:
BASEPRI设置为0x40(低4位无效),即中断优先级在4 ~ 15内的中断都会被屏蔽,0 ~3 的中断优先级中断正常运行;
6.1 中断管理实验
- 实验目的:
- 使用两个定时器,一个优先级为3,一个优先级为6,两个定时器每1秒,打印一段字符串,当关中断时,停止打印,开中断时继续打印;
- 中断优先级分组设置为分组4;
- 系统管理的中断优先级范围为4 ~ 15;,故上述优先级为3的定时器中断,UCOS-III 并不能对它进行管理;
- 实验过程:
- 在“5.3 任务挂起与恢复实验”工程中基础上,删掉不要的task2、task3相关代码,只保留start_task、task1两个任务;
- 在
main()函数中找到HAL_Init()函数并进入,将中断优先级分组改为分组4:
/* Set Interrupt Group Priority */
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
- 在“实验8-1 基本定时器中断实验”中找到驱动TIMER 文件夹,并将对应源文件、头文件添加到本代码工程中;

- 编写定时器头文件代码:
#ifndef __BTIM_H
#define __BTIM_H
#include "./SYSTEM/sys/sys.h"
/******************************************************************************************/
/* 基本定时器 定义 */
/* TIMX 中断定义
* 默认是针对TIM6/TIM7
* 注意: 通过修改这4个宏定义,可以支持TIM1~TIM8任意一个定时器.
*/
/* TIM6 */
#define BTIM_TIMX_INT TIM6
#define BTIM_TIMX_INT_IRQn TIM6_DAC_IRQn
#define BTIM_TIMX_INT_IRQHandler TIM6_DAC_IRQHandler
#define BTIM_TIMX_INT_CLK_ENABLE() do{ __HAL_RCC_TIM6_CLK_ENABLE(); }while(0) /* TIM6 时钟使能 */
/* TIM7 */
#define BTIM_TIM7_INT TIM7
#define BTIM_TIM7_INT_IRQn TIM7_IRQn
#define BTIM_TIM7_INT_IRQHandler TIM7_IRQHandler
#define BTIM_TIM7_INT_CLK_ENABLE() do{ __HAL_RCC_TIM7_CLK_ENABLE(); }while(0) /* TIM7 时钟使能 */
/******************************************************************************************/
void btim_timx_int_init(uint16_t arr, uint16_t psc); /* 基本定时器6 定时中断初始化函数 */
void btim_tim7_int_init(uint16_t arr, uint16_t psc); /* 基本定时器7 定时中断初始化函数 */
#endif
- 定时器源文件中添加定时器句柄:
TIM_HandleTypeDef g_timx_handle; /* 定时器6句柄 */
TIM_HandleTypeDef g_tim7_handle; /* 定时器7句柄 */
- 设置定时器底层驱动,开启时钟,设置中断优先级:
{
if (htim->Instance == BTIM_TIMX_INT)
{
BTIM_TIMX_INT_CLK_ENABLE(); /* 使能TIM时钟 */
HAL_NVIC_SetPriority(BTIM_TIMX_INT_IRQn, 6, 0); /* 抢占6,无子优先级,组4 */
HAL_NVIC_EnableIRQ(BTIM_TIMX_INT_IRQn); /* 开启ITM3中断 */
}
if (htim->Instance == BTIM_TIM7_INT)
{
BTIM_TIM7_INT_CLK_ENABLE(); /* 使能TIM时钟 */
HAL_NVIC_SetPriority(BTIM_TIM7_INT_IRQn, 3, 0); /* 抢占3,无子优先级,组4 */
HAL_NVIC_EnableIRQ(BTIM_TIM7_INT_IRQn); /* 开启ITM3中断 */
}
}
- 编写中断服务函数与回调函数:
void BTIM_TIM7_INT_IRQHandler(void)
{
HAL_TIM_IRQHandler(&g_tim7_handle); /* 定时器中断公共处理函数 */
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == BTIM_TIMX_INT)
{
printf("TIM6正在运行,优先级为6\r\n");
}
if (htim->Instance == BTIM_TIM7_INT)
{
printf("TIM7正在运行,优先级为3\r\n");
}
}
- start_task:初始化cup 库,配置Systick 中断及优先级,创建task1任务;
- task1:中断测试任务,调用中断开/关函数来体现对中断的管理:
void task1(void *p_arg)
{
OS_ERR err;
uint8_t timer = 0;
CPU_SR_ALLOC(); // 调用临界区时,该函数必须先调用
while(1)
{
if(++timer == 5)
{
timer = 0;
printf("关中断!!\r\n");
CPU_CRITICAL_ENTER(); // 进入临界区,关中断
delay_ms(5000); // 系统演示,而非UCOS-III 延时
printf("开中断!!\r\n");
CPU_CRITICAL_EXIT(); // 退出临界区(),开中断
}
OSTimeDly(1000,OS_OPT_TIME_DLY,&err);
}
}
代码运行结果如下:关中断后,优先级为6的中断被关闭,而优先级为3的不受UCOS-III 影响;
ZLBF:B7gJ1dF5sP8rV2bN6zM0cH3kQ
7. 临界段代码保护及任务调度锁
7.1 临界段代码保护
临界段代码:又叫临界区代码,指那些必须完整运行,不能被打断的代码段;
临界段代码一般应用场合:
- 需严格按时序初始化的外设,如IIC、SPI 等;
- 系统本身的代码段;
- 用户设置的不想要被中断打断的代码块;
临界区的进入与退出调用以下代码,它们必须成对使用,不支持嵌套调用,使用时,应保持临界段耗时短:
CPU_SR_ALLOC(); // 调用临界区时,该函数必须先调用
CPU_CRITICAL_ENTER(); // 进入临界区,关中断
... /* 临界区代码 */
CPU_CRITICAL_EXIT(); // 退出临界区(),开中断
7.2 任务调度锁
任务调度锁用于对调度器上锁与解锁;当调度器上锁时,禁止任务调度,反之允许任务调度;
任务调度锁只是将调度器挂起,并不影响中断的执行,中断功能保持正常,但其他任务不能进行抢占运行;仅仅是防止任务之间的资源争夺;
任务调度上锁函数OSSchedLock():允许嵌套调用,成对使用,上锁多少次,就需要对应解锁多少次;
任务调度解锁函数OSSchedUnlock();
// 应用示例
OS_ERR err;
OSSchedLock(&err); /* 任务调度上锁 */
/* 代码块 */
OSSchedUnlock(&err); /* 任务调度解锁 */
8. 启动任务调度器
参考资料:《Cortex M3权威指南(中文)》、《Cortex M3与M4权威指南》、《UCOS-III开发指南》
UCOS-III 的初始化:通过函数OSInit() 实现,必须在调用任何其他UCOS-III 函数之前调用它;其内部流程如下:
- 对一些全局变量赋初始值;
- 初始化就绪列表、Tick 列表等;
- 创建3个系统任务:
- 空闲任务:任务优先级最低,为31,当系统无其他就绪任务时,系统运行空闲任务,空闲任务不能被阻塞;
- 统计任务:任务优先级为30,用于统计CPU 使用率和各个任务的堆栈使用量;如系统有一半时间运行空闲任务,一半时间运行其他任务,则CPU 使用率为50%;
- 软件定时器任务:任务优先级为29,用于在特定时间段内处理单次或周期性的软件定时器;
启动任务调度器:通过函数OSStart();实现,只有当应用任务最少被创建1个时,才能启动任务调度器;其内部流程如下:
- 获取当前最高优先级任务;
- 将调度器运行状态标志设置为开启状态;
- 获取最高优先级任务的任务控制块;
- 调用
OSStartHighRdy(),启动第一个任务,详见下面8.1 启动第一个任务;
前导置零指令:CPU_CntLeadZeros(OSPrioTbl[0]),系统通过该指令,获取当前就绪列表中有任务的最高优先级;
如下图,当前就绪列表中有任务的最高优先级为3,则指令返回值为3;
OSPrioTbl[]见1.4 任务列表 - 就序列表;
8.1 启动第一个任务
开启任务调度器后,系统会挑优先级最高的任务进行启动,本质是将优先级最高的任务A 的寄存器值恢复到CPU 寄存器;任务A 的寄存器值,在其被创建时,就保存在任务堆栈里;
关于启动第一个任务的相关寄存器功能介绍,详见《Cortex M3权威指南(中文)》第27 页;
双堆栈指针:在裸机程序中,都只采用单堆栈指针MSP,而在OS 中,大多都采用双堆栈指针MSP 和PSP,保证系统安全运行;在UCOS-III中,程序在运行过程中需要一定的栈空间来保存局部变量等信息,当有信息被保存到栈中时,MCU 会自动更新SP 指针:
- 主堆栈指针(
MSP):在中断中使用,由OS 内核、异常服务例程以及所有需要特权访问的应用程序代码来使用; - 进程堆栈指针(
PSP):在中断以外使用,用于常规应用程序代码(不处于异常服务例程中时)
8.2 任务切换
本质是CPU 寄存器的切换;
如任务A 切换到任务B,主要步骤为:(下面过程在
PendSV中断中发生):
- 暂停任务A 的执行,将此时任务A 的寄存器保存到任务堆栈,该过程叫保存现场;
- 将任务B 的被存于任务堆栈中的各个寄存器值恢复到CPU 寄存器中,该过程叫恢复现场;
关于任务切换详见《UCOS-III开发指南》第八章
PendSV 中断的触发:执行UCOS-III 提供的函数OSSched()(任务中使用)、OSIntExit()(中断中使用);
关于
PendSV中断触发的相关寄存器功能介绍,详见《Cortex M3权威指南(中文)》第131页;
8.3 时间片调度
时间片调度:同等优先级的任务轮流地享有相应的运行时间;在UCOS-III 中,一个时间片就等于SysTick 中断周期(一般SysTick 中断周期设置为1ms 一次);
8.3.1 开启时间片调度函数OSSchedRoundRobinCfg()
该函数用于开启时间片调度,设置时间片的默认值;
在使用该函数前,要先使能宏OS_CFG_SCHED_ROUND_ROBIN_EN;
函数原型输入形参:
void OSSchedRoundRobinCfg (CPU_BOOLEAN en,
OS_TICK dflt_time_quanta,
OS_ERR *p_err)
en:使能时间片调度,OS_TRUE使能,OS_FALSE失能;dflt_time_quanta:默认的时间片长度;若设置为0,则系统使用默认值OSCfg_TickRate_Hz / 10(由于STM32F1系列芯片OSCfg_TickRate_Hz为1000,故默认值为100个滴答定时器中断);p_err:指向接收错误代码变量的指针;
8.3.2 时间片调度实验
- 实验目的:
- 设计3个任务:start_task、task1、task2,其中task1、task2优先级都是2;
- start_task:开启时间片调度,设置时间片的默认值,创建其他2个任务;
- task1、task2设置不同的时间片长度并通过窗口打印信息;
- 实验过程:
- 使能宏
OS_CFG_SCHED_ROUND_ROBIN_EN,默认是使能的;该宏定义在os.cfg.h中; - 将task1、task2的优先级都改为2:
#define TASK1_PRIO 2
#define TASK2_PRIO 2
- 在滴答定时器初始化函数后面写入时间片调度函数:
OS_CPU_SysTickInit(cnts);// 初始化滴答定时器
OSSchedRoundRobinCfg (OS_TRUE,10,&err); // 使能时间片调度,并设置时间片默认长度为10个时间片
- 创建task1、task2:
/* 创建task1 */
task1_stack = (CPU_STK *)mymalloc(SRAMIN,TASK1_STACK_SIZE*sizeof(CPU_STK)); // 向系统申请内存
OSTaskCreate ((OS_TCB*) &task1_tcb,
(CPU_CHAR*) "task1",
(OS_TASK_PTR) task1,
(void*) 0,
(OS_PRIO) TASK1_PRIO,
(CPU_STK*) task1_stack,// 以申请内存方式提供任务堆栈
(CPU_STK_SIZE) TASK1_STACK_SIZE/10,
(CPU_STK_SIZE) TASK1_STACK_SIZE,
(OS_MSG_QTY) 0,
(OS_TICK) 0, // 若设置为0,则使用前面所设置的时间片长度
(void*) 0,
(OS_OPT) (OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR),
(OS_ERR*) &err);
/* 创建task2 */
task2_stack = (CPU_STK *)mymalloc(SRAMIN,TASK2_STACK_SIZE*sizeof(CPU_STK)); // 向系统申请内存
OSTaskCreate ((OS_TCB*) &task2_tcb,
(CPU_CHAR*) "task2",
(OS_TASK_PTR) task2,
(void*) 0,
(OS_PRIO) TASK2_PRIO,
(CPU_STK*) task2_stack,// 以申请内存方式提供任务堆栈
(CPU_STK_SIZE) TASK2_STACK_SIZE/10,
(CPU_STK_SIZE) TASK2_STACK_SIZE,
(OS_MSG_QTY) 0,
(OS_TICK) 5,
(void*) 0,
(OS_OPT) (OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR),
(OS_ERR*) &err);
- 编写task1、task2任务函数:
/* task1 */
void task1(void *p_arg)
{
uint32_t task1_num = 0;
CPU_SR_ALLOC(); // 调用临界区时,该函数必须先调用
while(1)
{
CPU_CRITICAL_ENTER(); // 进入临界区,关中断
printf("task1运行次数:%d\r\n",++task1_num);
delay_ms(1);
CPU_CRITICAL_EXIT(); // 退出临界区(),开中断
}
}
/* task2 */
void task2(void *p_arg)
{
uint32_t task2_num = 0;
CPU_SR_ALLOC(); // 调用临界区时,该函数必须先调用
while(1)
{
CPU_CRITICAL_ENTER(); // 进入临界区,关中断
printf("task2运行次数:%d\r\n",++task2_num);
delay_ms(1);
CPU_CRITICAL_EXIT(); // 退出临界区(),开中断
}
}
代码运行结果如下:task1
ZLBF:2mQ5sV9bF1jH7dG3kP6rT0zX
参考:
- 正点原子精英V2资料;
更多推荐







所有评论(0)