driver-platform-spi-des.md 11.3 KB
Newer Older
D
duangavin123 已提交
1 2 3 4 5
# SPI


## 概述

6
SPI指串行外设接口(Serial Peripheral Interface),是一种高速的,全双工,同步的通信总线。SPI是由Motorola公司开发,用于在主设备和从设备之间进行通信,常用于与闪存、实时时钟、传感器以及模数转换器等进行通信。
D
duangavin123 已提交
7

8 9 10 11 12
SPI以主从方式工作,通常有一个主设备和一个或者多个从设备。主设备和从设备之间一般用4根线相连,它们分别是:
  - SCLK:时钟信号,由主设备产生;
  - MOSI:主设备数据输出,从设备数据输入;
  - MISO:主设备数据输入,从设备数据输出;
  - CS:片选,从设备使能信号,由主设备控制。
D
duangavin123 已提交
13

14
一个主设备和两个从设备的连接示意图如下所示,Device A和Device B共享主设备的SCLK、MISO和MOSI三根引脚,Device A的片选CS0连接主设备的CS0,Device B的片选CS1连接主设备的CS1。
D
duangavin123 已提交
15 16 17

  **图1** SPI主从设备连接示意图

18
  ![image](figures/SPI主从设备连接示意图.png "SPI主从设备连接示意图")
D
duangavin123 已提交
19 20 21 22 23 24 25 26 27 28 29 30 31 32

- SPI通信通常由主设备发起,通过以下步骤完成一次通信:
  1. 通过CS选中要通信的从设备,在任意时刻,一个主设备上最多只能有一个从设备被选中。
  2. 通过SCLK给选中的从设备提供时钟信号。
  3. 基于SCLK时钟信号,主设备数据通过MOSI发送给从设备,同时通过MISO接收从设备发送的数据,完成通信。

- 根据SCLK时钟信号的CPOL(Clock Polarity,时钟极性)和CPHA(Clock Phase,时钟相位)的不同组合,SPI有以下四种工作模式:
  - CPOL=0,CPHA=0 时钟信号idle状态为低电平,第一个时钟边沿采样数据。
  - CPOL=0,CPHA=1 时钟信号idle状态为低电平,第二个时钟边沿采样数据。
  - CPOL=1,CPHA=0 时钟信号idle状态为高电平,第一个时钟边沿采样数据。
  - CPOL=1,CPHA=1 时钟信号idle状态为高电平,第二个时钟边沿采样数据。

- SPI接口定义了操作SPI设备的通用方法集合,包括:
  - SPI设备句柄获取和释放。
33
  - SPI读写: 从SPI设备读取或写入指定长度数据。
D
duangavin123 已提交
34 35 36
  - SPI自定义传输:通过消息传输结构体执行任意读写组合过程。
  - SPI设备配置:获取和设置SPI设备属性。

37
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**<br>
D
duangavin123 已提交
38 39 40 41 42 43 44
> 当前只支持主机模式,不支持从机模式。


## 接口说明

  **表1** SPI驱动API接口功能介绍

45
| 接口名 | 接口描述 | 
D
duangavin123 已提交
46
| -------- | -------- |
47 48 49 50 51 52 53 54 55
| SpiOpen | 获取SPI设备句柄 | 
| SpiClose | 释放SPI设备句柄 | 
| SpiRead | 读取指定长度的数据 | 
| SpiWrite | 写入指定长度的数据 | 
| SpiTransfer | SPI数据传输接口 | 
| SpiSetCfg | 根据指定参数,配置SPI设备 | 
| SpiGetCfg | 获取SPI设备配置参数 | 

> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**<br>
D
duangavin123 已提交
56 57 58 59 60 61 62 63 64 65 66 67
> 本文涉及的所有接口,仅限内核态使用,不支持在用户态使用。


## 使用指导


### 使用流程

使用SPI的一般流程如下图所示。

  **图2** SPI使用流程图

68
  ![image](figures/SPI使用流程图.png "SPI使用流程图")
D
duangavin123 已提交
69 70 71


### 获取SPI设备句柄
W
wenjun 已提交
72 73 74

