Skip to content

Multi-Platform Applications

At some point, you are going to build something that you may wish to port to another microcontroller or system. SJSU-Dev2 is designed in a way that makes doing this, without bloating your application's binary size with implementation details from each platform you want to support.

See the example code demos/multiplatform/dual_platforms/.

The method for making a project multi-platform has 3 phases to it, see file for more details.

Phase 1: Create interface pointers

In this phase you will want to define all of the peripherals you plan to use as pointers. Typically, references are preferred for interfaces, but references cannot be re-assigned at runtime, thus we must use pointers.

const sjsu::Gpio * status_led_gpio;
const sjsu::Gpio * power_on_gpio;
const sjsu::Gpio * kill_switch_gpio;

const sjsu::Spi * spi_bus;
const sjsu::I2c * i2c_bus;

const sjsu::Gpio * motor_direction_gpio;
const sjsu::Pwm * motor_controller_pwm;

Phase 2: Assign pointers in if constexpr blocks per platform

Now that we have made our list of peripherals or application needs, we need to assign these pointers to real peripheral driver objects for the specified platform.

There will need to be if constexpr block for each platform you care to support.

Within each block a static peripheral object will need to be created and constructed for each of the pointers in phase 1. After construction, each peripheral pointer will need to be assigned to the address of each static peripheral object. In this phase you can also create temporary objects within the block if this is found useful.

The static keyword is required for objects that have their addresses referenced outside the scope of the if constexpr block. Using the static keyword will allocate the object statically and will make the object available for the whole lifetime of the application. Not doing this means that the objects will be destructed after the program leaves the scope of the if constexpr block. If this happens, the behavior of the program is undefined.

if constexpr (sjsu::build::kPlatform == sjsu::build::Platform::lpc17xx)
{
  static sjsu::lpc17xx::Gpio lpc17xx_status_led_gpio(1, 9);
  static sjsu::lpc17xx::Gpio lpc17xx_poweron_gpio(1, 0);
  // etc...

  poweron_gpio    = &lpc17xx_poweron_gpio;
  status_led_gpio = &lpc17xx_status_led_gpio;
  // etc...
}
else if constexpr (sjsu::build::kPlatform == sjsu::build::Platform::lpc40xx)
{
  static sjsu::lpc17xx::Gpio lpc40xx_status_led_gpio(1, 9);
  static sjsu::lpc17xx::Gpio lpc40xx_poweron_gpio(1, 0);
  // etc...

  poweron_gpio    = &lpc40xx_poweron_gpio;
  status_led_gpio = &lpc40xx_status_led_gpio;
  // etc...
}
else if constexpr (sjsu::build::kPlatform == sjsu::build::Platform::linux)
{
  static sjsu::lpc17xx::Gpio linux_status_led_gpio(1, 9);
  static sjsu::lpc17xx::Gpio linux_poweron_gpio(1, 0);
  // etc...

  poweron_gpio    = &linux_poweron_gpio;
  status_led_gpio = &linux_status_led_gpio;
  // etc...
}
else
{
  sjsu::LogError("Invalid platform for this application!");
  sjsu::Halt();
}

Phase 3: Pass references to devices and systems objects if any

If you are passing the L1 peripherals to devicesS then this phase is the time to do it. This phase is outside of the if constexpr block and is where you list and construct every hardware abstraction. Same goes for application level objects. A small example below

sjsu::Button poweron_button(*poweron_gpio);
sjsu::RCCarController rc_controller(*motor_direction_gpio,
                                    *motor_controller_pwm,
                                    /* etc ... */);

Done

At this point you can begin using your peripherals, HALs, application layer.