尝试写个简单基于事件调度系统TEvent 20081017 【恢复】
TEvent 20081017Author: ATmega32
1.数据结构
事件值
uint8EventValue;
其中EVT_TASK_ENTRY(0XFF)和EVT_TASK_DELAY(0XFE)内核已使用
事件结构体
typedef struct
{
TLinkLink;
uint8Flag;
uint8EventValue;
}TEvent;
任务结构体
typedef struct
{
uint8 Flag;
TQueue EventQueue;
TTaskExecute *Execute;
}TTask;
定时器结构体
typedef struct
{
TLink Link;
size_tRel;
size_tAbs;
uint8 Flag;
uint8 TaskId;
uint8 EventValue;
}TTimer;
休眠结构体
typedefstruct
{
volatile uint8 SleepEnable;
volatile uint8 SleepLevel;
}
TSleepBlock;
2.需要配置的地方(在EV_Config.h)
#define TASK_MAX_COUNT 3
最大任务数,用户可使用的任务ID为0到TASK_MAX_COUNT-1
#define EVENT_MAX_COUNT30
最大事件数,EventTab为用户时间提供存储空间
#define TIMER_MAX_COUNT10
最大定时器数,TimerTab为用户定时器提供存储空间
3.用户接口函数
void EVT_FreeMemorry(TEvent *Event);
释放事件存储空间
TEvent *EVT_AllocMemory(void);
分配事件存储空间,如果分配成功,返回事件结构体指针,如果分配失败,返回NULL
TEvent * EVT_Create(uint8 EventValue);
创建一个事件,包含分配事件存储空间并赋值事件值,如果分配成功,返回事件结构体指针,如果分配失败,返回NULL
uint8 EVT_Scheduler(void);
内核调度,如果内核调度对所有任务循环调度一周,如果没有任何事件,则返回0,否则,返回1
此返回值可作为系统进入休眠条件
void TSK_Init(TTaskExecute *Execute,uint8 TaskId);
任务初始化,初始化时,允许内核调度执行任务事件
void TSK_InitWithEntryEvent(TTaskExecute *Execute,uint8 TaskId);
任务初始化,初始化时,允许内核调度执行任务事件,并触发一个EVT_TASK_ENTRY(0XFF)的事件
void TSK_Disable(uint8 TaskId);
禁止内核调度执行任务事件
void TSK_Enable(uint8 TaskId);
允许内核调度执行任务事件,任务初始化设为允许
void TSK_EventPutLock(uint8 TaskId);
禁止向ID为TaskId的任务事件队列写入事件
void TSK_EventPutUnlock(uint8 TaskId);
允许向ID为TaskId的任务事件队列写入事件
void TSK_EventGetLock(uint8 TaskId);
禁止从ID为TaskId的任务事件队列读取事件
void TSK_EventGetUnlock(uint8 TaskId);
允许从ID为TaskId的任务事件队列读取事件
void TSK_EventLock(uint8 TaskId);
禁止向ID为TaskId的任务事件队列写入和读取事件
void TSK_EventUnlock(uint8 TaskId);
允许向ID为TaskId的任务事件队列写入和读取事件
void TSK_EventClear(uint8 TaskId);
清除ID为TaskId的任务事件队列里的所有事件
TEvent * EVT_PostToTask(uint8 EventValue,uint8 TaskId);
向ID为TaskId的任务触发一个事件,事件值为EventValue,如果成功,返回事件结构体指针,否则,返回NULL
TTimer * EVT_DelayPostToTask(uint8 EventValue,size_t delay,uint8 TaskId);
分配一个定时器,此定时器将在一定延时后,向ID为TaskId的任务触发一个事件。
如果分配定时器成功,返回定时器结构体指针,否则,返回NULL
TEvent * EVT_Post(uint8 EventValue);
向当前任务触发一个事件,事件值为EventValue,如果成功,返回事件结构体指针,否则,返回NULL
此函数只在能在任务中使用
TTimer * EVT_DelayPost(uint8 EventValue,size_t delay);
分配一个定时器,此定时器将在一定延时后,向当前任务触发一个事件。
如果分配定时器成功,返回定时器结构体指针,否则,返回NULL。
此函数只能在任务中使用
TTimer * TSK_Delay(size_t delay)
等同于EVT_DelayPost(EVT_TASK_DELAY,delay),实际上并非函数,是一个宏
分配一个定时器,此定时器将在一定延时后,向当前任务触发一个事件值为EVT_TASK_DELAY的事件。
如果分配定时器成功,返回定时器结构体指针,否则,返回NULL。
此函数只能在任务中使用
void EVT_Delete(uint8 TaskId,TEvent *Event);
删除ID为TaskId的任务的事件队列里Event事件结构体指针指向的事件
void TMR_TicksHander(void);
定时器节拍处理
TTimer *TMR_AllocMemory(void);
分配一个定时器存储空间,如果成功,返回定时器结构体指针,否则,返回NULL
void TMR_FeeMemory(TTimer *Timer);
释放定时器存储空间
void TMR_Delete(TTimer *Timer);
删除定时器队列里Timer定时器结构体指针指向的定时器结构体
void TMR_Clear(void);
删除定时器队列里所有定时器
TTimer * TMR_Post(size_t Abs,uint8 Flag,uint8 TaskId,uint8 EventValue);
创建一个定时器并加入到定时器队列,在Abs个节拍延时后,向ID为TaskId的任务触发一个事件值为EventValue的事件,
如果Flag为TMR_ONESHOT(0X01),则事件触发后,定时器会被删除,
如果Flag为TMR_PERIODIC(0x02),则会周期性触发事件
void SLP_Enable(void);
允许调度内核休眠
void SLP_Disable(void);
禁止调度内核休眠
uint8 SLP_IsEnable(void);
判断内核使用允许休眠
void SLP_SetLevel(uint8);
设置休眠等级
uint8 SLP_GetLevel(void);
读取休眠等级
点击此处下载 ourdev_459012.rar(文件大小:124K) (原文件名:test_tevent.rar) 更新一下:20081128
增加TEvent在CC2430,MSP430F1611,LM3S1138和C++Builder下移植。
http://cache.amobbs.com/bbs_upload782111/files_11/ourdev_521036.JPG
(原文件名:Image0015.JPG)
点击此处下载 ourdev_521076.rar(文件大小:2.73M) (原文件名:TEvent.rar)
本贴被 ATmega32 编辑过,最后修改时间:2008-11-29,08:44:00. 感觉稍微复杂了一点……还可以再轻量级一点…… 好东西~顶一个 20081125
点击此处下载 ourdev_513455.rar(文件大小:263K) (原文件名:TEvent.rar)
IAR 5.11B,WinAVR20080610
共用一个工程,PD7接LED,闪烁。 谢谢了,学习中! 顶一下,以后再看 谢谢,学习一下,楼主,里面TEvent_AVR 用的是IAR 哪个版本的,我用IAR_FOR AVR 5.11B 提示版本不够新
本贴被 thoro_avr 编辑过,最后修改时间:2008-11-25,13:51:03. 上官金虹,太感谢了 哈哈哈哈,只是个加了个消息机制状态机啊,楼上几位怎么扯到陈明计去了? 每个任务都有自己一份上下文环境,这个上下文环境包含中断标志。
也就是说,每个任务都有自己的中断标志位。
任务A切换到B任务时,会从B任务上下文环境读取B任务的中断标志位,此时的中断标志位与任务A切换任务之前是否关闭中断无关。
当再次切换到任务A是,会从任务A上下文环境读取中断标志位,恢复到任务A切换之前的中断状态。
本贴被 ATmega32 编辑过,最后修改时间:2008-11-25,11:08:54. 如果任务不需要OS_ENTER_CRITICAL(); OS_EXIT_CRITICAL(); 我还是不明白,
如果任务主动放弃CPU,以K_TMO为参数调用延时节拍函数OS_Wait时,首先执行
OS_ENTER_CRITICAL()时,已经关闭了中断,那么,如果在每个任务中不“添加” OS_EXIT_CRITICAL()指令,
那不是非要等到“当前任务”再次获得CPU,才开中断吗?
喔,终于看到了。陈明计的例子2如下:
void TaskC(void)
{
uint8 x,y;
while (1)
{
OS_ENTER_CRITICAL();
x = random(80);
y = random(20);
PC_DispChar(x, y + 5, '3', DISP_FGND_LIGHT_GRAY);
OS_EXIT_CRITICAL();
OSWait(K_TMO,1);
}
}
总结:是否必须按照如下格式编写任务(我刚刚学SMALL RTOS,问题比较幼稚,请不要见笑。)
void Task(void)
{
//该任务的初始化代码
while (1)
{
OS_ENTER_CRITICAL();
//任务代码
OS_EXIT_CRITICAL();
OSWait(K_TMO,1);
}
} 一般的任务不需要OS_ENTER_CRITICAL(); OS_EXIT_CRITICAL();
x = random(80);
y = random(20);
PC_DispChar(x, y + 5, '3', DISP_FGND_LIGHT_GRAY);
这段函数是不可重入的。(任务在调用这几个函数时,不允许其他任何任务或者中断调用这两个函数),
所以必须加上OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL(); 以下是陈明计的一个例子:
#include "config.h"
void TaskA(void);
void TaskB(void);
void TaskC(void);
void main(void)
{
OSInit();
TMOD = (TMOD & 0XF0) | 0X01;
TL0 = 0x0;
TH0 = 0x0;
TR0 = 1;
ET0 = 1;
OSTaskCreate(TaskA, NULL, 0);
OSTaskCreate(TaskB, NULL, 1);
OSTaskCreate(TaskC, NULL, 2);
while(1)
{
PCON = PCON | 0x01; /* CPU进入休眠状态 */
}
}
void TaskA(void)
{
while (1)
{
OSWait(K_TMO,5);
}
}
void TaskB(void)
{
while (1)
{
OSWait(K_TMO,10);
}
}
void TaskC(void)
{
while (1)
{
OSWait(K_TMO,15);
}
}
从上面的例子可以看出,
调用OSSched()切换到另外一个任务时,又是从目前就绪表中“优先级最高任务”的断点处开始执行,上面的例子中,并没有开中断的指令呀!
上官金虹,我有一个疑问,请指教:
在SMALL RTOS51中OS_Wait()系统函数中,
case K_TMO: /* 等待超时,即延时一段时间 */
OS_ENTER_CRITICAL();
while (OSWaitTick != 0) /* 判断超时时间是否到 */
{
OSClearSignal(OSTaskID); /* 任务进入等待状态 */
OSSched(); /* 运行下一个任务 */
}
OS_EXIT_CRITICAL();
return TMO_EVENT;
问题是:当超时未到时,中断一直是关闭的,那么这段时间不是无法进入中断服务程序了吗? 这里并不是一直循环等待。
当延时时间未到,调用OSSched()会切换到另外的任务,当切换到某个任务后,中断自然会打开。
本贴被 ATmega32 编辑过,最后修改时间:2008-11-24,13:52:38. 我个人觉得还蛮好用的。
这个调度内核最大优点是可移植性好。
全部用C写的,没有汇编。
我AVR,MSP430,CC2430,STM32,LM3S都用这个。
有什么问题大家一起讨论。
循环调度部分,这样写感觉更清晰些:
while(1)
{
while(EVT_Scheduler());
if(SLP_IsEnable())
{
//休眠
}
}
本贴被 ATmega32 编辑过,最后修改时间:2008-11-24,00:09:20. 不是很明白 好东西,谢 dddd 更新一下;20081107
点击此处下载 ourdev_487505.rar(文件大小:293K) (原文件名:TEvent.rar) 简单例子:
#include "config.h"
#define TASK1ID 0
#define TASK2ID 1
#define EVT_LED_ON 0
#define EVT_LED_OFF 1
uint8 task1(uint8 EventValue)
{
switch(EventValue)
{
case EVT_TASK_ENTRY: //任务入口事件,任务初始化 TSK_InitWithEntryEvent(task2,TASK2ID)时触发了EVT_TASK_ENTRY事件
EVT_Post(EVT_LED_ON); //向当前任务触发一个EVT_LED_ON事件
break;
case EVT_LED_ON:
PORTD&=~_BV(7);
EVT_DelayPost(EVT_LED_OFF,OS_TICKS_PER_SEC/10); //创建一个定时器,延时1/10 S后,向所在任务触发一个EVT_LED_OFF事件,事件触发后,定时器会北删除
break;
case EVT_LED_OFF:
PORTD|=_BV(7);
EVT_DelayPost(EVT_LED_ON,OS_TICKS_PER_SEC/10); //创建一个定时器,延时1/10 S后,向所在任务触发一个EVT_LED_ON事件,事件触发后,定时器会北删除
break;
}
return 0;
//返回值0:表示内核继续扫描当前任务是否还有其它事件
}
uint8 task2(uint8 EventValue) //事件只用来触发任务运行,不区分事件值具体是多少,任务按步(step)运行。
{
EventValue=EventValue;
static uint8 step=0;
if(step==0)
{
PORTD|=_BV(6);
PORTD&=~_BV(4);
step=1;
TSK_Delay(OS_TICKS_PER_SEC/10);
//创建一个定时器,延时1/10 S后,触发一个EVT_TASK_DELAY事件,事件触发后,定时器会北删除
return 1;
//返回值1:表示内核将扫描下一个任务的事件,不管当前任务是否还有其它事件
}
if(step==1)
{
PORTD|=_BV(4);
PORTD&=~_BV(5);
step=2;
TSK_Delay(OS_TICKS_PER_SEC/10);
return 1;
}
if(step==2)
{
PORTD|=_BV(5);
PORTD&=~_BV(6);
step=0;
TSK_Delay(OS_TICKS_PER_SEC/10);
return 1;
}
return 0;
}
int main()
{
DDRD=_BV(4)|_BV(5)|_BV(6)|_BV(7);
PORTD=_BV(4)|_BV(5)|_BV(6)|_BV(7);
TCNT2 = 0;
TCCR2A=_BV(WGM21);
TCCR2B = T2_CLK_DIV64;
OCR2A=OCR2A_INIT;
TIMSK2=_BV(OCIE2A);
TSK_InitWithEntryEvent(task1,TASK1ID); //任务初始化,并触发一个EVT_TASK_ENTRY事件
TSK_InitWithEntryEvent(task2,TASK2ID); //任务初始化,并触发一个EVT_TASK_ENTRY事件
sei();
while(1)
{
if(EVT_Scheduler()) //任务调度
{
}
else //全部任务扫描一次,没有任何事件,内核处于空闲状态
{
if(SLP_IsEnable()) //如果允许休眠
{
if(SLP_GetLevel()==NORMAL_SLEEP) //如果休眠级别是一般休眠
{
//一般休眠
}
else //如果休眠级别是深度休眠
{
//深度休眠
}
}
}
}
}
SIGNAL(TIMER2_COMPA_vect)
{
TMR_TicksHander(); //系统节拍
}
本贴被 ATmega32 编辑过,最后修改时间:2008-10-17,17:35:23. lz,和你那个teventsmall有何区别? 基本上一样的(可能稍微有点变动)。
我原本是打算把tevent分成teventtiny,teventsmall。
(teventsmall就是原来的tevent)
不过后来teventsmall完全都没有再使用,已经从硬盘删除了。
teventtiny我现在改名叫VoidEvent,
另外,还有一个VoidTask,是多任务抢占内核。
不过都是自己写的玩。 那楼主现在在用什么操作系统? 有意思,顶上官兄。
页:
[1]