注册 登录
电子工程世界-论坛 返回首页 EEWORLD首页 频道 EE大学堂 下载中心 Datasheet 专题
四蹄踏雪II号的个人空间 https://home.eeworld.com.cn/space-uid-224269.html [收藏] [复制] [分享] [RSS]
日志

AVR学习日志(五十二)—学习使用基于时间的调度器

已有 1697 次阅读2010-11-30 10:27

(200.40 K) 该附件被下载次数 2

发一个基于时间的调度器,这还是懒猫两个月前弄,只是比较懒所以今天整理一下。首先声明,此调度器非懒猫本人原创,是参考Michael J.Pont的《Patterns for Time-Triggered Embedded Systems》这本书移值过来的,懒猫只是拿来学习之用,如果你想用于商业用途,最好给Michael J.Pont商量一下^_^Michael J.Pont用的处理器是C8051,而我用的处理器是Atiny48,AVR这款单片机片内只有4k程序空间,所以想跑ucos之类的系统,有点不大可能,除非你把ucos阉割的体无完肤。

什么是调度器,顾名思义,在处理器上调度器就是按一定方式安排单片机执行相关的任务,广义上来说调度器可以算一个简单的操作系统,把整个世界看成一个操作系统的话,它应该只是一个小家庭或小集体吧^_^从实现方式上来说调度器可以看作是一个由许多任务共享的定时器中断服务程序。调度器一般可以分为:合作式调度器与抢占式调度器以及混合式调度器。合作式调度器一般比较简单,它只提供了一种单任务的系统结构,即任务以周期性单次运行,当任务需要运行时,被添加到等待队列,当处理器空闲时,运行等待下一下,任务直到完成,然后由调度器来控制下一个。抢占式调度器对外部事件的响应速度快,但一般认为它不可预测,可靠性不太好,因为在运行中等待的任务运行一段固定时间,如果没有完成,将被暂停并放回到等待队列。然后下一个等待任务将运行一段固定时间,再执行类似前面的内容,所以你根本无法准确的预测它这一时刻在执行什么,就像你在炒股,却不能预测股市走向,那投资风险就大了哦……有了极端就有了庸之道,所以至于混合式当然是兼备了前两者的一些特点,它提供了有限的多任务功能,支持多个合作式调度任务,支持一个抢占式任务。

好了调度器方面的知识就先介绍互这吧,建议你有空了能看看Michael J.Pont这个英国佬的那本书,写的还不错,至少比某些国内的所谓的大学教授呀专家呀写的好。懒猫弄的是基于时间触发的合作式调度器,下面给出整理好的程序,共有三个部分,两个头文件TIMER0.H,includes.h,还有一个C文件main.c.别忘了懒猫用的处理器是ATtiny48,编译环境是winavr100110,如果你想在其它单片机或其它编译环境下运行,注意修改相关参数哦。TIMER0.h里面主要是timer0的一些宏定义(实际上我没有扩展,所以也没有用上),includes.h里面是一些要包含的头文件,main.c里面有调度器的所有函数,下面由上来,详细信息可以看附件中的文件:

一 includes.h

/****************************************************************************

//文件名称:INCLUDES.H

//功 能:General including header for whole project

//作 者:LanMaoAiFei

//建立日期:2010.09.25

//版 本:V1.0

//备 注:MCU-ATtiny48 Compiler-winavr100110

****************************************************************************/

#ifndef _INCLUDES_H_

#define _INCLUDES_H_

#include

#include

#include

#include

#include

#include

#include

#include

#include

//#include "timer0.h"

#endif

二、TIMER0.h

这个头文件没什么用,所以就不贴出来了,有兴趣的可以参看附件中的源文件。

三、main.c

/****************************************************************************

//文件名称:MAIN.C

//功 能:main program entry for the whole project

//作 者:LanMaoAiFei

//建立日期:2010.09.25

//版 本:V1.0

//备 注:MCU-ATtiny48 Compiler-winavr100110

****************************************************************************/

#include "includes.h"

#include

//按键输入宏定义

#define KEY_FIR (PIND&(1<

#define KEY_SEC (PIND&(1<

#define KEY_THD (PIND&(1<

#define KEY_FOU (PIND&(1<

//LED灯状态定义

#define LED_ON(N) PORTD &= ~_BV(N+3)

#define LED_OFF(N) PORTD |= _BV(N+3)

#define LED_NF(N) PORTD ^= _BV(N+3)

//宏定义最大执行任务数

#define SCH_MAX_TASKS 10

