After reading a post, I embarked on another long rambling about #included header files (a topic that keeps popping up)
Reference Post: http://www.keil.com/forum/docs/thread14314.asp (And also a whole bunch of those #include/header file questions)
On that post, Per was on the correct path as far as inclusion (header) files.
Also, Andy is right when he explains about the function prototype 'extern' being used for completeness. Be complete.
I disagree with the questions on "slow computer", "max optimization", or the regularity of altering a single uber-header file simply because compile time on the computer. The compiler time should not a factor in building embedded code: GET A FASTER PC if you have a problem with that. (And if you like to compile after every few added lines, the you are simply hacking code and not really thinking about what you are doing during your code-monkey time).
The question of "small" or "large" program means nothing to me because even the 'small' programs will either have a single main.c file (module) or be broken into various .C modules--even for a small program. A large program will have many modules: if it doesn't then you have a serious problem with your code-monkey work anyway. (I've seen horror source code: a single .C file that contained EVERYTHING and had 10-15 conditional compile options---major retards worked on that piece of ___ ).
All .C source file shall have an associated .H file:
SPI.C shall also have a SPI.H file that SPI.C includes UART3.C shall also have a UART3.H file that UART3.C includes RoadKill.C shall also have a RoadKill.H file that RoadKill.C includes main.c shall also have a main.H file that main.c includes
If RoadKill.C needs access to SPI.C functionality, RoadKill.C shall include the SPI.H file for accessors and/or mutators or its SPI specific #defines.
If main.c needs RoadKill.C functionality and SPI functionality, then main.c shall also include both of those associated .H files (RoadKill.H and SPI.H).
All common .H files such as CPU reg definitions are part a the standard inclusion section. In that standard inclusion section, things that globally affect the system should go into that file be included in all modules. I have a header file called Logic.H which does some fairly simple things like the definition of TRUE and FALSE. Another it dTypes.H which has the typedfes for the data types ( #typedef unsigned char u8; etc. ).
Any global data should have its own "global_data.H" file (I use "gData.H" as my 'global data' header file (which is as empty as I can get it). I use main.C to ALLOCATE the data space, while all other modules REFERENCE (via 'extern') to the data-stores ("variables" for you non-engineer types).
Some people use alternate conditional compile pre-processor parameters. (I do not use them for code inclusion or exclusion)
#ifdef INCLUDE_DEBUG_CODE printf( "Code-Monkey Testing ID# %d) End-of-Conversion failed", debug_number ); debug_number++; // bump testing value #endif
ALL 'debug' code should be removed prior to qualification (which I'm sure you all do before you release it).
--Cpt. Vince Foster 2nd Cannon Place Fort Marcy Park, VA
<< more rambling to follow >>
<< continued post >>
--OR--
The commonly used shoe-horned *new* code for an alternate version of a product's software:
/*********** For new the "Murmur-Maker 3000" pacemaker model **********/ /* ** Compiler's Conditional Compiler settings are located in a separate .H ** file: ** e.g. ** CConfig.H */ #define NEW_PACE_MAKER_CODE 1 // Enable the new code /* END OF CConfig.H file */
/*--------------------------------------------------------------------------. ; New Code added with the new model of the pacemaker hardware ; '--------------------------------------------------------------------------*/ #ifdef NEW_PACE_MAKER_CODE // for the "Murmur-Maker 3000" pacemaker model /* ** Header file for MM2000.H ** */ ... // standard Murmer Maker 2000 model include file stuff /*--------------------------------------------------------------------------. ; If the new model is to be used, then include it ; '--------------------------------------------------------------------------*/ #ifdef NEW_PACE_MAKER_CODE // for the "Murmur-Maker 3000" pacemaker model #include "MM3000.H" // added new stuff for the new model #endif /* END OF MM2000.H file */
/* ** Header file for the MM3000.H specific include stuff.. */ /*--------------------------------------------------------------------------. ; #defines for MM3000 variant ; ;---------------------------------------------------------------------------; ;Values for the pacemaker's built-in AED-"Automated External Defibrillator" ; '--------------------------------------------------------------------------*/ #define AED_FIRST_DELAY 45 // seconds #define AED_SECOND_DELAY 25 // seconds #define AED_LOW_LEVEL 120 // from 120 to 200 joules for 'low level' AED zap #define AED_HIGH_LEVEL 360 // from 360 to 400 joules on 'high level' AED zap #define AED_HIGHEST_LEVEL 400 // from 360 to 400 joules on 'highest' AED zap #define AED_SMALL_DURATION 10 // milliseconds (aka "mS" -- don't complain about #define AED_MEDIUM_DURATION 15 // milliseconds the 'ms' versus 'mS' #define AED_LONGEST_DURATION 30 // milliseconds (in context, mS is correct) #define EMERGENCY_911 "911" // Phone Number for local 911 #define CORONERS_OFFICE "1.800.GUY.DEAD" // Phone Number for local coroner's office #define HUMAN_RESOURCES "1.800.MGR.EVIL" // Phone Number for company's HR manager #define ATTORNEY "1.800.BAD.GUYS" // Phone Number for an attorney #define EMERGENCY_911_MSG1 "Heart-Attack victim at %f Longitude, %f, Latitude: send ambulance" #define EMERGENCY_911_MSG2 "Heart-Attack victim at %f Longitude, %f, Latitude is better" #define LAWSUIT_MSG "Need huge settlement for non-responsive government due to %s" #define THE_REASON1 "Obambi's New Socialized Medicial Plan" #define THE_REASON2 "Obambi's Change" #define MAX_NAME_SIZE 50 // any name longer than that, and they shouldn't live anyway. /*--------------------------------------------------------------------------. ; Data-Store allocation for MM3000 variant ; '--------------------------------------------------------------------------*/ float GPS_Long; // location of victim float GPS_Lat; // location of victim u8 AED_Used; // { TRUE | FALSE } u32 Murmer_Maker_ID;// Victim's ID u8 who[ MAX_NAME_SIZE ];// Victim's name /*--------------------------------------------------------------------------. ; External prototype declarations ; ;---------------------------------------------------------------------------; ; These prototypes would actually be located in other .H include files ; ; but I put them here for the example. ; '--------------------------------------------------------------------------*/ extern void Built_In_AED_Shock( u8, u16, u16 ); // "Automated External Defibrillator" extern void AED_Wait( u16 ); // also from the AED.C module extern void Get_GPS( float *, float * ); // from GPS.C extern u16 Get_Pacemaker_Serial_Number( void ); // from new_features_module.C /* END OF MM3000.H file */
You can figure out how to implement the 'extern' version of the data allocation methods for REFERENCE purposes yourself.
/* ** Module: BIT.C ("Built-In-Test," or "Basic Internal Testing") ** [ used by the standard 'old' version of code with the new conditinoal ** code added to the 'old' version ] */ ErrorCode BIT( void ) { ... // normal module 'old' code prior to the conditional model code Code_Monkey = ALIVE; // patient w/ pacemaker is currently alive /*--------------------------------------------------------------------------. ; If the new model is to be used, then include the 'new' code... ; '--------------------------------------------------------------------------*/ #ifdef NEW_PACE_MAKER_CODE // for the *new* "Murmur-Maker 3000" pacemaker model AED_Used = FALSE; // pre-initialize to 'not used... yet' /*------------------------------------------------------------------. ; Check to make sure patient is doing okay: try to fix if needed ; '------------------------------------------------------------------*/ if( heart_rate < ALMOST_DEAD ) // NEEDS AN AED 'Zap' ?? { Built_In_AED_Shock( ENABLE, AED_LOW_LEVEL, AED_SMALL_DURATION ); // Set phaser to 'Stun' AED_Wait( AED_FIRST_DELAY ); // can't re-zap them for a specified number of time Get_GPS( &GPS_Long, &GPS_Lat ); // get GPS coords asap (AFTER AED attempt) Murmer_Maker_ID = Get_Pacemaker_Serial_Number( ); // retreive code-monkey's unique ID who = Get_Name( Murmer_Maker_ID ); // get the code-monkey's lab name call_made = Built_In_Cell_Call( EMERGENCY_911, EMERGENCY_911_MSG1, GPS_Long, GPS_Lat, who ); if( call_made != TRUE ) { (void) Built_In_Cell_Call( ATTORNEY, LAWSUIT_MSG, THE_REASON ); } AED_Used = TRUE; // enable further re-shocking if needed Code_Monkey = AMBULANCE; // return value that he at least needs an Ambulance } /*------------------------------------------------------------------. ; If the heart didn't come back to 'normal' shock him again. ; '------------------------------------------------------------------*/ if( (heart_rate < ALMOST_DEAD) && (AED_Used != FALSE) ) { Built_In_AED_Shock( ENABLE, AED_HIGH_LEVEL, AED_MEDIUM_DURATION );// Spend more of his // family joules AED_Used = TRUE; AED_Wait( AED_SECOND_DELAY ); } /*------------------------------------------------------------------. ; If the heart STILL didn't come back to 'normal' max-out the shock ; '------------------------------------------------------------------*/ if( (heart_rate < ALMOST_DEAD) && (AED_Used != FALSE) ) { Built_In_AED_Shock( ENABLE, AED_HIGH_LEVEL, AED_LONGEST_DURATION );// Go ahead and just // detonate his heart AED_Used = TRUE; AED_Wait( AED_THIRD_DELAY ); } /*------------------------------------------------------------------. ; He's dead. (sarcasm) Notify the his friends (/sarcasm). ; '------------------------------------------------------------------*/ if( (heart_rate < LIKELY_DEAD) && (AED_Used != FALSE) ) { AED_Used = TRUE; Code_Monkey = DEAD; // for the function's return value: he's dead. Get_GPS( &GPS_Long, &GPS_Lat ); // update in case call_made = Built_In_Cell_Call( CORONERS_OFFICE, "Dead Guy at %f Longitude, %f, Latitude", GPS_Long, GPS_Lat ); if( call_made != TRUE ) { (void) Built_In_Cell_Call( ATTORNEY, LAWSUIT_MSG, THE_REASON2 ); } Murmer_Maker_ID = Get_Pacemaker_Serial_Number( ); // retreive code-monkey's unique ID Code_Monkey_Cage_Number = Get_Cubical( Murmer_Maker_ID ); // calculate which code-monkey died call_made = Built_In_Cell_Call( HUMAN_RESOURCES, "Need new code-monkey in cube %d", Code_Monkey_Cage_Number ); if( call_made != TRUE )// *if* HR didn't answer the phone, let the local peons know about it... { Buzzer( ENABLED ); // Enable that "Flat Line" noise to let his cube-mates } // know that he needs to be replaced. } /*------------------------------------------------------------------. ; Victim is back on-line, and can do more code-monkey work now ; '------------------------------------------------------------------*/ if( (heart_rate > LIVABLE) && (AED_USED == TRUE) ) { Built_In_Cell_Call( EMERGENCY_911, EMERGENCY_911_MSG2, GPS_Long, GPS_Lat ); AED_Used = FALSE; // reset flag } #else // standard "The Murmur-Maker 2000" pacemaker /*------------------------------------------------------------------. ; Standard Model "2000" situation handling method... ; '------------------------------------------------------------------*/ if( heart_rate < ALMOST_DEAD ) { Enter_PowerDown_Mode( ); // He is going to die anyway, so save the } // battery for the next code-monkey. #endif ... // normal module 'old' code ensues... /*------------------------------------------------------------------. ; Return the patient's status { ALIVE | AMBULANCE | DEAD } ; '------------------------------------------------------------------*/ return( Code_Monkey ); } /* End of BIT.C module */
<< even more rambling to follow >>
That would be a candidate for separate header files just to enable / disable these conditional compile situations. I never use them "conditional compilation" for debugging, or 'alternate version' stuff.
Alternate Version software should be a totally separate set of software in its own directory, etc.
But if I did, in the example above I would have separated the #define/data allocation/prototype statements in the module to its corrisponding header file. I would have used the #ifdef NEW_PACE_MAKER_CODE surounding the #define statements in the header file, and in the .C source file.
In my "Rules for Code Monkeys" book (aka my "Rule-Book Against [Coding] Radicals" ) there is a method of how to properly include files, how they get segrataed, and the structure used within each source file and each header file.
These methods are the best methods ever seen on this planet, and I do not plan on divulging this valuable information. But I simply love telling you about how much better I am than everybody else is... 'ego troubles' you know.
I need a bumper sticker that says "I'm 99.9% sure I'm better than you" (yes, if you are clued in, you'll get that point).
Back to the issue of #include files:
If you have a reason to separate your source code into modules, and you always do, then those same reasons to separate the source code modules should be applied to the header files.
Full Compiles vs Partial Compile:
Try to do a full compile every few times after you have done a partial recompile and link. You don't want to be surprised when an error crops up due to the tool's failure to capture some mis-match that occured while doing the partial compile and link process. It caught me once, and since then I make it a policy to not trust that the software is working properly after a partial compile and link. (When you modify one module, and the compiler compiles only that file, then links it in with the previously compiled modules to form the 'executable' is known as a partial compile process. Even though it might take extra 'minutes' to recompile the whole project, it is worth doing it to prevent hours of trying to figure out that you 'time savings' partial compile had un-intended consequences).
[END OF RAMBLING]
Just want to extend on: "Try to do a full compile every few times after you have done a partial recompile and link." If you select to use something other than the Keil compier - try to get a build computer. Let the build computer extract a full set of source code from a tag in the source code repository into a clean directory and build.
Use for all serious testing and for all releases to leave the building.
Make sure that the full build machine (OS, compiler, ...) are backed up. Preferably select to run the build environment in a virtual machine such as VMWare so you don't have to worry if you can't replace the old hardware after a failure.
If using the Keil tools - make sure you have normal makefiles or dumb "compile-all" scripts so you can use your own machine and just run a script to check out and build the code. At least you will know that changes to the IDE project file format will not suddenly stop you from maintaining older projects.
And avoid __DATE__ or __TIME__ preprocessor constants in the code since it is always nice to be able to make a perfect reproduction of the binary. An automatic tool can always be used to post-process the binary and append a timestamp at the same time as CRC-32 or MD5 or similar to let the BIST decide if the flash contents is to be trusted.
Oops - way outside the debate about header files...
Per,
I totally agree.
I have ALL of my source files in a single project directory, and avoid at all costs the inclusion of intrinsic compiler tool files:
I avoid this: #include <stdio.h> and will copy it over to the project folder and do this: #include "stdio.h" // I don't recall ever actually using stdio.h, // but this is just an example
I also use the Keil's ability to generate the make batch files and 'modify' them to meet my source-code standards.
This way I can have ALL files needed for a complete build in one directory set. Create a CD from it. Bring it over to the Qualified Station and before performing the formal ATP (Acceptance Test Procedure) I build the CSCI ("Computer Software Configuration Item"---the executable file) from that station using the CD's source files.
Even when I copy an intrinsic <library.h> file, I will usually re-write the file to 'my standards' so I can understand what is going on, and then trust the code. I just (about 15 minutes ago), saw that a vendor supplied library utility I was going to use that did this:
temp = (new_reg_value) & (MASKING_VALUE) // where temp was a u32 // new_reg_value is a u32 // masking_value is a u16 value // so the translation is this: (u32)temp = (u32)(new_reg_value) & ((u16)MASKING_VALUE); // the MASKING_VALUE is 0xFFFF (u32)temp = (u32)(new_reg_value) & (u16)0xFFFF; // This assumes that the compiler tool will mask // off the leading upper 16-bits and act like this: (u32)temp = (u32)(new_reg_value) & (u32)0x0000FFFF;
So now I don't trust their library, and will most likely re-write the functions I need from it.
But I digress as usual.
"So now I don't trust their library, and will most likely re-write the functions I need from it."
No. What you are saying is that you do not trust the compiler to do what it must do to follow the standard, i.e. make sure that both sides of an operator are the same size, and to know what to do with the smaller operand if the larger operand is signed or unsigned.
In this case, I am always allowed to do:
my_128_bit result = my_64_bit_value & 2;
The compiler is required to make sure that the "2" is correctly expanded to perform a masking of the 64-bit operand.
And the compiler is required to make sure to expand the 64-bit intermediate result into a 128-bit value (while taking care of sign extension or zero-fill) before assign to the 128-bit target.
If you distrust the compiler to manage this, then your only option is to go full assembler.
I would be more worried if a 16-bit variable got anded by a 32-bit constant bitmask. I would always wonder if that 16-bit variable would possibly fail to store some important bits used somewhere else in the program.
But you are correct in form: Clearly writing the code with same size all the way will produce the same result, but will at the same time help indicating that the developer wasn't just lucky that the language standard is well thought-out, but decided the size of the variables and bitmasks based on real requirements.
Nothing as bad as finding code originally written with a 16-bit bitmap and then later modified to fit more flags, but where some parts of the code may have been forgotten resulting in unwanted pruning of new flags when some parts of the code gets called.
For some reason the language designers chose to give us named data types - maybe they didn't want us to duplicate data type declarations all over the source and later forgot to change all declarations when a change is required of the data type. If nothing else, we can rename our modified datatype and get all affected parts of the code to produce compilation errors for us to investigate.
Right again. I don't trust compilers either. So that is why I try to make the code as complete as possible.
In your example of
One would assume that the compiler should handle the situation, but i would have written it as:
my_128_bit result = (u128)( ((u128)my_64_bit_value) & ((u128)2) );
I can over-use type-casting, but it makes me feel better.
My needs from the library are limited (ARM w/ STMicro's library set), and I have been re-writing some of the supplied functions as I need them so I have 'total' control of the software.
The library is very useful, and I use their functions until the code works as close as I need it to, then I go back and 'optimize' the library functions into the modules that use them. Then I'll have exactly what is needed and not depend upon an outside source's work.
This way, when the software is finished, I won't need the vendor's library set, and will function using more direct 'standardized' Cortex M-3 mapped SFRs accessing, instead of the generic methods the vender uses... which may have side-effects; I worry about that.
My biggest concern has to do with library updates from both Keil and STMicro. But once I get my code-monkey work running, it should be free from any updates in their source code. The concern is how the Keil compiler itself may then deal with the updated libraries and if the core compiler updates are still compatible with my rendition of the re-worked library source.