一、概述
此笔记主要是学习 Linux 中的 I2C 驱动,顺便验证一下 TCS34725 传感器的使用,主要内容还是程序记录,方便编写其他 I2C 设备驱动时做参考,所以关于 TCS34725 这里就不过多描述了,需要的小伙伴可以浏览我之前的笔记:TCS34725 颜色传感器设备驱动程序
二、添加 I2C 设备
学习到 I2C 驱动的小伙伴应该都知道平台设备这个概念了,所以这里需要使用到 I2C 总线,由于 I2C 总线驱动基本都是由板子厂商帮我们移植好的,所以这里就不关注 I2C 总线驱动了,有需要的小伙伴自行了解。
添加设备也有两种方式,这里我以设备树的形式添加设备为例,传统的添加方式相比设备树比较麻烦一些,这里就跳过这部分类容。
-
打开设备树文件,向 I2C 节点中追加 TCS34725 传感器的设备信息,如下所示:
&i2c0{ rgb_colour@29{ compatible = "colour,tcs34725"; reg = <0x29>; }; };
注意:在
&i2c0
的i2c0一定是i2c设备节点的标签,我尝试使用节点名称引用,发现编译不通过,所以当你设备树中的i2c节点没有标签的话,自行添加一个。当然也是可以直接添加到i2c设备节点中的。 -
编译设备树,并烧写设备树文件
-
查看设备节点是否添加成功
通过命令ls /sys/bus/i2c/devices/
查看 I2C 设备,其中 0-0029 就是我们添加的设备,可以通过设备的 name 属性查看设备的名称,如下图所示:
注意:图中的中 name 属性变量,就是在设备树中添加的 compatible 属性,也是 I2C 总线用于匹配设备驱动时的匹配名称。
三、I2C 设备驱动编写
为了方便测试,这里是以模块的形式加载驱动设备的,没有直接在内核文件中,所以测试时不用重新编译内核文件。
-
出入口函数
这两个函数是模块的出入口函数,编写驱动模块是就少不了它两/* 将上面两个函数指定为驱动的入口和出口函数 */ module_init(tcs3472x_driver_init); module_exit(tcs3472x_driver_exit);
-
加载和卸载 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); }
-
设备信息
/* 传统匹配方式 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
函数
-
.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 < 0) { pr_err("%s Couldn't alloc_chrdev_region, ret = %d rn", 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 rn"); 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 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 函数进行操作了。
-
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 函数进行完成的
-
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 = %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 < 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: %xn", read_buf); /* 2. 通过设备识别号判断是否是 tcs3472x类型设备 */ if ( !((read_buf == 0x44) || (read_buf == 0x4D)) ) { printk(PRINTK_GRADE "The current device is not a tcs3472x devicen"); 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 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 < 0) { pr_err("%s Couldn't alloc_chrdev_region, ret = %d rn", 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 rn"); 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 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 <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!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 < 0){ printf("Failed to set integration time!rn"); } /* 设置增益倍数 */ 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!rn"); } /* 延时 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 rn", data_buf[0], data_buf[1], data_buf[2], data_buf[3]); } else { printf("read file %s failed!rn", filename); } /* 延时 2s */ usleep(2000000); } close(fd); return 0; }
六、测试
-
加载模块, 命令是
insmod i2c_tcs34725_module.ko
-
运行测试 app,命令是
./drive_read_app /dev/I2C_TCS3472x
-
卸载模块,命令是
rmmod i2c_tcs34725_module
到此 TCS34725 的驱动完成了,有不好的地方望各位大佬指出,最后声明一下,此程序只供学习,出现任何问题概不负责。