嵌入式应用软件架构

嵌入式应用软件架构

  • 架构1: 顺序执行(前后台系统)
  • 架构2: 时间片轮询
  • 架构3: 嵌入式实时操作系统

参考文献:

《ARM嵌入式应用程序架构设计实例精讲》

架构1: 顺序执行(前后台系统)

特点:
  1.程序设计简单,思路比较清晰,基本不考虑每个函数执行所需。
  2.实时性和并行性要求不太高的情况下使用。
  3.程序逻辑复杂度提升时,不利于升级维护,也不利于代码优化。


架构例程:

/**************************************************************************************
* FunctionName   : main()
* Description    : 主函数
* EntryParameter : None
* ReturnValue    : None
**************************************************************************************/
int main(void)
{ 
    InitSys();                  // 初始化系统
    InitModule();				// 初始化各个模块
    
     while (1) {
		Task1();
		Task2();
		...
		Taskn();
    }
}


架构2: 时间片轮询

特点:
  1.实时性较高,无需时刻进入每个任务函数。
  2.须使用一个定时器,假设定时器中断为1ms作为系统的最小节拍(当然你可以改成10ms,这个和操作系统一样,中断过于频繁效率就低,中断太长,实时性差)。
  3.程序逻辑复杂度提升时,利于升级维护,也利于代码优化。



  • 定义一个数值:
#define TASK_NUM   (3)                  //  这里定义的任务数为3,表示有三个任务会使用此定时器定时。

uint16 TaskCount[TASK_NUM] ;           //  这里为三个任务定义三个变量来存放定时值
uint8  TaskMark[TASK_NUM];             //  同样对应三个标志位,为0表示时间没到,为1表示定时时间到。
  • 在定时器中断服务函数中添加:
/**************************************************************************************
* FunctionName : TimerInterrupt()
* Description : 定时中断服务函数
* EntryParameter : None
* ReturnValue : None
**************************************************************************************/
void TimerInterrupt(void)
{
    uint8 i;

    for (i=0; i<TASKS_NUM; i++)
    {
        if (TaskCount[i])
        {
              TaskCount[i]--;
              if (TaskCount[i] == 0)
              {
                    TaskMark[i] = 0x01;
              }
        }
   }
}
//代码解释:定时中断服务函数,在中断中逐个判断,如果定时值为0了,表示没有使用此定时器或此定时器已经完成定时,不着处理。
//否则定时器减一,直到为零时,相应标志位值1,表示此任务的定时值到了。
  • 在我们的应用程序中,在需要的应用定时的地方添加如下代码,下面就以任务1为例:
TaskCount[0] = 20;       // 延时20ms
TaskMark[0]  = 0x00;     // 启动此任务的定时器
//到此我们只需要在任务中判断TaskMark[0] 是否为0x01即可。其他任务添加相同,至此一个定时器的复用问题就实现了。

通过上面对1个定时器的复用我们可以看出,在等待一个定时的到来的同时我们可以循环判断标志位,同时也可以去执行其他函数。


时间片轮询法架构:

  1. 设计一个结构体:
// 任务结构
typedef struct _TASK_COMPONENTS
{
    uint8 Run;                 // 程序运行标记:0-不运行,1运行
    uint8 Timer;              // 计时器
    uint8 ItvTime;              // 任务运行间隔时间
    void (*TaskHook)(void);    // 要运行的任务函数
} TASK_COMPONENTS;       // 任务定义
  1. 任务运行标志出来,此函数就相当于中断服务函数,需要在定时器的中断服务函数中调用此函数,这里独立出来,并于移植和理解。
/**************************************************************************************
* FunctionName   : TaskRemarks()
* Description    : 任务标志处理
* EntryParameter : None
* ReturnValue    : None
**************************************************************************************/
void TaskRemarks(void)
{
    uint8 i;

    for (i=0; i<TASKS_MAX; i++)          // 逐个任务时间处理
    {
         if (TaskComps[i].Timer)          // 时间不为0
        {
            TaskComps[i].Timer--;         // 减去一个节拍
            if (TaskComps[i].Timer == 0)       // 时间减完了
            {
                 TaskComps[i].Timer = TaskComps[i].ItvTime;       // 恢复计时器值,从新下一次
                 TaskComps[i].Run = 1;           // 任务可以运行
            }
        }
   }
}
  1. 任务处理
/**************************************************************************************
* FunctionName   : TaskProcess()
* Description    : 任务处理
* EntryParameter : None
* ReturnValue    : None
**************************************************************************************/
void TaskProcess(void)
{
    uint8 i;

    for (i=0; i<TASKS_MAX; i++)           // 逐个任务时间处理
    {
         if (TaskComps[i].Run)           // 时间不为0
        {
             TaskComps[i].TaskHook();         // 运行任务
             TaskComps[i].Run = 0;          // 标志清0
        }
    }  
}
//此函数就是判断什么时候该执行那一个任务了,实现任务的管理操作,
//应用者只需要在main()函数中调用此函数就可以了,并不需要去分别调用和处理任务函数。


