VxWorks 7是Wind River推出的一款现代实时操作系统(RTOS),其BSP(Board Support Package)开发过程在模块化和工具支持上有了显著改进。BSP是硬件与操作系统之间的桥梁,负责硬件初始化、设备驱动和系统配置。本文将详细介绍在VxWorks 7上开发BSP的步骤,并提供技术细节和代码示例。
BSP的核心组件 #
在VxWorks 7中,一个完整的BSP通常包含以下文件和功能:
- romInit.s:汇编语言编写的启动代码,用于最低级别的硬件初始化。
- sysLib.c:系统库,提供硬件相关的核心函数(如时钟、中断控制)。
- sysALib.s:汇编工具函数,通常与
sysLib.c
配合使用。 - config.h:硬件配置头文件,定义编译选项和硬件参数。
- Makefile:编译脚本,控制BSP构建过程。
开发环境准备 #
- 安装Wind River Workbench:确保安装最新版本的WorkBench(例如4.6或更高版本),支持VxWorks 7。
- 参考BSP:从Wind River提供的模板(如wrSbcArmv8或intel_x86_64)开始,复制到新项目目录。
- 硬件文档:获取目标板卡的芯片手册(如NXP i.MX8的数据手册),明确CPU架构、内存地址和外设寄存器。
详细开发步骤 #
创建BSP项目 #
在WorkBench中选择“File > New > VxWorks Board Support Package”,输入项目名称(如myBsp)和目标架构(如ARMv8)。生成的项目包含以下基础文件:
- romInit.s:启动入口。
- sysLib.c:系统函数。
- config.h:配置文件。
定制硬件初始化 #
- 修改启动代码 (romInit.s)
.section .text
.globl romInit
romInit:
/* 设置异常向量表基地址 */
ldr x0, =_vector_table
msr VBAR_EL1, x0
/* 初始化栈指针 */
ldr x0, =__stack_top
mov sp, x0
/* 配置时钟(PLL) */
ldr x0, =0x40000000 /* 时钟控制寄存器地址 */
ldr x1, =0x00001234 /* PLL配置值 */
str x1, [x0]
/* 跳转到C代码 */
bl sysInit
b .
- 说明:这段代码设置异常向量表,初始化栈,并配置系统时钟(具体值需参考硬件手册)。最后跳转到
sysInit
函数。
- 配置内存映射 (sysLib.c)
在sysLib.c中定义内存布局:
#include "vxWorks.h"
#include "sysLib.h"
LOCAL char *sysPhysMemTop = (char *)0x80000000; /* 假设DRAM起始地址 */
LOCAL UINT32 sysMemSize = 0x10000000; /* 256MB内存 */
void sysHwInit(void)
{
/* 初始化内存控制器 */
*(volatile UINT32 *)0x40001000 = 0x00000101; /* 内存控制寄存器 */
}
char *sysMemTop(void)
{
return sysPhysMemTop;
}
- 说明:sysHwInit初始化硬件,sysMemTop返回内存顶端地址。寄存器地址和值需根据硬件手册调整。
- 中断初始化
为ARM GIC(Generic Interrupt Controller)配置中断:
void sysIntInit(void)
{
/* 启用GIC分发器 */
*(volatile UINT32 *)0xF9000000 = 0x1; /* GICD_CTLR */
/* 配置IRQ优先级 */
*(volatile UINT32 *)0xF9001000 = 0xA0; /* GICD_IPRIORITYR */
}
- 说明:具体地址和值需参考GIC手册(如ARM GICv3规范)。
实现串口驱动 #
以UART驱动为例,假设目标硬件使用16550兼容串口:
#include "drv/serial/serial.h"
#define UART_BASE 0xF8000000
#define UART_THR (UART_BASE + 0x00) /* 发送寄存器 */
#define UART_RBR (UART_BASE + 0x00) /* 接收寄存器 */
#define UART_LSR (UART_BASE + 0x14) /* 状态寄存器 */
void uartInit(void)
{
/* 设置波特率115200,8N1 */
*(volatile UINT32 *)(UART_BASE + 0x0C) = 0x83; /* LCR */
*(volatile UINT32 *)(UART_BASE + 0x00) = 0x0C; /* DLL */
*(volatile UINT32 *)(UART_BASE + 0x04) = 0x00; /* DLM */
*(volatile UINT32 *)(UART_BASE + 0x0C) = 0x03; /* LCR */
}
int uartPutChar(char c)
{
while (!(*(volatile UINT32 *)UART_LSR & 0x20)); /* 等待发送缓冲区空 */
*(volatile UINT32 *)UART_THR = c;
return 1;
}
int uartGetChar(void)
{
if (*(volatile UINT32 *)UART_LSR & 0x01) /* 检查数据是否就绪 */
return *(volatile UINT32 *)UART_RBR;
return EOF;
}
- 说明:这段代码初始化UART并提供基本的收发函数。寄存器偏移量和配置值需匹配硬件。
调整配置文件 (config.h) #
启用必要组件并定义硬件参数:
#define CPU _VX_ARMV8A /* ARMv8-A架构 */
#define SYS_CLK_RATE 1000000 /* 系统时钟1MHz */
#define INCLUDE_SERIAL /* 启用串口支持 */
#define DEFAULT_BOOT_LINE "uart(0,115200)"
编译与调试 #
- 在WorkBench中选择“Build > Build Project”,生成vxWorks镜像。
- 使用JTAG(如Segger J-Link)烧录镜像到目标板。
- 通过串口终端(Tera Term或Minicom)观察启动日志:
VxWorks 7.0
BSP Version: 1.0
CPU: ARMv8-A
Memory Size: 256MB
- 使用WorkBench调试器设置断点,验证
uartPutChar
等函数。
优化与测试 #
- 测试外设功能(如串口发送“Hello, VxWorks!”)。
- 使用System Viewer分析性能瓶颈,优化中断处理或内存访问。
串口驱动开发注意事项 #
- 异常处理:在romInit.s中正确设置异常向量,避免系统崩溃。
- 驱动复用:将驱动封装为VxWorks组件(如INCLUDE_MYSERIAL),便于跨项目使用。
- 硬件调试:若遇到问题,使用示波器或逻辑分析仪检查信号完整性。
- 文档化:记录每个寄存器的配置依据,方便团队协作。
总结 #
VxWorks 7的BSP开发结合了强大的工具支持和灵活的模块化设计。通过定制启动代码、实现驱动和配置系统,开发者可以将操作系统无缝适配到目标硬件。上述代码示例基于ARMv8架构,但原理适用于其他平台(如PowerPC或x86)。借助WorkBench的调试功能和Wind River的文档,这一过程既高效又可控。
实现网络驱动 #
网络驱动开发是BSP中的高级任务,通常基于VxWorks的**END(Enhanced Network Driver)**框架。以下以NXP i.MX8的ENET控制器为例,逐步实现。
- 网络驱动框架概述
VxWorks 7使用END框架与网络栈(如TCP/IP)交互。END驱动需要实现以下核心函数:
- xxxInit:初始化硬件。
- xxxSend:发送数据包。
- xxxRecv:接收数据包。
- xxxIoctl:控制接口(如设置MAC地址)。
- xxxStart/xxxStop:启动/停止设备。
- 定义驱动数据结构
在myEnet.c中定义驱动私有数据:
#include "endLib.h"
#include "muxLib.h"
#define ENET_BASE 0x5B040000 /* 以太网基地址 */
#define ENET_TX_DESC 0x5B041000 /* 发送描述符地址 */
#define ENET_RX_DESC 0x5B042000 /* 接收描述符地址 */
typedef struct {
END_OBJ endObj; /* END对象,必须第一个 */
UINT32 baseAddr; /* 控制器基地址 */
UINT8 macAddr[6]; /* MAC地址 */
BOOL running; /* 运行状态 */
M_BLK_ID txQueue; /* 发送队列 */
M_BLK_ID rxQueue; /* 接收队列 */
} MY_ENET_DEV;
- 初始化网络硬件 (myEnetInit)
LOCAL MY_ENET_DEV *pEnetDev = NULL;
STATUS myEnetInit(MY_ENET_DEV *pDev)
{
/* 分配设备结构 */
pDev = (MY_ENET_DEV *)malloc(sizeof(MY_ENET_DEV));
if (!pDev) return ERROR;
pDev->baseAddr = ENET_BASE;
pDev->running = FALSE;
/* 设置默认MAC地址 */
pDev->macAddr[0] = 0x00; pDev->macAddr[1] = 0x1A;
pDev->macAddr[2] = 0x2B; pDev->macAddr[3] = 0x3C;
pDev->macAddr[4] = 0x4D; pDev->macAddr[5] = 0x5E;
/* 初始化硬件寄存器 */
*(volatile UINT32 *)(ENET_BASE + 0x10) = 0x1; /* 启用控制器 */
*(volatile UINT32 *)(ENET_BASE + 0x14) = 0x3; /* 100Mbps,全双工 */
/* 初始化描述符环(DMA) */
*(volatile UINT32 *)ENET_TX_DESC = 0x80000000; /* 标记描述符就绪 */
*(volatile UINT32 *)ENET_RX_DESC = 0x80000000;
return OK;
}
- 说明:初始化包括设置MAC地址、启用控制器和配置DMA描述符。寄存器地址需参考硬件手册。
- 发送数据包 (myEnetSend)
STATUS myEnetSend(MY_ENET_DEV *pDev, M_BLK_ID pMblk)
{
if (!pDev->running) return ERROR;
/* 将数据写入发送缓冲区 */
char *data = netMblkToBufCopy(pMblk, NULL, NULL);
*(volatile UINT32 *)(ENET_BASE + 0x20) = (UINT32)data; /* 数据地址 */
*(volatile UINT32 *)(ENET_BASE + 0x24) = pMblk->mBlkHdr.mLen; /* 数据长度 */
/* 触发发送 */
*(volatile UINT32 *)(ENET_BASE + 0x28) = 0x1;
/* 释放M_BLK */
netMblkFree(pMblk);
return OK;
}
- 说明:从M_BLK提取数据,写入硬件缓冲区并触发发送。
- 接收数据包 (myEnetRecv)
LOCAL void myEnetHandleRecv(MY_ENET_DEV *pDev)
{
M_BLK_ID pMblk;
/* 检查接收状态 */
if (*(volatile UINT32 *)(ENET_BASE + 0x30) & 0x1) {
/* 分配M_BLK */
pMblk = netMblkAlloc();
if (!pMblk) return;
/* 从硬件读取数据 */
UINT32 len = *(volatile UINT32 *)(ENET_BASE + 0x34);
char *data = (char *)(*(volatile UINT32 *)(ENET_BASE + 0x38));
netMblkFromBufCopy(pMblk, data, len);
/* 上报给网络栈 */
muxReceive(&pDev->endObj, pMblk);
/* 清除接收标志 */
*(volatile UINT32 *)(ENET_BASE + 0x30) = 0x0;
}
}
- 说明:通过中断或轮询检测接收状态,将数据封装为M_BLK并传递给网络栈。
- 启动设备 (myEnetStart)
STATUS myEnetStart(MY_ENET_DEV *pDev)
{
if (pDev->running) return OK;
/* 启用中断 */
*(volatile UINT32 *)(ENET_BASE + 0x40) = 0x3; /* 启用发送/接收中断 */
intEnable(IRQ_ENET); /* 启用IRQ,假设中断号为IRQ_ENET */
pDev->running = TRUE;
return OK;
}
- 注册驱动到网络栈
END_OBJ *myEnetLoad(char *initString, void *pArg)
{
MY_ENET_DEV *pDev;
if (myEnetInit(pDev) == ERROR) return NULL;
/* 绑定到MUX */
if (endLoad(initString, &pDev->endObj, myEnetStart, myEnetStop,
myEnetSend, myEnetRecv, myEnetIoctl) == ERROR) {
free(pDev);
return NULL;
}
return &pDev->endObj;
}
void myEnetRegister(void)
{
muxDevLoad(0, myEnetLoad, "", FALSE, NULL);
muxDevStart(0);
}
- 说明:myEnetLoad将驱动注册到MUX(Multiplexor),使其与TCP/IP栈对接。
调整配置文件 (config.h) #
启用网络支持:
#define INCLUDE_END /* 启用END框架 */
#define INCLUDE_MUX /* 启用MUX层 */
#define INCLUDE_IPV4 /* 启用IPv4支持 */
#define INCLUDE_IFCONFIG /* 启用ifconfig命令 */
#define MY_ENET_UNIT 0 /* 网络设备单元号 */
编译与测试 #
- 编译BSP,生成vxWorks镜像。
- 烧录并启动,连接以太网线。
- 在VxWorks shell中测试:
-> ifconfig("myenet0", "192.168.1.100", "255.255.255.0")
-> ping("192.168.1.1")
- 检查日志输出,确保发送/接收正常。
网络驱动开发注意事项 #
- DMA管理:确保描述符环正确初始化,避免数据丢失。
- 中断处理:为高吞吐量场景优化中断频率,必要时使用NAPI-like轮询。
- 性能测试:使用iperf测试带宽,验证驱动效率。
- 兼容性:确保驱动与VxWorks网络栈(如LwIP或BSD栈)无缝集成。
总结 #
网络驱动的开发是BSP中的复杂任务,但通过END框架和VxWorks的模块化设计,可以高效实现。以太网驱动涉及硬件初始化、DMA管理和数据收发,需要开发者深入理解硬件手册和网络协议。