Porting¶
How to bring the interfaces to a new MCU: what to implement, in what order, and how to check it.
What a port is¶
A port is a folder inc/mcu/<platform>/ plus src/mcu/<platform>/ with one class per interface you need. Each
class derives from a Base* class and implements its pure virtual methods with the vendor SDK. Nothing in
inc/base/ or in the code above it changes. Start with the interfaces your board's drivers use (usually GPIO,
SPI, I2C, UART, then CAN, ADC, PWM and a timer), not all fifteen.
The methods to implement¶
| Interface | Methods a port must implement |
|---|---|
BaseAdc (7) |
Initialize(), Deinitialize(), GetMaxChannels(), IsChannelAvailable(), ReadChannelV(), ReadChannelCount(), ReadChannel() |
BaseBluetooth (45) |
Initialize(), Deinitialize(), IsInitialized(), Enable(), Disable(), IsEnabled(), SetMode(), GetMode(), GetLocalAddress(), SetDeviceName(), GetDeviceName(), ConfigureClassic(), SetDiscoverable(), IsDiscoverable(), ConfigureBle(), StartAdvertising(), StopAdvertising(), IsAdvertising(), StartScan(), StopScan(), IsScanning(), GetDiscoveredDevices(), ClearDiscoveredDevices(), Connect(), Disconnect(), IsConnected(), GetConnectedDevices(), Pair(), Unpair(), IsPaired(), GetPairedDevices(), SendData(), GetAvailableData(), ReadData(), DiscoverServices(), DiscoverCharacteristics(), ReadCharacteristic(), WriteCharacteristic(), SubscribeCharacteristic(), GetState(), GetRssi(), RegisterEventCallback(), UnregisterEventCallback(), RegisterDataCallback(), UnregisterDataCallback() |
BaseCan (8) |
Initialize(), Deinitialize(), SendMessage(), ReceiveMessage(), SetReceiveCallback(), SetAcceptanceFilter(), GetStatus(), Reset() |
BaseGpio (12) |
SetDirectionImpl(), GetDirectionImpl(), SetOutputModeImpl(), GetOutputModeImpl(), SetPullModeImpl(), GetPullModeImpl(), SetPinLevelImpl(), GetPinLevelImpl(), IsPinAvailable(), GetMaxPins(), GetDescription(), Initialize() |
BaseI2c (6) |
Initialize(), Deinitialize(), Write(), Read(), WriteRead(), GetDeviceAddress() |
BaseLogger (26) |
Initialize(), Deinitialize(), IsInitialized(), EnsureInitialized(), SetLogLevel(), GetLogLevel(), Error(), Warn(), Info(), Debug(), Verbose(), Log(), LogV(), LogWithLocation(), Flush(), IsLevelEnabled(), GetStatistics(), GetDiagnostics(), ResetStatistics(), ResetDiagnostics(), IsHealthy(), GetLastError(), GetLastErrorMessage(), PrintStatistics(), PrintDiagnostics(), PrintStatus() |
BaseNvs (15) |
Initialize(), Deinitialize(), SetU32(), GetU32(), SetString(), GetString(), SetBlob(), GetBlob(), EraseKey(), Commit(), KeyExists(), GetSize(), GetDescription(), GetMaxKeyLength(), GetMaxValueSize() |
BasePeriodicTimer (12) |
Initialize(), Deinitialize(), Start(), Stop(), SetPeriod(), GetPeriod(), GetStats(), ResetStats(), GetDescription(), GetMaxPeriod(), GetMinPeriod(), GetResolution() |
BasePio (14) |
Initialize(), Deinitialize(), ConfigureChannel(), Transmit(), StartReceive(), StopReceive(), IsChannelBusy(), GetChannelStatus(), GetCapabilities(), SetTransmitCallback(), SetReceiveCallback(), SetErrorCallback(), ClearChannelCallbacks(), ClearCallbacks() |
BasePwm (15) |
Initialize(), Deinitialize(), EnableChannel(), DisableChannel(), IsChannelEnabled(), SetDutyCycle(), SetDutyCycleRaw(), SetFrequency(), SetPhaseShift(), StartAll(), StopAll(), UpdateAll(), SetComplementaryOutput(), GetDutyCycle(), GetFrequency() |
BaseSpi (4) |
Initialize(), Deinitialize(), Transfer(), GetDeviceConfig() |
BaseTemperature (5) |
Initialize(), Deinitialize(), ReadTemperatureCelsiusImpl(), GetSensorInfo(), GetCapabilities() |
BaseUart (8) |
Initialize(), Deinitialize(), Write(), Read(), BytesAvailable(), FlushTx(), FlushRx(), Printf() |
BaseUsbSerialJtag (6) |
Initialize(), Deinitialize(), Write(), Read(), FlushTx(), IsHostConnected() |
BaseWifi (30) |
Initialize(), Deinitialize(), IsInitialized(), SetMode(), GetMode(), ConfigureStation(), Connect(), Disconnect(), IsConnected(), GetRssi(), GetIpInfo(), ConfigureAccessPoint(), StartAccessPoint(), StopAccessPoint(), IsAccessPointActive(), GetConnectedStationCount(), StartScan(), GetScanResults(), IsScanning(), GetState(), GetConnectedSsid(), GetConnectedBssid(), SetPowerSave(), GetPowerSave(), RegisterEventCallback(), UnregisterEventCallback(), GetMacAddress(), SetMacAddress(), GetChannel(), SetChannel() |
Everything else in an interface has a working default in the base class: lazy initialisation, polarity
handling, statistics, batch helpers, and "not supported" returns for optional features. Override those only to
do better (for example, a hardware TransferChain() for SPI, or GetBusStatus() for CAN).
A complete GPIO port¶
The smallest complete implementation, compiled by the host build from
tests/docs/port_gpio.cpp. BaseGpio turns SetActive() into
SetPinLevelImpl(HIGH) or LOW depending on the polarity, and validates the object before calling the port.
#include "BaseGpio.h"
// A GPIO for a new MCU. BaseGpio does the polarity, validation and statistics;
// the port only moves the pin.
class MyMcuGpio : public BaseGpio {
public:
explicit MyMcuGpio(
hf_pin_num_t pin, hf_gpio_direction_t dir = hf_gpio_direction_t::HF_GPIO_DIRECTION_OUTPUT,
hf_gpio_active_state_t active = hf_gpio_active_state_t::HF_GPIO_ACTIVE_HIGH) noexcept
: BaseGpio(pin, dir, active) {}
bool Initialize() noexcept override {
// Configure the pin's mux, direction and pulls with the vendor SDK.
return SetDirectionImpl(current_direction_) == hf_gpio_err_t::GPIO_SUCCESS;
}
bool IsPinAvailable() const noexcept override {
return pin_ >= 0 && pin_ < GetMaxPins();
}
hf_u8_t GetMaxPins() const noexcept override {
return 32; // pins on this MCU
}
const char* GetDescription() const noexcept override {
return "MyMcu GPIO";
}
protected:
hf_gpio_err_t SetDirectionImpl(hf_gpio_direction_t d) noexcept override {
direction_ = d;
return hf_gpio_err_t::GPIO_SUCCESS;
}
hf_gpio_err_t GetDirectionImpl(hf_gpio_direction_t& d) const noexcept override {
d = direction_;
return hf_gpio_err_t::GPIO_SUCCESS;
}
hf_gpio_err_t SetOutputModeImpl(hf_gpio_output_mode_t m) noexcept override {
mode_ = m;
return hf_gpio_err_t::GPIO_SUCCESS;
}
hf_gpio_err_t GetOutputModeImpl(hf_gpio_output_mode_t& m) const noexcept override {
m = mode_;
return hf_gpio_err_t::GPIO_SUCCESS;
}
hf_gpio_err_t SetPullModeImpl(hf_gpio_pull_mode_t p) noexcept override {
pull_ = p;
return hf_gpio_err_t::GPIO_SUCCESS;
}
hf_gpio_pull_mode_t GetPullModeImpl() const noexcept override {
return pull_;
}
hf_gpio_err_t SetPinLevelImpl(hf_gpio_level_t level) noexcept override {
level_ = level; // write the output data register here
return hf_gpio_err_t::GPIO_SUCCESS;
}
hf_gpio_err_t GetPinLevelImpl(hf_gpio_level_t& level) noexcept override {
level = level_; // read the input data register here
return hf_gpio_err_t::GPIO_SUCCESS;
}
private:
hf_gpio_direction_t direction_{hf_gpio_direction_t::HF_GPIO_DIRECTION_INPUT};
hf_gpio_output_mode_t mode_{hf_gpio_output_mode_t::HF_GPIO_OUTPUT_MODE_PUSH_PULL};
hf_gpio_pull_mode_t pull_{hf_gpio_pull_mode_t::HF_GPIO_PULL_MODE_FLOATING};
hf_gpio_level_t level_{hf_gpio_level_t::HF_GPIO_LEVEL_LOW};
};
Rules for an implementation¶
| Rule | Why |
|---|---|
Constructors store configuration only; Initialize() touches hardware and sets initialized_ |
Objects are created as globals and members before the scheduler runs |
| Return the interface's error codes, never vendor codes | Callers compare against *_SUCCESS and the *_ERR_* list |
Every method is noexcept; no exceptions, no RTTI |
The component builds with -fno-exceptions -fno-rtti |
Allocate in Initialize(), never in an interrupt |
Interrupt handlers must not block or allocate |
Guard shared state with PlatformMutex |
It becomes a real mutex when a backend is configured (see Architecture) |
| Callbacks from interrupts get copies, not pointers into driver buffers | The buffer may be reused before the callback's work runs |
timeout_ms = 0 means "do not wait" |
BaseCan::TrySendMessage() and polling receives rely on it |
Wire it into the build¶
- Add
HF_TARGET_MCU_<NAME>and its family block toinc/utils/McuSelect.h. - Add a monotonic time source
inc/mcu/<platform>/<X>Time.hand its#elifininc/utils/HfTime.h. - Add a
PlatformMutexBackend.hfor the platform's RTOS where the stack is assembled (hf-core does this for FreeRTOS). - Build the sources with the platform's toolchain; the ESP-IDF component in the root
CMakeLists.txtis the model.
Check a port¶
| Check | How |
|---|---|
| It compiles against the interface | Instantiate every class once, as port_gpio.cpp does; an unimplemented pure virtual is a compile error |
| Lifecycle | Call each method before Initialize() (expect *_ERR_NOT_INITIALIZED), then after |
| Polarity | An active-low BaseGpio: SetActive() must drive the pin low |
| CAN | Loopback mode, then TrySendMessage() until CAN_ERR_QUEUE_FULL, then GetBusStatus() |
| On hardware | Port the matching ESP32 test application from examples/esp32/main |
The ESP32 classes are the reference implementation; the STM32 classes show the CubeMX-handle style.
Next: Utilities.