您的当前位置:首页嵌入式系统开发毕业论文

嵌入式系统开发毕业论文

2021-10-07 来源:乌哈旅游


第1章 绪论

1.1 嵌入式系统

1.1.1 嵌入式系统简介

随着计算机技术和微电子技术的迅速发展,嵌入式系统应用领域越来越广泛。当今,嵌入式系统已成为一个时髦的名词,就像当初的计算机热潮,似乎比当初的计算机热潮涉及的领域更广泛,应用技术人员更多,相关国民经济产值也更庞大。报纸、杂志、网络都把嵌入式系统当作讨论的热门话题。

嵌入式系统一般指非PC系统,有计算机功能但又不称之为计算机的设备或器材。它是以应用为中心,软硬件可裁减的,适应应用系统对功能、可靠性、成本、体积、功耗等综合性严格要求的专用计算机系统。嵌入式系统主要由嵌入式微处理器、外围硬件设备、嵌入式操作系统以及用户的应用程序等4个部分组成,它是集软硬件于一体的可独立工作的\"器件\"。嵌入式系统的软件部分包括操作系统软件(要求实时和多任务操作)和应用程序编程。操作系统控制着应用程序编程与硬件的交互作用,而应用程序控制着系统的运作和行为。

嵌入式操作系统是一种支持嵌入式系统应用的操作系统软件,它是嵌入式系统(包括硬、软件系统)极为重要的组成部分,通常包括与硬件相关的底层驱动软件、系统内核、设备驱动接口、通信协议、图形界面、标准化浏览器等Browser。嵌入式操作系统具有通用操作系统的基本特点,如能够有效管理越来越复杂的系统资源;能够把硬件虚拟化,使得开发人员从繁忙的驱动程序移植和维护中解脱出来;能够提供库函数、驱动程序、工具集以及应用程序。与通用操作系统相比较,嵌入式操作系统在系统实时高效性、硬件的相关依赖性、软件固态化以及应用的专用性等方面具有较为突出的特点。

1.1.2 实时多任务操作系统

RTOS(Real Time multi-tasking Operation System),即实时多任务操作系统是嵌入式应用软件的基础和开发平台。目前在中国大多数嵌入式软件开发还是基于处理器直接编写,没有采用商品化的RTOS,不能将系统软件和应用软件分开处理。RTOS是一段嵌入在目标代码中的软件,用户的其它应用程序都建立在RTOS之上。不但如

此,RTOS还是一个可靠性和可信性很高的实时内核,将CPU时间、中断、I/O、定时器等资源都包装起来,留给用户一个标准的API,并根据各个任务的优先级,合理地在不同任务之间分配CPU时间。

TOS是针对不同处理器优化设计的高效率实时多任务内核,优秀商品化的RTOS可以面对几十个系列的嵌入式处理器MPU、MCU、DSP、SOC等提供类同的API接口,这是RTOS基于设备独立的应用程序开发基础。因此基于RTOS上的C语言程序具有极大的可移植性。据专家测算,优秀RTOS上跨处理器平台的程序移植只需要修改1~5%的内容。在RTOS基础上可以编写出各种硬件驱动程序、专家库函数、行业库函数、产品库函数,和通用性的应用程序一起,可以作为产品销售,促进行业内的知识产权交流,因此RTOS又是一个软件开发平台。

RTOS是嵌入式系统的软件开发平台。RTOS最关键的部分是实时多任务内核,它的基本功能包括任务管理、定时器管理、存储器管理、资源管理、事件管理、系统管理、消息管理、队列管理、旗语管理等,

这些管理功能是通过内核服务函数形式交给用户调用的,也就是RTOS的API。 RTOS的引入,解决了嵌入式软件开发标准化的难题。随着嵌入式系统中软件比重不断上升、应用程序越来越大,对开发人员、应用程序接口、程序档案的组织管理成为一个大的课题。引入RTOS相当于引入了一种新的管理模式,对于开发单位和开发人员都是一个提高。

基于RTOS开发出的程序,具有较高的可移植性,实现90%以上设备独立,一些成熟的通用程序可以作为专家库函数产品推向社会。嵌入式软件的函数化、产品化能够促进行业交流以及社会分工专业化,减少重复劳动,提高知识创新的效率。 嵌入式工业的基础是以应用为中心的芯片设计和面向应用的软件开发。实时多任务操作系统(RTOS)进入嵌入式工业的意义不亚于历史上机械工业采用三视图的贡献,对嵌入式软件的标准化和加速知识创新是一个里程碑。

目前,商品化的RTOS可支持从8BIT的8051到32BIT的PowerPC及DSP等几十个系列的嵌入式处理器。提供高质量源代码RTOS的著名公司主要集中在美国。

1.1.3 嵌入式操作系统的发展状况

国外嵌入式操作系统已经从简单走向成熟,主要有Vxwork、QNX、PalmOS、Windows CE、嵌入式Linux等。国内的嵌入式操作系统研究开发有2种类型,一类是基于国外

操作系统2次开发完成的,如海信的基于Windows CE的机顶盒系统;另一类是中国自主开发的嵌入式操作系统,如凯思集团公司自主研制开发的嵌入式操作系统Hopen OS(“女娲计划”)等。

Windows CE内核较小,能作为一种嵌入式操作系统应用到工业控制等领域。其优点在于便携性、提供对微处理器的选择以及非强行的电源管理功能。内置的标准通信能力使Windows CE能够访问Internet并收发E_mail或浏览Web。除此之外,Windows CE特有的与Windows类似的用户界面使最终用户易于使用。Windows CE的缺点是速度慢、效率低、价格偏高、开发应用程序相对较难。

3Com公司的Palm OS在掌上电脑和PDA市场上独占其霸主地位,它有开放的操作系统应用程序接口(API),开发商可根据需要自行开发所需的应用程序。

Microwave的OS-9是为微处理器的关键实时任务而设计的操作系统,广泛应用于高科技产品中,包括消费电子产品、工业自动化、无线通讯产品、医疗仪器、数字电视/多媒体设备。它提供了很好的安全性和容错性。与其他的嵌入式系统相比,它的灵活性和可升级性非常突出。

Lynx Real-time Systems的LynxOS是一个分布式、嵌入式、可规模扩展的实时操作系统,它遵循POSIX.1a、POSIX.1b和POSIX.1c标准。LynxOS支持线程概念,提供256个全局用户线程优先级;提供一些传统的、非实时系统的服务特征;包括基于调用需求的虚拟内存,一个基于Motif的用户图形界面,与工业标准兼容的网络系统以及应用开发工具。

pSOS 。ISI公司已经被WinRiver公司兼并,现在pSOS属于WindRiver公司的产品。这个系统是一个模块化、高性能的实时操作系统,专为嵌入式微处理器设计,提供一个完全多任务环境,在定制的或是商业化的硬件上提供高性能和高可靠性。可以让开发者根据操作系统的功能和内存需求定制成每一个应用所需的系统。开发者可以利用它来实现从简单的单个独立设备到复杂的、网络化的多处理器系统。

QNX是由加拿大QSSL公司开发的分布式、实时的、可扩充的操作系统,它部分遵循POSIX相关标准,如:POSIX.1b实时扩展。它提供了一个很小的微内核以及一些可选的配合进程,具有高度的伸缩性,可灵活地剪裁。其内核仅提供4种服务:进程调度、进程间通信、底层网络通信和中断处理,其进程在独立的地址空间运行。所有其它OS服务,都实现为协作的用户进程,因此QNX内核非常小巧(QNX4.x大约为

12Kb)而且运行速度极快。这个灵活的结构可以使用户根据实际的需求,将系统配置成微小的嵌入式操作系统或是包括几百个处理器的超级虚拟机操作系统。因此,可以广泛地嵌入到智能机器、智能仪器仪表、机顶盒、通讯设备、PDA等应用中去。

Hopen OS是凯思集团自主研制开发的嵌入式操作系统,由一个体积很小的内核及一些可以根据需要进行定制的系统模块组成。其核心Hopen Kernel一般为10KB左右大小,占用空间小,并具有实时、多任务、多线程的系统特征。在众多的实时操作系统和嵌入式操作系统产品中,WindRiver公司的VxWorks是较为有特色的一种实时操作系统。

VxWorks是目前嵌入式系统领域中使用最广泛、市场占有率最高的系统。VxWorks 支持各种工业标准,包括POSIX、ANSI C 和TCP/IP网络协议。同时支持多种处理器,如x86、i960、Sun Sparc、Motorola MC68xxx、MIPS RX000、POWER PC等等。大多数的VxWorks API是专有的。采用GNU的编译和调试器。VxWorks 运行系统的核心是一个高效率的微内核,该微内核支持各种实时功能,包括快速多任务处理、中断支持、抢占式和轮转式调度。目前在全世界装有VxWorks 系统的智能设备数以百万计,其应用范围遍及互联网、电信和数据通信等众多领域。

开放源代码的嵌入式Linux操作系统无疑有着很大的优势。嵌入式Linux自身具备一整套工具链,容易自行建立嵌入式系统的开发环境和交叉运行环境,并且可以跨越在嵌入式系统开发中仿真工具(ICE)的障碍。内核的完全开放,使得可以自己设计和开发出真正的硬实时系统;对于软实时系统,在Linux中也容易得到实现。强大的网络支持,使得可以利用Linux的网络协议栈将其开发成为嵌入式的TCP/IP网络协议栈。

1.1.4 嵌入式系统软件的特点

嵌入式处理器的应用软件是实现嵌入式系统功能的关键,对嵌入式处理器系统软件和应用软件的要求也和通用计算机有所不同。

1.软件要求固态化存储。 为了提高执行速度和系统可靠性,嵌入式系统中的软件一般都固化在存储器芯片或单片机本身中,而不是存贮于磁盘等载体中。

