[ARM] [Sam] Uh is it a good idea to map registers to structs - Page 1
Forum section: electronics / general

author: Simon
author_rank: Unknown
timestamp: January 21, 2022, 08:36:12 am
content: 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

author: Ian.M
author_rank: Super Contributor
timestamp: January 21, 2022, 09:02:04 am
content: 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.

author: Simon
author_rank: Unknown
timestamp: January 21, 2022, 09:19:20 am
content: 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.

author: emece67
author_rank: Frequent Contributor
timestamp: January 21, 2022, 09:35:38 am
content: .

author: Simon
author_rank: Unknown
timestamp: January 21, 2022, 09:53:16 am
content: I'm using the SAMC, the link on there for that just takes you to microchip and an install file.

author: Simon
author_rank: Unknown
timestamp: January 21, 2022, 09:58:02 am
content: 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.

author: Simon
author_rank: Unknown
timestamp: January 21, 2022, 10:04:56 am
content: But they do not struct the registers that I can see so maybe that is a warning.....

author: westfw
author_rank: Super Contributor
timestamp: January 21, 2022, 10:07:45 am
content: QuoteYes, 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.)

author: westfw
author_rank: Super Contributor
timestamp: January 21, 2022, 10:13:56 am
content: Seehttps://microchipdeveloper.com/32arm:sam-bare-metal-c-programming#toc1(Although that describes the “old” scheme)

author: Berni
author_rank: Super Contributor
timestamp: January 21, 2022, 10:32:55 am
content: 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)

author: newbrain
author_rank: Super Contributor
timestamp: January 21, 2022, 10:41:55 am
content: Quote from: Simon on January 21, 2022, 09:53:16 amOn 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.

author: emece67
author_rank: Frequent Contributor
timestamp: January 21, 2022, 10:53:53 am
content: .

author: mfro
author_rank: Regular Contributor
timestamp: January 21, 2022, 11:06:16 am
content: A more general answer to the question would be: it depends.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 hardwareknow your compiler

