Registers and bit masks

Registers and bit masks

Simplest things create the biggest problems

When I started with embedded my knowledge about programming was (and probably still is) very elementary. I often used logical operator like logical NOT (!), logical OR (| |) and logical AND (&&) but was almost ignoring the bitwise operators.

Handling registers is actually so simple that at first approach I wasted a couple of days and this was because bitwise operators are required to. Talking with friends of mine coming from Arduino, this problem seems to be much diffused. The trap seems to be a lack of basic knowledge of informatics and electronics. So in this article we will discuss about something obvious for experts and insidious for beginners.

The elementary Software-Hardware interaction

In Embedded, the elementary software-hardware interaction is performed writing/reading registers.In digital electronics registers are special structures storing bits of information in such a way that systems can write to or read out all the bits simultaneously. However, register are not only used as buffer storage. They could be used to make status reporting (changing their value whether a certain event has occurred), as input/output or to configure certain features.

Configuring a peripheral by software always means write/read one or more bit in a register. Software targeted on a specific platform could often encapsulate these operations hiding them to the user. When software is cross-platform or when user needs to change the hardware configurations at run time, acting on registers is almost mandatory.

Registers length often is the same of architecture word length (but could be also much smaller). So, every bit of the register represent a certain configuration and we are not interested on acting on its entire value but we want to read/write only certain bits. As example, consider this register from STM32F7 MCUs:

The SPI Control Register 1 from STM32F746G.

We can notice here some few thinks:

  1. The register length is 16-bit;
  2. In this register we can individuate 14 bit-fields each of them allow to set a certain configuration;
  3. Some configurations requires more than a bit (See BR);
  4. Every bit is available for both operation of write and read;

Case analysis

Baud rate control for SPI
The Baud rate control field from a SPI CR.

Basically, configuring that register from the beginning means compose the entire value bit after bit and write value on it. But if register is somehow already configured and we need to change only some bit-fields of it things become interesting.

As example, lets take a look to some of the SPI_CR1 bit-field descriptions to figure out what are the elementary operation that typically we need to perform dealing with registers. These operations are typically performed using bitwise operators and bit-masks. They are very commonly used when we need to act on bit-fields.

In the register description there are informations about meaning of every bit, as example the description of BR is shown in Fig. 2. This bit-field is represented by more than a bit and could be used to set SPI Clock frequency. We would act on this bit-field without edit others. Considering 1-bit length bit-fields could be interesting change their value to “1”, to “0”, toggle their value or check if their value is “1” or “0”.

Bitwise operators

Languages without an explicit Boolean data type, like C90 and Lisp, may represent truth values by some other data. For example C uses an integer type, where relational expressions like i < j and logical expressions connected by && and | | are defined to have value 1 if true and 0 if false, whereas the test parts of if, while, for, etc., treat any non-zero value as true.

Using logical operators, operands are considered always like boolean and the operation return another boolean: using other words doesn’t matter the single bit value. So 1001, 10000, 1, -5, -6 are all treat as true and only 0 as false. For the same reason !3204 and !(-2) both return 0 as result.

In bitwise operation instead, bit value is important since that operators do the operation precisely bitwise. This operators are ones’ complement (~), bitwise OR (|), bitwise AND (&) and bitwise XOR (^). In the following example we are going to use binary notation to make things much simpler but the concepts also apply to other notations.

 * AND vs bitwise AND
0b01001010 &&           0b01001010 & 
0b01011111              0b01011111
-------------           ------------
      TRUE              0b01001010
 * OR vs bitwise OR
0b10100011 ||           0b10100011 | 
0b01010011              0b01010011
-------------           ------------
      TRUE              0b11110011
 * NOT vs complement
!  0b10100011            ~0b10100011 
-------------           ------------
        FALSE             0b01011100
!  0b00000000            ~0b00000000 
-------------           ------------
        TRUE              0b11111111

Bit Masks

Using certain operand in a single bitwise operation we can set to ‘1’, to ‘0’ or toggle multiple bits in a word. This operand is commonly known as mask or bit-mask and the operation is called masking or bit-masking. In what follows we will refer to a undefinded bit as X.

