Skip to content

Binding CubeMX Peripherals to Firmware

Prerequisite Reading

This article assumes you are familiar with the racecar/ Architecture.

It uses the peripheral configuration from the previous article.

Open your board_config.ioc file in CubeMX and click "Generate Code." This will surround board_config.ioc with autogenerated files and folders containing C code which initializes the microcontroller using the graphical CubeMX configuration.

Generated Files

We must lift the generated C functions and variables into C++ and "bind" them to our firmware.

Start by creating a new file platforms/stm32f767/bindings.cc.

platforms/stm32f767/bindings.cc
// ============================== CubeMX Includes ==============================
#include "main.h"
#include "stm32f7xx_hal.h"

extern "C" {
// This requires extern since it is not declared in a header, only defined in
// cubemx/../main.c
void SystemClock_Config();
}

// ============================= Firmware Includes =============================
#include "../../bindings.hpp"

// =============================== MCAL Namespace ==============================
namespace mcal {
using namespace mcal::periph::stm32f767;
// --------- Define C++ peripheral objects from CubeMX generated code ----------

}  // namespace mcal

// ============================ Bindings Namespace =============================
namespace bindings {
// ------------- Bind mcal peripherals to the bindings.hpp handles -------------

// ---------------- Implement other platform-specific functions ----------------
void Initialize() {
    HAL_Init();            // from stm32f7xx_hal.h
    SystemClock_Config();  // from cubemx/src/main.c

    // ------------------ Add additional initialization code -------------------
}

}  // namespace bindings
Where is bindings.hpp?

We created bindings.hpp in the project structure article, but you will need to recreate it for each project.

Use the following bindings.hpp to continue the LV Controller example from the MCU Configuration article. Place it in the project folder (adjacent to main.cc, NOT in any platform folder).

bindings.hpp
// This file is for illustrative purposes only and does not indicate the
// complete or correct LV Controller code.

#include "shared/periph/analog_input.hpp"
#include "shared/periph/can.hpp"
#include "shared/periph/gpio.hpp"

namespace bindings {

extern shared::periph::DigitalOutput& tssi_red_en;
extern shared::periph::DigitalOutput& tssi_green_en;

extern shared::periph::AnalogInput& dcdc_sense;
extern shared::periph::AnalogInput& suspension_travel3;
extern shared::periph::AnalogInput& suspension_travel4;

extern shared::periph::CanBase& veh_can_base;

// Note: JTAG isn't used in any of our code, but the CubeMX pin configuration is
// still necessary.

extern void Initialize();

}  // namespace bindings

Initialization

CubeMX generates functions to initialize the MCU according to our configuration, but we are responsible for calling them at the start of our program.

The app-level cannot call these functions since it must be platform-independent. To get around this, we let each platform define a void Initialize() function to call its startup code. So far, our Initialize() sets up the HAL and clock, but each peripheral will add to it.

Peripherals

A C++ peripheral is created by wrapping the generated variables in an MCAL peripheral class.

For each hardware peripheral that you use (ex GPIO, ADC1, CAN2):

  1. Include <periph>.h (generated by CubeMX) to access the initialization functions.
  2. Call MX_<PERIPH>_INIT() in Initialize().
  3. Include the mcal/stm32f767/periph/<periph>.hpp to access the racecar/ peripheral class.

Then for each peripheral instance of that type (ex TSSI_RED_EN and TSSI_GN_EN GPIOs):

  1. Define C++ peripheral in the mcal:: namespace using the constants and handles generated by CubeMX.
  2. Bind the mcal:: object to the shared:: handle inside the bindings namespace.

Digital Input/Output (GPIO)

Construct an stm32 DigitalOutput or DigitalInput with the <NAME>_GPIO_Port and <NAME>_Pin constants defined in CubeMX's main.h.

CubeMX generates these constants based on the pin configurations. For example, TSSI_RED_EN is connected to PC2, thus CubeMX generates

cubemx/inc/main.h
#define TSSI_RED_SIG_GPIO_Port GPIOC
#define TSSI_RED_SIG_Pin GPIO_PIN_2
bindings.cc
// ============================== CubeMX Includes ==============================
#include "gpio.h"
#include "main.h"
#include "stm32f7xx_hal.h"

extern "C" {
// This requires extern since it is not declared in a header, only defined in
// cubemx/../main.c
void SystemClock_Config();
}

// ============================= Firmware Includes =============================
#include "../../bindings.hpp"
#include "mcal/stm32f767/periph/gpio.hpp"

// =============================== MCAL Namespace ==============================
namespace mcal {
using namespace mcal::periph::stm32f767;
// --------- Define C++ peripheral objects from CubeMX generated code ----------

DigitalOutput tssi_red_en{TSSI_RED_SIG_GPIO_Port, TSSI_RED_SIG_Pin};
DigitalOutput tssi_green_signal{TSSI_GN_SIG_GPIO_Port, TSSI_GN_SIG_Pin};

}  // namespace mcal

// ============================ Bindings Namespace =============================
namespace bindings {
// ------------- Bind mcal peripherals to the bindings.hpp handles -------------
shared::periph::DigitalOutput& tssi_red_en = mcal::tssi_red_en;
shared::periph::DigitalOutput& tssi_green_en = mcal::tssi_green_en;

// ---------------- Implement other platform-specific functions ----------------
void Initialize() {
    HAL_Init();            // from cubemx/inc/stm32f7xx_hal.h
    SystemClock_Config();  // from cubemx/src/main.c

    // ------------------ Add additional initialization code -------------------
    MX_GPIO_Init();  // from cubemx/inc/gpio.h
}

}  // namespace bindings

