4. SPI控制器硬件初始化:时钟使能、GPIO复用配置、SPI控制器寄存器初始化

SPI控制器硬件初始化是驱动开发的基础环节,直接影响后续DMA传输的稳定性和吞吐量上限。本节按照硬件初始化流程,依次讲解时钟使能、GPIO复用配置以及SPI控制器核心寄存器(波特率、数据格式、中断使能)的初始化方法。

初始化顺序原则:必须先使能时钟,再配置GPIO复用,最后操作SPI控制器寄存器。违反该顺序可能导致寄存器写入无效或总线挂死。

4.1 时钟使能

时钟使能是SPI模块正常工作的前提。在VxWorks中,通常通过操作SoC的时钟管理单元(CMU/CCM)寄存器来开启SPI外设时钟。不同平台(如i.MX6、STM32MP1、Zynq)的时钟树结构不同,但操作逻辑一致:找到对应SPI实例的时钟门控位,置位使能。

平台 时钟寄存器示例 使能位 注意事项
NXP i.MX6 CCM_CCGRx CG位(0b11为全使能) 需同时使能SPI和GPIO时钟域
STM32MP1 RCC_MP_APBxENSETR SPIxEN位 复位后默认关闭,需显式使能
Xilinx Zynq SLCR.SPI_CLK_CTRL CLKACT位 还需配置时钟源分频
/* i.MX6 SPI时钟使能示例(VxWorks BSP层) */
#define CCM_CCGR1         0x020C406C
#define SPI1_CLK_ENABLE   (0x3 << 20)   /* CG位[21:20] */

void spiClockEnable(void)
{
    UINT32 regVal;
    regVal = sysIn32(CCM_CCGR1);
    regVal |= SPI1_CLK_ENABLE;
    sysOut32(CCM_CCGR1, regVal);
}
性能提示:时钟使能后建议加入短暂延时(约10μs),等待时钟稳定后再进行后续配置,避免首次寄存器访问失败。

4.2 GPIO复用配置

SPI信号线(SCLK、MOSI、MISO、CS)必须通过GPIO复用控制器(IOMUX)配置为SPI功能模式。错误的复用配置会导致信号无法正确路由到SPI控制器引脚。

典型配置步骤:

  1. 查找引脚复用表:根据原理图确定SPI引脚对应的Pad和复用模式(ALT值)。
  2. 配置复用寄存器:将对应Pad的MUX_MODE设置为SPI功能。
  3. 配置电气特性:设置驱动能力(DSE)、压摆率(SRE)、上下拉(PUE/PUS)等参数。
  4. 配置GPIO方向:对于CS引脚,若使用软件控制,需配置为GPIO输出模式。
/* i.MX6 SPI1引脚复用配置示例 */
#define IOMUXC_SW_MUX_CTL_PAD_CSI0_DAT4  0x020E01B4
#define IOMUXC_SW_PAD_CTL_PAD_CSI0_DAT4  0x020E0468

void spiGpioMuxConfig(void)
{
    /* 配置CSI0_DAT4为SPI1_SCLK (ALT2) */
    sysOut32(IOMUXC_SW_MUX_CTL_PAD_CSI0_DAT4, 0x2);
    /* 设置驱动强度: 0x1B0 = 100kΩ下拉, 驱动等级R0/6 */
    sysOut32(IOMUXC_SW_PAD_CTL_PAD_CSI0_DAT4, 0x1B0);
    
    /* 类似配置MOSI、MISO、CS引脚... */
}
常见错误:只配置了复用模式而忽略电气特性,导致高速SPI(>20MHz)时信号质量下降,出现误码。建议在DMA调试前用示波器检查信号边沿。

4.3 SPI控制器寄存器初始化

SPI控制器寄存器初始化是硬件配置的核心,直接决定通信速率、数据格式和中断行为。以下以典型SPI控制器(兼容MCSPI/ECSPI)为例,讲解三个关键寄存器组。

4.3.1 波特率配置

SPI波特率由时钟源频率和分频系数共同决定。控制器通常提供预分频器和位周期计数器两级分频。

/* SPI波特率计算公式 */
SPI_CLK = CLK_SOURCE / (PRESCALER + 1) / (2 * (BIT_COUNTER + 1))

/* 示例:目标波特率10MHz,时钟源60MHz */
/* 选择PRESCALER=0(不分频),BIT_COUNTER=2 */
/* SPI_CLK = 60MHz / 1 / (2 * 3) = 10MHz */
寄存器字段 位域 典型值 说明
PRESCALER [15:12] 0x0 预分频值,0~15对应1~16分频
BIT_COUNTER [11:8] 0x2 位周期计数器,实际分频为2*(n+1)
CLK_POL [6] 0/1 时钟极性:0=空闲低,1=空闲高
CLK_PHA [7] 0/1 时钟相位:0=第一个边沿采样,1=第二个边沿采样

4.3.2 数据格式配置

数据格式寄存器定义每帧的位宽、传输顺序(MSB/LSB优先)以及字对齐方式。对于DMA传输,建议将数据格式配置为与DMA传输宽度匹配(如8位、16位或32位)。

/* 数据格式配置示例:8位数据,MSB优先 */
#define SPI_CONREG_CHANNEL_MODE  (0x0 << 4)   /* 通道模式:主模式 */
#define SPI_CONREG_DATA_RATE     (0x2 << 8)   /* 波特率分频 */
#define SPI_CONREG_DATA_WIDTH    (0x7 << 20)  /* 8位数据宽度 */
#define SPI_CONREG_MSB_FIRST     (0x0 << 23)  /* MSB优先 */
#define SPI_CONREG_SPI_ENABLE    (0x1 << 0)   /* SPI使能 */

