SPI 使用说明#
概述#
K230 平台包含三个 SPI 控制器:
SPI 控制器 |
基地址 |
类型 |
中断号 |
|---|---|---|---|
spi0 |
0x91584000 |
OSPI |
146-154 |
spi1 |
0x91582000 |
QSPI 0 |
155-163 |
spi2 |
0x91583000 |
QSPI 1 |
164-172 |
SPI 控制器基于synopsys DesignWare APB SSI 核心
注意:所有 SPI 控制器默认状态均为
disabled,需在设备树中启用后方可使用。
设备树配置#
在设备树中启用 SPI(以 spi0 为例):
//arch/riscv/boot/dts/canaan/k230-canmv-01studio-lcd.dts
aliases {
spi0 = &spi0;
};
&iomux {
ospi_pins: ospi_pins {
pin_clk_mo_mi{
pins = K230_IO15,K230_IO16,K230_IO17;
function = "alt1";
};
pin_cs{
pins = K230_IO14;
function = "alt1";
bias-pull-up;
};
};
};
&spi0
{
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&ospi_pins>;
spidev0: spidev@0 {
compatible = "rohm,dh2228fv";
reg = <0>;
spi-max-frequency = <4000000>;
status = "okay";
};
};
用户态工具使用#
K230 SDK buildroot 提供了用户空间 SPI 工具:
spi-config: 查询和设置 SPI 配置
spi-pipe: 通过 SPI 进行数据传输
BR2_PACKAGE_SPI_TOOLS:
This package contains some simple command line tools to help
using Linux spidev devices.
https://github.com/cpb-/spi-tools
Symbol: BR2_PACKAGE_SPI_TOOLS [=y]
Type : bool
Prompt: spi-tools
Location:
-> Target packages
-> Hardware handling
Defined at package/spi-tools/Config.in:1
使用示例
# 查询当前配置
spi-config -d /dev/spidev0.0 -q
# 输出: /dev/spidev0.0: mode=0, lsb=0, bits=8, speed=500000
# 设置 SPI 模式为 mode 3
spi-config -d /dev/spidev0.0 -m 3
# 读取spi nor芯片id,指定 blocksize 为 4 字节
echo -n -e '\x9F\x00\x00\x00' | spi-pipe -d /dev/spidev0.0 -s 1000000 -b 4 | hexdump -C
测试例子
#把收发环回,测试读写
spi-config -d /dev/spidev0.0 -m 0 -s 10000000
loop=0
while true
do
dd if=/dev/random of=test_pattern bs=4096 count=1 2>/dev/null
spi-pipe -d /dev/spidev0.0 -b 8192 < test_pattern > readback
cmp test_pattern readback
if [ $? -ne 0 ]
then
echo "loop:$loop read error"
exit -1
fi
let loop++
echo $loop
# sleep 0.1~
done
C 语言示例#
SPI NOR Flash 读写例子,先读取id,后进行读写测试;
// 先读取id,后进行读写测试;
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>
#include <stdint.h>
#define SPI_DEVICE "/dev/spidev0.0"
#define SPI_FREQ 1000000
// SPI NOR Flash 命令
#define CMD_JEDEC_ID 0x9F
#define CMD_READ_STATUS 0x05
#define CMD_WRITE_ENABLE 0x06
#define CMD_PAGE_PROGRAM 0x02
#define CMD_READ_DATA 0x03
// 等待 Flash 就绪
int spi_nor_wait_ready(int fd, int timeout)
{
uint8_t tx[] = {CMD_READ_STATUS};
uint8_t rx[2] = {0};
int i;
for (i = 0; i < timeout; i++) {
struct spi_ioc_transfer tr = {
.tx_buf = (unsigned long)tx,
.rx_buf = (unsigned long)rx,
.len = 2,
.speed_hz = SPI_FREQ,
.bits_per_word = 8,
};
if (ioctl(fd, SPI_IOC_MESSAGE(1), &tr) < 0) {
return -1;
}
if (!(rx[1] & 0x01)) { // WIP bit cleared
return 0;
}
usleep(1000); // 1ms delay
}
return -1; // Timeout
}
int spi_nor_read_jedec_id(int fd, uint8_t *id)
{
uint8_t tx[] = {CMD_JEDEC_ID, 0x00, 0x00, 0x00};
uint8_t rx[4] = {0};
struct spi_ioc_transfer tr = {
.tx_buf = (unsigned long)tx,
.rx_buf = (unsigned long)rx,
.len = 4,
.speed_hz = SPI_FREQ,
.bits_per_word = 8,
};
if (ioctl(fd, SPI_IOC_MESSAGE(1), &tr) < 0) {
return -1;
}
// ID 从 rx[1] 开始 (3 字节)
id[0] = rx[1];
id[1] = rx[2];
id[2] = rx[3];
return 0;
}
int spi_nor_read_status(int fd, uint8_t *status)
{
uint8_t tx[] = {CMD_READ_STATUS};
uint8_t rx[2] = {0};
struct spi_ioc_transfer tr = {
.tx_buf = (unsigned long)tx,
.rx_buf = (unsigned long)rx,
.len = 2,
.speed_hz = SPI_FREQ,
.bits_per_word = 8,
};
if (ioctl(fd, SPI_IOC_MESSAGE(1), &tr) < 0) {
return -1;
}
*status = rx[1];
return 0;
}
int spi_nor_write_enable(int fd)
{
uint8_t tx[] = {CMD_WRITE_ENABLE};
struct spi_ioc_transfer tr = {
.tx_buf = (unsigned long)tx,
.rx_buf = 0,
.len = 1,
.speed_hz = SPI_FREQ,
.bits_per_word = 8,
};
return ioctl(fd, SPI_IOC_MESSAGE(1), &tr);
}
int spi_nor_page_program(int fd, uint32_t addr, uint8_t *data, size_t len)
{
uint8_t tx[4 + len];
tx[0] = CMD_PAGE_PROGRAM;
tx[1] = (addr >> 16) & 0xFF;
tx[2] = (addr >> 8) & 0xFF;
tx[3] = addr & 0xFF;
memcpy(&tx[4], data, len);
struct spi_ioc_transfer tr = {
.tx_buf = (unsigned long)tx,
.rx_buf = 0,
.len = 4 + len,
.speed_hz = SPI_FREQ,
.bits_per_word = 8,
};
return ioctl(fd, SPI_IOC_MESSAGE(1), &tr);
}
int spi_nor_read_data(int fd, uint32_t addr, uint8_t *data, size_t len)
{
uint8_t tx[4 + len];
uint8_t rx[4 + len];
tx[0] = CMD_READ_DATA;
tx[1] = (addr >> 16) & 0xFF;
tx[2] = (addr >> 8) & 0xFF;
tx[3] = addr & 0xFF;
struct spi_ioc_transfer tr = {
.tx_buf = (unsigned long)tx,
.rx_buf = (unsigned long)rx,
.len = 4 + len,
.speed_hz = SPI_FREQ,
.bits_per_word = 8,
};
if (ioctl(fd, SPI_IOC_MESSAGE(1), &tr) < 0) {
return -1;
}
memcpy(data, &rx[4], len);
return 0;
}
int main(int argc, char *argv[])
{
int fd;
uint8_t id[3];
uint8_t status;
uint8_t test_data[256];
uint8_t read_data[256];
int i;
fd = open(SPI_DEVICE, O_RDWR);
if (fd < 0) {
perror("Failed to open SPI device");
return -1;
}
// 配置 SPI
uint8_t mode = SPI_MODE_0;
uint32_t speed = SPI_FREQ;
ioctl(fd, SPI_IOC_WR_MODE, &mode);
ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
// 读取 JEDEC ID
printf("Reading JEDEC ID...\n");
if (spi_nor_read_jedec_id(fd, id) == 0) {
printf("Manufacturer ID: 0x%02X\n", id[0]);
printf("Memory Type: 0x%02X\n", id[1]);
printf("Capacity: 0x%02X\n", id[2]);
}
// 读取状态寄存器
printf("\nReading Status Register...\n");
if (spi_nor_read_status(fd, &status) == 0) {
printf("Status: 0x%02X\n", status);
}
// 擦除和写入测试
printf("\nTesting write operation...\n");
// 生成测试数据
for (i = 0; i < 256; i++) {
test_data[i] = i & 0xFF;
}
// 写入使能
spi_nor_wait_ready(fd, 100);
spi_nor_write_enable(fd);
// 页编程 (256 字节)
spi_nor_page_program(fd, 0x000000, test_data, 256);
spi_nor_wait_ready(fd, 1000);
// 读回验证
spi_nor_read_data(fd, 0x000000, read_data, 256);
printf("\nVerify: %s\n",
memcmp(test_data, read_data, 256) == 0 ? "PASS" : "FAIL");
close(fd);
return 0;
}
Python 示例#
使用busio库#
import time
import board
import busio
# Initialize hardware SPI bus (auto CS is configured at bottom level,
# or using default hardware CS pin)
_,sck,mosi,miso = board.pin.spiPorts[0]
spi = busio.SPI(sck,mosi,miso)
print("--- Reading SPI NOR Flash ID (hardware auto CS control) ---")
# Acquire SPI bus lock
spi.try_lock()
# Configure SPI settings (Mode 0, 1MHz clock)
spi.configure(baudrate=1000000, polarity=0, phase=0)
# Prepare buffers
# bytes total: 1 command byte + 3 bytes for receiving ID
tx_buf = bytes([0x9F, 0x00, 0x00, 0x00])
rx_buf = bytearray(4) # length must match tx_buf
# Simultaneous full-duplex read/write (within single CS active cycle)
# Hardware automatically pulls CS low -> sends 4 bytes while receiving 4 bytes -> CS high
spi.write_readinto(tx_buf, rx_buf)
# Release bus lock
spi.unlock()
# Parse and print result
# Byte 0 in rx_buf is dummy data (sent 0x9F), JEDEC ID follows in bytes 1-3
manufacturer_id = rx_buf[1]
memory_type = rx_buf[2]
capacity_id = rx_buf[3]
print("-" * 40)
print(f"Raw response data (RAW): {[hex(x) for x in rx_buf]}")
print(f"Manufacturer ID: 0x{manufacturer_id:02X}")
print(f"Memory Type: 0x{memory_type:02X}")
print(f"Capacity ID: 0x{capacity_id:02X}")
print("-" * 40)
纯python#
import os
import fcntl
import struct
import ctypes
# --- Linux SPI ioctl 命令常量 ---
# 如果 0x40016B01 报错,部分内核需要使用 0x40046B01 (按 32 位 mode 写入)
SPI_IOC_WR_MODE = 0x40016B01
SPI_IOC_WR_MAX_SPEED_HZ = 0x40046B04
SPI_IOC_MESSAGE_1 = 0x40206B00 # 传输 1 个 spi_ioc_transfer 结构体
def read_flash_id(bus=0, device=0):
spi_device = f"/dev/spidev{bus}.{device}"
if not os.path.exists(spi_device):
print(f"错误: 找不到设备文件 {spi_device}")
return
fd = os.open(spi_device, os.O_RDWR)
try:
# 设置 SPI 模式 (Mode 0)
try:
fcntl.ioctl(fd, SPI_IOC_WR_MODE, struct.pack('B', 0))
except OSError:
# 尝试用 4 字节的无符号整数格式写入 Mode(部分新版内核要求)
fcntl.ioctl(fd, 0x40046B01, struct.pack('I', 0))
# 设置 SPI 频率 (100 kHz)
speed_hz = 100000
fcntl.ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, struct.pack('I', speed_hz))
print(f"SPI 初始化成功: Mode=0, Speed={speed_hz/1000}kHz")
# 准备发送和接收缓冲区
tx_data = bytes([0x9F, 0x00, 0x00, 0x00])
# 创建一个可写的接收缓冲区
rx_buf = ctypes.create_string_buffer(4)
# 获取缓冲区的 64 位内存地址
tx_addr = ctypes.addressof(ctypes.create_string_buffer(tx_data))
rx_addr = ctypes.addressof(rx_buf)
# 严格按照 Linux x86_64 / RISCV64 的 spi_ioc_transfer 结构体打包:
# __u64 tx_buf (Q)
# __u64 rx_buf (Q)
# __u32 len (I)
# __u32 speed_hz (I)
# __u16 delay_usecs (H)
# __u8 bits_per_word (B)
# __u8 cs_change (B)
# __u8 tx_nbits (B)
# __u8 rx_nbits (B)
# __u16 pad (H)
spi_ioc_transfer = struct.pack(
"=QQIIHBBBBH",
tx_addr, # tx_buf
rx_addr, # rx_buf
4, # len
speed_hz, # speed_hz
0, # delay_usecs
8, # bits_per_word (通常是 8 位的倍数)
0, # cs_change
0, # tx_nbits
0, # rx_nbits
0 # pad
)
# 执行 传输
fcntl.ioctl(fd, SPI_IOC_MESSAGE_1, spi_ioc_transfer)
# 解析返回结果
result = [b for b in rx_buf.raw]
print("-" * 40)
print(f"原始返回数据: {[hex(x) for x in result]}")
print(f"厂商 ID (Manufacturer ID): 0x{result[1]:02X}")
print(f"设备类型 (Memory Type): 0x{result[2]:02X}")
print(f"容量 ID (Capacity ID): 0x{result[3]:02X}")
print("-" * 40)
except OSError as e:
print(f"操作失败: {e}")
finally:
os.close(fd)
if __name__ == "__main__":
# 如果设备节点是 /dev/spidev0.0 传入 (0, 0)
read_flash_id(0, 0)
