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.

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.

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

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.

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

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.

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.

A more elegant use of Bit Masks

Consider that code:

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

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

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