时间触发嵌入式系统设计模式 第14章 笔记

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wowocpp/article/details/82887244

1 函数指针 代码:

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

   Main.C (v1.00)

  ------------------------------------------------------------------
   
  函数指针的演示


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

#include "Main.h"
#include "Printf51.h"
#include <stdio.h>

// ------ 私有函数原型 ------------------------------
void Square_Number(int, int*);

/* ................................................................. */
/* ................................................................. */

int main(void)
   {
   int a = 2, b = 3;      
   void (* pFn)(int, int*); /* 声明 pFn 为 fn 的指针的,fn带有 
                               int 参数和 int 指针参数 
                               (返回 void) */
   int Result_a, Result_b;
   
   //准备在 Keil 硬件模拟器上使用printf()
   Printf51_Init();

   pFn = Square_Number;   //  pFn存放 Square_Number 	的地址
 
   printf("Function code starts at address: %u\n", (tWord) pFn);
   printf("Data item a starts at address:   %u\n\n", (tWord) &a);
   
   // 以传统的方式 调用'Square_Number' 
   Square_Number(a,&Result_a);
   
   // 使用函数指针调用'Square_Number'
   (*pFn)(b,&Result_b);
                         
   printf("%d squared is %d (using normal fn call)\n", a, Result_a); 
   printf("%d squared is %d (using fn pointer)\n", b, Result_b); 

   while(1);

   return 0;
   }

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

void Square_Number(int a, int* b)
   {
   // 演示- 计算a的平方
   *b = a * a;
   }

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


2 简单例子代码

一个用来重复闪烁LED的调度器
一秒亮
一秒灭
如此循环
在这里插入图片描述

3 将简单的例子 移植到 Nu-LB-NUC140 ARM CM0 单片机上面

page210

以下代码 来自 keil freertos

/* Definitions that map the FreeRTOS port interrupt handlers to their CMSIS
standard names. */
#define vPortSVCHandler SVC_Handler
#define xPortPendSVHandler PendSV_Handler
#define xPortSysTickHandler SysTick_Handler
SysTick_Handler

void xPortSysTickHandler( void )
{
uint32_t ulPreviousMask;

	ulPreviousMask = portSET_INTERRUPT_MASK_FROM_ISR();
	{
		/* Increment the RTOS tick. */
		if( xTaskIncrementTick() != pdFALSE )
		{
			/* Pend a context switch. */
			*(portNVIC_INT_CTRL) = portNVIC_PENDSVSET;
		}
	}
	portCLEAR_INTERRUPT_MASK_FROM_ISR( ulPreviousMask );
}
    void prvSetupTimerInterrupt( void )
{
	/* Configure SysTick to interrupt at the requested rate. */
	*(portNVIC_SYSTICK_LOAD) = ( configCPU_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;
	*(portNVIC_SYSTICK_CTRL) = portNVIC_SYSTICK_CLK | portNVIC_SYSTICK_INT | portNVIC_SYSTICK_ENABLE;
}

#define portNVIC_SYSTICK_CTRL		( ( volatile uint32_t *) 0xe000e010 )
#define portNVIC_SYSTICK_LOAD		( ( volatile uint32_t *) 0xe000e014 )

#define __HSI       (50000000UL)    /*!< PLL default output is 50MHz */
uint32_t SystemCoreClock  = __HSI;             /*!< System Clock Frequency (Core Clock) */
#define configCPU_CLOCK_HZ              ( SystemCoreClock )
#define configTICK_RATE_HZ              ( ( portTickType ) 1000 )

E:\Nu_LB_Nuc140\Nu_LB_NUC140_BSP\SampleCode\Nu-LB-NUC140\Scheduler\KEIL

4 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"


extern void SCH_Init(void) ;
	 
extern void SCH_Update(void) ;

void SysTick_Handler(void)
{
	
	SCH_Update();
	
}


void InitSysTickClk()
{
	
	SCH_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;
	
}

void LED_Flash_Update(void)
{

	GPIO_TOGGLE(PC14);
	
}

void LED_Flash_UpdateD(void)
{

	GPIO_TOGGLE(PC12);
	
}


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);
	

   while(1)
   {
      SCH_Dispatch_Tasks();
   }	
}

scheduler.h


#ifndef _SCHEDULER_H
#define _SCHEDULER_H


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

// 调度器内核函数
void  SCH_Dispatch_Tasks(void);
unsigned char SCH_Add_Task(void (*) (void), const unsigned int, const unsigned int);  
unsigned char SCH_Delete_Task(const unsigned char);
void  SCH_Report_Status(void);

// ------ 公用的常数 -----------------------------------------

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

// 在程序的运行期间
// 每个新建项目都必须调整
#define SCH_MAX_TASKS   (3)   
 
#endif

scheduler.c

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

   SCH51.C (v1.00) 

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

   /// 这里是调度器内核函数 ///

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

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

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

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




#include "scheduler.h"

typedef unsigned char tByte;
typedef unsigned int  tWord;
typedef unsigned long tLong;


#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)


#define RETURN_NORMAL  0
#define RETURN_ERROR   1

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

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

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

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

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

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



// 任务队列
sTask SCH_tasks_G[SCH_MAX_TASKS];

