Author Topic: [ARM] [Sam] Uh is it a good idea to map registers to structs  (Read 3642 times)

0 Members and 1 Guest are viewing this topic.

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 17814
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
[ARM] [Sam] Uh is it a good idea to map registers to structs
« on: January 21, 2022, 08:36:12 am »
So trying to write code for something like a counter that has 3 modes is becoming hard work as though bare metal programming is not already hard work but in my view better as you understand the chip. I see that the SAM ARM chips no longer have header files that create these struct like they did for the mega 0 series.

I've read around and found just about everyone condemning the idea in blogs, but microchips own developer help tell you to do this. So what is the verdict.

You will find me hence forth in an undisclosed location in fear of my life for the reactions this topic is certain to get  :-DD
 

Online Ian.M

  • Super Contributor
  • ***
  • Posts: 12855
Re: [ARM] [Sam] Uh is it a good idea to map registers to structs
« Reply #1 on: January 21, 2022, 09:02:04 am »
This: https://blog.feabhas.com/2019/01/peripheral-register-access-using-c-structs-part-1/ covers *some* of why mapping structs to peripheral registers can be a really bad idea.   Also, the compiler may silently generate multiple accesses to the volatile register, with unexpected side effects to satisfy what the programmer sees as a simple write to a bitfield.

The manufacturers can do it in their supplied headers, as they can be (and should be) qualified by the manufacturer with their recommended/supplied compiler to ensure that there are either no undesirable side effects, or that they are well documented, and workarounds provided.   Write your own struct to register mapping, and *YOU* take on that heavy responsibility.   You should definitely emit a warning, or even an error if the compiler and its version don't match one you have tested with your structs.
 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 17814
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: [ARM] [Sam] Uh is it a good idea to map registers to structs
« Reply #2 on: January 21, 2022, 09:19:20 am »
Well microchip are so good that they make a mess of explaining registers. Boy is the TC CTRLA register a damn nightmare. I have to first set the counter to the one of three modes I want so that I can write to registers that otherwise do not exist but then I cannot set the pre-scaler unless I do it when I enable the counter. Apparently non of the bits I want to write are synchronised but something weird is happening and it took trial and error.

And no having the sum total of the "clear" instructions on how this works broken up into little titbits of information spread across 1'500 pages.
 

Offline emece67

  • Frequent Contributor
  • **
  • !
  • Posts: 614
  • Country: 00
Re: [ARM] [Sam] Uh is it a good idea to map registers to structs
« Reply #3 on: January 21, 2022, 09:35:38 am »
.
« Last Edit: August 19, 2022, 05:10:40 pm by emece67 »
 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 17814
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: [ARM] [Sam] Uh is it a good idea to map registers to structs
« Reply #4 on: January 21, 2022, 09:53:16 am »
I'm using the SAMC, the link on there for that just takes you to microchip and an install file.
 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 17814
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: [ARM] [Sam] Uh is it a good idea to map registers to structs
« Reply #5 on: January 21, 2022, 09:58:02 am »
They are in fact already included in the C file of the SystemInit() that is just there in the project. I would need to include them where I want them. So microchip do structure map registers. May have a look. Depends on how painful it is to use.
 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 17814
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: [ARM] [Sam] Uh is it a good idea to map registers to structs
« Reply #6 on: January 21, 2022, 10:04:56 am »
But they do not struct the registers that I can see so maybe that is a warning.....
 

Offline westfw

  • Super Contributor
  • ***
  • Posts: 4199
  • Country: us
Re: [ARM] [Sam] Uh is it a good idea to map registers to structs
« Reply #7 on: January 21, 2022, 10:07:45 am »


Quote
So microchip do structure map registers




Yes, they do. It’s practically required to be at all compatible with the ARM CMSIS standard(s)


Beware that the format and naming scheme changed significantly after Microchip took over.  In particular, all of the bitfield unions went away (which is probably good.  They were dangerous!)


(They also still provide non-structure-based defines.)
 

Offline westfw

  • Super Contributor
  • ***
  • Posts: 4199
  • Country: us
Re: [ARM] [Sam] Uh is it a good idea to map registers to structs
« Reply #8 on: January 21, 2022, 10:13:56 am »
See https://microchipdeveloper.com/32arm:sam-bare-metal-c-programming#toc1


(Although that describes the “old” scheme)

 

Offline Berni

  • Super Contributor
  • ***
  • Posts: 4946
  • Country: si
Re: [ARM] [Sam] Uh is it a good idea to map registers to structs
« Reply #9 on: January 21, 2022, 10:32:55 am »
Yep it is pretty standard to use a struct to map out hardware registers.

