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:
- 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. - 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. - 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”