时间触发嵌入式系统设计模式 第17章 笔记 混合式调度器

本文探讨了嵌入式系统中实时任务的调度机制,包括合作式和抢占式任务的处理方式,以及如何利用定时器实现精确延时。介绍了任务调度的内核函数,如任务添加、删除和更新,展示了如何在特定的硬件平台上实现任务调度。

co-operative — 合作式任务
pre-emptive — 抢占式 任务

Nu_LB_NUC140 定时器 精确延时

E:\Nu_LB_Nuc140\Nu_LB_NUC140_BSP\SampleCode\Nu-LB-NUC140\Scheduler17

1 Nu-LB-NUC140 源码

main.c

//
// GPIO_LED : GPIO output to control an on-board red LED
// 
// EVB : Nu-LB-NUC140
// MCU : NUC140VE3CN

// low-active output control by GPC12

#include <stdio.h>
#include "NUC100Series.h"
#include "MCU_init.h"
#include "SYS_init.h"
#include "scheduler.h"



void SysTick_Handler(void)
{
	
	hSCH_Update();
	
}


void InitSysTickClk()
{
	
	hSCH_Init();
	
	SysTick->LOAD = 1000 *CyclesPerUs -1;
	SysTick->VAL  = (0x00);
	
	NVIC_EnableIRQ(SysTick_IRQn);
	
	SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_ENABLE_Msk |SysTick_CTRL_TICKINT_Msk;
	
}



// ------私有的常数 -----------------------------------------

// 用于锁定机制
#define LOCKED 1
#define UNLOCKED 0

// ------ 私有变量定义------------------------------



// 锁定标志
static int LED_lock_G = UNLOCKED;

/*------------------------------------------------------------------*-

  LED_Flash_Init()

  - 准备闪烁LED

-*------------------------------------------------------------------*/
void LED_Short_Init(void)
{
   LED_lock_G = UNLOCKED;
}


/*------------------------------------------------------------------*-

  Hardware_Delay_T0()

  产生(大约)N毫秒延迟的函数 

 使用定时器 0 (很容易移植到定时器1).

-*------------------------------------------------------------------*/
void Hardware_Delay_T0(const unsigned int N)   
{
		volatile int delay ;
		int i ;
		 for(i=0;i< N; i++){
		 
			 delay = 2000;
			 while(delay--);
		 }
	   
}   



/*------------------------------------------------------------------*-

  LED_Short_Update()

  在指定的端口引脚上闪烁LED(或产生脉冲给蜂鸣器,等等).

  必须按需要的闪烁频率的两倍计时,这样,对于1HZ的闪烁,(0.5秒亮,0.5秒灭)必须以2HZ计时。

-*------------------------------------------------------------------*/
void LED_Short_Update(void)
   {
  // 该端口有一个锁
   // 如果被锁定,简单的返回
   if (LED_lock_G == LOCKED)
      {
      return;
      }

    // 端口空闲 - 锁定它
   LED_lock_G = LOCKED;

   // 使LED从灭变亮 (反之亦然)
	 GPIO_TOGGLE(PC12);

   // 放开该端口
   LED_lock_G = UNLOCKED;
   }

/*------------------------------------------------------------------*-

  LED_Long_Update()

  演示长任务 (运行时间10秒).
-*------------------------------------------------------------------*/
void LED_Long_Update(void)
   {
   unsigned char  i;

  //该端口有一个锁
   //如果被锁定,简单的返回
   if (LED_lock_G == LOCKED)
      {
      return;
      }

  // 端口空闲 - 锁定它
   LED_lock_G = LOCKED;

   for (i = 0; i < 20; i++)
   {
			 GPIO_TOGGLE(PC14);
       Hardware_Delay_T0(1000);

   }

   //放开该端口
   LED_lock_G = UNLOCKED;
   }



