SPI的原理
SPI(Serial Peripheral Interface)是一种同步的串行通信协议,它允许在单片机和外设之间高速地传输数据。SPI协议主要有以下特点:
采用全双工通信模式,同时支持主从模式(Master/Slave)和多主模式。
通信时使用四根线:MISO(Master In Slave Out,主输入从输出),MOSI(Master Output Slave In,主输出从输入),SCLK(Serial Clock,时钟线)和SS(Slave Select,片选线)。其中,MISO、MOSI和SCLK分别连接到主控制器和外设,而SS则是由主控制器来控制的,用于选择要通信的被动器件。
SPI采用传输字节的方式进行通信,即每次传输一个字节。可以通过扩展传输协议,操作多个字节的数据。
主设备产生时钟,数据在时钟上升沿或下降沿时采样,可以调整时钟极性和相位。传输数据的最大速率取决于通信双方的具体实现和电路特性。
树莓派的SPI引脚
树莓派的GPIO引脚中有多个可用于SPI通信的引脚,具体如下:
-
MOSI(Master Output Slave Input),BCM GPIO 10 (pin 19)
-
MISO (Master Input Slave Output),BCM GPIO 9 (pin 21)
-
SCLK(Serial Clock),BCM GPIO 11 (pin 23)
-
SS(Slave Select),BCM GPIO 8, BCM GPIO 7, BCM GPIO 18, BCM GPIO 17 (分别对应pin24、pin26、pin12、pin11)
需要与其他设备进行SPI通信时,需将这些引脚连接到目标设备的对应引脚上。一般来说,连接方式包括以下几个步骤:
将主设备的MOSI引脚连接到从设备的MISO引脚上,将主设备的MISO引脚连接到从设备的MOSI引脚上。
将主设备的SCLK引脚连接到从设备的SCLK引脚上。
将主设备的SS引脚与单独的CS引脚或多个可编程的GPIO引脚连接起来,并组成一个合适的接口。
按照硬件和软件规定的协议进行SPI通信。
案例:在树莓派上连接 MCP3008 8通道ADC进行SPI通信
方式一:用spidev库
import spidev
# 打开SPI设备连接
spi = spidev.SpiDev()
spi.open(0, 0) # 设备编号为0,CE0引脚作为SS
# 定义SPI数据传输参数,其中CPOL=0、CPHA=1表示SPI协议的模式0
spi.max_speed_hz = 1350000
spi.mode = 0b01
# 获取指定通道上的原始ADC值
def read_adc(channel):
adc = spi.xfer2([1, (8 + channel) << 4, 0])
data = ((adc[1] & 3) << 8) + adc[2]
return data
# 测试读取第一个通道的原始ADC值
print(read_adc(0))
spi.close() # 关闭SPI设备连接
方式二:通过RPi.GPIO库来直接操作树莓派的GPIO引脚进行SPI通信
在软件SPI模式下,需要通过设置好MOSI(Master Out Slave In)、MISO(Master In Slave Out)、SCLK(时钟)和CE(片选)4个GPIO引脚的输入输出状态,来完成数据的发送和接收过程。
对于发送数据来说,需要按照SPI协议的时序,先拉低CE引脚,确定转换速率后,以每个位一个时钟的速度,通过MOSI引脚发送相应的比特数据给外设,最终拉高CE引脚,结束一次数据传输。
而对于接收数据来说,同样需要按照SPI协议的时序,先拉低CE引脚,确定转换速率后,以每个位一个时钟的速度,通过MOSI引脚向外设发送指令以获取数据,并以同样的速度通过MISO引脚接收返回的比特数据,最终拉高CE引脚,完成一次数据传输。
import RPi.GPIO as GPIO
import time
# 定义每个GPIO引脚对应的编号(BCM编码)
MOSI = 10 # 主设备数据输出 (Master Out Slave In)
MISO = 9 # 主设备数据输入 (Master In Slave Out)
SCLK = 11 # SPI时钟信号
SS = 8 # 从设备选择信号(Slave Select)
# 初始化GPIO引脚状态
GPIO.setmode(GPIO.BCM)
GPIO.setup(MOSI, GPIO.OUT)
GPIO.setup(MISO, GPIO.IN)
GPIO.setup(SCLK, GPIO.OUT)
GPIO.setup(SS, GPIO.OUT)
# 获取指定通道上的原始ADC值
def read_adc(channel):
# 将SS拉低
GPIO.output(SS, GPIO.LOW)
time.sleep(0.0001)
# 发送读取指令,并获取返回的原始ADC值
command = channel | 0x18 # 将通道转换命令整合到5位,格式为:1 1 start channel SGL/DIF ODD/Sign MSBF
send_bits(command, 5)
adc_value = receive_bits(12)
# 将SS拉高
GPIO.output(SS, GPIO.HIGH)
time.sleep(0.0001)
return adc_value
# 向从设备发送数据位序列
def send_bits(bits, length):
for bit in range(length - 1, -1, -1):
GPIO.output(MOSI, (bits & (1 << bit)) > 0)
toggle_clock()
# 接收从设备返回的数据位序列
def receive_bits(length):
bits = 0
for bit in range(length - 1, -1, -1):
# 首先拉高SCLK以便让对方发出数据
GPIO.output(SCLK, GPIO.HIGH)
time.sleep(0.0001)
# 然后将MISO的电平读取到bits变量中
if GPIO.input(MISO):
bits |= (1 << bit)
# 最后拉低SCLK准备下一次数据传输
GPIO.output(SCLK, GPIO.LOW)
time.sleep(0.0001)
return bits
# 求解第一个通道(CH0)上的原始ADC值
print(read_adc(0))
# 关闭GPIO引脚状态
GPIO.cleanup()