时间片轮询实例:时钟显示,按键扫描,和工作状态显示。

  1. 定义一个上面定义的那种结构体变量
/**************************************************************************************
* Variable definition                            
**************************************************************************************/
/* 实际工程中最好改成 register_task(TaskDisplayClock) 和 register_task(TaskDisplayClock) 函数 */
static TASK_COMPONENTS TaskComps[] =
{
    {0, 60, 60, TaskDisplayClock},            // 显示时钟
    {0, 20, 20, TaskKeySan},               // 按键扫描
    {0, 30, 30, TaskDispStatus},            // 显示工作状态

     // 这里添加你的任务。。。。

}; 
//大概意思是,我们有三个任务,没1s执行以下时钟显示,因为我们的时钟最小单位是1s,所以在秒变化后才显示一次就够了。

//由于按键在按下时会参数抖动,而我们知道一般按键的抖动大概是20ms,那么我们在顺序执行的函数中一般是延伸20ms,
//而这里我们每20ms扫描一次,是非常不错的出来,即达到了消抖的目的,也不会漏掉按键输入。

//为了能够显示按键后的其他提示和工作界面,我们这里设计每30ms显示一次,如果你觉得反应慢了,你可以让这些值小一点。
//后面的名称是对应的函数名,你必须在应用程序中编写这函数名称和这三个一样的任务。
  1. 任务列表
// 任务清单
typedef enum _TASK_LIST
{
    TAST_DISP_CLOCK,            // 显示时钟
    TAST_KEY_SAN,             // 按键扫描
    TASK_DISP_WS,             // 工作状态显示
     // 这里添加你的任务。。。。
     TASKS_MAX                                           // 总的可供分配的定时任务数目
} TASK_LIST;
//我们这里定义这个任务清单的目的其实就是参数TASKS_MAX的值,其他值是没有具体的意义的,只是为了清晰的表面任务的关系而已。
  1. 编写任务函数
/**************************************************************************************
* FunctionName   : TaskDisplayClock()
* Description    : 显示任务

* EntryParameter : None
* ReturnValue    : None
**************************************************************************************/
void TaskDisplayClock(void)
{

 

}

/**************************************************************************************
* FunctionName   : TaskKeySan()
* Description    : 扫描任务
* EntryParameter : None
* ReturnValue    : None
**************************************************************************************/
void TaskKeySan(void)
{


}

/**************************************************************************************
* FunctionName   : TaskDispStatus()
* Description    : 工作状态显示
* EntryParameter : None
* ReturnValue    : None
**************************************************************************************/
void TaskDispStatus(void)
{


}

// 这里添加其他任务。。。。。。。。。
  1. 主函数
/**************************************************************************************
* FunctionName   : main()
* Description    : 主函数
* EntryParameter : None
* ReturnValue    : None
**************************************************************************************/
int main(void)
{ 
    InitSys();                  // 初始化

    while (1)
    {
        TaskProcess();             // 任务处理
    }
}

注意任务之间进行数据传递时,需要采用全局变量,除此之外还需要注意划分任务以及任务的执行时间,在编写任务时,尽量让任务尽快执行完成。



架构3: 嵌入式实时操作系统

特点:
  1.任务的管理本封装好。
  2.多线程、多进程(任务之间数据共享:全局变量、信号、消息队列……)


uCOS系统架构例程:

/**************************************************************************************
* FunctionName   : main()
* Description    : 主函数
* EntryParameter : None
* ReturnValue    : None
**************************************************************************************/
int main(void)
{ 
    OSInit();                // 初始化uCOS-II

    OSTaskCreate((void (*) (void *)) TaskStart,        // 任务指针
                (void   *) 0,            // 参数
                (OS_STK *) &TaskStartStk[TASK_START_STK_SIZE - 1], // 堆栈指针
                (INT8U   ) TASK_START_PRIO);        // 任务优先级

    OSStart();                                       // 启动多任务环境
                                       
    return (0); 
}
/**************************************************************************************
* FunctionName   : TaskStart()         
* Description    : 任务创建,只创建任务,不完成其他工作
* EntryParameter : None
* ReturnValue    : None
**************************************************************************************/
void TaskStart(void* p_arg)
{
    OS_CPU_SysTickInit();                                       // Initialize the SysTick.

#if (OS_TASK_STAT_EN > 0)
    OSStatInit();                                               // 这东西可以测量CPU使用量
#endif

 OSTaskCreate((void (*) (void *)) TaskLed,     // 任务1
                (void   *) 0,               // 不带参数
                (OS_STK *) &TaskLedStk[TASK_LED_STK_SIZE - 1],  // 堆栈指针
                (INT8U   ) TASK_LED_PRIO);         // 优先级

 // Here the task of creating your
               
    while (1)
    {
        OSTimeDlyHMSM(0, 0, 0, 100);
    }
}


你可能感兴趣的:(嵌入式基础概念)