【Linux】TCS34725 颜色传感器设备驱动

一、概述

此笔记主要是学习 Linux 中的 I2C 驱动,顺便验证一下 TCS34725 传感器的使用,主要内容还是程序记录,方便编写其他 I2C 设备驱动时做参考,所以关于 TCS34725 这里就不过多描述了,需要的小伙伴可以浏览我之前的笔记:TCS34725 颜色传感器设备驱动程序

二、添加 I2C 设备

学习到 I2C 驱动的小伙伴应该都知道平台设备这个概念了,所以这里需要使用到 I2C 总线,由于 I2C 总线驱动基本都是由板子厂商帮我们移植好的,所以这里就不关注 I2C 总线驱动了,有需要的小伙伴自行了解。

添加设备也有两种方式,这里我以设备树的形式添加设备为例,传统的添加方式相比设备树比较麻烦一些,这里就跳过这部分类容。

  1. 打开设备树文件,向 I2C 节点中追加 TCS34725 传感器的设备信息,如下所示:

    &i2c0{
        rgb_colour@29{
            compatible = "colour,tcs34725";
            reg = ;
        };
    };
    

    注意:&i2c0的i2c0一定是i2c设备节点的标签,我尝试使用节点名称引用,发现编译不通过,所以当你设备树中的i2c节点没有标签的话,自行添加一个。当然也是可以直接添加到i2c设备节点中的。

  2. 编译设备树,并烧写设备树文件
  3. 查看设备节点是否添加成功
    通过命令 ls /sys/bus/i2c/devices/ 查看 I2C 设备,其中 0-0029 就是我们添加的设备,可以通过设备的 name 属性查看设备的名称,如下图所示:
    【Linux】TCS34725 颜色传感器设备驱动插图

    注意:图中的中 name 属性变量,就是在设备树中添加的 compatible 属性,也是 I2C 总线用于匹配设备驱动时的匹配名称。

三、I2C 设备驱动编写