2.软件代码高质量、高可靠性。 尽管半导体技术的发展使处理器速度不断提高、片上存储器容量不断增加,但在大多数应用中,存储空间仍然是宝贵的,还存在实时性的要求。为此要求程序编写和编译工具的质量要高,以减少程序二进制代码长度、

提高执行速度。

3.系统软件(OS)的高实时性是基本要求。 在多任务嵌入式系统中,对重要性各不相同的任务进行统筹兼顾的合理调度是保证每个任务及时执行的关键,单纯通过提高处理器速度是无法完成和没有效率的,这种任务调度只能由优化编写的系统软件来完成,因此系统软件的高实时性是基本要求。

4.多任务操作系统是知识集成的平台和走向工业标准化道路的基础。

1.2 嵌入式开发概述

1.2.1 嵌入式系统开发需要开发工具和环境

通用计算机具有完善的人机接口界面,在上面增加一些开发应用程序和环境即可进行对自身的开发。而嵌入式系统本身不具备自举开发能力,即使设计完成以后用户通常也是不能对其中的程序功能进行修改的,必须有一套开发工具和环境才能进行开发,这些工具和环境一般是基于通用计算机上的软硬件设备以及各种逻辑分析仪、混合信号示波器等。

1.2.2 嵌入式系统软件需要RTOS开发平台

通用计算机具有完善的操作系统和应用程序接口(API),是计算机基本组成不可分离的一部分,应用程序的开发以及完成后的软件都在OS平台上面运行,但一般不是实时的。嵌入式系统则不同,应用程序可以没有操作系统直接在芯片上运行;但是为了合理地调度多任务、利用系统资源、系统函数以及和专家库函数接口,用户必须自行选配RTOS开发平台,这样才能保证程序执行的实时性、可靠性,并减少开发时间,保障软件质量。

1.2.3 嵌入式系统开发人员以应用专家为主

通用计算机的开发人员一般是计算机科学或计算机工程方面的专业人士,而嵌入式系统则是要和各个不同行业的应用相结合的,要求更多的计算机以外的专业知识,其开发人员往往是各个应用领域的专家。因此开发工具的易学、易用、可靠、高效是基本要求。

1.2.4 嵌入式系统高级编程语言

Ada语言是20世纪70年代美国国防部开发并投入使用的功能强大的通用系统开发语言,最初为Ada83。它支持模块化、独立编译、协处理等功能。其可靠性、可维

护性、可读性都是相当好的。后来,为了更好地支持OOP(Object-Oriented Programming),对其进行了改进,形成了目前广泛使用的Ada95。使用Ada语言可以大大改善系统的清晰性、可靠性、可维护性等性能指标[2,3]。它是美国国防部指定的唯一一种可用于军用系统开发的语言。

C语言是由Dennis Richie于1972年在AT&Bell实验室研究成功并投入使用的系统编程语言。其设计目标是使C既具有汇编语言的效率,又具有高级语言的易编程性。其最具代表性的应用是UNIX操作系统。从20世纪80年代中期C语言涉足实时系统后,受到了普遍欢迎。目前是使用最广泛的嵌入式系统编程语言。C++是由Bjarne Stroustrup 于1995年在Bell实验室研制成功并投入使用的。C++在支持现代软件工程、OOP、结构化等方面对C进行了卓有成效的改进,但在程序代码容量、执行速度、程序复杂程度等方面比C语言程序性能差一些。

Modula-2是由Nicklans Wirth在70年代后期根据Pascal 和Modula开发的系统设计语言。其主要目标是在模块化、系统编程、协同处理等方面对Pascal 进行改进。Modula-2具有很强的类型检查能力和丰富的低级功能支持。因此,可用它设计一个完整的实时程序而不用汇编语言的支持。Modula-3是1988年由DEC(Digital Equipment Company)和ORC(Olivetti Research Center)根据Modula-2开发研制并投入使用的系统开发语言。目标是设计一个功能强大但结构简单的通用编程语言。它在协同处理、OOP、自动垃圾收集以及对C语言和UNIX的支持等方面对Modula-2进行了改进

Java是网络语言,而嵌入式系统则在功能、价格、体积、功耗、上市时间等方面有特殊要求。因此Java语言受速度和代码容量的限制,本身并不适合于嵌入式系统的应用。但Sun公司并不愿意放弃这个发展潜力巨大的应用市场,对Java进行改进后发表了J2ME(Java2 Micro Edition)。它是Java API的一个子集,只包含了Java的关键特性,是专门针对对内存具有苛刻要求的嵌入式系统而设计的。J2ME粗略地将应用对象划分为两大类:内存在128KB~512KB之间的设备和内存大于512KB的设备,根据不同的类别提供不同的用户接口和软件包。

第2章 ARM处理器结构和ARM指令集

2.1 ARM处理器结构

ARM(Advanced RISC Machines)是微处理器行业的一家知名企业,该企业设计

了大量高性能、廉价、耗能低的RISC处理器、相关技术及软件。技术具有性能高、成本低和能耗省,适用于多种领域,比如嵌入控制、消费/教育类多媒体、DSP和移动式应用等。

ARM处理器的框架如图2-1所示

ABE A[31:0] 地址 自增器 Incrementer 地址寄存器 P C 寄存器 A B u s B PC Update BIGEND MCLK nWAIT

指令 解码站 乘法器 B u 指令 nRW MAS[1:0] ISYNC nIRQ nFIQ nRESET ABORT nTRANS nMREQ SEQ LOCK nM[4:0] nOPC nCPI CPA CPB

解码 解码 读数据 寄存器 写数据 及 桶形 移位器 32 位 ALU s 控制 逻辑 寄存器

图2-1 ARM处理器架构

2.1.1 寄存器和处理器模式

1.ARM 有7个基本工作模式:

User:非特权模式,大部分任务执行在这种模式。限制你的内存访问并且你不能

直接读取硬件设备 正常程序执行的模式

FIQ:当一个高优先级(fast)中断产生时将会进入这种模式 高速数据传输和通道处理

IRQ:当一个低优先级(normal)中断产生时将会进入这种模式 通常的中断处理

Supervisor:当复位或软中断指令执行时将会进入这种模式 供操作系统使用的一种保护模式 Abort: 当存取异常时将会进入这种模式

虚拟存储及存储保护

Undef: 当执行未定义指令时会进入这种模式 软件仿真硬件协处理器

System: 使用和User模式相同寄存器集的特权模式 特权级的操作系统任务 (2)寄存器组

在26位体系下,ARM 处理器有二十七个寄存器,其中一些是在一定条件下使用的,所以一次只能使用十六个。

寄存器 0 到寄存器 7 是通用寄存器并可以用做任何目的。不象 80x86 处理器那样要求特定寄存器被用做栈访问,或者象 6502 那样把数学计算的结果放置到一个累加器中,ARM 处理器在寄存器使用上是高度灵活的。

寄存器 8 到 12 是通用寄存器,但是在切换到 FIQ 模式的时候,使用它们的影子(shadow)寄存器。

寄存器 13 典型的用做 OS 栈指针,但可被用做一个通用寄存器。这是一个操作系统问题,不是一个处理器问题,所以如果你不使用栈,只要你以后恢复它,你可以在你的代码中自由的占用(corrupt)它。每个处理器模式都有这个寄存器的影子寄存器。

寄存器 14 专职持有返回点的地址以便于写子例程。当你执行带连接的分支的时候,把返回地址存储到 R14 中。同样在程序第一次运行的时候,把退出地址保存在 R14 中。R14 的所有实例必须被保存到其他寄存器中(不是实际上有效)或一个栈中。这个寄存器在各个处理器模式下都有影子寄存器。一旦已经保存了连接地址,这个寄存器就可以用做通用寄存器了。

寄存器 15 是程序计数器。它除了持有指示程序当前使用的地址的二十六位数之外,还持有处理器的状态。

为更清晰一些, 提供下列图表:

User 模式 SVC 模式 IRQ 模式 FIQ 模式 APCS

R0 ------- R0 ------- R0 ------- R0 a1 R1 ------- R1 ------- R1 ------- R1 a2 R2 ------- R2 ------- R2 ------- R2 a3 R3 ------- R3 ------- R3 ------- R3 a4 R4 ------- R4 ------- R4 ------- R4 v1 R5 ------- R5 ------- R5 ------- R5 v2 R6 ------- R6 ------- R6 ------- R6 v3 R7 ------- R7 ------- R7 ------- R7 v4 R8 ------- R8 ------- R8 R8_fiq v5 R9 ------- R9 ------- R9 R9_fiq v6 R10 ------ R10 ------ R10 R10_fiq sl R11 ------ R11 ------ R11 R11_fiq fp R12 ------ R12 ------ R12 R12_fiq ip R13 R13_svc R13_irq R13_fiq sp R14 R14_svc R14_irq R14_fiq lr ------------- R15 / PC ------------- pc

最右侧的列是 APCS 代码使用的名字。APCS,ARM 过程调用标准(ARM Procedure Call Standard),提供了紧凑的编写例程的一种机制,定义的例程可以与其他例程交织在一起。最显著的一点是对这些例程来自哪里没有明确的限制。它们可以编译自 C、 Pascal、也可以是用汇编语言写成的。 APCS 定义了:

• • • •

对寄存器使用的限制。 使用栈的惯例。

在函数调用之间传递/返回参数。

可以被‘回溯’的基于栈的结构的格式,用来提供从失败点到程序入口的函数(和给予的参数)的列表。

程序计数器构造如下图2-2:

328 222118 7 6 5 4 0 N Z C V Q f J U n d e f i n e d s x I F T mode c  条件位:

图2-2 程序计数器构造

 N = 1-结果为负,0-结果为正或0  Z = 1-结果为0,0-结果不为0  C =1-进位,0-借位

 V =1-结果溢出,0结果没溢出  Q 位:

 仅ARM 5TE/J架构支持  指示增强型DSP指令是否溢出  J 位

 仅ARM 5TE/J架构支持

 J = 1: 处理器处于Jazelle状态  中断禁止位:

 I = 1: 禁止 IRQ.  F = 1: 禁止 FIQ.  T Bit

 仅ARM xT架构支持

 T = 0: 处理器处于 ARM 状态  T = 1: 处理器处于 Thumb 状态  Mode位(处理器模式位):

 0b10000 User  0b10001 FIQ  0b10010 IRQ

 0b10011 Supervisor  0b10111 Abort  0b11011 Undefined  0b11111 System  当处理器执行在ARM状态:

 所有指令 32 bits 宽  所有指令必须 word 对齐

 所以 pc值由bits [31:2]决定, bits [1:0] 未定义 (所以指令不能halfword / byte对齐).

 当处理器执行在Thumb状态:

 所有指令 16 bits 宽  所有指令必须 halfword 对齐

 所以 pc值由bits [31:1]决定, bits [0] 未定义 (所以指令不能 byte对齐).

 当处理器执行在Jazelle状态:

 所有指令 8 bits 宽

 处理器执行 word 存取一次取4条指令

2.2 ARM指令集

2.2.1 ARM存贮访问指令 ARM存贮访问指令如下表2-1:

表2-1 ARM存贮访问指令

助记符 说明 操作 Rd←Rm*Rs (Rd≠Rm) Rd←Rm*Rs+Rn (Rd≠Rm) (RdLo,RdHi)←Rm*Rs 条件码位置 MUL{cond}{S} MLA{cond}{S} UMULL{cond}{S} MUL Rd,Rm,32位乘法指令 Rs MLA Rd,Rm,32位乘法指令 Rs,Rn UMULL RdLo,64位无符号乘RdHi,Rm,Rs 法指令 UMLAL RdLo,64位无符号乘RdHi,Rm,Rs 法指令 SMULL RdLo,64位无符号乘RdHi,Rm,Rs 法指令 SMLAL RdLo,64位无符号乘RdHi,Rm,Rs 法指令 (RdLo,RdHi)←Rm*Rs+(RdLo,UMLAL{cond}{S} RdHi) (RdLo,RdHi)←Rm*Rs SMULL{cond}{S} (RdLo,RdHi)←Rm*Rs+(RdLo,SMLAL{cond}{S} RdHi) 2.2.2 数据处理指令

数据处理指令如下表2-2

表2-2 数据处理指令 助记符号

MOV Rd,operand2 MVN Rd,operand2 ADD Rd,Rn,operand2 SUB Rd,Rn,operand2 RSB Rd,Rn,operand2 ADC Rd,Rn,operand2 RSC Rd,Rn,operand2 AND Rd,Rn,operand2 ORR Rd,Rn,operand2 EOR Rd,Rn,operand2 BIC Rd,Rn,operand2 CMP Rd,Rn,operand2 CMN Rd,Rn,operand2 TST Rd,Rn,operand2

说明 数据转送 数据非转送 加法运算指令 减法运算指令 逆向减法指令 带进位加法 带进位减法 带进位逆向减法 逻辑或操作指令 逻辑异或操作指令 位清除指令 比较指令 负数比较指令 位测试指令

操作

Rd<-operand2

Rd<-(NOT)operand2 Rd<-Rn+operand2 Rd<-Rn-operand2 Rd<-operand2-Rn

Rd<-Rn+operand2+carry Rd<-Rn-perand2-(NOT)Carry Rd<-Rn&operand2 Rd<-Rn|operand2 Rd<-Rn^operand2 Rd<-Rn(~operand2)

标志N、Z、C、V<-Rn-operand2 标志N、Z、C、V<-Rn+operand2 标志N、Z、C、V<-Rn&operand2

条件码位置 MOV {cond}{S} MVN {cond}{S} ADD {cond}{S} SUB {cond}{S} RSB {cond}{S} ADC {cond}{S} RSC {cond}{S} AND {cond}{S} ORR {cond}{S} EOR {cond}{S} BIC {cond} CMP {cond} CMN {cond} TST {cond}

TEQ Rd,Rn,operand2 相等测试指令 标志N、Z、C、V<-Rn^operand2 TEQ {cond}

2.2.3 乘法指令

乘法指令如下表2-3: 表2-3 乘法指令

助记符 MUL Rd,Rm,Rs MLA Rd,Rm,Rs,Rn 说明 32位乘法指令 32位乘法指令 操作 条件码位置 Rd←Rm*Rs MUL{cond}{S} (Rd≠Rm) Rd←Rm*Rs+Rn (Rd≠Rm) (RdLo,RdHi)←Rm*Rs MLA{cond}{S} UMULL{cond}{S} UMULL RdLo,RdHi,64位无符号乘法Rm,Rs 指令 UMLAL RdLo,RdHi,64位无符号乘法Rm,Rs 指令 SMULL RdLo,RdHi,64位无符号乘法Rm,Rs 指令 SMLAL{cond}{S} (RdLo,RdHi)UMLAL{cond}{S} ←Rm*Rs+(RdLo,RdHi) (RdLo,RdHi)←Rm*Rs SMULL{cond}{S} SMLAL{cond}{S} SMLAL{cond}{S} SMLAL{cond}{S} 2.2.4 跳转指令

跳转指令如下表2-4:

表2-4 跳转指令

助记符 B label BL label RX Rm 说明 跳转指令 带链接的跳转指令 带状态切换的跳转指令 操作 Pc←label LR←PC-4,PC←label PC←lable,切换处理状态 条件码位置 B{cond} BL{cond} BX{cond} 2.2.5 杂项指令

杂项指令如下表2-5: 表2-5 杂项指令

助记符 SWI immed_24 MRS Rd,psr MRS psr_fields,Rd/#immed_8r 说明 软中断指令 读状态寄存器指令 写状态寄存器指令 操作 产生软中断,处理器进入管理模式 Rd←psr,psr为CPSR或SPSR Psr_fields←Rd/#immed_8r,psr为CPSR或SPSR 条件码位置 SWI{cond} MRS{cond} MSR{cond}

2.2.6 Thumb指令集

Thumb指令可以作是ARM指令压缩形式的子集,是针对代码密度的问题而提出的,它具有16位的代码密度。Thumb不是一个完整的体系结构,不能指望处理只执行Thumb指令而不支持ARM指令集。Thumb指令集没有协处理器指令。ARM和Thumb之间切换使用BX 指令。

Thumb指令与ARM指令在实现上差别不大,在此就不再对Thumb指令集进行详细的介绍了。

第3章 µC/OS-Ⅱ的移植

3.1 移植的要求和准备

这一章介绍如何将µC/OS-Ⅱ移植到不同的处理器上。所谓移植,就是使一个实时内核能在某个微处理器或微控制器上运行。为了方便移植,大部分的µC/OS-Ⅱ代码是用C语言写的;但仍需要用C和汇编语言写一些与处理器相关的代码,这是因为µC/OS-Ⅱ在读写处理器寄存器时只能通过汇编语言来实现。由于µC/OS-Ⅱ在设计时就已经充分考虑了可移植性,所以µC/OS-Ⅱ的移植相对来说是比较容易的。

μC/OS-II功能强大,支持56个用户任务,其内核为占先式,支持信号量、邮箱、消息队列等多种常用的进程间通信机制,现已成功应用到众多商业嵌入式系统中,是一个成熟稳定的实时内核。与大多商用RTOS不同的是,μC/OS-II公开所有的源代码,90%的代码使用标准的ANSI C语言书写,程序可读性强、移植性好;同时它可免费获得,即使商业应用也只收取少量的许可费用。因此,对μC/OS-II实时操作系统的学习研究、开发、应用具有重要意义。

要使µC/OS-Ⅱ正常运行,处理器必须满足以下要求: ■ 处理器的C编译器能产生可重入代码。 ■ 用C语言就可以打开和关闭中断。

■ 处理器支持中断,并且能产生定时中断(通常在10至100Hz之间)。 ■ 处理器支持能够容纳一定量数据(可能是几千字节)的硬件堆栈。 ■处理器有将堆栈指针和其它CPU寄存器读出和存储到堆栈或内存中的指令 如果用户理解了处理器和C编译器的技术细节,移植µC/OS-Ⅱ的工作实际上是非常简单的。前提是您的处理器和编译器满足了µC/OS-Ⅱ的要求,并且已经有了必要工具。移植工作包括以下几个内容: ■用#define设置一些常量的值(OS_CPU.H) ■声明10个数据类型(OS_CPU.H) ■用#define声明三个宏(OS_CPU.H)

■用C语言编写六个简单的函数(OS_CPU_C.C) ■编写四个汇编语言函数(OS_CPU_A.ASM)

3.2 移植具体过程

3.2.1 µC/OS-Ⅱ的软硬件结构体系

Samsung S3C44B0X微处理器是三星公司专为手持设备和其它嵌入式应用提供的高性价比的微控制器解决方案。它使用ARM公司的16位/32位RISC结构,内核是ARM7TDMI,工作在66MHz,片上集成了以下部件:8K Cache、外部存储器控制器、LCD控制器、4个DMA通道、2个UART、1个多主I2C总线控制器、1个I2C总线控制器,以及5通道PWM定时器和1个内部定时器、8通道12位ADC等,能够与常用的外围设备实现无缝连接,功能强大。目前,国内应用较为广泛。

