1. SPI核心架构与工作模式深度解析
串行外设接口,也就是我们常说的SPI,在嵌入式开发里就像电路板上的“高速公路”,负责微控制器和各种外设芯片之间的高速数据交换。我接触过不少TI的处理器,像AM335x、AM57xx系列,它们的SPI控制器功能相当强大,但手册里那些寄存器描述读起来确实有点绕。今天我就结合自己踩过的坑和实际调优经验,把SPI的主从模式、FIFO管理以及能显著提升性能的Turbo模式,掰开揉碎了讲清楚。
SPI通信的本质是一个主设备带着一个或多个从设备“跳舞”。主设备产生时钟(SPI_SCLK)和控制信号(SPI_SCS[n]),从设备则根据这个节奏收发数据。TI的SPI控制器厉害之处在于,它把很多复杂的时序控制和数据缓冲逻辑都硬件化了,我们通过配置一堆寄存器就能实现各种高级功能。比如,当你需要连续从传感器读取大量数据,但又不想让CPU频繁被中断打扰时,就可以启用接收专用模式(Master Receive-Only)和FIFO;当你对传输速率有极致要求,比如驱动高分辨率显示屏,Turbo模式可能就是你的“杀手锏”。理解这些模式背后的硬件行为,是写出稳定、高效SPI驱动代码的关键。
1.1 主设备接收专用模式(Master Receive-Only)的精髓
手册里提到,主设备接收专用模式是为了“避免CPU重复填充发送寄存器,最小化数据移动”。这句话听起来有点学术,我用人话翻译一下:在这个模式下,你只需要在通信开始时,往发送寄存器(TX Register)里扔一次数据(哪怕是无效的“哑数据”),之后SPI控制器就会自动用这个数据去“勾引”从设备,并把从设备返回的数据源源不断地收进来,而CPU在此期间可以完全不管发送的事。
这背后的硬件逻辑是这样的:在普通的全双工模式下,SPI控制器每完成一个字的传输,发送寄存器(TX)就会变空(TX_empty),接收寄存器(RX)就会变满(RX_full),从而触发中断或DMA请求,要求CPU或DMA控制器赶紧来“伺候”它——既要填充新的发送数据,又要取走接收到的数据。而在接收专用模式下,规则变了。
核心规则解析:
- 规则1(调度规则):一个通道能否被调度去使用共享的移位寄存器,取决于其发送寄存器(TX)和接收寄存器(RX)的状态。这个规则在接收专用模式下依然适用。
- 规则2(TX状态规则):在普通模式下,发送寄存器变空会触发TX_empty事件。但在接收专用模式下,这条规则被“屏蔽”了。一旦你首次写入了发送寄存器,硬件就会将其状态“锁定”为“满”(Full)。这意味着,后续无论传输了多少数据,TX_empty和TX_underflow这两个中断状态位永远不会被置位。你的CPU从此再也不会被“需要发送数据”这个中断所打扰。
- 规则3(RX状态规则):接收寄存器变满会触发RX_full事件。这条规则在接收专用模式下依然有效,用于通知系统数据已就绪,可以读取。
实操配置要点:要启用此模式,你需要配置对应通道的MCSPI_CHxCONF寄存器中的TRM(Transmit/Receive Mode) 位域。通常,TRM = 2代表发送专用,TRM = 1代表接收专用(具体值需查阅具体芯片的数据手册)。配置完成后,你只需要做以下几件事:
- 向发送寄存器写入一个初始数据字(这个数据内容通常对从设备没有意义,或者是一个固定的读取命令,具体看从设备协议)。
- 使能该通道。
- 然后,SPI控制器就会用这个初始数据,持续地、周期性地发起通信,并将从设备的回复数据存入接收寄存器。
- 你只需要处理接收寄存器满(RX_full)中断或DMA请求,不断读取数据即可。
注意:在这种模式下,
EOT(End Of Transfer) 位是判断每个SPI字传输完成的关键标志。而RX_full位在启用FIFO后意义会发生变化,这点我们后面讲FIFO时会详细说。
一个典型的应用场景是连续读取一个ADC(模数转换器)。你发送一次“开始转换并读取”的命令后,就可以进入接收专用模式,ADC会持续返回转换结果,你的CPU只需要专注地把这些结果数据从SPI的接收缓冲区搬走,效率非常高。
1.2 单通道主模式与片选信号手动控制
当你的SPI总线上只有一个从设备时,或者你虽然接了多个设备,但某一时刻只与其中一个通信时,就可以使用单通道主模式。手册里提到了3引脚和4引脚模式,这其实关乎一个关键信号:片选(SPI_SCS[n])。
- 3引脚模式(
MCSPI_MODULCTRL[PIN34]=1):此模式下,SPI_SCS[n]引脚被强制拉低(通常为有效状态)。这意味着你的从设备的片选端必须永久使能(常接地)。这适用于“一对一”且从设备支持永久选中的场景,可以节省一个GPIO引脚。在这种模式下,只要发送寄存器或FIFO非空,控制器就会立即开始传输。 - 4引脚模式(
MCSPI_MODULCTRL[PIN34]=0):这是最常见的情况。SPI_SCS[n]引脚由控制器自动或由软件手动控制。在单通道模式下,你可以通过设置MCSPI_MODULCTRL[SINGLE]=1来启用它。
手动控制片选的威力(Force SPI_SCS[n])这是单通道模式下非常实用的一个高级功能。通常,SPI控制器会在每个数据字传输的开始和结束时自动拉低和拉高片选。但在某些特定从设备协议中(比如某些存储器或传感器),需要在一个通信帧(包含多个SPI字,比如先发命令字,再发地址字,最后读写数据字)内保持片选持续有效。
这时,你就可以使用MCSPI_CHxCONF[FORCE]位进行手动控制。它的工作原理是:
- 设置
FORCE=1,并结合EPOL(片选有效极性) 位,主动驱动SPI_SCS[n] 线到你想要的状态(有效)。 - 然后使能通道。此时,SPI_SCS[n] 线会按照你设定的极性被激活(比如拉低)。
- 之后,你可以进行多次“使能通道->传输数据->禁用通道”的操作序列,而片选线会一直保持有效状态,直到你最后将
FORCE位写回0,或者禁用通道并清除FORCE位。
切换通道时的“坑”与填坑方法手册里特别强调了编程提示:当单通道传输正在进行时,如果你想切换到另一个通道(比如改变了时钟相位、极性等配置),必须严格按顺序操作:
- 等待当前传输完成:轮询或等待
MCSPI_CHxSTAT[EOT]位被置位。这是硬件完成一个SPI字移位的标志。 - 先禁用当前通道:清除当前通道的
MCSPI_CHxCTRL[EN]位。 - 再使能新通道:设置新通道的
MCSPI_CHxCTRL[EN]位。
这个顺序绝对不能错。如果先使能新通道再禁用旧通道,可能会导致总线冲突或不可预知的时序。我曾在调试一个多设备切换的驱动时,因为忽略了这一步,导致数据错乱,排查了半天才发现是通道切换时序问题。
重要警告:在片选信号激活期间(SPI_SCS[n]为有效电平),绝对不要动态修改
EPOL(片选极性)、POL(时钟极性)和PHA(时钟相位)。这些是通信的基本时序参数,在通信中改变会直接导致数据错乱。TRM(传输模式)也只能在通道禁用时修改。
2. 性能加速器:Turbo模式与FIFO缓冲区管理
如果说基础的SPI通信是“绿皮火车”,那么Turbo模式加上FIFO就是“高铁”。它们都是为了解决同一个核心问题:减少总线访问延迟,提升有效数据吞吐量。
2.1 Turbo模式:榨干SPI总线的每一分带宽
Turbo模式的目的是“当单个通道使能时,通过允许在移位寄存器和接收寄存器满之前持续传输,来提高SPI接口的吞吐量”。这听起来有点抽象,我们对比一下普通模式就明白了。
在普通的多通道轮询或单通道非Turbo模式下,SPI控制器传输数据是“小心翼翼”的。它遵循一个严格的规则(规则3):只有当接收寄存器为空时,一个已使能的通道才能被调度去使用移位寄存器。这意味着,CPU或DMA必须及时把接收寄存器里的数据读走,为接收下一个数据腾出空间,传输才能继续。如果CPU忙不过来,数据传输就会“卡住”。
Turbo模式则打破了这条枷锁。在Turbo模式下,规则3不再适用。新的规则是:只要接收寄存器是满的(RXS=1),该通道就可以一直被调度,直到移位寄存器也满了为止。这相当于给SPI控制器内部开了一个“小缓存”。发送端可以提前把多个数据字压入发送寄存器(或FIFO),接收端也可以在移位寄存器填满多个字后,再一次性通知CPU来取。
带来的好处是显而易见的:
- 减少中断/DMA请求频率:CPU或DMA可以一次性处理更多数据,而不是每传一个字就被打断一次。
- 隐藏总线访问延迟:当CPU正在处理其他任务时,SPI控制器可以依靠内部的“小缓存”继续传输一段时间,不会立即停滞。
- 实现更高的持续传输速率:特别是在高时钟频率下,Turbo模式能更有效地利用时钟周期,避免因等待CPU响应而产生的“气泡”。
配置与注意事项:
- 启用条件:必须工作在单通道主模式 (
MCSPI_MODULCTRL[SINGLE]=1),并在对应通道的MCSPI_CHxCONF寄存器中设置Turbo位。 - 发送数据准备:在启动传输前,务必确保发送寄存器或FIFO中有足够的数据。因为一旦开始,SPI控制器会以尽可能快的速度连续发送,如果数据供给不上,会导致发送下溢。
- 接收数据及时读取:虽然Turbo模式放宽了读取时限,但你仍然需要在移位寄存器被填满之前将接收端的数据读走,否则后续数据将无法接收。由于
RX_overflow位在此模式下永远不会被置位(手册明确说明),你无法通过中断感知溢出,所以更需要依靠合理的DMA配置或轮询策略来保证数据流畅通。 - 与手动片选模式的兼容:Turbo模式可以和之前提到的“保持SPI_SCS[n]有效”模式同时使用,实现高速、连续的多字帧传输。
2.2 FIFO缓冲区:解放CPU的利器
FIFO(先入先出缓冲区)是SPI控制器内部一个可选的硬件缓冲区。它的存在,就是为了把CPU从频繁的、琐碎的字节搬运工作中解放出来,让CPU可以专注于更上层的业务逻辑,或者干脆去休眠省电。
FIFO的核心管理逻辑:两个水位线TI SPI控制器的FIFO管理非常精巧,它通过两个可编程的水位线来触发中断或DMA请求:
- 几乎空水位线 (AEL - Almost Empty Level):用于发送路径。当FIFO中剩余的数据量小于或等于AEL设置的值时,控制器就会产生TX_empty事件(或DMA TX请求),告诉系统:“缓冲区快空了,赶紧再给我点数据!” 这样,系统就有充足的时间在FIFO被完全抽干之前补充数据,避免传输中断。
- 几乎满水位线 (AFL - Almost Full Level):用于接收路径。当FIFO中已存储的数据量达到或超过AFL设置的值时,控制器就会产生RX_full事件(或DMA RX请求),告诉系统:“缓冲区快满了,赶紧来把数据搬走!” 这样,系统就有充足的时间在FIFO溢出之前取走数据。
关键配置陷阱:字节与字长的对齐这是配置FIFO时最容易出错的地方!手册里用加粗的“责任”一词强调了两次:It is the responsibility of the driver to set these values as a multiple of SPI word length。
- FIFO的深度和AEL/AFL水位线的单位是字节(Byte)。
- 但SPI传输的单位是字(Word),其长度由
MCSPI_CHxCONF[WL]配置,可以是4到32位(即0.5到4字节)。 - 因此,你在设置AEL和AFL时,必须确保
(AEL + 1)和(AFL + 1)的结果是你当前SPI字长(换算成字节)的整数倍。
举个例子:如果你的SPI字长是12位(即1.5字节),那么每个SPI字会占用FIFO的2个字节(因为FIFO按字节寻址,会向上对齐)。如果你想在FIFO还剩2个SPI字(即4字节)的空间时请求数据,那么AEL应该设置为3(因为AEL+1=4)。如果设置成非整数倍,会导致DMA传输计数错误或数据错位,引发难以调试的故障。
FIFO的多种使用姿势SPI的FIFO可以根据传输模式进行灵活配置,甚至可以拆分使用:
- 收发模式(TRM=0):
- 禁用FIFO:最基础的模式,数据直接通过发送/接收寄存器进出。
- 仅接收使能FIFO(
FFER=1, FFEW=0):接收数据先进入FIFO缓冲,发送数据仍直通。适合主设备读取从设备大量数据的场景。 - 仅发送使能FIFO(
FFER=0, FFEW=1):发送数据先存入FIFO缓冲,接收数据直通。适合主设备向从设备发送大量数据的场景。 - 收发均使能FIFO(
FFER=1, FFEW=1):此时,总的FIFO深度(FFNBYTE)会被平均分成两半,一半用于发送缓冲,一半用于接收缓冲。这是真正的全双工缓冲模式,能最大化地平衡收发数据流。
- 仅接收模式(TRM=1):只能使能接收FIFO (
FFER=1)。 - 仅发送模式(TRM=2):只能使能发送FIFO (
FFEW=1)。
字计数(WCNT)与传输结束管理这是一个非常实用的功能,尤其适合DMA传输。你可以通过设置MCSPI_XFERLEVEL[WCNT]来指定本次要通过FIFO传输的SPI字的总数量。当传输字数达到这个设定值时,控制器会产生一个“字计数结束”中断。
这样做的好处是:
- 精确控制:你确切地知道这次DMA传输了多少数据,便于进行数据包封装和处理。
- 自动停止:传输完成后,你可以安全地禁用通道,而不必担心FIFO里还有残留数据。如果你不使用WCNT(设为0),则需要手动禁用通道,并且需要通过轮询
MCSPI_CHxSTAT[FFE](FIFO空标志) 来确保所有数据都已传输完毕,才能关闭SPI,否则可能会丢失最后几个字。
重要提示:如果启用了FIFO和WCNT功能,并且你使用TX_EMPTY中断来填充数据,那么WCNT的值必须是AEL的整数倍。这是因为TX_EMPTY中断是基于AEL水位线触发的,如果WCNT不是AEL的整数倍,最后一次中断触发时,要填充的数据量可能无法填满FIFO到WCNT要求的最终位置,导致逻辑复杂化。最稳妥的做法是使用DMA而非中断来配合WCNT工作。
3. 高级时序控制与从设备模式要点
除了模式和缓冲区,精确的时序控制和对从设备模式的理解,同样是实现稳定通信的基石。
3.1 片选时序控制(TCS)
在一些时序要求严格的从设备(如某些Flash存储器或高速ADC)中,需要在片选信号有效和第一个时钟边沿之间(建立时间),以及最后一个时钟边沿和片选信号无效之间(保持时间),插入一段可控制的延迟。TI的SPI控制器通过MCSPI_CHxCONF[TCS]位域提供了这个功能。
TCS的值���表延迟的“基数”,实际的延迟时间需要根据时钟分频比 (Fratio)、时钟相位 (PHA) 和时钟粒度 (CLKG) 来计算。手册中的表格22-3给出了详细的计算公式。这里有一个极易忽略的坑:当时钟分频比被设置为1(即时钟旁路,SCLK直接等于参考时钟)时,如果PHA=1,需要在片选有效到第一个时钟沿的延迟中额外增加半个时钟周期;如果PHA=0,则需要在最后一个时钟沿到片选无效的延迟中额外增加半个时钟周期。这是因为在时钟旁路模式下,内部时钟生成逻辑的相位关系有所不同。如果不注意这一点,可能导致建立或保持时间不满足从设备要求,通信在高速下失败。
3.2 时钟分频粒度(CLKG)
SPI时钟(SCLK)由参考时钟(CLKSPIREF)分频得到。TI控制器提供了两种分频粒度:
- 2的幂次方粒度 (
CLKG=0):这是默认模式。分频比Fratio = 2^CLKD。这意味着你只能得到2、4、8、16……这样的分频比。优点是无论分频比是多少,输出的SCLK时钟占空比都是精确的50%。 - 单周期粒度 (
CLKG=1):分频比Fratio = EXTCLK:CLKD + 1。这里EXTCLK是MCSPI_CHxCTRL寄存器中的一个位,与CLKD拼接成一个12位的值,因此分频比可以从1到4096任意设置,灵活性极高。但需要注意:当分频比为奇数时(例如3、5、7),控制器会通过调整以参考时钟下降沿为基准,来尽力维持50%的占空比,但并非所有奇数分频都能做到绝对精确,在设计对时钟占空比敏感的应用时需要实测验证。
选择哪种粒度,取决于你的应用需求。如果需要非常精确的非2的幂次方时钟(例如为了匹配某个特定从设备的最高时钟),就使用单周期粒度。如果只是需要标准分频且保证50%占空比,2的幂次方粒度更简单可靠。
3.3 从设备(Slave)模式下的关键差异
当你的处理器作为从设备时(例如被另一个主控MCU访问),配置和思维模式需要转换。
- 唯一通道:在从模式下,只有通道0(Channel 0)是有效的。使能其他通道没有任何作用。所有通信都必须通过通道0的寄存器进行配置和数据交换。
- 时钟由外部提供:SCLK的频率和时序完全由外部主设备控制。因此,从设备模式下的时钟配置位(如CLKD)是无效的。
- 片选信号选择:你可以通过
MCSPI_CH0CONF[SPIENSLV]位域,选择使用SPI_SCS[3:0]中的哪一个引脚作为本从设备的片选输入。这允许一个SPI接口硬件上连接多个主设备(虽然不常见),或者灵活适配主设备的片选线连接。 - 配置时机:从设备的配置(字长、相位、极性等)必须在通道禁用时写入
MCSPI_CH0CONF寄存器。一旦通道使能,在通信过程中修改这些关键参数可能导致通信失败。 - 发送数据预装载:作为从设备,你需要在被主设备选中(片选有效)之前,就将要回复的数据写入发送寄存器。因为一旦片选有效且时钟开始,从设备就需要立即输出数据。如果发送寄存器为空,可能会输出不确定的电平或旧数据。
从设备模式下的FIFO使用、中断/DMA机制与主模式类似,但同样只针对通道0生效。在从设备设计中,合理使用FIFO和DMA对于处理突发的高速数据流同样至关重要。
4. 实战配置流程、常见问题与调试技巧
理解了原理,最终要落到代码和调试上。下面我以一个典型的“使用DMA和FIFO进行高速数据块读取”为例,梳理一下配置流程和常见问题。
4.1 一个完整的主设备DMA接收配置流程
假设场景:从SPI Flash中连续读取1KB数据,使用DMA,启用FIFO和Turbo模式。
- 引脚复用与时钟使能:配置相关管脚为SPI功能,并使能SPI模块和DMA控制器的时钟。
- SPI基础配置:
- 设置
MCSPI_MODULCTRL:MS=0(主模式),PIN34=0(4引脚模式),SINGLE=1(单通道模式,因为我们要用Turbo)。 - 配置
MCSPI_CH0CONF:TRM=1(接收专用模式)。WL根据Flash数据手册设置(通常为8或16)。POL和PHA根据Flash数据手册设置(通常为0,0或0,1)。CLKG和CLKD设置所需SCLK频率。Turbo=1(启用Turbo模式)。FFER=1(启用接收FIFO)。TCS根据需要设置片选时序。
- 设置
- FIFO与DMA配置:
- 计算并设置
MCSPI_XFERLEVEL:AFL:假设FIFO深度为64字节,SPI字长为8位(1字节)。我们希望FIFO收到48字节时触发DMA读取,则AFL = 48 - 1 = 47。确保48是字长字节数(1)的整数倍。WCNT:我们要读1KB,即1024字节。由于字长为1字节,所以WCNT = 1024。
- 配置DMA控制器:设置源地址为SPI接收寄存器地址,目标地址为内存缓冲区,传输宽度为字节(或字,取决于
MOA设置),传输总量为1024。将DMA请求与SPI的RX事件关联。 - 在SPI的
MCSPI_CH0CONF中,使能DMA接收 (DMAR=1)。
- 计算并设置
- 启动传输:
- 向SPI发送寄存器写入Flash的“读数据”命令(可能需要先发送命令和地址,这里简化)。
- 使能SPI通道 (
MCSPI_CH0CTRL[EN]=1)。 - 使能DMA通道。
- 传输完成处理:
- 等待DMA传输完成中断或轮询DMA完成标志。
- 等待SPI的“字计数结束”中断(如果使能了相应中断),或轮询传输状态。
- 禁用SPI通道。
4.2 常见问题排查实录
问题1:数据错位或全是0xFF/0x00。
- 排查思路:
- 首先检查相位和极性 (POL/PHA):这是SPI通信中最常见的错误。用逻辑分析仪抓取SCLK、MOSI、MISO、CS的波形,与从设备数据手册的时序图严格比对第一个时钟边沿是采样还是输出,空闲电平和采样边沿是否正确。我遇到过很多次,把POL/PHA配反了,读回来的数据看似有变化但完全不对。
- 检查字长 (WL):确保主从设备配置的字长一致。如果从设备是16位,主设备配成8位,就会导致每两个字节错位一次。
- 检查字节序 (Endianness):对于大于8位的字长,要确认控制器发送和接收数据的字节顺序(MSB first还是LSB first)是否与从设备匹配。
- 在接收专用模式下,检查发送的数据:你发送的“哑数据”是什么?有些从设备会把这个数据解释为命令。尝试发送0x00或0xFF看看。
问题2:使能Turbo模式后,通信速度没有提升,甚至出错。
- 排查思路:
- 确认是否满足单通道条件:检查
MCSPI_MODULCTRL[SINGLE]是否已设置为1。 - 检查数据供给/消费速度:Turbo模式要生效,前提是数据流不能断。如果是发送,确保DMA或CPU填充FIFO的速度快于SPI发送的速度;如果是接收,确保DMA或CPU清空FIFO的速度快于SPI接收的速度。否则,Turbo模式的优势无法体现,反而可能因为缓冲区管理不善导致溢出或下溢。使用逻辑分析仪观察TX_EMPTY和RX_FULL中断的频率,如果中断非常密集,说明缓冲区设置太小或系统响应太慢。
- 检查FIFO水位线 (AEL/AFL) 设置:水位线设置得太激进(例如AEL设得太大,AFL设得太小),会导致DMA请求过早触发,增加系统开销,抵消Turbo带来的好处。水位线设置得太保守,则容易导致缓冲区溢出或下溢。需要根据系统处理能力和数据量进行权衡。
- 确认是否满足单通道条件:检查
问题3:使用DMA配合FIFO时,传输的数据量总是不对,最后几个字节丢失。
- 排查思路:
- 首要怀疑对象:AEL/AFL/WCNT对齐问题!这是最高频的坑。反复核对:
(AEL+1)、(AFL+1)、WCNT是否都是SPI字长(字节数)的整数倍?用计算器算,别凭感觉。 - 检查DMA传输大小:DMA配置的传输总量(字节数)是否等于
WCNT * (字长字节数)?如果启用了多字访问 (MOA),DMA的传输宽度和次数是否正确? - 传输结束流程:是否在DMA完成和WCNT中断后,等待了足够的时间,并检查了FIFO空标志 (
FFE) 才禁用SPI?在禁用SPI前,可以尝试再读一次接收寄存器,以确保FIFO中残留的数据被取出。 - 时钟与片选时序:在高频下,TCS设置不当可能导致最后一个数据位未被从设备正确锁存。适当增加TCS值试试。
- 首要怀疑对象:AEL/AFL/WCNT对齐问题!这是最高频的坑。反复核对:
问题4:作为从设备时,主设备读不到数据。
- 排查思路:
- 确认通道0:所有配置是否都针对
MCSPI_CH0CONF和MCSPI_CH0CTRL?其他通道的配置在从模式下无效。 - 预装载发送数据:在主设备发起传输(拉低片选并产生时钟)之前,你的数据是否已经写入
MCSPI_TX0寄存器?作为从设备,输出数据必须是“就绪”状态。 - 片选引脚映射:
SPIENSLV设置是否正确?主设备拉低的片选线是否对应你配置的SPI_SCS[n]引脚? - 相位/极性匹配:从设备的POL/PHA必须与主设备严格一致。
- 确认通道0:所有配置是否都针对
调试SPI,逻辑分析仪是必备神器。不要只依赖打印日志。通过分析仪直观地看波形、看时序、看数据,绝大部分问题都能无处遁形。尤其是检查片选、时钟的建立保持时间,以及数据线的变化是否发生在正确的时钟边沿,这些是软件打印无法替代的。把原理吃透,把配置做细,再结合得力的工具,搞定复杂的SPI通信也就不在话下了。