Porting GuideΒΆ

This guide explains how to port Nexus to a new MCU platform.

OverviewΒΆ

Porting Nexus to a new platform involves implementing the Hardware Abstraction Layer (HAL) for your target MCU. The OSAL and middleware layers are platform-independent and require no modification.

PrerequisitesΒΆ

Before starting, ensure you have:

  • MCU datasheet and reference manual

  • Vendor SDK or register definitions

  • ARM GCC toolchain (for ARM Cortex-M targets)

  • Basic understanding of the target MCU architecture

Porting StepsΒΆ

Step 1: Create Platform DirectoryΒΆ

Create a new directory under platforms/:

platforms/
└── my_platform/
    β”œβ”€β”€ CMakeLists.txt
    β”œβ”€β”€ include/
    β”‚   └── platform_config.h
    β”œβ”€β”€ src/
    β”‚   β”œβ”€β”€ startup.c
    β”‚   β”œβ”€β”€ system_init.c
    β”‚   └── hal/
    β”‚       β”œβ”€β”€ hal_gpio.c
    β”‚       β”œβ”€β”€ hal_uart.c
    β”‚       └── ...
    └── linker/
        └── my_platform.ld

Step 2: Implement HAL DriversΒΆ

Implement each HAL module for your platform. Start with GPIO as it’s the simplest:

hal_gpio.c:

/**
 * \file            hal_gpio.c
 * \brief           GPIO HAL implementation for my_platform
 */

#include "hal/hal_gpio.h"
#include "my_platform_registers.h"

hal_status_t hal_gpio_init(hal_gpio_port_t port, uint8_t pin,
                           const hal_gpio_config_t* config)
{
    if (config == NULL) {
        return HAL_ERROR_NULL_POINTER;
    }

    if (port >= HAL_GPIO_PORT_MAX || pin > 15) {
        return HAL_ERROR_INVALID_PARAM;
    }

    /* Platform-specific initialization */
    GPIO_TypeDef* gpio = get_gpio_base(port);

    /* Configure direction */
    if (config->direction == HAL_GPIO_DIR_OUTPUT) {
        gpio->MODER |= (1 << (pin * 2));
    } else {
        gpio->MODER &= ~(3 << (pin * 2));
    }

    /* Configure pull-up/pull-down */
    /* ... */

    return HAL_OK;
}

hal_status_t hal_gpio_write(hal_gpio_port_t port, uint8_t pin,
                            hal_gpio_level_t level)
{
    GPIO_TypeDef* gpio = get_gpio_base(port);

    if (level == HAL_GPIO_LEVEL_HIGH) {
        gpio->BSRR = (1 << pin);
    } else {
        gpio->BSRR = (1 << (pin + 16));
    }

    return HAL_OK;
}

/* Implement remaining GPIO functions... */

Step 3: Create Startup CodeΒΆ

Implement startup code for your MCU:

startup.c:

/**
 * \file            startup.c
 * \brief           Startup code for my_platform
 */

#include <stdint.h>

/* Stack pointer (defined in linker script) */
extern uint32_t _estack;

/* Entry point */
extern int main(void);

/* Weak interrupt handlers */
void Default_Handler(void) { while(1); }
void Reset_Handler(void);
void NMI_Handler(void)      __attribute__((weak, alias("Default_Handler")));
void HardFault_Handler(void) __attribute__((weak, alias("Default_Handler")));
/* ... more handlers ... */

/* Vector table */
__attribute__((section(".isr_vector")))
const void* vector_table[] = {
    &_estack,
    Reset_Handler,
    NMI_Handler,
    HardFault_Handler,
    /* ... more vectors ... */
};

void Reset_Handler(void)
{
    /* Copy .data section */
    /* Zero .bss section */
    /* Call system init */
    /* Call main */
    main();
    while(1);
}

Step 4: Create Linker ScriptΒΆ

Create a linker script for your MCU:

