【正点原子STM32连载】第六十三章 UCOSII实验1-任务调度摘自【正点原子】STM32F103 战舰开发指南V1.2

1)实验平台:正点原子stm32f103战舰开发板V4
2)平台购买地址:https://detail.tmall.com/item.htm?id=609294757420
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/thread-340252-1-1.html#

第六十三章 UCOSII实验1-任务调度

前面我们所有的例程都是跑裸机程序,简称裸跑,从本章开始,我们将分3个章节向大家介绍UCOSII(实时多任务操作系统内核)的使用。本章,我们将向大家介绍UCOSII最基本也是最重要的应用:任务调度。本章分为如下几个小节:
63.1嵌入式实时操作系统介绍
63.2 硬件设计
63.3 程序设计
63.4 下载验证

63.1 嵌入式实时操作系统介绍
63.1.1 裸机系统和多任务系统的区别
在嵌入式设备的开发过程中,我们使用的是两种程序,一是裸机程序,前面所有的实验章节使用的都是使用裸机程序;二是多任务程序,即接下来的三章都是多任务程序。很多人会有疑问用得好好的裸机程序,为什么要用多任务程序呢?
裸机程序最大的特点,就是主函数中会有一个大循环,大循环中就会有很多个小任务的实现。任务间是按照顺序进行执行的,换句话来说它们执行等级是一样的,下一个任务想要执行必须等上一个任务执行完成才能进行。这个运行着的大循环我们称之为后台程序。中断是可以打断系统当前的后台任务优先被执行,等待执行完后,再回到原来后台被打断处继续执行后台程序,中断处理程序称之前台程序。这种使用前后台裸机程序的叫做前后台系统,如图63.1.1.1所示:
在这里插入图片描述

图63.1.1.1 前后台系统
这样的前后台系统在实时性处理方便存在缺陷,例如Task1是重要任务,需要能够得到及时的响应,但是在执行Task3的时候,产生中断。现在的情况是执行Task1条件满足,理想的处理方式就是Task1需要立刻被执行,但是前后台程序中做不到,因为任务是被顺序执行的,即使Task1十万火急,也必须要等待Task3处理完毕才能被执行。
前面的情况对于要求实时性比较强的产品来说,是不允许的。所以出现了多任务程序,这种使用多任务程序的系统,叫做嵌入式实时操作系统。它把任务分为不同的优先级,当运行条件被满足时,高优先级任务可以打断低优先级任务优先运行,从而极大地提高了系统的实时性。嵌入式实时操作系统执行任务示意图如图63.1.1.2所示:
在这里插入图片描述

图63.1.1.2 嵌入式实时操作系统执行任务示意图
嵌入式实时操作系统相比前后台系统明显体现在实时性方面,同时它在多任务管理、任务间通信、内存管理、定时器管理、设备管理等方面也提供了一套完整的机制,极大程度上便利了嵌入式应用程序的开发、管理和维护。
63.1.2 UCOSII介绍
现在市面上有许多实时操作系统,国外的实时操作系统就有FreeRTOS,UCOS和RTX,国内的实时操作系统就有RT_Thread、LiteOS等。其中FreeRTOS使用率世界最高,UCOS发展历史最悠久。在这里我们主要是对UCOSII进行学习。
UCOSII,全称是Micro Control Operation System Two,是由Micrium公司提供,是一个可移植、可固化、可裁剪的、占先式多任务实时内核,它适用于多种微处理器,微控制器和数字处理芯片。早在1992年就由美国嵌入式专家Jean J.Labrosse在《嵌入式系统编程》杂志中提出,并公布源码。UCOSII只是一个实时操作系统内核,它仅仅包含了任务调度,任务管理,时间管理,内存管理和任务间的通信和同步等基本功能。没有提供输入输出管理,文件系统,网络等额外的服务。该实时系统十分适合初次接触嵌入式实时操作系统的朋友。
本章实验中我们使用的是UCOSII V2.93.01版本,它的体系结构如图63.1.2.1所示。
在这里插入图片描述

