(1)系统内部任务
1.空闲任务 OS_IdleTask()
空闲任务必须创建,是uC/OS-III创建的第一个任务,但是不需要我们手动创建,在调用OS_Init()初始化时就会被创建,优先级总是
OS_CFG_PRIO_MAX-1(最低优先级)真正的无限循环,即不会调用任务
会使其进入等待状态的服务函数
2.时钟节拍任务 OS_TickTask()
**时钟节拍任务也是必须创建的,同样不需要我们手工创建,**该任务的优先级应当只比用户的系统中最重要的任务的优先级略低一点,f4中设置为1
时钟节拍任务的作用:跟踪正在延时的任务,以及在指定时间内等待某个内核对象的任务
时钟节拍列表=数据表(OSCfg_TickWheel[])+计数器(OSTickCtr),
数据表的表项数目OS_CFG_TICK_WHEEL_SIZE一般设置为任务数的1/4左右,且最好为素数;
每当时钟节拍任务接收到时钟节拍ISR发送的信号量时,OSTickCtr自动+1
每次节拍中断发生时,只有其中一个表项上的任务可能延迟结束在使用时钟节拍列表需要先初始化时钟节拍列表,也就是将OSCfg_TickWheel[]数组中的元素清零
表中元素的数据类型OS_TICK_SPOKE结构体
3.统计任务 OS_StatTaskInit()
作用:总CPU利用率、各任务的CPU利用率和各任务的堆栈使用量
如果要使用统计任务就要在main()函数创建的第一个也是唯一 一个应用任务中调用OSStatTaskCPUUsageInit(),这个应用任务应该分配一个很高的优先级(0除外);uC/OS-III允许用户在调用OSStart()之前创建任意数目的任务,然而,要使用统计任务时,只能创建一个任务(start task()开始任务),在该任务中调用OSStatTaskCPUUsageInit()。
4.定时任务 OS_TmrTask()
一个递减的计数器,减为0时,可以启动一个用户自定义的回调函数(Callback Funcion),避免在回调函数中使用阻塞调用(OSTimeDly()、OSTimeDlyHMSM()),回调函数的执行实在定时器任务内完成的;与时钟节拍任务使用相同的中断源,优先级一般为中等优先级
5.中断服务管理任务 OS_IntQTask()
该任务负责"延迟"在ISR中调用的系统post服务函数的行为,任务优先级最高为0当ISR(中断服务函数)调用post函数,要发送的数据和发送的目的地都会存入一个特别的缓冲队列中,当所有嵌套的ISR都执行完成后,ucos会做任务切换,运行中断服务 管理任务,该任务会把缓存队列中存放的信息发送给相应的任务
6.钩子函数 HOOK
作用:扩展任务功能,被其他任务调用 算是消息机制
1.OSIdleTaskHook(),空闲任务调用这个函数,可以用来让CPU进入低功耗模式
2、OSInitHook(), 系统初始化函数OSInit()调用此函数。
3、OSStatTaskHook(),统计任务每秒中都会调用这个函数,此函数允许你向统计任务中添加自己的应用函数。
4、OSTaskCreateHook(),任务创建的钩子函数。
5、OSTaskDelHook(), 任务删除的钩子函数。
6、OSTaskReturnHook(),任务意外返回时调用的钩子函数,比如删除某个任务
7、OSTaskSwHook(), 任务切换时候调用的钩子函数。
8、OSTimeTickHook(),滴答定时器调用的钩子函数。
(2)中断和时间管理
1.中断管理
中断是一个硬件机制,主要用来通知CPU一个异步事件发生,这样CPU就会将当前CPU寄存器的值存入栈,进而去处理中断服务函数。
编写UCOSIII的中断服务程序的时候需要使用到两个函数OSIntEnter()和OSIntExit()
格式:和写普通的32中断服务函数一样,只不过在开头和结尾要调用函数
void xxxx_IRQHandler(void)
{
OSIntEnter();
... //中断服务程序
OSIntExit();
}
直接发布和延迟发布
UCOSIII对从中断发布消息或信号的处理有两种模式:直接发布和延迟发布
直接发布
1.外设产生中断请求;
2.中断服务程序开始运行,该中断服务程序中可能会包含有发送信号量、消息、事件标志组等事件。那么等待这个事件的任务的优先级要么比当前被中断的任务高,要么比其低;
3.如果中断对应的事件使得某个比被中断的任务优先级低的任务进入就绪态,则中断退出后仍恢复运行被中断的任务;
4.如果中断对应的事件使得某个比被中断的任务优先级更高的任务进入就绪态,则UCOSIII将进行任务切换,中断服务程序推出后就执行更高优先级的任务;
5**.如果使用直接发布模式的话,则UCOSIII就必须关中断以保护临界段代码,防止中断处理程序访问这些临界段代码。**
缺点:
直接发布模式,如果进行临界段保护:UCOSIII会对临界段代码采用关闭中断的保护措施,这样就会延长中断的响应时间。虽然UCOSIII已经采用了所有可能的措施来降低中断关闭时间,但仍然有一些复杂的功能会使得中断关闭相对较长的时间
延迟发布
1.外设产生中断请求;
2.中断服务程序开始运行,该中断服务程序中可能会包含有发送信号量、消息、事件标志组等事件。那么等待这个事件的任务的优先级要么比当前被中断的任务高,要么比其低;
3.中断服务程序通过调用系统的发布服务函数向任务发布消息或信号,在延迟发布模式下,这个过程不是直接进行发布操作,而是将这个发布函数调用和相应的参数写入到专用队列中,该队列称为中断队列。然后使中断队列处理任务进入就绪态,这个任务是UCOSIII的内部任务,并且具有最高优先级0;
4.中断服务程序处理结束时,UCOSIII切换执行中断队列处理任务,该任务从中断队列中提取出发布函数调用信息,此时仍需要关闭中断以防止中断服务程序同时对中断队列进行访问。中断队列处理任务提取出发布函数调用的信息后重新开中断,锁定任务调度器,然后进行发布函数调用,相当于发布函数调用一直是在任务级代码中进行的,这样本来应该在中断中处理的代码(发布函数调用)就被放到了任务级完成;
5.中断队列处理任务将中断队列处理完,将自身挂起,并重新启动任务调度来运行处于最高优先级的就绪任务。如果原先被中断的任务仍然是最高优先级的就绪任务,则UCOSIII恢复运行这个任务;
6.由于中断队列处理任务的发布操作使得更重要的任务进入就绪态,内核将切换到更高优先级的任务运行。延迟发布模式下,从中断发布消息和信号是延迟发布的,就是将要发布的内容下入到一个专用的队列中,最后由中断服务管理任务进行发布。
延迟发布模式,如果进行临界段保护:在使用延迟发布模式额外增加的操作都是为了避免使用关中断来保护临界段代码。延迟发布模式下,是通过任务调度来实现的(任务调度到中断服务管理任务),那么就需要锁定任务调度来保护临界段代码。这些额外增加的操作仅包括将发布调用及其参数复制到中断队列中、从中断队列提取发布调用和相关参数以及一次额外的任务切换。
直接发布和延迟发布的对比:
直接发布模式下,UCOSIII通过关闭中断来保护临界段代码;
延迟发布模式下,UCOSIII通过锁定任务调度来保护临界段代码。在延迟发布模式下,UCOSIII在访问中断队列时,仍然需要关闭中断,但这个时间是非常短的。
如果应用中存在非常快速的中断请求源,则当UCOSIII在直接发布模式下的中断关闭时间不能满足要求的时候,可以使用延迟发布模式来降低中断关闭时间。
UCOSIII的临界段代码保护
有一些代码我们需要保证其完成运行,不能被打断,这些不能被打断的代码就是临界段代码,也叫临界区
在进入临界段代码的时候使用宏OS_CRITICAL_ENTER(),退出临界区的时使用宏OS_CRITICAL_EXIT(),OS_CRITICAL_EXIT_NO_SCHED()。
2.时钟管理
(1)OSTimeTick()函数
UCOSIII需要一个系统时钟节拍,作为系统心跳
UCOSIII通过时钟节拍来对任务进行整个节拍的延迟,并为等待事件的任务提供超时判断。时钟节拍中断必须调用OSTimeTick()函数,我们使用Systick来为系统提供时钟,因此在Systick的中断服务程序中就必须调用OSTimeTick(),
(2)任务延迟
UCOSIII中的任务是一个无限循环并且还是一个抢占式内核,为了使高优先级的任务不至于独占CPU,可以给其他优先级较低任务获取CPU使用权的机会,UCOSIII中除空闲任务外的所有任务必须在合适的位置调用系统提供的延时函数,让当前的任务暂停运行一段时间并进行一个任务切换。
延时函数:
OSTimeDly()和OSTimeDlyHMSM()。
OSTimeDly()函数有三种工作模式:相对模式、周期模式和绝对模式;
OSTimeDlyHMSM()函数仅在相对模式下工作。
延迟时间以节拍数为单位:OSTimeDly()
void OSTimeDly (OS_TICK dly, //指定延时的时间长度,这里单位为时间节拍数。
OS_OPT opt, //指定延迟使用的选项
OS_ERR *p_err)
{
...
}
OSTimeDlyHMSM() 一般采用这种更直接
void OSTimeDlyHMSM (CPU_INT16U hours, //需要延时的小时数
CPU_INT16U minutes, //需要延时的分钟数
CPU_INT16U seconds,
CPU_INT32U milli,
OS_OPT opt, //选项
OS_ERR *p_err)
{
...
}
取消任务的延时 OSTimeDlyResume()函数
一 个 任 务 可 以 通 过 调 用 这 个 函 数 来 “ 解 救 ” 那 些 因 为 调 用 了OSTimeDly()或者OSTimeDlyHMSM()函数而进入等待态的任务,
void OSTimeDlyResume (OS_TCB *p_tcb,
OS_ERR *p_err)
{
...
}
延时任务任务可通过在其他任务中调用函数OSTimeDlyResume()取消延时而进入就绪状态,此函数最后会引发一次任务调度。
获取和设置系统时间:
UCOSIII定义了一个CPU_INT32U类型的全局变量OSTickCtr来记录系统时钟节拍数,在调用OSInit()时被初始化为0,以后每发生1个时钟节拍,OSTickCtr加1。
OSTimeSet()允许用户改变当前时钟节拍计数器的值,慎用
OSTimeGet()用来获取动迁时钟节拍计数器的值。
3.软件定时器
在STM32中使用定时器来进行定时任务属于硬件定时器
UCOSIII软件定时器:
定时器其实就是一个递减计数器,当计数器递减到0的时候就会触发一个动作,这个动作就是回调函数,当定时器计时完成时就会自动的调用这个回调函数。因此我们可以使用这个回调函数来完成一些设计
回调函数:
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方法直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
定时器的分辨率由我们定义的系统节拍频率OS_CFG_TICK_RATE_HZ决定,比如我们定义为200,系统时钟周期就是5ms,定时器的最小分辨率肯定就是5ms。但是定时器的实际分 辨 率 是 通 过 宏OS_CFG_TMR_TASK_RATE_HZ定 义 的 , 这 个 值 绝 对 不 能 大 于OS_CFG_TICK_RATE_HZ。
定义OS_CFG_TMR_TASK_RATE_HZ为100,则定时器的时间分辨率为10ms。
软件定时器相关API函数
创建一个定时器:OSTmrCreate()
p_tmr:指向定时器的指针,宏OS_TMR是一个结构体
p_name:定时器名称
dly:初始化定时器的延迟值
period:重复周期
opt:定时器运行选项,这里有2个模式可以选择。OS_OPT_TMR_ONE_SHOT单次定时器、OS_OPT_TMR_PERIODIC周期定时器
p_callback:指向回调函数的名字
p_callback_arg:回调函数的参数
p_err:调用此函数以后返回的错误码
定时器可以分为单次定时器 和周期定时器(无初始化延迟) 周期定时器(有初始化延迟)
单次定时器:
使用OSTmrCreate()函数创建定时器时把参数opt设置为OS_OPT_TMR_ONE_SHOT,就是创建的单次定时器。创建一个单次定时器以后,我们一旦调用OSTmrStart()函数,定时器就会从创建时定义的dly开始倒计数,直到减为0调用回调函数,定时器就停止了。
周期定时器(无初始化延迟)
使用OSTmrCreate()函数创建定时器时把参数opt设置为OS_OPT_TMR_PERIODIC,就是创建的周期定时器。当定时器倒计数完成后,定时器就会调用回调函数,并且重置计数器开始下一轮的定时,就这样一直循环下去。如果使用OSTmrCreate()函数创建定时器的时候,参数dly为0的话,那么定时器在每个周期开始时计数器的初值就为period
周期定时器(有初始化延迟)
在创建定时器的时候也可以创建带有初始化延时的,初始化延时就是OSTmrCreate()函数中的参数dly就是初始化延迟,定时器的第一个周期就是dly。当第一个周期完成后就是**用参数period作为周期值,**调用OSTmrStart()函数开启有初始化延时的定时器,