5. SPI通讯

在前面章节里,我们给大家讲到了I2C通讯协议使用相关的内容。 在本章里,我们接着为大家介绍另一种通讯协议的使用,SPI通讯协议。 SPI通讯协议同样也是在电子器件通讯中常见的一种通讯协议。 不过SPI通讯协议的通讯速率要更高,而且它采用的是全双工的通讯方式。 所以我们常常能在对通信速率有要求的电子器件上,发现SPI通讯协议下规定的接口。 比如一些 小尺寸的LCD屏幕AD转换芯片nRF系列射频模块SPI-FLASH 等等等等。

关于SPI协议的具体内容,我们在此不作过多探究,大家可以自行搜索SPI通讯协议的相关知识学习。

本章中,我们的重点是使用前面介绍的 python-peripheryAdafruit Blinka 两个Python库, 来使用到我们鲁班猫板卡上的SPI主机。

5.1. SPI实验

一般情况下,我们可以通过需要一个具体的通讯对象来完成本节实验。 但是由于SPI通讯协议支持全双工的方式进行通讯,所以我们可以通过回环测试来进行实验验证。

在实验中,我们将SPI接口的MOSI、MISO接口连接在一起即可。

当然大家在学习完回环实验之后,自然也可以通过鲁班猫板卡上的SPI资源去控制其他SPI接口的硬件。

5.2. 实验准备

在开始实验前,需要做一些准备。

5.2.1. 添加SPI资源

在板卡上的部分资源可能默认未被开启,在使用前请根据需要修改 /boot/uEnv.txt 文件, 可添加对应设备树插件的加载,重启系统,以在系统中添加对应资源。

如本节实验中,通常情况下在鲁班猫系统中默认使能 SPI 的功能, 如大家发现SPI的设备树插件未加载,请做相应修改。

同时如果大家在使用中发现SPI功能引脚被复用或有冲突,请要将对应占用SPI引脚资源的设备树插件取消加载, 否则SPI资源可能无法使用。若已开启对应资源,忽略以下步骤。

如笔者使用的鲁班猫i.MX6ULL MINI板卡,ec-spi3与uart2资源就互相冲突。

方法参考如下:

broken

添加SPI设备树插件,以使系统支持SPI功能,如下:

broken
# 以鲁班猫i.MX6ULL MINI板卡SPI设备树插件内容为例:
dtoverlay=/usr/lib/linux-image-4.19.35-imx6/overlays/imx-fire-ecspi3.dtbo

如若运行代码时出现“Device or resource busy”或者运行代码卡死等等现象, 请按上述情况检查并按上述步骤操作。

如出现 Permission denied 或类似字样,请注意用户权限,大部分操作硬件外设的功能,几乎都需要root用户权限,简单的解决方案是在执行语句前加入sudo或以root用户运行程序。

# 在终端中输入如下命令,可以查看到SPI资源:
ls /dev/spi*

板卡上的SPI资源示例,仅供参考:

broken

5.2.2. 硬件连接

注:硬件连接小节,需要大家根据自身开发环境等情况对本章内容进行参考调整。

本章中,我们通过上述Python库,编写对SPI主机进行使用的测试代码, 并通过SPI回环来进行代码正确性测试。

笔者硬件连接如图,仅供参考:

broken

回环连接如上图示。

5.3. 方式一:使用python-periphery

python-periphery 库支持的SPI功能是基于Linux的SPI系统实现的,所以要想利用该库使用到SPI的功能, 需要板卡提供支持。像鲁班猫板卡,就可以完美使用 python-periphery 库SPI通讯功能。 这样这样一来,就不需要我们在软件层面上利用GPIO模拟SPI主机功能了。

关于SPI子系统相关内容,参考 《Linux SPI通讯》

5.3.1. 安装 python-periphery

重要

如在前面小节中安装了此库,务必跳过此操作。

python-periphery 的安装方式如下:

# 在板卡使用如下命令安装
sudo pip3 install python-periphery

5.3.2. periphery使用SPI功能

使用 python-periphery 库进行SPI功能使用的示例代码如下:

配套代码 io/periphery/spi_test.py文件内容
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
""" periphery spi 测试 """
from periphery import SPI

# 待发送数据列表
data_out = [0xAA, 0xBB, 0xCC, 0xDD]

