Although expertise may be required in many application domains, a lot of embedded software is focused on the control of peripheral devices and this can present some interesting challenges for developers who are not used to working “close to the hardware”. In particular, the developer must be quite comfortable “bit bashing” – manipulating individual bits or groups of bits within a register – and this is what we explore in this article.
Typically, device registers consist of bits that individually, or in groups, control or configure the device. So, the first aspect of device programming that needs to be considered is working at the bit level – and that entails working in binary. As the C language does not directly support binary notation most programmers use hexadecimal. This is quite straightforward, and it just takes practice to see the binary values represented by hex numbers.
Alternatively, you can roll your own binary constants. Just create a “binary.h” file, which contains 256 lines like this:
＃define b00000000 ((unsigned char) 0x00)
＃define b00000001 ((unsigned char) 0x01)
＃define b00000010 ((unsigned char) 0x02)
You are welcome to contact me (email@example.com) and I can send you the file and save you the effort.
Most programmers, when learning C, encounter bit fields in structures and, not unreasonably, conclude that they are the optimal way to manipulate bitwise data – single bits and groups. However, this is not a valid conclusion. For internal data structures, bit fields are a perfectly reasonable way to manipulate data. However, their implementation is entirely compiler dependent. This means that there is no guarantee that they will map on to a device register in the expected manner. If you use them and your code works OK, that is fine, but bear in mind that your code is entirely non-portable and even a compiler update might break it.
The only reliable way to work with the bits in a register is to use the OR and AND operators: | and &. In simple terms, you use OR to set one or more bits, without affecting the others, and you use AND (with the ones-complement – the inverse – of the bit pattern) to similarly clear bits. Here are some simple examples:
device_reg = device_reg | 0x01; // set bit 0
device_reg = device_reg & ~0x06; // clear bits 1 and 2
I would probably use the compound assignment operators and write the code like this:
device_reg |= 0x01; // set bit 0
device_reg &= ~0x06; // clear bits 1 and 2
but it amounts to the same thing – just a matter of style.
If you want to toggle/flip a bit, the exclusive-OR operator in C is ^ which works in the same way.
An additional challenge with some device registers is that they are “write-only”. In other words, unlike a normal memory cell, you can write a value into the register, but it is not possible to read it back. As the code shown above entails a read/modify/write sequence, such write-only ports would be problematic.
The solution, which may be implemented in a variety of ways, is to keep a “shadow” copy of the register contents. Any operations are performed on the shadow, which is written out to the register whenever it is modified. The only remaining challenge is to ensure that the shadow and register are always in sync; an interrupt might occur between operations or some code might simply not obey the rules and update the register directly. So, a little care is needed.
|Colin Walls has over forty years’ experience in the electronics industry, largely dedicated to embedded software. A frequent presenter at conferences and seminars and author of numerous technical articles and two books on embedded software, Colin is an embedded software technologist with Mentor, a Siemens business, and is based in the UK. His regular blog is located at: https://blogs.sw.siemens.com/embedded-software/. He may be reached by email at firstname.lastname@example.org.|
- Structures and classes in C++
- Surprises in C code
- If only I had one more [insert function here]!
- Embedded Linux device drivers: Writing a kernel device driver
- 3 driver design techniques for microcontrollers
- Interfacing with modern sensors: Interface design
- 10 simple tricks to optimize your C code in small embedded systems
- Using the Nucleus SE real-time operating system
For more Embedded, subscribe to Embedded’s weekly email newsletter.