int main(void)
{
    SYS_Init(); 	 
		UART_Open(UART0, 115200);
		printf("Hello World \r\n");	
	
    GPIO_SetMode(PC, BIT12, GPIO_MODE_OUTPUT);
		GPIO_SetMode(PC, BIT14, GPIO_MODE_OUTPUT);
		GPIO_SetMode(PC, BIT15, GPIO_MODE_OUTPUT);
			

		InitSysTickClk();

//    SCH_Add_Task(LED_Flash_Update, 0, 1000);
	
//		SCH_Add_Task(LED_Flash_UpdateD, 0, 2000);

    LED_Short_Init();	
	
		// 添加“短”任务 (1000 ms亮, 1000 ms灭)
		// THIS IS A PRE-EMPTIVE TASK  // 这是一个抢占式任务
		hSCH_Add_Task(LED_Short_Update, 0, 1000, 0);

		// 添加“长”任务 (运行时间 10 秒)
		// THIS IS A CO-OPERATIVE TASK   // 这是一个合作式任务
		hSCH_Add_Task(LED_Long_Update, 0, 20000, 1);	

   while(1)
   {
      hSCH_Dispatch_Tasks();
   }	
}

2 scheduler.h


#ifndef _SCHEDULER_H
#define _SCHEDULER_H


// ------ 公用的函数原型 -------------------------------

// 调度器内核函数
void  hSCH_Dispatch_Tasks(void);
unsigned char hSCH_Add_Task(void (*)(void), unsigned int , unsigned int , unsigned char );  
unsigned char   hSCH_Delete_Task(unsigned char);
void  hSCH_Report_Status(void);
void hSCH_Update(void)   ;
// ------ 公用的常数 -----------------------------------------

// 在程序的运行期间任一时刻请求的任务最大数目

//
// 每个新建项目都必须调整
#define hSCH_MAX_TASKS   (2)   


void hSCH_Init(void) ;
 
#endif


3 scheduler.c

/*------------------------------------------------------------------*-

   hSCH51.C (v1.00) 

  ------------------------------------------------------------------

   /// 混合式调度器内核 ///

   *** 这里是调度器内核函数 ***
   --- 这些函数可以用于所有 8051 芯片 ---

   *** hSCH_MAX_TASKS 必须由用户设置 ***
   --- 参见 "Sch51.h" ---

   *** 包括省电模式***
   --- 必须确认省电模式被修改以适用于所选定的芯片(通常只有在使用扩展8051----)
   --- 诸如 c515c, c509,等等才需要 ---

  -*------------------------------------------------------------------*/

#include "scheduler.h"




// 杂项宏定义
#ifndef TRUE
#define FALSE 0
#define TRUE (!FALSE)
#endif

#define RETURN_NORMAL (unsigned char ) 0
#define RETURN_ERROR (unsigned char ) 1
	
#define ERROR_SCH_TOO_MANY_TASKS (1)
#define ERROR_SCH_CANNOT_DELETE_TASK (2)

#define ERROR_SCH_WAITING_FOR_SLAVE_TO_ACK (3)
#define ERROR_SCH_WAITING_FOR_START_COMMAND_FROM_MASTER (3)

#define ERROR_SCH_ONE_OR_MORE_SLAVES_DID_NOT_START (4)
#define ERROR_SCH_LOST_SLAVE (5)

#define ERROR_SCH_CAN_BUS_ERROR (6)

#define ERROR_I2C_WRITE_BYTE (10)
#define ERROR_I2C_READ_BYTE (11)
#define ERROR_I2C_WRITE_BYTE_AT24C64 (12)
#define ERROR_I2C_READ_BYTE_AT24C64 (13)
#define ERROR_I2C_DS1621 (14)

#define ERROR_USART_TI (21)
#define ERROR_USART_WRITE_CHAR (22)

#define ERROR_SPI_EXCHANGE_BYTES_TIMEOUT (31)
#define ERROR_SPI_X25_TIMEOUT (32)
#define ERROR_SPI_MAX1110_TIMEOUT (33)

#define ERROR_ADC_MAX150_TIMEOUT (44)





// ------ 公用数据类型声明 ----------------------------