author: Nominal Animal
author_rank: Super Contributor
timestamp: January 21, 2022, 11:12:43 am
content: 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 definedstruct samc21_portto correspond to the SAMC21 PORT registers (Section 28.7. Register Summary in thedatasheet), and you want to expose the port viaPORTAandPORTA_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.OUTTGLinstead ofPORTA->OUTTGL.(If you rename the symbol as sayPORTA_struct, and define a macro#define PORTA (&PORTA_struct), you can usePORTA->OUTTGLin this case as well.)In the second case,PORTAandPORTA_BRIDGEare not variables at all, they are just expressions (expanded from preprocessor macros).(If you were to define e.g.PORTAas(*(volatile struct samc21_port *)0x60000000), noting the dereference at the beginning, you would usePORTA.OUTTGLin this case too.)In the third case, each access may incur an extra memory load or dereference, because while the variable is markedconst, 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++constexprto 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.

author: SiliconWizard
author_rank: Super Contributor
timestamp: January 21, 2022, 07:10:51 pm
content: 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:typedef struct __attribute__((__packed__, aligned(4))) { ... } xxx_tNote 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.

author: Nominal Animal
author_rank: Super Contributor
timestamp: January 22, 2022, 07:41:13 am
content: You forgot about bitfields, SiliconWizard.

author: Simon
author_rank: Unknown
timestamp: January 25, 2022, 03:27:26 pm
content: 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 register2) they union that with a register type of the size of the register3) This union is a new "type"Code: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 peripheral5) this includes defining these types as volatile6) by using nonsensical things like __IO and the rest that all get replaced by "volatile" so all mean the same thingCode:typedef struct {__IO NVMCTRL_CTRLA_Type CTRLA; /**< \brief Offset: 0x00 (R/W 16) Control A */RoReg8 Reserved1;__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;__IO NVMCTRL_INTENSET_Type INTENSET; /**< \brief Offset: 0x10 (R/W 8) Interrupt Enable Set */RoReg8 Reserved3;__IO NVMCTRL_INTFLAG_Type INTFLAG; /**< \brief Offset: 0x14 (R/W 8) Interrupt Flag Status and Clear */RoReg8 Reserved4;__IO NVMCTRL_STATUS_Type STATUS; /**< \brief Offset: 0x18 (R/W 16) Status */RoReg8 Reserved5;__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;__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 asCode:#define NVMCTRL ((Nvmctrl *)0x41004000UL) /**< \brief (NVMCTRL) APB Base Address */8 ) which in effect is a massive pointer to the peripheral base address such that9) 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: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!

author: Nominal Animal
author_rank: Super Contributor
timestamp: January 25, 2022, 03:36:35 pm
content: If you use#define NVMCTRL ((Nvmctrl *)0x41004000UL) /**< \brief (NVMCTRL) APB Base Address */then you useNVMCTRL->PARAM.NVMPbecause this NVMCTRL is a pointer to the APB Base.If you use#define NVMCTRL (*(Nvmctrl *)0x41004000UL) /**< \brief (NVMCTRL) APB Base */then you useNVMCTRL.PARAM.NVMPbecause this NVMCTRL is the structure instanceatthe APB Base address.Does this clear it up for you, or should I elaborate? (I'd be happy to.)

author: NorthGuy
author_rank: Super Contributor
timestamp: January 25, 2022, 03:41:00 pm
content: 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.

author: Siwastaja
author_rank: Super Contributor
timestamp: January 25, 2022, 03:46:42 pm
content: 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 ofNVMCTRL->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 volatilehasto be there somehow; otherwise, nothing would work at all, and having completely nonfunctional headers would be exposed pretty soon.

author: Simon
author_rank: Unknown
timestamp: January 25, 2022, 04:17:46 pm
content: Quote from: Nominal Animal on January 25, 2022, 03:36:35 pmWhere ? this does not seem to existI 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.

author: Simon
author_rank: Unknown
timestamp: January 25, 2022, 04:21:34 pm
content: 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 '->'?

author: mfro
author_rank: Regular Contributor
timestamp: January 25, 2022, 04:30:40 pm
content: Quote from: Simon on January 25, 2022, 04:17:46 pmWhat you tried (wrongly) wasCode:uint16_t EEPROM_START = NVMCTRL.PARAM.NVMP ;Make thatCode:uint16_t EEPROM_START = NVMCTRL->PARAM.bit.NVMP ;and it will (most likely) 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]

author: Siwastaja
author_rank: Super Contributor
timestamp: January 25, 2022, 04:35:30 pm
content: The error message is quite helpful: it tells you it definitelyisa pointer.Now just access the struct through that pointer. In other words, dereference it. Using * operator, or the -> shorthand, like the compiler suggests.

author: Simon
author_rank: Unknown
timestamp: January 25, 2022, 04:58:14 pm
content: 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_STARTalso 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.

author: Siwastaja
author_rank: Super Contributor
timestamp: January 25, 2022, 05:16:29 pm
content: You can't access a peripheral register as an initializer for a global or static variable. Their initializers must be compile time constants. Think about it: when would the compiler run the code needed to get that value? For a local variable, that's OK, then the compiler emits the code to evaluate the value at that point.If you want to "initialize" a global (or static) variable like that, just define it without initialization:uint16_t EEPROM_START;and then wherever you want to write the value at the NVMP register to it, do it:EEPROM_START = NVMCTRL->PARAM.bit.NVMP;If this information is available right at the boot, just do it beginning of main(), for example.Am I right guessing this NVMP is some information field so that you use it instead of hard-coded number so you can port the code to different MCUs, with different value of NVMP?

author: Simon
author_rank: Unknown
timestamp: January 25, 2022, 05:23:09 pm
content: Quote from: Siwastaja on January 25, 2022, 05:16:29 pmYesAlthough as these numbers are defined in the header for each variant I just need to use the define.

