SPI

外设特性

SPV1x有2个SPI模块,每个SPI模块支持以下功能特性:

  1. 支持SPI Master/Slave模式

  2. 支持全双工和半双工模式。 SPI的接收使能由SPI_CTL.SPI_RX_EN位控制,发送使能由SPI_CTL.SPI_TX_EN位控制。通过SPI_RX_EN和SPI_TX_EN的组合设置,实现全双工和半双工通讯。

    ../../_images/kiwi-spi-mode.png
  3. 支持软件/硬件NSS信号控制 在SPI主机模式下,通过配置SPI_CTL.CS_MODE位选择软件NSS信号控制和硬件NSS信号控制。 当选择软件NSS信号控制时,用户通过SPI_CTL.SPI_CS位控制NSS引脚的电平。 当选择硬件NSS信号控制时,SPI在启动数据发送前会自动拉低NSS引脚,待FIFO中的数据全部从数据引脚上发送完成后,SPI自动拉高NSS引脚。如果用户连续向FIFO中写入N字节数据,那么在整个N字节发送过程中,NSS引脚都会保持低电平。

  4. 支持1bit/2bit/4bit位宽收发

  5. 支持3线SPI(NSS,SCK,DAT0)

  6. 支持MODE0/MODE1/MODE2/MODE3 SPV1x的MODE0~3分别对应不同时钟极性(CPOL)和时钟相位(CPHA)的组合,其关系如下:

    ../../_images/kiwi-spi-cp.png

    不同CPOL和CPHA配置下,SPI的时序如下:

    ../../_images/kiwi-spi-sequence.png
  7. 支持MSB(高位在前)/LSB(低位在前)

  8. 支持DIO_SWAP(MOSI和MISO交换)功能

  9. 支持Loopback功能

  10. 收发各自有8级深度的FIFO

  11. 支持中断和DMA请求

外设使用

SoC的SPI分为标准SPI功能和扩展SPI功能。 标准SPI功能分为标准SPI主机和标准SPI从机。 扩展SPI功能分为3线SPI、2bit位宽SPI和4bit位宽SPI。 SoC的SPI有6个信号:NSS,SCK,DAT0,DAT1,DAT2,DAT3。在不同的功能场景下,所用到的SPI信号也不同。

../../_images/kiwi-spi-signal.png

1. 使用SPI前的配置

SPI使用前,需要配置工作时钟,并解除模块的复位状态。

  1. 在CMU_SPInCLK中选择SPI的时钟源,并设置SPI的时钟分频值。 此步骤决定在SPI主机模式下,SCK的时钟频率。

  2. 配置CMU_CLKEN0.SPIn,使能SPIn的时钟。

  3. 配置RMU_RSTEN0.SPIn,解除SPIn的复位状态。

  4. 初始化SPI需要用到的GPIO引脚。

2. 标准SPI功能

标准SPI功能可与市面上的各种SPI设备进行通讯。

1. 标准SPI主机功能配置

  1. 配置SPI_CTL.MS_MODE位,选择Master功能。

  2. 配置SPI_CTL.SPI_WIDTH位,选择Single(1bit位宽)。

  3. 配置SPI_CTL.DIO_MODE位,选择4 Wire SPI(MOSI and MISO Seperate)。

  4. 配置SPI_CTL.SPI_MODE字段,选择时钟极性和时钟相位。常见配置为Mode0(CPOL=0,CPHA=0)。

  5. 根据需要配置SPI_CTL.LSB_MODE位,选择MSB或LSB。常见配置为MSB。

  6. 根据需要配置SPI_CTL.DIO_SWAP位,以交换MOSI和MISO引脚。

  7. 配置SPI_CTL.SPI_EN位,使能SPI模块。

  8. 配置SPI_CTL.SPI_RX_EN和SPI_CTL.SPI_TX_EN,使能SPI收发功能。

  9. 根据需要配置SPI的中断。配置SPI_IE寄存器中的相关位,以使能响应的中断。如果使用FIFO阈值中断,还需要配置SPI_CTL.TX_FIFO_THR和SPI_CTL.RX_FIFO_THR。

  10. 根据需要使能DMA请求。配置SPI_IE.RX_DMA_EN位,使能SPI接收的DMA请求。配置SPI_IE.TX_DMA_EN位,使能SPI发送的DMA请求。当使用SPI接收的DMA功能时,还需要将SPI_CTL.RX_FIFO_THR设为1,使得SPI每收到一个字节数据,都发起一次DMA请求,将数据读到指定的内存空间。

  11. 通过CPU或DMA向TX_FIFO写入数据,SPI会自动开始数据传输。传输开始后,通过CPU或DMA读取RX_FIFO中的数据。

