GCC global variables - Page 1
Forum section: electronics / general

author: Simon
author_rank: Unknown
timestamp: August 03, 2019, 04:42:09 pm
content: I have a problem, it was not there at first then I did something unrelated and it cropped up then removed that unrelated thing and it stayed.i have a program, and most af the variables need to be accessible to all functions. In particular the main time counter variable that controls the timing of the program running. So at first I put all external variables declarations in the one header file that is included, but that caused issues like the variable could not be found or was declared more than once. So i tried putting the creating of variabres in the setup.c file where all the setup code is put. and i put in every other c file a declaration for this variable so that all files know that it exists: extern uint16_t timer;but still no joy, so the program sets up and starts one of the timer/counters at 1KHz and every time the counter gets to the top an interrupt fires and increments the "timer" variable, the interupt routine is happening because I have verified it. but when i then test in my main while loop for this variable having exceeded 10 nothing happens so the while loop code seems to net be looking at this variable and maybe more than one has been created?This is infuriating, I have done this before and it worked. Books don't help because I assume GCC is not C99!what do i do? can it be something to do with Atmel Studio, who's is the preprocessor in atmel studio?

author: Nusa
author_rank: Super Contributor
timestamp: August 03, 2019, 04:48:08 pm
content: Try using the "volatile" keyword for any global variables changed in interrupt routines. You may be fighting compiler optimizations.

author: Simon
author_rank: Unknown
timestamp: August 03, 2019, 04:52:02 pm
content: Yes the variable is volatile

author: SiliconWizard
author_rank: Super Contributor
timestamp: August 03, 2019, 04:56:21 pm
content: Quote from: Nusa on August 03, 2019, 04:48:08 pmNot knowing anything more about the OP's source code, I'd vote for this one as well. Has been discussed a few times on this forum.Typically the compiler has no way to know that interrupt routines are ever called, contrary to other functions in your code that are explicitely called, so it may assume that whatever is done inside them has no effect. The volatile keyword makes the compiler assume that the qualified variable is going to get modified outside of the function(s) using it (in "read-only" manner).

author: SiliconWizard
author_rank: Super Contributor
timestamp: August 03, 2019, 04:56:56 pm
content: Quote from: Simon on August 03, 2019, 04:52:02 pmMaybe post the relevant part(s) of your code then... If you can.

author: Mechatrommer
author_rank: Super Contributor
timestamp: August 03, 2019, 04:58:53 pm
content: have you make guard ring in your header file? such as...Code:#ifndef MYFILE_HPP#define MYFILE_HPPextern int myvar;// your codes#endif // MYFILE_HPPmyvar must be defined in one of c files (only once!) such asCode:// myfile.cint myvar;// or volatile int myvar;a snipped code from your actual code provided here may help..

author: Andy Watson
author_rank: Super Contributor
timestamp: August 03, 2019, 05:01:39 pm
content: When you change something that is global - like say a common header file - you have to ensure that all the modules that are affected by the change are also re-compiled - this should be written into your make file, but it's not always the case. You might have to force a re-compile of the whole project for the changes to take effect.

author: Simon
author_rank: Unknown
timestamp: August 03, 2019, 05:03:44 pm
content: Quote from: Andy Watson on August 03, 2019, 05:01:39 pmhow do i do that?

author: SiliconWizard
author_rank: Super Contributor
timestamp: August 03, 2019, 05:03:57 pm
content: Just a thought here. You said you used the volatile qualifier, but how?If you're just using it at the declaration, such as: "volatile int myvar;" but fail to add it in the header file (which would be: "extern volatile int myvar;", but maybe you just wrote: "extern int myvar;"), I think it's going to be ignored by the compiler while compiling any other C source file that includes the header. SO it must be added to the extern declaration as well.

author: brucehoult
author_rank: Super Contributor
timestamp: August 03, 2019, 05:05:06 pm
content: It's very simple.Ideally you should put: "extern uint16_t timer;" in the header file, and then in *one* .c file put either "uint16_t timer;" to have it initialized to zero or else "uint16_t timer = 1234;" to initialize it to some other value (which could be zero, to be explicit).As a special convenience, if you want it to be zero initialized then you can simply put "uint16_t timer;" in the header file and nothing in any .c file. If you do this then the .o from every .c file will contain a definition of timer but the linker will merge them all together,But it's better to put "extern" in the header file and without "extern" in *one* .c file.

author: Simon
author_rank: Unknown
timestamp: August 03, 2019, 05:06:36 pm
content: main.cCode:#include <inttypes.h>#include <avr/io.h>#include "main.h"// System variablesextern uint16_t timer;int main(void){setup();// test codePORTA.DIRSET = 0x01 << PA7;while (1){if(timer > 10){schedule_10ms();timer = 0;// test codePORTA.OUTTGL = 0x01 << PA7;}}}setup.cCode:#include "main.h"// System variablesvolatile uint16_t timer;volatile uint16_t pulse0;volatile uint16_t pulse1;volatile uint8_t mode;volatile uint8_t diagnostic = 0;volatile uint16_t fan_speed;volatile uint16_t scav_speed;void setup(){PORTF.DIRCLR = 0x01 << 4; // button inputPORTF.PIN4CTRL = 0x01 << 3; // enable button pin pullupPORTD.DIRCLR = 0x01 << 3 || 0x01 << 4; //taco 0 & 1 inputPORTD.DIRSET = 0b11100000; // led outputs 0-2 PD5:7PORTF.DIRSET = 0x1 << PF3; // led output 3PORTF.DIRSET = 0x1 << PF0; //power switch controlPORTC.DIRSET = 0x01 << PC0; // fan_pwmPORTC.DIRSET = 0x01 << PC1; // scav_pwmPORTC.DIRSET = 0x01 << PC2; // charge pump pwmPORTMUX.TCAROUTEA = 0x02; // Set TCA output on portCTCA0.SINGLE.INTCTRL = 0x01; //enable OFL interruptTCA0.SINGLE.CTRLB = 0b01110011; // single slope PWM with all 3 output channel enabledTCA0.SINGLE.CMP0 = 0xF; // Channel 0 compare valueTCA0.SINGLE.CMP1 = 0xF; // Channel 1 compare valueTCA0.SINGLE.CMP2 = 0xF; // Channel 2 compare valueTCA0.SINGLE.PER = 3333; // Top value for PWM counter to give 1KHz with a (10/3)MHz CLK_PERTCA0.SINGLE.CTRLA = 0x01; //enable counter with no clock scalerTCB0.CTRLB = (( TCB0.CTRLB & ~( 0x07 )) | ( 0x05 << 0 )); // Set counter mode to input capture frequency and pulse width measurementTCB0.INTCTRL = ( TCB0.INTCTRL & ~0x01 ) | ( 0x01 << 0 ); // Enable capture compare interruptEVSYS.CHANNEL2 = 0b01001011; // event channel 2 connects to pin PD3EVSYS.USERTCB0 = 3;// connect the counter to event channel 2 (3-1)TCB0.CTRLA = (( TCB0.CTRLA & ~( 0x03 << 1 )) | ( 0x0 << 1 )); // Set clock to main clock (CLK_PER)TCB0.CTRLA = (( TCB0.CTRLA & ~0x01 ) | 0x01 ); // Enable the counter (connects the counter to the clock source)TCB1.CTRLB = (( TCB1.CTRLB & ~( 0x07 )) | ( 0x05 << 0 )); // Set counter mode to input capture frequency and pulse width measurementTCB1.INTCTRL = ( TCB1.INTCTRL & ~0x01 ) | ( 0x01 << 0 ); // Enable capture compare interruptEVSYS.CHANNEL3 = 0b01001100; // event channel 2 connects to pin PD4EVSYS.USERTCB1 = 4;// connect the counter to event channel 3 (4-1)TCB1.CTRLA = (( TCB1.CTRLA & ~( 0x03 << 1 )) | ( 0x0 << 1 )); // Set clock to main clock (CLK_PER)TCB1.CTRLA = (( TCB1.CTRLA & ~0x01 ) | 0x01 ); // Enable the counter (connects the counter to the clock source)sei();}interrupts.cCode:#include "main.h"#include <avr/interrupt.h>// System variablesextern uint16_t timer;extern uint16_t pulse0;extern uint16_t pulse1;ISR(TCA0_OVF_vect){timer = timer + 1;TCA0.SINGLE.INTFLAGS = 0x01;}ISR ( TCB0_INT_vect ){pulse0 = TCB0.CCMP;TCB0.INTFLAGS = 0x01;}ISR ( TCB1_INT_vect ){pulse1 = TCB1.CCMP;TCB1.INTFLAGS = 0x01;}

