To make this as easy as possible, I have defined all of the CRC parameters as constants. To change to the CRC16 standard, simply change the values of the three constants. For CRC32, change the three constants and redefine width as type unsignedlong .
/*
* The CRC parameters. Currently configured for CCITT.
* Simply modify these to switch to another CRC standard.
*/
#define POLYNOMIAL 0x1021
#define INITIAL_REMAINDER 0xFFFF
#define FINAL_XOR_VALUE 0x0000
/*
* The width of the CRC calculation and result.
* Modify the typedef for an 8 or 32-bit CRC standard.
*/
typedef unsigned short width;
#define WIDTH (8 * sizeof(width))
#define TOPBIT (1 << (WIDTH – 1))
The function crcInit should be called first. It implements the peculiar binary division required by the CRC algorithm. It will precompute the remainder for each of the 256 possible values of a byte of the message data. These intermediate results are stored in a global lookup table that can be used by the crcCompute function. By doing it this way, the CRC of a large message can be computed a byte at a time rather than bit by bit. This reduces the CRC calculation time significantly.
/*
* An array containing the pre-computed intermediate result for each
* possible byte of input. This is used to speed up the computation.
*/
width crcTable[256];
/**********************************************************************
*
* Function: crcInit()
*
* Description: Initialize the CRC lookup table. This table is used
* by crcCompute() to make CRC computation faster.
*
* Notes: The mod-2 binary long division is implemented here.
*
* Returns: None defined.
*
**********************************************************************/
void crcInit(void) {
width remainder;
width dividend;
int bit;
/*
* Perform binary long division, a bit at a time.
*/
for (dividend = 0; dividend < 256; dividend++) {
/*
* Initialize the remainder.
*/
remainder = dividend << (WIDTH – 8);
/*
* Shift and XOR with the polynomial.
*/
for (bit = 0; bit < 8; bit++) {
/*
* Try to divide the current data bit.
*/
if (remainder & TOPBIT) {
remainder = (remainder << 1) ^ POLYNOMIAL;
} else {
remainder = remainder << 1;
}
}
/*
* Save the result in the table.
*/
crcTable[dividend] = remainder;
}
} /* crcInit() */
Finally, we arrive at the actual workhorse routine, crcCompute . This is a routine that you can call over and over from your application to compute and verify CRC checksums. An additional benefit of splitting the computation between crcInit and crcCompute is that the crcInit function need not be executed on the embedded system. Instead, this function can be run in advance — on any computer — to produce the contents of the lookup table. The values in the table can then be stored in ROM (requiring only 256 bytes of storage) and referenced over and over by crcCompute .
/**********************************************************************
*
* Function: crcCompute()
*
* Description: Compute the CRC checksum of a binary message block.
*
* Notes: This function expects that crcInit() has been called
* first to initialize the CRC lookup table.
*
* Returns: The CRC of the data.
*
**********************************************************************/
width crcCompute(unsigned char * message, unsigned int nBytes) {
unsigned int offset;
unsigned char byte;
width remainder = INITIAL_REMAINDER;
/*
* Divide the message by the polynomial, a byte at a time.
*/
for (offset = 0; offset < nBytes; offset++) {
byte = (remainder >> (WIDTH – 8)) ^ message[offset];
remainder = crcTable[byte] ^ (remainder << 8);
}
/*
* The final remainder is the CRC result.
*/
return (remainder ^ FINAL_XOR_VALUE);
} /* crcCompute() */
6.4 Working with Flash Memory
From the programmer's viewpoint, flash is arguably the most complicated memory device ever invented. The hardware interface has improved somewhat since the original devices were introduced in 1988, but there is still a long way to go. Reading from Flash memory is fast and easy, as it should be. In fact, reading data from a Flash is not all that different from reading from any other memory device. [17] 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.
The processor simply provides the address, and the memory device returns the data stored at that location. Most Flash devices enter this type of "read" mode automatically whenever the system is reset; no special initialization sequence is required to enable reading.
Writing data to a Flash is much harder. Three factors make writes difficult. First, each memory location must be erased before it can be rewritten. If the old data is not erased, the result of the write operation will be some logical combination of the old and new values, and the stored value will usually be something other than what you intended.
The second thing that makes writes to a Flash difficult is that only one sector, or block, of the device can be erased at a time; it is impossible to erase a single byte. The size of an individual sector varies by device, but it is usually on the order of several thousand bytes. For example, the Flash device on the Arcom board — an AMD 29F010 — has eight sectors, each containing 16 kilobytes.
Finally, the process of erasing the old data and writing the new varies from one manufacturer to another and is usually rather complicated. These device programming interfaces are so awkward that it is usually best to add a layer of software to make the Flash memory easier to use. If implemented, this hardware-specific layer of software is usually called the Flash driver.
Because it can be difficult to write data to the Flash device, it often makes sense to create a Flash driver. The purpose of the Flash driver is to hide the details of a specific chip from the rest of the software. This driver should present a simple application programming interface (API) consisting of the erase and write operations. Parts of the application software that need to modify data stored in Flash memory simply call the driver to handle the details. This allows the application programmer to make high-level requests like "erase the block at address D0000h" or "write a block of data, beginning at address D4000h." It also keeps the device-specific code separate, so it can be easily modified if another manufacturer's Flash device is later used.
Читать дальше