2. 标准SPI从机功能配置

  1. 配置SPI_CTL.MS_MODE位,选择Slave功能。

  2. 配置SPI_CTL.SPI_WIDTH位,选择Single(1bit位宽)。

  3. 配置SPI_CTL.DIO_MODE位,选择4 Wire SPI(MOSI and MISO Seperate)。

  4. 配置SPI_CTL.SPI_MODE字段,选择时钟极性和时钟相位。常见配置为Mode0(CPOL=0,CPHA=0)。

  5. 根据需要配置SPI_CTL.LSB_MODE位,选择MSB或LSB。常见配置为MSB。

  6. 根据需要配置SPI_CTL.DIO_SWAP位,以交换MOSI和MISO引脚。

  7. 配置SPI_CTL.SPI_EN位,使能SPI模块。

  8. 配置SPI_CTL.SPI_RX_EN和SPI_CTL.SPI_TX_EN,使能SPI收发功能。

  9. 根据需要配置SPI的中断。配置SPI_IE寄存器中的相关位,以使能响应的中断。如果使用FIFO阈值中断,还需要配置SPI_CTL.TX_FIFO_THR和SPI_CTL.RX_FIFO_THR。

  10. 根据需要使能DMA请求。配置SPI_IE.RX_DMA_EN位,使能SPI接收的DMA请求。配置SPI_IE.TX_DMA_EN位,使能SPI发送的DMA请求。当使用SPI接收的DMA功能时,还需要将SPI_CTL.RX_FIFO_THR设为1,使得SPI每收到一个字节数据,都发起一次DMA请求,将数据读到指定的内存空间。

  11. 在主机发起传输前,向TX_FIFO预写入要发送给主机数据。在主机发起传输后,读取RX_FIFO中主机发送过来的数据,并向TX_FIFO继续写入后续要发送给主机的数据。

3. 扩展SPI功能

扩展SPI功能用于某些特定的场景。当设备时序能与扩展SPI功能时序相兼容时,使用扩展SPI功能可以节省资源或提升性能。

1. 3线SPI功能配置

3线SPI功能将MISO和MOSI信号进行了合并。对于只需要半双工和单工SPI通讯的场景,使用3线SPI功能,既可以保证性能,又能节省GPIO引脚。

  1. 配置SPI_CTL.MS_MODE位,选择Master功能。

  2. 配置SPI_CTL.SPI_WIDTH位,选择Single(1bit位宽)。

  3. 配置SPI_CTL.DIO_MODE位,选择3 Wire SPI(MOSI share with MISO)。

  4. 配置SPI_CTL.SPI_MODE字段,选择时钟极性和时钟相位。常见配置为Mode0(CPOL=0,CPHA=0)。

  5. 根据需要配置SPI_CTL.LSB_MODE位,选择MSB或LSB。常见配置为MSB。

  6. 配置SPI_CTL.SPI_EN位,使能SPI模块。

  7. 当需要发送数据时,配置SPI_CTL.SPI_TX_EN为1,SPI_CTL.SPI_RX_EN为0。当需要接收数据时,配置SPI_CTL.SPI_TX_EN为0,SPI_CTL.SPI_RX_EN为1。

  8. 根据需要配置SPI的中断。配置SPI_IE寄存器中的相关位,以使能响应的中断。如果使用FIFO阈值中断,还需要配置SPI_CTL.TX_FIFO_THR和SPI_CTL.RX_FIFO_THR。

  9. 根据需要使能DMA请求。配置SPI_IE.RX_DMA_EN位,使能SPI接收的DMA请求。配置SPI_IE.TX_DMA_EN位,使能SPI发送的DMA请求。当使用SPI接收的DMA功能时,还需要将SPI_CTL.RX_FIFO_THR设为1,使得SPI每收到一个字节数据,都发起一次DMA请求,将数据读到指定的内存空间。

  10. 当处于发送模式时,通过CPU或DMA向 TX_FIFO写入数据,SPI会自动开始数据传输。

  11. 当处于接收模式时,使用RX_NUM功能发起数据的读取操作,然后通过CPU或DMA读取RX_FIFO中的数据。

