Skip to content
Menu
Klein Embedded
  • About
Klein Embedded
January 8, 2022February 26, 2022

Multiple instances of a module in C

When writing modules for your application, sometimes it might be enough to have just a single instance of the module (e.g. a global “logging” module), but oftentimes you will need to create multiple instances of a module (e.g. a “queue” module). In this article I will take a look at various ways to implement this in C.

In the examples below, we will be writing a very simple LED driver module. We will be able to configure the port and pin number that the LED is connected to, and to turn the LED on and off.

A single instance

To start simple, we’ll first explore the most basic way to implement this functionality. Imagine we have only a single LED on our PCB – a status LED – and we don’t expect to add more in the future. We might be tempted to just write the module in a way that we have the possibility of creating multiple LED instances in the future, should the need arise. But as good programmers, we will of course adhere to the YAGNI principle and only implement the simplest solution that solves the problem.

The header will contain an initialization function, which is used to set the port and pin number, and functions for turning the LED on and off:

#include <stdint.h>

void led_init(uint8_t port, uint8_t pin);
void led_turn_on();
void led_turn_off();

The configuration (port and pin) and the state of the LED (whether it is on or off) is stored as static variables (I will call these instance variables) in the source file, and thus hidden from the user of the module:

#include "led.h"
#include "gpio.h"

static uint8_t port;
static uint8_t pin;

void led_init(uint8_t led_port, uint8_t led_pin)
{
  port = led_port;
  pin = led_pin;
  led_turn_off();
}

void led_turn_on()
{
  gpio_set(port, pin);

}

void led_turn_off()
{
  gpio_clear(port, pin);

}

This is just fine for a single instance – but what if we need to control more than one LED?

Multiple instances using internal arrays

Suppose we add a Bluetooth module to our PCB and we now want an additional LED that will indicate if a Bluetooth connection is active. How can we extend our LED module in order to control more than one LED? The first thing that comes to mind, is simply to turn the instance variables from the previous example into arrays. Then we assign an ID number to each LED, which corresponds to the index in the array. We will also need to specify the maximum number of LEDs, in order to make sure the arrays are large enough:

#include <stdint.h>

#define MAX_NUM_LEDS 2

void led_init(uint8_t id, uint8_t port, uint8_t pin);
void led_turn_on(uint8_t id);
void led_turn_off(uint8_t id);

All the functions now take id as the first parameter, so we know which LED to control.

#include "led.h"
#include "gpio.h"

static uint8_t port[MAX_NUM_LEDS];
static uint8_t pin[MAX_NUM_LEDS];

void led_init(uint8_t id, uint8_t led_port, uint8_t led_pin)
{
  port[id] = led_port;
  pin[id] = led_pin;
  led_turn_off(id);
}

void led_turn_on(uint8_t id)
{
  gpio_set(port[id], pin[id]);
}

void led_turn_off(uint8_t id)
{
  gpio_clear(port[id], pin[id]);
}

To keep track of which ID belongs to which LED, we will make a header file with all our #defines for the project:

/* LED identifiers */
#define STATUS_LED     0
#define BLUETOOTH_LED 1

This solution works, but it does have a few drawbacks:

  1. Every time we want to add more LEDs, we will have to change MAX_NUM_LEDS. We could just set it to some arbitrary “large enough” number, but we will be wasting memory for each ID that is not being used.
  2. With only two instance variables, arrays do not seem so bad. If we get a lot of instance variables, however, it may get unwieldy. Grouping the variables together for each instance (i.e. in a struct) might be a better solution.
  3. All LEDs are essentially global: Any LED can be manipulated anywhere in the program. This is generally discouraged, as you risk increasing the coupling in your program and it will be harder to determine which parts of your program use each LED.

Let’s see if we can find a better solution.

Multiple instances using public structs

Instead of assigning an ID to each LED, let’s group all the instance variables together in a struct and then pass a pointer to that struct as the first parameters to our functions:

#include <stdint.h>

typedef struct led_handle led_handle_t;

void led_init(led_handle_t *self, uint8_t port, uint8_t pin);
void led_turn_on(led_handle_t *self);
void led_turn_off(led_handle_t *self);

struct led_handle
{
  uint8_t port;
  uint8_t pin;
};

Now, we don’t have to worry about how many LEDs the application will use, because the structs will be allocated outside our module. In the source file, we no longer need the arrays. We simply dereference the pointer given to us by the user.

#include "led.h"
#include "gpio.h"

void led_init(led_handle_t *self, uint8_t led_port, uint8_t led_pin)
{
  self->port = led_port;
  self->pin = led_pin;
  led_turn_off(self);
}

