2. VxWorks SPI框架概述
在VxWorks 7中,SPI子系统采用分层、模块化的架构设计,将硬件细节与上层应用逻辑解耦。理解这一框架是进行DMA传输优化和吞吐量调优的基础。本章将深入剖析VxWorks 7的SPI驱动架构、总线控制器与从设备抽象层,以及核心的库函数接口。
2.1 VxWorks 7 SPI驱动架构
VxWorks 7的SPI驱动架构遵循VxBus设备驱动模型,分为三个主要层次:应用层、框架层和硬件抽象层。这种分层设计使得驱动开发者可以专注于特定硬件的适配,而无需关心上层协议细节。
架构核心原则: 分层解耦、接口标准化、支持即插即用(VxBus枚举)。
各层职责明确,数据流自上而下传递。应用层通过标准POSIX或VxWorks原生API发起SPI事务;框架层负责事务排队、DMA配置和中断分发;硬件抽象层则直接操作寄存器完成数据收发。
2.2 SPI总线控制器与从设备抽象层
VxWorks 7引入了清晰的总线控制器和从设备抽象概念,这是实现多实例、多设备管理的关键。
2.2.1 总线控制器抽象 (SPI Controller)
每个SPI硬件接口(如SPI1、SPI2)对应一个SPI_CONTROLLER实例。该结构体封装了控制器的硬件资源、配置参数和操作函数指针表。
| 成员 | 类型 | 描述 |
|---|---|---|
regBase | UINT32 | 控制器寄存器基地址 |
busNum | UINT32 | 总线编号(0, 1, 2...) |
maxSpeedHz | UINT32 | 支持的最大SCK频率 |
dmaChan | DMA_CHAN * | 关联的DMA通道句柄 |
lock | SEM_ID | 总线互斥信号量 |
methods | SPI_CONTROLLER_METHODS | 操作函数表(init, xfer, config等) |
2.2.2 从设备抽象 (SPI Slave Device)
挂载在SPI总线上的每个外设(如Flash、ADC、传感器)都被抽象为SPI_SLAVE结构体。该结构体描述了从设备的通信参数,使得同一控制器可以分时复用驱动多个不同配置的设备。
- 片选管理: 每个从设备拥有独立的GPIO片选引脚或硬件片选编号。
- 通信参数: 包含时钟极性(CPOL)、时钟相位(CPHA)、数据位宽(8/16/32位)、最大速率。
- DMA偏好: 标记该设备是否启用DMA传输以及DMA传输的最小阈值(如大于64字节使用DMA)。
设计优势: 通过抽象层,驱动代码无需关心具体硬件寄存器布局。更换BSP时,只需实现
SPI_CONTROLLER_METHODS中的函数指针,上层代码完全不变。
2.3 SPI库函数接口
VxWorks提供了一套标准的SPI库函数(spiLib),用于初始化、配置和收发数据。这些接口是编写SPI设备驱动的核心工具。
2.3.1 核心API列表
| 函数原型 | 功能描述 |
|---|---|
spiDevCreate() | 创建并注册一个SPI从设备实例 |
spiDevDelete() | 删除SPI从设备实例并释放资源 |
spiIoctl() | 控制命令接口(设置速率、模式、DMA使能等) |
spiRead() | 从SPI从设备读取指定字节数 |
spiWrite() | 向SPI从设备写入指定字节数 |
spiExchange() | 全双工同步收发(同时读写) |
spiCallbackInstall() | 注册SPI事务完成回调函数(用于异步模式) |
2.3.2 典型使用流程
一个标准的SPI设备驱动初始化流程如下:
- 获取控制器句柄: 通过VxBus API或设备树获取
SPI_CONTROLLER指针。 - 创建从设备: 调用
spiDevCreate(),传入控制器句柄、片选引脚、速率等参数。 - 配置参数: 使用
spiIoctl()设置CPOL/CPHA、DMA阈值等。 - 执行数据传输: 调用
spiExchange()或spiWrite()/spiRead()。 - 清理: 卸载驱动时调用
spiDevDelete()。
2.3.3 代码示例:SPI从设备创建与全双工传输
#include <vxWorks.h>
#include <spiLib.h>
#include <spiController.h>
/* 假设已通过VxBus获取到控制器句柄 */
SPI_CONTROLLER *pSpiCtrl = (SPI_CONTROLLER *)sysSpimGetCtrl(0);
/* 创建从设备实例 */
SPI_SLAVE *pSlave = spiDevCreate(pSpiCtrl, /* 控制器 */
10, /* 片选GPIO编号 */
8000000, /* 8 MHz */
SPI_MODE_0, /* CPOL=0, CPHA=0 */
8); /* 8位数据宽度 */
if (pSlave == NULL)
{
printf("SPI slave creation failed!\n");
return ERROR;
}
/* 准备收发缓冲区 */
UINT8 txBuf[4] = {0x90, 0x00, 0x00, 0x00}; /* 读命令 */
UINT8 rxBuf[4] = {0};
/* 执行全双工传输 */
STATUS status = spiExchange(pSlave, txBuf, rxBuf, 4);
if (status == OK)
{
printf("Received data: 0x%02x 0x%02x 0x%02x 0x%02x\n",
rxBuf[0], rxBuf[1], rxBuf[2], rxBuf[3]);
}
/* 清理 */
spiDevDelete(pSlave);
注意事项:
spiExchange()是同步阻塞调用,在中断上下文中使用时需格外小心。- DMA传输通常需要缓冲区物理地址连续且对齐到缓存行边界。使用
cacheDmaMalloc()分配DMA缓冲区。 - 多任务环境下,确保每个从设备操作前后正确获取/释放控制器信号量,避免总线冲突。
2.4 框架与性能的关联
SPI框架的设计直接影响DMA传输的效率和吞吐量上限。以下是几个关键设计点:
- 零拷贝接口: 框架层支持直接传递用户缓冲区物理地址给DMA引擎,避免数据在中间缓冲区的搬移。
- 中断聚合: 支持批量完成中断(如传输完成N个字节后产生一次中断),减少CPU中断响应次数。
- 事务队列: 框架层维护一个待处理事务链表,DMA引擎可以连续处理多个事务而无需CPU逐次启动。