图63.1.2.1 UCOSII体系结构图
该版本比早期的UCOSII(如V2.52)多了很多功能,比如软件定时器,支持任务数最大达到255个等,而且修正了很多已知BUG。
在UCOSII的移植,我们只需要修改:cpu_cfg.h、lib_cfg.h和os_cfg.h、app_cfg.h四个文件即可,其中cpu_cfg.h该文件主要是配置CPU相关的一些宏定义,如时间戳、前导置零指令相关等内容;lib_cfg.h,该文件主要用于配置 µC/LIB 组件,如使能内存库中优化的内存相关操作函数 ;os_cfg.h该文件是系统的配置文件,用于配置系统默认的功能, 比如消息队列、信号量、事件标志位等;app_cfg.h该文件用于配置软件定时器的任务优先级等。
图中定时器的作用是为UCOSII提供系统时钟节拍,实现任务切换和任务延时等功能。这个时钟节拍由OS_TICKS_PER_SEC(在os_cfg.h中定义)设置,一般我们设置UCOSII的系统时钟节拍为1ms~100ms,具体根据你所用处理器和使用需要来设置。本章,我们利用STM32F1的SYSTICK定时器来提供UCOSII时钟节拍。
63.1.3 任务定义
在前面也说到有任务,在前面的多任务系统中,我们根据功能的不同,把整个系统分成一个个独立的循环函数,这些函数称为任务。而UCOSII就是一个能对这些任务的运行进行管理和调度的多任务操作系统。UCOSII最大支持的任务数达到了255个,但是对于我们来说一般64个任务已经足够。
任务类型有两种:一种是系统任务,另一种是用户任务。由系统提供的任务叫系统任务,由用户编写的任务叫用户任务。系统任务是为应用程序提供某种服务或为系统本身服务的。UCOSII具有2个系统任务,即空闲任务和统计任务,占用最低2个优先级。空闲任务是UCOSII优先级最低的任务,当所有其他任务均没有使用CPU时,空闲任务就会占用CPU。统计任务是UCOSII优先级倒数第二低的任务,用于统计CPU的使用率和各个任务的堆栈使用情况。
相对于系统任务而言,我们开发者用得多的就是用户任务。用户任务需要注意的是:用户任务对应的函数是一个带有无限循环体的函数,没有返回值;每一个用户任务具有唯一的优先级号。
实时操作系统为了更好的调度任务,给每一个任务都定义了一个任务控制块TCB(Task Control Block)。这个任务控制块就相当于任务在系统里的身份证,存放着任务的所有信息,比如任务函数指针,任务堆栈指针,任务优先级等。
由于CPU只有一个,所以一个时刻只会有一个任务占用CPU处于运行状态,而其他任务只能处于其他状态。UCOSII系统中的任务具有5种,系统运行起来的时候,每一个任务都处在以下5种状态之一的状态下,这5种状态分别是睡眠状态、就绪状态、运行状态、等待状态和中断服务状态。
睡眠状态,任务在没有被配备任务控制块或被剥夺了任务控制块时的状态。
就绪状态,系统为任务配备了任务控制块且在任务就绪表中进行了就绪登记,任务已经准备好了,但由于该任务的优先级比正在运行的任务的优先级低,还暂时不能运行,这时任务的状态叫做就绪状态。
运行状态,该任务获得CPU 使用权,并正在运行中,此时的任务状态叫做运行状态。
等待状态,正在运行的任务,需要等待一段时间或需要等待一个事件发生再运行时,该任务就会把CPU 的使用权让给别的任务而使任务进入等待状态。
中断服务状态,一个正在运行的任务一旦响应中断申请就会中止运行而去执行中断服务程序,这时任务的状态叫做中断服务状态。
UCOSII任务的5个状态转换关系如图63.1.3.1所示:
在这里插入图片描述