author: Simon
author_rank: Unknown
timestamp: August 03, 2019, 05:19:05 pm
content: Quote from: SiliconWizard on August 03, 2019, 05:03:57 pmI think you hit the nail on the head. Ideally I wanted to put all external (and volatile) variables in the one header file so that they are universally available.

author: djacobow
author_rank: Super Contributor
timestamp: August 03, 2019, 05:29:00 pm
content: I think the variable needs to be declared "volatile" in the header where it is "extern" as well as in the c file where the variable is actually declared.Remember, C does compilation one file at a time. When the compiler is compiling a file that references an "extern" variable, everything it knows about that variable is in the header. In the example I just saw posted from Simon, any unit files that use "timer" will not know to treat it as volatile except the one unit file where the variable was actually instantiated.

author: SiliconWizard
author_rank: Super Contributor
timestamp: August 03, 2019, 05:34:54 pm
content: QuoteThat's fine. Just don't forget to make all your extern declarations fully qualified (ie. contain the same qualifiers as in the original declarations).Not going to discuss the merits or drawbacks of using an excessive number of global variables here. I will just suggest limiting them when you can, because it can get messy real fast.A few critical globals - that's fine.One possible approach with your timer example, unless accessing (reading) the variable is extremely time-critical, you could use an accessor function instead of making the variable global. Said function is declared inside the same C file as the variable, and just returns its value. Just export the prototype of the function. Several added benefits of this (to name a few): it makes the possibility of modifying the variable outside of its intended scope impossible (it becomes effectively "read-only" to the outside source files), and it allows to add extra tests or guarding instructions when reading it: for instance, if the variable is too large to be read atomically on your target, your accessor function can include guarding instructions (for instance: disabling the timer interrupt, reading the variable in a local variable, re-enabling the timer interrupt, and finally returning the local variable...)

author: Mechatrommer
author_rank: Super Contributor
timestamp: August 03, 2019, 05:38:34 pm
content: Quote from: djacobow on August 03, 2019, 05:29:00 pmafter looking at his code, this will be my thought too, but not 100% sure since i havent code for sometime. maybe..volatile uint16_t timer; // <- definitionexternvolatileuint16_t timer; // <- declarationps: definition is not declaration in computing science terminologygoogling the net may give some light...https://www.google.com/search?q=extern+volatile&ie=utf-8&oe=utf-8&client=firefox-bhttps://www.avrfreaks.net/forum/extern-volatile-declaration

author: Simon
author_rank: Unknown
timestamp: August 03, 2019, 05:52:15 pm
content: Quote from: SiliconWizard on August 03, 2019, 05:34:54 pmYes I know that in theory nothing should be external but I find that it helps greatly in structuring the program as it is far easier to then break the code into individual functions that will operate on the same variable set.With regard to the timer variable it needs up-counting by the interrupt routine, testing by the main if statement which could be done with what yo describe but it needs zeroing again by the "if"s code.

author: tggzzz
author_rank: Super Contributor
timestamp: August 03, 2019, 06:59:46 pm
content: Don't forget the differences between volatile and atomic declarations. Whether that makes any difference depends on the processor, the compiler, the compiler version, and the compilation switches.Don't forget what volatiledoesn'tguarantee about, for example, re-ordering evaluation.No, despite having used C since 1981, I am not a C expert anymore. If you want a full answer and understanding, consult a language lawyer

author: Simon
author_rank: Unknown
timestamp: August 03, 2019, 07:03:53 pm
content: what is an atomic declaration?what is re-ordering evaluation.

author: coppice
author_rank: Super Contributor
timestamp: August 03, 2019, 07:19:51 pm
content: Quote from: Simon on August 03, 2019, 07:03:53 pmOn an 8 bit machine accessing 16 or 32 bit variables is not atomic. That is, they will usually be accessed as two or four 8 bit chunks, and the variable might be changed by other code between these accesses.The re-ordering issue is that you should not assume that things will be executed in the order they occur in your source code. Variables may be accessed in a very different order, typically to keep a better flow through the machine's pipelines.

author: Simon
author_rank: Unknown
timestamp: August 03, 2019, 07:22:56 pm
content: Ah right. In actual fact i have no idea why i made it a 16 bit variable as I am only counting to 10. I'll be sorting that out when I make corrections later.

author: SiliconWizard
author_rank: Super Contributor
timestamp: August 03, 2019, 08:40:21 pm
content: Quote from: Simon on August 03, 2019, 05:52:15 pmWhich in the same approach, can be perfectly done adding another function that resets the counter. And call that instead of directly assigning "0" to the variable.

author: mikerj
author_rank: Super Contributor
timestamp: August 03, 2019, 09:41:31 pm
content: Quote from: SiliconWizard on August 03, 2019, 08:40:21 pmUsing small functions to get/set the value of variables is a much better idea than having a huge clump of global variables. Also I don't see any mention of shadowing, perhaps there is another variable of the same name which is closer in scope to the function using it, often caused by beginners using header files to define variables.

