• 单片机温度检测课程设计,和步进电机课程设计下载

    http://weblook.blogbus.com/files/12020155622.doc

    http://weblook.blogbus.com/files/12020155621.doc

    http://weblook.blogbus.com/files/12020155620.doc


  • 网上资源有设计指导书.:http://www.gfxy.com/jpkc/jiaju/sheji/夹具课程设计指导书.doc 机械夹具课程设计 机械系统设计课程设计下载

    http://weblook.blogbus.com/files/12020146310.rar

     

    全部课程设计在www.csdn.net 下载频道里面搜索夹具课程设计 有2个部分。自己下吧

    http://do...
  • 长春一汽实习报告下载长春一汽实习报告下载长春一汽实习报告下载长春一汽实习报告下载

    以上传到csdn上了

    http://download.csdn.net/source/308335

     

     

    长春一汽实习报告下载长春一汽实习报告下载长春一汽实习报告下载长春一汽实习报告下载长春一汽实习报告下载
  • 我 的资源

    2008-02-03

    研华PCL818中文手册     研华PCL818中文手册

    Tag: 研华PCL818中文手册     发布日期:2007-12-14 18:33:02
    有人下载

    pdf 绿色软件     pdf 绿色软件 pdf 绿色软件

    Tag: pdf   绿色软件     发...
  • 搜索引擎

    2008-02-01

    登录搜索引擎是推广网站很重要的一步。为了方便大家,整理了主要搜索引擎,希望对大家有帮助。以下地址都经过了测试,可以使用,若您在使用时发现死链接,请通知我们,以便更好的为大家服务。同时也欢迎大家推荐好的搜索引擎。另外要指出的是,贡献流量的主要是几个著名的搜索引擎,其它的对流量贡献微乎其微。如果您时间不多,大可不必全部登录。当然,如果时间充足,又没有很多推广经费,登录一下也大有好处。 

     

    1、百度免费推广(baidu)。登陆页面http:...
  • C51内核-原创

    2007-10-12

    10月份开始写,大约1000行代码吧,第一次自己写个,心没有低。 <
    >由于C51内核很小 <
    >暂时有:任务调度,任务切换,信号量处理,延时及超时服务,消息邮箱管理。 <
    >一定要坚持写完。。。。<
    >概念:>>>><
    >>> >>>>>>台系统>>>>应用程序是一个无限的循环循环中调用相应的函数完成相应的操作这部分可以看成后台行为(background).也可以叫做任务级.>>>><
    >>> 前台系统>>中断服务程序处理异步事件这部分可以看成前台行为(foreground).也可以叫做中断级.>><
    >>> 代码的临界区>>指处理时不可分割的代码.一旦这部分代码开始执行则不允许任何中断打入.为确保临界区代码的执行在进入临界区之前要关中断而临界区代码执行完以后要立即开中断.>><
    >>> 资源>>任何为任务所占用的实体都可称为资源.资源可以是输入输出设备例如键盘显示器等.资源也可以是一个变量一个结构或一个数组等.>><
    >>> 共享资源>>可以被一个以上任务使用的资源叫做共享资源.为了防止数据被破坏每个任务在与共享资源大交道时必须独占该资源.这叫做互斥.>><
    >>> 多任务>>多任务运行的实现实际上是靠CPU在许多任务之间的转换调度.>><
    >>> 任务>>一个任务也称为一个线程是一个简单的程序该程序可以认为CPU完全只属于该程序自己.每个任务都是一个无限的循环.>><
    >>> 每个任务都处在以下5种状态之一的状态下这5中状态分别是休眠态就绪态运行态挂起态和被中断态.>><
    >>> 休眠态>>相当于该任务驻留在内存中但并不被多任务内核所调度.>><
    >>> 就绪态>>意味着该任务已经准备好可以运行了但由于该任务的优先级比正在运行的任务的优先级低还暂时不能运行.>><
    >>> 运行态>>是指该任务掌握了CPU的控制权正在运行中.>><
    >>> 挂起态>>指该任务在等待等待某一事件的发生.>><
    >>> 被中断态>>发生中断时CPU提供相应的中断服务原来正在运行的任务暂不能运行.>><
    >>>文章引用自:>><
    >
    >> >>内核>>>>多任务系统中内核负责管理各个任务或者说为每个任务分配CPU时间并且负责任务之间的通信.内核提供的基本服务是任务切换.实时内核可以大大简化应用系统的设计它允许将应用分成若干个任务由实时内核来管理他们.(非占先式占先式)<
    > >>调度>>>>是内核的主要职责之一就是决定该论到哪个任务运行了.多数实时内核是基于优先级调度法的.<
    > >>任务切换>>>>当多任务内核决定运行另外的任务时它保存正在运行任务的当前状态即CPU寄存器中的全部内容.这些内容保存在任务的当前状态保存区也就是任务自己的栈区之中.入栈工作完成以后就把下一个将要运行的任务的当前状态从该任务的栈中从新装入CPU的寄存器并开始下一个任务的运行.这个过程就成为任务切换.<
    > >>静态优先级>>>>应用程序执行过程中诸任务优先级不变则称之为静态优先级.在静态优先级系统中诸任务以及它们的时间约束在程序编译时是已知的.<
    > >>动态优先级>>>>应用程序执行过程中任务的优先级是可变的则称之为动态优先级.
  • C51内核-原创

    2007-10-12

    10月份开始写,大约1000行代码吧,第一次自己写个,心没有低。 <
    >由于C51内核很小 <
    >暂时有:任务调度,任务切换,信号量处理,延时及超时服务,消息邮箱管理。 <
    >一定要坚持写完。。。。<
    >概念:>>>><
    >>> >>>>>>台系统>>>>应用程序是一个无限的循环循环中调用相应的函数完成相应的操作这部分可以看成后台行为(background).也可以叫做任务级.>>>><
    >>> 前台系统>>中断服务程序处理异步事件这部分可以看成前台行为(foreground).也可以叫做中断级.>><
    >>> 代码的临界区>>指处理时不可分割的代码.一旦这部分代码开始执行则不允许任何中断打入.为确保临界区代码的执行在进入临界区之前要关中断而临界区代码执行完以后要立即开中断.>><
    >>> 资源>>任何为任务所占用的实体都可称为资源.资源可以是输入输出设备例如键盘显示器等.资源也可以是一个变量一个结构或一个数组等.>><
    >>> 共享资源>>可以被一个以上任务使用的资源叫做共享资源.为了防止数据被破坏每个任务在与共享资源大交道时必须独占该资源.这叫做互斥.>><
    >>> 多任务>>多任务运行的实现实际上是靠CPU在许多任务之间的转换调度.>><
    >>> 任务>>一个任务也称为一个线程是一个简单的程序该程序可以认为CPU完全只属于该程序自己.每个任务都是一个无限的循环.>><
    >>> 每个任务都处在以下5种状态之一的状态下这5中状态分别是休眠态就绪态运行态挂起态和被中断态.>><
    >>> 休眠态>>相当于该任务驻留在内存中但并不被多任务内核所调度.>><
    >>> 就绪态>>意味着该任务已经准备好可以运行了但由于该任务的优先级比正在运行的任务的优先级低还暂时不能运行.>><
    >>> 运行态>>是指该任务掌握了CPU的控制权正在运行中.>><
    >>> 挂起态>>指该任务在等待等待某一事件的发生.>><
    >>> 被中断态>>发生中断时CPU提供相应的中断服务原来正在运行的任务暂不能运行.>><
    >>>文章引用自:>><
    >
    >> >>内核>>>>多任务系统中内核负责管理各个任务或者说为每个任务分配CPU时间并且负责任务之间的通信.内核提供的基本服务是任务切换.实时内核可以大大简化应用系统的设计它允许将应用分成若干个任务由实时内核来管理他们.(非占先式占先式)<
    > >>调度>>>>是内核的主要职责之一就是决定该论到哪个任务运行了.多数实时内核是基于优先级调度法的.<
    > >>任务切换>>>>当多任务内核决定运行另外的任务时它保存正在运行任务的当前状态即CPU寄存器中的全部内容.这些内容保存在任务的当前状态保存区也就是任务自己的栈区之中.入栈工作完成以后就把下一个将要运行的任务的当前状态从该任务的栈中从新装入CPU的寄存器并开始下一个任务的运行.这个过程就成为任务切换.<
    > >>静态优先级>>>>应用程序执行过程中诸任务优先级不变则称之为静态优先级.在静态优先级系统中诸任务以及它们的时间约束在程序编译时是已知的.<
    > >>动态优先级>>>>应用程序执行过程中任务的优先级是可变的则称之为动态优先级.
  • 嵌入式C语言位操作的移植与优化
    作者: 上海芯兆电子科技有限公司 王东征
    引言 单片机的应用越来越广泛,种类也越来越多。由于嵌入式C语言可读性强、移植性好,与汇编语言相比大大减轻了软件工程师的劳动强度,因而越来越多的单片机工程师开始使用C语言编程。但C语言的可移植性仅限于与硬件无关的子程序,而与具体硬件有关的子程序则无法移植。在单片机应用中,位操作(特别是对引脚的位操作)非常普遍,如EEPROM数据和IC卡数据的读写、字段式LCD显示等,很多带串口的集成电路都需要单片机用软件来做I/O口读写程序。如何让这些子程序既有很好的通用性,生成代码的效率又高,是很多软件工程师都在考虑的问题。这里介绍两种C语言位操作的移植方法。 1 用逻辑运算实现位操作 请看下面这个子程序: INT8U Card102RdByte(void) <
    > INT8U Temp8U n = 8<
    > do Temp8U ltlt= 1<
    > if( PIN_CARD_SDA_RD() ) Temp8U = 0x01<
    > PIN_CARD_CLK_H()PIN_CARD_CLK_L()<
    > while(--n)<
    > return Temp8U<
    > 这是通过单片机引脚从88SC102卡中读一个字节的子程序。程序采用μC/OSII中的书写风格,即变量和函数采用“驼峰”写法,由define定义的常量和内联函数采用全部大写加下划线的写法。 此程序驱动一个引脚输出CARD_CLK高低信号,从另一个引脚一位一位读取CARD_SDA数据。 1.1 用于MSP430系列单片机 此程序应用到MSP430单片机上(本文用的是MSP430F413单片机),头文件中要有如下定义: typedefunsigned charINT8U<
    >#i ncludeltmsp430x41x.hgt<
    >definePIN_CARD_SDA_RD()(P6IN amp 0x01)<
    >definePIN_CARD_CLK_H()P6OUT =0x04<
    >definePIN_CARD_CLK_L()P6OUT amp= ~0x04 汇编结果如下: In segment CODE align 2 keep with next<
    >__code unsigned char Card102RdByte(void)<
    > Card102RdByte<
    >0000007E42MOV.B0x8 R14<
    > Card102RdByte_0<
    >0000024C5CRLA.<
    />12<
    >000004D2B33400BIT.B0x1 amp0x34<
    >0000080128JNCCard102RdByte_1<
    >00000A5CD3BIS.B0x1 R12<
    > Card102RdByte_1<
    >00000CE2D23500BIS.B0x4 amp0x35<
    >000010E2C23500BIC.B0x4 amp0x35<
    >0000147E53ADD.B0xff R14<
    >0000164E93CMP.B0x0 R14<
    >000018F423 JNECard102RdByte_0<
    >00001A3041RET 这与手工汇编编程的结果几乎一样,代码效率很高。 1.2 用于51系列单片机 在51系列单片机中应用此程序,头文件要加入以下定义: #i ncludeReg932.h//Philips LPC932单片机<
    >sbitCradClk=P01<
    >sbitCardSDA=P00<
    >definePIN_CARD_SDA_RD()CardSDA<
    >definePIN_CARD_CLK_H()CradClk=1<
    >definePIN_CARD_CLK_L()CradClk=0 原来的程序不作任何改动汇编结果如下: FUNCTION Card102RdByte (BEGIN)<
    >-- Variable emp8U__xassigned to Register 7__x--<
    >-- Variable __xassigned to Register 6__x--<
    >00007E08MOVR608H<
    >0002C0007<
    >0002EFMOVAR7<
    >000325E0ADDAACC<
    >0005FFMOVR7A<
    >0006308003JNBCardSDAC0008<
    >0009430701ORLAR701H<
    >000CC0008<
    >000CD281SETBCradClk<
    >000EC281CLRCradClk<
    >0010DEF0DJNZR6C0007<
    >0012C0009<
    >001222RET<
    > FUNCTION Card102RdByte (END) 由汇编结果可知,对位的直接清零和置位已达到最简,只是读位值不够理想。 1.3 用于196/296系列单片机 在80C196MC、80C296SA等单片机中,片上I/O口是可以窗口映射到低端地址的。采用这种方式,I/O口可以直接寻址,因而程序代码最短,执行速度也最快,但这样做C程序就无法移植了。若不用窗口技术,则片上I/O口是内存地址映射的,与普通内存地址一样操作。头文件中加入如下定义即可利用原来的程序: INT8UPOUTPIN<
    >pragmalocate(POUT=0x880)<
    >pragmalocate(PIN=0x881)//外扩I/O口地址定位<
    >definePIN_CARD_SDA_RD()(PIN amp 0x01)<
    >definePIN_CARD_CLK_H()POUT =0x04<
    >definePIN_CARD_CLK_L()POUT amp= ~0x04 汇编后的代码是56字节,代码效率也很高。 采用逻辑运算实现位操作,C程序简单明了,移植性好,可读性更好。但96系列单片机无法利用JBC和JBS位操作指令,51系列单片机也无法利用JB和JNB等其特有的位操作指令来提高代码效率。用位段结构实现位操作可以弥补这个不足。 2 用位段结构实现位操作 把原来的程序改写如下: INT8U Card102RdByte(void)①<
    >②<
    > INT8U n = 8③<
    > ifndef C51_ASM④<
    > bdata ACCImg⑤<
    > endif⑥<
    > do ACC ltlt= 1⑦<
    > GET_CARD_SDA()⑧<
    > PIN_CARD_CLK_H() PIN_CARD_CLK_L() ⑨<
    > while(--n) ⑩<
    > return ACC <
    > 2.1 在51系列单片机中的应用 在C51中使用ACC是不必在每个子程序中定义的,所以要在文件的开头加上 define C51_ASM。这样,第④、⑤、⑥句会被忽略。在头文件中加上以下定义 sbitACC_0=ACC0 <
    >defineGET_CARD_SDA()ACC_0 = CardSDA 其余定义如本文第一部分所述。结果第⑧句汇编变为“MOV CCardSDA”和“MOV ACC_0C”两句。句,函数要通过R7返回参数,程序已达到最简。 FUNCTION Card102RdByte (BEGIN)<
    >-- Variable __xassigned to Register 7-<
    >00007F08MOVR708H<
    >0002C0007<
    >000225E0ADDAACC<
    >0004A281MOVCCardSDA<
    >000692E0MOVACC_0C<
    >0008D280SETBCardClk<
    >000AC280CLRCardClk<
    >000CDFF4DJNZR7C0007<
    >000EFFMOVR7A<
    >000FC0008<
    >000F22RET<
    > FUNCTION Card102RdByte (END) 还可以像196/296那样定义一个位段结构,使用JB指令,有兴趣的读者可以自己试一下。 2.2 在196/296系列单片机中的应用 在196/296中应用这段程序,要增加一个局部变量ACCImg的定义,就是前面程序中的第④、⑤、⑥三句。再在头文件中增加一个如下的位段结构定义: typedef struct unsigned Bit01<
    > unsigned Bit11<
    > unsigned Bit21<
    > unsigned Bit31<
    > unsigned Bit41<
    > unsigned Bit51<
    > unsigned Bit61<
    > unsigned Bit71<
    > Divide_to_bit<
    >typedef union INT8U Byte<
    > Divide_to_bit DivBit<
    > bdata 端口地址变量要定义成以下数据类型: bdata PIN 同时,在头文件中加上宏定义: defineACC ACCImg.Byte<
    >defineACC_0 ACCImg.DivBit.Bit0<
    >defineGET_CARD_SDA() if(PIN.DivBit.Bit0) ACC =0x01 这样ACCImg就定义成了一个低端寄存器ACC是它的字节访问形式。源程序中的第⑧句读引脚,汇编的结果使用了JBC指令,整个程序比不用位段减少了字节,达到了优化代码的目的。 cseg<
    >0000Card102RdByte<
    > Statement3<
    >0000B10800Rldbn8<
    > Statement7<
    >0003 0004 <
    >0003740101RaddbACCImgACCImg<
    > Statement8<
    >0006B30181081CldbTmp0PIN<
    >000B 331C03jbcTmp030005<
    >000E 910101 RorbACCImg1<
    >0011 0005 <
    > Statement9<
    >0011 B30180081CldbTmp0POUT<
    >0016 91041CorbTmp04<
    >0019 C70180081CstbTmp0POUT<
    >001E 71FB1C andbTmp00FBH<
    >0021 C70180081C stbTmp0POUT<
    > Statement10 <
    >00261500Rdecbn<
    >0028980000RcmpbR0n<
    >002BD7D6bne 0004<
    > Statement11<
    >002DB0011C RldbTmp0ACCImg<
    >00302000
    0001<
    > Statement12<
    >0032 0001 <
    >0032F0ret 2.3 在MSP430系列单片机中的应用 MSP430系列单片机没有位操作指令,所以不必定义位段结构,直接把ACC定义成一个无符号8位数即可。头文件中是这样定义的: ifndef C51_ASM//此句使头文件也可以与C51的共用<
    > typedef INT8U bdata <
    > define ACC ACCImg<
    > define GET_CARD_SDA() if(P6IN amp 0x01) ACC =0x01<
    >endif 汇编的结果与用逻辑运算的方法进行位操作竟完全一样。 结语 对引脚的位操作有3种: 直接置位或清零,从端口输入数据和从端口输出数据。前两种上文已介绍过了。从端口输出数据的C程序如下: do<
    > OUT_SIO_DA()<
    > CLK_H()<
    > ACC ltlt= 1//移位可扩展时钟脉冲宽度<
    > CLK_L()<
    >while 其中: 第一句OUT_SIO_DA(),51系列可定义成位操作SIO_SDA = ACC_7;196/296和430系列可如上文定义成一个if语句。 位段操作程序中采用了ACC这个名字作为一个局部变量。在C51中这刚好是主累加器,对于2401、IC卡等半双工器件的程序很实用,但当SPI总线输入/输出同时操作时,就没这么方便了。 用逻辑运算实现位操作不存在任何移植的障碍。μC/OS-II中的位操作就是全用逻辑运算实现的。位段定义可能存在不同编译器分配顺序不同的问题,但考虑到32位高速CPU不会用软件模拟这种串口的操作,这样的程序只会用在51、196/296、MSP430等无片内Cache的中低速单片机中,所以用位段操作引脚的方法仍有意义。具体是使用逻辑运算还是使用位段进行位操作,完全看个人喜好。本文程序采用的编译器是Keil C51 V7.03、IAR C430 V2.10A和 Tasking C96 V5.0。 参考文献 1 程军. Intel 80C196单片机应用实践与C语言开发M. 北京 北京航空航天大学出版社2000.<
    >2 美 La
    osse J. 嵌入式实时操作系统μC/OS-IIM. 第2版. 邵贝贝,等译. 北京 北京航空航天大学出版社2003. 王东征主要研究方向为SoC应用。
  • 嵌入式C语言位操作的移植与优化
    作者: 上海芯兆电子科技有限公司 王东征
    引言 单片机的应用越来越广泛,种类也越来越多。由于嵌入式C语言可读性强、移植性好,与汇编语言相比大大减轻了软件工程师的劳动强度,因而越来越多的单片机工程师开始使用C语言编程。但C语言的可移植性仅限于与硬件无关的子程序,而与具体硬件有关的子程序则无法移植。在单片机应用中,位操作(特别是对引脚的位操作)非常普遍,如EEPROM数据和IC卡数据的读写、字段式LCD显示等,很多带串口的集成电路都需要单片机用软件来做I/O口读写程序。如何让这些子程序既有很好的通用性,生成代码的效率又高,是很多软件工程师都在考虑的问题。这里介绍两种C语言位操作的移植方法。 1 用逻辑运算实现位操作 请看下面这个子程序: INT8U Card102RdByte(void) <
    > INT8U Temp8U n = 8<
    > do Temp8U ltlt= 1<
    > if( PIN_CARD_SDA_RD() ) Temp8U = 0x01<
    > PIN_CARD_CLK_H()PIN_CARD_CLK_L()<
    > while(--n)<
    > return Temp8U<
    > 这是通过单片机引脚从88SC102卡中读一个字节的子程序。程序采用μC/OSII中的书写风格,即变量和函数采用“驼峰”写法,由define定义的常量和内联函数采用全部大写加下划线的写法。 此程序驱动一个引脚输出CARD_CLK高低信号,从另一个引脚一位一位读取CARD_SDA数据。 1.1 用于MSP430系列单片机 此程序应用到MSP430单片机上(本文用的是MSP430F413单片机),头文件中要有如下定义: typedefunsigned charINT8U<
    >#i ncludeltmsp430x41x.hgt<
    >definePIN_CARD_SDA_RD()(P6IN amp 0x01)<
    >definePIN_CARD_CLK_H()P6OUT =0x04<
    >definePIN_CARD_CLK_L()P6OUT amp= ~0x04 汇编结果如下: In segment CODE align 2 keep with next<
    >__code unsigned char Card102RdByte(void)<
    > Card102RdByte<
    >0000007E42MOV.B0x8 R14<
    > Card102RdByte_0<
    >0000024C5CRLA.<
    />12<
    >000004D2B33400BIT.B0x1 amp0x34<
    >0000080128JNCCard102RdByte_1<
    >00000A5CD3BIS.B0x1 R12<
    > Card102RdByte_1<
    >00000CE2D23500BIS.B0x4 amp0x35<
    >000010E2C23500BIC.B0x4 amp0x35<
    >0000147E53ADD.B0xff R14<
    >0000164E93CMP.B0x0 R14<
    >000018F423 JNECard102RdByte_0<
    >00001A3041RET 这与手工汇编编程的结果几乎一样,代码效率很高。 1.2 用于51系列单片机 在51系列单片机中应用此程序,头文件要加入以下定义: #i ncludeReg932.h//Philips LPC932单片机<
    >sbitCradClk=P01<
    >sbitCardSDA=P00<
    >definePIN_CARD_SDA_RD()CardSDA<
    >definePIN_CARD_CLK_H()CradClk=1<
    >definePIN_CARD_CLK_L()CradClk=0 原来的程序不作任何改动汇编结果如下: FUNCTION Card102RdByte (BEGIN)<
    >-- Variable emp8U__xassigned to Register 7__x--<
    >-- Variable __xassigned to Register 6__x--<
    >00007E08MOVR608H<
    >0002C0007<
    >0002EFMOVAR7<
    >000325E0ADDAACC<
    >0005FFMOVR7A<
    >0006308003JNBCardSDAC0008<
    >0009430701ORLAR701H<
    >000CC0008<
    >000CD281SETBCradClk<
    >000EC281CLRCradClk<
    >0010DEF0DJNZR6C0007<
    >0012C0009<
    >001222RET<
    > FUNCTION Card102RdByte (END) 由汇编结果可知,对位的直接清零和置位已达到最简,只是读位值不够理想。 1.3 用于196/296系列单片机 在80C196MC、80C296SA等单片机中,片上I/O口是可以窗口映射到低端地址的。采用这种方式,I/O口可以直接寻址,因而程序代码最短,执行速度也最快,但这样做C程序就无法移植了。若不用窗口技术,则片上I/O口是内存地址映射的,与普通内存地址一样操作。头文件中加入如下定义即可利用原来的程序: INT8UPOUTPIN<
    >pragmalocate(POUT=0x880)<
    >pragmalocate(PIN=0x881)//外扩I/O口地址定位<
    >definePIN_CARD_SDA_RD()(PIN amp 0x01)<
    >definePIN_CARD_CLK_H()POUT =0x04<
    >definePIN_CARD_CLK_L()POUT amp= ~0x04 汇编后的代码是56字节,代码效率也很高。 采用逻辑运算实现位操作,C程序简单明了,移植性好,可读性更好。但96系列单片机无法利用JBC和JBS位操作指令,51系列单片机也无法利用JB和JNB等其特有的位操作指令来提高代码效率。用位段结构实现位操作可以弥补这个不足。 2 用位段结构实现位操作 把原来的程序改写如下: INT8U Card102RdByte(void)①<
    >②<
    > INT8U n = 8③<
    > ifndef C51_ASM④<
    > bdata ACCImg⑤<
    > endif⑥<
    > do ACC ltlt= 1⑦<
    > GET_CARD_SDA()⑧<
    > PIN_CARD_CLK_H() PIN_CARD_CLK_L() ⑨<
    > while(--n) ⑩<
    > return ACC <
    > 2.1 在51系列单片机中的应用 在C51中使用ACC是不必在每个子程序中定义的,所以要在文件的开头加上 define C51_ASM。这样,第④、⑤、⑥句会被忽略。在头文件中加上以下定义 sbitACC_0=ACC0 <
    >defineGET_CARD_SDA()ACC_0 = CardSDA 其余定义如本文第一部分所述。结果第⑧句汇编变为“MOV CCardSDA”和“MOV ACC_0C”两句。句,函数要通过R7返回参数,程序已达到最简。 FUNCTION Card102RdByte (BEGIN)<
    >-- Variable __xassigned to Register 7-<
    >00007F08MOVR708H<
    >0002C0007<
    >000225E0ADDAACC<
    >0004A281MOVCCardSDA<
    >000692E0MOVACC_0C<
    >0008D280SETBCardClk<
    >000AC280CLRCardClk<
    >000CDFF4DJNZR7C0007<
    >000EFFMOVR7A<
    >000FC0008<
    >000F22RET<
    > FUNCTION Card102RdByte (END) 还可以像196/296那样定义一个位段结构,使用JB指令,有兴趣的读者可以自己试一下。 2.2 在196/296系列单片机中的应用 在196/296中应用这段程序,要增加一个局部变量ACCImg的定义,就是前面程序中的第④、⑤、⑥三句。再在头文件中增加一个如下的位段结构定义: typedef struct unsigned Bit01<
    > unsigned Bit11<
    > unsigned Bit21<
    > unsigned Bit31<
    > unsigned Bit41<
    > unsigned Bit51<
    > unsigned Bit61<
    > unsigned Bit71<
    > Divide_to_bit<
    >typedef union INT8U Byte<
    > Divide_to_bit DivBit<
    > bdata 端口地址变量要定义成以下数据类型: bdata PIN 同时,在头文件中加上宏定义: defineACC ACCImg.Byte<
    >defineACC_0 ACCImg.DivBit.Bit0<
    >defineGET_CARD_SDA() if(PIN.DivBit.Bit0) ACC =0x01 这样ACCImg就定义成了一个低端寄存器ACC是它的字节访问形式。源程序中的第⑧句读引脚,汇编的结果使用了JBC指令,整个程序比不用位段减少了字节,达到了优化代码的目的。 cseg<
    >0000Card102RdByte<
    > Statement3<
    >0000B10800Rldbn8<
    > Statement7<
    >0003 0004 <
    >0003740101RaddbACCImgACCImg<
    > Statement8<
    >0006B30181081CldbTmp0PIN<
    >000B 331C03jbcTmp030005<
    >000E 910101 RorbACCImg1<
    >0011 0005 <
    > Statement9<
    >0011 B30180081CldbTmp0POUT<
    >0016 91041CorbTmp04<
    >0019 C70180081CstbTmp0POUT<
    >001E 71FB1C andbTmp00FBH<
    >0021 C70180081C stbTmp0POUT<
    > Statement10 <
    >00261500Rdecbn<
    >0028980000RcmpbR0n<
    >002BD7D6bne 0004<
    > Statement11<
    >002DB0011C RldbTmp0ACCImg<
    >00302000
    0001<
    > Statement12<
    >0032 0001 <
    >0032F0ret 2.3 在MSP430系列单片机中的应用 MSP430系列单片机没有位操作指令,所以不必定义位段结构,直接把ACC定义成一个无符号8位数即可。头文件中是这样定义的: ifndef C51_ASM//此句使头文件也可以与C51的共用<
    > typedef INT8U bdata <
    > define ACC ACCImg<
    > define GET_CARD_SDA() if(P6IN amp 0x01) ACC =0x01<
    >endif 汇编的结果与用逻辑运算的方法进行位操作竟完全一样。 结语 对引脚的位操作有3种: 直接置位或清零,从端口输入数据和从端口输出数据。前两种上文已介绍过了。从端口输出数据的C程序如下: do<
    > OUT_SIO_DA()<
    > CLK_H()<
    > ACC ltlt= 1//移位可扩展时钟脉冲宽度<
    > CLK_L()<
    >while 其中: 第一句OUT_SIO_DA(),51系列可定义成位操作SIO_SDA = ACC_7;196/296和430系列可如上文定义成一个if语句。 位段操作程序中采用了ACC这个名字作为一个局部变量。在C51中这刚好是主累加器,对于2401、IC卡等半双工器件的程序很实用,但当SPI总线输入/输出同时操作时,就没这么方便了。 用逻辑运算实现位操作不存在任何移植的障碍。μC/OS-II中的位操作就是全用逻辑运算实现的。位段定义可能存在不同编译器分配顺序不同的问题,但考虑到32位高速CPU不会用软件模拟这种串口的操作,这样的程序只会用在51、196/296、MSP430等无片内Cache的中低速单片机中,所以用位段操作引脚的方法仍有意义。具体是使用逻辑运算还是使用位段进行位操作,完全看个人喜好。本文程序采用的编译器是Keil C51 V7.03、IAR C430 V2.10A和 Tasking C96 V5.0。 参考文献 1 程军. Intel 80C196单片机应用实践与C语言开发M. 北京 北京航空航天大学出版社2000.<
    >2 美 La
    osse J. 嵌入式实时操作系统μC/OS-IIM. 第2版. 邵贝贝,等译. 北京 北京航空航天大学出版社2003. 王东征主要研究方向为SoC应用。
  • 如果在多个目录中都有文件,你可以在每一个目录下创建一个标签文件。Vim 只能跳转到<
    >那个目录下的标签。<
    >通过设定 ags__x选项,你可以使用多个相关的标签文件。 比如:set tags=./tags./../tags./*/tags这会使 Vim 找到当前文件所在目录及其父目录和所有子目录下的标签文件。<
    >这已经是不少的标签文件了,但也许仍不够。比如,当编辑 ~/proj/src 目录下的<
    >一个文件时,你无法找到 ~/proj/sub/tags 目录下的标签文件。对这种情况,Vim<
    >提供了一个查找整个目录树下标签文件的方法,比如:set tags=~/proj/**/tags单 个 标 记 文 件当 Vim 在多个地方查找标签文件时,你会听到硬盘在格格作响。这样会有点慢。在这种<
    >情况下,你最好将这些时间花在生成一个大的标签文件上。你可以要等一会儿。<
    >这得借助上面提到的 Exuberant ctags 程序。它有一个选项可以搜索整个目录树:cd ~/proj<
    >ctags -R .这样做的好处是 Exuberant ctags 可以识别多种文件类型,它不仅适用于 C 和 C <
    >程序,还适用于 Eiffel 甚至 Vim 脚本。请参考 ctags 文档进行调整所用参数。<
    >现在你只需要告诉 Vim 你的标签文件在何处:set tags=~/proj/tags<
    >
  • 如果在多个目录中都有文件,你可以在每一个目录下创建一个标签文件。Vim 只能跳转到<
    >那个目录下的标签。<
    >通过设定 ags__x选项,你可以使用多个相关的标签文件。 比如:set tags=./tags./../tags./*/tags这会使 Vim 找到当前文件所在目录及其父目录和所有子目录下的标签文件。<
    >这已经是不少的标签文件了,但也许仍不够。比如,当编辑 ~/proj/src 目录下的<
    >一个文件时,你无法找到 ~/proj/sub/tags 目录下的标签文件。对这种情况,Vim<
    >提供了一个查找整个目录树下标签文件的方法,比如:set tags=~/proj/**/tags单 个 标 记 文 件当 Vim 在多个地方查找标签文件时,你会听到硬盘在格格作响。这样会有点慢。在这种<
    >情况下,你最好将这些时间花在生成一个大的标签文件上。你可以要等一会儿。<
    >这得借助上面提到的 Exuberant ctags 程序。它有一个选项可以搜索整个目录树:cd ~/proj<
    >ctags -R .这样做的好处是 Exuberant ctags 可以识别多种文件类型,它不仅适用于 C 和 C <
    >程序,还适用于 Eiffel 甚至 Vim 脚本。请参考 ctags 文档进行调整所用参数。<
    >现在你只需要告诉 Vim 你的标签文件在何处:set tags=~/proj/tags<
    >
  • 毕业后...

    2007-10-11

    <
    />>><
    /><
    />>><
    /><
    />>><
    /><
    />>><
    /><
    />>><
    /><
    />>><
    /><
    />>><
    /><
    /><
    />>><
    /><
    />>><
    /> <
    />>><
    /><
    /><
    /><
    />>><
    /><
    />>><
    /><
    />>><
    /><
    />>><
    /><
    />>><
    /><
    />>><
    /><
    />>><
    /><
    />>><
    /><
    />>><
    /><
    />>><
    /><
    />>><
    /><
    />>><
    /><
    />>><
    /><
    />
  • 毕业后...

    2007-10-11

    <
    />>><
    /><
    />>><
    /><
    />>><
    /><
    />>><
    /><
    />>><
    /><
    />>><
    /><
    />>><
    /><
    /><
    />>><
    /><
    />>><
    /> <
    />>><
    /><
    /><
    /><
    />>><
    /><
    />>><
    /><
    />>><
    /><
    />>><
    /><
    />>><
    /><
    />>><
    /><
    />>><
    /><
    />>><
    /><
    />>><
    /><
    />>><
    /><
    />>><
    /><
    />>><
    /><
    />>><
    /><
    />
  • 2007年10月7日

    2007-10-07

  • 2007年10月7日

    2007-10-07

  • 问:C51 怎样将1个子程序段定位在1个固定的地址位置? <
    ><
    >以下2问题均要用C51解决<
    ><
    >1。 怎样将1个子程序段定位在1个固定的地址位置?<
    ><
    > 例如将 INT BCD2HEX(INT XX)定位在1000H<
    ><
    >2。 HOW在EEPROM 中固定的位置存放1字符串?<
    ><
    > 如在200H处放“CYRIGHT 2001-11”<
    ><
    >答: 函数定位与变量定位... <
    ><
    >1、函数定位:<
    ><
    >假如要把C源文件 tools.c 中的函数<
    ><
    >int BIN2HEX(int xx)<
    ><
    ><
    ><
    > ...<
    ><
    ><
    ><
    >放在CODE MEMORY的0x1000处,先编译该工程,然后打开该工程的M51文件,在<
    ><
    >* * * C O D E M E M O R Y * * *<
    ><
    >行下找出要定位的函数的名称,应该形如:<
    ><
    >CODE xxxxH xxxxH UNIT PR_BCD2HEXTOOLS<
    ><
    >然后在:<
    ><
    >Project-gtOptions for Target ...-gtBL51 Locate:Code<
    ><
    >中填写如下内容:<
    ><
    >PR_BCD2HEXTOOLS(0x1000)<
    ><
    >再次Build,在M51中会发现该函数已放在CODE MEMORY的0x1000处了<
    ><
    >2、赋初值的变量定位:<
    ><
    >要将某变量定位在一绝对位置且要赋初值,此时用 _at_ 不能完成,则如下操作:<
    ><
    >在工程中建立一个新的文件,如InitVars.c,在其中对要处理的变量赋初值(假设是code变<
    ><
    >量):<
    ><
    >char code myVer = CYRIGHT 2001-11<
    ><
    >然后将该文件加入工程,编译,打开M51文件,若定义的是code型,则在<
    ><
    >* * * C O D E M E M O R Y * * *<
    ><
    >下可找到:<
    ><
    >CODE xxxxH xxxxH UNIT COINITVARS<
    ><
    >然后在:<
    ><
    >Project-gtOptions for Target ...-gtBL51 Locate:Code<
    ><
    >中填入:<
    ><
    >COINITVARS(0x200)<
    ><
    >再次编译即可。<
    ><
    >相应地,如为xdata变量,则InitVars.c中写:<
    ><
    >char xdata myVer = CYRIGHT 2001-11<
    ><
    >然后将该文件加入工程,编译,打开M51文件,在<
    ><
    >* * * X D A T A M E M O R Y * * *<
    ><
    >下可找到:<
    ><
    >XDATA xxxxH xxxxH UNIT XDINITVARS<
    ><
    >然后在:<
    ><
    >Project-gtOptions for Target ...-gtBL51 Locate:Xdata<
    ><
    >中填入:<
    ><
    >XDINITVARS(0x200)<
    ><
    >再次编译即可。相应地,若定义的是data/idata等变量,则相应处理即可。<
    ><
    >3、若有多个变量或函数要进行绝对地址定位,则应按地址从低到高的顺序排列。<
    ><
    ><
    >
  • 问:C51 怎样将1个子程序段定位在1个固定的地址位置? <
    ><
    >以下2问题均要用C51解决<
    ><
    >1。 怎样将1个子程序段定位在1个固定的地址位置?<
    ><
    > 例如将 INT BCD2HEX(INT XX)定位在1000H<
    ><
    >2。 HOW在EEPROM 中固定的位置存放1字符串?<
    ><
    > 如在200H处放“CYRIGHT 2001-11”<
    ><
    >答: 函数定位与变量定位... <
    ><
    >1、函数定位:<
    ><
    >假如要把C源文件 tools.c 中的函数<
    ><
    >int BIN2HEX(int xx)<
    ><
    ><
    ><
    > ...<
    ><
    ><
    ><
    >放在CODE MEMORY的0x1000处,先编译该工程,然后打开该工程的M51文件,在<
    ><
    >* * * C O D E M E M O R Y * * *<
    ><
    >行下找出要定位的函数的名称,应该形如:<
    ><
    >CODE xxxxH xxxxH UNIT PR_BCD2HEXTOOLS<
    ><
    >然后在:<
    ><
    >Project-gtOptions for Target ...-gtBL51 Locate:Code<
    ><
    >中填写如下内容:<
    ><
    >PR_BCD2HEXTOOLS(0x1000)<
    ><
    >再次Build,在M51中会发现该函数已放在CODE MEMORY的0x1000处了<
    ><
    >2、赋初值的变量定位:<
    ><
    >要将某变量定位在一绝对位置且要赋初值,此时用 _at_ 不能完成,则如下操作:<
    ><
    >在工程中建立一个新的文件,如InitVars.c,在其中对要处理的变量赋初值(假设是code变<
    ><
    >量):<
    ><
    >char code myVer = CYRIGHT 2001-11<
    ><
    >然后将该文件加入工程,编译,打开M51文件,若定义的是code型,则在<
    ><
    >* * * C O D E M E M O R Y * * *<
    ><
    >下可找到:<
    ><
    >CODE xxxxH xxxxH UNIT COINITVARS<
    ><
    >然后在:<
    ><
    >Project-gtOptions for Target ...-gtBL51 Locate:Code<
    ><
    >中填入:<
    ><
    >COINITVARS(0x200)<
    ><
    >再次编译即可。<
    ><
    >相应地,如为xdata变量,则InitVars.c中写:<
    ><
    >char xdata myVer = CYRIGHT 2001-11<
    ><
    >然后将该文件加入工程,编译,打开M51文件,在<
    ><
    >* * * X D A T A M E M O R Y * * *<
    ><
    >下可找到:<
    ><
    >XDATA xxxxH xxxxH UNIT XDINITVARS<
    ><
    >然后在:<
    ><
    >Project-gtOptions for Target ...-gtBL51 Locate:Xdata<
    ><
    >中填入:<
    ><
    >XDINITVARS(0x200)<
    ><
    >再次编译即可。相应地,若定义的是data/idata等变量,则相应处理即可。<
    ><
    >3、若有多个变量或函数要进行绝对地址定位,则应按地址从低到高的顺序排列。<
    ><
    ><
    >
  • 要包含一个单片机硬件的资源头文件。<
    >各模块使用了定义在Common.h中的一些数据类型。如BIT(bit) BYTE(unsigned char)等,具体请参见源程序。<
    ><
    >时钟模块<
    > 在单片机软件设计中 时钟是重要资源 为了充分利用时钟资源 故设计本时钟模块。 本模块使用定时器0,在完成用户指定功能的同时 还能够自动处理一些其它模块中与时钟相关的信息。<
    > 时钟模块由声明文件Timer.h以及实现文件Timer.c组成。<
    > 用户应该在Config.h中定义宏TIMER_RELOAD来设定定时器0的重装载初值。推荐的定时器0的中断时间大于1毫秒。<
    ><
    > 在程序的初始化阶段调用时钟模块的初始化函数InitTimerModule()之后,就可以使用时钟模块所以支持的各种功能。具体描述如下:<
    >延时:当用户需要进行一定时间的延时时,可以通过调用Delay()来进行,参数为时钟中断的次数。如时钟中断周期为1ms, 想进行100ms的延时, 则可以调用Delay(100)。<
    >注意:<
    >如果延时的绝对时间小于时钟中断的周期,则不能够用本方法做到延时。<
    ><
    >定时:当程序中需要使用定时功能时,如等待某外部事件,如果在一定时间内发生则继续执行,如果在这段时间内发生,则认为出现错误,转向错误处理机制。<
    >在此推荐一种编程模式,但用户可以用自己认为更合理的方式处理此类问题。<
    >这里简单说明一下关于阻塞式函数及非阻塞式函数。简单说,阻塞式函数就是当检测完成条件,如果不能够完成则等待,如:<
    >void CheckSomething()<
    ><
    > // gbitSuccessFlag is a global variable<
    > while(gbitSuccessFlag == FALSE)<
    > <
    > // do nothing but waiting<
    > <
    ><
    >可以看到,当bitSuccessFlag没有被设置为TRUE时,函数保持等待状态不返回,这样就是阻塞式的函数。<
    >另外一种情况:<
    >BIT CheckSomething()<
    ><
    > if(gbitSuccessFlag == TRUE)<
    > <
    > // …<
    > return TRUE<
    > <
    > return FALSE<
    ><
    >在这里,如果所检测的事件有没有完成,函数进行检测之后,立刻返回,通过返回值报告完成情况,如果没有完成,则等待调用者分配再次执行的机会。这样的函数就是非阻塞函数。<
    >在应用定时功能时,首先要将检测函数定义成非阻塞函数。如上面的第二个版本的CheckSomething。<
    >然后下面模式:<
    >BIT bitDone = FALSE<
    >ResetClock() // clear timer interrupt times counter<
    >while(GetClock() lt MAX_WAITINGTIME)<
    ><
    > if(CheckSomething() == TRUE)<
    > <
    > bitDone = TRUE<
    >
    eak<
    > <
    ><
    >if(bitDone == FALSE)<
    ><
    > // process time out<
    ><
    ><
    >或者简单写成:<
    >BIT bitDone = FALSE<
    >ResetClock()<
    >while(GetClock() lt MAX_WAITINGTIME ampamp (bitDone = CheckSomething))<
    >if(bitDone == FLASE)<
    > <
    > // …<
    ><
    ><
    >软件看门狗:实现具有局限性的看门狗功能。在程序中合适的地方加入对软件看门狗的复位函数ResetWatchDog(),在Config.h中加入宏 TIMER_WATCHDOGTIMEOUT。当程序运行时,如果在发生TIMER_WATCHDOGTIMEOUT次时钟中断之内没有复位软件看门狗,则系统复位。<
    >注意:<
    >如果没有加入TIMER_WATCHDOGTIMEOUT宏,程序中的ResetWatchDog没有任何用处,不用删除。<
    >如果系统不能实现时钟中断,则软件看门狗也同时失去功能。<
    >目前版本的的时钟模块的复位功能并不是完全复位,主要表现在当复位之后,系统将不再响应任何中断。所以软件看门狗只是一个程序的调试功能,不应该将它用于正式工作的程序,此时应该使用硬件看门狗。<
    ><
    >用户自定义任务:如果想在时钟中断内执行一些耗时较短的任务,可以定义回调函数OnTimerInterrupt。函数原形为:void OnTimerInterrupt()<
    >如果想在发生时钟中断时执行一些功能,而这些功能又耗时相对较长,不合适放在中断响应函数内部,则可以在程序中的主循环中的任意地方添加 ImpTimerService(),同时提供原形为void OnTimerEvent()的回调函数。具体的程序如下所示:<
    >void main()<
    ><
    > Initialize()<
    > while(TRUE)<
    > <
    > // … working<
    > ImpTimerService()<
    > // … working<
    > <
    ><
    ><
    >void OnTimerEvent()<
    ><
    > // do some task<
    ><
    ><
    >对通讯模块提供支持:如通讯中的各种超时等,见通讯模块中的详细说明。<
    >对键盘扫描模块提供支持:可以自动调用键盘扫描模块,见键盘扫描模块中的详细说明。<
    >对程序调试提供支持:在程序开发过程中,有时为了判断程序是不是在工作,常用利用单片机系统的某一空闲引脚通过一个限流电阻接一个发光二极管,在程序中间隔固定时间交替控制发光管的明暗。实现这个功能只要在Config.h文件中定义TIMER_FLASHLED宏,如:<
    >define TIMER_FLASHLED P1_0<
    >则当时钟中断发生256次之后,改变发光管的状态。<
    ><
    >通讯模块<
    > 串口资源做为单片机与外界通信的常用手段,通讯模块提供了完全缓冲的串口通讯底层机制,适用于长度不大的数据包的发送及接收。如果处理关键数据,需要用户自己提供纠错协议。<
    > 通讯模块由声明文件SComm.h及实现文件SComm.c组成。<
    > 初始化:调用函数InitSCommModule()来初始化通讯模块:<
    > void InitSCommModule(BYTE byTimerReload BIT bitTurbo)<
    > 参数说明:<
    >byTimerReload 定时器1的重装载初始值。<
    >bitTurob 当此参数为TRUE时,串行通讯在定时器1的溢出速率基础上加倍。为FALSE时,串行通讯速率为定时器1的溢出速率。<
    ><
    > 缓冲区:模块使用了由宏SCOMM_SENDBUFSIZE、SCOMM_RECEBUFSIZE及SCOMM_PKGBUFSIZE所指定长度的三个缓冲区,分别为发送、接收及数据包(用于处理接收到的数据)缓冲区(如果没有使用异步接收功能,则不需要使用数据包缓冲区)。<
    > 在缺省时,这三个宏都被定义为10,但用户可以自已按照系统的RAM资源占用情况在Config.h中重定义缓冲区的大小。需要注意的是,如果缓冲的长度不够,当发送或接收长数据包的时候可能会发生问题,关于数据缓冲区的最小值的设置可以参考下面的说明。<
    > 注意:需要尽快取出接收缓冲区中的数据,否则当缓冲区满之后,新的数据将被简单的丢掉。<
    ><
    > 字节级服务函数: 在Config.h文件中定义了宏SCOMM_DriverInterface(如:define SCOMM_DriverInterface),则可以使用字节级服务函数,即通讯模块的底层函数。<
    > 共有两个函数可以使用:<
    > void SendByte(BYTE byData)<
    > 发送一个字节,如果当前缓冲区满,则等待。参数byData为要发送的数据。<
    >BYTE ReceByte()<
    >接收一个字节,如果当前缓冲区中没有数据,则此函数阻塞,直到接收到数据为止。接收到数据通过返回值返回。<
    >可以通过调用IsSendBufEmpty() IsSendBufFull() IsReceBufEmpty() IsReceBufFull() 宏来判断缓冲区的空或满,以防系统阻塞。<
    >不推荐直接使用这一级的服务函数,应该使用高层次上的服务函数或者在这一级服务函数的基础上构造自己的通讯函数。<
    > <
    > 数据包级服务函数:在Config.h文件中定义宏SCOMM_PackageInterface(如 define SCOMM_PackageInterface)则可以使用数据包级服务函数。<
    > 共有两个函数可以使用:<
    >void SendPackage(BYTE* pbyData BYTE byLen)<
    >发送数据包,参数pbyData为将要发送的数据包缓冲区(数组)的指针,byLen为将要发送的数据包的长度。<
    >当没有定义SCOMM_DriverInterface时,数据被完全缓冲。即不能够发送长度超过发送缓冲区长度的数据包。当定义了SCOMM_DriverInterface时,采用单字节发送,这时不限制需要发送的数据的长度。<
    ><
    >BYTE RecePackage(BYTE* pbyData BYTE byLen)<
    >接收数据包,参数pbyData为存放将要接收的数据的缓冲区,byLen为缓冲区长度。返回值为接收到的字节数,当模块的接收缓冲区为空时,函数非阻塞,立即返回,返回值为零。<
    ><
    >同步发送接收服务函数<
    >比如在一个串行总线多机通讯系统中,主机需要定时循检各从机的状态,往往是发一个包含从机地址及指令的数据包给从机,之后等待一定的时间,从机需要在这段时间之内给主机一个应答,如果没有这个应答,则认为从机工作状态出错,转去进行相应的处理。在这个模型里,主机不能够不进行等待而给另一台从机发送指令,也不能够不管从机在很久没有应答的情况下继续等待。还有一种情况,比如当使用485总线进行通信时,如果是两条通讯线则系统只能工作在半双工模式下,总线在同一时间内只能工作在发送或接收,为了防止发送和接收相互干扰,这时的通讯常常需要使用同步发送和接收。<
    >当在Config.h文件中定义宏SCOMM_SyncInterface后,则可以使用通讯模块提供同步发送接收函数:<
    >void SendPackage(BYTE* pbyData BYTE byLen)<
    >发送数据包,参数pbyData为将要改善的数据包的缓冲区指针,byLen为将要发送的数据包的长度。<
    >这个函数可以保证等待一个完整的数据包完全发送出去之后,它才返回,在这段时间内,它会阻塞运行。<
    ><
    >BYTE SyncRecePackage(BYTE* pbyBuf BYTE byBufLen WORD wTimeout BYTE byParam)<
    >接收数据包。返回值为接收到的数据包长度。参数pbyBuf为将要接收数据包的缓冲区的指针,byBufLen为提供的缓冲区的长度,wTimeout为通信超时值,如果在发生了由wTimeout所指定次数的时钟中断而还没有接收到或没有接收到完整的数据包时,函数返回零,最后一个参数byParam的含义见后面的解释。<
    ><
    >异步发送接收服务函数:<
    >在一个简单的系统或多机通讯系统中的从机上,一般情况下不需要复杂的停等的工作模式,而且往往单片机需要对硬件进行控制和检测,不允许长时间的停下来检测通讯,但又要求当需要通讯时需要尽快的反应速度,这时就需要使用异步发送和接收服务函数。<
    >使用异步发送和接收服务函数需要在Config.h文件中定义SCOMM_AsyncInterface宏。<
    >同样提供两个服务函数:<
    >void SendPackage(BYTE* pbyData BYTE byLen)<
    >发送数据包,参数pbyData为将要改善的数据包的缓冲区指针,byLen为将要发送的数据包的长度。<
    >这里的函数的接口与同步发送和接收的服务函数相同。关于这里的细节,见后面对同步和异步服务函数的说明。<
    ><
    >void AsyncRecePackage(BYTE byParam)<
    >接收数据包,参数byParam的意义见后面的描述。<
    >使用异步通讯需要用户定义一个回调函数,原型如下:<
    >void OnRecePackage(BYTE* pbyData BYTE byBufLen)<
    >当异步接收服务函数接收到数据包之后,调用OnRecePackage回调函数,在pbyData指定的缓冲区中存放数据包,byBufLen为数据包的长度。<
    >在Config.h文件中定义宏SCOMM_TIMEOUT可以设定异步接收的超时值,当开始接收数据包,但没有收完数据而发生了SCOMM_TIMEOUT次时钟中断后,认为接收超时,将已接收到的数据删除。<
    ><
    >同步和异步通讯服务函数<
    >有些情况下,比如一个通讯系统中,由一台计算机通过串口控制主机,主机通过串口连接很多从机,主机的串口采用分时复用,在这样的模型中,主机和控制计算机之间的通讯可以使用,异步通讯方式,而主机与从机可以使用同步通讯方式。而同步和异步的发送函数接口是相同的,在这样的情况下,发送都是同步的。在这样的模型中,当使用不同的接收函数之前,需要注意清除接收缓冲区中的内容,通讯模块提供函数ClearReceBuffer来做到这一点,此函数原型如下:<
    >void ClearReceBuffer();<
    ><
    >通讯过程中,数据包往往是有固定的格式的,这种格式需要根据用户所使用的协议的不同而不同。同步和异步接收服务函数支持从接收到的数据中识别出一定格式的数据包。<
    >举例说明:目前使用的协议决定数据包的格式为固定的包头0xff,固定的长度4个字节。其它的细节在这里不重要,所以忽略掉。<
    >为了能够使用用SyncRecePackage或AsyncRecePackage函数从接收到的数据中识别出如上格式的数据包,有两种方法:<
    >第一种办法是在Config.h文件中定义宏SCOMM_SimplePackageFormat,说明数据包为一种简单格式,比如上面的协议。<
    >之后还要定义两个宏分别用来识别数据包头和数据包尾,两个宏分别是:<
    >IsPackageHeader(x)和IsPackageTailer(x y z)<
    >接收函数(SyncRecePackage和AsyncRecePackage)在没有开始接收数据包(准确的说是还没有从接收到的数据包中找到包头的时候),会对接收到的每一个字节的数据调用IsPackageHeader宏,将相应的数据作为参数,如果IsPackageHeader宏的结果为 TRUE,则认为找到了数据包头,否则继续对下一个字节进行判断。<
    >上面的协议对应的IsPackageHeader宏可以写为<
    >define IsPackageHeader(x) ((x) == 0xff)<
    >当接收到包头之后,接收函数会对接下来的每一个字节数据调用IsPackagTailer宏来判断是不是已经接收完数据包,三个参数分别为:<
    >x 当前判断的数据。<
    >y 从包头开始到当前被判断的数据止的计数值,即当前已经接收到的字节数。<
    >z:用户在调用SyncRecePackage或AsyncRecePackage时指定的byParam参数。<
    >与IsPackageHeader相似,如果宏IsPackageTailer的运算结果为TRUE,则认为接收到完整的数据包,则调用相应的回调函数(对于异步接收函数)或返回(对于同步接收函数)。如果运算结果为FALSE则继续判断下一个字节的数据。<
    >上面的协议对应的IsPackageTailer宏可以写为:<
    >define IsPackageTailer(x y z) ((y) gt= (z))<
    >当然,用户也可以将IsPackageHeader和IsPackageTailer定义成为函数,通过BIT类型的返回值来向调用者提供与相应宏相同的信息。<
    ><
    >另一种办法需要在Config.h文件中定义宏SCOMM_ComplexPackageFormat。(需要注意的是,不能够同时定义 SCOMM_SimplePackageFormat和SCOMM_ComplexPackageFormat宏,否则会造成严重的不可预见性错误。<
    >这时需要提供回调函数QueryPackageFormat,原形如下:<
    >BYTE QueryPackageFormat(BYTE byData BYTE byCount BYTE byParam)<
    >函数中三个参数的含义与使用简单数据包格式时判断数据包尾的宏的参数相同。<
    >函数通过返回值来通知作为调用者的接收函数对接收到的数据如何处理,但目前这种方法仅为需要处理复杂数据包格式时的一种可选方法,但不推荐。用户如果想使用这种方法可以自己更改接收函数中相应的<
    >ifdef SCOM_ComplexPackageFormat<
    >endif // SCOMM_ComplexPackageFormat<
    >预编译指令之间的内容。<
    >例如指定QueryPackageFormat的返回值的含义:<
    >0:继续找数据包头或继续找数据包尾。<
    >1:找到数据包头。<
    >2:找到数据包尾。<
    >3:数据包出错,需要抛弃。<
    >然后更改源代码来实现上面的协议。<
    ><
    >注意:当用户需要使用字符串的时候,可以利用简单的包装函数将字符串转换为字节数组。所以没有必要提供专用的字符串处理函数。<
    ><
    >键盘扫描模块<
    > 键盘扫描模块有两种工作方式 一种为自动的由时钟模块调用 另一种是由程序员自行调用。<
    >1) 由时钟模块自动调用的方式<
    >将时钟模块实现文件(Timer.h)及键盘扫描模块的实现文件(KBScan。c)包含进工程 在Config.h 文件中添加TIMER_KBSCANDELAY宏。时钟模块自动对时钟中断进行计数 当达到TIMER_KBSCANDELAY宏所定义的值后 自动调用键盘扫描模块中的函数KBScanProcess()进行键盘扫描,也就是说,这个宏的值可以决定按键消抖动的时间。 <
    >用户应该提供两个回调函数OnKBScan()及onKeysPressed()。 在函数OnKBScan中进行键盘扫描 并返回扫描码。扫描码的类型缺省为BYTE 当键盘规模较大时 BYTE不能够完全包含键盘信息时 可在Config.h文件中重定义宏KBVALUE 如下<
    >define KBVALUE WORD<
    >这样 就可以使用16位的键盘扫描码 如果此时还达不到要求 可以将键盘扫描码定义成一个结构 但这样做将会增加代码量及消耗更多的RAM资源 故不推荐。 <
    > 扫描模块调用OnKBScan取得扫描码 并调用用户可以重定义的宏IsNoKeyPressed来判断是否有键按下 缺省的IsNoKeyPressed实现如下<
    >define IsNoKeyPressed(x) ((x) == 0x00) <
    >即认为OnKBScan返回0扫描码时为没有键按下 如果扫描函数返回其它非零扫描码做为无键按下的扫描码时 可以在Config.h文件中重定义IsNoKeyPressed宏的实现。<
    > 8位键盘扫描码(缺省值)时 相应的扫描函数为<
    >BYTE OnKBScan()<
    > 当扫描模块经过软件消抖动之后 发现有键按下 就会调用另一个回调函数onKeysPressed。 函数的声明应该如下<
    >void onKeyPressed(BYTE byKBValue BYTE byState)<
    >其中中的参数byKBValue的类型为BYTE 此为缺省值 如果使用其它类型的扫描码 就将此参数变为相应类型。这个值由OnKBScan返回。另一个参数byState在通常情况下为零。但当用户在Config.h中定义宏KBSCAN_<
    />USTCOUNT,同时键盘上的某键被按住不放时 扫描模块对它自己的调用(注意这里和TIMER_KBSCANDELAY宏不同 TIMER_KBSCANDELAY是时钟中断足够的次数后调用扫描模块 而KBSCAN_<
    />USHCOUNT为扫描模块自身的被调用次数)进行计数,当达到KBSCAN_<
    />USTCOUNT时,扫描模块调用 onKeysPressed,此时第一个参数的含义不变,而byState变成1 同时计数器复位,又经过一段时间后,用值为3的byState 调用onKeysPressed。 这样就可以很方便的实现多功能键或者检测某键的长时间被按下。<
    >2)由用户自行调用<
    >由用户自行在程序中调用扫描模块,而不是由时钟中断自行调用。其它与方式1相同。<
    ><
    >注意:<
    >1) 函数KBScanProcess为非阻塞函数,它将在很快的时间内返回,等待再次分配给它执行的机会。<
    >2) 函数KBScanProcess是在时钟中断外部运行的,它的过程可以被任何中断打断,但不影响系统运行。<
    >3) byState的最大值为250,之后被复位为零。<
    ><
    >应用举例<
    > 现在来举例说明上述几个模块的使用方法。<
    > 硬件环境描述:<
    > 为了控制一盏灯,需要单片机提供一个做控制功能的开关量,这里不描述外部接口电路,只说明当单片机的P10脚为高电平时,灯灭,当P10脚为低电平时,灯亮。<
    >可以通过计算机由串口发送命令来控制,或通过一个按键(push button不是自锁式的按键)来手动控制(按键接在P11脚上,当键没有按下时,P11电平为高,键按下时,引脚电平被接低),当使用按键手动控制的时候,需要给计算机发送通知。<
    >设定串口通讯指令如下:<
    >数据包由0xff做包头,4个字节长,第二个字节为命令代码,第三个字节为数据,最后一个字节为校验位。<
    >命令和数据代码有如下组合:<
    >(计算机发给单片机)<
    >0x10 0x01 计算机控制灯亮。(数据位是非零值即可)<
    >0x10 0x00 计算机控制灯灭。<
    >(单片机发给计算机)<
    >0x11 0x01:单片机正常执行控制指令,返回。(数据位是非零值即可)<
    >0x11 0x00 单片机不能够正常执行控制指令,或控制指令错(不明含义的数据包或校验错等)。<
    >0x12 0x01:手动控制灯亮。(数据位是非零值即可)<
    >0x12 0x00 手动控制灯灭。<
    ><
    > 建立工程:<
    > 在硬盘上建立文件夹Projects,在Projects下建立Common文件夹及Example文件夹。将各模块的头文件及实现文件拷贝到 Common文件夹下(推荐使用这样的文件组织结构,其它工程也可以建立在Projects下,各工程共享Common文件夹中的代码)。<
    > 启动KeilC的IDE,在Example下建立新工程,将各模块的实现文件包含进工程。<
    > 在Example文件夹下建立Output文件夹,更改工程设置,将Output作为输出文件和List文件的输出文件夹(推荐使用这样的结构,当保存工程文件时,可以简单的删除Output文件夹中的内容而不会误删有用的工程文件)。<
    > 建立工程配置头文件Config.h及工程主文件Example.c,并将Exmaple.c文件加入工程。<
    ><
    > 输入代码:<
    > 代码的具体编写过程略。下面是最后的Config.h文件及Example.c文件。<
    >//<
    >// file Config.h<
    >//<
    >ifndef _CONFIG_H_<
    >define _CONFIG_H_<
    >#i nclude ltAtmel/At89x52.hgt // 使用AT89C52做控制<
    >#i nclude “../Common/Common.h” // 使用自定义的数据类型<
    >define TIMER_RELOAD 922 // 11.0592MHz晶振,1ms中断周期<
    >define TIMER_KBSCANDELAY 40 // 40ms重检测按键状态,即40ms消抖<
    >define SCOMM_AsyncInterface // 使用异步通讯服务<
    >define IsPackageHeader(x) ((x) == 0xff) // 判断包头是不是0xff<
    >define IsPackageTailer(x y z) ((y) lt= (z)) // 判断包的长度是不是足够<
    >endif // _CONFIG_H_<
    ><
    >//<
    >// file Example.c<
    >//<
    >#i nclude ltAtmail/At89x52.hgt<
    >#i nclude “../Common/Common.h”<
    >#i nclude “../Common/Timer.h”<
    >#i nclude “../Common/Scomm.h”<
    >#i nclude “../Common/KBScan.h”<
    ><
    >BIT gbitLampState = 1 // 灯的状态,缺省为off<
    ><
    >static void Initialize()<
    ><
    > InitTimerModule() // 初始化时钟模块<
    > InitSCommModule(0xfd TRUE) // 初始化通讯模块,11.0592MHz晶振,<
    > // 波特率为19200<
    > EA = 1; // 开中断<
    ><
    ><
    >void main()<
    ><
    > Initialize() // 初始化<
    > while(TRUE) // 主循环<
    > <
    > ImpTimerService() // 实现时钟中断服务,如键盘扫描<
    > AsyncRecePackage(4) // 接收4个字节长的数据包<
    ><
    ><
    ><
    >// 在中断外部响应时钟中断事件<
    >void OnTimerEvent() <
    ><
    > // do nothing<
    ><
    ><
    >// 控制外部灯<
    >static void TriggerLamp(BIT bEnable) <
    ><
    > P10 = ~bEnable // 需要反相控制<
    ><
    ><
    >// 键扫描回调函数<
    >BYTE KBScan() <
    ><
    > BIT b<
    > P11 = 1 // 读之前拉高引脚电平<
    > b = P11 // 读入引脚状态<
    > return ~b // 数据反相做扫描码<
    ><
    ><
    >// 计算校验和<
    >static BYTE CalcCheckSum(BYTE* pbyBuf BYTE byLen)<
    ><
    > BYTE by bySum = 0<
    > for(by = 0 by lt byLen by )<
    > bySum = pbyBufby<
    > return 0 – bySum<
    ><
    ><
    >// 接收到键盘消息回调函数<
    >void onKeyPressed(BYTE byValue BYTE byState)<
    ><
    > BYTE by4<
    > if(byState == 0)<
    > <
    > switch(byValue)<
    > <
    > case 0x01<
    > gbitLampState = ~g bitLampState // 灯状态取反<
    > TriggerLamp(gbitLampState) // 执行控制<
    > by0 = 0xff // 构造数据包<
    > by1 = 0x12<
    > by2 = (BYTE)gbitLampState<
    > by3 = CalcCheckSum(by 3) // 求校验和<
    > SendPackage(by 4) // 发送数据包<
    >
    eak<
    > // 处理其它扫描码<
    > default<
    >
    eak<
    > <
    ><
    ><
    >// 接收到数据包回调函数<
    >void OnRecePackage(BYTE* pbyBuf BYTE byBufLen)<
    ><
    > BYTE by4<
    > by0 = 0xff<
    > by1 = 0x11<
    > if(byBufLen != 4 pbyBuf3 != CalcCheckSum(pbyBuf 3))<
    > <
    > by2 = 0<
    > by3 = CalcCheckSum(by 3)<
    > SendPackage(by 4) // 处理长度或校验和不正确<
    > <
    ><
    > switch(pbyBuf1)<
    > <
    > case 0x10<
    > gbitLampState = (BIT)pbyBuf2<
    > TriggerLamp(gbitLampState)<
    > by2 = 1<
    > by3 = CalcCheckSum(by 3)<
    > SendPackage(by 4) // 发送成功执行通知<
    >
    eak<
    ><
    > default // 不知道的命令<
    > by2 = 0<
    > by3 = CalcCheckSum(by 3)<
    > SendPackage(by 4) // 发送没有成功执行通知<
    >
    eak<
    > <
    >
  • <
    > 应用单片机的时候,经常会遇到需要短时间延时的情况。需要的延时时间很短,一般都是几十到几百微妙(us)。有时候还需要很高的精度,比如用单片机驱动DS18B20的时候,误差容许的范围在十几us以内,不然很容易出错。这种情况下,用计时器往往有点小题大做。而在极端的情况下,计时器甚至已经全部派上了别的用途。这时就需要我们另想别的办法了。<
    > 以前用汇编语言写单片机程序的时候,这个问题还是相对容易解决的。比如用的是12MHz晶振的51,打算延时20us,只要用下面的代码,就可以满足一般的需要:<
    > mov r0 09h<
    >lo djnz r0 lo<
    >51单片机的指令周期是晶振频率的1/12,也就是1us一个周期。mov r0 09h需要2个极其周期,djnz也需要2个极其周期。那么存在r0里的数就是(20-2)/2。用这种方法,可以非常方便的实现256us以下时间的延时。如果需要更长时间,可以使用两层嵌套。而且精度可以达到2us,一般来说,这已经足够了。<
    > 现在,应用更广泛的毫无疑问是Keil的C编译器。相对汇编来说,C固然有很多优点,比如程序易维护,便于理解,适合大的项目。但缺点(我觉得这是C的唯一一个缺点了)就是实时性没有保证,无法预测代码执行的指令周期。因而在实时性要求高的场合,还需要汇编和C的联合应用。但是是不是这样一个延时程序,也需要用汇编来实现呢?为了找到这个答案,我做了一个实验。<
    > 用C语言实现延时程序,首先想到的就是C常用的循环语句。下面这段代码是我经常在网上看到的:<
    >void delay2(unsigned char i)<
    ><
    > for( i != 0 i--)<
    ><
    >到底这段代码能达到多高的精度呢?为了直接衡量这段代码的效果,我把 Keil C 根据这段代码产生的汇编代码找了出来:<
    > FUNCTION _delay2 (BEGIN)<
    > SOURCE LINE 18<
    >---- Variable __xassigned to Register 7__x----<
    > SOURCE LINE 19<
    > SOURCE LINE 20<
    >0000 C0007<
    >0000 EF MOV AR7<
    >0001 6003 JZ C0010<
    >0003 1F DEC R7<
    >0004 80FA SJMP C0007<
    > SOURCE LINE 21<
    >0006 C0010<
    >0006 22 RET <
    > FUNCTION _delay2 (END)<
    >真是不看不知道~~~一看才知道这个延时程序是多么的不准点~~~光看主要的那四条语句,就需要6个机器周期。也就是说,它的精度顶多也就是6us而已,这还没算上一条 lcall 和一条 ret。如果我们把调用函数时赋的i值根延时长度列一个表的话,就是:<
    >i delay time/us<
    >0 6<
    >1 12<
    >2 18<
    >...<
    >因为函数的调用需要2个时钟周期的lcall,所以delay time比从函数代码的执行时间多2。顺便提一下,有的朋友写的是这样的代码:<
    >void delay2(unsigned char i)<
    ><
    > unsigned char a<
    > for(a = i a != 0 a--)<
    ><
    >可能有人认为这会生成更长的汇编代码来,但是事实证明:<
    > FUNCTION _delay2 (BEGIN)<
    > SOURCE LINE 18<
    >---- Variable __xassigned to Register 7__x----<
    > SOURCE LINE 19<
    > SOURCE LINE 21<
    >---- Variable __xassigned to Register 7__x----<
    >0000 C0007<
    >0000 EF MOV AR7<
    >0001 6003 JZ C0010<
    >0003 1F DEC R7<
    >0004 80FA SJMP C0007<
    > SOURCE LINE 22<
    >0006 C0010<
    >0006 22 RET <
    > FUNCTION _delay2 (END)<
    >其生成的代码是一样的。不过这的确不是什么好的习惯。因为这里实在没有必要再引入多余的变量。我们继续讨论正题。有的朋友为了得当更长的延时,甚至用了这样的代码:<
    >void delay2(unsigned long i)<
    ><
    > for( i != 0 i--)<
    ><
    >这段代码产生的汇编代码是什么样子的?其实不用想也知道它是如何恐怖的amp......让我们看一看:<
    > FUNCTION _delay2 (BEGIN)<
    > SOURCE LINE 18<
    >0000 8F00 R MOV i 03HR7<
    >0002 8E00 R MOV i 02HR6<
    >0004 8D00 R MOV i 01HR5<
    >0006 8C00 R MOV iR4<
    > SOURCE LINE 19<
    > SOURCE LINE 20<
    >0008 C0007<
    >0008 E4 CLR A<
    >0009 FF MOV R7A<
    >000A FE MOV R6A<
    >000B FD MOV R5A<
    >000C FC MOV R4A<
    >000D AB00 R MOV R3i 03H<
    >000F AA00 R MOV R2i 02H<
    >0011 A900 R MOV R1i 01H<
    >0013 A800 R MOV R0i<
    >0015 C3 CLR C<
    >0016 120000 E LCALL CULCMP<
    >0019 601A JZ C0010<
    >001B E500 R MOV Ai 03H<
    >001D 24FF ADD A0FFH<
    >001F F500 R MOV i 03HA<
    >0021 E500 R MOV Ai 02H<
    >0023 34FF ADDC A0FFH<
    >0025 F500 R MOV i 02HA<
    >0027 E500 R MOV Ai 01H<
    >0029 34FF ADDC A0FFH<
    >002B F500 R MOV i 01HA<
    >002D E500 R MOV Ai<
    >002F 34FF ADDC A0FFH<
    >0031 F500 R MOV iA<
    >0033 80D3 SJMP C0007<
    > SOURCE LINE 21<
    >0035 C0010<
    >0035 22 RET <
    > FUNCTION _delay2 (END)<
    >呵呵,这倒是的确可以延迟很长时间~~~但是毫无精度可言了。<
    > 那么,用C到底能不能实现精确的延时呢?我把代码稍微改了一下:<
    >void delay1(unsigned char i)<
    ><
    > while(i--)<
    ><
    >因为根据经验,越简洁的C代码往往也能得出越简洁的机器代码。那这样结果如何呢?把它生成的汇编代码拿出来看一看就知道了。满怀希望的我按下了“Build target”键,结果打击是巨大的:<
    > FUNCTION _delay1 (BEGIN)<
    > SOURCE LINE 13<
    >---- Variable __xassigned to Register 7__x----<
    > SOURCE LINE 14<
    >0000 C0004<
    > SOURCE LINE 15<
    >0000 AE07 MOV R6AR7<
    >0002 1F DEC R7<
    >0003 EE MOV AR6<
    >0004 70FA JNZ C0004<
    > SOURCE LINE 16<
    >0006 C0006<
    >0006 22 RET <
    > FUNCTION _delay1 (END)<
    >虽说生成的代码跟用for语句是不大一样,不过我可以毫无疑问的说,这两种方法的效率是一样的。似乎到此为止了,因为我实在想不出来源程序还有什么简化的余地。看来我就要得出来这个结论了:“如果需要us级的延时精度,需要时用汇编语言。”但是真的是这样吗?我还是不甘心。因为我不相信大名鼎鼎的 Keil C 编译器居然连 djnz 都不会用???因为实际上程序体里只需要一句 lo djnz r7 lo。近乎绝望之际(往往人在这种情况下确可以爆发出来,哦呵呵呵~~~),我随手改了一下:<
    >void delay1(unsigned char i)<
    ><
    > while(--i)<
    ><
    >心不在焉的编译,看源码:<
    > FUNCTION _delay1 (BEGIN)<
    > SOURCE LINE 13<
    >---- Variable __xassigned to Register 7__x----<
    > SOURCE LINE 14<
    >0000 C0004<
    > SOURCE LINE 15<
    >0000 DFFE DJNZ R7C0004<
    > SOURCE LINE 16<
    >0002 C0006<
    >0002 22 RET <
    > FUNCTION _delay1 (END)<
    >天~~~奇迹出现了......我想这个程序应该已经可以满足一般情况下的需要了。如果列个表格的话:<
    >i delay time/us<
    >1 5<
    >2 7<
    >3 9<
    >...<
    >计算延时时间时,已经算上了调用函数的lcall语句所花的2个时钟周期的时间。<
    > 终于,结果已经明了了。只要合理的运用,C还是可以达到意想不到的效果。很多朋友抱怨C效率比汇编差了很多,其实如果对Keil C的编译原理有一个较深入的理解,是可以通过恰当的语法运用,让生成的C代码达到最优化。即使这看起来不大可能,但还是有一些简单的原则可循的:1.尽量使用unsigned型的数据结构。2.尽量使用char型,实在不够用再用int,然后才是long。3.如果有可能,不要用浮点型。4.使用简洁的代码,因为按照经验,简洁的C代码往往可以生成简洁的目标代码(虽说不是在所有的情况下都成立)。5...想不起来了,哦呵呵呵~~~<
    ><
    >
  • 要包含一个单片机硬件的资源头文件。<
    >各模块使用了定义在Common.h中的一些数据类型。如BIT(bit) BYTE(unsigned char)等,具体请参见源程序。<
    ><
    >时钟模块<
    > 在单片机软件设计中 时钟是重要资源 为了充分利用时钟资源 故设计本时钟模块。 本模块使用定时器0,在完成用户指定功能的同时 还能够自动处理一些其它模块中与时钟相关的信息。<
    > 时钟模块由声明文件Timer.h以及实现文件Timer.c组成。<
    > 用户应该在Config.h中定义宏TIMER_RELOAD来设定定时器0的重装载初值。推荐的定时器0的中断时间大于1毫秒。<
    ><
    > 在程序的初始化阶段调用时钟模块的初始化函数InitTimerModule()之后,就可以使用时钟模块所以支持的各种功能。具体描述如下:<
    >延时:当用户需要进行一定时间的延时时,可以通过调用Delay()来进行,参数为时钟中断的次数。如时钟中断周期为1ms, 想进行100ms的延时, 则可以调用Delay(100)。<
    >注意:<
    >如果延时的绝对时间小于时钟中断的周期,则不能够用本方法做到延时。<
    ><
    >定时:当程序中需要使用定时功能时,如等待某外部事件,如果在一定时间内发生则继续执行,如果在这段时间内发生,则认为出现错误,转向错误处理机制。<
    >在此推荐一种编程模式,但用户可以用自己认为更合理的方式处理此类问题。<
    >这里简单说明一下关于阻塞式函数及非阻塞式函数。简单说,阻塞式函数就是当检测完成条件,如果不能够完成则等待,如:<
    >void CheckSomething()<
    ><
    > // gbitSuccessFlag is a global variable<
    > while(gbitSuccessFlag == FALSE)<
    > <
    > // do nothing but waiting<
    > <
    ><
    >可以看到,当bitSuccessFlag没有被设置为TRUE时,函数保持等待状态不返回,这样就是阻塞式的函数。<
    >另外一种情况:<
    >BIT CheckSomething()<
    ><
    > if(gbitSuccessFlag == TRUE)<
    > <
    > // …<
    > return TRUE<
    > <
    > return FALSE<
    ><
    >在这里,如果所检测的事件有没有完成,函数进行检测之后,立刻返回,通过返回值报告完成情况,如果没有完成,则等待调用者分配再次执行的机会。这样的函数就是非阻塞函数。<
    >在应用定时功能时,首先要将检测函数定义成非阻塞函数。如上面的第二个版本的CheckSomething。<
    >然后下面模式:<
    >BIT bitDone = FALSE<
    >ResetClock() // clear timer interrupt times counter<
    >while(GetClock() lt MAX_WAITINGTIME)<
    ><
    > if(CheckSomething() == TRUE)<
    > <
    > bitDone = TRUE<
    >
    eak<
    > <
    ><
    >if(bitDone == FALSE)<
    ><
    > // process time out<
    ><
    ><
    >或者简单写成:<
    >BIT bitDone = FALSE<
    >ResetClock()<
    >while(GetClock() lt MAX_WAITINGTIME ampamp (bitDone = CheckSomething))<
    >if(bitDone == FLASE)<
    > <
    > // …<
    ><
    ><
    >软件看门狗:实现具有局限性的看门狗功能。在程序中合适的地方加入对软件看门狗的复位函数ResetWatchDog(),在Config.h中加入宏 TIMER_WATCHDOGTIMEOUT。当程序运行时,如果在发生TIMER_WATCHDOGTIMEOUT次时钟中断之内没有复位软件看门狗,则系统复位。<
    >注意:<
    >如果没有加入TIMER_WATCHDOGTIMEOUT宏,程序中的ResetWatchDog没有任何用处,不用删除。<
    >如果系统不能实现时钟中断,则软件看门狗也同时失去功能。<
    >目前版本的的时钟模块的复位功能并不是完全复位,主要表现在当复位之后,系统将不再响应任何中断。所以软件看门狗只是一个程序的调试功能,不应该将它用于正式工作的程序,此时应该使用硬件看门狗。<
    ><
    >用户自定义任务:如果想在时钟中断内执行一些耗时较短的任务,可以定义回调函数OnTimerInterrupt。函数原形为:void OnTimerInterrupt()<
    >如果想在发生时钟中断时执行一些功能,而这些功能又耗时相对较长,不合适放在中断响应函数内部,则可以在程序中的主循环中的任意地方添加 ImpTimerService(),同时提供原形为void OnTimerEvent()的回调函数。具体的程序如下所示:<
    >void main()<
    ><
    > Initialize()<
    > while(TRUE)<
    > <
    > // … working<
    > ImpTimerService()<
    > // … working<
    > <
    ><
    ><
    >void OnTimerEvent()<
    ><
    > // do some task<
    ><
    ><
    >对通讯模块提供支持:如通讯中的各种超时等,见通讯模块中的详细说明。<
    >对键盘扫描模块提供支持:可以自动调用键盘扫描模块,见键盘扫描模块中的详细说明。<
    >对程序调试提供支持:在程序开发过程中,有时为了判断程序是不是在工作,常用利用单片机系统的某一空闲引脚通过一个限流电阻接一个发光二极管,在程序中间隔固定时间交替控制发光管的明暗。实现这个功能只要在Config.h文件中定义TIMER_FLASHLED宏,如:<
    >define TIMER_FLASHLED P1_0<
    >则当时钟中断发生256次之后,改变发光管的状态。<
    ><
    >通讯模块<
    > 串口资源做为单片机与外界通信的常用手段,通讯模块提供了完全缓冲的串口通讯底层机制,适用于长度不大的数据包的发送及接收。如果处理关键数据,需要用户自己提供纠错协议。<
    > 通讯模块由声明文件SComm.h及实现文件SComm.c组成。<
    > 初始化:调用函数InitSCommModule()来初始化通讯模块:<
    > void InitSCommModule(BYTE byTimerReload BIT bitTurbo)<
    > 参数说明:<
    >byTimerReload 定时器1的重装载初始值。<
    >bitTurob 当此参数为TRUE时,串行通讯在定时器1的溢出速率基础上加倍。为FALSE时,串行通讯速率为定时器1的溢出速率。<
    ><
    > 缓冲区:模块使用了由宏SCOMM_SENDBUFSIZE、SCOMM_RECEBUFSIZE及SCOMM_PKGBUFSIZE所指定长度的三个缓冲区,分别为发送、接收及数据包(用于处理接收到的数据)缓冲区(如果没有使用异步接收功能,则不需要使用数据包缓冲区)。<
    > 在缺省时,这三个宏都被定义为10,但用户可以自已按照系统的RAM资源占用情况在Config.h中重定义缓冲区的大小。需要注意的是,如果缓冲的长度不够,当发送或接收长数据包的时候可能会发生问题,关于数据缓冲区的最小值的设置可以参考下面的说明。<
    > 注意:需要尽快取出接收缓冲区中的数据,否则当缓冲区满之后,新的数据将被简单的丢掉。<
    ><
    > 字节级服务函数: 在Config.h文件中定义了宏SCOMM_DriverInterface(如:define SCOMM_DriverInterface),则可以使用字节级服务函数,即通讯模块的底层函数。<
    > 共有两个函数可以使用:<
    > void SendByte(BYTE byData)<
    > 发送一个字节,如果当前缓冲区满,则等待。参数byData为要发送的数据。<
    >BYTE ReceByte()<
    >接收一个字节,如果当前缓冲区中没有数据,则此函数阻塞,直到接收到数据为止。接收到数据通过返回值返回。<
    >可以通过调用IsSendBufEmpty() IsSendBufFull() IsReceBufEmpty() IsReceBufFull() 宏来判断缓冲区的空或满,以防系统阻塞。<
    >不推荐直接使用这一级的服务函数,应该使用高层次上的服务函数或者在这一级服务函数的基础上构造自己的通讯函数。<
    > <
    > 数据包级服务函数:在Config.h文件中定义宏SCOMM_PackageInterface(如 define SCOMM_PackageInterface)则可以使用数据包级服务函数。<
    > 共有两个函数可以使用:<
    >void SendPackage(BYTE* pbyData BYTE byLen)<
    >发送数据包,参数pbyData为将要发送的数据包缓冲区(数组)的指针,byLen为将要发送的数据包的长度。<
    >当没有定义SCOMM_DriverInterface时,数据被完全缓冲。即不能够发送长度超过发送缓冲区长度的数据包。当定义了SCOMM_DriverInterface时,采用单字节发送,这时不限制需要发送的数据的长度。<
    ><
    >BYTE RecePackage(BYTE* pbyData BYTE byLen)<
    >接收数据包,参数pbyData为存放将要接收的数据的缓冲区,byLen为缓冲区长度。返回值为接收到的字节数,当模块的接收缓冲区为空时,函数非阻塞,立即返回,返回值为零。<
    ><
    >同步发送接收服务函数<
    >比如在一个串行总线多机通讯系统中,主机需要定时循检各从机的状态,往往是发一个包含从机地址及指令的数据包给从机,之后等待一定的时间,从机需要在这段时间之内给主机一个应答,如果没有这个应答,则认为从机工作状态出错,转去进行相应的处理。在这个模型里,主机不能够不进行等待而给另一台从机发送指令,也不能够不管从机在很久没有应答的情况下继续等待。还有一种情况,比如当使用485总线进行通信时,如果是两条通讯线则系统只能工作在半双工模式下,总线在同一时间内只能工作在发送或接收,为了防止发送和接收相互干扰,这时的通讯常常需要使用同步发送和接收。<
    >当在Config.h文件中定义宏SCOMM_SyncInterface后,则可以使用通讯模块提供同步发送接收函数:<
    >void SendPackage(BYTE* pbyData BYTE byLen)<
    >发送数据包,参数pbyData为将要改善的数据包的缓冲区指针,byLen为将要发送的数据包的长度。<
    >这个函数可以保证等待一个完整的数据包完全发送出去之后,它才返回,在这段时间内,它会阻塞运行。<
    ><
    >BYTE SyncRecePackage(BYTE* pbyBuf BYTE byBufLen WORD wTimeout BYTE byParam)<
    >接收数据包。返回值为接收到的数据包长度。参数pbyBuf为将要接收数据包的缓冲区的指针,byBufLen为提供的缓冲区的长度,wTimeout为通信超时值,如果在发生了由wTimeout所指定次数的时钟中断而还没有接收到或没有接收到完整的数据包时,函数返回零,最后一个参数byParam的含义见后面的解释。<
    ><
    >异步发送接收服务函数:<
    >在一个简单的系统或多机通讯系统中的从机上,一般情况下不需要复杂的停等的工作模式,而且往往单片机需要对硬件进行控制和检测,不允许长时间的停下来检测通讯,但又要求当需要通讯时需要尽快的反应速度,这时就需要使用异步发送和接收服务函数。<
    >使用异步发送和接收服务函数需要在Config.h文件中定义SCOMM_AsyncInterface宏。<
    >同样提供两个服务函数:<
    >void SendPackage(BYTE* pbyData BYTE byLen)<
    >发送数据包,参数pbyData为将要改善的数据包的缓冲区指针,byLen为将要发送的数据包的长度。<
    >这里的函数的接口与同步发送和接收的服务函数相同。关于这里的细节,见后面对同步和异步服务函数的说明。<
    ><
    >void AsyncRecePackage(BYTE byParam)<
    >接收数据包,参数byParam的意义见后面的描述。<
    >使用异步通讯需要用户定义一个回调函数,原型如下:<
    >void OnRecePackage(BYTE* pbyData BYTE byBufLen)<
    >当异步接收服务函数接收到数据包之后,调用OnRecePackage回调函数,在pbyData指定的缓冲区中存放数据包,byBufLen为数据包的长度。<
    >在Config.h文件中定义宏SCOMM_TIMEOUT可以设定异步接收的超时值,当开始接收数据包,但没有收完数据而发生了SCOMM_TIMEOUT次时钟中断后,认为接收超时,将已接收到的数据删除。<
    ><
    >同步和异步通讯服务函数<
    >有些情况下,比如一个通讯系统中,由一台计算机通过串口控制主机,主机通过串口连接很多从机,主机的串口采用分时复用,在这样的模型中,主机和控制计算机之间的通讯可以使用,异步通讯方式,而主机与从机可以使用同步通讯方式。而同步和异步的发送函数接口是相同的,在这样的情况下,发送都是同步的。在这样的模型中,当使用不同的接收函数之前,需要注意清除接收缓冲区中的内容,通讯模块提供函数ClearReceBuffer来做到这一点,此函数原型如下:<
    >void ClearReceBuffer();<
    ><
    >通讯过程中,数据包往往是有固定的格式的,这种格式需要根据用户所使用的协议的不同而不同。同步和异步接收服务函数支持从接收到的数据中识别出一定格式的数据包。<
    >举例说明:目前使用的协议决定数据包的格式为固定的包头0xff,固定的长度4个字节。其它的细节在这里不重要,所以忽略掉。<
    >为了能够使用用SyncRecePackage或AsyncRecePackage函数从接收到的数据中识别出如上格式的数据包,有两种方法:<
    >第一种办法是在Config.h文件中定义宏SCOMM_SimplePackageFormat,说明数据包为一种简单格式,比如上面的协议。<
    >之后还要定义两个宏分别用来识别数据包头和数据包尾,两个宏分别是:<
    >IsPackageHeader(x)和IsPackageTailer(x y z)<
    >接收函数(SyncRecePackage和AsyncRecePackage)在没有开始接收数据包(准确的说是还没有从接收到的数据包中找到包头的时候),会对接收到的每一个字节的数据调用IsPackageHeader宏,将相应的数据作为参数,如果IsPackageHeader宏的结果为 TRUE,则认为找到了数据包头,否则继续对下一个字节进行判断。<
    >上面的协议对应的IsPackageHeader宏可以写为<
    >define IsPackageHeader(x) ((x) == 0xff)<
    >当接收到包头之后,接收函数会对接下来的每一个字节数据调用IsPackagTailer宏来判断是不是已经接收完数据包,三个参数分别为:<
    >x 当前判断的数据。<
    >y 从包头开始到当前被判断的数据止的计数值,即当前已经接收到的字节数。<
    >z:用户在调用SyncRecePackage或AsyncRecePackage时指定的byParam参数。<
    >与IsPackageHeader相似,如果宏IsPackageTailer的运算结果为TRUE,则认为接收到完整的数据包,则调用相应的回调函数(对于异步接收函数)或返回(对于同步接收函数)。如果运算结果为FALSE则继续判断下一个字节的数据。<
    >上面的协议对应的IsPackageTailer宏可以写为:<
    >define IsPackageTailer(x y z) ((y) gt= (z))<
    >当然,用户也可以将IsPackageHeader和IsPackageTailer定义成为函数,通过BIT类型的返回值来向调用者提供与相应宏相同的信息。<
    ><
    >另一种办法需要在Config.h文件中定义宏SCOMM_ComplexPackageFormat。(需要注意的是,不能够同时定义 SCOMM_SimplePackageFormat和SCOMM_ComplexPackageFormat宏,否则会造成严重的不可预见性错误。<
    >这时需要提供回调函数QueryPackageFormat,原形如下:<
    >BYTE QueryPackageFormat(BYTE byData BYTE byCount BYTE byParam)<
    >函数中三个参数的含义与使用简单数据包格式时判断数据包尾的宏的参数相同。<
    >函数通过返回值来通知作为调用者的接收函数对接收到的数据如何处理,但目前这种方法仅为需要处理复杂数据包格式时的一种可选方法,但不推荐。用户如果想使用这种方法可以自己更改接收函数中相应的<
    >ifdef SCOM_ComplexPackageFormat<
    >endif // SCOMM_ComplexPackageFormat<
    >预编译指令之间的内容。<
    >例如指定QueryPackageFormat的返回值的含义:<
    >0:继续找数据包头或继续找数据包尾。<
    >1:找到数据包头。<
    >2:找到数据包尾。<
    >3:数据包出错,需要抛弃。<
    >然后更改源代码来实现上面的协议。<
    ><
    >注意:当用户需要使用字符串的时候,可以利用简单的包装函数将字符串转换为字节数组。所以没有必要提供专用的字符串处理函数。<
    ><
    >键盘扫描模块<
    > 键盘扫描模块有两种工作方式 一种为自动的由时钟模块调用 另一种是由程序员自行调用。<
    >1) 由时钟模块自动调用的方式<
    >将时钟模块实现文件(Timer.h)及键盘扫描模块的实现文件(KBScan。c)包含进工程 在Config.h 文件中添加TIMER_KBSCANDELAY宏。时钟模块自动对时钟中断进行计数 当达到TIMER_KBSCANDELAY宏所定义的值后 自动调用键盘扫描模块中的函数KBScanProcess()进行键盘扫描,也就是说,这个宏的值可以决定按键消抖动的时间。 <
    >用户应该提供两个回调函数OnKBScan()及onKeysPressed()。 在函数OnKBScan中进行键盘扫描 并返回扫描码。扫描码的类型缺省为BYTE 当键盘规模较大时 BYTE不能够完全包含键盘信息时 可在Config.h文件中重定义宏KBVALUE 如下<
    >define KBVALUE WORD<
    >这样 就可以使用16位的键盘扫描码 如果此时还达不到要求 可以将键盘扫描码定义成一个结构 但这样做将会增加代码量及消耗更多的RAM资源 故不推荐。 <
    > 扫描模块调用OnKBScan取得扫描码 并调用用户可以重定义的宏IsNoKeyPressed来判断是否有键按下 缺省的IsNoKeyPressed实现如下<
    >define IsNoKeyPressed(x) ((x) == 0x00) <
    >即认为OnKBScan返回0扫描码时为没有键按下 如果扫描函数返回其它非零扫描码做为无键按下的扫描码时 可以在Config.h文件中重定义IsNoKeyPressed宏的实现。<
    > 8位键盘扫描码(缺省值)时 相应的扫描函数为<
    >BYTE OnKBScan()<
    > 当扫描模块经过软件消抖动之后 发现有键按下 就会调用另一个回调函数onKeysPressed。 函数的声明应该如下<
    >void onKeyPressed(BYTE byKBValue BYTE byState)<
    >其中中的参数byKBValue的类型为BYTE 此为缺省值 如果使用其它类型的扫描码 就将此参数变为相应类型。这个值由OnKBScan返回。另一个参数byState在通常情况下为零。但当用户在Config.h中定义宏KBSCAN_<
    />USTCOUNT,同时键盘上的某键被按住不放时 扫描模块对它自己的调用(注意这里和TIMER_KBSCANDELAY宏不同 TIMER_KBSCANDELAY是时钟中断足够的次数后调用扫描模块 而KBSCAN_<
    />USHCOUNT为扫描模块自身的被调用次数)进行计数,当达到KBSCAN_<
    />USTCOUNT时,扫描模块调用 onKeysPressed,此时第一个参数的含义不变,而byState变成1 同时计数器复位,又经过一段时间后,用值为3的byState 调用onKeysPressed。 这样就可以很方便的实现多功能键或者检测某键的长时间被按下。<
    >2)由用户自行调用<
    >由用户自行在程序中调用扫描模块,而不是由时钟中断自行调用。其它与方式1相同。<
    ><
    >注意:<
    >1) 函数KBScanProcess为非阻塞函数,它将在很快的时间内返回,等待再次分配给它执行的机会。<
    >2) 函数KBScanProcess是在时钟中断外部运行的,它的过程可以被任何中断打断,但不影响系统运行。<
    >3) byState的最大值为250,之后被复位为零。<
    ><
    >应用举例<
    > 现在来举例说明上述几个模块的使用方法。<
    > 硬件环境描述:<
    > 为了控制一盏灯,需要单片机提供一个做控制功能的开关量,这里不描述外部接口电路,只说明当单片机的P10脚为高电平时,灯灭,当P10脚为低电平时,灯亮。<
    >可以通过计算机由串口发送命令来控制,或通过一个按键(push button不是自锁式的按键)来手动控制(按键接在P11脚上,当键没有按下时,P11电平为高,键按下时,引脚电平被接低),当使用按键手动控制的时候,需要给计算机发送通知。<
    >设定串口通讯指令如下:<
    >数据包由0xff做包头,4个字节长,第二个字节为命令代码,第三个字节为数据,最后一个字节为校验位。<
    >命令和数据代码有如下组合:<
    >(计算机发给单片机)<
    >0x10 0x01 计算机控制灯亮。(数据位是非零值即可)<
    >0x10 0x00 计算机控制灯灭。<
    >(单片机发给计算机)<
    >0x11 0x01:单片机正常执行控制指令,返回。(数据位是非零值即可)<
    >0x11 0x00 单片机不能够正常执行控制指令,或控制指令错(不明含义的数据包或校验错等)。<
    >0x12 0x01:手动控制灯亮。(数据位是非零值即可)<
    >0x12 0x00 手动控制灯灭。<
    ><
    > 建立工程:<
    > 在硬盘上建立文件夹Projects,在Projects下建立Common文件夹及Example文件夹。将各模块的头文件及实现文件拷贝到 Common文件夹下(推荐使用这样的文件组织结构,其它工程也可以建立在Projects下,各工程共享Common文件夹中的代码)。<
    > 启动KeilC的IDE,在Example下建立新工程,将各模块的实现文件包含进工程。<
    > 在Example文件夹下建立Output文件夹,更改工程设置,将Output作为输出文件和List文件的输出文件夹(推荐使用这样的结构,当保存工程文件时,可以简单的删除Output文件夹中的内容而不会误删有用的工程文件)。<
    > 建立工程配置头文件Config.h及工程主文件Example.c,并将Exmaple.c文件加入工程。<
    ><
    > 输入代码:<
    > 代码的具体编写过程略。下面是最后的Config.h文件及Example.c文件。<
    >//<
    >// file Config.h<
    >//<
    >ifndef _CONFIG_H_<
    >define _CONFIG_H_<
    >#i nclude ltAtmel/At89x52.hgt // 使用AT89C52做控制<
    >#i nclude “../Common/Common.h” // 使用自定义的数据类型<
    >define TIMER_RELOAD 922 // 11.0592MHz晶振,1ms中断周期<
    >define TIMER_KBSCANDELAY 40 // 40ms重检测按键状态,即40ms消抖<
    >define SCOMM_AsyncInterface // 使用异步通讯服务<
    >define IsPackageHeader(x) ((x) == 0xff) // 判断包头是不是0xff<
    >define IsPackageTailer(x y z) ((y) lt= (z)) // 判断包的长度是不是足够<
    >endif // _CONFIG_H_<
    ><
    >//<
    >// file Example.c<
    >//<
    >#i nclude ltAtmail/At89x52.hgt<
    >#i nclude “../Common/Common.h”<
    >#i nclude “../Common/Timer.h”<
    >#i nclude “../Common/Scomm.h”<
    >#i nclude “../Common/KBScan.h”<
    ><
    >BIT gbitLampState = 1 // 灯的状态,缺省为off<
    ><
    >static void Initialize()<
    ><
    > InitTimerModule() // 初始化时钟模块<
    > InitSCommModule(0xfd TRUE) // 初始化通讯模块,11.0592MHz晶振,<
    > // 波特率为19200<
    > EA = 1; // 开中断<
    ><
    ><
    >void main()<
    ><
    > Initialize() // 初始化<
    > while(TRUE) // 主循环<
    > <
    > ImpTimerService() // 实现时钟中断服务,如键盘扫描<
    > AsyncRecePackage(4) // 接收4个字节长的数据包<
    ><
    ><
    ><
    >// 在中断外部响应时钟中断事件<
    >void OnTimerEvent() <
    ><
    > // do nothing<
    ><
    ><
    >// 控制外部灯<
    >static void TriggerLamp(BIT bEnable) <
    ><
    > P10 = ~bEnable // 需要反相控制<
    ><
    ><
    >// 键扫描回调函数<
    >BYTE KBScan() <
    ><
    > BIT b<
    > P11 = 1 // 读之前拉高引脚电平<
    > b = P11 // 读入引脚状态<
    > return ~b // 数据反相做扫描码<
    ><
    ><
    >// 计算校验和<
    >static BYTE CalcCheckSum(BYTE* pbyBuf BYTE byLen)<
    ><
    > BYTE by bySum = 0<
    > for(by = 0 by lt byLen by )<
    > bySum = pbyBufby<
    > return 0 – bySum<
    ><
    ><
    >// 接收到键盘消息回调函数<
    >void onKeyPressed(BYTE byValue BYTE byState)<
    ><
    > BYTE by4<
    > if(byState == 0)<
    > <
    > switch(byValue)<
    > <
    > case 0x01<
    > gbitLampState = ~g bitLampState // 灯状态取反<
    > TriggerLamp(gbitLampState) // 执行控制<
    > by0 = 0xff // 构造数据包<
    > by1 = 0x12<
    > by2 = (BYTE)gbitLampState<
    > by3 = CalcCheckSum(by 3) // 求校验和<
    > SendPackage(by 4) // 发送数据包<
    >
    eak<
    > // 处理其它扫描码<
    > default<
    >
    eak<
    > <
    ><
    ><
    >// 接收到数据包回调函数<
    >void OnRecePackage(BYTE* pbyBuf BYTE byBufLen)<
    ><
    > BYTE by4<
    > by0 = 0xff<
    > by1 = 0x11<
    > if(byBufLen != 4 pbyBuf3 != CalcCheckSum(pbyBuf 3))<
    > <
    > by2 = 0<
    > by3 = CalcCheckSum(by 3)<
    > SendPackage(by 4) // 处理长度或校验和不正确<
    > <
    ><
    > switch(pbyBuf1)<
    > <
    > case 0x10<
    > gbitLampState = (BIT)pbyBuf2<
    > TriggerLamp(gbitLampState)<
    > by2 = 1<
    > by3 = CalcCheckSum(by 3)<
    > SendPackage(by 4) // 发送成功执行通知<
    >
    eak<
    ><
    > default // 不知道的命令<
    > by2 = 0<
    > by3 = CalcCheckSum(by 3)<
    > SendPackage(by 4) // 发送没有成功执行通知<
    >
    eak<
    > <
    >
  • <
    > 应用单片机的时候,经常会遇到需要短时间延时的情况。需要的延时时间很短,一般都是几十到几百微妙(us)。有时候还需要很高的精度,比如用单片机驱动DS18B20的时候,误差容许的范围在十几us以内,不然很容易出错。这种情况下,用计时器往往有点小题大做。而在极端的情况下,计时器甚至已经全部派上了别的用途。这时就需要我们另想别的办法了。<
    > 以前用汇编语言写单片机程序的时候,这个问题还是相对容易解决的。比如用的是12MHz晶振的51,打算延时20us,只要用下面的代码,就可以满足一般的需要:<
    > mov r0 09h<
    >lo djnz r0 lo<
    >51单片机的指令周期是晶振频率的1/12,也就是1us一个周期。mov r0 09h需要2个极其周期,djnz也需要2个极其周期。那么存在r0里的数就是(20-2)/2。用这种方法,可以非常方便的实现256us以下时间的延时。如果需要更长时间,可以使用两层嵌套。而且精度可以达到2us,一般来说,这已经足够了。<
    > 现在,应用更广泛的毫无疑问是Keil的C编译器。相对汇编来说,C固然有很多优点,比如程序易维护,便于理解,适合大的项目。但缺点(我觉得这是C的唯一一个缺点了)就是实时性没有保证,无法预测代码执行的指令周期。因而在实时性要求高的场合,还需要汇编和C的联合应用。但是是不是这样一个延时程序,也需要用汇编来实现呢?为了找到这个答案,我做了一个实验。<
    > 用C语言实现延时程序,首先想到的就是C常用的循环语句。下面这段代码是我经常在网上看到的:<
    >void delay2(unsigned char i)<
    ><
    > for( i != 0 i--)<
    ><
    >到底这段代码能达到多高的精度呢?为了直接衡量这段代码的效果,我把 Keil C 根据这段代码产生的汇编代码找了出来:<
    > FUNCTION _delay2 (BEGIN)<
    > SOURCE LINE 18<
    >---- Variable __xassigned to Register 7__x----<
    > SOURCE LINE 19<
    > SOURCE LINE 20<
    >0000 C0007<
    >0000 EF MOV AR7<
    >0001 6003 JZ C0010<
    >0003 1F DEC R7<
    >0004 80FA SJMP C0007<
    > SOURCE LINE 21<
    >0006 C0010<
    >0006 22 RET <
    > FUNCTION _delay2 (END)<
    >真是不看不知道~~~一看才知道这个延时程序是多么的不准点~~~光看主要的那四条语句,就需要6个机器周期。也就是说,它的精度顶多也就是6us而已,这还没算上一条 lcall 和一条 ret。如果我们把调用函数时赋的i值根延时长度列一个表的话,就是:<
    >i delay time/us<
    >0 6<
    >1 12<
    >2 18<
    >...<
    >因为函数的调用需要2个时钟周期的lcall,所以delay time比从函数代码的执行时间多2。顺便提一下,有的朋友写的是这样的代码:<
    >void delay2(unsigned char i)<
    ><
    > unsigned char a<
    > for(a = i a != 0 a--)<
    ><
    >可能有人认为这会生成更长的汇编代码来,但是事实证明:<
    > FUNCTION _delay2 (BEGIN)<
    > SOURCE LINE 18<
    >---- Variable __xassigned to Register 7__x----<
    > SOURCE LINE 19<
    > SOURCE LINE 21<
    >---- Variable __xassigned to Register 7__x----<
    >0000 C0007<
    >0000 EF MOV AR7<
    >0001 6003 JZ C0010<
    >0003 1F DEC R7<
    >0004 80FA SJMP C0007<
    > SOURCE LINE 22<
    >0006 C0010<
    >0006 22 RET <
    > FUNCTION _delay2 (END)<
    >其生成的代码是一样的。不过这的确不是什么好的习惯。因为这里实在没有必要再引入多余的变量。我们继续讨论正题。有的朋友为了得当更长的延时,甚至用了这样的代码:<
    >void delay2(unsigned long i)<
    ><
    > for( i != 0 i--)<
    ><
    >这段代码产生的汇编代码是什么样子的?其实不用想也知道它是如何恐怖的amp......让我们看一看:<
    > FUNCTION _delay2 (BEGIN)<
    > SOURCE LINE 18<
    >0000 8F00 R MOV i 03HR7<
    >0002 8E00 R MOV i 02HR6<
    >0004 8D00 R MOV i 01HR5<
    >0006 8C00 R MOV iR4<
    > SOURCE LINE 19<
    > SOURCE LINE 20<
    >0008 C0007<
    >0008 E4 CLR A<
    >0009 FF MOV R7A<
    >000A FE MOV R6A<
    >000B FD MOV R5A<
    >000C FC MOV R4A<
    >000D AB00 R MOV R3i 03H<
    >000F AA00 R MOV R2i 02H<
    >0011 A900 R MOV R1i 01H<
    >0013 A800 R MOV R0i<
    >0015 C3 CLR C<
    >0016 120000 E LCALL CULCMP<
    >0019 601A JZ C0010<
    >001B E500 R MOV Ai 03H<
    >001D 24FF ADD A0FFH<
    >001F F500 R MOV i 03HA<
    >0021 E500 R MOV Ai 02H<
    >0023 34FF ADDC A0FFH<
    >0025 F500 R MOV i 02HA<
    >0027 E500 R MOV Ai 01H<
    >0029 34FF ADDC A0FFH<
    >002B F500 R MOV i 01HA<
    >002D E500 R MOV Ai<
    >002F 34FF ADDC A0FFH<
    >0031 F500 R MOV iA<
    >0033 80D3 SJMP C0007<
    > SOURCE LINE 21<
    >0035 C0010<
    >0035 22 RET <
    > FUNCTION _delay2 (END)<
    >呵呵,这倒是的确可以延迟很长时间~~~但是毫无精度可言了。<
    > 那么,用C到底能不能实现精确的延时呢?我把代码稍微改了一下:<
    >void delay1(unsigned char i)<
    ><
    > while(i--)<
    ><
    >因为根据经验,越简洁的C代码往往也能得出越简洁的机器代码。那这样结果如何呢?把它生成的汇编代码拿出来看一看就知道了。满怀希望的我按下了“Build target”键,结果打击是巨大的:<
    > FUNCTION _delay1 (BEGIN)<
    > SOURCE LINE 13<
    >---- Variable __xassigned to Register 7__x----<
    > SOURCE LINE 14<
    >0000 C0004<
    > SOURCE LINE 15<
    >0000 AE07 MOV R6AR7<
    >0002 1F DEC R7<
    >0003 EE MOV AR6<
    >0004 70FA JNZ C0004<
    > SOURCE LINE 16<
    >0006 C0006<
    >0006 22 RET <
    > FUNCTION _delay1 (END)<
    >虽说生成的代码跟用for语句是不大一样,不过我可以毫无疑问的说,这两种方法的效率是一样的。似乎到此为止了,因为我实在想不出来源程序还有什么简化的余地。看来我就要得出来这个结论了:“如果需要us级的延时精度,需要时用汇编语言。”但是真的是这样吗?我还是不甘心。因为我不相信大名鼎鼎的 Keil C 编译器居然连 djnz 都不会用???因为实际上程序体里只需要一句 lo djnz r7 lo。近乎绝望之际(往往人在这种情况下确可以爆发出来,哦呵呵呵~~~),我随手改了一下:<
    >void delay1(unsigned char i)<
    ><
    > while(--i)<
    ><
    >心不在焉的编译,看源码:<
    > FUNCTION _delay1 (BEGIN)<
    > SOURCE LINE 13<
    >---- Variable __xassigned to Register 7__x----<
    > SOURCE LINE 14<
    >0000 C0004<
    > SOURCE LINE 15<
    >0000 DFFE DJNZ R7C0004<
    > SOURCE LINE 16<
    >0002 C0006<
    >0002 22 RET <
    > FUNCTION _delay1 (END)<
    >天~~~奇迹出现了......我想这个程序应该已经可以满足一般情况下的需要了。如果列个表格的话:<
    >i delay time/us<
    >1 5<
    >2 7<
    >3 9<
    >...<
    >计算延时时间时,已经算上了调用函数的lcall语句所花的2个时钟周期的时间。<
    > 终于,结果已经明了了。只要合理的运用,C还是可以达到意想不到的效果。很多朋友抱怨C效率比汇编差了很多,其实如果对Keil C的编译原理有一个较深入的理解,是可以通过恰当的语法运用,让生成的C代码达到最优化。即使这看起来不大可能,但还是有一些简单的原则可循的:1.尽量使用unsigned型的数据结构。2.尽量使用char型,实在不够用再用int,然后才是long。3.如果有可能,不要用浮点型。4.使用简洁的代码,因为按照经验,简洁的C代码往往可以生成简洁的目标代码(虽说不是在所有的情况下都成立)。5...想不起来了,哦呵呵呵~~~<
    ><
    >
  • 我的百度博客

    2007-10-01

    http//hi.baidu.com/weblook
  • 我的百度博客

    2007-10-01

    http//hi.baidu.com/weblook
  • <
    />>文章来源:http//www.ebailu.com/PageDigest.aspid=47><
    />
    ><
    />>1.豪勇七蛟龙(The Magnificent Seven)><
    />大型颁奖晚会最喜欢用的背景音乐,地球人都知道。伯恩斯坦作曲。<
    /><
    />地址><
    /><
    />>2.故乡的原风景><
    />《神雕侠侣》多次引用,哀伤感人。出自扶桑作曲家宗次郎1991年的专辑《木道》。<
    /><
    />地址><
    /><
    />>3.CCTV《天气预报》主题曲><
    />据说是迄今为止中间电视台唯一没有改变过的背景音乐,《天气预报》一直使用它。《渔舟唱晚》(即天气预报背景音乐),是当年在上海颇有名气的电子琴 演奏家浦琪璋根据同名民族乐曲改编演奏的。她原来是上海乐团的独奏演员是从“小荧星”艺术团毕业的在艺术上颇有成就,曾与上海轻音乐乐团合作过许多脍 炙人口的曲子,如:《幸福的傣乡》等等。音乐界的屠巴海经常与她合作。这首曲子完成后浦琪璋便退出乐界。但此曲却因为被黄金档节目央视的《天气预报》采 用为背景音乐而受到了广大中国人民的喜爱。当年浦琪璋用“雅马哈”三排键盘的音乐会电子琴改编演奏这首曲子时,也没有想到此曲会成为黄金时段节目的黄金背 景音乐,更想不到它会影响到那么多国人。<
    /><
    />地址><
    /><
    />>4.简单的礼物(Simple Gifts)><
    />美国VOA广播电台(美国之音)的SPECIAL ENGLISH(慢速音乐)节目的背景音乐吗?太熟悉了,只不过电台版的速度要比这个快一些。<
    /><
    />地址><
    /><
    />>5.雪的梦幻(Snowdreams)><
    /><
    />这首《雪的梦幻》(Snowdreams)出自班德瑞的春野这张专辑。相当经典的纯音乐,被电台和电视台使用的次数已经无法统计,常在一些情感类(尤其爱情,有一点淡淡的哀伤)的播讲中充当背景音乐。<
    /><
    />地址><
    /><
    />>6.童年(Childhood Memory)><
    /><
    />这 首《童年》(Childhood Memory)出自班德瑞的《日光海岸》这张专辑。确实曲如其名,让人回想起过去的时光,听了有种想哭的冲动……Long笛与黑管永远是管乐重梦幻组合, 叠轻柔的钢琴上,顺记忆穿针引线,副歌中穿插一段凝人和声,是整首曲子接在主题后经营出来的高潮,刚巧呼应着全程串场风铃声,两者在编曲中分工架起,迷雾 般的帷幕,带人回溯到孩提时代那段年幼无助但却也无忧无虑的时刻。可惜的是,像这样的甜蜜回忆,一旦成人,能分享的人也所剩无多了。<
    /><
    />地址><
    /><
    />>7.宋家王朝(THE SOONG SISTER)><
    /><
    />这首《宋家王朝》出自扶桑作曲大师喜多郎之手,个人感觉既恢弘又凄婉,港台的电视剧多爱用此背景音乐,比如李若彤版《神雕侠侣》。<
    /><
    />地址><
    /><
    />>8.你的笑颜(Your Smile)><
    /><
    />这首曲子出自班德瑞的《仙境》这张专辑,似乎常被用电台作为午夜节目的背景音乐。相对于其它几首情感节目的背景音乐,这首曲子并不显得特别悲伤和哀婉,但是一样会触到你的神经。<
    /><
    />地址><
    /><
    />>9.春野(ONE DAY IN SPRING)><
    /><
    />《春野》(ONE DAY IN SPRING)出自班德瑞的专辑《春野》,常被用作节目开头的曲子或是新闻、起床曲的开头背景音乐。是一首非常舒缓柔美的经典乐。<
    /><
    />地址><
    /><
    />>10.安妮的仙境(Annie’’s Wonderland)><
    /><
    />《安妮的仙境》(Annies Wonderland)出自班德瑞的《仙境》这张专辑,柔美而有力度,适合朗诵配乐。<
    /><
    />地址><
    /><
    />>11.凤凰卫视《天气预报》主题曲><
    />这首《和兰花在一起》(With an orchid)出自Yanni(雅尼)的专辑《If I Could Tell You》,被凤凰卫视用于天气预报的背景音乐。<
    />飘逸的曲子<
    />清清淡淡<
    />如行云流水般的音律<
    />洁净而从容<
    />在遥远的夢幻里流连<
    />寻找渴望已久的宁静<
    />魂牵梦绕的风景<
    />在尘间<
    />寂静的夜里<
    />停留在此刻<
    /><
    />地址><
    /><
    />>12.神秘园之歌(Song From A Secret Garden)><
    /><
    />这 首《神秘园之歌》出自神秘园的第一张专辑《SONGS FROM A SECRETGARDEN》。据说这首歌伤感能杀死人,让人象迷失在神秘的丛林里,黑暗的看不到天,找不到出口,沉溺在这样的黑暗中,象一个人孤独的坐在 路边大树的阴影下,静静看路上人来人往,别人看不到你你也不想被看到,因为你害怕,因为你孤独,你知道这不是什么好事,但只有这样,受伤的心才能感到一丝 安全,这样的孤独一直在诱惑你……<
    /><
    />地址><
    /><
    />>13.Windancer><
    /><
    />这首曲子出自神秘园的专辑《White
  • <
    />>文章来源:http//www.ebailu.com/PageDigest.aspid=47><
    />
    ><
    />>1.豪勇七蛟龙(The Magnificent Seven)><
    />大型颁奖晚会最喜欢用的背景音乐,地球人都知道。伯恩斯坦作曲。<
    /><
    />地址><
    /><
    />>2.故乡的原风景><
    />《神雕侠侣》多次引用,哀伤感人。出自扶桑作曲家宗次郎1991年的专辑《木道》。<
    /><
    />地址><
    /><
    />>3.CCTV《天气预报》主题曲><
    />据说是迄今为止中间电视台唯一没有改变过的背景音乐,《天气预报》一直使用它。《渔舟唱晚》(即天气预报背景音乐),是当年在上海颇有名气的电子琴 演奏家浦琪璋根据同名民族乐曲改编演奏的。她原来是上海乐团的独奏演员是从“小荧星”艺术团毕业的在艺术上颇有成就,曾与上海轻音乐乐团合作过许多脍 炙人口的曲子,如:《幸福的傣乡》等等。音乐界的屠巴海经常与她合作。这首曲子完成后浦琪璋便退出乐界。但此曲却因为被黄金档节目央视的《天气预报》采 用为背景音乐而受到了广大中国人民的喜爱。当年浦琪璋用“雅马哈”三排键盘的音乐会电子琴改编演奏这首曲子时,也没有想到此曲会成为黄金时段节目的黄金背 景音乐,更想不到它会影响到那么多国人。<
    /><
    />地址><
    /><
    />>4.简单的礼物(Simple Gifts)><
    />美国VOA广播电台(美国之音)的SPECIAL ENGLISH(慢速音乐)节目的背景音乐吗?太熟悉了,只不过电台版的速度要比这个快一些。<
    /><
    />地址><
    /><
    />>5.雪的梦幻(Snowdreams)><
    /><
    />这首《雪的梦幻》(Snowdreams)出自班德瑞的春野这张专辑。相当经典的纯音乐,被电台和电视台使用的次数已经无法统计,常在一些情感类(尤其爱情,有一点淡淡的哀伤)的播讲中充当背景音乐。<
    /><
    />地址><
    /><
    />>6.童年(Childhood Memory)><
    /><
    />这 首《童年》(Childhood Memory)出自班德瑞的《日光海岸》这张专辑。确实曲如其名,让人回想起过去的时光,听了有种想哭的冲动……Long笛与黑管永远是管乐重梦幻组合, 叠轻柔的钢琴上,顺记忆穿针引线,副歌中穿插一段凝人和声,是整首曲子接在主题后经营出来的高潮,刚巧呼应着全程串场风铃声,两者在编曲中分工架起,迷雾 般的帷幕,带人回溯到孩提时代那段年幼无助但却也无忧无虑的时刻。可惜的是,像这样的甜蜜回忆,一旦成人,能分享的人也所剩无多了。<
    /><
    />地址><
    /><
    />>7.宋家王朝(THE SOONG SISTER)><
    /><
    />这首《宋家王朝》出自扶桑作曲大师喜多郎之手,个人感觉既恢弘又凄婉,港台的电视剧多爱用此背景音乐,比如李若彤版《神雕侠侣》。<
    /><
    />地址><
    /><
    />>8.你的笑颜(Your Smile)><
    /><
    />这首曲子出自班德瑞的《仙境》这张专辑,似乎常被用电台作为午夜节目的背景音乐。相对于其它几首情感节目的背景音乐,这首曲子并不显得特别悲伤和哀婉,但是一样会触到你的神经。<
    /><
    />地址><
    /><
    />>9.春野(ONE DAY IN SPRING)><
    /><
    />《春野》(ONE DAY IN SPRING)出自班德瑞的专辑《春野》,常被用作节目开头的曲子或是新闻、起床曲的开头背景音乐。是一首非常舒缓柔美的经典乐。<
    /><
    />地址><
    /><
    />>10.安妮的仙境(Annie’’s Wonderland)><
    /><
    />《安妮的仙境》(Annies Wonderland)出自班德瑞的《仙境》这张专辑,柔美而有力度,适合朗诵配乐。<
    /><
    />地址><
    /><
    />>11.凤凰卫视《天气预报》主题曲><
    />这首《和兰花在一起》(With an orchid)出自Yanni(雅尼)的专辑《If I Could Tell You》,被凤凰卫视用于天气预报的背景音乐。<
    />飘逸的曲子<
    />清清淡淡<
    />如行云流水般的音律<
    />洁净而从容<
    />在遥远的夢幻里流连<
    />寻找渴望已久的宁静<
    />魂牵梦绕的风景<
    />在尘间<
    />寂静的夜里<
    />停留在此刻<
    /><
    />地址><
    /><
    />>12.神秘园之歌(Song From A Secret Garden)><
    /><
    />这 首《神秘园之歌》出自神秘园的第一张专辑《SONGS FROM A SECRETGARDEN》。据说这首歌伤感能杀死人,让人象迷失在神秘的丛林里,黑暗的看不到天,找不到出口,沉溺在这样的黑暗中,象一个人孤独的坐在 路边大树的阴影下,静静看路上人来人往,别人看不到你你也不想被看到,因为你害怕,因为你孤独,你知道这不是什么好事,但只有这样,受伤的心才能感到一丝 安全,这样的孤独一直在诱惑你……<
    /><
    />地址><
    /><
    />>13.Windancer><
    /><
    />这首曲子出自神秘园的专辑《White
  • 函数指针代码

    2007-10-01

    2.<
    ><
    >CODEincludelt
  • 函数指针代码

    2007-10-01

    2.<
    ><
    >CODEincludelt
  • Linux 启动

    2007-09-29

    本文将描述了从开机到登录的 Linux 启动全过程。<
    >(1) 从BIOS到内核<
    ><
    >BIOS自检<
    ><
    >计算机在接通电源之后首先由BIOS进行自检,即进行所谓的POST(Power On Self <
    >Test),然后依据BIOS内设置的引导顺序从硬盘、软盘或CDROM中读入“引导块”。 在 PC 中,引导 Linux 是从 BIOS 中的地址 0xFFFF0 处开始的。BIOS 的第一个步骤是加电自检(POST)。POST 的工作是对硬件进行检测。BIOS 的第二个步骤是进行本地设备的枚举和初始化。给定 BIOS 功能的不同用法之后,BIOS 由两部分组成:POST 代码和运行时服务。当 POST 完成之后,它被从内存中清理了出来,但是 BIOS 运行时服务依然保留在内存中,目标操作系统可以使用这些服务。 <
    ><
    >要引导一个操作系统,BIOS 运行时会按照 CMOS 的设置定义的顺序来搜索处于活动状态并且可以引导的设备。引导设备可以是软盘、CD-ROM、硬盘上的某个分区、网络上的某个设备,甚至是 USB 闪存。通常,Linux 都是从硬盘上引导的,其中主引导记录(M<
    />)中包含主引导加载程序。M<
    /> 是一个 512 字节大小的扇区,位于磁盘上的第一个扇区中(0 道 0 柱面 1 扇区)。当 M<
    /> 被加载到 RAM 中之后,BIOS 就会将控制权交给 M<
    />。<
    ><
    >提取 M<
    /> 的信息<
    ><
    >要查看 M<
    /> 的内容,请使用下面的命令:<
    ><
    > dd if=/dev/hda of=m
    .bin bs=512 count=1 od -xa m
    .bin <
    ><
    >这个 dd 命令需要以 root 用户的身份运行,它从 /dev/hda(第一个 IDE 盘) 上读取前 512 个字节的内容,并将其写入 m
    .bin 文件中。od 命令会以十六进制和 ASCII 码格式打印这个二进制文件的内容。 (2)启动GRUB/LILO<
    ><
    >GRUB和LILO都是引导加载程序。最简单地讲,引导加载程序(boot loader) 会引导操作系统。当机器引导它的操作系统时,BIOS 会读取引导介质上最前面的 512 字节(即人们所知的 主引导记录(master boot record,M<
    />))。在单一的 M<
    /> 中只能存储一个操作系统的引导记录,所以当需要多个操作系统时就会出现问题。所以需要更灵活的引导加载程序。<
    ><
    >GRUB 与 LILO 的比较<
    ><
    >如本文开始处所述,所有引导加载程序都以类似的方式工作,满足共同的目的。不过,LILO 和 GRUB 之间有很多不同之处:<
    ><
    >LILO 没有交互式命令界面,而 GRUB 拥有。 <
    >LILO 不支持网络引导,而 GRUB 支持。 <
    >LILO 将关于可以引导的操作系统位置的信息物理上存储在 M<
    /> 中。如果修改了 LILO 配置文件,必须将 LILO 第一阶段引导加载程序重写到 M<
    />。相对于 GRUB,这是一个更为危险的选择,因为错误配置的 M<
    /> 可能会让系统无法引导。使用 GRUB,如果配置文件配置错误,则只是默认转到 GRUB 命令行界面。<
    >安全提示:<
    ><
    >关于安全性,任何可以接触到引导磁盘/CD 的人,只需要使用没有设置安全性的 grub.conf 或 lilo.conf,就可以绕过本文中提及的所有安全措施。特别是使用 GRUB 时,因为能够引导到单用户模式,所以是一个严重的安全漏洞。解决此问题的一个简单方法是在机器的 BIOS 中禁止通过 CD 和软盘进行引导,并确保为 BIOS 设置了一个口令,使得其他人不能修改这些设置。 (3)加载内核<
    ><
    >当内核映像被加载到内存之后,内核阶段就开始了。内核映像并不是一个可执行的内核,而是一个压缩过的内核映像。通常它是一个 zImage(压缩映像,小于 512KB)或一个 bzImage(较大的压缩映像,大于 512KB),它是提前使用 zlib 进行压缩过的。在这个内核映像前面是一个例程,它实现少量硬件设置,并对内核映像中包含的内核进行解压,然后将其放入高端内存中,如果有初始 RAM 磁盘映像,就会将它移动到内存中,并标明以后使用。然后该例程会调用内核,并开始启动内核引导的过程。<
    ><
    >GRUB 中的手工引导<
    ><
    >在 GRUB 命令行中,我们可以使用 initrd 映像引导一个特定的内核,方法如下:<
    ><
    >grubgt kernel /bzImage-2.6.14.2<
    >Linux-bzImage setup=0x1400 size=0x29672e<
    ><
    >grubgt initrd /initrd-2.6.14.2.img<
    >Linux-initrd 0x5f13000 0xcc199 bytes<
    ><
    >grubgt boot<
    ><
    >Uncompressing Linux... Ok booting the kernel.<
    >如果您不知道要引导的内核的名称,只需使用斜线(/)然后按下 Tab 键即可。GRUB 会显示内核和 initrd 映像列表。 (4)执行init进程<
    ><
    >init进程是系统所有进程的起点,内核在完成核内引导以后,即在本线程(进程)空间内加载init程序,它的进程号是1。init进程是所有进程的发起者和控制者。因为在任何基于Unix的系统(比如Linux)中,它都是第一个运行的进程,所以init进程的编号(Process ID,PID)永远是1。如果init出现了问题,系统的其余部分也就随之而垮掉了。<
    ><
    >init进程有两个作用。第一个作用是扮演终结父进程的角色。因为init进程永远不会被终止,所以系统总是可以确信它的存在,并在必要的时候以它为参照。如果某个进程在它衍生出来的全部子进程结束之前被终止,就会出现必须以init为参照的情况。此时那些失去了父进程的子进程就都会以init作为它们的父进程。快速执行一下ps -af 命令,可以列出许多父进程ID(Parent Process ID,PPID)为1的进程来。<
    ><
    >init的第二个角色是在进入某个特定的运行级别(Runlevel)时运行相应的程序,以此对各种运行级别进行管理。它的这个作用是由/etc/inittab文件定义的。 (5)通过/etc/inittab文件进行初始化<
    ><
    >init的工作是根据/etc/inittab来执行相应的脚本进行系统初始化,如设置键盘、字体, 装载模块,设置网络,等等。<
    ><
    >对于RedhatLinux来说,执行的顺序为: <
    ><
    >/etc/rc.d/rc.sysinit 由init执行的第一个脚本 <
    >/etc/rc.d/rc.sysinit主要做在各个运行模式中相同的初始化工作,包括: <
    >设置初始的PATH变量。<
    >配置网络。<
    >为虚拟内存启动交换。<
    >设置系统的主机名。<
    >检查root文件系统,以进行必要的修复。<
    >检查root文件系统的配额。<
    >为root文件系统打开用户和组的配额。<
    >以读/写的方式重新装载root文件系统。<
    >清除被装载的文件系统表/etc/mtab。<
    >把root文件系统输入到mtab。<
    >使系统为装入模块做准备。<
    >查找模块的相关文件。<
    >检查文件系统,以进行必要的修复。<
    >加载所有其他文件系统。<
    >清除几个/etc文件:/etc/mtab、/etc/fastboot和/etc/nologin。<
    >删除UUCP的lock文件。<
    >删除过时的子系统文件。<
    >删除过时的pid文件。<
    >设置系统时钟。<
    >打开交换。<
    >初始化串行端口。<
    >装入模块。<
    ><
    >/etc/rc.d/rcX.d/KS<
    ><
    >首先终止“K”开头的服务,然后启动“S”开头的服务。<
    ><
    >对每一个运行级别来说,在/etc/rc.d子目录中都有一个对应的下级目录。这些运行级别的下级子目录的命名方法是rcX.d,其中的X就是代表运行级别的数字。比如说,运行级别3的全部命令脚本程序都保存在/etc/rc.d/rc3.d子目录中。在各个运行级别的子目录中,都建立有到/etc/rc.d/init.d子目录中命令脚本程序的符号链接,但是,这些符号链接并不使用命令脚本程序在 /etc/rc.d/init.d子目录中原来的名字。如果命令脚本程序是用来启动一个服务的,其符号链接的名字就以字母S打头;如果命令脚本程序是用来关闭一个服务的,其符号链接的名字就以字母K打头。许多情况下,这些命令脚本程序的执行顺序都很重要。如果没有先配置网络接口,就没有办法使用DNS服务解析主机名!为了安排它们的执行顺序,在字母S或者 K的后面紧跟着一个两位数字,数值小的在数值大的前面执行。比如:/etc/rc.d/rc3.d/S50inet就会在 /etc/rc.d/rc3.d/S55named之前执行。存放在/etc/rc.d/init.d子目录中的、被符号链接上的命令脚本程序是真正的实干家,是它们完成了启动或者停止各种服务的操作过程。当 /etc/rc.d/rc运行通过每个特定的运行级别子目录的时候,它会根据数字的顺序依次调用各个命令脚本程序执行。它先运行以字母K打头的命令脚本程序,然后再运行以字母S打头的命令脚本程序。对以字母K打头的命令脚本程序来说,会传递St参数;类似地对以字母S打头的命令脚本程序来说,会传递 Start参数。 <
    ><
    >执行/etc/ec.d/rc.local<
    >Redhat Linux中的运行模式2、3、5都把/etc/rc.d/rc.local做为初始化脚本中的最后一个,所以用户可以自己在这个文件中添加一些需要在其他初始化工作之后,登录之前执行的命令。在维护Linux系统运转的日子里,肯定会遇到需要系统管理员对开机或者关机命令脚本进行修改的情况。如果所做的修改只在引导开机的时候起作用,并且改动不大的话,可以考虑简单地编辑一下/etc/rc.d/rc.local脚本。这个命令脚本程序是在引导过程的最后一步被执行的。<
    ><
    >执行 /bin/login 程式 <
    ><
    >login 程序会提示使用者需输入账号及密码 接着编码并确认密码的正确性 若二者相合 则为使用者进行初始化环境 并将控制权交给 shell,即等待用户登录。<
    >多次为止Linux启动过程全部结束。<
    ><
    >最后使用图解释全部过程。<
    ><
    > <
    ><
    >总结:与 Linux 本身非常类似,Linux 的启动引导过程也非常灵活,可以支持众多的处理器和硬件平台。LILO 引导加载程序对引导能力进行了扩充,但是它却缺少文件系统的感知能力。最新一代的引导加载程序,例如 GRUB将更加灵活
  • Linux 启动

    2007-09-29

    本文将描述了从开机到登录的 Linux 启动全过程。<
    >(1) 从BIOS到内核<
    ><
    >BIOS自检<
    ><
    >计算机在接通电源之后首先由BIOS进行自检,即进行所谓的POST(Power On Self <
    >Test),然后依据BIOS内设置的引导顺序从硬盘、软盘或CDROM中读入“引导块”。 在 PC 中,引导 Linux 是从 BIOS 中的地址 0xFFFF0 处开始的。BIOS 的第一个步骤是加电自检(POST)。POST 的工作是对硬件进行检测。BIOS 的第二个步骤是进行本地设备的枚举和初始化。给定 BIOS 功能的不同用法之后,BIOS 由两部分组成:POST 代码和运行时服务。当 POST 完成之后,它被从内存中清理了出来,但是 BIOS 运行时服务依然保留在内存中,目标操作系统可以使用这些服务。 <
    ><
    >要引导一个操作系统,BIOS 运行时会按照 CMOS 的设置定义的顺序来搜索处于活动状态并且可以引导的设备。引导设备可以是软盘、CD-ROM、硬盘上的某个分区、网络上的某个设备,甚至是 USB 闪存。通常,Linux 都是从硬盘上引导的,其中主引导记录(M<
    />)中包含主引导加载程序。M<
    /> 是一个 512 字节大小的扇区,位于磁盘上的第一个扇区中(0 道 0 柱面 1 扇区)。当 M<
    /> 被加载到 RAM 中之后,BIOS 就会将控制权交给 M<
    />。<
    ><
    >提取 M<
    /> 的信息<
    ><
    >要查看 M<
    /> 的内容,请使用下面的命令:<
    ><
    > dd if=/dev/hda of=m
    .bin bs=512 count=1 od -xa m
    .bin <
    ><
    >这个 dd 命令需要以 root 用户的身份运行,它从 /dev/hda(第一个 IDE 盘) 上读取前 512 个字节的内容,并将其写入 m
    .bin 文件中。od 命令会以十六进制和 ASCII 码格式打印这个二进制文件的内容。 (2)启动GRUB/LILO<
    ><
    >GRUB和LILO都是引导加载程序。最简单地讲,引导加载程序(boot loader) 会引导操作系统。当机器引导它的操作系统时,BIOS 会读取引导介质上最前面的 512 字节(即人们所知的 主引导记录(master boot record,M<
    />))。在单一的 M<
    /> 中只能存储一个操作系统的引导记录,所以当需要多个操作系统时就会出现问题。所以需要更灵活的引导加载程序。<
    ><
    >GRUB 与 LILO 的比较<
    ><
    >如本文开始处所述,所有引导加载程序都以类似的方式工作,满足共同的目的。不过,LILO 和 GRUB 之间有很多不同之处:<
    ><
    >LILO 没有交互式命令界面,而 GRUB 拥有。 <
    >LILO 不支持网络引导,而 GRUB 支持。 <
    >LILO 将关于可以引导的操作系统位置的信息物理上存储在 M<
    /> 中。如果修改了 LILO 配置文件,必须将 LILO 第一阶段引导加载程序重写到 M<
    />。相对于 GRUB,这是一个更为危险的选择,因为错误配置的 M<
    /> 可能会让系统无法引导。使用 GRUB,如果配置文件配置错误,则只是默认转到 GRUB 命令行界面。<
    >安全提示:<
    ><
    >关于安全性,任何可以接触到引导磁盘/CD 的人,只需要使用没有设置安全性的 grub.conf 或 lilo.conf,就可以绕过本文中提及的所有安全措施。特别是使用 GRUB 时,因为能够引导到单用户模式,所以是一个严重的安全漏洞。解决此问题的一个简单方法是在机器的 BIOS 中禁止通过 CD 和软盘进行引导,并确保为 BIOS 设置了一个口令,使得其他人不能修改这些设置。 (3)加载内核<
    ><
    >当内核映像被加载到内存之后,内核阶段就开始了。内核映像并不是一个可执行的内核,而是一个压缩过的内核映像。通常它是一个 zImage(压缩映像,小于 512KB)或一个 bzImage(较大的压缩映像,大于 512KB),它是提前使用 zlib 进行压缩过的。在这个内核映像前面是一个例程,它实现少量硬件设置,并对内核映像中包含的内核进行解压,然后将其放入高端内存中,如果有初始 RAM 磁盘映像,就会将它移动到内存中,并标明以后使用。然后该例程会调用内核,并开始启动内核引导的过程。<
    ><
    >GRUB 中的手工引导<
    ><
    >在 GRUB 命令行中,我们可以使用 initrd 映像引导一个特定的内核,方法如下:<
    ><
    >grubgt kernel /bzImage-2.6.14.2<
    >Linux-bzImage setup=0x1400 size=0x29672e<
    ><
    >grubgt initrd /initrd-2.6.14.2.img<
    >Linux-initrd 0x5f13000 0xcc199 bytes<
    ><
    >grubgt boot<
    ><
    >Uncompressing Linux... Ok booting the kernel.<
    >如果您不知道要引导的内核的名称,只需使用斜线(/)然后按下 Tab 键即可。GRUB 会显示内核和 initrd 映像列表。 (4)执行init进程<
    ><
    >init进程是系统所有进程的起点,内核在完成核内引导以后,即在本线程(进程)空间内加载init程序,它的进程号是1。init进程是所有进程的发起者和控制者。因为在任何基于Unix的系统(比如Linux)中,它都是第一个运行的进程,所以init进程的编号(Process ID,PID)永远是1。如果init出现了问题,系统的其余部分也就随之而垮掉了。<
    ><
    >init进程有两个作用。第一个作用是扮演终结父进程的角色。因为init进程永远不会被终止,所以系统总是可以确信它的存在,并在必要的时候以它为参照。如果某个进程在它衍生出来的全部子进程结束之前被终止,就会出现必须以init为参照的情况。此时那些失去了父进程的子进程就都会以init作为它们的父进程。快速执行一下ps -af 命令,可以列出许多父进程ID(Parent Process ID,PPID)为1的进程来。<
    ><
    >init的第二个角色是在进入某个特定的运行级别(Runlevel)时运行相应的程序,以此对各种运行级别进行管理。它的这个作用是由/etc/inittab文件定义的。 (5)通过/etc/inittab文件进行初始化<
    ><
    >init的工作是根据/etc/inittab来执行相应的脚本进行系统初始化,如设置键盘、字体, 装载模块,设置网络,等等。<
    ><
    >对于RedhatLinux来说,执行的顺序为: <
    ><
    >/etc/rc.d/rc.sysinit 由init执行的第一个脚本 <
    >/etc/rc.d/rc.sysinit主要做在各个运行模式中相同的初始化工作,包括: <
    >设置初始的PATH变量。<
    >配置网络。<
    >为虚拟内存启动交换。<
    >设置系统的主机名。<
    >检查root文件系统,以进行必要的修复。<
    >检查root文件系统的配额。<
    >为root文件系统打开用户和组的配额。<
    >以读/写的方式重新装载root文件系统。<
    >清除被装载的文件系统表/etc/mtab。<
    >把root文件系统输入到mtab。<
    >使系统为装入模块做准备。<
    >查找模块的相关文件。<
    >检查文件系统,以进行必要的修复。<
    >加载所有其他文件系统。<
    >清除几个/etc文件:/etc/mtab、/etc/fastboot和/etc/nologin。<
    >删除UUCP的lock文件。<
    >删除过时的子系统文件。<
    >删除过时的pid文件。<
    >设置系统时钟。<
    >打开交换。<
    >初始化串行端口。<
    >装入模块。<
    ><
    >/etc/rc.d/rcX.d/KS<
    ><
    >首先终止“K”开头的服务,然后启动“S”开头的服务。<
    ><
    >对每一个运行级别来说,在/etc/rc.d子目录中都有一个对应的下级目录。这些运行级别的下级子目录的命名方法是rcX.d,其中的X就是代表运行级别的数字。比如说,运行级别3的全部命令脚本程序都保存在/etc/rc.d/rc3.d子目录中。在各个运行级别的子目录中,都建立有到/etc/rc.d/init.d子目录中命令脚本程序的符号链接,但是,这些符号链接并不使用命令脚本程序在 /etc/rc.d/init.d子目录中原来的名字。如果命令脚本程序是用来启动一个服务的,其符号链接的名字就以字母S打头;如果命令脚本程序是用来关闭一个服务的,其符号链接的名字就以字母K打头。许多情况下,这些命令脚本程序的执行顺序都很重要。如果没有先配置网络接口,就没有办法使用DNS服务解析主机名!为了安排它们的执行顺序,在字母S或者 K的后面紧跟着一个两位数字,数值小的在数值大的前面执行。比如:/etc/rc.d/rc3.d/S50inet就会在 /etc/rc.d/rc3.d/S55named之前执行。存放在/etc/rc.d/init.d子目录中的、被符号链接上的命令脚本程序是真正的实干家,是它们完成了启动或者停止各种服务的操作过程。当 /etc/rc.d/rc运行通过每个特定的运行级别子目录的时候,它会根据数字的顺序依次调用各个命令脚本程序执行。它先运行以字母K打头的命令脚本程序,然后再运行以字母S打头的命令脚本程序。对以字母K打头的命令脚本程序来说,会传递St参数;类似地对以字母S打头的命令脚本程序来说,会传递 Start参数。 <
    ><
    >执行/etc/ec.d/rc.local<
    >Redhat Linux中的运行模式2、3、5都把/etc/rc.d/rc.local做为初始化脚本中的最后一个,所以用户可以自己在这个文件中添加一些需要在其他初始化工作之后,登录之前执行的命令。在维护Linux系统运转的日子里,肯定会遇到需要系统管理员对开机或者关机命令脚本进行修改的情况。如果所做的修改只在引导开机的时候起作用,并且改动不大的话,可以考虑简单地编辑一下/etc/rc.d/rc.local脚本。这个命令脚本程序是在引导过程的最后一步被执行的。<
    ><
    >执行 /bin/login 程式 <
    ><
    >login 程序会提示使用者需输入账号及密码 接着编码并确认密码的正确性 若二者相合 则为使用者进行初始化环境 并将控制权交给 shell,即等待用户登录。<
    >多次为止Linux启动过程全部结束。<
    ><
    >最后使用图解释全部过程。<
    ><
    > <
    ><
    >总结:与 Linux 本身非常类似,Linux 的启动引导过程也非常灵活,可以支持众多的处理器和硬件平台。LILO 引导加载程序对引导能力进行了扩充,但是它却缺少文件系统的感知能力。最新一代的引导加载程序,例如 GRUB将更加灵活
  • 什么是交叉编译(转)<
    ><
    > 什么是交叉编译<
    >作者: 来源:zz 发表时间:2006-06-12 浏览次数: 58 字号:大 中 小<
    ><
    >OpenARM-CrossCompile-HOWTO<
    ><
    >一、版权说明<
    >目的:<
    >本版权声明的目的是希望保证任何人能够真实、有效地享有使用本版权说明所约束的<
    >文档的充分自由;同时,保护文档原作者的权利,不仅使他们得到应得的荣誉,而且<
    >保证原作者不对因其他人的改动而导致的后果负任何责任。<
    ><
    >声明:<
    >a、除非另外声明,文档的版权是属于其作者的。本计划所有文档作者保留所有权利。<
    ><
    >b、由本计划所生成的自由文档允许每个人自由的使用(包括对本文档进行各种商业性<
    >的或是非商业性的复制和再分发),而无须向 ENARM 计划或作者支付任何费用,条<
    >件是本版权声明出现在所有的副本中,并且,自由文档版权声明神圣不可侵犯,使用者<
    >不得对声明中的任何条款作任何形式的修改,也不得附加任何其它的条件。<
    ><
    >c、允许任何人在遵循本版权声明的前提下对自由文档进行修订后再复制和发布。如果<
    >您修订了自由文档或者发布了修订版本的副本,您就不再是一般意义上的使用者而成为<
    >修订人,这些规定就转化为你的责任。修订人应遵循如下规定:<
    ><
    >1。修订版应含有与上一版本具有相同内容的自由文档版权声明,但就其所修改的部分,<
    >修订者拥有其自己的版权。<
    >2。保护原作者以及前期版本的修订者的署名权。修订者应在修订版本中保留原作者以及<
    >前期修订者的姓名及联系方式<
    >3。专门建立并保留一部分内容名为“历史纪录”,针对每一版本的修订,记录该版本<
    >的作者,修订时间,联系方式及发布者等相关信息<
    >4。修订者有告诉读者如何获得上一版本文档的义务,如果是在网站上发布,应做出指<
    >向上一版本文档位置的链接<
    ><
    >d、任何人可以引用本文档中的一部分内容,或是将一部分本文档内容与其他文档内容<
    >混合在一起使用,条件是必须在引用的文档部分中加入本版权声明。对上述行为可依<
    >照 c 项规定办理<
    ><
    >e、转译。如果需要根据本计划中文档转译为其它种语言,此种行为将被视同为对修订<
    >版本的再次修订,可依照 c 项规定办理。<
    ><
    >f、在任何情况下,原作者不承担因使用本文档而导致的对任何当事人所造成的直接的,<
    >间接的,特殊的,附加的或者相伴而生的损坏,包括利益损失的责任,即使原作者在文<
    >档中已经建议了这些损失的可能性时也是如此。<
    ><
    ><
    ><
    ><
    ><
    >二、什么是交叉编译<
    >什么是交叉编译呢,简单地说,就是在一个平台上生成另一个平台上的可执行代码。这里需要注意的是所谓<
    >平台,实际上包含两个概念:体系结构(Architecture)、操作系统(Operating System)。同一个体系结<
    >构可以运行不同的操作系统;同样,同一个操作系统也可以在不同的体系结构上运行。举例来说,我们常说<
    >的x86 Linux平台实际上是Intel x86体系结构和Linux for x86操作系统的统称;而x86 WinNT平台实际上是<
    >Intel x86体系结构和Windows NT for x86操作系统的简称。<
    ><
    >一个经常会被问到的问题就是,“既然我们已经有了主机编译器,那为什么还要交叉编译呢?”其实答案很简<
    >单,没办法啊!有时是因为目的平台上不允许或不能够安装我们所需要的编译器,而我们又需要这个编译器<
    >的某些特征;有时是因为目的平台上的资源贫乏,无法运行我们所需要编译器;有时又是因为目的平台还没<
    >有建立,连操作系统都没有,根本谈不上运行什么编译器。<
    ><
    >另一个经常会被问到的问题就是:“既然可以交叉编译,那还要主机编译干吗?”其实答案也很简单,交叉编<
    >译是不得已而为之!与主机编译相比,交叉编译受的限制更多,虽然在理论上我们可以做任何形式的交叉编<
    >译,但事实上,由于受到专利、版权、技术的限制,并不总是能够进行交叉编译,尤其是在业余条件下!举<
    >例来说,我们至今无法生成惠普公司专有的som格式的可执行文件,因此我们根本无法做目的平台为<
    >HPPA-HPUX的交叉编译。<
    ><
    >就我们这个项目而言,需要交叉编译的原因有两个:首先,在项目的起始阶段,目的平台尚未建立,因此需<
    >要做交叉编译,以生成我们所需要的bootloader(启动引导代码)以及操作系统核心;其次,当目的平台能<
    >启动之后,由于目的平台上资源的限制,当我们编译大型程序时,依然可能需要用到交叉编译。<
    ><
    ><
    ><
    >三、交叉编译的基础知识<
    >在做实际工作之前,我想我们应该先掌握一些关于交叉编译的基本知识,其实说白了也就是理解一些我们经<
    >常会碰到的英文单词;)<
    ><
    >host 主机平台。<
    >target 目的平台。<
    >perfix 交叉编译器的安装位置。<
    >xxx-xxxx-xxxxx 平台描述。<
    ><
    >我们在主机平台上开发程序,并在这个平台上运行交叉编译器,编译我们的程序;而由交叉编译器生<
    >成的程序将在目的平台上运行。这里值得说明得是平台描述,象arm-linux、i386-pc-linux2.4.3这样的字<
    >符串我们经常会看到,其实它是用来描述平台的,它有完整格式、缩减格式和别名之分。完整格式是:<
    >CPU-制造厂商-操作系统,如sparc-sun-sunos4.1.4,说明平台所使用的CPU是sparc,制造厂商是sun,上面<
    >运行的操作系统是SunOS,版本是4.1.4。当然,我们都不愿记这么长的东西,因此可以使用短格式,短格式<
    >中有选择地去处了制造厂商、软件版本等信息,因此我们同样可以用sparc-sunos或sparc-sunos-sunos4来<
    >描述这个平台。如果觉得这个还是太麻烦,那就可以使用别名,sun4m就可以很简单地描述这个平台。需要<
    >注意的是,并不是所有的平台都有别名,也不是所有的短格式都可以正确地描述平台。<
    ><
    ><
    ><
    >四、我需要准备些什么<
    >怎么说呢,你先得准备好主机平台,对我们这个项目来说,我们建议采用x86 Linux做主机平台,因为这样需<
    >要的设置工作最少。当然你也可以使用你所喜欢的平台或你所能得到的平台,其中的区别在于你可能必须做<
    >更多的设置工作,当然也有这种可能,就是你所选择的主机平台根本不能生成适用于目标平台的正确的交叉<
    >编译器。<
    ><
    >对于交叉编译器,可以自己生成,也可以从网上下载。区别在于从网上下载非常简单方便,但也许你找不到<
    >适合你所选择的平台的。而自己生成交叉编译器,有时会遇到很多挫折,但这的确是个有趣的值得怀念的经<
    >历。<
    ><
    >如果你想自己生成交叉编译器,那你必须先准备下面这些东西<
    ><
    >1、磁盘空间。至少要500M左右的空间,如果想一气呵成的话,那就要900M-1G的空间。<
    >2、各种源代码。你至少要准备binutils-2.11.2、gcc-2.95.3、linux-2.4.6、newlib-1.8.2或glibc-2.2.2<
    >的源代码。<
    ><
    >如果你所使用的主机平台不是运行的linux,那你还必须注意以下这些问题<
    ><
    >1、GNU bash必须是默认shell,所以你也许得把/bin/sh改成bash。<
    >2、你要确认已经安装了GNU bison,因为这些软经同样使用了bison扩展。<
    >3、GNU gmake最好是系统默认得make,因为这些软件都使用了gmake扩展,如果不是,在需要make时,记得<
    >使用gmake。<
    >4、如果你想生成交叉glibc,则GNU gsed必须是默认sed,因为glibc会用到gsed的扩展。<
    >5、如果你想生成交叉glibc,那还必须准备glibc-linuxthreads-2.2.2的源代码。<
    >6、确认正确的路径搜索顺序,最好让GNU软件首先被执行。<
    ><
    ><
    ><
    >五、怎样生成交叉编译器<
    >我们建议使用现成的脚本来生成交叉编译器,因为在配置交叉编译器时,会经常使用一些难以理解的开关项。<
    ><
    >待续。。。。。。<
    ><
    ><
    ><
    >六、参考文献<
    >http//www.objsw.com/CrossGCC/ 这是一个老牌的交叉编译FAQ,可惜最近已经很少更新了。<
    >http//crossgcc.billgatliff.com/ 这是一个新生的交叉编译FAQ,目前更新比较频繁。<
    >crossgccsourceware.cygnus.com 交叉编译器的邮件列表
  • 什么是交叉编译(转)<
    ><
    > 什么是交叉编译<
    >作者: 来源:zz 发表时间:2006-06-12 浏览次数: 58 字号:大 中 小<
    ><
    >OpenARM-CrossCompile-HOWTO<
    ><
    >一、版权说明<
    >目的:<
    >本版权声明的目的是希望保证任何人能够真实、有效地享有使用本版权说明所约束的<
    >文档的充分自由;同时,保护文档原作者的权利,不仅使他们得到应得的荣誉,而且<
    >保证原作者不对因其他人的改动而导致的后果负任何责任。<
    ><
    >声明:<
    >a、除非另外声明,文档的版权是属于其作者的。本计划所有文档作者保留所有权利。<
    ><
    >b、由本计划所生成的自由文档允许每个人自由的使用(包括对本文档进行各种商业性<
    >的或是非商业性的复制和再分发),而无须向 ENARM 计划或作者支付任何费用,条<
    >件是本版权声明出现在所有的副本中,并且,自由文档版权声明神圣不可侵犯,使用者<
    >不得对声明中的任何条款作任何形式的修改,也不得附加任何其它的条件。<
    ><
    >c、允许任何人在遵循本版权声明的前提下对自由文档进行修订后再复制和发布。如果<
    >您修订了自由文档或者发布了修订版本的副本,您就不再是一般意义上的使用者而成为<
    >修订人,这些规定就转化为你的责任。修订人应遵循如下规定:<
    ><
    >1。修订版应含有与上一版本具有相同内容的自由文档版权声明,但就其所修改的部分,<
    >修订者拥有其自己的版权。<
    >2。保护原作者以及前期版本的修订者的署名权。修订者应在修订版本中保留原作者以及<
    >前期修订者的姓名及联系方式<
    >3。专门建立并保留一部分内容名为“历史纪录”,针对每一版本的修订,记录该版本<
    >的作者,修订时间,联系方式及发布者等相关信息<
    >4。修订者有告诉读者如何获得上一版本文档的义务,如果是在网站上发布,应做出指<
    >向上一版本文档位置的链接<
    ><
    >d、任何人可以引用本文档中的一部分内容,或是将一部分本文档内容与其他文档内容<
    >混合在一起使用,条件是必须在引用的文档部分中加入本版权声明。对上述行为可依<
    >照 c 项规定办理<
    ><
    >e、转译。如果需要根据本计划中文档转译为其它种语言,此种行为将被视同为对修订<
    >版本的再次修订,可依照 c 项规定办理。<
    ><
    >f、在任何情况下,原作者不承担因使用本文档而导致的对任何当事人所造成的直接的,<
    >间接的,特殊的,附加的或者相伴而生的损坏,包括利益损失的责任,即使原作者在文<
    >档中已经建议了这些损失的可能性时也是如此。<
    ><
    ><
    ><
    ><
    ><
    >二、什么是交叉编译<
    >什么是交叉编译呢,简单地说,就是在一个平台上生成另一个平台上的可执行代码。这里需要注意的是所谓<
    >平台,实际上包含两个概念:体系结构(Architecture)、操作系统(Operating System)。同一个体系结<
    >构可以运行不同的操作系统;同样,同一个操作系统也可以在不同的体系结构上运行。举例来说,我们常说<
    >的x86 Linux平台实际上是Intel x86体系结构和Linux for x86操作系统的统称;而x86 WinNT平台实际上是<
    >Intel x86体系结构和Windows NT for x86操作系统的简称。<
    ><
    >一个经常会被问到的问题就是,“既然我们已经有了主机编译器,那为什么还要交叉编译呢?”其实答案很简<
    >单,没办法啊!有时是因为目的平台上不允许或不能够安装我们所需要的编译器,而我们又需要这个编译器<
    >的某些特征;有时是因为目的平台上的资源贫乏,无法运行我们所需要编译器;有时又是因为目的平台还没<
    >有建立,连操作系统都没有,根本谈不上运行什么编译器。<
    ><
    >另一个经常会被问到的问题就是:“既然可以交叉编译,那还要主机编译干吗?”其实答案也很简单,交叉编<
    >译是不得已而为之!与主机编译相比,交叉编译受的限制更多,虽然在理论上我们可以做任何形式的交叉编<
    >译,但事实上,由于受到专利、版权、技术的限制,并不总是能够进行交叉编译,尤其是在业余条件下!举<
    >例来说,我们至今无法生成惠普公司专有的som格式的可执行文件,因此我们根本无法做目的平台为<
    >HPPA-HPUX的交叉编译。<
    ><
    >就我们这个项目而言,需要交叉编译的原因有两个:首先,在项目的起始阶段,目的平台尚未建立,因此需<
    >要做交叉编译,以生成我们所需要的bootloader(启动引导代码)以及操作系统核心;其次,当目的平台能<
    >启动之后,由于目的平台上资源的限制,当我们编译大型程序时,依然可能需要用到交叉编译。<
    ><
    ><
    ><
    >三、交叉编译的基础知识<
    >在做实际工作之前,我想我们应该先掌握一些关于交叉编译的基本知识,其实说白了也就是理解一些我们经<
    >常会碰到的英文单词;)<
    ><
    >host 主机平台。<
    >target 目的平台。<
    >perfix 交叉编译器的安装位置。<
    >xxx-xxxx-xxxxx 平台描述。<
    ><
    >我们在主机平台上开发程序,并在这个平台上运行交叉编译器,编译我们的程序;而由交叉编译器生<
    >成的程序将在目的平台上运行。这里值得说明得是平台描述,象arm-linux、i386-pc-linux2.4.3这样的字<
    >符串我们经常会看到,其实它是用来描述平台的,它有完整格式、缩减格式和别名之分。完整格式是:<
    >CPU-制造厂商-操作系统,如sparc-sun-sunos4.1.4,说明平台所使用的CPU是sparc,制造厂商是sun,上面<
    >运行的操作系统是SunOS,版本是4.1.4。当然,我们都不愿记这么长的东西,因此可以使用短格式,短格式<
    >中有选择地去处了制造厂商、软件版本等信息,因此我们同样可以用sparc-sunos或sparc-sunos-sunos4来<
    >描述这个平台。如果觉得这个还是太麻烦,那就可以使用别名,sun4m就可以很简单地描述这个平台。需要<
    >注意的是,并不是所有的平台都有别名,也不是所有的短格式都可以正确地描述平台。<
    ><
    ><
    ><
    >四、我需要准备些什么<
    >怎么说呢,你先得准备好主机平台,对我们这个项目来说,我们建议采用x86 Linux做主机平台,因为这样需<
    >要的设置工作最少。当然你也可以使用你所喜欢的平台或你所能得到的平台,其中的区别在于你可能必须做<
    >更多的设置工作,当然也有这种可能,就是你所选择的主机平台根本不能生成适用于目标平台的正确的交叉<
    >编译器。<
    ><
    >对于交叉编译器,可以自己生成,也可以从网上下载。区别在于从网上下载非常简单方便,但也许你找不到<
    >适合你所选择的平台的。而自己生成交叉编译器,有时会遇到很多挫折,但这的确是个有趣的值得怀念的经<
    >历。<
    ><
    >如果你想自己生成交叉编译器,那你必须先准备下面这些东西<
    ><
    >1、磁盘空间。至少要500M左右的空间,如果想一气呵成的话,那就要900M-1G的空间。<
    >2、各种源代码。你至少要准备binutils-2.11.2、gcc-2.95.3、linux-2.4.6、newlib-1.8.2或glibc-2.2.2<
    >的源代码。<
    ><
    >如果你所使用的主机平台不是运行的linux,那你还必须注意以下这些问题<
    ><
    >1、GNU bash必须是默认shell,所以你也许得把/bin/sh改成bash。<
    >2、你要确认已经安装了GNU bison,因为这些软经同样使用了bison扩展。<
    >3、GNU gmake最好是系统默认得make,因为这些软件都使用了gmake扩展,如果不是,在需要make时,记得<
    >使用gmake。<
    >4、如果你想生成交叉glibc,则GNU gsed必须是默认sed,因为glibc会用到gsed的扩展。<
    >5、如果你想生成交叉glibc,那还必须准备glibc-linuxthreads-2.2.2的源代码。<
    >6、确认正确的路径搜索顺序,最好让GNU软件首先被执行。<
    ><
    ><
    ><
    >五、怎样生成交叉编译器<
    >我们建议使用现成的脚本来生成交叉编译器,因为在配置交叉编译器时,会经常使用一些难以理解的开关项。<
    ><
    >待续。。。。。。<
    ><
    ><
    ><
    >六、参考文献<
    >http//www.objsw.com/CrossGCC/ 这是一个老牌的交叉编译FAQ,可惜最近已经很少更新了。<
    >http//crossgcc.billgatliff.com/ 这是一个新生的交叉编译FAQ,目前更新比较频繁。<
    >crossgccsourceware.cygnus.com 交叉编译器的邮件列表
  • 10家招聘网站

    2007-09-28

    1、前程无忧 www.51job.com 2、中华英才网 www.chinahr.com 3、智联招聘 www.zhain.com 4、上海招聘网 www.shjob.cn 5、易才网 www.job1998.com 6、中国人才热线 www.cjol.com 7、我的工作网 www.myjob.com.cn 8、1010Job 精英招聘 www.1010job.com 9、南方人才网 www.job168.com 10、卓博人才网 www.jobcn.com