以前搞硬件的经验,最近突然翻出来了。分享给大家;主要讲解uC/OS-II常用函数;虽说现在转行软件了,但是感觉之前搞硬件的经验还真是很有用对于理解底层等很有帮助。比如这里对于操作系统还是有点用的;好了,开始唠叨下。
μC/OS-II由Micrium公司提供,是一个可移植、可固化的、可裁剪的、占先式多任务实时内核,它适用于多种微处理器,微控制器和数字处理芯片(已经移植到超过100种以上的微处理器应用中)。同时,该系统源代码开放、整洁、一致,注释详尽,适合系统开发。
在uC/OS II的学习中,OSInit(OS_CORE.C )(函数原型位于);是一个重要的函数,它在OS应用中的main()函数中首先被调用,是OS运行的第一个函数,它完成各初始变量的初始化。主要工作:
OSInitHookBegin(); /* 调用用户特定的初始化代码(通过一个接口函数实现用户要求的插件式进入系统中)*/
OS_InitMisc(); /* 初始化变量*/
OS_InitRdyList(); /* 初始化就绪列表*/
OS_InitTCBList(); /* 初始化OS_TCB空闲列表*/
OS_InitEventList(); /* 初始化OS_EVENT空闲列表*/
OS_InitTaskIdle(); /创建空闲任务/
程序注释详解:
void OSInit (void)
{
#if OS_TASK_CREATE_EXT_EN > 0u
#if defined(OS_TLS_TBL_SIZE) && (OS_TLS_TBL_SIZE > 0u)
INT8U err;
#endif
#endif
OSInitHookBegin(); /* 调用用户特定的初始化代码(通过一个接口函数实现用户要求的插件式进入系统中)*/
OS_InitMisc(); /* 初始化变量*/ /* Initialize miscellaneous variables */
OS_InitRdyList(); /* 初始化就绪列表*/ /* Initialize the Ready List */
OS_InitTCBList(); /* 初始化OS_TCB空闲列表*/ /* Initialize the free list of OS_TCBs */
OS_InitEventList(); /* 初始化OS_EVENT空闲列表*/ /* Initialize the free list of OS_EVENTs */
#if (OS_FLAG_EN > 0u) && (OS_MAX_FLAGS > 0u)
OS_FlagInit(); /* 初始化事件标志结构*/ /* Initialize the event flag structures */
#endif
#if (OS_MEM_EN > 0u) && (OS_MAX_MEM_PART > 0u)
OS_MemInit(); /* 初始化内存管理器*/ /* Initialize the memory manager */
#endif
#if (OS_Q_EN > 0u) && (OS_MAX_QS > 0u)
OS_QInit(); /* 初始化消息队列结构*/ /* Initialize the message queue structures */
#endif
#if OS_TASK_CREATE_EXT_EN > 0u
#if defined(OS_TLS_TBL_SIZE) && (OS_TLS_TBL_SIZE > 0u)
OS_TLS_Init(&err); /* 创建任务前初始化TLS*/ /* Initialize TLS, before creating tasks */
if (err != OS_ERR_NONE) {
return;
}
#endif
#endif
OS_InitTaskIdle(); /* 创建空闲任务(无条件)Create the Idle Task */
#if OS_TASK_STAT_EN > 0u
OS_InitTaskStat(); /* 创建统计任务*/ /* Create the Statistic Task */
#endif
#if OS_TMR_EN > 0u
OSTmr_Init(); /* 初始化时间管理器*/ /* Initialize the Timer Manager */
#endif
OSInitHookEnd(); /*调用用户特定的初始化代码*/
#if OS_DEBUG_EN > 0u
OSDebugInit();
#endif
}
1、主要作用:建立一个新任务。任务的建立可以在多任务环境启动之前,也可以在正在运行的任务中建立。中断处理程序中不能建立任务;注意,ISR中禁止建立任务,一个任务必须为无限循环结构。
2、函数原型:INT8U OSTaskCreate(void (*task)(void *pd), void *pdata, OS_STK *ptos, INT8U prio);
3、参数说明:
void (*task)(void *pd):指向任务代码首地址的指针。
void *pdata:指向一个数据结构,该结构用来在建立任务时向任务传递参数。
OS_STK *ptos: 指向堆栈任务栈顶的指针
INT8U prio:任务优先级
4、返回值介绍:
OS_NO_ERR:函数调用成功。
OS_PRIO_EXIST:具有该优先级的任务已经存在。
OS_PRIO_INVALID:参数指定的优先级大于OS_LOWEST_PRIO。
OS_NO_MORE_TCB:系统中没有OS_TCB可以分配给任务了。
5、函数主体在os_task.c中
1、主要作用:建立一个新任务。与OSTaskCreate()不同的是,OSTaskCreateExt()允许用户设置更多的细节内容。任务的建立可以在多任务环境启动之前,也可以在正在运行的任务中建立,但中断处理程序中不能建立新任务。,且不
2、函数原型:NT8U OSTaskCreateExt (void (*task)(void *pd),void *pdata, OS_STK *ptos,INT8U prio ,INT16U id, OS_STK *pbos,INT32U stk_size,void *pext,INT16U opt)
3、参数说明:
void (*task)(void *pd):指向任务代码首地址的指针。
void *pdata:指向一个数据结构,该结构用来在建立任务时向任务传递参数。
OS_STK *ptos: 指向堆栈任务栈顶的指针
INT8U prio:任务优先级
INT16U id: 任务ID,2.52版本,无实际作用,保留作为扩展用
OS_STK *pbos: 指向堆栈底部的指针,用于OSTaskStkChk()函数
INT32U stk_size:指定任务堆栈的大小,由OS_STK类型决定
void *pext:定义数据结构的指针,作为TCB的扩展
INT16U opt) :存放于任务操作相关的信息,详见uCOS-II.H
4、返回值说明:
OS_NO_ERR:函数调用成功。
OS_PRIO_EXIST:具有该优先级的任务已经存在。
OS_PRIO_INVALID:参数指定的优先级大于OS_LOWEST_PRIO。
OS_NO_MORE_TCB:系统中没有OS_TCB可以分配给任务了。
5、函数主体在os_task.c中
1、主要作用: 无条件挂起一个任务。调用此函数的任务也可以传递参数 OS_PRIO_SELF,挂起调用任务本身。当前任务挂起后,只有其他任务才能唤醒被挂起的任务。任务挂起后,系统会重新进行任务调度,运行下一个优先级最高的就绪任务。唤醒挂起任务需要调用函数OSTaskResume()。任务的挂起是可以叠加到其他操作上的。例如,任务被挂起时正在进行延时操作,那么任务的唤醒就需要两个条件:延时的结束以及其他任务的唤醒操作。又如,任务被挂起时正在等待信号量,当任务从信号量的等待对列中清除后也不能立即运行,而必须等到被唤醒后。
2、函数原型:INT8U OSTaskSuspend(INT8U prio);
3、参数说明:prio为指定要获取挂起的任务优先级,也可以指定参数 OS_PRIO_SELF,挂起任务本身。此时,下一个优先级最高的就绪任务将运行。
4、返回值说明:
OS_NO_ERR:函数调用成功。
OS_TASK_SUSPEND_IDLE:试图挂起μC/OS-II中的空闲任务(Idle task)。此为非法操作。
OS_PRIO_INVALID:参数指定的优先级大于 OS_LOWEST_PRIO 或没有设定 OS_PRIO_SELF 的值。
OS_TASK_SUSPEND_PRIO:要挂起的任务不存在。
5、函数主体在os_task.c中
1、主要作用: 唤醒一个用 OSTaskSuspend() 函数挂起的任务。OSTaskResume() 也是唯一能“解挂”挂起任务的函数。
2、函数原型:INT8U OSTaskResume(INT8U prio);
3、参数说明:prio指定要唤醒任务的优先级。
4、返回值说明:
OS_NO_ERR:函数调用成功。
OS_TASK_RESUME_PRIO:要唤醒的任务不存在
OS_TASK_NOT_SUSPENDED:要唤醒的任务不在挂起状态。
OS_PRIO_INVALID:参数指定的优先级大于或等于OS_LOWEST_PRIO。
5、函数主体在os_task.c中
1、主要作用:调用该函数的任务将自己延时一段时间并执行一次任务调度,一旦规定的延时时间完成或有其它的任务通过调用OSTimeDlyResume()取消了延时,调用OSTimeDly()函数的任务马上进入就绪状态(前提是先将任务调度后执行的任务执行到程序尾,且调用OSTimeDly的任务此时优先级最高)。
2、函数原型:void OSTimeDly (INT16U ticks);
3、参数说明:ticks为需要延时的时钟节拍数;
4、返回值:无
5、函数主体在os_time.c中
1、主要作用:函数是以小时(H)、分(M)、秒(S)和毫秒(m)四个参数来定义延时时间的,函数在内部把这些参数转换为时钟节拍,再通过单次或多次调用OSTimeDly()进行延时和任务调度,所以延时原理和调用延时函数OSTimeDly()是一样的。调用 OSTimeDlyHMSM() 后,如果延时时间不为0,系统将立即进行任务调度。
2、函数原型:INT8U OSTimeDlyHMSM (INT8U hours,INT8U minutes,INT8U seconds,INT16U milli);
3、参数说明:
hours 为延时小时数,范围从0-255。
minutes 为延时分钟数,范围从0-59
seconds 为延时秒数,范围从0-59
milli 为延时毫秒数,范围从0-999
4、返回值说明:
OS_NO_ERR:函数调用成功。
OS_TIME_INVALID_MINUTES:参数错误,分钟数大于59。
OS_TIME_INVALID_SECONDS:参数错误,秒数大于59。
OS_TIME_INVALID_MILLI:参数错误,毫秒数大于999。
OS_TIME_ZERO_DLY:四个参数全为0。
5、函数主体在os_time.c中
1、主要作用:任务在延时之后,进入阻塞态。当延时时间到了就从阻塞态恢复到就绪态,可以被操作系统调度执行。但是,并非回到就绪态就只有这么一种可能,因为即便任务的延时时间没到,还是可以通过函数OSTimeDlyResume恢复该任务到就绪态的。另外,OSTimeDlyResume也不仅仅能恢复使用OSTimeDly或OSTimeDlyHMSM而延时的任务。对于因等待事件发生而阻塞的,并且设置了超时(timeout)时间的任务,也可以使用OSTimeDlyResume来恢复。对这些任务使用了OSTimeDlyResume,就好像已经等待超时了一样。但是,对于采用OSTaskSuspend挂起的任务,是不允许采用OSTimeDlyResume来恢复的。
2、函数原型:INT8U OSTimeDlyResume (INT8U prio)
3.参数说明:prio 被恢复任务的优先级
4、返回值:
OS_ERR_TASK_NOT_EXIST:任务优先级指针表中没有此任务
OS_NO_ERR:函数调用成功。
OS_ERR_PRIO_INVALID:参数指定的优先级大于或等于OS_LOWEST_PRIO。
OS_ERR_TIME_NOT_DLY:任务没有被延时阻塞
5、函数主体在os_time.c中
如果我们想对一个公共资源进行互斥访问,例如:如果我们想让两个任务Task1和Task2都可以调用Fun()函数,但不能同时调用,最好定义信号量:Semp = OSSemCreate(1),同理在各自的任务中都需要调用OSSemPend(Semp,0,&err)请求此信号量,如果可用,则调用Fun(),然后再用OSSemPost(Semp)释放该信号量。这里就实现了一个资源的互斥访问。
(注:初始化OSSemCreate(1),那么一个任务中有OSSemPend,那么可以执行,执行之后cnt==0,其他任务的OSSemPend无法获得sem,只能等待,除非任务一有OSSemPost,使其cnt++,这样其他任务的Pend可以执行。)
同理,如果一个任务要等待n个事件发生后才能执行,则应定义为Semp = OSSemCreate(n)。然后在这n个任务分别运行时调用OSSemPost(Semp),直到这n个事件均发生后,这个任务才能运行。
OSSemCreate(cnt)赋初始值cnt,OSSemPend一次,cnt– 一次,OSSemPost一次,cnt++一次。
下面对信号量函数做简要介绍:
1、主要作用:该函数建立并初始化一个信号量,信号量的作用如下:
允许一个任务和其他任务或者中断同步
取得设备的使用权
标志事件的发生
2、函数原型:OS_EVENT *OSSemCreate(INT16U value);
3、参数说明:value 参数是所建立的信号量的初始值,可以取0到65535之间的任何值。
4、返回值:OSSemCreate() 函数返回指向分配给所建立的信号量的控制块的指针。如果没有可用的控制块,OSSemCreate() 函数返回空指针。
5、函数主体在os_sem.c中
1、主要作用: 该函数用于任务试图取得设备的使用权、任务需要和其他任务或中断同步、任务需要等待特定事件的发生的场合。如果任务调用OSSemPend() 函数时,信号量的值大于零,OSSemPend() 函数递减该值。如果调用时信号量值等于零,OSSemPend() 函数将任务加入该信号量的等待队列。OSSemPend() 函数挂起当前任务直到其他的任务或中断设置信号量或超出等待的预期时间。如果在预期的时钟节拍内信号量被设置,μC/OS-Ⅱ默认让最高优先级的任务取得信号量并回到就绪状态。一个被OSTaskSuspend() 函数挂起的任务也可以接受信号量,但这个任务将一直保持挂起状态直到通过调用OSTaskResume() 函数恢复该任务的运行。
2、函数原型:void OSSemPend ( OS_EVNNT *pevent, INT16U timeout, int8u *err );
3、参数说明:
pevent 是指向信号量的指针。该指针的值在建立该信号量时可以得到。(参考OSSemCreate() 函数)。
timeout 允许一个任务在经过了指定数目的时钟节拍后还没有得到需要的信号量时恢复就绪状态。如果该值为零表示任务将持续地等待信号量,最大的等待时间为65535个时钟节拍。这个时间长度并不是非常严格的,可能存在一个时钟节拍的误差。
err 是指向包含错误码的变量的指针,返回的错误码可能为下述几种:
OS_NO_ERR :信号量不为零。
OS_TIMEOUT :信号量没有在指定数目的时钟周期内被设置。
OS_ERR_PEND_ISR :从中断调用该函数。虽然规定了不允许从中断调用该函数,但μC/OS-Ⅱ仍然包含了检测这种情况的功能。
OS_ERR_EVENT_TYPE :pevent 不是指向信号量的指针。
4、返回值:无
5、函数主体在os_sem.c中
1、主要作用: 该函数用于设置指定的信号量。如果指定的信号量是零或大于零,OSSemPost() 函数递增该信号量的值并返回。如果有任何任务在等待该信号量,则最高优先级的任务将得到信号量并进入就绪状态。任务调度函数将进行任务调度,决定当前运行的任务是否仍然为最高优先级的就绪任务。
2、函数原型:INT8U OSSemPost(OS_EVENT *pevent);
3、参数说明:pevent 是指向信号量的指针。该指针的值在建立该信号量时可以得到。(参考OSSemCreate() 函数)。
4、返回值:
OS_NO_ERR :信号量被成功地设置
OS_SEM_OVF :信号量的值溢出
OS_ERR_EVENT_TYPE :pevent 不是指向信号量的指针
5、函数主体在os_sem.c中
消息队列是µC/OS-II中另一种通讯机制,它可以使一个任务或者中断服务子程序向另一个任务发送以指针方式定义的变量。因具体的应用有所不同,每个指针指向的数据结构变量也有所不同。为了使用µC/OS-II的消息队列功能,需要在OS_CFG.H 文件中,将OS_Q_EN常数设置为1,并且通过常数OS_MAX_QS来决定µC/OS-II支持的最多消息队列数。
1、主要作用:该函数用于建立一个消息队列。任务或中断可以通过消息队列向一个或多个任务发送消息。消息的含义是和具体的应用密切相关的。
2、函数原型:OS_EVENT *OSQCreate (void **start, INT8U size);
3、参数说明:start 是消息内存区的首地址,消息内存区是一个指针数组
size 是消息内存区的大小。
4、返回值说明:OSQCreate() 函数返回一个指向消息队列控制块的指针。如果没有空闲的控制块,OSQCreate() 函数返回空指针
5、函数主体在os_q.c中
1、主要作用: 该函数用于任务等待消息。消息通过中断或任务发送给需要的任务。消息是一个指针变量,在不同的应用中消息的具体含义不同。如果调用 OSQPend() 函数时队列中已经存在消息,那么该消息被返回给 OSQPend() 函数的调用者,该消息同时从队列中清除。如果调用 OSQPend() 函数时队列中没有消息,OSQPend() 函数挂起调用任务直到得到消息或超出定义的超时时间。如果同时有多个任务等待同一个消息,μC/OS-Ⅱ默认最高优先级的任务取得消息。一个由 OSTaskSuspend() 函数挂起的任务也可以接受消息,但这个任务将一直保持挂起状态直到通过调用 OSTaskResume() 函数恢复任务的运行。
2、函数原型:void *OSQPend (OS_EVENT *pevent, INT16U timeout, INT8U *err);
3、参数说明:pevent 是指向消息队列的指针,该指针的值在建立该队列时可以得到。(参考 OSQCreate() 函数)。 timeout 允许一个任务以指定数目的时钟节拍等待消息。超时后如果还没有得到消息则恢复成就绪状态。如果该值设置成零则表示任务将持续地等待消息,最大的等待时间为65535个时钟节拍。这个时间长度并不是非常严格的,可能存在一个时钟节拍的误差。
err 是指向包含错误码的变量的指针。OSQPend() 函数返回的错误码可能为下述几种:
* OS_NO_ERR :消息被正确地接受。
* OS_TIMEOUT :消息没有在指定的时钟周期数内接收到消息。
* OS_ERR_PEND_ISR :从中断调用该函数。虽然规定了不允许从中断中调用该函数,但μC/OS-Ⅱ仍然包含了检测这种情况的功能。
* OS_ERR_EVENT_TYPE :pevent 不是指向消息队列的指针。
4、返回值说明:OSQPend() 函数返回取得的消息并将 *err 置为 OS_NO_ERR。如果没有在指定数目的时钟节拍内接受到消息,OSQPend() 函数返回空指针并将 *err 设置为 OS_TIMEOUT。
5、函数主体在os_q.c中
INT8U OSQPost (OS_EVENT *pevent, void *msg)
发送一个消息到消息队列 FIFO模式
INT8U OSQPostFront (OS_EVENT *pevent, void *msg)
发送一个消息到消息队列 FIFO模式
void *OSQAccept (OS_EVENT *pevent)
无等待地从消息队列中得到一个消息
INT8U OSQFlush (OS_EVENT *pevent)
清空一个消息队列
INT8U OSQQuery (OS_EVENT *pevent, OS_Q_DATA *pdata)
查询一个消息队列的当前状态
使用消息队列的通常步骤为:
1)定义指针: OS_EVENT *proSQ
定义指针数组: void *start[4]
2) 在主程序中建立信号列队:*proSQ = OSQCreate(start,4);
3)在任务中等待消息列队中得到消息或者给消息列队发送消息
在ANSI C中可以用malloc()和free()两个函数动态地分配内存和释放内存。但是,在嵌入式实时操作系统中,多次这样做会把原来很大的一块连续内存区域,逐渐地分割成许多非常小而且彼此又不相邻的内存区域,也就是内存碎片。由于这些碎片的大量存在,使得程序到后来连非常小的内存也分配不到。另外,由于内存管理算法的原因,malloc()和free()函数执行时间是不确定的。
在μC/OS-II中,操作系统把连续的大块内存按分区来管理。每个分区中包含有整数个大小相同的内存块。利用这种机制,μC/OS-II 对malloc()和free()函数进行了改进,使得它们可以分配和释放固定大小的内存块。这样一来,malloc()和free()函数的执行时间也是固定的了。
在一个系统中可以有多个内存分区。这样,用户的应用程序就可以从不同的内存分区中得到不同大小的内存块。但是,特定的内存块在释放时必须重新放回它以前所属于的内存分区。显然,采用这样的内存管理算法,上面的内存碎片问题就得到了解决。
内存控制块
为了便于内存的管理,在μC/OS-II中使用内存控制块(memory control blocks)的数据结构来跟踪每一个内存分区,系统中的每个内存分区都有它自己的内存控制块。
内存控制块的定义如下:
typedef struct {
void *OSMemAddr; //指向内存分区起始地址的指针
void *OSMemFreeList; //是指向下一个空闲内存控制块或者下一个空闲的内存块的指针
INT32U OSMemBlkSize; //是内存分区中内存块的大小
INT32U OSMemNBlks; //内存分区中总的内存块数量
INT32U OSMemNFree; //内存分区中当前可以得空闲内存块数量
} OS_MEM;
如果要在μC/OS-II中使用内存管理,需要在OS_CFG.H文件中将开关量OS_MEM_EN设置为1。这样μC/OS-II 在启动时就会对内存管理器进行初始化[由OSInit()调用OSMemInit()实现]。常数OS_MAX_MEM_PART(见文件OS_CFG.H)定义了最大的内存分区数,该常数值最小应为2。
1 主要作用: 该函数建立并初始化一个用于动态内存分配的区域,该内存区域包含指定数目的、大小确定的内存块。应用可以动态申请这些内存块并在用完后将其释放回这个内存区域。该函数的返回值就是指向这个内存区域控制块的指针,并作为OSMemGet(),OSMemPut(),OSMemQuery() 等相关调用的参数。
2函数原型:OS_MEM *OSMemCreate( void *addr, INT32U nblks, INT32U blksize, INT8U *err );
3参数说明:addr 建立的内存区域的起始地址。可以使用静态数组或在系统初始化时使用 malloc() 函数来分配这个区域的空间。
nblks 内存块的数目。每一个内存区域最少需要定义两个内存块。
blksize 每个内存块的大小,最小应该能够容纳一个指针变量。
err 是指向包含错误码的变量的指针。Err可能是如下几种情况:
OS_NO_ERR :成功建立内存区域。
OS_MEM_INVALID_ADDR :非法地址,即地址为空指针。
OS_MEM_INVALID_PART :没有空闲的内存区域。
OS_MEM_INVALID_BLKS :没有为内存区域建立至少两个内存块。
OS_MEM_INVALID_SIZE :内存块大小不足以容纳一个指针变量。
4返回值:
OSMemCreate() 函数返回指向所创建的内存区域控制块的指针。如果创建失败,函数返回空指针。
1 主要作用: 该函数用于从内存区域分配一个内存块。用户程序必须知道所建立的内存块的大小,并必须在使用完内存块后释放它。可以多次调用 OSMemGet() 函数。它的返回值就是指向所分配内存块的指针,并作为 OSMemPut() 函数的参数。
2函数原型:void *OSMemGet(OS_MEM *pmem, INT8U *err);
3参数说明:pmem 是指向内存区域控制块的指针,可以从 OSMemCreate() 函数的返回值中得到。
err 是指向包含错误码的变量的指针。Err可能是如下情况:
OS_NO_ERR :成功得到一个内存块。
OS_MEM_NO_FREE_BLKS :内存区域中已经没有足够的内存块。
4返回值:
OSMemGet() 函数返回指向所分配内存块的指针。如果没有可分配的内存块,OSMemGet() 函数返回空指针。
1 主要作用:该函数用于释放一个内存块,内存块必须释放回它原先所在的内存区域,否则会造成系统错误。
2函数原型:INT8U OSMemPut (OS_MEM *pmem, void *pblk);
3参数说明:pmem 是指向内存区域控制块的指针,可以从 OSMemCreate() 函数的返回值中得到。
pblk 是指向将被释放的内存块的指针。
4返回值:
OSMemPut() 函数的返回值为下述之一:
OS_NO_ERR :成功释放内存块
OS_MEM_FULL :内存区域已满,不能再接受更多释放的内存块。这种情况说明用户程序出现了错误,释放了多于用 OSMemGet() 函数得到的内存块。
1 主要作用:该函数用于得到内存区域的信息。
2函数原型:INT8U OSMemQuery(OS_MEM *pmem, OS_MEM_DATA *pdata);
3参数说明:pmem 是指向内存区域控制块的指针,可以从 OSMemCreate() 函数的返回值中得到。
pdata 是一个指向 OS_MEM_DATA 数据结构的指针,该数据结构包含了以下的域:
void OSAddr; /* 指向内存区域起始地址的指针 */
void OSFreeList; /* 指向空闲内存块列表起始地址的指针 */
INT32U OSBlkSize; /* 每个内存块的大小 */
INT32U OSNBlks; /* 该内存区域中的内存块总数 */
INT32U OSNFree; /* 空闲的内存块数目 */
INT32U OSNUsed; /* 已使用的内存块数目 */
1、 uC/OS II 函数说明 之–OSTaskCreate()与OSTaskCreateExt()
2、uC-OS-II_中文手册