Bit manipulation

Manipulating individual bits in registers is important in microcontroller programming. Data is always read and written in parallel, also when there is just one bit of interest. Logic and bitwise operators are used to mask the bits that are not of interest and are therefore a key factor for the domain of microcontroller programming.

Register

Bits are most often directly manipulated in registers. Here is an example register from the STM32F05x microcontroller with a random value in hexadecimal format:

GPIOC->ODR: 0xFF0000FF

We could clear bit 0 of this register like this:

GPIOC->ODR = 0x00000000;

But as you can see, it also clears all the other bits. So before updating a register, we will read it’s content first and then modify it by using a mask, which is also known as a read-modify-write cycle.

Mask

A mask makes sure that we write or read only the bit(s) of interest. Here is an example of a mask with 16 bits cleared and 16 bits sets:

MASK = 0x0000FFFF;

The operator used with the mask determines the result of the operation.

Individual bit set: bitwise-or

Sets the bits in REG that are set in MASK, all other bits are unchanged

REG = REG | MASK;

REG |= MASK;

Example evaluation:

GPIOC->ODR = GPIOC->ODR | 0x0000FFFF;
GPIOC->ODR = 0xFF0000FF | 0x0000FFFF;
GPIOC->ODR = 0xFF00FFFF;

Individual bit clear: bitwise-and with inverse

Clears the bits in REG that are set in MASK, all other bits are unchanged

REG = REG & (~MASK);

REG &= ~MASK;

Example evaluation:

GPIOC->ODR = GPIOC->ODR & (~0x0000FFFF);
GPIOC->ODR = 0xFF0000FF & (~0x0000FFFF);
GPIOC->ODR = 0xFF0000FF &   0xFFFF0000;
GPIOC->ODR = 0xFF000000;

Individual bit toggle: bitwise-xor

Toggles the bits in REG that are set in MASK, all other bits are unchanged

REG = REG ^ MASK;

REG ^= MASK;

Example evaluation:

GPIOC->ODR = GPIOC->ODR ^ 0x0000FFFF;
GPIOC->ODR = 0xFF0000FF ^ 0x0000FFFF;
GPIOC->ODR = 0xFF00FF00;

Check the status of an individual bit

To check the status of a bit, a mask is used to ignore all bits not of interest.
Let’s check if bit 0 in the register GPIOA->IDR is set. Then the mask must be 0x00000001.

if(GPIOA->IDR & 0x00000001)
{
    // Yes, bit is set
}
else
{
    // No, bit is not set
}

Notice how the mask and the bitwise-and operator make sure that all bits not of interest will be 0 for sure! If bit 0 in GPIOA->IDR is also 0, the condition will yield 0, which is equivalent to false. If bit 0 in GPIOA->IDR is 1, the condition will not be 0, which is equivalent to true.

We can also check if bit 0 is cleared 🙂

if(!(GPIOA->IDR & 0x00000001))
{
    // Yes, bit is cleared
}
else
{
    // No, bit is not cleared
}