【Linux SPI驱动开发】

SPI总线

SPI(Serial Peripheral Interface,串行外设接口)是一种高速、全双工、同步的串行通信总线协议,广泛用于微控制器与外设(如传感器、存储器、显示屏等)之间的短距离通信。

SPI的核心特性:

**全双工通信:**数据可以同时发送和接收。

**同步传输:**通过时钟信号(SCLK)控制数据传输速率。

**主从架构:**一个主设备(Master)控制通信,多个从设备(Slave)通过片选信号(SS/CS)被选中。

**高速传输:**通常可达几十MHz(比I2C更快)。

**硬件简单:**无需复杂的地址或协议处理。

SPI传输错误处理包括超时、校验、片选和时钟同步,确保数据可靠性。

SPI具体详解在嵌入式STM32核心专栏中有文章介绍。

数码管原理

M74HC595

SPI通用驱动

Linux SPI子系统

SPI控制器驱动支持

SPI通用驱动

SPI通用设备驱动接口及关键结构体

工作模式设置

字长设置

频率设置

spi_transfer结构体

数据内容个数

用户态SPI设备驱动编写

cs 复制代码
#include <stdio.h>      // 标准输入输出,用于printf、perror等
#include <stdlib.h>     // 标准库,用于exit等函数
#include <unistd.h>     // 提供usleep等系统调用
#include <fcntl.h>      // 文件控制,用于open、O_RDWR等文件操作宏
#include <sys/ioctl.h>  // IO控制,用于ioctl系统调用
#include <string.h>     // 字符串操作,用于memset
#include <linux/spi/spidev.h>  // SPI设备相关的结构体和宏定义

// SPI通信参数宏定义
#define DATA_BITS 8     // SPI通信的位宽,8位
#define DATA_SPEED 400000  // SPI通信速率,400KHz

int main(int argc, const char *argv[])
{
    int ret = 0;                // 函数返回值临时存储
    int mode = SPI_MODE_0;      // SPI工作模式:MODE0 (CPOL=0, CPHA=0)
    int speed = DATA_SPEED;     // SPI通信速率
    int bits = DATA_BITS;       // SPI位宽
    int fd;                     // SPI设备文件描述符
    int i = 0;                  // 数码管位选择和数字索引计数器
    int pos = 0;                // 数码管位选控制值
    // 共阴极7段数码管段码表 (0-F的显示编码)
    // 每一位对应一个段:0x3f = 0b00111111 对应数字0的显示
    unsigned char num[20] = {
        0x3f, // 0: 段码对应显示数字0
        0x06, // 1: 段码对应显示数字1
        0x5b, // 2: 段码对应显示数字2
        0x4f, // 3: 段码对应显示数字3
        0x66, // 4: 段码对应显示数字4
        0x6d, // 5: 段码对应显示数字5
        0x7d, // 6: 段码对应显示数字6
        0x07, // 7: 段码对应显示数字7
        0x7f, // 8: 段码对应显示数字8
        0x6f, // 9: 段码对应显示数字9
        0x77, // A: 段码对应显示字母A
        0x7c, // B: 段码对应显示字母B
        0x39, // C: 段码对应显示字母C
        0x5e, // D: 段码对应显示字母D
        0x79, // E: 段码对应显示字母E
        0x71, // F: 段码对应显示字母F
    };
		
    struct spi_ioc_transfer tr; // SPI传输结构体,用于ioctl方式的SPI通信
    unsigned char buf[2];       // SPI发送缓冲区:buf[0]位选, buf[1]段码

    // 初始化SPI传输结构体,将所有成员置0
    memset(&tr, 0 ,sizeof(tr));

    // 检查命令行参数:必须传入SPI设备文件路径(如/dev/spidev0.0)
    if(argc != 2)
    {
        printf("usage: %s <device-file>\n",argv[0]); // 提示正确用法
        exit(-1);                                    // 退出程序
    }

    // 打开SPI设备文件,读写模式
    fd = open(argv[1],O_RDWR);
    if(fd < 0)
    {
        perror("open");  // 打印打开失败原因
        exit(-1);        // 退出程序
    }

    // 设置SPI工作模式
    ret = ioctl(fd, SPI_IOC_WR_MODE, &mode);
    if(ret < 0)
    {
        perror("ioctl mode"); // 打印设置模式失败原因
        exit(-1);             // 退出程序
    }

    // 设置SPI每字位数(8位)
    ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);
    if(ret < 0)
    {
        perror("ioctl bits"); // 打印设置位宽失败原因
        exit(-1);             // 退出程序
    }

    // 设置SPI最大通信速率
    ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
    if(ret < 0)
    {
        perror("ioctl speed"); // 打印设置速率失败原因
        exit(-1);              // 退出程序
    }

    // 打印SPI配置信息,确认配置成功
    printf("spi mode: %d\n",mode);
    printf("bits per word: %d\n",bits);
    printf("max speed: %d KHz\n",speed/1000);

    // 无限循环:轮流显示4位数码管
    while(1)
    {
        // 计算位选值:1 << (i%4) 依次选中第0/1/2/3位数码管
        // 例如i=0 → pos=1(0x01), i=1 → pos=2(0x02), i=2 → pos=4(0x04), i=3 → pos=8(0x08)
        pos = 1 << (i % 4);
        buf[0] = pos;          // 第1个字节:数码管位选控制
        buf[1] = num[i];       // 第2个字节:对应位置要显示的数字段码
      
#if 1
        // 方式1:直接通过write函数发送SPI数据(简单方式)
        if(write(fd,buf,2) < 0)
            perror("write");   // 打印发送失败原因
#else  
        // 方式2:通过ioctl+spi_ioc_transfer结构体发送(标准SPI操作方式)
        tr.tx_buf = (unsigned long)buf; // 发送缓冲区地址
        tr.len = 2;                     // 发送数据长度(2字节)
        ioctl(fd,SPI_IOC_MESSAGE(1),&tr);// 发送1组SPI消息
#endif

        i++;                  // 计数器自增,切换到下一位
        if(i == 4)            // 只控制4位数码管,到第4位后重置为0
            i = 0;
        usleep(3000);         // 延时3ms,控制显示刷新速度,避免闪烁
    }

    return 0;
}

