Strategy for C on STM32

Thread Starter

ApacheKid

Joined Jan 12, 2015
1,533
This may strike some as overkill or redundancy given what ST provide in their various libraries, but I've been getting slightly frustrated at the weak use of intellisense when experimenting with these devices.

The large number of register flag constants seem to be represented as raw #define statements which appear in the global namespace, and coding relies on prefixes for setting register fields, for example RCC_

For example to enable GPIOA clocks we code:

Code:
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
I want instead to be able to rely less on memory and intimate familiarity when coding stuff like this, so I devised this as an experiment:

Code:
typedef struct
{
    const uint32_t ENABLE_OTGHSULPI;
    const uint32_t ENABLE_OTGHS;
    const uint32_t ENABLE_ETHMACPTP;
    const uint32_t ENABLE_ETHMACRX;
    const uint32_t ENABLE_ETHMACTX;
    const uint32_t ENABLE_ETHMAC;
    const uint32_t ENABLE_DMA_2;
    const uint32_t ENABLE_DMA_1;
    const uint32_t ENABLE_CCM_DATA_RAM;
    const uint32_t ENABLE_BKP_SRAM;
    const uint32_t ENABLE_CRC;
    const uint32_t ENABLE_GPIOI_CLOCK;
    const uint32_t ENABLE_GPIOH_CLOCK;
    const uint32_t ENABLE_GPIOG_CLOCK;
    const uint32_t ENABLE_GPIOF_CLOCK;
    const uint32_t ENABLE_GPIOE_CLOCK;
    const uint32_t ENABLE_GPIOD_CLOCK;
    const uint32_t ENABLE_GPIOC_CLOCK;
    const uint32_t ENABLE_GPIOB_CLOCK;
    const uint32_t ENABLE_GPIOA_CLOCK;

} AHB1ENR_FLAGS;

extern  AHB1ENR_FLAGS AHB1ENR =
{
    // reserved 1 bit1
    1 << 30,
    1 << 29,
    1 << 28,
    1 << 27,
    1 << 26,
    1 << 25,
    // reserved 2 bits
    1 << 22,
    1 << 21,
    1 << 20,   
    // reserved 1 bits
    1 << 18,
    // reserved 5 bits
    1 << 12,   
    // reserved 3 bits
    1 << 8,
    1 << 7,
    1 << 6,
    1 << 5,
    1 << 4,
    1 << 3,
    1 << 2,
    1 << 1,
    1 << 0
};
This then allows me to code the same thing like this:

Code:
    RCC->AHB1ENR |= AHB1ENR.ENABLE_GPIOA_CLOCK;
And because the fields are struct members we get intellisense:

REGINTSENSE.jpg

I don't think we can get this kind of intellisense support in the existing libraries (unless perhaps we use HAL or some richer API, which may be undesirable for some needs).

Just by working like this for a short time we begin to get deep insights into what each register provides.

As I say, I'm just experimenting but the sheer number of registers and fields within them is daunting and the lack of namespaces (which is all these structs are doing really) makes even simple experimentation slow going...

Thoughts?
 

nsaspook

Joined Aug 27, 2009
13,081
I don't think you will get any deep insights from the reorganization but if the mnemonic provides a shortcut to building the required mental pattern (GROK) to program the device efficiently for you, that's great.
 

402DF855

Joined Feb 9, 2013
271
C:
...
const uint32_t AHB1ENR_ENABLE_GPIOB_CLOCK = 2;
const uint32_t AHB1ENR_ENABLE_GPIOA_CLOCK = 1;
Cool, but intellisense works with the traditional bit assignment technique too, depending on your editor.
C:
extern  AHB1ENR_FLAGS AHB1ENR =
Just wondering what the extern is doing there?
 

Thread Starter

ApacheKid

Joined Jan 12, 2015
1,533
C:
...
const uint32_t AHB1ENR_ENABLE_GPIOB_CLOCK = 2;
const uint32_t AHB1ENR_ENABLE_GPIOA_CLOCK = 1;
Cool, but intellisense works with the traditional bit assignment technique too, depending on your editor.
C:
extern  AHB1ENR_FLAGS AHB1ENR =
Just wondering what the extern is doing there?
The extern is to declare the structure as global, a single constant instance accessible throughout the entire app.

A general pattern has emerged where I just initialize nested structures, I'm just exploring this for fun, here's what I can write:

Code:
    GPIOA->MODER |= MODE.ANALOG.P4 | MODE.ANALOG.P5 | MODE.ALTERNATE.P11;
As a newbie one can see right away that there are four modes and sixteen ports, being able to see the equivalent for the many other registers would be a big help - IMHO.

Can you shed light on what you mean by "intellisense works with the traditional bit assignment technique too "?

Thanks
 
Last edited:

Thread Starter

ApacheKid

Joined Jan 12, 2015
1,533
Here I'm using MS VS 2019:View attachment 197602
OK I see, what's odd here is this is what I see (also with VS 2019 but with VisualGDB)

ISENSE.jpg

Actually now that I look, this is because whatever libraries I'm using all those values begin: RCC_AHB1ENR_ - so yes intellisense does work.

But there's no hierarchy just a long flat set of values, no real problem but I'm used to working in C# the past few years and I guess that has spoiled me a bit !

Consider the DAC control register, it is composed of several multipart fields, so for example this is a natural way to write something

XXX |= DACCON.WAVE2.NOISE_ENABLED;

This isn't a huge thing but I am used to abstracting stuff (having used C# a lot) and I have no desire to start using C++ !
 
Last edited:

nsaspook

Joined Aug 27, 2009
13,081
Consider the DAC control register, it is composed of several multipart fields, so for example this is a natural way to write something

XXX |= DACCON.WAVE2.NOISE_ENABLED;

This isn't a huge thing but I am used to abstracting stuff (having used C# a lot) and I have no desire to start using C++ !
While I agree it's a natural way for some I don't think it should be the default low-level pattern.

I've used it in the past to good effect with C to set default module configurations with several MCU types but it's not the best fit in all circumstances.
 
Top