一、概述

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

二、添加 I2C 设备

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

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

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

    &i2c0{
    rgb_colour@29{
    compatible = "colour,tcs34725";
    reg = <0x29>;
    };
    };

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

  2. 编译设备树,并烧写设备树文件

  3. 查看设备节点是否添加成功

    通过命令 ls /sys/bus/i2c/devices/ 查看 I2C 设备,其中 0-0029 就是我们添加的设备,可以通过设备的 name 属性查看设备的名称,如下图所示:

    注意:图中的中 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_init\n");
    ret = i2c_add_driver(&tcs3472x_driver);
    return ret;
    } /**
    * @brief 驱动出口函数
    * @return 0,成功;其他负值,失败
    */
    static void __exit tcs3472x_driver_exit(void)
    {
    pr_info("tcs3472x_driver_exit\n");
    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 \r\n", 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 \r\n");
    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 < 0)
    {
    pr_err("%s Couldn't alloc_chrdev_region, ret = %d \r\n", TCS3472x_NAME, ret);
    return -ENOMEM;
    } /* 2、初始化 cdev */
    /* 关联字符设备结构体 cdev 与文件操作结构体 file_operations */
    tcs_dev->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 < 0)
    {
    pr_err("fail to add cdev \r\n");
    goto del_unregister;
    } /* 4、创建类 */
    tcs_dev->class = class_create(THIS_MODULE, TCS3472x_NAME);
    if (IS_ERR(tcs_dev->class))
    {
    pr_err("Failed to create device class \r\n");
    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 = %x\n", 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=%d\n", 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 = &reg; // 需要读取的寄存器首地址
    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=%d\n",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 < 0)
    {
    return -1;
    } *data = val[1] << 8 | val[0];
    return ret;
    } /**
    * @brief 向 tcs3472x 设备的寄存器中写入 8 位数据
    *
    * @param dev tcs3472x 设备
    * @param reg 寄存器地址
    * @param val 写入的值
    * @return 返回执行的结果
    */
    static int i2c_tcs3472x_write8(struct tcs3472x_dev *dev, u8 reg, u8 data)
    {
    int ret = 0;
    u8 write_buf = data;
    ret = i2c_write_regs((struct i2c_client *)dev->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 <stdbool.h>
/*********************
* 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 <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/i2c.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/io.h>
#include <linux/device.h>
#include <linux/platform_device.h> #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 = %x\n", 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=%d\n", 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 = &reg; // 需要读取的寄存器首地址
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=%d\n",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 < 0)
{
return -1;
} *data = val[1] << 8 | val[0];
return ret;
} /**
* @brief 向 tcs3472x 设备的寄存器中写入 8 位数据
*
* @param dev tcs3472x 设备
* @param reg 寄存器地址
* @param val 写入的值
* @return 返回执行的结果
*/
static int i2c_tcs3472x_write8(struct tcs3472x_dev *dev, u8 reg, u8 data)
{
int ret = 0;
u8 write_buf = data;
ret = i2c_write_regs((struct i2c_client *)dev->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 < 0)
{
return -1;
}
/* 读取 colour_g */
ret = i2c_tcs3472x_read16(dev, TCS34725_GDATAL, &dev->colour_g);
if (ret < 0)
{
return -1;
}
/* 读取 colour_b */
ret = i2c_tcs3472x_read16(dev, TCS34725_BDATAL, &dev->colour_b);
if (ret < 0)
{
return -1;
}
/* 读取 colour_c */
ret = i2c_tcs3472x_read16(dev, TCS34725_CDATAL, &dev->colour_c);
if (ret < 0)
{
return -1;
} return ret;
} /**
* @brief 启动 tcs3472x
*
* @param dev tcs3472x 设备
* @return 返回执行的结果
*/
static int tcs3472x_device_start(struct tcs3472x_dev *dev)
{
int ret = 0;
u8 read_buf;
// printk(PRINTK_GRADE "tcs3472x start......\n"); /* 1. 获取TCS34725型号 */
ret = i2c_tcs3472x_read8(dev, TCS34725_ID, &read_buf);
if (ret < 0)
{
return -1;
}
// printk(PRINTK_GRADE "tcs3472x type is: %x\n", read_buf); /* 2. 通过设备识别号判断是否是 tcs3472x类型设备 */
if ( !((read_buf == 0x44) || (read_buf == 0x4D)) )
{
printk(PRINTK_GRADE "The current device is not a tcs3472x device\n");
return -1;
} /* 3.设置集成时间,默认设置为 2.4ms */
ret = i2c_tcs3472x_write8(dev, TCS34725_ATIME, TCS34725_INTEGRATIONTIME_2_4MS);
if (ret < 0)
{
return -1;
} /* 4.设置增益倍数,默认设置为 60x*/
ret = i2c_tcs3472x_write8(dev, TCS34725_CONTROL, TCS34725_GAIN_60X);
if (ret < 0)
{
return -1;
} /* 5.启用传感器 */
ret = i2c_tcs3472x_write8(dev, TCS34725_ENABLE, TCS34725_ENABLE_PON | TCS34725_ENABLE_AEN);
if (ret < 0)
{
return -1;
} /* 保证第一次采集时留有充足的时间 */
// mdelay(10);
return ret;
} /**
* @brief 停止 tcs3472x
*
* @param dev tcs3472x 设备
* @return 返回执行的结果
*/
static int tcs3472x_device_stop(struct tcs3472x_dev *dev)
{
int ret = 0;
u8 read_buf;
/* 读取原有状态 */
ret = i2c_tcs3472x_read8(dev, TCS34725_ENABLE, &read_buf);
if (ret < 0)
{
return -1;
}
/* 停止 tcs3472x */
ret = i2c_tcs3472x_write8(dev, TCS34725_ENABLE, read_buf & ~(TCS34725_ENABLE_PON | TCS34725_ENABLE_AEN));
if (ret < 0)
{
return -1;
}
return ret;
} /**
* @brief 设置 tcs3472x 集成时间
*
* @param dev tcs3472x 设备
* @param integration_time 集成时间
* @return 返回执行的结果
*/
static int tcs3472x_integration_time(struct tcs3472x_dev *dev, tcs34725_integration_time_t integration_time)
{
int ret = 0;
switch (integration_time)
{
case TCS34725_INTEGRATIONTIME_2_4MS:
ret = i2c_tcs3472x_write8(dev, TCS34725_ATIME, TCS34725_INTEGRATIONTIME_2_4MS);
break; case TCS34725_INTEGRATIONTIME_24MS:
ret = i2c_tcs3472x_write8(dev, TCS34725_ATIME, TCS34725_INTEGRATIONTIME_24MS);
break; case TCS34725_INTEGRATIONTIME_50MS:
ret = i2c_tcs3472x_write8(dev, TCS34725_ATIME, TCS34725_INTEGRATIONTIME_50MS);
break; case TCS34725_INTEGRATIONTIME_101MS:
ret = i2c_tcs3472x_write8(dev, TCS34725_ATIME, TCS34725_INTEGRATIONTIME_101MS);
break; case TCS34725_INTEGRATIONTIME_154MS:
ret = i2c_tcs3472x_write8(dev, TCS34725_ATIME, TCS34725_INTEGRATIONTIME_154MS);
break; case TCS34725_INTEGRATIONTIME_700MS:
ret = i2c_tcs3472x_write8(dev, TCS34725_ATIME, TCS34725_INTEGRATIONTIME_700MS);
break; default:
ret = i2c_tcs3472x_write8(dev, TCS34725_ATIME, TCS34725_INTEGRATIONTIME_2_4MS);
break;
}
return ret;
} /**
* @brief 设置 tcs3472x 增益倍数
*
* @param dev tcs3472x 设备
* @param gain_multiple 增益倍数
* @return 返回执行的结果
*/
static int tcs3472x_gain_multiple(struct tcs3472x_dev *dev, tcs34725_gain_multiple_t gain_multiple)
{
int ret = 0;
switch (gain_multiple)
{
case TCS34725_GAIN_1X:
ret = i2c_tcs3472x_write8(dev, TCS34725_CONTROL, TCS34725_GAIN_1X);
break; case TCS34725_GAIN_4X:
ret = i2c_tcs3472x_write8(dev, TCS34725_CONTROL, TCS34725_GAIN_4X);
break; case TCS34725_GAIN_16X:
ret = i2c_tcs3472x_write8(dev, TCS34725_CONTROL, TCS34725_GAIN_16X);
break; case TCS34725_GAIN_60X:
ret = i2c_tcs3472x_write8(dev, TCS34725_CONTROL, TCS34725_GAIN_60X);
break; default:
ret = i2c_tcs3472x_write8(dev, TCS34725_CONTROL, TCS34725_GAIN_60X);
break;
}
return ret;
} /**
* @brief 打开设备
*
* @param inode 传递给驱动的 inode
* @param filp 设备文件,file 结构体有个叫做 private_data 的成员变量
* 一般在 open 的时候将 private_data 指向设备结构体。
* @return 0 成功;其他 失败
*/
static int tcs3472x_open(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(PRINTK_GRADE "tcs3472x open\r\n"); 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 read\r\n"); ret = tcs3472x_colour_data(tcs_dev); // printk(PRINTK_GRADE "R = %d G = %d B = %d C = %d\r\n",
// 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 write\r\n");
if (cnt != 2)
{
printk(PRINTK_GRADE "data in wrong format!\r\n");
} /* 接收用户空间传递的数据 */
ret = copy_from_user(write_buf, buf, cnt);
if(ret != 0){
printk(PRINTK_GRADE "kernel recevdata failed!\r\n");
} /* 第一个参数为 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!\r\n");
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!\r\n");
printk(PRINTK_GRADE "tcs3472x release\r\n"); 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 \r\n", 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 \r\n");
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 < 0)
{
pr_err("%s Couldn't alloc_chrdev_region, ret = %d \r\n", TCS3472x_NAME, ret);
return -ENOMEM;
} /* 2、初始化 cdev */
/* 关联字符设备结构体 cdev 与文件操作结构体 file_operations */
tcs_dev->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 < 0)
{
pr_err("fail to add cdev \r\n");
goto del_unregister;
} /* 4、创建类 */
tcs_dev->class = class_create(THIS_MODULE, TCS3472x_NAME);
if (IS_ERR(tcs_dev->class))
{
pr_err("Failed to create device class \r\n");
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_init\n");
ret = i2c_add_driver(&tcs3472x_driver);
return ret;
} /**
* @brief 驱动出口函数
* @return 0,成功;其他负值,失败
*/
static void __exit tcs3472x_driver_exit(void)
{
pr_info("tcs3472x_driver_exit\n");
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 <stdio.h>
#include <linux/types.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <assert.h>
#include <string.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h> #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!\r\n");
return -1;
} filename = argv[1]; /* 打开驱动文件 */
fd = open(filename, O_RDWR);
if(!fd){
printf("Can't open file %s\r\n", filename);
return -1;
} /* 设置集成时间 */
write_buf[0] = 1;
write_buf[1] = TCS34725_INTEGRATIONTIME_700MS;
ret = write(fd, write_buf, 2);
if(ret < 0){
printf("Failed to set integration time!\r\n");
} /* 设置增益倍数 */
write_buf[0] = 2;
write_buf[1] = TCS34725_GAIN_60X;
ret = write(fd, write_buf, 2);
if(ret < 0){
printf("Failed to set gain multiple!\r\n");
} /* 延时 1s,保证第一次采集时留有充足的时间 */
sleep(1); /* 从驱动文件读取数据 */
while (1)
{
ret = read(fd, data_buf, sizeof(data_buf));
if (ret == 0)
{
printf("R = %d, G = %d, B = %d, C = %d \r\n",
data_buf[0], data_buf[1], data_buf[2], data_buf[3]);
}
else
{
printf("read file %s failed!\r\n", filename);
}
/* 延时 2s */
usleep(2000000);
} close(fd); return 0;
}

六、测试

  1. 加载模块, 命令是 insmod i2c_tcs34725_module.ko

  2. 运行测试 app,命令是 ./drive_read_app /dev/I2C_TCS3472x

  3. 卸载模块,命令是 rmmod i2c_tcs34725_module

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

【Linux】TCS34725 颜色传感器设备驱动的更多相关文章

  1. Linux学习 : 总线-设备-驱动模型

    platform总线是一种虚拟的总线,相应的设备则为platform_device,而驱动则为platform_driver.Linux 2.6的设备驱动模型中,把I2C.RTC.LCD等都归纳为pl ...

  2. Linux SPI总线和设备驱动架构之四:SPI数据传输的队列化

    我们知道,SPI数据传输可以有两种方式:同步方式和异步方式.所谓同步方式是指数据传输的发起者必须等待本次传输的结束,期间不能做其它事情,用代码来解释就是,调用传输的函数后,直到数据传输完成,函数才会返 ...

  3. Linux SPI总线和设备驱动架构之三:SPI控制器驱动

    通过第一篇文章,我们已经知道,整个SPI驱动架构可以分为协议驱动.通用接口层和控制器驱动三大部分.其中,控制器驱动负责最底层的数据收发工作,为了完成数据的收发工作,控制器驱动需要完成以下这些功能:1. ...

  4. Linux与Windows的设备驱动模型对比

    Linux与Windows的设备驱动模型对比 名词缩写: API 应用程序接口(Application Program Interface ) ABI 应用系统二进制接口(Application Bi ...

  5. linux PMBus总线及设备驱动分析

    PMBus协议规范介绍 PMBus是一套对电源进行配置.控制和监控的通讯协议标准.其最新版本为1.3,该规范还在不断演进中,比如新标准中新增的zone PMBus.AVSBus等特性.在其官网上有详细 ...

  6. 【情人节选帽子】TCS34725颜色传感器和Python图形界面编程(STM32 HAL库)

    截图 描述: l  STM32 HAL库编程 l  使用模拟IIC通信,方便程序移植 l  Python界面编写,蘑菇头的帽子是什么颜色 l  STM32 HAL库串口通信 l  Python界面使用 ...

  7. 【Linux操作系统分析】设备驱动处理流程

    1 驱动程序,操作系统,文件系统和应用程序之间的关系 字符设备和块设备映射到操作系统中的文件系统,由文件系统向上提供给应用程序统一的接口用以访问设备. Linux把设备视为文件,称为设备文件,通过对设 ...

  8. Linux Platform devices 平台设备驱动

    设备总线驱动模型:http://blog.csdn.net/lizuobin2/article/details/51570196 本文主要参考:http://www.wowotech.net/devi ...

  9. 《网蜂A8实战演练》——8.Linux USB 主机控制器和设备驱动

    USB 的全称是 Universal Serial Bus,顾名思义:通用串行总线. 提到总线,联想一下,在你心目中总线总是用来干嘛的?还记得 I2C 总线? I2C 总线上挂有二条信号线,一条是 S ...

  10. Linux SPI总线和设备驱动架构之二:SPI通用接口层

    通过上一篇文章的介绍,我们知道,SPI通用接口层用于把具体SPI设备的协议驱动和SPI控制器驱动联接在一起,通用接口层除了为协议驱动和控制器驱动提供一系列的标准接口API,同时还为这些接口API定义了 ...

随机推荐

  1. Go素数筛选分析

    Go素数筛选分析 1. 素数筛选介绍 学习Go语言的过程中,遇到素数筛选的问题.这是一个经典的并发编程问题,是某大佬的代码,短短几行代码就实现了素数筛选.但是自己看完原理和代码后一脸懵逼(仅此几行能实 ...

  2. Linux实战笔记_CentOS7_yum相关配置

    配置yum源优先级 配置优先级 yum -y install yum-plugin-priorities.noarch vi /etc/yum.repos.d/localISO.repo priori ...

  3. Error creating bean with name ‘com.ai.ecs.ecop.pointExchange.service.NewGoodsService‘

    Error creating bean with name 'com.ai.ecs.ecop.pointExchange.service.NewGoodsService' 查看服务注册中心的格式是否正 ...

  4. 7.httpie

    可以使用curl或httpie测试我们的服务器.Httpie是用Python编写的用户友好的http客户端   安装:pip3 install httpie #get请求示例 输入命令:http ht ...

  5. RegExp正则表达式的匹配

    JavaScript RegExp 对象 RegExp 对象 正则表达式是描述字符模式的对象. 正则表达式用于对字符串模式匹配及检索替换,是对字符串执行模式匹配的强大工具. 语法 var patt=n ...

  6. 一次 Java log4j2 漏洞导致的生产问题

    一.问题 近期生产在提交了微信小程序审核后(后面会讲到),总会出现一些生产告警,而且持续时间较长.我们查看一些工具和系统相关的,发现把我们的 gateway 差不多打死了. 有一些现象. 网关有很多接 ...

  7. Python基础之模块:4、正则表达式和re模块

    目录 一.正则表达式 1.正则表达式前戏 2.字符组 3.特殊符号 4.量词 5.贪婪匹配与非贪婪匹配 6.转义符 7.正则表达式实战 二.re模块 1.模块导入 2.常见操作方法 1.findall ...

  8. C/C++ 知海拾遗

    C语言知识拾遗 2022/11/11 memset()函数用法 包含头文件:<string.h> 作用:给任意类型变量数组初始化,即万能初始化函数. 使用形式:memset( void* ...

  9. Git 分支管理策略汇总

    原文链接: Git 分支管理策略 最近,团队新入职了一些小伙伴,在开发过程中,他们问我 Git 分支是如何管理的,以及应该怎么提交代码? 我大概说了一些规则,但仔细想来,好像也并没有形成一个清晰规范的 ...

  10. vim-瞬间移动打发

    一,常用基本命令 set rnu 显示相对行号 1.ctrl+F 将屏幕向下滚动一屏 2.ctrl+u pageUp,ctrl+d pageDown 3.行内移动:W,b在单词间移动,ge向上跳,f ...