SPI设备驱动

spi_device结构体

cs 复制代码
struct spi_device {
    // 1. 设备模型核心成员(继承自device)
    struct device           dev;

    // 2. SPI总线核心参数(硬件相关)
    struct spi_master       *master;    // 指向所属的SPI控制器(主机)
    u32                     max_speed_hz; // 最大通信速率(Hz)
    u8                      chip_select;  // 片选号(CS线编号)
    u8                      mode;         // SPI模式(SPI_MODE_0 ~ SPI_MODE_3)
#define SPI_CPHA        0x01    // 时钟相位
#define SPI_CPOL        0x02    // 时钟极性
#define SPI_MODE_0      (0|0)   // CPOL=0, CPHA=0(最常用)
#define SPI_MODE_1      (0|SPI_CPHA)
#define SPI_MODE_2      (SPI_CPOL|0)
#define SPI_MODE_3      (SPI_CPOL|SPI_CPHA)

    // 3. 数据格式参数
    u8                      bits_per_word; // 每字位数(如8/16位)
    u16                     delay_usecs;   // 片选切换延时(微秒)
    u8                      cs_change:1;   // 片选是否保持有效(0=释放,1=保持)

    // 4. 标识与匹配相关
    const char              *modalias;     // 设备别名(用于驱动匹配)
    u32                     speed_hz;      // 实际使用的通信速率(可覆盖max_speed_hz)
    int                     irq;           // 设备中断号(若有)

    // 5. 私有数据(驱动层自定义)
    void                    *dev_data;     // 驱动绑定的私有数据(替代旧版drvdata)
};

spi_driver结构体

cs 复制代码
// SPI驱动核心结构体
struct spi_driver {
    // 1. 继承自device_driver,是驱动模型的核心成员
    struct device_driver driver;
    
    // 2. 设备匹配成功时的回调函数(最核心)
    // 当SPI设备和驱动匹配时,内核会调用此函数,用于初始化设备
    int (*probe)(struct spi_device *spi);
    
    // 3. 设备移除/驱动卸载时的回调函数
    // 用于释放probe中申请的资源(如内存、中断、DMA等)
    void (*remove)(struct spi_device *spi);
    
