一、OS的初始化与启动
- 1.OS初始化,初始化各种内核对象和全局变量
函数原型:void OSInit (OS_ERR *p_err)
参数:p_err,用于返回错误码
返回值:无
它是第一个执行的函数。
- 2.启动OS,创建任务后调用
函数原型:void OSStart (OS_ERR *p_err)
参数:p_err,用于返回错误码
返回值:无
二、任务的管理
- 1.创建任务
void OSTaskCreate (OS_TCB *p_tcb,//类似于线程id,控制任务
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,//空出10%的栈空间给到堆栈检测函数使用,反过来说,当前任务只能使用90%栈空间
CPU_STK_SIZE stk_size,//任务栈的大小,以字(32位)为单位
OS_MSG_QTY q_size,//任务内消息队列的大小,若不使用,写0
OS_TICK time_quanta,//时间片轮转调度算法,若不使用,写0(这是时间片的长度)
void *p_ext,//提供额外存储空间用于存储浮点运算单元寄存器,若不提供,写NULL
OS_OPT opt,//在创建任务的时候,提供额外操作,如果不使用,写OS_OPT_TASK_NONE
OS_ERR *p_err)//返回错误码,没有错误的就返回OS_ERR_NONE
例如,创建任务1
//task control block,任务1控制块
OS_TCB Task1_TCB;
//任务1函数,类似线程函数
void task1(void *parg);
//任务1的任务堆栈,大小为128字,即512字节
CPU_STK task1_stk[128];
//创建任务1
OSTaskCreate( (OS_TCB *)&Task1_TCB,//任务控制块,等同于线程id
(CPU_CHAR *)"Task1",//任务的名字,可以自定义
(OS_TASK_PTR)task1,//任务函数,等同于线程函数
(void *)0,//给任务函数传递参数
(OS_PRIO)5,//任务优先级,数值越小,优先级越高
(CPU_STK *)task1_stk,//任务堆栈基地址
(CPU_STK_SIZE)128/10,//任务堆栈深度限位,用到这个位置,任务不能再继续使用
(CPU_STK_SIZE)128,//任务堆栈大小
(OS_MSG_QTY)0,//任务内消息队列的大小,不使用,写0
(OS_TICK)0,//时间片轮转调度算法,若不使用,写0(默认时间片长度)
(void *)0,//提供额外存储空间用于存储浮点运算单元寄存器,若不提供,写NULL
(OS_OPT)OS_OPT_TASK_NONE,//在创建任务的时候,提供额外操作,如果不使用,写OS_OPT_TASK_NONE
&err//返回错误码,没有错误的就返回OS_ERR_NONE
);
注意:
创建任务特别检查传递的数组的大小是否空间充足,因为它是作为任务的栈空间使用,若空间不足,会导致程序不能执行,直接跑到HardFault_Handler这个函数。
原因如下:
1)任务里申请大空间的局部变量,例如数组、结构体......,将会占用大量的栈空间
2)任务里包含很多复杂的函数,将会占用大量的栈空间
3)若任务需要用到浮点数运算,要特别注意,出现HardFault_Handler可以尝试分配大一点任务栈,或则提供额外存储空间用于存储浮点运算单元寄存器
- 2.任务挂起
暂停任务的执行
void OSTaskSuspend (OS_TCB *p_tcb,
OS_ERR *p_err)
参数: p_tcb,类似于线程id,控制任务
p_err,返回错误码,没有错误的就返回OS_ERR_NONE
返回值:无
用途:1)如果当前任务不是经常要执行的,可以挂起
2)保护共享资源,可以挂起
- 3.任务恢复执行
void OSTaskResume (OS_TCB *p_tcb,
OS_ERR *p_err)
参数:p_tcb,类似于线程id,控制任务
p_err,返回错误码,没有错误的就返回OS_ERR_NONE
返回值:无
4.任务的删除
void OSTaskDel (OS_TCB *p_tcb,
OS_ERR *p_err)
参数:p_tcb,类似于线程id,控制任务
p_err,返回错误码,没有错误的就返回OS_ERR_NONE
返回值:无
用途:专门用于初始化硬件,一般来说,硬件只做一次初始化,完毕后可将该任务删除。
三、中断代码编写
void USART1_IRQHandler(void)
{
uint8_t d=0;
//进入中断,告诉UCOS,要停止任务调度,因为中断处理是一个原子过程,不可拆分 bug kernel:interrupt atom
//中断里面不能再有任务调度
OSIntEnter();
//添加中断处理代码
.....
//退出中断,告诉UCOS,准备可以进行任务调度
OSIntExit();
}
四、互斥锁
互斥锁常用于任务之间的共享资源(任务之间访问到相同的函数、相同的全局变量)访问,当某个任务得到互斥锁后,就可以访问共享资源,其他任务等待该任务释放互斥锁才能进行访问。
void task(void *parg)
{
while(1)
{
加锁
访问共享资源
解锁(立即)
.....
加锁
访问共享资源
解锁(立即)
....
}
}
- 1.创建互斥锁
void OSMutexCreate (OS_MUTEX *p_mutex,
CPU_CHAR *p_name,
OS_ERR *p_err)
参数:p_mutex,互斥锁对象
p_name,互斥锁名字
p_err,返回错误码,没有错误的就返回OS_ERR_NONE
返回值:无
- 2.等待互斥锁,若等待成功,则锁定共享资源
void OSMutexPend (OS_MUTEX *p_mutex,
OS_TICK timeout,
OS_OPT opt,
CPU_TS *p_ts,
OS_ERR *p_err)
参数:p_mutex,互斥锁对象
timeout,超时时间,默认写0,一直等待
opt,设置当前等待互斥锁的阻塞方式,默认写OS_OPT_PEND_BLOCKING,阻塞等待
p_ts,用于记录等待互斥锁花了多长时间,默认写NULL,不记录。
p_err,返回错误码,没有错误的就返回OS_ERR_NONE
- 3.释放互斥锁,解锁
void OSMutexPost (OS_MUTEX *p_mutex,
OS_OPT opt,
OS_ERR *p_err)
参数:p_mutex,互斥锁对象
opt,释放互斥锁后希望其他等待锁的任务(最高优先级就绪)得到立即执行,可以填写这个参数OS_OPT_POST_NONE。
若使用了OS_OPT_POST_NO_SCHED这个参数,得到互斥锁的任务不会立即执行,会等到当前任务让出cpu才会执行。
p_err,返回错误码,没有错误的就返回OS_ERR_NONE
五、信号量
-
信号量常用于任务的同步,通过该信号,就能够控制某个任务的执行,这个信号具有计数值,因此,可以称为计数信号量。
-
信号量的P、V操作,P表示申请一个资源,每次P操作使信号量减1,V是释放一个资源,每次V操作使信号量加1。
-
信号量表示的是当前可用的资源个数,当信号量为负时,申请资源的进程就只能等待了。
所以,信号量是负的多少,就表明有多少个进程申请了资源但无资源可用只能处于等待状态。 -
1.创建信号量
void OSSemCreate (OS_SEM *p_sem,
CPU_CHAR *p_name,
OS_SEM_CTR cnt,
OS_ERR *p_err)
参数:p_sem,信号量对象
p_name,信号量名字
cnt,信号量的初值
p_err,返回错误码,没有错误的就返回OS_ERR_NONE
- 2.等待信号量
OS_SEM_CTR OSSemPend (OS_SEM *p_sem,
OS_TICK timeout,
OS_OPT opt,
CPU_TS *p_ts,
OS_ERR *p_err)
参数:p_sem,信号量对象
timeout,超时时间,默认写0,一直等待
opt,设置当前等待互斥锁的阻塞方式,默认写OS_OPT_PEND_BLOCKING,阻塞等待
p_ts,用于记录等待互斥锁花了多长时间,默认写NULL,不记录。
p_err,返回错误码,没有错误的就返回OS_ERR_NONE
- 3.释放信号量
OS_SEM_CTR OSSemPost (OS_SEM *p_sem,
OS_OPT opt,
OS_ERR *p_err)
参数:p_sem,信号量对象
opt,信号量接收方
OS_OPT_POST_1,只释放信号量给最高优先级且就绪的任务,类似于UDP的点播
OS_OPT_POST_ALL,释放给所有等待信号量的任务,类似于UDP的广播
p_err,返回错误码,没有错误的就返回OS_ERR_NONE
六、消息队列
消息队列常用于数据的传输,等待消息队列成功后,得到的是消息内容指针(也就是数据的首地址)。
- 1.创建消息队列
void OSQCreate (OS_Q *p_q,
CPU_CHAR *p_name,
OS_MSG_QTY max_qty,
OS_ERR *p_err)
参数:p_q,消息队列对象
p_name,消息队列的名字
max_qty,消息队列支持多少条消息
p_err,返回错误码,没有错误的就返回OS_ERR_NONE
- 2.等待消息
void *OSQPend (OS_Q *p_q,
OS_TICK timeout,
OS_OPT opt,
OS_MSG_SIZE *p_msg_size,
CPU_TS *p_ts,
OS_ERR *p_err)
参数:p_q,消息队列对象
timeout,超时时间,默认写0,一直等待
opt,设置当前等待互斥锁的阻塞方式,默认写OS_OPT_PEND_BLOCKING,阻塞等待
p_msg_size,消息的大小(用于获取消息的带下)
p_ts,用于记录等待消息花了多长时间,默认写NULL,不记录。
p_err,返回错误码,没有错误的就返回OS_ERR_NONE
返回值:NULL,没有接收到消息
非NULL,指向消息内容
注意:等待消息且使用完毕后,需将消息内容清空,否则在后续的使用可能出现问题。
- 3.发送消息
void OSQPost (OS_Q *p_q,
void *p_void,
OS_MSG_SIZE msg_size,
OS_OPT opt,
OS_ERR *p_err)
参数:p_q,消息队列对象
p_void,消息的内容
msg_size,消息的大小
opt,OS_OPT_POST_FIFO+OS_OPT_POST_ALL(发送给所有等待消息的任务)或许OS_OPT_POST_FIFO
p_err,返回错误码,没有错误的就返回OS_ERR_NONE
七、事件标志组
事件标志组是专门管理标志位,一个事件标志组可以管理32个标志位。
在前后台系统(裸机模式下),经常会用到标志位,查询标志位是否置位,有明显的缺点,CPU得不到休息,会一直工作查询,增加CPU的功耗。
int main(void)
{
while(1)
{
if(flag1)
{
}
if(flag2)
{
}
if(flag3)
{
}
}
}
- 1.创建事件标志组
void OSFlagCreate (OS_FLAG_GRP *p_grp,
CPU_CHAR *p_name,
OS_FLAGS flags,
OS_ERR *p_err)
参数:p_grp,事件标志组对象
p_name,事件标志组的名字
flags,事件标志组里所有标志位的初值,默认写0
p_err,返回错误码,没有错误的就返回OS_ERR_NONE
- 2.等待事件标志组
OS_FLAGS OSFlagPend (OS_FLAG_GRP *p_grp,
OS_FLAGS flags,
OS_TICK timeout,
OS_OPT opt,
CPU_TS *p_ts,
OS_ERR *p_err)
参数:p_grp,事件标志组对象
flags,要等待哪些标志位;0x01<==>0000 0001,则等待bit0;0x05<==>0000 0101,则等待bit0和bit2;0x83<==>1000 0011,则等待bit0、bit1、bit7。
timeout,超时时间,默认写0,一直等待
opt,默认写以下格式
OS_OPT_PEND_FLAG_SET_ANY + OS_OPT_PEND_FLAG_CONSUME+OS_OPT_PEND_BLOCKING
OS_OPT_PEND_FLAG_SET_ANY,等待任意一个标志位置位
OS_OPT_PEND_FLAG_CONSUME,等待任意一个标志位成功后,就自动将其清零
OS_OPT_PEND_BLOCKING,阻塞等待
p_ts,用于记录等待事件花了多长时间,默认写NULL,不记录。
p_err,返回错误码,没有错误的就返回OS_ERR_NONE
返回值:返回等待成功的标志组的所有标志位。
- 3.设置事件标志组
OS_FLAGS OSFlagPost (OS_FLAG_GRP *p_grp,
OS_FLAGS flags,
OS_OPT opt,
OS_ERR *p_err)
参数:p_grp,事件标志组对象
flags,结合opt参数一起使用。设置/清零哪些标志位。同上flags
opt,
OS_OPT_POST_FLAG_SET,对应的bit置位
OS_OPT_POST_FLAG_CLR,对应的bit清零
p_err,返回错误码,没有错误的就返回OS_ERR_NONE
八、软件定时器
内核提供了一个模拟定时器的机制,类似于任务,但是占用资源少,只能做一些简单的定时控制。
在软件定时器,不能添加时间管理函数、阻塞等待函数(等待互斥锁/信号量/事件标志组/消息队列)。
- 1.创建软件定时器
void OSTmrCreate (OS_TMR *p_tmr,
CPU_CHAR *p_name,
OS_TICK dly,
OS_TICK period,
OS_OPT opt,
OS_TMR_CALLBACK_PTR p_callback,
void *p_callback_arg,
OS_ERR *p_err)
参数:p_tmr,软件定时器对象
p_name,软件定时器的名字
dly,启动定时器后,延迟多长时间执行,默认隐含dly*10ms
period,定时周期,默认隐含period*10ms
opt
OS_OPT_TMR_ONE_SHOT,软件定时器执行一遍
OS_OPT_TMR_PERIODIC,软件定时器周期性执行
p_callback,软件定时器执行的回调函数
void p_callback (OS_TMR *p_tmr, void *p_arg);
p_callback_arg,传递参数给软件定时器的回调函数
p_err,返回错误码,没有错误的就返回OS_ERR_NONE
- 2.启动软件定时器
CPU_BOOLEAN OSTmrStart (OS_TMR *p_tmr,
OS_ERR *p_err)
参数:p_tmr,软件定时器对象
p_err,返回错误码,没有错误的就返回OS_ERR_NONE
返回值:DEF_TRUE is the timer was started
DEF_FALSE if not or upon an error
注意:当前p_callback会1秒执行一遍,当调用睡眠2秒和等待事件标志组,该函数还是1秒被调用一次。
void p_callback(OS_TMR *p_tmr, void *p_arg)
{
OS_ERR err;
OS_FLAGS flags;
printf("timer_callback ...
");
//无效
OSFlagPend(&g_flag_grp,0x03,0, OS_OPT_PEND_FLAG_SET_ANY+OS_OPT_PEND_FLAG_CONSUME+OS_OPT_PEND_BLOCKING,NULL,&err);
//无效
delay_ms(2000);
}
九、等待多个内核对象
内核对象,可以是信号量、消息队列、互斥锁、事件标志组等。
- 等待多个内核对象,只能等待信号量和消息队列。
//This function only allows you to pend on semaphores and/or message queues.
OS_OBJ_QTY OSPendMulti (OS_PEND_DATA *p_pend_data_tbl,
OS_OBJ_QTY tbl_size,
OS_TICK timeout,
OS_OPT opt,
OS_ERR *p_err)
参数:p_pend_data_tbl,内核对象的数组
tbl_size,内核对象的数目
timeout,超时时间,默认写0,一直等待
opt,设置当前等待多个内核对象的阻塞方式,默认写OS_OPT_PEND_BLOCKING,阻塞等待
p_err,返回错误码,没有错误的就返回OS_ERR_NONE
返回值:>0,就绪内核对象的数目
=0,超时或发生错误
注意:使用上述函数,确保OS_CFG_Q_EN、OS_CFG_SEM_EN、OS_CFG_PEND_MULTI_EN宏定义开关有效,并可在os_cfg.h文件找到。
- 等待多个内核对象示例:
//1.修改os_cfg.h文件中如下宏,使用等待多个内核对象
//#define OS_CFG_PEND_MULTI_EN 0u /* Enable (1) or Disable (0) code generation for multi-pend feature */
#define OS_CFG_PEND_MULTI_EN 1u /* Enable (1) or Disable (0) code generation for multi-pend feature */
//2.创建任务块
......
//3.创建信号量对象、消息队列对象
static OS_SEM g_sem;
OS_Q g_queue;
//创建内核对象数组
OS_PEND_DATA g_pend_tb1[2];
//4.创建任务,用于测试
......
//5.创建信号量、创建消息队列
OSSemCreate(&g_sem, "sem", 0, &err);
OSQCreate(&g_queue, "queue", 32, &err);
void task1(void *parg)
{
OS_ERR err;
OS_OBJ_QTY obj_num = 0;
//6.初始化等待多个内核对象数组
g_pend_tb1[0].PendObjPtr = (OS_PEND_OBJ *)&g_sem;
g_pend_tb1[1].PendObjPtr = (OS_PEND_OBJ *)&g_queue;
printf("task1 is create ok
");
while(1)
{
//7.等待多个内核对象,并进行分析
obj_num = OSPendMulti(g_pend_tb1,/
2,//等待两个内核对象
0,//一直等待
OS_OPT_PEND_BLOCKING,//阻塞等待
&err);
if(obj_num)
{
if(g_pend_tb1[0].RdyObjPtr == (OS_PEND_OBJ *)&g_sem)
{
printf("sem get.
");
}
if(g_pend_tb1[1].RdyObjPtr == (OS_PEND_OBJ *)&g_queue)
{
printf("message: %s Length: %d
", (char *)g_pend_tb1[1].RdyMsgPtr, g_pend_tb1[1].RdyMsgSize);
//不需要了,则清空已经读取的数据
memset(g_pend_tb1[1].RdyMsgPtr, 0 , g_pend_tb1[1].RdyMsgSize);
}
}
//delay_ms(1000);
}
}
理想代码框架:
- 1.一个任务管理一个硬件
- 2.任务间数据传输使用消息队列
- 3.任务间共享资源访问使用互斥锁
- 4.任务的同步使用信号量
- 5.标志位的管理使用事件标志组