A better way to access STM32 registers

Thread Starter

ApacheKid

Joined Jan 12, 2015
1,657
I was playing with a simple demo that sets up the clock on an STM32F4 board, I got the demo working.

But I didn't like the look of the code, for example

C:
    RCC->CFGR &=   ~0x07E00000;
    RCC->CFGR |=    0x07600000;
If that's not clear to you, join the club. It's actually setting the MC01 clock output pin to be driven by the PLL and the MC01PRE (the divider) to 7 (divide by 5).

But by defining a simple bit field typedef and a macro, that code can be rewritten:

C:
    RCC_CFGR->MCO1 = 3;
    RCC_CFGR->MCO1_PRE = 7;
Not only is this much more readable, it leads to less generated machine instructions too:

1707234256289.png

all of a sudden this "low level" code started to look less low level.

Why on earth didn't ST do something like that, if they'd just defined a bitfield typedef for each of these various registers, and an associated simple macro, it would so much easier to follow examples and samples and so on. Furthermore, by making the typedef contain a union where the entire datum is just an int32, we can use that member to zeroize the entire register:

C:
    RCC_CFGR->ALLBITS = 0;
Here's the typedef and macro:

C:
#define RCC_CFGR ((CFGR_Type_ptr)&(RCC->CFGR))

typedef union
{
    struct
    {
        __IO BIT SW : 2;
        __IO BIT SWS : 2;
        __IO BIT HPRE : 4;
        __IO BIT UNUSED2 : 2;
        __IO BIT PPRE1 : 3;
        __IO BIT PPRE2 : 3;
        __IO BIT RTCPRE : 5;
        __IO BIT MCO1 : 2;
        __IO BIT UNUSED1 : 1;
        __IO BIT MCO1_PRE : 3;
        __IO BIT MCO2_PRE : 3;
        __IO BIT MCO2 : 2;
   
    };
    uint32_t ALLBITS;
} CFGR_Type, * CFGR_Type_ptr;
 
Last edited:

MrChips

Joined Oct 2, 2009
30,925
Creating structures of bit fields is not the responsibility of the MCU manufacturer.
This is usually supplied by the IDE platform provider or you can find it on various open sources, for example. github.
Or you can create your own header files.

I can search to see what is used in my libraries.
 

Thread Starter

ApacheKid

Joined Jan 12, 2015
1,657
Creating structures of bit fields is not the responsibility of the MCU manufacturer.
This is usually supplied by the IDE platform provider or you can find it on various open sources, for example. github.
Or you can create your own header files.

I can search to see what is used in my libraries.
Well yes, I wasn't implying they had a responsibility, only that it's an improvement for all concerned to have readable code.

All those & and | and &= and |= and so on, those are just ugly and obfuscate what is actually simply setting groups of bits.
 

MrChips

Joined Oct 2, 2009
30,925
Well yes, I wasn't implying they had a responsibility, only that it's an improvement for all concerned to have readable code.

All those & and | and &= and |= and so on, those are just ugly and obfuscate what is actually simply setting groups of bits.
Sorry, I am aware that you are not a big fan of C but that is the way it is. If you want you can create a macro or function to make it more readable.
 

Thread Starter

ApacheKid

Joined Jan 12, 2015
1,657
Sorry, I am aware that you are not a big fan of C but that is the way it is. If you want you can create a macro or function to make it more readable.
I wasn't complaining about C here MrChips, but about how very taxing it can be for a relative novice to these devices to understand and follow code example and articles, that's all. I can imagine newcomers to all this being either daunted or alienated by some of the stuff.

I've used C for decades, written huge volumes of system level code with C and always striven to make it readable, easier to understand, of course I've never worked professionally with these MCUs either so I depend on clarity a great deal.

Here's the example from the book (a decent book by the way)

1707267098620.png

Basically it gives no idea what specific bits are being set/unset here or what the roles of those bits is, imagine mistyping just one of the hex chars in some of those constants, perhaps putting one too many/few zeros, and then struggling to debug the stuff.

I know C very well and I'm not a novice with microprocessors or electronics, but that didn't help me - I had to thumb back and forth to unravel all that hex.
 

