跳过正文

VxWorks BSP 板级支持包开发手册

VxWorks BSP Handbook
目录

1. 引言
#

板级支持包(BSP) 是 VxWorks 的硬件抽象层,负责在系统启动时初始化处理器、内存、时钟、中断控制器和外设,然后将控制权移交至操作系统内核。如果没有合适的 BSP,VxWorks 无法在您的硬件上运行。

BSP 通常高度依赖特定硬件,但共享一些通用组件,例如:

  • 早期处理器初始化
  • 内存管理(内存管理单元设置)
  • 设备初始化和配置
  • 启动中断控制器
  • 通过 VxBus 支持设备驱动程序

由于 BSP 开发涉及使用 C 语言和汇编语言的低级编程,理解处理器手册和电路板原理图至关重要。

2. BSP 开发工作流程
#

2.1 硬件文档
#

首先收集以下资料:

  • 处理器/片上系统参考手册
  • 内存映射和引脚复用的电路板原理图
  • 外设数据手册,包含寄存器和中断信息

2.2 选择起始 BSP
#

通常可以找到适用于相似处理器的 VxWorks BSP。例如,可以通过更新内存和设备树条目,将基于 Cortex-A9 的 BSP 适配到新的 A9 板上。

2.3 移植 BSP
#

  • 内存映射:调整物理和虚拟内存范围,确保内存管理单元设置正确。
  • 时钟和锁相环设置:根据外设需求初始化板上时钟。
  • 串口/控制台设置:用于早期调试输出。
  • 中断控制器:初始化中断控制器以处理设备中断请求(IRQ)。
  • 设备树:描述所有硬件外设。

2.4 构建与测试
#

  • 使用 Wind River 的 makevxprj 工具构建 BSP。
  • 使用 JTAG 或串口控制台调试启动过程。
  • 使用 VxWorks shell 命令(devsild)进行运行时诊断。

3. BSP 目录结构
#

示例布局:

your_bsp/
├── Makefile          # 构建规则和编译器标志
├── config.h          # BSP 全局定义(处理器频率、内存大小)
├── sysLib.c          # 系统初始化函数
├── sysClk.c          # 系统时钟(定时器)支持
├── sysSerial.c       # 串口驱动接口
├── hwconf.c          # 硬件资源配置表
├── device-tree/      # 设备树源文件 (*.dts)
│   ├── myboard.dts
│   └── ...
└── README.md

3.1 sysLib.c
#

包含处理器设置、内存初始化和系统启动入口点,例如:

void sysHwInit(void)
{
    /* 禁用中断 */
    vxCpuIntDisable();

    /* 初始化内存控制器 */
    sysMemInit();

    /* 初始化串口以进行早期调试 */
    sysSerialHwInit();

    /* 设置中断控制器 */
    sysIntInit();

    /* 设置系统定时器 */
    sysClkInit();

    /* 启用中断 */
    vxCpuIntEnable();
}

3.2 config.h
#

定义板卡参数,如时钟频率、内存大小和 BSP 配置宏。示例:

#define SYS_CLK_RATE        100     /* 100 ticks per second */
#define LOCAL_MEM_LOCAL_ADRS 0x80000000
#define LOCAL_MEM_SIZE       0x10000000  /* 256MB */
#define UART_BASE_ADDR       0x10000000
#define UART_BAUD_RATE       115200

4. BSP 核心组件
#

4.1 引导加载程序概述
#

引导加载程序的任务是执行非常早期的系统初始化并将 VxWorks 镜像加载到内存中。通常,这是一个小型程序或 U-Boot 变体,负责:

  • 配置处理器模式(缓存、内存管理单元开启或关闭)
  • 设置初始动态随机存储器(DRAM)控制器
  • 初始化串口以进行调试输出
  • 从闪存、网络或 SD 卡加载内核镜像
  • 跳转到 VxWorks 内核的 sysStart() 入口

4.2 早期初始化代码示例
#

void sysHwInit0(void)
{
    /* 全局禁用中断 */
    vxCpuIntDisable();

    /* 初始化时钟 */
    clkInit();

    /* 初始化串口以进行早期控制台输出 */
    uartInit(UART_BASE_ADDR, UART_BAUD_RATE);

    /* 打印启动信息 */
    printf("BSP early initialization complete.\n");
}

4.3 引导加载程序早期初始化
#

