HALF DUPLEX SPI

SPI简介

SPI 的物理线支持 三种类型

  • 片选线(NCS / CS / SS),某些数据手册会将 片选引脚 称为 NSS /NCS / SEL (Select) / CS (Chip Select) / STE (Slaver Transmit Enable)。
  • 时钟线(CLK / SCK)
  • 数据线(DA 或 MOSI 和 MISO )(此处的 DA 特指单根支持半双工的数据线)。

需要注意的是,SPI的通信线有太多的名称,需要自行对照芯片手册进行查询,避免混淆。

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

SPI分类

SPI通信按GPIO 数量可以分类为 2~6线 SPI,包含单工、半双工、全双工三总。

按GPIO数量分类 通信制式 通信线
两线SPI 单工/半双工 CLK / DA 或 CLK / MOSI 或 CLK / MISO
三线SPI 单工/半双工/全双工 CS / CLK / DA 或 CLK / MOSI / MISO
四线SPI 全双工 CS / CLK / MOSI / MISO
六线SPI 全双工 CS / CLK / DA1 / DA2 / DA3 / DA4

例如,据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)

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

DEBUG RECORDS

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

时序图错误

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

image-20220419210753902

读写单个字符

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

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
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();
}
}
}

static uint8_t read_byte(void)
{
uint8_t data = 0;
for(int i = 7; i>= 0; i--)
{
SCLK_SET_LOW;
delay_nop(LEVEL_DELAY_SLICES);

SCLK_SET_HIGH;
delay_nop(LEVEL_DELAY_SLICES);

data |= HAL_GPIO_ReadPin(SPI_DATA_GPIO_Port, SPI_DATA_Pin) << i;
}

return data;
}

SOURCE CODE

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

需要适配以下几个功能:

  1. 控制精细度在 \([10^{-9}s:10^{-6}s]\) 之间的 精准延时(如果做不到,通信速率就上不去 1MHz )。
  2. 控制引脚电平的宏定义。
  3. 设置数据接口输出设置数据接口输入 的代码。
  4. 拉高电平拉低电平 的代码。
  5. 对照着 正确的时序图 撸一遍 读单字符写单字符 的代码。
  6. 对照着 正确的时序图 撸一遍 写寄存器读寄存器 的代码。

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

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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
 /*-----------------------------------------------------------------------------
* HEARDER FILES
*---------------------------------------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>

#include "spi_access.h"
#include "stm32l4xx_hal.h"
#include "log.h"
#include "main.h"

/*-----------------------------------------------------------------------------
* MACRO DEFINITIONS
*---------------------------------------------------------------------------*/
// 通信引脚定义
#define SPI_DATA_Pin GPIO_PIN_5
#define SPI_DATA_GPIO_Port GPIOC

#define SPI_CLK_Pin GPIO_PIN_6
#define SPI_CLK_GPIO_Port GPIOC

#define SPI_CS_Pin GPIO_PIN_8
#define SPI_CS_GPIO_Port GPIOC

// 定义电平
#ifndef HIGH_LEVEL
#define HIGH_LEVEL GPIO_PIN_SET
#endif

#ifndef LOW_LEVEL
#define LOW_LEVEL GPIO_PIN_RESET
#endif

// 定义引脚电平控制
#define SDIO_SET_HIGH {HAL_GPIO_WritePin(SPI_DATA_GPIO_Port,SPI_DATA_Pin,HIGH_LEVEL);}
#define SDIO_SET_LOW {HAL_GPIO_WritePin(SPI_DATA_GPIO_Port,SPI_DATA_Pin,LOW_LEVEL);}

#define SDIO_SET_IN {HAL_GPIO_ReadPin(SPI_DATA_GPIO_Port, SPI_DATA_Pin);}

#define SCLK_SET_HIGH {HAL_GPIO_WritePin(SPI_CLK_GPIO_Port, SPI_CLK_Pin, HIGH_LEVEL);}
#define SCLK_SET_LOW {HAL_GPIO_WritePin(SPI_CLK_GPIO_Port, SPI_CLK_Pin, LOW_LEVEL);}

#define SNCS_SET_HIGH {HAL_GPIO_WritePin(SPI_CS_GPIO_Port, SPI_CS_Pin, HIGH_LEVEL);}
#define SNCS_SET_LOW {HAL_GPIO_WritePin(SPI_CS_GPIO_Port, SPI_CS_Pin, LOW_LEVEL);}

// 电平延时
#define LEVEL_DELAY_SLICES 80

// 寄存器(最大)尝试写入次数
#define MAX_WRITE_TIMES 10

/*-----------------------------------------------------------------------------
* DATA TYPE DECLARATION
*---------------------------------------------------------------------------*/
// spi句柄
typedef struct
{
int handle;
} spi_dev;

/*-----------------------------------------------------------------------------
* GLOBAL PARAMETERS
*---------------------------------------------------------------------------*/
static bool two_wired_spi; // 两线SPI使能开关

/*-----------------------------------------------------------------------------
* PRIVATE FUNCTIONS DEFINITION
*---------------------------------------------------------------------------*/
/**
* @brief delay for a slice of cpu
* @param {volatile uint32_t} times
* @return {*}
*/
static void delay_nop(volatile uint32_t times)
{
for(uint32_t i = 0; i < times; i++)
{
__NOP();
}
}

