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,不改的话数据就反了。
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 初始化后的检查清单
代码写完了,别急着跑。我建议你对照这个清单检查一遍:
- 时钟使能了吗?——没使能时钟,寄存器写不进去
- 引脚配置对了吗?——复用功能选对了没?
- 主从模式设对了吗?——主设备配成从模式,通信会卡死
- 极性和相位匹配吗?——和从设备手册一致吗?
- 数据帧长度正确吗?——8位还是16位?
- 波特率合理吗?——不超过从设备上限
嗯,这个清单是我自己总结的。每次写完初始化代码,我都会过一遍。别嫌麻烦,这一步能省掉你后面大量的调试时间。
好了,这一章就到这里。下一章咱们聊聊SPI的数据收发——怎么用中断和DMA来提升效率。到时候我会分享一个我在汽车项目里用DMA做SPI通信的实战案例,保证干货满满。