void sysHwInit0(void)
{
    vxCpuIntDisable();
    *(volatile uint32_t *)0xF8006000 = 0x12345678;  /* DDR 时序 */
    *(volatile uint32_t *)0xF8006004 = 0x0000AABB;
    pllConfig(0x1F, 0x2A);
    uartInit(0x9000000, 115200);
    printf("Board early init done, UART ready.\n");
}

4.4 sysStart() 入口点
#

sysStart() 函数是引导加载程序加载操作系统镜像后调用的内核启动例程。通常它执行进一步的硬件初始化,然后调用用户应用程序入口 usrRoot()

5. 设备树配置
#

5.1 什么是设备树?
#

设备树(DT)是一种以平台无关方式描述系统硬件组件的数据结构。设备树避免在 BSP 代码中硬编码硬件参数,允许操作系统和驱动程序在启动时动态发现和配置设备。

VxWorks 使用从 .dts 源文件编译的设备树二进制文件(.dtb)来初始化硬件资源并通过 VxBus 绑定驱动程序。

5.2 设备树语法示例
#

/ {
    uart0: serial@10000000 {
        compatible = "ns16550";
        reg = <0x10000000 0x1000>;
        interrupts = <5>;
        clock-frequency = <24000000>;
    };

    timer0: timer@10002000 {
        compatible = "arm,armv7-timer";
        reg = <0x10002000 0x1000>;
        interrupts = <30>;
    };
};
  • compatible:标识设备类型或绑定的驱动程序
  • reg:设备寄存器的基地址和大小
  • interrupts:中断请求号(IRQ)
  • 其他属性(例如时钟频率)用于配置设备

5.3 在 BSP 中集成设备树
#

  • .dts 文件放置在 bsp/your_bsp/device-tree/ 目录
  • 在 BSP Makefile 中添加设备树编译,使用 dtc(设备树编译器)
  • 在 BSP 初始化代码中加载并解析设备树二进制文件,然后探测设备

hwconf.c 示例(伪代码):

void hwconfInit(void)
{
    dtb = loadDeviceTreeBlob("/boot/myboard.dtb");
    vxBusInit(dtb);
}

5.4 复杂设备树片段
#

i2c1: i2c@40800000 {
    compatible = "arm,my-i2c";
    reg = <0x40800000 0x1000>;
    interrupts = <23>;
    clock-frequency = <100000>;

    temp_sensor@48 {
        compatible = "ti,tmp102";
        reg = <0x48>;
    };

    eeprom@50 {
        compatible = "at,24c256";
        reg = <0x50>;
    };
};

6. 内存管理单元与缓存设置
#

6.1 内存管理单元设置的重要性
#

内存管理单元(MMU)控制虚拟内存转换、访问权限和缓存属性。正确的内存管理单元配置对于以下方面至关重要:

  • 保护内存区域
  • 启用缓存以提高性能
  • 将外设映射为非缓存区域

6.2 定义物理内存区域
#

使用 VM_REGION 结构在 sysPhysMemDesc[] 中描述内存布局。示例:

VM_REGION sysPhysMemDesc[] = {
    {
        (VIRT_ADDR) LOCAL_MEM_LOCAL_ADRS,
        (PHYS_ADDR) LOCAL_MEM_LOCAL_ADRS,
        LOCAL_MEM_SIZE,
        VM_STATE_MASK_VALID | VM_STATE_MASK_WRITABLE,
        VM_STATE_VALID | VM_STATE_WRITABLE
    },
    {
        (VIRT_ADDR) PERIPH_BASE_ADDR,
        (PHYS_ADDR) PERIPH_BASE_ADDR,
        PERIPH_SIZE,
        VM_STATE_MASK_VALID | VM_STATE_MASK_WRITABLE | VM_STATE_MASK_CACHEABLE,
        VM_STATE_VALID | VM_STATE_WRITABLE /* 无缓存 */
    }
};

6.3 启用缓存
#

在启动代码中尽早启用指令和数据缓存:

void cacheEnable(void)
{
    cacheEnable(INSTRUCTION_CACHE);
    cacheEnable(DATA_CACHE);
}

6.4 直接内存访问(DMA)缓冲区的缓存管理
#

对于处理器和 DMA 设备共享的缓冲区,刷新或使缓存失效以确保一致性:

void prepareDmaBuffer(void *buffer, size_t size)
{
    cacheFlush(DATA_CACHE, buffer, size);
}

