For many years, I have been troubled by a reuse issue in C, such as when we establish an I2C IP register address offset, and there are multiple I2Cs on the SoC, each with irregular offsets. How can we solve the problem of calling this function with different offsets for multiple I2Cs in a single case on the SoC?
【Problem Analysis】In the management of multiple peripherals in SoC, there are the following technical pain points regarding IP core register address mapping:
-
Heterogeneous address distribution: The same IP core has non-continuous/non-linear base address offsets in different instances.
-
Code reuse dilemma: Hardware differences make it difficult for the software layer to achieve a unified access interface.
-
Dynamic access requirements: Runtime indices in loop structures cannot be compatible with pre-compiled solutions.
【Evolutionary Solutions】1. Basic Solution (Macro Concatenation)
#define I2C_FUNC(n, offset) i2c##n##_write_reg(offset + I2C##n##_BASE
This solution can achieve basic address access based on static indices of the IP, but it has strong limitations and cannot adapt to loop variables.2. Improved Solution (Address Mapping Table)
const I2C_Config i2c_config[] = { 0x40005000, // I2C0 0x40006000, // I2C1 0x40017000 // I2C2}; // Unified access interfacevoid i2c_write_reg(uint8_t bus_num, uint32_t offset, uint8_t val){ uint32_t abs_addr = i2c_config[bus_num] + offset; xxxxxx}
This solution can solve the problems of Solution 1.Of course, this solution still has some challenging aspects. Here, the variable has been changed to an array, and how to handle modifications and replacements of the array in different environments is a problem that needs to be addressed (this issue is beyond the scope of this article).3. Function Encapsulation
typedef struct { uint32_t base; func point 1; func point 2;} I2C_Controller;void init_i2c_controllers(I2C_Controller* ctrls, const I2C_Config* cfg, int num){ for(int i=0; i<num; 3);="" accessfor(int="" buses[3];init_i2c_controllers(buses,="" code="" ctrls[i].base="cfg[i].base_addr;" dynamic="" examplei2c_controller="" function="" i="0;" i++){="" i2c_config,="" i<3;="" mapping;="" pointer="" usage="" usage;}<="" }}=""></num;>
This solution inherits from Solution 2 and further mitigates the complexity of using offsets by separating the solution provider (I2C owner) from the user (SoC), compressing the complexity within the IP and reducing the complexity for SoC usage.Of course, we can encapsulate the basic functions into functional functions, passing them through structs to solve more general problems and further reduce the difficulty of upper-level usage.