图63.1.3.1 UCOSII任务转换关系
63.1.4 任务调度
UCOSII的任务调度思想是:“近似每时每刻让优先级最高的就绪任务处于运行状态”。在具体做法上,它在系统或者用户任务调用系统函数及执行中断服务程序结束时来调用调度器,以确定应该运行的任务并运行它。
在多任务系统中,令CPU中止当前正在运行的任务转而去运行另一个任务的工作叫任务切换,而按照某种规则进行任务切换的工作叫做任务调度。
在UCOSII中,任务调度是由任务调度器来完成。任务调度器的主要工作就有两个:①在任务就绪表中查找具有最高优先级别的就绪任务 ②实现任务切换
63.2 硬件设计

  1. 例程功能
    创建了3个任务start_task、led0_task和led1_task。其中start_task是创建其他2个任务(led0和led1)的。当start_task创建完其他2个任务后就会挂起。led0_task和led1_task这两个任务分别是让LED0,LED1闪烁。LED0每秒钟亮80ms;LED1每秒钟亮300ms。
  2. 硬件资源
    1)LED灯
    LED0 – PB5
    LED1 – PE5
  3. 原理图
    本章用到的硬件用到LED灯:LED0和LED1。电路在开发板上已经连接好了,所以在硬件上不需要动任何东西,直接下载代码就可以测试使用。其连接原理图如图63.2.1所示:
    在这里插入图片描述

图63.2.1 LED与STM32F103连接原理图
63.3 程序设计
63.3.1 UCOSII驱动函数
在这里主要对本实验用到的UCOSII驱动函数进行介绍。

  1. OSTaskCreateExt函数
    创建任务函数,该函数是OSTaskCreate函数的扩展,并提供了一些附加功能。OSTaskCreateExt函数创建任务更加灵活,不过会增加一些额外的开销。其声明如下:
    INT8U OSTaskCreateExt (void (*task)(void *p_arg),
    void *p_arg,
    OS_STK *ptos,
    INT8U prio,
    INT16U id,
    OS_STK *pbos,
    INT32U stk_size,
    void *pext,
    INT16U opt)
    函数描述:
    用于创建一个任务
    函数形参:
    函数OSTaskCreatExt具有9个形参,如表63.3.1.1所示:
    参数 描述
    (*task)(void *p_arg) 指向任务的指针
    *p_arg 传递给任务的参数
    *ptos 指向任务堆栈栈顶的指针
    prio 任务的优先级
    id 任务的标识
    *pbos 任务堆栈栈底的指针
    stk_size 任务堆栈的容量
    *pext 指向附加数据域的指针

opt 用于设定操作选项
OS_OPT_TASK_NONE 表示没有任何选项。
OS_OPT_TASK_STK_CHK 指定是否允许检测该任务的堆栈。
OS_OPT_TASK_STK_CLR 指定是否清除该任务的堆栈。
OS_OPT_TASK_SAVE_FP 指定是否存储浮点寄存器,CPU需要有浮点运算。
函数返回值:
OS_ERR_NONE:函数调用成功
OS_ERR_PRIO_EXIST:具有该优先级的任务已经存在
OS_ERR_PRIO_INVALID:参数指定的优先级大于最大优先级
OS_ERR_TASK_CREATE_ISR:在ISR中创建任务
OS_ERR_ILLEGAL_CREATE_RUN_TIME:尝试在安全关键操作启动后创建任务
注意事项:
1,任务必须被创建在多任务开始之前或者运行的任务中
2,任务不能由ISR创建
3,任务必须在死循环中,并且不能返回
2. OSTaskSuspend函数
任务挂起函数,其声明如下:
INT8U OSTaskSuspend (INT8U prio)
函数描述:
用于将任务挂起
函数形参:
Prio:要挂起任务的优先级。
函数返回值:
OS_ERR_NONE:函数调用成功
OS_ERR_TASK_SUSPEND_IDLE:挂起空闲任务
OS_ERR_PRIO_INVALID:参数指定的优先级大于最大优先级
OS_ERR_TASK_SUSPEND_PRIO:需要挂起的任务不存在
OS_ERR_TASK_NOT_EXITS:任务被分配到一个互斥执行
3. OSTaskDel函数
删除任务函数,其声明如下:
INT8U OSTaskDel (INT8U prio)
函数描述:
用于删除任务
函数形参:
Prio:要删除任务的优先级。如果任务不知道自己优先级,还可以传递参数OS_PRIO_SELF。被删除的任务将回到休眠状态。
函数返回值:
OS_ERR_NONE:函数调用成功
OS_ERR_TASK_DEL_IDLE:删除空闲任务
OS_ERR_PRIO_INVALID:参数指定的优先级大于最大优先级
OS_ERR_TASK_DEL:任务被分配给互斥量执行
OS_ERR_TASK_NOT_EXIST:要删除的任务不存在
OS_ERR_TASK_DEL_ISR:在中断处理函数中删除任务
4.OSInit函数
UCOSII系统初始化函数,其声明如下:
void OSInit (void)
函数描述:
用于初始化UCOSII内部
函数形参:

函数返回值:

5. OSStart函数
多任务启动函数,其声明如下:
void OSStart (void)
函数描述:
用于用于启动多任务
函数形参:

函数返回值:

注意事项:
多任务的的启动是通过调用OSStart实现的,而在启动UCOSII之前至少需要建立一个应用任务。
63.3.2 程序流程图
图63.3.2.1 UCOSII任务调度实验
我们在main函数中进行外设和UCOS的初始化后,通过创建起始任务,并在起始任务创建LED0和LED1任务。任务初始化完成后,程序不断在LED0和LED1任务间切换。
63.3.3 程序解析
在STM32上运行UCOSII的步骤:
1、移植UCOSII
要使得UCOSII在STM32上正常运行,首先需要移植UCOSII,这部分我们已经为大家做好了。(要学习更详细的UCOS移植方法,可以参考我们关于UCOS的专门教程)
这里我们需要注意的一个地方,SYSTEM文件夹里面的系统函数直接支持UCOSII,只需要在sys.h文件里将:SYSTEM_SUPPORT_UCOS宏定义改为1,即可通过delay_init函数初始化UCOSII的系统时钟节拍,为UCOSII提供时钟节拍。
2、编写任务函数并设置其堆栈大小和优先级等参数
编写任务函数,以便UCOSII调用。
设置函数堆栈大小,这个需要根据函数的需求来设置,如果任务函数的局部变量多,嵌套层数多,那么对应的堆栈就得大一些,如果堆栈设置小了,很可能出现的结果就是CPU进入HardFault,遇到这种情况,你就必须把堆栈设置大一点了。另外,有些地方还需要注意堆栈字节对齐的问题,如果任务运行出现莫名其妙的错误(比如用到sprintf出错),请考虑是不是字节对齐的问题。
设置任务优先级,这个需要大家根据任务的重要性和实时性设置,记住高优先级的任务有优先使用CPU的权力。
3、初始化UCOSII,并在UCOSII中创建任务
调用OSInit,初始化UCOSII,通过调用OSTaskCreate函数创建我们的任务。
4、启动UCOSII
调用OSStart,启动UCOSII。
通过以上4个步骤,UCOSII就开始在STM32上面运行了,这里还需要注意我们必须对os_cfg.h进行部分配置,以满足我们的需求。

  1. main.c代码
    在main.c文件下,只初始化一些外设,然后调用ucosii的例程入口函数uc_os2_demo(),如下代码所示:
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "uc-os2_demo.h"

int main(void)
{
    HAL_Init();                         		/* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9); 	/* 设置时钟,72M */
    delay_init(72);                     		/* 初始化延时函数 */
    led_init();                         		/* 初始化LED */
    uc_os2_demo();                      		/* 运行uC/OS-II例程 */
}
可以看到,在main.c文件中只包含了一个main()函数,main()函数主要就是完成了一些外设的初始化,如系统时钟、延时、LED等,并在最后调用了函数uc_os2_demo()。
下面看一下uc-os2_demo.c的代码:
#include "uc-os2_demo.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"

/*uC/OS-II*********************************************************************************************/
#include "os.h"
#include "cpu.h"

/******************************************************************************************/
/* UCOSII任务设置 */

/* START 任务 配置
 * 包括: 任务优先级 堆栈大小 等
 */
#define START_TASK_PRIO                 10		/* 开始任务的优先级设置为最低 */
#define START_STK_SIZE                  128 		/* 堆栈大小 */

OS_STK START_TASK_STK[START_STK_SIZE];			/* 任务堆栈 */
void start_task(void *pdata);					/* 任务函数 */

/* LED0 任务 配置
 * 包括: 任务优先级 堆栈大小 等
 */
#define LED0_TASK_PRIO                  7			/* 开始任务的优先级设置为最低 */
#define LED0_STK_SIZE                   128		/* 堆栈大小 */
OS_STK LED0_TASK_STK[LED0_STK_SIZE];			/* 任务堆栈 */
void led0_task(void *pdata);					/* 任务函数 */

