I would like to do conditional compiling and include different C files based on the hardware being used. For example:
#ifdef HARDWARE_MODULE_1
#include "hardware_module_1.c"
#endif
#ifdef HARDWARE_MODULE_2
#include "hardware_module_2.c"
...
Everything works fine this way only debugging becomes a problem. Breakpoints don't work correctly when placed in the C file that's included that way. The program ends up breaking somewhere else random in the code (different C file altogether) When I manually add file to the project everything seems to be working fine. I'm using uVision V4.60.6.10
I think you would find it easier to set up multiple targets and including one of those two files in each.
Multiple targets is most probably the best route.
A different route is to instead have the C files look like:
hardware_module_1.h:
#ifdef HARDWARE_MODULE_1 void hardware_module1_stuff(void); #endif
hardware_module_1.c:
#include "config.h" #ifdef HARDWARE_MODULE_1 #include "harware_module_1.h" void hardware_module1_stuff(void) { ... } #endif // HARDWARE_MODULE_1
main.c:
#include #config.h" #ifdef HARDWARE_MODULE_1 #include "hardware_module_1.h" #endif #ifdef HARDWARE_MODULE_2 #include "hardware_module_2.h" #endif ... #ifdef HARDWARE_MODULE_1 hardware_module1_stuff(); #endif #ifdef HARDWARE_MODULE_2 hardware_module2_stuff(); #endif
I think you would find it easier to set up multiple targets and including one of those two files in each
I never really played with multiple targets. If I add a new file to a target it shows in other targets as well. Am I missing something?
Still not clear though why would breakpoints not work in the approach described above
You can specify that a specific file should not be included in the build for a specific target. Then you'll see a different icon for the file when you look at the source tree for that target.
Including the source file into the build is done the same way that you specify a different set of compilation options for a specific source file.
Still not clear though why would breakpoints not work in the approach described above breakpoints in includes have never worked.
BUT #ifdef mode1 module 1 stuff #elseif mod2 module 2 stuff .... will work
that can also be done this way file 1 #ifdef mod1
file2 #ifdet mod2
Presumably, because the name of the #included file is lost - the compiler only "sees" the name of the "outer" file which did the #include ?
As the others have said, the way to do this is by having different targets.
Or just have the entire content of the file within a #if ...
All right. If I go the route of multiple targets how do I handle includes from those hardware specific header files. For example, each hardware file defines SYSCLK value which is used in other header files. However, now I can't include the header file since I don't know which file is being used
Note that you can have different project settings for the different targets.
random_source_file.c:
#include "config.h" do_stuff ...
config.h:
#if defined(HARDWARE_1) #include "config_hw_1.h" #elif defined(HARDWARE_2) #include "config_hw_2.h" #else #error "Undefined hardware." #endif ...
hw_init(); // will call the hw_init() for the target-specific hardware. ... for (;;) { ... hw_process(); ... }
So you have to define HARDWARE_x symbol in the options for each target. I don't really see any advantages to this approach then. It already works like that. I define the type of hardware I use in the options. Based on that all that files get included automatically. I don't really have any other target specific settings which would have to be changed besides that. The only problem is that you can't really debug those files which seems to be a uVision bug.
"It already works like that" Hrm. I would say your current solution works rather badly. And that's a big reason why you came here and started this thread.
The difference with targets is that you can do:
hw_init();
and the hw_init() from the specific target_xx.c will be called. So you get very clean code with few #ifdef section.
If you do not use targets, then you either need to do:
#if defined(HW_1) hw1_init(); #elif defined(HW_2) hw2_init(); #elif ... #endif
Or you need to either:
#include "config.h" #if defined(HW_1) #include "hardware_1.h" #elif defined(HW_2) #include "hardware_2.h" ...
or:
#include "config.h" #include "hardware_1.h" #include "hardware_2.h" #include "hardware_3.h" ...
and then in each hardware include file do:
// hardware_x.h #if defined(HW_x) && !defined(HW_x_H) #define HW_x_H ... #endif // defined(HW_x) && defined(HW_x_H)
and in every hardware_x.c do:
#include "config.h" #if defined(HW_x) #include "hw_x.h" ... #endif defined(HW_x)
In short - you get much fewer #if conditions in your source code if you are using targets, since the only #if you need is to decide which hw_x.h to include. Possibly, you might need:
#if HW_HAVE_FEATURE_X hw_init_feature_x(); #endif #if HW_HAVE_FEATURE_Y hw_init_feature_y(); #endif
if there are significant differences in capabilities between different hardwares.
Another advantage with targets, is that you can request Keil to build all targets. So a single build command can give you: firmware_hw_1.axf firmware_hw_2.axf firmware_hw_3.axf
Then all you need to do is copy the resulting output files into "v3.2.1/".
And you can manage that without changing any source files. So your source code management tool will not complain that needs to be checked in.
And if you then use a build machine, then it's common practice to only allow builds from the source code repository. Which mean you aren't allowed to modify any file to change the define from HW_1 to HW_2.
What you should also notice, is that C header files are not intended for the actual implementation of the code. The main exception is when you have small functions that you need to use in multiple source files and want them inlined. But that requires that the C compiler supports the concept of inline functions, allowing you to write:
__inline char intrusion_detected(void) { return (FIO1PIN & (1u << P1_INTRUSION)) != 0; }
"The only problem is that you can't really debug those files which seems to be a uVision bug."
You should probably think twice about the use of the word bug. I would say, it's a question of you having made an incorrect assumption after having designing your code in a way that is quite far from normal best practices.
Don't assume that a debugger will allow you to place breakpoints in a header file, since C header files aren't intended to store the implementation of functions. The debug information is likely to point symbols into a line number for an object file. And the object file is created from a *.c file. So don't expect that a debugger will be able to find magical translation information that will tell that the source line number in the object file isn't pointing to a line in the corresponding *.c file, but is actually a line number in a *.h file. And where should then the debug information store the name of this *.h file, so the debugger knows to open the correct header file?
Incorrect assumptions are really a very large source of errors in this world.
"The difference with targets is that you can do:
and the hw_init() from the specific target_xx.c will be called. So you get very clean code with few #ifdef section"
Like I said before it is EXACTLY how my code works. The only difference is that you add your target_xx.c file directly to your project and I include it in preprocessor:
#if defined(HARDWARE_1) #include "config_hw_1.c" #elif defined(HARDWARE_2) #include "config_hw_2.c" #else #error "Undefined hardware." #endif
random_source_file.h:
#if defined(HARDWARE_1) #include "config_hw_1.h" #elif defined(HARDWARE_2) #include "config_hw_2.h" #else #error "Undefined hardware." #endif
I don't see an overabundance of #if statements here and the code is pretty straightforward.
As far as a bug goes - I never said that I included code in the header files! Where are you getting it from? In the example above let's say HARDWARE_1 is defined. I compile code and put a valid breakepoint in the code in config_hw_1.c. Don't see anything "quite far from normal best practices" so far. I run the the program and it does break however not in the place of the breakpoint and not even at the same file. It places the yellow marker someplace else and not even on the code line but at some comments.
I thought about it twice and I'm calling it a bug.
I've thought about what you've written and responded with more than twice now. Better I don't say what conclusion I've come up with!
"As far as a bug goes - I never said that I included code in the header files! "
The big issue here is the following sequence, where you pretend that "hardware_module_1.c" is a header file and decides to #include it.
#ifdef HARDWARE_MODULE_1 #include "hardware_module_1.c" #endif
It doesn't matter if you include a *.c file from a *.h file or from a *.c file. The thing is that you should _not_ include a *.c file. If you want breakpoints, you should keep the code in the c file, and have the compiler directly process the c file.
The reason the C language got the concept of header files, is that the *.c (header files) are intended to be #include'd. And the *.c files are intended to be sent to the compiler to produce object files that can then be linked. So each object file is the result of one (1) *.c file.
You made the assumption that you should get a good result by including function implementations. Your assumption was wrong. And to my knowledge, Keil hasn't promised that they will have explicit support for breakpoints in code that has been #include'd.
"I don't see an overabundance of #if statements here and the code is pretty straightforward."
What you fail to notice here, is that while there exists two different alternatives that gives the same number of #ifdef needed, one alternative only includes function prototypes and one includes the actual implementation. So one alternative does what #include is intended to be used for, while the other alternative does not. One alternative works, and one alternative breaks your debugging.
So once more - #include is intended for header files. *.c are not header files. Whenever you decides to #include a *.c file - or place the actual implementation of functions in a *.h file, you'll have to accept the problems that follows. It is possible to drive a car to/from work in the reverse gear. But you can't call it a bug if the car is hard to control, gives you a pained neck, or if the police stops you because the break lights are on the wrong side of the vehicle in relation to your travel direction. The way you use #include is contrary to best practices - and you get to suffer the problems that follows from selecting a bad solution.
It's up to you to decide if you want to continue to complain about Keil, or if you decide to change your design and code as the language creators intended the language to be used. But no one will care about your complaints as long as you continue to do your transports with the car in reverse gear.
"I thought about it twice and I'm calling it a bug."
We can agree on that. Just that it isn't a bug in the Keil tools, but in code far closer to you.
"And to my knowledge, Keil hasn't promised that they will have explicit support for breakpoints in code that has been #include'd"
Neither does it make the opposite statement. Besides, Including C file into another C file is legal from the language standpoint.
" And the *.c files are intended to be sent to the compiler to produce object files that can then be linked. So each object file is the result of one (1) *.c file"
There is nothing wrong with producing a single compilation unit from several source files.
"You made the assumption that you should get a good result by including function implementations. Your assumption was wrong."
Why? For all your pontification and colorful albeit misapplied metaphors you failed to give a factual reason for what exactly goes wrong when you include .c file.
There is also another advantage in my approach. Other tools (or IDE's) will compile the project correctly.