• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# SPI
2
3## 概述
4
5### 功能简介
6
7SPI指串行外设接口(Serial Peripheral Interface),是一种高速的,全双工,同步的通信总线。SPI是由Motorola公司开发,用于在主设备和从设备之间进行通信。
8
9SPI接口定义了操作SPI设备的通用方法集合,包括:
10  - SPI设备句柄获取和释放。
11  - SPI读写:从SPI设备读取或写入指定长度数据。
12  - SPI自定义传输:通过消息传输结构体执行任意读写组合过程。
13  - SPI设备配置:获取和设置SPI设备属性。
14
15### 运作机制
16
17在HDF框架中,SPI的接口适配模式采用独立服务模式,在这种模式下,每一个设备对象会独立发布一个设备服务来处理外部访问,设备管理器收到API的访问请求之后,通过提取该请求的参数,达到调用实际设备对象的相应内部方法的目的。独立服务模式可以直接借助HDFDeviceManager的服务管理能力,但需要为每个设备单独配置设备节点,若设备过多可能增加内存占用。
18
19SPI以主从方式工作,通常有一个主设备和一个或者多个从设备。主设备和从设备之间一般用4根线相连,它们分别是:
20  - SCLK:时钟信号,由主设备产生;
21  - MOSI:主设备数据输出,从设备数据输入;
22  - MISO:主设备数据输入,从设备数据输出;
23  - CS:片选,从设备使能信号,由主设备控制。
24
25一个主设备和两个从设备的连接示意图如下所示,Device A和Device B共享主设备的SCLK、MISO和MOSI三根引脚,Device A的片选CS0连接主设备的CS0,Device B的片选CS1连接主设备的CS1。
26
27**图1** SPI主从设备连接示意图
28
29![image](figures/SPI主从设备连接示意图.png "SPI主从设备连接示意图")
30
31- SPI通信通常由主设备发起,通过以下步骤完成一次通信:
32  1. 通过CS选中要通信的从设备,在任意时刻,一个主设备上最多只能有一个从设备被选中。
33  2. 通过SCLK给选中的从设备提供时钟信号。
34  3. 基于SCLK时钟信号,主设备数据通过MOSI发送给从设备,同时通过MISO接收从设备发送的数据,完成通信。
35
36- 根据SCLK时钟信号的CPOL(Clock Polarity,时钟极性)和CPHA(Clock Phase,时钟相位)的不同组合,SPI有以下四种工作模式:
37  - CPOL=0,CPHA=0 时钟信号idle状态为低电平,第一个时钟边沿采样数据。
38  - CPOL=0,CPHA=1 时钟信号idle状态为低电平,第二个时钟边沿采样数据。
39  - CPOL=1,CPHA=0 时钟信号idle状态为高电平,第一个时钟边沿采样数据。
40  - CPOL=1,CPHA=1 时钟信号idle状态为高电平,第二个时钟边沿采样数据。
41
42### 约束与限制
43
44SPI模块当前只支持主机模式,不支持从机模式。
45
46## 使用指导
47
48### 场景介绍
49
50SPI通常用于与闪存、实时时钟、传感器以及模数/数模转换器等支持SPI协议的设备进行通信。
51
52### 接口说明
53
54SPI模块提供的主要接口如表1所示,具体API详见//drivers/hdf_core/framework/include/platform/spi_if.h55
56**表1** SPI驱动API接口功能介绍
57
58| 接口名 | 接口描述 |
59| -------- | -------- |
60| DevHandle SpiOpen(const struct SpiDevInfo \*info) | 获取SPI设备句柄 |
61| void SpiClose(DevHandle handle) | 释放SPI设备句柄 |
62| int32_t SpiRead(DevHandle handle, uint8_t \*buf, uint32_t len) | 读取指定长度的数据 |
63| int32_t SpiWrite(DevHandle handle, uint8_t \*buf, uint32_t len) | 写入指定长度的数据 |
64| int32_t SpiTransfer(DevHandle handle, struct SpiMsg \*msgs, uint32_t count) | SPI数据传输接口 |
65| int32_t SpiSetCfg(DevHandle handle, struct SpiCfg \*cfg) | 根据指定参数,配置SPI设备 |
66| int32_t SpiGetCfg(DevHandle handle, struct SpiCfg \*cfg) | 获取SPI设备配置参数 |
67
68### 使用流程
69
70使用SPI的一般流程如下图所示。
71
72  **图2** SPI使用流程图
73
74  ![image](figures/SPI使用流程图.png "SPI使用流程图")
75
76#### 获取SPI设备句柄
77
78在使用SPI进行通信时,首先要调用SpiOpen获取SPI设备句柄,该函数会返回指定总线号和片选号的SPI设备句柄。
79
80```c
81DevHandle SpiOpen(const struct SpiDevInfo *info);
82```
83
84  **表2** SpiOpen参数和返回值描述
85
86| **参数** | **参数描述** |
87| -------- | -------- |
88| info | SPI设备描述符 |
89| **返回值** | **返回值描述** |
90| NULL | 获取SPI设备句柄失败 |
91| 设备句柄 | 对应的SPI设备句柄 |
92
93假设系统中的SPI设备总线号为0,片选号为0,获取该SPI设备句柄的示例如下:
94
95```c
96struct SpiDevInfo spiDevinfo;       /* SPI设备描述符 */
97DevHandle spiHandle = NULL;         /* SPI设备句柄  */
98spiDevinfo.busNum = 0;              /* SPI设备总线号 */
99spiDevinfo.csNum = 0;               /* SPI设备片选号 */
100
101/* 获取SPI设备句柄 */
102spiHandle = SpiOpen(&spiDevinfo);
103if (spiHandle == NULL) {
104    HDF_LOGE("SpiOpen: failed\n");
105    return;
106}
107```
108
109#### 获取SPI设备属性
110
111在获取到SPI设备句柄之后,需要配置SPI设备属性。配置SPI设备属性之前,可以先获取SPI设备属性,获取SPI设备属性的函数如下所示:
112
113```c
114int32_t SpiGetCfg(DevHandle handle, struct SpiCfg *cfg);
115```
116
117  **表3** SpiGetCfg参数和返回值描述
118
119| **参数** | **参数描述** |
120| -------- | -------- |
121| handle     | SPI设备句柄 |
122| cfg        | SPI设备配置参数 |
123| **返回值** | **返回值描述** |
124| 0          | 获取配置成功 |
125| 负数       | 获取配置失败 |
126
127```c
128int32_t ret;
129struct SpiCfg cfg = {0};                /* SPI配置信息*/
130ret = SpiGetCfg(spiHandle, &cfg);       /* 获取SPI设备属性 */
131if (ret != 0) {
132    HDF_LOGE("SpiGetCfg: failed, ret %d\n", ret);
133}
134```
135
136#### 配置SPI设备属性
137
138在获取到SPI设备句柄之后,需要配置SPI设备属性,配置SPI设备属性的函数如下所示:
139
140```c
141int32_t SpiSetCfg(DevHandle handle, struct SpiCfg *cfg);
142```
143
144  **表4** SpiSetCfg参数和返回值描述
145
146| **参数** | **参数描述** |
147| -------- | -------- |
148| handle     | SPI设备句柄 |
149| cfg        | SPI设备配置参数 |
150| **返回值** | **返回值描述** |
151| 0          | 配置成功 |
152| 负数       | 配置失败 |
153
154```c
155int32_t ret;
156struct SpiCfg cfg = {0};                     /* SPI配置信息*/
157cfg.mode = SPI_MODE_LOOP;                    /* 以回环模式进行通信 */
158cfg.transferMode = PAL_SPI_POLLING_TRANSFER; /* 以轮询的方式进行通信 */
159cfg.maxSpeedHz = 115200;                     /* 最大传输频率 */
160cfg.bitsPerWord = 8;                         /* 读写位宽为8比特 */
161ret = SpiSetCfg(spiHandle, &cfg);            /* 配置SPI设备属性 */
162if (ret != 0) {
163    HDF_LOGE("SpiSetCfg: failed, ret %d\n", ret);
164}
165```
166
167#### 进行SPI通信
168
169- 向SPI设备写入指定长度的数据
170
171  如果只向SPI设备写一次数据,则可以通过以下函数完成:
172
173  ```c
174  int32_t SpiWrite(DevHandle handle, uint8_t *buf, uint32_t len);
175  ```
176
177  **表5** SpiWrite参数和返回值描述
178
179  | **参数** | **参数描述** |
180  | -------- | -------- |
181  | handle     | SPI设备句柄 |
182  | buf        | 待写入数据的指针 |
183  | len        | 待写入的数据长度 |
184  | **返回值** | **返回值描述** |
185  | 0          | 写入成功 |
186  | 负数       | 写入失败 |
187
188  ```c
189  int32_t ret;
190  uint8_t wbuff[4] = {0x12, 0x34, 0x56, 0x78};
191  /* 向SPI设备写入指定长度的数据 */
192  ret = SpiWrite(spiHandle, wbuff, 4);
193  if (ret != 0) {
194      HDF_LOGE("SpiWrite: failed, ret %d\n", ret);
195  }
196  ```
197
198- 从SPI设备读取指定长度的数据
199
200  如果只读取一次数据,则可以通过以下函数完成:
201
202  ```c
203  int32_t SpiRead(DevHandle handle, uint8_t *buf, uint32_t len);
204  ```
205
206  **表6** SpiRead参数和返回值描述
207
208  | **参数** | **参数描述** |
209  | -------- | -------- |
210  | handle | SPI设备句柄 |
211  | buf | 待读取数据的指针 |
212  | len | 待读取的数据长度 |
213  | **返回值** | **返回值描述** |
214  | 0 | 读取成功 |
215  | 负数 | 读取失败 |
216
217  ```c
218  int32_t ret;
219  uint8_t rbuff[4] = {0};
220  /* 从SPI设备读取指定长度的数据 */
221  ret = SpiRead(spiHandle, rbuff, 4);
222  if (ret != 0) {
223      HDF_LOGE("SpiRead: failed, ret %d\n", ret);
224  }
225  ```
226
227- 自定义传输
228
229  如果需要发起一次自定义传输,则可以通过以下函数完成:
230
231  ```c
232  int32_t SpiTransfer(DevHandle handle, struct SpiMsg *msgs, uint32_t count);
233  ```
234
235  **表7** SpiTransfer参数和返回值描述
236
237  | **参数** | **参数描述** |
238  | -------- | -------- |
239  | handle | SPI设备句柄 |
240  | msgs | 待传输数据的数组 |
241  | count | msgs数组长度 |
242  | **返回值** | **返回值描述** |
243  | 0 | 执行成功 |
244  | 负数 | 执行失败 |
245
246  ```c
247  int32_t ret;
248  uint8_t wbuff[1] = {0x12};
249  uint8_t rbuff[1] = {0};
250  struct SpiMsg msg;        /* 自定义传输的消息 */
251  msg.wbuf = wbuff;         /* 写入的数据 */
252  msg.rbuf = rbuff;         /* 读取的数据 */
253  msg.len = 1;              /* 读取、写入数据的长度都是1 */
254  msg.csChange = 1;         /* 进行下一次传输前关闭片选 */
255  msg.delayUs = 0;          /* 进行下一次传输前不进行延时 */
256  msg.speed = 115200;       /* 本次传输的速度 */
257  /* 进行一次自定义传输,传输的msg个数为1 */
258  ret = SpiTransfer(spiHandle, &msg, 1);
259  if (ret != 0) {
260      HDF_LOGE("SpiTransfer: failed, ret %d\n", ret);
261  }
262  ```
263
264#### 销毁SPI设备句柄
265
266SPI通信完成之后,需要销毁SPI设备句柄,销毁SPI设备句柄的函数如下所示:
267
268```c
269void SpiClose(DevHandle handle);
270```
271
272该函数会释放掉申请的资源。
273
274  **表8** SpiClose参数描述
275
276| **参数** | **参数描述** |
277| -------- | -------- |
278| handle | SPI设备句柄 |
279
280```c
281SpiClose(spiHandle); /* 销毁SPI设备句柄 */
282```
283
284### 使用实例
285
286本例拟对Hi3516DV300开发板上SPI设备进行操作。
287
288SPI设备完整的使用示例如下所示,首先获取SPI设备句柄,然后配置SPI设备属性,接着调用读写接口进行数据传输,最后销毁SPI设备句柄。
289
290```c
291#include "hdf_log.h"
292#include "spi_if.h"
293
294void SpiTestSample(void)
295{
296    int32_t ret;
297    struct SpiCfg cfg;                  /* SPI配置信息 */
298    struct SpiDevInfo spiDevinfo;       /* SPI设备描述符 */
299    DevHandle spiHandle = NULL; /* SPI设备句柄 */
300    struct SpiMsg msg;                  /* 自定义传输的消息 */
301    uint8_t rbuff[4] = { 0 };
302    uint8_t wbuff[4] = { 0x12, 0x34, 0x56, 0x78 };
303    uint8_t wbuff2[4] = { 0xa1, 0xb2, 0xc3, 0xd4 };
304
305    spiDevinfo.busNum = 0;              /* SPI设备总线号 */
306    spiDevinfo.csNum = 0;               /* SPI设备片选号 */
307    spiHandle = SpiOpen(&spiDevinfo);   /* 根据spiDevinfo获取SPI设备句柄 */
308    if (spiHandle == NULL) {
309        HDF_LOGE("SpiOpen: failed\n");
310        return;
311    }
312    /* 获取SPI设备属性 */
313    ret = SpiGetCfg(spiHandle, &cfg);
314    if (ret != 0) {
315        HDF_LOGE("SpiGetCfg: failed, ret %d\n", ret);
316        goto err;
317    }
318    cfg.maxSpeedHz = 115200;                /* 将最大时钟频率改为115200 */
319    cfg.bitsPerWord = 8;                    /* 传输位宽改为8比特 */
320    /* 配置SPI设备属性 */
321    ret = SpiSetCfg(spiHandle, &cfg);
322    if (ret != 0) {
323        HDF_LOGE("SpiSetCfg: failed, ret %d\n", ret);
324        goto err;
325    }
326    /* 向SPI设备写入指定长度的数据 */
327    ret = SpiWrite(spiHandle, wbuff, 4);
328    if (ret != 0) {
329        HDF_LOGE("SpiWrite: failed, ret %d\n", ret);
330        goto err;
331    }
332    /* 从SPI设备读取指定长度的数据 */
333    ret = SpiRead(spiHandle, rbuff, 4);
334    if (ret != 0) {
335        HDF_LOGE("SpiRead: failed, ret %d\n", ret);
336        goto err;
337    }
338    msg.wbuf = wbuff2;  /* 写入的数据 */
339    msg.rbuf = rbuff;   /* 读取的数据 */
340    msg.len = 4;        /* 读取写入数据的长度为4 */
341    msg.csChange = 1;   /* 进行下一次传输前关闭片选 */
342    msg.delayUs = 0;    /* 进行下一次传输前不进行延时 */
343    msg.speed = 115200; /* 本次传输的速度 */
344    /* 进行一次自定义传输,传输的msg个数为1 */
345    ret = SpiTransfer(spiHandle, &msg, 1);
346    if (ret != 0) {
347        HDF_LOGE("SpiTransfer: failed, ret %d\n", ret);
348        goto err;
349    }
350err:
351    /* 销毁SPI设备句柄 */
352    SpiClose(spiHandle);
353}
354```
355