在使用SPI进行通信时,首先要调用SpiOpen获取SPI设备句柄,该函数会返回指定总线号和片选号的SPI设备句柄。

D
duangavin123 已提交
75 76 77 78 79 80 81 82 83

```
DevHandle SpiOpen(const struct SpiDevInfo *info); 
```

  **表2** SpiOpen参数和返回值描述

| **参数** | **参数描述** |
| -------- | -------- |
84 85 86 87
| info | SPI设备描述符 |
| **返回值** | **返回值描述** |
| NULL | 获取SPI设备句柄失败 |
| 设备句柄 | 对应的SPI设备句柄 |
W
wenjun 已提交
88 89 90

假设系统中的SPI设备总线号为0,片选号为0,获取该SPI设备句柄的示例如下:

D
duangavin123 已提交
91

W
wenjun 已提交
92 93
```
struct SpiDevInfo spiDevinfo;       /* SPI设备描述符 */
fix doc  
胡斌峰 已提交
94
DevHandle spiHandle = NULL;         /* SPI设备句柄  */
W
wenjun 已提交
95 96 97 98 99 100 101 102 103 104 105
spiDevinfo.busNum = 0;              /* SPI设备总线号 */
spiDevinfo.csNum = 0;               /* SPI设备片选号 */

/* 获取SPI设备句柄 */
spiHandle = SpiOpen(&spiDevinfo);
if (spiHandle == NULL) {
    HDF_LOGE("SpiOpen: failed\n");
    return;
}
```

D
duangavin123 已提交
106 107

### 获取SPI设备属性
W
wenjun 已提交
108 109 110

在获取到SPI设备句柄之后,需要配置SPI设备属性。配置SPI设备属性之前,可以先获取SPI设备属性,获取SPI设备属性的函数如下所示:

D
duangavin123 已提交
111 112 113 114 115 116 117 118 119

```
int32_t SpiGetCfg(DevHandle handle, struct SpiCfg *cfg);
```

  **表3** SpiGetCfg参数和返回值描述

| **参数** | **参数描述** |
| -------- | -------- |
120
| handle     | SPI设备句柄 |
D
duangavin123 已提交
121
| cfg        | SPI设备配置参数 |
122 123 124
| **返回值** | **返回值描述** |
| 0          | 获取配置成功 |
| 负数       | 获取配置失败 |
D
duangavin123 已提交
125

W
wenjun 已提交
126 127 128 129

```
int32_t ret;
struct SpiCfg cfg = {0};                /* SPI配置信息*/
D
duangavin123 已提交
130
ret = SpiGetCfg(spiHandle, &cfg);       /* 获取SPI设备属性 */
W
wenjun 已提交
131 132 133 134 135
if (ret != 0) {
    HDF_LOGE("SpiGetCfg: failed, ret %d\n", ret);
}
```

D
duangavin123 已提交
136 137

### 配置SPI设备属性
W
wenjun 已提交
138 139 140

在获取到SPI设备句柄之后,需要配置SPI设备属性,配置SPI设备属性的函数如下所示:

D
duangavin123 已提交
141 142 143 144 145 146 147 148 149

```
int32_t SpiSetCfg(DevHandle handle, struct SpiCfg *cfg);
```

  **表4** SpiSetCfg参数和返回值描述

| **参数** | **参数描述** |
| -------- | -------- |
150
| handle     | SPI设备句柄 |
D
duangavin123 已提交
151
| cfg        | SPI设备配置参数 |
152 153 154
| **返回值** | **返回值描述** |
| 0          | 配置成功 |
| 负数       | 配置失败 |
D
duangavin123 已提交
155

W
wenjun 已提交
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170

```
int32_t ret;
struct SpiCfg cfg = {0};                     /* SPI配置信息*/
cfg.mode = SPI_MODE_LOOP;                    /* 以回环模式进行通信 */
cfg.transferMode = PAL_SPI_POLLING_TRANSFER; /* 以轮询的方式进行通信 */
cfg.maxSpeedHz = 115200;                     /* 最大传输频率 */ 
cfg.bitsPerWord = 8;                         /* 读写位宽为8个比特 */
ret = SpiSetCfg(spiHandle, &cfg);            /* 配置SPI设备属性 */
if (ret != 0) {
    HDF_LOGE("SpiSetCfg: failed, ret %d\n", ret);
}
```