2. 2bit位宽SPI功能配置

2bit位宽SPI功能与标准SPI功能使用相同的引脚,但是数据引脚做了重新定义。2bit位宽SPI功能只支持半双工模式。

  1. 配置SPI_CTL.MS_MODE位,选择Master功能。

  2. 配置SPI_CTL.SPI_WIDTH位,选择Dual(2bit位宽)。

  3. 配置SPI_CTL.DIO_MODE位,选择4 Wire SPI(MOSI and MISO Seperate)。

  4. 配置SPI_CTL.SPI_MODE字段,选择时钟极性和时钟相位。常见配置为Mode0(CPOL=0,CPHA=0)。

  5. 根据需要配置SPI_CTL.LSB_MODE位,选择MSB或LSB。常见配置为MSB。

  6. 配置SPI_CTL.SPI_EN位,使能SPI模块。

  7. 当需要发送数据时,配置SPI_CTL.SPI_TX_EN为1,SPI_CTL.SPI_RX_EN为0。当需要接收数据时,配置SPI_CTL.SPI_TX_EN为0,SPI_CTL.SPI_RX_EN为1。

  8. 根据需要配置SPI的中断。配置SPI_IE寄存器中的相关位,以使能响应的中断。如果使用FIFO阈值中断,还需要配置SPI_CTL.TX_FIFO_THR和SPI_CTL.RX_FIFO_THR。

  9. 根据需要使能DMA请求。配置SPI_IE.RX_DMA_EN位,使能SPI接收的DMA请求。配置SPI_IE.TX_DMA_EN位,使能SPI发送的DMA请求。当使用SPI接收的DMA功能时,还需要将SPI_CTL.RX_FIFO_THR设为1,使得SPI每收到一个字节数据,都发起一次DMA请求,将数据读到指定的内存空间。

  10. 当处于发送模式时,通过CPU或DMA向 TX_FIFO写入数据,SPI会自动开始数据传输。

  11. 当处于接收模式时,使用RX_NUM功能发起数据的读取操作,然后通过CPU或DMA读取RX_FIFO中的数据。

3. 4bit位宽SPI功能配置

4bit位宽SPI功能使用全部6个SPI信号。4bit位宽SPI功能与2bit位宽SPI类似,也只支持半双工模式。

  1. 配置SPI_CTL.MS_MODE位,选择Master功能。

  2. 配置SPI_CTL.SPI_WIDTH位,选择Quad(4bit位宽)。

  3. 配置SPI_CTL.DIO_MODE位,选择4 Wire SPI(MOSI and MISO Seperate)。

  4. 配置SPI_CTL.SPI_MODE字段,选择时钟极性和时钟相位。常见配置为Mode0(CPOL=0,CPHA=0)。

  5. 根据需要配置SPI_CTL.LSB_MODE位,选择MSB或LSB。常见配置为MSB。

  6. 配置SPI_CTL.SPI_EN位,使能SPI模块。

  7. 当需要发送数据时,配置SPI_CTL.SPI_TX_EN为1,SPI_CTL.SPI_RX_EN为0。当需要接收数据时,配置SPI_CTL.SPI_TX_EN为0,SPI_CTL.SPI_RX_EN为1。

  8. 根据需要配置SPI的中断。配置SPI_IE寄存器中的相关位,以使能响应的中断。如果使用FIFO阈值中断,还需要配置SPI_CTL.TX_FIFO_THR和SPI_CTL.RX_FIFO_THR。

  9. 根据需要使能DMA请求。配置SPI_IE.RX_DMA_EN位,使能SPI接收的DMA请求。配置SPI_IE.TX_DMA_EN位,使能SPI发送的DMA请求。当使用SPI接收的DMA功能时,还需要将SPI_CTL.RX_FIFO_THR设为1,使得SPI每收到一个字节数据,都发起一次DMA请求,将数据读到指定的内存空间。

  10. 当处于发送模式时,通过CPU或DMA向TX_FIFO写入数据,SPI会自动开始数据传输。

  11. 当处于接收模式时,使用RX_NUM功能发起数据的读取操作,然后通过CPU或DMA读取RX_FIFO中的数据。

