SPI Usage Instructions#
Overview#
The K230 platform contains three SPI controllers:
SPI Controller |
Base Address |
Type |
Interrupt Number |
|---|---|---|---|
spi0 |
0x91584000 |
OSPI |
146-154 |
spi1 |
0x91582000 |
QSPI 0 |
155-163 |
spi2 |
0x91583000 |
QSPI 1 |
164-172 |
The SPI controller is based on the Synopsys DesignWare APB SSI core
Note: All SPI controllers are
disabledby default and must be enabled in the device tree before use.
Device Tree Configuration#
Enable SPI in the device tree (using spi0 as an example):
//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";
};
};
User-space Tool Usage#
K230 SDK buildroot provides user-space SPI tools:
spi-config: Query and set SPI configuration
spi-pipe: Data transmission via 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
Usage Examples
# Query current configuration
spi-config -d /dev/spidev0.0 -q
# Output: /dev/spidev0.0: mode=0, lsb=0, bits=8, speed=500000
# Set SPI mode to mode 3
spi-config -d /dev/spidev0.0 -m 3
# Read spi nor chip id, specify blocksize as 4 bytes
echo -n -e '\x9F\x00\x00\x00' | spi-pipe -d /dev/spidev0.0 -s 1000000 -b 4 | hexdump -C
Test Example
# Loopback send/receive to test read/write
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 Language Example#
SPI NOR Flash read/write example: first read the ID, then perform read/write testing;
// 先读取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 commands
#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
// Wait for Flash ready
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 starts from rx[1] (3 bytes)
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;
}
// Configure 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);
// Read 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]);
}
// Read status register
printf("\nReading Status Register...\n");
if (spi_nor_read_status(fd, &status) == 0) {
printf("Status: 0x%02X\n", status);
}
// Erase and write test
printf("\nTesting write operation...\n");
// Generate test data
for (i = 0; i < 256; i++) {
test_data[i] = i & 0xFF;
}
// Write enable
spi_nor_wait_ready(fd, 100);
spi_nor_write_enable(fd);
// Page program (256 bytes)
spi_nor_page_program(fd, 0x000000, test_data, 256);
spi_nor_wait_ready(fd, 1000);
// Read back to verify
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 Example#
Using busio Library#
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)
Pure Python#
import os
import fcntl
import struct
import ctypes
# --- Linux SPI ioctl command constants ---
# If 0x40016B01 reports an error, some kernels require using 0x40046B01 (write mode as 32-bit)
SPI_IOC_WR_MODE = 0x40016B01
SPI_IOC_WR_MAX_SPEED_HZ = 0x40046B04
SPI_IOC_MESSAGE_1 = 0x40206B00 # Transfer 1 spi_ioc_transfer structure
def read_flash_id(bus=0, device=0):
spi_device = f"/dev/spidev{bus}.{device}"
if not os.path.exists(spi_device):
print(f"Error: Device file not found {spi_device}")
return
fd = os.open(spi_device, os.O_RDWR)
try:
# Set SPI mode (Mode 0)
try:
fcntl.ioctl(fd, SPI_IOC_WR_MODE, struct.pack('B', 0))
except OSError:
# Try writing Mode in 4-byte unsigned integer format (required by some newer kernels)
fcntl.ioctl(fd, 0x40046B01, struct.pack('I', 0))
# Set SPI frequency (100 kHz)
speed_hz = 100000
fcntl.ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, struct.pack('I', speed_hz))
print(f"SPI initialization successful: Mode=0, Speed={speed_hz/1000}kHz")
# Prepare send and receive buffers
tx_data = bytes([0x9F, 0x00, 0x00, 0x00])
# Create a writable receive buffer
rx_buf = ctypes.create_string_buffer(4)
# Get the 64-bit memory address of the buffer
tx_addr = ctypes.addressof(ctypes.create_string_buffer(tx_data))
rx_addr = ctypes.addressof(rx_buf)
# Pack strictly according to the spi_ioc_transfer structure of Linux x86_64 / RISCV64:
# __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 (usually a multiple of 8 bits)
0, # cs_change
0, # tx_nbits
0, # rx_nbits
0 # pad
)
# Execute transfer
fcntl.ioctl(fd, SPI_IOC_MESSAGE_1, spi_ioc_transfer)
# Parse return result
result = [b for b in rx_buf.raw]
print("-" * 40)
print(f"Raw response data: {[hex(x) for x in result]}")
print(f"Manufacturer ID: 0x{result[1]:02X}")
print(f"Memory Type: 0x{result[2]:02X}")
print(f"Capacity ID: 0x{result[3]:02X}")
print("-" * 40)
except OSError as e:
print(f"Operation failed: {e}")
finally:
os.close(fd)
if __name__ == "__main__":
# If the device node is /dev/spidev0.0, pass in (0, 0)
read_flash_id(0, 0)