Most of the time the hardware registers have the same number of bits as the CPU natural bit count and the same as the width of the bus that the peripheral is connected to. So in these cases no particular compiler magic is needed to have things work since the CPU will wants to access the registers a whole register at a time while the compiler won't optimize it out since its marked as volatile. So a 32bit CPU makes a 32bit read onto a 32bit bus from a 32bit register.

When it comes to say 16bit registers on a 32bit system then you will want to explicitly tell the compiler that this is a packed struct (compiler specific) or it might insert gaps in the struct for the compilers convenience. Will be fine for any access to it as most 32bit CPUs have extra 16bit and 8bit memory operation instructions and they are faster than doing a 32bit read and then masking off bits so the compiler will use them.

But when it comes to individual bit manipulation of structs... that's the wild west. Most computers don't have an instruction for directly flipping a bit in memory, nor is the memory bus capable of doing such a operation, so the compiler will do a workaround using some extra code that does a read,modify,store for it. You have no idea how it will do that exactly. Also the C code for direct bit access can be inconsistent. So what is generally done is that individual bits of a struct are not exposed, but instead you get a #define with the bitmask for working with that bit. You have to do the read and write yourself (so you can do the right kind if that is important) and use that mask to flip bits yourself.

Another bonus for using structs for hardware registers is that for IDEs that do not have a good hardware register viewing plugin you can just put that struct into a watch window and see all the registers(But as before careful for read sensitive locations)
 

Offline newbrain

  • Super Contributor
  • ***
  • Posts: 1719
  • Country: se
Re: [ARM] [Sam] Uh is it a good idea to map registers to structs
« Reply #10 on: January 21, 2022, 10:41:55 am »
I'm using the SAMC, the link on there for that just takes you to microchip and an install file.
On that page, in the Microchip section, you can find the package for your specific MCU family, specifically there is one for SAMC20 and one for SAMC21.
The package (.pack) is just a zip file, you can extract it from windows if you rename it to .zip or directly with 7-zip.
Inside, in the include folder for your MCU, you'll find the CMSIS include file with all the peripheral registers definition.

The correct way to use it is to include the sam.h file, and provide the needed #defines to your toolchain.
There are also startup, system, and link script files for the major compilers, including of course gcc.
Nandemo wa shiranai wa yo, shitteru koto dake.
 

Offline emece67

  • Frequent Contributor
  • **
  • !
  • Posts: 614
  • Country: 00
Re: [ARM] [Sam] Uh is it a good idea to map registers to structs
« Reply #11 on: January 21, 2022, 10:53:53 am »
.
« Last Edit: August 19, 2022, 05:10:49 pm by emece67 »
 

Online mfro

  • Regular Contributor
  • *
  • Posts: 210
  • Country: de
Re: [ARM] [Sam] Uh is it a good idea to map registers to structs
« Reply #12 on: January 21, 2022, 11:06:16 am »
A more general answer to the question would be: it depends  :-DD.

The purist would say: don't do it, it's potentially dangerous and non-portable.

The pragmatist would (probably, depending on the circumstances) say: if you know what you're doing, the benefits might outweigh the risk.

In some situations, for example, it's extremely convenient to assign to a set of adjacent hardware registers with one single structure assignment. That way you can hold presets in a structure and only need one single assignment for a 'context' switch.

Usual preconditions apply:
  • know your hardware
  • know your compiler

Beethoven wrote his first symphony in C.
 

Online Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6242
  • Country: fi
    • My home page and email address
Re: [ARM] [Sam] Uh is it a good idea to map registers to structs
« Reply #13 on: January 21, 2022, 11:12:43 am »
What are the alternatives, really?

Looking at the possible ways to do it (including example code, so one has something real to base an opinion on) might be more useful.



Even when structures are used to describe the register contents, they can be accessed in different ways. 

Consider PORTA on SAMC21.  You can access it either through the IOBUS at addresses 0x60000000–0x6000005F, or through the AHB-APB bridge at addresses 0x41000000–0x4100005F.  Let's assume you have defined struct samc21_port to correspond to the SAMC21 PORT registers (Section 28.7. Register Summary in the datasheet), and you want to expose the port via PORTA and PORTA_BRIDGE; the former used for CPU access (single cycle operations, highest priority), and the latter for DMA and such (slower, lower priority accesses).

