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