Best practices for reusable embedded C?
I'm w开发者_开发问答riting C code for an embedded system (dsPIC33 platform), and I'm considering building up a reusable code library to use among multiple projects.
What are the best practices for tying the library to each project?
Obviously the library will have some hardware-specific (and thus project-specific) dependencies, so it's reasonable to assume that it will be compiled with each project (instead of linked in binary form).
What I've come up with so far is to keep the library centrally located, but require a project-specific libraryConfig.h that includes function definitions, macros, etc. This requires that the library include the header in its own code, which means that the project source directory will need to be in the include path (not just the library source directory). That kind of messes up the distinction between #include ""
and #include <>
, doesn't it?
Is this how it's done normally?
A very good question and the answer isn't simple. Several things to consider. Here are a few opinions from my experience so far.
Common Code vs Project-Local Copy
One important decision is whether to use "common" library code that is updated automatically from a central location (your company's "reuse library"), or whether to keep a project-local copy.
This is discussed in detail in this SO question.
The benefit of a central library is that work done once can benefit many projects. The difficulty with a project-local copy is that any bug fixes and improvements aren't contributed back to the central library, and any bug fixes in the central library may not be brought into your project.
But a potential difficulty with using a central library is if people on their particular modify it in an uncontrolled way to suit their project, and it unintentionally breaks other projects. I've seen that personally, in "common" code that became full of #ifdefs and regularly broke other projects.
To get good value out of common code aka central reuse library:
The library:
- must have well-defined requirements, API, and unit tests
- must avoid project-specific code; it should be general-purpose
- should have a mechanism for cleanly setting project-specific settings (this can be seen as part of the API, effectively)
- must have a formal release process, with version numbers and fixes, issues must be tracked.
Individual projects:
- shouldn't automatically and blindly get "the latest", but should be able to get a particular "release" with a specified version number. Then projects should have control over if/when they update to a newer version. The project should be able to clearly track, "we are using version 1.2.3 of library xyz".
- should avoid "forking" the library code if at all possible. E.g. avoid adding project-specific "features" to the library code.
- should track any local modifications to the library code
- should regard bugs as library bugs, to be fixed in the central library if at all possible. The company should have processes to fix them in the central library, test the library with its own unit test suite (probably improving the unit tests to catch the bug in future). Then release a new version of the central library as needed, and deploy to other projects if/when those projects see fit.
If a company doesn't have such a process in place, then a project should just make a local copy of a piece of code (say, copied from a previous project) and then take full project-local responsibility from then on. You're still getting some benefit from reuse in that situation, because you're not rewriting it from scratch.
Project-Specific Configuration
If the code needs project-specific configuration, ideally that should be kept to as small a part of the code as possible--not scattered through a bunch of source files. Ideally, a single header file. But quite possibly a .C file as well (say, if you need to define some look-up tables). The library should provide a template, with the options well-commented.
For a good example of how this can be done, see the µC/OS-II RTOS (book) by Jean Labrosse, from Micrium.
It doesn't mess up the distinction, which is almost entirely platform-defined anyway. The only defined behaviour is that if an include using ""
fails to find the file, then it searches again as if you'd said <>
.
I think you're doing the right thing. The normal way to handle a platform-specific header, in my experience, is that you give it a name you're as confident as possible will never collide with anything else, and #include it with ""
. Then you tell the platform porter to do whatever compiler-specific thing is necessary to ensure that it is found. Normally that just means specifying some compiler argument like -I, for wherever he wants to keep the file. So yes, one of his project's directories. But if all else fails he can always copy his file into some place where his compiler will look. He could even copy it into his local copy of your library source, if his compiler is being unreasonably difficult about the whole thing.
Another way is to have a file in the library, selectplatform.h, looking like this:
// obviously WIN32 isn't an embedded platform, and GCC is too broad
// to be supported by a single header file. Replace with whatever platforms
// it is you do support out of the box.
#if _WIN32
#include "platforms/msvc32.h"
#elif __GNUC__
#include "platforms/gcc.h"
#else
#error "You must add a new clause to selectplatform.h for your platform"
#endif
This avoids the need for compiler configuration, but has the downside that every new platform port has to modify the file. If you're the only one doing any porting that's definitely not a problem. Otherwise that one file is forked by third parties. Then maybe they add a new file to platforms/
in your library, or maybe they put their file elsewhere. So with third parties, it's only probably not a problem. They can contribute their changes (possibly including their platform's header) back upstream if they and you both want.
No.
Normally you define a path to your lib's includes directory using a command flag in your compiler (usually, it is -I flag).
Say, if you are using GCC compiler, and your library's header files are in the
/usr/local/include/mylibheaders
then you must call compiler with following option:
-I/usr/local/include/mylibheader/mycurrentplatform
where mycurrentplatform directory is different for each project and contains project-specific libraryConfig.h
Thus, you can use #include<libraryConfig.h>
in every project.
This is really more of a configuration management question than a C question. In my experience, using a good version control program is most helpful. Find one that allows you to define a "project" by pulling source code from several different locations. Realize that your version control program's definition of a "project" will then become an essential element in building the project.
It is also important to be able to make changes to your library code for a project branch and check them into your version control system several times without having to check in the changes to the main library location until the changes are proven since they may affect many different projects.
Your library modules may also end up with a file that defines library options for each specific project. A practice I have adopted is naming these interface files _PAL.h where the _PAL indicates a Project Abstraction Layer file.
精彩评论