# eOSAL3.x **Repository Path**: e-osal/e-osal3.x ## Basic Information - **Project Name**: eOSAL3.x - **Description**: 事件驱动eOSAL V3版本;此版本基于V2版本进行重构 本次修改后的eOSAL提供的API更接近RTOS的风格 修改内容如下: 1采用链表方式创建对向(task/timer); 2每个任务独立消息池; 3,支持静态和动态方式创建对向; 4,event和timer合并 5,低功耗tickless框架 - **Primary Language**: C - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 3 - **Forks**: 0 - **Created**: 2025-05-26 - **Last Updated**: 2026-01-21 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README EventDrivenClassOSAL-3.x详解 eOASL (Event Driven Class OSAL) 基于事件驱动的模拟操作系统 前言 什么是OSAL • OSAL为:Operating System Abstraction Layer,即“操作系统抽象层”,支持多任务运行,它并不是一个传统意义上的操作系统,但是实现了部分类似操作系统的功能。 以上原文来自于网络 •OSAL概念是由TI公司在ZIGBEE协议栈引入,他的意思是"操作系统抽象层",我认为叫做"模拟操作系统"更为合适,它并非一个真正的OS,而是模拟OS的一些方法为广大编程者提供一种编写MCU程序的方法,是一个架构,一种思维模式;当有一个事件发生的时候,OSAL负责将此事件分配给能够处理此事件的任务,然后此任务判断事件的类型,调用相应的事件处理程序进行处理。 •OSAL_EventDrivenClass借鉴了TI OSAL/NXP RTOS/FreeRTOS/RT-Thread等操作系统优势,兼顾了代码小/逻辑简单/等优势,适用于低端MCU开发使用. •网络上有大量把OSAL叫做“操作系统抽象层”,而我认为“操作系统抽象层”这个词应该说是为了抽象不同操作系统的API,将系统API统一,然后我们所看到OASL并非此功能,这里不做过多讨论。 OSAL和RTOS的区别 •本人理解OSAL只是一个裸机编码框架,并非操作系统,然后OSAL实现了让裸机写程序,就像在操作系统上写程序一样简单,所以OSAL是一个适用于低端MCU的编程框架;为了简化后面文档,后面把OSAL当做操作系统处理。 •RTOS实时操作系统,开源的RTOS主要包括RT-Thread、Huawei LiteOS、AliOS Things、TencentOS-tiny、FreeRTOS、Arm Mbed OS、MS-RTOS、Zephyr、Contiki-NG、NuttX、RIOT、Apache Mynewt、Drone、eCos、F9 Microkernel、Tock、Mark3、Atomthreads、Trampoline等。 •RTOS和OSAL本质上的区别: ○RTOS具备任务(线程)调度和切换功能,具备任务优先级和任务抢占等功能,把一个CPU划分的弱干个片运行,让开发者把一个CPU看成多个CPU来处理。 ○OSAL不具备多任务(线程)调度和切换功能,任务无优先级和抢占功能,采用轮询方式调用函数,利用TICK或TIME定时,实现了部分OS的功能。 EventDrivenClassOSAL: •是一个由事件驱动类的OSAL,顾名思义此OSAL完全由事件进行驱动,没有事件任务就没有事干,那么OSAL就会调度空闲任务,等待事件的发生. EventDrivenClassOSAL特点: •此系统完全由C语言编写,不会涉及汇编,且代码量非常少,整个系统不足1000行;适合初学者学习和使用。 •内存占用小,特别是对栈的占用级低,完成可以做到同裸机编程占用一样的栈空间;适用于各类低端MCU。 •OSAL实现了类似RTOS的编程思路,使代码更利于模块化设计,一个应用程序有多个独立的小功能模块组成,可使模块代码偶合度极低,便于多人协作开发,模块的重复使用。 EventDrivenClassOSAL适用性: •依赖于一个定时器,可适用于各种MCU,包含传统8051。 •此OSAL适用于对实时性不严格,对产品成本严格控制,使用较低端的MCU的场景.可适于以上场景的用绝大部分应用. 关于内存占用情况(V1.x版本): •基于新唐NUVOTON M0-58MCU硬件平台,使用官方标准库,一个示例代码(代码包括4个按键驱动示例程序,一个软件BUZZ驱动程序,2个任务,用来作按键消息分发和按键消息处理,可以说一个简单的应用已经完成80%.)内存使用情况如下: •Program Size: Code=6132 RO-data=472 RW-data=64 ZI-data=672 •可以看出OSAL内存是占用远远于一般的OS,并且OSAL已经实现任务的消息队,列把任务/事件/定时器的堆空间已经包括,在写应用代码时不会在重复创建. 可移植性: •目前我公司将OSAL成功的在GD/STM32/HC32等众多MCU上应用。 陷阱: •由于OSAL只是一个简单的编程架构,为了节能代码量和资源的占用,提供代码运行效率,没有过多考虑安全性,请不要用于对安全性要求较高的产品。 •OSAL非多线程多任务,实际上还一个单任务在死循环,所以要理解系统的工作原理,以免因不明白运行原理给自己挖些陷阱。 鸣谢: •特别感谢我的同事“罗天浩”提供了OSAL的部分框架,特别是万能的消息队列,使OS部得更简洁. •感谢业界各位朋友提供宝贵的意见和建议,如“ Seven Pounds 发现队列未进入临界保护等”. 声明: •文档中部分见解属于个人见解,未经过验证(如:OSAL定义),如果错误敬请谅解,欢迎批评指导. 愿景: •帮助使用低端MCU的嵌入式开始从业者,不断提关工作效率,实现代码的复用性。 源代码下载: •最新的版本,请加QQ群413012273,在共享文件中获得。 作者:余乐瀛 QQ:470284225 讨论群: 413012273 yuleying@126.com 时间:20220619 变更记录 版本号 变更人 开发平台 说明 V0.5 余乐瀛 M0-M058 发布的第一版. V0.6 余乐瀛 M0-M058 增加消息队列中断保护 V0.7 余乐瀛 M0-M058 修改看门狗监控SYS_TICK V1.0 余乐瀛 M0-M058 增加动态内存管理(移植FreeRTOS的Heap4),任务函数增加一个指针参数,利用指针变量和动态内存在任务间传输大数据. V1.1 余乐瀛 M0-M058 修改OS日志. V1.2 余乐瀛 M0-M058 增加定时器创建和删除,临界保护,防止创建/重置/删除定时器时在临界段定时器异常. V1.3 余乐瀛 M23-GD32E23x 系统平台为GD32E23x,在任务消息增加数据长度字段. V1.4 余乐瀛 M23-GD32E23x 系统平台为GD32E23x,增加系统定时器可定义最大时间(将原16位形参改为32位) V2.0 余乐瀛 M0+ HC32L136 1,修改命名规则,去掉变量的类型前缀 2,osEvent所有的API,增加一个uint32_t param的参数,可以在创建时传递信息. 3,osEvent提供新的方法(Events),以ID号进行增/删/查/改,解决原有API无法共用回调函数的问题. 4,osScheduler增加API-taskDelayMs,与之前的osDelayMs的区别是taskDelayMs不再调度其它任务,而只调度osEvent. 5,osTask增加一个参数dataSize,这样的好处是用来传递数据时,不需要封装成结构体,可以直接传递数据和数据大小. 6,osQueue的API宏定义,从大写改到小写,以方便大家到做函数来使用. V2.1 余乐瀛 M23 GD32E23x M4 GD32F310 1,修改osTimer,提高解决定时器不精确问题. 2,增加HAL库 V2.2 余乐瀛 M23 GD32E23x M4 GD32F310 1,优化HAL库 V2.3 余乐瀛 M23 GD32E23x M4 GD32F310 1,优化HAL库 2,OSAL增加osDelayMs(阻塞task,而不阻塞event) V2.4 余乐瀛 M23 GD32E23x M4 GD32F310 1,修改osEvent,解决2.0版本增加Events方法时造成createEvent的BUG. 2,修改osScheduler,增加us延时接口(基于TICK的精准延时,但最小延时单位取决于MCU的主频,优化之前的延时函数,用新的算法,使延时更精准. V3.x 余乐瀛 N32WB031 1,采用链表方式重构代码 2,新增消息队列调度机制 3, 任务支持定时器调度 4, 去掉event,用timer替代 关键术语 序号 术语 说明 源代码 https://gitee.com/yuleying/e-osal3.x 软件整体架构 OASL采用3层架构:硬件层, OASL+驱动层, 应用层 第一层 硬件层:为芯片原创提供的库文件 第二层 ○OSAL:为系统层,为用户提供编程框架. ○驱动层:为驱动抽象层,抽象标准驱动库,让应用与硬件隔离. ○驱动层不依赖于OSAL,是一个独立的框架,用户在不需要OSAL的时候也可单独使用HAL ○OSAL不依赖于驱动,用户可以跳过驱动支持操作LIB层,实现一些特定的功能. 第三层 应用层,用户自己应用程序. OSAL3.x详解 概要 OSAL框架 系统硬件层(eOsHardware): •TICK(心跳):为系统时间节拍使用;task/timer/delay均基于此定时器来运行;故此定时器是整个OSAL的心脏。 •WDT(看门狗):使用芯片主看门口狗,用户根据不同的芯片移植看门狗程序。 •硬件层也是OS适配不同处理器需要移植的一层,默认使用的ARM处理器,目前发现ARM的M0、M23、M3、M4都可以兼容;其它处理器需要自行移植。 •系统移植方法在后续章节中详细讲解。 系统调度器(osScheduler): •系统调度器主要用于调度 soft timer(软件定时器)、task(任务),是OSAL的大脑,应用程序的运行就是这里来调度的。 任务(osTask): •任务是一个程序的入口(这里说的程序是指一个较小的功能模块),任务通常指所接受的工作,所担负的职责,是指为了完成某个有方向性的目的而产生的活动。 •一个较大型的程序可以划分为弱干的小功能模块,比如我们开发一个由按键和数码管组成的产品,我们可以用一个任务专门负责按键的扫描;另外一个任务处理数码管的显示;两个任务不直接关联,而是采用消息队列来通讯,那么这两个代码模块就是相对独立的程序,不用某个模块是,直接把相应的文件删除,而不影响其它模块和整个代码的运行。 •任务一旦被创建就会一直存在,因为考虑系统设计的原因,没有删除任务,只要没有消息触发任务,那么任务就不会运行. 事件(osEvent): •osEvent是思路来源于NXP的BLE模块思路,使用他可以即时使或者延时回调指定的函数。 •osEvent引入极大的方便我们处理一些需要延时做的事情,或者需要在中断中较长时间处理的事情。 •举个列子,我们驱动一个I2C触摸按键,当有按键按下时,触摸芯片将INT脚拉低,MCU中入中断,无需在中断中调用I2C去获取按键,而是创建一个即时事件后,就退出中断;退出中断后,系统会调度创业的时间函数,在事件中在调用I2C读取按键值;这样开发的好处避免了中断的长期时间占用。 •再举个例子,我们需要控制一个LED的亮和灭,传统的做法是打开LED后,写一个死延时,等延时到后再去关闭LED,那么在这个延时期间,MCU就无法处理其它事务;而引入了osEvent后,打开LED时,只需要创建一个延时的Event,当时间后到,自动回调关闭LED的函数;应用程序就不需要关心LED关闭的事情了。 •事件一般是临时突发的,不可预期的,需要快速响应处理的一类活动,事件与项目,任务的显著区别就是事件是没有明确的目的的,完全不可预期。 •事件的显著特性就是其临时性和突发性,可能并不会经常发生,只是偶然性,以致不可预期。 •事件可以是即时事件,也可以是延时事件,任务创建后只会运行一次,如果希望事件能各周期运行,那么只需要在事件运行时,重新创建一次就行. •事件在未执行时再次被创建时,系统不会再次创建一个新的事件,而是将已经创建但未执行的事件延时重新设置. •事件在未执行前可以被清除. •eOSAL3.x版本不再单独提供osEvent,从而用定时器替代; 定时器(eOsTimer): •定时器是将tick抽象成了多个定时器。 •定时器可以是硬实时的。 •结合系统调度可以实现软件定时器。 •定时器基于系统定时器(TICK)来运行的. •定时器一旦被创建将会周期性自动运行,不需要重装初值,直到被删除. •可以使用单次性定时器,替代osEvent •硬件定时器是在中断中回调,所有定时器处理的事件不能太久,以免影响系统的正常运行. •软件定时器在非中断中回调,可以处理较复杂的逻辑. 队列(osQueue): •任务创建时已经为任务创建一个队列来传递消息给任务. •用于如果需要自定义不各种格式的队列,可以自行创建. 内存管理(heap_4): •这里直接使用了FreeRTOS的内存管理,当然也可以用C库或者其它的内存管理。 硬件抽象层(DRV): •V2.1版本引入了DRV层,将硬件进行抽象设计,应用不再需要直接调用芯片的库或者寄存器。 •本DEV不同于其它操作系统的DEV的地方是,DEV是独立于OS,可以直接用于裸机的编程;如果其它朋友有需要也可以直接使用。 eOsTask任务: 任务简介: •eOsTask 支持定时运行(消息"TASK_TIME");创建任务时 time != 0; ○定时运行是指,设置时间到后,就会自动运行一次任务. •eOsTask 支持循环运行(消息"TASK_LOOP");创建任务时 time = 0; ○循环运行是指,每次轮询到当前任务都会运行一次任务. •eOsTask 支持消息驱动(消息"自定义");创建任务时 设置消息缓存大小; ○当收到消息,任务就会被运行 •eOsTask 支持静态/动态两种方式创建,见后线章节点 •eOsTask 支持删除 任务消息: •eOSAL支持事件(消息驱动)调度任务;消息结构体如下 C /* Define task structure */ /* 定义任务消息结构 */ typedef struct strTaskMessage { uint16_t flag; uint16_t size; void *param; /* 定时器回调函数参数 */ }tsTaskMsg; •发送任务消息 C /** * @brief 发送消息给任务 * @param task 任务指针 * @param flag 消息 * @param pData 数据指针 * @param dataSize 数据大小 * @return 发送成功返回true, 发送失败返回false */ extern uint8_t eOsSendMsgToTask(tsTask *task, uint16_t flag, void *param, uint16_t size); 任务回调函数原型: •在osTadk.h中定义了任务函数的原型 C /* Defining task function pointer array types */ /* 定义任务函数指针数组类型*/ typedef void(*tpTaskFunc)(uint8_t msg, void *pData, uint16_t dataSize); 任务创建: •静态创建 C /** * @brief 初始化任务 * @param task 任务指针 * @param flag 任务标志 * @param time 任务时间 * @param msgBuf 消息缓冲区 * @param msgNum 消息数量 * @param func 任务函数指针 * @return false:任务创建失败, true:任务创建成功 * @note 此函数为静态初始化任务 */ extern uint8_t eOsInitTask(tsTask *task, tpTaskFunc func, uint32_t time, tsTaskMsg *msgBuf, uint8_t msgNum); examples: //静态定义任务控制块 tsTask gMainTask; //静态定义消息缓冲区 static tsTaskMsg gMainTaskMsg[5]; //任务代码 void mainTask(uint8_t msg ,void *pData, uint16_t dataSize) { switch(msg) { /* 系统初始化 */ case TASK_INIT: { LOGI("mainTask init"); }break; case TASK_TIME: { LOGI("mainTask time"); }break; case TASK_LOOP: { LOGI("mainTask loop"); }break; } } //静态创建(初始化)任务 eOsInitTask(&gMainTask, mainTask, 0, gMainTaskMsg, sizeof(gMainTaskMsg)/sizeof(tsTaskMsg)); •动态创建 C /** * @brief 动态创建任务 * @param flag 任务标志 * @param time 任务时间 * @param msgNum 消息数量 * @param func 任务函数指针 * @return 任务指针 * @note 此函数为动态创建任务 * @note 此函数为动态创建任务,任务内存由用户分配,任务内存由用户释放 * */ extern tsTask *eOsCreateTask(tpTaskFunc func, uint32_t time, uint8_t msgNum); examples: tsTask *gMainTask; //任务代码 void mainTask(uint8_t msg ,void *pData, uint16_t dataSize) { switch(msg) { /* 系统初始化 */ case TASK_INIT: { LOGI("mainTask init"); }break; case TASK_TIME: { LOGI("mainTask time"); }break; case TASK_LOOP: { LOGI("mainTask loop"); }break; } } gMainTask = eOsCreateTask(mainTask,1000, 10); 任务删除 •有时创建的任务工作任务已经完成,可以被删除,调用uint8_t delTask(tpfTaskFunc pfFunction)可以删除任务. C++ /** * @brief 删除任务 * @param task 任务指针 * @return */ extern uint8_t eOsDelTask(tsTask *task); •注意被删除后,应用层无法对应用是否删除进行判断,此时用户要避免还要此任务发送消息. 任务初始化: •任务创建时系统会自动发一个初始化消息给任务,任务接是到此消息时可以初始化各变量,或者硬件资源. C /* 给当前任务发送初始化消息 */ sendMsgToTask(gtTask.taskIdMax, TASK_INIT, NULL, 0); /* 系统初始化 */ case TASK_INIT: { adcInit(>CfgAdcVol); createTimer(1000,secTimer); }break; 任务消息接收: •一旦有消息发给任务,对应的任务函数就会被调度,并且通过任务函数的形参收到消息. 任务消息处理: •任务被调度时,可以从形参msg获取消息, pData获取携带的数据, dataSize获取数据长度,任务根据消息内存做出相应的处理,处理完成后返回,系统再次调度其它任务. •当任务在处理消息发生新的消息,任务可以向自己发送另外一个消息. C++ void mainTask(uint8_t msg ,void *pData, uint16_t dataSize) { switch(msg) { /* 系统初始化 */ case TASK_INIT: { adcInit(>CfgAdcVol); }break; /* 按键 */ case MAIN_KEY_MSG: { switch((uint32_t)pData) { case KEY_0: case KEY_1: { data = (uint8_t)(uint32_t)pData; printf("key=%d",data ); }break; } } } } delay: •由于OSAL没有任务切换功能,任务中通常不允许较长时间的Delay,如果必需用到Delay,那么可以使用OS提供的delay函数 C++ /* 系统移植时请将此内联函数放置于TICK定时器器 */ extern void eOsSchedulerTimerHandle(void); /* ** 系统指令延时,调用此函数,会使所有任务和事件阻塞,非必要情况下尽量少用 */ extern void eOsDirectiveDelay(uint32_t delay); /** * @brief 系统指令延时,调用此函数,会使所有任务和事件阻塞,非必要情况下尽量少用 * */ extern void eOsDirectiveDelay_us(uint32_t usDelay); /* ** 较精准延时,但最小延时不得小于TICK周期; */ extern void eOsDirectiveDelay_ms(uint32_t msDelay); /* ** 任务阻塞时,事件会被调度. */ extern void eOsDelay_ms(uint32_t msDelay); /* 特别声明:由于eOsDelay_us时所有任务会阻塞, ** 任务阻塞时,事件会被调度. */ extern void eOsDelay_us(uint32_t usDelay); •当调用eOsDelay_ms和eOsDelay_us 软件定时器依然可以被调度 任务优先级 •由于OSAL没有任务切换,不能实现多任务,故没有任务优先级的说法. •只有一个任务退出,其它任务才能被调度,所有任务不能长时间被占用,更不能写成while(1) 。 eOsTimer定时器: 定时器回调函数原型 C /** * @brief 定义定时器回调函数指针类型 * @param param:回调参数 * @return 回调函数返回值,false 删除定时器,true 保留定时器 */ typedef uint8_t(*tpTimerFunc)(void* param); 定时器工作原理: •硬件定时器是基于TICK中断的回调实现的,其是在中断中运行,故使用定时器时避免耗时太多。 •硬件定时器是基于系统调度实现,可以处理逻辑较复杂的应用. 定时器创建: •静态创建 C /** * @brief 初始化定时器 * @note 此API动态创建定时器,使用同一个控制块的定时器,会被覆盖(重置定时器),同一个func允许创建多个定时器 * @param timer:定时器控制块 * @param func :回调函数 * @param flag :标志,定时器标志 * @param time :定时器器间隔 mS * @param runs :运行次数,0表示无限次 * @param param:回调参数 * @return 初始化状态 */ extern uint8_t eOsInitTimer(tsTimer *timer, tpTimerFunc func, uint16_t flag, uint32_t time, uint16_t runs, void *param); examples: static tsTimer gHardTimer; uint8_t hardTimerTest(void *param) { LOGI("hardTimerTest"); return 1; //1:保留定时器 } //静态初始化定时器 eOsInitTimer(&gHardTimer, hardTimerTest, E_OS_TIM_FLAG_HARD|E_OS_TIM_FLAG_CYCLE|E_OS_TIM_FLAG_RUN, 5000, 0, NULL); •动态创建 C /** * @brief 创建定时器 * @note 此API动态创建定时器,使用同一个func的定时器,会被覆盖(重置定时器) * @param func:回调函数 * @param flag:标志,定时器标志 * @param time:定时器器间隔 * @param runs :运行次数,0表示无限次* * @param param:回调参数 * @return 定时器控制块 */ extern tsTimer *eOsCreateTimer(tpTimerFunc func, uint16_t flag, uint32_t time, uint16_t runs, void *param); examples: uint8_t softTimerTest(void *param) { LOGI("soft once 2 timer test"); sendDevProper(1); return 0; //返回0,删除定时器 } eOsCreateTimer(softTimerTest, E_OS_TIM_FLAG_SOFT|E_OS_TIM_FLAG_RUN, 6000, 1, NULL); 重置定时器: •再次创建定时器时,定时器会被重置 定时器删除: •以下几种情况会删除定时器 ○单次定时器运行一次后自动删除 ○周期定时器,但设置了运行次数,达到指定次数后会自动删除 ○定时器回调返回0时,会自动删除 ○手动删除,通过定时器控制块,调用eOsDelTimer删除,通过回调函数调用eOsDelTimerForCb删除 C /** * @brief 删除定时器 * @param timer:定时器控制块 * @return 删除状态 */ extern int eOsDelTimer(tsTimer *timer); /** * @brief 通过回调函数删除定时器 * @note:此函数会删除所有回调函数相同的定时器 * @param func:回调函数 * @return 删除状态 */ extern int eOsDelTimerForCb(tpTimerFunc func); osQueue消息队列: 消息队列是OSAL的基础,任务的通讯和调度可以说就是基于消息队列来实现的,用户根据需要可自由创建各种类型的队列。 队列的底层是链表,研究队列可适合初始化学习链表。 消息队列创建方法: 定义消息结构 根据用户根据需求创建一个队列缓冲区,队列可以是单个字节的数据类型,可以是一个复杂的结构体类型,可参考任务部分创建的消息类型. 示例: C /* Define task structure */ /* 定义任务消息结构 */ typedef struct strTaskMessage { uint8_t taskId; //任务ID uint8_t message; //消息值(标题) uint16_t dataSize; //消息携带的数据大小 void *pData; //消息携带的数据缓冲区指针 }tsTaskMsg; 创建消息队列缓冲区(根据消息结构) C static tsTask gtTaskBuffer[MAX_TASK_MSG_BUF_NUM]; 创建消息队列对象 使用结构体tsQueue,创建一个变量.以后读队列和取队列均通过此变量操作. C tsQueue gtTaskQueue; 创建队列(使用消息缓冲区和已创建的消息队列对象) C /** * function: * 作用: 创建一个消息队列,队列可以是任意形式的 * tsQueue *psQueue: Queue structure pointer * 参数1: 消息队列对象指针,实际是一个机构体变量,用于存放链表. * void *pBuf: Buffer pointer for storing messages * 参数2: 用于存放消息的缓冲区指针, * 缓冲区可以是结构体类型BUF,可以是一个基本类型的BUF. * uint8_t deep: sizeof(buf)/sizeof(buf[0]) * 参数3: 缓冲区的深度,sizeof(buf)/sizeof(width); * uint8_t width: Length of message data * 参数4: 消息数据宽度;sizeof(data) * return : false of true * 返回: 成功 或 失败 **/ bool createQueue(tsQueue *psQueue, void *pBuf, uint8_t deep, uint8_t width) 使用bool createQueue(tsQueue *psQueue, void *pBuf, uint8_t bufLen, uint8_t dataLen); 来创建队列 ○psQueue:消息队列对象指针,实际是一个机构体变量,用于存放链表. ○pBuf:消息队列的数据缓冲区,用于存放消息,用户根据需求动态申请或者用静态或者全局变量. ○deep:消息队列深度, sizeof(buf)/sizeof(dataLen),可以看成最多可以装多少组消息. ○width消息队列宽度,可以看成单组消息的数据大小. ○返回值:创建成功与否 createQu(S_QUEUE, P_BUF):使用了宏定义将队列进行了简化 ○S_QUEUE:队列的对象,宏定义将对象取地址操作来创建队列。 ○P_BUF:队列的缓冲区,用户需要根据需要创建大小合适的缓冲区(该缓冲区必须是一个静态变量或者全局变量,然后将缓冲区传递进来,采用宏定义的缓冲区不能使用动态内存分配,因为宏定义无法判断出动态分配的内存大小。 C /* 创建消息队列 */ createQu(sgTaskQueue,gsTaskBuffer); 发送消息 C /*** ** Function name: PushQueue ** Descriptions: 数据入队(正常入队) ** Param[input]: psQueue 队列指针 ** Param[input]: vpBuf 数据指针 ** Param[input]: num 消息的数量,注意:这里并非数据长度,而是装入消息的数量 ** Returned value: 操作结果 ***/ bool pushQueue(tsQueue *psQueue,void *pBuf, uint8_t num); bool pushQueue(tsQueue *psQueue,void *pBuf,uint8_t ucLen): 第一个参数psQueue:消息队列对象指针,此对象是在创业消息队列前已经被创建。 第二个参数pBuf:是发送数据的指针, 第三个参数num:消息的数量,注意:这里并非数据长度,而是装入消息的数量,每个消息的大小是固定的。 返回值:发送成功与否 使用宏定义来简化消息的发送。 C #define pushQuBuf(S_QUEUE, P_BUF, NUM) pushQueue(&S_QUEUE, (void *)P_BUF, NUM) #define pushQuOne(S_QUEUE, S_BUF) pushQueue(&S_QUEUE, (void *)&S_BUF, 1) pushQuOne()发送一个消息, pushQuBuf()用来发数多个消息 消息的插入 C /** ** Function name: PushQueueHead ** Descriptions: 数据入队(插队),此数据会优先出队 ** Param[input]: psQueue 队列指针 ** Param[input]: vpBuf 数据指针 ** Param[input]: num 数据长度 ** Param[output]: node ** Returned value: 操作结果 **/ bool pushQueuePrior(tsQueue *psQueue,void *pbuf,uint8_t num) 消息队列采用先进先出策略,有时有希望部分消息能各得到优先处理,这里就可以采用插入的方式处理。 bool pushQueuePrior(tsQueue *psQueue,void *pbuf,uint8_t num) 第一个参数psQueue:消息队列对象指针,此对象是在创业消息队列前已经被创建。 第二个参数pBuf:是发送数据的指针, 第三个参数num:消息的数量,注意:这里并非数据长度,而是装入消息的数量,每个消息的大小是固定的。 返回值:发送成功与否 接收消息 判断是否有消息 uint8_t getQueueDataNum(tsQueue *psQueue)可以判断队列中是否有消息和消息的条数. C++ /* * 获取当前队列数据个数 */ uint8_t getQueueDataNum(tsQueue *psQueue) 宏定义#define getQuDataNum(S_QUEUE) getQueueDataNum(&S_QUEUE)简化操作 C #define getQuDataNum(S_QUEUE) getQueueDataNum(&S_QUEUE) 取消息 C /* ** Function name: PullQueue ** Descriptions: 数据出队(队头出队队) ** Param[input]: psQueue 队列指针 ** Param[input]: pbuf 数据指针 ** Param[input]: num 消息的数量(或称为条数) ** Returned value: 取出消息的条数 *********************************************************************************************************/ uint8_t pullQueue(tsQueue *psQueue, void *pbuf, uint8_t num) 使用uint8_t pullQueue(tsQueue *psQueue,void *pbuf,uint8_t len)可以取出消息: 第一个参数psQueue:消息队列对象指针 第二个参数pbuf:为取消息缓冲区, 第三个参数num:要取的消息条数 返回:实际期出消息的条数。 系统的移植: •系统的移植较为简单, 只需要定义全局中断开关、创建一个定时器,并移植看门狗程序即可运行. •在osHardware.c 中可以看到void sysTickInit(void)定时器初始化函数,根据不同的平台就行修改并初始化定时器. •修改中断入口void SYSTEM_TIME_ISR (void). •osHardware.h中规定了#define TICK_RATE_HZ 1000u定时器的运行频率,尽量使用此参数来自动计算定时器的初值,改部此参数可以达到修改定时器的目的;同时定时器和事件的事件参数均为自然时间,系统通过此参数转变为相对时间.故此参数需要准确. •osHardware.h中定义了#define DISABLE_IRQ() __disable_irq()和#define ENABLE_IRQ() __enable_irq(),打开全局中断和关闭全局中断,使部分程序进入临界段进行保护,避免被中断服务打断。 •系统默认调用了WDT功能,不同的MCU需要自己修改实现. •系统目前是在ARM_M0和ARM_M23(这两个平台TICK基本上是兼容的)的平台上开的,如果移植到其它平台时编译器未提供 #include #include 这两个库,需要自己定义,定义文件见”osTypedef.h”. •部分MCU在初始化TICK有特殊要求,建议移植时一定要看芯片用户手册. 任务延时和多任务 •OSAL非抢占式,不带任务切换,故在任务中不能写死循环,任务执行某事件完成后必需立即返回,以便其它使用使用CPU,这样就大大提高了系统实时性,感觉系统像一个多任务在运行. •当某任务实际情况确实需要延时,可调用系统提供的bOsDelayMs,在当前任务延时的时候,系统会调度其它任务,达到多任务的目的,但由于bOsDelayMs可能会再次调度被延时的任务,造成递归调用,而使栈空间资源过多的消耗,故不建议bOsDelayMs同时被多个任务使用.在万不得以的情况下需要使用,需要适当增加栈区大小,并设置bOsDelayMs的调用限制. 驱动抽象层 •为了更好的实现模块化设计,彻底解除应用与硬件的耦合,OSAL设计了DRV层. •引入DRV同时还可以快速的完成程序的开发. drvGPIO 驱动做为HAL的基础驱动库,大多外设均需要依赖于bspGPIO来实现。 采用HAL的bspGPIO最大的好处就是不需要调用复杂的函数去初始化GPIO,而是采用简单的配置即可初始化GPIO,极的方便地程序的开发,也便于在同一个MCU上开发不同的程序,大大提高了工作效率。 C typedef struct { rcu_periph_enum gpioRcu; //RCU_GPIOAx uint32_t gpioPeriph; //GPIOx uint32_t pin; //GPIO_PIN_x uint32_t mode; //GPIO_MODE_INPUT,GPIO_MODE_OUTPUT,GPIO_MODE_AF,GPIO_MODE_ANALOG uint32_t pull; //GPIO_PUPD_NONE,GPIO_PUPD_PULLUP,GPIO_PUPD_PULLDOWN uint32_t otype; //GPIO_OTYPE_PP,GPIO_OTYPE_OD uint32_t speed; //GPIO_OSPEED_2MHZ,GPIO_OSPEED_10MHZ,GPIO_OSPEED_50MHZ uint32_t af; //GPIO_AF_x }tsCfgGpio; void gpioConfig(tsCfgGpio *cfg) { /* enable clock */ rcu_periph_clock_enable(cfg->gpioRcu); /* connect port to cfg */ gpio_af_set(cfg->gpioPeriph, cfg->af, cfg->pin); gpio_mode_set(cfg->gpioPeriph, cfg->mode, cfg->pull, cfg->pin); gpio_output_options_set(cfg->gpioPeriph, cfg->otype, cfg->speed, cfg->pin); } #define UART_TX {RCU_GPIOA, GPIOA, GPIO_PIN_3, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_OTYPE_OD, GPIO_OSPEED_10MHZ, GPIO_AF_1) 例:初始化GPIO C /* 配置GPIO */ #define UART_TX {RCU_GPIOA, GPIOA, GPIO_PIN_3, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_OTYPE_OD, GPIO_OSPEED_10MHZ, GPIO_AF_1) tsCfgGpio gtCfgTx = UART_TX; //定义UART_TX脚对象 gpioConfig(>CfgTx);//初始化GPIO drvUART C typedef struct { rcu_periph_enum rcu; uint32_t com; uint32_t nvicIrq; uint32_t nvicIrqPriority; uint32_t nvicIrqSubPriority; tsCfgGpio txIo; tsCfgGpio rxIo; }tsCfgUart; #define DEV_UART0_0 /*UART0_A2A3, AF1*/ \ {RCU_USART0, USART0, USART0_IRQn, 2, 2,/*COM*/\ RCU_GPIOA, GPIOA, GPIO_PIN_2, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_OTYPE_PP, GPIO_OSPEED_10MHZ, GPIO_AF_1, /*TX*/\ RCU_GPIOA, GPIOA, GPIO_PIN_3, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_OTYPE_OD, GPIO_OSPEED_10MHZ, GPIO_AF_1 /*RX*/} typedef void (*pUartCallBackFun)(uint8_t *pBuf, uint16_t size); /*! \brief init Uart function \param[in] cfg: USARTx(x = 0,1,2) \param[in] baudRate: 2400,9600... 115200 \param[in] ePort: teUartPort \param[in] bufSize: 0~65535 \param[in] pCallBack: UART Call Back Funce \retval none GPIO_AF:参考 datasheet 2.6.7 GD32E230xx pin alternate functions 注意:uart1不支持超时定时器,串口成帧需要应用层自行实现 */ void uartInit(tsCfgUart *cfg, uint32_t baudRate,uint8_t *pReceBuf, uint32_t bufSize, pUartCallBackFun pCallBack) 接下来我看看我们是如何快速初始化UART的 C++ /* UART接收回调函数 */ void rxCcb(uint8_t *pucBuf, uint16_t usSize) { uint8_t *pPtr; bool bRet ; pPtr = (uint8_t *)osMalloc(usSize+1); if(NULL == pPtr) return; memcpy(pPtr,pucBuf,usSize); bRet = sendMsgToTask(TaskId, MSG, (void *)pPtr, usSize); if(false == bRet) osFree(pPtr); } tsCfgUart gtCfgUart = DEV_UART0_0; //UART对象 uartInit(>CfgUart,9600, rxBuffer, BUF_UART_SIZE, rxCcb); //初始化UART uartWrite(>CfgUart, txBuffer, 128); //UART发送 通过以上代码,您是否发现,现在我们操作UART是如此的简单;已经做到应用程序和UART硬件完全的隔离,这使得同一个应用可以被运行在不同的MCU,而不需要花太多的精力去修改;您需要做的只是移植驱动程序。