try:
    # 申请SPI资源,打开 spidev2.0 控制器,配置SPI主机为工作模式0、工作速率为1MHz
    spi = SPI("/dev/spidev2.0", 0, 1000000)

    # 发送数据,同时接收数据到data_in列表中
    data_in = spi.transfer(data_out)

    # 打印发送的数据内容
    print("发送的数据: [0x{:02x}, 0x{:02x}, 0x{:02x}, 0x{:02x}]".format(*data_out))
    print("接收到的数据: [0x{:02x}, 0x{:02x}, 0x{:02x}, 0x{:02x}]".format(*data_in))
finally:
    # 关闭申请的SPI资源
    spi.close()

代码说明:

  • 第5行,申请SPI资源,占用SPI3主机,配置对应的工作模式

  • 第9行,构造准备发送的消息列表

  • 第12行,开启SPI传输,因为SPI为全双工通讯方式,发送数据的同时也可接收数据

  • 第19行,释放SPI资源,释放SPI3主机

5.3.2.1. 实验操作

示例代码使用LubanCat i.MX6ULL MINI板卡,操作如下:

# 确认已安装python-periphery包
# 确认使能了SPI设备树插件

# 在板卡spi_test.py所在的目录执行如下命令
python3 spi_test.py

# 可看到终端打印出的接受到的数据与发送出去的一致

由于在硬件连接时,已经将SPI接口的MOSI、MISO接口连接, 所以SPI控制器发送出数据的同时也会将数据接收回来,这就是回环测试。

5.4. 方式二:使用Adafruit Blinka

Adafruit Blinkapython-periphery 功能类似,SPI通讯也是基于Linux的SPI子系统实现的。 只是在代码上的用法上有较大差异。

5.4.1. 安装Adafruit Blinka

重要

如在前面小节中安装了此库,务必跳过此操作。

Adafruit Blinka 的安装方式如下:

# 在板卡使用如下命令安装
sudo apt -y install python3-libgpiod python-periphery # 注:如已安装,忽略此步
sudo pip3 install Adafruit-Blinka

5.4.2. Adafruit-Blinka使用SPI主机

使用这个 Adafruit Blinka 进行输入输出的示例代码如下:

配套代码 io/blinka/spi_test.py文件内容
 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
""" blinka spi 测试 """
import busio
from board import *

# 数据发送、接受缓冲区
OutBuffer = [0xAA, 0xBB, 0xCC, 0xDD]
InBuffer = bytearray(4)

try:
    # 申请spi资源
    spi = busio.SPI(SCLK, MOSI, MISO)

    # 配置时先锁定SPI
    spi.try_lock()
    # 配置SPI主机工作速率为1MHz、时钟极性为0、时钟相位为0、单个数据位数为8位
    spi.configure(1000000, 0, 0, 8)
    # 配置操作完成解锁
    spi.unlock()

    # SPI通讯,发送的同时也进行读取,发送的数据存放在OutBuffer,读取的数据存放在InBuffer
    spi.write_readinto(OutBuffer, InBuffer)

    print(
        "(write_readinto)接收到的数据: [0x{:02x}, 0x{:02x}, 0x{:02x}, 0x{:02x}]".format(
            *InBuffer
        )
    )

    # SPI通讯,只写使用方法演示
    spi.write(OutBuffer)
    print(
        "(OutBuffer)发送的数据: [0x{:02x}, 0x{:02x}, 0x{:02x}, 0x{:02x}]".format(*OutBuffer)
    )

    InBuffer = [0, 0, 0, 0]
    # SPI通讯,只读使用方法演示
    spi.readinto(InBuffer)
    print(
        "(readinto)接收到的数据: [0x{:02x}, 0x{:02x}, 0x{:02x}, 0x{:02x}]".format(*InBuffer)
    )
finally:
    # 释放spi资源
    spi.deinit()

代码说明:

  • 第11行,申请SPI资源,占用SPI3主机

  • 第14~18行,配置SPI3主机的工作模式,在配置前需要先锁定资源

  • 第21行,使用SPI3主机发送字节数组OutBuffer里的数据,同时读取数据到InBuffer

  • 第30行,使用SPI3仅发送数据,不读取

  • 第37行,使用SPI3仅读取数据,不发送

Adafruit Blinka 库未提供SPI对象的使用示例,相关的API功能大家可以参考: CircuitPython SPI 下给出的介绍。