author: rstofer
author_rank: Super Contributor
timestamp: August 04, 2019, 01:20:29 am
content: Atomic access means that the entire store operation is completed without possible interruption. This doesn't work when you have a multi-byte variable that is moving in and out of narrow memory. In fact, since you don't look at the assembly code, you can't assume it works at all.Inside the interrupt routine the handler 'owns' the variable. Nothing in the mainline can influence what happens in the interrupt routine. That isn't true for the mainline. Consider what happens when the assembly code is halfway done modifying a multi-byte variable and the interrupt fires off and changes everything. It creates a mess!So, when your mainline code simply must modify a value that is manipulated in an interrupt handler, the only safe thing to do is shut down the interrupts before the mainline changes the value and re-enable the interrupts after the change. It's called a 'critical region' in RTOS parlance.Volatile doesn't handle this at all. Volatile simply makes the compiler emit code to re-evaluate a variable every time through a loop. Ordinarily, a variable would be considered 'loop invariant' if it isn't changed inside the loop and evaluated just once, probably before entering the loop.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: August 04, 2019, 02:21:12 am
content: Another option, if a somewhat heavy-handed one, is to put all the variables into one .c file, and expose only accessor functions. This is equivalent to the OOP practice of private variables, private helper functions, and public getters/setters.Probably not be worthwhile on a small or embedded project, but then again, embedded CPUs are awfully powerful these days; a Cortex M0 at 160MHz doing the job of a PIC at 8MHz say? Lots of room for verbosity.If you have atomic access to worry about (the getters/setters may sometimes be called from interrupts?), you can set an atomic flag that another function is currently executing, and decide to pass on execution (the function would return an error code that it failed). It would be very convenient to simply say, "go back to that other function and finish it up, then come back here" -- but that is actually terribly difficult to set up (such capability is one of the distinguishing features of a proper multitasking OS), so for ordinary C code it is much easier to fail out.Or you can do atomic buffering operations, queuing the getting/setting operations so they are always done exactly in sequence. But, such a getter needs a target pointer, or callback function, to know what to do with its returned data; and that operation in turn probably needs to be performed atomically, too. So it's not that much easier, and still just as prone to bugs.If you have a simple, special-case, one-way data flow, that doesn't necessarily need to be in sequence (like a graphical frame buffer, which is new every time): double buffering is an excellent way to handle it. Set an atomic flag which indicates which buffer to read from, and write to the opposite buffer. Toggle the flag once a read and write cycle has been completed. That way, no reading or writing is ever trampled.Tim

author: brucehoult
author_rank: Super Contributor
timestamp: August 04, 2019, 02:45:12 am
content: Quote from: T3sl4co1l on August 04, 2019, 02:21:12 amWhere have you found a Cortex M0 at 160 MHz?I've never seen an M0 offered at more than 50 MHz, or an M0+ at more than 75.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: August 04, 2019, 02:55:46 am
content: Right, M4*. But 50 or 75MHz is still quite a lot of CPU.Tim

author: Nominal Animal
author_rank: Super Contributor
timestamp: August 04, 2019, 05:07:57 am
content: In this particular situation, I like to createglobals.hthat is included by all C source files accessing any of those globals:Code:#ifndef GLOBALS_H#define GLOBALS_H#ifdef MAIN#define EXTERN#else#define EXTERN extern#endifEXTERN volatile int myvar;#undef EXTERN#endif /* GLOBALS_H */and in the source file containingmain(), have#define MAINbeforeanys.This way, there is only one declaration, and one definition in the compilation unit containing main(). Makes maintenance simple.If you have atomic variables, I like to declare them inatomics.h, define inatomics.c, and haveatomics.hinclude static inline accessor functions usingGCC atomic builtins. For example,atomics.h:Code:#ifndef ATOMICS_H#define ATOMICS_Hextern volatile unsigned int mycounter_value;static inline unsigned int mycounter(void){return __atomic_load_n(&mycounter_value, __ATOMIC_SEQ_CST);}static inline unsigned int mycounter_postinc(void){return __atomic_fetch_add(&mycounter_value, 1, __ATOMIC_SEQ_CST);}static inline unsigned int mycounter_predec(void){return __atomic_sub_fetch(&mycounter_value, 1, __ATOMIC_SEQ_CST);}#endif /* ATOMICS_H */andatomics.c:Code:"atomics.h"volatile unsigned int mycounter_value;You only use the accessor functions, and notmycounter_valuedirectly, elsewhere in your code. Makes porting code to other compilers easier, too -- although ICC and clang support those as well.Technically, it is not necessary to markmycounter_valuevolatile, because it is not to be accessed directly by anything other than the static inline functions, and they ensure it is fetched; but having it marked volatile does not hurt.If you have mixed C/C++ sources, addextern "C" {and}guards as usual. If you use C++, use native atomics. If you need the code to compile on older versions of GCC, use the legacy__sync builtinsinstead.(I wish someone would disable the damn stuff in this forum, so that C preprocessor statements would not be indented and linkified.)

author: nigelwright7557
author_rank: Frequent Contributor
timestamp: August 04, 2019, 05:25:15 am
content: Quote from: coppice on August 03, 2019, 07:19:51 pmAtomic usually means done in one instruction.Havoc comes when its done in 3 instructions like a read, modify,write sequence.You get an interrupt half way through that modifies the same variable.The interrupt ends and the old variable is used in the main code.

author: Whales
author_rank: Super Contributor
timestamp: August 04, 2019, 05:30:11 am
content: I encountered similar issues once before, but on win32.A (DLL) library I was using had globals. My program could access them directly and through getter/setter funcs.That didn't go too well. Both my program and the DLL ended up with completely different "views" or "copies" of the globals to play with. In my case I think it was also because of the compiler I was using (TCC).I will +1 the suggestion of doing a complete from-scratch build. Delete all of the temporary object files, so every file is forced to rebuild.

author: Mechatrommer
author_rank: Super Contributor
timestamp: August 04, 2019, 06:20:43 am
content: Quote from: Whales on August 04, 2019, 05:30:11 amhave you made a success on sharing dll global variable amongs win32 exes? because what i understand, each exe (even multiple instances of the same exe) will always make a copy of their own to avoid multi threading race problem or malicious access from another program.

author: magic
author_rank: Super Contributor
timestamp: August 04, 2019, 07:05:50 am
content: Quote from: tggzzz on August 03, 2019, 06:59:46 pmDamnit, you scared meIt's not that bad actually, from current GCC manual athttps://gcc.gnu.org/onlinedocs/gcc/Volatiles.htmlQuoteFor those wondering, sequence points are a well-defined concept in C whose exact definition can be looked up, I guess. It includes different expressions, soCode:extern volatile int a, b;a++;b++;executes as one expects and incremented a before b.This however is not guaranteed to be so:Code:return a++ + b++;

author: tggzzz
author_rank: Super Contributor
timestamp: August 04, 2019, 07:28:25 am
content: Quote from: T3sl4co1l on August 04, 2019, 02:21:12 amAt higher optimisation levels, won't the compiler inline such getter/setter functions? Even the Java HotSpot runtime does that!

author: Mechatrommer
author_rank: Super Contributor
timestamp: August 04, 2019, 08:04:44 am
content: yup just a taboo version of excessive exposure. accessor functions in OOP is provided so the class can make other housekeeping task when access/modifying a variable member, if the function is simply = the var to be accessed, it will be much simpler or equally as to make the variable public.

author: tggzzz
author_rank: Super Contributor
timestamp: August 04, 2019, 08:08:24 am
content: Quote from: Mechatrommer on August 04, 2019, 08:04:44 amYou miss the point. Get the compiler to do the optimisations, safely, transparently, and (with HotSpot) only where it really benefits the application.

