|
Casting and Data Abstraction in C Written by Kerry Crouse When designing embedded systems, there exists the problem of accessing memory mapped registers. There are a number of methods which can be used to access memory mapped registers and setting up pointers to these locations is the most frequent method used to access these registers. Since I've been programming, I've come across several ways of specifying these registers: using the actual numeric address, using a variable containing the address, using a variable containing a pointer to the address, using a macro containing the address, using a macro containing a pointer to the address, and using a macro containing a dereferenced pointer to the address. Numeric AddressThis is simply the address of a register with an appropriate casting. For instance, a byte location at hexadecimal address 1000 would be cast as: ((unsigned char *) 0x1000)
This cast leaves us with a pointer to location 0x1000. To use this pointer, we must dereference the pointer using an additional ‘*' so we can actually assign data. The construct appears as: *((unsigned char *) 0x1000) = 86;
The advantage of this approach is the visibility of the address and the speed of execution. Upon inspection we can see this is data. Variable StorageA variable may be used to hold the address. This approach ends up very similar to the Numeric Address approach. For example: unsigned char *SerRcv = (unsigned char *) 0x1000;
*SerRcv = 86; The advantages of the Variable storage approach is that the address can be modified at any time, the address can be changed in one place, and a symbolic name can be used for the location. The down side of this technique is that the variable location must be calculated for each access which begs the question: how often, if ever, will the address change at run time. Macro AddressThis is similar to the Numeric address in a macro wrapper. Using the example above, the code appears as: #define SER_XMT_ADR 0x1000
An assignment appears as: *((unsigned char *) SER_XMT_ADR) = 86;
Advantages are the address can be changed at one place at compile time and a symbolic name is used. The address, however, is used in a cumbersome way. Macro Address PointerThis is the same as the Numeric address with a macro wrapper. Using the example above, the code looks like this: #define SER_XMT_P ((unsigned char *) 0x1000)
An assignment looks like this: *SER_XMT_P = 86;
The advantages are that a symbolic name is used amd the address can be changed in only one place at compile time. Dereferenced Macro Address PointerThis is almost the same as the Macro address pointer. Using the example above, the code looks like this: #define SER_XMT (*((unsigned char *) 0x1000))
An assignment looks like this: SER_XMT = 86;
The advantages are the address can be changed at one place at compile time, a representative symbolic name is used, and the location looks and behaves like a built-in register. Using Dereferenced Macro Address PointersIf the pointers are part of a larger I/O structure, the whole structure may be embedded into a macro representation of the I/O architecture. #define IO_BASE 0x1000
Other Dereferenced Macro Address PointersNot only can base address types be used as address pointers, but structures can also be used. An example follows:struct Serial
An example of a bitfield looks like this: #define SER_STAT *((char *)(SER_BASE + 8))
The serial status register is used to check the serial receiver bit. This check won't work if the bit resets after it is read. SummaryThe method of addressing a fixed memory location should be efficient from a performance standpoint, efficient from a speed perspective, easy for programmers to understand, and easy to change without breaking the code. All of the address calculations are done in a macro, therefore the performance is excellent. There are no variables involved, so the speed is fast. Due to the symbolic name, the function is easy for programmers to understand. Lastly, since the address calculations drop out of a macro, register and even board level locations can be changed easily. A macro using dereferenced cast pointer constants can provide all of these attributes. |