nsaspook

Joined Aug 27, 2009
13,418
I wasn't complaining about C here MrChips, but about how very taxing it can be for a relative novice to these devices to understand and follow code example and articles, that's all. I can imagine newcomers to all this being either daunted or alienated by some of the stuff.

I've used C for decades, written huge volumes of system level code with C and always striven to make it readable, easier to understand, of course I've never worked professionally with these MCUs either so I depend on clarity a great deal.

Here's the example from the book (a decent book by the way)

View attachment 314642

Basically it gives no idea what specific bits are being set/unset here or what the roles of those bits is, imagine mistyping just one of the hex chars in some of those constants, perhaps putting one too many/few zeros, and then struggling to debug the stuff.

I know C very well and I'm not a novice with microprocessors or electronics, but that didn't help me - I had to thumb back and forth to unravel all that hex.
It's all part of joining the embedded priesthood. You need to look to learn that using structure abstractions are not always 32-bit atomic when updating, reading/writing system registers or shared memory.

All those & and | and &= and |= and so on, might not just be ugly and obfuscate. They might be necessary to the proper sequencing of critical code sections when there are interrupts or threads.
https://wiki.sei.cmu.edu/confluence...en+accessing+bit-fields+from+multiple+threads

Not saying it won't work, just that you need to be careful where you step as there are booby-traps on the trail and snakes in the trees.
 
Last edited:

Thread Starter

ApacheKid

Joined Jan 12, 2015
1,657
I wonder if these C compilers see this:

Code:
    RCC_CR->CSS_ON = 0;
    RCC_CR->HSE_BYP = 0;
    RCC_CR->HSE_RDY = 0;
    RCC_CR->HSE_ON = 1;
and recognize that each assignment is a successive assignment to a bit field within the same datum, and then optimize that by building the appropriate & and I stuff, to do the four updates in a single write...
 

MrChips

Joined Oct 2, 2009
30,925
All those & and | and &= ~ and |=
are not the fault of any specific microcontroller designer or manufacturer. This is inherent in the math of boolean arithmetic. A programmer properly versed in C language would understand what these operators do.

The following code is erroneous because it sets other bits to zero.
RCC_CFGR->MCO1 = 3;
RCC_CFGR->MCO1_PRE = 7;

What I find annoying is code such as
DDR |= (0x01<<3);
DDR &= ~(0x01<<4);
even if one uses declared constant labels.
 

MrChips

Joined Oct 2, 2009
30,925
I wonder if these C compilers see this:

Code:
    RCC_CR->CSS_ON = 0;
    RCC_CR->HSE_BYP = 0;
    RCC_CR->HSE_RDY = 0;
    RCC_CR->HSE_ON = 1;
and recognize that each assignment is a successive assignment to a bit field within the same datum, and then optimize that building the appropriate & and I stuff, to do the four updates in a single write...
No.
The above code sequence does not accomplish what the programmer wants to happen.
How should one write code to set a register to a certain bit pattern?, i.e.

REGISTER = 0b1010001100001111;

or clear bits 22, 21, 19 and set bits 20, 5, 2?
 

MrChips

Joined Oct 2, 2009
30,925
By the way, when configuring hardware registers on STM32, one generally does not write directly to the hardware.
The IDE has configuration library functions that do the job for you. If you use STM32CubeIDE, the platform generates the code for you automatically.

If you want to write the functions yourself, here is an example of what the code looks like:
C:
void LED_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;

    /* Enable the GPIO_LED Clock */
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);

    GPIO_InitStructure.GPIO_Pin = LED1 | LED2 | LED3 | LED4;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOD, &GPIO_InitStructure);
}

Toggling LED:

    GPIO_SetBits(GPIOD, LED1);
    GPIO_ResetBits(GPIOD, LED2);
STM32 has native SET and RESET bit instructions. Hence there is no need to use |= and &= ~.

Here is what is inside the GPIO_SetBits( ) function:
GPIOx->BSRRL = GPIO_Pin;

and in the GPIO_ResetBits( ) function:
GPIOx->BSRRH = GPIO_Pin;
 

Thread Starter

ApacheKid