    // 4. 设备匹配表(可选,用于匹配SPI设备)
    // 匹配规则:根据spi_device的modalias/name等匹配
    const struct spi_device_id *id_table;
    
    // 5. 可选:休眠/唤醒回调(用于电源管理)
    int (*suspend)(struct spi_device *spi, pm_message_t mesg);
    int (*resume)(struct spi_device *spi);
};

设备驱动中消息封装及数据传输

cs 复制代码
struct spi_message {
    // 1. 关联的SPI设备(传输的目标设备)
    struct spi_device       *spi;

    // 2. 传输链表:挂载多个spi_transfer(核心)
    struct list_head        transfers;

    // 3. 传输状态与统计
    unsigned                is_dma_mapped:1; // 是否启用DMA映射
    int                     status;          // 传输结果(0=成功,负数=失败)
    unsigned int            actual_length;   // 实际传输的字节数

    // 4. 异步传输回调(可选)
    void                    (*complete)(void *context); // 传输完成回调
    void                    *context;                   // 回调函数的上下文参数

    // 5. 内部同步用(一般驱动层不直接操作)
    struct list_head        queue;
    void                    *state;
};

SPI接口

一、SPI 驱动注册 / 注销相关 API

1. int spi_register_driver(struct spi_driver *driver)
  • 功能 :向 Linux 内核注册一个 SPI 驱动,注册后内核会自动匹配总线上的 SPI 设备,匹配成功则调用驱动的probe函数。

  • 参数struct spi_driver *driver --- 指向已初始化的spi_driver结构体实例(包含probe/remove/id_table等)。

  • 返回值 :0 表示注册成功,负数(如-EINVAL)表示失败。

  • 使用示例

    cs 复制代码
    // 定义SPI驱动结构体
    static struct spi_driver spi_demo_driver = {
        .driver = {
            .name = "spi-demo",
            .owner = THIS_MODULE,
            .of_match_table = spi_demo_of_match,
        },
        .probe = spi_demo_probe,
        .remove = spi_demo_remove,
        .id_table = spi_demo_id_table,
    };
    
    // 模块加载时注册驱动
    static int __init spi_demo_init(void)
    {
        return spi_register_driver(&spi_demo_driver);
    }
    module_init(spi_demo_init);
2. void spi_unregister_driver(struct spi_driver *driver)
  • 功能 :从内核注销已注册的 SPI 驱动,注销时会对已匹配的设备调用remove函数释放资源。

  • 参数struct spi_driver *driver --- 要注销的 SPI 驱动实例(需和注册时的实例一致)。

  • 返回值:无。

  • 使用示例

    cs 复制代码
    // 模块卸载时注销驱动
    static void __exit spi_demo_exit(void)
    {
        spi_unregister_driver(&spi_demo_driver);
    }
    module_exit(spi_demo_exit);
3. #define module_spi_driver(__spi_driver)
  • 功能 :内核提供的宏,简化 SPI 驱动的注册 / 注销代码 (替代手动写module_init/module_exit),是驱动开发的最佳实践。

  • 原理 :宏会自动生成module_init(调用spi_register_driver)和module_exit(调用spi_unregister_driver)的实现。

  • 使用示例 (替代上面的spi_register_driver/spi_unregister_driver):

    cs 复制代码
    // 定义SPI驱动结构体(同上)
    static struct spi_driver spi_demo_driver = {
        .driver = {
            .name = "spi-demo",
            .owner = THIS_MODULE,
            .of_match_table = spi_demo_of_match,
        },
        .probe = spi_demo_probe,
        .remove = spi_demo_remove,
        .id_table = spi_demo_id_table,
    };
    
    // 一行代码完成注册/注销,无需手动写init/exit函数
    module_spi_driver(spi_demo_driver);

二、SPI 数据传输相关 API

1. void spi_message_init(struct spi_message *m)
  • 功能 :初始化spi_message结构体,清空链表、重置状态(必须在使用spi_message前调用)。

  • 参数struct spi_message *m --- 要初始化的spi_message实例。

  • 返回值:无。

  • 使用示例

    cs 复制代码
    struct spi_message msg;
    spi_message_init(&msg); // 初始化message
    msg.spi = spi; // 绑定到目标SPI设备(spi是probe中拿到的spi_device指针)
