跳到主要内容

SPI通讯

SPI(Serial Peripheral Interface),即串行外围设备接口,是一种同步的,全双工的,多设备的,多主机的通信协议,用于连接外围设备,如ADC、DAC、数据存储器、定时器、接受器等。RK3588 SPI支持特性如下:

  • 默认采用摩托罗拉 SPI 协议
  • 支持 8 位和 16 位
  • 软件可编程时钟频率和传输速率高达 50MHz
  • 支持 SPI 4 种传输模式配置
  • 每个 SPI 控制器支持一个到两个片选
  • 框架支持 slave 和 master 两种模式
# 代码路径
drivers/spi/spi.c         # spi驱动框架
drivers/spi/spi-rockchip.c     # rk spi各接口实现
drivers/spi/spidev.c        # 创建spi设备节点,用户态使用。
drivers/spi/spi-rockchip-test.c # spi测试驱动,需要自己手动添加到Makefile编译
Documentation/spi/spidev_test.c # 用户态spi测试工具

由于开发板默认未启用SPI,本章节仅对用户模式的SPI进行配置介绍,其他可参考SDK文档SDK/docs/common/SPI/Rockchip_Developer_Guide_Linux_SPI_CN.pdf

SPI资源

User mode SPI device 指的是用户空间直接操作 SPI 接口,这样方便众多的 SPI 外设驱动跑在用户空间,不需要改到内核,方便驱动移植开发。以SPI0_xxx_M2为例,修改dts文件,请注意接口复用状态,避免冲突

RK3588_SPI

Net nameNUMNUMNet name
SPI4_MISO_M2GPIO1_A0_3V312GPIO4_D5_3V3
SPI4_MOSI_M2GPIO1_A1_3V334GPIO4_D4_3V3
SPI4_CLK_M2GPIO1_A2_3V356GPIO4_D3_3V3
SPI4_CS0_M2GPIO1_A3_3V378GPIO4_D2_3V3
SPI0_CS0_M2GPIO1_B4_3V3910GPIO1_C6_1V8
GND1112GND
SPI0_CLK_M2GPIO1_B3_3V31314SARADC_VIN21V8 SARADC in
SPI0_MOSI_M2GPIO1_B2_3V31516VCC3V3_SYS
SPI0_MISO_M2GPIO1_B1_3V31718VCC12V_DCIN
VCC5V0_SYS1920VCC12V_DCIN

DTS修改与编译

# 修改rk3588-toybrick-x0.dtsi文件
&spi0{
status = "okay";
pinctrl-0 = <&spi0m2_cs0 &spi0m2_pins>;
max-freq = <50000000>;
spidev1: spidev@0{
compatible = "rockchip,spidev";
status = "okay";
reg = <0x0>;
spi-max-frequency = <50000000>;
};
};

重新编译kernel(./edge build -k)并更新烧录boot_linux.img至开发板,通过命令 ls /dev/spi*查看spi设备

RK3588_SPI1 </left>

SPI应用测试程序

编写应用程序进行测试,并保存为spi_test.c,采用gcc编译后运行./spi_test

spi_test.c
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>

#define SPI_DEV_PATH "/dev/spidev0.0"

int fd;
static unsigned mode = SPI_MODE_0;
static uint8_t bits = 8;
static uint32_t speed = 1000000; // 设置SPI速度为1MHz
static uint16_t delay;

void transfer(int fd, uint8_t const *tx, uint8_t *rx, size_t len)
{
int ret;
struct spi_ioc_transfer tr = {
.tx_buf = (unsigned long)tx,
.rx_buf = (unsigned long)rx,
.len = len,
.delay_usecs = delay,
.speed_hz = speed,
.bits_per_word = bits,
.cs_change = 0, // 设置为1以在每次传输前切换片选,这里不切换片选
};

ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);

if (ret < 1) {
perror("SPI transfer failed");
printf("Error code: %d\n", ret);
} else {
printf("Transfer successful\n");
}
}

void spi_init(void)
{
int ret;
// 打开 SPI 设备
fd = open(SPI_DEV_PATH, O_RDWR);
if (fd < 0) {
perror("Can't open SPI device");
exit(1);
}

// 设置 SPI 工作模式
ret = ioctl(fd, SPI_IOC_WR_MODE, &mode);
if (ret == -1) {
perror("Can't set SPI mode");
exit(1);
}

// 设置位数
ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);
if (ret == -1) {
perror("Can't set bits per word");
exit(1);
}

// 设置SPI速度
ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
if (ret == -1) {
perror("Can't set max speed");
exit(1);
}

// 打印设置
printf("SPI mode: 0x%x\n", mode);
printf("Bits per word: %d\n", bits);
printf("Max speed: %d Hz\n", speed);
}

int main(int argc, char *argv[])
{
if (argc != 2) {
printf("Usage: %s <string_to_send>\n", argv[0]);
return 1;
}

char *tx_buffer = argv[1]; // 获取要发送的字符串作为命令行参数

// 初始化SPI接口
spi_init();

// 设置要接收数据的缓冲区
unsigned char rx_buffer[strlen(tx_buffer) + 1];
memset(rx_buffer, 0, sizeof(rx_buffer)); // 初始化接收缓冲区

// 打印发送和接收的数据长度
size_t len = strlen(tx_buffer);
printf("Data length: %zu\n", len);

// 执行SPI数据传输
transfer(fd, tx_buffer, rx_buffer, len);

// 打印发送和接收的数据
printf("Sent: %s\n", tx_buffer);
printf("Received: %s\n", rx_buffer);

// 检查发送和接收的数据是否一致
if (memcmp(tx_buffer, rx_buffer, len) == 0) {
printf("Data matches\n");
} else {
printf("Data mismatch\n");
}

// 关闭SPI设备
close(fd);

return 0;
}

测试执行结果

RK3588_SPI2

注意

短接MOSI和MISO线路可以自发自收数据,在未短接时报告错误Data mismatch