Search code examples
cstructembeddedglobal-variablesabstraction

How to share a hardware abstraction struct without making it global


This is a question about sharing data that is "global", mimicking a piece of addressable memory that any function could access.

I'm writing code for an embedded project, where I've decoupled my physical gpio pins from the application. The application communicates with the "virtual" gpio port, and device drivers then communicate with the actual hardware. The primary motivation for this is the comfort it allows me in switching out what pins are connected to what peripheral when developing, and to include things like button matrices that use fewer physical pins while still handling them as regular gpio device registers.

typedef struct GPIO_PinPortPair
{
    GPIO_TypeDef     *port;       /* STM32 GPIO Port */
    uint16_t          pin;        /* Pin number */
} GPIO_PinPortPair;

typedef struct GPIO_VirtualPort
{
    uint16_t          reg;        /* Virtual device register */
    uint16_t          edg;        /* Flags to signal edge detection */
    GPIO_PinPortPair *grp;        /* List of physical pins associated with vport */
    int               num_pins;   /* Number of pins in vport */
} GPIO_VirtualPort;

This has worked well in the code I've written so far, but the problem is that I feel like I have to share the addresses to every defined virtual port as a global. A function call would look something like this, mimicking the way it could look if I were to use regular memory mapped io.

file1.c

GPIO_VirtualPort LEDPort;
/* LEDPort init code that associates it with a list of physical pins */ 

file2.c

extern GPIO_VirtualPort LEDPort;
vgpio_write_pin(&LEDPort, PIN_1, SET_PIN);

I've searched both SO and the internet for best practices when it comes to sharing variables, and I feel like I understand why I should avoid global variables (no way to pinpoint where in code something happens to the data) and that it's better to use local variables with pointers and interface functions (like a "get current tick" function rather than reading a global tick variable).

My question is, given that I want to the keep the syntax as simple as possible, what is the best way to define these struct variables and then make them available for functions in other modules to use? Is it okay to use these struct variables as globals? Should I use some kind of master-array of pointers to every virtual port I have and use a getter function to avoid using extern variables?


Solution

  • I like to do it this way:

    file1.h

    typedef enum
    {
      VirtualPortTypeLED
    } VirtualPortType;
    
    typedef struct GPIO_PinPortPair
    {
        GPIO_TypeDef     *port;       /* STM32 GPIO Port */
        uint16_t          pin;        /* Pin number */
    } GPIO_PinPortPair;
    
    typedef struct GPIO_VirtualPort
    {
        uint16_t          reg;        /* Virtual device register */
        uint16_t          edg;        /* Flags to signal edge detection */
        GPIO_PinPortPair *grp;        /* List of physical pins associated with vport */
        int               num_pins;   /* Number of pins in vport */
    } GPIO_VirtualPort;
    

    file1.c

    GPIO_VirtualPort LEDPort;
    
    void VirtualPortInit()
    {
      /* fill in all structures and members here */
      LEDPort.reg = 0x1234;
      ...
    }
    
    GPIO_VirtualPort *VirtualPortGet(VirtualPortType vpt)
    {
      switch(vpt) {
        case VirtualPortTypeLED:
          return &LEDPort;
      }
    
      return NULL;
    }
    

    file2.c

    #include file1.h
    
    GPIO_VirtualPort *myLed;
    
    VirtualPortInit();
    myLed = VirtualPortGet(VirtualPortTypeLED);
    

    Btw, I didn't compile this ... :)