2. void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
  • 功能 :将一个spi_transfer传输片段挂载到spi_message的传输链表尾部(一个 message 可挂载多个 transfer)。

  • 参数

    • struct spi_transfer *t --- 已配置的spi_transfer实例(包含 tx_buf/rx_buf/len 等);
    • struct spi_message *m --- 目标spi_message实例。
  • 返回值:无。

  • 使用示例

    cs 复制代码
    struct spi_transfer t = {
        .tx_buf = tx_data, // 发送缓冲区
        .len = 2,          // 发送2字节
        .speed_hz = 400000,// 传输速率400KHz
    };
    struct spi_message msg;
    
    spi_message_init(&msg);
    msg.spi = spi;
    spi_message_add_tail(&t, &msg); // 将transfer挂载到message
3. int spi_sync(struct spi_device *spi, struct spi_message *message)
  • 功能阻塞式同步传输 整个spi_message(等待传输完成后才返回),是内核 SPI 传输最核心的函数。

  • 参数

    • struct spi_device *spi --- 目标 SPI 设备;
    • struct spi_message *message --- 已挂载 transfer 的 spi_message 实例。
  • 返回值 :0 表示传输成功,负数(如-EIO)表示失败;传输完成后,message->status也会记录状态,message->actual_length记录实际传输字节数。

  • 使用示例

    cs 复制代码
    int ret = spi_sync(spi, &msg);
    if (ret < 0) {
        dev_err(&spi->dev, "SPI同步传输失败:%d\n", ret);
        return ret;
    }
    dev_dbg(&spi->dev, "传输完成,实际发送%d字节\n", msg.actual_length);
4. int spi_write(struct spi_device *spi, const void *buf, size_t len)
  • 功能简化版 SPI 写操作 (只发不收),内部封装了spi_message/spi_transfer的创建和传输,适合简单的写场景。

  • 参数

    • struct spi_device *spi --- 目标 SPI 设备;
    • const void *buf --- 要发送的数据缓冲区;
    • size_t len --- 要发送的字节数。
  • 返回值:成功返回发送的字节数,失败返回负数。

  • 使用示例 (替代你用户态代码中的write(fd, buf, 2)):

    cs 复制代码
    u8 tx_buf[2] = {0x01, 0x3f}; // 位选+段码
    int ret = spi_write(spi, tx_buf, 2);
    if (ret != 2) {
        dev_err(&spi->dev, "SPI写数据失败,实际发送%d字节\n", ret);
    }
5. int spi_read(struct spi_device *spi, void *buf, size_t len)
  • 功能简化版 SPI 读操作 (只收不发),内部封装了spi_message/spi_transfer,适合简单的读场景。

  • 参数

    • struct spi_device *spi --- 目标 SPI 设备;
    • void *buf --- 接收数据的缓冲区;
    • size_t len --- 要接收的字节数。
  • 返回值:成功返回接收的字节数,失败返回负数。

  • 使用示例

    cs 复制代码
    u8 rx_buf[2] = {0};
    int ret = spi_read(spi, rx_buf, 2);
    if (ret != 2) {
        dev_err(&spi->dev, "SPI读数据失败\n");
    } else {
        dev_dbg(&spi->dev, "读取到数据:0x%02x 0x%02x\n", rx_buf[0], rx_buf[1]);
    }
6. int spi_write_then_read(struct spi_device *spi, const void *txbuf, unsigned n_tx, void *rxbuf, unsigned n_rx)
  • 功能先写后读的组合操作(无需手动拆分两个 transfer),适合 "发送命令→接收响应" 的场景(如传感器、Flash 等)。

  • 参数

    • struct spi_device *spi --- 目标 SPI 设备;
    • const void *txbuf --- 写数据缓冲区;
    • unsigned n_tx --- 写字节数;
    • void *rxbuf --- 读数据缓冲区;
    • unsigned n_rx --- 读字节数。
  • 返回值:0 表示成功,负数表示失败。

  • 使用示例 (发送 1 字节命令,读取 2 字节响应):

    cs 复制代码
    u8 cmd = 0x01;    // 读数据命令
    u8 resp[2] = {0}; // 响应缓冲区
    int ret = spi_write_then_read(spi, &cmd, 1, resp, 2);
    if (ret < 0) {
        dev_err(&spi->dev, "先写后读失败:%d\n", ret);
    }