//最大错误数

#define Error_SCH_TOO_MANY_TASKS 0x0a

//没有任务可删除

#define ERROR_SCH_CANNOT_DELETE_TASK 0xff

//定义错误输出端口

#define Error_port PORTB

//定义布尔变量类型

typedef enum {

FALSE = 0,

TRUE = 1,

}BOOL;

//宏定义错误状态与正确状态

#define RETURN_ERROR FALSE

#define RETURN_NORMAL TRUE

BOOL LED_State_G;

//公用数据类型声明,可能的话存储在DATA区以供快速存取,每个任务的存储器总和是7个字节

typedef struct

{

//指向任务的指针(必须是一个“void(void)”函数)

void (*pTask)(void);

//延迟(时标)直到函数将(下一次)运行

uint16_t Delay;

//连续运行之间的间隔(时标)

uint16_t Period;

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

uint8_t RunMe;

}sTask;

//任务队列

sTask SCH_Tasks_G[SCH_MAX_TASKS];

//用来显示错误代码

uint8_t Error_Code_G = 0;

//跟踪自从上次记录错误以来的时间

uint16_t Error_Tick_Count_G;

//上次的错误代码(在一分钟之后复位)

uint8_t Last_Error_Code_G;

/*************************************************************************

//函数名称:uint8_t SCH_Add_Task(void(uint16_t *pFunction)(),

// const uint16_t DELAY,

// const uint16_t PERIOD)

//功 能:任务调度函数

//入口参数:void(uint16_t *pFunction)() - 被调度函数的名称

// const uint16_t DELAY - 在任务第一次被运行之前的间隔(时标)

// const uint16_t PERIOD - 如果PERIOD为0,该函数将在DELAY确定的

// 时间被调用一次;如果PERIOD非0,那么

// 该函数将按PERIOD的值在确定的间隔被重

// 复调用

//出口参数:返回被添加任务在任务队列中的位置

//备 注:当一个任务(函数)需要运行时SCH_Dispatch_Tasks()

// 将运行它,这个函数必需被主函数循环(重复)调用

*************************************************************************/

uint8_t SCH_Add_Task(void(*pFunction)(void),

const uint16_t DELAY,

const uint16_t PERIOD)

{

uint8_t 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; //返回任务位置(以便以后删除)

}

/*************************************************************************

//函数名称:BOOL SCH_Delete_Task(const uint8_t TASK_INDEX)

//功 能:从调度器中删除任务

//入口参数:TASK_INDEX - 任务索引,由SCH_Add_Task()提供

//出口参数:RETURN_ERROR或RETURN_NORMAL

//备 注:无

*************************************************************************/

uint8_t SCH_Delete_Task(const uint8_t TASK_INDEX)

{

uint8_t Return_code;

if(SCH_Tasks_G[TASK_INDEX].pTask ==0)

{

//这里没有任务,设置全局错误变量为没任务可删除

Error_Code_G = ERROR_SCH_CANNOT_DELETE_TASK;

//同时返回错误代码

Return_code = 0; //RETURN_ERROR;

}

else

{

Return_code = 1;//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;

}

/*************************************************************************

//函数名称:void SCH_Dispatch_Tasks(void)

//功 能:任务调度函数

//入口参数:NULL

//出口参数:NULL

//备 注:当一个任务(函数)需要运行时SCH_Dispatch_Tasks()

// 将运行它,这个函数必需被主函数循环(重复)调用

*************************************************************************/

void SCH_Dispatch_Tasks(void)

{

uint8_t 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标志

//周期性任务将自动地再次运行

//如果是个“单次”任务,将它从队列中删除

if(SCH_Tasks_G[Index].Period == 0)

{

SCH_Delete_Task(Index);

}

}

}

//报告系统状况

//SCH_Report_Status();

//这里调度器进入空闲模式

//SCH_Go_To_Sleep();

}

/*************************************************************************

//函数名称:void SCH_Report_Status(void)

//功 能:用来显示错误代码

//入口参数:无

//出口参数:无

//备 注:无

*************************************************************************/

void SCH_Report_Status(void)