author: Mechatrommer
author_rank: Super Contributor
timestamp: August 04, 2019, 08:11:02 am
content: such minor optimisation is moot in this age, what we need is a working application as intended...

author: T3sl4co1l
author_rank: Super Contributor
timestamp: August 04, 2019, 08:15:19 am
content: Quote from: tggzzz on August 04, 2019, 07:28:25 amYes, if they're small enough and/or used infrequently enough that it's viable of course. Though putting them between modules may prohibit that, depending on if they're in the same build/link environment (versus an object library, or dynamic for that matter), and depending on what kind of optimizations are allowed (link-time optimization doesn't work for a library, but does for a completeEven if it optimizes down to the same thing (literally "x = y" one-liners), the semantics may be different from an all-shared build, and can be safer from a programming standpoint (it's harder for any one .c file to break everything?), if not from an execution standpoint (ideally, different modules might be protected from each other explicitly in separate memory spaces; though that's more at the scale of interprocess, not intermodule, programming).But this is very much beyond the limits of my knowledge, as far as nuanced semantics and optimizations and memory management and operating systems are concerned. I'll leave the precise logic to those in the know.Tim

author: tggzzz
author_rank: Super Contributor
timestamp: August 04, 2019, 08:27:41 am
content: Quote from: T3sl4co1l on August 04, 2019, 08:15:19 amRegrettably such "precise logic" is an oxymoron, like "military intelligence". Both do exist, but they aren't what an external observer might expect or comprehendA C compiler can only guess at what is sufficiently small and/or frequent to inline, and is often hobbled by the possibility of aliasing. Java Hotspot measures what is actually frequently used, and there can be no aliasing at that level.

author: Simon
author_rank: Unknown
timestamp: August 04, 2019, 08:53:28 am
content: Well for this application it's not much of a problem because of what it does. The time variable should be 8 bit as it's only counting to 10. The other interrupt accessed variables are a tacho which for this application is not an issue. Speed adjustments are done one step at a time in small steps with no complicated predictive control loop. The speed control algorithm just checks if the speed is higher or lower than threshold and nudges the control signal up or down. so if an error is made due to the variable being modified part way through evaluation it would mean a tiny speed nudge up or down more than wanted but this would not be a problem and there is an amount of dithering already intentionally put into the speed control so it would not even show.It looks like my intention to move to SAMC ARM devices a good idea as much of these problems go away unless the variable is over 32bit and code will be more efficient as the time is spent "doing it" rather than trying to work around limitations of the hardware.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: August 04, 2019, 09:05:02 am
content: Quote from: tggzzz on August 04, 2019, 08:27:41 amWell no, it doesn't guess, it actually checks, it does it and compares on the metric, whatever that is (CPU cycles, object size..).At least that's what I understand from GCC's documentation, what of it I've read. I think... they do two passes? One for assembler, one for linkage (if enabled).I forget when clang is supposed to do optimization, if it's before LLVM generation, or after? AFAIK there's a lot of smarts in there that would be able to do these optimizations either way though.Thereareclear rules that all of these things are run by (they're literally just more dumb computer programs), but they're really only well known to those very deeply familiar with the quirks and processes of compiler internals, or compiler devs themselves. Or, those that have run into the edge cases here and there, and found that it's actually a bit of semantics that they didn't mean to put in there but actually did...Tim

author: Siwastaja
author_rank: Super Contributor
timestamp: August 04, 2019, 09:05:43 am
content: Instead of exposing all program-wide global variables in the headers, I often opt for declaring them where I need them. This gives the declaration a smaller scope. Like this:void do_something(){......extern volatile int battery_voltage;battery_voltage used right here;......}Since heavily relying on such globals would get messy soon, having the declaration in plain sight helps the reader to understand that this variableindeedis:A) defined in another module,B) a volatile,C) likely a hack, which could be replaced by something betterI do this especially when I feel like it's a temporary hack. Very local declaration documents it as such, and keeps affected area as small as possible. If I find myself then needing to declare it everywhere, I need to rethink.I only add the externs to the header files if I feel like I'm comfortable with them being there. I.e., it's really used everywhere, I remember what it is and why it exists - and why it's not behind access functions. For example, a global, very widely and often used millisecond counter, named properly.

author: Siwastaja
author_rank: Super Contributor
timestamp: August 04, 2019, 09:09:49 am
content: Quote from: tggzzz on August 04, 2019, 08:27:41 amNo need to guess, modern C compilers (including GCC) have built-in instrumentation techniques. Basically, you compile it with a suitable flag, it injects instrumentation code, you run the program, and it outputs a statistic blob, which you input for the next compilation. This guides inlining, but also branch probabilities.Without such instrumentation, it would be guessing, yes.

author: nctnico
author_rank: Super Contributor
timestamp: August 04, 2019, 09:40:15 am
content: Global variables cannot always be avoided. What I usually do is put them in one or more structs.

author: tggzzz
author_rank: Super Contributor
timestamp: August 04, 2019, 10:21:08 am
content: Quote from: Siwastaja on August 04, 2019, 09:09:49 amOh, I didn't realise it was almost catching up with Java in that respect. They probably took note of HP's Dynamo experiment in 1999.Now all it has to achieve is to do that automatically when you run the code on a different processor, e.g. one that didn't exist when the compilation was done. Or when the library blob (without source) is linked with an application and other library blobs.Aliasing requires pessimisation.

author: tggzzz
author_rank: Super Contributor
timestamp: August 04, 2019, 10:28:02 am
content: Quote from: T3sl4co1l on August 04, 2019, 09:05:02 amMany experienced and intelligent people have been bitten more than once by C.I'd be interested to see understand how a library binary blob (i.e. without source) is optimised when linked with other library blobs and an application. Bonus points for when the compiler version/optimisation levels are different or it is a newly released processorBut I suspect that kind of thing is why .NET updates take your machine offline for so long

author: legacy
author_rank: Super Contributor
timestamp: August 04, 2019, 11:44:41 am
content: Quote from: Nominal Animal on August 04, 2019, 05:07:57 amI vote for this solution. It's the same used in multicore libraries, and it does the job.

author: NorthGuy
author_rank: Super Contributor
timestamp: August 04, 2019, 01:25:16 pm
content: Quote from: Siwastaja on August 04, 2019, 09:09:49 amModern CPUs do this at run time even better and you don't even need to lift a finger.However, the abundance of bloated software tells us that compiler/CPU optimizations are powerless against human bloat.

author: tggzzz
author_rank: Super Contributor
timestamp: August 04, 2019, 01:37:27 pm
content: Quote from: NorthGuy on August 04, 2019, 01:25:16 pmOnly for recently executed branches, and at the expense of leaking information across security domain barriers.The latter has meant that (x86) microcode updates have had to disable some optimisations, and reduced performance. It will be interesting to see if such optimisations survive in the future.

