Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Suggestions about managing hardware-dependent configurations in C using separate files? [closed]

I'm currently working on an embedded systems project that utilizes the same C99 code base across three hardware models. The primary differences between these models are encapsulated in constant arrays that store sensor mappings. Below is a simplified example illustrating the configuration for each hardware model:

// Configuration example
#if defined(MODEL_A)
    // Configuration for Model A
    #define PRODUCT_CODE 1
    #define NUMBER_OF_SENSORS 5
    const int temperature_sensor_map[NUMBER_OF_SENSORS] = { 0, 1, 2, 3, 4 };
#elif defined(MODEL_B)
    // Configuration for Model B
    #define PRODUCT_CODE 2
    #define NUMBER_OF_SENSORS 3
    const int temperature_sensor_map[NUMBER_OF_SENSORS] = { 0, 2, 4 };
#define NUMBER_OF_SENSORS 3
#elif defined(MODEL_C)
    // Configuration for Model C
    // ...
#else
    #error Define MODEL_A, MODEL_B, or MODEL_C
#endif

To improve the management of these configurations and facilitate tracking changes in Git, I'm considering breaking the configuration into multiple files, each tailored to a specific hardware model. However, this approach would involve creating seven files, which seems overly complex:

- config.h (Main configuration header)
- config_model_a.h and config_model_a.c (Configuration for Model A)
- config_model_b.h and config_model_b.c (Configuration for Model B)
- config_model_c.h and config_model_c.c (Configuration for Model C)

Below is an example of the code.

Note: the use of global variables, as shown below, is not a concern, because the custom debugger used in this project has certain limitations, being able to inspect global variables only.

// File config.h
#ifndef CONFIG_H
#define CONFIG_H

#if defined(MODEL_A)
    #include "config_model_a.h"
#elif defined(MODEL_B)
    #include "config_model_b.h"
#elif defined(MODEL_C)
    #include "config_model_c.h"
#else
    #error Define MODEL_A, MODEL_B, or MODEL_C
#endif

extern const int temperature_sensor_map[];

#endif // End of include guard
// File config_model_a.h

#define PRODUCT_CODE 1
#define NUMBER_OF_SENSORS 5
// File config_model_a.c

#if defined(MODEL_A)
    const int temperature_sensor_map[NUMBER_OF_SENSORS] = { 0, 1, 2, 3, 4 };
#endif
// File config_model_b.h

#define PRODUCT_CODE 2
#define NUMBER_OF_SENSORS 3
// File config_model_b.c

#if defined(MODEL_B)
    const int temperature_sensor_map[NUMBER_OF_SENSORS] = { 0, 2, 4 };
#endif

Files for Model C follow a similar pattern

However, I'm exploring alternative approaches that don't rely on the build system and maintain clean, portable code. Specifically, I'm interested in solutions that eliminate the need for multiple include files or .c files.

Do you have suggestions that don't depend on the build system?

(I don't want to rely on the build system because we currently use Eclipse CDT but plan to transition to CMake in the future and I want the solution to work in both systems)

like image 792
Akira Cleber Nakandakare Avatar asked Nov 19 '25 15:11

Akira Cleber Nakandakare


1 Answers

If you do not want to rely on the buid system, one solution is to do the configuration at runtime. For this, your software needs to be able to recognize the hardware model (e.g. using a different voltage divider for each model).

The idea is to create an interface, which is used by software components that need to read the sensors. You then create a pair of .h/.c config files, in which you provide functions to get the right sensor.

// sensor_config.h

// Enum with models
typedef enum {
    MODEL_A,
    MODEL_B,
    // ...
} model_t;

// Your interface
typedef struct {
    int (*get_product_code)(void);
    int (*get_number_of_sensors)(void);
    int (*get_sensor_map_at)(int);
} sensor_config_t ;

// Getter
sensor_config_t* get_sensor_config(model_t model);

// other models...

And the .c file

// sensor_config.c

// Model A

#define N_SENSORS_A 5

static int get_product_code_model_a(void);
static int get_number_of_sensors_model_a(void);
static int get_sensor_at_model_a(int sensor_id);

static sensor_config_t sensor_a 
{
    &get_product_code_model_a,
    &get_number_of_sensors_model_a,
    &get_sensor_at_model_a
};

static int get_product_code_model_a(void)
{
    return 1;
}

static int get_number_of_sensors_model_a(void)
{
    return N_SENSORS_A;
}

static int get_sensor_at_model_a(int sensor_id)
{
    static const int mapping[N_SENSORS_A] = {0, 1, 2, 3, 4};
    
    // TODO: check index...

    return mapping[sensor_id];
}

// Model B

#define N_SENSORS_B 3

static int get_product_code_model_b(void);
static int get_number_of_sensors_model_b(void);
static int get_sensor_at_model_b(int sensor_id);

static sensor_t sensor_b 
{
    &get_product_code_model_b,
    &get_number_of_sensors_model_b,
    &get_sensor_at_model_b
};

static int get_product_code_model_b(void)
{
    return 2;
}

static int get_number_of_sensors_model_b(void)
{
    return N_SENSORS_B;
}

static int get_sensor_at_model_b(int sensor_id)
{
    static const int mapping[N_SENSORS_B] = {0, 2, 4};
    
    // TODO: check index...

    return mapping[sensor_id];
}

// other models...

// Getter    
sensor_config_t* get_sensor_config(model_t model)
{
    if (model == MODEL_A)
    {
         return &sensor_a;
    }
    else if (model == MODEL_B)
    {
        return &sensor_b;
    }
    else 
    { 
        // More models...
    } 
    return NULL;
}
        

Before your sensors get configured/read out, the software reads the hardware model and gets the right sensor.

// main.c

#include "sensor_config.h"

// Your sensor object
sensor_config_t* my_sensor_config = NULL;

void main(void){
 
    // Assign the right sensor config depending on the model
    model_t hw_model = read_hw_model(); // Read voltage from divider and deduct model    
    my_sensor_config = get_sensor_config(hw_model);
    // TODO : check that my_sensor_config is not NULL...    
        
    while(1)
    {
         // Do something with the sensor config
         int product_code = my_sensor_config->get_product_code();
         int n_sensors  = my_sensor_config->get_number_of_sensors();
         int first_sensor = my_sensor_config->get_sensor_at(0);
         int second_sensor = my_sensor_config->get_sensor_at(1);
         // ...
     }
}

Note : This adds a lot of overhead and potential errors. If you are limited in terms of memory and computing power, you might want to stick with a solution using the build system. The downside is that you would have as many binaries as models, whereas you only have one binary with the solution with runtime configuration. There is no silver bullet, you will need to make trade-offs.

like image 89
emiled Avatar answered Nov 21 '25 05:11

emiled



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!