void led_turn_on(led_handle_t *self)
{
  gpio_set(self->port, self->pin);
}

void led_turn_off(led_handle_t *self)
{
  gpio_clear(self->port, self->pin);
}

Note, that in this example, the struct led_handle is publicly defined in the header file, meaning that the user of the module is free to change any variable within that struct – breaking encapsulation! It does, however, also allow the user to statically allocate memory for the struct, which may be necessary on very memory-limited microcontrollers.

If we don’t mind using dynamic memory allocation, we can improve the solution even further.

Multiple instances using private structs

Instead of declaring struct led_handle in the header file, we will now only provide the user with a pointer to the struct, without revealing any of its members. We also need to add a constructor (led_create()) and a destructor (led_destroy()), in order to allocate and deallocate memory for the struct.

#include <stdint.h>
#include <stdbool.h>

typedef struct led_handle* led_handle_ptr_t;

led_handle_ptr_t led_create();
void led_destroy(led_handle_ptr_t self);
void led_init(led_handle_ptr_t self, uint8_t port, uint8_t pin);
void led_turn_on(led_handle_ptr_t self);
void led_turn_off(led_handle_ptr_t self);

In the source file, we declare struct led_handle and implement the two new functions:

#include "led.h"
#include "gpio.h"
#include <stdlib.h>

struct led_handle
{
  uint8_t port;
  uint8_t pin;
};

led_handle_ptr_t led_create()
{
  return (led_handle_ptr_t) malloc(sizeof(struct led_handle));
}

void led_destroy(led_handle_ptr_t self)
{
  free(self);
}

void led_init(led_handle_ptr_t self, uint8_t led_port, uint8_t led_pin)
{
  self->port = led_port;
  self->pin = led_pin;
  led_turn_off(self);
}

void led_turn_on(led_handle_ptr_t self)
{
  gpio_set(self->port, self->pin);
}

void led_turn_off(led_handle_ptr_t self)
{
  gpio_clear(self->port, self->pin);
}

In my opinion, this way of creating instances makes the application code very clean and easy to follow. Suppose we have two tasks in our program: One for blinking the status LED to indicate that the system is running, and one for handling Bluetooth communication. In main() we can instantiate the two LEDs and pass them to each of the tasks, making it obvious where each LED is used:

#include "led.h"
#include "blink_task.h"
#include "bluetooth_task.h"
#include "project_defines.h"

int main()
{
  led_handle_ptr_t status_led = led_create(STATUS_LED_PORT, STATUS_LED_PIN);
  led_handle_ptr_t bluetooth_led = led_create(BLUETOOTH_LED_PORT, BLUETOOTH_LED_PIN);  

  blink_task_init(status_led);
  bluetooth_task_init(bluetooth_led);

  while (1)
  {
    blink_task_run();
    bluetooth_task_run();
  }
}

1 thought on “Multiple instances of a module in C”

  1. Pingback: Interfaces in C - Klein Embedded

Leave a Reply Cancel reply

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

Subscribe to the newsletter

Get notified by email when a new blog post is published.

Check your inbox or spam folder to confirm your subscription.

Recent Posts

  • STM32 without CubeIDE (Part 4): CMake, FPU and STM32 libraries
  • Adding right-click context menu items in Windows 10
  • CI/CD with Jenkins and Docker
  • STM32 without CubeIDE (Part 3): The C Standard Library and printf()
  • Understanding the (Embedded) Linux boot process

Recent Comments

  1. john on STM32 without CubeIDE (Part 4): CMake, FPU and STM32 libraries
  2. Kristian Klein-Wengel on STM32 without CubeIDE (Part 2): CMSIS, make and clock configuration
  3. Nora on STM32 without CubeIDE (Part 2): CMSIS, make and clock configuration
  4. Kristian Klein-Wengel on STM32 without CubeIDE (Part 3): The C Standard Library and printf()
  5. Milos on STM32 without CubeIDE (Part 3): The C Standard Library and printf()

Archives

  • October 2025
  • June 2023
  • May 2023
  • April 2023
  • March 2023
  • January 2023
  • December 2022
  • November 2022
  • October 2022
  • September 2022
  • August 2022
  • June 2022
  • May 2022
  • April 2022
  • March 2022
  • February 2022
  • January 2022
  • December 2021

Categories

  • C++
  • DevOps
  • DSP
  • Electronics
  • Embedded C
  • Embedded Linux
  • Firmware
  • Project
  • Python
  • Software Design
  • Testing
  • Tutorial
  • Uncategorized
©2025 Klein Embedded