STM32F103之UCOSII从移植到任务所有操作、中断、信号量、邮箱、信号量集、软件定时器。刚学完,还没空整理,移植可直接参考原子,文章大量内容也是从原子移植整理的

【STM32F103之UCOSII从移植到任务所有操作、中断、信号量、邮箱、信号量集、软件定时器。刚学完,还没空整理,移植可直接参考原子,文章大量内容也是从原子移植整理的】刚开始学得时候接直接找例程,移植库!移植整个程序就可以,UCOS入门很简单(能用),等把握整体运行之后再回去看各个任务怎么调度啊分配啊啥的,学习期间可以一边移植一边看着理论,知道哪些地方是有理论的,也知道哪里有理论自己没搞清楚,一边操作运行一边在现象中看理论!!!
这个文档只是我在自己程序里加的注释,自己看的,怕忘了,回头在整理,有问题的可以联系,一起学习。
/*
●--------分配任务优先级-----------------------------------------------
分配优先级时不要用使用0、1、2、3最高从4开始用,最低的四个也不要用,os_cfg.h准备了55个任务,够用了
●--------任务操作-----------------------------------------------
OSTaskSuspend(LED_R_TASK_PRIO); //挂起红色LED灯闪烁任务
OSTaskResume(LED_R_TASK_PRIO); //恢复红色LED灯闪烁任务
OSTaskDel(LED_R_TASK_PRIO); //删除红色LED灯闪烁任务!不可恢复
OSTaskChangePrio(KEY_SCAN_TASK_PRIO,5); //键盘扫描优先级调整至高于红色LED闪烁任务(5)
●----------中断---------------------------------------------
中断在usart的串口一里
void XX__IRQHandler(void)
{
OSIntEnter();
//巴拉巴拉中断服务函数
OSIntExit();
}
●-------统计任务--------------------------------------------
统计任务:
进入临界值之前调用
OSStatInit(); // 初始化统计任务
之后在需要的地方调用OSCPUUsage之类的变量获取CPU参数
printf(“CPU:%d\r\n”,OSCPUUsage);
●----信号量---------------------------------------------------
信号量由两部分组成:一个是信号量的计数值,它是一个 16 位的无符号整
数(0到65,535 之间); 另一个是由等待该信号量的任务组成的等待任务表。OS_EVENT里面的东西没有全部用上
用户要在 OS_CFG.H中将 OS_SEM_EN 开关量常数置成 1,这样 μC/OS-II 才能支持信号量。
信号量有3种用途
1)表达事件的发生次数或者己发生事件的数量。
2)表达资源可用性,例如有一台打印机.信号量值为1表示打印机空闲,为0表示打一印机被占用。
这是资源可用量的一个特例,也可以说,信号量值为1表示有1台打印机空闲.为O表示无打印机空闲。
3)表达资源可用量,例如有10个串口.信号量值用于表达空闲串口数量。
使用:
1、变量定义: OS_EVENT * sem_beep; //蜂鸣器信号量指针
2、创建信号量: sem_beep=OSSemCreate(0); //创建信号量
OS_EVENT *OSSemCreate (INT16U cnt)。该函数返回值为已创建的信号量的指针,而参数cnt则是信号量计数器(OSEventCnt)的初始值。
3、发送信号量函数: OSSemPost(sem_beep); 每次调用 sem_beep->OSEventCnt减1 然后减到0保持0
INT8U OSSemPost(OS_EVENT *pevent)。其中,pevent
为信号量指针,该函数在调用成功后,返回值为OS_ON_ERR,否则会根据具体错误返回OS_ERR_EVENT_TYPE(不是指向消息队列的指针)、OS_SEM_OVF。
检查是否有任务在等待该信号量,如果没有,将信号量的计数值加数值加1并返回;
如果有,将优先级最高的任务从等待任务列表中删除,并使它进入就绪状态
4、等待一个信号量:OSSemPend(sem_beep,0,&err); 每次调用 sem_beep->OSEventCnt加1
void OSSemPend(OS_EVENT *pevent, INT16U timeout, INT8U *err)。
其中,参数pevent是被请求信号量的指针,timeout为等待时限,err为错误信息。
如果信号量的计数值大于0, 将它减1并返回;
如果信号量的值等于0,则调用本函数的任务将被阻塞起来,等待另一个任务把它唤醒;
体现在程序中,例:
OSSemPost(sem_beep); 在按键中,OSSemPend(sem_beep,0,&err); 在绿色闪光灯中,创建信号量时初始值设为20,
则绿色灯闪烁20次后停止闪烁,每按一次按键则闪烁一次灯
5、删除信号量函数:OSSemDel(sem_beep,1,&err); //删除信号量
OS_EVENT *OSSemDel(OS_EVENT *pevent,INT8U opt, INT8U *err)。
其中,pevent 为要删除的信号量指针,opt 为删除条件选项,err 为错误信息。
信号量删除后,被信号量限制的任务将无限制运行。
在使用一个信号量之前,首先要建立该信号量,也即调用 OSSemCreate()函数(见下一节),
对信号量的初始计数值赋值。该初始值为 0 到 65,535 之间的一个数。如果信号量是用来表示一
个或者多个事件的发生,那么该信号量的初始值应设为 0。如果信号量是用于对共享资源的访问,
那么该信号量的初始值应设为 1(例如,把它当作二值信号量使用)。最后,如果该信号量是用
来表示允许任务访问 n 个相同的资源,那么该初始值显然应该是 n,并把该信号量作为一个可计
数的信号量使用。
●----邮箱---------------------------------------------------
能够传送任意数量的数据
一个任务或ISR可以通过邮箱向另一个任务发送一个指针型的变量,该指针指向一个包含了特定“ 消息”(message)的数据结构
使用:
1、定义变量:
OS_EVENT * msg_key; //按键邮箱事件块指针
2、创建消息邮箱:
msg_key=OSMboxCreate((void*)0); //创建消息邮箱
OS_EVENT *OSMboxCreate(void msg)。函数中的参数 msg 为消息的指针,函数的返回值为消息邮箱的指针。
调用函数 OSMboxCreate 需先定义 msg 的初始值。在一般的情况下,这个初始值为NULL;
但也可以事先定义一个邮箱,然后把这个邮箱的指针作为参数传递到函数OSMboxCreate 中,使之一开始就指向一个邮箱
3、发送消息:也就是改变邮箱的值
OSMboxPost(msg_key,(void
)temp_key); //发送消息
INT8U OSMboxPost (OS_EVENT *pevent,void *msg)。其中 pevent 为消息邮箱的指针(要写入的邮箱),msg 为消息指针(要写入的内容)。
4、读取内容:
(u32)OSMboxPend(msg_key,10,&err);
void *OSMboxPend (OS_EVENT *pevent, INT16U timeout,INT8U *err)。其中 pevent 为请求邮箱指针,timeout 为等待时限,err 为错误信息。
这个函数的主要作用就是查看邮箱指针 OSEventPtr 是否为 NULL,如果不是 NULL 就把邮箱中的消息指针返回给调用函数的任务,
同时用 OS_NO_ERR 通过函数的参数 err 通知任务获取消息成功;如果邮箱指针OSEventPtr 是 NULL,则使任务进入等待状态,并引发一次任务调度。
使用时(u32)取决于OSMboxPost中的msg是什么类型,如果msg是字符串,那么(u32)就可以换成(char *);
函数原型是没有返回值的,但是在使用读取内容时可以加一个强制类型转换然后将读取函数视为一个有返回值的函数,返回值就是邮箱内容。
5、删除邮箱函数:
OSMboxDel(msg_key,1,&err);
OS_EVENT *OSMboxDel(OS_EVENT pevent,INT8U opt,INT8U err)。其中pevent 为消息邮箱指针,opt 为删除选项,err 为错误信息。
opt可以直接写1,写0 的话是不删除的,删除之后读取的邮箱值为0 ,或者为空?opt也可以加一个逻辑判断入OSMboxDel(msg_key,(1>0),&err); ,这种也可以
●----消息队列---------------------------------------------------
可以在任务之间传递多条消息,利用一种类似于进栈出栈的方式,目前只试出来进去的数只能取出一次就消失的方式。
运行时使用先进后出或先进先出的方式向队列发送数据,再请求消息队列
使用:
1、创建变量:
OS_EVENT * q_msg; //消息队列
2、创建消息队列:
q_msg=OSQCreate(&MsgGrp[0],256);
OSQCreate的原型为:OS_EVENT OSQCreate(void**start,INT16U size)。其中,start 为存放消息缓冲区指针数组的地址,size 为该数组大小。该函数的返回值为消息队列指针
首先需要定义一指针数组,然后把各个消息数据缓冲区的首地址存入这个数组中
3、请求消息队列函数:也就是读取
buff=(char )OSQPend(q_msg,0,&err); //函数有返回值,返回内容,由于函数本体是无返回值的,不加强制转换会有警告,不加也不影响运行
void
OSQPend(OS_EVENT
pevent,INT16U timeout,INT8U err)。其中,pevent 为所请求的消息队列的指针,timeout 为任务等待时限,err 为错误信息
4、向消息队列发送消息函数:也就是写入
OSQPost(q_msg,q_msg_char[0]); //发送队列(先进先出)
OSQPostFront(q_msg,q_msg_char[0]); //发送队列(先进后出)
INT8U OSQPost(OS_EVENT
pevent,void msg)
INT8U OSQPostFront (OS_EVENT
pevent,void
msg)。
其中,pevent 为消息队列的指针,msg 为待发消息的指针。
5、当前队列大小
((OS_Q
)(q_msg->OSEventPtr))->OSQEntries %d可打印
●----信号量集---------------------------------------------------
任务常常需要与多个事件同步,即要根据多个信号量!组合作用!的结果来决定任务的运行方式
信号量集所能管理的信号量都是一些二值信号,所有信号量集实质上是一种可以对多个输入的逻辑信号进行基本逻辑运算的组合逻辑
信号量集的变量定义结构体:
typedef struct
{
INT8U OSFlagType; //识别是否为信号量集的标志
void *OSFlagWaitList; //指向等待任务链表的指针
OS_FLAGS OSFlagFlags; //所有信号列表 也就是所有二值逻辑即数据
}OS_FLAG_GRP;
比较麻烦是 void *OSFlagWaitList 他指向另一个结构体——等待任务链表节点 OS_FLAG_NODE
typedef struct
{
void *OSFlagNodeNext; //指向下一个节点的指针
void *OSFlagNodePrev; //指向前一个节点的指针
void *OSFlagNodeTCB; //指向对应任务控制块的指针
void *OSFlagNodeFlagGrp; //反向指向信号量集的指针
OS_FLAGS OSFlagNodeFlags; //信号过滤器
INT8U OSFlagNodeWaitType; //定义逻辑运算关系的数据
} OS_FLAG_NODE;
OSFlagFlags——变量定义结构体中的所有信号列表,也就是所有二值逻辑即数据
OSFlagNodeFlags——信号过滤器,他确定了OSFlagFlags中所有的二值逻辑,最后输出时要用哪些,哪些没用
OSFlagNodeWaitType——是定义逻辑运算关系的一个常数(根据需要设置),它是经过过滤之后对逻辑进行运算的公式
按照理解,二值逻辑的数据流顺序按照 OSFlagFlags->OSFlagNodeFlags->OSFlagNodeWaitType->请求任务
OSFlagNodeWaitType其可选值和对应的逻辑关系
|---------------------------------------------------------------|
|常数 |信号有效状态 | 等待任务的就绪条件 |
|---------------------------------------------------------------|
|OS_FLAG_WAIT_CLR_ALL 或 | 0 | 信号全部有效(全 0) |
|OS_FLAG_WAIT_CLR_AND | | |
|---------------------------------------------------------------|
|OS_FLAG_WAIT_CLR_ANY 或 | 0 |信号有一个或一个以上有效(有 0)|
|OS_FLAG_WAIT_CLR_OR | | |
|---------------------------------------------------------------|
|OS_FLAG_WAIT_SET_ALL 或 | 1 |信号全部有效(全 1) |
|OS_FLAG_WAIT_SET_AND | | |
|---------------------------------------------------------------|
|OS_FLAG_WAIT_SET_ANY 或 | 1 |信号有一个或一个以上有效(有 1)|
|OS_FLAG_WAIT_SET_OR | | |
|---------------------------------------------------------------|
举个简单的例子,假设请求信号量集的任务设置 OSFlagNodeFlags 的值为 0X0F,设置
OSFlagNodeWaitType 的值为 WAIT_SET_ANY,那么只要 OSFlagFlags 的低四位的任何一位为
1,请求信号量集的任务将得到有效的请求,从而执行相关操作,如果低四位都为 0,那么请求
信号量集的任务将得到无效的请求。
使用:
1、定义变量:
OS_FLAG_GRP * flags_key; //信号量集
OS_FLAG_GRP是三个的那个变量 有用的是第一个数,是不是信号量集,第二个是u16类型的信号量(一个位一个信号量)
2、创建信号量集
flags_key=OSFlagCreate(0,&err); //创建信号量集
OS_FLAG_GRP *OSFlagCreate(OS_FLAGS flags,INT8U *err).其中,flags 为信号量的初始值(即 OSFlagFlags 的值),err为错误信息,
返回值为该信号量集的标志组的指针(定义的变量flags_key),应用程序根据这个指针对信号量集进行相应的操作。
3、向信号量集发送信号函数,即更改信号量集的信号状态
OSFlagPost(flags_key,0x0f,OS_FLAG_SET,&err); //第二个参数表示更改那些位的数值,例0x0f表示更改后四位的数值,OS_FLAG_SET(1)表示第二位标识的位全部置一OS_FLAG_CLR全部清零
OS_FLAGS OSFlagPost(OS_FLAG_GRP *pgrp, OS_FLAGS flags, INT8U opt, INT8U err).其中,pgrp 为所请求的信号量集指针,flags 为选择所要发送的信号,opt 为信号有效选项,err 为错误信息。
所谓任务向信号量集发信号,就是对信号量集标志组中的信号进行置“1”(置位)或
置“0”(复位)的操作。至于对信号量集中的哪些信号进行操作,用函数中的参数 flags
来指定;对指定的信号是置“1”还是置“0”,用函数中的参数 opt 来指定(opt =
OS_FLAG_SET 为置“1”操作;opt = OS_FLAG_CLR 为置“0”操作)。
4、请求信号量集函数,即读取信号量集并根据信号量和筛选条件返回结果。
(u16)flags=OSFlagPend(flags_key,0x0f,OS_FLAG_WAIT_SET_ALL,10,&err); //0x0f表示此次只取后四位数据进行运算
OS_FLAGS OSFlagPend(OS_FLAG_GRP
pgrp, OS_FLAGS flags, INT8U wait_type, INT16Utimeout, INT8U *err)。
其中,pgrp 为所请求的信号量集指针,flags 为滤波器(即OSFlagNodeFlags 的值),wait_type 为逻辑运算类型(即 OSFlagNodeWaitType 的值),timeout为等待时限,err 为错误信息。
这个函数在应用中比较费解,他的返回值除0为无效,即经过滤器的信号不满足wait_type条件,其他信号很复杂,
可以判定返回值是否为0(为0错误)或&err是否为0(为0正确)。

超时时间:设为0,当信号量集不满足过滤条件和匹配条件时,该任务会一直卡OSFlagPend,改成其他数后会在超时后跳过该函数 返回值:返回匹配条件的信号量集,比如 OS_FLAG_WAIT_SET_ALLflags=0x0007,直到信号量集为0x0007时才会返回非0数,且返回0x0007; OS_FLAG_WAIT_CLR_ALLflags=0x0007,直到信号全为0,才会返回非0数,且返回0x0007 OS_FLAG_WAIT_CLR_ANYflags=0x0007,返回(OSFlagFlags&flags)|(~flags)或flags-(OSFlagFlags|flags) 只取OSFlagFlags中flags为1的位,然后flages减上一步的结果,或者说对flages的上一步结果为1的位置零 例:初始值0x1000>> OSFlagPost:0x0000>>返回7 初始值0x1000>> OSFlagPost:0x0001>>返回6 初始值0x1000>> OSFlagPost:0x0002>>返回5 初始值0x1000>> OSFlagPost:0x0003>>返回4 初始值0x1000>> OSFlagPost:0x0004>>返回3 初始值0x1000>> OSFlagPost:0x0005>>返回2 初始值0x1000>> OSFlagPost:0x0001>>返回1 OS_FLAG_WAIT_SET_ANYflags=0x0007,返回(OSFlagFlags&flags) 例:初始值0x1000>> OSFlagPost:0x0000>>返回1 初始值0x1000>> OSFlagPost:0x0001>>返回2 初始值0x1000>> OSFlagPost:0x0002>>返回3 初始值0x1000>> OSFlagPost:0x0003>>返回4 初始值0x1000>> OSFlagPost:0x0004>>返回5 初始值0x1000>> OSFlagPost:0x0005>>返回6 初始值0x1000>> OSFlagPost:0x0001>>返回7 OS_FLAG_WAIT_SET_ALL//过滤之后所有信号都为1err为0,其余为10 OS_FLAG_WAIT_SET_ANY//过滤之后任意信号为1err为0,其余为10 OS_FLAG_WAIT_CLR_ANY//过滤之后任意信号为0err为0,其余为10 OS_FLAG_WAIT_CLR_ALL//过滤之后所有信号都为0err为0,其余为10

5、删除信号量集
OS_FLAG_GRP *OSFlagDel(
OS_FLAG_GRP *pgrp, //待删除的信号量集指针
INT8U opt, //删除方式
INT8U *err //错误信息
);
查询信号量集的状态
OS_FLAGS OSFlagQuery(
OS_FLAG_GRP *pgrp, //待查询的信号量集的指针
INT8U *err // 错误信息
);
6、所谓任务向信号量集发信号,就是对信号量集标志组中的信号进行置“1”(置位)或置“0”(复位)的操作。
至于对信号量集中的哪些信号进行操作,用函数中的参数flags来指定;
对指定的信号是置“1”还是置“0”,用函数中的参数opt来指定(opt = OS_FLAG_SET为置“1”操作;opt = OS_FLAG_CLR为置“0”操作)。
●-----软件定时器--------------------------------------------------
这个看起来挺简单的
1、配置系统文件:
os_cfg.h文件下做如下配置
#define OS_TMR_EN 1u // //使能软件定时器功能Enable (1) or Disable (0) code generation for TIMERS //
#define OS_TMR_CFG_MAX 16u // //最大软件定时器个数 Maximum number of timers //
#define OS_TMR_CFG_NAME_EN 1u // //使能软件定时器命名 Determine timer names //
#define OS_TMR_CFG_WHEEL_SIZE 8u // //软件定时器轮大小 Size of timer wheel (#Spokes) //
#define OS_TMR_CFG_TICKS_PER_SEC 10u // //软件定时器的时钟节拍(100=10ms) Rate at which timer management task runs (Hz)
#if OS_TMR_EN>0
#define OS_TASK_TMR_PRIO 0u //软件定时器的优先级,设置为最高
#endif
2、变量定义:
OS_TMR * tmr1; //软件定时器1
3、创建软件定时器:
tmr1=OSTmrCreate(10,10,OS_TMR_OPT_PERIODIC,(OS_TMR_CALLBACK)tmr1_callback,0,“tmr1”,&err); //100ms执行一次
创建软件定时器通过函数 OSTmrCreate 实现,该函数原型为:
OS_TMR OSTmrCreate(INT32U dly, INT32U period, INT8U opt, OS_TMR_CALLBACK callback,voidcallback_arg, INT8U *pname, INT8U *perr)。
dly,用于初始化定时时间,对单次定时(ONE-SHOT 模式)的软件定时器来说,这就是该定时器的定时时间,
而对于周期定时(PERIODIC 模式)的软件定时器来说,这是该定时器第一次定时的时间,从第二次开始定时时间变为 period。
period,在周期定时(PERIODIC 模式),该值为软件定时器的周期溢出时间。
opt,用于设置软件定时器工作模式。可以设置的值为:OS_TMR_OPT_ONE_SHOT或 OS_TMR_OPT_PERIODIC,
如果设置为前者,说明是一个单次定时器;设置为后者则表示是周期定时器。
callback,为软件定时器的回调函数,当软件定时器的定时时间到达时,会调用该函数。
callback_arg,回调函数的参数。
pname,为软件定时器的名字。
perr,为错误信息。
4、定时器回调函数:相当于中断服务函数
void tmr1_callback(OS_TMR *ptmr,void *p_arg)
{}
软件定时器的回调函数有固定的格式,我们必须按照这个格式编写
void (*OS_TMR_CALLBACK)(void *ptmr, void parg)。
函数名可以随意设(例:tmr1_callback)
ptmr 这个参数,软件定时器用来传递当前定时器的控制块指针,所以我们一般设置其类型为 OS_TMR
类型
parg)为回调函数的参数,这个就可以根据自己需要设置了,你也可以不用,但是必须有这个参数。
5、开启软件定时器函数:
OSTmrStart(tmr1,&err); //启动软件定时器1
BOOLEAN OSTmrStart (OS_TMR *ptmr, INT8U *perr)。其中 ptmr 为要开启的软件定时器指针,perr为错误信息
6、停止软件定时器函数:
OSTmrStop(tmr1,OS_TMR_OPT_NONE,0,&err);
BOOLEAN OSTmrStop (OS_TMR *ptmr,INT8U opt,void *callback_arg,INT8U *perr)。
其中 ptmr 为要停止的软件定时器指针
opt 为停止选项,可以设置的值及其对应的意义为:
OS_TMR_OPT_NONE,直接停止,不做任何其他处理
OS_TMR_OPT_CALLBACK,停止,用初始化的参数执行一次回调函数
OS_TMR_OPT_CALLBACK_ARG,停止,用新的参数执行一次回调函数
callback_arg,新的回调函数参数。
perr,错误信息。
●-------------------------------------------------------

    推荐阅读