Joined Jan 12, 2015
1,657
All those & and | and &= ~ and |=
are not the fault of any specific microcontroller designer or manufacturer. This is inherent in the math of boolean arithmetic. A programmer properly versed in C language would understand what these operators do.

The following code is erroneous because it sets other bits to zero.
RCC_CFGR->MCO1 = 3;
RCC_CFGR->MCO1_PRE = 7;
I'm very interested in you elaborating on this.

That replaces this:
Code:
RCC->CFGR &= ~0x07E00000; // 0000 0111 1110 0000 ... (after the ~ this is 1111 1000 0001 1111)
RCC->CFGR |=  0x07600000; // 0000 0111 0110 0000 ...
the "76" bit pattern corresponds exactly to the 3 and the 7 in my code (if you scrutinize the struct offsets etc). I don't think my replacement code is doing anything different to the original. That boolean logic cannot set bits on and off in one operation, that's why they use the & then the | my code can, assigning 3 sets some bits off and others on in a single operation.
 
Last edited:

Thread Starter

ApacheKid

Joined Jan 12, 2015
1,657
No.
The above code sequence does not accomplish what the programmer wants to happen.
How should one write code to set a register to a certain bit pattern?, i.e.

REGISTER = 0b1010001100001111;

or clear bits 22, 21, 19 and set bits 20, 5, 2?
The first is trivial, simply write 0xA30F to the entire 32-bit register.

The second question, well that depends, the registers comprise several blocks of bits, 1, 2 or more contiguous bits that represent some function and so that would be abstracted using the bitfield struct. If one really wanted to set/unset an arbitrary set of bits in one go then one can still do that.
 

Thread Starter

ApacheKid

Joined Jan 12, 2015
1,657
Also there isn't a standard order for bit fields in a int...

https://en.cppreference.com/w/c/language/bit_field
The order of bit-fields within an allocation unit (on some platforms, bit-fields are packed left-to-right, on others right-to-left)

So it may work for a particular compiler but have all the bits be backwards on another...
Yes that's true, C does have a habit of being standardized very flexibly!
 

Thread Starter

ApacheKid

Joined Jan 12, 2015
1,657
Yes, I see that and look, this is the kind of cryptic stuff that makes it a struggle to read:

1707320216994.png

This is totally fine but is far from clear, the individual hex constants here are close to meaningless unless one has mentally internalized the register definitions. So that's all I'm speaking of here, enabling code to be more readable.

Look at the PLLCFGR assignment, can anyone tell by looking at that code that PLLM4 and PLLQ2 are being set ON? (Also it looks like bits 31 thru 28 are not all being set to zero, but those are reserved bits and should be, perhaps that's CPU specific):

1707321092942.png

But I will accept that you are correct, these settings are frequently created by the tools, they do all that detailed work, so a lot of this is moot when coming from that world.
 
Last edited:

MrChips

Joined Oct 2, 2009
30,925
Firstly, we don't use literal constants such as 0x07E0000 because we don't know what function they affect. Also the code would break when you go to a different variety of the same processor.

Here is the RCC_CFGR register. We will focus only on bits 27-24 and bits 23-20.
STM32F407 RCC_CFGR.jpg
Instead of worrying about which bits are needed, we use the functional labels which are defined in the stm32f4xx_rcc.h header file.
https://github.com/espruino/Espruino/blob/master/targetlibs/stm32f4/lib/stm32f4xx_rcc.h

Here is an excerpt:
C:
/** @defgroup RCC_MCO1_Clock_Source_Prescaler
  * @{
  */
#define RCC_MCO1Source_HSI               ((uint32_t)0x00000000)
#define RCC_MCO1Source_LSE               ((uint32_t)0x00200000)
#define RCC_MCO1Source_HSE               ((uint32_t)0x00400000)
#define RCC_MCO1Source_PLLCLK            ((uint32_t)0x00600000)
#define RCC_MCO1Div_1                    ((uint32_t)0x00000000)
#define RCC_MCO1Div_2                    ((uint32_t)0x04000000)
#define RCC_MCO1Div_3                    ((uint32_t)0x05000000)
#define RCC_MCO1Div_4                    ((uint32_t)0x06000000)
#define RCC_MCO1Div_5                    ((uint32_t)0x07000000)
#define IS_RCC_MCO1SOURCE(SOURCE) (((SOURCE) == RCC_MCO1Source_HSI) || ((SOURCE) == RCC_MCO1Source_LSE) || \
                                   ((SOURCE) == RCC_MCO1Source_HSE) || ((SOURCE) == RCC_MCO1Source_PLLCLK))
                                
