3、SPI控制器驱动模型
SPI控制器驱动是VxWorks下SPI子系统与硬件SPI外设之间的桥梁。本章聚焦struct spi_ctlr核心结构体、控制器初始化标准流程以及注册/注销机制,帮助开发者建立从“硬件寄存器”到“内核对象”的完整映射。
3.1 SPI控制器结构体:struct spi_ctlr
VxWorks使用struct spi_ctlr描述一个SPI控制器实例,该结构体定义在spiLib.h中,包含硬件资源、总线参数、回调函数和DMA配置等字段。
| 字段 | 类型 | 说明 |
|---|---|---|
| name | char * | 控制器名称,用于调试和绑定 |
| busNum | UINT32 | 总线编号,系统内唯一 |
| mode | UINT32 | SPI模式(CPOL/CPHA组合) |
| maxSpeedHz | UINT32 | 最大SCK频率,单位Hz |
| bitsPerWord | UINT8 | 每字位数,通常8/16/32 |
| chipSelect | UINT32 | 当前片选线编号 |
| pLock | SEM_ID | 互斥信号量,保护并发访问 |
| pRegBase | volatile UINT8 * | 寄存器基地址(MMIO映射) |
| pDmaChan | DMA_CHAN * | 关联DMA通道(可选) |
| transfer | STATUS (*)(struct spi_ctlr *, struct spi_msg *) | 底层传输函数指针 |
| config | STATUS (*)(struct spi_ctlr *, struct spi_config *) | 控制器配置回调 |
设计要点:
transfer和config是驱动开发者必须实现的两个回调。前者负责实际的SPI数据收发,后者用于动态调整时钟、极性和相位。
3.2 控制器初始化流程
初始化一个SPI控制器驱动通常分为5个步骤,以下流程图展示了从硬件探测到内核注册的完整路径。
3.3 初始化代码示例
以下代码展示了典型的SPI控制器初始化实现,基于i.MX6 ECSPI外设进行适配:
STATUS spiEimxInit
(
struct spi_ctlr *pCtlr,
UINT32 busNum,
UINT32 regBase,
UINT32 maxClk
)
{
/* 1. 清零结构体 */
bzero (pCtlr, sizeof (struct spi_ctlr));
/* 2. 填充基础信息 */
pCtlr->name = "ecspi";
pCtlr->busNum = busNum;
pCtlr->maxSpeedHz = maxClk;
pCtlr->bitsPerWord = 8;
pCtlr->mode = SPI_MODE_0; /* CPOL=0, CPHA=0 */
/* 3. 映射寄存器地址 */
pCtlr->pRegBase = (volatile UINT8 *)regBase;
/* 4. 创建互斥信号量 */
pCtlr->pLock = semMCreate (SEM_Q_PRIORITY | SEM_INVERSION_SAFE);
if (pCtlr->pLock == NULL)
return (ERROR);
/* 5. 绑定传输与配置回调 */
pCtlr->transfer = spiEimxTransfer;
pCtlr->config = spiEimxConfig;
/* 6. 硬件级复位 */
REG_WRITE32 (pCtlr->pRegBase + ECSPI_CONREG, 0);
return (OK);
}
常见错误: 忘记调用
semMCreate会导致并发传输时数据竞争;寄存器基地址必须使用volatile修饰,否则编译器优化可能跳过硬件访问。
3.4 注册与注销
VxWorks SPI核心层提供两个标准API用于管理控制器生命周期:
| API | 原型 | 说明 |
|---|---|---|
| spiCtlrRegister | STATUS spiCtlrRegister(struct spi_ctlr *) | 将控制器加入全局链表,使能总线访问 |
| spiCtlrUnregister | STATUS spiCtlrUnregister(struct spi_ctlr *) | 从全局链表移除,释放资源 |
3.5 注册与注销流程
下图展示了控制器注册和注销在内核中的完整路径:
3.6 注册与注销代码示例
/* 注册控制器 */
STATUS spiEimxRegister (struct spi_ctlr *pCtlr)
{
if (pCtlr == NULL)
return (ERROR);
/* 调用核心层注册函数 */
if (spiCtlrRegister (pCtlr) != OK)
{
logMsg ("spiCtlrRegister failed for bus %d\n",
pCtlr->busNum, 0, 0, 0, 0, 0);
return (ERROR);
}
logMsg ("SPI controller bus %d registered\n",
pCtlr->busNum, 0, 0, 0, 0, 0);
return (OK);
}
/* 注销控制器 */
STATUS spiEimxUnregister (struct spi_ctlr *pCtlr)
{
if (pCtlr == NULL)
return (ERROR);
/* 调用核心层注销函数 */
if (spiCtlrUnregister (pCtlr) != OK)
{
logMsg ("spiCtlrUnregister failed for bus %d\n",
pCtlr->busNum, 0, 0, 0, 0, 0);
return (ERROR);
}
/* 释放驱动私有资源 */
semDelete (pCtlr->pLock);
bzero (pCtlr, sizeof (struct spi_ctlr));
return (OK);
}
最佳实践: 在系统启动脚本中按总线编号顺序注册控制器;注销时先调用
spiCtlrUnregister再释放私有内存,避免内核访问已释放的结构体。
3.7 控制器生命周期总结
| 阶段 | 关键操作 | 注意事项 |
|---|---|---|
| 探测 | 读取设备树/硬件寄存器获取基地址、中断号 | 确认MMIO地址范围不与其他外设冲突 |
| 初始化 | 分配结构体、填充字段、创建信号量 | 信号量类型建议使用SEM_Q_PRIORITY |
| 注册 | 调用spiCtlrRegister加入全局链表 | 注册后立即生效,从设备可发起通信 |
| 运行 | 处理transfer回调,与从设备交互 | 确保中断处理函数不阻塞信号量 |
| 注销 | 调用spiCtlrUnregister,释放资源 | 必须在所有从设备解绑后执行 |