STM32理论 —— μCOS-Ⅲ

  • μCos-Ⅲ全称是Micro C OS Ⅲ,由Micriμm 公司发布的一个基于C 语言编写的第三代小型实时操作系统(RTOS);

  • RTOS 与裸机相比最大的优势在于多任务管理实时性,它提供了多任务管理和任务间通信的功能;

  • μCOS-Ⅲ与FreeRTOS的区别:μCOS-III 的源码可读性比较强,代码写的非常规范。国内资料较多;

  • RTOS 的任务调度结构:如下图,高优先级任务能抢占低优先级任务,而中断能打断任意任务,每个任务都有自己的任务堆栈,用于保存任务的寄存器值;除非高优先级任务挂起,否则在一直运行高优先级任务过程中,低优先级任务无法被运行;
    在这里插入图片描述

  • μCos-Ⅲ官方文档https://micrium.atlassian.net/wiki/spaceshttps://docs.silabs.com/micrium/latest/micrium-common-api/

1. 任务调度器


任务调度器就是决定当前执行哪个任务;

μCos-Ⅲ 支持2种任务调度方式

  1. 抢占式调度:针对优先级不同的任务,优先级高的任务可抢占优先级低的任务;
  2. 时间片调度:针对优先级相同的任务,当多个任务优先级相同且就绪时,调度器会根据用户设置的时间片轮流运行这些任务。时间片以一次系统时钟节拍为单位(滴答定时器的中断频率),µC/OS-III 默认设置的任务时间片为 100,则 µC/OS-III 会在当前任务运行 100 次系统时钟节拍的时间后,切换到另一个相同任务优先级的任务中运行。

1.1 抢占式调度

  1. 创建3个任务;
  2. 任务1、任务2、任务3的优先级分别设置为3、2、1(数字越小,优先级越高);
  3. 任务1先运行,过程中任务2就绪,在抢占式调度器作用下任务2抢占运行,任务1进入就绪态;
  4. 任务2运行中任务3就绪,在抢占式调度器作用下任务3抢占运行,任务2进入就绪态;
  5. 任务3运行中被挂起(系统延时或等待信号量等),此时处于就绪态中优先级最高的任务2运行;
  6. 任务3阻塞解除(延时时间到货接收到信号量),任务3恢复到就绪态,在抢占式调度器作用下任务3抢占运行,任务2进入就绪态;

在这里插入图片描述

1.2 时间片调度

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

在这里插入图片描述

一个时间片运行时间,取决于滴答定时器中断频率;

1.3 任务状态

  1. 运行态:即正在运行的任务,STM32是单核CPU,故同一时间只能运行一个任务;
  2. 就绪态:即任务能被执行但还没被执行;
  3. 挂起态:任务在运行中因延时或等待某一事件发生时被挂起;
  4. 休眠态:任务被创建在内存中,但未在任务调度器中(任务被删除);
  5. 中断态:任务在运行中被中断任务打断,CPU 跳转去执行中断服务函数,直到中断结束;

上诉5种任务状态并非可互相转换,转换关系如下图:
在这里插入图片描述

1.4 任务列表

  1. 就绪列表:存放就绪态任务的列表,OSRdyList[x],其中x 表示任务优先级值,一般范围值为0~ 31;任务调度器总是在就绪列表中,选择最高优先级的任务来执行;
  2. Tick 列表:存放正在等待延时超时正在等待挂起超时的任务的列表,OSTickList
  3. 挂起列表:存放等待信号量、事件的任务的列表,PendList

2. UCOSIII 移植过程


源代码包含: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 字符操作、数学、内存管理、字符串操作的库;
    在这里插入图片描述

0. 准备

参考:《UCOS-III 开发指南》 - 第二章 µC/OS-III 移植

  • uC-OSIII 源码:正点原子精英V2资料\6,软件资料\2,UCOS学习资料\UCOSIII资料\UCOS-III及其相关组件源代码
  • 基础工程:正点原子精英V2资料\4,程序源码\2,标准例程-HAL库版本\2,标准例程-HAL库版本\实验33 内存管理实验

  • 开始移植:

1. 在基础工程的 Middlewares 文件夹中新建一个 uC-OS3 子文件夹

2. 将uCOSIII 源代码的3个文件夹都拷贝到该文件夹中

在这里插入图片描述

3. 打开基础工程,新建4个分组

Middlewares/uC-OS3/BSP、Middlewares/uC-OS3/CPU、Middlewares/uC-OS3/LIB 和 Middlewares/uC-OS3/OS3;
在这里插入图片描述

4. 在分组Middlewares/uC-OS3/BSP 中添加以下源文件

在这里插入图片描述
路径分别为:
- bsp_cpu.c:uC-CPU-1.32.01\BSP\Template
- bsp_os_dt.c:uC-OS3-3.08.01\Template

5. 在分组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

6. 在分组Middlewares/uC-OS3/LIB 中添加以下源文件

在这里插入图片描述

路径都为:uC-LIB-1.39.01

7. 在分组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

8. 包含头文件路径

在这里插入图片描述

9. 修改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);
	 }
}

10. 在stm32f1xx_it.cstm32f1xx_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]

11. 在头文件cpu_cfg_h中修改宏定义

#if 1 // 此处0改为1
#define  CPU_CFG_NVIC_PRIO_BITS                            4u
#endif

12. 添加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移植实验

13. 最后工作

  1. 修改工程名称:
    在这里插入图片描述
  2. 移除USMART 组件(如果没有用到)
    在这里插入图片描述
    main 函数中,删除头文件包含语句:#include "./USMART/usmart.h",删除初始化函数语句usmart_dev.init(72); /* 初始化USMART */

14. 添加uCOSIII 任务,验证是否成功移植

  1. 在已经移植好的工程文件中找到这源码与对应头文件,粘贴到基础工程中:
    • 工程路径 - User 文件夹中的uc-os3_demo.cuc-os3_demo.h
  2. 在基础工程中添加源代码与包含头文件路径;
    在这里插入图片描述
  3. 删除main函数中的while(1)循环,并在main函数最后,调用uCOSIII 的入口函数uc_os3_demo();,并包含其头文件#include "uc-os3_demo.h"
  4. 烧录到开发板中,查看是否正常运行;

工程路径:正点原子精英V2资料\精英STM32F103开发板 V2-资料盘(A盘)\4,程序源码\3,扩展例程\3,扩展例程\1,uC-OS3例程\1,uC-OS3例程\UCOS-III实验例程2 UCOS-III移植实验

- 备份移植完成工程

移植完成工程备份路径:百度网盘 - 知识资源 - 电子工程 - 正点原子stm32f103精英开发板V2资料 - 资料备份
在这里插入图片描述

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 内核的一些功能,这里重点讲解几个重要的宏定义:

  1. OS_CFG_PRIO_MAX:定义任务优先级的最大数值。系统中最高任务优先级的数值为 0,最低任务优先级的数值为 OS_CFG_PRIO_MAX-1,默认值为32;
  2. OS_CFG_SCHED_ROUND_ROBIN_EN:配置使能或禁用时间片调度功能。当此宏配置为 1 时,则使能时间片调度功能;
  3. OS_CFG_FLAG_ENOS_CFG_FLAG_DEL_ENOS_CFG_FLAG_MODE_CLR_ENOS_CFG_FLAG_PEND_ABORT_EN:事件标志是能或禁用;

3.2 os.cfg_app.h 重点宏定义

主要用于配置于应用程序相关的 µC/OS-III 内核配置,这里重点讲解几个重要的宏定义:

  1. OS_CFG_ISR_STK_SIZE:定义用于异常的 Main Stack 栈的大小,默认单位为字(由变量类型 CPU_STK 决定),如配置为128u即Main Stack 栈的大小为128字;
  2. OS_CFG_IDLE_TASK_STK_SIZE:定义空闲任务任务栈的大小,默认单位为字(由变量类型 CPU_STK 决定);
  3. OS_CFG_TICK_RATE_HZ:定义系统时钟节拍的频率,单位:赫兹,如配置为1000u即滴答定时器的中断频率为1000us=1ms;

3.3 cpu_cfg.h 重点宏定义