author: Nominal Animal
author_rank: Super Contributor
timestamp: January 25, 2022, 05:23:26 pm
content: I definitely missed the fact that the structure inside the union wasn't anonymous, since I habitually use anonymous members in such cases. Apologies.Quote from: Simon on January 25, 2022, 04:58:14 pmWhat is this expression supposed to achieve?Right now it says "assign the current value of the bitfieldNVMPto variableEEPROM_START".If you put that in the global scope, the compiler cannot tell exactlywhenit is supposed to do that assignment.If the right side was a compile-time constant, then it would not matter; so, for sanity, only constant expressions are allowed as initializers for file scope variables.If instead you wanted to setEEPROM_STARTaddress to the address of theNVMPmember, something likeuint16_t EEPROM_START = (uint16_t)&(NVMCTRL->PARAM.bit.NVMP);but that will not work, becauseNVMPis a bitfield, and you can't take the address of a bitfield in C. Instead, you'll want to useuint16_t EEPROM_START = (uint16_t)&(NVMCTRL->PARAM.bit);or evenuint16_t EEPROM_START = (uint16_t)&(NVMCTRL->PARAM);

author: Simon
author_rank: Unknown
timestamp: January 25, 2022, 05:37:50 pm
content: So I'll just use the simple defines. I want the code to be easy to write and easy to read. As there is a header file per device variant that uses the same define names it will serve the same purpose. I just type REG_peripheral name and keep following the suggestions until I get the register I want. I can do bit masks.

author: Siwastaja
author_rank: Super Contributor
timestamp: January 25, 2022, 06:06:36 pm
content: Classical bit masks are fine, you don't need to use bitfields, and that union gives you the choice. The idea of bitfields is readability (and obviously writability), but there are a few catches, like the implementation defined ordering (which is no issue if you always use GCC and the same platform), and the performance issues when accessing volatile registers, but that isn't because of the bitfields per se, it's just that bitfields make the problem much more severe.Even with bitmasks, you hit the performance issues: if you want to set a bit and clear another, you normally do:reg &= ~(1UL << 5);reg |= 1UL<<3;But this results in two read-modify-write cycles for volatile reg, while what you usually want, but don't bother writing, is:tmp = reg; // tmp is NOT qualified volatiletmp &= ~(1UL <<5);tmp |= 1UL<<3;reg = tmp;If only C had some mechanism to say, hey, you can temporarily ignore the volatile qualifiers, let the compiler combine and reorder operations, and then bang, end that region with some special keyword to assign the results to the memory locations. That would save hundreds of bytes of initialization code space right away.

author: SiliconWizard
author_rank: Super Contributor
timestamp: January 25, 2022, 06:11:33 pm
content: Quote from: Siwastaja on January 25, 2022, 06:06:36 pmYou're right! Performance is usually not an issue when accessing non-volatile variables, because the optimizer will group accesses and it won't make a difference with using bit masks. But if using a volatile-qualified object, that's completely different. *Every* access of a bitfield will lead to an actual memory access. This can be highly inefficient.Now it's all in the way you use the bit fields. As you showed, you can also have performance issues if you use bit masks in a number of separate assignments (with volatile objects).In both cases, one way of circumventing it is to use temporary non-volatile variables and assign (or read from) only ONCE to the volatile object. Not as elegant, of course.

author: Siwastaja
author_rank: Super Contributor
timestamp: January 25, 2022, 06:35:47 pm
content: Quote from: SiliconWizard on January 25, 2022, 06:11:33 pmI'm always right, even when I'm not!Anyway, this is a good example how C is not "portable assembler". The solution whichlooksmore elegant, shorter, and "right", produces the unwanted result, which is longer, and doing more than necessary. While the code that produces just the minimum elegant output, looks longer and ugly.This limitation is because the actual machine (and thus, assembly language) has the concept of type per every operation, while C has type assigned to each variable, and accesses happen automatically based on that stored type. Usually the type stays consistent which makes programming easier, but when it does not, it results in quite confusing casting orgies, or usage of temporary variables like in my example, because you can't cast the volatile away in that example. And all you wanted is to tell compiler, "modify the peripheral register like this", and the only tools you have is the choice between "access the peripheral register always when its name appear in code" or "don't access the peripheral register at all", and you need to work around that type limitation.But I have to clarify this is all pretty acceptable because C at least is quite well standardized and not massively complicated, so you just have to learn how it works. This is at leastpossible, and it's not a moving target.

