您的位置:首页 > 新闻 > 资讯 > RK3568驱动指南|第十六篇 SPI-第193章 Linux中通用SPI设备驱动

RK3568驱动指南|第十六篇 SPI-第193章 Linux中通用SPI设备驱动

2025/1/7 11:27:27 来源:https://blog.csdn.net/BeiJingXunWei/article/details/140179391  浏览:    关键词:RK3568驱动指南|第十六篇 SPI-第193章 Linux中通用SPI设备驱动

瑞芯微RK3568芯片是一款定位中高端的通用型SOC,采用22nm制程工艺,搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码,支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU,可用于轻量级人工智能应用。RK3568 支持安卓 11 和 linux 系统,主要面向物联网网关、NVR 存储、工控平板、工业检测、工控盒、卡拉 OK、云终端、车载中控等行业。


【公众号】迅为电子

【粉丝群】258811263(加群获取驱动文档+例程)

【视频观看】嵌入式学习之Linux驱动(第十六篇 SPI_全新升级)_基于RK3568


  1. 第193章 Linux中通用SPI设备驱动

在前面的章节中我们从0开始编写了一个mcp2515的驱动程序,而跟I2C设备类似,在Linux内核中也有着通用SPI设备驱动,在本章节将会讲解通用SPI设备驱动的使用,并讲解如何在应用程序中通过ioctl对SPI进行配置和使用。

193.1 内核和设备树配置

通用SPI设备驱动在迅为提供的Linux内核中默认已经勾选了,具体路径如下所示:

> Device Drivers

 > SPI support

除了内核支持之外,还需要修改设备树,由于之前已经使能了SPI0,所以这直接修改之前编写的mcp2515设备树节点,具体设备树为“kernel/arch/arm64/boot/dts/rockchip/rk3568-evb1-ddr4-v10.dtsi”,修改完成的mcp2515节点如下所示:rockchip,spidev

&spi0 {status = "okay";pinctrl-0 = <&spi0m1_cs0  &spi0m1_pins>;pinctrl-1 = <&spi0m1_cs0  &spi0m1_pins_hs>;mcp2515:mcp2515@0 {compatible = "rockchip,spidev";reg = <0>;spi-max-frequency = <10000000>;status = "okay";};
};

保存退出之后,重新编译内核源码,最后将编译得到的boot.img烧写到开发板上。

而为了方便起见,迅为已经将修改完成的设备树以及编译完成的内核镜像放到了“iTOP-3568开发板\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动程序\119_mcp2515_07\01_编译好的内核镜像”路径下。

开发板启动之后,如果存在/dev/spidev0.0设备节点,证明设备树及内核配置正确,如下图所示:

/dev/spidev0.0 表示一个 SPI 总线上的具体设备。0.0 是一个标识符,用于区分系统中的不同 SPI 控制器和设备。这个标识符由两部分组成:

第一个数字 0:表示SPI总线的编号。一个系统中可能有多个SPI控制器,每个控制器对应一个总线编号,从0开始。
第二个数字0:表示连接在该SPI总线上的具体设备编号。一个SPI总线上可以连接多个设备,每个设备通过片选信号(Chip Select, CS)进行区分,设备编号从0开始。

在下个小节中,将会讲解内核源码中携带的spidev_test SPI测试程序进行讲解。

193.2 spidev_test工具使用

spidev_test是一个用于测试和调试SPI设备的命令行工具,通常在Linux系统上使用,它允许用户直接通过SPI总线与设备进行通信,可以发送数据并接收来自设备的响应。

spidev_test源码位于Linux源码的kernel/tools/spi目录下,如下图所示:

然后使用以下命令对该工具进行交叉编译

make CC=/home/topeet/Linux/linux_sdk/prebuilts/gcc/linux-x86/aarch64/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-gcc LD=/home/topeet/Linux/linux_sdk/prebuilts/gcc/linux-x86/aarch64/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-ld

编译好的文件如下图所示。 