为了方便测试,这里是以模块的形式加载驱动设备的,没有直接在内核文件中,所以测试时不用重新编译内核文件。

  1. 出入口函数
    这两个函数是模块的出入口函数,编写驱动模块是就少不了它两

    /* 将上面两个函数指定为驱动的入口和出口函数 */
    module_init(tcs3472x_driver_init);
    module_exit(tcs3472x_driver_exit);
    
  2. 加载和卸载 I2C 设备
    通过 i2c_add_driver 和 i2c_del_driver 函数加载和卸载 I2C 设备的,代码如下所示:

    /**
     * @brief 驱动入口函数
     * @return 0,成功;其他负值,失败
    */
    static int __init tcs3472x_driver_init(void)
    {
        int ret;
        pr_info("tcs3472x_driver_initn");
        ret = i2c_add_driver(&tcs3472x_driver);
        return ret;
    }
    
    /**
     * @brief 驱动出口函数
     * @return 0,成功;其他负值,失败
    */
    static void __exit tcs3472x_driver_exit(void)
    {
        pr_info("tcs3472x_driver_exitn");
        i2c_del_driver(&tcs3472x_driver);
    }
    
  3. 设备信息

    /* 传统匹配方式 ID 列表 */
    static const struct i2c_device_id gtp_device_id[] = {
        {"colour,tcs34721", 0},
        {"colour,tcs34725", 0},
        {"colour,tcs34723", 0},
        {"colour,tcs34727", 0},
        {}};
    
    /* 设备树匹配表 */
    static const struct of_device_id tcs3472x_of_match_table[] = {
        {.compatible = "colour,tcs34721"},
        {.compatible = "colour,tcs34725"},
        {.compatible = "colour,tcs34723"},
        {.compatible = "colour,tcs34727"},
        {/* sentinel */}};
    
    /* i2c总线设备结构体 */
    struct i2c_driver tcs3472x_driver = {
        .probe = tcs3472x_probe,
        .remove = tcs3472x_remove,
        .id_table = gtp_device_id,
        .driver = {
            .name = "colour,tcs3472x",
            .owner = THIS_MODULE,
            .of_match_table = tcs3472x_of_match_table,
        },
    };
    

    注意:

    • 设备树中的 compatible 属性会和匹配表中查找,当名称一样时,设备和驱动就匹配成功了。
    • 匹配成功时会调用 .probe 函数
    • 卸载模块是会调用 .remove 函数
  4. .probe 和 .remove 函数

    /**
     * @brief i2c 驱动的 probe 函数,当驱动与设备匹配以后此函数就会执行
     * @param client i2c 设备
     * @param id i2c 设备 ID
     * @return 0,成功;其他负值,失败
    */
    static int tcs3472x_probe(struct i2c_client *client, const struct i2c_device_id *id)
    {
        int ret = -1;                       // 保存错误状态码
        struct tcs3472x_dev *tcs_dev;       // 设备数据结构体  
    
        /*---------------------注册字符设备驱动-----------------*/
    
        /* 驱动与总线设备匹配成功 */
        printk(KERN_EMERG "t  %s match successed  rn", client->name);
    
        /* 申请内存并与 client->dev 进行绑定。*/
        /* 在 probe 函数中使用时,当设备驱动被卸载,该内存被自动释放,也可使用 devm_kfree() 函数直接释放 */
        tcs_dev = devm_kzalloc(&client->dev, sizeof(*tcs_dev), GFP_KERNEL);
        if(!tcs_dev)
        {
            pr_err("Failed to request memory rn");
            return -ENOMEM;
        }
    
        /* 1、创建设备号 */
        /* 采用动态分配的方式,获取设备编号,次设备号为0 */
        /* 设备名称为 TCS3472x_NAME,可通过命令 cat /proc/devices 查看 */
        /* TCS3472x_CNT 为1,只申请一个设备编号 */
        ret = alloc_chrdev_region(&tcs_dev->devid, 0, TCS3472x_CNT, TCS3472x_NAME);
        if (ret cdev.owner = THIS_MODULE;
        cdev_init(&tcs_dev->cdev, &tcs3472x_ops);
    
        /* 3、添加一个 cdev */
        // 添加设备至cdev_map散列表中
        ret = cdev_add(&tcs_dev->cdev, tcs_dev->devid, TCS3472x_CNT);
        if (ret class = class_create(THIS_MODULE, TCS3472x_NAME);
        if (IS_ERR(tcs_dev->class)) 
        {
            pr_err("Failed to create device class rn");
            goto del_cdev;
        }
    
        /* 5、创建设备,设备名是 TCS3472x_NAME */
        /*创建设备 TCS3472x_NAME 指定设备名,*/
        tcs_dev->device = device_create(tcs_dev->class, NULL, tcs_dev->devid, NULL, TCS3472x_NAME);
        if (IS_ERR(tcs_dev->device)) {
            goto destroy_class;
        }
        tcs_dev->client = client;
    
        /* 保存 ap3216cdev 结构体 */
        i2c_set_clientdata(client, tcs_dev);
    
        return 0;
    
    destroy_class:
        device_destroy(tcs_dev->class, tcs_dev->devid);
    del_cdev:
        cdev_del(&tcs_dev->cdev);
    del_unregister:
        unregister_chrdev_region(tcs_dev->devid, TCS3472x_CNT);
        return -EIO;
    }
    
    /**
     * @brief i2c 驱动的 remove 函数,移除 i2c 驱动的时候此函数会执行
     * @param client i2c 设备
     * @return 0,成功;其他负值,失败
    */
    static int tcs3472x_remove(struct i2c_client *client)
    {
        struct tcs3472x_dev *tcs_dev = i2c_get_clientdata(client);
    
        /*---------------------注销字符设备驱动-----------------*/
    
        /* 1、删除 cdev */
        cdev_del(&tcs_dev->cdev);
        /* 2、注销设备号 */
        unregister_chrdev_region(tcs_dev->devid, TCS3472x_CNT);
        /* 3、注销设备 */
        device_destroy(tcs_dev->class, tcs_dev->devid);
        /* 4、注销类 */
        class_destroy(tcs_dev->class);
        return 0;
    }
    

    注意:从上面代码中可以看出,这里主要是字符设备的操作过程成,所以到这里就可以直接使用 file_operations 函数进行操作了。

  5. I2C 数据的读写

    /**
     * @brief 向 I2C 从设备的寄存器写入数据
     * 
     * @param client I2C 设备
     * @param reg 要写入的寄存器首地址
     * @param val 要写入的数据缓冲区
     * @param len 要写入的数据长度
     * @return 返回执行的结果
     */
    static int i2c_write_regs(struct i2c_client *client, u8 reg, u8 *buf, u8 len)
    {
        int ret = 0;
        u8 write_buf[256];
        struct i2c_msg msg; //要发送的数据结构体
    
        /* 寄存器首地址 */
        write_buf[0] = reg;
        /* 将要写入的数据拷贝到数组 write_buf 中 */
        memcpy(&write_buf[1], buf, len);
    
        msg.addr = client->addr;             // I2C 从设备在总线上的地址
        msg.flags = 0;                      // 标记为发送数据
        msg.buf = write_buf;                // 要写入的数据缓冲区
        msg.len = len + 1;                  // 要写入的数据长度
    
        // printk(PRINTK_GRADE "i2c write reg = %x  data = %xn", msg.buf[0], msg.buf[1]);
        /* 执行发送 */
        ret = i2c_transfer(client->adapter, &msg, 1);
        if (ret != 1)
        {
            printk(PRINTK_GRADE "i2c write failed=%d reg=%06x len=%dn", ret, reg, len);
            return -1;
        }
        return 0;
    }
    
    /**
     * @brief 读取 I2C 从设备的寄存器数据
     * 
     * @param client I2C 设备
     * @param reg 要读取的寄存器首地址
     * @param val 要读取的数据缓冲区
     * @param len 要读取的数据长度
     * @return 返回执行的结果
     */
    static int i2c_read_regs(struct i2c_client *client, u8 reg, u8 *val, u32 len)
    {
        int ret = 0;
        struct i2c_msg msg[2];
    
        /* msg[0] 是读取从设备寄存器的首地址 */
        msg[0].addr = client->addr;      // I2C 从设备在总线上的地址
        msg[0].flags = 0;                   // 标记为发送数据
        msg[0].buf = ®                  // 需要读取的寄存器首地址
        msg[0].len = 1;                     // reg 的长度
    
        /* msg[1] 是读取的数据 */
        msg[1].addr = client->addr;      // I2C 从设备在总线上的地址
        msg[1].flags = I2C_M_RD;            // 标记为读取数据
        msg[1].buf = val;                   // 读取数据的保存位置
        msg[1].len = len;               // 要读取的数据长度
    
        ret = i2c_transfer(client->adapter, msg, 2);
        if (ret != 2)
        {
            printk(PRINTK_GRADE "i2c read failed=%d reg=%06x len=%dn",ret, reg, len);
            return -1;
        }
        return 0;
    }
    

    注意: I2C 的读写都是通过 i2c_transfer 函数进行完成的

  6. I2C 读写函数的使用

    /**
     * @brief 从 tcs3472x 设备的寄存器中读取 8 位数据
     * 
     * @param dev tcs3472x 设备
     * @param reg 寄存器地址
     * @param val 读取的值
     * @return 返回执行的结果
     */
    static int i2c_tcs3472x_read8(struct tcs3472x_dev *dev, u8 reg, u8 *val)
    {
        return i2c_read_regs((struct i2c_client *)dev->client, TCS34725_COMMAND_BIT | reg, val, 1);
    }
    
    /**
     * @brief 从 tcs3472x 设备的寄存器中读取 16 位数据
     * 
     * @param dev tcs3472x 设备
     * @param reg 寄存器地址
     * @param val 读取的值
     * @return 返回执行的结果
     */
    static int i2c_tcs3472x_read16(struct tcs3472x_dev *dev, u8 reg, u16 *data)
    {
        int ret = 0;
        u8 val[2];
        ret = i2c_read_regs((struct i2c_client *)dev->client, TCS34725_COMMAND_BIT | reg, val, 2);
        if (ret client, TCS34725_COMMAND_BIT | reg, &write_buf, 1);
        return ret;
    }
    

注意:到这里相信对 I2C 的驱动编写就没什么难度了吧,驱动的编写流程也算是完成了,最后在吧设备使用的代码添加进行,I2C 的驱动就算完成了。

四、程序源码

tcs3472x.h

/**
 * @file tcs3472x.h
 *
 */

#ifndef _TCS3472X_H_
#define _TCS3472X_H_

/*********************
 *      INCLUDES
 *********************/
// #include 
/*********************
 *      DEFINES
 *********************/

#define TCS34725_address          (0x29)    // 设备地址
#define TCS34725_COMMAND_BIT      (0x80)    // 命令字节

/* TCS34725传感器配置寄存器 */
#define TCS34725_ENABLE           (0x00)    // 启用传感器
#define TCS34725_ATIME            (0x01)    // 集成时间
#define TCS34725_WTIME            (0x03)    // R / W 等待时间
#define TCS34725_AILTL            (0x04)    // 清除通道下限中断阈值
#define TCS34725_AILTH            (0x05)
#define TCS34725_AIHTL            (0x06)    // 清除通道上限中断阈值
#define TCS34725_AIHTH            (0x07)    // 配置寄存器
#define TCS34725_PERS             (0x0C)    // 中断永久性过滤器
#define TCS34725_CONFIG           (0x0C)    // 中断永久性过滤器
#define TCS34725_CONTROL          (0x0F)    // 增益倍数
#define TCS34725_ID               (0x12)    // 设备识别号 0x44 = TCS34721/TCS34725, 0x4D = TCS34723/TCS34727
#define TCS34725_STATUS           (0x13)    // 设备状态
#define TCS34725_CDATAL           (0x14)    // 光照强度低字节
#define TCS34725_CDATAH           (0x15)    // 光照强度高字节
#define TCS34725_RDATAL           (0x16)    // 红色数据低字节
#define TCS34725_RDATAH           (0x17)
#define TCS34725_GDATAL           (0x18)    // 绿色数据低字节
#define TCS34725_GDATAH           (0x19)
#define TCS34725_BDATAL           (0x1A)    // 蓝色数据低字节
#define TCS34725_BDATAH           (0x1B)

/* 启动传感器 */
#define TCS34725_ENABLE_AIEN      (0x10)    // RGBC中断使能
#define TCS34725_ENABLE_WEN       (0x08)    // 等待启用:写1激活等待计时器,写0禁用等待计时器
#define TCS34725_ENABLE_AEN       (0x02)    // RGBC启用:写1激活RGBC,写0禁用RGBC
#define TCS34725_ENABLE_PON       (0x01)    // 通电:写入1激活内部振荡器,0禁用内部振荡器

/**********************
 *      TYPEDEFS
 **********************/

/* 集成时间配置参数
 * 最大RGBC计数 = (256 - cycles) × 1024 
 * 集成时间 ≈ (256 - cycles) × 2.4ms */
typedef enum
{
    TCS34725_INTEGRATIONTIME_2_4MS  = 0xFF,   // 2.4ms - 1 cycles   - Max Count: 1024
    TCS34725_INTEGRATIONTIME_24MS   = 0xF6,   // 24ms  - 10 cycles  - Max Count: 10240
    TCS34725_INTEGRATIONTIME_50MS   = 0xEC,   // 50ms  - 20 cycles  - Max Count: 20480
    TCS34725_INTEGRATIONTIME_101MS  = 0xD5,   // 101ms - 42 cycles  - Max Count: 43008
    TCS34725_INTEGRATIONTIME_154MS  = 0xC0,   // 154ms - 64 cycles  - Max Count: 65535
    TCS34725_INTEGRATIONTIME_700MS  = 0x00    // 700ms - 256 cycles - Max Count: 65535
}
tcs34725_integration_time_t;

/* 增益倍数 */
typedef enum
{
    TCS34725_GAIN_1X                = 0x00,   // 1X增益
    TCS34725_GAIN_4X                = 0x01,   // 4X增益
    TCS34725_GAIN_16X               = 0x02,   // 16X增益
    TCS34725_GAIN_60X               = 0x03    // 60X增益
}
tcs34725_gain_multiple_t;

/**********************
 * GLOBAL PROTOTYPES
 **********************/

/**********************
 *      MACROS
 **********************/

#endif /* _TCS3472X_H_ */

i2c_tcs34725_module.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include "tcs3472x.h"
/***************************************************************
文件名 : i2c_tcs34725_module.c
作者 : jiaozhu
版本 : V1.0
描述 : 颜色传感器 TCS34725 驱动文件。
其他 : 无
日志 : 初版 V1.0 2023/1/4
***************************************************************/

#define PRINTK_GRADE KERN_INFO

/*------------------字符设备内容----------------------*/
#define TCS3472x_NAME "I2C_TCS3472x"
#define TCS3472x_CNT (1)

struct tcs3472x_dev {
    struct i2c_client *client;          // i2c 设备
    dev_t devid;                        // 设备号
    struct cdev cdev;                   // cdev
    struct class *class;                // 类
    struct device *device;              // 设备
    struct device_node *node;           // 设备节点
    u16 colour_r, colour_g, colour_b, colour_c;     // tcs3472x 设备的RGBC数据
};

/**
 * @brief 向 I2C 从设备的寄存器写入数据
 * 
 * @param client I2C 设备
 * @param reg 要写入的寄存器首地址
 * @param val 要写入的数据缓冲区
 * @param len 要写入的数据长度
 * @return 返回执行的结果
 */
static int i2c_write_regs(struct i2c_client *client, u8 reg, u8 *buf, u8 len)
{
    int ret = 0;
    u8 write_buf[256];
    struct i2c_msg msg; //要发送的数据结构体

    /* 寄存器首地址 */
    write_buf[0] = reg;
    /* 将要写入的数据拷贝到数组 write_buf 中 */
    memcpy(&write_buf[1], buf, len);

    msg.addr = client->addr;             // I2C 从设备在总线上的地址
    msg.flags = 0;                      // 标记为发送数据
    msg.buf = write_buf;                // 要写入的数据缓冲区
    msg.len = len + 1;                  // 要写入的数据长度

    // printk(PRINTK_GRADE "i2c write reg = %x  data = %xn", msg.buf[0], msg.buf[1]);
    /* 执行发送 */
    ret = i2c_transfer(client->adapter, &msg, 1);
    if (ret != 1)
    {
        printk(PRINTK_GRADE "i2c write failed=%d reg=%06x len=%dn", ret, reg, len);
        return -1;
    }
    return 0;
}

/**
 * @brief 读取 I2C 从设备的寄存器数据
 * 
 * @param client I2C 设备
 * @param reg 要读取的寄存器首地址
 * @param val 要读取的数据缓冲区
 * @param len 要读取的数据长度
 * @return 返回执行的结果
 */
static int i2c_read_regs(struct i2c_client *client, u8 reg, u8 *val, u32 len)
{
    int ret = 0;
    struct i2c_msg msg[2];

    /* msg[0] 是读取从设备寄存器的首地址 */
    msg[0].addr = client->addr;      // I2C 从设备在总线上的地址
    msg[0].flags = 0;                   // 标记为发送数据
    msg[0].buf = ®                  // 需要读取的寄存器首地址
    msg[0].len = 1;                     // reg 的长度

    /* msg[1] 是读取的数据 */
    msg[1].addr = client->addr;      // I2C 从设备在总线上的地址
    msg[1].flags = I2C_M_RD;            // 标记为读取数据
    msg[1].buf = val;                   // 读取数据的保存位置
    msg[1].len = len;               // 要读取的数据长度

    ret = i2c_transfer(client->adapter, msg, 2);
    if (ret != 2)
    {
        printk(PRINTK_GRADE "i2c read failed=%d reg=%06x len=%dn",ret, reg, len);
        return -1;
    }
    return 0;
}

/**
 * @brief 从 tcs3472x 设备的寄存器中读取 8 位数据
 * 
 * @param dev tcs3472x 设备
 * @param reg 寄存器地址
 * @param val 读取的值
 * @return 返回执行的结果
 */
static int i2c_tcs3472x_read8(struct tcs3472x_dev *dev, u8 reg, u8 *val)
{
    return i2c_read_regs((struct i2c_client *)dev->client, TCS34725_COMMAND_BIT | reg, val, 1);
}

/**
 * @brief 从 tcs3472x 设备的寄存器中读取 16 位数据
 * 
 * @param dev tcs3472x 设备
 * @param reg 寄存器地址
 * @param val 读取的值
 * @return 返回执行的结果
 */
static int i2c_tcs3472x_read16(struct tcs3472x_dev *dev, u8 reg, u16 *data)
{
    int ret = 0;
    u8 val[2];
    ret = i2c_read_regs((struct i2c_client *)dev->client, TCS34725_COMMAND_BIT | reg, val, 2);
    if (ret client, TCS34725_COMMAND_BIT | reg, &write_buf, 1);
    return ret;
}

/**
 * @brief 读取 tcs3472x 设备颜色和光照强度数据,注意每次读取时,
 * 需要保证颜色传感器之间有足够的采样时间,集成时间 ≈ (256 - cycles) × 2.4ms
 * 
 * @param dev tcs3472x 设备
 * @return 返回执行的结果
 */
static int tcs3472x_colour_data(struct tcs3472x_dev *dev)
{
    int ret = 0;
    /* 读取 colour_r */
    ret = i2c_tcs3472x_read16(dev, TCS34725_RDATAL, &dev->colour_r);
    if (ret colour_g);
    if (ret colour_b);
    if (ret colour_c);
    if (ret f_path.dentry->d_inode->i_cdev;
    struct tcs3472x_dev *tcs_dev = container_of(cdev, struct tcs3472x_dev, cdev);

    printk(PRINTK_GRADE "tcs3472x openrn");

    return tcs3472x_device_start(tcs_dev);
}

/**
 * @brief 从设备读取数据
 * 
 * @param filp 要打开的设备文件(文件描述符)
 * @param buf 返回给用户空间的数据缓冲区
 * @param cnt 要读取的数据长度
 * @param offt 相对于文件首地址的偏移
 * @return 0 成功;其他 失败
 */
static ssize_t tcs3472x_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    u16 data[4];
    int ret = 0;
    /* 从 file 结构体获取 cdev 指针,再根据 cdev 获取 ap3216c_dev 首地址 */
    struct cdev *cdev = filp->f_path.dentry->d_inode->i_cdev;
    struct tcs3472x_dev *tcs_dev = container_of(cdev, struct tcs3472x_dev, cdev);

    // printk(PRINTK_GRADE "tcs3472x readrn");

    ret = tcs3472x_colour_data(tcs_dev);

    // printk(PRINTK_GRADE "R = %d    G = %d    B = %d    C = %drn",
    //  tcs_dev->colour_r, tcs_dev->colour_g, tcs_dev->colour_b, tcs_dev->colour_c);

    data[0] = tcs_dev->colour_r;
    data[1] = tcs_dev->colour_g;
    data[2] = tcs_dev->colour_b;
    data[3] = tcs_dev->colour_c;
    /* 将数据传递给用户空间 */
    ret = copy_to_user(buf, data, sizeof(data));

    return ret;
}

/**
 * @brief 向设备写数据
 * @param filp 设备文件,表示打开的文件描述符
 * @param buf 要写给设备写入的数据
 * @param cnt 要写入的数据长度
 * @param offt 相对于文件首地址的偏移
 * @return 写入的字节数,如果为负值,表示写入失败
*/
static ssize_t tcs3472x_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    int ret = 0;
    u8 write_buf[256];
    /* 从 file 结构体获取 cdev 指针,再根据 cdev 获取 ap3216c_dev 首地址 */
    struct cdev *cdev = filp->f_path.dentry->d_inode->i_cdev;
    struct tcs3472x_dev *tcs_dev = container_of(cdev, struct tcs3472x_dev, cdev);

    // printk(PRINTK_GRADE "tcs3472x writern");
    if (cnt != 2)
    {
        printk(PRINTK_GRADE "data in wrong format!rn");
    }

    /* 接收用户空间传递的数据 */
    ret = copy_from_user(write_buf, buf, cnt);
    if(ret != 0){
        printk(PRINTK_GRADE "kernel recevdata failed!rn");
    }

    /* 第一个参数为 1 时,表示设备集成时间 */
    if (write_buf[0] == 1)
    {
        ret = tcs3472x_integration_time(tcs_dev, write_buf[1]);
    }
    /* 第一个参数为 2 时,设置增益倍数 */
    else if (write_buf[0] == 2)
    {
        ret = tcs3472x_gain_multiple(tcs_dev, write_buf[1]);
    }
    else
    {
        printk(PRINTK_GRADE "data in wrong format!rn");
        ret = -1;
    }

    return ret;
}

/**
 * @brief 关闭/释放设备
 * @param filp 要关闭的设备文件(文件描述符)
 * @return 0 成功;其他 失败
*/
static int tcs3472x_release(struct inode *inode, struct file *filp)
{
    /* 从 file 结构体获取 cdev 指针,再根据 cdev 获取 ap3216c_dev 首地址 */
    struct cdev *cdev = filp->f_path.dentry->d_inode->i_cdev;
    struct tcs3472x_dev *tcs_dev = container_of(cdev, struct tcs3472x_dev, cdev);

    //printk("chrdevbase release!rn");
    printk(PRINTK_GRADE "tcs3472x releasern");

    tcs3472x_device_stop(tcs_dev);
    return 0;
}

/* 设备操作函数结构体 */
static struct file_operations tcs3472x_ops = {
    .owner = THIS_MODULE, 
    .open = tcs3472x_open,
    .read = tcs3472x_read,
    .write = tcs3472x_write,
    .release = tcs3472x_release,
};

/**
 * @brief i2c 驱动的 probe 函数,当驱动与设备匹配以后此函数就会执行
 * @param client i2c 设备
 * @param id i2c 设备 ID
 * @return 0,成功;其他负值,失败
*/
static int tcs3472x_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    int ret = -1;                       // 保存错误状态码
    struct tcs3472x_dev *tcs_dev;       // 设备数据结构体  

    /*---------------------注册字符设备驱动-----------------*/

    /* 驱动与总线设备匹配成功 */
    printk(KERN_EMERG "t  %s match successed  rn", client->name);

    /* 申请内存并与 client->dev 进行绑定。*/
    /* 在 probe 函数中使用时,当设备驱动被卸载,该内存被自动释放,也可使用 devm_kfree() 函数直接释放 */
    tcs_dev = devm_kzalloc(&client->dev, sizeof(*tcs_dev), GFP_KERNEL);
    if(!tcs_dev)
    {
        pr_err("Failed to request memory rn");
        return -ENOMEM;
    }

    /* 1、创建设备号 */
    /* 采用动态分配的方式,获取设备编号,次设备号为0 */
    /* 设备名称为 TCS3472x_NAME,可通过命令 cat /proc/devices 查看 */
    /* TCS3472x_CNT 为1,只申请一个设备编号 */
    ret = alloc_chrdev_region(&tcs_dev->devid, 0, TCS3472x_CNT, TCS3472x_NAME);
    if (ret cdev.owner = THIS_MODULE;
    cdev_init(&tcs_dev->cdev, &tcs3472x_ops);

    /* 3、添加一个 cdev */
    // 添加设备至cdev_map散列表中
    ret = cdev_add(&tcs_dev->cdev, tcs_dev->devid, TCS3472x_CNT);
    if (ret class = class_create(THIS_MODULE, TCS3472x_NAME);
    if (IS_ERR(tcs_dev->class)) 
    {
        pr_err("Failed to create device class rn");
        goto del_cdev;
    }

    /* 5、创建设备,设备名是 TCS3472x_NAME */
    /*创建设备 TCS3472x_NAME 指定设备名,*/
    tcs_dev->device = device_create(tcs_dev->class, NULL, tcs_dev->devid, NULL, TCS3472x_NAME);
    if (IS_ERR(tcs_dev->device)) {
        goto destroy_class;
    }
    tcs_dev->client = client;

    /* 保存 ap3216cdev 结构体 */
    i2c_set_clientdata(client, tcs_dev);

    return 0;

destroy_class:
    device_destroy(tcs_dev->class, tcs_dev->devid);
del_cdev:
    cdev_del(&tcs_dev->cdev);
del_unregister:
    unregister_chrdev_region(tcs_dev->devid, TCS3472x_CNT);
    return -EIO;
}

/**
 * @brief i2c 驱动的 remove 函数,移除 i2c 驱动的时候此函数会执行
 * @param client i2c 设备
 * @return 0,成功;其他负值,失败
*/
static int tcs3472x_remove(struct i2c_client *client)
{
    struct tcs3472x_dev *tcs_dev = i2c_get_clientdata(client);

    /*---------------------注销字符设备驱动-----------------*/

    /* 1、删除 cdev */
    cdev_del(&tcs_dev->cdev);
    /* 2、注销设备号 */
    unregister_chrdev_region(tcs_dev->devid, TCS3472x_CNT);
    /* 3、注销设备 */
    device_destroy(tcs_dev->class, tcs_dev->devid);
    /* 4、注销类 */
    class_destroy(tcs_dev->class);
    return 0;
}

/* 传统匹配方式 ID 列表 */
static const struct i2c_device_id gtp_device_id[] = {
    {"colour,tcs34721", 0},
    {"colour,tcs34725", 0},
    {"colour,tcs34723", 0},
    {"colour,tcs34727", 0},
    {}};

/* 设备树匹配表 */
static const struct of_device_id tcs3472x_of_match_table[] = {
    {.compatible = "colour,tcs34721"},
    {.compatible = "colour,tcs34725"},
    {.compatible = "colour,tcs34723"},
    {.compatible = "colour,tcs34727"},
    {/* sentinel */}};

/* i2c总线设备结构体 */
struct i2c_driver tcs3472x_driver = {
    .probe = tcs3472x_probe,
    .remove = tcs3472x_remove,
    .id_table = gtp_device_id,
    .driver = {
        .name = "colour,tcs3472x",
        .owner = THIS_MODULE,
        .of_match_table = tcs3472x_of_match_table,
    },
};

/**
 * @brief 驱动入口函数
 * @return 0,成功;其他负值,失败
*/
static int __init tcs3472x_driver_init(void)
{
    int ret;
    pr_info("tcs3472x_driver_initn");
    ret = i2c_add_driver(&tcs3472x_driver);
    return ret;
}

/**
 * @brief 驱动出口函数
 * @return 0,成功;其他负值,失败
*/
static void __exit tcs3472x_driver_exit(void)
{
    pr_info("tcs3472x_driver_exitn");
    i2c_del_driver(&tcs3472x_driver);
}

/* 将上面两个函数指定为驱动的入口和出口函数 */
module_init(tcs3472x_driver_init);
module_exit(tcs3472x_driver_exit);

/* LICENSE 和作者信息 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("JIAOZHU");
MODULE_INFO(intree, "Y");

Makefile

# 模块需要的.o文件
obj-m := i2c_tcs34725_module.o

# linux内核源码和当前路径
KERNELDIR := /home/work/arm_linux/kernel
CURRENT_PATH := $(shell pwd)

# EXTRA_CFLAGS := -I $(CURRENT_PATH)

# 配置编译器
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
CC  = $(CROSS_COMPILE)gcc

# 模块编译目标
all: 
    $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
    $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

五、测试程序

drive_read_app.c

#include "sys/stat.h"
#include  
#include  
#include  
#include  
#include  
#include  
#include  
#include  
#include  
#include  
#include  
#include 

#include "tcs3472x.h"
/***************************************************************
文件名 : drive_read_app.c
作者 : jiaozhu
版本 : V1.0
描述 : 驱动读取测试
其他 : 使用方法:./drive_read_app [/dev/xxx]
argv[1] 需要读取的驱动
日志 : 初版 V1.0 2023/1/4
***************************************************************/

/**
* @brief main 主程序
* @param argc argv 数组元素个数
* @param argv 具体参数
* @return 0 成功;其他 失败
*/
int main(int argc, char *argv[])
{
    int fd;
    char *filename;
    unsigned short data_buf[4];
    unsigned char write_buf[2];
    int ret = 0;

    if(argc != 2){
        printf("Error Usage!rn");
        return -1;
    }

    filename = argv[1];

    /* 打开驱动文件 */
    fd = open(filename, O_RDWR);
    if(!fd){
        printf("Can't open file %srn", filename);
        return -1;
    }

    /* 设置集成时间 */
    write_buf[0] = 1;
    write_buf[1] = TCS34725_INTEGRATIONTIME_700MS;
    ret = write(fd, write_buf, 2);
    if(ret 

六、测试

  1. 加载模块, 命令是 insmod i2c_tcs34725_module.ko
    【Linux】TCS34725 颜色传感器设备驱动插图1
  2. 运行测试 app,命令是 ./drive_read_app /dev/I2C_TCS3472x
    【Linux】TCS34725 颜色传感器设备驱动插图2
  3. 卸载模块,命令是 rmmod i2c_tcs34725_module
    【Linux】TCS34725 颜色传感器设备驱动插图3

到此 TCS34725 的驱动完成了,有不好的地方望各位大佬指出,最后声明一下,此程序只供学习,出现任何问题概不负责。

文章来源于互联网:【Linux】TCS34725 颜色传感器设备驱动

THE END
分享
二维码