4. RX_NUM功能

当SPI只需要接收数据时,可以使用RX_NUM功能完成指定长度的数据接收。 配置SPI_RX_NUM.RX_NUM为预期接收的数据字节数,同时配置SPI_RX_NUM.RX_START启动SPI接收。RX_NUM功能启动后,CPU或DMA应及时读取RX_FIFO中的数据,否则RX_NUM功能会因RX_FIFO无可用空间而暂停工作。

注意事项

  1. SPV1x的SPI数据长度固定为8bit。对于需要更大数据长度的场景(如16bit),可以将16bit拆分成2个8bit数据进行传输。

  2. SPI的DMA请求使能位在SPI_IE寄存器中。

  3. 当SPI处于主机全双工模式时,发送完1字节的同时也会收到1字节,即使收到的数据无用,也要及时读走,避免造成RX_FIFO上溢。

  4. 当SPI处于主机模式时,在开始通讯前,需要先拉低NSS引脚,然后再开始进行数据收发。数据收发过程中,需要保持NSS为低电平。

  5. 当SPI处于从机模式时,TX_FIFO中存储着要发送给主机的数据。TX_FIFO需要根据实际应用场景预先填入数据,否则当主机发起通讯时,TX_FIFO为空会导致TX_FIFO的下溢。

  6. 使用SPI中断时,除了配置SPI_IE寄存器,还需要配置CLIC和使能全局中断。如果SPI在使用过程中,会动态开启和关闭SPI内部中断,则在SPI中断函数中,需要同时判断SPI_IESPI_PD,确认中断使能且相应Pending bit置位,再去执行对应的处理逻辑。处理完对应的中断后,软件需要显式的对SPI_PD中对应的Pending bit写1,以清除其Pending状态。

  7. 写入TX_FIFO中的数据需要一定的时间才能从TX引脚发送完成(通过CPU写入和DMA写入都如此)。当SPI_STA.SPI_IDLE为1,且SPI_STA.TX_EMPTY为1时,就可以确定写入的数据已全部传输完成。

  8. 当SPI处于主机模式时,使能SPI模块时钟后,需要等待2个SCK的周期,以确保SPI时钟稳定后再开始数据收发。


API说明

SPI API提供标准SPI主机的功能。

void spi_init(uint32_t chx, uint32_t baud)

SPI初始化。

参数
  • chx – SPI通道,0~1。

  • baud – SPI波特率(SPI SCK信号的频率)。

返回

void spi_deinit(uint32_t chx)

SPI去初始化。

参数
  • chx – SPI通道,0~1。

返回

void spi_reset_rx_fifo(uint32_t chx)

SPI 复位接收FIFO。

参数
  • chx – SPI通道,0~1。

返回

void spi_reset_tx_fifo(uint32_t chx)

SPI 复位发送FIFO。

参数
  • chx – SPI通道,0~1。

返回

void spi_nss_ctrl(uint32_t chx, soc_enable_t en)

SPI NSS信号控制。

参数
  • chx – SPI通道,0~1。

  • en – Enable,NSS有效(低电平);Disable,NSS无效(高电平)。

