Reusable Driver APIs

More often than not are my C/C++ applications platform bound.

Regardless if the target platform is a 64KiB Flash with 8KiB RAM microcontroller or a PC running Linux or Windows, they can’t easily be ported to a different OS or processor, even if they are similar.

This may sound quite natural, considering the fact that the operating systems and their (driver) APIs look very different. In addition, new technology is arising every year. Besides, why should the UART drivers for an STM32 board and a Linux workstation look similar?

On the other hand, a STM32F401 comes with 80KiB RAM, 384KiB Flash memory and a 84MHz CPU with a hardware FPU. Writing an industrial grade application that uses significant amounts of the processor’s memory and power disallows interrupt driven spaghetti style bare metal code. Event, message and/or semaphore driven spaghetti code applications using a real-time kernel aren’t any better for that purpose. I’ve seen 32KiB binary code size applications that could barely be modified by their authors because they were so complicated.

What makes embedded applications easier to understand, test and port is in my opinion when their domain specific high-level source code is decoupled from their lower layers. This allows for better designs and enables running, simulating and testing them on other platforms like Linux or Windows. Of course, a UML design model, modular programming, layering, loose coupling, simple and stable module interfaces also help in this regard.

Again, this seems only possible if the domain specific part of the application isn’t intermingled with the real-time kernel, interrupts and/or device drivers. I currently see two ways how to decouple an application from device drivers:

  1. A device driver abstraction layer is inserted between the domain specific part of the application and the device driver layer.
  2. A specific device driver (say, the one for the UART) has the same API and behaviour on all platforms.

Following the latter seems more attractive to me. No additional code that is different on different platforms. It would be convenient and effective if I could use code like the following across the STM32 family, AND say in a Linux C/C++ environment too:

#include <cfgGlobal.h> // Defines BOOL, FALSE, TRUE, etc
#include "cfgProject.h" // Defines project specific configuration
#include "drvSysTick.h"
#include "drvUART.h"

#define UART_INST                       (0)

int main(void)
{
  int nbrOfBytes;
  BOOL run;
  char rxBuf[128];
  char txBuf[128];

  /* Initialize the micro-controller's most basic components. */
  initBasics();

  /* Start the drivers. */
  drvSysTick_start(1000); // [us]
  drvUART_start(UART_INST, 115200, 0); // 115200/8/N/1

  /* Loop here until done. */
  run = TRUE;
  while (run)
  {
    drvSysTick_waitForTick();

    /* Do the important stuff. */
    nbrOfBytes = drvUART_read(UART_INST, rxBuf, sizeof(rxBuf));

    // Execute handlers

    drvUART_write(UART_INST, txBuf, nbrOfBytes);
  }

  /* Stop the drivers. */
  drvUART_stop(UART_INST);
  drvSysTick_stop();

  return 0;
}

I may be dreaming, yet let me see how far I get.

Cheers,
Andreas


‘Interface’ is just another word for ‘problem’.