嵌入式SPI总线

  这篇详细介绍嵌入式的SPI总线,方便以后写该总线的器件驱动。

一、SPI总线讲解

  SPI的主要构成有三根线,一个是数据输出线MOSI,一个是数据输入线MISO,一个是时钟线SCLK。

  SPI总线实现的方式分为两种:硬件IIC & 软件模拟SPI。硬件SPI有主从之分;当然,软件SPI也是标准的SPI协议,当然有分主从,但一般情况下,(MCU)软件SPI为主机模式,即发送请求接收从机的响应信息。

  可能大家不知道为什么我这MCU就是SPI主机,凭啥?你可以反过来想一下,MCU通讯的其他器件实质上都是从机,MCU要你的数据才会去找你;如果你MCU也是从机模式,从机与从机怎么通信?由于每种MCU的硬件SPI总线配置各不相同,故接下来主要讲 软件SPI。

  • SPI通信是串行同步全双工(同时收发)
  • IIC通信是串行同步半双工(单收或单发)
  • 无论是 硬件SPI 还是 软件SPI ,两种方式只是提供最基础的桥梁——提供了读、写1字节方式。如何调用IIC从器件,还是得查对应SPI从器件的datasheet。SPI总线好比中文的拼音,具体要怎么说话、说什么话,还是得看datasheet。

Ps:硬件SPI总线 & 软件SPI总线 速率方面是有点差距的。具体没有实测,但是既然是用了软件SPI总线,就不要纠结速度问题了。但是硬件SPI总线的速度也不是无上限的,而且还要根据主、从机的配置。


  以W5500为例,该芯片的SPI总线速率最高达到80Mhz。如果主控采用stm32f103,主频为72Mhz,SPI时钟最大为18Mhz。因此stm32f103无法发挥W5500完全的性能。

  而如果使用STM40x处理器,SPI时钟使用42Mhz,使用DMA方式,可以达到回环测试17Mbps的速率(收+发),单独发送可以达到13Mbps的速率。

  也就是说,SPI的速率本身就受器件限制。如果从机SPI时钟较低,主机被限制;主机时钟较低,从机性能被限制。编写硬件SPI驱动的时候,要多注意 主控芯片、从机器件 的datasheet。

二、(主机)软件SPI总线

  硬件上为4根线:

  • MISO :主设备数据输入,从设备数据输出。
  • MOSI :主设备数据输出,从设备数据输入。
  • SCLK :时钟信号,由主设备产生。
  • CS :从设备片选信号,由主设备控制。

Ps:外设的写操作和读操作是同步完成的。如果只进行写操作,主机只需忽略接收到的字节;反之,若主机要读取从机的一个字节,就必须发送一个空字节来引发从机的传输。

  由上图的(硬件)SPI通信协议可知,时钟信号的相位和极性,决定了获取数据的方式;2*2=4,于是有四种模式;

CPOL决定时钟空闲状态(1为空闲高电平,0为空闲低电平)

CPHA决定获取数据的时钟相位(1为延迟一个相位,第二次SCLK突变为空闲状态获取数据;0为直接相位,SCLK空闲状态突变时,直接获取数据)

  以上就是软件SPI的要点;实现具体的软件SPI,需要对器件的datasheet进行查阅,看是哪种模式。

模式 CPOL CPHA
MODE0 0 0
MODE1 0 1
MODE2 1 0
MODE3 1 1

下面简单列一个 CPOL=1,CPHA=1的SPI写操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/************************************************** 
函数:SOFT_SPI_RW()
描述: 根据SPI协议,写一字节数据到从机,同时从从机 读出一字节
**************************************************/
u8 SOFT_SPI_RW(u8 byte)
{
u8 i,Temp=0;
for(i=0;i<8;i++) // 循环8次
{
SPI1_SCK = 0 //拉低时钟
if(byte&0x80)
SPI1_MOSI = 1;//若最到位为高,则输出高
else
SPI1_MOSI = 0;//若最到位为低,则输出低
byte <<= 1; //低一位移位到最高位
SPI1_SCK = 1; //拉高时钟
Temp <<= 1; //数据左移
if(SPI1_MISO)
Temp++; //若从从机接收到高电平,数据自加一
SPI1_SCK = 0; //拉低时钟
}
return (Temp); //返回数据
}