// 用来显示错误代码
// 错误代码的详细资料参见 Main.H 
// 关于错误端口的详细资料参见 Port.H 

unsigned char Error_code_G = 0;

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

static void SCH_Go_To_Sleep(void);

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

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

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


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

  SCH_Dispatch_Tasks()

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

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

   // 调度 (运行) 下一个任务 (如果有任务就绪)
   for (Index = 0; Index < SCH_MAX_TASKS; Index++)
   {
      
		 if (SCH_tasks_G[Index].RunMe > 0) 
      {
         (*SCH_tasks_G[Index].pTask)();  // 运行任务

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

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

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

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

}


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

  SCH_Add_Task()

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

  PERIOD   -  'PERIOD' 如果为 0, 则该函数u将在由“DELAY”g确定的时间被调用一次.
              'PERIOD' 如果非 0, 那么该函数将按PERIOD的值所确定的间隔被重复调用(下面的例子将有助于理解这些)
          

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

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


  例子:

  Task_ID = SCH_Add_Task(Do_X,1000,0,0);
   使函数 Do_X() 在1000 个调度器时标之后运行一次

  Task_ID = SCH_Add_Task(Do_X,0,1000,1);
   使函数 Do_X() 每隔1000 个调度器时标运行一次
         

  Task_ID = SCH_Add_Task(Do_X,300,1000,0);
   使函数 Do_X() 每隔1000 个时标定时运行一次。任务将首先在T=300个时标时被执行,然后是1300个时标,
   2300个时标 ,等等            
 
-*------------------------------------------------------------------*/
unsigned char SCH_Add_Task(void (* pFunction)(), 
                   const unsigned int DELAY, 
                   const unsigned int PERIOD)    
   {
   unsigned char Index = 0;
   
   // 首先在队列中找到一个空隙(如果有的话)
   while ((SCH_tasks_G[Index].pTask != 0) && (Index < SCH_MAX_TASKS))
      {
      Index++;
      } 
   
   // 是否已经到达队列的结尾 ??   
   if (Index == SCH_MAX_TASKS)
      {
       // 任务队列已满
      //
      // 设置全局错误变量
      Error_code_G = ERROR_SCH_TOO_MANY_TASKS;

      // 同时返回错误代码
      return SCH_MAX_TASKS;  
      }
      
   // 如果能运行到这里,说明任务队列中有空间
   SCH_tasks_G[Index].pTask  = pFunction;
     
   SCH_tasks_G[Index].Delay  = DELAY;
   SCH_tasks_G[Index].Period = PERIOD;

   SCH_tasks_G[Index].RunMe  = 0;

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

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

  SCH_Delete_Task()

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

  返回值:  RETURN_ERROR or RETURN_NORMAL

-*------------------------------------------------------------------*/
unsigned char  SCH_Delete_Task(const tByte TASK_INDEX) 
   {
   unsigned char Return_code;

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

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

   SCH_tasks_G[TASK_INDEX].RunMe   = 0;

   return Return_code;       // 返回状态
   }


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

  SCH_Report_Status()

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

  这个版本将在连接到端口的LED上显示错误代码,
  如果需要的话,可以修改为通过串行连接等方式报告错误。
  错误只在有限的时间内显示(在 1ms 时标间隔时,60000 时标 = 1 分钟 。).
  此后错误代码被复位为0. 

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

  更加详尽的资料参见第10章。

-*------------------------------------------------------------------*/
void SCH_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
   }


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

  SCH_Go_To_Sleep()

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

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

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

-*------------------------------------------------------------------*/
void SCH_Go_To_Sleep()
   {

   }
	 
	 
	 
/*------------------------------------------------------------------*-

  SCH_Update  中断调用

   这是调度器的中断服务程序.  初始化函数 SCH_Init_T1()中的定时器设置决定了它的调用频率。
   这个版本由定时器1中断触发
 
-*------------------------------------------------------------------*/
void SCH_Update(void) 
{
   tByte Index;

   // 重装定时器
   // 注意:计算单位为“时标”(不是毫秒)
   for (Index = 0; Index < SCH_MAX_TASKS; Index++)
   {
       // 检测这里是否有任务
      if (SCH_tasks_G[Index].pTask)
      {
         if (SCH_tasks_G[Index].Delay == 0)
         {
           // 任务需要运行
            SCH_tasks_G[Index].RunMe += 1;  //  'RunMe' 标志加1

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

		 for (i = 0; i < SCH_MAX_TASKS; i++) 
		 {
				SCH_Delete_Task(i);	
		 }

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

 

5 主要结构体

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

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

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

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

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

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

pTask ---- 该任务 的函数指针

Period ---- 该任务 间隔多长时间 执行一次

Delay ---- 设置为Period 的大小,然后一直递减 知道 等于0. 执行函数指针对应函数一次
然后重新设置为Period 的大小
RunMe — 任务是是否 需要执行

重点看:
void SCH_Update(void) ------ 定时器 tick 中断 调用,负责设置标志。

void SCH_Dispatch_Tasks(void) ----- 循环 while 调用,负责根据标志,执行任务。

6 注意

需要 用户 自己 手动设置 运行的任务数量
在这里插入图片描述

(稍后补充)

猜你喜欢

转载自blog.csdn.net/wowocpp/article/details/82887244