帮你贴出来文字
EventDrivenClassOSAL详解前言 什么是OSAL: 目前本人已知OSAL概念是由TI公司在ZIGBEE协议栈引入,他的意思是”操作系统抽象层”,我认为叫做”模拟操作系统”更为合适,它并非一个真正的OS,而是模拟OS的一些方法为广大编程者提供一种写MCU程序的方法. EventDrivenClassOSAL: 是一个由事件驱动类的OSAL,顾名思义此OSAL完全由事件进行驱动,没有事件任务就没有事干,那么OSAL就会调度空闲任务,等待事件的发生. EventDrivenClassOSAL特点: 此系统完全由C语言编写,不会涉及汇编,摒弃了内存管理,链表等复杂的方法,且代码量非常少,整个系统不足1000行;适合初学者使用,内存占用小,也适用于各类MCU,不挑剔硬件平台,依耐于硬件的资源只需要一个定时器. EventDrivenClassOSAL适用性: 此OSAL适用于对实时性不严格,对产品成本严格控制,使用较低端的MCU的场景.可适于以上场景的用绝大部分应用. 关于内存占用情况: 基于新唐NUVOTON M0-58MCU硬件平台,使用官方标准库,一个示例代码(代码包括4个按键驱动示例程序,一个软件BUZZ驱动程序,2个任务,用来作按键消息分发和按键消息处理,可以说一个简单的应用已经完成80%.)内存使用情况如下: Program Size: Code=6132 RO-data=472RW-data=64 ZI-data=672 可以看出OSAL内存是占用远远于一般的OS,并且OSAL已经实现任务的消息队,列把任务/事件/定时器的堆空间已经包括,在写应用代码时不会在重复创建. 后续会补充一个基于8051的示例代码内存使用情况. 鸣谢: 特别感谢我的同事“罗天浩,LTH”提供了OSAL的部分框架,特别是万能的消息队列,使OS部得更简洁. 感谢业界各位朋友提供宝贵的意见和建议,如“ SevenPounds 发现队列未进入临界保护等”. 声明: 文档中部分见解属于个人见解,未经过验证(如:OSAL定义),如果错误敬请谅解,欢迎批评指导. 源代码下载: 作者:余乐瀛 QQ:470284225 讨论群: 413012273 EMAIL:yuleying@126.com 时间:20160926
版本说明 OSAL_EventDrivenClass_V0.5_ARM-M0:发布的第一版. OSAL_EventDrivenClass_V0.6_ARM-M0:增加消息队列中断保护. OSAL_EventDrivenClass_V0.7_ARM-M0:修改看门狗监控SYS_TICK. OSAL_EventDrivenClass_V1.0_ARM-M0:增加动态内存管理(移植FreeRTOS的Heap4),任务函数增加一个指针参数,利用指针变量和动态内存在任务间传输大数据. OSAL的使用方法 概要: OSAL由”任务Task”,”事件Event”,”定时器Timer”,”队列Queue”组成. 任务: 任务通常指所接受的工作,所担负的职责,是指为了完成某个有方向性的目的而产生的活动。 任务一旦被创建就会一直存在,因为考虑系统设计的原因,没有删除任务,只要没有消息触发任务,那么任务就不会运行. 事件: 事件一般是临时突发的,不可预期的,需要快速响应处理的一类活动,事件与项目,任务的显著区别就是事件是没有明确的目的的,完全不可预期。 事件的显著特性就是其临时性和突发性,可能并不会经常发生,只是偶然性,以致不可预期。 事件可以是即时事件,也可以是延时事件,任务创建后只会运行一次,如果希望事件能各周期运行,那么只需要在事件运行时,重新创建一次就行. 事件在未执行时再次被创建时,系统不会再次创建一个新的事件,而是将已经创建但未执行的事件延时重新设置. 事件在未执行前可以被清除. 定时器: 定时器属于用软件实现了不同的硬件定时器;定时器基于系统定时器(TICK)来运行的. 定时器一旦被创建将会周期性自动运行,不需要重装初值,直到被删除. 如果需要一次性定时器,推荐使用事件来完成. 由于定时器是在中断里面完成,所有定时器处理的事件不能太久,以免影响系统的正常运行. 队列: 任务创建时已经为任务创建一个队列来传递消息给任务. 用于如果需要自定义不各种格式的队列,可以自行创建. 详解: 任务: 任务配置: 在”osConfig.h”文件定义了任务最大数#define MAX_TASK_NUM 8u,需要开发者根据情况需求调整大小,此值越大所占内成RAM就越大, 任务创建: 调用函数usCreateTask创建一个任务,函数原型uint8_t usCreateTask(pvTaskFuncxfunction), xfunction是任务的函数指针;创建成功返回任务的ID,创建失败返回0; 任务函数的原型为: void Func(uint8_t ucId,uint8_t ucMsg); ucId为运行时OS传入的任务的ID, ucMsg发给任务的消息. 任务初始化: 任务创建时系统会自动发一个初始化消息给任务,任务接是到此消息时可以初始化各变量,或者硬件资源. 任务调度: 当有消息发给任务时,任务调度器()会从任务消息取出消息,并回调任务函数. 任务消息发送: 调用bSendMsgToTask可以发送消息给指定的任务. 原型:bSendMsgToTask(uint8_t ucTaskId, uint8_t usMsg), ucTaskId为接收消息的任务ID, usMsg为发送的消息内容.消息发送成功返回true,否则返回false. 消息处理: 任务被调度时,可以从形参ucMsg接收到消息,任务根据消息内存做出相应的处理,处理完成后返回,系统再次调度其它任务. 当任务在处理消息发生新的消息,任务可以向自己发送另外一个消息. 任务中通常不允许Delay,大多Delay可以由延时事件来完成,如果必需用到Delay,那么可以使用OS提供的vOsDelayMs函数;当任务调用此函数Delay时系统会调用其它任务;当然如果本任务收到新的消息时,还在会被调用那么就变成了递归调用,这时需要保护好所使用的全局变量或静态变量. 空闲任务: 当所有任务都处于空闲时,如果在使能空闲任务的情况下,系统会调度空闲任务,用户可以在空闲任务处理扫描事件,或者让CPU休眠. 事件: 事件配置: 在”osConfig.h”文件定义了任务最大数#define EVNT_CONCURRENT 16u,需要开发者根据情况需求调整大小,此值越大所占内成RAM就越大. 注意:此值并不是可以创建事件的数量,而是事件同时运行的并发数量.事件一旦调度运行后,就会释放出内存来给其它事件使用. 事件创建: 调用函数bCreateEvnt可以创建一个事件, 函数原型bool bCreateEvnt(uint16_t usDelayMs, pvEvntFunc pxFunction);第一个参数是事件延时时间,以mS为单位,传0为即时事件,第二个参数为事件回调函数.返回创建成功与失败. 事件调度: 事件创建后,延时时间到时,系统会自动调度任务,任务只会运行一次,如果需要周期运行,需要在任务函数中再次创建. 事件删除: 事件创建后可调用bCleanEvnt,删除已经创建的事件; 函数原型bool bCleanEvnt(pvEvntFunc pxFunction),参数为事件的回调函数指针,返回成功或失败. 定时器: 定时器配置: 在”osConfig.h”文件定义了任务最大数#define MAX_TIMER_NUM 3u,需要开发者根据情况需求调整大小,此值越大所占内成RAM就越大. 定时器创建: 调用函数bCreateTimer可以创建一个事件, 函数原型bool bCreateTimer(uint32_t uiTimeMs, pvTimerFunc pxFunction);第一个参数设定定时器的时间,以mS为单位 ,第二个参数为定时器回调函数.返回创建成功与失败. 定时器调度: 定时器设置定时器到后,会在定时器中断中直接回调函数处理定时器,这样达到抢占CPU的目的,但正由于在中断中处理,定时器中不能写太多程序,以免影响定时器正常运行. 定时器删除: 事件创建后可调用bDelTimer,删除已经创建的事件; 函数原型bool bDelTimer(pvTimerFunc pxFunction),参数为定时器的回调函数指针,返回成功或失败. 队列: 用户根据需要可自由创建各种类型的队列. 队列创建方法: 创建消息结构 根据用户根据需求创建一个队列缓冲区,队列可以是单个字节的数据类型,可以是一个复杂的结构体类型,可参考任务部分创建的消息类型. file:///C:/Users/gaoya/AppData/Local/Temp/msohtmlclip1/01/clip_image002.jpg file:///C:/Users/gaoya/AppData/Local/Temp/msohtmlclip1/01/clip_image004.jpg 创建队列类型变量 使用结构体tsQueue,创建一个变量.以后读队列和取队列均通过此变量操作. 创建队列 使用B_CREATE_QUEUE创建队列 原型#define B_CREATE_QUEUE(T_QUEUE, P_BUF) bCreateQueue(&T_QUEUE,(void *)P_BUF, sizeof(P_BUF)/sizeof(P_BUF[0]), sizeof(P_BUF[0])),使用了宏定义将队列进行了简化, xOsCreateQueue(queue,buf),第一个参数传队列的变量名,第二个参数传缓冲区的指针: 发送消息 使用B_PUSH_QUEUE_BUF来发数多个消息,使用B_PUSH_QUEUE_ONE发送一个消息. 原型#define B_PUSH_QUEUE_BUF(T_QUEUE, P_BUF, UC_LEN) bPushQueue(&T_QUEUE, (void *)P_BUF,UC_LEN) 第一个参数为创建的队列类型变量指针,可以看作的队列的名字;第二个参数是发送数据的指针,第三个参数是发送数据的长度. 原型#define B_PUSH_QUEUE_ONE(T_QUEUE, T_BUF) bPushQueue(&T_QUEUE, (void *)&T_BUF,1) 第一个参数为创建的队列类型变量名称,第二个参数是发送数据的指针. 接收消息 使用uint8_t UC_GET_QUEUE_DATA_NUM可以判断队列中是否有消息和消息的条数. 原型#define UC_GET_QUEUE_DATA_NUM(T_QUEUE)ucGetQueueDataNum(&T_QUEUE),使用宏定义简化参数,简化后参数为队列的名称. 使用UC_PULL_QUEUE可以取出消息. #define UC_PULL_QUEUE(T_QUEUE, P_BUF, UC_LEN) ucPullQueue(&T_QUEUE, (void *)P_BUF,UC_LEN) 第一个参数为队列名称,第二个参数为消息缓冲区,第三个参数要取的消息长度. 系统的移植: 系统的移植较为简单, 只需要定义全局中断开关、创建一个定时器,并移植看门狗程序即可运行. 在osHardware.c 中可以看到voidvSysTickInit(void)定时器初始化函数,根据不同的平台就行修改并初始化定时器. 修改中断入口void SYSTEM_TIME_ISR(void). osHardware.h中规定了#define TICK_RATE_HZ 1000u定时器的运行频率,尽量使用此参数来自动计算定时器的初值,改部此参数可以达到修改定时器的目的;同时定时器和事件的事件参数均为自然时间,系统通过此参数转变为相对时间.故此参数需要准确. osHardware.h中定义了#define DISABLE_IRQ() __disable_irq()和#defineENABLE_IRQ() __enable_irq(),打开全局中断和关闭全局中断,使部分程序进入临界段进行保护,避免被中断服务打断。 系统目前是在ARM_M0的平台上开的,如果移植到其它平台时编译器未提供 #include <stdint.h> #include<stdbool.h>这两个库,需要自己定义,定义文件见”osTypedef.h”. 任务延时和多任务 OSAL非抢占式,不带任务切换,故在任务中不能写死循环,任务执行某事件完成后必需立即返回,以便其它使用使用CPU,这样就大大提高了系统实时性,感觉系统像一个多任务在运行. 当某任务实际情况确实需要延时,可调用系统提供的bOsDelayMs,在当前任务延时的时候,系统会调度其它任务,达到多任务的目的,但由于bOsDelayMs可能会再次调度被延时的任务,造成递归调度,而使栈空间资源过多的消耗,故不建议bOsDelayMs同时被多个任务使用.在万不得以的情况下需要使用,需要适当增加栈区大小,并设置bOsDelayMs的调用限制.
|