Bitwise and Bitmasking Operations

Bitwise and Bitmasking Operations

Bitwise Operations

Bitwise operations are logical operations that are performed on binary representations of numbers. They include AND, OR, XOR, NOT, and shift operations. These operations can help manipulate individual bits in a register and memory and are often used in low-level programming and optimization. Bitwise operations are common in the design of embedded systems as they are many times faster than addition, multiplication and division on resource-constrained devices.

Bitwise Operators in C

  1. Bitwise AND(&), OR(|), NOT(~) and XOR(^): Let reg1 and reg2 be two operand registers. The following examples illustrate the different possible bitwise operations on them.

     reg1 &= reg2               //reg1 = reg1 AND reg2
     reg1 |= reg2               //reg1 = reg1 OR reg2
     reg1 ^= reg2               //reg1 = reg1 XOR reg2
     reg1 = ~reg1               //reg1 = NOT reg1
    
  2. Bit shifts: In these operations, the digits are moved to the left or right. Since registers have finite lengths, some bits may be shifted out or in from either direction. There are wide varieties of bit shifting, such as logical, arithmetic and circular shifts. The logical shift is the most relevant to our discussion and is presented below.

    Logical left shift: In a logical left shift, zeroes are shifted from the right to fill the open spaces created by shifting the bits to the left. A logical left shift by n is mathematically equivalent to multiplying a number by 2^n.

    Logical right shift: In a logical left shift, zeroes are shifted from the left to fill the open spaces created by shifting the bits to the right. A logical right shift by n is mathematically equivalent to multiplying a number by 1/2^n and rounding toward zero.

     uint8_t y, x = 16; int n = 2;
     //Logical left shift
     y = x << 2 ;   //Equivalent to y = 16 * 4
     //Logical left shift
     y = x >> 2 ;   //Equivalent to y = 16 / 4
    

Bitmasking Operations

A bitmask is a binary value that can manipulate individual bits in a register. It is represented by a sequence of ones and zeroes, with each bit position corresponding to a specific bit in the number. Bitmasks are often used with bitwise operations, such as AND, OR, and XOR. Bitmasks are particularly useful in device driver development where setting and unsetting individual or group of bits in a register is a common operation. Common bitmasking operations and the construction of the appropriate bitmasks in C/C++ are Illustrated below.

  • Clearing an individual bit in a position i.

      //Assume ARR is a 32 bit register
      ARR &= 1UL<<i;       //Clears the ith bit 
      ARR &= ~1UL;         //Clears the LSB, bit 0
      ARR &= ~(1UL<<5);    //Clears the 5th bit
    
  • Clearing a group of bits starting from the ith position from the least significant digit(LSB).

      //To  Clear a group of n bits starting from ith position from the LSB, do
      ARR &= ~((2^n - 1)UL <<i); 
      ARR &= ~(7UL<<4);         //Clears the 4th, 5th and 6th bits
    
  • Setting an individual bit in a position i.

      ARR |= 1UL<<i;      //Sets the ith bit
      ARR |= 1UL;         //Sets the LSB, bit 0
      ARR |=  (1UL<<5);   //Sets the 5th bit
    
  • Setting a group of bits starting from the ith position from the LSB.

      //To  Set a group of n bits starting from ith position from the LSB, do
      ARR |= (2^n - 1)UL <<i; 
      ARR |= 7UL<<4;         //Sets the 4th, 5th and 6th bits
    
  • Toggling an individual bit in a position i

      ARR ^= 1UL<<i;      //Toggles the ith bit
      ARR ^=  1UL;        //Toggles the LSB, bit 0
      ARR ^= 1UL<<5;      //Toggles the  5th bit
    
  • Toggling a group of bits starting from the ith position from the LSB.

      //To  Set a group of n bits starting from ith position from the LSB, do
      ARR ^= (2^n - 1)UL <<i; 
      ARR ^= 7UL<<4;         //Sets the 4th, 5th and 6th bits
    
  • Checking bits: We can check if a bit is ON or OFF using bitmasking.

      bit_status = (ARR & 1UL<<i)>0 //retrieves status of bit i 
      bit5_status = (ARR & 1UL<<5)>0; //retrieves status of bit 5
    

It is always a good practice to clear a group of bits before setting them. Also, individual bits should be addressed using the appropriate bitmask to preserve the state of other bits and prevent unexpected behaviour. We will heavily utilize these operations in developing peripheral drivers in the upcoming sections.

Further reading