There are three main ways you can declare the two variables.
  • Use a linker script to assign the exact address for the symbols:
        extern volatile struct samc21_port PORTA;
        extern volatile struct samc21_port PORTA_BRIDGE;
     
  • Declare the "variables" as macros referring to a specific memory address:
        #define  PORTA         ((volatile struct samc21_port *)0x60000000)
        #define  PORTA_BRIDGE  ((volatile struct samc21_port *)0x41000000)
     
  • Declare variables as pointers to the structures:
        volatile struct samc21_port *const PORTA = 0x60000000;
        volatile struct samc21_port *const PORTA_BRIDGE = (volatile struct samc21_port *const)0x41000000;
     
  • Declare variables as compile-time constant pointers to the structures: (C++)
        volatile struct samc21_port *constexpr PORTA = 0x60000000;
        volatile struct samc21_port *constexpr PORTA_BRIDGE = 0x41000000;
These each have their upsides and downsides.

In the first case, the compiler cannot make any compile-time assumptions about the address, and may generate silly code because of that.  For example, instead of accessing a nearby memory address by subtracting or adding the difference to the pointer, it will have to load the full 32-bit address.
This is the only one that uses e.g. PORTA.OUTTGL instead of PORTA->OUTTGL.
(If you rename the symbol as say PORTA_struct, and define a macro #define PORTA (&PORTA_struct), you can use PORTA->OUTTGL in this case as well.)

In the second case, PORTA and PORTA_BRIDGE are not variables at all, they are just expressions (expanded from preprocessor macros).
(If you were to define e.g. PORTA as (*(volatile struct samc21_port *)0x60000000), noting the dereference at the beginning, you would use PORTA.OUTTGL in this case too.)

In the third case, each access may incur an extra memory load or dereference, because while the variable is marked const, the compiler may decide to generate code that loads the address from some address in Flash, instead of constructing it then and there.  However, this seems to be the most common construction.

The fourth case is similar to the second case, but uses the C++ constexpr to denote that the address of the pointer is a compile-time constant.

In all cases, one will need to write some test code to be used with any new compiler, to see that it constructs the structure as desired (since things like bitfield fill order is up to the compiler), and that the chosen use pattern generates acceptable code.  If it doesn't, well, there isn't much you can do except test if the code generated by another compatible definition (that requires no code changes) yields better results... but that is the risk here.
 
The following users thanked this post: thm_w

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 14445
  • Country: fr
Re: [ARM] [Sam] Uh is it a good idea to map registers to structs
« Reply #14 on: January 21, 2022, 07:10:51 pm »
I do that all the time.
You need to use a compiler that allows defining alignment, to make sure you won't run into issues. All compilers I know of do. GCC has an attribute for that.
Typically how I define a struct for 32-bit aligned registers:

Code: [Select]
typedef struct __attribute__((__packed__, aligned(4))) { ... } xxx_t
Note that C11 introduces the _Alignas keyword, so you don't even need to resort to compiler-defined extensions. If you write C11-compliant code, you can do this in a 100% standard and portable way.

I see no reason against doing this, as long as you take care of alignment as said above.
 

Online Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6242
  • Country: fi
    • My home page and email address
Re: [ARM] [Sam] Uh is it a good idea to map registers to structs
« Reply #15 on: January 22, 2022, 07:41:13 am »
You forgot about bitfields, SiliconWizard.
 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 17814
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: [ARM] [Sam] Uh is it a good idea to map registers to structs
« Reply #16 on: January 25, 2022, 03:27:26 pm »
Right, I've just lost the will to live in the SAMC header files. I'm so structured I don't know my arse from my mouth anymore!

So if I have got this right.

1) They make a structure of the register
2) they union that with a register type of the size of the register
3) This union is a new "type"

Code: [Select]

typedef union {
  struct {
    uint32_t NVMP:16;          /*!< bit:  0..15  NVM Pages                          */
    uint32_t PSZ:3;            /*!< bit: 16..18  Page Size                          */
    uint32_t :1;               /*!< bit:     19  Reserved                           */
    uint32_t RWWEEP:12;        /*!< bit: 20..31  RWW EEPROM Pages                   */
  } bit;                       /*!< Structure used for bit  access                  */
  uint32_t reg;                /*!< Type      used for register access              */
} NVMCTRL_PARAM_Type;


4) the create a new structured "type" of all of the registers of that peripheral
5) this includes defining these types as volatile
6) by using nonsensical things like __IO and the rest that all get replaced by "volatile" so all mean the same thing

Code: [Select]