// 如果可能的话,存储在 DATA 区, 以供快速存取  
// 每个任务的存储器总和是 8 个字节
typedef  struct 
   {
   // 指向任务的指针 (必须是 'void (void)' 函数)
   void (* pTask)(void);  

   //延迟 (时标) 直到函数将 (下一次) 运行
   // - 详细说明参见 SCH_Add_Task()
   unsigned int Delay;       

   // 在连续的运行之间的间隔 (时标) 
   // - 详细说明参见 SCH_Add_Task() 
   unsigned int Period;       

   // 当任务需要运行时 (由调度器) 加1
   unsigned char RunMe;       

   // 如果任务是合作式的,设置为1
   // 如果任务是抢占式的,设置为0
   unsigned char Co_op;     
   } sTaskH; 

	 
	 

// ------ 公用变量定义 ------------------------------

// 任务队列
sTaskH hSCH_tasks_G[hSCH_MAX_TASKS];

// 用来显示错误代码
// 错误代码的详细资料参见 Main.H 
// 关于错误端口的详细资料参见 Port.H 
//更详细的资料参考本书第14章的内容
unsigned char Error_code_G = 0;

// ------ 私有函数原型 ------------------------------

static void hSCH_Go_To_Sleep(void);

// ------ 私有变量 ----------------------------------------

// 记住自从上一次记录错误以来的时间 (见下文)
static unsigned int Error_tick_count_G;

// 上次的错误代码 (在1分钟之后复位)
static unsigned char Last_error_code_G;


/*------------------------------------------------------------------*-

  hSCH_Dispatch_Tasks()

  这是“调度”函数.  当一个任务 (函数)需要运行时, hSCH_Dispatch_Tasks() 将运行它.
  这个函数必须被主循环 (重复)调用.

-*------------------------------------------------------------------*/
void hSCH_Dispatch_Tasks(void) 
   {
   unsigned char Index;

   // 调度 (运行) 下一个任务 (如果有任务就绪)
   for (Index = 0; Index < hSCH_MAX_TASKS; Index++)
      {
      // 只调度合作式任务
      if ((hSCH_tasks_G[Index].Co_op) && (hSCH_tasks_G[Index].RunMe > 0)) 
         {
         (*hSCH_tasks_G[Index].pTask)();  // 运行任务

         hSCH_tasks_G[Index].RunMe -= 1;   //  RunMe 标志复位/减1

         // 周期性的任务将自动的再次运行
         // - 如果这是个'单次' 任务, 将它从队列中删除
         if (hSCH_tasks_G[Index].Period == 0)
            {
            //比通过调用来删除任务更快
            hSCH_tasks_G[Index].pTask = 0;
            }
         }
      }

   // 报告系统状况
   hSCH_Report_Status();  

   // 这里调度器进行空闲模式 
   hSCH_Go_To_Sleep();          
   }

