红外遥控开关设计外文翻译资料

 2022-03-26 19:25:24

. Peripherals

Each pizza glides into a slot like a circuit board into a computer, clicks into place as the smart box interfaces with the onboard system of the car. The address of the customer is communicated to the car, which computes and projects the optimal route on a heads-up display.

—Neal Stephenson, Snow Crash

In addition to the processor and memory, most embedded systems contain a handful of other hardware devices. Some of these devices are specific to the application domain, while others—like timers and serial ports—are useful in a wide variety of systems. The most generically useful of these are often included within the same chip as the processor and are called internal, or on-chip, peripherals. Hardware devices that reside outside the processor chip are, therefore, said to be external peripherals. In this chapter we#39;ll discuss the most common software issues that arise when interfacing to a peripheral of either type.

7.1 Control and Status Registers

The basic interface between an embedded processor and a peripheral device is a set of control and status registers. These registers are part of the peripheral hardware, and their locations, size, and individual meanings are features of the peripheral. For example, the registers within a serial controller are very different from those in a timer/counter. In this section, I#39;ll describe how to manipulate the contents of these control and status registers directly from your C/C programs.

Depending upon the design of the processor and board, peripheral devices are located either in the processor#39;s memory space or within the I/O space. In fact, it is common for embedded systems to include some peripherals of each type. These are called memory-mapped and I/O-mapped peripherals, respectively. Of the two types, memory-mapped peripherals are generally easier to work with and are increasingly popular.

Memory-mapped control and status registers can be made to look just like ordinary variables. To do this, you need simply declare a pointer to the register, or block of registers, and set the value of the pointer explicitly. For example, if the P2LTCH register from Chapter 2, were memory-mapped and located at physical address 7205Eh, we could have implemented toggleLed entirely in C, as shown below. A pointer to an unsigned short—a 16-bit register—is declared and explicitly initialized to the address 0x7200:005E. From that point on, the pointer to the register looks just like a pointer to any other integer variable:

unsigned short * pP2LTCH = (unsigned short *) 0x7200005E;

void

toggleLed(void)

{

*pP2LTCH ^= LED_GREEN; /* Read, xor, and modify. */

} /* toggleLed() */

Note, however, that there is one very important difference between device registers and ordinary variables. The contents of a device register can change without the knowledge or intervention of your program. That#39;s because the register contents can also be modified by the peripheral hardware. By contrast, the contents of a variable will not change unless your program modifies them explicitly. For that reason, we say that the contents of a device register are volatile, or subject to change without notice.

The C/C keyword volatile should be used when declaring pointers to device registers. This warns the compiler not to make any assumptions about the data stored at that address. For example, if the compiler sees a write to the volatile location followed by another write to that same location, it will not assume that the first write is an unnecessary use of processor time. In other words, the keyword volatile instructs the optimization phase of the compiler to treat that variable as though its behavior cannot be predicted at compile time.

Here#39;s an example of the use of volatile to warn the compiler about the P2LTCH register in the previous code listing:

volatile unsigned short * pP2LTCH = (unsigned short *) 0x7200005E;

It would be wrong to interpret this statement to mean that the pointer itself is volatile. In fact, the value of the variable pP2LTCH will remain 0x7200005E for the duration of the program (unless it is changed somewhere else, of course). Rather, it is the data pointed to that is subject to change without notice. This is a very subtle point, and it is easy to confuse yourself by thinking about it too much. Just remember that the location of a register is fixed, though its contents might not be. And if you use the volatile keyword, the compiler will assume the same.

The primary disadvantage of the other type of device registers, I/O-mapped registers, is that there is no standard way to access them from C or C . Such registers are accessible only with the help of special machine-language instructions. And these processor-specific instructions are not supported by the C or C language standards. So it is necessary to use special library routines or inline assembly (as we did in Chapter 2) to read and write the registers of an I/O-mapped device.

7.2 The Device Driver Philosophy

When it comes to designing device drivers, you should always focus on one easily stated goal: hide the hardware completely. When you#39;re finished, you want the device driver module to be the only piece of software in the entire system that reads or writes that particular device#39;s control and status registers directly. In addition, if the device generates any interrupts, the interrupt service routine that responds to them should be an integral part of the device driver. In this section, I#39;ll explain why I recommend this philosophy and how it can be achieved.

