开源 | 嵌入式物联网项目开发 - 基于任务和事件的OSAL调度器

在进行设备端硬件模块开发之前,先来分享一个单片机通用的软件调度器框架 --- 基于任务和事件的OSAL调度器。

OSAL的全称是Operating System Abstraction Layer,即操作系统抽象层。OSAL最初的概念是由德州仪器TI在ZigBee的协议栈Z-Stack上引入的,严格意义上来说,它并不是一个传统意义的操作系统,但可以实现部分类似操作系统的功能。

基于任务和事件的调度器,可以为MCU单片机编程提供一种通用意义上的框架,事件event是这个框架的最小单位,多个事件组成一个任务task。当一个事件发生时,OSAL负责将该事件分配给能够处理这个事件的任务,然后任务判断该事件的类型,再调用相应的事件处理函数进行处理。

本次开源项目设备端的MCU代码均采用OSAL调度器,OSAL调度器的框架如下图所示:

开源 | 嵌入式物联网项目开发 - 基于任务和事件的OSAL调度器_第1张图片

这个OSAL调度器主要实现了以下两个功能:任务调度和时间管理。

任务调度:采用任务数组tasks_events来管理各层对应的任务事件,tasks_arr是任务事件的处理数组,使用osal_set_event()函数,可以为对应的任务设置事件,使用osal_clear_event()函数,可以清除对应的任务事件。

时间管理:OSAL使用STM32的滴答时钟systick作为时间基准,可以虚拟出多个软件定时器,使用这些软件定时器,可以设置延时任务事件,当延时时间到达后,会为对应的任务事件设置触发标志位,然后执行延时事件。

OSAL调度器主要涉及以下源文件:

开源 | 嵌入式物联网项目开发 - 基于任务和事件的OSAL调度器_第2张图片

osal.c和osal.h:主要是任务的注册和调度实现,以及提供任务事件函数设置函数和任务事件清除函数。

osal_clock.c和osal_clock.h:主要是OSAL调度器使用STM32的滴答时钟systick作为时基,更新系统的软件定时器计数值,以及查找定时任务是否到达定时时间。。

osal_timers.c和osal_timers.h:主要是通过链表的方式管理系统的软件定时器,对外提供软件定时任务的启动函数,软件定时任务的停止函数。

 

如何使用OSAL调度器?

先来看看OSAL调度器的调度流程图,如下图所示:

开源 | 嵌入式物联网项目开发 - 基于任务和事件的OSAL调度器_第3张图片

以上流程图,板级初始化和OSAL任务初始化,在main函数中完成,具体的调用方式可以查看GitHub里面的源代码文件。run_system()函数主要在while(1)大循环中调用,不断更新软件定时器并查找是否有定时任务,如果有立即执行的任务或定时任务,则执行任务事件函数,事件处理函数执行完成后,清除事件标志位。

run_system()函数主要在osal.c文件中实现,函数的实现方式如下所示:

void run_system(void)
{
    unsigned char idx = 0;
    
    osal_time_update();    //更新系统时间,查找定时器任务
    
    do {
        if (tasks_events[idx])break;   //查找当前就绪的任务事件
    }while (++idx < tasks_cnt);
    
    if (idx < tasks_cnt) {    //当前有任务就绪    
        unsigned short events;

        __disable_irq();         
        events = tasks_events[idx];     //在任务列表取出任务
        tasks_events[idx] = 0;         
        __enable_irq(); 

        events = (tasks_arr[idx])( idx, events ); //调用事件处理函数
        __disable_irq();  
        tasks_events[idx] |= events;      //保存未处理的事件
        __enable_irq();  
    }
}

举个例子:例如我们需要添加一个看门狗的任务来监测系统是否正在运行,并定时进行喂狗操作。那么可以新建任务源文件iwdg_task.c和头文件iwdg_task.h,然后在osal.h文件中定义一个任务ID:IWDG_TASK_ID,这个ID就代表看门狗任务,看门狗任务的一系列事件,可以在头文件iwdg_task.h中进行定义。

源文件iwdg_task.c提供看门狗任务的初始化函数和任务执行函数,每个任务都需要提供任务的初始化函数和任务执行函数,格式如下图所示。

/***********************************************************
** 函 数 名: uint16 iwdg_task(uint8 task_id, uint16 events)
** 输   入: task_id-任务id,event-事件id
** 输   出: events-未处理的事件
** 功能描述: 看门狗任务处理
** 全局变量: 无
** 调用模块: 无
** 作   者: 
** 日   期: 
***********************************************************/
static uint16 iwdg_task(uint8 task_id, uint16 events)
{
    (void)task_id; 
             
    if( events & IWDG_FEED_EVENT )
    {        
        iwdg_feed();   //定时喂狗
        return ( events ^ IWDG_FEED_EVENT );
    }
    
    if( events & IWDG_RESET_EVENT)
    {
        while(1){delay_ms(10);}
    }
    
    return 0;
}

