本指南详细介绍了如何为 RoboMaster C 型开发板(后文简称“C 板”)移植 openvela 操作系统。C 板的主控芯片为 STM32F407IGH6。由于 NuttX 官方已提供对 STM32F407IG 系列芯片的支持,本指南将重点阐述板级支持包(Board Support Package, BSP)的适配过程。
完成本教程后,您将能够为一个新的硬件平台创建基础的 BSP,并成功运行 openvela 系统,实现对板载 LED 的控制。
您将学到什么
- openvela 的板级移植核心思想与启动流程。
- 如何实现板级初始化所需的关键 C 语言接口函数。
- 如何配置 GPIO,并为系统状态和用户应用编写 LED 驱动。
- 如何通过 Kconfig 将新开发板集成到 openvela 的构建系统中。
- 如何为新开发板创建 defconfig 默认配置文件。
准备工作
在开始之前,请确保您已完成以下准备工作:
- 获取源码:参考文档快速入门下载最新代码。
- 了解 openvela 架构:建议您预先阅读 openvela 架构以理解其分层设计。
- 查阅系统启动流程文档,获取更详细的启动时序和函数调用关系图。
一、移植原理与启动流程
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_INITIALIZE ,stm32_bringup() 将由 board_late_initialize() 调用。
- 如果没有打开 CONFIG_BOARD_LATE_INITIALIZE,stm32_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.h 和 include/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 用于管理内核和应用的功能配置。您需要执行以下三步来集成新板。
-
在 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. -
在 nuttx/boards/Kconfig 中设置默认板名:
当选中您的板型时,让 ARCH_BOARD 变量自动设置为您的 BSP 目录名。
config ARCH_BOARD string # ... 其他板的 default 设置 ... default "stm32f407-robomaster" if ARCH_BOARD_STM32F407_RM -
在 nuttx/boards/Kconfig 中加载板级 Kconfig 文件:
确保选中您的板型时,会加载 BSP 目录下的 Kconfig 文件。
if ARCH_BOARD_STM32F407_RM source "boards/arm/stm32/stm32f407-robomaster/Kconfig" endif -
在 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
说明:关于 Kconfig 和 defconfig 的更多用法,请参考 Kconfig 使用指南 。
四、总结
至此,您已经完成了为 RoboMaster C 型开发板移植 openvela 的核心步骤。您创建了一个包含启动逻辑、驱动实现和构建系统集成的完整 BSP。不要忘记在 scripts/ 目录下提供正确的链接器脚本 ld.script 和 Make.defs 文件。
完成所有文件后,您就可以编译并烧录固件。
五、后续步骤
成功将 openvela 移植到 C 板后,您可以尝试运行一个应用程序来验证您的工作:
- 点亮 LED:参考在 STM32F411 上使用 openvela 点亮 LED的应用层代码,编写一个简单的程序来控制 C 板上的用户 LED。