search
暂无数据
为 STM32F407 开发板移植 openvela更新时间:2026-02-07 10:57:52

本指南详细介绍了如何为 RoboMaster C 型开发板(后文简称“C 板”)移植 openvela 操作系统。C 板的主控芯片为 STM32F407IGH6。由于 NuttX 官方已提供对 STM32F407IG 系列芯片的支持,本指南将重点阐述板级支持包(Board Support Package, BSP)的适配过程。

完成本教程后,您将能够为一个新的硬件平台创建基础的 BSP,并成功运行 openvela 系统,实现对板载 LED 的控制。

您将学到什么

  • openvela 的板级移植核心思想与启动流程。
  • 如何实现板级初始化所需的关键 C 语言接口函数。
  • 如何配置 GPIO,并为系统状态和用户应用编写 LED 驱动。
  • 如何通过 Kconfig 将新开发板集成到 openvela 的构建系统中。
  • 如何为新开发板创建 defconfig 默认配置文件。

准备工作

在开始之前,请确保您已完成以下准备工作:

  1. 获取源码:参考文档快速入门下载最新代码。
  2. 了解 openvela 架构:建议您预先阅读 openvela 架构以理解其分层设计。
  3. 查阅系统启动流程文档,获取更详细的启动时序和函数调用关系图。

一、移植原理与启动流程

openvela 的移植本质上是为操作系统框架提供一套与特定硬件交互的接口。这些接口构成了板级支持包(BSP),它位于 nuttx/boards/ 目录下。本指南将通过实现一个最小功能的 BSP,让 openvela 在 C 板上启动并运行。

关键启动函数

openvela 在启动过程中,会按照特定顺序调用一系列由 BSP 提供的函数来完成硬件初始化。要成功启动系统,您必须在 BSP 中实现以下关键函数:

函数 描述
void stm32_boardinitialize(void) 由系统启动代码 __start 调用。这是最早执行的板级初始化函数,用于配置最基础的硬件,如时钟和调试串口。
void board_late_initialize(void) 如果配置了 CONFIG_BOARD_LATE_INITIALIZE,此函数会在 OS 初始化后期被调用,通常用于初始化那些依赖 OS 服务的驱动。
int stm32_bringup(void) 如果配置 CONFIG_BOARD_LATE_INITIALIZE 选项,由 board_late_initialize() 调用; 如果没有配置 CONFIG_BOARD_LATE_INITIALIZE 选项,由 board_app_initialize(arg) 调用。 在 stm32_bringup 里将实现驱动的初始化。
int board_app_initialize(uintptr_t arg) 由 boardctl() 接口通过 BOARDIOC_INIT 命令触发,用于执行应用层或用户自定义的初始化。

此外,如果您的系统配置 CONFIG_ARCH_LEDS 用于显示系统状态(如启动、Panic),则还需要实现以下函数:

函数 描述
void board_autoled_initialize(void) 初始化用于系统状态指示的 LED 引脚,通常设置为 GPIO 输出模式。
void board_autoled_on(int led) 根据系统状态(如 LED_STARTED)点亮指定的 LED。
void board_autoled_off(int led) 根据系统状态(如 LED_PANIC 结束)熄灭指定的 LED。

硬件配置:时钟与引脚

除了实现函数接口,您还需要在板级头文件 board.h 中定义宏,以配置 STM32 的时钟树、外设引脚功能等。这些宏将被芯片级的驱动代码(如 stm32_rcc.c)使用。

以下是 C 板时钟树的配置示例,它描述了如何将 12MHz 的外部高速晶振(HSE)通过 PLL 倍频至 168MHz 作为系统主时钟(SYSCLK)。

