【Linux】TCS34725 顏色感測器裝置驅動

2023-01-07 12:00:21

一、概述

此筆記主要是學習 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 的驅動完成了,有不好的地方望各位大佬指出,最後宣告一下,此程式只供學習,出現任何問題概不負責。