S32K3学习笔记—S32K3之LPSPI
文章目录
1.前言
SPI是比较重要的一种通信方式,大多数复杂驱动和MCU之间都支持SPI通信,甚至还见过有需求是用SPI做一个bootloder,只要有想法皆可实现。本文主要是记录一下自己用过的几种SPI不同形式,以下是基于S32K324做的一个SPI通信的例子,会分别在两个核分别注册一路SPI,以及记录一下同步、异步、有无DMA的SPI方式。本文比较长,主要是想尽可能的把知道的都详细记录一下。
2.原理
SPI,是一种高速的,全双工,同步的通信总线, 主要用于MCU和外设进行通信, 有主从模式可配置,有4个外部接口,片选CS,时钟SCK,数据输入MISO,数据输出MOSI.
Block diagram
1.control logic:控制器参数和通信参数, 通过寄存器和TX FIFO配置
2.shift register:将数据从总线移动到RX FIFO
3.FIFO:发送和接收4byte的FIFO
4.External SPI Interface : 四个外部接口
Register
1.对于SPI的寄存器是必须要去看的,芯片手册上对每个寄存器都有解释,这里就不重复,芯片手册链接如下:S32K3XXRM.pdf
Sequence/Job/Channel
这三者的关系必须要理清楚,Channel是软件层面的,与硬件的物理通道没关系。Channel只是对Bufff做一些配置。而Job是将Channel和外设绑定的。因此一个Channel可以同时属于多个Job。但如果管理不好,很容易造成buff里面的数据混乱。所以下面的例子和我自己实际用法都是Job和Channel一一对应。同样,一个Sequence下面可以有多个Job,并且Job是有优先级的。传输都是以Sequence为单位的,接收是具体到某个Channel。
TRAILINHG/LEADING
1.CPOL-时钟极性 : 时钟空闲时的电平, CPOL = 0,表示时钟空闲为低电平
2.CPHA-时钟相位: 读取数据和发送数据的时钟沿, CPHA = 0,表示一个时钟周期内上升沿读取数据
总结一下表格:
CPOL = 1 | CPHA = 0 | TRAILING |
---|---|---|
CPHA = 1 | LEADING | |
CPOL = 0 | CPHA = 0 | TRAILING |
CPHA = 1 | LEADING |
如果接收的数据波形不对,可能是这个配置错了
3.EB配置
根据SPI的模式不同其依赖的模块也不一样。如果是同步模式,其依赖于 MCU、PORT、SPI。如果是异步SPI 所用的模块包括,MCU、PORT、SPI、MCL、Platfom。
3.1 通用配置
对于MCU模块来说,就比较简单,只需要做如下操作,添加两个给MCU模块的时钟就行,MCU其他的配置请参考:S32K3学习笔记—S32K3之MCU模块
1.根据芯片手册可知,SPI0支持最高80M,SPI1-5支持最大40M,此处选择时钟源AIPS_PLAT_CLK和AIPS_SLOW_CLK
其次是PORT模块,我们是要在两个核上各自注册一路SPI,因此相应的PORT口也需要注册到不同的核,具体操作如下:
1.此处名字,建议按之前的PORTA、B等来分,可参考:S32K3学习笔记—S32K3之MCU模块,此处为了方便记录和说明,单独把一路SPI的PIN单独建一个分组
2.将core0的SPI的PORT全部配置一下
接下就是PortPin的配置,可参考:S32K3学习笔记—S32K3之Gpt、Dio、Platform 来配置DIO、Platform, 下面简答配置一个
1. clk是输出方向
2. PIN的模式,选LSPI0_LSPI0_SCK_OUT
3. 默认低电平
1. 将这几个pin都分配到core0
我们只需要按照这个步骤把SPI0的这几个Port都这样类似注册到core0,模式、MSCR及输出方向别设置错就行。值得注意的一点就是CS的初始化默认电平,需要根据时钟空闲电平来设置。这个需要看具体的从芯片的SPI时序才能确认。
同理将SPI1的port分配到core1就行。
至此,基本的通用配置就完成了,接下来就是分模式来配置两种SPI。
3.2 同步SPI配置
同步SPI我们就用的比较的简单,不用DMA的形式,当然也可以选择用DMA,具体看需求。
1.多核使能
2.SPI通道buffers支持的种类。 0—IB 、1—EB 、 2—both IB and EB
3.是否使能中断处理sequences
4.SpiLevel ,此处先配置同步。 0— 同步、 1—异步、2—同步&异步
5.是否对不同的 sequences并发调用Spi_SyncTransmit()
6.是否使能DMA
1.选择EB还是IB, 需要和前面的SpiChannelBuffersAllowed对应
2.传输数据的宽度,这是从芯片决定的,按照从芯片SPI的数据位来确定,1~64bits,需要8个bit对齐,例如配置12bit,那么在传输的时候,我们只能取低12位,高四位忽略。
3.只有选择EB,此处才可以动态配置数据缓冲区的最大宽度,单位byte
4.数据传输的第一个起始位是高位还是低位,也是根据从芯片来选择
5.将SPI资源分配到核0
接下来就是外设驱动配置,这个地方主要是根据从芯片来配置,
1.波特率设置,根据芯片和实际需求设置
2.片选通道选择
3.片选传输时的极性
4、6.使能是否需要自动拉低片选,如果使能就不需要手动拉片选了
5.数据传输的极性,LEADING、TRAILING,具体选择原理中有奖
7.硬件通道,CSIB0可以理解为SPI0
8.时钟空闲电平
1.将此SPI的分配到核0
在配置完channel和ExternalDevice之后,就需要将这两个联系起来,SpiJob就可以将两者联系起来。具体配置如下:
1.SpiJob优先级,0最低,3最高
2.此SpiJob对外部设备的引用
1.对于SpiChannelList中,主要是引用SpiChannel,通过SpiJob联系Spichannel和SpiExternalDevice
1.对于同步来说,我们只需要配置SpiSequenceId就行
2.是否使能DMA传输
1.这个地方是联系SpiJob和SpiSequence的,每个SpiSequence可以有很多个Job
1.选择SPI通道
2.SPI的主从模式
3.是否为同步传输。如使能只能进行同步传输
4.时钟参考,这个地方适合MCU模块建立连接的
至此,core0的这一路SPI算是配置完成。类似的我们可以在core1配置另外一路SPI,没啥区别,就是将资源分配到core1和除SPI0外的SPI的时钟是40M,具体配置如下:
1.其他的和core0一直,这个地方将SPIchannel注册到core1
1.SpiExternalDevice注册到core1
1.SPI1_5的时钟和SPI0的不一致,此次选择SPI1_5
SPI同步的形式就配置完成,可以的做个验证。
3.3 异步SPI配置
可能比较大一点的项目都是同步+异步的形式,下面将记录一下异步SPI将用DMA的形式,其关联模块包括Mcl、Platform
首先是Platform模块,使能DMA和SPI的中断。
1.使能对应的DMA和SPI中断
DMA先简单配置,先用起来,后续专门来记录这个DMA
1.多核使能
2.用于初始化和取消初始化Mcl驱动程序的分区
3.使能DMA
1.dmaLogicInstance_ConfigType默认配置就行,分配到core0
1.硬件通道选择
2.注册DMA回调函数
3.注册到core1
4.使能全局配置
1.使能DMA请求,其他的保持默认配置就好
1.EB&IB
2.同步&异步
3.使能DMA
1.异步模式配置一个回调通知函数,代码需要自注册
1.失能同步传输
2.使能异步传输
3.配置DMA的逻辑通道
至此,异步SPI的配置就完成了,接下来就是调试,可以简单的测试。
4.代码调试
4.1 同步SPI调试
对于同步传输,最简单的就是找到一个从芯片的可读可写的一个寄存器,直接先写一下寄存器再读一下寄存器,看是否SPI通信成功。
int main_c0(void)
{
Spi_DataBufferType TxChBuf0[4] = {0x10,0x11,0x0,0x12};
Spi_DataBufferType RxChBuf0[4];
/* Init clock */
#if (STD_ON == MCU_PRECOMPILE_SUPPORT)
Mcu_Init(NULL_PTR);
#else
Mcu_Init(&Mcu_Config_VS_0);
#endif
#if (STD_ON == MCU_INIT_CLOCK)
/* Initialize the clock tree and apply PLL as system clock */
Mcu_InitClock(McuClockSettingConfig_0);
#if (STD_OFF == MCU_NO_PLL)
while ( MCU_PLL_LOCKED != Mcu_GetPllStatus() )
{
/* Busy wait until the System PLL is locked */
}
Mcu_DistributePllClock();
#endif
Mcu_SetMode(McuModeSettingConf_0);
#else
#error "The Mcu Init Clock API should be enabled from the Mcu driver"
#endif
/* Initialize all pins using the Port driver */
#if (STD_ON == PORT_PRECOMPILE_SUPPORT)
Port_Init(NULL_PTR);
#else
Port_Init(&Port_Config_VS_0);
#endif
/* Initialize Platform driver */
Platform_Init(NULL_PTR);
Spi_Init(NULL_PTR);
/* Set up external buffer to transmission and reception */
Spi_SetupEB(SpiConf_SpiSequence_SpiSequence_0, TxChBuf0, RxChBuf0, 4);
/* This sequence of slave: transferring 10 frame 16 bits using Dma */
Spi_SyncTransmit(SpiConf_SpiSequence_SpiSequence_0);
while (SPI_SEQ_OK != Spi_GetSequenceResult(SpiConf_SpiSequence_SpiSequence_0));
for(;;)
{
/* do nothing */
}
return (0U);
}
int main_c1(void)
{
Spi_DataBufferType TxChBuf1[4] = {0x10,0x11,0x0,0x12};
Spi_DataBufferType RxChBuf1[4];
/* Initialize all pins using the Port driver */
#if (STD_ON == PORT_PRECOMPILE_SUPPORT)
Port_Init(NULL_PTR);
#else
Port_Init(&Port_Config_VS_0);
#endif
/* Initialize Platform driver */
Platform_Init(NULL_PTR);
Spi_Init(NULL_PTR);
/* Set up external buffer to transmission and reception */
Spi_SetupEB(SpiConf_SpiSequence_SpiSequence_1, TxChBuf1, RxChBuf1, 4);
/* This sequence of slave: transferring 10 frame 16 bits using Dma */
Spi_SyncTransmit(SpiConf_SpiSequence_SpiSequence_1);
while (SPI_SEQ_OK != Spi_GetSequenceResult(SpiConf_SpiSequence_SpiSequence_1));
for(;;)
{
/* do nothing */
}
return (0U);
}
SPI就是要么一步成功,要么从头查起,一旦出问题就得拿个示波器去抓时序波形,波形是最直观的,也是最快能定位问题的。
4.2异步SPI调试
core0的操作和同步的一样,这只记录一下core1的调试。
int main_c1(void)
{
Spi_DataBufferType TxChBuf1[4] = {0x10,0x11,0x0,0x12};
Spi_DataBufferType RxChBuf1[4];
/* Initialize all pins using the Port driver */
#if (STD_ON == PORT_PRECOMPILE_SUPPORT)
Port_Init(NULL_PTR);
#else
Port_Init(&Port_Config_VS_0);
#endif
/* Initialize Platform driver */
Platform_Init(NULL_PTR);
Spi_Init(NULL_PTR);
/*******************************the first*****************************************/
/* Set up external buffer to transmission and reception */
Spi_SetupEB(SpiConf_SpiSequence_SpiSequence_1, TxChBuf1, RxChBuf1, 4);
/* This sequence of slave: transferring 10 frame 16 bits using Dma */
Spi_AsyncTransmit(SpiConf_SpiSequence_SpiSequence_1);
while (SPI_BUSY != Spi_axSpiHwUnitQueueArray[0].Status)
{
Spi_MainFunction_Handling();
}
/*******************************the first*****************************************/
/*******************************The second*****************************************/
/* Using interrupt in transfer */
Spi_SetAsyncMode(SPI_INTERRUPT_MODE);
/* Set up external buffer to transmission and reception */
Spi_SetupEB(SpiConf_SpiSequence_SpiSequence_1, TxChBuf1, RxChBuf1, 4);
/* This sequence of slave: transferring 10 frame 16 bits using Dma */
Spi_AsyncTransmit(SpiConf_SpiSequence_SpiSequence_1);
/*The next steps are all in the DMA_ISR*/
/*******************************The second*****************************************/
for(;;)
{
/* do nothing */
}
return (0U);
}
void SPI_CallBack2(void)
{
Spi_HWUnitType HWUnit = 0;
uint8 txchn = 0,rxchn = 0;
for(;HWUnit < (Spi_HWUnitType) SPI_MAX_HWUNIT;HWUnit++)
{
if(TRUE == Spi_apxSpiConfigPtr[Spi_GetCoreID]->HWUnitConfig[HWUnit].PhyUnitConfig->IpConfig.LpspiIpConfig->DmaUsed)
{
txchn = Spi_apxSpiConfigPtr[Spi_GetCoreID]->HWUnitConfig[HWUnit].PhyUnitConfig->IpConfig.LpspiIpConfig->TxDmaChannel;
rxchn = Spi_apxSpiConfigPtr[Spi_GetCoreID]->HWUnitConfig[HWUnit].PhyUnitConfig->IpConfig.LpspiIpConfig->RxDmaChannel;
Mcl_SetDmaChannelCommand(txchn, DMA_IP_CH_CLEAR_DONE);
Mcl_SetDmaChannelCommand(rxchn, DMA_IP_CH_CLEAR_DONE);
}
else
{
/* do nothing*/
}
}
}