author: NorthGuy
author_rank: Super Contributor
timestamp: January 25, 2022, 08:38:05 pm
content: Quote from: Siwastaja on January 25, 2022, 06:06:36 pmI don't think bitfields are less efficient than masks. Bitfield operations are likely to be implemented through masks by the compiler, so it is likely to be exactly the same as writing down the masks. If you want to reduce reads you can always create a temprary bitfielded variable the same as you did with masks.Moreover, many CPUs have bitfield manipulation instructions, so using bitfields may be more efficient than using masks, especially with multi-bit bitfields.There are some cases where masks must be used, for example when different parts of the same hardware register are accessed from both the main code and from the interrupt. Other than that, there's nothing wrong with using bitfields.

author: Simon
author_rank: Unknown
timestamp: January 25, 2022, 09:32:20 pm
content: It's Microchip, the manufacturer that supply the header files, full of structured and bit fielded registers, I assume they, cough, know what they are doing. i also assume they only warrant it to work with "their" compiler that even though it is the GCC-ARM one they may check each update to make sure that bitfield packing has not been broken before redistributing it. I'll just use the register defines: KISS

author: westfw
author_rank: Super Contributor
timestamp: January 26, 2022, 12:34:42 am
content: QuoteThat's part of the ARM CMSIS spec. They use it to distinguish read/write registers vs read-only or write-only registers.https://www.keil.com/pack/doc/CMSIS/Core/html/group__peripheral__gr.htmlIdon't know if there are any compilers that can actually make use of such information, but I guess it makes the .h file slightly more self-documenting, which I guess is good. (But as per an ongoing avr-freaks discussion, we also sort of need a tag of some kind for registers whose read value and write value are different.)(Of course, if we're talking about NVMCTRL->PARAM, it's actually a read-only register, even though the .h files have it tagged as __IO. Sigh.)QuoteYou didn't look at the changes in the new (MPLABX) packs, did you? All the bitfields are gone.Code:#define NVMCTRL_PARAM_NVMP_Pos _U_(0) /**< (NVMCTRL_PARAM) NVM Pages Position */#define NVMCTRL_PARAM_NVMP_Msk (_U_(0xFFFF) << NVMCTRL_PARAM_NVMP_Pos) /**< (NVMCTRL_PARAM) NVM Pages Mask */#define NVMCTRL_PARAM_NVMP(value) (NVMCTRL_PARAM_NVMP_Msk & ((value) << NVMCTRL_PARAM_NVMP_Pos)):typedef struct{ /* Non-Volatile Memory Controller */__IO uint16_t NVMCTRL_CTRLA; /**< Offset: 0x00 (R/W 16) Control A */__I uint8_t Reserved1;__IO uint32_t NVMCTRL_CTRLB; /**< Offset: 0x04 (R/W 32) Control B */__IO uint32_t NVMCTRL_PARAM; /**< Offset: 0x08 (R/W 32) NVM Parameter */__IO uint8_t NVMCTRL_INTENCLR; /**< Offset: 0x0C (R/W 8) Interrupt Enable Clear */:} nvmctrl_registers_t;(Heh. Still labeled as __IO, though.)QuoteCode:void myParamInit() {NVMCTRL_PARAM_Type tmpParam = NVMCTRL->PARAM;tmpParam.bit.NVMP = foo;tmpParam.bit.PSZ = bar;tmpParam.bit.RWEEO = baz;NVMCTRL->PARAM = tmpParam;}Or something like that. It's a neat use of the way that Atmel defined all the sub-types. Last time I looked, ASF libraries were full of code like this (but I can't find an example now. :-( )(of course, you need to realize that this is useful.)

author: westfw
author_rank: Super Contributor
timestamp: January 26, 2022, 12:46:26 am
content: QuoteYawn. I've been doing this for 30+ years now, and the benefits far outweigh the "dangers."QuoteI'm not sure how alignment comes into it. Presumably the addresses of the IO registers are aligned as they need to be.You do need a compiler that will "pack" the structures, and you need to be away of (CPU-dependent) bitfield ordering (IFF you use bitfields.) But any compiler vendor who does something really strange with structure packing (without a workaround) is going to be laughed out of the market. And if we're talking about IO registers, they're already highly not-portable.(Now, mapping structures onto something like networking packets (or protocol headers within a packet) is somewhat more dangerous. THEY can have all sorts of alignment issues.)

author: Nominal Animal
author_rank: Super Contributor
timestamp: January 26, 2022, 01:22:59 am
content: Quote from: westfw on January 26, 2022, 12:34:42 amOne non-struct approach would be something likeCode:/* Update Param with new values.Use negative to indicate no change in corresponding value, orzero or positive to set the new value.*/void myParamSet(int foo, int bar, int baz) { /* ... omitted ... */ }where the omitted function body reads the original register(s), modifies them according to the parameters – noting that this assumes valid values are nonnegative and at least two bits smaller than a full unsigned int – and then updates the registers.For example,myParamSet(0, -1, -1);would only clearfooto zero, leavingbarandbazunchanged.This is particularly useful in the case when the registers being modified are normally locked using some kind of protection register, as it can hide such details. It also ensures a single read-modify-write cycle.This does not work that well when the bit fields in the register are used in completely different contexts. That is relatively rare, though.This is also not that useful when there are too many fields (function parameters), unless the function is static and gets inlined (in which case the parameters do not need to be passed on the stack).Note: I am not saying this is better. I am just showing what one of the alternatives looks like.

author: SiliconWizard
author_rank: Super Contributor
timestamp: January 26, 2022, 01:55:04 am
content: Quote from: Siwastaja on January 25, 2022, 06:35:47 pmOh yep. Sure. We're certainly even splitting hairs here. That potential "inefficiency" will matter only if you're after saving a few cycles. If so, it's good to know what is efficient and what isn't, but even so, in this case, you sometimes have to resort to assembly. Otherwise, whatever approach you find more readable and more comfortable with is usually the way to go.What is important to know is that with volatile-qualified objects, the compiler can't optimize accesses and must honour all of them, in the order they appear. It has implications beyond efficiency. In some cases, for peripheral registers for instance, setting or clearing some bits *in a certain order* actually matters, so you wouldn't want the compiler to group accesses behind your back.

author: Simon
author_rank: Unknown
timestamp: January 26, 2022, 08:04:52 am
content: This is low level setup code that has to know the addresses of the registers. In the past I wrote my own, now I have delved into the files and found them defined in a clear enough way I am happy to not bother reinventing that wheel. But the structures and bit fields are going to be more pain than they are worth. I believe most of the desired bit field masks are already defined so meh.

author: Simon
author_rank: Unknown
timestamp: January 26, 2022, 08:39:16 am
content: While we are on the subject of documentation, attached is the memory organization. It's nonsense to me.Surely it should be, ( "row n" * "page bytes" * 4 ) + ( "page n" * "page bytes" ) + base address of page/row 0.Given#define FLASH_USER_PAGE_ADDR _UL_(0x00800000) /**< FLASH_USER_PAGE base address */#define FLASH_PAGE_SIZE 64Sorow 0 page 0 starts at 0*64*4 + 0*64 + 0x00800000 = 0x00800000row 0 page 1 starts at 0*64*4 + 1*64 + 0x00800000 = 0x00800040row 0 page 2 starts at 0*64*4 + 2*64 + 0x00800000 = 0x00800080row 0 page 3 starts at 0*64*4 + 3*64 + 0x00800000 = 0x008000C0row 1 page 0 starts at 1*64*4 + 0*64 + 0x00800000 = 0x00800100row 1 page 1 starts at 1*64*4 + 1*64 + 0x00800000 = 0x00800140....................................................

author: Nominal Animal
author_rank: Super Contributor
timestamp: January 26, 2022, 02:52:24 pm
content: Quote from: Simon on January 26, 2022, 08:39:16 amThat's how I read it, too.In other words, letPbe the size of each page in bytes,Bbe the base address,rbe the row number one wants to access, andpthe page number (0 to 3) one wants, the page address isB+P* (4 *r+p)which also matches your example addresses.

author: Simon
author_rank: Unknown
timestamp: January 26, 2022, 02:57:49 pm
content: Well that is not how I read their diagram but that is how I read the text and how I would implement it.

author: NorthGuy
author_rank: Super Contributor
timestamp: January 26, 2022, 03:02:44 pm
content: Quote from: SiliconWizard on January 26, 2022, 01:55:04 amThese are general consideration which appy whether you use structs or not. The difference in using structs (as opposed to accessing individual registers) is that they provide you a pointer to the hardware module with all the attached benefits.The pointer to the struct is a reference to the hardware module. Therefore, if your MCU has 5 moduli of the same kind, your code can access any of them without knowing which one of the modules it is accessing. This gives you an opportunity to switch to a different module if needed - either within your project, or in the next project, or even dynamically at the run time. You can even use dynamic allocation - such as allocating DMA moduli on demand.IMHO, this is a huge benefit. And on the load/store architecture there's almost no penalty in terms of efficiency.

author: Simon
author_rank: Unknown
timestamp: January 26, 2022, 03:16:52 pm
content: Quote from: NorthGuy on January 26, 2022, 03:02:44 pmThe other way is what I do. I define each address:Peripheral base addressOffset between instancesEach register of an instanceThis makes it very easy to write code that can access a giver register of any instance of that peripheral so you write setup code or common functions once.void do_thing_that_wants_to_write_to_a_register(instance){"pointery stuff" base address + instance offset * instance + register = thing ;}

author: Siwastaja
author_rank: Super Contributor
timestamp: January 26, 2022, 03:22:25 pm
content: Quote from: NorthGuy on January 26, 2022, 03:02:44 pmManufacturers do love to ruin that fun by making every instantiation of the peripheral slightly different. Different field widths in timers, different FIFO sizes, some instances lack some of the features. Or just have completely different peripherals which really do the same thing. Think about STM32H7 series, they have three (3!) different DMAs: MDMA, DMA and BDMA, and specifically some peripherals can only use BDMA, and some can only use DMA. Besides, DMA and BDMA are very similar and both are just design reuse from older ST devices (BDMA is the F0 series DMA renamed, DMA from higher-up devices), so why not have DMA1, DMA2, DMA3 all of the same design, instead of DMA1, DMA2 and BDMA. Maybe they saved 100 transistors. Probably not even that.Some manufacturers indeed love making programmer's job as difficult as possible. On the other hand, now I'm writing firmware for Nordic Semiconductor devices and I struggle in the opposite way, the peripheral design is just so clean and easy to use I need to concentrate in the actual application code, and the mental quality bar is now set higher. With STM32 for example, significant part of effort goes to working around broken mess ST gave us to handle, and code that works around it simply can't look beautiful.I can see my workflow totally changed: with ST devices it's "read documentation for hours, write code, test, doesn't work, read more, modify, test, find HW bugs, work around, test, read more, modify, test, modify, need more coffee, it seems to work, repeat 5 times, verify it actually works". With nRF parts it's "read documentation quickly, write code SLOOWLY and painfully because you need to actually concentrate, test -> works perfectly the first time".

author: cv007
author_rank: Frequent Contributor
timestamp: January 26, 2022, 04:19:45 pm
content: QuoteIt 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/7ec4YsK68This 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.hppYou 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.hppLearn 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

author: westfw
author_rank: Super Contributor
timestamp: January 27, 2022, 01:18:04 am
content: QuoteThis 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 withThe 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.)QuoteNot 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;Is more likely to get optimized effectively thanCode:USART1_brgen = BAUD2DIV(9600);USART1_ctrla = foo;USART1_ctrb = bar;(But see alsohttps://embdev.net/topic/426508)

author: cv007
author_rank: Frequent Contributor
timestamp: January 27, 2022, 11:25:33 am
content: QuoteI meant bitfields inside the struct.QuoteI 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/15rWsh8M7Produces the exact same code, but the functions are both harder to create and read.

author: bson
author_rank: Unknown
timestamp: January 27, 2022, 09:06:56 pm
content: 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 fornon-volatilebinary bit fields, like protocol headers. In this case the compiler can freely optimize access.

author: cv007
author_rank: Frequent Contributor
timestamp: January 28, 2022, 01:15:01 am
content: QuoteYou 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).