三、API 使用场景总结

API 分类 函数 / 宏 适用场景
驱动注册 / 注销 spi_register_driver 手动注册 SPI 驱动(不推荐,优先用宏)
spi_unregister_driver 手动注销 SPI 驱动
module_spi_driver 简化驱动注册 / 注销(推荐,一行代码搞定)
复杂传输 spi_message_init 初始化 message(必须)
spi_message_add_tail 挂载多个 transfer 到 message
spi_sync 阻塞式同步传输(核心,支持多段 transfer)
简单传输 spi_write 只发不收的简单场景(替代手动封装 message)
spi_read 只收不发的简单场景
spi_write_then_read 先写命令、后读响应的场景(如传感器)

SPI设备驱动

cs 复制代码
#include <linux/kernel.h>      // 内核核心头文件,提供printk等基础函数
#include <linux/module.h>      // 模块管理头文件,提供模块注册/注销相关宏
#include <linux/spi/spi.h>     // SPI子系统头文件,定义SPI相关结构体和API
#include <linux/fs.h>          // 文件系统头文件,定义字符设备操作接口
#include <linux/slab.h>        // 内核内存分配头文件,提供kmalloc等函数
#include <linux/cdev.h>        // 字符设备头文件,定义cdev结构体及操作函数
#include <linux/device.h>      // 设备模型头文件,用于创建设备类和设备节点
#include <linux/uaccess.h>     // 内核与用户空间数据交互头文件
#include <linux/of_device.h>   // 设备树匹配相关头文件

MODULE_LICENSE("GPL");        // 声明模块许可证为GPL,避免内核报警

// 共阴极7段数码管段码表,索引0-15对应显示0-F
char code[] = {
	0x3f, /*display 0*/
	0x06, /*display 1*/
	0x5b, /*display 2*/
	0x4f, /*display 3*/
	0x66, /*display 4*/
	0x6d, /*display 5*/
	0x7d, /*display 6*/
	0x07, /*display 7*/
	0x7f, /*display 8*/
	0x6f, /*display 9*/
	0x77, /*display A*/
	0x7c, /*display B*/
	0x39, /*display C*/
	0x5e, /*display D*/
	0x79, /*display E*/
	0x71  /*display F*/
};

// 自定义IOCTL命令:用于用户态向内核态发送显示控制指令
// _IOW表示用户写、内核读,'S'为魔数,0为命令号,int为参数类型
#define CMD_WRITE_DIGITAL _IOW('S',0,int)

// 设备私有数据结构体:整合字符设备和SPI设备相关资源
struct m74hc595_device{
	dev_t devno;               // 字符设备号(主+次设备号)
	struct cdev cdev;          // 字符设备核心结构体
	struct class *cls;         // 设备类,用于创建/dev节点
	struct device *dev;        // 设备实例,对应/dev下的设备文件
	struct spi_device *spi;    // 关联的SPI设备结构体
};

// 字符设备open函数:打开设备文件时调用
static int m74hc595_chrdev_open(struct inode *inode,struct file *file)
{
	// 通过inode中的cdev成员反向获取整个m74hc595_device结构体
	struct m74hc595_device *m74hc595 = container_of(inode->i_cdev, struct m74hc595_device,cdev);
	// 将设备结构体绑定到file的私有数据,供后续操作使用
	file->private_data = m74hc595;
	return 0;	
}

// 字符设备close函数:关闭设备文件时调用
static int m74hc595_chrdev_close(struct inode *inode,struct file *file)
{
	return 0;
}

// SPI数据传输函数:向74HC595发送位选和段码数据
// spi:目标SPI设备;pos_code:16位数据(低8位位选,高8位数字索引)
int m74hc595_spi_transfer(struct spi_device *spi,unsigned short pos_code)
{
	struct spi_message message;    // SPI消息容器,组织传输片段
	struct spi_transfer xfer[1] = {0}; // SPI传输片段,单片段传输
	unsigned char tx[2] = {0};     // 发送缓冲区:tx[0]位选,tx[1]段码
	int error;                     // 传输结果状态码
	
	// 解析pos_code:低8位提取位选值,高8位作为索引查段码表
	tx[0] = pos_code & 0xff;
	tx[1] = code[pos_code >> 8];

	// 配置SPI传输片段:指定发送缓冲区和长度
	xfer[0].tx_buf = (const void *)&tx;
	xfer[0].len = sizeof(tx)/sizeof(tx[0]);

	// 初始化SPI消息,将传输片段添加到消息链表
	spi_message_init(&message);
	spi_message_add_tail(&xfer[0],&message);

	// 同步执行SPI传输,等待传输完成
	error = spi_sync(spi,&message);
	if(error < 0)
	{
		printk("SPI write error: %d\n",error);
		return error;
	}

	return 0;
}

