简介
设备驱动程序开发是嵌入式系统的核心。在业界领先的实时操作系统(RTOS)VxWorks 中,硬件驱动程序是使用 VxBus 框架实现的。
本指南将全面介绍 VxBus 驱动开发,涵盖从架构到实际实现的方方面面。无论您是 VxWorks 7 的新手,还是从旧版 BSP 风格迁移而来,本文都将帮助您掌握 VxBus,并构建高效、可维护的驱动程序。
为什么 VxBus 在 VxWorks 中如此重要 #
在早期版本的 VxWorks 中,驱动程序通常被硬编码到 BSP(板级支持包)中。这种方法使得维护困难,并且在硬件发生变化时可移植性有限。
为了解决这个问题,Wind River 公司引入了 VxBus——一个标准化、可扩展和模块化的驱动程序框架。
VxBus 的优势:
- 🔌 模块化 – 驱动程序与 BSP 解耦。
- ⚡ 高效 – 设备发现和初始化是自动化的。
- 🔄 可重用 – 同一个驱动程序可以支持多个平台。
- 🛠️ 可维护 – 代码更清晰,降低了 BSP 的复杂性。
- 🌍 标准化 – 支持设备树(Device Tree),确保与行业标准对齐。
VxBus 架构概览 #
VxBus 驱动模型借鉴了 Linux 等现代操作系统框架。它围绕着总线-驱动-设备的层次结构展开:
- 总线(Bus) – 代表一种互连方式(例如,PCI、I²C、SPI、USB)。
- 设备(Device) – 连接到总线的硬件实例,在**设备树(DTS)**中定义。
- 驱动(Driver) – 管理设备的软件逻辑。
在启动时,VxBus 会执行以下步骤:
- 读取设备树。
- 枚举设备。
- 将每个设备的
compatible
字符串与已注册的驱动程序进行匹配。 - 调用驱动程序的
probe
和attach
函数。
VxBus 驱动开发分步指南 #
1. 在设备树中定义您的设备 #
**设备树源文件(DTS)**是硬件描述的基石。例如,要定义一个 I²C GPIO 扩展器,可以这样做:
i2c@0x10000000 {
compatible = "nxp,pca9535";
reg = <0x10000000 0x1000>;
interrupts = <10 0>; // 适用的中断线
};
📌 提示: 尽可能使用供应商标准的 compatible
字符串。这可确保您的驱动程序具有可移植性,并与上游约定保持一致。
2. 实现驱动程序骨架 #
一个 VxBus 驱动程序通常包含 probe、attach 和 detach 方法。
LOCAL STATUS myDriverProbe(VXB_DEV_ID pDev) {
// 检查此驱动程序是否支持该设备
return OK;
}
LOCAL STATUS myDriverAttach(VXB_DEV_ID pDev) {
// 初始化寄存器、内存、中断等
return OK;
}
LOCAL STATUS myDriverDetach(VXB_DEV_ID pDev) {
// 释放资源,禁用中断
return OK;
}
LOCAL VXB_DRV_METHOD myDriverMethods[] = {
{ VXB_DEVMETHOD_CALL(vxbDevProbe), myDriverProbe },
{ VXB_DEVMETHOD_CALL(vxbDevAttach), myDriverAttach },
{ VXB_DEVMETHOD_CALL(vxbDevDetach), myDriverDetach },
VXB_DEVMETHOD_END
};
VXB_DRV myDriver = {
{ NULL },
"myDriver",
"示例 VxBus 驱动程序",
VXB_BUSID_PLB, // 总线类型
0,
sizeof(VXB_DEV_ID),
myDriverMethods,
NULL,
NULL,
NULL
};
VXB_DRV_DEF(myDriver);
3. 向 VxBus 注册驱动程序 #
在系统初始化期间添加驱动程序:
IMPORT VXB_DRV myDriver;
void sysHwInit(void) {
vxbDevRegister(&myDriver);
}
4. 处理中断 #
许多设备都依赖中断来实现高效操作。在 VxBus 中,您可以像这样请求一条 IRQ 中断线:
LOCAL void myIsr(void * arg) {
VXB_DEV_ID pDev = (VXB_DEV_ID) arg;
// 处理中断
}
LOCAL STATUS myDriverAttach(VXB_DEV_ID pDev) {
int irq = vxbIntConnect(pDev, 0, myIsr, pDev);
if (irq != OK) {
return ERROR;
}
vxbIntEnable(pDev, 0, myIsr, pDev);
return OK;
}
5. DMA 和缓冲区管理 #
对于网卡或存储控制器等高性能设备,直接内存访问(DMA)至关重要。
VxBus 提供了用于 DMA 安全内存分配的 API:
void * dmaBuf;
dmaBuf = cacheDmaMalloc(1024);
if (dmaBuf == NULL) {
return ERROR;
}
📌 始终使用 cacheDmaMalloc()
或类似的 API 来确保缓存一致性。
6. 测试与调试 #
用于驱动程序测试的常用 VxWorks 命令:
-> devs
– 列出所有已注册的设备。-> i2cShow
/-> spiShow
– 显示总线设备。-> d (0xADDRESS, 16)
– 内存/寄存器值转储。
调试方法:
- 在早期开发阶段使用
printf
。 - 启用 Wind River Workbench 或 WDB 调试器来设置断点。
- 添加
errnoGet()
检查以诊断错误。
实际案例:VxBus UART 驱动程序 #
为了使 VxBus 驱动开发更具体,我们来创建一个简化的 UART 驱动程序。此示例假设有一个内存映射的 UART 设备,具有基本的发送(TX)和接收(RX)寄存器。
1. 设备树入口 #
首先,在设备树中定义 UART:
uart0: serial@0x101f1000 {
compatible = "simple,uart";
reg = <0x101f1000 0x1000>;
interrupts = <5 0>;
clock-frequency = <48000000>;
};
这里:
reg
定义了 UART 的基地址和大小。interrupts
指定了 IRQ 中断线。clock-frequency
提供了用于波特率计算的输入时钟频率。
2. 寄存器映射 #
此示例假设 UART 具有以下寄存器:
偏移量 | 寄存器 | 描述 |
---|---|---|
0x00 | UART_DR | 数据寄存器(读/写) |
0x04 | UART_SR | 状态寄存器 |
0x08 | UART_CR | 控制寄存器 |
0x0C | UART_BAUD | 波特率分频寄存器 |
3. 驱动程序实现 #
#define UART_DR 0x00
#define UART_SR 0x04
#define UART_CR 0x08
#define UART_BAUD 0x0C
#define UART_SR_TX_READY (1 << 0)
#define UART_SR_RX_READY (1 << 1)
#define UART_CR_TX_EN (1 << 0)
#define UART_CR_RX_EN (1 << 1)
typedef struct {
VXB_DEV_ID dev;
void * base; // 映射的寄存器基地址
int irq;
} UART_DEV;
LOCAL void uartIsr(void * arg) {
UART_DEV * pDrv = (UART_DEV *) arg;
UINT32 sr = vxbRead32(pDrv->dev, (UINT32 *) (pDrv->base + UART_SR));
if (sr & UART_SR_RX_READY) {
char c = vxbRead8(pDrv->dev, (UINT8 *) (pDrv->base + UART_DR));
printf("UART RX: %c\n", c);
}
}
LOCAL STATUS uartProbe(VXB_DEV_ID pDev) {
return OK; // 在此演示中始终声称支持
}
LOCAL STATUS uartAttach(VXB_DEV_ID pDev) {
UART_DEV * pDrv = (UART_DEV *) vxbMemAlloc(sizeof(UART_DEV));
if (!pDrv) return ERROR;
pDrv->dev = pDev;
pDrv->base = (void *) vxbRegMap(pDev, 0, 0);
pDrv->irq = vxbIntConnect(pDev, 0, uartIsr, pDrv);
// 启用 TX/RX
vxbWrite32(pDev, (UINT32 *)(pDrv->base + UART_CR), UART_CR_TX_EN | UART_CR_RX_EN);
// 配置波特率
vxbWrite32(pDev, (UINT32 *)(pDrv->base + UART_BAUD), 48000000 / 115200);
vxbDevSoftcSet(pDev, pDrv);
return OK;
}
LOCAL STATUS uartDetach(VXB_DEV_ID pDev) {
UART_DEV * pDrv = (UART_DEV *) vxbDevSoftcGet(pDev);
if (!pDrv) return ERROR;
vxbIntDisconnect(pDev, pDrv->irq, uartIsr, pDrv);
vxbMemFree(pDrv);
return OK;
}
LOCAL VXB_DRV_METHOD uartMethods[] = {
{ VXB_DEVMETHOD_CALL(vxbDevProbe), uartProbe },
{ VXB_DEVMETHOD_CALL(vxbDevAttach), uartAttach },
{ VXB_DEVMETHOD_CALL(vxbDevDetach), uartDetach },
VXB_DEVMETHOD_END
};
VXB_DRV uartDriver = {
{ NULL },
"uartDriver",
"简单 UART VxBus 驱动程序",
VXB_BUSID_PLB,
0,
sizeof(UART_DEV),
uartMethods,
NULL,
NULL,
NULL
};
VXB_DRV_DEF(uartDriver);
4. 驱动程序注册 #
将驱动程序添加到 BSP 初始化中:
IMPORT VXB_DRV uartDriver;
void sysHwInit(void) {
vxbDevRegister(&uartDriver);
}
5. 测试 UART 驱动程序 #
- 启动系统并运行:
-> devs
您应该能看到 UART 设备被列出。
- 通过写入 TX 寄存器发送数据:
UART_DEV * pDrv = (UART_DEV *) vxbDevSoftcGet(<device>);
vxbWrite8(pDrv->dev, (UINT8 *)(pDrv->base + UART_DR), 'A');
- 当字符到达时,ISR 会将其打印到控制台。
VxBus 驱动开发的高级主题 #
设备树叠加(Device Tree Overlays) #
无需重新构建 BSP,您可以通过应用 DTS 叠加来添加新的硬件支持——这非常适合原型设计。
电源管理 #
现代 VxBus 驱动程序应支持挂起/恢复(suspend/resume)钩子,以应对对功耗敏感的设备。
多实例设备 #
确保您的驱动程序能优雅地处理多个实例(例如,多个 I²C 控制器)。
错误恢复 #
为瞬时故障(例如,总线错误、超时)实现重试逻辑。
VxBus 驱动程序的最佳实践 #
✅ 保持驱动程序通用 – 避免硬编码特定于开发板的细节。 ✅ 使用标准 API – 遵循 VxBus 和 VxWorks 内核 API。 ✅ 记录接口 – 帮助未来的开发人员集成您的驱动程序。 ✅ 遵循模块化 – 分离硬件初始化、ISR 和数据处理。 ✅ 基准测试性能 – 在负载下测试延迟和吞吐量。
结论 #
对于 VxWorks 工程师来说,使用 VxBus 开发驱动程序是一项必备技能。通过理解其架构、利用设备树并遵循最佳实践,您可以构建可移植、高效且面向未来的驱动程序。
从基本的驱动程序骨架到支持 DMA 的高级实现,掌握 VxBus 将为您构建稳定、高性能的嵌入式系统打开大门。