/*------------------------------------------------------------------*-

  hSCH_Add_Task()

  使用任务 (函数) 每隔一定时隔或在用户定义的延迟之后 运行
  
  Fn_P  - 将被调度的函数的名称.
          注意: 所有被调度的函数必须是 'void, void' -
          即函数没有参数, 并且返回类型为 void 
                   
  Del   - 在任务第一次被运行之前的间隔

  Rep   -  'Rep' 如果为 0, 则该函数只被调用一次,由 'Del'确定调用的时间.
          'Rep' 如果非 0, 那么该函数将按Per的值所确定的间隔被重复调用(下面的例子将有助于理解这些)
          
  Co-op - 如果是合作式任务设置为1; 如果是抢占式任务设置为0

  RETN:   返回被添加任务在任务队列中的位置.如果返回值是hSCH_MAX_TASKS ,那么该任务不能被加到队列中
           (空间不够).  如果返回值 < hSCH_MAX_TASKS, 那么该任务被成功添加。

          注意: 如果以后要删除任务, 将需要这个返回值,参见  hSCH_Delete_Task().


  例子:

  Task_ID = hSCH_Add_Task(Do_X,1000,0,0);
   使函数 Do_X() 在1000 个调度器时标之后运行一次(抢占式任务)

  Task_ID = hSCH_Add_Task(Do_X,0,1000,1);
   使函数 Do_X() 每隔1000 个调度器时标运行一次(合作式任务)
         

  Task_ID = hSCH_Add_Task(Do_X,300,1000,0);
   使函数 Do_X() 每隔1000 个调度器时标运行一次。任务将首先在T=300个时标时被执行,然后是1300个时标,
   2300个时标 ,等等(抢占式任务)
       
 
-*------------------------------------------------------------------*/
unsigned char hSCH_Add_Task(void (* Fn_p)(), // 任务函数指针
                   unsigned int   Del,    // 直到任务第一次运行时的时标数 
                   unsigned int   Per,    // 重复运行之间的时标数
                   unsigned char     Co_op)  // Co_op / pre_emp
   {
   unsigned char Index = 0;
   
   // 首先在队列中找到一个空隙(如果有的话)
   while ((hSCH_tasks_G[Index].pTask != 0) && (Index < hSCH_MAX_TASKS))
      {
      Index++;
      } 
   
   // 是否已经到达队列的结尾 ??  
   if (Index == hSCH_MAX_TASKS)
      {
      // 任务队列已满
      //
      // 设置全局错误变量
      Error_code_G = ERROR_SCH_TOO_MANY_TASKS;

      // 同时返回错误代码
      return hSCH_MAX_TASKS;  
      }
      
   // 如果能运行到这里,说明任务队列中有空间
   hSCH_tasks_G[Index].pTask = Fn_p;
     
   hSCH_tasks_G[Index].Delay  = Del;
   hSCH_tasks_G[Index].Period = Per;

   hSCH_tasks_G[Index].Co_op = Co_op;

   hSCH_tasks_G[Index].RunMe  = 0;

   return Index; // 返回任务的位置 (以便以后删除)
   }

/*------------------------------------------------------------------*-

  hSCH_Delete_Task()

  从调度器删除任务.  注意:并不从存储器中删除相关的函数。仅仅是不再由调度器调用这个函数 
  参数:   Task_index - 任务索引.  由 hSCH_Add_Task()提供. 

  返回:  RETURN_ERROR or RETURN_NORMAL

-*------------------------------------------------------------------*/
unsigned char  hSCH_Delete_Task(unsigned char  Task_index) 
   {
   unsigned char  Return_code;

   if (hSCH_tasks_G[Task_index].pTask == 0)
      {
      // 这里没有任务
      //
      // 设置全局错误变量
      Error_code_G = ERROR_SCH_CANNOT_DELETE_TASK;

      // ...同时返回错误代码
      Return_code = RETURN_ERROR;
      }
   else
      {
      Return_code = RETURN_NORMAL;
      }      
   
   hSCH_tasks_G[Task_index].pTask   = 0;
   hSCH_tasks_G[Task_index].Delay   = 0;
   hSCH_tasks_G[Task_index].Period  = 0;

   hSCH_tasks_G[Task_index].RunMe   = 0;

   return Return_code;       // 返回状态
   }


/*------------------------------------------------------------------*-

  hSCH_Report_Status()

  用来显示错误代码的简单的函数.

  这个版本在连接到端口的LED上显示错误代码,如果需要的话。

  可以修改为通过串行连接等方式报告错误。
  错误只在有限的时间内显示(在 1ms 时标间隔时,60000 时标 = 1 分钟 。).
  此后错误代码被复位为0. 

  这些代码可以很容易的修改为“永远”显示最近的错误。这对于系统可能更为合理。

  更加详尽的资料参见第14章。
 
-*------------------------------------------------------------------*/
void hSCH_Report_Status(void)
   {
#ifdef SCH_REPORT_ERRORS
   // 只在需要报告错误时适用
   // 检查新的错误代码
   if (Error_code_G != Last_error_code_G)
      {
      // 假定LED采用负逻辑
      Error_port = 255 - Error_code_G;
      
      Last_error_code_G = Error_code_G;

      if (Error_code_G != 0)
         {
         Error_tick_count_G = 60000;
         }
      else
         {
         Error_tick_count_G = 0;
         }
      }
   else
      {
      if (Error_tick_count_G != 0)
         {
         if (--Error_tick_count_G == 0)
            {
            Error_code_G = 0; // 复位错误代码
            }
         }
      }
#endif
   }