µC/OS-Ⅱ应用程序 µC/OS-Ⅱ处理器无关代码 OS-CORE.C OS-Q.C OS-MBOX.C OS-MEN.C OS-TASK.C µC/OS-Ⅱ.C OS-TIME.C µC/OS-Ⅱ.H OS-SEM.C µC/OS-Ⅱ处理器相关代码 OS_CPU.H OS_CPU_A.ASM OS_CPU_C.C µC/OS-Ⅱ编译器相关定义 µC/OS-Ⅱ设置 (应用相关) OS-CFG.H INCLUDES.H 处 理 器 存储器 系统时钟 外围设备 图3-1 µC/OS-Ⅱ软硬件体系结构

图3.1说明了μC/OS-II的软硬件体系结构。应用程序处于整个系统的顶层,每个任务都可以认为自已独占了CPU,因而可以设计成为一个无限循环。μC/OS-II处理器无关的代码提供了μC/OS-II的系统服务,应用程序可以使用这些API函数进行内存管理、任务间通信及创建、删除任务等。

大部分的μC/OS-II代码是使用ANSI C语言书写的,因此μC/OS-II的可移植性好,然而仍需要使用C和汇编语言写一些处理器相关代码。μC/OS-II的移植需要满足以下要求:

1.处理器的C编译器可以产生可重入代码; 2.可以使用C调用进入和退出临界区代码;

3.处理器必须支持硬件中断,并且需要一个定时中断源; 4.处理器需要能够容纳一定数据的硬件堆栈;

5.处理器需要有能够在CPU寄存器与内核和堆栈交换数据的指令。 S3C44B0X处理器完全满足上述要求。

3.2.2 实时内核μC/OS-II在S3C44B0X上的移植

我们使用ARM SDT编译器,移植μC/OS-II主要包括以下几个步骤。 1.设置OS_CPU.H中与处理器和编译器相关的代码 ************************************************* 与编译器相关的数据类型

************************************************* typedef unsigned char BOOLEAN;

typedef unsigned char INT8U; //8位无符号整数 typedef signed char INT8S; //8位有符号整数 typedef unsigned short INT16U; //16位有符号整数 typedef signed short INT16S; //16位无符号整数 typedef unsigned long INT32U; //32位无符号整数 typedef signed long INT32S; //32位有符号整数

typedef float FP32; //单精度浮点数 typedef double FP64; //双精度浮点数

typedef unsigned int OS_STK; /*堆栈入口宽度为16位与ARM处理器相关的代码:*/

#define OS_ENTER_CRITICAL () ARMEnableInt() //开启中断 #define OS_STK_GROWTH 1 //堆栈由高地址向低地址增长 2.用C语言编写6个操作系统相关的函数(OS_CPU_C.C)

void OSTaskStkInit(void(task)(void *pd),void *pdata,void *ptos,INT16U opt)

{ unsigned int *stk;

opt =opt; /*因“opt”变量没有用到,防止编译器产生警告*/ stk =(unsigned int *)ptos; /*装载堆栈指针*/ /*为新任务创建上下文*/

*--stk=(unsigned int)task; /*lr*/ *--stk=(unsigned int)task /*pc*/ *--stk=0; /*r12*/ *--stk=0; /*r11*/ *--stk=0; /*r10*/ *--stk=0; /*r9*/ *--stk=0; /*r8*/

*--stk=0; /*r7*/ *--stk=0; /*r6*/ *--stk=0; /*r5*/ *--stk=0; /*r4*/ *--stk=0; /*r3*/ *--stk=0; /*r2*/ *--stk=0; /*r1*/

*--stk=(unsigned int)pdata; /*r0*/ *--stk=(SVC32MODE|0x0|); /*cpsr IRQ,

*--stk=(SVC32MODE|0x0); /*spsr IRQ,关闭FIQ*/ return((void*)stk); }

后5个函数是钩子函数,可以不加代码: void OSTaskCreateHook(OS_TCB *ptcb) void OSTaksDelHool (OS_TCB *ptcb) void OSTaskSwHook(void) void OSTaskStatHook(void)

(3)用汇编语言编写4个与处理器相关的函数(OS_CPU.ASM)

OSStartHighRdy() ;运行优先级最高的就绪任务

LDR r4,addr_OSTCBCur ;得到当前任务的TCB地址 LDR r5,addr_OSTCBHighRdy ;得到高优先级任务的TCB地址 LDR r5,addr_OSTCBHighRdy ;得到高优先级任务的TCB地址 LDR r5,[r5] ;得到堆栈指针 LDR sp,[r5] STR r5,[r4] LDMFD sp!,{r4} MSR CPSR_cxsf,r4

LDMFD sp!,{r0-r12,lr,pc} END

OSCtxsw() STMFD sp!,{lr} STMFD sp!,{lr} STMFD sp!,{r0-r12} MRS r4,CPSR

STMFD sp!,{r4} MRS r4,SPSR STMFD sp!,{r4}

;OSPrioCur=OSPrioHighRdy;切换到新的堆栈

;设置新的当前任务的TCB地址 ;开始新的任务 ;任务级的任务切换函数 ;保存PC指针 ;保存lr指针

;保存寄存器文件和返回地址 ;保存当前PSR

LDR r4,addr_OSPrioCur LDR r5,addr_OSPrioHighRdy LDRB r6,[r5] STRB r6,[r4]

LDR r4,addr_OSTCBCur LDR r5,[r4]

STR sp,[r5] LDR r6,addr_OSTCBHighRdy LDR r6,[r6]

LDR sp,[r6] ;OSTCBCur=OSTCBHighRdy STR r6,[r4]

LDMFD sp!,{r4} MSR SPSR_cxsf,r4 LDMFD sp!,{r4} MSR SPSR_cxsf,r4

TCB地址

TCB上TCB地址

TCB地址

;得到当前任务的 ;保存栈指针在占先任务的

;取得高优先级任务的 ;得到新任务的堆栈指针;得到当前新任务的LDMFD sp!,{r0-r12,lr pc}

OSIntCtxSw() ;中断级的任务切换函数 LDMIA sp!,{al-vl,lr} SUBS pc,lr,#4 SUB lr,lr,#4 MOV r12,lr MRS lr,SPSR

AND lr,lr,#0XFFFFFE0 ORR lr,lr,#0XD3 MSR CPSR_CXSF,lr

OSTickISR() STMDB sp!,{r0-r11,lr} MRS r0,CPSR

ORR r0,r0,#0x80 MSR CPSR_cxsf,r0 LDR r0,I_ISPC LDR r1,=BIT_TIMER0 STR r1,{r0} BL IrqStart

;中断服务函数 ;设置中断禁止标志 ;中断结束BL osTimeTick BL IrqFinish LDR r0,=need_to_s LDR R2,[R0] CMP r2,#1

LDREQ pc,=_CON_SW

完成上述工作后,μC/OS-II就可以运行在ARM处理器上了。

3.2.3 使用μC/OS-II系统应注意的问题

1.μC/OS-II和Linux等分时操作系统不同,不支持时间片轮转法。它是一个基于优先级的实时操作系统。每一个任务的优先级必须不同(分析它的源码会发现,μC/OS-II把任务的优先级当作任务在标识来使用,如果优先级相同,任务将无法区分)。进入就绪态的优先级最高的任务首先得到CPU的使用权,只有等它交出CPU的使用权后,其它任务才可以被执行。所以,它只能就是多任务,不能就是多进程,至少不是我们所熟悉的那种多进程。

2.μC/OS-II对共享资源提供了保护的机制。μC/OS-II是一个支持多任务的操作系统。我们可以把一个完整的程序划分成几个任务,不同的任务执行不同的功能。对于共享资源(比如串口),μC/OS-II也提供了很好的解决办法,一般情况下使用的是信号量方法。我们创建一个信号量并对它进行初始化,当一个任务需要使用一个共享资源时,它必须先申请得到这个信号量。在这个过程中即使有优先权更高的任务进入了就绪态,因为无法得到信号量,也不能使用该资源。在μC/OS-II中称为优先级反转。简单地说,就是高优先级任务必须等待低优先级任务的完成。在上述情况下,在两个任务之间发生优先级后转是无法避免的。所以不在使用μC/OS-II时,必须对所开发的系统了解清楚才能选择对于某种共享资源是否使用信号量。

3.μC/OS-II内存管理不够完善。在分析许多μC/OS-II的应用实例中发现,任务栈空间和内存分区的创建采用了定义全局数组的方法,这样实现起来固然简单,但不够灵活有效。

编译器会将全局数组作为未初始化的全局变量,放到应用程序映像的数据段。数组的大小是固定的,生成映像后不可能在使用中动态地改变。对于任务栈空间来说,数组定义大了会造成内存浪费;定义小了任务栈溢出,会造成系统崩溃。对于内存分区,在不知道系统初始化后给用户留下了多少自由内存空间的情况下,很难定义内存分区所使用数组的大小。此外,现在μC/OS-II只支持固定大小的内存分区,容易造成内存浪费。μC/OS-II将来应该被改进以支持可变大小的内存分区。因此,系统初始化后能清楚地掌握自由内存空间的情况是很重要的。所以,应避免使用全局数组分配内存空间,关键是要知道整个应用程序在编译、链接后代码段和数据段的大小,在目标板内存中是如何定位,以及目标板内存的大小。

第4章 网络转串口驱动的开发

4.1 Lwip在µC/OS-Ⅱ上的移植

4.1.1 开源TCP/IP协议栈LwIP简介

随着嵌入式系统与网络的日益结合,在嵌入式实时操作系统中引入TCP/IP协议栈,以支持嵌入式设备接入网络,成为嵌入式领域重要的研究方向。uC/0S II是近年来发展迅速的一个开放源码实时操作系统,但它只是一个实时的任务调度及通信内核,缺少对外围设备和接口的支持,如没有文件系统、网络协议、图形界面。现在就以TCP/IP协议栈LwIP为基础,给uC/0S II加上了网络支持。

