No.68425 作者:xyg2003 邮件:xyg345@163.net ID:27898 登陆:7次 文章数:2篇 最后登陆IP:218.13.198.2 最后登陆:2005/12/1 19:31:32 注册:2004/11/19 20:42:52 财富:141 发帖时间:2004/12/8 20:24:17 发贴者IP:220.194.195.129 标题:xyg2003:19bytes!玩转嵌入式rtos―r&s在mcs51上的移植 摘要:No.6842519bytes!玩转嵌入式rtos―r&s在mcs51上的移植 19bytes!玩转嵌入式rtos―r&s在mcs51上的移植 阮海深 2004-12-8 修订 r&s是一款层次清晰的嵌入式内核,努力追求稳定与快速;r&s主体部分是严格按照ansi c标准语法构建的,幸运的是,当前流行的处理器都有支持的标准c编译环境,只需改动少量与处理器相关代码,即可很容易将r&s移植到具体的处理器上;本文通过在mcs51的移植实例,探讨r&s的一般移植思路。 一 r&s的文件结构 1 \arch,依赖于具体体系结构部分,包括中断、时钟服务、处理器栈指针切换,是移植的重点; 2 \example,演示例子; 3 \inc,内核头文件,包括默认的配置文件config.h; 4 \kernel,基本内核模块,不依赖具体处理器部分代码; 5 \ipc,任务间通讯支持,包括二值信号量、信号量、互斥锁、邮箱、消息队列; 6 \lib,用户支持库,基本IO,文件系统,网络支持…(待完善); 二 移植 r&s的移植过程是非常简单的,实际上只需重新实现5个函数和几个常数宏定义,移植涉及到文件均在目录\arch下: ¨ basetype.h ¨ intr.h ¨ trace.h ¨ misc.c ¨ context.asm ¨ trace.c 共3个头文件与3个源文件,其中1个asm文件。 basetype.h包含了关于cpu位宽、栈的增长方向、存储字节序以及基本数据类型描述。 intr.h、misc.c、context.asm定义了临界段相关的宏、任务上下文切换、以及系统时钟中断服务,涉及到以下项修改: ¨ 临界段控制宏CRITICAL_ENTER、CRITICAL_EXIT与中断控制宏DISABLE_IRQ、ENABLE_IRQ定义 ¨ 任务栈初始化 __stack_init、__entry_init ¨ 任务栈指针切换 __switch_start、__switch_to ¨ 系统时钟服务 __timer_irs trace.h、trace.c是用户接口输出支持,包括对__ASSERT的实现。提供对r&s的DEBUG版本、字符输出lib/pintk等相关的支持。对于没有用户接口硬件支持的系统,可以省略这两个文件。 1 修改basetype.h相关内容 #define ARCH_CPU_BITS 8 //cpu位宽 #define ARCH_STACK_GROW UPWARDS //栈增长方向 #define ARCH_MM_BYTEORDER BIG_ENDIAN //字节序模式:大头模式,即低地址高字节 //以下是基本数据类型 #define __arch_u8 char #define __arch_u16 short #define __arch_u32 long #define __arch_u64 long long //重定义SP指针和基本类型修饰(可选内容) #define __sp char idata * #define __const_ code #define __p_ data 2 移植intr.h、misc.c、context.asm 1) 临界段宏CRITICAL_ENTER,CRITICAL_EXIT定义 DISABLE_IRQ、ENABLE_IRQ分别定义中断开关控制。 宏CRITICAL_ENTER,CRITICAL_EXIT定义了系统临界段代码保护,一般是通过开、关中断实现。在提供内嵌汇编语句的编译器中,可以很方便定义插入汇编指令来开、关中断;在c51中,可以直接使用c语法来操作51的特殊寄存器,非常方便。 这里我们通过简单的进入临界段CRITICAL_ENTER,关闭中断EA=0,退出临界段CRITICAL_EXIT打开中断EA=1实现。注意,这种模式要避免临界段的嵌套。在稍微复杂的应用,进入临界段要先将中断状态保存到堆栈中,然后关闭中断,退出临界段时,从堆栈中恢复中断状态。 #define DISABLE_IRQ EA = 0 #define ENABLE_IRQ EA = 1 #define CRITICAL_ENTER EA=0 #define CRITICAL_EXIT EA=1 2) 任务栈初始化 __stack_init、__entry_init 初始化任务栈,实际上是模拟函数调用过程,给任务以假象--哦,我是从这个地方中断的,现在我应该回到那继续!我们利用任务的入口地址entry初始化一个假想栈,这样任务就可以从返回到入口开始工作。 实际上,r&s没有让任务马上进入任务入口函数entry运行,在任务开始前,总该做点什么,这个工作交给了__entry_init,很多时候,只需在__entry_init为每个任务简单的打开中断。 static void __entry_init(void) { ENABLE_IRQ; //在任务开始前,我们首先打开中断 } //返回entry 现在利用__stack_init构造一个初始化任务栈,使得任务能够最终返回到入口entry开始。 struct reg_context //构造一个初始化任务栈 { u8 entry_l; u8 entry_h; u8 eninit_l; u8 eninit_h; u8 arg; }; typedef struct regs_context regs_t; u8* __stack_init(entry_t entry, arg_t arg, sp_t stack_base) { regs_t* regs; regs = (regs_t*) stack_base; //regs指向任务栈基地址 regs-> entry_l = (u8)entry; //任务入口地址低位 regs-> entry_h = (u16)entry > > 8; //任务入口地址高位 regs-> eninit_l = (u8)__entry_init; //任务初始化地址(低位) regs-> eninit_h = (u16)__entry_init > > 8; //任务初始化地址(高位) regs-> arg = (u8)arg; //任务入口参数 return (u8*)regs + 3; //返回指向任务初始化函数的栈 } 这里有个问题,标准的c编译器都是支持栈传递函数参数的,但在c51中,函数调用默认是寄存器传递。所以,任务入口参数arg并没有真正传入到任务中,解决这个问题并不难,你得查看编译器手册,知道参数是通过那个寄存器传送的,然后在__entry_init中提取arg内容复制到参数寄存器就可以了。 3) 栈切换实现__switch_start、__switch_to 任务栈指针切换实现__switch_start、__switch_to也非常简单;任务栈初始化的构造和任务栈指针切换一定要严格对应一致的。 __switch_start功能是在r&s启动多任务时候,将处理器栈指针SP指向第一个任务栈过程。 void __switch_start(sp_t next_sp) { SP = next_sp; //将SP指向第一个任务栈地址 } //这里隐含了一条return语句,将返回到第一个用户任务 __switch_to工作是在r&s进行任务切换时,保存SP到当前任务栈,然后把栈指针SP指向新的任务栈。实现如下: void __switch_to(sp_t* pcurrent_sp, sp_t next_sp) { *pcurrent_sp = (sp_t)SP; //保存当前任务栈地址 SP = next_sp; //SP指向新的任务栈 } //这里隐含了一条return语句,将返回到新的用户任务 4) 系统时钟服务void __timer_irs (void) 系统时钟服务为r&s提供一个周期的时钟节拍,实现延时和超时控制等操作,时钟节拍一般取10~100Hz。r&s的延时精度取决于时钟节拍的精度,可以利用处理器内含定时器或外置专门的定时芯片实现系统节拍,在对定时要求精度很高的场合,可使用自循环的硬件定时器获得,典型的r&s中断处理流程如下: 关中断;(可选) 任务调度锁定_sched_lock++(可选) 保存现场; 模拟一次中断 开中断;(可选) 任务调度解锁_sched_lock--(可选) 切换任务 恢复现场; 返回 _sched_lock是一字节的无符号整数,只有当_sched_lock值为0时,才允许r&s调度器进行任务切换;如果允许中断嵌套,你需要在中断中锁住r&s调度器,避免在中断嵌套中发生任务调度;如果不允许中断嵌套,那么_sched_lock是不必的。根据_sched_lock可以算出r&s允许达255层深的中断嵌套。 CSEG AT 000BH INC #_sched_lock LJMP IT0_IRS CSEG AT 0100H ;*==============================================*/ IT0_IRS: ;中断入口 PUSH ACC ;保存现场 PUSH B PUSH PSW PUSH DPH PUSH DPL PUSH 00H PUSH 01H PUSH 02H PUSH 03H PUSH 04H PUSH 05H PUSH 06H PUSH 07H ;------------------------------------------------------- ;模拟中断 MOV A, #LOW IT0_OUT PUSH ACC MOV A, #HIGH IT0_OUT PUSH ACC LCALL __do_tick ;中断处理 MOV TH0,#T0H_COUNTER ;刷新定时器 MOV TL0,#T0L_COUNTER ; RETI ;中断返回 ;------------------------------------------------------- IT0_OUT: DEC #_sched_lock LCALL __schedule ;任务切换 ......
>>返回讨论的主题
|