SPI

1. SPI驱动说明

1.1 简介

SPI(Serial Peripheral Interface,串行外设接口)是一种高速、全双工、同步通信总线.
思澈平台SPI总线特性主要包括:

  • 支持4到32Bit的数据位宽;

  • HPSYS 中的 SPI 最大时钟频率为 48MHz,LPSYS 中的 SPI 最大时钟频率为 8MHz;

  • 发送和接收都支持(polling)/中断(INT)/DMA三种模式;

  • 支持3线SPI或4线SPI;

  • 支持主模式和从模式;

  • 支持半双工和全双工通讯;

  • 支持配置时钟极性相位;

  • 支持配置数据大端和小端;

  • 支持SPI/SSP/Microwire;

SPI 总线的工作过程,可以参考 SPI 总线设备

1.2 SPI总线 资源分布

思澈平台最多支持 4 个SPI实例,其中:2个在HCPU上,2个在 LCPU;
具体分布如下:

平台

HCPU

LCPU

52x

SPI1/SPI2

56x

SPI1/SPI2

SPI3/SPI4

55x

SPI1/SPI2

SPI3/SPI4

58x

SPI1/SPI2

SPI3/SPI4

SPI总线名称依次为“spi1”~“spi4”;
SPI总线的配置信息还可以参考spi_config.h,位于:solution2.0\sdk\customer\boards\include\config 下 sf32lb52x、sf32lb56x、sf32lb55x、sf32lb58x等目录下;

备注

HCPU可以使用LCPU的SPI,但LCPU只能使用LCPU的SPI

1.3 SPI 三种工作模式

SPI总线支持询(polling)/中断(INT)/DMA三种工作模式;

模式

CPU占用率

代码复杂度

适用场景

轮询

高,CPU持续参与,需不断检查状态,占用CPU资源

低速、小数据量、简单应用;

中断

中,CPU无需持续查询状态,仅在事件发生时响应中断,减少CPU等待时间

中等数据量、实时性要求较高;

DMA

低,DMA控制器直接管理数据传输,CPU无需参与数据搬运

高速、大数据量、高吞吐量场景;

备注

DMA模式,仅作用于数据段,适合高吞吐,低CPU占有率,通讯过程中不需要CPU参与,被CPU打断的概率低;

1.4 SPI 功能配置

1.4.1 SPI 功能配置说明

SPI总线支持的功能都可以配置。
配置功能通过结构体struct rt_spi_configuration实现。

struct rt_spi_configuration
{
    rt_uint8_t mode;        //配置相位和极性,主模式或从模式,数据大端或小端;
    rt_uint8_t data_width;  //配置数据位宽
    rt_uint16_t frameMode;  //配置SPI总线类型,cs有效电平,3线或4线,SPI ready, SPI
    rt_uint32_t max_hz;     //SPI总线时钟
};

配置详细说明:
mode
用途:配置相位和极性,主模式或从模式;
其中:
bit0 相位CPHA, 0-在首个时钟变化沿采样数据, 1-在第二个时钟变化沿采样数;
bit1 极性CPOL, 0-时钟信号初始状态为低电平, 1-时钟信号的初始电平是高电平;
CPHA 和 CPOL组合对应SPI的4种工作时序模式
如下图:

../../_images/spi_cpha_cpol.png

宏定义:
RT_SPI_MODE_0 CPHA:0,CPOL:0;
RT_SPI_MODE_1 CPHA:1,CPOL1:0;
RT_SPI_MODE_2 CPHA:0,CPOL:1;
RT_SPI_MODE_3 CPHA:1,CPOL:1;
bit2 数据大小端, 1-大端MSG, 0-小端LSB;
宏定义:
RT_SPI_LSB 小端LSB
RT_SPI_MSB 大端MSB
bit3 主从模式,其中:0-主模式master, 1-从模式Slave;
宏定义:
RT_SPI_MASTER 主模式
RT_SPI_MASTER 从模式
mdata_width
用途:配置数据位宽
其中:驱动支持的位宽包括8,16,32;
frameMode
用途:配置SPI总线类型,3线或4线;
其中:
bit6 SI/SO pin shared 即三线模式 其中:0-四线模式,1-三线模式;
宏定义:
RT_SPI_3WIRE
bit8~bit9 总线类型,支持:SPI/SSP/Microwire; 其中:0-SPI, 1-SSP, 2-Microwire;
宏定义:
RT_SPI_MOTO 对应SPI
RT_SPI_TI 对应SSP
RT_SPI_NM 对应Microwire

其他bit为强制默认配置,用户可以不用关注

1.4.2 SPI 功能配置示例

假设SPI 需求:spi master 4线 SPI_MODE_0 8bit数据位宽 时钟8M;
配置如下:

rt_spi_configuration spi_cfg;
spi_cfg.mode = RT_SPI_MODE_0 | RT_SPI_MSB | RT_SPI_MASTER; //master 大端 SPI_MODE_0
spi_cfg.data_width = 8; //数据位宽 8
spi_cfg.frameMode =  RT_SPI_MOTO; // SPI
spi_cfg.max_hz = 8000000;  // 时钟8M

1.5 SPI 总线硬件说明

SPI 4线 和3线 pin定义对比

SPI

时钟

芯片选择

通讯pin

4线

SPIx_CLK

SPIx_CS

SPIx_DIO/SPIx_DI

3线

SPIx_CLK

SPIx_CS

SPIx_DIO

3线SPI时,发送/接收会复用SPIx_DIO;
4线SPI时,发送使用SPIx_DIO,接收使用SPIx_DI;

1.6 SPI 总线接口

SPI打开、配置和通讯,相关接口如下

函数

描述

rt_device_find()

根据SPI总线设备名称查找设备获取设备句柄

rt_device_open()

打开SPI总线或设备

rt_device_close()

关闭SPI总线或设备

rt_spi_bus_attach_device()

挂载SPI设备到SPI总线

rt_spi_configure()

配置SPI设备

rt_spi_take_bus()

获取SPI总线控制权

rt_spi_release_bus()

释放SPI总线控制权

rt_spi_take()

手动配置CS

rt_spi_release()

手动释放CS

rt_spi_transfer_message()

自定义传输数据

rt_spi_transfer()

传输一次数据

rt_spi_send()

发送一次数据

rt_spi_recv()

接受一次数据

rt_spi_send_then_send()

连续两次发送

rt_spi_send_then_recv()

先发送后接收

1.6.1 几种通讯接口的应用场景

函数

特点

使用场景

rt_spi_transfer_message()

自定义传输message,应用最灵活,但配置相对麻烦

复杂或特殊的传输时序

rt_spi_transfer()

只需自定义传输buff,支持半双工和全双工

通用的传输接口

rt_spi_transfer()/rt_spi_recv()

半双工接口,单独发送或接收

半双工通用接口

rt_spi_send_then_send()

半双工接口,一般用于写命令,先发命令或地址,再发要写入的参数

寄存器写

rt_spi_send_then_recv()

半双工接口,一般用于读命令,先发命令或地址,再读取参数

寄存器读

1.6.2 CS的控制

SPI驱动默认都是硬件自动控制CS。
如果需要手动配置CS,需要rt_spi_transfer_message()配合rt_spi_take()/rt_spi_release()来实现; 应用示例:

rt_spi_take()       //cs 拉低
struct rt_spi_message message;
/*cs_take和cs_release都为0时,关闭硬件自动控制cs*/
message.cs_take = 0;    
message.cs_release = 0;  
//...
rt_spi_transfer_message();
rt_spi_release()    //cs 拉高

1.6.3 SPI跨线程调用

SPI跨线程调用的时候,需要调用rt_spi_take_bus()获取SPI总线的使用权;
使用完毕后,必须调用调用rt_spi_release_bus()释放SPI总线的使用权;

1.6.4 SPI总线使用流程

../../_images/spi_flow_chart.png

1.7 SPI的使用

SPI的使用需经过配置选型、硬件绑定、驱动调用三个核心步骤,以下为详细说明:

1.7.2 配置 Pinmux(绑定 IO 与 SPI总线)

Pinmux 配置的核心作用是建立物理 IO 引脚与目标SPI总线的硬件映射关系,告知芯片“哪个 IO 负责SPI通讯”。只有完成绑定,SPI总线才能和SPI设备进行通讯。

  • 以4线spi配置示例
    PA28 作为 SPI1_CLK(SPI1总线的时钟线);
    PA29 作为 SPI1_CS(SPI1总线的芯片选择); PA24 作为 SPI1_DIO(SPI1总线的输出);
    PA25 作为 SPI1_DI(SPI1总线的输入);

配置代码如下:

// 函数:HAL_PIN_Set(引脚标识, 功能别名, 上下拉配置, 核心选择)
HAL_PIN_Set(
    PAD_PA28,       // 物理 IO 引脚 PA28
    SPI1_CLK,       // SPI1总线的时钟线
    PIN_NOPULL,     // 上下拉配置(SPI master 作为输出, 配置NOPULL)
    1               // 1:引脚位于HCPU;0:引脚位于LCPU
);
HAL_PIN_Set(
    PAD_PA29,       // 物理 IO 引脚 PA29
    SPI1_CS,       // SPI1总线的芯片选择
    PIN_NOPULL,     // 上下拉配置(SPI master 作为输出, 配置NOPULL)
    1               // 1:引脚位于HCPU;0:引脚位于LCPU
);
HAL_PIN_Set(
    PAD_PA24,       // 物理 IO 引脚 PA24
    SPI1_DIO,       // SPI1总线的数据输出线
    PIN_NOPULL,     // 上下拉配置(SPI master 作为数据输出, 配置NOPULL)
    1               // 1:引脚位于HCPU;0:引脚位于LCPU
);
HAL_PIN_Set(
    PAD_PA25,       // 物理 IO 引脚 PA25
    SPI1_DI,       // SPI1总线的数据输入线
    PIN_PULLUP,     // 上下拉配置(SPI master 作为数据输入, 配置PULLUP)
    1               // 1:引脚位于HCPU;0:引脚位于LCPU
);

备注

3线SPI 数据线只使用SPIx_DIO,不会用到SPIx_DI;

1.7.3 应用SPI总线

以“TF卡使用SPI1总线”为例,代码参考:\sdk\rtos\rtthread\components\drivers\spi\spi_msd.c

/*绑定sdcard 设备到spi1总线*/
rt_hw_spi_device_attach("spi1", "sdcard");
/*查找spi设备"sdcard"*/
rt_device_t spi_dev = rt_device_find("sdcard");
/*打开spi设备"sdcard"*/
rt_device_open(spi_dev, RT_DEVICE_FLAG_RDWR);

/*配置spi设备"sdcard"*/
struct rt_spi_configuration cfg;
cfg.data_width = 8;
cfg.mode = RT_SPI_MASTER | RT_SPI_MODE_3 | RT_SPI_MSB; /* SPI Compatible Mode 3 */
cfg.max_hz = 400 * 1000; /* 400K/s */
cfg.frameMode = RT_SPI_MOTO;
rt_spi_configure(msd->spi_device, &cfg);

/*spi设备"sdcard"通讯*/
rt_spi_take();//获取SPI总线
rt_spi_transfer_message();//传输
rt_spi_release();//释放SPI总线

1.8 SPI 中断模式的配置

SPI 中断模式,可以在rt_device_open(bus_handle, flag_open);时,通过flag_open来区分;
轮询模式
flag_open = RT_DEVICE_FLAG_RDWR
中断模式
flag_open = RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX | RT_DEVICE_FLAG_INT_TX
以下显示所有支持的flag

#define RT_DEVICE_FLAG_RDONLY           0x001           /**< read only */
#define RT_DEVICE_FLAG_WRONLY           0x002           /**< write only */
#define RT_DEVICE_FLAG_RDWR             0x003           /**< read and write */
#define RT_DEVICE_FLAG_INT_RX           0x100           /**< INT mode on Rx */
#define RT_DEVICE_FLAG_DMA_RX           0x200           /**< DMA mode on Rx */
#define RT_DEVICE_FLAG_INT_TX           0x400           /**< INT mode on Tx */
#define RT_DEVICE_FLAG_DMA_TX           0x800           /**< DMA mode on Tx */

1.9 SPI DMA模式的配置

DMA模式配置包括menuconfig使能SPI总线DMA,配置dma_config,SPI总线flag_open包含RT_DEVICE_FLAG_DMA_TX /RT_DEVICE_FLAG_DMA_RX;

1.9.2 配置dma_config

在dma_config.c中,配置SPI相关的宏, SPI TX和SPI RX会占用不同的DMA通道。

#define SPI1_DMA_RX_IRQHandler         DMAC1_CH3_IRQHandler     /*DMA中断回调*/
#define SPI1_RX_DMA_IRQ_PRIO           0                        /*DMA中断优先级*/
#define SPI1_RX_DMA_INSTANCE           DMA1_Channel3            /*DMA中断通道实例*/
#define SPI1_RX_DMA_IRQ                DMAC1_CH3_IRQn           /*DMA中断*/
#define SPI1_RX_DMA_REQUEST            DMA_REQUEST_29           /*SPI1_RX的DMA设备序号,序号唯一*/

#define SPI1_DMA_TX_IRQHandler         DMAC1_CH4_IRQHandler     /*DMA中断回调*/
#define SPI1_TX_DMA_IRQ_PRIO           0                        /*DMA中断优先级*/
#define SPI1_TX_DMA_INSTANCE           DMA1_Channel4            /*DMA中断通道实例*/
#define SPI1_TX_DMA_IRQ                DMAC1_CH4_IRQn           /*DMA中断*/
#define SPI1_TX_DMA_REQUEST            DMA_REQUEST_28           /*SPI1_TX的DMA设备序号,序号唯一*/

下图展示了所有平台的SPI总线的DMA_REQUEST(悬空部分表格表示对应平台没有对应的SPI总线或SPI总线不支持DMA)

SPI总线

52x

55x

56x

58x

SPI1_TX

DMAC1_28

DMAC1_28

DMAC1_28

DMAC1_28

SPI1_RX

DMAC1_29

DMAC1_29

DMAC1_29

DMAC1_29

SPI2_TX

DMAC1_30

DMAC1_30

DMAC1_30

DMAC1_30

SPI2_RX

DMAC1_31

DMAC1_31

DMAC1_31

DMAC1_31

SPI3_TX

DMAC2_16

DMAC2_16

DMAC3_16

SPI3_RX

DMAC2_17

DMAC2_17

DMAC3_17

SPI4_TX

DMAC2_18

DMAC2_18

DMAC3_18

SPI4_RX

DMAC2_19

DMAC2_19

DMAC3_19

1.9.3 配置dma_config SPI 总线flag_open包含RT_DEVICE_FLAG_DMA_TX /RT_DEVICE_FLAG_DMA_RX

参考代码如下:

rt_device_open(spi_dev,  RT_DEVICE_FLAG_DMA_RX | RT_DEVICE_FLAG_DMA_TX | RT_DEVICE_FLAG_RDWR);

1.9.4 SPI DMA通讯的buff要求

SPI DMA 通讯一般需要把读写buff放到sram,以满足通讯效率。
buff放到sram的两种方法:

  1. 通过声明的方式的让buff变量处于sram;
    例如:

RETM_BSS_SECT_BEGIN(spi_buf)
static uint8_t sram_buff[12] RETM_BSS_SECT(sram_buff);
RETM_BSS_SECT_END
  1. 从heap申请sram内存;
    例如:

void * buff = app_sram_alloc(12);