一、概述

此笔记主要是学习 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. 9.MongoDB系列之创建副本集(二)

    1. 如何设计副本集 大多数:选取主节点时需要由大多数决定,主节点只有在得到大多数支持时才能继续作为主节点,写操作被复制到大多数成员时就是安全的写操作.这里的大多数定义为"副本集中一半以上的 ...

  2. 云计算_Apache CloudStack

    注:基于系统版本CentOS 7.2.1511部署 修改主机名/IP地址/hosts解析 hostnamectl set-hostname centos1 hostnamectl set-hostna ...

  3. 使用ssm框架搭建的图书管理系统

    等下贴代码 学生图书管理系统 实现的功能: 1.图书信息查询(暂时未分页处理) 2.图书信息修改(点击保存按钮后返回修改后的图书信息界面) 3.删除一本图书(删除某一本书籍,在图书管理界面则不存在该条 ...

  4. JavaScript基础&实战(5)js中的数组、forEach遍历、Date对象、Math、String对象

    文章目录 1.工厂方法创建对象 1.1 代码块 1.2.测试结果 2.原型对象 2.1 代码 2.2 测试结果 3.toString 3.1 代码 3.2 测试结果 4.数组 4.1 代码 5.字面量 ...

  5. NLP之基于Transformer的句子翻译

    Transformer 目录 Transformer 1.理论 1.1 Model Structure 1.2 Multi-Head Attention & Scaled Dot-Produc ...

  6. 7.websocket收发消息

    客户端主动向服务端发起websocket连接,服务端接收到连接后通过(握手) 客户端 websocket socket = new WebSocket('ws://127.0.0.1/ws/'); 服 ...

  7. kubelet忽然不可用

    原因,有可能机器的cpu信息有变化(扩容或者缩容)解决办法: 删掉/opt/var/lib/kubelet目录下(或者/data/lib/kubelet)cpu_manager_state文件 然后m ...

  8. iOS开发应用上传AppStore的步骤

    原文:http://blog.csdn.net/ayangcool/article/details/46647693   前言:作为一名IOS开发者,把开发出来的App上传到App Store是必须的 ...

  9. Java多线程-ThreadPool线程池-2(四)

    线程池是个神器,用得好会非常地方便.本来觉得线程池的构造器有些复杂,即使讲清楚了对今后的用处可能也不太大,因为有一些Java定义好的线程池可以直接使用.但是(凡事总有个但是),还是觉得讲一讲可能跟有助 ...

  10. JavaWeb3

    1. 会话技术 会话:一次会话中包含多次请求和响应 一次会话:浏览器第一次给服务器资源发送请求,会话建立,直到有一方断开为止 功能:在一次会话的范围内的多次请求间共享数据 方式: 客户端会话技术:Co ...