Analog Input (ADC)

Unlike with GPIO pins, CubeMX does not include the ADC name in the generated handles. You must manually align the ADC peripheral number (ADC1, ADC2, or ADC3) and channel for each peripheral.

For example, DCDC_SNS was connected to ADC1_IN10, so we must pass the pointer to the ADC1 handle &hadc1 along with the channel 10 constant.

Note that each ADC# has its own MX_ADC#_Init() function.

bindings.cc
// ============================== CubeMX Includes ==============================
#include "adc.h"
#include "gpio.h"
#include "main.h"
#include "stm32f7xx_hal.h"

extern "C" {
// This requires extern since it is not declared in a header, only defined in
// cubemx/../main.c
void SystemClock_Config();
}

// ============================= Firmware Includes =============================
#include "../../bindings.hpp"
#include "mcal/stm32f767/periph/analog_input.hpp"
#include "mcal/stm32f767/periph/gpio.hpp"

// =============================== MCAL Namespace ==============================
namespace mcal {
using namespace mcal::periph::stm32f767;
// --------- Define C++ peripheral objects from CubeMX generated code ----------

DigitalOutput tssi_red_en{TSSI_RED_SIG_GPIO_Port, TSSI_RED_SIG_Pin};
DigitalOutput tssi_green_signal{TSSI_GN_SIG_GPIO_Port, TSSI_GN_SIG_Pin};

AnalogInput dcdc_sense{&hadc1, ADC_CHANNEL_10};
AnalogInput suspension_travel3{&hadc1, ADC_CHANNEL_15};
AnalogInput suspension_travel4{&hadc1, ADC_CHANNEL_14};

}  // namespace mcal

// ============================ Bindings Namespace =============================
namespace bindings {
// ------------- Bind mcal peripherals to the bindings.hpp handles -------------
shared::periph::DigitalOutput& tssi_red_en = mcal::tssi_red_en;
shared::periph::DigitalOutput& tssi_green_en = mcal::tssi_green_en;

shared::periph::AnalogInput& dcdc_sense = mcal::dcdc_sense;
shared::periph::AnalogInput& suspension_travel3 = mcal::suspension_travel3;
shared::periph::AnalogInput& suspension_travel4 = mcal::suspension_travel4;

// ---------------- Implement other platform-specific functions ----------------
void Initialize() {
    HAL_Init();            // from cubemx/inc/stm32f7xx_hal.h
    SystemClock_Config();  // from cubemx/src/main.c

    // ------------------ Add additional initialization code -------------------
    MX_GPIO_Init();  // from cubemx/inc/gpio.h
    MX_ADC1_Init();  // from cubemx/inc/adc.h
}

}  // namespace bindings

CAN

Connect the CAN handle to the peripheral.

As with ADC, each CAN# has its own MX_CAN#_Init() function.

bindings.cc
// ============================== CubeMX Includes ==============================
#include "adc.h"
#include "can.h"
#include "gpio.h"
#include "main.h"
#include "stm32f7xx_hal.h"

extern "C" {
// This requires extern since it is not declared in a header, only defined in
// cubemx/../main.c
void SystemClock_Config();
}

// ============================= Firmware Includes =============================
#include "../../bindings.hpp"
#include "mcal/stm32f767/periph/analog_input.hpp"
#include "mcal/stm32f767/periph/can.hpp"
#include "mcal/stm32f767/periph/gpio.hpp"

// =============================== MCAL Namespace ==============================
namespace mcal {
using namespace mcal::periph::stm32f767;
// --------- Define C++ peripheral objects from CubeMX generated code ----------

DigitalOutput tssi_red_en{TSSI_RED_SIG_GPIO_Port, TSSI_RED_SIG_Pin};
DigitalOutput tssi_green_signal{TSSI_GN_SIG_GPIO_Port, TSSI_GN_SIG_Pin};

AnalogInput dcdc_sense{&hadc1, ADC_CHANNEL_10};
AnalogInput suspension_travel3{&hadc1, ADC_CHANNEL_15};
AnalogInput suspension_travel4{&hadc1, ADC_CHANNEL_14};

CanBase veh_can_base{&hcan2};

}  // namespace mcal

// ============================ Bindings Namespace =============================
namespace bindings {
// ------------- Bind mcal peripherals to the bindings.hpp handles -------------
shared::periph::DigitalOutput& tssi_red_en = mcal::tssi_red_en;
shared::periph::DigitalOutput& tssi_green_en = mcal::tssi_green_en;

shared::periph::AnalogInput& dcdc_sense = mcal::dcdc_sense;
shared::periph::AnalogInput& suspension_travel3 = mcal::suspension_travel3;
shared::periph::AnalogInput& suspension_travel4 = mcal::suspension_travel4;

shared::periph::CanBase& veh_can_base = mcal::veh_can_base;

// ---------------- Implement other platform-specific functions ----------------
void Initialize() {
    HAL_Init();            // from cubemx/inc/stm32f7xx_hal.h
    SystemClock_Config();  // from cubemx/src/main.c

    // ------------------ Add additional initialization code -------------------
    MX_GPIO_Init();  // from cubemx/inc/gpio.h
    MX_ADC1_Init();  // from cubemx/inc/adc.h
    MX_CAN2_Init();  // from cubemx/inc/can.h
}

}  // namespace bindings