/***********************************************************
** 函 数 名: void iwdg_task_init(void)
** 输   入: 无
** 输   出: 无
** 功能描述: 看门狗任务初始化
** 全局变量: 无
** 调用模块: 无
** 作   者: 
** 日   期: 
***********************************************************/
void iwdg_task_init(void)
{   
    register_task_array(iwdg_task,IWDG_TASK_ID);
    
    iwdg_init(IWDG_Prescaler_64,625);  
    osal_start_timer( IWDG_TASK_ID , IWDG_FEED_EVENT , IWDG_FEED_PERIOD , IWDG_FEED_PERIOD );
}

在函数iwdg_task_init()中,先通过register_task_array()函数,绑定IWDG_TASK_ID任务ID和iwdg_task()任务处理函数,然后再初始化STM32的看门狗外设,最后通过osal_start_timer()函数启动一个定时器,不断定时进行喂狗操作。

喂狗事件IWDG_FEED_EVENT和喂狗周期IWDG_FEED_PERIOD是在头文件iwdg_task.h中进行定义的,这里需要注意的是,每个任务事件都是按位操作的,目前每个任务下面最多定义16个事件如下图所示。

#ifndef __IWDG_TASK_H_
#define __IWDG_TASK_H_
  
#define IWDG_FEED_EVENT         0x0001     //定时喂狗事件
#define IWDG_RESET_EVENT        0x0002     //看门狗复位事件

#define IWDG_FEED_PERIOD        500        //定时喂狗周期
#define IWDG_RESET_PERIOD       1000       //看门狗复位时间

extern void iwdg_task_init(void);

#endif

对于OSAL调度器,以下函数接口使用得比较多:

/***********************************************************
** 函 数 名: unsigned char osal_set_event(unsigned char task_id, unsigned short event_flag)
** 输   入: task_id-任务id,event_flag-事件标志
** 输   出: 无
** 功能描述: 立即事件设置函数
** 全局变量: 无
** 调用模块: 无
** 作   者: 
** 日   期: 
***********************************************************/
extern unsigned char osal_set_event(unsigned char task_id, unsigned short event_flag);

/***********************************************************
** 函 数 名: unsigned char osal_clr_event(unsigned char task_id, unsigned short event_flag)
** 输   入: task_id-任务id,event_flag-事件标志
** 输   出: 无
** 功能描述: 立即事件清除函数
** 全局变量: 无
** 调用模块: 无
** 作   者: 
** 日   期: 
***********************************************************/
extern unsigned char osal_clear_event(unsigned char task_id, unsigned short event_flag);

/***********************************************************
** 函 数 名: unsigned char osal_stop_timer( unsigned char task_id, unsigned short event_id )
** 输   入: task_id-任务id,event_id-事件id
** 输   出: 无
** 功能描述: 停止定时器事件
** 全局变量: 无
** 调用模块: 无
** 作   者: 
** 日   期: 
***********************************************************/
extern unsigned char osal_stop_timer( unsigned char task_id, unsigned short event_id );

/***********************************************************
** 函 数 名: unsigned char osal_start_timer(unsigned char task_id, unsigned short event_id,\
                                            unsigned int timeout_value ,unsigned int reload_timeout_value)
** 输   入: task_id-任务id,event_id-事件id,timeout_value-超时值,reload_timeout_value-重装值
** 输   出: 1-添加定时器事件成功,FALSE-添加定时器事件失败
** 功能描述: 添加定时器事件
** 全局变量: 无
** 调用模块: 无
** 作   者: 
** 日   期: 
***********************************************************/
extern unsigned char osal_start_timer(unsigned char task_id, unsigned short event_id, \
                                      unsigned int timeout_value ,unsigned int reload_timeout_value);

函数osal_set_event()和函数osal_clear_event()主要用来设置和清除立即执行的任务事件,当任务事件设置后,在run_system()函数中,该事件会立即执行。

函数osal_start_timer()和osal_stop_timer()主要用来设置和停止定时器任务事件,当定时时间到达后事件才会执行。osal_start_timer()的第三个参数timeout_value表示首次执行的超时时间,第四个参数reload_timeout_value表示下一次执行的重装载时间。如果要定时任务事件只触发一次,只需要把第四个参数reload_timeout_value置为0即可。

对于德州仪器TI提供的原生OSAL调度器,还有消息通信机制,不同的任务之间通过消息队列来进行数据传输,以实现任务间的数据访问。但这里为了使调度器简单易用,并没有把消息通信机制一并整合进来,使用这个调度器,多个任务间的数据仍然以共享内存的方式进行访问。

 

项目的开源地址:

https://github.com/embediot/Embedded-IoT-Project

https://gitee.com/embediot/Embedded-IoT-Project

 

 

开源 | 嵌入式物联网项目开发 - 基于任务和事件的OSAL调度器_第4张图片

你可能感兴趣的:(开源,嵌入式物联网应用开发,嵌入式Linux,物联网应用开发,开源项目)