/*------------------------------------------------------------------*-

  hSCH_Go_To_Sleep()

  本调度器在时钟时标之间将进入空闲模式来节省功耗。下一个时钟时标将使处理器返回到正常工作状态。
  注意: 如果这个函数由宏来实现,或简单地将这里的代码粘贴到“调度”函数中,可以有少量的性能改善。
  然而,通过采用函数调用的方式来实现,可以在开发期间更容易的使用Keil硬件模拟器中的“性能分析器”来估计
  调度器的性能。这方面的例子参见第14章。 

  *** 如果使用看门狗的话,可能需要禁止 这个功能 ***

  *** 根据硬件的需要修改 ***

-*------------------------------------------------------------------*/
void hSCH_Go_To_Sleep()
   {
  // PCON |= 0x01;    // 进入空闲模式 (通用 8051 版本)

   // 在 80c515 / 80c505 上,进入空闲模式需要两个连续的指令,用来避免意外的触发。
   //PCON |= 0x01;    // 进入空闲模式 (#1)
   //PCON |= 0x20;    // 进入空闲模式 (#2)
   }

	 
	 
	 
	 
void hSCH_Update(void)   
   {
   unsigned char  Index;


   //注意: 计算单位为 *时标* (不是毫秒)
   for (Index = 0; Index < hSCH_MAX_TASKS; Index++)
      {
      // 检测这里是否有任务
      if (hSCH_tasks_G[Index].pTask)
         {
         if (hSCH_tasks_G[Index].Delay == 0)
            {
            // 任务需要运行
            // 
            if (hSCH_tasks_G[Index].Co_op)
               {
               // 如果是合作式任务, RunMe标志加1
               hSCH_tasks_G[Index].RunMe += 1;  
               }
            else
               {
              // 如果是抢占式任务,立即运行它
               (*hSCH_tasks_G[Index].pTask)();  // Run the task

               hSCH_tasks_G[Index].RunMe -= 1;   // Reset / reduce RunMe flag

                // 周期性的任务将自动的再次运行
               // - 如果这是个“单次”任务, 将它从队列中删除。
               if (hSCH_tasks_G[Index].Period == 0)
                  {
                  hSCH_tasks_G[Index].pTask  = 0;
                  }
               }

            if (hSCH_tasks_G[Index].Period)
               {
               // 调度定期的任务再次运行
               hSCH_tasks_G[Index].Delay = hSCH_tasks_G[Index].Period;
               }
            }
         else
            {
           // 还没有准备好运行,延迟减1 
            hSCH_tasks_G[Index].Delay -= 1;
            }
         }         
      }
   }   	 
	 
	 
	 
/*------------------------------------------------------------------*-

  hSCH_Init_T2()

  调度器初始化函数,准备调度器数据结构并且设置定时器以所需的频率中断

  必须在使用调度器之前调用这个函数
 

-*------------------------------------------------------------------*/
void hSCH_Init(void) 
   {
   unsigned char  i;

   for (i = 0; i < hSCH_MAX_TASKS; i++) 
      {
      hSCH_Delete_Task(i);
      }

   // 复位全局错误变量
   // - SCH_Delete_Task() 将产生一个错误代码 
   //   (因为任务队列是空的)
   Error_code_G = 0;  


   }	 

/*------------------------------------------------------------------*-
  ----文件结束 -------------------------------------------------
-*------------------------------------------------------------------*/




4 解析

抢占式 任务 在 中断中完成
合作任务 在while 循环中完成

有一个互斥的变量,该变量
的作用是 当 while中运行的任务正常运行的时候,
如果被中断,那么抢占式的任务 ,会马上退出
在这里插入图片描述

(稍后补充)

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值