/* LED1 任务 配置
 * 包括: 任务优先级 堆栈大小 等
 */
#define LED1_TASK_PRIO                  6			/* 开始任务的优先级设置为最低 */
#define LED1_STK_SIZE                   128		/* 堆栈大小 */
OS_STK LED1_TASK_STK[LED0_STK_SIZE];			/* 任务堆栈 */
void led1_task(void *pdata);					/* 任务函数 */

/******************************************************************************************************/
/**
 * @brief       uC/OS-II例程入口函数
 * @param       无
 * @retval      无
 */
void uc_os2_demo(void)
{
    OSInit();										/* UCOS初始化 */
    OSTaskCreateExt((void(*)(void *) )start_task,	/* 任务函数 */
                    (void *		)0,					/* 传递给任务函数的参数 */
                    (OS_STK *		)&START_TASK_STK[START_STK_SIZE - 1],
                    (INT8U			)START_TASK_PRIO,	/* 任务优先级 */
                    (INT16U		)START_TASK_PRIO,	/* 任务ID */
                    (OS_STK *		)&START_TASK_STK[0],/* 任务堆栈栈底 */
                    (INT32U		)START_STK_SIZE,	/* 任务堆栈大小 */
                    (void *		)0,					/* 用户补充的存储区 */
/* 任务选项,为了保险起见,所有任务都保存浮点寄存器的值 */
                    (INT16U         )	OS_TASK_OPT_STK_CHK | 
OS_TASK_OPT_STK_CLR | 
OS_TASK_OPT_SAVE_FP); 
    OSStart();                                             /* 开始任务 */
    
    for (;;)
    {
        /* 不会进入这里 */
    }
}

/**
 * @brief       开始任务
 * @param       无
 * @retval      无
 */
void start_task(void *pdata)
{
    OS_CPU_SR cpu_sr = 0;
    CPU_INT32U cnts;
    
    OSStatInit();					/* 开启统计任务 */
    
    /* 根据配置的节拍频率配置SysTick */
    cnts = (CPU_INT32U)(HAL_RCC_GetSysClockFreq() / OS_TICKS_PER_SEC);
    OS_CPU_SysTickInit(cnts);
    OS_ENTER_CRITICAL();			/* 进入临界区(关闭中断) */
    /* LED0任务 */
    OSTaskCreateExt((void(*)(void *) )led0_task,
                    (void *         )0,
                    (OS_STK *		)&LED0_TASK_STK[LED0_STK_SIZE - 1],
                    (INT8U			)LED0_TASK_PRIO,
                    (INT16U         )LED0_TASK_PRIO,
                    (OS_STK *		)&LED0_TASK_STK[0],
                    (INT32U         )LED0_STK_SIZE,
                    (void *         )0,
                    (INT16U		)	OS_TASK_OPT_STK_CHK |
OS_TASK_OPT_STK_CLR |
OS_TASK_OPT_SAVE_FP);
    /* LED1任务 */
    OSTaskCreateExt((void(*)(void *) )led1_task,
                    (void *         )0,
                    (OS_STK *		)&LED1_TASK_STK[LED1_STK_SIZE - 1],
                    (INT8U			)LED1_TASK_PRIO,
                    (INT16U         )LED1_TASK_PRIO,
                    (OS_STK *		)&LED1_TASK_STK[0],
                    (INT32U         )LED1_STK_SIZE,
                    (void *         )0,
                    (INT16U		)	OS_TASK_OPT_STK_CHK |
								  	OS_TASK_OPT_STK_CLR |
OS_TASK_OPT_SAVE_FP);

    OS_EXIT_CRITICAL();				/* 退出临界区(开中断) */
    OSTaskSuspend(START_TASK_PRIO);	/* 挂起开始任务 */
}

/**
 * @brief       LED0任务
 * @param       pdata : 传入参数(未用到)
 * @retval      无
 */
void led0_task(void *pdata)
{
    while (1)
    {
        LED0(0);
        OSTimeDly(80);
        LED0(1);
        OSTimeDly(920);
    }
}