Of course, attempts to hide the hardware completely are difficult. Any programming interface you select will reflect the broad features of the device. That#39;s to be expected. The goal should be to create a programming interface that would not need to be changed if the underlying

全文共34188字,剩余内容已隐藏,支付完成后下载完整资料


7.外围设备

每一个比萨饼滑到一个槽道中,像是一个电路板滑到一个 计算机里一样,而这个位置又好像是汽车里随车携带的系统与只能盒之间的接口。客户的地址传达给汽车,汽车在一个平视的显示器上计算盒计划最优的行车路线。

——Neal Stephenson 《Snow Crash》

除了处理器和存储器之外,大部分嵌入式系统还包含一些其他的硬件设备。一些设备是专门用于应用领域的,而其他设备如时钟和串行端口。在各种系统中都是有用的。这些设备常常和处理器包含在同一个芯片里,它们叫作内部外围设备或者芯片级外围设备。因此,驻留在处理器芯片以外的硬件设备叫作外部外围设备。这一章里,我们要讨论在与这两种外围设备(简称外设)接口的时候产生的软件问题。

7.1控制和状态寄存器

在嵌入式芯片和一个外围设备之间最基本的接口就是一组控制和状态寄存器。这些寄存器是外围设备硬件的一部分,它们的位置、大小以及具体的意义表明了外围设备的特性。比如,串行端口的寄存器不同于一个时钟/计数器。本节,我们要讨论怎样直接通过 C/C 程序操纵这些控制和状态寄存器中的内容。

根据处理器和板子的设计,外围设备位于处理器的存储空间或者输入输出空间里。实际上,嵌入式系统含有这两种类型的外设是很普遍的。它们分别被叫作存储映像外设和输入输出映像外设。这两种类型中,存储映像外设通常更加容易使用,逐渐地流行起来。

在储映像控制和状态寄存器可以看作是普通变量。要做到这一点,你仅仅需要声明一个指向寄存器或者寄存器块的指针,并且显式地设置指针的值。比如,如果第二章“你的第一个嵌入式程序”中的寄存器 P2LTCH 是存储映象的,并且位于物理地址 7205Eh,那么我们就可以完全用 C 来实现 toggleLed(),如下所示。一个指向 unsigned short 的指针——一个 16 位的寄存器——被声明并且被显式地初始化为地址 Ox7200:005E。从那个点开始,指向寄存器的指针看上去就好像是任何一个指向其他整数变量的指针:

unsigned short * pP2LTCH = (unsigned short *) 0x7200005E;

void

toggleLed(void)

{

*pP2LTCH ^= LED_GREEN; /* Read, xor, and modify. */

} /* toggleLed() */

然而,请注意,在设备寄存器和普通变量之间有着显著的差别。设备寄存器的内容会在你的程序不知道或者不介入的情况下发生改变。那是因为寄存器的内容还会被外设硬件修改。相反,变量中的内容不会改变。除非你的程序显式地修改了它们。因此,我们说设备寄存器的内容是易失的,或者会在不注意的时候被改变。

当声明指向设备寄存器的指针的时候,应该使用 C/C 的关键字 volatile。它会警告编译器不要对存储在这个地址里的数据做任何假设。比如,如果编译器看见两个紧跟着的向同一个易失位置的写操作。它不会假设第一个操作是在不必要地浪费处理器时间。换言之,关键字 volatile 提醒编译器,在优化阶段处理那个变量时,应该把它的行为看成在编译时无法预知。

这里有一个例子,使用 volatile 向编译器发出警告,注意前面的代码列表里

的寄存器 P2LTCH。

volatile unsigned short * pP2LTCH = (unsigned short *) 0x7200005E;