// 字符设备IOCTL函数:处理用户态的控制指令
static long m74hc595_chrdev_ioctl(struct file *file,unsigned int cmd , unsigned long arg)
{
	int ret = 0;
	unsigned short pos_code;      // 存储用户态传入的控制参数
	// 从file私有数据中获取设备结构体
	struct m74hc595_device *m74hc595 = file->private_data;

	// 将用户态数据拷贝到内核态,失败则返回错误
	if(copy_from_user(&pos_code,(void *)arg,sizeof(pos_code)) != 0)
	{
		printk("error copy_from_user\n");
		return -1;
	}

	// 根据指令执行对应操作
	switch(cmd)
	{
		case CMD_WRITE_DIGITAL:
			// 执行SPI数据传输
			ret = m74hc595_spi_transfer(m74hc595->spi,pos_code);
			break;
		default:
			printk("cmd error \n");
			break;
	}
	
	return ret;
}

// 字符设备操作集:绑定open/close/ioctl等函数
static const struct file_operations m74hc595_fops = {
	.owner = THIS_MODULE,              // 声明所属模块
	.open	= m74hc595_chrdev_open,    // 打开设备函数
	.release = m74hc595_chrdev_close,  // 关闭设备函数
	.unlocked_ioctl = m74hc595_chrdev_ioctl, // IOCTL处理函数
};

// 注册字符设备:完成设备号分配、cdev注册、设备类/节点创建
int register_m74hc595_chrdev(struct m74hc595_device *m74hc595)
{
	int ret;
	// 初始化cdev结构体,绑定操作集
	cdev_init(&m74hc595->cdev,&m74hc595_fops);
	// 动态分配字符设备号(主设备号由内核分配,次设备号从0开始)
	ret = alloc_chrdev_region(&m74hc595->devno,0,1,"m74hc595");
	if(ret < 0)
	{
		printk("Fail to alloc chrdev region\n");
		goto err_alloc_chrdev_region;
	}

	// 将cdev添加到内核,完成字符设备注册
	ret = cdev_add(&m74hc595->cdev,m74hc595->devno,1);
	if(ret < 0)
	{
		printk("Fail to cdev add\n");
		goto err_cdev_add;
	}

	// 创建设备类(在/sys/class/下生成m74hc595目录)
	m74hc595->cls = class_create(THIS_MODULE,"m74hc595");
	if(IS_ERR(m74hc595->cls))
	{
		printk("Fail to class create\n");
		ret =PTR_ERR(m74hc595->cls);
		goto err_class_create;
	}

	// 创建设备节点(在/dev/下生成m74hc595_device文件)
	m74hc595->dev = device_create(m74hc595->cls,NULL,m74hc595->devno,NULL,"m74hc595_device");
	if(IS_ERR(m74hc595->dev))
	{
		printk("Fail to device create\n");
		ret =PTR_ERR(m74hc595->dev);
		goto err_device_create;
	}	

	return 0;

// 错误处理:反向释放已申请的资源
err_device_create:
	class_destroy(m74hc595->cls);
err_class_create:
	cdev_del(&m74hc595->cdev);
err_cdev_add:
	unregister_chrdev_region(m74hc595->devno,1);
err_alloc_chrdev_region:
	return ret;
}

// 注销字符设备:释放注册时申请的所有资源
void unregister_m74hc595_chrdev(struct m74hc595_device *m74hc595)
{
	device_destroy(m74hc595->cls,m74hc595->devno);
	class_destroy(m74hc595->cls);
	cdev_del(&m74hc595->cdev);
	unregister_chrdev_region(m74hc595->devno,1);
}