Masking bits to 1

This operation uses the principle that X OR 1 = 1 and X OR 0 = X.

0b00000001       0b00010000       0b00011000
------------     ------------     ------------
0bXXXXXXX1       0bXXX1XXXX       0bXXX11XXX

Introducing shift left operator << we can perform the same operations in a different way.

  (1 << 0)         (1 << 4)      (0b11 << 3)
------------     ------------     ------------
0bXXXXXXX1       0bXXX1XXXX       0bXXX11XXX

Concluding the operation word | (1 << n) masks the n-th bit of “word” to “1“.

Masking bits to 0

This operation uses the principle that X AND 1 = X and X AND 0 = 0.

0b11111110       0b11101111       0b11100111
------------     ------------     ------------
0bXXXXXXX0       0bXXX0XXXX       0bXXX00XXX

Introducing shift left << and the ones’ complement operators we can perform the same operations in a different way.

 ~(1 << 0)        ~(1 << 4)     ~(0b11 << 3)
------------     ------------     ------------
0bXXXXXXX0       0bXXX0XXXX       0bXXX00XXX

Concluding the operation word & ~(1 << n) masks the n-th bit of “word” to “0“.

Toggling bits

This operation uses the principle that X XOR 1 = ~X and X XOR 0 = X.

0b10101010 ^     0b10101010 ^     0b10101010 ^
0b00000001       0b00010000       0b1000011
------------     ------------     ------------
0b10101011       0b10111010       0b00101001

Concluding the operation word ^ (1 << n) toggle the n-th bit of “word“.

Checking bit value

When we need to select a certain bit from a word we can still use bit-masks.

0b00000001       0b00010000       0b00011000
------------     ------------     ------------
0b0000000X       0b000X0000       0b000XX000

A more elegant use of Bit Masks

Consider that code:

addr = 0x40088030;
value = 43;
WriteRegister (addr, value);

It is clearly hard to understand what kind of configuration we made even for ourself. It is more functional something like:

#define  SPI_CR1_REG                              0x40088030
#define  SPI_CR1_CPHA                             0x01
#define  SPI_CR1_CPOL                             0x02
#define  SPI_CR1_MSTR                             0x04
#define  SPI_CR1_BR_0                             0x08
#define  SPI_CR1_BR_1                             0x10
#define  SPI_CR1_BR_2                             0x20
WriteRegister(SPI_CR1_REG, SPI_CR1_BR_2| SPI_CR1_BR_0 |
              SPI_CR1_CPHA | SPI_CR1_CPOL);

That means we are setting CPOL and CPHA to 1 and BR to 101 (or 5). This because:

/* actually means */
0x20| 0x04| 0x02| 0x01
/* or */
0b0010 0000 | 0b0000 0100 | 0b0000 0010 | 0b0000 0001
/* and result is */
0b0010 0111

Replies to Registers and bit masks

  • Dear Rocco,

    first, the blog seems so interesting. Thus, congratulations.
    I’m newbie about MCU, so forgive my question if you find it trivial.

    I’ve few doubts concerning the 4th paragraph. You stated that:

    That means we are setting CPOL and CPHA to 1 and CR (probably you mean BR) to 101 (or 5).

    but, referring the SPIx_CR1 sheet in Fig. 1, if we write 0b0010 0111, we are setting CPOL and CPHA to 1, but also MSTR. At the same time, we are writing 100 in the BR[2..0]. I am correct in that understanding?

    I have another doubt.

    Why you assign CPHA as 0x02 rather than 0x01 as in the datasheet?

    #define SPI_CR1_CPOL 0x01
    #define SPI_CR1_CPHA 0x02


    • Hi Giovanni,
      the whole example was a mess. I reviewed it so thanks for pointing it out.

      I was meaning BR,
      The bitmasks were wrong and not in line with the figure: now they have been fixed.

      Anyway, looking this article now it seems incomplete. I will completely rewrite it soon.

Leave a Reply