typedef struct {
  __IO NVMCTRL_CTRLA_Type        CTRLA;       /**< \brief Offset: 0x00 (R/W 16) Control A */
       RoReg8                    Reserved1[0x2];
  __IO NVMCTRL_CTRLB_Type        CTRLB;       /**< \brief Offset: 0x04 (R/W 32) Control B */
  __IO NVMCTRL_PARAM_Type        PARAM;       /**< \brief Offset: 0x08 (R/W 32) NVM Parameter */
  __IO NVMCTRL_INTENCLR_Type     INTENCLR;    /**< \brief Offset: 0x0C (R/W  8) Interrupt Enable Clear */
       RoReg8                    Reserved2[0x3];
  __IO NVMCTRL_INTENSET_Type     INTENSET;    /**< \brief Offset: 0x10 (R/W  8) Interrupt Enable Set */
       RoReg8                    Reserved3[0x3];
  __IO NVMCTRL_INTFLAG_Type      INTFLAG;     /**< \brief Offset: 0x14 (R/W  8) Interrupt Flag Status and Clear */
       RoReg8                    Reserved4[0x3];
  __IO NVMCTRL_STATUS_Type       STATUS;      /**< \brief Offset: 0x18 (R/W 16) Status */
       RoReg8                    Reserved5[0x2];
  __IO NVMCTRL_ADDR_Type         ADDR;        /**< \brief Offset: 0x1C (R/W 32) Address */
  __IO NVMCTRL_LOCK_Type         LOCK;        /**< \brief Offset: 0x20 (R/W 16) Lock Section */
       RoReg8                    Reserved6[0x6];
  __I  NVMCTRL_PBLDATA0_Type     PBLDATA0;    /**< \brief Offset: 0x28 (R/  32) Page Buffer Load Data 0 */
  __I  NVMCTRL_PBLDATA1_Type     PBLDATA1;    /**< \brief Offset: 0x2C (R/  32) Page Buffer Load Data 1 */
} Nvmctrl;


7) in the partnumber specific header of that family this is then defined as

Code: [Select]

#define NVMCTRL           ((Nvmctrl  *)0x41004000UL) /**< \brief (NVMCTRL) APB Base Address */


8 ) which in effect is a massive pointer to the peripheral base address such that
9) NVMCTRL.PARAM.NVMP can be rea and used as a variable?

Phew that is a lot of going in circles. I sort of get it as the chip number specific header "just" has to define the top level structure as the base address and all the other files stay the same as the registers are all organized the same.

Oh, but now that I try to compiles I get for:

Code: [Select]

uint16_t EEPROM_START = NVMCTRL.PARAM.NVMP ;



Error      '1090535424u' is a pointer; did you mean to use '->'?   

I don't know what the hell I want to use!




 

Online Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6242
  • Country: fi
    • My home page and email address
Re: [ARM] [Sam] Uh is it a good idea to map registers to structs
« Reply #17 on: January 25, 2022, 03:36:35 pm »
If you use
    #define  NVMCTRL  ((Nvmctrl  *)0x41004000UL) /**< \brief (NVMCTRL) APB Base Address */
then you use
    NVMCTRL->PARAM.NVMP
because this NVMCTRL is a pointer to the APB Base.

If you use
    #define  NVMCTRL  (*(Nvmctrl  *)0x41004000UL) /**< \brief (NVMCTRL) APB Base */
then you use
    NVMCTRL.PARAM.NVMP
because this NVMCTRL is the structure instance at the APB Base address.