LwIP是瑞士计算机科学院(Swedish Institute of Computer Science)的Adam Dunkels等开发的一套用于嵌入式系统的开放源代码TCP/IP协议栈。LwIP的含义是Light Weight(轻型)IP协议。LwIP可以移植到操作系统上,也可以在无操作系统的情况下独立运行。LwIP TCP/IP实现的重点是在保持TCP协议主要功能的基础上减少对RAM的占用,一般它只需要几十K的RAM和40K左右的ROM就可以运行,这使LwIP协议栈适合在低端嵌入式系统中使用。LwIP的特性如下:■支持多网络接口下的IP转发■支持ICMP协议 ■包括实验性扩展的的UDP(用户数据报协议)■包括阻塞控

制,RTT估算和快速恢复和快速转发的TCP(传输控制协议)■提供专门的内部回调接口(Raw API)用于提高应用程序性能■可选择的Berkeley接口API(多线程情况下)

4.1.2 基于uC/0S II的网络平台概述

嵌入式操作系统uC/0S II是一个公开源代码的占先式多任务的微内核RTOS,其性能和安全性可以与商业产品竞争。uC/0S II的特点可以概括为以下几个方面:公开源代码,代码结构清晰、明了,注释详尽,组织有条理,可移植性好。可裁剪,可固化。内核属于抢占式,最多可以管理56个任务。uC/0S II自1992年的第一版(uC/0S)以来已经有好几百个应用,是一个经实践证明好用且稳定可靠的内核。目前国内对uC/0S II的研究和应用都很多。

TCP/IP是Internet的基本协议,以其实用性、高效性已经成为事实上的工业标准。嵌入式设备要与Internet网络直接交换信息,就必须支持TCP/IP协议。目前嵌入式设备上TCP/IP方案有很多种,但面向低端应用的开源嵌入式网络平台还很少见。 uC/0S II是一个富有开放色彩的RTOS,只要买一本书就可获得源代码,对学校和教育的使用完全免费,商业应用的费用相对也很低。但是它目前的一些第三方TCP/IP支持都是完全商业化的,用户需要付费才能获得,很少给出源代码,这影响了uC/0S II的研究和推广。通过把开放源代码的TCP/IP协议栈LwIP移植到uC/0S II上来,就获得了一套可免费研究、学习的嵌入式网络软件平台。系统示意图如下图4-1:

图4-1 uC/0S II+LwIP系统示意图

4.1.3 LwIP在uC/0S II下的实现

LwIP协议栈在设计时就考虑到了将来的移植问题,因此把所有与硬件、OS、编译器相关的部份独立出来,放在/src/arch目录下。因此LwIP在uC/0S II上的实现就是修改这个目录下的文件,其它的文件一般不应该修改。下面分几部份分别说明相应文件的实现原理和过程。 现在网上最新的版本是V0.6.4 1.lwip的进程模型(process model)

tcp/ip协议栈的process model一般有几种方式.

(1)tcp/ip协议的每一层是一个单独进程.链路层是一个进程,ip层是一个进程,tcp层是一个进程。 这样的好处是网络协 议的每一层都非常清晰,代码的调试和理解都非常容易.但是最大的坏处数据跨层传递时会引起上下文切换(context switch). 对于接收一个TCP segment要引起3次context switch(从网卡驱动程序到链路层进程,从链路层进程到ip层进程,从ip层进程到TCP进程).通常对于操作系统来说,任务切换是要浪费时间的.过频的context swich是不可取的.

(2)另外一种方式是TCP/IP协议栈在操作系统内核当中.应用程序通过操作系统的系统调用(system call)和协议栈来进行通讯。 这样TCP/IP的协议栈就限定于特定的操作系统内核了.如windows就是这种方式.。

(3)lwip的process model:所有tcp/ip协议栈都在一个进程当中。 这样tcp/ip协议栈就和操作系统内核分开了.而应用层程序既可以是单独的进程也可以驻留在tcp/ip进程中.如果应用程序是单独的进程可以通过操作系统的邮箱,消息队列等和tcp/ip进程进行通讯.

如果应用层程序驻留tcp/ip进程中,那应用层程序就利用内部回调函数口(Raw API)和tcp/ip协议栈通讯.对于ucos来说进程就是一个系统任务.lwip的process model请参看下图.在图中可以看到整个tcp/ip协议栈都在同一个任务(tcpip_thread)中.应用层程序既可以是独立的任务(如图中的t),也可以在tcpip_thread中(如图左上角)中利用内部回调函数口(Raw API)和tcp/ip协议栈通讯 2.Port Lwip to uCos

在这个项目中我用的硬件平台是s3c44b0x+rtl8019.ucos在44b0上的移植。lwip会为每个网络连接动态分配一些信号量(semaphone)和消息队列(Message Queue),当连接断开时会删掉这些semaphone和Queue.而Ucos-2.0不支持semaphone和Queue的删除,所以要选择一些较高版本的ucos.我用的是ucos-2.51.

(1) Lwip的操作系统封装层(operating system.emulation layer) Lwip为了适应不同的操作系统,在代码中没有使用和某一个操作系统相关的系统调用和数据结构.而是在lwip和操作系统之间增加了一个操作系统封装层.操作系统封装层为操作系统服务(定时,进程同步,消息传递)提供了一个统一的接口.在lwip中进程同步使用semaphone和消息传递采用”mbox”(其实在ucos的实现中我们使用的是Message Queue来实现lwip中的”mbox”,下面大家可以看到这一点) Operating system emulation layer的原代码在…/lwip/src/core/sys.c中.而和具体的操作系统相关的代码在../lwip/src/arch/sys_arch.c中.

操作系统封装层的主要函数如下: void sys_init(void)//系统初始化

sys_thread_t sys_thread_new(void (* function)(void *arg), void *arg,int prio)//创建一个新进程

sys_mbox_t sys_mbox_new(void)//创建一个邮箱

void sys_mbox_free(sys_mbox_t mbox)//释放并删除一个邮箱

void sys_mbox_post(sys_mbox_t mbox, void *data) //发送一个消息到邮箱 void sys_mbox_fetch(sys_mbox_t mbox, void **msg)//等待邮箱中的消息 sys_sem_t sys_sem_new(u8_t count)//创建一个信号量 void sys_sem_free(sys_sem_t sem)//释放并删除一个信号量 void sys_sem_signal(sys_sem_t sem)//发送一个信号量 void sys_sem_wait(sys_sem_t sem)//等待一个信号量

void sys_timeout(u32_t msecs, sys_timeout_handler h, void *arg)//设置一个超时事件

void sys_untimeout(sys_timeout_handler h, void *arg)//删除一个超时事件 …

关于操作系统封装层的信息可以阅读lwip的doc目录下面的sys_arch.txt.文件. (2) Lwip在ucos上的移植. ① 系统初始化

sys_int必须在tcpip协议栈任务tcpip_thread创建前被调用. #define MAX_QUEUES 20 #define MAX_QUEUE_ENTRIES 20 typedef struct {

OS_EVENT* pQ;//ucos中指向事件控制块的指针 void* pvQEntries[MAX_QUEUE_ENTRIES];//消息队列 //MAX_QUEUE_ENTRIES消息队列中最多消息数 } TQ_DESCR, *PQ_DESCR;

typedef PQ_DESCR sys_mbox_t;//可见lwip中的mbox其实是ucos的消息队列 static char pcQueueMemoryPool[MAX_QUEUES * sizeof(TQ_DESCR) ]; void sys_init(void) { u8_t i;

s8_t ucErr;

pQueueMem = OSMemCreate( (void*)pcQueueMemoryPool, MAX_QUEUES, sizeof(TQ_DESCR), &ucErr );//为消息队列创建内存分区 //init lwip task prio offset curr_prio_offset = 0;

//init lwip_timeouts for every lwip task //初始化lwip定时事件表,具体实现参考下面章节 for(i=0;i② 创建一个和tcp/ip相关新进程。 lwip中的进程就是ucos中的任务,创建一个新进程的代码如下:

#define LWIP_STK_SIZE 10*1024/*和tcp/ip相关任务的堆栈大小.可以根据情况自*/

/*己设置,44b0开发板上有8M的sdram,所以设大 一点也没有关系*/

//max number of lwip tasks

#define LWIP_TASK_MAX 5 //和tcp/ip相关的任务最多数目 //first prio of lwip tasks

#define LWIP_START_PRIO 5 /*和tcp/ip相关任务的起始优先级,在本例中优先级可*/

//以从(5-9).注意tcpip_thread在所有tcp/ip相关进程中//应该是优先级最高的.在本例中就是优先级5

//如果用户需要创建和tcp/ip无关任务,如uart任务等, //不要使用5-9的优先级

OS_STK LWIP_TASK_STK[LWIP_TASK_MAX][LWIP_STK_SIZE];/*和tcp/ip相关进程的堆栈区*/

u8_t curr_prio_offset ;