D
duangavin123 已提交
171
### 进行SPI通信
W
wenjun 已提交
172

D
duangavin123 已提交
173
- 向SPI设备写入指定长度的数据
W
wenjun 已提交
174

D
duangavin123 已提交
175
  如果只向SPI设备写一次数据,则可以通过以下函数完成:
W
wenjun 已提交
176 177


D
duangavin123 已提交
178 179 180 181 182 183 184 185
  ```
  int32_t SpiWrite(DevHandle handle, uint8_t *buf, uint32_t len);
  ```

  **表5** SpiWrite参数和返回值描述

  | **参数** | **参数描述** |
  | -------- | -------- |
186
  | handle     | SPI设备句柄 |
D
duangavin123 已提交
187 188
  | buf        | 待写入数据的指针 |
  | len        | 待写入的数据长度 |
189 190 191
  | **返回值** | **返回值描述** |
  | 0          | 写入成功 |
  | 负数       | 写入失败 |
D
duangavin123 已提交
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206


  ```
  int32_t ret;
  uint8_t wbuff[4] = {0x12, 0x34, 0x56, 0x78};
  /* 向SPI设备写入指定长度的数据 */
  ret = SpiWrite(spiHandle, wbuff, 4);
  if (ret != 0) {
      HDF_LOGE("SpiWrite: failed, ret %d\n", ret);
  }
  ```

- 从SPI设备读取指定长度的数据

  如果只读取一次数据,则可以通过以下函数完成:
W
wenjun 已提交
207

D
duangavin123 已提交
208 209 210 211 212 213 214 215 216

  ```
  int32_t SpiRead(DevHandle handle, uint8_t *buf, uint32_t len); 
  ```

  **表6** SpiRead参数和返回值描述

  | **参数** | **参数描述** |
  | -------- | -------- |
217 218 219 220 221 222
  | handle | SPI设备句柄 |
  | buf | 待读取数据的指针 |
  | len | 待读取的数据长度 |
  | **返回值** | **返回值描述** |
  | 0 | 读取成功 |
  | 负数 | 读取失败 |
D
duangavin123 已提交
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


  ```
  int32_t ret;
  uint8_t rbuff[4] = {0};
  /* 从SPI设备读取指定长度的数据 */
  ret = SpiRead(spiHandle, rbuff, 4);
  if (ret != 0) {
      HDF_LOGE("SpiRead: failed, ret %d\n", ret);
  }
  ```

- 自定义传输

  如果需要发起一次自定义传输,则可以通过以下函数完成:


  ```
  int32_t SpiTransfer(DevHandle handle, struct SpiMsg *msgs, uint32_t count);
  ```

  **表7** SpiTransfer参数和返回值描述

  | **参数** | **参数描述** |
  | -------- | -------- |
248 249 250 251 252 253
  | handle | SPI设备句柄 |
  | msgs | 待传输数据的数组 |
  | count | msgs数组长度 |
  | **返回值** | **返回值描述** |
  | 0 | 执行成功 |
  | 负数 | 执行失败 |
D
duangavin123 已提交
254 255 256 257 258 259


  ```
  int32_t ret;
  uint8_t wbuff[1] = {0x12};
  uint8_t rbuff[1] = {0};
260
  struct SpiMsg msg;        /* 自定义传输的消息 */
D
duangavin123 已提交
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275
  msg.wbuf = wbuff;         /* 写入的数据 */
  msg.rbuf = rbuff;         /* 读取的数据 */
  msg.len = 1;              /* 读取、写入数据的长度都是1 */
  msg.csChange = 1;         /* 进行下一次传输前关闭片选 */
  msg.delayUs = 0;          /* 进行下一次传输前不进行延时 */
  msg.speed = 115200;       /* 本次传输的速度 */
  /* 进行一次自定义传输,传输的msg个数为1 */
  ret = SpiTransfer(spiHandle, &msg, 1);
  if (ret != 0) {
      HDF_LOGE("SpiTransfer: failed, ret %d\n", ret);
  }
  ```


