UCOSIII下的SYSTEM文件夹
1.1 delay.c文件夹中函数:
delay.c文件是 ALIENTEK 编写的文件,主要是使用滴答定时器来完成高精度延时。在使用 UCOS 的时候还完成了针对 UCDOS 的相关配置,主要是滴答定时器作为 CUOS 系统时基的配置
函数 | 描述 |
---|---|
delay_osschedlock() | 任务调度器加锁,对UCOS中的对应函数做封装 |
delay_osschedunlock() | 任务调度器解锁,对UCOS中的对应函数做封装 |
delay_ostimedly() | 延时,按照节拍数延时,对UCOS中的对应函数做封装 |
SysTick_Handler() | 滴答定时器中断服务函数 |
delay_init() | 滴答定时器/延时初始化 |
delay_us() | 微秒延时,不会引发任务调度 |
Delay_ms() | 毫秒延时,最小ms延时时间为UCOS系统心跳时间 |
1.2、滴答定时器:
在以前,操作系统以及所有使用了时基的系统,都必须由硬件定时器来产生 “滴答” 中断来作为系统时基。在STM32中SysTick就是用于产生系统时基的,SysTick有4个控制寄存器
地址 | 寄存器(描述) |
---|---|
OXE000E010 | SysTick->CTRL(控制及状态寄存器) |
0XE000E014 | SysTick->LOAD(重装载值寄存器) |
0XE000E018 | SysTick->VAL(当前数值寄存器) |
0XE000E01C | SysTick->CALIB(校准数值寄存器) |
滴答定时器的中断优先级为最低!在汇编文件:os_cpu_a.asm文件中定义
1.3、delay.c文件
static u8 fac_us=0; //us延时倍乘数
static u16 fac_ms=0; //ms延时倍乘数,在ucos下,代表每个节拍的ms数
本例程仅作UCOSII和UCOSIII的支持,其他OS,
-
满足 OS_CRITICAL_METHOD 的条件则支持 UCOSII
-
满足 CPU_CFG_CRITICAL_METHOD 的条件则支持UCOSIII
//支持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
任务
2.1、什么是任务
在设计复杂、大型程序的时候,将这些负责的程序分割成许多个简单的小程序,这些小程序就是单个任务,所有的小任务和谐的工作,最终完成复杂的功能。在操作系统中这些小任务可以并发执行,从而提高CPU的使用效率。
UCOSIII就是一个可剥夺的多任务系统,我们使用UCOSIII的一个重要原因就是他的多任务处理能力。
2.2、UCOSIII中的任务
在UCOSIII中任务就是程序实体,UCOSIII能够管理和调度这些小任务(程序)。
UCOSIII中的任务由三部分组成:任务堆栈、任务控制块和任务函数。
- 任务堆栈:上下文切换的时候用来保存任务的工作环境,就是STM32的内部寄存器值。
- 任务控制块:任务控制块用来记录任务的各个属性。
- 任务函数:由用户编写的任务处理代码,是实实在在干活的。
//任务优先级
#define LED0_TASK_PRIO 4
//任务堆栈大小
#define LED0_STK_SIZE 128
//任务控制块
OS_TCB Led0TaskTCB;
//任务堆栈
CPU_STK LED0_TASK_STK[LED0_STK_SIZE];
void led0_task(void *p_arg);
创建任务
OS_CRITICAL_ENTER(); //进入临界区
//创建LED0任务
OSTaskCreate((OS_TCB * )&Led0TaskTCB,
(CPU_CHAR * )"led0 task",
(OS_TASK_PTR )led0_task,
(void * )0,
(OS_PRIO )LED0_TASK_PRIO,
(CPU_STK * )&LED0_TASK_STK[0],
(CPU_STK_SIZE)LED0_STK_SIZE/10,
(CPU_STK_SIZE)LED0_STK_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);
任务函数之一
//led0任务函数
void led0_task(void *p_arg)
{
OS_ERR err;
p_arg = p_arg;
while(1)
{
LED0=0;
OSTimeDlyHMSM(0,0,0,200,OS_OPT_TIME_HMSM_STRICT,&err); //延时200ms
LED0=1;
OSTimeDlyHMSM(0,0,0,500,OS_OPT_TIME_HMSM_STRICT,&err); //延时500ms
}
}
UCOSIII中的任务函数编写:
void XXX_task(void *p_arg)
{
while(1)
{
.... // 任务处理过程
}
}
可以看出用任务函数通常是一个无限循环,当然了,也可以是一个只执行一次的任务。任务的参数是一个void类型的,这么做的目的是可以传递不同类型的数据甚至是函数。
可以看出任务函数其实就是一个C语言的函数,但是在使用UCOSIII的情况下这个函数不能有用户自行调用,任务函数何时执行,何时停止完全由操作系统来控制
OSTimeDlyHMSM(0,0,0,200,OS_OPT_TIME_HMSM_STRICT,&err); 任务切换代码
2.3、UCOSIII系统任务
UCOSIII默认有5个系统任务:
- 1、空闲任务:UCOSIII创建的第一个任务,UCOSIII必须创建的任务,此任务由UCOSIII自动创建,不需要用户手动创建。
- 2、时钟节拍任务:此任务也是必须创建的任务。
- 3、统计任务:可选任务,用来统计CPU使用率和各个任务的堆栈使用量。此任务是可选任务,由宏OS_CFG_STAT_TASK_EN控制是否使用此任务。
- 4、定时任务:用来向用户提供定时服务,也是可选任务,由宏OS_CFG_TMR_EN控制是否使用此任务。
- 5、中断服务管理任务:可选任务,由宏OS_CFG_ISR_POST_DEFERRED_EN控制是否使用此任务。
从用户的角度看,UCOSIII的任务一共有5种状态:
- 1、休眠态:任务已经在CPU的flash中了,但是还不受UCOSIII管理。
- 2、就绪态:系统为任务分配了任务控制块,并且任务已经在就绪表中登记,这时这个任务就具有了运行的条件,此时任务的状态就是就绪态。
- 3、运行态:任务获得CPU的使用权,正在运行。
- 4、等待态:正在运行的任务需要等待一段时间,或者等待某个事件,这个任务就进入了等待态,此时系统就会把CPU使用权转交给别的任务。
- 5、中断服务态:当发送中断,当前正在运行的任务会被挂起,CPU转而去执行中断服务函数,此时任务的任务状态叫做中断服务态。
任务堆栈
1.1、任务堆栈的创建:
任务堆栈是任务的重要部分,堆栈是在 RAM 中按照 "先进先出(FIFO)"的原则组织的一块连续的存储空间。为了满足任务切换和响应中断时保存 CPU 寄存器中的内容及任务调用其他函数时的需要,每个任务都应该有自己的堆栈。
#define START_STK_SIZE 512 // 堆栈大小
CPU_STK_START_TASK_STK[START_STK_SIZE]; // 定义一个数组来作为任务堆栈
任务堆栈的大小为:512 * 4 = 2048子节
解释是:CPU_STK为CPU_INT32U类型,也就是unsigned int类型,为4子节的,那么任务堆栈START_TASK_STK的大小就为:512 * 4 = 2048子节
typedef unsigned int CPU_INT32U; /* 32-bit unsigned integer */
typedef CPU_INT32U CPU_STK; /* Defines CPU stack word size (in octets). */
//任务堆栈
CPU_STK LED0_TASK_STK[LED0_STK_SIZE];
1.2、任务堆栈初始化:
任务如何才能切换回上一个任务并且还能接着从上次被中断的地方开始运行?
恢复现场即可,现场就是CPU的内部各个寄存器。因此在创建一个新任务时,必须把系统启动这个任务时所需的CPU各个寄存器初始化值事先存放在任务堆栈中。这样当任务获得CPU使用权时,就把任务堆栈的内容复制到CPU的各个寄存器,从而可以让任务顺利地启动并运行了。
把任务初始化数据存放到任务堆栈的工作就叫做任务堆栈的初始化,UCOSIII提供了完成堆栈初始化的函数:OSTaskStkInit()
CPU_STK *OSTaskStkInit (OS_TASK_PTR p_task,
void *p_arg,
CPU_STK *p_stk_base,
CPU_STK *p_stk_limit,
CPU_STK_SIZE stk_size,
OS_OPT opt)
用户一般不会直接操作堆栈初始化函数,任务堆栈初始化函数由任务创建函数OSTaskCreate()调用。不同的CPU对于的寄存器和对堆栈的操作方式不同,因此在移植UCOSIII的时候需要用户根据各自所选的CPU来编写任务堆栈初始化函数。
1.3、怎么使用创建的任务堆栈
作为任务创建函数OSTaskCreate()的参数,函数OSTaskCreate()如下:
void 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)
2.1、任务控制块结构:
任务控制块是用来记录与任务相关的信息的数据结构,每个任务都要有自己的任务控制块。任务控制块由用户自行创建,如下代码为创建一个任务控制块:
OS_TCB StartTaskTCB; // 创建一个任务控制块
OS_TCB为一个结构体,描述了任务控制块,任务控制块中的成员变量用户不能直接访问,更不可能改变他们
OS_TCB为一个结构体,其中有些成员采用了条件编译的方式来确定。
struct os_tcb
{
CPU_STK *StkPtr;
void *ExtPtr;
CPU_STK *StkLimitPtr;
OS_TCB *NextPtr;
OS_TCB *Prev;
.... // 此处省略N个成员变量
#if OS_CFG_DBG_EN > Ou
OS_TCB *DbgPrevPtr;
OS_TCB *DbgNextPtr;
CPU_CHAR *DbgNamePtr;
#endif
}
2.2、任务控制块初始化:
函数OSTaskCreate()在创建任务的时候会对任务的任务控制块进行初始化。函数OS_TaskInitTCB()用于初始化任务控制块。用户不需要自行初始化任务控制块。
3、UCOSIII任务就绪表
3.1、优先级:
UCOSIII中任务优先级数由宏OS_CFG_PRIO_MAX来配置,UCOSIII中数值越小,优先级越高。最低可用优先级就是 OS_CFG_PRIO_MAX-1。
3.2、就绪表:
UCOSIII中就绪表由2部分组成:
- 优先级位映射表OSPrioTbl[]:用来记录哪个优先级下有任务就绪。
- 就绪任务列表OSRdyList[]:用来记录每一个优先级下所有就绪的任务。
OSPrioTbl[]在os_prio.c中有定义:
CPU_DATA OSPrioTbl[OS_PRIO_TBL_SIZE];
在STM32中CPU_DATA为unsiged int,有4个字节,32位。因此表OSPrioTbl每个参数有32位,其中每个位对应一个优先级。
OS_PRIO_TBL_SIZE=((OS_CFG_PRIO_MAX-1u)/DEF_INT_CPU_NBR_BITS) + 1)
OS_CFG_PRIO_MAX由用户自行定义,默认为64.
DEF_INT_CPU_NBR_BITS = CPU_CFG_DATA_SIZE * DEF_OCTET_NBR_BITS
CPU_CFG_DATA_SIZE=CPU_WORD_SIZE_32=4
DEF_OCTET_NBR_BITS=8
所以,当系统有64个优先级的时候:
OS_PRIO_TBL_SIZE = ((64-1) / (4*8)+1)=2
函数OS_PrioGetHighest()用于找到就绪了的最高优先级的任务
OS_PRIO OS_PrioGetHighest (void)
{
CPU_DATA *p_tbl;
OS_PRIO prio;
prio = (OS_PRIO)0;
p_tbl = &OSPrioTbl[0];
while (*p_tbl == (CPU_DATA)0) { /* Search the bitmap table for the highest priority */
prio += DEF_INT_CPU_NBR_BITS; /* Compute the step of each CPU_DATA entry */
p_tbl++;
}
prio += (OS_PRIO)CPU_CntLeadZeros(*p_tbl); /* Find the position of the first bit set at the entry */
return (prio);
}
就绪任务列表:
通过上一步我们已经知道了哪个优先级的任务已经就绪了,但是UCOSIII支持时间片轮转调度,同一个优先级下可以有多个任务,因此我们还需要在确定是优先级下的哪个任务就绪了
struct os_rdy_list {
OS_TCB *HeadPtr; // 用于创建链表,指向链表头
OS_TCB *TailPtr; // 用于创建链表,指向链表尾
OS_OBJ_QTY NbrEntries //此优先级下的任务数量
}
1、UCOSIII任务调度
1.1、可剥夺任务调度:
任务调度就是中止当前正在运行的任务转而去执行其他的任务。
UCOSIII是可剥夺型内核,因此当一个高优先级的任务准备就绪,并且此时发生了任务调度,那么这个高优先级的任务就会获得CPU的使用权!
UCOSIII中的任务调度是由任务调度器来完成的,任务调度器有2种:
- 任务级调度器:函数为OSSched()
- 中断级调度器:函数为OSIntExit(),当退出外部中断服务函数的时候使用中断任务调度
任务级调度器:
void OSSched (void)
{
CPU_SR_ALLOC();
if (OSIntNestingCtr > (OS_NESTING_CTR)0) { /* ISRs still nested? */
return; /* Yes ... only schedule when no nested ISRs */
}
if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0) { /* Scheduler locked? */
return; /* Yes */
}
CPU_INT_DIS();
OSPrioHighRdy = OS_PrioGetHighest(); /* Find the highest priority ready */
OSTCBHighRdyPtr = OSRdyList[OSPrioHighRdy].HeadPtr;
if (OSTCBHighRdyPtr == OSTCBCurPtr) { /* Current task is still highest priority task? */
CPU_INT_EN(); /* Yes ... no need to context switch */
return;
}
#if OS_CFG_TASK_PROFILE_EN > 0u
OSTCBHighRdyPtr->CtxSwCtr++; /* Inc. # of context switches to this task */
#endif
OSTaskCtxSwCtr++; /* Increment context switch counter */
#if defined(OS_CFG_TLS_TBL_SIZE) && (OS_CFG_TLS_TBL_SIZE > 0u)
OS_TLS_TaskSw();
#endif
OS_TASK_SW(); /* Perform a task level context switch */
CPU_INT_EN();
}
中断级调度器:
void OSIntExit (void)
{
CPU_SR_ALLOC();
if (OSRunning != OS_STATE_OS_RUNNING) { /* Has the OS started? */
return; /* No */
}
CPU_INT_DIS();
if (OSIntNestingCtr == (OS_NESTING_CTR)0) { /* Prevent OSIntNestingCtr from wrapping */
CPU_INT_EN();
return;
}
OSIntNestingCtr--;
if (OSIntNestingCtr > (OS_NESTING_CTR)0) { /* ISRs still nested? */
CPU_INT_EN(); /* Yes */
return;
}
if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0) { /* Scheduler still locked? */
CPU_INT_EN(); /* Yes */
return;
}
OSPrioHighRdy = OS_PrioGetHighest(); /* Find highest priority */
OSTCBHighRdyPtr = OSRdyList[OSPrioHighRdy].HeadPtr; /* Get highest priority task ready-to-run */
if (OSTCBHighRdyPtr == OSTCBCurPtr) { /* Current task still the highest priority? */
CPU_INT_EN(); /* Yes */
return;
}
#if OS_CFG_TASK_PROFILE_EN > 0u
OSTCBHighRdyPtr->CtxSwCtr++; /* Inc. # of context switches for this new task */
#endif
OSTaskCtxSwCtr++; /* Keep track of the total number of ctx switches */
#if defined(OS_CFG_TLS_TBL_SIZE) && (OS_CFG_TLS_TBL_SIZE > 0u)
OS_TLS_TaskSw();
#endif
OSIntCtxSw(); /* Perform interrupt level ctx switch */
CPU_INT_EN();
}
1.2、任务调度点:
1、释放信号量或者发送消息,也可通过配置相应的参数不发生任务调度。
2、使用延时函数OSTimeDly() 或者 OSTimeDlyHMSM()
3、任务等待的事情还没发生(等待信号量,消息队列等)
4、任务取消等待
5、创建任务
6、删除任务
7、删除一个内核对象
8、任务改变自身的优先级或者其他任务的优先级
9、任务通过调用OSTaskSuspend()将自身任务挂起
10、任务解挂某个挂起的任务
11、退出所有的嵌套
12、通过OSSchedUnlock()给调度器解锁
13、任务调用OSSchedRoundRobinYield()放弃其执行时间片
14、用户调用OSSched()
1.2、调度器上锁和解锁:
有时候我们并不希望发生任务调度,因为始终有一些代码的执行过程是不能被打断的。此时我们就可以使用函数OSSchedLock()对调度器加锁,当我们想要恢复任务调度的时候就可以使用函数OSSchedUnlock()给已经上锁的任务调度器解锁
1.3、时间片轮转调度:
UCOSIII允许一个优先级下有多个任务,每个任务可以执行指定的时间(时间片),然后轮到下一个任务,这个过程就是时间片轮转调度,当一个任务不想在运行的时候就可以放弃其时间片。
时间片轮转调度为:OS_SchedRoundRobin()
时间片轮转调度实例:
任务 | 时间片数 |
---|---|
Task1 | 4 |
Task2 | 4 |
Task3 | 4 |
什么是任务切换
当UCOSIII需要切换到另一个任务时,它将保存当前任务的现场到当前任务的堆栈中,主要是CPU寄存器值,然后恢复新的现场并且执行新的任务,这个过程就是任务切换。
任务切换分为两种:任务级切换和中断级切换
- 任务级切换函数为:OSCtxSw()
- 中断级切换函数为:OSIntCtxSw()
OSCtxSw
LDR R0, =NVIC_INT_CTRL ; Trigger the PendSV exception (causes context switch)
LDR R1, =NVIC_PENDSVSET
STR R1, [R0]
BX LR
OSIntCtxSw
LDR R0, =NVIC_INT_CTRL ; Trigger the PendSV exception (causes context switch)
LDR R1, =NVIC_PENDSVSET
STR R1, [R0]
BX LR
3.1、UCOSIII系统初始化:
在使用 UCOSIII 之前我们必须先初始化UCOSIII,函数OXInit()用来完成 UCOSIII 的初始化,而且OSInit()必须先于其他 UCOSIII 函数调用,包括OSStart()
int main(void)
{
OS_ERR err;
......
// 其他函数,一般为外设初始化函数
......
OSInit(&err);
// 其他函数,一般为创建任务
......
OSStart(&err);
}
创建开始任务:
进入临界区和退出临界区之间是不会被打断的
OSInit(&err); //初始化UCOSIII
OS_CRITICAL_ENTER();//进入临界区
//创建开始任务
OSTaskCreate((OS_TCB * )&StartTaskTCB, //任务控制块
(CPU_CHAR * )"start task", //任务名字
(OS_TASK_PTR )start_task, //任务函数
(void * )0, //传递给任务函数的参数
(OS_PRIO )START_TASK_PRIO, //任务优先级
(CPU_STK * )&START_TASK_STK[0], //任务堆栈基地址
(CPU_STK_SIZE)START_STK_SIZE/10, //任务堆栈深度限位
(CPU_STK_SIZE)START_STK_SIZE, //任务堆栈大小
(OS_MSG_QTY )0, //任务内部消息队列能够接收的最大消息数目,为0时禁止接收消息
(OS_TICK )0, //当使能时间片轮转时的时间片长度,为0时为默认长度,
(void * )0, //用户补充的存储区
(OS_OPT )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR, //任务选项
(OS_ERR * )&err); //存放该函数错误时的返回值
OS_CRITICAL_EXIT(); //退出临界区
OSStart(&err); //开启UCOSIII
CPU初始化:
CPU_Init();
#if OS_CFG_STAT_TASK_EN > 0u
OSStatTaskCPUUsageInit(&err); //统计任务
#endif
#ifdef CPU_CFG_INT_DIS_MEAS_EN //如果使能了测量中断关闭时间
CPU_IntDisMeasMaxCurReset();
#endif
#if OS_CFG_SCHED_ROUND_ROBIN_EN //当使用时间片轮转的时候
//使能时间片轮转调度功能,时间片长度为1个系统时钟节拍,既1*5=5ms
OSSchedRoundRobinCfg(DEF_ENABLED,1,&err);
#endif
3.2、系统启动:
找到当前任务的最高级开始执行
void OSStart (OS_ERR *p_err)
{
#ifdef OS_SAFETY_CRITICAL
if (p_err == (OS_ERR *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
return;
}
#endif
if (OSRunning == OS_STATE_OS_STOPPED) {
OSPrioHighRdy = OS_PrioGetHighest(); /* Find the highest priority */
OSPrioCur = OSPrioHighRdy;
OSTCBHighRdyPtr = OSRdyList[OSPrioHighRdy].HeadPtr;
OSTCBCurPtr = OSTCBHighRdyPtr;
OSRunning = OS_STATE_OS_RUNNING;
OSStartHighRdy(); /* Execute target specific code to start task */
*p_err = OS_ERR_FATAL_RETURN; /* OSStart() is not supposed to return */
} else {
*p_err = OS_ERR_OS_RUNNING; /* OS is already running */
}
}
额外部分:
有关于uCos的启动过程讲解有很多文章,这里主要记录已下OSStartHighRdy()在stm32下是怎么写和运行的:
stm32上电
运行SystemInit(): 配置时钟,这个不要也可以,不要就是8MHz时钟
运行main() : 调用OSInit(), OSTaskCreate()一个任务,OSSart()
- OSInit():至少做3件事1、初始化零散的uCos全变量;2、把任务控制块、事件控制块链表化;3、创建已空闲任务IDLE(系统的);
- OSTaskCreate():建立一个属于用户的任务(非系统的),这个是必须有的。
- OSSart():上面几步其实都是填充数据,建立任务也是填充数据(写入任务的函数指针),OSSart()功能是找一个优先级最高填到变量里。其实他幕后是执行着是OSStartHighRdy();
OSStartHighRdy():这个功能很明确:配置系统软件中断优先级、PSP栈指针清零、OSRunning = TRUE、触发软件中断、使能全局中断 目的:触发软件中断进行一次调度,这个模块就运行一次而且不返回。
进入软件中断服务程序:OSCtxSw() 也是uCos移植必改的模块;
- 1、把正在运行的任务的cpu寄存器及变量存到PSP指向的内存(任务控制块OSTCBStkPrt)。
- 2、把PSP栈指针也存进去;
- 3、调用Hook;
- 4、把新的任务(最高优先级)的任务控制块指针OSTCBHighRdy给当前B任务控制块指针(将要运行)OSTCBCur
- 5、当前B任务控制块指针的栈指针OSTCBStkPrt取出CPU寄存器值