/**
 * @brief       LED1任务
 * @param       pdata : 传入参数(未用到)
 * @retval      无
 */
void led1_task(void *pdata)
{
    while (1)
    {
        LED1(0);
        OSTimeDly(300);
        LED1(1);
        OSTimeDly(300);
    }
}

对于uc-os2_demo.c文件,这里先简单介绍一下这个文件的代码结构,这个文件的代码结构可分为五个部分,分别是包含头文件、µC/OS-II相关配置、应用程序入口函数、开始任务入口函数、其他任务入口函数,接下来分别地介绍以上一个部分的代码。
① 包含的头文件分成两个部分,分别为µC/OS-II相关的头文件和其他头文件,如下所示:

#include "uc-os2_demo.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"

/*uC/OSII********************************************************/
#include "os.h"
#include "cpu.h"

② µC/OS-II的配置主要包括所创建µC/OS-II任务的相关定义(任务优先级、任务栈大小、任务栈、任务函数)以及定义应用于全局的µC/OS-II相关变量(信号量、事件标志、消息队列、软件定时器等),如下所示:

/* START 任务 配置
 * 包括: 任务优先级 堆栈大小 等
 */
#define START_TASK_PRIO                 10	/* 开始任务的优先级设置为最低 */
#define START_STK_SIZE                  128	/* 堆栈大小 */

OS_STK START_TASK_STK[START_STK_SIZE];		/* 任务堆栈 */
void start_task(void *pdata);				/* 任务函数 */

/* LED0 任务 配置
 * 包括: 任务优先级 堆栈大小 等
 */
#define LED0_TASK_PRIO                  7		/* 开始任务的优先级设置为最低 */
#define LED0_STK_SIZE                   128	/* 堆栈大小 */
OS_STK LED0_TASK_STK[LED0_STK_SIZE];		/* 任务堆栈 */
void led0_task(void *pdata);				/* 任务函数 */

/* LED1 任务 配置
 * 包括: 任务优先级 堆栈大小 等
 */
#define LED1_TASK_PRIO                  6		/* 开始任务的优先级设置为最低 */
#define LED1_STK_SIZE                   128	/* 堆栈大小 */
OS_STK LED1_TASK_STK[LED0_STK_SIZE];		/* 任务堆栈 */
void led1_task(void *pdata);				/* 任务函数 */
③ 这部分就是函数uc_os2_demo(),函数uc_os2_demo()一开始就是对µC/OS-II内核进行初始化,接着创建一个开始任务,最后开启µC/OS-II内核的任务调度,如下所示:
/**
 * @brief       uC/OS-II例程入口函数
 * @param       无
 * @retval      无
 */
void uc_os2_demo(void)
{
    OSInit();											/* UCOS初始化 */
    OSTaskCreateExt((void(*)(void *) )start_task,		/* 任务函数 */
                      (void *		)0,						/* 传递给任务函数的参数 */
                    (OS_STK *		)&START_TASK_STK[START_STK_SIZE - 1],
                    (INT8U			)START_TASK_PRIO,		/* 任务优先级 */
                    (INT16U		)START_TASK_PRIO,		/* 任务ID */
                    (OS_STK *		)&START_TASK_STK[0],	/* 任务堆栈栈底 */
                    (INT32U         )START_STK_SIZE,		/* 任务堆栈大小 */
                    (void *		)0,						/* 用户补充的存储区 */
/* 任务选项,为了保险起见,所有任务都保存浮点寄存器的值 */
                    (INT16U          )	OS_TASK_OPT_STK_CHK |
OS_TASK_OPT_STK_CLR |
OS_TASK_OPT_SAVE_FP);
    OSStart();                                                 /* 开始任务 */
    
    for (;;)
    {
        /* 不会进入这里 */
    }
}
④ 这部分就是开始任务的入口函数,开始任务主要用于初始化µC/CPU组件、配置µC/OS-II内核节拍的时基定时器、并且还会创建其他用于实验演示的任务,如下所示:
/**
 * @brief       开始任务
 * @param       无
 * @retval      无
 */
