本文将详细介绍AUTOSAR MCAL SPI模块的知识点及注意事项,本模块的配置会在其他文章进行分享。本文大部分内容来源于标准,并参照了NXP S32K1系列的 MCAL SPI的代码。
耐心看完本文后,你就对AUTOSAR MCAL SPI有了非常深入的了解。
目录
1. 模块简介
2. 模块限制
3. 主要概念:Sequence/Job/Channel
4. Sequence/Job/Channel发送时序
5. MCAL SPI模式分类
6. SPI Buffer
7. SPI其他重要机制
8. 注意事项
1. 模块简介
SPI驱动程序给那些通过SPI总线连接的设备提供读写服务。常见的设备,比如EEPROM,外挂Watchdog,或其他一些专用集成电路(ASIC)。当然,SPI驱动程序也提供对片上SPI外设的配置及使用。
驱动程序对功能和配置都划分了多个等级以达到高扩展性及灵活性。比如功能方面,划分为3个等级。根据不同等级可配置为同步或异步。
当然还抽象了很多其他概念,比如Sequence, Job, Channel等。重要概念/关键点请见下图:
2. 模块限制
- 只支持Master模式。
- 只支持全双工模式。
- LEVEL 2 是为那些需要单独提供至少两路SPI总线的芯片而指定的。 否则,使用这种级别的功能没有意义。
- 外设时钟初始化及分频由MCU模块负责
- 如果有寄存器需要和其他外设共享,这种寄存器由MCU负责。
3. 主要概念:Sequence/Job/Channel
首先需要说明一下这里的Channel是软件层面的,与硬件的物理通道/Channel没任何关系,也没有进行任何绑定。软件层面的这个Channel只是对BUFF做一些配置。
Job是和外设绑定的。所以一个Channel可以同时属于多个Job,如上CH2及CH5。CH2和CH5的区别在于前者是用于不同外设,后者用同一外设。个人不推荐这么使用,因为一旦管理不好,很容易造成buff里面的数据混乱。一个Job也同样可以同时属于多个Sequence,如上Job0 。
一个Job下面可以有多个Channel,如上Job1, Job2,Job3;且应至少有一个Channel,否则没意义,如上Job5是不允许的。这种情况通常是这个Job/Seq有多个用户使用,针对每个用户分配自己独立的Channel。
同样,一个Sequence下面可以有多个Job,如上Seq3;应至少有一个Job,否则 没意义,如上Seq2是不允许的。
同一个Sequence下面的多个Job,他们拥有相同的优先级。
一个外设可有多个Job绑定。
上图可以发现,一个Sequence下面可以有不同外设(总线)的Job。
传输是以Sequence为单位,只能操作Sequence。接收是具体到某个Channel。获取状态或者回调,Job及Sequence都可以(Level1, Level2)。
4. Sequence/Job/Channel发送时序
如上图所示,发送任务需要等Sequence下面的所有Job及所有Channel都发送完成后,总线才空闲(释放)。
如果多个Job共用一个Channel(Sequence也一样),那么在传输过程中用户需要保证不调用读/写相关函数,否则数据一致性无法保证(数据混乱)。
在传输过程中,读/写函数自己是无法保证数据的完整性的。
允许同时发起多个Sequence传输请求。比如当一个Seq正在传输过程中,允许发起另外的Seq传输请求,但相同的Seq不能同时发起多次。驱动程序会根据当前情况选择接受或拒绝。比如新的Seq与原Seq有共享的Job,这种情况不会接受请求以防止冲突等。
Sequence传输请求是可以被取消的(Spi_Cancel),取消这个操作以Job为原子单位进行处理。取消之后还是会调用回调函数通知用户(如果使能了)。需要注意的是,取消操作是否会对外设造成损坏或其他未定义行为需要用户自己把控,驱动程序是不负责的。
Sequence下面可以包含多个Job,每个Job传输完成后会根据优先级策略(请见后文)重新进行调度。Polling模式下调度是靠用户周期调用Spi_MainFunction 函数来实现的。
5. MCAL SPI模式分类
√: support/available x: not support/unavailable IB/EB: 请见后文
LEVEL 0: 提供一组简化的服务,只处理简单的同步传输。对于包含简单SPI网络的ECU来说,这种情况经常发生,即便对于使用高速外部设备的ECU来说也是如此。
同步传输意味着一旦调用了传输服务函数,在该传输请求完成之前,程序将会一直被阻塞(一个好的代码实现,通常也会有超时监控机制,超时后也会释放CPU)。
LEVEL 1: 提供一组简化的服务,只处理异步传输。对于那些使用SPI且定义了不同优先级的功能来说,异步传输经常被用到。还有一些低速外设也同样适合该异步模式。
异步传输意味着当传输正在进行时,调用传输服务的用户不会被阻塞。驱动程序可以在传输结束时通知用户(可由用户进行配置)。异步传输模式又可以分为通过轮询或中断来实现。
LEVEL 2: 前面两种的组合。包含全部功能。可满足不同速率,不同优先级等复杂需求场景。是为那些需要单独提供至少两路SPI总线的芯片而指定的。 否则,使用这种级别的功能没有意义。
如果配置为(异步)轮询模式(仅在API等级为Level 2时有效),需要周期调用:Spi_MainFunction。
由上表可以看到,DMA只有在异步模式(Async Mode)下才有效。
6. SPI Buffer
IB: Internal Buffers EB: External Buffers
上表中的优缺点来自于标准文档的翻译,有些语句不太通顺,请看下面文字描述。不影响你理解。
IB: 静态分配。 EB: 可以是静态分配,也可以是动态分配,取决于用户的使用环境。
有些硬件层面提供了比较大的Buffer,这种情况IB类型可以充分发挥硬件特性,提高其性能(这是IB Buffer的设计初衷)(注意,如果有多个channel同时挂到一个device上面,则该功能使用有限制)。如果硬件没有Buffer,则需要软件来模拟实现。
IB类型的Buffer其大小是固定的。EB类型Buffer可以通过API进行设置。
SPI驱动不负责保证IB Buffer里数据的连续性。如果某个Channel被多个Job/Seq使用,SPI驱动也不负责维护该Buffer被多个Job/Seq重写这种场景。
但是发送和接收的Buffer是分开独立的。也就是发送Buffer不会被接收的数据覆盖。
EB Buffer的设计初衷是为了尽量重用外部(这里指用户)Buffer,因为很多情况下,用户已经有了一个Buffer,那么使用EB类型Buffer,只需要将用户Buffer的指针提供给SPI驱动以达到共用的目的。所以SPI驱动也是无法对该Buffer管理的,需要用户来保证其一致性。还有一种场景是,有时候我们的Buffer大小是变化的(比如多个使用者的需求可能不同,或者一个使用者数据长度是变化的),这种情况也需要使用EB类型Buffer来解决(因为IB类型Buffer是固定大小的)。当然EB类型Buffer也可以是固定大小。但是Buffer大小的最大值需要静态配置。总的来说EB类型Buffer使用更灵活。
Channel传输有自己的参数(Spi_SetupEB),但参数(source/target)也可以是NULL,如果发送的时候Source为NULL,则会使用默认参数传输,如果接收的时候Target为NULL,则会忽略接收到的数据。每个Channel,Spi_SetupEB函数只能在发起传输请求前调用一次,除非有信息需要变更,比如长度信息。
如果Data Width也是uint8,uint16,uint32三种类型,由于和Data Buffer Type一致,所以可以直接进行转发。但是如果Data Width是8~32,类型不一致,那需要小心。比如,如果Data Width设置12Bits。Data Buffer Type只能选择uint16,这种情况下,发送的时候只能发送低12Bits,忽略高4位。接收的时候只接收低12位,高4位用0填充。
7. SPI其他重要机制
- 优先级:数字越小优先级越低
- Sequence链接的Job应具有相同或递减的优先级
- Sequence可被中断
- 并发同步传输
该驱动可能会被多个软件模块同时使用,这些模块可能彼此独立,也可能属于不同的层。为了防止同时访问时发生冲突,因此增加了优先级机制,每个Job将分配一个优先级。这种场景通常发生在基于异步机制的实时系统中。
一个Sequence下面的多个Job,要么这些Job都具有相同的优先级。如果优先级不同,则第一个Job优先级最高,优先级最低的放最后(配置的时候就需要这样做)。尽管驱动内部有基于优先级的调度器,但调度器更多用来处理那些允许被中断的Sequence执行过程中发起了一个新的更高优先级的Sequence任务的调度。
Level 1和Level 2可以配置某个Sequence是否可以被抢占。如果使能被抢占功能,则该Sequence启动传输以后,过程中如果有更高优先级的Job发起传输请求,则会挂起(Suspend)当前Sequence去执行更高优先级的Job,打断是以Job为原子单位进行的,也就是必须等待某个Job执行完,下一次调度点执行的时候才切换,请参照前面的时序图。
如果没有使能抢占功能,则该Sequence一旦开始传输,必须等他传输完成后方可执行其他Sequence的传输请求。
如果某个Sequence被抢占,用户需要清楚是否存在多个Sequence共用同一个Channel的情况,如果有,则自己需要管理好Channel的数据,防止抢占过程中被更高优先级的Job将原来Channel里面的数据覆盖掉(通常不建议这么配置)。
同步传输也是可以同时发起多个不同Sequence传输请求的,但用户必须使能该功能(SPI_SUPPORT_CONCURRENT_SYNC_TRANSMIT :Level 0, Level2下有效);
8. 注意事项
- 使用本模块中的服务前,必须先调用Spi _Init()函数初始化。
- 如果使用了DMA功能,Spi _Init()函数必须在Port_Init()函数及Mcl_Init()函数之后调用。
- 如果配置为(异步)轮询模式(仅在API等级为Level 2时有效),需要周期调用:Spi_MainFunction。
- 如果使用固定优先级策略,SpiPhyRxDmaChannel优先级必须大于SpiPhyTxDmaChannel优先级。
- 如果配置为(异步)中断模式,且启用了DMA:
- 必须使能DMA相应通道中断。
- 必须将SPI模块的接收/发送中断函数注册到DMA完成回调函数里,配置见DMA模块配置。
SPI模块中断函数:Spi_LPspi_IsrRxDma_LPSPI_X/Spi_LPspi_IsrTxDma_LPSPI_X,其中X为通道号。
以通道0为例,中断函数为:Spi_LPspi_IsrRxDma_LPSPI_0/Spi_LPspi_IsrTxDma_LPSPI_0。
6.如果配置为(异步)中断模式,但是使用FIFO,需要将中断函数(Spi_LPspi_IsrTDF_LPSPI_X)注册到中断向量表里面,其中X为通道号。
由于MCAL本身不提供中断向量表注册功能,可以参照SDK或者MCAL示例工程里的中断注册函数:sys_registerIsrHandler()。
7.如果使能DMA传输模式,且D-CACHE使能的情况下,不能使用内部buf(IB)来发送或接收,必须将源地址及目标地址的buf放在NON-CACHE区域以避免数据一致性问题(如果放在CACHE区域可能会发生数据乱的现象)。可以在链接文件(*.ld)里面使用分区指令将需要发送或接收的变量进行隔离。
8.在SPI模块的回调函数中只能调用下面这些函数,其他函数不允许在里面调用(协议标准规定):
- Spi_ReadIB
- Spi_WriteIB
- Spi_SetupEB
- Spi_GetJobResult
- Spi_GetSequenceResult
- Spi_GetHWUnitStatus
- Spi_Cancel