μC/OS-II ,这个以前都读成了miu C OS two。其实应该读做“micro C O S two”,μ代表“微小”之意,字母C代表“控制器”,所以总体含义为“微控制器 操作系统版本2”。不过不知道这么读,会不会被不专业的人嘲笑,着实尴尬。
反正不要叫成“U”cOS就好。
这篇文章主要介绍一下如何在μC/OS-II下进行程序开发。简单点说,就是如何调用那些系统函数。少说细节和究竟,只说怎么搞。
一般主文件,即main.c中的main函数写法如下,以下是我在STM32上运行μC/OS-II的例子
int main(void)
{
//芯片初始化
STM_Init();
//其他初始化
RCC_Config();
GPIO_Config();
USART_Config();
NVIC_Config();
//系统初始化
OSInit();
//创建任务
OSTaskCreate(Task_1,(void *)0,&Task_1_stk[TASK_1_STK_SIZE-1],TASK_1_PRIO);
OSTaskCreate(Task_2,(void *)0,&Task_2_stk[TASK_2_STK_SIZE-1],TASK_2_PRIO);
//启动任务
OSStart();
return 0;
}
关键函数就是OSInit和OSStart。一个是预备,一个是开始。
操作系统,最核心的内容,就是多任务执行,所以这也是最容易切入一个操作系统的点,初始化完成,创建两个函数,看到他们交替运行,便是极好的。
任务的优先级越高,反映优先级的值则越低(这个和FreeRTOS是相反的)。在最新的µC/OS-Ⅱ版本中,任务的优先级数也可作为任务的标识符使用。其实就是优先级能表示该任务,因为一个任务只能选一个优先级,一个优先级只能被一个任务使用。
功能 | 函数 | 备注 |
---|---|---|
建立任务 | OSTaskCreate() | |
建立任务 | OSTaskCreateExt() | |
堆栈检验 | OSTaskStkChk() | |
删除任务 | OSTaskDel() | |
请求删除任务 | OSTaskDelReq() | |
改变任务的优先级 | OSTaskChangePrio() | |
挂起任务 | OSTaskSuspend() | |
恢复任务 | OSTaskResume() | |
获得有关任务的信息 | OSTaskQuery() |
INT8U OSTaskCreate(void (*task)(void *pd), void *pdata, OS_STK *ptos, INT8U prio);
参数 | 函数 |
---|---|
task | 是指向任务代码的指针。 |
pdata | 指向一个数据结构,该结构用来在建立任务时向任务传递参数。 |
ptos | 为指向任务堆栈栈顶的指针。 |
prio | 为任务的优先级。每个任务必须有一个唯一的优先级作为标识。数字越小,优先级越高 |
返回值 | OSTaskCreate()的返回值为下述之一: |
OS_NO_ERR:函数调用成功。 | |
OS_PRIO_EXIST:具有该优先级的任务已经存在。 | |
OS_PRIO_INVALID:参数指定的优先级大于 OS_LOWEST_PRIO。 l OS_NO_MORE_TCB:系统中没有 OS_TCB 可以分配给任务了。 |
有了任务,如果想多任务并行,那自然是需要任务有运行态和非运行态,这样才能让出CPU。
功能 | 函数 | 备注 |
---|---|---|
任务延时函数 | void OSTimeDly(INT32U ticks); | |
按时分秒延时函数 | void OSTimeDlyHMSM (INT8U hours, INT8U minutes, INT8U seconds, INT16U ms) | |
让处在延时期的任务结束延时 | INT8U OSTimeDlyResume(INT8U prio); | 这个是主动唤醒一个处于延迟等待的任务 |
系统时间 | INT32U OSTimeGet(void); | 获取系统时钟的计数器值,这是一个32位的值 |
void OSTimeSet(INT32U ticks); |
用户开发,难免用到申请内存,不过这次的提供的内存管理和我们常用的malloc不太一样,系统提供一系列函数,来供我们分配内存时候调用。其实说简单点,更像是一个基于静态数组,进行的内存重新划分方式提供可以使用的内存块。
开启内存管理需要打开宏
功能 | 函数 | 备注 |
---|---|---|
建立一个内存分区 | OS_MEM *OSMemCreate(void *addr, INT32U nblks, INT32U blksize, INT8U *perr); | |
分配一个内存块 | void *OSMemGet(OS_MEM *pmem, INT8U *perr); | |
释放一个内存块 | INT8U OSMemPut(OS_MEM *pmem, void *pblk); | |
查询一个内存分区的状态 | INT8U OSMemQuery(OS_MEM *pmem, OS_MEM_DATA *p_mem_data); |
举个例子吧,看一下就明白了。demo才是真正有价值的代码。
OS_MEM *CommMem;
//一共16个块,每块32个int大小
INT32U CommBuf[16][32];
static OS_STK Task_mem_stk[80];
void Task_mem (void *p_arg)
{
INT8U err;
INT8U *pmsg;
(void)p_arg;
for (;;)
{
//申请一块内存,这个大小就是之前注册的时候的大小,32个int的大小
pmsg = OSMemGet(CommMem, &err);
if (pmsg != (INT8U *)0)
{
printf("get mem success\n");
strcpy(pmsg,"test");
printf("content:%s\n",pmsg);
err = OSMemPut(CommMem, (void *)pmsg);
if (err == OS_ERR_NONE)
{
printf("put mem success\n");
}
else
{
printf("put mem fail\n");
}
}
else
{
printf("get mem fail\n");
}
OSTimeDlyHMSM(0, 0, 2, 0);
}
}
int main(void)
{
INT8U err;
STM_Init();
RCC_Config();
GPIO_Config();
USART_Config();
NVIC_Config();
OSInit();
//创建内存块组
CommMem = OSMemCreate(&CommBuf[0][0], 16, 32 * sizeof(INT32U), &err);
OSTaskCreate(Task_mem,(void *)0,&Task_mem_stk[79],5);
OSStart();
return 0;
}
这里也是操作系统的标志内容,任务之间的通讯,也是稍微复杂一点的东西,不过一通百通,操作系统都会有类似的东西,信号量,消息队列等的。
µC/OS-II 中的信号量由两部分组成:一个是信号量的计数值,它是一个 16 位的无符号整数(0 到 65,535 之间);另一个是由等待该信号量的任务组成的等待任务表。
功能 | 函数 | 备注 |
---|---|---|
建立一个信号量 | OS_EVENT *OSSemCreate(INT16U value); | |
删除一个信号量 | OS_EVENT *OSSemDel(OS_EVENT *pevent, INT8U opt, INT8U *perr); | |
等待一个信号量 | void OSSemPend(OS_EVENT *pevent, INT32U timeout, INT8U *perr); | |
发送一个信号量 | INT8U OSSemPost(OS_EVENT *pevent); | |
无等待地请求一个信号量 | INT16U OSSemAccept(OS_EVENT *pevent); | 用于中断ISR中调用 |
查询一个信号量的当前状态 | INT8U OSSemQuery(OS_EVENT *pevent, OS_SEM_DATA *p_sem_data); |
还是以一个demo例子说明
OS_EVENT *DispSem;
static OS_STK Task_1_stk[TASK_1_STK_SIZE];
static OS_STK Task_2_stk[TASK_2_STK_SIZE];
/*****************************************************
*任务等待信号量的任务
*****************************************************/
void Task_pend(void *p_arg)
{
INT8U err;
(void)p_arg;
while(1)
{
printf("wait signal!\r\n");
OSSemPend(DispSem, 0, &err);
printf("get signal!\r\n");
}
}
/******************************************************
*发送信号量的任务
******************************************************/
void Task_post(void *p_arg)
{
INT8U err;
(void)p_arg;
while(1)
{
err = OSSemPost(DispSem);
switch (err)
{
case OS_ERR_NONE:
printf("post signal!\r\n");
break;
case OS_ERR_SEM_OVF:
/* Semaphore has overflowed */
printf("overflowed signal!\r\n");
break;
}
OSTimeDlyHMSM(0, 0, 2, 0);
}
}
int main(void)
{
STM_Init();
OSInit();
RCC_Config();
GPIO_Config();
USART_Config();
NVIC_Config();
DispSem = OSSemCreate(1);
OSTaskCreate(Task_pend,(void *)0,&Task_1_stk[TASK_1_STK_SIZE-1],TASK_1_PRIO);
OSTaskCreate(Task_post,(void *)0,&Task_2_stk[TASK_2_STK_SIZE-1],TASK_2_PRIO);
OSStart();
return 0;
}
任务1负责等待信号,任务2负责发送信号,那么每两秒,任务1就能等到信号,然后再等下一次任务2发送信号。结果如下
邮箱是µC/OS-II 中另一种通讯机制,它可以使一个任务或者中断服务子程序向另一个任务发送一个指针型的变量。该指针指向一个包含了特定“消息”的数据结构。
功能 | 函数 | 备注 |
---|---|---|
建立一个邮箱 | OS_EVENT *OSMboxCreate(void *pmsg); | |
删除一个邮箱 | OS_EVENT *OSMboxDel(OS_EVENT *pevent, INT8U opt, INT8U *perr); | |
等待一个邮箱中的消息 | void *OSMboxPend(OS_EVENT *pevent, INT32U timeout, INT8U *perr); | |
发送一个消息到邮箱中 | INT8U OSMboxPost(OS_EVENT *pevent, void *pmsg); | |
无等待地从邮箱中得到一个消息 | void *OSMboxAccept(OS_EVENT *pevent); | 这个不会挂起任务,所以用于ISRs |
查询一个邮箱的状态 |
INT8U OSMboxQuery(OS_EVENT *pevent, OS_MBOX_DATA *p_mbox_data);
还是demo程序进入
OS_EVENT *CommMbox;
static OS_STK Task_1_stk[TASK_1_STK_SIZE];
static OS_STK Task_2_stk[TASK_2_STK_SIZE];
INT8U CommRxBuf[100];
/*****************************************************
*任务等待邮箱消息的任务
*****************************************************/
void Task_mbox_pend(void *p_arg)
{
INT8U err;
void *pmsg;
(void)p_arg;
while(1)
{
printf("wait mbox!\r\n");
pmsg = OSMboxPend(CommMbox, 0, &err);
if (err == OS_ERR_NONE)
{
printf("get mbox[%s]!\r\n",(char*)pmsg);
}
else
{
/* Code for message not received within timeout */
}
}
}
/******************************************************
*发送邮箱消息的任务
******************************************************/
void Task_mbox_send(void *p_arg)
{
INT8U err;
(void)p_arg;
while(1)
{
err = OSMboxPost(CommMbox, (void *)&CommRxBuf[0]);
switch (err)
{
case OS_ERR_NONE:
printf("post mbox!\r\n");
break;
default:
printf("err[%d]\r\n",err);
break;
}
OSTimeDlyHMSM(0, 0, 2, 0);
}
}
int main(void)
{
STM_Init();
OSInit();
RCC_Config();
GPIO_Config();
USART_Config();
NVIC_Config();
strcpy((char*)CommRxBuf,"msg content");
CommMbox = OSMboxCreate((void *)0);
OSTaskCreate(Task_mbox_pend,(void *)0,&Task_1_stk[TASK_1_STK_SIZE-1],TASK_1_PRIO);
OSTaskCreate(Task_mbox_send,(void *)0,&Task_2_stk[TASK_2_STK_SIZE-1],TASK_2_PRIO);
OSStart();
return 0;
}
显示结果
这里还有一个以邮箱作为二值信号的方法,用来进行资源访问的保护
//使用邮箱作为二值信号量
OS_EVENT *MboxSem;
void Task1 (void *pdata)
{
INT8U err;
for (;;)
{
OSMboxPend(MboxSem, 0, &err); /* 获得对资源的访问权 */
/* 任务获得信号量,对资源进行访问 */
OSMboxPost(MboxSem, (void*)1); /* 释放对资源的访问权 */
}
}
消息队列是µC/OS-II 中另一种通讯机制,它可以使一个任务或者中断服务子程序向另一个任务发送以指针方式定义的变量。与邮箱不同的地方就是这个消息可以发多个,有队首和队尾可供插入。
功能 | 函数 | 备注 |
---|---|---|
建立一个消息队列 | OS_EVENT *OSQCreate(void **start, INT8U size); | |
删除一个消息队列 | OS_EVENT *OSQDel(OS_EVENT *pevent, INT8U opt, INT8U *perr); | |
等待一个消息队列中的消息 | void *OSQPend(OS_EVENT *pevent, INT32U timeout, INT8U *perr); | |
向消息队列发送一个消息(FIFO) | INT8U OSQPost(OS_EVENT *pevent, void *pmsg); | |
向消息队列发送一个消息(LIFO) | INT8U OSQPostFront(OS_EVENT *pevent, void *pmsg); | |
无等待地从一个消息队列中取得消息 | void *OSQAccept(OS_EVENT *pevent, INT8U *perr); | 这个不会挂起任务,所以用于ISRs |
清空一个消息队列 | INT8U *OSQFlush(OS_EVENT *pevent); | |
查询一个消息队列的状态 | INT8U OSQQuery(OS_EVENT *pevent, OS_Q_DATA *p_q_data); |
还是demo进入
OS_EVENT *CommQ;
void *CommMsg[10];
INT8U CommRxBuf1[64];
INT8U CommRxBuf2[64];
static OS_STK Task_1_stk[TASK_1_STK_SIZE];
static OS_STK Task_2_stk[TASK_2_STK_SIZE];
/*****************************************************
*任务等待消息队列的任务
*****************************************************/
void Task_mq_pend(void *p_arg)
{
INT8U err;
void *pmsg;
(void)p_arg;
while(1)
{
printf("wait mbox!\r\n");
pmsg = OSQPend(CommQ, 0, &err);
if (err == OS_ERR_NONE)
{
printf("get mbox[%s]!\r\n",(char*)pmsg);
}
else
{
/* Message not received, must have timed out */
}
}
}
/******************************************************
*发送消息队列的任务
******************************************************/
void Task_mq_post(void *p_arg)
{
INT8U err;
(void)p_arg;
while(1)
{
err = OSQPostFront(CommQ, (void *)&CommRxBuf2[0]);
switch (err)
{
case OS_ERR_NONE:
printf("post msg2!\r\n");
break;
default:
printf("err[%d]\r\n",err);
break;
}
err = OSQPost(CommQ, (void *)&CommRxBuf1[0]);
switch (err)
{
case OS_ERR_NONE:
printf("post msg1!\r\n");
break;
default:
printf("err[%d]\r\n",err);
break;
}
OSTimeDlyHMSM(0, 0, 2, 0);
}
}
int main(void)
{
STM_Init();
OSInit();
RCC_Config();
GPIO_Config();
USART_Config();
NVIC_Config();
strcpy((char*)CommRxBuf1,"msg 1");
strcpy((char*)CommRxBuf2,"msg 2");
CommQ = OSQCreate(&CommMsg[0], 10);
OSTaskCreate(Task_mq_pend,(void *)0,&Task_1_stk[TASK_1_STK_SIZE-1],TASK_1_PRIO);
OSTaskCreate(Task_mq_post,(void *)0,&Task_2_stk[TASK_2_STK_SIZE-1],TASK_2_PRIO);
OSStart();
return 0;
}
这里使用了两种发送,一种是后进先出的OSQPostFront,还有一种先进先出OSQPost。
这篇主要介绍了一下如何在μC/OS-II下进行开发,把主要的功能函数怎么调用介绍了一下。这些资料在代码的Doc下面能找到更具体的说明,demo基本都是我自己写出来测试的。
最近的疫情又开始爆发,六朝古都到十三朝古都,最近又转向了九朝古都,为啥都在这些古都爆发也不得而知,看来古都的气运已然消逝殆尽。
疫情当前,其实除了防患病毒,更重要的是保持理性,不要被一些人一些势力递过来的刀子所撩拨到自己的神经,也不要上头去过度的指责某些点,我们并不是生活在一个安全的星球,而只是生活在一个安全的国家,时刻保持警惕,免得被人利用。