主要用于配置 µC/CPU 组件,配置一些于 CPU 硬件相关的配置项,这里重点讲解几个重要的宏定义:

  1. CPU_CFG_TS_32_ENCPU_CFG_TS_64_EN:32/64位硬件定时器作为时间戳功能的时基定时器,STM32硬件定时器为32位;
  2. CPU_CFG_LEAD_ZEROS_ASM_PRESENT:用于指示 CPU 具有硬件计算前导零的指令,如果硬件支持,则必须打开,否则失能该宏定义,则使用软件实现;

3.4 lib_cfg.h 重点宏定义

主要用于配置 µC/LIB 组件,这里重点讲解几个重要的宏定义:

  1. LIB_MEM_CFG_HEAP_SIZE:定义用于内存库管理的内存堆的大小,单位:字节;对于内存较小的cpu,简易直接定义成0;

4. 任务创建与删除


  • 任务创建函数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) // 指向接收错误代码变量的指针
  1. 任务控制块的结构体成员介绍:任务控制块的结构体成员有很多,这里只挑几个常用的讲解:
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; // 任务剩余时间片
    ...
  1. 任务选项
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(线程本地存储)支持,一般不要
  1. 函数错误代码
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,代表删除当前任务本身

  1. 函数错误代码
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 任务创建流程

  • 在调用任何UCOS-III 函数之前,必须先初始化UCOS-III,即调用函数OSInit();
  • 在任务被创建之后,需开启任务调度器,任务才能被任务调度器调度运行,即调用OSStart();

任务创建流程如下:

  1. 定义函数入口参数(任务堆栈、任务优先级…);
  2. 调用创建任务API 函数;
  3. 实现任务函数功能;

上述3步后,任务即被创建,之后,任务立即进入就绪态,由任务调度器调度运行;


下面开始进行任务创建与删除实验:

  • 实验目的:创建以下4个任务:
  1. start_task:初始化cup 库,配置Systick 中断及优先级,创建其他3个任务;
  2. task1:实现LED0每500ms闪烁一次;
  3. task2:实现LED1每500ms闪烁一次;
  4. task3:判断按键KEY0 是否按下,按下则删除task1;
  • 实验过程:
  1. 在移植好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


  1. 在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);
  1. 定义任务控制块结构体:
OS_TCB start_task_tcb
  1. 定义并编写start_task 任务函数:

void start_task(void *p_arg)
{
	OS_ERR err;
	OSTaskDel((OS_TCB*)0,&err)// 由于任务start_task 只执行一次,故在其最后添加自我删除的函数;
}
  1. 定义任务优先级: #define START_TASK_PRIO 5
  2. 定义任务堆栈:
 #define START_TASK_STACK_SIZE 256
 CPU_STK start_task_stack[START_TASK_STACK_SIZE];
  1. 同样的方式,定义并编写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;
    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);
    
    /* 初始化uC/OS-III */
    OSInit(&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 = 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 = 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 = 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 延时
	}
}

代码运行结果如下:
在这里插入图片描述
代码运行逻辑如下:

  1. start_task 被创建并开始执行,task1在start_task 内被创建,由于优先级比start_task 高,故抢占运行;
  2. 在task1 进入延时时,返回start_task,此时task2在start_task 内被创建,由于优先级比start_task 和task1高,故抢占运行;
  3. 在task2 进入延时时,返回start_task,此时task3在start_task 内被创建,由于优先级比start_task 、task1和task2都高,故抢占运行;
  4. 此后,start_task 被删除,task1、task2、task3 循环运行

在这里插入图片描述

若想要在start_task 任务创建过程中,关闭任务调度器功能,即不允许task1、task2、task3开始执行,可使用临界区的方法实现:

	CPU_CRITICAL_ENTER(); // 进入临界区
	/* 创建task1 */
	...
								
	/* 创建task2 */
	...
	
	/* 创建task3 */
	...
								
	CPU_CRITICAL_EXIT(); // 退出临界区()	

代码运行结果如下:
在这里插入图片描述


参考:

  • 正点原子精英V2资料;
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Truffle7电子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值