sysOut32(SPI1_BASE + CONREG_OFFSET, 
         SPI_CONREG_CHANNEL_MODE |
         SPI_CONREG_DATA_RATE |
         SPI_CONREG_DATA_WIDTH |
         SPI_CONREG_MSB_FIRST);

4.3.3 中断使能配置

中断使能寄存器控制SPI控制器在哪些事件下产生中断。对于DMA传输模式,通常只使能错误中断(溢出、帧错误),而数据就绪中断由DMA控制器接管。

中断类型 使能位 DMA模式建议 说明
数据就绪(RX_READY) INTEN[0] 关闭 由DMA请求信号替代
发送空(TX_EMPTY) INTEN[1] 关闭 由DMA请求信号替代
溢出错误(OVRFLOW) INTEN[2] 开启 检测DMA传输异常
帧错误(FRAME_ERR) INTEN[3] 开启 检测SPI协议错误
DMA完成(DMA_DONE) INTEN[4] 可选 用于DMA传输完成回调
/* 中断使能配置:仅使能错误中断 */
#define SPI_INTEN_OVRFLOW_EN   (0x1 << 2)
#define SPI_INTEN_FRAME_ERR_EN (0x1 << 3)

sysOut32(SPI1_BASE + INTEN_OFFSET, 
         SPI_INTEN_OVRFLOW_EN | SPI_INTEN_FRAME_ERR_EN);
优化建议:在DMA传输模式下,关闭数据就绪中断可减少CPU中断负载约70%,将CPU从每字节中断中解放出来,专注于上层协议处理。

4.4 硬件初始化完整流程

下图展示了SPI控制器硬件初始化的完整流程及各步骤的依赖关系:

SPI控制器硬件初始化流程图 SPI控制器硬件初始化完整流程 1. 时钟使能 2. GPIO复用配置 3. SPI控制器寄存器初始化 3.1 波特率配置 3.2 数据格式配置 3.3 中断使能配置 4. 初始化完成 说明:子步骤3.1~3.3无严格顺序要求,但必须在步骤1和步骤2完成后执行

4.5 初始化代码模板

以下给出一个完整的SPI控制器硬件初始化函数模板,整合了时钟、GPIO和寄存器配置:

/* spiHwInit.c - SPI硬件初始化完整实现 */
#include "vxWorks.h"
#include "sysLib.h"

/* 寄存器基地址(以i.MX6 ECSPI1为例) */
#define SPI1_BASE           0x02008000
#define SPI1_CONREG         (SPI1_BASE + 0x00)
#define SPI1_CONFIGREG      (SPI1_BASE + 0x04)
#define SPI1_INTEN          (SPI1_BASE + 0x0C)
#define SPI1_TESTREG        (SPI1_BASE + 0x10)

/* 时钟和GPIO配置宏(由BSP提供) */
#define CCM_CCGR1           0x020C406C
#define SPI1_CLK_ENABLE     (0x3 << 20)

STATUS spiHwInit(UINT32 baudRateHz, UINT8 dataBits, UINT8 mode)
{
    UINT32 clkDiv;
    UINT32 conRegVal;
    
    /* 步骤1:时钟使能 */
    sysOut32(CCM_CCGR1, sysIn32(CCM_CCGR1) | SPI1_CLK_ENABLE);
    sysMsDelay(1);  /* 等待时钟稳定 */
    
    /* 步骤2:GPIO复用配置(调用BSP函数) */
    if (spiGpioMuxConfig() != OK) {
        return ERROR;
    }
    
    /* 步骤3:计算波特率分频 */
    clkDiv = spiCalcBaudDiv(baudRateHz);
    if (clkDiv == 0) {
        return ERROR;
    }
    
    /* 步骤4:配置SPI控制寄存器 */
    conRegVal = (0x0 << 4)    /* 主模式 */
              | (clkDiv << 8) /* 波特率分频 */
              | ((dataBits - 1) << 20) /* 数据宽度 */
              | (0x0 << 23)   /* MSB优先 */
              | (mode << 6);  /* 时钟极性和相位 */
    
    sysOut32(SPI1_CONREG, conRegVal);
    
    /* 步骤5:配置中断使能(仅错误中断) */
    sysOut32(SPI1_INTEN, (0x1 << 2) | (0x1 << 3));
    
    /* 步骤6:使能SPI控制器 */
    sysOut32(SPI1_CONREG, sysIn32(SPI1_CONREG) | 0x1);
    
    return OK;
}
调试要点:初始化完成后,建议读取SPI状态寄存器确认控制器已进入就绪状态。若状态寄存器显示总线错误或超时,应检查时钟频率是否超出SPI控制器支持范围(通常不超过50MHz)。

4.6 初始化验证清单

完成初始化后,使用以下清单逐项验证,确保硬件配置正确:

验证项 验证方法 预期结果
时钟使能状态 读取时钟门控寄存器 对应SPI位为0b11
GPIO复用模式 读取IOMUX寄存器 MUX_MODE为SPI ALT值
波特率实际值 示波器测量SCLK引脚 误差<5%
数据格式 发送已知数据并回环 接收数据与发送一致
中断使能 读取INTEN寄存器 错误中断位为1,数据中断位为0

通过以上步骤,SPI控制器的硬件初始化即告完成,为后续DMA传输配置奠定了稳定的硬件基础。