{

//只在需要报告错误时适用

#ifdef SCH_REPORT_ERRORS

//检查新的错误代码

if(Error_Code_G != Last_Error_Code_G)

{

//这里暂时采用PORTB口做为错误输出口

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

}

/************************************************

//函数名称:void port_init(void)

//功 能:端口初始化

//入口参数:NULL

//出口参数:NULL

//备 注:NULL

************************************************/

void port_init(void)

{

//PORTC = 0x01;

//DDRC = 0x01; //PC0为PWM输出口

PORTD = 0xff;

DDRD = 0xf0; //高四位输出接LED灯,低四位输入接按键

}

/************************************************

//函数名称:extern void TC0_init( void )

//功 能:TIMER/COUNTER0 initialization.

//入口参数:NULL

//出口参数:NULL

//备 注:NULL

************************************************/

void TC0_init( void )

{

uint8_t i;

for(i = 0; i < SCH_MAX_TASKS; i++)

{

SCH_Delete_Task(i);

}

//复位全局错误代码

//SCH_Delete_Task()将产生一个错误代码(因为队列是空的)

Error_Code_G = 0;

TCNT0 = 0x83; //64频内部8M晶振,定时8ms

/*Timer0 works in Normal mode with TOP = 0xFF.

With the CLKio = 1MHz, scaled timer0 input clock is 8/64MHz.*/

TCCR0A = (0<

/*Mask all timer0 relevant interrupts.*/

TIMSK0 = (0<

}

/************************************************

//函数名称:ISR( TIMER0_OVF_vect )

//功 能:TIMER/COUNTER0 HARDWARE-LEVEL ISR: TC0 General Timeout Server

// When timer0 work in normal mode with input clock 1/256MHz, every 64ms

// when it counts to 0xFF an overflow interrupt would be generated.

//入口参数:NULL

//出口参数:NULL

//备 注:NULL

************************************************/

ISR( TIMER0_OVF_vect )

{

/*Increase the software counter.*/

//tmr0_soft_cnt++;

TCNT0 = 0x83; //64频内部8M晶振,定时1ms

uint8_t 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_Start(void)

//功 能:通过允许中断来启动调度器

//入口参数:无

//出口参数:无

//备 注:无

*************************************************************************/

void SCH_Start(void)

{

sei();

}

/*************************************************************************

//函数名称:void Led_Flash_Task(void)

//功 能:一个测试任务,led灯闪烁

//入口参数:无

//出口参数:无

//备 注:无

*************************************************************************/

void Led_Flash_Task(void)

{

//使LED从灭到亮

if(LED_State_G == TRUE)

{

LED_ON(4);

LED_State_G = FALSE;

}

else

{

LED_OFF(4);

LED_State_G = TRUE;

}

}

void Led_Flash_Test(void)

{

LED_NF(3);

}

void Led_Flash_led(void)

{

LED_NF(2);

}

void my_task(void)

{

LED_NF(1);

}

void key_det_task(void)

{

if(!KEY_FIR )

{

while(!KEY_FIR );

SCH_Add_Task(my_task,10,500);

}

if(!KEY_SEC)

{

while(!KEY_SEC);

SCH_Delete_Task(1);

}

}

/************************************************************************

//函数名称:int main(void)

//功 能:主函数

//入口参数:NULL

//出口参数:NULL

//备 注:NULL

************************************************************************/

int main(void)

{

port_init(); //端口初始化

TC0_init();//设置调度器(调度器初始化)

SCH_Add_Task(Led_Flash_Task,0,200);

SCH_Add_Task(Led_Flash_Test,10,500);

SCH_Add_Task(Led_Flash_led,10,1000);

SCH_Add_Task(key_det_task,10,100);

SCH_Start(); //启动调度器

while(1)

{

SCH_Dispatch_Tasks();

}

return 0;

}

主要就是一个调度器,与外围电路联系不大,不过还是把懒猫的测试电路发一下吧,见下面。程序貌似有点长,不过懒猫大部分都注释过了,应该不难理解,主要思想就是拉一个定时器让它计时,让它来指挥调度器什么时候来调度下一下任务,当然如果想把处理器的开销与功耗降低到最小,高度器的时间间隔应该设置为所有任务的运行间隔(以及时间偏移量)的”最大公因数”,所有任务的运行时间都应小于调度时间间隔,以保证调度程序总是能够在任何任务需要运行的时候调用它。你也可以加上中断,当没有任务时可以让它进入低功耗模式,也履行一下所谓的低碳生活,为地球多节约一度电……

----测试电路图

好了,懒猫该继续修行去了~~

最后再喊一下口号,鼓励一下自己:

每天进步一点点,开心多一点^_^

发表评论 评论 (1 个评论)
回复 hongqi1029 2012-8-19 16:44
谢谢

facelist doodle 涂鸦板

您需要登录后才可以评论 登录 | 注册

热门文章