### 销毁SPI设备句柄
W
wenjun 已提交
276 277 278

SPI通信完成之后,需要销毁SPI设备句柄,销毁SPI设备句柄的函数如下所示:

D
duangavin123 已提交
279 280 281 282

```
void SpiClose(DevHandle handle);
```
W
wenjun 已提交
283 284 285

该函数会释放掉申请的资源。

D
duangavin123 已提交
286 287 288 289
  **表8** SpiClose参数描述

| **参数** | **参数描述** |
| -------- | -------- |
290
| handle | SPI设备句柄 |
D
duangavin123 已提交
291

W
wenjun 已提交
292 293 294 295 296

```
SpiClose(spiHandle); /* 销毁SPI设备句柄 */
```

D
duangavin123 已提交
297

D
duangavin123 已提交
298 299 300
## 使用实例

  SPI设备完整的使用示例如下所示,首先获取SPI设备句柄,然后配置SPI设备属性,接着调用读写接口进行数据传输,最后销毁SPI设备句柄。
D
duangavin123 已提交
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 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366

```
#include "hdf_log.h"
#include "spi_if.h"

void SpiTestSample(void)
{
    int32_t ret;
    struct SpiCfg cfg;                  /* SPI配置信息 */
    struct SpiDevInfo spiDevinfo;       /* SPI设备描述符 */
    DevHandle spiHandle = NULL; /* SPI设备句柄 */
    struct SpiMsg msg;                  /* 自定义传输的消息 */
    uint8_t rbuff[4] = { 0 };
    uint8_t wbuff[4] = { 0x12, 0x34, 0x56, 0x78 };
    uint8_t wbuff2[4] = { 0xa1, 0xb2, 0xc3, 0xd4 };

    spiDevinfo.busNum = 0;              /* SPI设备总线号 */
    spiDevinfo.csNum = 0;               /* SPI设备片选号 */
    spiHandle = SpiOpen(&spiDevinfo);   /* 根据spiDevinfo获取SPI设备句柄 */
    if (spiHandle == NULL) {
        HDF_LOGE("SpiOpen: failed\n");
        return;
    }
    /* 获取SPI设备属性 */
    ret = SpiGetCfg(spiHandle, &cfg);
    if (ret != 0) {
        HDF_LOGE("SpiGetCfg: failed, ret %d\n", ret);
        goto err;
    }
    cfg.maxSpeedHz = 115200;                /* 将最大时钟频率改为115200 */
    cfg.bitsPerWord = 8;                    /* 传输位宽改为8比特 */
    /* 配置SPI设备属性 */
    ret = SpiSetCfg(spiHandle, &cfg);
    if (ret != 0) {
        HDF_LOGE("SpiSetCfg: failed, ret %d\n", ret);
        goto err;
    }
    /* 向SPI设备写入指定长度的数据 */
    ret = SpiWrite(spiHandle, wbuff, 4);
    if (ret != 0) {
        HDF_LOGE("SpiWrite: failed, ret %d\n", ret);
        goto err;
    }
    /* 从SPI设备读取指定长度的数据 */
    ret = SpiRead(spiHandle, rbuff, 4);
    if (ret != 0) {
        HDF_LOGE("SpiRead: failed, ret %d\n", ret);
        goto err;
    }
    msg.wbuf = wbuff2;  /* 写入的数据 */
    msg.rbuf = rbuff;   /* 读取的数据 */
    msg.len = 4;        /* 读取写入数据的长度为4 */
    msg.csChange = 1;   /* 进行下一次传输前关闭片选 */
    msg.delayUs = 0;    /* 进行下一次传输前不进行延时 */
    msg.speed = 115200; /* 本次传输的速度 */
    /* 进行一次自定义传输,传输的msg个数为1 */
    ret = SpiTransfer(spiHandle, &msg, 1);
    if (ret != 0) {
        HDF_LOGE("SpiTransfer: failed, ret %d\n", ret);
        goto err;
    }
err:
    /* 销毁SPI设备句柄 */
    SpiClose(spiHandle);
}
```