/* Clocking *****************************************************************/
/*
 * System Clock source           : PLL (HSE)
 * SYSCLK(Hz)                    : 168000000
 * HCLK(Hz)                      : 168000000
 * AHB Prescaler                 : 1
 * APB1 Prescaler                : 4
 * APB2 Prescaler                : 2
 * HSE Frequency(Hz)             : 12000000     (STM32_BOARD_XTAL)
 * PLLM                          : 12           (STM32_PLLCFG_PLLM)
 * PLLN                          : 336          (STM32_PLLCFG_PLLN)
 * PLLP                          : 2            (STM32_PLLCFG_PLLP)
 * PLLQ                          : 7            (STM32_PLLCFG_PLLQ)
 * Main regulator output voltage : Scale1 mode
 * Flash Latency(WS)             : 5
 * Prefetch Buffer               : ON
 * Instruction cache             : ON
 * Data cache                    : ON
 */

二、代码实现步骤

本节将指导您完成本项目的文件创建和代码编写。

1、创建代码目录结构

首先,在 nuttx/boards/arm/stm32/ 目录下,创建一个名为 stm32f407-robomaster 的新目录,并建立如下的子目录和文件结构。

代码路径: nuttx/boards/arm/stm32/stm32f407-robomaster/

stm32f407-robomaster
├── CMakeLists.txt
├── configs                           # defconfig 配置路径
│   ├── led
│   │   └── defconfig                 # LED 示例的默认配置
│   └── nsh
│       └── defconfig                # NuttShell (NSH) 的默认配置
├── include
│   └── board.h                       # 板级硬件配置头文件
├── Kconfig                           # 板级 Kconfig 配置文件
├── scripts                           # 链接脚本
│   ├── ld.script                     # 链接器脚本
│   └── Make.defs                     # 板级 Make 定义
└── src
    ├── CMakeLists.txt
    ├── Make.defs
    ├── stm32_appinit.c               # 实现 board_app_initialize
    ├── stm32_autoleds.c              # 实现系统状态 LED 控制
    ├── stm32_boot.c                  # 实现 stm32_boardinitialize
    ├── stm32_bringup.c               # 实现驱动初始化
    ├── stm32f407-robomaster.h        # 板级私有头文件,定义 GPIO
    └── stm32_userleds.c              # 实现用户层 LED 驱动接口

2、实现核心初始化函数

src/ 目录下,您需要创建并填充以下 C 文件。

stm32_boot.c:早期硬件初始化

此文件负责实现 stm32_boardinitialize(),用于在系统启动的最初阶段配置必要的硬件。

#include <nuttx/config.h>
#include "stm32f407-robomaster.h" // 板级私有定义

void stm32_boardinitialize(void)
{
#ifdef CONFIG_ARCH_LEDS
  /* 如果启用了系统状态LED,则初始化它们 */  
  board_autoled_initialize();
#endif
}


#ifdef CONFIG_BOARD_LATE_INITIALIZE
void board_late_initialize(void)
{
  /* 执行板级后期初始化 */
  stm32_bringup();
}
#endif

stm32_bringup.c:设备驱动初始化

此文件中的 stm32_bringup() 函数负责初始化并注册所有板载设备的驱动程序。在本例中,我们只注册用户 LED 驱动。

#include "stm32.h"

#ifdef CONFIG_USERLED
#  include <nuttx/leds/userled.h>
#endif

#include "stm32f407-robomaster.h"

int stm32_bringup(void)
{
  int ret = OK;

#ifdef CONFIG_USERLED
  /* 注册用户 LED 驱动,使其在 /dev/userleds 节点可用 */
  ret = userled_lower_initialize("/dev/userleds");
  if (ret < 0)
    {
      syslog(LOG_ERR, "ERROR: userled_lower_initialize() failed: %d\n", ret);
    }
#endif
  /* 在此添加其他驱动的初始化,例如 I2C, SPI, SDIO 等 */
  return ret;
}

stm32_appinit.c:应用初始化桥梁

主要实现 int board_app_initialize(uintptr_t arg) 函数:

  • 如果打开 CONFIG_BOARD_LATE_INITIALIZEstm32_bringup() 将由 board_late_initialize() 调用。
  • 如果没有打开 CONFIG_BOARD_LATE_INITIALIZEstm32_bringup() 将由 board_app_initialize(arg) 调用。
    int board_app_initialize(uintptr_t arg)
    {
    #ifdef CONFIG_BOARD_LATE_INITIALIZE
      /* 如果定义了后期初始化,bringup 已被调用,此处无需操作 */
      return OK;
    #else
      /* 否则,在此处调用 bringup 来初始化驱动 */
      return stm32_bringup();
    #endif
    }

