Electronics > Microcontrollers

[ARM] [Sam] Uh is it a good idea to map registers to structs

<< < (10/10)

cv007:

--- Quote ---Using structs is a very good idea
--- End quote ---
It is ideal so you can let the compiler deal with bit manipulation, but the downside is you usually have to create them yourself. Not so bad for 8bit, but gets worse when your 32bit mcu has a lot of peripherals. You still have to keep in mind what is happening behind the scenes as the code gives the appearance something simple is happening as it looks like simple assignment. In the end your code should be 'hidden' by higher level functions, so these low level bits of code are in many instances a one time creation so the bigger benefit is in any necessary reading of the code later (don't have to look over a bunch of bit manipulation code).

One upside to creating your own structs, is you can get a complete 'driver' in a single header where all relevant information is sitting in one header file so there is no need to be hunting down headers. You can also choose to do your own thing on an as-needed basis, but then you still have to deal with manufacturer headers for anything else (learn to use what they provide for you in any case).

A simple example of a gpio 'driver' for an nrf52-
https://godbolt.org/z/7ec4YsK68
This is a little more advanced (and in c++), but shows you can also get a pin specific register layout where there is no 'manual' bit manipulation in any code. This piece of code can live in a header, can be used for any nrf52 by having an mcu specific PIN enum. In this case, one can even create it in the online compiler since it does not depend on the manufacturer headers. The downside in this case is the use of templates, which is fine but your code gets 'template infected' and can get to a point where its just too much. Without templates and without c++ you can still get to the same destination and since you would be using the functions it would look the same on the outside.

A compromise is to use the headers provided to you, creating the functions you will use for the most part-
https://github.com/cv007/NUCLEO_G031K8/blob/main/Gpio.hpp
You end up doing the bit manipulation yourself, but in the end it gets used the same as the nrf52 code above- you use functions to do things and you are not constantly having to directly deal with registers. In this case the manufacturer header is a single file, and in the case of your sam you end up with a couple folders plus a file or two but is still manageable.
edit-- sorry, the above version I created the register struct, this is the one that uses the st headers, -
https://github.com/cv007/NUCLEO32_G031K8_B/blob/main/Gpio.hpp


Learn to use the provided headers in any case, since you will most likely have to use them at some point anyway.


edit- there is also a mistake in the nrf52 example, which is the type of mistake one can make using these structs/bitfields (last function is wrong). Corrected-
https://godbolt.org/z/PhndsPWYo

westfw:

--- Quote ---Learn to use the provided headers
--- End quote ---
This is a very important point that I don't think anyone else has mentioned yet.  If  you're "professional", it is likely that any prospective employer will want you to be familiar with The Vendor's Standard Way of Doing Things.  No one wants a programmer that generates code that looks weird to everyone else on the project, or whoever your successor is likely to be.  Even in the OSSW world, everyone who ever looks at your code will thank you if it looks like "standard Atmel-style ARM code", and doesn't use custom-named register definitions extracted from the .SVD XML and a bunch of custom macros.
(Most ARM vendors, including Atmel/Microchip have both structure-oriented and individual register definitions, though.  So yiou can be a hold-out for a while.)


--- Quote --- you usually have to create them [(structs_] yourself.
--- End quote ---
Not so, any more.  ARM specifically recommends using structures for peripherals (the part of CMSIS that most vendors actually implement.)  It's been a long time since I've seen a 32bit microcontroller that hasn't had vendor-provided structure-based definitions for their on-chip peripherals.  (The "fun" part is when you want to use something like that for asm code...)

Use of bitfields is convenient, but less common and more dangerous.

Anonymous unions are nice, but most vendors seem to be reluctant to use any C features from any version later than C99.
Having a structure member "ctrl" is nice from  a typing. reading, and logic point of view, but sort-of sucky if you're trying to find all the places in your code that modified the uart ctrl register, since other peripherals likely have a register named "ctrl" as well.

Having an IDE with context-sensitive completion and help is nice, but "uart->c" and "uartreg_c" probably act similarly.

Code like:
--- Code: ---   uart_t *u = &USART1;
   u->brgen = BAUD2DIV(9600);
   u->ctrla = foo;
   u->ctrb = bar;
--- End code ---
Is more likely to get optimized effectively than
--- Code: ---   USART1_brgen = BAUD2DIV(9600);
   USART1_ctrla = foo;
   USART1_ctrb = bar;
--- End code ---
(But see also https://embdev.net/topic/426508 )

cv007:

--- Quote ---Not so, any more.
--- End quote ---
I meant bitfields inside the struct.


--- Quote ---Use of bitfields is convenient, but less common and more dangerous.
--- End quote ---
I showed a case where a mistake can be made for some of the 'odd' registers (typically the write 1 to do something type), but I'm not sure that is any worse than sorting out a flawed bit manipulation statement that may be several lines of code (if using the defines given for bit positions, with their long-winded names). Either one can be easily/quickly fixed as it won't be your first mistake of this type, plus you typically test what you just wrote so your problem code is usually right in front of you. I'm not sure what other dangers are lurking, but have not seen the compiler doing anything other than what is expected with these structs that have bitfields (I only use gcc, various mcu's, all using bitfields).

Here is the nrf52 example with the helpful bitfields and register struct template removed (plain struct)-
https://godbolt.org/z/15rWsh8M7
Produces the exact same code, but the functions are both harder to create and read.

bson:
Volatile structs with bit field members makes for poor optimization; every single struct member access has to be kept as-is since it's an implied optimization memory barrier, and they can't be collected by the compiler into combined bit mask operations.  I think it's a much better idea to simply have volatile unsigned integers of the right size and do the masking yourself.  Usually you can clear multiple fields and update them in a single operation.

The exception is for non-volatile binary bit fields, like protocol headers.  In this case the compiler can freely optimize access.

cv007:

--- Quote ---Volatile structs with bit field members makes for poor optimization;
--- End quote ---
You still have the choice to not use them, assuming a union was created so you can also access the whole register. There may be peripheral registers that have a number of bitfield 'groups' that need to be set in a single function, you can adjust as needed and are not required to use the bitfields.

In the nrf52 example, the pin properties are in a single register and there are functions that deal with individual pin properties that make use of the bitfields. In addition, there is an init function that 'accumulates' the property values (provided by enum values in any order) through the private init_ function templates that end up setting all the pin properties in a single write (compiler sorts it all out at compile time, also notice bitfields are also in use in these functions).

Navigation

[0] Message Index

[*] Previous page

There was an error while thanking
Thanking...
Go to full version