7. 中断与定时器初始化
#

7.1 中断控制器设置
#

BSP 必须初始化中断控制器(例如 ARM 上的 GIC),以:

  • 路由设备中断请求
  • 启用/禁用中断
  • 设置中断优先级

sysIntInit() 示例:

void sysIntInit(void)
{
    gicInit();
    gicEnableDistributor();
}

7.2 连接中断服务例程(ISR)
#

使用 intConnect() 将中断向量与中断服务例程关联:

STATUS sysSerialIntConnect(void)
{
    return intConnect(INUM_TO_IVEC(UART_INT_VEC), (VOIDFUNCPTR)sysSerialIntHandler, 0);
}

7.3 系统时钟初始化
#

系统时钟驱动操作系统滴答和定时。典型的 BSP 设置:

STATUS sysClkConnect(FUNCPTR routine, int arg)
{
    sysClkRoutine = routine;
    sysClkArg = arg;
    return OK;
}

void sysClkInt(void)
{
    if (sysClkRoutine)
        sysClkRoutine(sysClkArg);
}

STATUS sysClkEnable(void)
{
    timerEnable(TIMER0, SYS_CLK_RATE);
    intConnect(INUM_TO_IVEC(TIMER0_INT_VEC), sysClkInt, 0);
    intEnable(TIMER0_INT_VEC);
    return OK;
}

7.4 ARM GIC 中断设置
#

void sysIntInit(void)
{
    gicDistInit();
    gicCpuInit();
    gicDistEnable();
    gicSetPriority(UART_INT_VEC, 0x80);
    gicEnableInterrupt(UART_INT_VEC);
    intConnect(INUM_TO_IVEC(UART_INT_VEC), (VOIDFUNCPTR)uartIsr, 0);
    intEnable(UART_INT_VEC);
}

8. 添加设备驱动程序
#

8.1 VxBus 框架
#

VxWorks 驱动程序由 VxBus 管理,VxBus 使用设备树信息动态探测和绑定驱动程序。

8.2 示例:串口驱动程序初始化
#

#include <vxBusLib.h>
#include <hwif/vxbus/vxBus.h>

LOCAL VXB_DEV_ID uartDev;

STATUS sysSerialHwInit(void)
{
    uartDev = vxbInstByNameFind("ns16550", 0);
    if (!uartDev)
        return ERROR;
    return OK;
}

8.3 驱动程序探测与绑定方法
#

驱动程序定义探测和绑定回调函数,供 VxBus 在设备枚举时调用:

LOCAL STATUS myUartProbe(VXB_DEV_ID pDev)
{
    /* 验证硬件存在 */
    return OK;
}

LOCAL STATUS myUartAttach(VXB_DEV_ID pDev)
{
    /* 映射寄存器,初始化设备 */
    return OK;
}

LOCAL VXB_DRV_METHOD myUartMethods[] = {
    { VXB_DEVMETHOD_CALL(vxbDevProbe), (FUNCPTR)myUartProbe },
    { VXB_DEVMETHOD_CALL(vxbDevAttach), (FUNCPTR)myUartAttach },
    VXB_DEVMETHOD_END
};

VXB_DRV_DEF(myUartDrv, myUartMethods, "My UART Driver");

8.4 串口驱动程序探测与绑定
#

LOCAL STATUS uartProbe(VXB_DEV_ID pDev)
{
    volatile uint32_t *reg = (volatile uint32_t *)vxbRegBaseAddr(pDev);
    if ((*reg & 0xFF) != EXPECTED_UART_ID)
        return ERROR;
    return OK;
}

LOCAL STATUS uartAttach(VXB_DEV_ID pDev)
{
    volatile uint32_t *base = (volatile uint32_t *)vxbRegBaseAddr(pDev);
    base[UART_BAUD_REG] = UART_BAUD_115200;
    base[UART_CTRL_REG] = UART_ENABLE | UART_RX_INT_ENABLE;
    return OK;
}

9. BSP 调试与测试
#

9.1 使用 VxWorks Shell
#

VxWorks shell 提供命令用于检查和调试 BSP 及驱动程序:

  • devs — 列出 VxBus 识别的设备
  • i — 显示活动中断
  • ld — 列出已加载的模块/驱动程序
  • sp — 启动任务以测试驱动程序

示例:

-> devs
ns16550@10000000 (serial)
timer@10002000

