本篇文章首先从理论讲起,从AUTOSAR规范以及MCAL手册两个不同角度(前者偏理论,后者偏实践)介绍了SPI模块的背景概念与理论,帮助读者在实际配置之前能有个理论的框架。然后详细的介绍了在TC397平台使用EB tresos对SPI驱动模块进行配置与调试的实战过程,帮助第一次接触这个模块的读者能够更快的上手来实现符合自己项目要求的开发工作,完成SPI通信。文章分别从同步和异步两种方式介绍了配置以及调试的过程,较为全面的介绍了SPI涉及的相关功能。
SPI作为常见片外外设芯片(如高低边驱动芯片,CAN收发器芯片,电源管理芯片等)的通信方式,对于扩展微控制器功能,完成板级设计需求有着至关重要的作用,涉及的外设芯片驱动都要使用微控制器内的SPI模块来完成驱动。AUTOSAR规范下的SPI相比常见的SPI做了很多扩展,首先它定义了不同等级的SPI模块实现来支持包括同步/异步的通信模式(它实实在在的定义了同步和异步的实现),这部分往往是嵌入式工程师针对产品需求以及外围芯片驱动特点予以特定的实现;同时它增加了针对同一SPI控制器下使用不同CS以及多控制器连接的片外外设驱动连续动作的支持,试图在SPI模块层面来定义片外外设的动作序列,而这部分往往在片外外设驱动中已有实现。所以,有常见SPI使用经验的嵌入式工程师,往往在使用AUTOSAR规范下的SPI时,会出现很多认知上的偏差,当想把它配置成常用的形式时,又经常会出现错误,本篇文章会帮您弥补这之间的认知偏差。
目录
AUTOSAR规范解析
SPI驱动程序为通过SPI总线连接的设备提供读写服务,例如向EEPROM、看门狗、I/O ASICs提供SPI通信访问。它还提供了所需的机制来配置片上SPI控制器。AUTOSAR定义了可选择的功能级别和可配置特性,以允许用户充分利用微控制器特性。
要配置SPI模块,应遵循以下步骤:
- 应选择SPI处理器/驱动器的功能级别,并配置可选功能。
- SPI Channels应根据数据使用情况定义,它们可以被缓冲在SPI模块(IB)内部或由用户(EB)提供。
- SPI Jobs应根据硬件属性(CS)分别定义,它们将包含使用这些硬件的信道列表。
- 作为最后一步,应定义Sequences of Jobs,以便以排序方式(优先级排序等)传输数据。
SPI模块的一般行为根据选择的功能级别可以是异步的或同步的。
缩略语
- SPI:Serial Peripheral Interface,主芯片与外围功能芯片常见的通信接口。
- CS:Chip Select,片选信号。
- MISO:Master Input Slave Output,主入从出信号。
- MOSI:Master Output Slave Input,主出从入信号。
- EB:Externally buffered channels,包含位于SPI控制器之外的要传输数据的缓冲区。
- IB:Internally buffered channels,包含位于SPI控制器内部要传输的数据的缓冲区。
- Channel:通道是一种数据交换格式,用于定义具有相同配置参数、相同大小和数据指针(源和目标)或位置的数据元素集合。
- Job:作业由一个或多个具有相同芯片选择(在处理作业期间不释放)的通道组成。作业被认为是原子的,因此不能被另一个作业中断。作业可以分配的优先级。
- Sequence:序列用于连续传输多个作业,但可以使用优先级机制在作业之间重新安排序列传输。序列传输是可中断的(由另一个序列传输中断),或者根据静态配置而定。
一般行为
我们首先大概来了解一下AUTOSAR规范定义的SPI三个功能级别:
- LEVEL 0:简单的同步SPI通信,访问基于同步处理和FIFO策略来处理多个访问。可配置不同类型缓冲区来优化利用硬件功能。
- LEVEL 1:异步的SPI通信,通信基于异步行为,并具有优先级策略来处理多个访问。缓冲区可以配置为“Simple Synchronous”等级。
- LEVEL 2:增强型(基于同步/异步)的SPI通信,通信基于异步行为或同步处理,在执行期间使用中断或轮询机制进行选择,并具有处理多个访问的优先级策略。可配置不同类型缓冲区来优化利用硬件功能。
下图介绍了AUTOSAR规范定义的Channel、Job以及Sequence之间的关系,简而言之Channel在逻辑上对应着与外围芯片交互的一种数据格式(地址+数据等),Job则对应着针对某个具体的从设备完成某个子功能的Channel列表,Sequence可以完成针对需要多个外设芯片联动实现的功能完成跨越从设备的Job列表。
通道数据的配置应基于硬件芯片支持的范围。一般为固定值(8/16/32位)。为了能够充分利用所有微控制器的功能,所有不同SPI级别都可以选择不同的缓冲区类型(EB/IB)。
EB/IB
- 内部缓冲通道(IB):用于传输/接收数据的缓冲器由SPI控制器或者驱动程序提供。
- 外部缓冲信道(EB):用于传输/接收数据的缓冲器由用户提供。
可以根据下面描述的3种用例,使用这两种通道缓冲:
- 用法0:SPI控制器/驱动程序只管理内部缓冲区。
- 用法1:SPI控制器/驱动程序只管理外部缓冲区。
- 用法2:SPI控制器/驱动程序管理这两种缓冲器类型。
内部缓冲器通道的目的是利用微控制器通过硬件实现这一特性,如果硬件不支持,这一特性应通过软件进行仿真。外部缓冲区通道使用位于外部的用户定义缓冲区。SPI控制器/驱动程序不会监视它们,用户应保证在传输过程中注意缓冲区中数据的一致性。下面是两种Buffer的不同特性。
- IB:缓冲机制隐藏在硬件中实现,给定的256字节大小的最大发送缓存满足大部分使用需求。从设备建议采用菊花链链接方式,数据传输小于10字节。
- EB:提供了用于大量数据传输的有效机制,为不同的设备发送多样的数据表,支持超过硬件Buffer IB提供的数据大小限制。
LEVEL 0/1/2
Level 0功能级别的SPI驱动仅仅提供一组有限的服务,仅处理简单的同步传输,它意味着调用传输服务的函数会被阻塞,直到传输完成。英飞凌提供的驱动中,用户会调用Spi_SyncTransmit()完成同步传输,他会在Spi_lSynTransErrCheck()函数中等待Spi_kGlobalConfigPtr->SyncTimeout(在MCAL中对应SpiSyncTransmitTimeoutDuration配置)定义次数的循环,来判断SPI对应状态寄存器(STATUS寄存器中的TXF)是否为发送完成或者有错误发生(STATUS寄存器中的ERRORFLAGS),若等待超时则返回错误。
在Level 0等级下,不用调用Spi_SetAsyncMode()来配置异步模式(因为不涉及异步),并且SPI_SUPPORT_CONCURRENT_SYNC_TRANSMIT配置得以生效,如果配置为STD_ON,则Spi_SyncTransmit()允许不同序列同时操作不同的SPI控制器(驱动用户需保证操作的SPI控制器为不同的,一般配置为STD_OFF以免发生误操作)。
Level 1功能级别仅仅提供处理异步通信的一组有限服务。异步传输意味着当传输正在进行时,调用传输服务的用户不会被阻止。此外,用户可以在传输结束时得到通知。Level 1功能级别可以允许同时传输多个序列。这意味着在序列传输期间,应可以评估传输另一个序列的请求,以便开始新序列或根据它的最高优先级作业拒绝它。异步需要使用Spi_SetAsyncMode()来配置异步模式(默认为轮询模式),如果使用轮询模式则需要周期调用Spi_MainFunction()来轮询处理中断状态(这里一般我们会配置为中断模式)。
英飞凌提供的异步传输通过调用Spi_AsyncTransmit()完成,SPI的静态代码默认使用了DMA完成了在SPI传输过程中数据搬运工作(而不是我们常见的在中断中完成),SPI的中断则用来触发DMA操作。整个异步发送过程简化如下(将Sequence的取消以及中断和优先级排序过程省略,发送过程包含接收):
上图介绍的流程是勾选了Job中的SpiFrameBasedCS选项,如果不勾选则Job之间的切换是利用SPI控制器的PT2中断(配置为在SPI_WAIT_STATE状态下引起中断),且使用Move Counter Mode,在这种模式下,当MC.MCOUNT对TXFIFO的写入访问(例如DMA移动)写入的数据被被移出时,从设备选择信号始终保持active状态,有兴趣的读者可以梳理这种模式下的流程。
Level 2功能级别为驱动使用用户提供完整的服务,包含所有Level 0与Level 1服务。
为了利用异步传输机制的优点,SPI模块的第1级和第2级功能级别具有关于打断传输序列的可选功能(通过配置SpiInterruptibleSeqAllowed使能)。它通过SpiInterruptibleSequence配置项选择,如果开启则当前的Sequence会被具有更高优先级Job的序列在执行下一个Job时打断。
Sequence diagrams
下图是SPI在整个生命周期的行为图。
下图为使用IB, 进行完成SPI同步读写的动态图,Channel和Job与Sequence配置如下表。
Sequence | Job | Channel | |
name | Priority | ||
ID0 | ID1 | High | ID0...ID3 |
ID2 | Low | ID4...ID10 |
下图为使用IB, 进行完成SPI异步读写的动态图,Channel和Job与Sequence配置如下表。
Sequence | Job | Channel | ||
Name | Interruptible | Name | Priority | |
ID0 | Yes | ID1 | 2 | ID0...ID3 |
ID2 | 1 | ID4...ID10 | ||
ID1 | No | ID3 | 3 | ID11...ID13 |
下图为使用EB, 进行完成SPI异步读写的动态图,Channel和Job与Sequence配置如下表。
Sequence | Job | Channel | ||
Name | Interruptible | Name | Priority | |
ID0 | Yes | ID1 | 2 | ID0...ID3 |
ID2 | 1 | ID4...ID10 | ||
ID1 | No | ID3 | 3 | ID11...ID13 |
MCAL用户手册
下图展示了SPI模块与其余模块的软硬件接口图。
QSPI是SPI的主要硬件外设, SPI驱动程序使用QSPI进行同步和异步数据传输。驱动程序使用的主要硬件功能包括:
- QSPI包含的FIFO(Tx和Rx)被配置为在continuous data模式下工作。
- QSPI包含的FIFO(Tx和Rx)的中断被配置为在single move模式下工作。
- SPI驱动程序在异步数据传输过程中使用QSPI的Move Counter Mode模式。
SPI驱动使用QSPI的硬件事件包括:
- FIFO发送事件-TXF。
- FIFO接收事件-RXF。
- 错误状态中断(TxFIFO underflow / overflow, RxFIFO underflow / overflow, Expect timeout, parity error-ERRORFLAGS。
- 状态转化:PT2。
SPI驱动程序依赖于SRC(中断路由器),根据发送FIFO事件、接收FIFO事件、错误情况和状态转换将中断发送到CPU或DMA,状态转换表示数据传输和接收的下图的何种状态。
SPI驱动程序依赖于DMA,SPI驱动程序使用Linked list mode的DMA在异步模式(LEVEL 1/2)中进行数据传输。SPI驱动程序使用DMA驱动程序提供的 API接口来使用DMA功能。使用的硬件事件包括:
- 如果在数据传输过程中遇到任何ME(移动引擎)错误,DMA会引发一个由DMA驱动程序处理的错误中断。
- 如果发生Channel传输完成事件,DMA通过调用Spi_QspiDmacallout来通知SPI模块,从而触发下一次SPI的Channel传输。
SPI驱动程序依赖于SCU和PORT,前者提供SPI时钟,后者配置MOSI、MISO、SCLK和SLSO引脚。对于CS_VIA_GPIO,SPI驱动程序直接访问PORT寄存器,控制芯片选择(SLSO)状态。
下图展示了SPI驱动的代码文件包含关系图。
集成要点
与AUTOSAR Stack集成
- EcuM:ECU管理模块是AUTOSAR基础软件的一部分,用于管理ECU。具体来说,在MCAL的上下文中,EcuM用于初始化和解初始化SPI。MCAL包中提供的EcuM模块是stub code,需要在集成阶段用完整的EcuM模块替换(添加调用SPI初始化和解初始化接口调用)。
- Memory mapping:内存映射是 AUTOSAR 中的一个概念,它允许将文本、变量、
存区域新定位到用户特定的内存空间。为了实现这一点在Spi_MemMap.h定义了不同的宏定义实现这一功能。MCAL包中提供了Spi_MemMap.h文件,作为stub code。集成人员必须在内存区宏中放置适当的编译宏。这些宏确保将元素重新定位到正确的内存区域。列出内存区宏的示例实现如下所示。#if defined SPI_START_SEC_VAR_CLEARED_ASIL_B_GLOBAL_UNSPECIFIED /*****User pragmas here for Non-cached LMU*****/ #undef SPI_START_SEC_VAR_CLEARED_ASIL_B_GLOBAL_UNSPECIFIED #undef MEMMAP_ERROR #elif defined SPI_STOP_SEC_VAR_CLEARED_ASIL_B_GLOBAL_UNSPECIFIED #ifdef _TASKING_C_TRICORE_ /*****User pragmas here for Non-cached LMU*****/ #undef SPI_STOP_SEC_VAR_CLEARED_ASIL_B_GLOBAL_UNSPECIFIED #undef MEMMAP_ERROR /**** CORE[x] CONFIG DATA -- PF[x] ****/ /*[x]=0..5*/ #elif defined SPI_START_SEC_CONFIG_DATA_ASIL_B_CORE[x]_UNSPECIFIED /*****User pragmas here for PF[x]*****/ #undef SPI_START_SEC_CONFIG_DATA_ASIL_B_CORE0_UNSPECIFIED #undef MEMMAP_ERROR #elif defined SPI_STOP_SEC_CONFIG_DATA_ASIL_B_CORE0_UNSPECIFIED /*****User pragmas here for PF[x]*****/ #undef SPI_STOP_SEC_CONFIG_DATA_ASIL_B_CORE0_UNSPECIFIED #undef MEMMAP_ERROR /**** CODE -- PF[x] ****/ #elif defined SPI_START_SEC_CODE_ASIL_B_GLOBAL /*****User pragmas here for PF[x]*****/ #undef SPI_START_SEC_CODE_ASIL_B_GLOBAL #undef MEMMAP_ERROR #elif defined SPI_STOP_SEC_CODE_ASIL_B_GLOBAL /*****User pragmas here for PF[x]*****/ #undef SPI_STOP_SEC_CODE_ASIL_B_GLOBAL #undef MEMMAP_ERROR #endif #if defined MEMMAP_ERROR #error "SPI_MemMap.h, wrong pragma command" #endif
- DET:DET模块是AUTOSAR基础软件的一部分,用于处理BSW模块报告的所有开发和运行时错误。SPI驱动程序通过Det_ReportError()将所有开发错误报告给DET模块。使用SPI驱动程序的用户必须处理通过Det_ReportError()报告给 DET模块的所有错误。MCAL包中提供了Det.h和 Det.c文件,作为stub code,在集成阶段需要将其替换为完整的DET模块。
- DEM:DEM模块是AUTOSAR基础软件的一部分,用于处理BSW模块上报的故障信息。SPI 驱动程序的用户应处理报告给DEM 模块的所有生产错误(失败/通过)。在AUTOSAR版本 4.2.2中用于报告的接口是 Dem_ReportErrorStatus(),对于AUTOSAR 版本4.4.0为 Dem_SetEventStatus()。MCAL中提供了Dem.h和 Dem.c文件为stub code,在集成阶段中需要替换为完整的DEM模块。
- SchM:SchM模块是RTE的一部分,用于管理BSW调度程序。SPI驱动程序使用SchM_Spi.h文件中定义的独占区域来保护SFRs和变量,防止不同线程的并发访问。SPI驱动程序中识别的SchM包括:Queue_Update与SyncLock,SchM_Spi.h和SchM_Spi.c文件作为示例代码在MCAL包中提供,需要由集成商进行更新。用户必须实现SPI驱动程序定义的SchM 函数,作为API调用的CPU的中断暂停/恢复。SchM函数的示例实现如下所示。
/**** Sample implementation of SchM_Spi.c ****/ void SchM_Enter_Spi_Queue_Update(void) { /* Start of Critical Section */ SuspendAllInterrupts(); /* Suspend CPU core interrupt */ } void SchM_Exit_Spi_Queue_Update(void) { /* End of Critical Section */ ResumeAllInterrupts(); /* Resume CPU core interrupt */ } void SchM_Enter_Spi_SyncLock(void) { /* Start of Critical Section */ SuspendAllInterrupts(); /* Suspend CPU core interrupt */ } void SchM_Exit_Spi_SyncLock(void) { /* End of Critical Section */ ResumeAllInterrupts(); /* Resume CPU core interrupt */ }
- Safety error:SPI驱动程序通过Mcal_ReportSafetyError()报告所有检测到的安全错误。驱动只执行安全错误的检测和报告。报告的错误应由用户处理。Mcal_ReportSafetyError()在Mcal_SafetyError.c和Mcal_SafetyError.h文件中提供,作为stub代码,必须由集成商增加此处理报告的错误处理。
- Notifications and callbacks:SPI驱动程序实现了用于任务和序列完成的Spi_JobEndNotification 和 Spi_SeqEndNotification通知功能,这些通知功能可以由用户在EB tresos工具中为每个任务和序列单独配置。在异步通信中,用户应在相应的DMA通道配置中配置Spi_QspiDmacallout函数作为RX通道的DMA回调。在完成了每个通道的传输以及更新BACON的配置后,在启动后续通道的传输后,配置的回调函数Spi_QspiDmaCallout由DMA驱动程序触发。
- Operating system(OS):操作系统或应用程序必须确保在SR寄存器中配置正确的服务类型和中断优先级。启用和禁用中断也必须由操作系统或应用程序管理。MCAL包提供的操作系统文件只是一个示例代码,必须由集成商替换为正式的代码。
多核与资源管理
SPI驱动程序支持所有CPU核同时执行其API。用户应在预编译时使用资源管理器模块将SPI的资源分配给CPU核。以下是与驱动程序中的多核相关的关键点:
- 一个控制器只能分配给一个内核,不能在内核之间共享,一个内核可以分配多个控制器。
- Channel可以在核心内重新使用,但是,应用程序代码必须保护数据。
- 应用程序必须确保传递给API的通道、作业和序列号属于同一个核心,否则相应的DET将从驱动程序触发。
- 硬件引起的中断必须由SPI控制器分配到的CPU核心来处理。
- 用户应该将常量、变量和配置数据定位到正确的内存空间。内存段标记为GLOBAL(适用于所有核心)和CORE【X】(特定于CPU核心)。用户应考虑以下几点,以确保驱动程序具有更好的性能:
- Code section:SPI驱动程序的可执行代码被放置在单个MemMap区块下,它可以被重新定位到任何PFlash区域。
- Data section:标记为特定于某个核心的RAM可变存储器区段应重新定位到同一核心的 DSPR/DLMU。标记为全局的区段应被重新定位到非缓存的LMU区域。
- Configuration data and constants:标记为特定核心的配置数据段应重新定位到同一核心的PFlash。标记为全局的段应重新调整到主核心的PFLASH。
- 如果驱动程序从单个(主)核心运行,则所有部分都可以移动到同一CPU核心的PFlash/DSPR/DLMU。
MCU Support
SPI驱动程序依赖于MCU驱动程序进行时钟配置。只有完成MCU初始化后,才能开始SPI驱动程序的初始化。配置参数McuQspiclockSourceSelection和McuQspiFrequency需要在EB tresos中正确配置。
Port Support
PORT驱动程序配置整个微控制器的端口引脚。用户必须通过PORT配置来配置SPI驱动程序使用的端口引脚,并在调用SPI初始化之前初始化端口引脚:
- MRST:主入从出
- MTSR:主出从入
- CLOCK:时钟
- SLSO:片选
它们四个的配置如下图:
注意MRST需要在SpiHwConfigurationQspi正确配置。
DMA Support
当QSPI以LEVEL 1或LEVEL 2异步模式运行时,应配置DMA通道。QSPI使用两个DMA通道,一个用QSPI的RX,另一个用于QSPI的TX。这些DMA通道必须仅为QSPI通信保留,不能重复使用。在 DMA中,在通用配置部分,启用 DmaTriggerApi作为最小配置。根据应用程序的要求启用其他配置项。DMA中不需要其他配置。DMA的Transaction control set(TCS)配置在SPI驱动中处理,不需要在 DMA 模块中进行任何配置。对于内部缓冲区(Spi_TxlBBuffercorex、Spi_RxlBBuffercorex)和外部缓冲区,地址空间0xD和0xC不得用于DMA相关用途。在Scratch Pad RAM中分配内存的MemMap部分应始终生成全局地址,而不是局部地址。下图为DMA Channel的配置,Channel2用作接收,Channel3用作发送。
注意接收和发送的DMA Channel需要在SpiHwConfigurationQspi完成关联。
Interrupt connections
QSPI的TX和RX中断触发DMA通道。DMA在通道传输结束时触发调用,并为下一个连续通道传输更新BACON。PT2中断指示作业完成。下图配置对应的中断处理为DMA处理。
下图配置Tx与Rx的中断优先级,注意优先级应该为DMA的Channel号。
下图是配置SPI错误和PT中断优先级,注意优先级的大小应该满足DMA error > QSPI error > IRQ DMA-Ch TX > IRQ DMA-Ch RX > QSPI PT2。错误和PT中断由CPU进行处理。
下面是CPU需要处理的SPI中断(包含SPI错误和PT中断)的处理函数例子。
/* Module header file inclusion */
#include "Spi.h"
ISR(QSPI0ERR_ISR)
{
/* Call QSPI0 Error Interrupt handler */
Spi_IsrQspiError(SPI_QSPI0_INDEX);
}
ISR(QSPI0PT_ISR)
{
/* Call QSPI0 PT2 interrupt handler for frame completion */
Spi_IsrQspiPT2(SPI_QSPI0_INDEX);
}
下面是CPU需要处理的DMA中断(包含DMA传输和错误中断)的处理函数例子。
ISR(DMAERR0SR_ISR)
{
/* Handle error through respective DMA ME */
Dma_MEInterruptDispatcher();
}
ISR(DMACH0SR_ISR)
{
/* DMA RX interrupt handler, SPI callback will be called through this interrupt */
Dma_ChInterruptHandler(0U);
}
注意,只有下列API允许在SPI的callback notifications中调用,其他的并不允许。
- Spi_ReadIB
- Spi_WriteIB
- Spi_SetupEB
- Spi_GetJobResult
- Spi_GetSequenceResult
- Spi_GetHWUnitStatus
- Spi_Cancel
SPI使用例程
下面是初始化SPI的例程。
#include "Mcu.h"
#include "Spi.h"
#include "Port.h"
#include "Irq.h"
#if (SPI_LEVEL_DELIVERED != 0)
#include "Dma.h"
#endif
int core0_main (void)
{
/* Initialize all dependent modules */
/* MCU Initialization */
Mcu_Init(&Mcu_Config);
Mcu_InitClock(0U);
while(Mcu_GetPllStatus() != MCU_PLL_LOCKED);
Mcu_DistributePllClock();
/* Port Initialization */
Port_Init(&Port_Config);
#if (SPI_LEVEL_DELIVERED != 0)
/* Initialize IRQ module */
IrqDma_Init();
IrqSpi_Init();
/* DMA initialization */
Dma_Init(&Dma_Config);
/* Enable service request for all the configured interrupts */
SRC_DMACH2.U |= 0x400U;
SRC_DMACH3.U |= 0x400U;
SRC_QSPI0RX.U |= 0x400;
SRC_QSPI0ERR.U |= 0x400;
SRC_QSPI0TX.U |= 0x400;
SRC_QSPI0PT.U |= 0x400;
#endif
/* Initialize SPI module */
Spi_Init(&Spi_Config);
}
下面是Level 0 的且使用IB示例配置。
创建IB类型的Channel并定义数据宽度和长度。
建立Job并绑定对应的SpiDevice。
选择Job的通信方式为同步。
选择Job包含的Channel。
建立Secquence,选择是否可以中断等信息。
将定义好的Job添加到Sequence中。
最后完成QSPI Hardware的配置。
下面是同步传输的示例代码,它分为以下三个部分:
- 首先通过Spi_WriteIB()函数正确配置internal buffer。
- 在完成internal buffer配置完成,使用Spi_SyncTransmit()完成SPI发送。
- 发送完成之后(意味着接收也一并完成了),通过Spi_ReadlB()从内部缓冲区读回接收到的数据。
下面是完整的代码。
/* Align data buffers to 4 byte boundary */
#define SPI_START_SEC_VAR_INIT_ASIL_B_CORE0_32
#include "Spi_MemMap.h"
/* Source buffers */
Spi_DataBufferType Spi_SrcBuf0[BUFFER_LENGTH] = {
0x11111111,
0x22222222,
0xAAAAAAAA,
0x55555555};
Spi_DataBufferType Spi_SrcBuf1[BUFFER_LENGTH] = {
0x22222222,
0x11111111,
0x77777777,
0xAAAAAAAA};
#define SPI_STOP_SEC_VAR_INIT_ASIL_B_CORE0_32
#include "Spi_MemMap.h"
/* Align data buffers to 4 byte boundary */
#define SPI_START_SEC_VAR_CLEARED_ASIL_B_CORE0_32
#include "Spi_MemMap.h"
/* Destination buffers */
Spi_DataBufferType Spi_DestBuf0[BUFFER_LENGTH];
Spi_DataBufferType Spi_DestBuf1[BUFFER_LENGTH];
#define SPI_STOP_SEC_VAR_CLEARED_ASIL_B_CORE0_32
#include "Spi_MemMap.h"
/* Initialize source buffers */
Spi_WriteIB((Spi_ChannelType)SpiConf_SpiChannel_SpiChannel_0,Spi_SrcBuf0);
Spi_WriteIB((Spi_ChannelType)SpiConf_SpiChannel_SpiChannel_1,Spi_SrcBuf1);
/* Transmit data */
u8returnvalue = Spi_SyncTransmit(SpiConf_SpiSequence_SpiSequence_0);
/* Read the received data from IB */
Spi_ReadIB((Spi_ChannelType)SpiConf_SpiChannel_SpiChannel_0,Spi_DestBuf0);
Spi_ReadIB((Spi_ChannelType)SpiConf_SpiChannel_SpiChannel_1,Spi_DestBuf1);
下面是异步传输的示例代码,异步传输的示例配置就不展示了,在文章的后续中有相关说明。
/* Align data buffers to 4 byte boundary */
#define SPI_START_SEC_VAR_INIT_ASIL_B_CORE0_32
#include "Spi_MemMap.h"
/* Source buffers */
Spi_DataBufferType Spi_SrcBuf0[BUFFER_LENGTH] = {
0x11111111,
0x22222222,
0xAAAAAAAA,
0x55555555};
Spi_DataBufferType Spi_SrcBuf1[BUFFER_LENGTH] = {
0x22222222,
0x11111111,
0x77777777,
0xAAAAAAAA};
#define SPI_STOP_SEC_VAR_INIT_ASIL_B_CORE0_32
#include "Spi_MemMap.h"
/* Align data buffers to 4 byte boundary */
#define SPI_START_SEC_VAR_CLEARED_ASIL_B_CORE0_32
#include "Spi_MemMap.h"
/* if Level-1 then support is only interrupt mode
* if Level-2 then ensure to call Spi_SetAsyncMode() to switch to interrupt mode
*/
#if(SPI_LEVEL_DELIVERED == 2U)
Spi_SetAsyncMode((Spi_AsyncModeType)1U);
#endif
/* Destination buffer */
Spi_DataBufferType Spi_DestBuf0[BUFFER_LENGTH];
Spi_DataBufferType Spi_DestBuf1[BUFFER_LENGTH];
#define SPI_STOP_SEC_VAR_CLEARED_ASIL_B_CORE0_32
#include "Spi_MemMap.h"
/* Write data to IB buffer */
Spi_WriteIB((Spi_ChannelType)SpiConf_SpiChannel_SpiChannel_0,Spi_SrcBuf0);
Spi_WriteIB((Spi_ChannelType)SpiConf_SpiChannel_SpiChannel_1,Spi_SrcBuf1);
/* Start data transmission */
u8returnvalue = Spi_AsyncTransmit(SpiConf_SpiSequence_SpiSequence_0);
/* Wait till the transmission is complete */
while(Spi_GetStatus() == SPI_BUSY);
/* Read the received data from IB buffer */
Spi_ReadIB((Spi_ChannelType)SpiConf_SpiChannel_SpiChannel_0,Spi_DestBuf0);
Spi_ReadIB((Spi_ChannelType)SpiConf_SpiChannel_SpiChannel_1,Spi_DestBuf1);
可以看出,上面这个示例是异步模式是中断的,如果是轮询,则采用下面的方式,在SPI_BUSY时循环调用Spi_MainFunction_Handling()。
/* In Level-2, set the asynchronous transmission mode to interrupt (1) / polling (0) */
Spi_SetAsyncMode((Spi_AsyncModeType)0U);
/* Write data to IB buffer */
Spi_WriteIB((Spi_ChannelType)SpiConf_SpiChannel_SpiChannel_0,Spi_SrcBuf0);
Spi_WriteIB((Spi_ChannelType)SpiConf_SpiChannel_SpiChannel_1,Spi_SrcBuf1);
/* start data transmission */
u8returnvalue = Spi_AsyncTransmit(SpiConf_SpiSequence_SpiSequence_0);
/* poll till the transmission completes */
while(Spi_GetStatus() == SPI_BUSY)
{
Spi_MainFunction_Handling();
}
/* Read data from IB buffer */
Spi_ReadIB((Spi_ChannelType)SpiConf_SpiChannel_SpiChannel_0,Spi_DestBuf0);
Spi_ReadIB((Spi_ChannelType)SpiConf_SpiChannel_SpiChannel_1,Spi_DestBuf1);
最后介绍一个两个线程同时异步发送的示例:
- Spi_SyncTransmit被一个线程调用。
- 在进行中的Spi_SyncTransmit期间,将接受另一个线程的Spi_AsyncTransmit请求(不同序列),并为相同的QSPI硬件排队。
/* Align data buffers to 4 byte boundary */
#define SPI_START_SEC_VAR_INIT_ASIL_B_CORE0_32
#include "Spi_MemMap.h"
/* Source buffers */
Spi_DataBufferType Spi_SrcBuf0[BUFFER_LENGTH] = {
0x11111111,
0x22222222,
0xAAAAAAAA,
0x55555555};
Spi_DataBufferType Spi_SrcBuf1[BUFFER_LENGTH] = {
0x22222222,
0x11111111,
0x77777777,
0xAAAAAAAA};
Spi_DataBufferType Spi_SrcBuf2[BUFFER_LENGTH] = {
0x33333333,
0x66666666,
0x77777777,
0xBBBBBBBB};
Spi_DataBufferType Spi_SrcBuf3[BUFFER_LENGTH] = {
0x44444444,
0x88888888,
0x11111111,
0xCCCCCCCC};
#define SPI_STOP_SEC_VAR_INIT_ASIL_B_CORE0_32
#include "Spi_MemMap.h"
/* Align data buffers to 4 byte boundary */
#define SPI_START_SEC_VAR_CLEARED_ASIL_B_CORE0_32
#include "Spi_MemMap.h"
/* Destination buffer */
Spi_DataBufferType Spi_DestBuf0[BUFFER_LENGTH];
Spi_DataBufferType Spi_DestBuf1[BUFFER_LENGTH];
Spi_DataBufferType Spi_DestBuf2[BUFFER_LENGTH];
Spi_DataBufferType Spi_DestBuf3[BUFFER_LENGTH];
#define SPI_STOP_SEC_VAR_CLEARED_ASIL_B_CORE0_32
#include "Spi_MemMap.h"
/* Write data to IB buffer */
Spi_WriteIB((Spi_ChannelType)SpiConf_SpiChannel_SpiChannel_0,Spi_SrcBuf0);
Spi_WriteIB((Spi_ChannelType)SpiConf_SpiChannel_SpiChannel_1,Spi_SrcBuf1);
Spi_WriteIB((Spi_ChannelType)SpiConf_SpiChannel_SpiChannel_2,Spi_SrcBuf2);
Spi_WriteIB((Spi_ChannelType)SpiConf_SpiChannel_SpiChannel_3,Spi_SrcBuf3);
/* Thread 1: Start data transmission Synchronous*/
u8returnvalue0 = Spi_SyncTransmit(SpiConf_SpiSequence_SpiSequence_0);
....
/* Thread 2: Request Asynchronous Transmission on same QSPI HW */
u8returnvalue1 = Spi_AsyncTransmit(SpiConf_SpiSequence_SpiSequence_1);
/* Wait until Transmission is complete */
while(Spi_GetStatus() == SPI_BUSY);
/* Read the received data from IB buffer */
Spi_ReadIB((Spi_ChannelType)SpiConf_SpiChannel_SpiChannel_0,Spi_DestBuf0);
Spi_ReadIB((Spi_ChannelType)SpiConf_SpiChannel_SpiChannel_1,Spi_DestBuf1);
Spi_ReadIB((Spi_ChannelType)SpiConf_SpiChannel_SpiChannel_2,Spi_DestBuf2);
Spi_ReadIB((Spi_ChannelType)SpiConf_SpiChannel_SpiChannel_3,Spi_DestBuf3);
示例程序中的中断的数量与触发位置如下表。
SPI中断数量与触发位置 | ||
SPI序列配置 | SPI异步发送 | |
中断模式 | 轮询模式 | |
Job数量为2 Channel数量为3 | 中断数量: 因为这两个Job为一个QSPI控制器的,一对DMA通道用作一个QSPI控制器中。因为Channel数为3,则DMA传输中断为3*2=6,其中3个DMA接收Channel中断是我们在意的。 因为Job数为2,则PT2中断为2。 DMA TRL(Transaction/Transfer Request Lost)中断与DMA传输中断数一致。 | 中断数量: QSPI Tx和Rx也需要配置,但是它们不打断CPU,这里我们不计算它们。DMA TRL中断仍然会发生。 |
中断位置: DMA的Rx中断在每一个Channel传出结束时触发。DMA TRL一样。 PT2中断在Job结束时触发(Job最后一个Channel完成)。 | 中断位置: DMA TRL在每一次Job结束时触发 |
注意:DmaTcsinterruptTransactionLoss时,TRL事件将传递到SPI驱动器。如果禁用此字段,则在DMA驱动器处将抑制TRL事件。在DMA通道配置中,应配置忽略TRL中断。如果Framebased Cs=TRUE,则不使用PT2中断,并且在Spi_QspiDmaCallout()中发生跳转到下一个作业的过渡。
关键架构考虑因素
首先是三种SPI的Level:
- Level 0:在这种模式下,要传输的数据被直接复制到TX FIFO中。数据按配置中定义的顺序传输。请注意,此功能是一个阻塞调用,即,直到传输完成或发生错误,API不会返回状态。
- Level 1:在此模式下,将配置相应的中断,并配置DMA在Linklist模式下进行数据传输。在Level1模式下需要配置以下中断:TXF、RXF、PT2和Error。在通道传输完成后,DMA驱动程序将调用QSPI模块注册的回调函数。请注意,如果启用了可中断序列功能,则所有可中断序列的工作都将按优先级顺序排列。请注意,对于异步传输,每个内核都分配了一个独立的队列,以按优先级处理工作。
- Level 2:异步传输进一步支持中断和轮询模式。在轮询模式下,DMA仍然用到了QSPI的TX和RX中断进行传输,但是通过轮询DMA传输是否完成来检查通道传输是否完成,以触发下一个通道传输的开始。通过轮询错误和PT2标志来指示帧是否完成。第二级的中断模式与第一级的实现相同。
然后我们讲一下SPI驱动牵扯到的一些一般配置(包括QSPI与DMA)。
- FIFO configuration:
- TXFIFO / RXFIFO配置为Single move模式。
- QSPI传输采用contentious move mode,且使能了Move counter mode。
- Asynchronous communication(L1和L2):
- 通过使用Move counter, 当MCCOUNT达到“0”时,SLSO的控制由硬件处理为未选中。当QSPI为异步通信时,下列中断与回调需要配置:
- TXF:由DMA进行处理,发送FIFO中断,请求往FIFO中添加数据。
- RXF:由DMA进行处理,接收FIFO中断,请求从FIFO中读取数据。
- ERROR:错误中断,由CPU处理。
- DMA Callback:由CPU处理,完成了一次DMA传输之后,在DMA中断中调用。
- DMA Error Callback:由CPU处理的DMA传输失败。
- PT2 interrupt:由CPU处理,在作业完成时发生。请注意,PT2中断需要知道帧的未尾,只有当QSPI处于IDLE状态时,计数器才能被加载。
- DMA usage / configuration:
- 请注意,应为QSPI的TX和RX分别分配两个专用DMA通道。
- DMA仅在QSPI配置为异步通信时使用,并配置为只包含一个DMA move(即,一个DMA传输有一个DMA move)。DMA配置为在DMA的Linklist模式下工作,并相应地维护多个列表进行传输和接收。
- 用于DMA的Linklist对应的所有TCS参数被保存在QSPI模块中,并应通过SPI模块根据信道配置进行适当配置而传递到DMA。
- 从移动引擎传送DMA过程中的任何错误都将由DMA模块处理,并应回调被分配到相应核心中的错误处理器。
- 为了实现异步通信,实现了仅适用于异步通信的优先级队列。请注意,队列中的作业按优先级顺序维护,并为每个内核分配一个队列以维护作业ID及其属性。
- 请注意,应为QSPI的TX和RX分别分配两个专用DMA通道。
- Synchronous communication(L0 mode)
- TXFIFO直接输入数据,而不需要使用DMA。
- 在同步通信中,数据传输在阻塞调用中完成,发送函数等待数据接收或直到超时发生。
- 没有用于同步通信的排队机制。
- 通过使用Move counter, 当MCCOUNT达到“0”时,SLSO的控制由硬件处理为未选中。当QSPI为异步通信时,下列中断与回调需要配置:
下面讲以下多核的决策。TC3xx被设计为最多具有6个QSPI实例,这会芯片具体型号。每个QSPI实例都被定义为一个独立的控制器,控制器可以被分配给其中一个核心,并且不能在核心之间共享。可以将多个控制器分配给一个核心。
所有控制器都将相互独立地工作,因此主核心的配置不适用于此驱动程序。但是,如果控制器已配置且未分配给任何核心,则将为主核心生成配置。任何核心都可以调用Spi_Init和Spi_Deinit,除了调用的核心之外,相同操作不会影响其他核心的操作。所有API只能访问为本地核心配置的信息。例如,Spi_GetJobResult()和Spi_GetSequenceResult()只能返回分配给相同核心的作业和序列的状态,不能返回分配给不同核心的序列的结果,如果请求跨核心信息,则会引发相应的DET。Spi_Getstatus将返回本地核心的SPI控制器状态。例如,如果两个控制器被分配给核心1,并且没有在通信,并且核心2正在进行通信,在核心1上调用Spi_Getstatus,则返回IDLE。如果在核心2上调用Spi_GetStatus,则返回BUSY。
下面再来讲一讲Sequence、jobs和channels。在一个序列中,不同控制器的不同序列之间不能共享作业,所有的作业都应该属于同一个硬件控制器。通道可以在同一个核心内的两个作业之间共享,但是保护通道的内容是应用程序代码的责任。一个任务最多可以传输 8190个元素。这是QSPI使用的计数器的限制。然而,这种限制不适用于Frame-based CS。对于Frame-based CS,最多可以在一个通道中传输16383个元素。对于同步传输,最多可以传输65534个元素。
下面我们再来谈谈Lookup tables再SPI中的应用。为了加速在配置结构中的访问,为Sequencelds、Jobids和 Channelids添加了查找表。ld 将物理索引映射到核心配置中的应用程序lds(查找中的索引)。尤其在多核环境中,由于配置分布在多个内核中,因此访问Sequence、Job和Channel非常耗时。
我们再来谈谈可中断的sequence行为:
- SPI_INTERRUPTIBLE_SEQ_ALLOWED==ON:
- 如果输入序列是可中断的,则序列中的单个作业按照作业的优先级排队。
- 如果传入的序列是不可中断的,则按照队列的顺序进行排序,且与别的打断别的序列时使用第一个作业优先级。
- PI_INTERRUPTIBLE_SEQ_ALLOWED==OFF:
- 所有输入序列都被认为是不可中断的,整个序列根据序列中第一个作业的优先级被放置在待执行Job队列中。
最后我们来谈一下不常用的SPI控制器可以作为external demultiplexer模式, SLSO1到SLSO4会被配置为外部设备的配置参数SpiCsldentifier的值。为了确保无故障选择,提供了选通信号 SLSO0,并且使用配置参数SpiSLSO0StrobeDelay配置选通信号延迟的值。所有这些SLSO线(SLSO0...SLSO4)的极性被配置为与外部设备的配置参数SpiCsPolarity相同的值。在任何给定时间点,QSPI可以以external demultiplexer模式或正常模式运行。
环境与目标
本文使用的为英飞凌提供的开发板KIT_A2G_TC397XA_TFT。我们选择QSPI2控制器,则使用P15.4为MRST,P15.5为MTSR,P15.6为SCLK,然后CS我们避开开发板上已经使用了的片选引脚(例如P14.2已经连接了TLF3068),我们选择P14.7作为片选。如下图这四个引脚在接插件的位置。
涉及的软件如下:
- EB-tresos:用于生成动态代码,具体工程搭建参考《【AUTOSAR MCAL】MCAL基础与EB tresos工程新建》。
- HighTech:用于编译生成elf文件,具体的工程搭建参考《【MCAL】HighTec集成TC3xx对应MCAL的Demo》。
- UDE 5.2:用于下载和调试程序。
涉及的参考文档如下表。
序号 | 参考资料 | 内容 |
1 | 《Infineon-AURIX_TC39x-UserManual-v02_00-EN》 | 英飞凌TC39x用户手册 |
2 | 《Infineon-AURIX_TC3xx_Part1-UserManual-v02_00-EN.pdf》 | 英飞凌TC3xx用户手册 |
3 | 《Infineon-AURIX_TC3xx_Part2-UserManual-v02_00-EN.pdf》 | 英飞凌TC3xx用户手册 |
4 | 《Infineon-TC39x-DataSheet-v01_00-EN》 | 英飞凌TC39x数据手册 |
5 | 《ApplicationKitManual-TC3X7-ADAS-V21.pdf》 | 开发板KIT_A2G_TC397XA_TFT说明 |
6 | 《MC-ISAR_TC3xx_UM_Spi.pdf》 | 英飞凌提供的TC3xx芯片FlsLoader用户手册 |
配置目标如下:
- 使用EB完成SPI同步传输八个8位宽的数据传输配置。
- 使用IB完成SPI异步传输四个8位宽的数据传输配置。
EB tresos配置
同步QSPI配置
SpiGeneral
这个容器包含SPI驱动的一些一般配置,我们主要需要关系的有以下几个选项:
- SpiChannelBuffersAllowed:指定用户可用的缓冲类型。
- 0:只是用Internal buffer。
- 只是用external buffer。
- 两者都使用。
- SpiHwStatusApi:该参数指定Spi_GetHWUnitStatus()是否可用。
- SpiInterruptibleSeqAllowed:该参数指定是否允许可中断序列,如果该字段为STD_ON时并且序列被定义为可中断的,序列才可以被中断。此配置项有个限制仅仅当
SpiLevelDelivered配置等于1或者2时才可以选择,也就是讲异步时候才会Sequence才可被中断。 - SpiLevelDelivered:选择驱动的等级。
- 0:Level 0,同步传输(不使用DMA,阻塞调用)。
- 1:Level 1,异步通信(使用DMA,非阻塞),需配置异步类型为中断模式。
- 2:Level 2,处理同步和异步通信。对于异步传输,配置支持轮询或中断模式,默认为轮询模式。(DMA用于异步序列,非阻塞)。
- SpiSupportConcurrentSyncTransmit:该参数指定是否允许并发同步传输。即。如果一个SPI控制器正在传输请求,那么如果启用了该特性,则应该接受另一个SPI控制器命令。请注意,该参数的值默认为False,如果应用程序需要同时在多个QSPI上传输同步序列,则必须启用该功能。
- SpiSyncTransmitTimeoutDuration:该参数在同步传输期间用作超时循环计数器。值是用户可配置的,可以根据应用的需要进行更改。超时值是作为所有SPI控制器都可以访问的根配置的一部分生成。
SpiMaxChannel
该参数包含已配置的通道数。它将在配置阶段由工具收集。
注意:此参数应在spichannel配置完成后“移除”和“添加”(由于工具不会自动刷新默认值)也就是说用户尽量在最后面配置这个容器,他会自己根据用户配置了几个Channel来自己生成数量,这个地方是必须配置项。
SpiMaxJob
类似于SpiMaxChannel配置,注意配置完Job添加工具会自动生成Job数。
SpiMaxSequence
类似于SpiMaxChannel配置,注意配置完Sequence添加工具会自动生成Sequence数。
SpiChannel
这个容器配置每个Channel的参数,这里我们需要关注的参数有:
- SpiChannelType:该参数指定通道使用的缓冲区类型(外部缓冲区/内部缓冲区)。
- SpiDataWidth:该参数以位为单位指定要传输的数据的宽度。注意:QSPI支持2 ~ 32位的数据宽度。
- SpiEbMaxLength:此参数定义了EB通道最大可能传输的数据数。仅当spichannelbufferallowed为1或2和SpiChannelType为EB可用。默认情况下,SpiEBMaxLength设置为8190个元素。
- SpiIbNBuffers:定义IB通道要发送的数据数。默认情况下,IB缓冲区的大小被选择为10个元素,因为许多到SPI外部设备的数据包可以容纳在这个数据长度中。
- SpiTransferStart:先发送LSB (Least Significant Bit)还是先发送MSB (Most Significant Bit)。
这里的配置大部分都会反应到BACON寄存器中。
下图是同步,EB的配置截图。
SpiDefaultData
当(对于内部缓冲区或外部缓冲区)传递给Spi WritelB(对于内部缓冲区)或Spi_SetupEB(对于外部缓冲区)的指针为NULL时,要传输的默认数据。
SpiExternalDevice
这里定义了针对某一个片外的SPI从设备的相关配置信息,我们需要关心的信息如下:
- SpiBaudrate::该参数定义QSPI通信波特率。该参数允许使用最大50MHz的值范围。仅当参数SpiAutoCalcBaudParams设置为TRUE时启用。(最大值受硬件限制,全双工通信33MHz,单工通信50MHz)
- SpiCsIdentifier:该参数指定SpiHwUnit指定硬件的CS (Chip Select)。范围从Channelo(0)到Channel15(15)。我们需要选CHANNEL4,是因为我们使用的CS的IO口为P14.7,所以根据下图的数据手册可知选择CHANNEL4。
- SpiCsPolarity:此参数定义芯片选择的有效极性。
- SpiDataShiftEdge:该参数定义SPI数据移位边沿。LEADING移位在前沿(采样在后沿),TRAILING移位在后沿。
- SpiEnableCs:该参数设置是否启用驱动的Chip Select处理功能。如果启用此参数,则可以在SpiCsSelection进一步详细说明芯片选择的类型。
- SpiHwUnit:选择哪一路QSPI控制器。
- SpiAutoCalcBaudParams:自动计算波特率。
- SpiAutoCalcDelayParams:自动计算时间延时。
- SpiIdleTime:此参数IDLE A/IDLE B时间是QSPI硬件延迟,在数据传输前。
- SpiTrailingTime:在数据传输的后阶段预期的延迟。
- SpiShiftClockIdleLevel:时钟空闲状态电平。
- SpiTimeClk2Cs:时钟和芯片选择之间的时差(以秒为单位)或Lead时间,该参数允许使用从0到0.0001秒的值范围。
- SpiParitySupport:奇偶校验的选择。
SpiCsSelection
当芯片选择处理功能使能(SpiEnableCs),当前参数指定芯片选择是否由SPI硬件引擎处理或SPI驱动程序通过通用IO口驱动。
- SpiCsSelection:
- CS_VIA_GPIO:芯片选择由SPI驱动程序通过GPIO处理。
- CS_VIA_PERIPHERAL_ENGINE:芯片选择是通过SPI控制器硬件处理的。
SpiJob
这个容器我们需要关注的配置项有:
- SpiFrameBasedCS:如果启用了基于帧的CS特性,则SPI接口传输的每个数据元素都会经历完整的Frame过程,并进行SLSO的激活和不激活的转换。这个配置同时也会影响基于中断的异步通信是基于PT2还是DMA传输中断来实现异步通信。这里我们不采用,还是遵循规范中定义的在一个Job中SLSO一直维持激活。
- SpiJobPriority:该参数定义作业的优先级。0最低3最高。
- SpiHwUnitSynchronous:同步异步选择。
- SpiChannelList:Q通道。
SpiSequence
这个容器我们需要关注的配置项有:
- SpiInterruptibleSequence::如果启用该特性,则根据多个序列间作业的优先级传输作业。只有当SpiLevelDelivered为1或2,且SpilnterruptibleSequenceAllowed为真时才有意义。true:可以中断序列。false:不中断序列默认情况下,该特性是禁用的,因此该字段设置为false。
- SpiSequenceId:这个ID被分配给序列。默认值设置为0,但是在添加多个序列时,ID会自动增加。
- SpiJobAssignment:Sequence关联的Job。
SpiHwConfiguration
这个容器包含对每个QSPI控制的配置,需要我们关系的配置有:
- SpiHwConfigKernel:这是哪一路QSPI配置。
- SpiSleepEnableQspix:在MCU的睡眠请求时禁用/启用进入睡眠模式。该参数在spi初始化期间用于配置CLC寄存器。true :当MCU请求休眠时,QSPIx进入休眠模式。false :当MCU请求休眠时,QSPIx不进入休眠模式。
- SpiHWPinMRSTQspix:MRST管脚的选择。因为我们使用P15.4作为MRST,所以选择MRST2A_PORT15_PIN4。
- SpiExternalDemux:该参数启用/禁用Spi外部解复用器。在外部解复用模式下,使用SLSO1到SLSO4驱动QSPI HW通道(0 ~ 15),采用SLSO0作为频闪信号,保证无故障选择。
Port
下面我们进行CS和其余四个引脚的配置,复用功能号的选择参考DataSheet即可。下图为P15.4的复用功能号位置举例。
下面是CS和其余四个引脚的配置截图。
MCU
异步QSPI配置
每个配置选项在同步QSPI配置章节已经描述很详细了,此处仅描述异步配置与同步不一样的关键点。注意SpiLevelDelivered配置,假设仅仅使用异步,不能配置成2,需要配置成1。
SpiChannel
这里我们使用IB。发送的数据为4个字节。
SpiHwUnitSynchronous
DMA
在General里,我们需要注意以下参数:
- DmaTriggerApiConfiguration:Dma_ChEnableHardwareTrigger()函数是否可用,这个SPI驱动在异步的时候需要用到
完成QSPI2对应两个Channel的配置,其中TCS并不用配置,把这两个通道预留出来即可。搬运接收FIFO的DMA通道完成回调函数配置为Spi_QspiDmaCallout。
最后配置DmaResourcePartition。
SpiHwDmaConfigurationQspi
发送和接收引用刚才配置好的通道。
回调函数
异步通信可以配置Job和Sequence结束的回调函数,如下图所示。
IRQ
因为我们测试工程没有使用OS,所以都是一类中断,首先是DMA的中断配置。
DMA的中断去向都是CPU,默认不用改动。然后是SPI的中断,下面是优先级配置。
下面是去向的配置,QSPI的接收和发送中断由DMA处理。
SPI驱动调试
同步传输测试代码与结果
首先需要完成SPI初始化。
/*Qspi init*/
Spi_Init(&Spi_Config);
然后下面是发送数据0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88的代码。
uint8 SpiwriteCommand[8]={0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88};
uint8 Spireaddata[8]={0};
Spi_JobResultType State_Spi = 0;
void Spi_DemoFunction(void)
{
Spi_SetupEB(SpiConf_SpiChannel_SpiChannel_SPI2CMD, SpiwriteCommand, Spireaddata, 8);
/* Use Spi to transmit data synchronously */
Spi_SyncTransmit(SpiConf_SpiSequence_SpiSequence_QSPI2_0);
State_Spi = Spi_GetJobResult(SpiConf_SpiJob_SpiJob_QSPI2_0);
}
最后通过逻辑分析仪,能够抓到SPI输出的数据为发送的数据。
异步传输测试代码与结果
首先需要完成SPI初始化。启动包括了相关中断的使能。
IrqDma_Init();
IrqSpi_Init();
SRC_DMACH0.B.SRE = 1;
SRC_DMACH1.B.SRE = 1;
SRC_QSPI2RX.B.SRE = 1;
SRC_QSPI2ERR.B.SRE = 1;
SRC_QSPI2TX.B.SRE = 1;
SRC_QSPI2PT.B.SRE = 1;
Dma_Init(&Dma_Config);
//Dma_ChStartTransfer(0);
//Dma_ChStartTransfer(1);
//Dma_ChInterruptEnable(0,DMA_EVENT_CH_RUNNING);
//Dma_ChInterruptEnable(1,DMA_EVENT_CH_RUNNING);
Spi_Init(&Spi_Config);
然后,我们发送0x11,0x22,0x33,0x44。注意,这里的异步发送完成选择了循环等待Sequence发送完成,然后读取的IB,实际的工程中不应该采取这样方法。
uint8 SpiwriteData[4]={0x11,0x22,0x33,0x44};
uint8 SpiReadData[4]={0};
void Spi_DemoFunction(void)
{
Spi_WriteIB(SpiConf_SpiChannel_SpiChannel_SPI2CMD,(const Spi_DataBufferType *)SpiwriteData);
Spi_AsyncTransmit(SpiConf_SpiSequence_SpiSequence_QSPI2_0);
while(Spi_GetSequenceResult(SpiConf_SpiSequence_SpiSequence_QSPI2_0) != SPI_SEQ_OK);
Spi_ReadIB(SpiConf_SpiChannel_SpiChannel_SPI2CMD,(Spi_DataType *)SpiReadData);
}
volatile uint32 QSPI2_SequenceRunning = 0;
volatile uint32 QSPI2_JobRunning= 0;
void QSPI2_SequenceNotification(void)
{
QSPI2_SequenceRunning++;
}
volatile uint32 QSPI2_JobRunning = 0;
void QSPI2_JobNotification(void)
{
QSPI2_JobRunning++;
}
下面是异步发送的结果。
十六宿舍 原创作品,转载必须标注原文链接。
©2023 Yang Li. All rights reserved.
欢迎关注 『十六宿舍』,大家喜欢的话,给个👍,更多关于嵌入式相关技术的内容持续更新中。