author: ejeffrey
author_rank: Super Contributor
timestamp: August 04, 2019, 02:23:34 pm
content: Quote from: rstofer on August 04, 2019, 01:20:29 amThat's not actually what volatile does. Volatile means that everyemor load and store in the C "virtual machine" will actually happen and preserve order. So one outcome is that variables accessed in a loop will be fetched from memory every iteration, but it does more than that.In practice, all C compliers guarantee that variables of certian types will be loaded and stored with a single instruction based on the target ISA. On a single core CPU that is sufficient to make them atomic, including interrupt context. This also works for signals if your platform supports them. So as long as you know what sizes your platform supports you can share volatile variables between main thread and interrupt context.

author: SiliconWizard
author_rank: Super Contributor
timestamp: August 04, 2019, 02:30:54 pm
content: Quote from: mikerj on August 03, 2019, 09:41:31 pmYeah, many benefits of doing that. Would be too long to list them all. And of course, it must not be overdone either: as always, common sense helps.But basically, accessing a variable only within a single source file makes things much clearer, hides the internals of a given module, avoids a lot of possible bugs (the possibility of modifying a variable all over the place can get very nasty for maintenance, and a source of potentially horrible bugs), makes modifications of said module much easier to do, makes you code in a much more structured and modular way (which improves many things including reusability), etc.I've noticed many people still have a very hard time thinking "modular" when programming, which is unfortunate, and only force themselves doing so (but not necessarily that well) using specific languages. You can write very decent modular code in C. Just because it doesn't include fancy OOP features doesn't mean you should write piles of spaghetti with it.

author: rstofer
author_rank: Super Contributor
timestamp: August 04, 2019, 02:58:36 pm
content: Quote from: ejeffrey on August 04, 2019, 02:23:34 pmWell, you can't store a 32 bit value in an 8 bit memory in one cycle no matter what the data type. Omitting 'volatile' because some particular architecture supports the full data width makes porting a bit more of an issue.I have been blocking critical operations with disable and enable of interrupts for a very long time. It's pretty much guaranteed to work and it also happens to be the way most RTOSes deal with the issue. Here is how FreeRTOS handles it:https://www.freertos.org/taskENTER_CRITICAL_taskEXIT_CRITICAL.htmlYou simply can't depend on the compiler. There is likely to be differences in the code sequence between -O0 and -O3 optimization levels. The compiler writers are very good but sometimes we out-clever ourselves. And who's to say the same compiler (or even toolchain) will be used on the code somewhere down the line.How many times has the programmer been caught out with a 'spin loop' (delay) that gets optimized away? It happens all the time. "How come my time delay code doesn't work?".

author: andersm
author_rank: Super Contributor
timestamp: August 04, 2019, 03:10:32 pm
content: Quote from: ejeffrey on August 04, 2019, 02:23:34 pmOnly wrt. other volatile accesses. The compiler is free to reorder non-volatile accesses around volatile accesses as it sees fit.QuoteOnly individual reads and writes though. The proposal thatdeprecates many uses of volatilein C++20 is worth reading.Edit: The final version of the paper ishere, but it doesn't include the rationale discussion.

author: rstofer
author_rank: Super Contributor
timestamp: August 04, 2019, 03:10:45 pm
content: Quote from: SiliconWizard on August 04, 2019, 02:30:54 pmGetters and setters is a really nice idea but may incur a time penalty for the function calls. In an interrupt routine (probably calling the 'setter'), the delay may be objectionable. Of course there is also a delay for the 'getter' call. Whether it matters depends on the application. Or, we can declare the functions as 'inline' and eliminate the overhead of the function call. This look appealing! We get the benefit of using getters and setters with no overhead for function call/return.Getters and setters don't solve the issue of needing to enable and disable interrupts around a critical region. That would still need to be handled somewhere. The call from inside the interrupt routine has no issues, it is every other call, wherever they are scattered, that have the problem.So, let the interrupt routine handle the variable directly with no protection and then build the protection into both the getter and the setter functions. Pretty much guaranteed to work because the interrupt routine can't be called in the middle of getting or setting.I have never used the functions but I really like the idea. It kind of depends on how many places in the code manipulate the shared variable. If it's just once in a mainline, well, there isn't much point in getting carried away. But if the getter is called all over the code, sure, putting the protection code inside a getter function is a great idea.In the case of these variables shared with an interrupt routine, I might put the getter and setter code inside the file for the interrupt handler. That seems like a logical place to put it and makes the intent quite clear.