Does this clear it up for you, or should I elaborate?  (I'd be happy to.)
 

Online NorthGuy

  • Super Contributor
  • ***
  • Posts: 3143
  • Country: ca
Re: [ARM] [Sam] Uh is it a good idea to map registers to structs
« Reply #18 on: January 25, 2022, 03:41:00 pm »
Using structs is a very good idea. ARM is a load/store architecture, so all accesses to memory are already indirect. You have nothing to lose.
 

Offline Siwastaja

  • Super Contributor
  • ***
  • Posts: 8168
  • Country: fi
Re: [ARM] [Sam] Uh is it a good idea to map registers to structs
« Reply #19 on: January 25, 2022, 03:46:42 pm »
Great example of using union to allow flexibility in code writing. Even better, IMHO, would be to make that struct inside union anonymous (without name) to allow accessing with less extra writing (and hide the existence of that union from view): i.e., if you removed the part "bit" from the definition, you could access the fields simply:

NVMCTRL->PSZ = 2;

instead of

NVMCTRL->bit.PSZ = 2;

which is what you need to do now. I don't like such extra writing because when you reference it to the manual, the only thing you see is PSZ field of NVMCTRL register. No "bit" anywhere.

In any case, such combination of bitfield and full 32-bit access type provides best of the two worlds, you can choose what to use. Sadly, C lacks a way of write a volatile-qualified variable in multiple parts then say "I'm done, now do the access". This is why writing the bitfields one by one is less efficient than writing the whole register in one go; but definitely more readable.

It would be indeed nice if manuals and headers contained absolute addresses directly but you have to give manufacturers some slack here, they have zillion of parts with basically the same peripherals but at different locations in memory... Hence, the idea of summing the addresses in a few steps. They do this in both manuals and headers.

I don't also like not-invented-here naming like __IO when you just want to say volatile, but to be fair, they might need some additional compiler-specific attributes or something, so it's not a stupid idea in general. And every vendor does the same, so you quickly learn that the __IO thing must mean volatile.

You can pretty much trust that the volatile has to be there somehow; otherwise, nothing would work at all, and having completely nonfunctional headers would be exposed pretty soon.
« Last Edit: January 25, 2022, 03:50:43 pm by Siwastaja »
 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 17814
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: [ARM] [Sam] Uh is it a good idea to map registers to structs
« Reply #20 on: January 25, 2022, 04:17:46 pm »
If you use
    #define  NVMCTRL  ((Nvmctrl  *)0x41004000UL) /**< \brief (NVMCTRL) APB Base Address */
then you use
    NVMCTRL->PARAM.NVMP
because this NVMCTRL is a pointer to the APB Base.

If you use
    #define  NVMCTRL  (*(Nvmctrl  *)0x41004000UL) /**< \brief (NVMCTRL) APB Base */
then you use
    NVMCTRL.PARAM.NVMP
because this NVMCTRL is the structure instance at the APB Base address.

Does this clear it up for you, or should I elaborate?  (I'd be happy to.)

Where ? this does not seem to exist

I see there are two identically names files for each peripheral - really?

All i wanted to do was read a value of of the register, that is this afternoon gone with no result.
 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 17814
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: [ARM] [Sam] Uh is it a good idea to map registers to structs
« Reply #21 on: January 25, 2022, 04:21:34 pm »
uint16_t EEPROM_START = NVMCTRL->PARAM.NVMP ;

Error      'NVMCTRL_PARAM_Type {aka volatile union <anonymous>}' has no member named 'NVMP'   


---------------------------------------------------------------------------------------------------------------------------

uint16_t EEPROM_START = NVMCTRL.PARAM.NVMP ;

Error      '1090535424u' is a pointer; did you mean to use '->'?   
 

Online mfro

  • Regular Contributor
  • *
  • Posts: 210
  • Country: de
Re: [ARM] [Sam] Uh is it a good idea to map registers to structs
« Reply #22 on: January 25, 2022, 04:30:40 pm »
All i wanted to do was read a value of of the register, that is this afternoon gone with no result.

What you tried (wrongly) was
Code: [Select]
uint16_t EEPROM_START = NVMCTRL.PARAM.NVMP ;

Make that
Code: [Select]
uint16_t EEPROM_START = NVMCTRL->PARAM.bit.NVMP ;

and it will (most likely  8)) work.

It has been explained above, already. In C, there is no way (other than fiddling with the linker script) to create a struct that lives at a specific hardware address (which is what your register does). You have to take the detour with a pointer to that struct.

NVMCTRL->PARAM.bit.NVMP is just another way to express (*NVMCTRL).PARAM.bit.NVP (which is what you need).

[edit: as discussed already, the union inside the struct is not anonymous, hence it has to be explicitely selected]
« Last Edit: January 25, 2022, 04:34:32 pm by mfro »
Beethoven wrote his first symphony in C.
 
The following users thanked this post: Nominal Animal

Offline Siwastaja

  • Super Contributor
  • ***
  • Posts: 8168
  • Country: fi
Re: [ARM] [Sam] Uh is it a good idea to map registers to structs
« Reply #23 on: January 25, 2022, 04:35:30 pm »
The error message is quite helpful: it tells you it definitely is a pointer.

Now just access the struct through that pointer. In other words, dereference it. Using * operator, or the -> shorthand, like the compiler suggests.
 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 17814
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: [ARM] [Sam] Uh is it a good idea to map registers to structs
« Reply #24 on: January 25, 2022, 04:58:14 pm »
uint16_t EEPROM_START = (*NVMCTRL).PARAM.bit.NVMP ;

Error      initializer element is not constant

---------------------------------------------------------------------------------

uint16_t EEPROM_START = NVMCTRL->PARAM.bit.NVMP ;

Error      initializer element is not constant

------------------------------------------------------------------

const uint16_t EEPROM_START

also does not work. I think I will stop trying to be clever and use the simple defines that include the information I am trying to calculate as plain defines.

 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf