I am writing C code with multiple modules like LCD display, flash memory, and GSM module etc. The project consists of thousands of lines of code, in different files.

The behavior of the system can be changed with run-time parameters (eg LCD brightness) using a buttons. And during compile time by redefining #define directives and const variables.

Currently all of these variables and constants lie with the rest of the modules in their respective files. I am finding is very hard to maintain the settings without losing modularity.

My question is two part:

  1. How do I make the compile time parameters more accessible? Moving all the variables to a separate file would certainly make life easier by making all the settings available in one place. Also some settings go in groups, so it makes more sense to change one #define in one file rather than fishing for many #defines in multiple files. But it does not seem right to destroy the modularity this way.
  2. I need to write code that will save all the runtime parameters to flash memory, and retrieve them on reboot. Ideally this should be done in getter and setter functions. But the problem is that all the settings and configurations are stored in a sector in flash memory. A parameter can't just be rewritten without erasing the flash memory. So LCD module can't change its brightness without being aware of all the other parameters. It makes more sense to keep all the settings in a struct, but the cost is moving the variables away from the modules.

What is the best way to tackle this problem without compromising on the readability and modularity of code.

  • How are you redefining your #defines at compile time? – Kala J Aug 21 '15 at 12:59
  • Your life may be improved with dotc – TehShrike Sep 13 '15 at 13:53

Placing all of the compile time switches into 1 central file makes finding and changing them easy. That said, compile time switches can be confusing, particularly if there is a significant quantity of them.

I worked on an embedded project where there were a few different boards to control different hardware, but had the same MCU, communication protocols, flash storage, and other similar properties. Rather than use #defines to switch between building the different versions of firmware, I put all of the core modules into a "Core" directory and the board-specific modules into their own directories. I setup the build process to build all versions of the firmware at once. This helps prevents a breaking change when working on the "Core" component.

Part of the same project involved saving various parameters to flash. Each module that needed to write to flash had its own flash parameters struct, along with a unique entry in a moduleID enum. When a module wanted to save its data, it would pass a pointer to the flash parameter struct, the size of the struct (in bytes), and its moduleID to the flash storage module.

The flash storage module just looks at the passed struct pointer as a chunk of memory. It would copy the data into flash, and add a small header that contained the moduleID and the size of memory. When a module wanted its data from flash, it would pass its moduleID to the flash storage module, and then the flash storage module would scan the flash block for the moduleID, load the data into RAM, and then return it to the calling param. This way, the flash storage didn't need to know about who called it. All it cared about was the moduleID passed to it and the size of the data it was handing.

| improve this answer | |

(I suggest to cross-compile on a Linux running on your laptop; it has lots of good tools helpful for cross-development)

The compile time parameters are probably best to be put in some header file with #define, but that header file could be generated (at build time) by some configuration script. (look perhaps into GNU autoconf for inspiration, but is is often enough to generate such configuration header by your own shell scripts).

The settings could indeed stay in a flash memory, and concretely you would access them as a single struct (containing all settable parameters). I'll probably suggest to have a setting facility which loads that  struct from flash to RAM, modify the data in RAM, and write out it in flash at end (BTW, most BIOSes on laptop or desktop PCs are doing that).

You might even use some metaprogramming techniques. You could generate (e.g. using some script in Guile or Python ...) at build time some C code for that struct, the accessing function, the help messages, etc...

Notice that recent versions GNU make are very configurable (even extensible with Guile), so your build procedure (i.e. your Makefile) can do some quite fancy things;in particular generate some of your C code (in generated header *.h files or generated *.c files)

| improve this answer | |

First, reconsider how you are defining module boundaries. "Compile-time settings" is a perfectly reasonable module. If it feels cleaner to split a responsibility off, it should be split off. As Basile pointed out, GNU autoconf is extremely common for this purpose. It generates one header file you can include elsewhere as needed. Just take care to make your #ifdef blocks as large as possible. Try not to pepper them everywhere, and try to use alternatives if possible.

Second, you need to define your own abstractions. For some reason, C has a tendency to create a blind spot to that in programmers, especially programmers who are accustomed to high-level programs in languages with massive libraries.

That means if you wish you had functions like write_setting_to_flash(LCD_BRIGHTNESS, brightness) and read_setting_from_flash(LCD_BRIGHTNESS) then write them. Choose the interface you wish you had and make it happen. Settings persistence is a separate responsibility from a driver. They should be handled separately.

Essentially, what you are seeing as one module (LCD) is really 3 or 4 (driver, compile-time settings, flash persistence). If you start looking at it that way, the design should fall into place much more cleanly.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.