Mentor Graphics has historically been dedicated to providing tools for electronic hardware designers and that still represents a very large proportion of the business. Ever since I was acquired into the company, I have found that the hardware focused guys have a healthy interest in software – embedded software in particular. Often, they are specifically concerned with the boundary between software and hardware …
The broad issue is quite straightforward. A peripheral device is likely to have a number of internal registers, which may be read from or written to by software. These normally appear just like memory locations and can, for the most part, be treated in the same way. Typically a device register will have bit fields – groups of bits that contain or receive specific information. Such fields may be single bits, groups of bits or a whole word. There may also be bits that are unused – reading from them or writing to them has no effect.
For example, a serial interface might have an 8 bit register, with the bits used like this:
- Bits 0-2: baud rate, where the values [0-7] have specific significance.
- Bits 3-4: parity, where 0=none, 1=even, 2=odd and 3 is an illegal setting.
- Bits 5-6: unused.
- Bit 7: interrupt enable.
Writing code to access such a device need not be hard, but there are at least 4 areas where care is needed:
1) The word size of the register. It is essential that the data type used in C corresponds correctly. Fortunately, C99 addressed this matter satisfactorily.
2) If the register is wider than 8 bits, there is a question about the endianity of the CPU. Some processors are fixed, others have their endianity locked in their hardware implementation and others may be changed in software. You just need to know.
3) Careful use of the keyword volatile is essential when accessing device registers to prevent over-enthusiastic optimization by the compiler removing repeated references to the address.
4) At first sight, bit fields in C structures look perfect for this application. You simply need to map the structure onto the register and then each of the fields can be accessed with ease. However, this is unwise for a couple of reasons: First, the layout of bit fields in a C structure is compiler dependent; even if your code works it would be non-portable. Second, the code which accesses a bit field to write a value would also need to read the data which is already there; some hardware does not allow read back, so a shadow copy of the register needs to be maintained in RAM.
For something that is apparently quite simple and straightforward, there are a surprising number of pitfalls. What is even more surprising, given that this is a matter of concern to developers of almost all embedded systems, is that no “standard” way to approach this issue has emerged.