// SPI驱动probe函数:SPI设备与驱动匹配成功时执行
static int m74hc595_spi_probe(struct spi_device *spi)
{
	struct m74hc595_device *m74hc595;
	int ret;
	// 申请设备私有数据内存(devm_kmalloc由内核自动管理释放)
	m74hc595 = devm_kmalloc(&spi->dev,sizeof(*m74hc595),GFP_KERNEL);
	if(!m74hc595)
	{
		printk("Fail to kmalloc\n");
		return -ENOMEM;
	}
	// 保存SPI设备指针,绑定私有数据到SPI设备
	m74hc595->spi = spi;
	spi_set_drvdata(spi,m74hc595);
	
	// 注册字符设备,创建用户态访问接口
	ret = register_m74hc595_chrdev(m74hc595);
	if(ret < 0)
	{
		printk("Fail to register m74hc595 chrdev\n");
		return ret;
	}
	return 0;
}

// SPI驱动remove函数:驱动卸载/设备移除时执行
static int m74hc595_spi_remove(struct spi_device *spi)
{
	// 获取绑定的私有数据,注销字符设备
	struct m74hc595_device *m74hc595 = spi_get_drvdata(spi);
	unregister_m74hc595_chrdev(m74hc595);
	return 0;
}

// 设备树匹配表:匹配设备树中compatible为"m74hc595"的节点
static const struct of_device_id m74hc595_of_ids[] = {
	{.compatible = "m74hc595",},
	{},
};
// 声明设备树匹配表,供内核匹配使用
MODULE_DEVICE_TABLE(of, m74hc595_of_ids);

// SPI驱动结构体:绑定probe/remove函数和设备树匹配表
static struct spi_driver m74hc595_spi_driver = {
	.driver = {
		.name = "m74hc595",                   // 驱动名称
		.of_match_table = of_match_ptr(m74hc595_of_ids), // 设备树匹配表
	},
	.probe = m74hc595_spi_probe,        // 匹配成功时的初始化函数
	.remove = m74hc595_spi_remove,      // 卸载时的清理函数
};

// 宏定义:自动完成SPI驱动的注册和注销
module_spi_driver(m74hc595_spi_driver);

应用层测试代码

cs 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define CMD_WRITE_DIGITAL _IOW('S',0,int)

int main(int argc , const char *argv[])
{
	int fd;
	int ret;
	char pos  = 0;
	char digital;
	unsigned short value;

	fd = open("/dev/m74hc595_device",O_RDWR);
	if(fd < 0)
	{
		perror("Fail to oprn");
		exit(-1);
	}

	while(1)
	{
		for(digital=0;digital<16;digital++)
		{
			value =( (digital<<8) | (1<<pos));
			ret =ioctl(fd,CMD_WRITE_DIGITAL,(unsigned long)&value);
			if(ret < 0)
			{
				perror("ioctl");
				return -1;
			}
			pos = (pos+1) % 4;
			sleep(1);
		}
	}
	return 0;
}
相关推荐
The️2 小时前
Linux驱动开发之Read_Write函数
linux·运维·服务器·驱动开发·ubuntu·交互
FserSuN7 小时前
AI编程 - 规范驱动开发(SDD)学习
驱动开发·学习·ai编程
TangDuoduo00051 天前
【Linux I2C设备驱动】
linux·驱动开发
The️1 天前
Linux驱动开发之Open_Close函数
linux·运维·驱动开发·mcu·ubuntu
LCG元1 天前
嵌入式GUI设计:STM32F429+LVGL,智能仪表盘界面开发指南
驱动开发·stm32·嵌入式硬件
小龙报2 天前
【51单片机】 给单片机加 “安全锁”!看门狗 WDT:原理 + 配置 + 复位验证全拆解,让程序稳定不跑飞
驱动开发·stm32·单片机·嵌入式硬件·物联网·51单片机·硬件工程
码农编程录2 天前
【notes12】kbuild,内核模块化,字符设备驱动,设备树,platform总线,设备驱动模型
驱动开发
乔碧萝成都分萝2 天前
二十六、IIO子系统 + SPI子系统 + ICM20608
linux·驱动开发·嵌入式
A星空1232 天前
二、交叉编译工具链(arm-linux-gnueabihf-gcc)安装与验证,搭建 TFTP+NFS 服务,调试开发板网络连通性;
linux·c++·驱动开发·单片机·嵌入式硬件