如果你把这个语句理解为“这个指针本身是易失的”,将是错误的。实际上,在 pP2LTH 中的变量在程序的运行期间会保持 0x7200005E(当然,除非它在别的地里想的太多就容易把自己搞糊涂。只要记住寄存器的位置是固定的,但是

其内容却不一定。如果你使用了 volatile 关键字,编译器也会这样认为。

另一种设备寄存器,I/O 映像寄存器的主要的缺点是 C/C 没有标准的方法来访问它们。这种寄存器只有通过特殊机器指令才是可访问的。这些处理器专用的指令是不被标准的 C/C 语言所支持的。因此有必要利用一个特殊的库例程或者是内联的汇编(就像我们在第二章做的那样)来读写一个 I/O 映像设备的寄存器。

7.2设备驱动原理

当开始设计设备驱动的时候,你应该总是把注意力集中在一个简单的规定目标上:完全隐藏硬件。当完成的时候,你的设备驱动模块应该是整个系统中唯一的一个直接读写某一个具体设备的控制和状态寄存器的软件。此外,如果设备产生任何中断,响应它们的中断服务例程应该是设备驱动完整的一部分。在本节中,我要解释为什么我要推荐这个原理以及它是如何实现的。

当然,企图完全隐藏硬件是困难的。你选择的任何编程接口都会反映设备广泛的特性。那是所期望的。目标应该是这样一个编程接口,这个接口在底层的外设被另一个它的通用型所替换时不需要改变。比如,所有的快闪存储设备共享一个扇区的概念(尽管扇区的大小可能在芯片间有所不同)。擦除操作只能在整个扇区上执行,并且一旦擦除,每一个字节或者字可以被重写。因此,上一章快速存储器驱动提供的编程接口应该适用于任何快速存储设备。AMD 29F010 的特性在这一层如愿以偿地被隐藏了。

嵌入式系统的设备驱动与工作站的差别很大。一个现代的计算机工作站,设备驱动最经常涉及的是满足操作系统的需要。比如,工作站操作系统通常对于它们本身与网卡之间的软件接口施加一个严格的要求,而不管底层硬件的特性和能力。想要用网卡的应用程序只能使用操作系统提供的网络 API,而不能直接访问网卡。这种情况下,完全隐藏硬件的目标容易满足。

相反,在嵌入式系统中的应用软件能够容易地访问你的硬件。实际上,因为所有的软件都连接在一起成为一个二进制映像,甚至在应用软件,操作系统以及设备方被修改了)。而它指向的那个数据会在无意间被改动。这是十分微妙的一点,而驱动之间几乎没有什么区别。这些界线的划分以及强制限制对硬件的

访问纯粹是软件开发者的责任。这两条是开发者必须有意识地做出的决定。换言之,在软件设计上,嵌入式程序员们可能比他们非嵌入式的同行更容易作弊。

好的设备驱动设计程序的好处有三点。第一,因为模块化,软件的总体结构更容易理解。第二,因为只有一个模块曾经直接和外设的寄存器相互作用,硬件的状态能更加容易地被跟踪。最后一点,但也是相当重要的一点,由硬件变动导致的软件变化集中在设备驱动程序上。这些好处都有助于减少嵌入式系统中程序错误的总数。但是你设计时不得不花一点精力来实现它们。

如果你认可把所有硬件特性以及相互作用隐藏在设备驱动里的原理。设备驱动通常由下面列出的五个部分组成。为了使得驱动的实现尽可能的简单和循序渐进,这些元素应该按照它们出现的顺序来开发。

1. 覆盖设备的存储映像控制及状态寄存器的数据结构

驱动程序开发过程的第一步是创建一个 C 风格的 struct,它看上去就好像你的设备的存储映像寄存器。这通常要研究外设的数据手册并且创建一个控制及状态寄存器和它们偏移地址的表。然后从最低偏移处的寄存器开始填充 struct。(如果一个或者多个位置是未用的,或者是保留的,一定要把哑元变量放在那里以填充这个附加的位置。)

一个这样数据结构的例子如下所示。这个结构描述了 80188EB 处理器芯片内其中的一个时钟 / 计数器单元。设备有三个寄存器,安排如下面的 TimerCounter 数据结构所示。每一个寄存器是 16 位宽,因此应该被看作一个无符号的整数,尽管它们中的一个 control 寄存器,实际上是一个个单独的符号位的集合。

struct TimerCounter

{

unsigned short count; // Current Count, offset 0x00

unsigned short maxCountA; // Maximum Count, offset 0x02

unsigned short _reserved; // Unused Space, offset 0x04

unsigned short control; // Control Bits, offset 0x06

};

为了使得在 control 寄存器中的位易于单独地读写,我们可能也要定义下面的位屏蔽;