author: djacobow
author_rank: Super Contributor
timestamp: August 04, 2019, 03:58:57 pm
content: "getters" and "setters" can be very cheap, potentially free, if you have inlining turned on. Even across unit files, they can "disappear" from the intermediate assembly if you are using link-time optimization (which you should -- it's great!)

author: rstofer
author_rank: Super Contributor
timestamp: August 04, 2019, 05:37:04 pm
content: Quote from: djacobow on August 04, 2019, 03:58:57 pmMore important to me is the fact that all references to the shared variable are contained in a single file. It doesn't matter how code outside the file calls the getter and setter, everything about the variable is contained in a single file. The variable isn't even known outside the file. The .h file will contain the prototypes for the functions and that's all that needs to be known to the outside.

author: magic
author_rank: Super Contributor
timestamp: August 04, 2019, 08:09:30 pm
content: Global setters/getters are last decade, the fancy way today is dependency injection.Code:#include "my_time_object.h"#include "irq_driven_time_counter.h"#include "something_that_needs_to_know_time.h"int main() {my_time_object *time = make_my_time_object();irq_driven_time_counter *idtc = make_irq_driven_time_counter(time);something_that_needs_to_know_time *blah = make_something_that_needs_to_know_time(time);...}That way both irq_driven_time_counter and something_that_needs_to_know_time only need to know how to operate a my_time_object and they don't care about how the program's global time variable is created or where to look for it. They just get a pointer (or reference in other languages) during initialization and remember it internally for their own use.As for profile guided optimization, it's not something that C has caught up with advanced Java VMs in recent years, it probably predates Java as a language itself. It just doesn't see much use because it's cumbersome - a test run of the program needs to be performed using realistic example data and then everything is recompiled again.Link time optimization is implemented by adding the compiler's internal code representation to the .o file so that optimizations can be continued as if nothing happened when several .o files are combined. This only works if all .o files are generated with the same compiler and linked using the same compiler's linker, which understands that compiler's internal representation. If a different linker is used or some of the .o files are missing the internal representation, normal machine code from the .o files is copied into the output without optimization.Somebody mentioned using built-in atomics. These are nice things but should be used with care and understanding of how they are implemented. For example, a normal compiler for x86 userspace applications will not generate code for disabling/reenabling interrupts around an atomic access. In fact, even GCC as configured for Linux kernel compilation won't do that. So better make sure to know what your compiler means by "atomic" before using it

author: nctnico
author_rank: Super Contributor
timestamp: August 04, 2019, 09:15:18 pm
content: Quote from: rstofer on August 04, 2019, 05:37:04 pmThe downside is that if you have many settings that you'll have many getters & setters. Recently I wrote firmware for a realtime system controller with a SCPI command interface. The core of this firmware has 3 layers: the SCPI processor, a generic process and a realtime interrupt driven control process. Each module has an API. Between the SCPI command interface and the generic process I made universal getter and setter functions which take an argument number (from an enumeration) to tell which variable to pass. Between the generic process and the interrupt driven process I made setters which block the interrupt during variable update. However for the getter I made a function which copies the dataset for the interrupt process so the generic process can read whatever it needs. It is just more efficient from a coding point of view.

author: tggzzz
author_rank: Super Contributor
timestamp: August 04, 2019, 09:47:38 pm
content: Quote from: magic on August 04, 2019, 08:09:30 pmThat's a fancy newnamefor something that most of us have been doing for many decades!QuoteHP's Dynamo project and compiler (c1999) was rather important w.r.t. profile driven optimisation of C.Practicality is very important. In Java the HotSpot optimisations are automatic and most developers don't even realise they are there and what is happening.

author: rstofer
author_rank: Super Contributor
timestamp: August 04, 2019, 11:26:04 pm
content: If I were doing this in C, it might look like the followingTimer.hCode:void inline SetTimer(int);int inline GetTimer(void);void InterruptHandler(void);Timer.cCode:#include “timer.h”int TimerValue; // we can't use static int with inline functions or the compiler whinesvoid inline SetTimer(int NewValue){/* disable interrupts */Timervalue = NewValue;/* enable interrupts */}int inline GetTimer(void){/* disable interrupts */int Temp = TimerValue;/* enable interrupts */return Temp;}void InterruptHandler(void){TimerValue++;}main.cCode:#include "timer.h"void main(void){int TimeNow = GetTimer();SetTimer(TimeNow + 10);}We have a 3 way choice to make: If the functions are inline, the variable TimerValue should not be static because GCC whines and snivels. We can ignore the warnings and try both a static value and inline functions. Or we can choose inline or static but not both.It really depends on how often the functions are called. If the overhead isn't a big deal, remove inline and use a static variable. This limits the scope to the timer.c file. If overhead is a big deal, omit static and use inline. Or try with both - it's just a warning, the computer didn't melt down.

author: magic
author_rank: Super Contributor
timestamp: August 05, 2019, 04:14:39 am
content: Quote from: tggzzz on August 04, 2019, 09:47:38 pmQuite possibly, software is a retarded field full of young kids who keep reinventing the wheel and cargo culting.Also, [most of whom]Plenty of software in the wild is a tangle of global objects and hidden dependencies.Quote from: tggzzz on August 04, 2019, 09:47:38 pmWell, back then I admittedly only knew pascalBut it took me two minutes to find this article, written in 1998, about something Intel developed based on their experience from 1992, so presumably somewhere during early to mid nineties.http://www.drdobbs.com/profile-guided-optimizations/184410561If nothing else, it clearly proves that the name "PGO" already existed in 1998. I'm pretty sure the idea is even older. Automatic instrumented profiling itself dates back to the early 80s, apparently.QuoteWait, what? That's clearly a very different beast than what is normally known as PGO in C/Fortran/etc.

author: Simon
author_rank: Unknown
timestamp: August 05, 2019, 06:32:35 am
content: But if i have a getter that returns the value would that need interrups disabling ? if so how does that work as the re-enable interups must come after the value is returned but return is alwas the last line of code in a function?

author: Nusa
author_rank: Super Contributor
timestamp: August 05, 2019, 06:55:26 am
content: Quote from: Simon on August 05, 2019, 06:32:35 amYou re-enable them before returning from the getter. The point of disabling interrupts is to make sure the value doesn't change DURING the fetch. Once you have a copy of it, you're good in most cases.Alternately, you move the interrupt control out of the getter/setter routines and do it around the call. This lets you decide if you want to do something critical with the value before re-enabling interrupts.If the functions are inline and the compiler honors the request, there isn't even going to be a call or a return. The body of the function will replace the function call by the compiler.

author: Simon
author_rank: Unknown
timestamp: August 05, 2019, 07:03:18 am
content: Oh, so I use a temporary variable in the function and copy the value into it and then return it. Yea, 32 bit CPU sounds much more efficient

author: Nominal Animal
author_rank: Super Contributor
timestamp: August 05, 2019, 07:06:48 am
content: I just noticed that nobody asserted this yet -- we are talking about microcontroller programming using GCC in a semi-freestanding environment, right?(As opposed to application programming in a hosted environment, or library programming? Because if we are, I must severely modify my suggestions.)

author: Nusa
author_rank: Super Contributor
timestamp: August 05, 2019, 07:07:47 am
content: Quote from: Simon on August 05, 2019, 07:03:18 amRight. And if the function is in-lined, the compiler optimizes that down to a simple assignment to your result variable.

author: Nominal Animal
author_rank: Super Contributor
timestamp: August 05, 2019, 07:15:53 am
content: With respect to atomic accesses, many hardware architectures can access memory atomically without disabling interrupts. This means that enabling/disabling interrupts may not be necessary, or even useful. GCC __atomic/__sync built-ins should implement those on all hardware architectures.When disabling and enabling interrupts is useful, it is important to allownesting, as otherwise you will have to leave it to the caller to manage the interrupts, and document the expectations for every function, every use case, separately.Nesting simply means that you need as manyenables as you havedisables to actually re-enable the interrupts. This is trivial to implement, although it is usually implemented in assembly to ensure the correctness. (In particular, you do not want to have time-of-check-to-time-of-use bugs, and you want the corner cases to be such that interrupts may be hardware-disabled more than once, but never enabled unless certain.)

author: Jeroen3
author_rank: Super Contributor
timestamp: August 05, 2019, 08:30:58 am
content: One other advantage of get/set functions is the ability to insert range checks and the option to easily create mocks for testing.You also only have one copy of the platform dependent accessibility code inside this function instead ofeverywhere the variable is referenced.Plus you can't pull a pointer of the thing anymore, so that's one more human error that will not occur on this variable anymore.

author: SiliconWizard
author_rank: Super Contributor
timestamp: August 05, 2019, 01:18:56 pm
content: Quote from: rstofer on August 04, 2019, 03:10:45 pmYes, but I think you didn't quite get my point/approach. I'm not advocating putting a bunch of globals in ONE file with getters and setters for everyone else as someone else suggested. I would actually find this almost as spaghetti-like as the direct externing of globals... I'm saying to make things truly modular.What it means here is that typically, I would declare (as static volatile) the variable in question in the same source file where the ISR lies, and every other function that has to have fast and direct access to the variable.No time penalty. Try and make all time-critical functions related to this within the same scope, while keeping a modular logic. In the OP's case, a "timer" module handling all tasks related to the timer for instance: ISR, resetting the counter, and anything else around it (possibly functions that convert the counter to a scaled time value if needed, etc.)Quote from: rstofer on August 04, 2019, 03:10:45 pmGetters or special setters (like a counter reset) would be for the "outside" world and less time-critical uses. So as said above, try and think of all the time-critical uses and keep that inside one module.For instance, for a timer counter like in the simple OP's example. Some overhead in the getter is no problem here as long as you can guarantee that the getter's overhead is less than the period between two interrupt calls. Which will be very often the case. In many cases, if a getter's overhead is unacceptable, that usually means that the logical and maybe only option would be to perform the given task that reads the variable inside of the ISR anyway...Of course as I said, you don't have to stubbornly follow this way of doing things in particular cases where directly accessing the variable would still be the only option. There may be cases obviously where this modularity makes things awkward. Common sense always helps.Quote from: rstofer on August 04, 2019, 03:10:45 pmYes, you can do that.I think it has been mentioned earlier in the thread (didn't read it all again), some have suggested that inlining may occur even without declaring functions as "inline". If in separate source files, I wouldn't count on that in the general case. I think this would lie in the "LTO" category (link-time optimizations), and depending on the compiler, that may or may not be available, may or may not handle inlining by itself, and may or may not require a special extra compiler option to be enabled.

author: SiliconWizard
author_rank: Super Contributor
timestamp: August 05, 2019, 01:22:30 pm
content: Quote from: Jeroen3 on August 05, 2019, 08:30:58 amYes, as I said earlier, it has many additional benefits. You can add range checks. Much easier to maintain. You can add guarding sections (for interrupted/multi-threaded code). You can convert some values on the fly. You can keep track of every time a "variable" is "read" (could be useful for debugging purposes). You can completely avoid the possibility of a variable being unintentionally modified outside of where it's supposed to be. The list is really almost endless.

author: tombi
author_rank: Regular Contributor
timestamp: August 05, 2019, 01:42:37 pm
content: One issue with globals is initialisation order. It is fine if all your code runs after all the initialisation is complete and if all variables don't depend on any other variable. But if you start adding globals in more than one module and you just want variable x to start with the value that variable y in this other module has... anyway. It can quickly degrade.Another way of doing this is to use a singleton pattern so instead you export one function that returns a pointer to a structure containing values you want to share. The first time it is called you initialize the structure containing the shared values and return it. After that the function to get the singleton value just passes back the initialized set of values. Of course you have to be careful to make it reentrant and thread safe (if you are doing threads) not to mention making sure the way you use the values is reentrant and thread safe. I'd use this for stuff like global configuration that has to be shared.Or better still don't use globals and be very careful about what gets shared outside of a module (basically keep the module's business private to the module) and provide access to these few things you have to provide access to by getter/setter. (What Silicon Wizard said).Ok this is pretty general advice - hope it helps....Tom

author: SiliconWizard
author_rank: Super Contributor
timestamp: August 05, 2019, 01:50:04 pm
content: Quote from: tombi on August 05, 2019, 01:42:37 pmYeah. But you should really avoid any kind of such cross-dependencies when you can. It's nasty anyway.Design-wise, such dependency should NOT be seen as a dependency between globals anyway IMO, but a dependency between said modules. That's not really a variable problem per se. It's all about module initialization. Some modules may be required to be ready before some others: just add whatever means is practical to handle this.

author: tggzzz
author_rank: Super Contributor
timestamp: August 05, 2019, 02:04:13 pm
content: Quote from: SiliconWizard on August 05, 2019, 01:22:30 pmUnless some miscreant has managed to create a pointer to it. The merepossibiltyof such aliasing prevents many optimisations.Yes, you can tell the compiler "no aliasing occurs", but then what about debuggers, and will the noalias continue to be valid after 3 years of maintenance?QuoteGetters and setters are A Good Thing.Even better is to not have exposed state.

author: SiliconWizard
author_rank: Super Contributor
timestamp: August 05, 2019, 02:20:39 pm
content: Quote from: tggzzz on August 05, 2019, 02:04:13 pmIndeed. But accessing such a variable through a pointer would pretty much defeat all benefits of the above approach. Mixing both ways of accessing a variable is a recipe for disasters, readability and maintenance issues.That said, you're right about compiler optimization: usually the default is they consider that aliasing may occur. I think the "restrict" qualifier in C99+ helps with that. I've only used it very sparingly though, and AFAIR, never in any "embedded" code. (Did it to optimize intensive FP routines meant for PC hosts. The result, with additional alignment attributes, was dramatically better)

author: nctnico
author_rank: Super Contributor
timestamp: August 05, 2019, 08:59:16 pm
content: Quote from: Jeroen3 on August 05, 2019, 08:30:58 amI'd like to emphasize the range checking. I always add these to external interfaces of modules. If a program goes haywire and starts to execute your program randomly (I've seen this happen!) then the range check will prevent damage and contain the situation. In some of my firmware I have 2 or even more range checks between the input value and the result which is used to control hardware.