3、实现 LED 驱动

openvela 将 LED 分为两类:一类用于指示系统状态(autoleds),另一类供用户应用程序控制(userleds)。

stm32_autoleds.c:系统状态 LED

该文件实现 board_autoled_* 系列函数,来控制实现 LED 的亮灭,由系统内核在特定事件(如启动、断言失败)发生时自动调用。

/****************************************************************************
 * Private Functions
 ****************************************************************************/

static inline void set_led(bool v)
{
  ledinfo("Turn LED %s\n", v? "on":"off");
  stm32_gpiowrite(GPIO_LEDR, v);
  stm32_gpiowrite(GPIO_LEDG, v);
  stm32_gpiowrite(GPIO_LEDB, v);
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/
#ifdef CONFIG_ARCH_LEDS
void board_autoled_initialize(void)
{
  /* 配置所有系统状态 LED 的 GPIO 为输出模式 */
  stm32_configgpio(GPIO_LEDR);
  stm32_configgpio(GPIO_LEDG);
  stm32_configgpio(GPIO_LEDB);
}

void board_autoled_on(int led)
{
  ledinfo("board_autoled_on(%d)\n", led);

  switch (led)
    {
    case LED_STARTED:      // OS 启动完成
    case LED_HEAPALLOCATE: // 堆内存分配失败
      /* As the board provides only one soft controllable LED, we simply
       * turn it on when the board boots.
       */

      set_led(true);
      break;

    case LED_PANIC:            // 系统 Panic
      /* 在 Panic 状态下,LED 通常会快速闪烁,由上层代码控制 */
      set_led(true);
      break;
    }
}

void board_autoled_off(int led)
{
  /* 实现与 board_autoled_on 对应的关灯逻辑 */
  switch (led)
    {
    case LED_PANIC:

      /* For panic state, the LED is blinking */

      set_led(false);
      break;
    }
}

#endif /* CONFIG_ARCH_LEDS */

stm32_userleds.c:用户应用 LED

该文件为通用的 LED 驱动(位于 drivers/leds/userled_lower.c)提供底层的硬件操作接口。应用程序通过标准的 open(), write(), ioctl() 等VFS接口访问 /dev/userleds 来控制这些 LED。

我们需要提供下面的函数接口给 userled_lower.c 调用:

#include <nuttx/config.h>
#include "stm32f407-robomaster.h"

#ifndef CONFIG_ARCH_LEDS

/* 定义板载用户 LED 对应的 GPIO 配置 */
static const uint32_t g_ledcfg[BOARD_NLEDS] =
{
  GPIO_LEDB,
  GPIO_LEDG,
  GPIO_LEDR
};

/****************************************************************************
 * Name: board_userled_initialize
 ****************************************************************************/

uint32_t board_userled_initialize(void)
{
  int i;

  /* 定义板载用户 LED 对应的 GPIO 配置 */
  for (i = 0; i < BOARD_NLEDS; i++)
    {
      stm32_configgpio(g_ledcfg[i]);
    }

  return BOARD_NLEDS;
}   
void board_userled(int led, bool ledon)
{
  /* 初始化用户 LED 的 GPIO */
  if ((unsigned)led < BOARD_NLEDS)
    {
      stm32_gpiowrite(g_ledcfg[led], ledon);
    }
}
  
void board_userled_all(uint32_t ledset)
{
  int i;

  /* Configure LED GPIOs for output */

  for (i = 0; i < BOARD_NLEDS; i++)
    {
      stm32_gpiowrite(g_ledcfg[i], (ledset & (1 << i)) != 0);
    }
}

最终,在 userled_upper.c 实现下面 LED 驱动接口:

static int     userled_open(FAR struct file *filep);
static int     userled_close(FAR struct file *filep);
static ssize_t userled_write(FAR struct file *filep, FAR const char *buffer,
                        size_t buflen);
static int     userled_ioctl(FAR struct file *filep, int cmd,
                         unsigned long arg);

4、定义硬件宏

src/stm32f407-robomaster.hinclude/board.h 中定义与硬件相关的宏。

stm32f407-robomaster.h:板级私有定义

此文件定义了板载外设的 GPIO 引脚和其他私有配置。

/* Configuration ************************************************************/

/* LED.  User LEDR: the red LED is a user LED connected to board LED D12
 * corresponding to MCU I/O PH10.
 *       User LEDG: the green LED is a user LED connected to board LED D12
 * corresponding to MCU I/O PH11.
 *       User LEDB: the blue LED is a user LED connected to board LED D12
 * corresponding to MCU I/O PH13.
 *
 * - When the I/O is HIGH value, the LED is on.
 * - When the I/O is LOW, the LED is off.
 */

#define GPIO_LEDB \
(GPIO_PORTH | GPIO_PIN10 | GPIO_OUTPUT_SET | GPIO_OUTPUT | GPIO_PULLUP | \
 GPIO_SPEED_50MHz)

#define GPIO_LEDG \
(GPIO_PORTH | GPIO_PIN11 | GPIO_OUTPUT_SET | GPIO_OUTPUT | GPIO_PULLUP | \
 GPIO_SPEED_50MHz)
 
#define GPIO_LEDR \
(GPIO_PORTH | GPIO_PIN12 | GPIO_OUTPUT_SET | GPIO_OUTPUT | GPIO_PULLUP | \
 GPIO_SPEED_50MHz)

 /* Buttons
 *
 * B1 USER: the user button is connected to the I/O PA0 of the STM32
 * microcontroller.
 */

#define MIN_IRQBUTTON   BUTTON_USER
#define MAX_IRQBUTTON   BUTTON_USER
#define NUM_IRQBUTTONS  (BUTTON_USER + 1)

#define GPIO_BTN_USER \
  (GPIO_INPUT |GPIO_PULLUP |GPIO_EXTI | GPIO_PORTA | GPIO_PIN0)

include/board.h:公共板级定义

此文件包含被 NuttX 内核和应用共享的板级定义,如时钟配置。

#ifndef __BOARDS_ARM_STM32_STM32F407_ROBOMASTER_INCLUDE_BOARD_H
#define __BOARDS_ARM_STM32_STM32F407_ROBOMASTER_INCLUDE_BOARD_H

/* 时钟配置宏 */
#define STM32_BOARD_XTAL        12000000ul
#define STM32_PLLCFG_PLLM       STM32_PLLCFG_PLLM_DIV(12)
#define STM32_PLLCFG_PLLN       STM32_PLLCFG_PLLN_MUL(336)
#define STM32_PLLCFG_PLLP       STM32_PLLCFG_PLLP_DIV(2)
#define STM32_PLLCFG_PLLQ       STM32_PLLCFG_PLLQ_DIV(7)

/* ...其他公共定义... */

#endif /* __BOARDS_ARM_STM32_STM32F407_ROBOMASTER_INCLUDE_BOARD_H */

三、集成到构建系统

完成代码编写后,需要修改构建系统配置,使 openvela 能够识别和编译您的新 BSP。

1、Kconfig 集成

Kconfig 用于管理内核和应用的功能配置。您需要执行以下三步来集成新板。

  1. nuttx/boards/Kconfig 中定义板级选项

    添加一个新的 config 条目,用于在配置菜单中显示您的开发板。

    config ARCH_BOARD_STM32F407_RM
        bool "STM32F407IGH6 ARM Development Board"    
        depends on ARCH_CHIP_STM32F407IG
        select ARCH_HAVE_LEDS
        select ARCH_HAVE_BUTTONS
        select ARCH_HAVE_IRQBUTTONS
        ---help---
            A configuration for the STM32F407 RoboMaster development board.

  2. nuttx/boards/Kconfig 中设置默认板名:

    当选中您的板型时,让 ARCH_BOARD 变量自动设置为您的 BSP 目录名。

    config ARCH_BOARD
        string
        # ... 其他板的 default 设置 ...
        default "stm32f407-robomaster"      if ARCH_BOARD_STM32F407_RM

  3. nuttx/boards/Kconfig 中加载板级 Kconfig 文件

    确保选中您的板型时,会加载 BSP 目录下的 Kconfig 文件。

    if ARCH_BOARD_STM32F407_RM
    source "boards/arm/stm32/stm32f407-robomaster/Kconfig"
    endif

  4. 在 stm32f407-robomaster/Kconfig 文件中可以添加此板特有的配置选项。

    我们这里以空框架演示:

    #
    # For a description of the syntax of this configuration file,
    # see the file kconfig-language.txt in the NuttX tools repository.
    #
        
    if ARCH_BOARD_STM32F407_RM
        
    endif # ARCH_BOARD_STM32F407_RM

2、创建默认配置 (defconfig)

defconfig 文件是一个最小化的系统配置集合,为用户提供了一个开箱即用的配置起点。您应该为每个核心功能(如 NSH、特定示例)提供一个 defconfig

nuttx/boards/arm/stm32/stm32f407-robomaster/configs/nsh/ 目录下的 defconfig 如下:

#
# This file is autogenerated: PLEASE DO NOT EDIT IT.
#
# You can use "make menuconfig" to make any modifications to the installed .config file.
# You can then do "make savedefconfig" to generate a new defconfig file that includes your
# modifications.
#
# CONFIG_ARCH_FPU is not set
# CONFIG_DISABLE_OS_API is not set
# CONFIG_NSH_ARGCAT is not set
# CONFIG_NSH_CMDOPT_HEXDUMP is not set
# CONFIG_NSH_DISABLE_IFCONFIG is not set
# CONFIG_NSH_DISABLE_PS is not set
# CONFIG_STM32_SYSCFG is not set
CONFIG_ARCH="arm"
CONFIG_ARCH_BOARD="stm32f407-robomaster"
CONFIG_ARCH_BOARD_STM32F407_RM=y
CONFIG_ARCH_CHIP="stm32"
CONFIG_ARCH_CHIP_STM32=y
CONFIG_ARCH_CHIP_STM32F407IG=y
CONFIG_ARCH_INTERRUPTSTACK=2048
CONFIG_ARCH_STACKDUMP=y
CONFIG_BOARD_LOOPSPERMSEC=8499
CONFIG_HAVE_CXX=y
CONFIG_INIT_ENTRYPOINT="nsh_main"
CONFIG_INTELHEX_BINARY=y
CONFIG_LINE_MAX=64
CONFIG_NSH_FILEIOSIZE=512
CONFIG_NSH_READLINE=y
CONFIG_PREALLOC_TIMERS=4
CONFIG_RAM_SIZE=114688
CONFIG_RAM_START=0x20000000
CONFIG_RAW_BINARY=y
CONFIG_RR_INTERVAL=200
CONFIG_SCHED_WAITPID=y
CONFIG_START_DAY=6
CONFIG_START_MONTH=6
CONFIG_START_YEAR=2020
CONFIG_STM32_FLASH_CONFIG_G=y
CONFIG_STM32_JTAG_SW_ENABLE=y
CONFIG_STM32_USART6=y
CONFIG_SYSTEM_NSH=y
CONFIG_TASK_NAME_SIZE=0
CONFIG_USART6_SERIAL_CONSOLE=y

说明:关于 Kconfigdefconfig 的更多用法,请参考 Kconfig 使用指南

四、总结

至此,您已经完成了为 RoboMaster C 型开发板移植 openvela 的核心步骤。您创建了一个包含启动逻辑、驱动实现和构建系统集成的完整 BSP。不要忘记在 scripts/ 目录下提供正确的链接器脚本 ld.scriptMake.defs 文件。

完成所有文件后,您就可以编译并烧录固件。

五、后续步骤

成功将 openvela 移植到 C 板后,您可以尝试运行一个应用程序来验证您的工作:

文档内容是否有帮助?
有帮助
无帮助