9.2 WindView 事件追踪
#

WindView 允许追踪中断和上下文切换等事件,以分析 BSP 性能。在关键 BSP 代码段中集成 WindView 宏以进行细粒度分析。

9.3 硬件调试器
#

使用 JTAG 或 BDI 进行:

  • 逐步执行启动代码
  • 检查寄存器和内存
  • 在 BSP 代码(如 sysHwInit)中设置断点

10. 最佳实践
#

  • 从相近的 BSP 开始:节省时间并减少错误
  • 增量测试:分别测试每个阶段 — 内存、控制台、中断、定时器
  • 使用设备树:避免硬编码硬件描述,便于维护
  • 记录一切:内存映射、中断请求、时钟设置
  • 隔离 BSP 和应用程序代码:保持模块化设计

11. 高级 BSP 主题
#

11.1 多核(SMP)启动
#

在 VxWorks 中启用 SMP
#

定义处理器数量并启用 SMP 配置宏:

#define _WRS_CONFIG_SMP 1
#define VX_SMP_NUM_CPUS 4

启动次级处理器
#

BSP 必须启动次级核心并初始化其内核数据:

void sysSecondaryCpuStart(void)
{
    sysSecondaryCpuInit();
    kernelCpuInit();
}

每个次级处理器运行此代码以加入 SMP 内核。

11.2 添加自定义外设驱动程序
#

设备树示例
#

spi1: spi@40013000 {
    compatible = "myvendor,myspi";
    reg = <0x40013000 0x1000>;
    interrupts = <12>;
    bus-frequency = <48000000>;
};

驱动程序框架
#

LOCAL STATUS myspiProbe(VXB_DEV_ID pDev) { return OK; }
LOCAL STATUS myspiAttach(VXB_DEV_ID pDev) { return OK; }

LOCAL VXB_DRV_METHOD myspiMethods[] = {
    { VXB_DEVMETHOD_CALL(vxbDevProbe),  (FUNCPTR)myspiProbe },
    { VXB_DEVMETHOD_CALL(vxbDevAttach), (FUNCPTR)myspiAttach },
    VXB_DEVMETHOD_END
};

VXB_DRV_DEF(myspiDrv, myspiMethods, "MyVendor SPI Driver");

11.3 性能优化
#

  • 启用缓存并明确管理 DMA 缓冲区的一致性
  • 使用快速中断连接(intConnect)并仅启用必要的中断请求
  • 将设备映射为非缓存内存区域以避免数据陈旧
  • 通过保持中断服务例程简短并推迟工作来最小化中断延迟

11.4 启动次级处理器(ARM Cortex-A9)
#

void sysSecondaryCpuStart(void)
{
    *(volatile uint32_t *)CPU_RELEASE_ADDR = SECONDARY_CPU_START_ADDR;
    while(!secondaryCpuReady());
    kernelCpuInit();
}

12. 调试高级 BSP 问题
#

  • 多核调试:使用支持多核调试的 JTAG 工具;注意同步问题
  • 驱动程序调试:使用 VxBus 命令如 vxbDevShow() 并检查驱动程序探测/绑定返回值
  • 内存问题:仔细检查内存管理单元映射;使用 VxWorks 内存工具监控堆/栈

13. 高级最佳实践
#

  • 增量启动 SMP,逐个测试每个处理器
  • 使用设备树叠加层测试新硬件而无需重新编译 BSP
  • 设计确定性的中断服务例程并使用内核安全的 API
  • 为 BSP 驱动程序接口维护详细的 API 文档

14. 结论
#

掌握 VxWorks 的 BSP 开发可以完全控制嵌入式系统硬件。从基本启动初始化到高级 SMP 支持和自定义驱动程序集成,精心设计的 BSP 是可靠、高性能实时应用的基础。

通过模块化设计、完善的文档和彻底的测试,您的 BSP 可以支持复杂的硬件平台,并使 VxWorks 系统满足苛刻的实时要求。

相关文章

VxWorks 与 Linux 的 BSP 开发对比分析
VxWorks Linux BSP 嵌入式系统 设备驱动 RTOS
VxWorks 7.0 基于设备树的驱动程序编写方法
VxWorks 设备树 驱动开发 BSP RTOS GPIO
VxWorks内核、设备驱动与BSP开发详解(第2版)
VxWorks Kernel Device Driver BSP