Skip to content

C Linker and Fresh Eyes

It’s often valuable to set a project down for a while. After setting down the Flight Logger project for a few months, I came back to it with fresh eyes and quickly realized that I could simplify how some hardware specific code is called from hardware agnostic code.

This issue and solution may be an obvious non-issue to more experienced C developers. I have recently come back to using C for a side project after a long time of using higher level, object-oriented, languages. This post is me thinking out loud about what I learned.

The Problem

Flight Logger uses hardware agnostic device libraries to interface with MPU-6050 and BMP390 sensors. However, the code to read/write from the I2C interface is hardware specific. So how can the device libraries read/write I2C data without knowing hardware specifics?

Initial Issue: How to reference platform specific library from platform agnostic code

The Initial Solution

Originally, I solved the problem by creating a struct of function pointers which point to the hardware specific I2C functions. The struct of pointers gives the device library a handle to perform I2C operations without knowing hardware specifics.

Initial Solution: Pass struct of platform specific function pointers to platform agnostic code
I2cFunctions Struct Definition
    typedef struct I2cFunctions {
        uint8_t (*f_I2cSendStart)(uint8_t client_address, uint8_t is_reading);
        void (*f_I2cWrite)(uint8_t data);
        uint8_t (*f_I2cRead)(uint8_t ack);
        void (*f_I2cSendStop)();
        
        void (*f_I2cInitialize)();
        void (*f_I2cWriteByte)(uint8_t addr, uint8_t reg, uint8_t value);
        void (*f_I2cWriteBytes)(uint8_t addr, uint8_t reg, uint8_t *value, uint8_t length);
        uint8_t (*f_I2cReadByte)(uint8_t addr, uint8_t reg);
        void (*f_I2cReadBytes)(uint8_t addr, uint8_t reg, uint8_t *buffer, uint16_t length);

    } I2cFunctions;
I2cFunctions Usage Example
void Mpu_6050_initialize(I2cFunctions *i2c_functions) {
    i2c_functions->f_I2cInitialize();
    
    i2c_functions->f_I2cWriteByte(MPU_6050_ADDR, MPU_6050_PWR_MGMT_1, 0x00);
    i2c_functions->f_I2cWriteByte(MPU_6050_ADDR, MPU_6050_CONFIG, 0x01);
    ...
}

This initial approach worked fine and was my plan going forward, when I set the project down for a while. When I picked it back up I saw that this was needlessly complicated.

Simplified Approach

After looking at the code with fresh eyes, I saw a simpler way of solving the problem. When I compile and link the libraries together, the linker finds function implementations and links them to function calls. So, for a simpler approach all I need to do is include the i2c.h header file (which is platform agnostic) when compiling the device libraries. This header file is a promise that the I2C functions will be available to the linker. Then I just have to supply the platform specific I2C library that implements the i2c.h functions to the linker and it will take care of the rest.

Simplified Solution: Let the linker do its job

This change also removed the need to include any platform specific code in main(). Initially main() was responsible for populating the I2cFunctions struct with function pointers to the platform specific implementations. With the new approach, there is no need to change the main() code to support a different platform. The only change needed is to include new platform specific libraries to the linker.

Here is a high level look at the header files referenced in FlightLoggerMain.c and the libraries that implement the functions defined in the header files which are passed to the linker.

The linker hooks everything up so that both the main code and the device libraries are able to call platform specific I2C functions without having any platform specific code in them.

Conclusion

Again, this may be an obvious non-issue to seasoned developers of C and other lower level languages. I came back to this project after a few months with fresh eyes. I have also come back to C after years with fresh eyes. In both cases having fresh eyes has given me a more clear perspective of ways to use a tool and solve a problem.

Leave a Reply

Your email address will not be published. Required fields are marked *