#define IS_RCC_MCO1DIV(DIV) (((DIV) == RCC_MCO1Div_1) || ((DIV) == RCC_MCO1Div_2) || \
                             ((DIV) == RCC_MCO1Div_3) || ((DIV) == RCC_MCO1Div_4) || \
                             ((DIV) == RCC_MCO1Div_5))
/**
Hence, the RCC configuration code would look something like this:
RCC_MCO1Config(RCC_MCO1Source_PLLCLK, RCC_MCO1Div_5);

---------------
True, you cannot clear selected bits and set selected bits with one instruction except to write to the entire register.
That is why SetBits( ) and ResetBits( ) are two separate functions.

SetBits( ) uses |=
ResetBits( ) uses &= ~

This is clearly understood by anyone well versed in boolean algebra.
 

Thread Starter

ApacheKid

Joined Jan 12, 2015
1,657
Ahh my mistake, the docs say " Bits 31:28 Reserved, must be kept at reset value" it does not say they must all be set to zero !
 

Ian0

Joined Aug 7, 2020
10,001
I use the Renesas RA4 which comes with an IDE based on Eclipse. I believe the STM32 uses the same platform.
It comes with a "configurator" which allows all these bits to be set graphically in a tick-box sort of way. Of course, it is the complete antithesis to portability. Seems like a nice idea until you want to copy a few (but not all) of the configurations that you have used before to a new piece of software - then it's a complete pain.
You can't make any changes to interrupts without deleting them and re-entering them, at which point all the interrupts get renumbered
I used it a few times, then went back to using the bit definitions that are #defined in the header file as per @MrChips post #17 (but Renesas doesn't make them easy to find)
All Renesas's software examples use it, presumably they are trying to convince people how good it is.
 

Thread Starter

ApacheKid

Joined Jan 12, 2015
1,657
I use the Renesas RA4 which comes with an IDE based on Eclipse. I believe the STM32 uses the same platform.
It comes with a "configurator" which allows all these bits to be set graphically in a tick-box sort of way. Of course, it is the complete antithesis to portability. Seems like a nice idea until you want to copy a few (but not all) of the configurations that you have used before to a new piece of software - then it's a complete pain.
You can't make any changes to interrupts without deleting them and re-entering them, at which point all the interrupts get renumbered
I used it a few times, then went back to using the bit definitions that are #defined in the header file as per @MrChips post #17 (but Renesas doesn't make them easy to find)
All Renesas's software examples use it, presumably they are trying to convince people how good it is.
Yes I have used Cube a few times here, and it does a huge amount for us, I can even import such a cube project into VisualGDB (which is a purchased product that runs within Visual Studio) and the project will get converted to a VS project, that incidentally works very well, VisualGDB is actually superbly robust and well behaved.

Of course I'm not a pro, I am just exploring this world and seeking to gain further insights into all of this, I'm not afraid of bits, registers, the various peripherals etc. I grew up programming 6502 and Z80 in raw HEX and occasionally assembler (Rockwell AIM65) and did lots of electronic stuff back then too, studied electronics/telecoms full time for two years in Liverpool.

So I actually want to see the specific register and field settings and understand what they do, why there set as they are, what their actually being set to and so on, experienced, seasoned pros don't care or already know all this.

Things have certainly changed in forty years, my studies were all in the late 70s, I wrote articles for EE, ETI, Wireless World and stuff but slowly drifted from this as I worked as a programmer (detached from hardware) the stuff I see today was the kind of stuff I'd read about in future gazing articles in ETI, see page 25 of this, this was the kind of stuff I grew up with (also see page 42, this was the very first time I encountered microprocessors).
 
Last edited:
Top