0

【Linux】TCS34725 颜色传感器设备驱动 - 浇筑菜鸟

 1 year ago
source link: https://www.cnblogs.com/jzcn/p/17032141.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

此笔记主要是学习 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 属性查看设备的名称,如下图所示:

    2406897-20230107094920730-86843300.png

    注意:图中的中 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 = ®					// 需要读取的寄存器首地址
    	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 = ®					// 需要读取的寄存器首地址
	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
    2406897-20230107102806885-1957343547.png

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

    2406897-20230107102950571-417506081.png
  3. 卸载模块,命令是 rmmod i2c_tcs34725_module
    2406897-20230107103056833-1083962368.png

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK