HALF DUPLEX SPI

SPI Introdution

SPI, Serial Peripheral Interface, 即串行设备接口,微控制器和外围IC(如传感器、ADC、DAC、移位寄存器、SRAM等)之间使用最广泛的接口之一。

SPI是一种同步的主从式接口,支持全部通信制式(单工、半双工、全双工)。与I2C的 多主多从 模式不同,SPI协议为 一主多从 模式。SPI所消耗的接口数也有所提升。

接线

SPI 的物理线支持 三种类型

  • 片选线(NCS / CS / SS),某些数据手册会将 片选引脚 称为 NSS /NCS / SEL (Select) / CS (Chip Select) / STE (Slaver Transmit Enable)
  • 时钟线(CLK / SCK)
  • 数据线(DA 或 MOSI 和 MISO )

需要注意的是,SPI的通信线有太多的名称,需要自行对照芯片手册进行查询,避免混淆。下方为标准全双工四线SPI的通信接线示意图

image-20210819075248043

SPI 通过对硬件资源的使用修改(禁用片选、禁 / 复用数据线)可以达到 全双工半双工 通信的目的。标准SPI为 四线SPI (CS / CLK / MOSI / MISO);最低支持2根数据引脚(仅保留 CLK / DA,其中DA为分时复用数据线 )来达到半双工通信的目的,可以增加一 / 多根 CS 来达到拓展SPI从设备片选的目的;也可以利用四根独立的半双工数据线,来达到 标准四线SPI 四倍通信能力 的 六线SPI(一般被称为 Quad-IO SPI)。

分类

各芯片厂商对SPI的通信协议好像并没有一个较为正式的名称,但都可以从通信制式的类型进行区分,并不能仅从 x线SPI 的数量来判断SPI通信协议的类型。

较为推荐的称呼方式是:线数 + 有无片选 +通信制式。比如:

  • 两线无片选半双工SPI,即 CLK 和 SDIO 两线,SDIO 支持分时双工。
  • 三线无片选半双工SPI,即 CLK 和 两根 SDIO,两根SDIO 都是分时双工。
  • 三线有片选半双工SPI,即 CLK 、SDIO 和 CS,SDIO 支持分时双工。
  • 三线无片选全双工SPI,即 CLK、MOSI 和 MISO。
  • 四线有片选全双工SPI,即 CLK、MOSI、MISO 和 CS。
  • ......

例如,据STM32L476RG芯片手册《RM0351》第1450页所示,SPI外设可以被配置为以下三种模式,即 三线全双工两线半双工两线单工

  • Full-duplex synchronous transfers on three lines
  • Half-duplex synchronous transfer on two lines (with bidirectional data line)
  • Simplex synchronous transfers on two lines (with unidirectional data line)

SDR 和 DDR 模式

扩展的 SPI 协议还增加了 SDR 模式(单倍速率 Single Data Rate)和 DDR 模式(双倍 速率 Double Data Rate)。例如在标准 SPI 协议的 SDR 模式下,只在 SCK 的单边沿进行数据传输,即一个 SCK 时钟只传输一位数据;而在它的 DDR 模式下,会在 SCK 的上升沿和下降沿都进行数据传输,即一个 SCK 时钟能传输两位数据,传输速率提高一倍。

数据传输

下以四线有片选全双工SPI为例进行说明。

要开始SPI通信,主机必须发送时钟信号,并通过使能CS信号选择从机。

片选 通常 是 低电平有效(active-low) 信号。

主机须在 CS 信号上发送逻辑 0 以选择从机。主机和从机可以分别通过 MOSI 和 MISO 引脚同时发送数据。在SPI通信期间,数据的发送(串行移出到MOSI/SDO总线上)和接收(采样或读入总线(MISO/SDI)上的数据)同时进行。串行时钟沿同步数据的移位和采样。

SPI接口允许用户灵活选择时钟的 上升沿 或 下降沿 来采样和/或移位数据。

需要注意:SPI接口传输的 数据位数 仍需查阅器件数据手册。

时钟极性和时钟相位

在SPI中,主机可以选择 时钟极性(Clock Polarity,CPOL)和 时钟相位(Clock Phase,CPHA)。

在空闲状态期间,CPOL 控制 时钟极性。空闲状态是指传输开始时CS为高电平且在向低电平转变的期间,以及传输结束时CS为低电平且在向高电平转变的期间。

CPHA 控制 时钟相位。根据CPHA位的状态,使用时钟上升沿或下降沿来采样和/或移位数据。主机必须 根据从机的要求选择时钟极性和时钟相位。根据CPOL和CPHA位的选择,有四种SPI模式可用,如下表:

SPI 模式 CPOL CPHA 空闲状态下的时钟极性 用于采样和/或移位数据的时钟相应
0 0 0 逻辑低电平 数据在上升沿采样,在下降沿移出
1 0 1 逻辑低电平 数据在下降沿采样,在上升沿移出
2 1 1 逻辑低电平 数据在下降沿采样,在上升沿移出
3 1 0 逻辑低电平 数据在上升沿采样,在下降沿移出

下图为TI公司F280049芯片的SPI时钟模式:

image-20210819084200284

下图为ADI公司ADSP21479芯片的SPI时钟模式:

image-20210819084447672

后述 图2 至 图5 显示了四种SPI模式下的通信示例。

在这些示例中,数据显示在MOSI和MISO线上。传输的开始和结束用绿色虚线表示,采样边沿用橙色虚线表示,移位边沿用蓝色虚线表示。请注意,这些图形仅供参考。要成功进行SPI通信,用户须参阅产品数据手册并确保满足器件的时序规格。

图2. SPI模式0,CPOL = 0,CPHA = 0:CLK空闲状态 = 低电平,数据在上升沿采样,并在下降沿移出

图2. SPI模式0,CPOL = 0,CPHA = 0:CLK空闲状态 = 低电平,数据在上升沿采样,并在下降沿移出

图3给出了SPI模式1的时序图。在此模式下,时钟极性为0,表示时钟信号的空闲状态为低电平。此模式下的时钟相位为1,表示数据在下降沿采样(由橙色虚线显示),并且数据在时钟信号的上升沿移出(由蓝色虚线显示)。

图3. SPI模式1,CPOL = 0,CPHA = 1:CLK空闲状态 = 低电平,数据在下降沿采样,并在上升沿移出

图3. SPI模式1,CPOL = 0,CPHA = 1:CLK空闲状态 = 低电平,数据在下降沿采样,并在上升沿移出

图4给出了SPI模式2的时序图。在此模式下,时钟极性为1,表示时钟信号的空闲状态为高电平。此模式下的时钟相位为1,表示数据在下降沿采样(由橙色虚线显示),并且数据在时钟信号的上升沿移出(由蓝色虚线显示)。

图4. SPI模式2,CPOL = 1,CPHA = 1:CLK空闲状态 = 高电平,数据在下降沿采样,并在上升沿移出

图4. SPI模式2,CPOL = 1,CPHA = 1:CLK空闲状态 = 高电平,数据在下降沿采样,并在上升沿移出。

图5给出了SPI模式3的时序图。在此模式下,时钟极性为1,表示时钟信号的空闲状态为高电平。此模式下的时钟相位为0,表示数据在上升沿采样(由橙色虚线显示),并且数据在时钟信号的下降沿移出(由蓝色虚线显示)。

图5. SPI模式3,CPOL = 1,CPHA = 0:CLK空闲状态 = 高电平,数据在上升沿采样,并在下降沿移出

图5. SPI模式3,CPOL = 1,CPHA = 0:CLK空闲状态 = 高电平,数据在上升沿采样,并在下降沿移出

多从机配置

多个从机可与单个SPI主机一起使用。从机可以采用常规模式连接,或采用菊花链模式连接。

常规SPI模式

image-20210819075431419
图6. 多从机SPI配置

在常规模式下,主机需要为每个从机提供单独的片选信号。一旦主机使能(拉低)片选信号,MOSI/MISO线上的时钟和数据便可用于所选的从机。如果使能多个片选信号,则MISO线上的数据会被破坏,因为主机无法识别哪个从机正在传输数据。

从图6可以看出,随着从机数量的增加,来自主机的片选线的数量也增加。这会快速增加主机需要提供的输入和输出数量,并限制可以使用的从机数量。可以使用其他技术来增加常规模式下的从机数量,例如使用多路复用器产生片选信号。

菊花链模式

image-20210819075542602

图7. 多从机SPI菊花链配置

在菊花链模式下,所有从机的片选信号连接在一起,数据从一个从机传播到下一个从机。在此配置中,所有从机同时接收同一SPI时钟。来自主机的数据直接送到第一个从机,该从机将数据提供给下一个从机,依此类推。

使用该方法时,由于数据是从一个从机传播到下一个从机,所以传输数据所需的时钟周期数与菊花链中的从机位置成比例(成比例倍增)。

例如在图7所示的8位系统中,为使第3个从机能够获得数据,需要24个时钟脉冲,而常规SPI模式下只需8个时钟脉冲。图8显示了时钟周期和通过菊花链的数据传播。并非所有SPI器件都支持菊花链模式。请参阅产品数据手册以确认菊花链是否可用。

图8. 菊花链配置:数据传播

图8. 菊花链配置:数据传播

HALF DUPLEX SPI

半双工通信可以是 两线SPI 或 三线SPI,仅仅是有无片选线的差异而已。

PIN DEFINITION

NCS: SPI控制读写使能信号;使用时需要被拉低,否则SDIO会处在 HIGH-Z 态,而SCLK信号也会被无视。也可以被用于在通信错误发生时重置SPI 通信。两线SPI不需要这根线,但个别芯片可能需要这个引脚接地(不能悬空),具体看芯片手册。

SDIO: SPI的数据读写端口;半双工读写。只有在“从被控设备读出数据”的情况下,SDIO 才会由被控制设备控制。

SCLK: SPI接口时钟;总是由主控制器生成和控制。

TRANSMISSION PROTOCOL

跟其他通信类似,SPI 的 SDIO 需要在 时钟低电平时 进行电平跳变。

半双工SPI读写操作都包含两个字(byte),第一个字包含1 bit的 数据方向(或称 控制位) 和 7 bit的 地址,第二个字包含 数据

关于数据方向/控制位

通常定义是,当需要写入数据时,控制位写 1 。当需要读出数据时,控制位写 0

image-20220118135819247

但不一定所有支持半双工SPI的通信控制位都是在MSB,有可能是在 LSB 。

如,下方右侧 SL8541E 的安卓平台主控,支持半双工SPI,但是其控制位就在地址字符的LSB上。

image-20220419235450360

写操作

主控制器在SCLK下降沿时改变 SDIO的电平,被控设备在SCLK上升沿时读取SDIO的电平。

image-20220118140526017

读取操作

SDIO会在SCLK下降沿时修改,主控制器需要在SCLK的上升沿时读取SDIO的电平信息。

注意:发送完地址字 之后需要延长半个周期的时钟低电平。

image-20220118141033005

Timing Serial

因为芯片自带的时序图有些许错误,所以就自己画了一个。

时序图绘图工具(开源):WAVEDROM

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
{signal: [
['CPOL=1,CPHA=1',
['Read',
['Control',
{name: 'NCS', wave: 'hl................h'},
{name: 'SCLK', wave: 'hn.......lnn......h'},
],
{name: 'SDIO', wave: '102222222x222222221',data: ["A6","A5","A4","A3","A2","A1","A0","D7","D6","D5","D4","D3","D2","D1","D0",]},
],
{},
['Write',
['Control',
{name: 'NCS', wave: 'hl...............h'},
{name: 'SCLK', wave: 'hn...............h'},
],
{name: 'SDIO', wave: '1.2222222222222221',data: ["A6","A5","A4","A3","A2","A1","A0","D7","D6","D5","D4","D3","D2","D1","D0",]},
]
],
{},
['CPOL=1,CPHA=0',
['Read',
['Control',
{name: 'NCS', wave: 'hl................h'},
{name: 'SCLK', wave: 'hpp......lpp......h'},
],
{name: 'SDIO', wave: '102222222x222222221',data: ["A6","A5","A4","A3","A2","A1","A0","D7","D6","D5","D4","D3","D2","D1","D0",],phase: 0.25},
],
{},
['Write',
['Control',
{name: 'NCS', wave: 'hl...............h'},
{name: 'SCLK', wave: 'hpp..............h'},
],
{name: 'SDIO', wave: '1.2222222222222221',data: ["A6","A5","A4","A3","A2","A1","A0","D7","D6","D5","D4","D3","D2","D1","D0"],phase: 0.25},
]
],

],
config: { hscale: 1 }
}

image-20220423193121090

DEBUG RECORDS

下面这个时序图错误的坑是真的大,调了好就才折腾明白。

某些芯片下,SDIO 的闲置状态可能不能是 Hi-Z 态。

时序图错误

内部调试时发现,SDIO在读取数据前后必须是 输出模式,最好 强制拉高,而不是 Hi-Z 态。

image-20220419210753902

读写单个字符

以下这两个函数中的 for() 使用了 MSB 发送数据,最好不要自作聪明将 i 的数据类型改为 uint8_t 或其他非负型数据,以下这种循环写法会让程序陷入死循环的。

另外,SPI 和 I2C 不一样,读取和写入的时序有较大差异。比如,I2C会在时钟高电平时读取电平数据,在低电平时改写电平数据。而 CPOL = 1, CPHA = 1 的SPI 会在上升沿时读取和写入数据,而 CPOL = 1, CPHA = 0 的 SPI会在下降沿时读取和写入数据。

主从设备必须将SPI特性保持一致,才能确保读写的稳定性。

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
/**
* @brief 写入8bit数据
* @param {uint8_t} data
* @return {*}
*/
static void write_byte(uint8_t data)
{
for(int i = 7; i>= 0; i--)
{
if( (data >> i) & 0x01)
{
write_high_level();
}
else
{
write_low_level();
}
}
}

/**
* @brief 读取8bit数据
* @param {*}
* @return {uint8_t} data
*/
static uint8_t read_byte(void)
{
uint8_t data = 0;
for(int i = 7; i>= 0; i--)
{
#if (CPHA == 1)
SCLK_SET_LOW;
data |= HAL_GPIO_ReadPin(SPI_DATA_GPIO_Port, SPI_DATA_Pin) << i;
delay_nop(LEVEL_DELAY_SLICES);

SCLK_SET_HIGH;
delay_nop(LEVEL_DELAY_SLICES);
#else

SCLK_SET_HIGH;
delay_nop(LEVEL_DELAY_SLICES);
SCLK_SET_LOW;
data |= HAL_GPIO_ReadPin(SPI_DATA_GPIO_Port, SPI_DATA_Pin) << i;
delay_nop(LEVEL_DELAY_SLICES);
#endif
}
return data;
}

STM32 SPI 配置

image-20220824175804976

image-20220824175908055

由外设SPI控制的 SCLK / SDIO 波形与手动控制的 SNCS 波形之间的矛盾。

image-20220824175942791

write_register(0x09, 0xA5),对应应该输出数据为 0x89, 0xA5 ,而 SNCS 在两处地方产生了错误的波形。

image-20220824175951156

SOURCE CODE

以下是适用于 STM32 平台的 GPIO-模拟 半双工SPI 代码,利用 空指令进行延时控制,实现半双工(三线/两线)SPI 的通信。

需要软件适配以下几个功能:

  1. 控制精细度在 \([10^{-9}s:10^{-6}s]\) 之间的 精准延时(如果做不到,通信速率就上不去 1MHz )。
  2. 控制引脚电平的宏定义(可选)。
  3. 设置数据接口输出设置数据接口输入 的代码。
  4. 拉高电平拉低电平 的代码,这里需要根据从设备特性来控制时钟的相位,即 CPHA = 1 或者 CPHA = 0
  5. 对照着 正确的时序图 写一遍 读单字符写单字符 的代码,这里需要根据从设备特性来控制时钟的相位,即 CPHA = 1 或者 CPHA = 0
  6. 对照着 正确的时序图 写一遍 写寄存器读寄存器 的代码,这里需要根据从设备特性来控制时钟的极性,决定时钟在闲时为高电平还是低电平,即 CPOL = 1 或者 CPOL = 0

注意:都是按顺序来完成的,所有后面的功能都需要依托于前面的功能来实现。而且,需要用示波器(有条件)进行测量以校准通信速率,并使用正确的时序图进行编码(如果是错误的,浪费时间,尽快跟厂家确认)。

代码部分可以参考 gmi 中对 spi 的定义和使用。

REFERENCE

  1. 实例解析非标准SPI
  2. 扩展SPI协议
  3. SPI接口简介
  4. SPI通信协议详解(spi总线)
  5. 第23 章 串行外设接口(SPI)