#define TIMER_ENABLE 0xC000 // Enable the timer.

#define TIMER_DISABLE 0x4000 // Disable the timer.

#define TIMER_INTERRUPT 0x2000 // Enable timer interrupts.

#define TIMER_MAXCOUNT 0x0020 // Timer complete?

#define TIMER_PERIODIC 0x0001 // Periodic timer?

2. 跟踪目前硬件和设备驱动状态的一组变量

驱动程序开发过程的第二步是确定需要那个变量来跟踪硬件和设备驱动的状态。比如,前面所讲的时钟/计数器单元的例子里,我们可能要知道硬件是否已经被初始化。而且,如果已经初始化,可能还要知道正在运行倒计数的计数器的值。

一些设备的驱动创建了不只一个软件设备。这纯粹是一个逻辑设备,它实现于基本外围硬件之上。比如,容易设想从单独的一个时钟/计数器单元可以创建多个的软件时钟。时钟/计数器单元应该被设置以产生一个周期性的时钟节拍(tick),设备驱动则通过保持每一个软件时钟的状态信息来管理一组不同长度的软件时钟。

3. 一个把硬件初始比到已知状态的例程

一旦你知道怎样跟踪物理和逻辑设备的状态,那么就该开始写实际与设备交互及控制设备的函数了。最好是从一个硬件的初始比例程开始,那是一个熟悉设备交互的好方法。

4. 合起来为设备驱动的用户提供 API 的一组例程

你成功地初始化设备之后,你就可以开始向驱动程序中加人其他的功能。如果顺利的话,你已经决定了各个不同例程的命名和目的,以及它们各自的参数和返回值。现在所要做的就是实现并测试它们。我们在下一部分将看到这种例程的例子。

5. 中断服务例程

最好在启用中断之前,就已经设计,实现并且测试了大部分的设备驱动例程。确定那些与中断相关问题的来源是十分具有挑战性的工作。如果你把其他驱动模块的错误与之混合起来,确定错误的来源甚至是不可能的。最好使用轮询来添补驱动工作之间的间隙。那样当你开始查找中断问题来源的时候,你就知道设备是怎样工作的(以及它确实在工作)。而且,可以肯定会有一些这样的错误。

7.3一个简单的时钟驱动

我们要讨论的设备驱动的例子是用来控制 80199EB 处理器的一个时钟/计数器单元。我选择用 C 来实现这个程序——并且本书所有剩下的例子部将用 C 来实现。尽管 C 在访问硬件寄存器方面没有提供比 C 更多的附加帮助,但是在对于这种类型的抽象方面有很多好的理由使用它。最明显的是,C 类与任何 C 特性或者编程技巧相比,允许我们更完全地隐藏实际的便件接口。比如,可以加入一个构造函数,在每一次新的时钟对象被声明的时候自动地设置硬件。这省去了应用软件对初始比例程的显式调用的需要。此外,有可能把对应于设备寄存器的数据结构隐藏在相关类的私有部分。这有助于防止应用程序员从程序的其他部分意外的读写设备案存器。

Timer 类的定义如下:

enum TimerState { Idle, Active, Done };

enum TimerType { OneShot, Periodic };

class Timer

{

public:

Timer();

~Timer();

int start(unsigned int nMilliseconds, TimerType = OneShot);

int waitfor();

void cancel();

TimerState state;

TimerType type;

unsigned int length;

unsigned int count;

Timer * pNext;

private:

static void interrupt Interrupt();

};

在讨论这个类的实现之前,让我们研究一下前面的声明并且考虑一下设备驱动的总体结构。我们看到的第一个东西是两个枚举类型。 TimerState 和TimerType。这些类型的主要目的是使得其他的代码更具可读性。从中,我们可以知道每一个软件时钟有一个当前状态——Idle、Act

全文共15337字,剩余内容已隐藏,支付完成后下载完整资料


资料编号:[15213],资料为PDF文档或Word文档,PDF文档可免费转换为Word

原文和译文剩余内容已隐藏,您需要先支付 30元 才能查看原文和译文全部内容!立即支付

以上是毕业论文外文翻译,课题毕业论文、任务书、文献综述、开题报告、程序设计、图纸设计等资料可联系客服协助查找。