SPI接口是一种同步串行总线(Serial Peripheral Interface)。
四线SPI接口连线图:
CS为片选脚,用于选中从机。
SCLK为时钟脚,用于数据传输时提供时钟信号。
MOSI为主output,从input,即主机发送脚。对应从机的引脚为SDI。
MISO为主input,从output,机主机接收脚。对应从机的引脚为SDO。
上述SPI为标准SPI协议(Standard SPI)或单线SPI协议(Single SPI),其中的单线是指该SPI协议中使用单根数据线 MOSI 进行发送数据,单根数据线 MISO 进行接收数据。为了适应更高速率的通讯需求,半导体厂商扩展SPI协议,主要发展出了 Dual/Quad/Octal SPI协议,加上标准SPI协议(Single SPI),这四种协议的主要区别是数据线的数量及通讯方式,见下表:
除了上述接法,SPI还支持半双工1bit模式:
SCLK:时钟线。
I/O:数据线,同一时刻要么主机发送,要么主机接收。
SS:片选脚。
Dual SPI的2bit模式:
由于是半双工,同一时刻要么主机使用MOSI、MISO线,要么从机使用MOSI、MISO线。
Quad SPI(4线)模式
与Dual SPI类似,也是针对SPI Flash,也是半双工,Quad SPI Flash增加了两根I/O线(SIO2,SIO3),目的是SCLK一次触发传输4bit数据。
以AC63芯片SPI接口为例,进行代码分析:
根据SPI接口的时序要求,SPI接口可以通过软件实现,也可以通过硬件实现。这里仅分析硬件实现方式。
查看数据手册可知,芯片最多支持三个SPI接口,分别是SPI0、SPI1、SPI2。SPI接口支持DMA发送、接收功能。
每个SPI接口可以映射到多个不同的引脚,分别为不同的组,即组A、组B、组C、组D。
SPI硬件包含控制寄存器、波特率寄存器、buf缓冲区寄存器、DMA地址寄存器、DMA计数寄存器。
寄存器结构体定义如下:
typedef struct {
__RW __u32 CON;
__WO __u32 BAUD;
__RW __u32 BUF;
__WO __u32 ADR;
__WO __u32 CNT;
} JL_SPI_TypeDef;
每个SPI接口寄存器基地址:
#define JL_SPI0_BASE (ls_base + map_adr(0x1c, 0x00))
#define JL_SPI0 ((JL_SPI_TypeDef *)JL_SPI0_BASE)
#define JL_SPI1_BASE (ls_base + map_adr(0x1d, 0x00))
#define JL_SPI1 ((JL_SPI_TypeDef *)JL_SPI1_BASE)
#define JL_SPI2_BASE (ls_base + map_adr(0x1e, 0x00))
#define JL_SPI2 ((JL_SPI_TypeDef *)JL_SPI2_BASE)
现将三个SPI寄存器首地址定义在数组中:
static JL_SPI_TypeDef *const spi_regs[SPI_MAX_HW_NUM] = {
JL_SPI0,
JL_SPI1,
JL_SPI2,
};
通过SPI的编号就可以进行对应SPI寄存器的访问操作。
SPI编号的定义:
enum {
SPI0, //0
SPI1,
SPI2,
SPI_MAX_HW_NUM, //3
};
SPI引脚分组说明:
SPI0:A、B、C、D组,支持1bit、2bit、4bit位宽
SPI1:A、B组,支持1bit位宽
SPI2:A、B组,支持1bit位宽
可以看出,SPI接口最多支持4组映射。
#define SPI_MAX_IO_GROUP 4
SPI IO引脚结构体:
struct spi_io {
u8 cs_pin;//片选
u8 di_pin;//输入
u8 do_pin;//输出
u8 clk_pin;//时钟
u8 d2_pin;//bit2
u8 d3_pin;//bit3
};
每个SPI支持的组数、组里面IO的定义,构成下面的结构体:
struct spi_io_mapping {
u32 num;//组数
struct spi_io io[SPI_MAX_IO_GROUP];//所有组的IO定义
};
SPI IO映射表:
static const struct spi_io_mapping spi_io_map[SPI_MAX_HW_NUM] = { //SPI_MAX_HW_NUM = 3
//SPI0
{
.num = 4,//SPI0有4组
//port A
.io[0] = {
.cs_pin = IO_PORTD_03,
.di_pin = IO_PORTD_02,
.do_pin = IO_PORTD_01,
.clk_pin = IO_PORTD_00,
.d2_pin = IO_PORTB_07,
.d3_pin = IO_PORTD_05,
},
//port B
.io[1] = {
.cs_pin = IO_PORTA_13,
.di_pin = IO_PORTA_14,
.do_pin = IO_PORTD_01,
.clk_pin = IO_PORTD_00,
.d2_pin = IO_PORTA_15,
.d3_pin = IO_PORTD_05,
},
//port C
.io[2] = {
.cs_pin = -1,//IO_PORTA_04,
.di_pin = IO_PORTA_06,
.do_pin = IO_PORTA_08,
.clk_pin = IO_PORTA_03,
.d2_pin = IO_PORTA_07,
.d3_pin = IO_PORTA_02,
},
//port D
.io[3] = {
.cs_pin = -1,//IO_PORTB_06,
.di_pin = IO_PORTB_08,
.do_pin = IO_PORTB_10,
.clk_pin = IO_PORTB_05,
.d2_pin = IO_PORTB_09,
.d3_pin = IO_PORTB_04,
}
},
//SPI1
{
.num = 2,//SPI1有2组
//port A
.io[0] = {
.cs_pin = -1,
.di_pin = IO_PORTB_02,
.do_pin = IO_PORTB_01,
.clk_pin = IO_PORTB_00,
.d2_pin = -1,
.d3_pin = -1,
},
//port B
.io[1] = {
.cs_pin = -1,
.di_pin = IO_PORTC_03,
.do_pin = IO_PORTC_05,
.clk_pin = IO_PORTC_04,
.d2_pin = -1,
.d3_pin = -1,
}
},
//SPI2
{
.num = 2,//SPI2有2组
//port A
.io[0] = {
.cs_pin = -1,
.di_pin = IO_PORTB_08,
.do_pin = IO_PORTB_10,
.clk_pin = IO_PORTB_09,
.d2_pin = -1,
.d3_pin = -1,
},
//port B
.io[1] = {
.cs_pin = -1,
.di_pin = IO_PORTA_13,
.do_pin = IO_PORT_DM,
.clk_pin = IO_PORT_DP,
.d2_pin = -1,
.d3_pin = -1,
}
},
};
SPI的选用需要按照上述各组IO的分配情况进行使用。