SPI


SPI

概述

  • SPI是串行外设接口(Serial Peripheral Interface)的缩写,是一种高速的,全双工,同步的通信总线。

  • SPI是由Motorola公司开发,用于在主设备和从设备之间进行通信,常用于与闪存、实时时钟、传感器以及模数转换器等进行通信。

  • SPI以主从方式工作,通常有一个主设备和一个或者多个从设备。主设备和从设备之间一般用4根线相连,它们分别是:

    • SCLK – 时钟信号,由主设备产生;
    • MOSI – 主设备数据输出,从设备数据输入;
    • MISO – 主设备数据输入,从设备数据输出;
    • CS – 片选,从设备使能信号,由主设备控制。
  • 一个主设备和两个从设备的连接示意图如下所示,Device A和Device B共享主设备的SCLK、MISO和MOSI三根引脚,Device A的片选CS0连接主设备的CS0,Device B的片选CS1连接主设备的CS1。

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

    image

  • 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设备属性。

icon-note.gif 说明: 当前只支持主机模式,不支持从机模式。

接口说明

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

功能分类 接口名
SPI设备句柄获取释放接口 - SpiOpen:获取SPI设备句柄
- SpiClose:释放SPI设备句柄
SPI读写接口 - SpiRead:读取指定长度的数据
- SpiWrite:写入指定长度的数据
- SpiTransfer:SPI数据传输接口
SPI设备配置接口 - SpiSetCfg:根据指定参数,配置SPI设备
- SpiGetCfg:获取SPI设备配置参数

icon-note.gif 说明: 本文涉及的所有接口,仅限内核态使用,不支持在用户态使用。

使用指导

使用流程

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

图2 SPI使用流程图

image

获取SPI设备句柄

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

DevHandle SpiOpen(const struct SpiDevInfo *info); 
1

表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;
}
1
2
3
4
5
6
7
8
9
10
11

获取SPI设备属性

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

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

表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);
}
1
2
3
4
5
6

配置SPI设备属性

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

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

表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);
}
1
2
3
4
5
6
7
8
9
10

进行SPI通信

  • 向SPI设备写入指定长度的数据

    如果只向SPI设备写一次数据,则可以通过以下函数完成:

    int32_t SpiWrite(DevHandle handle, uint8_t *buf, uint32_t len);
    
    1

    表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);
    }
    
    1
    2
    3
    4
    5
    6
    7
  • 从SPI设备读取指定长度的数据

    如果只读取一次数据,则可以通过以下函数完成:

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

    表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);
    }
    
    1
    2
    3
    4
    5
    6
    7
  • 自定义传输

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

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

    表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);
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

销毁SPI设备句柄

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

void SpiClose(DevHandle handle);
1

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

表8 SpiClose参数描述

参数 参数描述
handle SPI设备句柄
SpiClose(spiHandle); /* 销毁SPI设备句柄 */
1

使用实例

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