4、SPI驱动初始化:编写SPI模块的初始化代码,配置时钟、极性、相位、数据帧格式等。

好,咱们进入正题。SPI驱动初始化,说白了就是给这个外设“上户口”。你得告诉它:你跑多快?数据怎么对齐?时钟空闲时是高是低?这些不配好,通信就是一团乱麻。

我个人习惯,写初始化代码前,先翻数据手册里的寄存器描述。TC3xx的SPI模块,核心寄存器就那么几个,但每个位域都有讲究。嗯,咱们一步步来。

4.1 时钟配置:别让速度成为瓶颈

SPI的时钟源通常来自系统时钟或外设时钟。TC3xx里,SPI模块挂载在SPB总线上,时钟频率可以独立分频。

我曾经在一个项目中,把SPI时钟配得太高,结果从设备直接罢工。后来查手册才发现,从设备最高只支持10MHz,而我配了20MHz。所以,时钟配置的第一原则:主设备时钟不能超过从设备支持的最高频率

配置时钟时,你需要关注两个参数:

  • 波特率分频器:决定SCK的频率
  • 时钟极性(CPOL):空闲时SCK的电平
  • 时钟相位(CPHA):数据在哪个边沿采样

你想想看,如果CPOL和CPHA搞反了,数据读出来全是乱的。我见过一个同事,调了两天SPI,最后发现是相位配反了——数据在错误的边沿被采样。

核心要点:时钟配置不是简单的设个分频系数,而是要结合从设备的时序要求来定。先看从设备手册,再写代码。

4.2 极性与相位:SPI通信的灵魂

SPI有四种模式,由CPOL和CPHA组合决定。我习惯用一张表来记:

模式 CPOL CPHA 空闲时钟 数据采样边沿
模式0 0 0 低电平 第一个跳变沿(上升沿)
模式1 0 1 低电平 第二个跳变沿(下降沿)
模式2 1 0 高电平 第一个跳变沿(下降沿)
模式3 1 1 高电平 第二个跳变沿(上升沿)

为什么模式0最常见?因为很多从设备默认就是模式0。但千万别想当然。我记得有一次调试一个温度传感器,手册上写的是模式2,我习惯性配了模式0,结果读出来的温度值忽高忽低。查了半天才发现是相位问题。

我的小技巧:如果不知道从设备用哪种模式,先看手册里的时序图。图上会明确标出数据采样点。照着图配,准没错。

4.3 数据帧格式:8位还是16位?

TC3xx的SPI支持可编程的数据帧长度,从4位到16位都可以。但实际项目中,8位和16位用得最多。

配置数据帧格式时,要注意:

  • 数据位宽:8位还是16位?这取决于从设备的数据寄存器宽度
  • 数据顺序:MSB first还是LSB first?大部分设备用MSB first
  • 传输格式:Motorola格式还是TI格式?TC3xx默认支持Motorola格式

我曾经踩过一个坑:一个ADC芯片要求16位数据,但数据手册上写的是“MSB first, 16-bit data”。我配了16位宽,但忘了改数据顺序——默认是MSB first,所以没问题。但如果你的从设备要求LSB first,不改的话数据就反了。

注意:数据帧长度改变后,发送和接收缓冲区的处理逻辑也要跟着变。比如从8位切到16位,你不能再按字节去读写FIFO,而要按半字。

4.4 初始化代码实战

好了,理论说完了,咱们看代码。下面是一个TC3xx SPI模块的初始化示例,我加了详细的注释:

/* SPI模块初始化函数 */
void Spi_Init(void)
{
    /* 1. 使能SPI模块时钟 */
    /* 在TC3xx中,需要先使能外设时钟 */
    SCU_PDR1.B.SPI0_EN = 1;  /* 使能SPI0时钟 */
    
    /* 2. 配置引脚功能 */
    /* 将P10.0-P10.3配置为SPI功能 */
    PORT10_IOCR0.B.PC0 = 0x01;  /* SCK */
    PORT10_IOCR0.B.PC1 = 0x01;  /* MTSR (MOSI) */
    PORT10_IOCR0.B.PC2 = 0x01;  /* MRST (MISO) */
    PORT10_IOCR0.B.PC3 = 0x01;  /* CS (片选) */
    
    /* 3. 配置SPI控制寄存器 */
    /* 设置为主模式,CPOL=0, CPHA=0 (模式0) */
    SPI0_CON.B.MODE = 0;       /* 主模式 */
    SPI0_CON.B.CPOL = 0;       /* 空闲时SCK为低 */
    SPI0_CON.B.CPHA = 0;       /* 第一个边沿采样 */
    
    /* 4. 配置数据帧格式 */
    SPI0_CON.B.DATA_WIDTH = 7; /* 8位数据 (0-7: 8位) */
    SPI0_CON.B.MSB_FIRST = 1;  /* MSB first */
    SPI0_CON.B.TI_MODE = 0;    /* Motorola格式 */
    
    /* 5. 配置波特率 */
    /* 假设系统时钟100MHz,目标SPI时钟1MHz */
    /* 分频系数 = 100MHz / (2 * 1MHz) = 50 */
    SPI0_BR.B.BR_VALUE = 49;   /* 分频系数 = BR_VALUE + 1 */
    
    /* 6. 使能SPI模块 */
    SPI0_CON.B.EN = 1;         /* 使能SPI */
    
    /* 7. 清空FIFO,确保初始状态干净 */
    SPI0_FC.B.TXFFLUSH = 1;    /* 清空发送FIFO */
    SPI0_FC.B.RXFFLUSH = 1;    /* 清空接收FIFO */
    
    /* 等待FIFO清空完成 */
    while(SPI0_FC.B.TXFFLUSH == 1);
    while(SPI0_FC.B.RXFFLUSH == 1);
    
    /* 初始化完成 */
    Spi_Status = SPI_READY;
}

这段代码里,我特别关注了FIFO的清空操作。为什么?因为如果上电后FIFO里有残留数据,第一次通信就可能读到脏数据。我在一个项目中就遇到过,复位后没清FIFO,结果第一个字节读回来全是0xFF。

4.5 初始化后的检查清单

代码写完了,别急着跑。我建议你对照这个清单检查一遍:

  1. 时钟使能了吗?——没使能时钟,寄存器写不进去
  2. 引脚配置对了吗?——复用功能选对了没?
  3. 主从模式设对了吗?——主设备配成从模式,通信会卡死
  4. 极性和相位匹配吗?——和从设备手册一致吗?
  5. 数据帧长度正确吗?——8位还是16位?
  6. 波特率合理吗?——不超过从设备上限

嗯,这个清单是我自己总结的。每次写完初始化代码,我都会过一遍。别嫌麻烦,这一步能省掉你后面大量的调试时间。

总结一下:SPI初始化看似简单,但细节决定成败。时钟、极性、相位、数据帧格式,这四个参数必须和从设备完全匹配。配错了,轻则数据错误,重则设备不响应。我的建议是:先读从设备手册,再写初始化代码,最后用逻辑分析仪抓波形验证。

好了,这一章就到这里。下一章咱们聊聊SPI的数据收发——怎么用中断和DMA来提升效率。到时候我会分享一个我在汽车项目里用DMA做SPI通信的实战案例,保证干货满满。