A lot of embedded software is focused on the control of peripheral devices and this can present some interesting challenges. In particular, the developer must be quite comfortable “bit bashing” – i.e. manipulating individual bits or groups of bits within a register …
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 – I wrote about a way around this here – most programmers use hexadecimal. This is quite straightforward and it just takes practice to see the binary values represented by hex numbers.
For the purposes of this discussion, I am going to consider an 8-bit port, where only the least significant 3 bits are used [any values set in the top 5 bits are ignored]. So the only meaningful values are 0 to 7.
Most programmers, when learning C, encounter bit fields 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 XOR operator in C is ^ which works in the same way.
An additional challenge with some device registers 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 read/modify/write sequence, such write-only ports would be problematic. It is only a matter of coding and I have written on this matter in detail here, here and here.