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


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工具的使用:
基本介绍:spidev_test是一个用于测试和验证Linux中SPI设备驱动程序的用户空间工具。它使用spidev接口与SPI设备通信。这个工具主要用来检查SPI设备是否工作正常,以及对SPI设备进行基本的读写操作。
主要选项和参数
-D /dev/spidevX.Y:指定要测试的SPI设备节点。
-s :设置SPI时钟频率(以Hz为单位),例如-s 1000000表示1 MHz。
-d :设置数据传输之间的延迟时间(以微秒为单位)。
-b :设置每个数据字的位数,通常是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 :设置SPI时钟频率(以Hz为单位),例如-s 1000000表示1 MHz。
-w :指定要写入到SPI设备的数据,可以是十六进制或ASCII格式的字符串。
-r :指定从SPI设备读取的数据大小(以字节为单位)。
-b :设置每个数据字的位数,通常是8或16。
-d :设置数据传输之间的延迟时间(以微秒为单位)。
(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
#include
#include
#include
#include
#include
#include
#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实验就完成了。
-
Linux
+关注
关注
87文章
11347浏览量
210437 -
开发板
+关注
关注
25文章
5123浏览量
98243 -
RK3568
+关注
关注
4文章
526浏览量
5238 -
迅为电子
+关注
关注
0文章
36浏览量
62
发布评论请先 登录
相关推荐
评论