注意

这是最新开发分支配套的文档,可能包含已发布版本中尚未提供的功能。如果您要查看特定版本的文档,请使用左侧的下拉菜单并选择所需要的版本。

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)

评论列表
条评论
登录