第十七章 SPI
目录
1 SPI简介
SPI 接口可以配置为支持 SPI 协议或者支持 I2S 音频协议。SPI 接口默认工作在 SPI 方式,可以通过软件把功能从 SPI 模式切换到 I2S 模式。
串行外设接口(SPI)允许芯片与外部设备以半/全双工、同步、串行方式通信。此接口可以被配置成主模式,并为外部从设备提供通信时钟(SCK)。接口还能以多主配置方式工作。
它可用于多种用途,包括使用一条双向数据线的双线单工同步传输,还可使用 CRC 校验的可靠通信。
I2S 也是一种 3 引脚的同步串行接口通讯协议。它支持四种音频标准,包括飞利浦 I2S 标准,MSB和 LSB 对齐标准,以及 PCM 标准。它在半双工通讯中,可以工作在主和从 2 种模式下。当它作为主设备时,通过接口向外部的从设备提供时钟信号。
警告:由于 SPI3/I2S3 的部分引脚与 JTAG 引脚共享(SPI3_NSS/I2S3_WS 与 JTDI,SPI3_SCK/I2S3_CK 与 JTDO),因此这些引脚不受 IO 控制器控制,他们(在每次复位后)被默认保留为 JTAG 用途。如果用户想把引脚配置给 SPI3/I2S3,必须(在调试时)关闭 JTAG并切换至 SWD 接口,或者(在标准应用时)同时关闭 JTAG 和 SWD 接口。
JTAG/SWD 复用功能重映射。
2 SPI和I2S主要特征
2.1 SPI特征
- 3 线全双工同步传输
- 带或不带第三根双向数据线的双线单工同步传输
- 8 或 16 位传输帧格式选择
- 主或从操作
- 支持多主模式
- 8 个主模式波特率预分频系数(最大为 fPCLK/2)
- 从模式频率(最大为 fPCLK/2)
- 主模式和从模式的快速通信
- 主模式和从模式下均可以由软件或硬件进行 NSS 管理:主/从操作模式的动态改变
- 可编程的时钟极性和相位
- 可编程的数据顺序,MSB 在前或 LSB 在前
- 可触发中断的专用发送和接收标志
- SPI 总线忙状态标志
- 支持可靠通信的硬件 CRC
- 在发送模式下,CRC 值可以被作为最后一个字节发送
- 在全双工模式中对接收到的最后一个字节自动进行 CRC 校验
- 可触发中断的主模式故障、过载以及 CRC 错误标志
- 支持 DMA 功能的 1 字节发送和接收缓冲器:产生发送和接受请求
2.2 I2S功能
- 单工通信(仅发送或接收)
- 主或者从操作
- 8 位线性可编程预分频器,获得精确的音频采样频率(8KHz 到 96kHz)
- 数据格式可以是 16 位,24 位或者 32 位
- 音频信道固定数据包帧为 16 位(16 位数据帧)或 32 位(16、24 或 32 位数据帧)
- 可编程的时钟极性(稳定态)
- 从发送模式下的下溢标志位和主/从接收模式下的溢出标志位
- 16 位数据寄存器用来发送和接收,在通道两端各有一个寄存器
- 支持的 I2S 协议:
- I2S 飞利浦标准
- MSB 对齐标准(左对齐)
- LSB 对齐标准(右对齐)
- PCM 标准(16 位通道帧上带长或短帧同步或者 16 位数据帧扩展为 32 位通道帧)
- 数据方向总是 MSB 在先
- 发送和接收都具有 DMA 能力
- 主时钟可以输出到外部音频设备,比率固定为 256xFs(Fs 为音频采样频率)
3 SPI功能描述
3.1 概述
SPI 的方框图见下图:
通常 SPI 通过 4 个引脚与外部器件相连:
- MISO:主设备输入/从设备输出引脚。该引脚在从模式下发送数据,在主模式下接收数据。
- MOSI:主设备输出/从设备输入引脚。该引脚在主模式下发送数据,在从模式下接收数据。
- SCK:串口时钟,作为主设备的输出,从设备的输入
- NSS:从设备选择。这是一个可选的引脚,用来选择主/从设备。它的功能是用来作为“片选引脚”,让主设备可以单独地与特定从设备通讯,避免数据线上的冲突。从设备的 NSS 引脚可以由主设备的一个标准 I/O 引脚来驱动。一旦被使能(SSOE 位),NSS 引脚也可以作为输出引脚,并在 SPI 处于主模式时拉低;此时,所有的 SPI 设备,如果它们的 NSS 引脚连接到主设备的 NSS 引脚,则会检测到低电平,如果它们被设置为 NSS 硬件模式,就会自动进入从设备状态。当配置为主设备、NSS 配置为输入引脚(MSTR=1,SSOE=0)时,如果 NSS 被拉低,则这个 SPI 设备进入主模式失败状态:即 MSTR 位被自动清除,此设备进入从模式
下图是一个单主和单从设备互连的例子:
单主和单从应用
1.这里 NSS 引脚设置为输入
MOSI 脚相互连接,MISO 脚相互连接。这样,数据在主和从之间串行地传输(MSB 位在前)。
通信总是由主设备发起。主设备通过 MOSI 脚把数据发送给从设备,从设备通过 MISO 引脚回传数据。这意味全双工通信的数据输出和数据输入是用同一个时钟信号同步的;时钟信号由主设备通过 SCK 脚提供。
从选择(NSS)脚管理
有 2 种 NSS 模式:
- 软件 NSS 模式:可以通过设置 SPI_CR1 寄存器的 SSM 位来使能这种模式(见图 212)。在这种模式下 NSS 引脚可以用作它用,而内部 NSS 信号电平可以通过写 SPI_CR1 的 SSI 位来驱动。
- 硬件 NSS 模式,分两种情况:
- NSS 输出被使能:当 W55MH32 工作为主 SPI,并且 NSS 输出已经通过 SPI_CR2 寄存器的SSOE 位使能,这时 NSS 引脚被拉低,所有 NSS 引脚与这个主 SPI 的 NSS 引脚相连并配置为硬件 NSS 的 SPI 设备,将自动变成从 SPI 设备。当一个 SPI 设备需要发送广播数据,它必须拉低 NSS 信号,以通知所有其它的设备它是主设备;如果它不能拉低 NSS,这意味着总线上有另外一个主设备在通信,这时将产生一个硬件失败错误(Hard Fault)。
- NSS 输出被关闭:允许操作于多主环境。
硬件/软件的从选择管理
时钟信号的相位和极性
SPI_CR 寄存器的 CPOL 和 CPHA 位,能够组合成四种可能的时序关系。CPOL(时钟极性)位控制在没有数据传输时时钟的空闲状态电平,此位对主模式和从模式下的设备都有效。如果 CPOL 被清'0',SCK 引脚在空闲状态保持低电平;如果 CPOL 被置'1',SCK 引脚在空闲状态保持高电平。如果CPHA(时钟相位)位被置'1',SCK 时钟的第二个边沿(CPOL 位为 0 时就是下降沿,CPOL 位为'1'时就是上升沿)进行数据位的采样,数据在第二个时钟边沿被锁存。如果 CPHA 位被清'0',SCK 时钟的第一边沿(CPOL 位为'0'时就是上升沿,CPOL 位为'1'时就是下降沿)进行数据位采样,数据在第一个时钟边沿被锁存。CPOL 时钟极性和 CPHA 时钟相位的组合选择数据捕捉的时钟边沿。
注意:
- 在改变 CPOL/CPHA 位之前,必须清除 SPE 位将 SPI 禁止。
- 主和从必须配置成相同的时序模式。
- SCK 的空闲状态必须和 SPI_CR1 寄存器指定的极性一致(CPOL 为'1'时,空闲时应上拉 SCK 为高电平;CPOL 为'0'时,空闲时应下拉 SCK 为低电平)。
- 数据帧格式(8 位或 16 位)由 SPI_CR1 寄存器的 DFF 位选择,并且决定发送/接收的数据长度。
数据时钟时序图
数据帧格式
根据 SPI_CR1 寄存器中的 LSBFIRST 位,输出数据位时可以 MSB 在先也可以 LSB 在先。
根据 SPI_CR1 寄存器的 DFF 位,每个数据帧可以是 8 位或是 16 位。所选择的数据帧格式对发送和/或接收都有效。
3.2 配置SPI为从模式
在从模式下,SCK 引脚用于接收从主设备来的串行时钟。SPI_CR1 寄存器中 BR[2:0]的设置不影响数据传输速率。
注: 建议在主设备发送时钟之前使能 SPI 从设备,否则可能会发生意外的数据传输。在通信时钟的第一个边沿到来之前或正在进行的通信结束之前,从设备的数据寄存器必须就绪。在使能从设备和主设备之前,通信时钟的极性必须处于稳定的数值。
请按照以下步骤配置 SPI 为从模式:
配置步骤
1. 设置 DFF 位以定义数据帧格式为 8 位或 16 位。
2. 选择 CPOL 和 CPHA 位来定义数据传输和串行时钟之间的相位关系(见图 213)。为保证正确的数据传输,从设备和主设备的 CPOL 和 CPHA 位必须配置成相同的方式。
3. 帧格式(SPI_CR1 寄存器中的 LSBFIRST 位定义的”MSB 在前”还是”LSB 在前”)必须与主设备相同。
4. 硬件模式下(参考从选择(NSS)脚管理部分),在完整的数据帧(8 位或 16 位)传输过程中,NSS引脚必须为低电平。在 NSS 软件模式下,设置 SPI_CR1 寄存器中的 SSM 位并清除 SSI 位。
5. 清除 MSTR 位、设置 SPE 位(SPI_CR1 寄存器),使相应引脚工作于 SPI 模式下。在这个配置中,MOSI 引脚是数据输入,MISO 引脚是数据输出。
数据发送
过程在写操作中,数据字被并行地写入发送缓冲器。
当从设备收到时钟信号,并且在 MOSI 引脚上出现第一个数据位时,发送过程开始(译注:此时第一个位被发送出去)。余下的位(对于 8 位数据帧格式,还有 7 位;对于 16 位数据帧格式,还有 15位)被装进移位寄存器。当发送缓冲器中的数据传输到移位寄存器时,SPI_SP 寄存器的 TXE 标志被设置,如果设置了 SPI_CR2 寄存器的 TXEIE 位,将会产生中断。
数据接收过程
对于接收器,当数据接收完成时:
移位寄存器中的数据传送到接收缓冲器,SPI_SR 寄存器中的 RXNE 标志被设置。
如果设置了 SPI_CR2 寄存器中的 RXNEIE 位,则产生中断。
在最后一个采样时钟边沿后,RXNE 位被置'1',移位寄存器中接收到的数据字节被传送到接收缓冲器。当读 SPI_DR 寄存器时,SPI 设备返回这个接收缓冲器的数值。读 SPI_DR 寄存器时,RXNE 位被清除。
3.3 配置SPI为主模式
在主配置时,在 SCK 脚产生串行时钟。
配置步骤
1. 通过 SPI_CR1 寄存器的 BR[2:0]位定义串行时钟波特率。
2. 选择 CPOL 和 CPHA 位,定义数据传输和串行时钟间的相位关系(见图 213)。
3. 设置 DFF 位来定义 8 位或 16 位数据帧格式。
4. 配置 SPI_CR1 寄存器的 LSBFIRST 位定义帧格式。
5. 如果需要 NSS 引脚工作在输入模式,硬件模式下,在整个数据帧传输期间应把 NSS 脚连接到高电平;在软件模式下,需设置 SPI_CR1 寄存器的 SSM 位和 SSI 位。如果 NSS 引脚工作在输出模式,则只需设置 SSOE 位。
必须设置 MSTR 位和 SPE 位(只当 NSS 脚被连到高电平,这些位才能保持置位)。在这个配置中,MOSI 引脚是数据输出,而 MISO 引脚是数据输入。
数据发送过程
当写入数据至发送缓冲器时,发送过程开始。
在发送第一个数据位时,数据字被并行地(通过内部总线)传入移位寄存器,而后串行地移出到MOSI 脚上;MSB 在先还是 LSB 在先,取决于 SPI_CR1 寄存器中的 LSBFIRST 位的设置。数据从发送缓冲器传输到移位寄存器时 TXE 标志将被置位,如果设置了 SPI_CR1 寄存器中的 TXEIE位,将产生中断。
数据接收过程
对于接收器来说,当数据传输完成时:
传送移位寄存器里的数据到接收缓冲器,并且 RXNE 标志被置位。
如果设置了 SPI_CR2 寄存器中的 RXNEIE 位,则产生中断。
在最后采样时钟沿,RXNE 位被设置,在移位寄存器中接收到的数据字被传送到接收缓冲器。读SPI_DR 寄存器时,SPI 设备返回接收缓冲器中的数据。读 SPI_DR 寄存器将清除 RXNE 位。
一旦传输开始,如果下一个将发送的数据被放进了发送缓冲器,就可以维持一个连续的传输流。在试图写发送缓冲器之前,需确认 TXE 标志应该为'1'。
注: 在 NSS 硬件模式下,从设备的 NSS 输入由 NSS 引脚控制或另一个由软件驱动的 GPIO 引脚控制。
3.4 配置SPI为单工通信
SPI 模块能够以两种配置工作于单工方式:
- 1 条时钟线和 1 条双向数据线;
- 1 条时钟线和 1 条数据线(只接收或只发送);
1 条时钟线和 1 条双向数据线(BIDIMODE=1)
设置 SPI_CR1 寄存器中的 BIDIMODE 位而启用此模式。在这个模式下,SCK 引脚作为时钟,主设备使用 MOSI 引脚而从设备使用 MISO 引脚作为数据通信。传输的方向由 SPI_CR1 寄存器里的 BIDIOE控制,当这个位是'1'的时候,数据线是输出,否则是输入。
1 条时钟和 1 条单向数据线(BIDIMODE=0)
在这个模式下,SPI 模块可以或者作为只发送,或者作为只接收。
- 只发送模式类似于全双工模式(BIDIMODE=0,RXONLY=0):数据在发送引脚(主模式时是 MOSI、从模式时是 MISO)上传输,而接收引脚(主模式时是 MISO、从模式时是 MOSI)可以作为通用的I/O 使用。此时,软件不必理会接收缓冲器中的数据(如果读出数据寄存器,它不包含任何接收数据)。
- 在只接收模式,可以通过设置 SPI_CR2 寄存器的 RXONLY 位而关闭 SPI 的输出功能;此时,发送引脚(主模式时是 MOSI、从模式时是 MISO)被释放,可以作为其它功能使用。
配置并使能 SPI 模块为只接收模式的方式是:
- 在主模式时,一旦使能 SPI,通信立即启动,当清除 SPE 位时立即停止当前的接收。在此模式下,不必读取 BSY 标志,在 SPI 通信期间这个标志始终为'1'。
- 在从模式时,只要 NSS 被拉低(或在 NSS 软件模式时,SSI 位为'0')同时 SCK 有时钟脉冲,SPI就一直在接收。
3.5 数据发送与接收过程
接收与发送缓冲器
在接收时,接收到的数据被存放在一个内部的接收缓冲器中;在发送时,在被发送之前,数据将首先被存放在一个内部的发送缓冲器中。对 SPI_DR 寄存器的读操作,将返回接收缓冲器的内容;写入 SPI_DR 寄存器的数据将被写入发送缓冲器中。
主模式下开始传输
- 全双工模式(BIDIMODE=0 并且 RXONLY=0)
- 当写入数据到 SPI_DR 寄存器(发送缓冲器)后,传输开始;
- 在传送第一位数据的同时,数据被并行地从发送缓冲器传送到 8 位的移位寄存器中,然后按顺序被串行地移位送到 MOSI 引脚上;
- 与此同时,在 MISO 引脚上接收到的数据,按顺序被串行地移位进入 8 位的移位寄存器中,然后被并行地传送到 SPI_DR 寄存器(接收缓冲器)中。
- 单向的只接收模式(BIDIMODE=0 并且 RXONLY=1)
- SPE=1 时,传输开始;
- 只有接收器被激活,在 MISO 引脚上接收到的数据,按顺序被串行地移位进入 8 位的移位寄存器中,然后被并行地传送到 SPI_DR 寄存器(接收缓冲器)中。
- 双向模式,发送时(BIDIMODE=1 并且 BIDIOE=1)
- 当写入数据到 SPI_DR 寄存器(发送缓冲器)后,传输开始;
- 在传送第一位数据的同时,数据被并行地从发送缓冲器传送到 8 位的移位寄存器中,然后按顺序被串行地移位送到 MOSI 引脚上;
- 不接收数据。
- 双向模式,接收时(BIDIMODE=1 并且 BIDIOE=0)
- SPE=1 并且 BIDIOE=0 时,传输开始;
- 在 MOSI 引脚上接收到的数据,按顺序被串行地移位进入 8 位的移位寄存器中,然后被并行地传送到 SPI_DR 寄存器(接收缓冲器)中。
- 不激活发送器,没有数据被串行地送到 MOSI 引脚上。
从模式下开始传输
- 全+双工模式(BIDIMODE=0 并且 RXONLY=0)
- 当从设备接收到时钟信号并且第一个数据位出现在它的 MOSI 时,数据传输开始,随后的数据位依次移动进入移位寄存器;
- 与此同时,在传输第一个数据位时,发送缓冲器中的数据被并行地传送到 8 位的移位寄存器,随后被串行地发送到 MISO 引脚上。软件必须保证在 SPI 主设备开始数据传输之前在发送寄存器中写入要发送的数据。
- 单向的只接收模式(BIDIMODE=0 并且 RXONLY=1)
- 当从设备接收到时钟信号并且第一个数据位出现在它的 MOSI 时,数据传输开始,随后数据位依次移动进入移位寄存器;
- 不启动发送器,没有数据被串行地传送到 MISO 引脚上。
- 双向模式,发送时(BIDIMODE=1 并且 BIDIOE=1)
- 当从设备接收到时钟信号并且发送缓冲器中的第一个数据位被传送到MISO引脚上的时候,数据传输开始;
- 在第一个数据位被传送到 MISO 引脚上的同时,发送缓冲器中要发送的数据被平行地传送到 8 位的移位寄存器中,随后被串行地发送到 MISO 引脚上。软件必须保证在 SPI 主设备开始数据传输之前在发送寄存器中写入要发送的数据;
- 不接收数据。
- 双向模式,接收时(BIDIMODE=1 并且 BIDIOE=0)
- 当从设备接收到时钟信号并且第一个数据位出现在它的 MOSI 时,数据传输开始;
- 从 MISO 引脚上接收到的数据被串行地传送到 8 位的移位寄存器中,然后被平行地传送到SPI_DR 寄存器(接收缓冲器);
- 不启动发送器,没有数据被串行地传送到 MISO 引脚上。
处理数据的发送与接收
当数据从发送缓冲器传送到移位寄存器时,设置 TXE 标志(发送缓冲器空),它表示内部的发送缓冲器可以接收下一个数据;如果在 SPI_CR2 寄存器中设置了 TXEIE 位,则此时会产生一个中断;写入 SPI_DR 寄存器即可清除 TXE 位。
注: 在写入发送缓冲器之前,软件必须确认 TXE 标志为'1',否则新的数据会覆盖已经在发送缓冲器中的数据。
在采样时钟的最后一个边沿,当数据被从移位寄存器传送到接收缓冲器时,设置 RXNE 标志(接收缓冲器非空);它表示数据已经就绪,可以从 SPI_DR 寄存器读出;如果在 SPI_CR2 寄存器中设置了 RXNEIE 位,则此时会产生一个中断;读出 SPI_DR 寄存器即可清除 RXNIE 标志位。
在一些配置中,传输最后一个数据时,可以使用 BSY 标志等待数据传输的结束。
主或从模式下(BIDIMODE=0 并且 RXONLY=0)全双工发送和接收过程模式
1. 设置 SPE 位为'1',使能 SPI 模块;
2. 在 SPI_DR 寄存器中写入第一个要发送的数据,这个操作会清除 TXE 标志;
3. 等待 TXE=1,然后写入第二个要发送的数据。等待 RXNE=1,然后读出 SPI_DR 寄存器并获得第一个接收到的数据,读 SPI_DR 的同时清除了 RXNE 位。重复这些操作,发送后续的数据同时接收 n-1 个数据;
4. 等待 RXNE=1,然后接收最后一个数据;
5. 等待 TXE=1,在 BSY=0 之后关闭 SPI 模块。
也可以在响应 RXNE 或 TXE 标志的上升沿产生的中断的处理程序中实现这个过程。
主模式、全双工模式下(BIDIMODE=0 并且 RXONLY=0)连续传输时,TXE/RXNE/BSY 的变化示意图
从模式、全双工模式下(BIDIMODE=0 并且 RXONLY=0)连续传输时,TXE/RXNE/BSY 的变化示意图
只发送过程(BIDIMODE=0 并且 RXONLY=0)
在此模式下,传输过程可以简要说明如下,使用 BSY 位等待传输的结束(见下两图):
1. 设置 SPE 位为'1',使能 SPI 模块;
2. 在 SPI_DR 寄存器中写入第一个要发送的数据,这个操作会清除 TXE 标志;
3. 等待 TXE=1,然后写入第二个要发送的数据。重复这个操作,发送后续的数据;
4. 写入最后一个数据到 SPI_DR 寄存器之后,等待 TXE=1;然后等待 BSY=0,这表示最后一个数据的传输已经完成。
也可以在响应 TXE 标志的上升沿产生的中断的处理程序中实现这个过程。
注:
- 对于不连续的传输,在写入 SPI_DR 寄存器的操作与设置 BSY 位之间有 2 个 APB 时钟周期的延迟,因此在只发送模式下,写入最后一个数据后,最好先等待 TXE=1,然后再等待 BSY=0。
- 只发送模式下,在传输 2 个数据之后,由于不会读出接收到的数据,SPI_SR 寄存器中的 OVR位会变为'1'。软件不必理会这个 OVR 标志位。
主设备只发送模式(BIDIMODE=0 并且 RXONLY=0)下连续传输时,,TXE/BSY 变化示意图
从设备只发送模式(BIDIMODE=0 并且 RXONLY=0)下连续传输时,TXE/BSY 变化示意图
双向发送过程(BIDIMODE=1 并且 BIDIOE=1)
在此模式下,操作过程类似于只发送模式,不同的是:在使能 SPI 模块之前,需要在 SPI_CR2 寄存器中同时设置 BIDIMODE 和 BIDIOE 位为'1'。
单向只接收模式(BIDIMODE=0 并且 RXONLY=1)在此模式下,传输过程可以简要说明如下(见):
- 在 SPI_CR2 寄存器中,设置 RXONLY=1;
- 设置 SPE=1,使能 SPI 模块:
a.主模式下,立刻产生 SCK 时钟信号,在关闭 SPI(SPE=0)之前,不断地接收串行数据;
b.从模式下,当 SPI 主设备拉低 NSS 信号并产生 SCK 时钟时,接收串行数据。
- 等待 RXNE=1,然后读出 SPI_DR 寄存器以获得收到的数据(同时会清除 RXNE 位)。重复这个操作接收所有数据。也可以在响应 RXNE 标志的上升沿产生的中断的处理程序中实现这个过程。
注: 如果在最后一个数据传输结束后关闭 SPI 模块,请按照第 16.3.8 节的建议操作
只接收模式(BIDIMODE=0 并且 RXONLY=1)下连续传输时,RXNE 变化示意图
单向接收过程(BIDIMODE=1 并且 BIDIOE=0)
在此模式下,操作过程类似于只接收模式,不同的是:在使能 SPI 模块之前,需要在 SPI_CR2 寄存器中设置 BIDIMODE 为'1'并清除 BIDIOE 位为'0'。
连续和非连续传输
当在主模式下发送数据时,如果软件足够快,能够在检测到每次 TXE 的上升沿(或 TXE 中断),并立即在正在进行的传输结束之前写入 SPI_DR 寄存器,则能够实现连续的通信;此时,在每个数据项的传输之间的 SPI 时钟保持连续,同时 BSY 位不会被清除。
如果软件不够快,则会导致不连续的通信;这时,在每个数据传输之间会被清除(见下图)。在主模式的只接收模式下(RXONLY=1),通信总是连续的,而且 BSY 标志始终为'1'。
在从模式下,通信的连续性由 SPI 主设备决定。不管怎样,即使通信是连续的,BSY 标志会在每个数据项之间至少有一个 SPI 时钟周期为低。
非连续传输发送(BIDIMODE=0 并且 RXONLY=0)时,TXE/BSY 变化示意图
4 利用DMA的SPI通信
为了达到最大通信速度,需要及时往 SPI 发送缓冲器填数据,同样接收缓冲器中的数据也必须及时读走以防止溢出。为了方便高速率的数据传输,SPI 实现了一种采用简单的请求/应答的 DMA 机制。
当 SPI_CR2 寄存器上的对应使能位被设置时,SPI 模块可以发出 DMA 传输请求。发送缓冲器和接收缓冲器亦有各自的 DMA 请求。
- 发送时,在每次 TXE 被设置为'1'时发出 DMA 请求,DMA 控制器则写数据至 SPI_DR 寄存器,TXE 标志因此而被清除。
- 接收时,在每次 RXNE 被设置为'1'时发出 DMA 请求,DMA 控制器则从 SPI_DR 寄存器读出数据,RXNE 标志因此而被清除。当只使用 SPI 发送数据时,只需使能 SPI 的发送 DMA 通道。此时,因为没有读取收到的数据,OVR被置为'1'(译注:软件不必理会这个标志)。当只使用 SPI 接收数据时,只需使能 SPI 的接收 DMA 通道。
在发送模式下,当 DMA 已经传输了所有要发送的数据(DMA_ISR 寄存器的 TCIF 标志变为'1')后,可以通过监视 BSY 标志以确认 SPI 通信结束,这样可以避免在关闭 SPI 或进入停止模式时,破坏最后一个数据的传输。因此软件需要先等待 TXE=1,然后等待 BSY=0。
注: 在不连续的通信中,在写数据到 SPI_DR 的操作与 BSY 位被置为'1'之间,有 2 个 APB 时钟周期的延迟,因此,在写完最后一个数据后需要先等待 TXE=1 再等待 BSY=0。
使用 DMA 发送
使用 DMA 接收
5 SPI中断
SPI 中断请求
中断事件 | 事件标志 | 使能控制位 |
发送缓冲器空标志 | TXE | TXEIE |
接收缓冲器非空标志 | RXNE | RXNEIE |
主模式失效事件 | MODF | ERRIE |
溢出错误 | OVR | ERRIE |
CRC 错误标志 | CRCERR | ERRIE |
6 例程设计
6.1 SPI_DMA例程
1.UART 模块:配置 USART1,将printf输出重定向到该串口,用于输出系统时钟信息与测试提示。
2.SPI 模块:使能 SPI1 和 GPIOA 时钟,配置相关引脚。
- 初始化 SPI1,设置为双线全双工、主模式,数据大小为 8 位等参数。
3.DMA 模块:使能 DMA1 时钟,配置 DMA1 通道 2 用于 SPI1 的接收,通道 3 用于 SPI1 的发送。
- 分别设置通道的外设地址、内存地址、数据传输方向等参数。
- 使能 SPI1 的 DMA 收发请求,但初始时禁用 DMA 通道。
4.主函数模块:初始化延时函数和 UART。
int main(void)
{
RCC_ClocksTypeDef clocks;
uint16_t i;
// 初始化延时和串口
delay_init();
UART_Configuration(115200);
// 打印系统时钟信息
RCC_GetClocksFreq(&clocks);
printf("\nSYSCLK: %3.1fMhz, HCLK: %3.1fMhz, PCLK1: %3.1fMhz, PCLK2: %3.1fMhz, ADCCLK: %3.1fMhz\n",
(float)clocks.SYSCLK_Frequency / 1000000, (float)clocks.HCLK_Frequency / 1000000,
(float)clocks.PCLK1_Frequency / 1000000, (float)clocks.PCLK2_Frequency / 1000000, (float)clocks.ADCCLK_Frequency / 1000000);
printf("SPI DMA Test.\n");
// 初始化缓冲区:发送数据为0x01~0xFF,接收缓冲区清零
for (i = 0; i < SPI_BUFF_SIZE; i++)
{
SPI_TX_BUFF[i] = i + 1;
}
memset(SPI_RX_BUFF, 0, sizeof(SPI_RX_BUFF));
// 初始化SPI和DMA
SPI_Configuration();
DMA_Configuration();
// ================== SPI发送测试 ==================
printf("Start SPI DMA transmission...\n");
GPIO_ResetBits(GPIOA, GPIO_Pin_4); // 拉低片选(CS有效)
printf("Chip select (CS) is pulled low.\n");
DMA_Cmd(DMA1_Channel3, ENABLE); // 启用发送通道
printf("DMA channel 3 for SPI Tx is enabled.\n");
// 等待发送完成(查询传输完成标志)
while (!DMA_GetFlagStatus(DMA1_FLAG_TC3))
{
printf("DMA transfer in progress...\n");
delay_ms(10);
}
printf("DMA transfer completed.\n");
// 清除标志并禁用通道
DMA_ClearFlag(DMA1_FLAG_TC3);
printf("DMA transfer complete flag is cleared.\n");
DMA_Cmd(DMA1_Channel3, DISABLE);
printf("DMA channel 3 for SPI Tx is disabled.\n");
delay_ms(100);
GPIO_SetBits(GPIOA, GPIO_Pin_4); // 拉高片选(CS无效)
printf("Chip select (CS) is pulled high.\n");
// 打印发送数据
printf("Data sent via SPI:\n");
for (i = 0; i < SPI_BUFF_SIZE; i++)
{
printf("%02X ", SPI_TX_BUFF[i]);
if ((i + 1) % 16 == 0) printf("\n");
}
printf("\n");
// ================== SPI接收测试(如需自发自收需硬件环路) ==================
printf("Start SPI DMA reception...\n");
GPIO_ResetBits(GPIOA, GPIO_Pin_4); // 再次拉低片选(仅示例,实际需硬件支持)
printf("Chip select (CS) is pulled low.\n");
DMA_Cmd(DMA1_Channel2, ENABLE); // 启用接收通道
printf("DMA channel 2 for SPI Rx is enabled.\n");
// 等待接收完成(假设发送后立即接收,需根据硬件调整)
while (!DMA_GetFlagStatus(DMA1_FLAG_TC2))
{
printf("DMA Rx transfer in progress...\n");
delay_ms(10);
}
printf("DMA Rx transfer completed.\n");
DMA_ClearFlag(DMA1_FLAG_TC2);
printf("DMA Rx transfer complete flag is cleared.\n");
DMA_Cmd(DMA1_Channel2, DISABLE);
printf("DMA channel 2 for SPI Rx is disabled.\n");
GPIO_SetBits(GPIOA, GPIO_Pin_4); // 拉高片选
// 打印接收数据(需硬件环路才能看到有效数据)
printf("Data received via SPI:\n");
for (i = 0; i < SPI_BUFF_SIZE; i++)
{
printf("%02X ", SPI_RX_BUFF[i]);
if ((i + 1) % 16 == 0) printf("\n");
}
printf("\n");
while (1); // 主循环保持运行
}
- 获取系统时钟频率并输出。
- 填充发送缓冲区,清空接收缓冲区。
- 配置 SPI 和 DMA。
- 拉低片选信号,使能 DMA 发送通道,等待发送完成后清除标志位、禁用通道,最后拉高片选信号。
6.2 SPI_MasterSlaveDma例程
该例程是一个基于 W55MH32 的 SPI DMA 通信测试程序,支持主从模式切换,以下是其详细运行过程:
1. 初始化阶段
(1)时钟配置
// 时钟配置函数:启用HSE并配置PLL倍频
void RCC_ClkConfiguration(void)
{
RCC_DeInit(); // 复位RCC配置到默认状态
// 启用外部高速时钟(HSE)
RCC_HSEConfig(RCC_HSE_ON);
while (RCC_GetFlagStatus(RCC_FLAG_HSERDY) == RESET); // 等待HSE稳定
// 禁用PLL并配置PLL参数(HSE×27倍频,假设HSE=8MHz,最终SYSCLK=216MHz)
RCC_PLLCmd(DISABLE);
WIZ_RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_27, 1); // HSE作为PLL输入,倍频27倍
RCC_PLLCmd(ENABLE);
while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET); // 等待PLL锁定
// 设置系统时钟源及总线分频
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); // 使用PLL作为系统时钟
RCC_HCLKConfig(RCC_SYSCLK_Div1); // AHB总线时钟 = SYSCLK (216MHz)
RCC_PCLK1Config(RCC_HCLK_Div2); // APB1总线时钟 = HCLK/2 (108MHz)
RCC_PCLK2Config(RCC_HCLK_Div1); // APB2总线时钟 = HCLK (216MHz)
// 启用内部低速(LSI)和高速(HSI)时钟(供其他外设使用)
RCC_LSICmd(ENABLE);
while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET);
RCC_HSICmd(ENABLE);
while (RCC_GetFlagStatus(RCC_FLAG_HSIRDY) == RESET);
}
- 调用RCC_ClkConfiguration():启用外部高速时钟(HSE),配置 PLL 为 HSE×27 倍频(最终 SYSCLK=72MHz)。
- 配置 AHB 时钟(HCLK)=72MHz,APB1(PCLK1)=36MHz,APB2(PCLK2)=72MHz。
- 启用内部低速时钟(LSI)和高速时钟(HSI)。
(2)串口初始化
- 调用UART_Configuration(115200):配置 USART1 波特率为 115200,8 位数据位,1 位停止位,无校验。
- 初始化 GPIOA 的 Pin9(TX)和 Pin10(RX)为复用推挽输出和浮空输入。
- 使能 USART1。
(3)打印时钟信息
- 通过RCC_GetClocksFreq()获取并打印系统各时钟频率:
SYSCLK: 72.0Mhz, HCLK: 72.0Mhz, PCLK1: 36.0Mhz, PCLK2: 72.0Mhz, ADCCLK: 36.0Mhz
2. 数据准备阶段
- 初始化发送缓冲区:
for (i = 0; i < SPI_BUFF_SIZE; i++) {
SPI_TX_BUFF[i] = i + 1; // 填充数据0x01~0xFF
}
清空接收缓冲区:
memset(SPI_RX_BUFF, 0, sizeof(SPI_RX_BUFF));
3. SPI 与 DMA 配置
(1)模式选择
- 通过条件编译选择主模式(SPI_MASTER)或从模式(SPI_SLAVE)。当前代码默认为主模式。
(2)GPIO 与时钟使能
- 主模式(SPI3):使能 SPI3、GPIOA、GPIOB、DMA2 时钟。
- 配置 GPIOB 的 Pin3(MOSI)、Pin4(MISO)、Pin5(SCK)为复用推挽输出。
- 配置 GPIOA 的 Pin15 为片选(CS)输出,初始拉高。
(3)DMA 配置
- 接收通道(DMA2_Channel1):外设地址为SPI3->DR,内存地址为SPI_RX_BUFF。
- 方向为外设到内存,数据宽度为 8 位,循环模式关闭。
- 使能 DMA 传输完成中断。
- 发送通道(DMA2_Channel2):外设地址为SPI3->DR,内存地址为SPI_TX_BUFF。
- 方向为内存到外设,其他参数与接收通道一致。
(4)SPI 初始化
- 主模式配置:全双工模式,8 位数据,CPOL = 低,CPHA = 第一边沿(模式 0)。
- 波特率预分频为 8(SPI 时钟 = 72MHz/8=9MHz)。
- 使能 SPI3。
4. 数据传输阶段
(1)触发 DMA 传输
void SpiDmaTrans(unsigned char *sendBuffer, unsigned char *reciveBuffer, unsigned int len)
{
// 主模式下拉低片选信号(假设PA15为片选引脚)
#ifdef SPI_MASTER
GPIO_ResetBits(GPIOA, GPIO_Pin_15); // 拉低片选,选中从设备
#endif
// 禁用旧的DMA请求和通道(确保配置干净)
SPI_I2S_DMACmd(UseSpi, SPI_I2S_DMAReq_Tx, DISABLE);
SPI_I2S_DMACmd(UseSpi, SPI_I2S_DMAReq_Rx, DISABLE);
DMA_Cmd(UseTxChannel, DISABLE);
DMA_Cmd(UseRxChannel, DISABLE);
// 配置DMA缓冲区地址
UseTxChannel->CMAR = (uint32_t)sendBuffer; // 发送缓冲区地址
UseRxChannel->CMAR = (uint32_t)reciveBuffer; // 接收缓冲区地址
// 设置传输数据长度(256字节)
UseTxChannel->CNDTR = len;
UseRxChannel->CNDTR = len;
// 重置传输完成标志
SpiTransFinish = 0;
// 启用DMA通道和SPI的DMA请求
DMA_Cmd(UseRxChannel, ENABLE); // 先启用接收通道(确保同步)
DMA_Cmd(UseTxChannel, ENABLE);
SPI_I2S_DMACmd(UseSpi, SPI_I2S_DMAReq_Rx, ENABLE); // 使能SPI接收DMA请求
SPI_I2S_DMACmd(UseSpi, SPI_I2S_DMAReq_Tx, ENABLE); // 使能SPI发送DMA请求
}
- 调用SpiDmaTrans():拉低片选信号(GPIOA Pin15)。
- 配置 DMA 缓冲区地址和数据长度(256 字节)。
- 启用 DMA 通道和 SPI 的 DMA 请求。
(2)中断处理
- 传输完成中断(UseSpiTransFinishHanlder):清除 DMA 完成标志,禁用 DMA 通道。
- 拉高片选信号(主模式)。
- 设置SpiTransFinish标志,通知主循环传输完成。
5. 数据验证与循环
- 主循环逻辑:等待 DMA 传输完成(SpiTransFinish标志)。
- 比较发送与接收数据:若不一致,打印错误信息并进入死循环。
- 若一致,打印>>success并延时 10ms 后重试。
- 比较发送与接收数据:若不一致,打印错误信息并进入死循环。
7 下载验证
7.1 SPI_DMA例程
程序启动
- 通过串口输出系统时钟频率信息,可确认时钟配置是否正确。
- 显示测试提示信息,表明开始进行 SPI 的 DMA 数据传输测试。
SPI 数据发送
- 拉低片选信号(GPIOA 的引脚 4),选中 SPI 从设备,准备传输数据。
- 使能 DMA 发送通道,开始通过 SPI1 将发送缓冲区的数据传输到从设备。
- 等待 DMA 传输完成,此过程中硬件上 SPI 总线会有相应信号传输。
- 传输完成后,清除 DMA 传输完成标志,禁用 DMA 发送通道。
- 拉高片选信号,结束本次数据传输。