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中运行的任务正常运行的时候,
如果被中断,那么抢占式的任务 ,会马上退出

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

被折叠的 条评论
为什么被折叠?



