迅为RK3568开发板驱动指南Linux中通用SPI设备驱动

描述

 

 

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

 

 

硬件:迅为RK3568开发板

RK3568RK3568

 

193.1 内核和设备树配置

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

 

> Device Drivers

 

 > SPI support

RK3568

 

 

 

除了内核支持之外,还需要修改设备树,由于之前已经使能了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";

    };

};

RK3568

 

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

 

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

 

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

 

RK3568

 

/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目录下,如下图所示:

 

RK3568

 

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

 

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

RK3568

 

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

RK3568

 

 

然后将编译好的可执行文件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

 

RK3568

 

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

 

写入和读取数据:

 

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

 

RK3568

 

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

 

连续传输:

 

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

 

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

 

RK3568

 

 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

RK3568

 

 

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

 

193.4.2 运行测试

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

RK3568

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

 

RK3568

 

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

 

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

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分