The Big Header file:
(Examples of typical header files can be found here)
ARM processors use memory mapped I/O. What this means is that all special registers can be accessed using the same address and data buses you would use to access variables you might use in a program. So, lets say there is a 32 bit register at address 0x12345678 that you need to write to. How can this be done in a language like C? Well the trick is to use pointers.
In C, a pointer represents the address of something. Pointers point at data storage locations that have an associated data type. This is important as it allows you to move the pointer on the next element simply by adding ‘1’ to it. The compiler figures out how far away the next data storage location is based on the type of data being pointed to. An example might help.
int K; int L; int *ptr; : : ptr = &K; // ptr now points to space occupied by variable K ptr = ptr +1; // ptr now points to space occupied by variable L, 4 bytes away.
The code above shows a trivial example where two integers are declared along with a “pointer to integer” variable. The next statement places the address of K into ptr making it ‘point at’ K. You might thing that adding 1 to K migh simply move it on 1 byte in memory, but C doesn’t work that way. Adding 1 to ptr makes it point at the next integer which is 4 bytes along (ARM uses 32 bit or 4 byte integers).
Now, how can we use this mechanism to write to a register at address 0x12345678? Well, it would be nice if we could do this:
int *ptr; ptr=0x12345678;
But, life and compilers is seldom this straightforward. If we do this we will likely get a bunch of warnings and, depending upon the compiler, some error messages. C-compilers complain if the datatype on the left side of the ‘=’ sign is not equal to the datatype on the right. Instead we have to do something like this:
int *ptr; ptr=(int *)0x12345678; // To write data to this memory location, we can now do the following: *ptr = 0; // To read the contents of this memory location into a variable called D, we could do this: D=*ptr.
Now, we could go and declare pointers to all of the ARM special function registers in this manner however there is a problem. The pointer takes up space, four bytes in fact. This would mean that we would be consuming the same amount of RAM as special function registers simply to allow our program reference them. This is clearly inefficient and may not even be possible on some processors with very limited RAM.
A more efficient way of doing this is a follows:
*((int *)0x12345678) = 0; // put zero into memory location 0x12345678 // and to read from this location we can do this: D = *((int *)0x12345678);
This is hardly a very intuitive way of programming and will inevitably lead to a number of errors as programmers mix up register addresses. The load on the programmer can be lessened by using the C pre-processor as follows:
// this is declared at the top of your source file or in an included header file.
#define THE_REGISTER (*((int *)0x12345678)) THE_REGISTER = 0; // put 0 into the register at 0x12345678 D = THE_REGISTER; // read the register contents into the variable D
Just prior to compiling, the C-preprocessor replaces all occurrences of THE_REGISTER with (*((int *)0x12345678)). All of the special function registers can be declared in this way without consuming additional RAM for pointers. It is usual practice these days to name the registers in the same way as they appear in the device’s datasheet. If you look around on the web you will probably find a header file for your device from the manufacturer with all of this tedious work done for you. A number of header files can be found here.