然后将编译好的可执行文件spidev_fdx和spidev_test拷贝到开发板上使用即可。接下来介绍一下工具的使用方法

1.spidev_test工具使用:

  1. 基本介绍:spidev_test是一个用于测试和验证Linux中SPI设备驱动程序的用户空间工具。它使用spidev接口与SPI设备通信。这个工具主要用来检查SPI设备是否工作正常,以及对SPI设备进行基本的读写操作。
  2. 主要选项和参数

    -D /dev/spidevX.Y:指定要测试的 SPI 设备节点。

    -s <speed>:设置 SPI 时钟频率(以 Hz 为单位),例如 -s 1000000 表示 1 MHz。

    -d <delay>:设置数据传输之间的延迟时间(以微秒为单位)。

    -b <bits per word>:设置每个数据字的位数,通常是 8 或 16。

    -H:以十六进制模式显示传输的数据。

    (3)示例操作

    读取设备信息:

    spidev_test -D /dev/spidevX.Y -s 1000000

这会使用 1 MHz 的时钟频率从 SPI 设备读取数据,默认情况下以十六进制显示。

写入和读取数据:

spidev_test -D /dev/spidevX.Y -s 1000000 -b 8 -d 1000 -H -p 'hello'

这条命令会向 SPI 设备写入字符串 'hello',并以十六进制模式显示设备的响应数据。-b 8 指定每个字的位数为 8,-d 1000 设置 1000 微秒的延迟。

连续传输:

spidev_test -D /dev/spidevX.Y -s 1000000 -b 8 -p 'abcdefgh'

这个示例将连续发送字节 'abcdefgh' 到 SPI 设备。

 2.spidev_fdx工具的使用

(1)基本介绍:spidev_fdx 是一个用于全双工 SPI 通信测试的命令行工具,主要用于在 Linux 系统上与 SPI 设备进行双向数据传输和测试。

(2)主要选项和参数

-D /dev/spidevX.Y:指定要测试的 SPI 设备节点。

-s <speed>:设置 SPI 时钟频率(以 Hz 为单位),例如 -s 1000000 表示 1 MHz。

-w <write_data>:指定要写入到 SPI 设备的数据,可以是十六进制或 ASCII 格式的字符串。

-r <read_size>:指定从 SPI 设备读取的数据大小(以字节为单位)。

-b <bits per word>:设置每个数据字的位数,通常是 8 或 16。

-d <delay>:设置数据传输之间的延迟时间(以微秒为单位)。

(3)示例操作

以下是几个使用 spidev_fdx 工具的示例操作:

发送和接收数据:

spidev_fdx -D /dev/spidevX.Y -s 1000000 -w 'hello' -r 5

这会向 SPI 设备写入字符串 'hello',并从设备读取 5 个字节的响应数据。

设置时钟频率和延迟:

spidev_fdx -D /dev/spidevX.Y -s 500000 -d 200 -w 'abcdef' -r 10

这个示例将 SPI 时钟频率设置为 500 kHz,数据写入延迟为 200 微秒,并向设备写入字符串 'abcdef',然后读取 10 个字节的响应数据。

193.3 应用程序中如何使用SPI

在第一个小节中使能了内核中的通用SPI,而在第二小节讲解了spidev_test工具的使用,在本小节将根据spidev_test工具的源码,编写mcp2515通用SPI驱动程序的应用程序。

在应用程序中可以通过ioctl来获取和配置SPI的相关属性,并实现SPI数据的发送和接收,SPI的ioctl宏定义在“/usr/include/linux/spi/spidev.h”,部分ioctl cmd如下所示:

