Mapping structures to hardware registers - Page 1
Forum section: electronics / general

author: Simon
author_rank: Unknown
timestamp: February 12, 2023, 07:12:29 pm
content: After a year of on and off programming and now coming to a new project I have been trying to get more into using whatever is available from the manufacturer, no not that code configuration bollocks.In my case I am using the SAMC but his would apply to any. So looking at the header files with some more understanding of structures, unions and pointers I believe I can use provided structures.So how do I do this? if I set up a pointer of type the typedef of the peripheral instance structure that points to the hardware address of the first register of the peripheral instance I can then use the variable to access the registers right?so something like:Code:Tc * tc0 = 0x42003000ul ;tc0.COUNT16.CTRLA.bit.ENABLE = 1 ;This would be general purpose counter of the SAMC

author: eutectique
author_rank: Frequent Contributor
timestamp: February 12, 2023, 07:38:04 pm
content: Quote from: Simon on February 12, 2023, 07:12:29 pmBasically, yes. Though it should betc0->COUNT16.CTRLA.bit.ENABLE = 1;Or call the function fromhri/hri_tc_c21.h:Code:static inline void hri_tc_set_CTRLA_ENABLE_bit(const void *const hw){TC_CRITICAL_SECTION_ENTER();((Tc *)hw)->COUNT16.CTRLA.reg |= TC_CTRLA_ENABLE;hri_tc_wait_for_sync(hw, TC_SYNCBUSY_SWRST | TC_SYNCBUSY_ENABLE);TC_CRITICAL_SECTION_LEAVE();}

author: ataradov
author_rank: Super Contributor
timestamp: February 12, 2023, 07:53:33 pm
content: You don't need to define those addresses either, since they are already defined in the same files that define structures. So if you are going to use the header files, might as well use them all the way.TC0 for example, is defined in samc21j18a.h (substitute your specific device) as:Code:#define TC0 ((Tc *)0x42003000UL)So, you just need to include one file, and it will bring in all the structures and peripherals and you can just do TC0->COUNT16.CTRLA.bit.ENABLE = 1; without anything else.

author: Simon
author_rank: Unknown
timestamp: February 12, 2023, 08:34:11 pm
content: Ah I see yes the address is defined in the device file rather than the peripheral file. That sort of makes sense and almost does not break my brain. Currently making headway through "understanding and using C pointers" by Richard Reese

author: ataradov
author_rank: Super Contributor
timestamp: February 12, 2023, 10:19:13 pm
content: In case of Atmel/Microchip files there are files for peripherals, which only define peripheral itself, not the way it is integrated into the device. Then there are separate filed for peripheral instances, which define things specific to an instance, like DMA IDs, clock IDs. And then there is a device file, which defines everything else specific to the device.This is not common for other vendors, usually you see one file for everything. And this is very nice because you can do things like:Code:#define MY_TIMER TC0....MY_TIMER->COUNT16.CTRLA.reg |= TC_COUNT16_CTRLA_ENABLE;And then you can configure the code to use any timer by simply changing the define and it reads well.In case of ST, for example, you have timer peripherals TIMx, but actually there are like 5 completely different designs for a timer, which present in the device at the same time. So, common defines like TIM_CR_ENABLE are not possible, and you have to use TIM1_CR_ENABLE.It is still fine if you know that TIM1,2,3 have the same design, and TIM4,5 have different design, you can always use TIM1_xxx in a first cace and TIM4 in the second, but it gets annoying to read.

author: cv007
author_rank: Frequent Contributor
timestamp: February 13, 2023, 02:05:21 am
content: You can also move yourself 'closer' to the registers so you do not have to go through the COUNTn struct for each use-Code:static tc_count16_registers_t* const tc1_16 = &TC1_REGS->COUNT16; //const means tc1_16 itself cannot changetc1_16->TC_CTRLA |= TC_CTRLA_ENABLE_Msk;The static is not needed if the pointer is local in scope, but if used as a global either in a c source file or a header then the static is needed (and inline so compiler will not complain if not used, which is usually wanted if in a header). The compiler will then optimize its use and the pointer needs no storage. I dislike defines, so I always avoid them when possible (and its possible more than you think).Or if defines are your thing-Code:#define TC1_16 (TC1_REGS->COUNT16)TC1_16.TC_CTRLA |= TC_CTRLA_ENABLE_Msk;...but defines are subject to name collisions, so if you already have a truckload of them they get more prone to name collisions and misuse with similar names ('normal/simple' define values are more likely and less prone to catch right away when misused than the defined pointer above).In C++, the same idea applies, except one can use a reference for . access instead of ->, and the whole class can live in a header as definitions inside a class are implicitly inline'd (plus no name collisions with names outside the class, so can name however you want).Code:class Tc1_16 {static inline tc_count16_registers_t& reg{TC1_REGS->COUNT16};public:void enable(){ reg.TC_CTRLA or_eq TC_CTRLA_ENABLE_Msk; }};I'm not sure where the .bit access is coming from in the original post, as I have not seen anything other than the registers in their structs. Maybe they had the bitfields created early on, not sure. They can be nice, but there are sometimes problems if you forget there is actually bit manipulation going on behind the scenes (looks like =, but is not). It is also not worth the time to create them yourself since in the end you will create functions to hide the dirty work, which should be a one time event per function and will only then deal with using the nicely named function (as in the class example above- enable() created one time and never have to create it again or know the details- hands only got a little dirty once).

author: ataradov
author_rank: Super Contributor
timestamp: February 13, 2023, 02:19:41 am
content: Quote from: cv007 on February 13, 2023, 02:05:21 amAtmel version had bitfields. Microchip version removed them. And generally made header files worse, making you use xxx_REGS everywhere and xxx_Msk for single bit values. It is stupid, and I just generate header files from SVD files with better defines.