2.1 FRAM_FM25L256为例——软件SPI

  FM25L256支持SPI模式的0和3,下面为CPOL=1,CPHA=1。基本的字节读写操作如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/************************************************** 
* 函数名 : FRAM_Write8bits
* 功 能 : 向存储器中写入一个字节
**************************************************/
static void FRAM_Write8bits(uint8_t u8Data)
{
uint8_t i;

for(i = 0; i < 8; i++)
{
FRAM_SCLK_L;
if(u8Data & 0x80)
FRAM_SDI_H;
else
FRAM_SDI_L;
DelayTime(SPI_DELAY_TIME);
FRAM_SCLK_H;
u8Data <<= 1;
DelayTime(SPI_DELAY_TIME);
}
FRAM_SCLK_L;
}

/**************************************************
* 函数名 : FRAM_Read8bits
* 功 能 : 从存储器中读出一个字节的数据
**************************************************/
static uint8_t FRAM_Read8bits(void)
{
uint8_t i;
uint8_t u8Data=0;

for(i = 0; i < 8; i++)
{
FRAM_SCLK_L;
u8Data <<= 1;
DelayTime(SPI_DELAY_TIME);
FRAM_SCLK_H;
if(FRAM_SDO_READ)
u8Data |= 0x01;
DelayTime(SPI_DELAY_TIME);
}
FRAM_SCLK_L;
return u8Data;
}

2.2 FRAM_FM25L256程序扩展——datasheet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
/************************************************** 
* 函数名 : FRAM_Write16bits
* 功 能 : 发送16位数据
**************************************************/
static void FRAM_Write16bits(uint16_t u16Data)
{
uint32_t i;

for(i = 0; i < 16; i++)
{
FRAM_SCLK_L;
if(u16Data & 0x8000)
FRAM_SDI_H;
else
FRAM_SDI_L;
DelayTime(SPI_DELAY_TIME);
FRAM_SCLK_H;
u16Data <<= 1;
DelayTime(SPI_DELAY_TIME);
}
FRAM_SCLK_L;
}
/**************************************************
* 函数名 : FRAM_Write32bits
* 返 回 : 写入的32位数据
**************************************************/
static void FRAM_Write32bits(uint32_t u32Data)
{
uint32_t i;

for(i = 0; i < 32; i++)
{
FRAM_SCLK_L;
if(u32Data & 0x80000000)
FRAM_SDI_H;
else
FRAM_SDI_L;
DelayTime(SPI_DELAY_TIME);
FRAM_SCLK_H;
u32Data <<= 1;
DelayTime(SPI_DELAY_TIME);
}
FRAM_SCLK_L;
}

/**************************************************
* 函数名 : FRAM_Read4Bytes
* 功 能 : 读取指定地址的4字节数据
* 入 参 : address : 读取数据的首地址
* 返 回 : uint32_t : 读出的32位数据
**************************************************/
uint32_t FRAM_Read4Bytes(uint32_t address)
{
uint32_t i = 0;
uint32_t temp = 0;

FRAM_SCLK_L;
FRAM_CS_L;
FRAM_Write8bits(FRAM_CMD_READ);
FRAM_Write16bits((uint16_t)(address));
for(i = 0; i < 4; i++)
{
temp = temp << 8;
temp |= FRAM_Read8bits();
}
FRAM_SCLK_L;
FRAM_CS_H;

return temp;
}

/**************************************************
* 函数名 : FRAM_Write4Bytes
* 功 能 : 写入指定地址的4字节数据
* 入 参 : address : 写入数据的首地址
* FramData : 写入的32位数据
**************************************************/
void FRAM_Write4Bytes(uint32_t address, uint32_t FramData)
{
uint8_t u8Temp = 0;

FRAM_SCLK_L;
FRAM_CS_L;
FRAM_Write8bits(FRAM_CMD_WREN);
FRAM_SCLK_L;
FRAM_CS_H;

FRAM_SCLK_L;
FRAM_CS_L;
FRAM_Write8bits(FRAM_CMD_WRITE);
FRAM_Write16bits((uint16_t)(address));
FRAM_Write32bits(FramData);
FRAM_SCLK_L;
FRAM_CS_H;

while(1)
{
FRAM_SCLK_L;
FRAM_CS_L;
FRAM_Write8bits(FRAM_CMD_RDSR); //读取状态寄存器
u8Temp = FRAM_Read8bits();
FRAM_SCLK_L;
FRAM_CS_H;

//该位表示写入启用状态(为0时,WPEN和 /WP 寄存器标志位不起作用)
//防止软件误改动铁电芯片寄存器配置
if(!(u8Temp & 0x02))
{
break;
}
}
}
-------------本文结束感谢您的阅读-------------