sys_thread_t sys_thread_new(void (* function)(void *arg), void *arg,int prio) {

if(curr_prio_offset < LWIP_TASK_MAX){

OSTaskCreate(function,(void*)0x1111, &LWIP_TASK_STK[curr_prio_offset][LWIP_STK_SIZE-1],

LWIP_START_PRIO+curr_prio_offset ); curr_prio_offset++; return 1; } else {

// PRINT(\" lwip task prio out of range ! error! \"); } }

从代码中可以看出tcpip_thread应该是最先创建的.

③ Lwip中的定时事件。 在tcp/ip协议中很多时候都要用到定时,定时的实现也是tcp/ip协议栈中一个重要的部分.lwip中定时事件的数据结构如下. struct sys_timeout {

struct sys_timeout *next;//指向下一个定时结构 u32_t time;//定时时间

sys_timeout_handler h;//定时时间到后执行的函数 void *arg;//定时时间到后执行函数的参数. };

struct sys_timeouts { struct sys_timeout *next; };

struct sys_timeouts lwip_timeouts[LWIP_TASK_MAX];

Lwip中的定时事件表的结构如下图,每个和tcp/ip相关的任务的一系列定时事件组成一个单向链表.每个链表的起始指针存在lwip_timeouts的对应表项中,如下图4-2

图4-2 Lwip中的定时事件表的结构

函数sys_arch_timeouts返回对应于当前任务的指向定时事件链表的起始指针.该指针存在lwip_timeouts[MAX_LWIP_TASKS]中. struct sys_timeouts null_timeouts;

struct sys_timeouts * sys_arch_timeouts(void) {

u8_t curr_prio; s16_t err,offset;

OS_TCB curr_task_pcb; null_timeouts.next = NULL; //获取当前任务的优先级

err = OSTaskQuery(OS_PRIO_SELF,&curr_task_pcb); curr_prio = curr_task_pcb.OSTCBPrio; offset = curr_prio - LWIP_START_PRIO;

//判断当前任务优先级是不是tcp/ip相关任务,优先级5-9 if(offset < 0 || offset >= LWIP_TASK_MAX) {

return &null_timeouts; }

return &lwip_timeouts[offset]; }

ping 192.168.1.95 –l 2000 –t,不间断用长度为2000的数据报进行ping测试,同时使用tftp客户端软件给192.168.1.95下载一个十几兆程序,同时再使用telnet连接192.168.1.95端口7(echo端口),往该端口写数测试echo功能.

在运行一段时间以后,开发板进入不再响应.我当时也是经过长时间的分析才发现是因为在低优先级任务运行ys_arch_timeouts()时被高优先级任务打断改写了curr_task_tcb的值,从而使sys_arch_timeouts返回的指针错误,进而导致系统死锁.函数sys_timeout给当前任务增加一个定时事件:

void sys_timeout(u32_t msecs, sys_timeout_handler h, void *arg) {

struct sys_timeouts *timeouts; struct sys_timeout *timeout, *t;

timeout = memp_malloc(MEMP_SYS_TIMEOUT);//为定时事件分配内存 if (timeout == NULL) { return; }

timeout->next = NULL; timeout->h = h;

timeout->arg = arg; timeout->time = msecs;

timeouts = sys_arch_timeouts();//返回当前任务定时事件链表起始指针 if (timeouts->next == NULL) {//如果链表为空直接增加该定时事件 timeouts->next = timeout; return; }

//如果链表不为空,对定时事件进行排序.注意定时事件中的time存储的是本事件 //时间相对于前一事件的时间的差值 if (timeouts->next->time > msecs) { timeouts->next->time -= msecs; timeout->next = timeouts->next; timeouts->next = timeout; } else {

for(t = timeouts->next; t != NULL; t = t->next) { timeout->time -= t->time; if (t->next == NULL || t->next->time > timeout->time) { if (t->next != NULL) {

t->next->time -= timeout->time; }

timeout->next = t->next; t->next = timeout; break; } } } }

函数sys_untimeout从当前任务定时事件链表中删除一个定时事件 void sys_untimeout(sys_timeout_handler h, void *arg) {

struct sys_timeouts *timeouts; struct sys_timeout *prev_t, *t;

timeouts = sys_arch_timeouts();//返回当前任务定时事件链表起始指针 if (timeouts->next == NULL)//如果链表为空直接返回 { return; }

//查找对应定时事件并从链表中删除.

for (t = timeouts->next, prev_t = NULL; t != NULL; prev_t = t, t = t->next) {

if ((t->h == h) && (t->arg == arg)) {

/* We have a match */

/* Unlink from previous in list */ if (prev_t == NULL) timeouts->next = t->next; else

prev_t->next = t->next;

/* If not the last one, add time of this one back to next */ if (t->next != NULL) t->next->time += t->time;

memp_free(MEMP_SYS_TIMEOUT, t); return; } } return; }

④ “mbox”的实现: ■mbox的创建

sys_mbox_t sys_mbox_new(void)

{

u8_t ucErr;

PQ_DESCR pQDesc; //从消息队列内存分区中得到一个内存块

pQDesc = OSMemGet( pQueueMem, &ucErr ); if( ucErr == OS_NO_ERR ) { //创建一个消息队列

pQDesc->pQ=OSQCreate(&(pQDesc->pvQEntries[0]), MAX_QUEUE_ENTRIES );

if( pQDesc->pQ != NULL ) { return pQDesc; } }

return SYS_MBOX_NULL; }

■发一条消息给”mbox”

const void * const pvNullPointer = 0xffffffff; void sys_mbox_post(sys_mbox_t mbox, void *data) {

INT8U err; if( !data )

data = (void*)&pvNullPointer; err= OSQPost( mbox->pQ, data); }

在ucos中,如果OSQPost (OS_EVENT *pevent, void *msg)中的msg==NULL 会返回一条OS_ERR_POST_NULL_PTR错误.而在lwip中会调用sys_mbox_post(mbox,NULL)发送一条空消息,我们在本函数中把NULL变成一个常量指针0xffffffff.

■从”mbox”中读取一条消息

#define SYS_ARCH_TIMEOUT 0xffffffff

void sys_mbox_fetch(sys_mbox_t mbox, void **msg)

{

u32_t time;

struct sys_timeouts *timeouts; struct sys_timeout *tmptimeout; sys_timeout_handler h; void *arg; again:

timeouts = sys_arch_timeouts();//返回当前任务定时事件链表起始指针 if (!timeouts || !timeouts->next) {//如果定时事件链表为空 sys_arch_mbox_fetch(mbox, msg, 0);//无超时等待消息 } else {

if (timeouts->next->time > 0) {

//如果超时事件链表不为空,而且第一个超时事件的time !=0

//带超时等待消息队列,超时时间等于超时事件链表中第一个超时事件的time, time = sys_arch_mbox_fetch(mbox, msg, timeouts->next->time); /*在后面分析中可以看到sys_arch_mbox_fetch调用了ucos中的OSQPend系统调用从消息队列中读取消息.*/

//如果”mbox”消息队列不为空,任务立刻返回,否则任务进入阻塞态.

/*需要重点说明的是sys_arch_mbox_fetch的返回值time:如果sys_arch_mbox_fetch*/

//因为超时返回,time=SYS_ARCH_TIMEOUT, //如果sys_arch_mbox_fetch因为收到消息而返回,

//time = 收到消息时刻的时间-执行sys_arch_mbox_fetch时刻的时间,单位是毫秒 //由于在ucos中任务调用OSQPend系统调用进入阻塞态,到收到消息重新开始执行 //这段时间没有记录下来,所以我们要简单修改ucos的源代码.(后面我们会看到). } else {

/*如果定时事件链表不为空,而且第一个定时事件的time ==0,表示该事件的定时*/ //时间到

time = SYS_ARCH_TIMEOUT; }

if (time == SYS_ARCH_TIMEOUT) { //一个定时事件的定时时间到 tmptimeout = timeouts->next; timeouts->next = tmptimeout->next; h = tmptimeout->h; arg = tmptimeout->arg;

memp_free(MEMP_SYS_TIMEOUT, tmptimeout);

//从内存中释放该定时事件,并执行该定时事件中的函数 if (h != NULL) { h(arg); }

/*因为定时事件中的定时时间到或者是因为sys_arch_mbo_fetch超时到而执行到*/

//这里,返回本函数开头重新等待mbox的消息 goto again; } else {

/*如果sys_arch_mbox_fetch无超时收到消息返回 则刷新定时事件链表中定时事件的time值.*/ if (time <= timeouts->next->time) { timeouts->next->time -= time; } else {

timeouts->next->time = 0; } } } }

u32_t sys_arch_mbox_fetch(sys_mbox_t mbox, void **data, u32_t timeout) {

u32_t ucErr;

u16_t ucos_timeout;

//在 lwip中 ,timeout的单位是ms // 在ucosII ,timeout 的单位是timer tick ucos_timeout = 0; if(timeout != 0){

ucos_timeout = (timeout )*( OS_TICKS_PER_SEC/1000); if(ucos_timeout < 1) ucos_timeout = 1; else if(ucos_timeout > 65535) ucos_timeout = 65535; }

//如果data!=NULL就返回消息指针, if(data != NULL){

*data = OSQPend( mbox->pQ, (u16_t)ucos_timeout, &ucErr ); }else{

OSQPend(mbox->pQ,(u16_t)ucos_timeout,&ucErr); }

//这里修改了ucos中的OSQPend系统调用,

//原来的void *OSQPend (OS_EVENT *pevent, INT16U timeout, INT8U *err) // err的返回值只有两种:收到消息就返回OS_NO_ERR,超时则返回OS_TIMEOUT /*这里先将err从8位数据改变成了16位数据 OSQPend(*pevent,timeout, INT16U *err)*/

//重新定义了OS_TIMEOUT

//在ucos中原有#define OS_TIMEOUT 20 //改为 #define OS_TIMEOUT -1

//err返回值的意义也改变了,如果超时返回OS_TIMEOUT

// 如果收到消息,则返回OSTCBCur->OSTCBDly修改部分代码如下 if (msg != (void *)0) { // Did we get a message? OSTCBCur->OSTCBMsg = (void *)0;

OSTCBCur->OSTCBStat = OS_STAT_RDY;

OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0; *err=OSTCBCur->OSTCBDly;//***************.12 OS_EXIT_CRITICAL();

return (msg); //Return message received }

//关于ucos的OSTBCur->OSTCBDly的含义请查阅ucos的书籍 if( ucErr == OS_TIMEOUT ) {