author: westfw
author_rank: Super Contributor
timestamp: February 13, 2023, 09:02:41 am
content: QuoteThe Atmel bitfields were rather non-compliant with CMSIS, and had performance and RMW issues that were less than obvious. The Microchip definitions are annoyingly verbose (but more consistent, I think? I mean, it used to be difficult to tell bit numbers from bitmasks from field values. And similar. Now, it's not, but you have to type _MSK, _POS, _VAL, and similar all over.)

author: cv007
author_rank: Frequent Contributor
timestamp: February 13, 2023, 10:36:53 am
content: I now see where these bitfields are from- I went to start.atmel.com to generate a project and see they use them there (they also seem to make code obfuscated and more complicated than necessary, it seems to me).brief summary of some different methods-https://godbolt.org/z/bd4xvP8qdWhen creating structs with bitfields, you just have to be aware of what is going on when using the bitfield and take a little time to think when creating a peripheral function so the function becomes a one time creation, and correct. I don't create my own register structs anymore because its just too much work, even if automated in some way. If done 'right', functions are the interface to peripherals anyway so you do not have to normally deal with register access at the app level. Dealing with existing headers, however unpleasant they may happen to be, is workable if you can keep their use mostly tucked away inside functions.

author: rstofer
author_rank: Super Contributor
timestamp: February 13, 2023, 03:48:10 pm
content: I'm used to seeing register addresses declared 'volatile'. Here's the explanation:https://www.linkedin.com/pulse/use-volatile-keyword-embedded-systems-ijaz-ahmad

author: ataradov
author_rank: Super Contributor
timestamp: February 13, 2023, 04:36:24 pm
content: Quote from: rstofer on February 13, 2023, 03:48:10 pmEach individual registers in all the structs are declared volatile (via IO defines), so there is no need to do it for the whole structure.

author: cv007
author_rank: Frequent Contributor
timestamp: February 13, 2023, 04:42:36 pm
content: QuoteThere are various ways to get that done- manufacturers will tend to declare struct members volatile (or volatile const) via a define like __IO or similar, so the volatile is taken care of at the struct member level with no need to add it anywhere else (like the defined pointer they also create).Code:typedef struct { volatile uint16_t CTRLA; } some_peripheral_t;#define T1 ((some_peripheral_t*)0x1000)T1->CTRLA = 1; //CTRLA is volatile, so will always write to itOr one can make the type volatile-Code:typedef struct { uint16_t CTRLA; } volatile some_peripheral_t;#define T1 ((some_peripheral_t*)0x1000)T1->CTRLA = 1; //some_peripheral_t is volatile, so CTRLA is also volatile, so will always write to itWhen on my own in C++, I usually create a 'normal' struct then make the reference provide the volatile (mostly to keep the struct creation less verbose)-Code:struct Reg { uint16_t CTRLA; };static inline volatile Reg& reg{ *(Reg*)0x1000 };reg.CTRLA = 1; //the reference made the whole struct volatile, so CTRLA is also volatile, so will always write to itThe advantage of the volatile at the struct member level, or via the typedef, is you then cannot forget to make a pointer/reference volatile.

author: Simon
author_rank: Unknown
timestamp: February 13, 2023, 05:49:27 pm
content: Quote from: ataradov on February 12, 2023, 07:53:33 pmOK, this bit confuses me slightly, how is this the same as:Tc * tc0 = 0x42003000ul ;Is it that I created a pointer variable, whereas in the header file it simply points to that address and sets out (maps out?) that the address space from that address follows the structure, of, the, ahem, "structure" ?

author: ataradov
author_rank: Super Contributor
timestamp: February 13, 2023, 06:03:43 pm
content: Quote from: Simon on February 13, 2023, 05:49:27 pmExactly. You created a named variable. The header just casts an address to the type without intermediate variable and hides it behind the defined name.Tc * tc0 = 0x42003000ul ;tc0->CTRLA = ....is equivalent to((Tc *)0x42003000UL)->CTRLA = ....

author: cv007
author_rank: Frequent Contributor
timestamp: February 13, 2023, 06:07:53 pm
content: #define TC0 ((Tc *)0x42003000UL)The define is simple word processor replacement by the preprocessor-TC0->CTRLAbecomes((Tc *)0x42003000UL)->CTRLATc * tc0 = 0x42003000ul ; //should be cast to prevent warning Tc* tc0 = (Tc*)0x42003000ul ;You are now into the compiler/language territory, and tc0 is now a pointer just as any other pointer is created.tc0->CTRLAbut, if this is a global var (not in a local scope, as inside a function, and not static), the compiler will make it a variable which requires storage and a few extra cycles to read the pointer var first when used. The compiler will not know what other source files may be using the var, so it needs to be stored.Outside a function, if you make it static it will be 'local' to the current source file only, and the compiler will typically optimize its use and no storage will be needed (the compiler knows its only used in the current source file, and no one outside it will modify it, so can optimize its use)-static Tc* tc0 = (Tc*)0x42003000ul ;If you want to move it into a header, then adding inline will prevent warnings about an unused var if the header is needed but this var is not used (optimizes away)static inline Tc* tc0 = (Tc*)0x42003000ul ;Also then not a bad idea to make the pointer const so no one tries to change it (tc0)-static inline Tc* const tc0 = (Tc*)0x42003000ul ;

author: Sherlock Holmes
author_rank: Frequent Contributor
timestamp: February 13, 2023, 06:19:17 pm
content: Quote from: Simon on February 12, 2023, 08:34:11 pmSomething worth considering at this relatively early stage your in, is if creating your owntypedefnames for structures, consider also creating atypedeffor pointers to that type.This then lets you write code that has improved readability:Code:typedef struct NrfReg_CONFIG_struct NrfReg_CONFIG, * NrfReg_CONFIG_ptr;Then in your consuming code you can declare structures and pointers to them more legibly:Code:NrfReg_CONFIG cfg_reg;NrfReg_CONFIG_ptr cfg_reg_ptr;cfg_reg_ptr = get_register();This small change starts to pay dividends as the code base grows, less of those pesky asterisks all over the place.

author: voltsandjolts
author_rank: Unknown
timestamp: February 13, 2023, 06:57:58 pm
content: I'll take '*' over '_ptr' any day of the week.With * pointers stand out, you can see at a glance what is going.With _ptr postfix it takes more mental processing of variable names to see if they are pointers.

author: ataradov
author_rank: Super Contributor
timestamp: February 13, 2023, 07:16:16 pm
content: Yes, don't do this nonsense if you want to have code readable by normal humans.Plus header files here are generated by the vendor and it is better to use them as is and not make your own. Unless automatically generated from some other source, like SVD file. But in this case you will get worse result all over.

author: Simon
author_rank: Unknown
timestamp: February 13, 2023, 09:12:32 pm
content: Quote from: ataradov on February 12, 2023, 10:19:13 pmI constantly get: Error expected ')' before numeric constant GccApplication1 C:\Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\SAMC21_DFP\1.2.176\samc21\include\samc21j17a.h 586

author: ataradov
author_rank: Super Contributor
timestamp: February 13, 2023, 09:41:37 pm
content: Quote from: Simon on February 13, 2023, 09:12:32 pmWith what code specifically?

author: Simon
author_rank: Unknown
timestamp: February 13, 2023, 09:50:09 pm
content: #define MY_TIMER TC0MY_TIMER->COUNT16.CTRLA.reg |= 0x1 << 1Basically anything I try comes up with the same error.

author: Simon
author_rank: Unknown
timestamp: February 13, 2023, 10:07:41 pm
content: My mistake, I think I was getting a bit too exited and forgot to put the code into the main function and left it in just the file.

author: cv007
author_rank: Frequent Contributor
timestamp: February 14, 2023, 12:23:06 am
content: If you also use static functions, you will not have to duplicate code/functions based on the define name you created, plus you end up with nice functions for the app to use.if you create-Code:#define MY_TIMER1 TC1#define MY_TIMER2 TC2#define MY_TIMER3 TC3#define MY_TIMER4 TC4and use them directly, you will for example have to duplicate code to enable the timer for each define, and unless you want to do a search/replace you will be stuck with the name you gave in the define-Code:MY_TIMER1->COUNT16.CTRLA.reg = something;MY_TIMER2->COUNT16.CTRLA.reg = something;MY_TIMER3->COUNT16.CTRLA.reg = something;MY_TIMER4->COUNT16.CTRLA.reg = something;If you create static functions, you will end up with one function to enable and can use any name you want as the function code is not tied to the define name-Code:tc_16_enable( MY_TIMER1 );tc_16_enable( TC2 );#define MY_SYSTIMER TC3tc_16_enable( MY_SYSTIMER );tc_16_enable( TC4 );and the compiler via optimization will produce the same code in either case.https://godbolt.org/z/8r7ToPqYfedit- I will add that I also usually skip using the bitfield defines, as they simply end up way too verbose and easy to get wrong as there are usually many define names that are similar. As in the example linked above, with the datasheet open, I simply document the bitfields as they are used in the functions and use the raw values. If I have to look over the code later, I don't have to dig through headers to make sure the define names are correct as its documented in the function and the values are not hiding behind a define. The datasheet is always open, so matching datasheet to code works better for me than making the extra step of matching datasheet to header name.

author: Simon
author_rank: Unknown
timestamp: February 14, 2023, 06:33:39 pm
content: I only wanted to talk about pointers and structures, one thing at a time.

author: westfw
author_rank: Super Contributor
timestamp: February 15, 2023, 02:01:24 am
content: Beware the vendors are not terribly consistent.SAMC has (latest MPLAB with _REG):Code:#define TC0_REGS ((tc_registers_t*)0x42003000) /* TC0 Registers Address */While recent AVRs have:Code:#define TCA0 (*(TCA_t *) 0x0A00) /* 16-bit Timer/Counter Type A */Note the extra "*" in the AVR definition, which turns the pointer-like def into a structure like def, meaning that you'd do:Code:TC0_REGS->TC_COUNT = n; // SAMC with "->"TCA0.SINGLE.CNT = count; // AVR, with "."You don't have to remember for sure, but it's useful to immediately recognize the error messages that occur when you get it wrong...

author: Simon
author_rank: Unknown
timestamp: February 15, 2023, 07:29:27 pm
content: Yes I remember working with those. I used a lot of defines with those as the "argument" could be used as the element in the structure name.My next quandary is say I do write functions that I pass the pointer to the structure to, yes that allows me to use one function to do the same thing on different instances of a peripheral, however I need to pass parameters to put into these registers.As I have discovered I can't just define bit fields like a variable definition, unless maybe I write something unusable.So is this where I write code that does create a structure variable, fill it with the data and then copy it to the register structure. After all at some point I will have to hard code the values into the program to copy from EEPROM to the registers.

author: ataradov
author_rank: Super Contributor
timestamp: February 15, 2023, 07:35:51 pm
content: It is not clear what you mean exactly, but if I understand correctly you want something like this:Code:typedef struct{int some_opt_a;int some_opt_b;} TcOptsvoid setup_tc(Tc *tc, TcOpts *opts){tc->COUNT16.CTRLA.reg = opts->some_opt_a;tc->COUNT16.COUNT.reg = opts->some_opt_b;......}....TcOpts opts;opts.some_opt_a = 1;opts.some_opt_a = 2;setup_tc(TC0, &opts);

author: Simon
author_rank: Unknown
timestamp: February 15, 2023, 08:34:53 pm
content: Yea, I "just" have to duplicate the peripheral structure as that is written as registers, I guess I need something that is not full of all the keywords in the chip vendors header files.

author: ataradov
author_rank: Super Contributor
timestamp: February 15, 2023, 08:41:48 pm
content: This is not a good approach anyway. You will have those huge structures with all the settings for all the cases and most of the time you don't need any of that. This is exactly what frameworks do, so you will be just doing the same thing.For portable code, a much better approach is to define application APIs in a generic way, like init_system_timer(), and then implement this function in a platform specific way using registers.How often do you need to initialize many timers exactly the same way? And even if you do, just copyiung the same code 8 times is still less code than filling out the structure 8 times and calling a generic function.And if you want quick configuration of the code that has a fixed overall structure, then you can just use defines for everything. Here is example of a configurable UART driver -https://github.com/ataradov/free-dap/blob/master/platform/samd11/hal_config.h. If I know 100% that may application will have one UART interface with this specific functionality, then there is no reason to make things more complicated and generic.

author: Simon
author_rank: Unknown
timestamp: February 15, 2023, 08:59:48 pm
content: Yes that is right it ends up a right mess, what I am trying to do is find the medium between writing a function to do a specific setup and a function that has an argument for every bit of every register.In my case counters that do PWM will be setup in similar ways, if I want to use the counter to count that is a separate function etc. a uart will be setup the same. I may have multiple uarts depending on how things pan out maybe the speed would be an argument.

author: Simon
author_rank: Unknown
timestamp: February 15, 2023, 09:10:36 pm
content: I refuse to use code configurators, I tried the MPLABX one and it's the most disorganized crazy thing I have ever seen. I cannot stake my future work on something that looks thrown together with such complacency that for all I know it won't be around tomorrow, it's like they just found places in netbeans to just hang stuff. If I can get low level stuff to work in bare metal I don't see why I have to learn a whole new language of terminology and crap aimed at PC programmers. I'm not a PC programmer and I am well aware that trying to treat hardware full of nuances like some dumb standard peice of ATX PC hardware is stupid at best, I can get more out of it by fiddling every last bit of registers and functionality.I tried to humor my junior collogue and give the STM32 stuff a crack, we ended up rowing because I lost the plot at interrupts and being told to watch videos and copy example code because we live in some utopia where civilization has collapsed and no one knows anything anymore or we are just some cargo cult. Granted some of my problem is understanding pointers which I am working on but when the interrupts are turned on their head I just lost it. So for example, UART interrupts, in hardware land each instance will have an interrupt line, the handler will then determine what caused the interrupt so:if (interrupt_register & situation_bit){do thing}now it is the handler goes off, the HHAL (hardly hardware abstraction layer) code detects what caused it, this then calls a function for that case in which the user has to put code that checks which instance made the call, so it's like you go in a circle before doing anything. This is nut and now I know why they use operating system terminology like callback, yes by the time you have fucked about like that it is no longer a hardware real time interrupt, it's some OS style call back that is not real time.Ah nah, I'm not learning something that actually makes life worse.

author: westfw
author_rank: Super Contributor
timestamp: February 16, 2023, 12:54:01 am
content: QuoteThat's SO last month. Ask ChatGPT!"Besides, if you use a good IDE, a lot of that will be auto-filled for you anyway." (grr.)I don't like code generators or "software frameworks" either, but not using the vendor-supplied symbol definitions is cutting off your nose to spite your face. In addition to being a PITA and error prone, it will make your code less readable by anyone who might need to read it. Sort-of like if you want someone to write assembly language code for an XXX, you don't want to tell them in the interview that you use a proprietary custom made version of the assembly language because you didn't like those 6-character opcodes. ("It's a bit like the Cray 1 instruction set, where add is just "a"")(In fact, this is part of the problem with a lot of the "frameworks." They'll define a whole new set of symbols for each possible configuration option that are neither simpler nor more obvious than the chip-specific definitions. Justdifferent. And more of them.In fact, one of my complaints about the RPi Pico and its SDK is that they haven't done a good job of keeping the chip definition and the library definitionsseparate. Grr.(Most vendors mix up the downloads; it's tough to get JUST the chip definitions without a whole IDE coming along for the ride. Which is another PITA.Microchip's "packs"are better than average, but not exactly well-advertised.)

author: ataradov
author_rank: Super Contributor
timestamp: February 16, 2023, 01:05:45 am
content: For up to date device packs, the best place is Keil site -https://www.keil.com/dd2/pack/They are maintaining an up to date list and have all a lot of older versions for all relevant vendors.As fas as universality between the vendors goes this is as good as it gets.And for the header files, I just use SVD files from the same packs and generate my own. For now I use ARM's SVDConv tool, but I'm very close to making my own because SVDConv output is a bit messy. And sometimes SVD files have errors, but this does not happen too often.

author: westfw
author_rank: Super Contributor
timestamp: February 16, 2023, 02:02:45 am
content: QuoteYeah, I noticed that. I have mixed feelings. It OUGHT to be safe, and for sure it's better than doing it manually, but it does have some of the non-readability issues.How different do your files come out? I haven't used SVDConv, so I'm not sure how much of its output comes direct from the SVD and how much you get to specify as "different."

author: ataradov
author_rank: Super Contributor
timestamp: February 16, 2023, 02:14:38 am
content: Quote from: westfw on February 16, 2023, 02:02:45 amNo idea yet. But the ultimate goal is readability. I have SVD parser itself, but I'm still working on a layout for the generator. There are some non-trivial scenarios that happen in real SVD files. And the naive way to generate them results in unreadable mess.I'll try to focus on this next weekend. I needed a parser for another project, and as usual, I decided that since I have a parser, making a generator would be easy. It was not easyQuote from: westfw on February 16, 2023, 02:02:45 amYou don't get to specify much of anything. You can ask it to generate structures, bit fields, #defines for everything in any combination. That's pretty much it.On the other hand for bit fields it generates anonymous unions, letting you have things like "UART->CTRLA = ..." (without ".reg") and "UART->CTRLA_b.ENABLE = 1". So it sort of gives you the best of two worlds.Its output is pretty messy, not helped by the fast that a lot of SVD files are really messy.

author: cv007
author_rank: Frequent Contributor
timestamp: February 16, 2023, 02:59:02 am
content: QuoteEnums can be used as arguments. If the headers already have them, use them. If they do not, you can create them as needed (only when needed).You can also group functions specific to your problem, then you deal with only the parts of the peripheral that are used and end up with a limited/specific set of functions.If I want a set of code to run led's, which require very little code but seem to need often enough, then maybe I will create a simple header that can deal with only turning an led on/off (a specific 'problem' that does not need to get into all areas of the port peripheral). If I already have port peripheral code that does the same thing but more complete, then probably skip the special led code.This idea works well on peripherals that have a lot of options, and something like the tc peripheral could be tailored to do a specific task. You then create a set of functions that only deal with your problem at hand, named such as tc_8npwm_funcname (the mode and wavegen values already determined, so will then have no need to have special functions/arguments to set those values). Obviously you cannot start doing this for every option, such as prescale (8 options), so you then deal with that as a separate function, if its needed (you can then create an enum for the prescale options to be passed as an argument to a prescale setting function).A simple example to blink some led's for a samd10, using the headers already provided to get to the registers which in this case from the microchip packs. The enums for the pins are mcu specific, and there are other ways to go about it but it shows you can create enums to define a set of values for a specific purpose. This header is a one time creation that does a specific job, with the 'end user' app code having no need to deal with any registers when it comes to turning an led on/off.Code:// === Led.h =========================//#pragma once#include <sam.h>#include <stdint.h>#include <stdbool.h>//samd10d14amtypedef enum{ //pin/32 is port (A=0,B=1,...), pin%32 is pin (0-31)PA02=2, PA03, PA04, PA05, PA06, PA07, PA08, PA09, PA10, PA11,PA14=14, PA15, PA16, PA17,PA22=22, PA23, PA24, PA25,PA27=27, PA28,PA30=30, PA31}pins_t;typedef struct{pins_t pin;bool invert;} constled_pin_t;//IOPORT clock is enabled at reset, no need to enable clock in Pmstatic inline voidled_high (led_pin_t p) //pass by value ok, will be optimized/inlined anyway{ //(could change to pointer if wanted, but same code will be generated in either case)uint32_t pinbm = 1<<(p.pin%32);uint32_t port = p.pin/32;PORT_IOBUS_REGS->GROUP.PORT_DIRSET = pinbm; //outputPORT_IOBUS_REGS->GROUP.PORT_OUTSET = pinbm; //high}static inline voidled_low (led_pin_t p){uint32_t pinbm = 1<<(p.pin%32);uint32_t port = p.pin/32;PORT_IOBUS_REGS->GROUP.PORT_DIRSET = pinbm; //outputPORT_IOBUS_REGS->GROUP.PORT_OUTCLR = pinbm; //low}static inline voidled_on (led_pin_t p){ p.invert ? led_low(p) : led_high(p); }static inline voidled_off (led_pin_t p){ p.invert ? led_high(p) : led_low(p); }//========================= Led.h ===// === main.c =======================#include <sam.h>#include <stdint.h>#include <stdbool.h>//#include "Led.h" //abovestatic led_pin_tled1 = { PA02, false }; //change/add as neededstatic led_pin_tled2 = { PA03, false }; //another ledstatic voiddelay1s (){ volatile uint32_t i = 1000000/10; while(i--){} }intmain (){while(1){led_on( led1 ); led_off( led2 ); delay1s();led_on( led2 ); led_off( led1 ); delay1s();}}//======================= main.c ===You can start out similar- build small sets of functions to do specific tasks. In the process, you learn to use the provided headers, and you will get a better idea what you want the app code to 'look like' and how you interact with it, which will change over time.QuoteProbably nice if you stick to a specific mcu, but if you jump around various mcu's or variations of the same mcu it can start to become a chore. I have previously created headers from atdf files for the avr0/1 series with my own scripting, which was for C++, with no defines at all (enums/constexpr's inside structs). Structs were also created to contain specific peripheral instance info- such as its vector name, pins it used, how to set portmux, etc., with no runtime cost). In the end there were too many small errors in the atdf files, plus special cases to watch out for, and I ended up modifying/correcting a generated header as a 'master' header that was then used as a template for all the variations of the same series. Was not really worth the trouble for my own use, but would be a nice thing for a manufacturer to do (they are not interested in C++, though).The other problem is you may end up using the hal/library/whatever the manufacturer provides, so now you have 2 sets of peripheral headers in use.QuoteThe only way to go. Not sure why many do not do this (with gcc you can use pragma's to temporarily ignore -Wpedantic if someone uses -Wpedantic).

author: ataradov
author_rank: Super Contributor
timestamp: February 16, 2023, 03:04:23 am
content: Quote from: cv007 on February 16, 2023, 02:59:02 amIt takes 1 minute and then a few more minutes for cleanups if the tool screwed up something or SVD file is bad. It happens pretty rarely, I have a test set of 700 SVD files and absolute majority are valid. Some are not ideal in a way the things are defined, but overall absolutely usable.Quote from: cv007 on February 16, 2023, 02:59:02 amWell, for me there is no risk of that happening. It may be a factor, of course. But in many cases there are no good header files, like RP2040 mentioned earlier. And a lot of Chinese MCUs.

author: cv007
author_rank: Frequent Contributor
timestamp: February 16, 2023, 06:00:02 am
content: QuoteI typically stay away from the hal/drivers, but when you have something like an efr32/nrf52 that needs to make bluetooth work, you have to step into their world. I will still create my own interface to their code that is pleasant to use and readable, and I may 'take over' a peripheral if warranted (gets used often), but mostly try to make peace with what they have. Some mcu's are simply designed more favorable for code creation, such as the above mcu's mostly due to the way they layout registers (one register = one function, not 6 different bitfield groups).I just tried svdconv I had in a Silabs folder- not that impressed. I guess the other thing is you are at the mercy of whoever made your svd converter so if you do not like it, you now have 2 sets of unlikable headers to choose from. I guess the most wanted item that still requires manual work, is the enums I use for the function arguments, which will typically fill in a register bitfield. In C++, these enums live inside the class so name collision is not a problem, plus you get compile time argument checking via the enum type. I am mostly left with using only the peripheral register struct system provided, as I do not use the massive list of defines they have for the bitfield values as I mentioned before.C++ version of previous C example, which I think ends up being nicer to use. There are various ways to make the code compile more optimally and get the same code generated as the C version, but you start losing the simplicity/readability you gained and it the end it makes little difference.Code:// === Led.hpp =========================//#pragma once#include <sam.h>#include <stdint.h>#include <stdbool.h>class Led {port_group_registers_t& reg_;bool invert_;uint32_t pinbm_;public://samd10d14amenumPINS : uint8_t{ //pin/32 is port (A=0,B=1,...), pin%32 is pin (0-31)PA02=2, PA03, PA04, PA05, PA06, PA07, PA08, PA09, PA10, PA11,PA14=14, PA15, PA16, PA17,PA22=22, PA23, PA24, PA25,PA27=27, PA28,PA30=30, PA31};//IOPORT clock is enabled at reset, no need to enable clock in PmLed (PINS pin, bool invert = false): reg_(PORT_IOBUS_REGS->GROUP[pin/32]),invert_(invert),pinbm_(1<<(pin%32)){off(); //default off (could be high or low)reg_.PORT_DIRSET = pinbm_; //output}autohigh () { reg_.PORT_OUTSET = pinbm_; }autolow () { reg_.PORT_OUTCLR = pinbm_; }autoon (){ invert_ ? low() : high(); }voidoff (){ invert_ ? high() : low(); }};//========================= Led.hpp ===// === main.cpp =======================#include <sam.h>#include <stdint.h>#include <stdbool.h>//#include "Led.hpp" //above//objectsstatic Led led1{ Led::PA02 };static Led led2{ Led::PA03 };//just the enumsstatic auto led3{ Led::PA04 };static auto led4{ Led::PA05 };//array of objectsstatic Led leds[]{{ Led::PA02 },{ Led::PA03 },{ Led::PA04 },{ Led::PA05 }};//array of enumsstatic Led::PINS leds_e[]{Led::PA02,Led::PA03,Led::PA04,Led::PA05};static voiddelay1s (){ volatile uint32_t i = 1000000/10; while(i--){} }intmain (){while(1){//individual objectsled1.on(); delay1s();led2.on(); delay1s();//created/used inline using enum valuesLed(led3).on(); delay1s();Led(led4).on(); delay1s();//array of objectsfor( auto& r : leds ) r.off();delay1s();//array of enums, objects created inlinefor( auto e : leds_e ) Led(e).on();delay1s();//array of objectsfor( auto& r : leds ) r.off();}}//======================= main.cpp ===One thing I just remembered about anonymous structs/bitfields- you do run into the occasional problem where you have a bitfield name collision among registers of the same peripheral. In those cases you have to choose an alternate name for one of them. Maybe that is why many shy away from them, so they can keep the bitfield name given in the datasheet in all cases.

author: newbrain
author_rank: Super Contributor
timestamp: February 16, 2023, 02:53:33 pm
content: Quote from: cv007 on February 16, 2023, 06:00:02 amOK, I need to ask.Does that indentation style has a name (I would have some choice ones...)?I need to make sure it's an offense beyond redemption in our design rules.

author: cv007
author_rank: Frequent Contributor
timestamp: February 16, 2023, 04:10:26 pm
content: I doubt anyone else uses this style, so if ide's add it to their format list they can name it after me. You can call it anything you want, it won't bother me.I want names on the left margin easily seen. My 'drivers' typically live in headers only, and when I want to see what functions are available in a header I can easily see them instead of hunting around trying to pick out the function names. Its a little more work but its no different than making any other code presentable. Its not always done, but if the code will be reused then it works well to do it.Another example below, where you can scroll down the page and look on the left margin and see all available functions. One header for gpio- if I need to look up a name because I don't remember (like if not used often), or need to see what the code does, it is easy to find. Any needed documentation is by the function and not located in some other header or at a different location in the same header.Scroll down the page looking at the left margin, you can easily see what is available to use in this header. You will not be distracted by any of the code, or have a problem picking out the function names-https://github.com/cv007/NUCLEO32_G031K8_B/blob/main/Gpio.hppNow try to find what is available in this-https://github.com/STMicroelectronics/stm32g0xx_hal_driver/blob/master/Src/stm32g0xx_hal_adc.c

author: Simon
author_rank: Unknown
timestamp: February 16, 2023, 06:51:45 pm
content: Quote from: westfw on February 16, 2023, 12:54:01 amI'm fine with the manufacturers header files I'm just trying to understand how to use them. From the little I have seen of the HHAL is being mis interpreted as a system to let a PC operating system programmer drive a micro controller which is a totally different beast. To do this you still need to understand the chip at the hardware level, then you have to understand all the jargon that does not actually apply and is being misused, then you have to understand the contorted mess. My the time you have done that you can just write the code you need.

author: Sherlock Holmes
author_rank: Frequent Contributor
timestamp: February 17, 2023, 04:54:23 am
content: Quote from: voltsandjolts on February 13, 2023, 06:57:58 pmFine, do what you like but, here's just one teeny example of why this is more readable:Code:Register * reg1_ptr, reg2_ptr, reg3_ptr;then considerCode:Register_ptr reg1_ptr, reg2_ptr, reg3_ptr;If you think these are the same, then good luck to you.

author: Sherlock Holmes
author_rank: Frequent Contributor
timestamp: February 17, 2023, 05:00:01 am
content: Quote from: ataradov on February 13, 2023, 07:16:16 pmWhat tangible advantages do you believe the explicit * has over a ptr typedef? I just showed onedisadvantageabove, tell me which bit is unreadable, I'll see if I can set aside some time to explain how it works. C is a primitive language, you must be master of it, not it of you.The * operator in this context, is what's known asright associative, it binds to the declared name not the type, read for yourself if you don't believe me, one of numerous problems inherent in this family of programming language grammars.https://stackoverflow.com/a/3247313/5347352See? do you think he is he speaking "nonsense" too? I've known several programmers during my career who prefer cryptic rather than plain coding styles, I think they do it because it gives them a much needed sense of intellectual superiority.

author: JPortici
author_rank: Super Contributor
timestamp: February 17, 2023, 06:19:37 am
content: Quote from: Sherlock Holmes on February 17, 2023, 05:00:01 amQuote from: Sherlock Holmes on February 17, 2023, 04:54:23 amThe fact that the variable is a pointer should be contained in the variable name (i prepend "p" or "ptr") and not in the type.When i look at a variable i see the variable name, i have to hover the cursor, or ctrl+click in order to see the type.As per your example above they are obviously not the same, and any half decent compiler will emit a warning the moment you assign an address, even if you cast it. And any compiler will emit an error when you dereference something that is not a pointer. I don't see how a bug could sneak inThat said, when you start having pointers of pointers and such things, i prefer aliasing those to a type so things like double/triple dereferencing doesn't happen (or those things that CS courses professors and graduates seems to like)

author: Simon
author_rank: Unknown
timestamp: February 17, 2023, 06:25:45 pm
content: who cares? how long have you been coding? I have not been that long and it has been long enough to discover that there are many ways of doing stuff and anyone thinks their way is the best.

author: newbrain
author_rank: Super Contributor
timestamp: February 17, 2023, 11:34:36 pm
content: Quote from: Simon on February 17, 2023, 06:25:45 pmBut some 'best' ways are better than others.There are many good practices that help produce code that's safer, more readable and more maintainable.Not having typedef for pointers* is one of those, and is sanctioned, among others by:The SEI-CERT Coding Standard for writing safe C programs, inrecommendations Nr 5.Linus Torvalds himself (and the other main kernel contributors), in theLinux Kernel Coding styleguideJPorticinewbrainBecause they have been coding long enough to discover that advice from experts is often worth heeding.*with the notable exception of a completely opaque handle, typically on the user side of a library.

author: Simon
author_rank: Unknown
timestamp: February 19, 2023, 11:47:44 am
content: I don't know enough to understand how the setting of the standard and the exceptions works. But I see that you can do many things that will work but that are not a good idea. For example my junior colleague insists he wants to make a function "static" so that "nothing can go wrong" as he only wants it accessible in that file. I have struggled to convince him that this is a bad idea despite the fact that he has already wasted 12 hours troubleshooting code that compiled but would not work because having made a function static he then declared it in a header file so the compiler was happy. I don't know how much the compiler is supposed to pick you up on and how easy is it to make a compiler that can pick this out as I assume it's main job is to follow compilation based on rules and not check if you know how to use the language.

author: HwAoRrDk
author_rank: Super Contributor
timestamp: February 19, 2023, 02:03:21 pm
content: Quote from: Simon on February 19, 2023, 11:47:44 amHe's not wrong. If a function is only necessary inside a .c file (for use by its other functions only), it's always best to limit its visibility by making it static.Functions should always be static unless you specifically intend for them to be called externally.Otherwise you risk clashes, where perhaps you have two globally-declared functions with the same name and signature, but different purposes, and which one executes when a call is made depends entirely on the happenstance of the order in which headers are included. Although the compiler should warn you about such re-declarations.Quote from: Simon on February 19, 2023, 11:47:44 amThose two things aren't necessarily mutually exclusive. You just wouldn't normally add a static function declaration to a header file, as nothing outside of a .c file needs to know (or care) about such static functions.But what has probably happened here is that your colleague was making a call to the static function before it had been defined, so the compiler's like "Function 'foobar'? Don't know it, haven't seen it yet". And adding a declaration of that function to the header file happened to place it before (by virtue of being included at the top of the file) the first call to the function. But conventionally you would put a function declaration for a static function somewhere near the top of the .c file, not in a corresponding .h file.

author: Simon
author_rank: Unknown
timestamp: February 19, 2023, 02:32:43 pm
content: Yes I get what you are saying but, would any sane person write two functions of the same name? Yes technically you can but for readability by yourself later when you have forgotten details and for anyone else it won't be fun.If you do not declare the existence of a function, then no other C file knows about it so it can't call it. I don't know what the "safety" here is, on the premise that you don't want to go calling 2 or more functions with the same name if you want to call that function from outside of the file then so be it? Me personally I would rather the compiler warn me that I have 2 identically named functions. Personally I would use a scheme of names that distinguishes between functions that are implicitly static (by not telling anyone about them) and functions that I want available to the outside that I would put something like the origin file name at the start of the function name to ensure that it can't clash with other names and that it is easier to trace back to where it is.Where I do use static is with inline with functions in header files, now again I have heard people be for and against using static inline functions from header files, I don't write all of my code like that but, where I have what I feel is good reason to do so I do.I don't know what the specific rules are and what gray areas there are but when you are still developing something to just go making a function static "just because" feels like it may cause a problem. I know why the program crashed, because during compile each C file is compiled separately, if you tell the compiler that this function is static it really will be so. If you tell the compiler that the same function in another C file is externally defined then the compiler has no reason to disbelieve this, I don't know if there should be a further check step but obviously at run time it does not work because you have successfully compiled 2 pieces of code and one of then tries to find something that "does not exist" because you made two explicit conflicting statements.On the other hand if you were to create code with more than one function of the same name defined (but not made static) then the compiler will warn you. If this is your own code I really do not see why you want two functions of the same name unless you are a glutton for confusing yourself as early as next week.

author: HwAoRrDk
author_rank: Super Contributor
timestamp: February 19, 2023, 03:30:02 pm
content: An example scenario in which a name clash could occur might be as follows:You're using a library that has a functionlog(char *msg). But you're unaware of this. You create your own function to do some logging, and name it the obvious first thing that comes to mind: 'log', and it happens to not only share the same name but also signature as the library function.But because of how the function declarations are ordered, due to accident of header include order, the library function is unwittingly (because you ignored the compiler warnings of function re-declaration) overriding yours, and when you calllog("...")you don't get your function but instead the library one, and much confusion and hair pulling ensues.

author: Siwastaja
author_rank: Super Contributor
timestamp: February 19, 2023, 04:50:17 pm
content: Internal functions and variables for a compilation unit (.c file) that are not used from elsewhere - all static, always. This is a language deficiency; the "static" way should be the default, and we should have the opposite, something like apublicorexportedor similar keyword to make functions and variables visible outside. Lacking that behavior, just try to remember thestatic. If nothing else, it possibly increases performance and/or reduces code size.For things accessed from other compilation units (.c files), then leave the static keyword out and add the prototype in the header file, which you then include wherever needed.Keep it simple.

author: Simon
author_rank: Unknown
timestamp: February 19, 2023, 05:01:16 pm
content: Quote from: HwAoRrDk on February 19, 2023, 03:30:02 pmYes and as I said a lot of the ways of doing things seem to be implementation/scenario dependent. If I were writing a library which these days is actually a bunch of source code that I give away to be reused because real libraries come compiled then I would make functions static unless they are meant to be global which is those listed in the header or documentation.But if I am developing my own embedded application I would not be in a hurry to make functions static as I may be moving things around as I organize the project and it's very easy to realize that I need some function to be called from outside of that C file and bung a declaration for it in the header file forgetting I made it static. The only "risk" is that I have a function name of my own making that happens to be the same as one that is globally available in some externally supplied source code. They say to make function names descriptive for a reason, it also avoids clashes that again, I would hope throw a compile error.I don't think a compiler should warn you about same named functions? it should be an error as the compiler can guarantee nothing of what the programmer intended and the programmer must fix this or there is a 50% or less chance that the program will work as expected, it might at some point and then get changed slightly with a new random behavior as a result.As things stand functions are by default global but for the actual programmer it is a bit like they are not because you have to run around telling everything about everything even though everything is by default available. How do you find a function that is a duplicate in a system where you are using hundreds of external files?

author: Simon
author_rank: Unknown
timestamp: February 19, 2023, 05:04:27 pm
content: Quote from: Siwastaja on February 19, 2023, 04:50:17 pmYes this is what I wonder, why is global default, why isn't it static? then you would not need the damn header files, if the function is keyworded as global then the compiler knows it's available for external calling but that would be too easy.Still, is it normal that if the function is defined static but declared in a header that the compiler not pick it up? I suppose given the assumption that there could be another of the same name, it is purely down to the programmer to know and to keep track of what he/she does.

author: Simon
author_rank: Unknown
timestamp: February 19, 2023, 05:08:23 pm
content: Back onto the subject. I am looking at the definition of the structures of register variables for the generic clock controller. There are 9 counts of one and 45 of another. I don't really see this reflected in the structure though:Code:#ifndef _SAMC21_GCLK_COMPONENT_#define _SAMC21_GCLK_COMPONENT_/* ========================================================================== *//** SOFTWARE API DEFINITION FOR GCLK *//* ========================================================================== *//** \addtogroup SAMC21_GCLK Generic Clock Generator *//*@{*/#define GCLK_U2122#define REV_GCLK 0x111/* -------- GCLK_CTRLA : (GCLK Offset: 0x00) (R/W 8) Control -------- */#if !(defined(__ASSEMBLY__) || defined(__IAR_SYSTEMS_ASM__))typedef union {struct {uint8_t SWRST:1; /*!< bit: 0 Software Reset */uint8_t :7; /*!< bit: 1.. 7 Reserved */} bit; /*!< Structure used for bit access */uint8_t reg; /*!< Type used for register access */} GCLK_CTRLA_Type;#endif /* !(defined(__ASSEMBLY__) || defined(__IAR_SYSTEMS_ASM__)) */#define GCLK_CTRLA_OFFSET 0x00 /**< \brief (GCLK_CTRLA offset) Control */#define GCLK_CTRLA_RESETVALUE _U_(0x00) /**< \brief (GCLK_CTRLA reset_value) Control */#define GCLK_CTRLA_SWRST_Pos 0 /**< \brief (GCLK_CTRLA) Software Reset */#define GCLK_CTRLA_SWRST (_U_(0x1) << GCLK_CTRLA_SWRST_Pos)#define GCLK_CTRLA_MASK _U_(0x01) /**< \brief (GCLK_CTRLA) MASK Register *//* -------- GCLK_SYNCBUSY : (GCLK Offset: 0x04) (R/ 32) Synchronization Busy -------- */#if !(defined(__ASSEMBLY__) || defined(__IAR_SYSTEMS_ASM__))typedef union {struct {uint32_t SWRST:1; /*!< bit: 0 Software Reset Synchroniation Busy bit */uint32_t :1; /*!< bit: 1 Reserved */uint32_t GENCTRL0:1; /*!< bit: 2 Generic Clock Generator Control 0 Synchronization Busy bits */uint32_t GENCTRL1:1; /*!< bit: 3 Generic Clock Generator Control 1 Synchronization Busy bits */uint32_t GENCTRL2:1; /*!< bit: 4 Generic Clock Generator Control 2 Synchronization Busy bits */uint32_t GENCTRL3:1; /*!< bit: 5 Generic Clock Generator Control 3 Synchronization Busy bits */uint32_t GENCTRL4:1; /*!< bit: 6 Generic Clock Generator Control 4 Synchronization Busy bits */uint32_t GENCTRL5:1; /*!< bit: 7 Generic Clock Generator Control 5 Synchronization Busy bits */uint32_t GENCTRL6:1; /*!< bit: 8 Generic Clock Generator Control 6 Synchronization Busy bits */uint32_t GENCTRL7:1; /*!< bit: 9 Generic Clock Generator Control 7 Synchronization Busy bits */uint32_t GENCTRL8:1; /*!< bit: 10 Generic Clock Generator Control 8 Synchronization Busy bits */uint32_t :21; /*!< bit: 11..31 Reserved */} bit; /*!< Structure used for bit access */struct {uint32_t :2; /*!< bit: 0.. 1 Reserved */uint32_t GENCTRL:9; /*!< bit: 2..10 Generic Clock Generator Control x Synchronization Busy bits */uint32_t :21; /*!< bit: 11..31 Reserved */} vec; /*!< Structure used for vec access */uint32_t reg; /*!< Type used for register access */} GCLK_SYNCBUSY_Type;#endif /* !(defined(__ASSEMBLY__) || defined(__IAR_SYSTEMS_ASM__)) */#define GCLK_SYNCBUSY_OFFSET 0x04 /**< \brief (GCLK_SYNCBUSY offset) Synchronization Busy */#define GCLK_SYNCBUSY_RESETVALUE _U_(0x00000000) /**< \brief (GCLK_SYNCBUSY reset_value) Synchronization Busy */#define GCLK_SYNCBUSY_SWRST_Pos 0 /**< \brief (GCLK_SYNCBUSY) Software Reset Synchroniation Busy bit */#define GCLK_SYNCBUSY_SWRST (_U_(0x1) << GCLK_SYNCBUSY_SWRST_Pos)#define GCLK_SYNCBUSY_GENCTRL0_Pos 2 /**< \brief (GCLK_SYNCBUSY) Generic Clock Generator Control 0 Synchronization Busy bits */#define GCLK_SYNCBUSY_GENCTRL0 (_U_(1) << GCLK_SYNCBUSY_GENCTRL0_Pos)#define GCLK_SYNCBUSY_GENCTRL1_Pos 3 /**< \brief (GCLK_SYNCBUSY) Generic Clock Generator Control 1 Synchronization Busy bits */#define GCLK_SYNCBUSY_GENCTRL1 (_U_(1) << GCLK_SYNCBUSY_GENCTRL1_Pos)#define GCLK_SYNCBUSY_GENCTRL2_Pos 4 /**< \brief (GCLK_SYNCBUSY) Generic Clock Generator Control 2 Synchronization Busy bits */#define GCLK_SYNCBUSY_GENCTRL2 (_U_(1) << GCLK_SYNCBUSY_GENCTRL2_Pos)#define GCLK_SYNCBUSY_GENCTRL3_Pos 5 /**< \brief (GCLK_SYNCBUSY) Generic Clock Generator Control 3 Synchronization Busy bits */#define GCLK_SYNCBUSY_GENCTRL3 (_U_(1) << GCLK_SYNCBUSY_GENCTRL3_Pos)#define GCLK_SYNCBUSY_GENCTRL4_Pos 6 /**< \brief (GCLK_SYNCBUSY) Generic Clock Generator Control 4 Synchronization Busy bits */#define GCLK_SYNCBUSY_GENCTRL4 (_U_(1) << GCLK_SYNCBUSY_GENCTRL4_Pos)#define GCLK_SYNCBUSY_GENCTRL5_Pos 7 /**< \brief (GCLK_SYNCBUSY) Generic Clock Generator Control 5 Synchronization Busy bits */#define GCLK_SYNCBUSY_GENCTRL5 (_U_(1) << GCLK_SYNCBUSY_GENCTRL5_Pos)#define GCLK_SYNCBUSY_GENCTRL6_Pos 8 /**< \brief (GCLK_SYNCBUSY) Generic Clock Generator Control 6 Synchronization Busy bits */#define GCLK_SYNCBUSY_GENCTRL6 (_U_(1) << GCLK_SYNCBUSY_GENCTRL6_Pos)#define GCLK_SYNCBUSY_GENCTRL7_Pos 9 /**< \brief (GCLK_SYNCBUSY) Generic Clock Generator Control 7 Synchronization Busy bits */#define GCLK_SYNCBUSY_GENCTRL7 (_U_(1) << GCLK_SYNCBUSY_GENCTRL7_Pos)#define GCLK_SYNCBUSY_GENCTRL8_Pos 10 /**< \brief (GCLK_SYNCBUSY) Generic Clock Generator Control 8 Synchronization Busy bits */#define GCLK_SYNCBUSY_GENCTRL8 (_U_(1) << GCLK_SYNCBUSY_GENCTRL8_Pos)#define GCLK_SYNCBUSY_GENCTRL_Pos 2 /**< \brief (GCLK_SYNCBUSY) Generic Clock Generator Control x Synchronization Busy bits */#define GCLK_SYNCBUSY_GENCTRL_Msk (_U_(0x1FF) << GCLK_SYNCBUSY_GENCTRL_Pos)#define GCLK_SYNCBUSY_GENCTRL(value) (GCLK_SYNCBUSY_GENCTRL_Msk & ((value) << GCLK_SYNCBUSY_GENCTRL_Pos))#define GCLK_SYNCBUSY_GENCTRL_GCLK0_Val _U_(0x1) /**< \brief (GCLK_SYNCBUSY) Generic clock generator 0 */#define GCLK_SYNCBUSY_GENCTRL_GCLK1_Val _U_(0x2) /**< \brief (GCLK_SYNCBUSY) Generic clock generator 1 */#define GCLK_SYNCBUSY_GENCTRL_GCLK2_Val _U_(0x4) /**< \brief (GCLK_SYNCBUSY) Generic clock generator 2 */#define GCLK_SYNCBUSY_GENCTRL_GCLK3_Val _U_(0x8) /**< \brief (GCLK_SYNCBUSY) Generic clock generator 3 */#define GCLK_SYNCBUSY_GENCTRL_GCLK4_Val _U_(0x10) /**< \brief (GCLK_SYNCBUSY) Generic clock generator 4 */#define GCLK_SYNCBUSY_GENCTRL_GCLK5_Val _U_(0x20) /**< \brief (GCLK_SYNCBUSY) Generic clock generator 5 */#define GCLK_SYNCBUSY_GENCTRL_GCLK6_Val _U_(0x40) /**< \brief (GCLK_SYNCBUSY) Generic clock generator 6 */#define GCLK_SYNCBUSY_GENCTRL_GCLK7_Val _U_(0x80) /**< \brief (GCLK_SYNCBUSY) Generic clock generator 7 */#define GCLK_SYNCBUSY_GENCTRL_GCLK8_Val _U_(0x100) /**< \brief (GCLK_SYNCBUSY) Generic clock generator 8 */#define GCLK_SYNCBUSY_GENCTRL_GCLK0 (GCLK_SYNCBUSY_GENCTRL_GCLK0_Val << GCLK_SYNCBUSY_GENCTRL_Pos)#define GCLK_SYNCBUSY_GENCTRL_GCLK1 (GCLK_SYNCBUSY_GENCTRL_GCLK1_Val << GCLK_SYNCBUSY_GENCTRL_Pos)#define GCLK_SYNCBUSY_GENCTRL_GCLK2 (GCLK_SYNCBUSY_GENCTRL_GCLK2_Val << GCLK_SYNCBUSY_GENCTRL_Pos)#define GCLK_SYNCBUSY_GENCTRL_GCLK3 (GCLK_SYNCBUSY_GENCTRL_GCLK3_Val << GCLK_SYNCBUSY_GENCTRL_Pos)#define GCLK_SYNCBUSY_GENCTRL_GCLK4 (GCLK_SYNCBUSY_GENCTRL_GCLK4_Val << GCLK_SYNCBUSY_GENCTRL_Pos)#define GCLK_SYNCBUSY_GENCTRL_GCLK5 (GCLK_SYNCBUSY_GENCTRL_GCLK5_Val << GCLK_SYNCBUSY_GENCTRL_Pos)#define GCLK_SYNCBUSY_GENCTRL_GCLK6 (GCLK_SYNCBUSY_GENCTRL_GCLK6_Val << GCLK_SYNCBUSY_GENCTRL_Pos)#define GCLK_SYNCBUSY_GENCTRL_GCLK7 (GCLK_SYNCBUSY_GENCTRL_GCLK7_Val << GCLK_SYNCBUSY_GENCTRL_Pos)#define GCLK_SYNCBUSY_GENCTRL_GCLK8 (GCLK_SYNCBUSY_GENCTRL_GCLK8_Val << GCLK_SYNCBUSY_GENCTRL_Pos)#define GCLK_SYNCBUSY_MASK _U_(0x000007FD) /**< \brief (GCLK_SYNCBUSY) MASK Register *//* -------- GCLK_GENCTRL : (GCLK Offset: 0x20) (R/W 32) Generic Clock Generator Control -------- */#if !(defined(__ASSEMBLY__) || defined(__IAR_SYSTEMS_ASM__))typedef union {struct {uint32_t SRC:3; /*!< bit: 0.. 2 Source Select */uint32_t :5; /*!< bit: 3.. 7 Reserved */uint32_t GENEN:1; /*!< bit: 8 Generic Clock Generator Enable */uint32_t IDC:1; /*!< bit: 9 Improve Duty Cycle */uint32_t OOV:1; /*!< bit: 10 Output Off Value */uint32_t OE:1; /*!< bit: 11 Output Enable */uint32_t DIVSEL:1; /*!< bit: 12 Divide Selection */uint32_t RUNSTDBY:1; /*!< bit: 13 Run in Standby */uint32_t :2; /*!< bit: 14..15 Reserved */uint32_t DIV:16; /*!< bit: 16..31 Division Factor */} bit; /*!< Structure used for bit access */uint32_t reg; /*!< Type used for register access */} GCLK_GENCTRL_Type;#endif /* !(defined(__ASSEMBLY__) || defined(__IAR_SYSTEMS_ASM__)) */#define GCLK_GENCTRL_OFFSET 0x20 /**< \brief (GCLK_GENCTRL offset) Generic Clock Generator Control */#define GCLK_GENCTRL_RESETVALUE _U_(0x00000000) /**< \brief (GCLK_GENCTRL reset_value) Generic Clock Generator Control */#define GCLK_GENCTRL_SRC_Pos 0 /**< \brief (GCLK_GENCTRL) Source Select */#define GCLK_GENCTRL_SRC_Msk (_U_(0x7) << GCLK_GENCTRL_SRC_Pos)#define GCLK_GENCTRL_SRC(value) (GCLK_GENCTRL_SRC_Msk & ((value) << GCLK_GENCTRL_SRC_Pos))#define GCLK_GENCTRL_SRC_XOSC_Val _U_(0x0) /**< \brief (GCLK_GENCTRL) XOSC oscillator output */#define GCLK_GENCTRL_SRC_GCLKIN_Val _U_(0x1) /**< \brief (GCLK_GENCTRL) Generator input pad */#define GCLK_GENCTRL_SRC_GCLKGEN1_Val _U_(0x2) /**< \brief (GCLK_GENCTRL) Generic clock generator 1 output */#define GCLK_GENCTRL_SRC_OSCULP32K_Val _U_(0x3) /**< \brief (GCLK_GENCTRL) OSCULP32K oscillator output */#define GCLK_GENCTRL_SRC_OSC32K_Val _U_(0x4) /**< \brief (GCLK_GENCTRL) OSC32K oscillator output */#define GCLK_GENCTRL_SRC_XOSC32K_Val _U_(0x5) /**< \brief (GCLK_GENCTRL) XOSC32K oscillator output */#define GCLK_GENCTRL_SRC_OSC48M_Val _U_(0x6) /**< \brief (GCLK_GENCTRL) OSC48M oscillator output */#define GCLK_GENCTRL_SRC_DPLL96M_Val _U_(0x7) /**< \brief (GCLK_GENCTRL) DPLL96M output */#define GCLK_GENCTRL_SRC_XOSC (GCLK_GENCTRL_SRC_XOSC_Val << GCLK_GENCTRL_SRC_Pos)#define GCLK_GENCTRL_SRC_GCLKIN (GCLK_GENCTRL_SRC_GCLKIN_Val << GCLK_GENCTRL_SRC_Pos)#define GCLK_GENCTRL_SRC_GCLKGEN1 (GCLK_GENCTRL_SRC_GCLKGEN1_Val << GCLK_GENCTRL_SRC_Pos)#define GCLK_GENCTRL_SRC_OSCULP32K (GCLK_GENCTRL_SRC_OSCULP32K_Val << GCLK_GENCTRL_SRC_Pos)#define GCLK_GENCTRL_SRC_OSC32K (GCLK_GENCTRL_SRC_OSC32K_Val << GCLK_GENCTRL_SRC_Pos)#define GCLK_GENCTRL_SRC_XOSC32K (GCLK_GENCTRL_SRC_XOSC32K_Val << GCLK_GENCTRL_SRC_Pos)#define GCLK_GENCTRL_SRC_OSC48M (GCLK_GENCTRL_SRC_OSC48M_Val << GCLK_GENCTRL_SRC_Pos)#define GCLK_GENCTRL_SRC_DPLL96M (GCLK_GENCTRL_SRC_DPLL96M_Val << GCLK_GENCTRL_SRC_Pos)#define GCLK_GENCTRL_GENEN_Pos 8 /**< \brief (GCLK_GENCTRL) Generic Clock Generator Enable */#define GCLK_GENCTRL_GENEN (_U_(0x1) << GCLK_GENCTRL_GENEN_Pos)#define GCLK_GENCTRL_IDC_Pos 9 /**< \brief (GCLK_GENCTRL) Improve Duty Cycle */#define GCLK_GENCTRL_IDC (_U_(0x1) << GCLK_GENCTRL_IDC_Pos)#define GCLK_GENCTRL_OOV_Pos 10 /**< \brief (GCLK_GENCTRL) Output Off Value */#define GCLK_GENCTRL_OOV (_U_(0x1) << GCLK_GENCTRL_OOV_Pos)#define GCLK_GENCTRL_OE_Pos 11 /**< \brief (GCLK_GENCTRL) Output Enable */#define GCLK_GENCTRL_OE (_U_(0x1) << GCLK_GENCTRL_OE_Pos)#define GCLK_GENCTRL_DIVSEL_Pos 12 /**< \brief (GCLK_GENCTRL) Divide Selection */#define GCLK_GENCTRL_DIVSEL (_U_(0x1) << GCLK_GENCTRL_DIVSEL_Pos)#define GCLK_GENCTRL_RUNSTDBY_Pos 13 /**< \brief (GCLK_GENCTRL) Run in Standby */#define GCLK_GENCTRL_RUNSTDBY (_U_(0x1) << GCLK_GENCTRL_RUNSTDBY_Pos)#define GCLK_GENCTRL_DIV_Pos 16 /**< \brief (GCLK_GENCTRL) Division Factor */#define GCLK_GENCTRL_DIV_Msk (_U_(0xFFFF) << GCLK_GENCTRL_DIV_Pos)#define GCLK_GENCTRL_DIV(value) (GCLK_GENCTRL_DIV_Msk & ((value) << GCLK_GENCTRL_DIV_Pos))#define GCLK_GENCTRL_MASK _U_(0xFFFF3F07) /**< \brief (GCLK_GENCTRL) MASK Register *//* -------- GCLK_PCHCTRL : (GCLK Offset: 0x80) (R/W 32) Peripheral Clock Control -------- */#if !(defined(__ASSEMBLY__) || defined(__IAR_SYSTEMS_ASM__))typedef union {struct {uint32_t GEN:4; /*!< bit: 0.. 3 Generic Clock Generator */uint32_t :2; /*!< bit: 4.. 5 Reserved */uint32_t CHEN:1; /*!< bit: 6 Channel Enable */uint32_t WRTLOCK:1; /*!< bit: 7 Write Lock */uint32_t :24; /*!< bit: 8..31 Reserved */} bit; /*!< Structure used for bit access */uint32_t reg; /*!< Type used for register access */} GCLK_PCHCTRL_Type;#endif /* !(defined(__ASSEMBLY__) || defined(__IAR_SYSTEMS_ASM__)) */#define GCLK_PCHCTRL_OFFSET 0x80 /**< \brief (GCLK_PCHCTRL offset) Peripheral Clock Control */#define GCLK_PCHCTRL_RESETVALUE _U_(0x00000000) /**< \brief (GCLK_PCHCTRL reset_value) Peripheral Clock Control */#define GCLK_PCHCTRL_GEN_Pos 0 /**< \brief (GCLK_PCHCTRL) Generic Clock Generator */#define GCLK_PCHCTRL_GEN_Msk (_U_(0xF) << GCLK_PCHCTRL_GEN_Pos)#define GCLK_PCHCTRL_GEN(value) (GCLK_PCHCTRL_GEN_Msk & ((value) << GCLK_PCHCTRL_GEN_Pos))#define GCLK_PCHCTRL_GEN_GCLK0_Val _U_(0x0) /**< \brief (GCLK_PCHCTRL) Generic clock generator 0 */#define GCLK_PCHCTRL_GEN_GCLK1_Val _U_(0x1) /**< \brief (GCLK_PCHCTRL) Generic clock generator 1 */#define GCLK_PCHCTRL_GEN_GCLK2_Val _U_(0x2) /**< \brief (GCLK_PCHCTRL) Generic clock generator 2 */#define GCLK_PCHCTRL_GEN_GCLK3_Val _U_(0x3) /**< \brief (GCLK_PCHCTRL) Generic clock generator 3 */#define GCLK_PCHCTRL_GEN_GCLK4_Val _U_(0x4) /**< \brief (GCLK_PCHCTRL) Generic clock generator 4 */#define GCLK_PCHCTRL_GEN_GCLK5_Val _U_(0x5) /**< \brief (GCLK_PCHCTRL) Generic clock generator 5 */#define GCLK_PCHCTRL_GEN_GCLK6_Val _U_(0x6) /**< \brief (GCLK_PCHCTRL) Generic clock generator 6 */#define GCLK_PCHCTRL_GEN_GCLK7_Val _U_(0x7) /**< \brief (GCLK_PCHCTRL) Generic clock generator 7 */#define GCLK_PCHCTRL_GEN_GCLK8_Val _U_(0x8) /**< \brief (GCLK_PCHCTRL) Generic clock generator 8 */#define GCLK_PCHCTRL_GEN_GCLK0 (GCLK_PCHCTRL_GEN_GCLK0_Val << GCLK_PCHCTRL_GEN_Pos)#define GCLK_PCHCTRL_GEN_GCLK1 (GCLK_PCHCTRL_GEN_GCLK1_Val << GCLK_PCHCTRL_GEN_Pos)#define GCLK_PCHCTRL_GEN_GCLK2 (GCLK_PCHCTRL_GEN_GCLK2_Val << GCLK_PCHCTRL_GEN_Pos)#define GCLK_PCHCTRL_GEN_GCLK3 (GCLK_PCHCTRL_GEN_GCLK3_Val << GCLK_PCHCTRL_GEN_Pos)#define GCLK_PCHCTRL_GEN_GCLK4 (GCLK_PCHCTRL_GEN_GCLK4_Val << GCLK_PCHCTRL_GEN_Pos)#define GCLK_PCHCTRL_GEN_GCLK5 (GCLK_PCHCTRL_GEN_GCLK5_Val << GCLK_PCHCTRL_GEN_Pos)#define GCLK_PCHCTRL_GEN_GCLK6 (GCLK_PCHCTRL_GEN_GCLK6_Val << GCLK_PCHCTRL_GEN_Pos)#define GCLK_PCHCTRL_GEN_GCLK7 (GCLK_PCHCTRL_GEN_GCLK7_Val << GCLK_PCHCTRL_GEN_Pos)#define GCLK_PCHCTRL_GEN_GCLK8 (GCLK_PCHCTRL_GEN_GCLK8_Val << GCLK_PCHCTRL_GEN_Pos)#define GCLK_PCHCTRL_CHEN_Pos 6 /**< \brief (GCLK_PCHCTRL) Channel Enable */#define GCLK_PCHCTRL_CHEN (_U_(0x1) << GCLK_PCHCTRL_CHEN_Pos)#define GCLK_PCHCTRL_WRTLOCK_Pos 7 /**< \brief (GCLK_PCHCTRL) Write Lock */#define GCLK_PCHCTRL_WRTLOCK (_U_(0x1) << GCLK_PCHCTRL_WRTLOCK_Pos)#define GCLK_PCHCTRL_MASK _U_(0x000000CF) /**< \brief (GCLK_PCHCTRL) MASK Register *//** \brief GCLK hardware registers */#if !(defined(__ASSEMBLY__) || defined(__IAR_SYSTEMS_ASM__))typedef struct {__IO GCLK_CTRLA_Type CTRLA; /**< \brief Offset: 0x00 (R/W 8) Control */RoReg8 Reserved1;__I GCLK_SYNCBUSY_Type SYNCBUSY; /**< \brief Offset: 0x04 (R/ 32) Synchronization Busy */RoReg8 Reserved2;__IO GCLK_GENCTRL_Type GENCTRL; /**< \brief Offset: 0x20 (R/W 32) Generic Clock Generator Control */RoReg8 Reserved3;__IO GCLK_PCHCTRL_Type PCHCTRL; /**< \brief Offset: 0x80 (R/W 32) Peripheral Clock Control */} Gclk;#endif /* !(defined(__ASSEMBLY__) || defined(__IAR_SYSTEMS_ASM__)) *//*@}*/#endif /* _SAMC21_GCLK_COMPONENT_ */

author: Simon
author_rank: Unknown
timestamp: February 19, 2023, 05:23:18 pm
content: OK, I sort of see how it's done. At the bottom the final structure seems to be made up of multiples of the register structure type, so it's a case of how do I what is the syntax that would specify the register I want? It's like they are doing with registers what they do with bits?

author: ataradov
author_rank: Super Contributor
timestamp: February 19, 2023, 05:34:06 pm
content: It is just a regular array of structures, and you access it as an array:Code:GCLK->PCHCTRL.reg = GCLK_PCHCTRL_GEN(0) | GCLK_PCHCTRL_CHEN;And making local functions static is not only a good idea, but many coding standards require it.

author: ataradov
author_rank: Super Contributor
timestamp: February 19, 2023, 05:43:12 pm
content: Quote from: Simon on February 19, 2023, 05:04:27 pmBecause when C was invented this was not a concern and actual number of characters in the file was important.Quote from: Simon on February 19, 2023, 05:04:27 pmYou still would need header files with C compilation model. Each translation unit compiles separately and must have everything it needs to compile independent of other units.Quote from: Simon on February 19, 2023, 05:04:27 pmFunction can be static and defined in a header file as long as static implementation is available at compile time of the unit that uses it. I do that all the time for inline functions.But static and non-static version are two different storage classes, so two different functions.I also don't understand how it can take more than 10 seconds to debug issues like this.

author: Simon
author_rank: Unknown
timestamp: February 19, 2023, 06:02:42 pm
content: OK, finally decoded it. It's arrays of those structure (not forgetting the union) types within then structure. Mind blown and unblown

author: Simon
author_rank: Unknown
timestamp: February 19, 2023, 06:08:37 pm
content: Quote from: Simon on February 19, 2023, 05:04:27 pmFunction can be static and defined in a header file as long as static implementation is available at compile time of the unit that uses it. I do that all the time for inline functions.But static and non-static version are two different storage classes, so two different functions.I also don't understand how it can take more than 10 seconds to debug issues like this.Well it was an ESP32 and not me so I don't know how that compiler behaves and what he did or did not realize.Yes I use static inline also, but these go into a header file, it's a specific use, usually for small things like turning a pin on or off which in register language can look odd and is not portable. I believe the STM32 (H)HAL is full of these.This is something you and others helped me explore a couple of years ago when I first set foot on a SAMC, I had a pin toggle function that ran in the main loop, I discovered that the pin toggling (my "hello world") was much slower with the function versus the raw code in the main loop, so I was introduced to static inline.

author: cv007
author_rank: Frequent Contributor
timestamp: February 19, 2023, 06:15:51 pm
content: QuoteOne thing you have to realize- the compiler only compiles 1 source file, and it does not care about any other sources files you may also compile. Just 1. Putting a static function in a header- header guards prevent the header being included more than once, so that 1 source file will only see the static function only once (so no errors from seeing it more than once), and since its static the compiler will treat it as if no one else is using it so can optimize the function as it sees fit, or discard if not used (adding the inline keyword prevents warning if unused).So if a function is in a header, and static, each source file can treat the function as if its the only one who uses it. All source files will then produce optimal uses of the function, or discard if not used. (edit- I'll also add any included headers become part of the 1 source file, so whether you created a static function in the source file or it came in via a header, the result is the same).Since the compiler only cares about the 1 source file its working on, there needs to be a way to tell it functions/vars will be available outside the file its working on. Now you get to the typical header declarations where variables and functions needed are declared, and the compiler will just assume they will be available. It will treat them as if they will be available, and let the linker connect up the addresses it needs (and the linker will not be happy when it cannot find something said to be available).Start with something simple, like blinking an led. You get to access and use a header struct, only the port peripheral will be needed, you get to see results, and once you figure out one peripheral and its struct the same will apply to any other.

author: Simon
author_rank: Unknown
timestamp: February 19, 2023, 06:23:08 pm
content: I understand why the compiler itself won't see the problem, my colleague did not and I tried to explain what you have said, but he assumed there should be some sort of error, I don't know either way, as you say, I would expect at some point there to be an issue as something should not fathom at some point. Having said that, does the linker or compiler check that the functions called exist? again, if the function has been declared as global for that compilation unit, why should even the linker try to verify that there is a destination for the function calls? I suppose it should by way of replacing the function call with the address of the function, which does not exist. I don't know how many layers there are to this and how they interact.

author: ataradov
author_rank: Super Contributor
timestamp: February 19, 2023, 06:38:58 pm
content: Quote from: Simon on February 19, 2023, 06:23:08 pmCompiler "knows" it exists somewhere because you told it so. In this case linker would complain when it tries to link all the compiled objects and does not find the function. The error in that case is very obvious and unambiguous.It would say something like "undefined reference to `<function name>'". You can then easily search for that name and see that the function is not declared correctly.

author: Simon
author_rank: Unknown
timestamp: February 19, 2023, 07:02:12 pm
content: Well I don't know how he got to the point of it running or maybe it was in some strange debug mode, not familiar myself with those things.

author: newbrain
author_rank: Super Contributor
timestamp: February 19, 2023, 07:59:03 pm
content: Quote from: Simon on February 19, 2023, 02:32:43 pmJust remember that this is only partly true, being dependent on the C version and compiler's defaults:It is true only from C99 onward: C90 would allowimplicit function declarations.Even though in C99 and later this is not allowed, you'll only get a warning from gccunless -pedantic-errorsis specified (and it should, together with -Wall and -Wextra).Clang is saner, as by default it gives an error from when using -std=c99 and later.Implicit function declarationmeans that if a function is used for which the compiler sees no prototype, it is assumed to have a return type of integer, and no assumption is made about the formal parameters.The actual arguments will then undergo the default promotions (instead of actual argument type conversion) with no checking performed against the (unknown) parameter list, leading to debugging woes.PS: I'm sorry I can't give an exact quote from C90, as I don't have that standard, but here's the relevant quote from K&R 2nd edition, 'almost'as authoritative:Quote

author: SiliconWizard
author_rank: Super Contributor
timestamp: February 19, 2023, 08:41:50 pm
content: Quote from: Siwastaja on February 19, 2023, 04:50:17 pmI agree, but C is pretty crude in terms of "separate compilation". It's almost as if it came as an afterthought.Making all objects "static" ("private") by default, and requiring an explicit "public" qualifier for exported stuff, would make sense if C had any concept of modules, but it unfortunately doesn't.Modula/-2/-3, Oberon and even Pascal in its more advanced implementations with 'units', where all light-years ahead of C regarding that.Modules are something I've been waiting for in C for ages, and C23 still doesn't bring them. Note that many new C features tend to be borrowed from the C++ standard, and C++ did introduce modules, but in a very nasty way IMHO, so if C eventually gets them as in C++, no thanks.Anyway, meanwhile, yes that can all be emulated manually using static and defining clear interfaces in .h files. Takes some discipline to avoid the spaghetti syndrome, but not a big deal indeed.

author: cv007
author_rank: Frequent Contributor
timestamp: February 19, 2023, 10:01:57 pm
content: Code:Back onto the subject.The header listing you gave points out many uses will probably have to create a temporary struct to make use of those bitfields provided in the atmel version of the headers, assuming they are used in functions as should be done. You do not want to start changing each bitfield separately since each bitfield access will require a write to the register (access above the register level was declared volatile). So instead of a handful of writes to the same register, you 'accumulate' the values by bitfield, then write the final .reg value to the register (the register type is not declared volatile, so the compiler optimizes into a single value, if that can be done at compile time).An example comparing the two methods- one function uses the bitfields, the other does not (as if they were not available)-https://godbolt.org/z/cn7PKbfxxThe example functions were also created so they could live in a header if desired (static inline), and when placed in a header their names have to be unique because they may be included by any other file, so now you also need to make the names unique in some way (like using gclk_ or GCLK_ as is done here for functions/enums).If there are functions created in a c file that are static, then no one else sees them and the only naming conflict possible is with names used by that source file (created, or included).Same example, gclk functions split into header and source file (by comments in this case). The busy() function is 'private' (static), the gclk_enable() is not (not static, declared in header for anyone to use). The gclk_enable function becomes a callable function because the compiler has to assume others may be using it (it cannot optimize/inline because it has no control over other compiled files, although now the inline keyword can be used in its original meaning but will not begin to explain that).https://godbolt.org/z/odYnEcvKsQuoteYou can get into C++ without the need to explore every feature they have. There are a lot of nice things one can take advantage of without diving into the deep end-https://godbolt.org/z/5v1oY7E93Reading the simple example top to bottom- class inside a header is equivalent to having header functions but much easier, enums as types for compile time argument checking, inline variables (c++17) so can also use vars in a header, private/public/protected access as needed, names that stay inside the class, overloaded functions, templates for even many simple uses, auto return values if you wish, and I still may be missing something in there.

author: ataradov
author_rank: Super Contributor
timestamp: February 19, 2023, 10:15:19 pm
content: Quote from: cv007 on February 19, 2023, 10:01:57 pmNot really. If you are doing initialization of the whole register, then you just use defines, like in the example I provided above:Code:GCLK->PCHCTRL.reg = GCLK_PCHCTRL_GEN(0) | GCLK_PCHCTRL_CHEN;But if later you just need to change one field, then you can use bitfields and avoid goofy bolean operators:Code:GCLK->PCHCTRL.bit.CHEN = 0;This is how it is commonly done.If you prefer to have a lot of lines with each bit field initialized, you can create instances of structs, of course. But this just makes longer source code for no reason.

author: cv007
author_rank: Frequent Contributor
timestamp: February 19, 2023, 10:53:19 pm
content: QuoteNo reason you cannot do both, but if you already have the bitfields you may as well use them. The compiler doesn't care either way, so choose whichever writes/reads the easiest.I think you already misused one define as far as I can see-GCLK->PCHCTRL.reg = GCLK_PCHCTRL_GEN(0) | GCLK_PCHCTRL_CHEN;From the headers I'm looking at, GCLK_PCHCTRL_CHEN takes a value so should beGCLK_PCHCTRL_CHEN(1)but maybe you are doing from memory and did not test via compiler.If that is true, then it points out the defines are very easy to get wrong (even if only was from memory). When you decide to create a function, those macros with arguments will not be of any help if function arguments were trying to be used with them. You also get the compiler to truncate the bitfield values, but if the wrong define/value is used in either case you will not get what you wanted. (with C++, if using your own enums as function arguments, they can go directly into the bitfield, and since the enum function argument is compile time type checked, the bitfield will not be truncating any values because you cannot pass an invalid value).https://godbolt.org/z/9a39r6roMedit- you can also eliminate the need for using all the defines related to register position/mask (probably 75% of defines), eliminate most register bit operations, and in the end get code that works the first time in most cases. Not hard to live without bitfields, but if you have them they are mostly worth using. One just has to keep in mind what the compiler is doing behind the scenes, and some bitfields are created by automation so are available but should not be used.

author: SiliconWizard
author_rank: Super Contributor
timestamp: February 19, 2023, 11:09:03 pm
content: Quote from: ataradov on February 19, 2023, 10:15:19 pmYep, now some people prefer to use a consistent style and use either one (bit fields) or the other (bit masks) exclusively.So your second statement would be:Code:GCLK->PCHCTRL &= ~GCLK_PCHCTRL_CHEN;If you're using the latter, it's common to define macros that set or reset bit masks so you don't inadvertently forget negating the masks for clearing bits, for instance.Really a matter of code style.Note that strictly speaking, both approaches are not semantically equivalent, as the bit mask one requires a read before write, while the bit field assignment does not, and if the underlying target has instructions that can set/reset bits individually, the compiler can use them and avoid having to read first altogether. Of course that's for being strict, as most modern targets these days don't have such bit instructions, but something to keep in mind nonetheless.

author: ataradov
author_rank: Super Contributor
timestamp: February 19, 2023, 11:09:57 pm
content: Quote from: cv007 on February 19, 2023, 10:53:19 pmNew Microchip headers define even single-bit fields as having a parameter. The code provided here is for the Atmel headers, where only fields that have more than one bit take a value.

author: ataradov
author_rank: Super Contributor
timestamp: February 19, 2023, 11:11:42 pm
content: Quote from: SiliconWizard on February 19, 2023, 11:09:03 pmBut if I want to change a multi-bit field it would be unreadable mess like this:Code:GCLK->PCHCTRL = (GCLK->PCHCTRL & ~GCLK_PCHCTRL_GEN_Msk) | GCLK_PCHCTRL_GEN(new_value) ;And bitfield alternative is justCode:GCLK->PCHCTRL.bit.GEN = new_value;Way more readable.

author: SiliconWizard
author_rank: Super Contributor
timestamp: February 19, 2023, 11:15:13 pm
content: Quote from: ataradov on February 19, 2023, 11:11:42 pmIf you're using bit masks, you'll preferably use two separate statements for setting and clearing bits.Really, a matter of code style, but you obviously don't need to write the above.If you're using bit fields wider than 1 bit, yes this is only one statement.Although you can probably use a compound literal for modifying several bit fields in one statement. I admit I've never used compound literals for bit fields though, I'd have to check if this can be done.

author: cv007
author_rank: Frequent Contributor
timestamp: February 19, 2023, 11:15:52 pm
content: QuoteI thought I had the Atmel version headers open, apparently not. My mistake.In any case, I think the bitfield functions self document better and end up easier to read/use. I dislike the truckload of defines that all look the same, so normally stay away from using them.edit- I also had line 101 wrong in my example, I should have used the userid argument (but same results).

author: ataradov
author_rank: Super Contributor
timestamp: February 19, 2023, 11:17:40 pm
content: Quote from: SiliconWizard on February 19, 2023, 11:15:13 pmThis would require introduction of an intermediate variable, since cleared value may not be valid.I agree, it is possible to live without bit fields, but they cost nothing and make life easier in many cases. There is no reason to not have them. As usual, common sense must be used.

author: cv007
author_rank: Frequent Contributor
timestamp: February 19, 2023, 11:22:49 pm
content: QuoteIf they are provided for you. Not that many do it. For Microchip, unless you know there are two versions available (and how to get them), you may be stuck without and don't know anything else exists.For something like a silabs efr32, they have set/clr registers for all registers, plus their registers are typically single function so it would make litle sense to start using bitfields. You end up with assignment to either set/clr or both, but still may need to move values into a bit position (when not using their defines, which they are probably a record holder for quantity).Living without bitfields, when the mcu registers are full of combined bitfield values in single registers, is not ideal but if you make use of functions you can get to the same place- in any case you will be calling functions regularly instead of re-writing the same tedious register manipulating code over and over. Put an extra thinking cap on when creating the functions that manipulate registers, take it off when done.

author: SiliconWizard
author_rank: Super Contributor
timestamp: February 19, 2023, 11:36:00 pm
content: I checked, and compound literals can absolutely be used for bit fields too.I used to use the style ataradov showed, but actually this is fine and fully consistent with only one style, small example:Code:#include <stdint.h>typedef struct{uint32_t EN : 1;uint32_t CFG : 8;uint32_t DIV : 4;uint32_t WIDTH : 5;} Register_t;volatile Register_t * REG1 = (volatile Register_t *) 0xABCD1234;void Foo(uint8_t Div){*REG1 = (Register_t){ .CFG = 0x01, .DIV = Div, .WIDTH = 16 };REG1->EN = 1;}Note that if you're enabling the '-Wconversion' warning flag, which I highly recommend, the above will give a warning about the initialization of DIV.The more correct version is:Code:.DIV = (uint8_t)(Div & 0xF),And with that, you're coding like a pro.

author: Siwastaja
author_rank: Super Contributor
timestamp: February 20, 2023, 08:05:24 am
content: Quote from: SiliconWizard on February 19, 2023, 11:36:00 pmYet, I would like to see this:QuoteQuite sadly, in this particular case, gcc (at least 9.4.0 I have installed) does not understand that after constraining Div between 0 and 15, .DIV = Div assignment cannot change the value, so one just has to ignore the warning:Code:testi.c:19:47: warning: conversion from ‘uint8_t’ {aka ‘unsigned char’} to ‘unsigned char:4’ may change value [-Wconversion]19 | *REG1 = (Register_t){ .CFG = 0x01, .DIV = Div, .WIDTH = 16 };It seems, the only way to silence the warning is to BOTH limit the range by bitmask right at the assignment (earlier if() does not work), PLUS cast it to unsigned integer type (uint8_t as in SiliconWizards code, or uint32_t works, too) because apparently the compiler things the default integer promotion could lead to negative values, I guess?Then one needs to hope compiler does better job at actual optimization stage than it does on warning condition detection stage, and removes the excessive & operation which was written just to silence a warning about something that never happens.I would hope the compilers did better job on proving the ranges of values when it can be done trivially at compile-time. This way, we could enable more warnings, and not bother with writing excess code just to silence compiler warnings, get rid of false positives, and still get warnings when they are relevant. Sometimes it feels like gcc does really good job at this (for example, deducing when variables may be used uninitialized, and not giving much false positives anymore), but other times it's a meh.

author: artag
author_rank: Super Contributor
timestamp: February 20, 2023, 01:51:44 pm
content: I was always told not to use bitfields for register access because they were implementation dependent. I can easily see why this would be the case. That they were a way to pack small data fields into structures for memory efficiency, and nothing more.I can't see any discussion in this thread about the implementation of bitfields. Maybe I missed it. K&R2 says (p150) 'almost everything about fields is implementation dependent' and then goes into details.Did they change in some later version of the standard ? If so, what guarantees are now made about packing, ordering and alignment ?

author: Nominal Animal
author_rank: Super Contributor
timestamp: February 20, 2023, 02:03:06 pm
content: Quote from: artag on February 20, 2023, 01:51:44 pmTrue: they are indeed implementation dependent.Quote from: artag on February 20, 2023, 01:51:44 pmGCC and Clang use ARM ABI rules for bitfields on ARM cores; seehere for GCC. This is because ARM contributes to GCC ARM backend development, and Clang prefers to be GCC-compatible wherever possible and sensible.So, what "we" are relying on here, is definitely not the C standard, but the implementation defined behaviour of GCC and Clang compilers on ARM targets.

author: Siwastaja
author_rank: Super Contributor
timestamp: February 20, 2023, 02:35:39 pm
content: Quote from: artag on February 20, 2023, 01:51:44 pmOn the other hand, MCU registers are highly implementation dependent anyway; it will only work with that particular MCU family, and maybe there are 2-3 compilers in existence and actual use (say, gcc and clang). If they implement bitfield ordering the same way (and gcc & clang do), and respect some kind of PACKED attribute, as any useful compiler does, then it is pretty much as portable as it's ever going to get.Now forprotocolswhere you expect the code to be compiled for microcontrollers but also computers with different architectures, operating systems and compilers, I totally get the hesitation of using bitfields. I like to do it anyway, but then as a developer you are responsible of making sure it works on all systems it's supposed to run on.Bitfields significantly help reducing bugs, because without them, you have to resort to quite advanced bit masking and shifting business; ESPECIALLY if you deal with signed integers other than 8, 16, 32 or 64 bit wide. Of course you could unit test all your code by running through all possible values and compare against reference implementation, but people are lazy and don't do that. Instead, if you can just treat everything as normal variables, with names and ranges, and compiler guarantees no cross-contamination between adjacent variables, guarantees generation of correct sign-extend code, correct masking and correct amount of shifting, that is so helpful I'm willing to trade of some portability for that.

author: cv007
author_rank: Frequent Contributor
timestamp: February 20, 2023, 04:26:40 pm
content: QuoteIf I'm using register bitfields and I also wanted -Wconversion, I would surround the peripheral register setting code (grouped together) with pragma's that save/disable -Wconversion and restore (see in the example below). There is no way I would want to start casting and masking bitfield registers which turns something nice into something that starts to lean towards ugly. The masking already takes place by the compiler, and the warning does nothing but tell you what you already know.When I use them in C++, most of the bitfield values are from enums (as function arguments) so in addition it is compile time checked for valid values so cannot easily get the bitfields mistakenly truncated (but the compiler will still warn if -Wconversion is on).With templates one can get a 'custom' register struct, with bitfields in this case tailored to a specific pin-https://godbolt.org/z/4q89GG8Y8which makes this class easy to write/read, but has little use outside this particular class (the altFunc function is the biggest win here). It ends up as efficient as anything else as its all compile time produced code, but the template use also makes it more difficult to use when you want to pass pins around to other functions. Not really worth using, but interesting nonetheless.

author: Simon
author_rank: Unknown
timestamp: February 20, 2023, 07:48:11 pm
content: I kind of assumed that as the chip vendor heavily relies on bitfields it must be OK.

author: ataradov
author_rank: Super Contributor
timestamp: February 20, 2023, 07:54:19 pm
content: In theory there may be a compiler that gets them wrong and they would be technically in the right. They will also not gain any market share, so they are irrelevant.In theory it is not good to rely on compiler-specific behaviour, but in practice all relevant compilers behave the same, so it is mostly a matter of C standards committee refusing to recognize and document behaviour that is already used by everyone.Just look at how much work Clang had to do to be able to compile the Linux kernel. They had to introduce GCC-specific things. They could have insisted that those are not standard-compliant. They also would be irrelevant. If you want your thing to be adopted, you need to implement the behaviour that users want, not some idealistic stuff.

author: SiliconWizard
author_rank: Super Contributor
timestamp: February 20, 2023, 09:14:06 pm
content: Quote from: Siwastaja on February 20, 2023, 08:05:24 amYou can't avoid the cast and masking I used to "silence" the warning. (The example I gave was meant to be purely about compound literals for bit fields, so there was no input validation here, but input validation is something I highly recommend too so, sure, add it in real-life code.)The mask isn't enough.The reason is simple (albeit annoying, but that's really all over the place then with C as this is a 'core' feature): integer promotion.Whatever the type of 'Div' is, 'Div & 0xF' becomes an 'int'. You then have to cast the whole thing.Integer promotion is kind of annoying and even error-prone, but that's just C.

author: Siwastaja
author_rank: Super Contributor
timestamp: February 21, 2023, 07:45:32 pm
content: Compiler can easily prove that if input range is restrained, integer promotion cannot change the value. C standard does not require warnings, so they can be implemented in any way. I don't understand why GCC fails with -Wconversion when it's usually pretty good in silencing false positives, especially -Wuninitialized (I remember this being improved a decade ago.)I don't personally use -Wconversion because it generates such massive amounts of false positive warning noise, and I don't want to ruin my codebase with excess tricks trying to silence them. I can also see it generates a warning when assigning a value between 0 and 255 to uint8_t. That's everyday stuff. Masking communicates the wrong thing to human reader: it communicates that the value issupposedoverflow the range.I do see the benefits oif -Wconversion though and I have some nasty bug maybe once a year which could have been detected by it. But as it stands, it's useless to me, but I'm sensitive to noise and false alarms in general. I would be one of those pilots who silence a badly designed ground proximity warning by pulling a fuse when it annoys the shit out of me all the time during normal takeoff or landing, to the point of increasing risk of accident.

author: cv007
author_rank: Frequent Contributor
timestamp: February 21, 2023, 08:44:52 pm
content: QuoteOnce you know that something like the use of -Wpedantic starts complaining about your anonymous bitfields, or when using -Wconversion does not like your bitfield access as-is, you may as well take care of it at that point. These two particular cases will mostly live in your peripheral code (anonymous bitfields+bitfield access), so the pragmas to disable specific options surrounding that code work well (created as defines for easy use). If your code is intended for others to also use, you have no control of what options they may use (and you probably don't want to start adding a bunch of conditions that are required to make your code compile without error, especially if its easy enough to take care of using the pragmas). It may still break with another compiler, but I don't think its too much to ask that a specific compiler be required (gcc).The problem if you just say you are not using these options is if later you decide you would like to use them, maybe only temporarily to see how the compiler really feels about your code. You now may be looking at a bunch of warnings and have to start looking at all of them when most are already known. If you already handled it in some way, you will be looking at only warnings that you may be interested in (and the few you may get you either ignore, comment for the next time you check/use these options, or 'fix' to please the compiler).I turn on -Wall -Wextra when I remember, but when using the c++ compiler it does a pretty good job of keeping you out of trouble by default.

author: AVI-crak
author_rank: Regular Contributor
timestamp: February 21, 2023, 09:27:58 pm
content: Quote from: Siwastaja on February 20, 2023, 08:05:24 amYour code will work, but it's scary to look at.When working with bit structures, it is important to consider the sum of all bit fields in the declared storage format.Your variant has a 32bit field, but only 18 bits are declared. This state gives the compiler a free hand, and now he can move the fields of the structure as he wants. Moreover, each new feature will have its own unique combinations of shifts. Formally, the compiler does not violate anything, there will be no warnings. But debugging will be a lot of fun.The dimension of the structure field must be equal to the sum of the bit fields!!!The modifier "volatile" or "const volatile" applied to the contents of a structure, or to its address, or to a variable reference to the address of a structure, always imposes a multiple read/write operation.Trying on the "volatile" modifier in any variant to the user's data structure does not make sense. Because it's just data, by the time the function ends, all operations are guaranteed to be executed without being forced. In addition, the structure itself can be optimized to be stored in the MK register completely (if possible), and transferred between functions without being stored in memory. This seriously reduces the program code, and speeds it up.A bit structure with an explicit mapping to a real memory address, even without the "volatile" modifier, will always be guaranteed to be read 1 time, modified, and written 1 time, within the same function. The compiler will not dare to change the storage address, but will be able to reduce read/write operations.Your version with the "volatile" modifier will be guaranteed to read/write within each state change of the structure, anywhere in the program.And the last mistake is the discrepancy between the dimension of the input parameters of the function and the field of the structure. The compiler in such situations goes crazy, and starts to do outright nonsense. To solve such problems, it is necessary to forcefully limit the dimension of the input parameter - this significantly reduces the code.The example at the link is provided as a practice.https://godbolt.org/z/azP5zfErf

author: Siwastaja
author_rank: Super Contributor
timestamp: February 22, 2023, 06:22:16 am
content: Quote from: AVI-crak on February 21, 2023, 09:27:58 pmIt was not my code. I just added range checking in an attempt to silence the compiler warning in a more sustainable way, only to see that failed. I agree that the sum of bitfield bits should total to the sizeof of the type; I always add something called "reserved" explicitly, and add a comment like "always write to 0 for backwards compatibility".

author: AVI-crak
author_rank: Regular Contributor
timestamp: February 22, 2023, 09:41:05 am
content: It seems to me that the godbolt service has a learning function, on the tenth attempt it agreed that there were no errors.

author: cv007
author_rank: Frequent Contributor
timestamp: February 22, 2023, 03:28:03 pm
content: QuoteThe reserved type naming of course is needed for any register gaps, but you can leave bitfields blank if unused-https://godbolt.org/z/57rYPEh3awhich means you don't end up with a bunch of reserved/unused names for the unused bits.For register bitfields, there will typically be a union with the full register, so the padding on the end is already taken care of due to the full register name/type. There is also the special 0 length bitfield which will cause a 're-alignment' (there is a better explanation than this if you look it up).edit-there may be different compiler register access instructions used (like ldr/ldrh/ldrb) that will depend on the resulting struct size if unused register bits are either just not specified or the special 0 length unnamed bitfield is used. Using the example above, the CTRLA bitfield is only seen as 2 bits wide even though it will align with the CTRLA u32, so the resulting access will be by byte. The CTRLB bitfield is 18 bits wide so word access will be used. If it is important for some reason to prevent byte access, any remaining unused bits in the bitfield struct will need to be 'manually' padded via an unnamed member the size of which takes up the remaining bits, so the struct is seen as 32bits (assuming a 32bit register).Its hard to say what mcu's and what peripherals allow byte access as it seems you will run into some that may not allow it. So manually padding to fill any bitfield will 'always' get you the same type of access as the other union member type.

author: SiliconWizard
author_rank: Super Contributor
timestamp: February 23, 2023, 08:30:48 pm
content: Quote from: Siwastaja on February 21, 2023, 07:45:32 pmI understand your point, but this is debatable, and in real-life code, there are many case where it would be much less obvious for the compiler to determine bounds.Quote from: Siwastaja on February 21, 2023, 07:45:32 pmI do, especially when using fixed-width integers. Much too slippery otherwise.I don't consider this "tricks" - this makes sense from a language POV, even if it may not be very pretty or if you consider it a matter of interpreting the standard.I really don't mind much. I am in favor of strong typing anyway. I wouldn't mind having to explicitely cast expressions of different types even if said types were structurally equivalent, such as with Ada.

author: Siwastaja
author_rank: Super Contributor
timestamp: February 24, 2023, 06:12:42 am
content: Quote from: SiliconWizard on February 23, 2023, 08:30:48 pmWell, when it is not possible to determine bounds during compile time, then the warning is in place; programmer should look at it closely, because if compiler can't do it, programmer likely can't, either.I don't think you really got the point. Consider this example:uint8_t state;int f(uint32_t arg){state = arg;}This is a programming error,because it lacks input validation. This is a function that maps u32 into u8, and if we write robust code, we need to cover for the whole numerical range of input and consider what happens to the output. For example, value of 257 is changed to 1.With -Wconversion, one gets a warning. Fair enough.Now one actually fixes the error:uint8_t state;int f(uint32_t arg){if(arg > UINT8_MAX) return ERR;state = arg;}Now using 257 returns an error. Values that are applied tostateare always equal to arg.But the warning is still there; it's a false positive.But instead, when one does this:uint8_t state;int f(uint32_t arg){state = (uint32_t)arg&0xff;}Now the error is still there -code is functionally equivalent to the first example!!!!- e.g., 257->1 - but warning is silenced.Programmer added code intent of which is not clear, and not only that, it is wrong.-Wconversion is completely broken and promotes the pattern of silencing warningsin a way which does not change anything (except to silence the warning), instead of fixing errors revealed by the warnings.

author: Simon
author_rank: Unknown
timestamp: March 01, 2023, 06:52:19 pm
content: I have actually discovered a mistake in the microchip header file of the SAMC21 series. The compiler keeps warning that the register or bits of the status flag register in the RTC are read only, this is not the case as the bits must be written to 1 to clear the interrupt - I have tried and they must be written to and this does clear the interrupt.

author: ataradov
author_rank: Super Contributor
timestamp: March 01, 2023, 06:57:25 pm
content: Show the code that complains.If you are doing something like "TC->INTFLAG.bit.MC0 = 1", then it is a mistake on your side and the file actually protected you. When you do this, the whole register would be cleared. This is one of the downsides of having those bit structures, but once again, good tools make it much less of an issue. You just need to trust the tools.

author: Simon
author_rank: Unknown
timestamp: March 01, 2023, 07:25:50 pm
content: I also tried the register option with bit shifts. This is the RTC not TC, the structures are set up as read only end of from what I can tell, the datasheet states that the bit must be written with a 1 to clear it and writing a 0 will have no effect. It worked fine without complaint when I used the basic assembler register definition. The complaints come from the header file stating that it is read only, if this is the case the program will lock up on the first firing of the interrupt. ultimately it works despite the warning, if it didn't work I'd just have a locked up program.

author: ataradov
author_rank: Super Contributor
timestamp: March 01, 2023, 07:36:55 pm
content: TC was just an example of incorrect code.It is hard to tell without looking at specific code, error messages and header file versions.The files I have only mark bitfields as RO and have a comment about that "__I to avoid read-modify-write on write-to-clear register".I seriously doubt there is anything wrong with the header files. RTC is commonly used and there were no issues with it, so I would look at mistakes in your code.

author: Simon
author_rank: Unknown
timestamp: March 01, 2023, 08:01:34 pm
content: OK, so how do I clear the interrupt if the register is __I ? I'm not doing a read modify write, and although the bit access of the compiler may using bit fields I tried writing to RTC->MODE0.INTFLAG.bit.PER6 = 0x1, not happy, RTC->MODE0.INTFLAG.reg = 0x1 << 6Both get complaints but work.The datasheet says:Bits 0, 1, 2, 3, 4, 5, 6, 7 – PERn Periodic Interval n [n = 7..0]This flag is cleared by writing a '1' to the flag.This flag is set on the 0-to-1 transition of prescaler bit [n+2], and an interrupt request will be generated if INTENCLR/SET.PERn is one.Writing a '0' to this bit has no effect.Writing a '1' to this bit clears the Periodic Interval n interrupt flag

author: Simon
author_rank: Unknown
timestamp: March 01, 2023, 08:11:10 pm
content: typedef union { // __I to avoid read-modify-write on write-to-clear registerstruct {__I uint16_t PER0:1; /*!< bit: 0 Periodic Interval 0 */__I uint16_t PER1:1; /*!< bit: 1 Periodic Interval 1 */__I uint16_t PER2:1; /*!< bit: 2 Periodic Interval 2 */__I uint16_t PER3:1; /*!< bit: 3 Periodic Interval 3 */__I uint16_t PER4:1; /*!< bit: 4 Periodic Interval 4 */__I uint16_t PER5:1; /*!< bit: 5 Periodic Interval 5 */__I uint16_t PER6:1; /*!< bit: 6 Periodic Interval 6 */__I uint16_t PER7:1; /*!< bit: 7 Periodic Interval 7 */__I uint16_t CMP0:1; /*!< bit: 8 Compare 0 */__I uint16_t :6; /*!< bit: 9..14 Reserved */__I uint16_t OVF:1; /*!< bit: 15 Overflow */} bit; /*!< Structure used for bit access */struct {__I uint16_t PER:8; /*!< bit: 0.. 7 Periodic Interval x */__I uint16_t CMP:1; /*!< bit: 8 Compare x */__I uint16_t :7; /*!< bit: 9..15 Reserved */} vec; /*!< Structure used for vec access */uint16_t reg; /*!< Type used for register access */} RTC_MODE0_INTFLAG_Type;So, how do I clear the interrupt in that? if I got a warning on both .reg and the bits and only the bits are read only what does it mean?

author: ataradov
author_rank: Super Contributor
timestamp: March 01, 2023, 08:17:13 pm
content: The ".reg" is not declared as "__I", and on the peripheral level where "RTC_MODE0_INTFLAG_Type INTFLAG;" is declared, it is __IO.You clear it using this code "RTC->MODE0.INTFLAG.reg = RTC_MODE0_INTFLAG_PER6;" .This should compile without errors. So, your fixed code is correct. But there is no issue with the header files.And this "RTC->MODE0.INTFLAG.bit.PER6 = 0x1," is exactly the mistake those "__I" prevent.This operation is equivalent to "RTC->MODE0.INTFLAG.reg = RTC->MODE0.INTFLAG.reg | (1<<6)." And this would clear all the bits, not just the one you want.

author: Simon
author_rank: Unknown
timestamp: March 01, 2023, 08:40:36 pm
content: well I'll try again, yes that works, so not sure what happened

author: John Coloccia
author_rank: Super Contributor
timestamp: March 03, 2023, 10:15:38 am
content: Quote from: newbrain on February 17, 2023, 11:34:36 pmFWIW, our own coding standard all but bans the use of typedef except for some very limited cases (opaque handles, for example). We don't even use them for structs or enums. After almost 30 years of doing this, I've come to the conclusion that software development is difficult enough without obfuscating your types.

author: Simon
author_rank: Unknown
timestamp: March 03, 2023, 08:25:09 pm
content: so in the case of a unique but repeated register set how do you say, this memory location mapped out in this way? I find that there are firstly a lot of diverging opinions and secondly there is a mix of ideas that apply to the embedded and to the on complex OS applications. The second I think makes a lot of the first. Sure plenty of stuff is wrong to do, I always try to take the path that will be the clearest when I came back to it later or someone else reads it.

author: ataradov
author_rank: Super Contributor
timestamp: March 03, 2023, 08:32:57 pm
content: Quote from: Simon on March 03, 2023, 08:25:09 pmWhat do you mean specifically? If you mean related groups of registers, then they are usually represented by a structure, which itself is added in the main peripheral structure as an array. A good example of this is USB endpoints.If you mean something else - please clarify.It is always possible to describe any register layout as a combination of unions, structs and arrays of structs.

author: Simon
author_rank: Unknown
timestamp: March 03, 2023, 09:15:01 pm
content: If you create a structure you have created one variable? but if you have more than one instance you need to create it as a typedef so that you write the template once and reuse it. But the previous post suggests that custom typedefs are a bad idea.

author: Nominal Animal
author_rank: Super Contributor
timestamp: March 03, 2023, 10:06:38 pm
content: Quote from: Simon on March 03, 2023, 09:15:01 pmNo, you do not.For example, considerCode:struct peripheral {// unions, bit fields, even sub-structures};#define PER1 (*(volatile struct peripheral *)0xADDE5510))#define PER2 (*(volatile struct peripheral *)0xADDE5520))#define PER3 (*(volatile struct peripheral *)0xADDE5530))Similarly, if one declares the structures as external, and has the linker provide their address,Code:volatile struct peripheral PER1;volatile struct peripheral PER2;volatile struct peripheral PER3;or if you use indirection (for some odd reason),Code:volatile struct peripheral *PER1p = (volatile struct peripheral *)0xADDE5510;volatile struct peripheral *PER2p = (volatile struct peripheral *)0xADDE5520;volatile struct peripheral *PER3p = (volatile struct peripheral *)0xADDE5530;No typedefs in sight.

author: ataradov
author_rank: Super Contributor
timestamp: March 03, 2023, 10:07:53 pm
content: What variable?The post suggests that pointer typedefs are a bad idea, unless I've lost the thread of this conversation. There is nothing wrong with just struct typedefs.But even if you don't want typdefes at all, you can always write "struct StuctName" everywhere. I see no benefits of this, but some places insist on doing that. Entire Linux kernel is written that way.

author: Simon
author_rank: Unknown
timestamp: March 03, 2023, 10:20:12 pm
content: I'm probably getting confused then. i have started to read a book on C++ and I do like it in some respects, for example the implicit type casting and the ability to separate digits in a number with " ' " which is like, wow, I just wanted to be able to do something like that in C, like 0xFFFF'FFFF, I know it sounds silly but I struggle to keep count of things like that and 0xFFFFFF can look not much different to me from 0xFFFFFFFF, it's a small touch but means a lot to me.

author: ataradov
author_rank: Super Contributor
timestamp: March 03, 2023, 10:23:35 pm
content: Quote from: Simon on March 03, 2023, 10:20:12 pmThis is coming in C23. And GCC already supports them starting with v12 (with --std=c2x).

author: SiliconWizard
author_rank: Super Contributor
timestamp: March 03, 2023, 10:44:41 pm
content: Quote from: ataradov on March 03, 2023, 10:07:53 pmYep. Just a matter of opinion more than anything else here.I think that people who have never really used anything else than C and assembly may find this rule of not typedef'ing structs a good idea, while people having been exposed to Pascal/Ada/etc would find it pretty odd. I personally always typedef structs, unions and even enums (not always for the latter, but often.)Regarding not typedef'ing pointers, I do agree with this OTOH, but my above remark would still apply. Defining pointer types in Pascal, for instance, was almost idiomatic.

author: SiliconWizard
author_rank: Super Contributor
timestamp: March 03, 2023, 10:49:19 pm
content: Quote from: ataradov on March 03, 2023, 10:23:35 pmYep.If you don't have access to that, you can always use macros to ease your pain. Some people are allergic to macros though. Go figure.Otherwise, you can write macros to define "large" constants, with absolutely ZERO difference in compiled result.Assuming that 16-bit hex numbers may be the sweet spot, you can write something like this for defining 32-bit constants:#define CONST32(MSW, LSW) ((MSW) << 16 | (LSW))You may not find it pretty. To each their own too.

author: ataradov
author_rank: Super Contributor
timestamp: March 03, 2023, 10:56:48 pm
content: You can do it even better with macros:Code:#define CONST2(MSW, LSW) (MSW ## LSW)int b = CONST2(0xffff,ffff);The size does not matter in this case, only the number of arguments. There is a way to do this with vararg macros, but it gets a bit nuts.

author: SiliconWizard
author_rank: Super Contributor
timestamp: March 03, 2023, 11:47:05 pm
content: Quote from: ataradov on March 03, 2023, 10:56:48 pmTrue!

author: cv007
author_rank: Frequent Contributor
timestamp: March 04, 2023, 04:35:30 am
content: QuoteYou can write C and use the C++ compiler- the compiler is providing these extra features and it can compile C code without breaking a sweat.Use the features you want, ignore the rest until it becomes useful. I don't think you give up anything, except maybe designated initializers (in any order, anyway). I don't think that is even true if you want to use the latest C++ standard (I stay put at C++17 for now).I'm not sure why more C programmers don't use the C++ compiler. The list of features you can start using will be long even for plain C code- you don't even need to get into the 'typical' C++ to take advantage. Typed enums, overloaded functions, references, templates, .... many more. Use 1 feature you like, then another, and before long you will start discovering things that make coding easier and more reliable.

author: ataradov
author_rank: Super Contributor
timestamp: March 04, 2023, 04:50:24 am
content: Quote from: cv007 on March 04, 2023, 04:35:30 amIn many cases good C code would generate warnings with C++ compiler. It is especially noticeable when handling constant pointers.It will still compile, but warnings are annoying, so you have to switch all your code base to C++, or compile some stuff as C and the rest as C++.But generally yes, use of C subset of C++ is a good idea. The only issue I've seen with this is that if you and your team don't have discipline, soon you are going to slip into full C++ mode and all of sudden you are throwing exceptions all over the place. It all starts with "this is the perfect case where templates are justified" and ends up badly if you are not careful.

author: SiliconWizard
author_rank: Super Contributor
timestamp: March 04, 2023, 05:39:26 am
content: Yep. You won't write C in that case, you'll write a subset of C++. Not the same thing. That'll be compliant C++, and will not be C anymore nor buildable with C compilers, something people who want maximum portability or *must* use certain tools won't do.Then the question of using C++ (whatever subset) is a very reccuring one, and there are tons of good reasons for not going that way. But also a number of good reasons for going that way. It's good to know the pros and cons beforehand.After which, it will depend on whether you're the sole decider. If you work for a company, chances are you won't get a say in which tools you're going to be using, unless you're a top manager.

author: cv007
author_rank: Frequent Contributor
timestamp: March 04, 2023, 06:01:19 am
content: QuoteProbably a different thing when you are talking 'team', but I only experience a team of 1 so can only imagine what happens with 'team' (and its not hard to imagine). I would think a good solution for 'team' would be to disallow any use of the C++ standard library. I rarely use the C++ standard library mainly out of habit, as many things will just not fit in the smaller mcu (and no C++ standard library for avr, so if you want something library-like, you create it). If one is talking about pc programming, that is entirely a different world when it comes to C++ as the pc user has virtually unlimited resources and there is nothing that naturally puts limits on what gets used unlike an mcu.Not that hard to do without the C++ standard library for the mcu, and still take full advantage of the C++ language (you and the library writers are using the same language). I have cout style printing (formatting), but created that myself and use it for avr and cortex-m (only requires about 300 lines of code). In use it also compiles to similar sizes as the printf/snprintf counterparts (less for minimal use, a little more for lots of use). I mainly created it because I could then get byte-by-byte output for cortex-m without a buffer or deal with trying to figure out the workings of printf (I have tried several times to understand the newlib source code for stdio, but always end up confused). The upside to this cout code for me is that it is in a single header, and any class can inherit it (and can use as a snprinf replacement also, but there is also less need for that since most things format directly to the destination).

author: Simon
author_rank: Unknown
timestamp: March 04, 2023, 11:38:42 am
content: Quote from: SiliconWizard on March 04, 2023, 05:39:26 amI am the sole decider as the senior/lead electronics engineer. I explained to my boss early on that as the world is doing any higher level library for ARM stuff in C++ I should set this as something to move too but want to have the time to learn it first properly rather than dive head in and delay projects.My impression is that C was revolutionary in it's day and was created so because of limited resources, like we discussed earlier the conventions of static and global functions were more to limit character use in the source code so save memory. When I used basic years ago I liked it's verbosity, if (something is true) then (do this stuff) else (do this other stuff) was pretty clear even to a layman even if they did not understand all of the code detail. In C this became if (something is true) (this thing) else (this other thing). I initially found the lack of the "then" unsettling but got over it. Perhaps C++ wanted to address some of this and add more features.When it comes to code writing there seem to be many levels you operate at and I think when people give an opinion about the whole language they are actually talking about one of the layers. I like the language to be explicit but even then a programmer can write code that is hard to decipher.For example my junior colleague wrote a function to handle serial communications on the SAMC, yes one function for receiving a message and then responding. This was at a higher level than the hardware and just dealt with taking the data from a RAM buffer and putting the reply into the RAM buffer. Well I firstly would not have put transmission handling and reception handling in the same function. This situation repeated itself again when he created a function to calculate the checksum and validate the checksum. This meant that the same function was called in both transmission and reception, this is of itself confusing to me. The function (naturally) then had to have an argument to say if this is the generation of the checksum or the decoding of the checksum. This I found even more confusing at a logical point of view but it is how he chose to write it and it would work. Even though the function had an extra argument and the code to check that and run one half or other of the function code I expect the compiler would optimize it out.However I strongly oppose writing like this, it was hard to read for another person, it would require acres of comments to explain it when it could have been written more logically as separate functions, the clue is in the name, a function should be one function, but there are people who enjoy obfuscation and people who are just not experienced enough to know that how many people write is not a good idea or that maybe if it is some code that you are expected to just use and not read it then whatever but if the code needs to be reviewed then it makes sense that the code itself is in logical clear readable to the human statements.Another thing I see a lot is things like if(! statement that if 0 needs the action to happen) I kind of see how this happens as many calculation in functions return a 0 if they are good, this is because 0 can only come about from a well defined condition and any number but 0 will be interpreted as 1 in this scenario, but gee, why not just do the inversion in the function return so that you don't have to make a convoluted back to front use of it. Sure if you are rattling out code at a rate of words per hour for which you are paid and that is the end of that fine, but if it will be read by a human that needs to understand it why not just make it flow like text.

author: SiliconWizard
author_rank: Super Contributor
timestamp: March 04, 2023, 07:00:21 pm
content: For those who'd like to make better use of C using "modern" features of recent standard revisions, and who do not know them very well, this can be interesting:

author: Nominal Animal
author_rank: Super Contributor
timestamp: March 04, 2023, 07:26:44 pm
content: A good example of a significant difference between freestanding C and freestanding C++, is named address spaces on Harvard architectures, especially AVR.Neither C nor the C++ standard actually defines named address spaces, as it is an extension. Clang supports both, even down to function prototypes (meaning you can have aprint(string)function or member, and different implementations depending on which address spacestringuses –– typically Flash or SRAM), GCC C++ does not, and possibly never will.This is the underlying reason why the Arduino environment on AVR architectures usually copies string literals to RAM, for example. It uses the GCC C++ compiler (g++), which does not support separate address spaces. If the hardware does not have an unified address space, strings have to be copied to RAM in order to be supplied as a parameter. There are a few function variants with a _P suffix that operate explicitly on the Flash address space strings, to alleviate the need somewhat.If you used Clang instead, that would not be necessary; one would just need to write function variants for both (RAM and Flash) address space parameters.Using GCC or Clang, in C, one can use the C99_Genericfor function polymorphism, as both compiler families include__flashaddress space specifier among type-identifying attributes. But this kind of polymorphism does not compare to e.g. C++ templates, or operator overloading (which can be very useful when using e.g. specific fixed-point number types).

author: Nominal Animal
author_rank: Super Contributor
timestamp: March 04, 2023, 07:38:04 pm
content: Quote from: Simon on March 04, 2023, 11:38:42 amThe main reason is that a function that returns 0 for success, and a nonzero error code for errors or exceptional conditions, allows bothif (!call(...)) {failed(); }anderr =call(...);if (err) {error(err); }patterns to be used with any such function, depending on whether the reason of the failure or exceptional situation matters to the caller.Note that the not operator (!) then also intuitively indicates that the function call did not perform as expected, so developer attention is needed.If 0 was the only exceptional condition, and all nonzero values (corresponding to logicalTrue) the expected condition, the latter pattern above is impossible.Whenever you have a few such functions in your library or used in your program, thenthe Principle of Least Surpriserequires that you consistently use the same interface for all similar functions, even those for which the second pattern does not apply at all.

author: John Coloccia
author_rank: Super Contributor
timestamp: March 06, 2023, 07:37:57 pm
content: Quote from: ataradov on March 03, 2023, 10:07:53 pmFirst, it prevents the typedef from changing into something which still compiles but gives the wrong behavior. For example, a struct turning into a pointer. If you're just passing it around, it will still compile, but in case one you're passing around copies, and in case two you're passing around a single reference.Second it helps the reviewer, and others, know exactly what kind of thing they're dealing with without having to track down the typedef. We're always looking for expressiveness and clarity in our software. Our opinion is:Code://less expressive and less clearCONTROL_BOARD cb;//more expressive and more clearstruct control_board cb;struct control_board *cb;int cb_id;//OK if CONTROL_BOARD is completely opaque, i.e. you can't peak inside or otherwise manipulate it outside of a library/apiCONTROL_BOARD cb;If we used typedefs, we would have a review plan in place for running a formal sweep every time a typedef changes, looking for problems like I noted above. I personally find that very boring and error prone, so we just avoid them altogether. There doesn't seem to be a downside.Whatever works, I suppose.

author: Siwastaja
author_rank: Super Contributor
timestamp: March 07, 2023, 09:27:33 am
content: Almost everyone (except DiTBHO) agrees it's a colossally bad idea to hide pointerness behind a typedef, the loss of readability for no reason whatsoever is obvious. You don't need to convince us about that.This is totally different to using typedef to get rid of the separatestructnamespace, which is originally a totally arbitrary idea of C, anyway. Named struct without object creationistype definition anyway. The only difference by doing the same using typedef is removal of struct namespace from the type.While namespaces are useful (and C lacks them), it's a totally weird idea to group every struct in a single namespace, just because they are structs. What abuse of namespaces.Some prefer having to spell out explicitlystructwhen accessing a struct, and that's fine; but I think it's equally fine not to do it.The argument that you can see something is a struct by having to write it every time is weak. You don't know something is anintfrom the object name, either. Some style guides go to great lengths encoding types to names, but then again do you also want to encode all possible qualifiers (const, volatile, etc.), and if not, why not? In reality, such code becomes unreadable mess unless you work with such style for a long time to get used to it. So most of us just have to check up the object type every time (this is where IDEs are helpful).The Linus Torvald's argument for not typedeffing structs is simply this: structs are usually large, so having to type more reminds you are dealing with something large. Fair enough, but I don't think this is a strong reason.

author: John Coloccia
author_rank: Super Contributor
timestamp: March 07, 2023, 05:09:49 pm
content: Quote from: Siwastaja on March 07, 2023, 09:27:33 amActually, that's another reason I'd forgotten to mention. It keeps structs in the struct namespace, which I think is generally a good thing. Same for enums. For example:Code:enum options {OPTION1,OPTION2,};...enum options options;...options = OPTION1 | OPTION2;Here's another horror: We use gotos in C for early out/cleanup because it lets us gather together our cleanup code, and prevents having to carry around flags who's only purpose is to crawl out of control structures when errors occur. Even MISRA has relented and now allows this specific behavior because it vastly simplifies the software, makes it easier to read, easier to verify and all around more reliable, but everyone still thinks it's blasphemy and we just don't worry about it.As long as you're consistent and your coding/review standards are appropriate to whatever you're doing, there's a lot that comes down to preference and whatever is appropriate for the environment you're in (safety critical, hobbyist, commercial etc). I'll give my opinions but then encourage people to use whatever style and constructs work for them. Just understand the tradeoffs, make a conscious decision, put the proper controls in place, and then stick to it.

author: Siwastaja
author_rank: Super Contributor
timestamp: March 07, 2023, 06:38:01 pm
content: Quote from: John Coloccia on March 07, 2023, 05:09:49 pmWhat a hilarious example, because it demonstrates the absolutely stupid namespacing of C: the only really useful behavior would be OPTION1 and OPTION2 becoming part of theenum optionsnamespace, but that's exactly what is not happening:Code:int main(){enum options options;options = OPTION1 | OPTION2;int thing = OPTION1; // compiles without an issue!!!}So all that namespace thing did was to force the programer to type "enum options" instead of options, reminding us that options is an enum type. A minor detail.C namespaces have nothing to do with actual namespaces, it's just a weird, arbitrary legacy thing. C does not have real namespaces (I'm quite OK with this limitation, though).

author: Simon
author_rank: Unknown
timestamp: March 07, 2023, 07:07:42 pm
content: enums and structs confuse the hell out of me, it's like I am defining a type either way and I'm not sure what to call it. I have now settled on making it a typedef every time as it seems that is what it is and it works better.

author: SiliconWizard
author_rank: Super Contributor
timestamp: March 07, 2023, 07:17:32 pm
content: Quote from: Siwastaja on March 07, 2023, 09:27:33 amIIRC from what he said earlier, he uses naming conventions to show those are pointer types. So it's not hidden. It's just a convention. And as I said, this was pretty much the same convention in Pascal, so people with a Pascal or similar background are more likely to use this convention.I personally do not use it with C, but I'm not ferociously against it either. I also use some naming conventions, just not that.As to namespaces, yes C "namespaces" are just a weird legacy.But I don't think "standalone" namespaces, such as in C++, are a good idea. They are also an ugly hack. I much prefer the concept of modules, which create namespaces of their own but encapsulate code and provide a consistent structure, not just an after-thought hack. This is part of why I find C++ modules ugly. They are themselves an ugly hack not providing much of what modules should provide, and they still require declaring an explicit namespace, that's absolutely horrific.

author: bson
author_rank: Unknown
timestamp: March 07, 2023, 11:57:20 pm
content: Quote from: westfw on February 13, 2023, 09:02:41 amThe problem as I see it is that the registers are volatile. So setting three bits and clearing two requires five bit operations.

author: westfw
author_rank: Super Contributor
timestamp: March 08, 2023, 02:25:58 am
content: QuoteThere was at least one vendor library that made use of local variables typed like the peripherals (but not volatile) that would overcome this problem.Code:uart_init() {SERCOM_UART_CTRLA_Type myctrla = SERCOM0->CTRLA;myctrla.bit.ENABLE = 1;myctrla.bit.TXPO = xx;myctrla.bit.RXPO = yy;// :SERCOM0->CTRLA = myctrla;}(might have been ASF, might have been one of ST's frameworks.)I'm not sure that this works with properly CMSIS-compliant headers, and I'm not entirely sure Ilikeit, but I thought it was "interesting", and it does give the compiler a chance for optimizations.

author: ataradov
author_rank: Super Contributor
timestamp: March 08, 2023, 02:45:56 am
content: ARM's own CMSIS header files have stuff like:Code:typedef union{struct{uint32_t ISR:9; /*!< bit: 0.. 8 Exception number */uint32_t _reserved0:15; /*!< bit: 9..23 Reserved */uint32_t T:1; /*!< bit: 24 Thumb bit (read 0) */uint32_t _reserved1:3; /*!< bit: 25..27 Reserved */uint32_t V:1; /*!< bit: 28 Overflow condition code flag */uint32_t C:1; /*!< bit: 29 Carry condition code flag */uint32_t Z:1; /*!< bit: 30 Zero condition code flag */uint32_t N:1; /*!< bit: 31 Negative condition code flag */} b; /*!< Structure used for bit access */uint32_t w; /*!< Type used for word access */} xPSR_Type;Although it is never used in their code, it is just defined for convenience. So, I'm not sure if there is any specific "compliance" thing.

author: Siwastaja
author_rank: Super Contributor
timestamp: March 08, 2023, 06:40:29 am
content: Quote from: Simon on March 07, 2023, 07:07:42 pmIt is confusing. There are three cases:(1) creation of a throw-away type and a single object of that type.(2) creation of a type in struct namespace, requiring the repetition of struct keyword when the type is used to create objects.(3) creation of a type in global namespace with typedef. Objects can be created with type name alone.

author: John Coloccia
author_rank: Super Contributor
timestamp: March 08, 2023, 10:14:15 am
content: Quote from: Siwastaja on March 07, 2023, 06:38:01 pmI just have no idea what you're talking about. C doesn't have user defined namespaces, but it has 4 distinct, real namespaces. Not sure why you keep saying it's a weird legacy thing.

author: Siwastaja
author_rank: Super Contributor
timestamp: March 08, 2023, 11:07:35 am
content: Quote from: John Coloccia on March 08, 2023, 10:14:15 amI don't know how to explain myself any better. Others seem to understand and agree. If you don't get it, then... you just don't. Good luck.

author: SiliconWizard
author_rank: Super Contributor
timestamp: March 08, 2023, 07:22:59 pm
content: Quote from: Siwastaja on March 08, 2023, 11:07:35 amYeah. Those C "namespaces" are type-bound, or rather bound to classes of types. That's the oddest thing that has ever existed in any programming language as I can recall.And while they do exist, they provide no benefit that I know of. For instance, who in their right mind would use the same identifier for an enum and for a struct?It is obviously a legacy thing.

author: JPortici
author_rank: Super Contributor
timestamp: March 08, 2023, 08:19:44 pm
content: Quote from: SiliconWizard on March 08, 2023, 07:22:59 pmExactly. Then again we have coding standards because there are people that will Buffalo buffalo Buffalo buffalo buffalo buffalo Buffalo buffaloi've seen my share ofCode:class classname {...}...classname classname;classname::staticmethod();classname.method();simply because you can.IIRC it is even permitted/encouraged in other languages, such as the Imperium Program LanguageOr at least the parser doesn't complain, the language creator said

author: Nominal Animal
author_rank: Super Contributor
timestamp: March 08, 2023, 09:17:03 pm
content: Quote from: JPortici on March 08, 2023, 08:19:44 pmand good principles, like thePrinciple of Least Surprise. When applied to source code statements, it simply means that Thou Shall Not Use Ambiguous Statements.In my own example code, you see this in e.g. "superfluous" quotes that help humans see exactly the intent of the statement, and how I usestatic inlinefor accessors and helpers even though plainstatichas the exact same behaviour in C.Ambiquity has surprisingly high maintenance cost. Since in C the name of a function is equivalent to a pointer to that function (i.e.,strlen == &strlen), one should avoid ambiquity in variable and function naming. The one division that makes sense to me is type names having a different "namespace" than variable and function names in C, because they are unambiguous in expressions. In C++, however, as JPortici showed, they can be ambiguous (in the example, class name vs. variable).(My point being, because I apply the Principle of Least Surprise, I often do have variables with the same name as their type in C, but carefully try and avoid that in C++ and Python [especially wrt. class names and variable names]. In Python, I prefer to use Capitalized names for classes, and member and variable names beginning with a lowercase letter, for this particular reason; adjusting my "preferred style" to minimize ambiquity.)For this reason, I like to investigate the reasons behind the choices in various coding styles and standards. It is illuminating.

author: nctnico
author_rank: Super Contributor
timestamp: March 08, 2023, 10:29:36 pm
content: What I usually do is have types start with a T for a struct, E for an enum and C for a class. That way it is somewhat clear what kind of type you are dealing with and what kind of parameter is passed to a function. But there are many other conventions with sound reasoning to use them. Whatever floats your boat

author: Nominal Animal
author_rank: Super Contributor
timestamp: March 08, 2023, 10:49:36 pm
content: Quote from: nctnico on March 08, 2023, 10:29:36 pmYep. In particular, when contributing to existing codebases, it always pays to familiarize oneself to the coding style/convention, before submitting even a preliminary suggestion for comments.It is good practice for oneself (to avoid ossifying within ones existing preferences), but it also shows you're willing to spend the time and effort yourself, changing yourself from an "user" asking for/demanding a change, into a "contributor" suggesting a change. In Libre/FOSS projects, that makes for a huge difference.When I happen to dislike a convention, knowing the reasons behind the convention (and how that particular convention helps with the issues seen in that particular codebase) significantly reduces my irritation when having to use it. "This is annoying, but there istheseideas and useful intent behind it, so I guess I can just deal with it" -type of thing.

author: nctnico
author_rank: Super Contributor
timestamp: March 08, 2023, 10:59:33 pm
content: I really like the existence of formatters to convert between coding conventions. I code the way I like to code because I think it is the most readable / understandable. Don't like it? Here is your converter and go do something useful with your timeStill I'm open to suggestions. A couple of years ago I got one complaint about my coding style and that was/is that I don't use spaces in expressions. That is a remnant of writing Basic on home computers where every space took another byte of precious memory. I found it a good suggestion, wondered why it hadn't occured to me before and I've made it a point to use more spaces from that moment on.

author: cv007
author_rank: Frequent Contributor
timestamp: March 09, 2023, 01:12:25 am
content: QuoteThe atmel headers do this. Each register with a bitfield becomes another struct/union type with its members not volatile. Not sure any other manufacturer has done register bitfields, so the list is short. This allows creating a temp var to be created where you can manipulate the temp bitfields then write the results to the full register (useful for when setting more than one bitfield in a register). It works fine since the compiler can optimize this, but gets a little tedious to write It also prevents the use of anonymous bitfields since you now need a name for these bitfield structs (so end up with .bit access).Another alternative is to give up some efficiency and just treat each bitfield separately-https://godbolt.org/z/66esq9a31It ends up doing more reads/writes than needed, but you will be able to use anonymous bitfields and most register manipulation becomes simple to write/read. You still always need to be aware what is happening behind the scenes (rmw), so have to watch out for things like clearing flag registers which cannot use the bitfield. Giving up efficiency in many cases is worth the ease of use- the smaller mcu probably does not have a lot of peripheral setup code and the larger mcu has the resources to not care very much that your setup code is not as efficient as it could be.Same example using manufactures headers-https://godbolt.org/z/5YnzaK35cYou now have bit manipulation code to produce, but since they are inside functions they are write-once and you will rarely have to deal with the internals of the function again.The path of least resistance is to use what is provided by the manufacturer for peripheral register access. Peripheral register manipulation should stay inside functions, so you shouldn't find yourself having to do register bit manipulation in your app code. In the end, since it seems only atmel has provided these type of headers, and creating them on your own has its own problems (automated or not), there is little chance anyone other than some atmel users are even presented with the bitfield option.