void start_task(void *pdata)
{
    OS_CPU_SR cpu_sr = 0;
    CPU_INT32U cnts;
    
    OSStatInit();				/* 开启统计任务 */
    
    /* 根据配置的节拍频率配置SysTick */
    cnts = (CPU_INT32U)(HAL_RCC_GetSysClockFreq() / OS_TICKS_PER_SEC);
    OS_CPU_SysTickInit(cnts);
    OS_ENTER_CRITICAL();		/* 进入临界区(关闭中断) */
    /* LED0任务 */
    OSTaskCreateExt((void(*)(void *) )led0_task,
                    (void *         )0,
                    (OS_STK *		)&LED0_TASK_STK[LED0_STK_SIZE - 1],
                    (INT8U			)LED0_TASK_PRIO,
                    (INT16U         )LED0_TASK_PRIO,
                    (OS_STK *		)&LED0_TASK_STK[0],
                    (INT32U         )LED0_STK_SIZE,
                    (void *         )0,
                    (INT16U		)	OS_TASK_OPT_STK_CHK |
OS_TASK_OPT_STK_CLR |
OS_TASK_OPT_SAVE_FP);
    /* LED1任务 */
    OSTaskCreateExt((void(*)(void *) )led1_task,
                    (void *		)0,
                    (OS_STK *		)&LED1_TASK_STK[LED1_STK_SIZE - 1],
                    (INT8U			)LED1_TASK_PRIO,
                    (INT16U         )LED1_TASK_PRIO,
                    (OS_STK *		)&LED1_TASK_STK[0],
                    (INT32U         )LED1_STK_SIZE,
                    (void *         )0,
                    (INT16U		)	OS_TASK_OPT_STK_CHK |
OS_TASK_OPT_STK_CLR |
OS_TASK_OPT_SAVE_FP);

    OS_EXIT_CRITICAL();				/* 退出临界区(开中断) */
    OSTaskSuspend(START_TASK_PRIO);	/* 挂起开始任务 */
}

从上面的代码可以看到start_task函数中创建了led0_task和led1_task两个任务,创建这两个任务后,将自己挂起。
我们单独创建start_task的目的是为了提供一个单一任务,实现应用程序开始之前的准备工作,比如外设初始化,创建任务,初始化统计任务,以及后面讲到的创建信号量、创建邮箱、创建消息队列、创建信号量集等。
在应用程序中经常有一些代码段必须不受任何干扰地连续运行,这样的代码段叫做临界段(或临界区)。因此,为了使临界段在运行时不受中断所打断,在临界段代码前必须用关中断指令使CPU屏蔽中断请求,而在临界段代码后必须用开中断指令接触屏蔽使得CPU可以响应中断请求。因为临界段代码不能被中断打断,将严重影响系统的实时性,所以临界段代码越短越好!
在start_task任务中,我们在创建led0_task和led1_task的时候,不希望中断打断,故使用了临界区。
⑤ 这部分包含了实验中用于演示的任务入口函数,如下所示:

/**
 * @brief       LED0任务
 * @param       pdata : 传入参数(未用到)
 * @retval      无
 */
void led0_task(void *pdata)
{
    while (1)
    {
        LED0(0);
        OSTimeDly(80);
        LED0(1);
        OSTimeDly(920);
    }
}

/**
 * @brief       LED1任务
 * @param       pdata : 传入参数(未用到)
 * @retval      无
 */
void led1_task(void *pdata)
{
    while (1)
    {
        LED1(0);
        OSTimeDly(300);
        LED1(1);
        OSTimeDly(300);
    }
}

上述两个任务较为简单,分别控制led0与led1不同频率的翻转,所以就不多做介绍。
以上就是uc_os2_demo.c文件的代码结构,本教程配套的3个ucosii的实验例程都会按照这个代码结构进行实验代码的编写,建议读者先熟悉以下这个代码结构。
另外,一个任务里面一般是必须有延时函数的,以释放CPU使用权,否则可能导致低优先级的任务因高优先级的任务不释放CPU使用权而一直无法得到CPU使用权,从而无法运行。
软件设计部分就为大家介绍到这里。
63.4 下载验证
将程序下载到开发板后,可以看到LED0一秒钟闪一次,而LED1则以固定的频率闪烁。说明两个任务(led0_task和led1_task)都已经正常运行,符合我们预期的设计。

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值