SPI通信 #
一、SPI协议基础 #
1.1 SPI概述 #
SPI(Serial Peripheral Interface)是一种高速全双工同步串行通信协议。
text
┌─────────────────────────────────────────────────────────┐
│ SPI总线连接方式 │
├─────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ │
│ │ 主机 │ │
│ │ (Master)│ │
│ └────┬────┘ │
│ │ │
│ ┌─────┼─────┬─────┬─────┐ │
│ │ │ │ │ │ │
│ SCLK MOSI MISO CS0 CS1 │
│ │ │ │ │ │ │
│ ├─────┼─────┼─────┤ │ │
│ │ │ │ │ │ │
│ ┌─┴──┐ ┌┴──┐ ┌┴──┐ ┌┴──┐ ┌┴──┐ │
│ │SCLK│ │SI │ │SO │ │CS │ │CS │ │
│ │ │ │ │ │ │ │ │ │ │ │
│ │ 从机0 │ │ │ │ │ │ │ │ │ │
│ └────┘ └───┘ └───┘ └───┘ └───┘ │
│ │
│ SCLK: 时钟线 (主机输出) │
│ MOSI: 主机输出从机输入 │
│ MISO: 主机输入从机输出 │
│ CS: 片选信号 (低电平有效) │
│ │
└─────────────────────────────────────────────────────────┘
1.2 SPI特性 #
| 特性 | 说明 |
|---|---|
| 通信方式 | 全双工同步串行 |
| 线路数量 | 4线(SCLK、MOSI、MISO、CS) |
| 时钟极性 | CPOL (0或1) |
| 时钟相位 | CPHA (0或1) |
| 传输速率 | 可达数MHz甚至数十MHz |
| 多从机 | 通过多个CS线支持 |
1.3 SPI工作模式 #
| 模式 | CPOL | CPHA | 说明 |
|---|---|---|---|
| Mode 0 | 0 | 0 | 空闲低电平,第一边沿采样 |
| Mode 1 | 0 | 1 | 空闲低电平,第二边沿采样 |
| Mode 2 | 1 | 0 | 空闲高电平,第一边沿采样 |
| Mode 3 | 1 | 1 | 空闲高电平,第二边沿采样 |
text
Mode 0 (CPOL=0, CPHA=0):
___ ___ ___ ___
___| |___| |___| |___| |___ SCLK
^ ^ ^ ^
采样 采样 采样 采样
Mode 1 (CPOL=0, CPHA=1):
___ ___ ___ ___
___| |___| |___| |___| |___ SCLK
^ ^ ^ ^
采样 采样 采样 采样
1.4 Raspberry Pi SPI引脚 #
text
3.3V ──[01] [02]── 5V
GPIO2 ──[03] [04]── 5V
GPIO3 ──[05] [06]── GND
GPIO4 ──[07] [08]── GPIO14
GND ──[09] [10]── GPIO15
GPIO17 ──[11] [12]── GPIO18
GPIO27 ──[13] [14]── GND
GPIO22 ──[15] [16]── GPIO23
3.3V ──[17] [18]── GPIO24
GPIO10 ──[19] [20]── GND GPIO10 = MOSI
GPIO9 ──[21] [22]── GPIO25 GPIO9 = MISO
GPIO11 ──[23] [24]── GPIO8 GPIO11 = SCLK
GND ──[25] [26]── GPIO7 GPIO8 = CE0
GPIO7 = CE1
二、启用SPI接口 #
2.1 系统配置 #
bash
# 启用SPI接口
sudo raspi-config
# 选择 Interface Options -> SPI -> Enable
# 或手动启用
sudo nano /boot/config.txt
# 添加或取消注释
dtparam=spi=on
# 重启
sudo reboot
2.2 验证SPI #
bash
# 检查SPI设备
ls /dev/spi*
# 输出示例:
# /dev/spidev0.0 /dev/spidev0.1
# 安装SPI工具
sudo apt install python3-spidev -y
三、Pi4J SPI编程 #
3.1 基本配置 #
java
package com.example.spi;
import com.pi4j.Pi4J;
import com.pi4j.io.spi.Spi;
import com.pi4j.io.spi.SpiConfig;
import com.pi4j.io.spi.SpiMode;
public class SPIBasicDemo {
public static void main(String[] args) {
var pi4j = Pi4J.newAutoContext();
SpiConfig config = Spi.newConfigBuilder(pi4j)
.id("spi-device")
.name("SPI Device")
.bus(0)
.chipSelect(0)
.mode(SpiMode.MODE_0)
.baud(1000000)
.build();
Spi spi = pi4j.create(config);
System.out.println("SPI设备已配置");
System.out.println("总线: " + config.bus());
System.out.println("片选: " + config.chipSelect());
System.out.println("模式: " + config.mode());
System.out.println("波特率: " + config.baud());
pi4j.shutdown();
}
}
3.2 数据传输 #
java
package com.example.spi;
import com.pi4j.Pi4J;
import com.pi4j.io.spi.Spi;
import com.pi4j.io.spi.SpiConfig;
import com.pi4j.io.spi.SpiMode;
public class SPITransferDemo {
public static void main(String[] args) {
var pi4j = Pi4J.newAutoContext();
Spi spi = pi4j.create(Spi.newConfigBuilder(pi4j)
.id("spi-demo")
.name("SPI Demo")
.bus(0)
.chipSelect(0)
.mode(SpiMode.MODE_0)
.baud(1000000)
.build());
byte[] txData = {0x01, 0x02, 0x03, 0x04};
System.out.print("发送数据: ");
for (byte b : txData) {
System.out.printf("0x%02X ", b);
}
System.out.println();
byte[] rxData = spi.transfer(txData);
System.out.print("接收数据: ");
for (byte b : rxData) {
System.out.printf("0x%02X ", b);
}
System.out.println();
byte singleByte = spi.transfer((byte) 0xAA);
System.out.printf("单字节传输: 发送 0xAA, 接收 0x%02X%n", singleByte);
pi4j.shutdown();
}
}
3.3 多从机控制 #
java
package com.example.spi;
import com.pi4j.Pi4J;
import com.pi4j.io.spi.Spi;
import com.pi4j.io.spi.SpiConfig;
import com.pi4j.io.spi.SpiMode;
import java.util.HashMap;
import java.util.Map;
public class MultiSlaveSPI {
private final var pi4j;
private final Map<String, Spi> devices = new HashMap<>();
public MultiSlaveSPI() {
this.pi4j = Pi4J.newAutoContext();
}
public void addDevice(String name, int bus, int chipSelect,
SpiMode mode, int baud) {
SpiConfig config = Spi.newConfigBuilder(pi4j)
.id(name)
.name(name)
.bus(bus)
.chipSelect(chipSelect)
.mode(mode)
.baud(baud)
.build();
devices.put(name, pi4j.create(config));
}
public byte[] transfer(String deviceName, byte[] data) {
Spi device = devices.get(deviceName);
if (device != null) {
return device.transfer(data);
}
return null;
}
public void shutdown() {
pi4j.shutdown();
}
public static void main(String[] args) {
MultiSlaveSPI spi = new MultiSlaveSPI();
spi.addDevice("sensor1", 0, 0, SpiMode.MODE_0, 1000000);
spi.addDevice("sensor2", 0, 1, SpiMode.MODE_1, 2000000);
byte[] response1 = spi.transfer("sensor1", new byte[]{0x01, 0x02});
byte[] response2 = spi.transfer("sensor2", new byte[]{0x03, 0x04});
spi.shutdown();
}
}
四、实际设备驱动开发 #
4.1 MCP3008 ADC驱动 #
java
package com.example.spi.device;
import com.pi4j.Pi4J;
import com.pi4j.io.spi.Spi;
import com.pi4j.io.spi.SpiConfig;
import com.pi4j.io.spi.SpiMode;
public class MCP3008ADC {
private static final int NUM_CHANNELS = 8;
private static final int RESOLUTION = 1024;
private static final double VREF = 3.3;
private final Spi spi;
public MCP3008ADC(var pi4j, int bus, int chipSelect) {
this.spi = pi4j.create(Spi.newConfigBuilder(pi4j)
.id("mcp3008")
.name("MCP3008 ADC")
.bus(bus)
.chipSelect(chipSelect)
.mode(SpiMode.MODE_0)
.baud(1000000)
.build());
}
public int readRaw(int channel) {
if (channel < 0 || channel >= NUM_CHANNELS) {
throw new IllegalArgumentException("Invalid channel: " + channel);
}
byte[] txData = {
(byte) (0x01),
(byte) ((channel + 8) << 4),
0x00
};
byte[] rxData = spi.transfer(txData);
int result = ((rxData[1] & 0x03) << 8) | (rxData[2] & 0xFF);
return result;
}
public double readVoltage(int channel) {
int raw = readRaw(channel);
return (raw * VREF) / (RESOLUTION - 1);
}
public double readPercentage(int channel) {
int raw = readRaw(channel);
return (raw * 100.0) / (RESOLUTION - 1);
}
public int[] readAllChannels() {
int[] values = new int[NUM_CHANNELS];
for (int i = 0; i < NUM_CHANNELS; i++) {
values[i] = readRaw(i);
}
return values;
}
public static void main(String[] args) throws InterruptedException {
var pi4j = Pi4J.newAutoContext();
MCP3008ADC adc = new MCP3008ADC(pi4j, 0, 0);
System.out.println("MCP3008 ADC测试");
System.out.println("参考电压: " + VREF + "V");
System.out.println();
while (true) {
System.out.println("=== 通道读数 ===");
for (int i = 0; i < NUM_CHANNELS; i++) {
int raw = adc.readRaw(i);
double voltage = adc.readVoltage(i);
System.out.printf("通道%d: 原始值=%4d, 电压=%.3fV%n",
i, raw, voltage);
}
System.out.println();
Thread.sleep(1000);
}
}
}
4.2 NRF24L01无线模块驱动 #
java
package com.example.spi.device;
import com.pi4j.Pi4J;
import com.pi4j.io.spi.Spi;
import com.pi4j.io.spi.SpiConfig;
import com.pi4j.io.spi.SpiMode;
import com.pi4j.io.gpio.digital.*;
public class NRF24L01 {
private static final int REGISTER_CONFIG = 0x00;
private static final int REGISTER_EN_AA = 0x01;
private static final int REGISTER_EN_RXADDR = 0x02;
private static final int REGISTER_SETUP_AW = 0x03;
private static final int REGISTER_SETUP_RETR = 0x04;
private static final int REGISTER_RF_CH = 0x05;
private static final int REGISTER_RF_SETUP = 0x06;
private static final int REGISTER_STATUS = 0x07;
private static final int REGISTER_RX_ADDR_P0 = 0x0A;
private static final int REGISTER_TX_ADDR = 0x10;
private static final int REGISTER_RX_PW_P0 = 0x11;
private static final int REGISTER_FIFO_STATUS = 0x17;
private static final byte COMMAND_R_REGISTER = 0x00;
private static final byte COMMAND_W_REGISTER = 0x20;
private static final byte COMMAND_R_RX_PAYLOAD = 0x61;
private static final byte COMMAND_W_TX_PAYLOAD = (byte) 0xA0;
private static final byte COMMAND_FLUSH_TX = (byte) 0xE1;
private static final byte COMMAND_FLUSH_RX = (byte) 0xE2;
private static final byte COMMAND_NOP = (byte) 0xFF;
private final Spi spi;
private final DigitalOutput ce;
public NRF24L01(var pi4j, int spiBus, int spiCs, int cePin) {
this.spi = pi4j.create(Spi.newConfigBuilder(pi4j)
.id("nrf24l01")
.name("NRF24L01 Wireless")
.bus(spiBus)
.chipSelect(spiCs)
.mode(SpiMode.MODE_0)
.baud(8000000)
.build());
this.ce = pi4j.create(DigitalOutput.newConfigBuilder(pi4j)
.id("nrf-ce")
.name("NRF CE")
.address(cePin)
.shutdown(DigitalState.LOW)
.initial(DigitalState.LOW)
.provider("pigpio-digital-output"));
}
public void initialize() {
ce.low();
writeRegister(REGISTER_CONFIG, (byte) 0x0C);
writeRegister(REGISTER_EN_AA, (byte) 0x01);
writeRegister(REGISTER_EN_RXADDR, (byte) 0x01);
writeRegister(REGISTER_SETUP_AW, (byte) 0x03);
writeRegister(REGISTER_SETUP_RETR, (byte) 0x5F);
writeRegister(REGISTER_RF_CH, (byte) 0x4C);
writeRegister(REGISTER_RF_SETUP, (byte) 0x07);
flushTX();
flushRX();
writeRegister(REGISTER_STATUS, (byte) 0x70);
}
public void setTXAddress(byte[] address) {
writeRegisterMulti(REGISTER_TX_ADDR, address);
writeRegisterMulti(REGISTER_RX_ADDR_P0, address);
}
public void setRXAddress(int pipe, byte[] address) {
writeRegisterMulti(REGISTER_RX_ADDR_P0 + pipe, address);
}
public void setPayloadSize(int size) {
writeRegister(REGISTER_RX_PW_P0, (byte) size);
}
public void powerUpTX() {
byte config = readRegister(REGISTER_CONFIG);
config = (byte) ((config & ~0x01) | 0x02);
writeRegister(REGISTER_CONFIG, config);
ce.high();
}
public void powerUpRX() {
byte config = readRegister(REGISTER_CONFIG);
config = (byte) (config | 0x03);
writeRegister(REGISTER_CONFIG, config);
ce.high();
}
public void powerDown() {
ce.low();
byte config = readRegister(REGISTER_CONFIG);
config = (byte) (config & ~0x02);
writeRegister(REGISTER_CONFIG, config);
}
public boolean transmit(byte[] data) {
flushTX();
byte[] txData = new byte[data.length + 1];
txData[0] = COMMAND_W_TX_PAYLOAD;
System.arraycopy(data, 0, txData, 1, data.length);
spi.transfer(txData);
ce.high();
try { Thread.sleep(1); } catch (InterruptedException e) {}
ce.low();
byte status;
int timeout = 100;
do {
status = readRegister(REGISTER_STATUS);
try { Thread.sleep(1); } catch (InterruptedException e) {}
} while ((status & 0x30) == 0 && --timeout > 0);
writeRegister(REGISTER_STATUS, (byte) 0x30);
return (status & 0x20) != 0;
}
public byte[] receive(int length) {
byte status = readRegister(REGISTER_STATUS);
if ((status & 0x40) == 0) {
return null;
}
writeRegister(REGISTER_STATUS, (byte) 0x40);
byte[] txData = new byte[length + 1];
txData[0] = COMMAND_R_RX_PAYLOAD;
byte[] rxData = spi.transfer(txData);
byte[] payload = new byte[length];
System.arraycopy(rxData, 1, payload, 0, length);
return payload;
}
public boolean dataAvailable() {
byte status = readRegister(REGISTER_STATUS);
return (status & 0x40) != 0;
}
private byte readRegister(int register) {
byte[] txData = {(byte) (COMMAND_R_REGISTER | register), COMMAND_NOP};
byte[] rxData = spi.transfer(txData);
return rxData[1];
}
private void writeRegister(int register, byte value) {
byte[] txData = {(byte) (COMMAND_W_REGISTER | register), value};
spi.transfer(txData);
}
private void writeRegisterMulti(int register, byte[] data) {
byte[] txData = new byte[data.length + 1];
txData[0] = (byte) (COMMAND_W_REGISTER | register);
System.arraycopy(data, 0, txData, 1, data.length);
spi.transfer(txData);
}
private void flushTX() {
spi.transfer(new byte[]{COMMAND_FLUSH_TX});
}
private void flushRX() {
spi.transfer(new byte[]{COMMAND_FLUSH_RX});
}
public static void main(String[] args) throws InterruptedException {
var pi4j = Pi4J.newAutoContext();
NRF24L01 nrf = new NRF24L01(pi4j, 0, 0, 25);
nrf.initialize();
byte[] address = {0xE7, 0xE7, 0xE7, 0xE7, 0xE7};
nrf.setTXAddress(address);
nrf.setPayloadSize(32);
System.out.println("NRF24L01测试");
nrf.powerUpTX();
byte[] message = "Hello NRF24L01!".getBytes();
boolean success = nrf.transmit(message);
System.out.println("发送结果: " + (success ? "成功" : "失败"));
pi4j.shutdown();
}
}
4.3 SD卡读写 #
java
package com.example.spi.device;
import com.pi4j.Pi4J;
import com.pi4j.io.spi.Spi;
import com.pi4j.io.spi.SpiConfig;
import com.pi4j.io.spi.SpiMode;
public class SDCardDriver {
private static final byte CMD0 = 0x40;
private static final byte CMD8 = 0x48;
private static final byte CMD55 = 0x77;
private static final byte CMD41 = 0x69;
private static final byte CMD17 = 0x51;
private static final byte CMD24 = 0x58;
private static final byte R1_IDLE = 0x01;
private static final byte R1_READY = 0x00;
private static final byte DATA_START = (byte) 0xFE;
private final Spi spi;
private boolean initialized = false;
public SDCardDriver(var pi4j, int bus, int chipSelect) {
this.spi = pi4j.create(Spi.newConfigBuilder(pi4j)
.id("sdcard")
.name("SD Card")
.bus(bus)
.chipSelect(chipSelect)
.mode(SpiMode.MODE_0)
.baud(500000)
.build());
}
public boolean initialize() {
for (int i = 0; i < 10; i++) {
spi.transfer((byte) 0xFF);
}
byte response = sendCommand(CMD0, 0);
if (response != R1_IDLE) {
return false;
}
response = sendCommand(CMD8, 0x000001AA);
long timeout = System.currentTimeMillis() + 1000;
while (System.currentTimeMillis() < timeout) {
response = sendCommand(CMD55, 0);
response = sendCommand(CMD41, 0x40000000);
if (response == R1_READY) {
initialized = true;
return true;
}
try { Thread.sleep(10); } catch (InterruptedException e) {}
}
return false;
}
public byte[] readBlock(long blockAddress) {
if (!initialized) return null;
byte response = sendCommand(CMD17, blockAddress);
if (response != R1_READY) {
return null;
}
long timeout = System.currentTimeMillis() + 1000;
byte token;
do {
token = spi.transfer((byte) 0xFF);
if (System.currentTimeMillis() > timeout) {
return null;
}
} while (token != DATA_START);
byte[] data = new byte[512];
for (int i = 0; i < 512; i++) {
data[i] = spi.transfer((byte) 0xFF);
}
spi.transfer((byte) 0xFF);
spi.transfer((byte) 0xFF);
return data;
}
public boolean writeBlock(long blockAddress, byte[] data) {
if (!initialized || data.length != 512) return false;
byte response = sendCommand(CMD24, blockAddress);
if (response != R1_READY) {
return false;
}
spi.transfer(DATA_START);
for (byte b : data) {
spi.transfer(b);
}
spi.transfer((byte) 0xFF);
spi.transfer((byte) 0xFF);
byte status = spi.transfer((byte) 0xFF);
return (status & 0x0F) == 0x05;
}
private byte sendCommand(byte command, long argument) {
spi.transfer((byte) 0xFF);
spi.transfer(command);
spi.transfer((byte) (argument >> 24));
spi.transfer((byte) (argument >> 16));
spi.transfer((byte) (argument >> 8));
spi.transfer((byte) argument);
byte crc = (command == CMD0) ? (byte) 0x95 : (byte) 0x01;
spi.transfer(crc);
byte response;
int attempts = 0;
do {
response = spi.transfer((byte) 0xFF);
attempts++;
} while ((response & 0x80) != 0 && attempts < 10);
return response;
}
public static void main(String[] args) {
var pi4j = Pi4J.newAutoContext();
SDCardDriver sd = new SDCardDriver(pi4j, 0, 0);
System.out.println("初始化SD卡...");
if (sd.initialize()) {
System.out.println("SD卡初始化成功");
byte[] data = sd.readBlock(0);
if (data != null) {
System.out.println("读取块0成功,前16字节:");
for (int i = 0; i < 16; i++) {
System.out.printf("%02X ", data[i]);
}
System.out.println();
}
} else {
System.out.println("SD卡初始化失败");
}
pi4j.shutdown();
}
}
五、SPI工具类封装 #
java
package com.example.spi;
import com.pi4j.Pi4J;
import com.pi4j.io.spi.Spi;
import com.pi4j.io.spi.SpiConfig;
import com.pi4j.io.spi.SpiMode;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class SPIUtils {
public static class SPIDevice {
private final Spi spi;
public SPIDevice(Spi spi) {
this.spi = spi;
}
public byte transferByte(byte data) {
return spi.transfer(data);
}
public byte[] transferBytes(byte[] data) {
return spi.transfer(data);
}
public short transferShort(short data, ByteOrder order) {
ByteBuffer txBuffer = ByteBuffer.allocate(2).order(order);
txBuffer.putShort(data);
byte[] rxData = spi.transfer(txBuffer.array());
return ByteBuffer.wrap(rxData).order(order).getShort();
}
public int transferInt(int data, ByteOrder order) {
ByteBuffer txBuffer = ByteBuffer.allocate(4).order(order);
txBuffer.putInt(data);
byte[] rxData = spi.transfer(txBuffer.array());
return ByteBuffer.wrap(rxData).order(order).getInt();
}
public void writeCommand(byte command, byte[] data) {
byte[] txData = new byte[data.length + 1];
txData[0] = command;
System.arraycopy(data, 0, txData, 1, data.length);
spi.transfer(txData);
}
public byte[] readCommand(byte command, int length) {
byte[] txData = new byte[length + 1];
txData[0] = command;
byte[] rxData = spi.transfer(txData);
byte[] result = new byte[length];
System.arraycopy(rxData, 1, result, 0, length);
return result;
}
}
public static SPIDevice createDevice(var pi4j, int bus, int chipSelect,
SpiMode mode, int baud) {
SpiConfig config = Spi.newConfigBuilder(pi4j)
.id("spi-" + bus + "-" + chipSelect)
.name("SPI Device")
.bus(bus)
.chipSelect(chipSelect)
.mode(mode)
.baud(baud)
.build();
return new SPIDevice(pi4j.create(config));
}
}
六、SPI调试技巧 #
6.1 常用调试方法 #
bash
# 检查SPI设备
ls -la /dev/spi*
# 使用Python测试SPI
python3 -c "
import spidev
spi = spidev.SpiDev()
spi.open(0, 0)
spi.max_speed_hz = 1000000
data = spi.xfer([0x01, 0x02, 0x03])
print('Received:', [hex(b) for b in data])
spi.close()
"
6.2 常见问题排查 #
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 通信失败 | 模式不匹配 | 检查设备SPI模式 |
| 数据错误 | 时序问题 | 降低波特率 |
| 无法检测 | 连接问题 | 检查接线、片选信号 |
| 丢包 | 缓冲区溢出 | 增加延时或使用DMA |
七、总结 #
SPI通信要点:
- 工作模式:理解CPOL和CPHA配置
- 全双工通信:同时发送和接收数据
- 多从机支持:通过片选信号控制
- 高速传输:适合大数据量传输场景
- 设备驱动:理解设备命令格式和时序要求
下一章我们将学习UART串口通信,实现设备间的异步数据传输。
最后更新:2026-03-27