# SPI
- [概述](#section193356154511)
- [接口说明](#section232141411476)
- [使用指导](#section71363452477)
- [使用流程](#section32846814820)
- [获取SPI设备句柄](#section1927265711481)
- [获取SPI设备属性](#section541133418493)
- [配置SPI设备属性](#section7870106145010)
- [进行SPI通信](#section13324155195013)
- [销毁SPI设备句柄](#section19661632135117)
- [使用实例](#section06541058155120)
## 概述
- SPI是串行外设接口(Serial Peripheral Interface)的缩写,是一种高速的,全双工,同步的通信总线。
- SPI是由Motorola公司开发,用于在主设备和从设备之间进行通信,常用于与闪存、实时时钟、传感器以及模数转换器等进行通信。
- SPI以主从方式工作,通常有一个主设备和一个或者多个从设备。主设备和从设备之间一般用4根线相连,它们分别是:
- SCLK – 时钟信号,由主设备产生;
- MOSI – 主设备数据输出,从设备数据输入;
- MISO – 主设备数据输入,从设备数据输出;
- CS – 片选,从设备使能信号,由主设备控制。
- 一个主设备和两个从设备的连接示意图如[图1](#fig15227181812587)所示,Device A和Device B共享主设备的SCLK、MISO和MOSI三根引脚,Device A的片选CS0连接主设备的CS0,Device B的片选CS1连接主设备的CS1。
**图 1** SPI主从设备连接示意图。
![](figure/zh-cn_image_0000001123742254.png)
- 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设备句柄获取和释放。
- SPI读写: 从SPI设备读取或写入指定长度数据。
- SPI自定义传输:通过消息传输结构体执行任意读写组合过程。
- SPI设备配置:获取和设置SPI设备属性。
>![](../public_sys-resources/icon-note.gif) **说明:**
>当前只支持主机模式,不支持从机模式。
### 接口说明
**表 1** SPI驱动API接口功能介绍
功能分类
|
接口名
|
描述
|
SPI设备句柄获取释放接口
|
SpiOpen
|
获取SPI设备句柄
|
SpiClose
|
释放SPI设备句柄
|
SPI读写接口
|
SpiRead
|
读取指定长度的数据
|
SpiWrite
|
写入指定长度的数据
|
SpiTransfer
|
SPI数据传输接口
|
SPI设备配置接口
|
SpiSetCfg
|
根据指定参数,配置SPI设备
|
SpiGetCfg
|
获取SPI设备配置参数
|
>![](../public_sys-resources/icon-note.gif) **说明:**
>本文涉及的所有接口,仅限内核态使用,不支持在用户态使用。
## 使用指导
### 使用流程
使用SPI的一般流程如[图2](#fig23885455594)所示。
**图 2** SPI使用流程图
![](figure/zh-cn_image_0000001123703482.png)
### 获取SPI设备句柄
在使用SPI进行通信时,首先要调用SpiOpen获取SPI设备句柄,该函数会返回指定总线号和片选号的SPI设备句柄。
DevHandle SpiOpen\(const struct SpiDevInfo \*info\);
**表 2** SpiOpen参数和返回值描述
参数
|
参数描述
|
info
|
SPI设备描述符
|
返回值
|
返回值描述
|
NULL
|
获取SPI设备句柄失败
|
设备句柄
|
对应的SPI设备句柄
|
假设系统中的SPI设备总线号为0,片选号为0,获取该SPI设备句柄的示例如下:
```
struct SpiDevInfo spiDevinfo; /* SPI设备描述符 */
DevHandle spiHandle = NULL; /* SPI设备句柄 */
spiDevinfo.busNum = 0; /* SPI设备总线号 */
spiDevinfo.csNum = 0; /* SPI设备片选号 */
/* 获取SPI设备句柄 */
spiHandle = SpiOpen(&spiDevinfo);
if (spiHandle == NULL) {
HDF_LOGE("SpiOpen: failed\n");
return;
}
```
### 获取SPI设备属性
在获取到SPI设备句柄之后,需要配置SPI设备属性。配置SPI设备属性之前,可以先获取SPI设备属性,获取SPI设备属性的函数如下所示:
int32\_t SpiGetCfg\(DevHandle handle, struct SpiCfg \*cfg\);
**表 3** SpiGetCfg参数和返回值描述
参数
|
参数描述
|
handle
|
SPI设备句柄
|
cfg
|
SPI设备配置参数
|
返回值
|
返回值描述
|
0
|
获取配置成功
|
负数
|
获取配置失败
|
```
int32_t ret;
struct SpiCfg cfg = {0}; /* SPI配置信息*/
ret = SpiGetCfg(spiHandle, &cfg); /* 获取SPI设备属性 */
if (ret != 0) {
HDF_LOGE("SpiGetCfg: failed, ret %d\n", ret);
}
```
### 配置SPI设备属性
在获取到SPI设备句柄之后,需要配置SPI设备属性,配置SPI设备属性的函数如下所示:
int32\_t SpiSetCfg\(DevHandle handle, struct SpiCfg \*cfg\);
**表 4** SpiSetCfg参数和返回值描述
参数
|
参数描述
|
handle
|
SPI设备句柄
|
cfg
|
SPI设备配置参数
|
返回值
|
返回值描述
|
0
|
配置成功
|
负数
|
配置失败
|
```
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);
}
```
### 进行SPI通信
- 向SPI设备写入指定长度的数据
如果只向SPI设备写一次数据,则可以通过以下函数完成:
int32\_t SpiWrite\(DevHandle handle, uint8\_t \*buf, uint32\_t len\);
**表 5** SpiWrite参数和返回值描述
参数
|
参数描述
|
handle
|
SPI设备句柄
|
buf
|
待写入数据的指针
|
len
|
待写入的数据长度
|
返回值
|
返回值描述
|
0
|
写入成功
|
负数
|
写入失败
|
```
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设备读取指定长度的数据
如果只读取一次数据,则可以通过以下函数完成:
int32\_t SpiRead\(DevHandle handle, uint8\_t \*buf, uint32\_t len\);
**表 6** SpiRead参数和返回值描述
参数
|
参数描述
|
handle
|
SPI设备句柄
|
buf
|
待读取数据的指针
|
len
|
待读取的数据长度
|
返回值
|
返回值描述
|
0
|
读取成功
|
负数
|
读取失败
|
```
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参数和返回值描述
参数
|
参数描述
|
handle
|
SPI设备句柄
|
msgs
|
待传输数据的数组
|
count
|
msgs数组长度
|
返回值
|
返回值描述
|
0
|
执行成功
|
负数
|
执行失败
|
```
int32_t ret;
uint8_t wbuff[1] = {0x12};
uint8_t rbuff[1] = {0};
struct SpiMsg msg; /* 自定义传输的消息*/
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设备句柄
SPI通信完成之后,需要销毁SPI设备句柄,销毁SPI设备句柄的函数如下所示:
void SpiClose\(DevHandle handle\);
该函数会释放掉申请的资源。
**表 8** SpiClose参数描述
```
SpiClose(spiHandle); /* 销毁SPI设备句柄 */
```
## 使用实例
SPI设备完整的使用示例如下所示,首先获取SPI设备句柄,然后配置SPI设备属性,接着调用读写接口进行数据传输,最后销毁SPI设备句柄。
```
#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);
}
```