Tải bản đầy đủ (.pdf) (6 trang)

HandBooks Professional Java-C-Scrip-SQL part 198 pdf

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (32.91 KB, 6 trang )

flashBase[UNLOCK2_OFFSET] = FLASH_CMD_UNLOCK2;
flashBase[COMMAND_OFFSET] = FLASH_CMD_BYTE_PROGRAM;
/*
* Perform the actual write operation.
*/
baseAddress[offset] = data[offset];
/*
* Wait for the operation to complete or time-out.
*/
while (((baseAddress[offset] & DQ7) != (data[offset] & DQ7)) &&
!(baseAddress[offset] & DQ5));
if ((baseAddress[offset] & DQ7) != (data[offset] & DQ7))
{
break;
}
}
return (offset);
} /* flashWrite() */
/******************************************************************
****
*
* Function: flashErase()
*
* Description: Erase a block of the Flash memory device.
*
* Notes: This function is specific to the AMD 29F010 Flash
* memory. In this device, individual sectors may be
* hardware protected. If this algorithm encounters
* a protected sector, the erase operation will fail
* without notice.
*


* Returns: O on success.
* Otherwise -1 indicates failure.
*
******************************************************************
****/
int
flashErase(unsigned char * sectorAddress)
{
unsigned char * flashBase = FLASH_BASE;
/*
* Issue the command sequence for sector erase.
*/
flashBase[UNLOCK1_OFFSET] = FLASH_CMD_UNLOCK1;
flashBase[UNLOCK2_OFFSET] = FLASH_CMD_UNLOCK2;
flashBase[COMMAND_OFFSET] = FLASH_CMD_ERASE_SETUP;
flashBase[UNLOCK1_OFFSET] = FLASH_CMD_UNLOCK1;
flashBase[UNLOCK2_OFFSET] = FLASH_CMD_UNLOCK2;
*sectorAddress = FLASH_CMD_SECTOR_ERASE;
/*
* Wait for the operation to complete or time-out.
*/
while (!(*sectorAddress & DQ7) && !(*sectorAddress & DQ5));
if (!(*sectorAddress & DQ7))
{
return (-1);
}
return (0);
} /* flashErase() */
Of course, this is just one possible way to interface to a Flash memory—and not a
particularly advanced one at that. In particular, this implementation does not

handle any of the chip's possible errors. What if the erase operation never
completes? The function flashErase will just keep spinning its wheels, waiting for
that to occur. A more robust implementation would use a software time-out as a
backup. For example, if the Flash device doesn't respond within twice the
maximum expected time (as stated in the databook), the routine could stop polling
and indicate the error to the caller (or user) in some way.
Another thing that people sometimes do with Flash memory is to implement a
small filesystem. Because the Flash memory provides nonvolatile storage that is
also rewriteable, it can be thought of as similar to any other secondary storage
system, such as a hard drive. In the filesystem case, the functions provided by the
driver would be more file-oriented. Standard filesystem functions like open, close,
read, and write provide a good starting point for the driver's programming
interface. The underlying filesystem structure can be as simple or complex as your
system requires. However, a well-understood format like the File Allocation Table
(FAT) structure used by DOS is good enough for most embedded projects.
[1] 128 kilobytes is one-eighth of the total 1-megabyte address space.
[2] The divisor is simply a binary representation of the coefficients of the
generator polynomial—each of which is either or 1. To make this even more
confusing, the highest-order coefficient of the generator polynomial (always a 1) is
left out of the binary representation. For example, the polynomial in the first
standard, CCITT, has four nonzero coefficients. But the corresponding binary
representation has only three 1's in it (bits 12, 5, and 0).
[3] There is one other potential twist called "reflection" that my code does not
support. You probably won't need that anyway.
[4] There is one small difference worth noting here. The erase and write cycles
take longer than the read cycle. So if a read is attempted in the middle of one of
those operations, the result will be either delayed or incorrect, depending on the
device.
Chapter 7. Peripherals
 7.1 Control and Status Registers

 7.2 The Device Driver Philosophy
 7.3 A Simple Timer Driver
 7.4 Das Blinkenlights, Revisited
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'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'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'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'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'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'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'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'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's to be expected. The goal should be to create a programming interface that
would not need to be changed if the underlying peripheral were replaced with

×