/**
* @brief delay for 1us
* @param {volatile uint32_t} times
* @return {*}
*/
static void delay_us(volatile uint32_t times)
{
for(uint32_t i = 0; i < times; i++)
{
__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
}
}

static void delay_ms(uint16_t times)
{
for(uint16_t i = 0; i < times; i++)
{
delay_us(1000);
}
}

static void sdio_input_mode(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};

GPIO_InitStruct.Pin = SPI_DATA_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
HAL_GPIO_Init(SPI_DATA_GPIO_Port, &GPIO_InitStruct);
}

static void sdio_output_mode(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};

GPIO_InitStruct.Pin = SPI_DATA_Pin ;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(SPI_DATA_GPIO_Port, &GPIO_InitStruct);
}

static void write_high_level(void)
{
SCLK_SET_LOW;
SDIO_SET_HIGH;
delay_nop(LEVEL_DELAY_SLICES);
SCLK_SET_HIGH;
delay_nop(LEVEL_DELAY_SLICES);
}

static void write_low_level(void)
{
SCLK_SET_LOW;
SDIO_SET_LOW;
delay_nop(LEVEL_DELAY_SLICES);
SCLK_SET_HIGH;
delay_nop(LEVEL_DELAY_SLICES);
}

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();
}
}
}

static uint8_t read_byte(void)
{
uint8_t data = 0;
for(int i = 7; i>= 0; i--)
{
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);
}

return data;
}

/*-----------------------------------------------------------------------------
* PUBLIC FUNCTIONS DEFINITION
*---------------------------------------------------------------------------*/
void *write_sensor_init(bool switch_two_wired_spi)
{
if(switch_two_wired_spi)
{
two_wired_spi = true;
}
else
{
two_wired_spi = false;
}

spi_dev *instance = NULL;
instance = malloc(sizeof(spi_dev));
memset(instance, 0, sizeof(spi_dev));

if(two_wired_spi)
{
SNCS_SET_LOW;
SCLK_SET_HIGH;
delay_ms(5);
SCLK_SET_LOW;
delay_ms(1);
SCLK_SET_HIGH;
delay_ms(5);
}
else
{
sdio_input_mode();
SCLK_SET_HIGH;
delay_nop(LEVEL_DELAY_SLICES);
SCLK_SET_LOW;
delay_nop(LEVEL_DELAY_SLICES);
SCLK_SET_HIGH;
delay_nop(LEVEL_DELAY_SLICES);
}

instance->handle = 1;
return (void *)instance;
}

void write_sensor_deinit(void* handle)
{
if (handle != NULL)
{
free(handle);
}
else
{
LOGE("ERROR: NULL spi handle.");
}
}


void spi_resynchronized()
{
if (two_wired_spi)
{
SCLK_SET_HIGH;
SDIO_SET_HIGH;
delay_ms(2);
SCLK_SET_LOW;
delay_us(1);
SCLK_SET_HIGH;
delay_ms(2);
SDIO_SET_IN;
delay_ms(1);
}
else
{
// 暂时未完成
return;
}
}

int write_register(uint8_t regAddr, uint8_t regData)
{
regAddr |= 0x80;

for(uint8_t write_times = 0; write_times < MAX_WRITE_TIMES; write_times++)
{
sdio_output_mode(); // 设置SDIO为输出模式
SDIO_SET_HIGH; // 拉高SDIO电平
SCLK_SET_HIGH; // 拉高SCLK电平
delay_us(1); // 2-wired spi

if(!two_wired_spi)
{
SNCS_SET_LOW; // 拉低SNCS电平
}
delay_nop(LEVEL_DELAY_SLICES);

write_byte(regAddr);
write_byte(regData);

if(!two_wired_spi)
{
SNCS_SET_HIGH;
sdio_input_mode();
delay_nop(LEVEL_DELAY_SLICES);
}
else
{
delay_us(500);
}

if(read_register(regAddr) == regData) return 0;
}

LOGE("ERROR: Write register failed.");
return -1;
}

uint8_t read_register(uint8_t regAddr)
{
uint8_t data = 0;
regAddr &= 0x7F; // 设置为读取模式

sdio_output_mode(); // 设置SDIO为输出模式
if(two_wired_spi) delay_us(10);
SDIO_SET_HIGH; // 拉高SDIO电平
SCLK_SET_HIGH; // 拉高SCLK电平
if(two_wired_spi) delay_us(100);

if(!two_wired_spi)
{
SNCS_SET_LOW; // 拉低SNCS电平
}
delay_nop(LEVEL_DELAY_SLICES);

write_byte(regAddr);

SCLK_SET_LOW;
sdio_input_mode();
if(two_wired_spi) delay_us(5);
else delay_nop(LEVEL_DELAY_SLICES);

data = read_byte();

SCLK_SET_HIGH;
sdio_output_mode();
delay_nop(LEVEL_DELAY_SLICES);
if(two_wired_spi) delay_us(40);
else SNCS_SET_HIGH;

return data;
}

REFERENCE

  1. [【硬件通信协议】5. 实例解析非标准SPI(三线SPI)_sishuihuahua的博客-CSDN博客_三线spi](https://blog.csdn.net/sishuihuahua/article/details/105047926)