author: SiliconWizard
author_rank: Super Contributor
timestamp: August 05, 2019, 09:49:42 pm
content: Yep. I actually do parameters checking in almost ALL my functions actually, not just at the interface level (except a very few that either wouldn't benefit from it or are extremely time-critical, in which case I try and make sure that parameters/variables they use will be in the right ranges in upper levels) and have been doing so for years now. That's what is sometimes called "defensive" programming, or something in that vein. I've never regretted doing that. Of course that incurs having to deal with a fair amount of error checking, which I happen not to mind either. I personally wouldn't do it any other way. The small overhead is well worth it IME. Of course, some other people don't like to do this and even expose a large list of arguments why. That's fine. I'm no evangelist.

author: hamster_nz
author_rank: Super Contributor
timestamp: August 05, 2019, 10:29:10 pm
content: Quote from: SiliconWizard on August 05, 2019, 09:49:42 pmWatch out, some youngster will re-invent this idea and call it "the explicit contract verification paradigm" and it will take off.

author: SiliconWizard
author_rank: Super Contributor
timestamp: August 05, 2019, 10:39:15 pm
content: Quote from: hamster_nz on August 05, 2019, 10:29:10 pmI know.Design by contract is a thing.Some newer languages include specific constructs for that. Reasonably recent versions of Ada, Eiffel (does anyone know this language) and quite a few others...The main difference with doing it "by hand" (such as with C), aside from moderate syntax sugar coating, would be the underlying exception handling stuff. Many people don't like error checking in C because it's of course a lot more work than just letting the language handle exceptions by itself. That's certainly an area where C is very rough, but I happen not to mind that much the extra work, because in many cases, proper error checking accounts for maybe 50% (probably exxagerating it a bit here) of the total code, and that's fine with me. It's every bit as important as the actual processing. Boeing should probably take a hint.Anyway, of course YMMV.

author: hamster_nz
author_rank: Super Contributor
timestamp: August 05, 2019, 11:25:50 pm
content: Quote from: SiliconWizard on August 05, 2019, 10:39:15 pmMany people hate it because it is so inconsistent - checking pointers are almost OK as NULL usually means "bad stuff happened". But for everything else all gloves are off, and you have to read the manpage for every function.Is a return of zero success or failure? because in C -1 is 'true" and zero is 'false'... and then there failures that give an EAGAIN and EINTR in errno, where you need to retry....

author: SiliconWizard
author_rank: Super Contributor
timestamp: August 05, 2019, 11:46:05 pm
content: Quote from: hamster_nz on August 05, 2019, 11:25:50 pmI still think it's mainly because it's a lot of work and housekeeping. I've seen a lot of code from people used to exception handling in other languages. Even with the added consistency and helpers, they tend NOT to handle all exceptions properly and tend to be pretty lazy about it.Inconsistency - well, yes. Again, C is pretty rough in that respect. I try to keep a consistent convention within my own code, but for external libraries, I do with what I'm given. I agree with the annoying inconsistency (especially the fact that in some libraries return codes are errors, and in others, that's just a "fail" or "OK" and you have to get the exact error calling something else, or using some kind of global like errno. Yuck!), but I do not agree with the fact having to read the manual is a problem in itself. I've seen too many people using functions they *thought* they knew, and it turned out they didn't really. If it's not my own code, I certainly read the f* manual (and hope it's not screwed up). Socket functions are an example. I always re-read the manual pages. They are a bit of a mess.

author: cv007
author_rank: Frequent Contributor
timestamp: August 05, 2019, 11:54:35 pm
content: I would suggest if you currently have no plan to organize code, just pick something simple and work your way to something better.Having a pair of h/c files for each peripheral and purpose seems to work well. Peripherals are already naturally organized in the datasheet and headers, so it also works well to use that as a separation point.The h file is simply to inform anyone else what they can use from the corresponding c file, and provide any info to make use of them. If no one needs anything in the c file, no need for an h file. The compiler compiles one c file at a time, so treat every c file from the compiler perspective as if its the only file you know about. That's where headers come into play- you tell the compiler there is 'something' 'somewhere else' (via an include), and the compiler will believe you (the linker eventually will need more than just the paperwork, though).From the original code Simon posted, a simple example would be to split out the tca0 things-Code://=====================================//tca0.h//=====================================#pragma once#include <stdint.h> //we need it here (uint16_t), so use it hereuint16_t tca0_time();void tca0_setup();//=====================================//tca0.c//=====================================#include "tca0.h" //only need in this case to make sure our version matches what we are telling others#include <avr/io.h> //<xc.h> //no one else needs this, so it goes here, not in tca0.h header#include <stdint.h> //we need it here, so use it here (that's why there are include guards)#include <avr/interrupt.h> //no one else needs this, so it goes here, not in tca0.h header#include <util/atomic.h> //no one else needs this, so it goes here, not in tca0.h headerstatic volatile uint16_t time; //private, we tell no oneISR(TCA0_OVF_vect){time++;TCA0.SINGLE.INTFLAGS = 0x01;}uint16_t tca0_time(){uint16_t ret;ATOMIC_BLOCK(ATOMIC_RESTORESTATE){ ret = time; }return ret;}void tca0_setup(){//setup tca0 (from setup.c code)}Now any other c file that needs the tca0 time, or needs to run tca0 setup, will just include tca0.h and they will have all the info they need. No one else needs to worry about potential atomic problems. If any changes needed to tca0, you already know which files to change- the c file with that name and the h file also if needed. With the 'public' tca0 vars/functions prefaced with tca0_, you will be informed where they are sourced every time you use them.There are plenty of holes in the above code/explanations, but its a start.Code:Yep. I actually do parameters checkingOne portion of 'parameter checking' can be done with enums-Code://=====================================//adc.h//=====================================#pragma once //actually, need old style inlcude guards with xc8-pic#include <stdint.h> //needed here (uint16_t), so use heretypedef enum {adc_AVSS = 0x3B,adc_TEMP,adc_DAC1,adc_FVR1,adc_FVR2} adc_ch_t;uint16_t adc_read(adc_ch_t);//=====================================//adc.c//=====================================#include "adc.h"#include <stdint.h>#include <xc.h>uint16_t adc_read(adc_ch_t ch){ADCON0bits.CHS = (uint8_t)ch;//other stuffreturn ADRES;}There is no way you can pass a channel that is incorrect, so no need to check if the channel is valid as the compiler already checked. Yes, you can cast to get around that, but by that point you know what you are doing and why and want no checking anyway.My 2 cents.

author: SiliconWizard
author_rank: Super Contributor
timestamp: August 06, 2019, 12:57:28 am
content: Quote from: cv007 on August 05, 2019, 11:54:35 pmWell obviously parameters checking goes well beyond the parameters that can have a limited set of values, so that would only deal with this specific case.Yet using enums (and using the corresponding enum types for the parameters/variables) for this kind of values is good practice IMO. Makes things a lot more readable.But unfortunately, you're sadly mistaken if you believe the compiler will catch anything much.Below a very simple example:Code:#include <stdint.h>typedef enum {adc_AVSS = 0x3B,adc_TEMP,adc_DAC1,adc_FVR1,adc_FVR2} adc_ch_t;uint16_t adc_read(adc_ch_t ch){return ch;}uint16_t Test1(void){uint16_t n;char yetAnother = 20;n = adc_read(adc_TEMP);// Enum valuen = adc_read(10);// Some int value not in adc_ch_tn = adc_read(yetAnother);// Some int value not in adc_ch_t from an intermediate variable of type char (why not?)yetAnother++;// Let's spice things upn = adc_read(yetAnother);// Still no luck...return n;}Compiled with:Code:gcc -Wall -pedantic -cWould you expect the second, third and fourth adc_read() call to yield a compiler warning because the value is not one of the enum values? Well, GCC doesn't give any warning, and -Wall -pedantic is pretty severe already. It kindly ignores the whole mishap... and no need to cast anything either...Contrary to languages such as ADA and VHDL (and maybe others), C doesn't check whether values assigned or compared to enum variables are part of the listed values. It's pretty dumb in that respect.

author: cv007
author_rank: Frequent Contributor
timestamp: August 06, 2019, 01:19:12 am
content: >There are plenty of holes in the above code/explanations, but its a start.I should have added there were holes in the code below also.You are right. The only help is the user forcing themselves to lookup/use the enum values.I mostly use c++, where this type of thing actually works as intended. There are quite a few nice things in c++, and assuming c acts the same way in some of these areas can be a problem when using both.

author: SiliconWizard
author_rank: Super Contributor
timestamp: August 07, 2019, 03:36:58 pm
content: Quote from: cv007 on August 06, 2019, 01:19:12 amOh, makes sense then. Yes C++ makes much fewer implicit casts than C, so an enum type and int are not compatible unless you explicitely cast.And you're right, mixing C and C++ rules is a very bad idea - it leads to all kinds of potential bugs, some much more serious than not statically checking some implicit cast... I've seen that quite often from developers mainly writing C++ and that had to write C for some embedded projects...All that said, what you suggest is good (in C++), but it doesn't replace explicit tests. All errors that could be checked at compile time (statically) should be. But it doesn't replace explicit testing at run-time.A whole range of bugs can come from parameters not having the right value at run-time under some conditions, conditions that can't be caught with static analysis. I even think this is what happens most often with bugs in safety-critical software. Teams use very good and expensive static analysis tools, and yet the bugs usually come from parameters getting unexpected values / or a combination of them not being properly checked.Parameters check can include operations on them. For instance, in some function you could be passing a set of parameters that all are in their respective expected range, but some operation on them would maybe also need to be within some specific range. Not always possible to do this in a purely static way. That's where added "contracts" become necessary. Again, some languages have native support for that, others not, but it's still easy to implement manually.Also note that any explicit test that can be fully asserted at compile time will usually be optimized out by a decent compiler, so usually only the ones that require to be executed at run-time will be in the resulting binary anyway. You thus get no time penalty when some test can be determined statically as always true or always false.