zoukankan      html  css  js  c++  java
  • 硬件——STM32 , 软件框架

    单片机应用程序的框架大概有三种:

    1,简单的前后台顺序执行程序.

    2,时间片轮询法.

    3,应用操作系统.

    下面我们主要来讲解时间片轮询法:

            在这里我们先介绍一下定时器的复用功能。就是使用1个定时器,可以是任意的定时器,这里不做特殊说明,下面假设有3个任务,那么我们应该做如下工作:

    1. 初始化定时器,这里假设定时器的定时中断为1ms(当然你可以改成10ms,这个和操作系统一样,中断过于频繁效率就低,中断太长,实时性差)。

    2. 定义一个数值:
    #define TASK_NUM   (3)                       //  这里定义的任务数为3,表示有三个任务会使用此定时器定时
    uint16 TaskCount[TASK_NUM] ;           //  这里为三个任务定义三个变量来存放定时值,用于存放每个任务需要的时间
    uint8  TaskMark[TASK_NUM];               //  同样对应三个标志位,为0表示时间没到,为1表示定时时间到


    3. 在定时器中断服务函数中添加:
    /**************************************************************************************
    * FunctionName : TimerInterrupt()
    * Description : 定时中断服务函数
    * EntryParameter : None
    * ReturnValue : None
    **************************************************************************************/
    void TimerInterrupt(void)                   //定时器溢出之后就会进入到这里,可以是1ms,也可以是10ms
    {
        uint8 i;

        for (i=0; i<TASKS_NUM; i++)        //分别去查看每个任务,这里TASKS_NUM是表示任务的个数,这里是3个
        {
            if (TaskCount[i])                         //TaskCount[i],这个是代表第i个任务当前延迟的时间,如果没有到延迟时间到0,就继续延迟
            {
                  TaskCount[i]--;                     //减少一个刻度
                  if (TaskCount[i] == 0)           //如果TaskCount[i]为0,说明我们指定的时间到了
                  {
                        TaskMark[i] = 0x01;       //TaskMark[i],表示第i个任务的延迟已经完成,可以执行了
                  }
            }
       }
    }




    4. 在我们的应用程序中,在需要的应用  [延迟]  的地方添加如下代码:

    TaskCount[0] = 20;        // 延时20ms,比如按键程序,需要我们每20ms检测一次,并不需要实时查看
    TaskMark[0]  = 0x00;     // 启动此任务的定时器


    到此我们只需要在任务中判断TaskMark[0] 是否为0x01即可。其他任务添加相同,至此一个定时器的复用问题就实现了。在等待一个定时的到来的同时我们可以循环判断标志位,同时也可以去执行其他函数。  [即:在一个函数运行的间隙中我们可以运行其他的函数]

    那么如果我们在一个函数延时的时候去执行其他函数,充分利用CPU时间,是不是和操作系统有些类似了呢?但是操作系统的任务管理和切换是非常复杂的。下面我们就将利用此方法架构一个新的应用程序。

    时间片轮询法的架构:

     1.设计一个结构体:这个是定义的一个  模子, 用于扣蛋糕用的,  实际上是定义任务包括几个要素,我们把任务的要素都列出来,

        这样就可以使每个任务都有规定格式的要素了


    typedef struct _TASK_COMPONENTS      //任务结构体,任务的几个要素  [这个是很重要的]
    {
        uint8 Run;                                               // 程序运行标记:0-不运行,1-运行  ,这里是程序会不会运行
        uint8 Timer;                                            // 计时器, 这个是用于在定时器中减减的
        uint8 ItvTime;                                          // 任务运行间隔时间, 这个是用于更新参数的
        void (*TaskHook)(void);                          // 要运行的任务函数
    } TASK_COMPONENTS;                           // 任务定义

    2. 任务运行标志出来,此函数就相当于中断服务函数,需要在定时器的中断服务函数中调用此函数,这里独立出来,便于移植和理解。
    /**************************************************************************************
    * FunctionName   : TaskRemarks()          //放到定时器中
    * Description    : 任务标志处理
    * EntryParameter : None
    * ReturnValue    : None
    **************************************************************************************/
    void TaskRemarks(void)                            //要放到  timer中断中
    {
        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;            // 任务可以运行, 任务的延迟已经过了, 可以继续运行了
                }
            }
       }
    }

    3. 任务处理:实现任务管理操作, 用于循环判断哪个任务需要运行了
    /**************************************************************************************
    * FunctionName   : TaskProcess()
    * Description    : 任务处理
    * EntryParameter : None
    * ReturnValue    : None
    **************************************************************************************/
    void TaskProcess(void)
    {
        uint8 i;
        for (i=0; i<TASKS_MAX; i++)           // 逐个任务时间处理
        {
             if (TaskComps[i].Run)                 // 这个标志不为0, 标志为0, 标志置1是在timer时间中断中, 是在时间查询那里的
            {
                 TaskComps[i].TaskHook();      // 运行任务, 这个是一个函数, 在运行完这个函数后, 我们下一步操作就是清除标志位
                 TaskComps[i].Run = 0;           // 标志清0
            }
        }   
    }

    假设我们有三个任务:时钟显示,按键扫描,和工作状态显示

    1. 定义一个上面定义的那种结构体变量:
    /**************************************************************************************
    * Variable definition                           
    **************************************************************************************/
    static TASK_COMPONENTS TaskComps[] =
    {   //0:程序刚开始不运行;  60:用于减减的计数;  60:函数重复周期,用于重装减减;  TaskDisplayClock:是一个函数名,用于重复运行的刷屏;
        {0, 60, 60, TaskDisplayClock},           // 显示时钟         
        {0, 20, 20, TaskKeySan},                   // 按键扫描
        {0, 30, 30, TaskDispStatus},              // 显示工作状态
         // 这里添加你的任务。。。。
    };

    在定义变量时,我们已经初始化了值,这些值的初始化,非常重要,跟具体的执行时间优先级等都有关系,这个需要自己掌握。

    上面的结构体初始化表示,我们有三个任务:

    ①每1s执行一下时钟显示,因为我们的时钟最小单位是1s,所以在秒变化后才显示一次就够了。
    ②由于按键在按下时电平会抖动,而我们知道一般按键的抖动大概是20ms,那么我们在顺序执行的函数中一般是延迟20ms等待电平稳定后才开始采集电平,而这里我们每20ms扫描一次,效果是非常不错的,即达到了消抖的目的,也不会漏掉按键输入。
    ③为了能够显示按键后的其他提示和工作界面,我们这里设计每30ms显示一次,如果你觉得反应慢了,你可以让这些值小一点。

    TaskDisplayClock, TaskKeySan,  TaskDispStatus 后面的名称是对应的函数名,你必须在应用程序中编写这函数来分别完成三个任务。

    2. 任务列表:(也叫作任务清单)
    typedef enum _TASK_LIST
    {
        TAST_DISP_CLOCK,               // 显示时钟
        TAST_KEY_SAN,                     // 按键扫描
        TASK_DISP_WS,                     // 工作状态显示
         // 这里添加你的任务。。。。
         TASKS_MAX                                           // 总的可供分配的定时任务数目
    } TASK_LIST;
    好好看看,我们这里定义这个任务清单的目的其实就是参数TASKS_MAX的值,其他值是没有具体的意义的,只是为了清晰的表面任务的关系而已。

  • 相关阅读:
    亿级 Web 系统的容错性建设实践
    Spring 4支持的Java 8新特性一览
    Java多线程干货系列—(一)Java多线程基础
    Sublime Text 2 实用快捷键(Mac OS X)
    spring-事务管理
    100 个 Linux 常用命令大全
    这些年MAC下我常用的那些快捷键
    Java 容器源码分析之HashMap多线程并发问题分析
    MySQL索引结构--由 B-/B+树看
    Java 容器之 Connection栈队列及一些常用
  • 原文地址:https://www.cnblogs.com/chulin/p/8184610.html
Copyright © 2011-2022 走看看