stm32-ucos使用
计算机操作系统
计算机只由硬件构成的叫做“裸机“,不能工作,必须有软件
多任务
把一个大任务分解为几个小任务,那在一个任务需要等待I/O时就可以交出CPU的使用权去运行其他的任务,可以极大的提高CPU的利用效率。
内核类型
可剥夺型内核
总是运行优先级别最高的任务,即使CPU正在运行某一个低优先级的任务,当高优先级的任务准备就绪时,就会剥夺低优先级的任务的CPU的使用权。
不可剥夺型内核
总是优先级别高的任务最先获得CPU的使用权,要求每个任务都能主动放弃CPU的使用权。
任务切换时间
多任务系统的任务之间的切换是需要时间的。操作系统的任务调度器就是做这项工作的。调度器在进行任务切换时需要一段时间,这段时间的长短也影响系统实时性,任务调度器进行任务切换所用的时间不能受到应用程序中其他因素(任务数目等)的影响。
终端延时
外部事件的发生会以中断申请信号的形式通知CPU,然后才运行中断服务程序来处理该事件,自CPU响应中断到CPU转向中断服务程序之间所用的时间叫做终端延时,也影响系统的实时性。
ucosii使用
用户应用程序的结构
1
2
3
4
5
6
7
8
9void task1(void *pdata);
void task2(void* pdata);
...
void main()
{
OSInit(); // 初始化ucosii
OSTaskCreate(task1,....); // 创建任务
OSStart(); // 启动任务
}使用
OSStart()
函数启动各项任务之后,任务就交给操作系统管理和调度了。系统任务
空闲任务
为了使CPU在没有用户任务可执行的时候有事可做,ucosii提供了一个空闲任务的系统任务,用户可以对空闲任务进行增加操作等,并且一个用户的应用程序必须使用这个空闲任务,不能用软件删除。
1
2
3
4
5
6
7
8
9
10
11
12
13void OS_TaskIdle(void* p_arg){
OS_CPU_SR cpu_sr = 0u; // 为CPU状态寄存器分配存储空间
p_arg = p_arg; // 不使用会出现警告,有些编译器出现报错
for (;;) {
OS_ENTER_CRITICAL();// 进入临界区,关闭所有中断
OSIdleCtr++;
OS_EXIT_CRITICAL(); // 开放中断
OSTaskIdleHook(); // 调用用户的任务的HOOK
}
}统计任务
系统的统计任务
OSTaskStat()
可以每秒计算一次CPU在单位时间内被使用的时间,并且把结果以百分比的形式存放在变量OSCPUUsage
中用户是否使用这个统计任务,可以根据应用程序的实际需要选择,如果要使用就必须把定义在头文件
OS_CFG.h
中的系统配置常数OS_TASK_STAT_EN
设置为1,并且在创建统计任务之前调用函数OSStatInit()
对统计任务初始化。
任务优先级别和优先权
ucosii的每个任务都必须具有一个唯一的优先级别。ucosii把任务的优先权分为64个优先级别,每一个优先级别都用一个数字表示。数字0表示任务的优先级别最高,数字越大则表示任务的优先级别越高。
通常程序的任务数小于64。用户可以在需要的时候在文件
OS_CFG.h
文件中通过给表示最低优先级别的常数OS_LOWEST_PRIO
赋值来定义程序中优先级别的数目。一旦被定义就意味着系统中可以使用的优先级别为0 ~ OS_LOWEST_PRIO
一共OS_LOWEST_PRIO+1个,也限制了程序的任务数量。系统总是把OS_LOWEST_PRIO
自动给空闲任务,把OS_LOWEST_PRIO-1
给统计任务(如果使用的话)。任务堆栈
任务堆栈的创建
为了方便定义任务堆栈,文件
OS_CPU.h
中专门定义了一个数据类型OS_STK
1
2
3
4typedef unsigned int OS_STK; // 类型长度为16位 2个字节
// 使用
OS_STK TaskStk[TASK_STK_SIZE]; // 定义一个数组作为任务堆栈创建任务函数
1
2
3
4INT8U OSTaskCreate (void (*task)(void *p_arg), // 指向任务的指针
void *p_arg, // 传递给任务的参数
OS_STK *ptos, // 任务堆栈栈顶的指针
INT8U prio) // 指定任务的优先级别参数
任务创建
程序通过函数
OSTaskCreate()
来创建一个任务函数对于创建任务的优先级别进行一系列判断,确定该优先级别合法并且未被使用之后,就调用函数
OSTaskStkInit
和OS_TCBInit
对任务堆栈和任务的控制块进行初始化,初始化成功之后,把任务计数器加一之外还要判断ucosii的核心是否处于运行状态(OSRunning == 1
),如果正在运行,调用OSSched()
进行任务调度。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48INT8U OSTaskCreate (void (*task)(void *p_arg),
void *p_arg,
OS_STK *ptos,
INT8U prio)
{
OS_STK *psp;
INT8U err;
OS_CPU_SR cpu_sr = 0u;
if (OSSafetyCriticalStartFlag == OS_TRUE) {
OS_SAFETY_CRITICAL_EXCEPTION();
return (OS_ERR_ILLEGAL_CREATE_RUN_TIME);
}
if (prio > OS_LOWEST_PRIO) { /* Make sure priority is within allowable range */
return (OS_ERR_PRIO_INVALID);
}
OS_ENTER_CRITICAL();
if (OSIntNesting > 0u) { /* Make sure we don't create the task from within an ISR */
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_CREATE_ISR);
}
if (OSTCBPrioTbl[prio] == (OS_TCB *)0) { /* Make sure task doesn't already exist at this priority */
OSTCBPrioTbl[prio] = OS_TCB_RESERVED;/* Reserve the priority to prevent others from doing ... */
/* ... the same thing until task is created. */
OS_EXIT_CRITICAL();
psp = OSTaskStkInit(task, p_arg, ptos, 0u); /* Initialize the task's stack */
err = OS_TCBInit(prio, psp, (OS_STK *)0, 0u, 0u, (void *)0, 0u);
if (err == OS_ERR_NONE) {
if (OSRunning == OS_TRUE) { /* Find highest priority task if multitasking has started */
OS_Sched();
}
} else {
OS_ENTER_CRITICAL();
OSTCBPrioTbl[prio] = (OS_TCB *)0;/* Make this priority available to others */
OS_EXIT_CRITICAL();
}
return (err);
}
OS_EXIT_CRITICAL();
return (OS_ERR_PRIO_EXIST);
}信号量和事件标志
信号量
信号量是用于同步和互斥的工具。信号量的值只能是0或正整数。信号量的值表示当前可用的资源数。当进程占有一个资源时,信号量减1,当进程释放一个资源时,信号量加1。如果信号量的值已经为0,那么等待进程将会被阻塞,直到有一个进程释放了一个资源,将信号量的值加1。
创建信号量
1
OS_EVENT *OSSemCreate(INT16U cnt); //信号量计数器初始值
请求信号量
1
2
3
4
5
6
7// 请求失败进入等待状态
void OSSemPend (OS_EVENT *pevent, // 信号量指针
INT32U timeout, // 等待时间限制
INT8U *perr) // 错误信息
// 请求失败继续运行
INT16U OSSemAccept (OS_EVENT *pevent) // 信号量指针发送信号量
1
INT8U OSSemPost (OS_EVENT *pevent) // 信号量指针
删除信号量
1
2
3
4
5
6
7OS_EVENT *OSSemDel (OS_EVENT *pevent, // 信号量指针
INT8U opt, // 删除条件选项
INT8U *perr) // 错误信息
opt:
1. OS_DEL_NO_PEND 等待任务表中没有等待任务时删除
2. OSDEL_ALLWAYS 无论有无等待任务都删除
!只能在任务中删除信号量,不能在中断中删除查询状态
1
2
3
4
5
6
7
8INT8U OSSemQuery (OS_EVENT *pevent,
OS_SEM_DATA *p_sem_data) // 存储信号量状态的结构
typedef struct os_sem_data {
INT16U OSCnt; /* Semaphore count */
OS_PRIO OSEventTbl[OS_EVENT_TBL_SIZE]; /* List of tasks waiting for event to occur */
OS_PRIO OSEventGrp; /* Group corresponding to tasks waiting for event to occur */
} OS_SEM_DATA;
事件标志
事件标志是一种进程同步工具,它可以用于同步进程之间的时间。事件标志的值只能为0或1。当事件标志的值为0时,等待进程将会被阻塞。当事件标志的值为1时,等待进程将会被唤醒,继续运行。事件标志主要用于等待某一个事件的发生,例如等待一个条件成立,或者等待一个时间的到来。
时间管理
ucosii提供了以下三种时间管理方法:
任务延迟
函数
OSTimeDly()
可以使当前执行的任务进入延迟状态,延迟的时间是以系统时钟节拍为单位的。任务延迟直到事件标志被设置
函数
OSTimeDlyHMSM()
可以使当前执行的任务进入延迟状态,延迟时间是以时、分、秒、毫秒表示的,当设定的时间到达或事件标志被设置时,任务将会恢复执行。获取系统时钟
函数
OSTimeGet()
可以获取当前系统时钟的值,该值是以时钟节拍计数的。可以用于时间戳或超时计数等。
任务通信与同步
队列
队列是一种任务通信的机制,用于在多个任务之间传递数据。队列的大小是固定的,队列可以存储指定类型的数据,每个数据的大小必须相同。队列分为FIFO队列和优先级队列。
信号量和事件标志
信号量和事件标志是用于同步和互斥的工具。信号量的值只能是0或正整数。信号量的值表示当前可用的资源数。当进程占有一个资源时,信号量减1,当进程释放一个资源时,信号量加1。如果信号量的值已经为0,那么等待进程将会被阻塞,直到有一个进程释放了一个资源,将信号量的值加1。事件标志是一种进程同步工具,它可以用于同步进程之间的时间。事件标志的值只能为0或1。当事件标志的值为0时,等待进程将会被阻塞。当事件标志的值为1时,等待进程将会被唤醒,继续运行。事件标志主要用于等待某一个事件的发生,例如等待一个条件成立,或者等待一个时间的到来。
互斥量
互斥量是一种用于保护共享资源的机制。当一个任务获得了互斥量的所有权,其他任务将无法访问共享资源,只有当该任务释放互斥量的所有权时,其他任务才能再次访问共享资源。
中断处理
中断是一种异步事件,当外设发生中断时,CPU会暂停当前的任务,转而执行中断的处理程序。ucosii提供了中断服务例程(ISR)的支持,可以在ISR中使用ucosii的信号量、消息队列和事件标志等机制来完成各种任务。需要注意的是,中断服务程序应该尽量短小,以保证系统的实时性。
常见问题解决
任务堆栈不够用
如果任务的堆栈不够用,会导致任务运行异常,可以增加任务堆栈的大小或者减少任务中使用的局部变量的数量。
任务优先级别设置不当
如果任务的优先级别设置不当,会导致低优先级的任务无法得到CPU的使用权,从而导致系统异常。可以通过调整任务的优先级别来解决该问题。
队列、信号量和事件标志使用不当
如果队列、信号量和事件标志使用不当,会导致任务之间的同步和通信出现问题,可以通过检查代码来解决该问题。
任务挂起和恢复
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// 任务挂起函数
INT8U OSTaskSuspend(INT8U prio); // prio是挂起任务的优先级别
// 参数为 OS_PRIO_SELF 时为挂起自身
// 根据具体的情况返回报错信息
/*
OS_NO_ERR 无错误
OS_TASK_PEND_IDLE
OS_PRIO_INVALID
OS_TASK_SUSPEND_PRIO
*/
// 任务恢复函数
INT8U OSTaskResume (INT8U prio);
/*
OS_NO_ERR 无错误
OS_TASK_NOT_SUSPEND
OS_PRIO_INVALID
OS_TASK_RESUME_PRIO
*/任务调度器上锁和解锁
调度器上锁解锁函数
OSSchedLock (void)
,OSSchedUnlock (void)
用于禁止任务调度,让cpu执行当前任务保持cpu 的控制权,解锁后可以进行调度。实现原理很简单,对全局变量锁定嵌套计数器
OSLockNesting
进行操作,OSLockNesting
记录了上锁函数OSSchedLock (void)
的调用次数,OSSchedLock (void)
中对变量进行加一操作,OSSchedUnlock (void)
对变量进行减一操作,在引起任务调度的函数中进行判断,若变量OSLockNesting
的值大于0,说明任务调度上锁,进行任务调度的函数中进行判断if (OSLockNesting > 0u)
,后return
,不进行任务调度。但要满足一个条件,调用者不是中断服务子函数。上锁和解锁要成对使用!因为上锁以后系统就会被锁住,其他任务都不能运行,这些函数包括
OSFlagPend
、OSMboxPend
、OSMutexPend
、OSQPend
、OSSemPend
等。例如在延时函数中不再进行任务调度,当前有中断函数运行或任务锁,直接返回,当前有嵌套锁定,直接返回。
1
2
3
4if (OSIntNesting > 0u) { /* See if trying to call from an IS */
return;}
if (OSLockNesting > 0u) { /* See if called with scheduler locked */
return;}