timeout = SYS_ARCH_TIMEOUT; } else {

if(*data == (void*)&pvNullPointer ) *data = NULL;

//单位转换,从ucos tick->ms

timeout = (ucos_timeout -ucErr)*(1000/ OS_TICKS_PER_SEC); }

return timeout; }

semaphone的实现和mbox类似,这里就不再重复了.

4.2 MAC和IP地址设置

4.2.1 嵌入式网络设备中MAC及IP地址的特点

MAC地址在这里特指以太网定义的一个48位(6字节)地址。每台连到以太网网络的计算机分配一个唯一的48位(6字节)数字,即它的以太网地址。为分配地址,以太网硬件制造商购买以太网地址块,在生产以太网接口硬件时顺序给它们分配。MAC地址属于物理层的地址,对于不同类型的网络,其物理址分配方式不同,但本论文中介绍的设置方法都适用。

IP地址(IP address)也叫互连网地址(Internet address)。它是TCP/IP的设计人员选择的一个类似于物理网络的编址地址。互连网上的每个主机都分配了一个唯一的32位(4字节)整数地址。IP地址属于软件协议层的地址。只要主机上运行TCP/IP协议,其IP地址的编址方式就符合这个统一标准。

在嵌入式系统中,操作系统和所有的应用软件都被固化到Flash等存储设备中。在嵌入式系统中很少使用外存。嵌入式系统的启动往往也是“自动”的,即从上电到处于工作状态,不用人的介入。这是嵌入式设备应用的要求和特点

嵌入式网络设备中的MAC及IP地址的设置有它的特点:

1.关心和接触嵌入式网络设备MAC地址的人比关心和接触通用计算机MAC地址的人多得多。 因为设计、研究和生产嵌入式网络设备的厂家比网卡的厂家多得多。

2.在嵌入式设备中往往没有硬盘,它的操作系统和应用软件通常是打包放在Flash等存储设备中。 系统启动时,把Flash中的代码释放到内存中,再在内存中运行。比如嵌入式操作系统Uclinux,在用于S3C4510B这样的带以太网接口的嵌入式设备时,把内核和应用程序代码压成一个映像文件包,在包中有网络部分MAC及IP地址。但这些MAC及IP地址的值是在编译映像文件时设定的,而且在编译后的映像文件中的值是不能直观地看到的,而且在编译后的映像文件中的值是不能直观地看到的,它是压缩了的二进制数据,不方便地映像文件中直接更改MAC及IP地址的值。

3.对于使用同一映像文件的嵌入式网络设备,如果不做进一步的处理,其MAC及IP地址是相同的。这显然不能满足应用,因为不同的设备应该有不同的MAC及IP地址。而编译生成映像文件往往要用十几甚至几十分钟。对于生产厂家,不可能为每台设备编译一个特定的映像文件。 4.2.2 MAC及IP地址的设置

本嵌入式网络设备系统的MAC及IP地址设置的基本思想是:把MAC及IP地址存放在Flash的未用扇区(一般在高扇区),嵌入式操作系统启动后,自动运行一个程序去读取MAC及IP地址并设置它。

用户如何把MAC及IP地址放到Flash中?通过计算机串口与网络设备的RS232接口(即串口)相连,使用超级终端的方式,运行网络设备中的程序把数据写入Flash中。

先运行提供的T文件出现界面如下图4-3所示:

选择好要下载的文件所在的目录。注意IP 地址要和 ARM 板上的一样,可以改

PC的,可以改PC的,也可以改ARMBOOT的,怎样改,在参数设置里会说的。文件名称也在参数设置里设置。

配置好后把它缩进任务栏(不可关闭)。

运行T (不输入地址就取默认地址 0X0C008000) 装载成功后 GO 0X0C008000 就可以运行。

下图4-4是下载并运行 test.bin 的一个例子(提供源码)

图4-3 T文件出现界面

图4-4 利用tftp下载并运行 test.bin 的一个例子

3:参数设置和程序自动装载运行:

到了这儿就不得不提参数的设置了: 输入 printenv 命令,如下图4-5:

有几条是常用的设置,说一下: 设置PC 机 的IP地址:

setenv severip 192.168.0.10 存储一下: saveenv 如下图4-6:

图4-5 输入 printenv 命令的结果界面 设置波特率:

setenv baudrate 115200

saveenv(不存入FLASH 下次启动无效) 设置下载文件名:

setenv bootfile “44btest.bin”(加上引号) saveenv

设置启动时倒记时(单位秒) setenv bootdelay 9 saveenv

也可以一次设置多个参数最后再 saveenv

其中 bootcmd 参数是设置启动后倒记时到后的自动执行命令的,非常有用, 可以用

批处理的方法来执行,象 DOS 的AUTOEXEC.BAT 例如:设置启动后显示帮助文件,并且自动从网口下载 BOOTFILE设置的文件并运行: setenv bootcmd ?\\ ; tftp\\ ; go 0x0c008000 saveenv

其中“\\ ; ”表示一个命令的延续 “\\ ”是转义符。“;”表示多命令隔开

这样也可以实现依次执行多命令 如: ?;go 0x0c008000 表示显示帮助,再转到

0x0c008000执行

图4-6 输入 saveenv 命令的结果界面

4.3 串口驱动的实现

4.3.1 串口原理简介

1. 异步串行I/O

异步串行方式是将数据的每一个字符一位接一位(例如先低位、后高位)地传送。数据的各不同位可以分时使用同一传输通道,因此串行I/O可以减少信号连线,最少用一对线即可进行。接收放对于同一根线上一连串的数字信号,首先要分割成位,再按位组成字符。为了恢复发送的信息,双方必须协调工作。在微型计算机中大量使用异步串行I/O方式,双方使用各自的时钟信号,而且允许时钟频率有一定误差,因此实现较容易。但是由于每个字符都要独立确定起始和结束(即每个字符都要重新同

步),字符和字符间还可能有长度不定的空闲时间,因此效率较低。

图4-7 异步串行通信中一个字符的传送格式

图4-7给出异步串行通信中一个字符的传送格式。开始前,线路处于空闲状态,送出连续“1”。传送开始时首先发一个“0”作为起始位,然后出现在通信线上的是字符的二进制编码数据。每个字符的数据位长可以约定为5位、6位、7位或8位,一般采用ASC编码。后面是奇偶校验位,根据约定,用奇偶校验位将所传字符中为“1”的位数凑成奇数个或偶数个。也可以约定不要奇偶校验,这样就取消奇偶校验位。最后是表示停止位的“1”信号,这个停止位可以约定持续1位、1.5位或2位的时间宽度。至此一个字符传送完毕,线路有进入空闲,持续为“1”。经过一段随机的时间后,下一个字符开始传送又发出起始位。

每一个数据位的宽度等于传送波特率的倒数。微机异步串行通信中,常用的波特率为50,95,110,150,300,600,1200,2400,4800,9600等。

接收芳按约定的格式接收数据,并进行检查,可以查出以下三种错误: 1.奇偶错 在约定奇偶检查的情况下,接收到的字符奇偶状态和约定不符。 2.贞格式错 一个字符从起始位到停止位的总数不对。

3.溢出错 若先接收的字符尚未被微机读取,后面的字符又传送过来,则产生

溢出错,每一种错误都会给出相应的出错信息,提示用户处理。 串口寄存器如下表4-1和4-2所示: 表4-1 串口寄存器 REGISTOR ADDRESS ULCON0 ULCON1

0X01D0000 0X01D4000

R/W R/W R/W

DESCRIPTION

UART CHANNEL 0 LINE CONTROL REGISTOR UART CHANNEL 1 LINE CONTROL REGISTOR

表4-2 串口寄存器

ULCON n RESERVED INFRA-RED MODE PARTITY MODE NUMBER OF STOP BIT WORD LENGTH

由上表可以看出,该串口寄存器的第六位决定是否使用红外模式,为5-3决定校验方式,位2决定停止位长度,位1,0决定每帧的数据位数。

UART控制寄存器UCONn,该寄存器决定UART的各种模式。UART FIFO控制寄存器UFCONn,UFCONn的第0位决定是否启用FIFO,UMCONn的第0位是请求发送位。 最重要的两个寄存器是发送寄存器UTXH 和接收寄存器URXH,我们最终实现串口通信就要用到这两个寄存器的相关函数。

BIT [ 7 ] [ 6 ] [ 5:3 ] [ 2 ] [ 1:0 ]

4.3.2 在µC/OS-Ⅱ操作系统上实现ARM串口功能

(1)在main函数中添加串行口的寄存器初始化代码,并添加串行口扫描任务。

串口扫描任务如下:

void Uart_Scan_Task1(void *Id) {

char c1;

POSMSG pmsg1; for (;;){ if(Uart_Getch(&c1,0,1)) { pmsg1=OSCreateMessage(NULL,OSM_SERIAL,0,c1); if(pmsg1) SendMessage(pmsg1); } }

(2)当系统收到串口信息时,将会自动向主任务发送一个串口消息。主任务接收到该消息,将会调用响应函数。 响应函数如下:

void onSerial(int portn, char c) {

LCD_ChangeMode(DspTxtMode);

LCD_printf(\"%c\\n\ Uart_SendByte(0,c); }

(3)添加主任务

void Main_Task(void *Id) //Main_Test_Task {

POSMSG pMsg=0;

ClearScreen();

//消息循环 for(;;){

pMsg=WaitMessage(0); //等待消息 switch(pMsg->Message){ case OSM_KEY: onKey(pMsg->WParam,pMsg->LParam); break;

case OSM_SERIAL: onSerial(pMsg->WParam,pMsg->LParam); break; }

4.3.3 整个网络转串口驱动的实现

我们开发的这个驱动的任务是要实现是使开发板能从网口下载文件,然后从串口输出到液晶屏。要实现文件能从开发板的网口下载,则需要移植一个在µC/OS-Ⅱ下的一个TCP/IP协议栈:Lwip。然后设置开发板的MAC地址和IP地址。如果应用层程序驻留tcp/ip进程中,那应用层程序就利用内部回调函数口(Raw API)和tcp/ip协议栈通讯.对于µC/OS来说进程就是一个系统任务。然后通过tftp软件从网口下载文件到开发板中存贮器中的指定地址。在应用层则调用µC/OS-Ⅱ的几个ARI函数来完成对文件的操作,最后用LCD_printf函数来完成字符在点阵液晶上的显示。串口驱动也是通过调用API函数,然后在main函数中建立一个串口主任务来完成串口的对数据的接收和发送。

整个驱动的原理框图如下图4-8所示:

点阵液晶 S3C44B0X 网络 µC/OS-Ⅱ+lwip PC tftp 串口调用

图4-8 整个驱动的原理框图 整个网络转串口驱动的代码如下:

#include\"..\ii\\includes.h\" // uC/OSII interface #include \"..\ii\\add\\osaddition.h\" #include \"..\\inc\\drv.h\" #include

#include \"..\\inc\\OSFile.h\" //文件类函数头文件

/******************任务定义***************/

OS_STK Main_Stack[STACKSIZE*8]={0, }; //Main_Test_Task堆栈 void Main_Task(void *Id); //Main_Test_Task #define Main_Task_Prio 12

OS_STK Led_Flash_Stack[STACKSIZE]= {0, }; //LED闪烁任务堆栈 void Led_Flash_Task(void *Id); //LED闪烁任务 #define Led_Flash_Prio 60

OS_STK Uart_Scan_Stack1[STACKSIZE]= {0, }; //Uart_Scan_Task1堆栈 void Uart_Scan_Task1(void *Id); //串行口扫描任务 #define Uart_Scan_Task1_Prio 10

/**************已经定义的OS任务*************/ #define Main_Task_Prio 12

#define Key_Scan_Task_Prio 58 /*键盘扫描任务(可以在人工控制开发板时用到*/

#define Lcd_Fresh_prio 59 //LED闪烁任务

#define Led_Flash_Prio 60 //LED闪烁任务

/*****************事件定义*****************/

OS_EVENT *Nand_Rw_Sem; //Nand_Flash读写控制权旗语 //and you can use it as folloeing:

Nand_Rw_Sem=OSSemCreate(1); /*创建Nand-Flash读写控制权旗语,初值为1满足互斥条件*/

OSSemPend(Nand_Rw_Sem,0,&err); OSSemPost(Nand_Rw_Sem);

OS_EVENT *Uart_Rw_Sem; //Uart读写控制权旗语 //and you can use it as folloeing:

Uart_Rw_Sem=OSSemCreate(1); /*创建Uart读写控制权旗语,初值为1满足互斥条件*/

OSSemPend(Uart_Rw_Sem,0,&err); OSSemPost(Uart_Rw_Sem);

//////////////////////////////////////////////////////////

void Led_Flash_Task(void *Id)//指示RTOS处于正常工作中 {

unsigned char led_state; Uart_Printf(0,\"\\n10\"); for (;;) {

Led_Display(led_state); led_state=~led_state; OSTimeDly(250); }

}//Led_Flash_Task

void Uart_Scan_Task1(void *Id) {

char c1;

POSMSG pmsg1; for (;;){

if(Uart_Getch(&c1,0,1)) {

pmsg1=OSCreateMessage(NULL,OSM_SERIAL,0,c1); if(pmsg1)

SendMessage(pmsg1); } }

}//Uart_Scan_Task

void initOSGUI()//初始化操作系统的图形界面 {

initOSMessage(); //系统消息初始化 initOSList(); initOSDC();

initOSCtrl(); //键盘控制初始化 initOSFile(); //系统文件初始化 }

/******************Main function.***********************/

int Main(int argc, char **argv) {

ARMTargetInit(); //执行目标文件(uHAL based ARM system) initialisation

OSInit(); // uC/OS-II系统初始化 uHALr_ResetMMU(); //mmu重置

LCD_Init(); //初始化LCD模块 LCD_printf(\"LCD initialization is OK\\n\"); LCD_printf(\"320 x 240 Text Mode\\n\");

LoadFont(); //载入字体

LoadConfigSys(); //载入系统硬件配置文件

// create the tasks in uC/OS and assign increasing //

// priorities to them so that Task3 at the end of //

// the pipeline has the highest priority. //

LCD_printf(\"Create task on uCOS-II...\\n\");

OSTaskCreate(Main_Task, (void *)0, (OS_STK *)&Main_Stack[STACKSIZE*8-1], Main_Task_Prio);// 1

OSTaskCreate(Led_Flash_Task, (void *)0, (OS_STK *)&Led_Flash_Stack[STACKSIZE-1], Led_Flash_Prio );// 10 OSTaskCreate(Uart_Scan_Task1, (void *)0, (OS_STK

*)&Uart_Scan_Stack1[STACKSIZE-1], Uart_Scan_Task1_Prio );// 11

OSAddTask_Init();

LCD_printf(\"Starting uCOS-II...\\n\");

//LCD_printf(\"Entering graph mode...\\n\"); //LCD_ChangeMode(DspGraMode);

initOSGUI(); //载入系统图形界面 InitRtc();

Nand_Rw_Sem=OSSemCreate(1); /*创建Nand-Flash读写控制权旗语,初值为1满足互斥条件*/

ARMTargetStart(); //Start the (uHAL based ARM system) system running //

OSStart(); // start the system //

// never reached // return 0; }//main

///////////////////////////////////////////////////////////////////////////////////

char Text[]={'T','E','S','T',' ',' ',' ',' ','T','X','T',0};

void onSerial(int portn,char c);

void Main_Task(void *Id) //Main_Test_Task {

POSMSG pMsg=0; char str[256];

FILE* pfile; ClearScreen();

//消息循环 for(;;){

pMsg=WaitMessage(0); //等待消息 switch(pMsg->Message){ case OSM_KEY:

onKey(pMsg->WParam,pMsg->LParam); break;

case OSM_SERIAL:

onSerial(pMsg->WParam,pMsg->LParam); break;

}

DeleteMessage(pMsg);//删除消息,释放资源 } }

////////////////打开文件并读取字符///////////////////////

LCD_ChangeMode(DspTxtMode);//改变显示模式 LCD_Cls();

p(Text);

if(p){

LCD_printf(\"Can't Open file!\\n\"); for(;;)

OSTimeDly(1000); }

while(LineReadOS, str)>2){ str[strlen(str)-2]='\\n'; str[strlen(str)-1]=0;

LCD_printf(str); //str为读出的字符变量 }

CloseOS);

for(;;)

OSTimeDly(1000);

/////////////////////文件读取完毕,将字符转由串口发送////////////////////////

void onSerial(int portn, char str) {

LCD_ChangeMode(DspTxtMode); LCD_printf(\"%c\\n\

Uart_SendByte(0,str); //将字符str序列发送到串口 }

结 论

嵌入式系统是一个很有发展空间的领域,以嵌入式系统为标志的后PC时代已经到来。但嵌入式系统是一个软硬件结合很紧密的课题,特别是涉及到bios开发、bootloader开发方面,则要求你对硬件要有一个较深的了解。所以学习嵌入式需要掌握了解很多知识,所以学习顺序一定不能乱,在了解嵌入式系统开发的体系结构后,一步一步的下手。搭建开发环境需要:硬件平台, 编译器, 调试器, RTOS, C/C++库, 协议栈等。建议有志于想把嵌入式学好者最好先分析一种RTOS的源代码--UCOS最容易;再分析一种通讯协议栈的实现方式--TCP/IP最实用 ;精通一种单片机的开发集成环境--keil C最经典;精通一种MCU的开发集成环境--ADS 1.2最流行。

通过这次毕业设计,我对计算机硬件方面,和对计算机的整个体系结构从底层驱动到最高应用层以及中间那些协议都有了比较有了一些较具体深入的认识。对日益渗入到人们生活的各个领域嵌入式系统的设计、开发过程也有了一个详细的了解。对我认识问题分析问题解决问题的能力是一个极好的煅炼,对我的计算机水平也是一个极大的提高。

总之,这次毕业设计,我受益匪浅。

致 谢

首先,感谢我的指导老师邬芝权老师,在毕业设计期间,邬老师耐心教导,从很多方面都给予了我悉心的指导。邬老师以其谦和的风范、和严谨治学的态度以及理论联系实践的实干精神都深深地感染了我。我在毕业设计期间,学会了在研究中认识问题、分析问题、解决问题的思维和方法。这一切,令我受用终生。

其次,感谢同在邬老师指导下做设计的同学,在毕业设计期间,大家相互学习、相互计论、相互研究,提出了有创意的见解,提供了大量有用的资料,丰富了我的论文内容。

最后,感谢所有关心和帮助过我们的人们。

参考文献

[1] 耿德根 等编著.《AVR高速嵌入式单片机原理与应用》. 修订版.北京航空航天大学出版社,2002年[2] 牛德芳 主编.《半导体传感器原理及其应用》.大连理工大学出版社,1993年[3] 李朝青 编著.《单片微机原理及其应用技术》.南开大学出版社,1999年[4] 李道华 等编著.《传感器电路分析与设计》.武汉大学出版社,2000年[5] 谭浩强 著.《C程序设计》. 第二版.清华大学出版社,2002年 [6] [7]21ic.com

[8]刘峥嵘 张智超 许振山 等编著.郝文化 审.《嵌入式Linux应用开发详解》. 机械工业出版社,2004.6

[9]马忠梅 李善平 康慨 叶楠 编著.《ARM & Linux嵌入式系统教程》. 北京航空航天大学出版社,2004.9

因篇幅问题不能全部显示,请点此查看更多更全内容