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.
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
.
// ============================== 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).
// 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):
- Include
<periph>.h
(generated by CubeMX) to access the initialization functions. - Call
MX_<PERIPH>_INIT()
inInitialize()
. - Include the
mcal/stm32f767/periph/<periph>.hpp
to access theracecar/
peripheral class.
Then for each peripheral instance of that type (ex TSSI_RED_EN
and TSSI_GN_EN
GPIOs):
- Define C++ peripheral in the
mcal::
namespace using the constants and handles generated by CubeMX. - Bind the
mcal::
object to theshared::
handle inside thebindings
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 generatescubemx/inc/main.h#define TSSI_RED_SIG_GPIO_Port GPIOC #define TSSI_RED_SIG_Pin GPIO_PIN_2
// ============================== 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.
// ============================== 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.
// ============================== 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