返回

uint8_t spi_transfer(uint32_t chx, uint8_t dat)

SPI传输1个字节。

参数
  • chx – SPI通道,0~1。

  • dat – 要发送的数据。

返回

接收到的数据

返回类型

uint8_t

void spi_bulk_transfer(uint32_t chx, uint8_t *out, uint8_t *in, uint32_t len)

SPI批量传输。

参数
  • chx – SPI通道,0~1。

  • out – 指向要发送的数据。

  • in – 指向容纳接收数据的缓冲区。

  • len – 数据长度。

返回

void spi_bulk_write(uint32_t chx, uint8_t *buf, uint32_t len)

SPI批量写数据。

参数
  • chx – SPI通道,0~1。

  • buf – 指向要发送的数据。

  • len – 数据长度。

返回

void spi_bulk_read(uint32_t chx, uint8_t *buf, uint32_t len)

SPI批量读数据。

参数
  • chx – SPI通道,0~1。

  • buf – 指向数据缓冲区。

  • len – 要读取的数据长度。

返回

API使用示例

  1. board.h 中设置 __USE_SPI0_PIN_CFG__USE_SPI1_PIN_CFG 为1,表示需要启用SPI0或SPI1引脚配置。

#define       __USE_SPI0                      (1)
  1. board.h 中设置并定义对应UART需要用到的引脚以及引脚对应的MFP值。

#define       SPI0_SCK_PIN            (GPIO_Pin_00)
#define       SPI0_SCK_MFP            (1)
#define       SPI0_NSS_PIN            (GPIO_Pin_01)
#define       SPI0_NSS_MFP            (1)
#define       SPI0_MOSI_PIN           (GPIO_Pin_02)
#define       SPI0_MOSI_MFP           (1)
#define       SPI0_MISO_PIN           (GPIO_Pin_03)
#define       SPI0_MISO_MFP           (1)

备注

  • 如果 __USE_SPI0_PIN_CFG__USE_SPI1_PIN_CFG 为0,则需要用户手动配置SPI引脚。

  1. 调用 spi_init() 初始化对应的SPI。

该函数会初始化SPI用到的引脚(如果 __USE_SPI0_PIN_CFG__USE_SPI1_PIN_CFG 为1),选择OSC_AUDIO(49.152MHz)时钟作为SPI的时钟源,根据目标波特率设置时钟分频值(1~256)。

spi_init(0, 1000000);
  1. 调用 spi_nss_ctrl() 控制NSS信号

通常在数据收发前,需要拉低NSS信号,在数据收发完成后,再拉高NSS信号。

spi_nss_ctrl(0, Enable);
spi_nss_ctrl(0, Disable);
  1. 调用 spi_transfer()spi_bulk_transfer() 进行数据双向收发,调用 spi_bulk_write() 进行数据单向发送,调用 spi_bulk_read() 进行数据单向读取。

这些函数进行SPI数据传输时,都是阻塞进行的。

recv = spi_transfer(0, 0xab);
spi_bulk_transfer(0, tx_buf, rx_buf, 16);
spi_bulk_write(0, tx_buf, 16);
spi_bulk_read(0, rx_buf, 16);
  1. 如果不需要再使用SPI,调用 spi_deinit() 去初始化。

去初始化会关闭SPI的时钟,并让模块处于复位状态。相应的引脚复用也会被清除(如果 __USE_SPI0_PIN_CFG__USE_SPI1_PIN_CFG 为1)。


寄存器定义

../../_images/kiwi-reg-spi-ctl-1.png ../../_images/kiwi-reg-spi-ctl-2.png
../../_images/kiwi-reg-spi-sta.png
../../_images/kiwi-reg-spi-rx-num.png
../../_images/kiwi-reg-spi-rx-cnt.png
../../_images/kiwi-reg-spi-tx-data.png
../../_images/kiwi-reg-spi-rx-data.png
../../_images/kiwi-reg-spi-pd.png
../../_images/kiwi-reg-spi-ie.png