MCAL - SPI(NXP - S32K14x)
- 配置工具:EB Tresos Studio
- 芯片类型:S32K146
1. 概述
如果有大佬对此部分有比较深入的介绍文章,希望能分享给我再深入学习一下。
1.1 SPI框图(S32K146)
SPI基础知识点:传送门
数据发送:
用户将数据写入到TDR
寄存器后,数据会被存入TX FIFO
,数据将通过Shift Register(移位寄存器)
按位从SOUT
发送出去。TCR
寄存器和CCR
寄存器分别决定了数据传输(CPOL、CPHA等)和传输时序。CCR
寄存器在lpspi
使能后就无法修改,TCR
寄存器被修改后会被写入到TX FIFO
并且被标记,当该值处于TX FIFO
的头部分时候,会根据当前的spi
状态来决定后续执行动作如下图如所示:
数据接收:
数据从SIN
输入,通过Shift Register(移位寄存器)
存入RX FIFO
。
1.2 引脚
各引脚功能介绍如下图所示,(SCK / PCS / SIN / SOUT)就是SPI中对应的(SCK / CS / MISO / MOSI)引脚:
1.3 时钟
数据收发框图中对应的时钟介绍如下图所示:
2. API
函数 | 描述 |
---|---|
Spi_Init | SPI模块初始化 |
Spi_DeInit | SPI模块恢复至默认状态 |
Spi_WriteIB | 将数据写入内部缓存区 |
Spi_AsyncTransmit | 异步传输数据 |
Spi_ReadIB | 从内部缓存区读取数据 |
Spi_SetEB | 设备外部数据缓存区 |
Spi_GetStatus | 获取SPI模块状态 |
Spi_GetJobResult | 获取任务结果 |
Spi_GetSequenceResult | 获取序列结果 |
Spi_SyncTransmit | 同步传输数据 |
Spi_GetHWUnitStatus | 获取硬件单元状态 |
Spi_Cancel | 取消执行序列 |
Spi_SetAsyncMode | 设置异步模式(中断/轮询) |
Spi_MainFunction_Handling | 轮询函数 |
3. 配置介绍
3.1 General
SpiChannelBuffersAllowed:
通道缓存允许方式(0:Inner / 1:Extern / 2:Both)
SpiInterruptibleSeqAllowed:
是否允许正在执行的序列能否被打断(挂起)
SpiLevelDelivered:
同步/ 异步
SpiSupportConcurrentSyncTransmit:
是否支持多个同步任务并行执行
SpiCPUClockRef:
时钟参考点
SpiGlobalDmaEnable:
传输方式(true:支持FIFO、DMA / false:支持FIFO)
SpiTransmitTimeout:
传输超时时间
超时时间的设置必须大于数据传输的时间,数据传输时间计算有以下两种方式:
①Continue: true
②Continue:false
SpiOptimizeOneJobSequences:
在同步模式下,提前缓存只有一个任务的Sequence信息,加快传输速度(待深入了解)
3.2 SpiPhyUnit
SpiPhyUnitMapping:
SPI硬件单元选择
SpiPhyUnitMode:
主从模式选择
SpiPhyUnitSync:
传输模式(true :同步 / false:异步)
配置项关联:同步(SpiLevelDelivered = 0 or 2),异步(SpiLevelDelivered = 1 or 2)
SpiPhyUnitClockRef:
参考时钟点
3.3 SpiChannel
SpiChannelType
:通道类型(IB/EB)
SpiDataWidth:
数据宽度
①数据宽度决定了底层访问TDR
、RDR
寄存器是按照1/2/4字节访问
②FRAMESZ = SpiDataWidth - 1
SpiDefaultData:
默认填充数据(在没有配置缓存区的时候,如果启动传输,则会传输默认的填充数据)
SpiIbNBuffers:
内部缓存区大小
SpiEbMaxLength:
外部缓存区最大长度
SpiTransferStart:
MSB/LSB(数据大小端)
3.3 SpiSequence
SpiInterruptibleSequence:
正在执行的序列能否被打断(挂起)
SpiSeqEndNotification:
Sequence完成回调
SpiJobAssignment:
任务关联
3.3 SpiJob
SpiHwUnitSynchronous:
以同步或异步的方式驱动硬件单元
SpiJobEndNotification:
任务结束回调
SpiJobStartNotification:
任务开始回调
SpiJobPriority:
任务优先级
SpiDeviceAssignment:
关联外部设备
SpiChannelList:
关联通道
3.4 SpiExternalDevice
SpiSlaveMode:
主从模式
SpiBaudrate:
波特率
SpiCsIdentifier:
选择片选引脚
SpiCsPolarity:
片选引脚有效电平
SpiCsSelection:
当使用LPSPI时,选择CS_VIA_PERIPHERAL_ENGINE
会配置TCR寄存器的PCS(关联SpiCsIdentifier)
SpiDataShiftEdge:
CPHA选择(边沿选择)
SpiEnableCs:
启用片选引脚
SpiHwUnit:
硬件单元选择(序号对应的是SpiPhyUnit中第几个)
SpiShiftClockIdleLevel:
CPOL选择(空闲电平)
SpiTimeClk2Cs:
SpiTimeCs2Clk:
SpiTimeCs2Cs:
SpiCsContinous:
连续模式(发送完FRAMES长度的数据帧后CS是否保持)
SpiByteSwap:
字节交换
SpiPinConfig:
4. 功能介绍
4.1 关联图
4.1.1 HwUnit
硬件单元:S32K146可选择的有LPSPI0
、LPSPI1
、LPSPI2
、FLEXIOSPI0
、FLEXIOSPI1
。
4.1.2 ExternalDevice
外部设备:指的是SPI硬件单元的配置。
我们通过SPI外设与外部设备(FLASH、TJA1145)进行通讯,需要确定波特率、通讯模式(CPHA、CPOL)等信息,都在此处配置。
4.1.3 Channel
通道:数据缓存区,可以配置为IB / EB。
-
IB - Inner Buffer(模块内部)
配置完成后,SPI模块会开辟2个数组作为通道的输入/输出缓存区指针,选择EB的时候则为NULL。
看网上很多写内部缓存区是SPI内部的FIFO,其实是不对的,从软件上看,SPI底层始终都是从RAM中获取数据,再把数据写到TDR寄存区,SPI模块会将TDR寄存器里面的数据搬运到FIFO中,SPI内部的FIFO只有4字大小。所以从用户角度看,不管是内部还是外部Buffer,就是
静态
(自动生成)和动态
(用户设置)的区别。/* Buffers Descriptors for IB Channels (if any) */ static VAR(Spi_BufferDescriptorType, SPI_VAR) Buffer_PBSpiChannel_0 = { BufferTX_PBSpiChannel_0, BufferRX_PBSpiChannel_0 };
-
EB - External Buffer(模块外部)
配置完成后,通道的输入/输出缓存区指针为NULL,且在SPI初始化的时候也会赋值为NULL且长度清0,配置为EB模式,需要用户调用
SPI_SetupEB
去设置输入/输出缓存区。
4.1.4 Job
任务:一个任务只能绑定一个外部设备,但是一个任务可以绑定多个通道,传输一个任务时,会先获取它绑定的外部设备信息,从外部设备信息中获取对应的硬件单元,将外部设备信息中定义的配置信息设置到对应的硬件单元的寄存器中,再按顺序将通道里面的数据进行传输。任务是可以设定优先级的,S32K146只能设置0/1/2/3这四个等级
。
4.1.5 Sequence
Sequence是数据传输的最小单位,一个Sequence可以包含多个Job。
当开始传输一个Sequence时,会按顺序获取绑定的Job,再以Job为单位依次进行传输。按我的理解这么设计是为了一次操作可以与多个外部设备进行数据交互。具体可以实现怎样花里胡哨的功能待探究~
4.2 异步 / 同步
等级 | 支持模式 |
---|---|
Level 0 | 同步 |
Level 1 | 异步(中断) |
Level 2 | 同步 / 异步(中断)/ 异步(轮询) |
注意设置为Level2时,默认下使用的是轮询模式,需要用户自己调用Spi_SetAsyncMode切换到中断模式
4.2.1 同步
用户调用Spi_SyncTransmit
开启数据传输,从下图可以看到,当发送数据和接收数据为空并且收/发FIFO为空的情况下才会退出,否则一直阻塞等待时间超时才退出。
while(
((Spi_LPspi_pSyncTransmitState->LPspi_LengthTX>0u) && (Spi_LPspi_pSyncTransmitState->LPspi_EndOfJobFlag==(uint8)FALSE)) ||
((Spi_LPspi_pSyncTransmitState->LPspi_EndOfJobFlag==(uint8)TRUE) && ((Spi_LPspi_pSyncTransmitState->LPspi_LengthTX>0u) ||
(Spi_LPspi_pSyncTransmitState->LPspi_LengthRX>0u))) ||
(LPspi_u32FSR_RX_TX_Status != 0u))
4.2.2 异步
#if (SPI_LEVEL_DELIVERED == LEVEL1)
/* handler uses interrupt mode only if LEVEL 1 is selected */
Spi_Ipw_IrqConfig(HWUnit, SPI_INTERRUPT_MODE);
#endif
#if (SPI_LEVEL_DELIVERED == LEVEL2)
/* handler uses polling mode only if LEVEL 2 is selected */
Spi_Ipw_IrqConfig(HWUnit, SPI_POLLING_MODE);
#endif
异步模式配置:
SpiLevelDelivered:
1 or 2
SpiHwUnitSynchronous:
ASYNCHRONOUS
SpiPhyUnitSync:
false
4.2.2.1 中断 / 轮询
轮询和中断的本质都是通过TDF
、RDF
中断完成标志来通知用户,只不过中断模式下使能了中断
,当中断标志置位后MCU内部主动调用中断服务函数执行后续操作(Job回调、Sequence回调、设置状态标志位、执行下一个任务等),轮询模式不使能中断
,因此中断完成标志置位后,只能通过周期函数对中断完成标志进行轮询判断,这个周期函数就是Spi_MainFunction_Handling
。
4.2.2.2 HwUnit Queue
当执行3个Sequence,每个Sequence中都包含1个优先级为1的任务,任务的执行顺序如下图所示,当执行Job0的时候,会先将队列头尾都填充Job0,如果此时Job0还未被执行,Job1入队了,则将Job0的NextJob连接到Job1上,Job1填充队尾(模拟链表插入操作),当Job2入队的时候,Job1的NextJob连接Job2,队尾填充Job2。当Job0出队被执行时,将Job0的NextJob(也就是Job1)填充到队头,依次类推。当Job2出队以后,NextJob为NULL,此时将队尾填充为NULL,并去扫描低优先级的队列是否还有任务需要执行。
参考资料:
S32K-RM.pdf - NXP
AUTOSAR_MCAL_SPI_UM[1].pdf - NXP