前言
本文介绍CP AUTOSAR 架构下的Spi组件,基于S32K144芯片、NXP提供的MCAL包,使用EB Tresos工具进行配置的经验,不具体介绍芯片SPI外设的功能。
Spi组件实现了SPI的传输,可以给外围器件如FLASH、EEPROM提供驱动接口。
Spi组件位于Communication Drivers层里。
一、原理解析
Spi Driver有以下几种概念:
(一)、数据缓冲区EB和IB:
IB:Spi Driver要发送接收的数据位于Spi组件之内。
EB:Spi Driver要发送接收的数据位于Spi组件之外。
也就是说如果在Spi_PBcfg.c里会有发送和接收的数组,如果Channel选择的是IB模式,那么本Channel的发送和接收的数据都在Spi_PBcfg.c里的那些数组。IB模式的Channel通过Spi_ReadIB和Spi_WriteIB来对Spi_PBcfg.c里的那些数组进行读写。
如果Channel选择的是EB模式,那么需要调用Spi_SetupEB来赋予本Channel的发送接收数据源地址,EB模式的Channel的发送接收数组就不是Spi_PBcfg.c里的数组了。
IB模式的数据缓冲区大小和地址在Spi_PBcfg.c里就配置好了,属于静态配置,EB模式的数据缓冲区则属于动态配置,大小和地址可灵活定制。
SPI_CHANNEL_BUFFERS_ALLOWED宏定义如果是0那么只允许有IB数据缓冲区,如果是1那么只允许有EB数据缓冲区,如果是2则允许IB和EB数据缓冲区都有。
(二)、Channel、Job、ExternalDevice、HWUnit、Squence:
Channel:Channel不是物理上的Channel,而是软件上的概念,定义了数据的属性,如数据缓冲区类型(IB、EB)、数据宽度、数据长度、LSB还是MSB、默认值、数据缓冲区地址等。一个Channel可以被多个Job选择。
Job:Job可以拥有多个Channel,最少需要一个Channel,多个Job可以拥有同一个Channel。可以为每个Job定义优先级,0为最低,3为最高。Job还附带ExternalDevice属性,多个Job可选择同一个ExternalDevice。
ExternalDevice:一路ExternalDevice可以被多个Job选择,附带了SPI的属性,如SPI波特率、主从模式(AUTOSAR文档里没有规定SPI驱动有从机模式,但S32K的MCAL里支持这配置)、时钟极性、时钟相位、片选和下一次片选的时间间隔等。ExternalDevice还附带HWUnit属性,HWUnit为芯片的SPI外设,多个ExternalDevice可以拥有同一个HWUnit,每个HWUnit只能对应一路SPI外设。
HWUnit:每路HWUnit对应芯片的一路SPI外设。
Squence:Squence是SPI组件传输的基本单位,一路Squence拥有最少一个Job或多个Job,一路Squence传输可以被取消传输。
也就说SPI的传输基本单位是Squence,Squence里包含了若干个Job,Job里包含了若干个Channel,Job附带ExternalDevice的属性(波特率、时钟极性、时钟相位等),ExternalDevice附带HWUnit的属性(HW上某路SPI),Channel附带SPI数据的属性(MSB还是LSB、数据宽度等)、数据缓冲区地址等。
Squence传输时首先判断Job的优先级,然后判断这个Job的ExternalDevice属性并修改对应的HW寄存器(芯片上某路SPI的属性,波特率、时钟极性、时钟相位、数据帧和数据帧之间时间间隔等),然后判断要传输哪个Channel,再次修改对应的HW寄存器(MSB还是LSB、数据宽度、发送接收等)。
多个Squence可以拥有同一个Job,多个Job可以拥有同一个Channel,多个Job可以拥有同一个ExternalDevice,多个ExternalDevice可以拥有同一个HWUnit,但每个HWUnit只能对应一路SPI外设。
(三)、异步传输、同步传输:
同步传输:同步传输调用Spi_SyncTransmit()来进行传输,也就是说调用Spi_SyncTransmit()程序进入阻塞状态,在传输完成或传输超时后退出。
同步传输仅在LEVEL0和LEVEL2下支持。
异步传输:异步传输调用Spi_AsyncTransmit()来进行传输,调用Spi_AsyncTransmit()不会使程序进入阻塞状态,可通过Spi_GetSequenceResult()来获取Sequence是否传输完成。
异步传输模式有中断模式和轮询模式两种,中断可配置SPI中断和DMA中断,轮询模式在Spi_MainFunction_Handling()里进行传输数据,如果用轮询模式需要周期运行Spi_MainFunction_Handling。
可以通过Spi_SetAsyncMode()来设置中断模式还是轮询模式。
异步传输仅在LEVEL1和LEVEL2下支持。
(四)、LEVEL0、LEVEL1、LEVEL2:
可通过SPI_LEVEL_DELIVERED这个宏定义来配置Spi组件传输模式。
LEVEL0下只支持同步传输,不支持异步传输。
LEVEL1下不支持同步传输,只支持异步传输的中断模式。
LEVEL3下支持同步传输,支持异步传输的轮询模式和中断模式,默认下为轮询模式。
二、代码架构
S32K的MCAL的SPI源代码如下:
Spi_FlexIO_Irq.c和Spi_FlexIO.c实现芯片的FlexIO的SPI功能的寄存器操作,Spi_LPspi_Irq.c和Spi_LPspi.c实现芯片的LPSPI功能的寄存器操作,Spi_IPW.c封装前面的LPSPI和FlexIO的功能实现,判断上层是使用哪种方式实现SPI的便调用Spi_LPspi.c或Spi_FlexIO.c里的函数,最后Spi.c封装一层提供AUTOSAR标准接口供外部组件调用。
三、主要变量和类型描述
Spi_PBcfg.c里描述了SPI组件的配置项。
SpiSequenceConfig_PB0[]描述了各个Sequence的属性,含Sequence支持的HWType(LPSpi、FlexIO等)、Job项、Sequence传输完成回调、是否可被取消传输等。Job项关联SpiJobConfig_PB0[]。
SpiJobConfig_PB0[]描述了各个Job的属性,含Channel项、Job传输开始和传输结束回调、Job优先级、ExternalDevice项、HWUnit等。Channel项关联SpiChannelConfig_PB0[],ExternalDevice项关联SpiExternalDeviceAttrsPB0[]。
SpiChannelConfig_PB0[]描述了各个Channel的属性,含数据缓冲区类型、初始数据、数据缓冲区地址等。
SpiExternalDeviceAttrsPB0[]描述了SPI连接的属性,如SPI的波特率、时钟相位、时钟极性等。Job传输时会根据SpiExternalDeviceAttrsPB0[]里的属性来修改ExternalDevice对应的哪路SPI的寄存器。
SpiAttributesConfig_PB0描述了每个Channel对应的SPI属性,如数据宽度、MSB还是LSB。
四、主要代码描述
Spi_Init()和Spi_DeInit()实现SPI组件的初始化和反初始化。
Spi_WriteIB()将数据写入IB缓冲区。入参Channel为Channel索引号,索引号在Spi_Cfg.h里有。入参DataBufferPtr为要写入某路Channel的IB缓冲区的地址,注意数据长度要和某路Channel的IB缓冲区的长度对齐。
Spi_ReadIB()读取IB缓冲区的数据。入参Channel为Channel索引号,索引号在Spi_Cfg.h里有。入参DataBufferPtr为要读出某路Channel的IB缓冲区的地址,注意数据长度要和某路Channel的IB缓冲区的长度对齐。
Spi_SetupEB()用来设置某路Channel的外部数据缓冲区地址。入参Channel为Channel索引号,索引号在Spi_Cfg.h里有。入参SrcDataBufferPtr为某路Channel的发送数据缓冲区地址,DesDataBufferPtr为某路Channel的接收数据缓冲区地址,Length为数据长度。Spi_SetupEB()在SPI_CHANNEL_BUFFERS_ALLOWED宏定义为1或2下才有。Spi_SetupEB()的使用必须是该路Channel的缓冲区属性为EB才有效果。
Spi_AsyncTransmit()和Spi_SyncTransmit()分别为异步传输和同步传输,使用条件受LEVEL0、LEVEL1、LEVEL2影响。入参Sequence为Sequence索引号,索引号在Spi_Cfg.h里有。
Spi_MainFunction_Handling(),当在LEVEL2下有使用异步传输轮询模式时,Spi_MainFunction_Handling()要周期运行。
Spi_GetHWUnitStatus()、Spi_GetJobResult()、Spi_GetSequenceResult()分别为获取某路HWUnit、Job、Sequence的状态。
五、EBTresos配置
主要配置如下:
SpiGeneral里有配置SPI的时钟源,传输超时时间等,SpiChannelBuffersAllowed为宏定义SPI_CHANNEL_BUFFERS_ALLOWED的选项。
SpiPhyUnit里有配置HWUnit的选项,如选择LPSpi还是FlexIO_Spi、DMA还是FIFO等。如果想要DMA传输,那么要勾选SpiPhyUnit里的SpiPhyUnitSync和SpiGeneral里的SpiGlobalDmaEnable,然后在SpiPhyUnit里的SpiPhyUnitAsyncMethod处选择DMA,然后选择DMA通道,DMA通道需要在Mcl组件里也配好。
SpiExternalDevice配置SPI的属性,如波特率、时钟极性、时钟相位等。SpiHwUnit处选择SpiPhyUnit对应的索引号。
SpiBaudrate处选择该Spi驱动的波特率。
SpiCsPolarity处选择Spi片选脚的极性,也就是数据传输时CS的电平。
SpiDataShiftEdge处选择时钟跳变时是第一个边沿采集数据还是第二个边沿采集数据。
SpiEnableCs处选择是否让外设自己输出CS。
SpiShiftClockIdleLevel选择时钟空闲时的电平状态。
SpiCsContinous处选择CS脚是否连续输出CS。
SpiPinConfig 处选择SOUT和SOUT脚的方向。
SpiDataShiftEdge和SpiShiftClockIdleLevel其实就是CPHA和CPOL,对应SPI的四种模式:
SpiChannel来配置Channel的属性。
SpiChannelType处选择Buffer类型是EB还是IB。
SpiDefaultData处选择IB类型Buffer的默认数据。
SpiIbNBuffers处选择IB类型Buffer的大小。
SpiDeviceAssignment处选择该Job用的哪路SpiExternalDevice。
SpiChannelList处选择该Job有哪些Channel。
SpiJobAssignment处选择该Sequence有哪些Job。
六、使用范例
在配置完SPI的各个配置项后,就可以调用Spi_Init()来进行初始化。初始化后调用Spi_AsyncTransmit()或Spi_SyncTransmit()进行传输SPI数据。
比如我配置了第一个Channel缓冲区为IB,第二个Channel缓冲区为EB。在调用Spi_Init()初始化后,我还需要调用Spi_SetupEB()来对第二个Channel进行配置收发数据缓冲区,而第一个Channel是IB,配置为IB时SPI组件里已经有初始发送数据和收发数据缓冲区了,如果我想对第一个Channel更改发送数据,则调用Spi_WriteIB进行更改。最后我调用Spi_GetSequenceResult()来获取是否传输完成,传输完成后第一个Channel的数据读取需要调用Spi_ReadIB()来进行读取,第二个Channel的数据在之前调用Spi_SetupEB()时给予的数据缓冲区里。
七、参考资料
https://www.autosar.org/fileadmin/standards/R22-11/CP/AUTOSAR_SWS_SPIHandlerDriver.pdf
https://www.autosar.org/fileadmin/standards/R22-11/CP/AUTOSAR_SRS_SPIHandlerDriver.pdf
https://www.autosar.org/fileadmin/standards/R23-11/CP/AUTOSAR_CP_EXP_LayeredSoftwareArchitecture.pdf
总结
AUTOSAR文档里写了SPI驱动不支持从机模式,但S32K的MCAL设置里还是有支持从机的选项。
SPI使用前要在Port组件里把引脚设置为SPI模式,Mcu组件里使能SPI外设时钟,设置SPI时钟参考点。