my_platform.ld:

/* Memory layout */
MEMORY
{
    FLASH (rx)  : ORIGIN = 0x08000000, LENGTH = 512K
    RAM (rwx)   : ORIGIN = 0x20000000, LENGTH = 128K
}

/* Entry point */
ENTRY(Reset_Handler)

/* Sections */
SECTIONS
{
    .isr_vector :
    {
        . = ALIGN(4);
        KEEP(*(.isr_vector))
        . = ALIGN(4);
    } > FLASH

    .text :
    {
        . = ALIGN(4);
        *(.text)
        *(.text*)
        . = ALIGN(4);
    } > FLASH

    /* ... more sections ... */
}

Step 5: Add CMake ConfigurationΒΆ

Create CMakeLists.txt for your platform:

CMakeLists.txt:

# Platform: my_platform

set(PLATFORM_SOURCES
    src/startup.c
    src/system_init.c
    src/hal/hal_gpio.c
    src/hal/hal_uart.c
    src/hal/hal_spi.c
    src/hal/hal_i2c.c
    src/hal/hal_timer.c
)

add_library(platform_my_platform STATIC ${PLATFORM_SOURCES})

target_include_directories(platform_my_platform PUBLIC
    include
    ${CMAKE_SOURCE_DIR}/hal/include
)

target_link_libraries(platform_my_platform PUBLIC
    hal_interface
)

# Linker script
set(LINKER_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/linker/my_platform.ld)
target_link_options(platform_my_platform PUBLIC
    -T${LINKER_SCRIPT}
)

Step 6: Add Toolchain FileΒΆ

If needed, create a toolchain file:

cmake/toolchains/my_platform.cmake:

set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR arm)

set(CMAKE_C_COMPILER arm-none-eabi-gcc)
set(CMAKE_CXX_COMPILER arm-none-eabi-g++)

set(CMAKE_C_FLAGS_INIT "-mcpu=cortex-m4 -mthumb -mfloat-abi=hard")
set(CMAKE_CXX_FLAGS_INIT "-mcpu=cortex-m4 -mthumb -mfloat-abi=hard")

HAL Implementation ChecklistΒΆ

Implement these HAL modules (in recommended order):

Module

Priority

Notes

GPIO

Required

Start here, simplest module

System

Required

Clock init, delay functions

UART

Required

Essential for debugging

Timer

Required

Needed for OSAL timing

SPI

Optional

If SPI peripherals are used

I2C

Optional

If I2C peripherals are used

ADC

Optional

If analog inputs are used

Testing Your PortΒΆ

  1. Build the blinky example:

    cmake -B build \
        -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/my_platform.cmake \
        -DNEXUS_PLATFORM=my_platform
    cmake --build build
    
  2. Flash and verify LED blinks

  3. Run unit tests on native platform:

    cmake -B build-test -DNEXUS_PLATFORM=native -DNEXUS_BUILD_TESTS=ON
    cmake --build build-test
    ctest --test-dir build-test
    
  4. Test each HAL module individually

Reference ImplementationΒΆ

See platforms/stm32f4/ for a complete reference implementation.

Key files to study:

  • platforms/stm32f4/src/hal/hal_gpio.c - GPIO implementation

  • platforms/stm32f4/src/startup.c - Startup code

  • platforms/stm32f4/linker/stm32f407.ld - Linker script

  • platforms/stm32f4/CMakeLists.txt - CMake configuration

Common IssuesΒΆ

Linker errors about undefined symbols:

Ensure all HAL functions are implemented, even if as stubs.

Hard fault on startup:

Check vector table alignment and stack pointer initialization.

Interrupts not working:

Verify NVIC configuration and interrupt handler names.

Timing issues:

Ensure system clock is configured correctly.

Getting HelpΒΆ

If you encounter issues:

  1. Check the reference implementation

  2. Review MCU documentation

  3. Open an issue on GitHub with details about your platform