/* 读取 / 写入 SPI 模式(SPI_MODE_0..SPI_MODE_3)(限制为 8 位) */
#define SPI_IOC_RD_MODE                 _IOR(SPI_IOC_MAGIC, 1, __u8)  // 读取 SPI 模式
#define SPI_IOC_WR_MODE                 _IOW(SPI_IOC_MAGIC, 1, __u8)  // 写入 SPI 模式/* 读取 / 写入 SPI 位顺序 */
#define SPI_IOC_RD_LSB_FIRST            _IOR(SPI_IOC_MAGIC, 2, __u8)  // 读取 SPI 低位优先
#define SPI_IOC_WR_LSB_FIRST            _IOW(SPI_IOC_MAGIC, 2, __u8)  // 写入 SPI 低位优先/* 读取 / 写入 SPI 设备字长(1..N) */
#define SPI_IOC_RD_BITS_PER_WORD        _IOR(SPI_IOC_MAGIC, 3, __u8)  // 读取 SPI 每字位数
#define SPI_IOC_WR_BITS_PER_WORD        _IOW(SPI_IOC_MAGIC, 3, __u8)  // 写入 SPI 每字位数/* 读取 / 写入 SPI 设备默认最大速度(Hz) */
#define SPI_IOC_RD_MAX_SPEED_HZ         _IOR(SPI_IOC_MAGIC, 4, __u32) // 读取 SPI 最大速度(Hz)
#define SPI_IOC_WR_MAX_SPEED_HZ         _IOW(SPI_IOC_MAGIC, 4, __u32) // 写入 SPI 最大速度(Hz)/* 读取 / 写入 SPI 模式字段 */
#define SPI_IOC_RD_MODE32               _IOR(SPI_IOC_MAGIC, 5, __u32) // 读取 SPI 模式(32 位)
#define SPI_IOC_WR_MODE32               _IOW(SPI_IOC_MAGIC, 5, __u32) // 写入 SPI 模式(32 位)

可以通过上述ioctl cmd来对SPI设备进行初始化,编写完成的初始化函数如下所示:

int fd;             // SPI设备文件描述符
int mode = SPI_MODE_0;  // SPI模式
int bits = 8;       // 每字比特数
int speed = 10000000;   // 最大SPI总线速度(Hz)int spi_init(void){int ret;// 打开SPI设备文件fd = open("/dev/spidev0.0", O_RDWR);if(fd < 0){printf("打开 /dev/spidev0.0 错误\n");return -1;}/** 设置SPI模式*/ret = ioctl(fd, SPI_IOC_WR_MODE32, &mode);if (ret == -1)printf("无法设置SPI模式\n");ret = ioctl(fd, SPI_IOC_RD_MODE32, &mode);if (ret == -1)printf("无法获取SPI模式\n");/** 设置每字比特数*/ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);if (ret == -1)printf("无法设置每字比特数\n");ret = ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits);if (ret == -1)printf("无法获取每字比特数\n");/** 设置最大传输速度*/ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);if (ret == -1)printf("无法设置最大传输速度\n");ret = ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed);if (ret == -1)printf("无法获取最大传输速度\n");printf("SPI模式: 0x%x\n", mode);printf("每字比特数: %d\n", bits);printf("最大速度: %d Hz (%d KHz)\n", speed, speed / 1000);return 0;
}

通过该函数可以设置SPI的模式、比特数以及最大传输速度,然后根据spidev_test工具源码的传输函数来编写传输函数,具体函数内容如下所示:

/** 执行SPI数据传输.* 参数:*   fd - SPI设备文件描述符*   tx - 发送缓冲区*   rx - 接收缓冲区*   len - 数据长度* 返回0表示成功,-1表示失败.*/
int transfer(int fd, char *tx, char *rx, int 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,};ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);if (ret < 1){printf("无法发送SPI消息\n");return -1;}return 0;
}

在前面的章节中一步步的编写了mcp2515的复位函数、配置函数、读函数和写函数,而现在可以直接在应用程序通过刚刚编写的传输函数向SPI设备发送一系列的SPI指令,一个编写完成的mcp2515的应用程序代码如下所示:

#include <stdio.h>
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>#define RESET   0xc0    // 复位命令
#define CANSTAT 0x0e    // CAN状态寄存器地址
#define READ    0x03    // 读命令
#define CANCTRL 0x0f    // CAN控制寄存器地址
#define WRITE   0x02    // 写命令int fd;             // SPI设备文件描述符
int mode = SPI_MODE_0;  // SPI模式
int bits = 8;       // 每字比特数
int speed = 10000000;   // 最大SPI总线速度(Hz)
int delay;          // 延迟时间(微秒)/** 初始化SPI通信.* 返回0表示成功,-1表示失败.*/
int spi_init(void){int ret;// 打开SPI设备文件fd = open("/dev/spidev0.0", O_RDWR);if(fd < 0){printf("打开 /dev/spidev0.0 错误\n");return -1;}/** 设置SPI模式*/ret = ioctl(fd, SPI_IOC_WR_MODE32, &mode);if (ret == -1)printf("无法设置SPI模式\n");ret = ioctl(fd, SPI_IOC_RD_MODE32, &mode);if (ret == -1)printf("无法获取SPI模式\n");/** 设置每字比特数*/ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);if (ret == -1)printf("无法设置每字比特数\n");ret = ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits);if (ret == -1)printf("无法获取每字比特数\n");/** 设置最大传输速度*/ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);if (ret == -1)printf("无法设置最大传输速度\n");ret = ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed);if (ret == -1)printf("无法获取最大传输速度\n");printf("SPI模式: 0x%x\n", mode);printf("每字比特数: %d\n", bits);printf("最大速度: %d Hz (%d KHz)\n", speed, speed / 1000);return 0;
}/** 执行SPI数据传输.* 参数:*   fd - SPI设备文件描述符*   tx - 发送缓冲区*   rx - 接收缓冲区*   len - 数据长度* 返回0表示成功,-1表示失败.*/
int transfer(int fd, char *tx, char *rx, int 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,};ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);if (ret < 1){printf("无法发送SPI消息\n");return -1;}return 0;
}int main(int argc, char *argv[]){char reset_cmd[1] = {RESET};    // 复位命令数组char rd_canstat[2] = {READ, CANSTAT};  // 读CAN状态寄存器命令数组char canstat[4] = {0};          // 存储CAN状态的缓冲区char wr_canctrl[] = {WRITE, CANCTRL, 0x00};  // 写CAN控制寄存器命令数组// 初始化SPI通信spi_init();// 执行SPI数据传输// 1. 发送复位命令transfer(fd, reset_cmd, NULL, sizeof(reset_cmd));// 2. 读取CAN状态transfer(fd, rd_canstat, canstat, sizeof(canstat));printf("CAN状态为 %x\n", canstat[2]);// 清空canstat缓冲区memset(canstat, 0, sizeof(canstat));// 3. 写入CAN控制transfer(fd, wr_canctrl, NULL, sizeof(wr_canctrl));// 4. 再次读取CAN状态transfer(fd, rd_canstat, canstat, sizeof(canstat));printf("CAN状态为 %x\n", canstat[3]);return 0;
}

193.4 运行测试

193.4.1 编译应用程序

上一小节编写好的app.c应用程序源码已经放在了“iTOP-3568开发板\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动程序\119_mcp2515_07\02_app”目录下。

首先进行应用程序的编译,因为测试APP是要在开发板上运行的,所以需要aarch64-linux-gnu-gcc来编译,输入以下命令,编译完成以后会生成一个app的可执行程序,如下图所示:

 aarch64-linux-gnu-gcc app.c -o app

然后将编译完成的可执行程序拷贝到开发板上.

193.4.2 运行测试

首先将193.1小节编译好的内核镜像烧写到开发板上,然后将可执行程序app文件拷贝到开发板上,拷贝完成如下所示:

 然后运行可执行程序app,如下图所示。

在应用程序中,发送完复位指令之后,第一条打印can状态寄存器的值为80,表示mcp2515已经处在了配置模式。第二条打印can状态寄存器的值为00,表示mcp2515已经处于正常模式,这就说明上一小节编写的应用程序正常运行。

至此,关于通用SPI驱动和在应用程序中使用SPI实验就完成了。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com