【Linux 中斷】紅外接收器裝置驅動

2023-03-09 12:01:04

一、概述

現在很多家電都使用了紅外,而智慧家居的誕生,連音響都帶了紅外遙控功能。為了解決家裡遙控器比較多的情況,多數手機都支援了紅外功能,這和以前的萬能遙控器一樣。這裡主要記錄紅外接收的驅動,當然明白怎麼接收的,對於遙控的發射就比較簡單了。

二、紅外接收器

  1. 外觀

  2. 接收的工作原理
    紅外探頭應該也是光敏電阻的一種,當接收到波長在750-1150NM的光時,OUT 引腳就會產生一個 38kHz 的 PWM 波。一般在電路中都會給 OUT 引腳進行一個上拉,所以沒有檢測到紅外光時,OUT 引腳是穩定的高電平。通過這個現象我們就可以進行無線通訊。
    注意:750-1150NM的光時是肉眼不可見的,不過可以通過手機攝像頭進行檢視

  3. 通訊協定
    瞭解完原理後,只需要配上相應的通訊協定就可以使用紅外進行無線通訊了。常用的紅外線訊號傳輸協定有ITT協定、NEC協定、NokiaNRC協定、Sharp協定、SonySIRC協定、PhilipSRC-5協定、PhilipsRC-6協定,以及PhilipsRECS-80協定等。

    需要了解不同協定區別的可以參考:幾種常用的紅外線訊號傳輸協定,紅外的協定種類比較多,部分公司也會自己指定不同的協定,比如小米公司的遙控器,見小米紅外遙控器如何適配到其他應用裝置之上

    此筆記主要使用 NEC 協定完成驅動的編寫,其他的協定驅動也可以參考完成。

三、 NEC協定

  1. 資料框格式

    引導碼 地址碼0 地址碼1 命令碼 命令反碼 引導碼(重複)
    LSB-MSB(0-7) LSB-MSB(8-15) LSB-MSB(16-23) LSB-MSB(24-31)

    注意:在標準的NEC協定中,地址碼1為地址碼0的反碼,而在許多遙控器中,地址碼0和地址碼1共同作為紅外遙控器的編碼值。

  2. PPM(脈衝位置調變)

  3. 接收波形

    注意:實際波形在低電平期間是一個 38kHz 的 PWM 波。

  4. 資料解析
    在接收資料時需要過濾 38kHz 的波形,如下所示:

    /**
     * @brief 紅外中斷響應函數
     * 
     * @param irq
     * @param dev_id
     * @return 0,成功;其他負值,失敗
    */
    static irqreturn_t infrared_interrupt(int irq, void *dev_id)
    {
    	unsigned previous_offset;		// 上一次的時間
    	unsigned start_offset;			// 波型的起始時間差
    	long long now = ktime_to_us(ktime_get());
        
    	/* 由於紅外接收感測器在接收到紅外訊號時產生一個38KHz的訊號,所以接收時需要過濾,使訊號變為一個低電平訊號 */
    	/*-------------------------------- 濾波 --------------------------------*/
    
    	/* 從當前時刻開始接收一個下降沿開始的方波週期 */
    	if (0 == infrared_pwm.flag )
    	{
    		infrared_pwm.start_time = now;
    		infrared_pwm.flag  = 1;
    	}
    	
    	/* 計算兩次下降沿的時差 */
    	previous_offset = now - infrared_pwm.previous;
    	infrared_pwm.previous = now;
    
    	/* 過濾紅外接收器自生產生38KHz的訊號,週期大約是 26us */
    	if (previous_offset < 60)
    	{
    		return IRQ_HANDLED;
    	}
    
    	/* 下降沿開始的時差,也就是一個週期的時間 */
    	start_offset = now - infrared_pwm.start_time;
    	/* 消除上次持續的訊號 */
    	if (start_offset == 0)
    	{
    		return IRQ_HANDLED;
    	}
    
    	/* 完成一個週期的資料採集 */
    	infrared_pwm.flag = 0;
    
    	// infrared_pwm.low_time = start_offset - previous_offset + 52;		// 低電平時間
    	// infrared_pwm.high_time = previous_offset - 52;					// 高電平時間
    	
    	/* NEC 解碼 */
    	infrared_nec_decode(start_offset);
    
    	return IRQ_HANDLED;
    }
    
    

四、linux 中斷驅動

在中斷驅動中我使用了非同步通知的方式,與應用程式進行通訊

/**
 * @brief 紅外接收器初始化函數
 * 
 * @return 0,成功;其他負值,失敗
*/
static int infrared_init(void)
{
	int res;

	/* 申請 GPIO 資源 */
	infrared_dev.gpio = INFRARED_GPIO;
	res = gpio_request(infrared_dev.gpio, "infrared");
	if (res) 
	{
		pr_err("infrared dev: Failed to request gpio\n");
		return res;
	}

	/* 將 GPIO 設定為輸入模式 */
	gpio_direction_input(infrared_dev.gpio);

	/* 申請中斷 */
	infrared_dev.irq_num = gpio_to_irq(infrared_dev.gpio);
	res = request_irq(infrared_dev.irq_num, infrared_interrupt, IRQF_TRIGGER_FALLING, "infrared", NULL);
	if (res) 
	{
		gpio_free(infrared_dev.gpio);
		return res;
	}

	return 0;
}

/**
 * @brief 開啟裝置
 * 
 * @param inode 傳遞給驅動的 inode
 * @param filp 裝置檔案,file 結構體有個叫做 private_data 的成員變數
 * 一般在 open 的時候將 private_data 指向裝置結構體。
 * @return 0 成功;其他 失敗
 */
static int infrared_open(struct inode *inode, struct file *filp)
{
	/* 將裝置資料設定為私有資料 */
	filp->private_data = &infrared_dev;

	printk(PRINTK_GRADE "infrared_open\n");
    return 0;
}

/**
 * @brief 從裝置讀取資料
 * 
 * @param filp 要開啟的裝置檔案(檔案描述符)
 * @param buf 返回給使用者空間的資料緩衝區
 * @param count 要讀取的資料長度
 * @param offt 相對於檔案首地址的偏移
 * @return 0 成功;其他 失敗
 */
static ssize_t infrared_read(struct file *filp, char __user *buf, size_t count, loff_t *offt)
{
	int res = 0;
	// struct infrared_dev_t *infrared_dev = filp->private_data;

	res = copy_to_user(buf, infrared_receive_data, count);
    if(res != 0) {
		printk(PRINTK_GRADE "111111111111111\n");
		return -1;
    }

	// printk(PRINTK_GRADE "infrared_read\n");
    return 0;
}

/**
 * @brief 向裝置寫資料
 * @param filp 裝置檔案,表示開啟的檔案描述符
 * @param buf 要寫給裝置寫入的資料
 * @param count 要寫入的資料長度
 * @param offt 相對於檔案首地址的偏移
 * @return 寫入的位元組數,如果為負值,表示寫入失敗
*/
static ssize_t infrared_write(struct file *filp, const char __user *buf, size_t count, loff_t *offt)
{
    int res = 0;
	// struct infrared_dev_t *infrared_dev = filp->private_data;

	char write_buf[1024] = {"0"};
	res = copy_from_user(write_buf, buf, count);
    if(res != 0) {
        return -1;
    }
	printk("kernel recevdata:%s\r\n", write_buf);
    return 0;
}

static int infrared_fasync(int fd, struct file *filp, int on)
{
	struct infrared_dev_t *infrared_dev = filp->private_data;

	printk(PRINTK_GRADE "infrared_fasync\n");
	/* 非同步通知初始化 */
    return fasync_helper(fd, filp, on, &infrared_dev->fasync_queue);
}

/**
 * @brief 關閉/釋放裝置
 * @param filp 要關閉的裝置檔案(檔案描述符)
 * @return 0 成功;其他 失敗
*/
static int infrared_release(struct inode *inode, struct file *filp)
{
	int res = 0;
	printk(PRINTK_GRADE "infrared_release\n");

	/* 刪除非同步通知 */
	infrared_fasync(-1, filp, 0);
    return res;
}

/* 裝置操作函數結構體 */
static struct file_operations infrared_ops = {
    .owner = THIS_MODULE, 
    .open = infrared_open,
    .read = infrared_read,
    .write = infrared_write,
    .release = infrared_release,
	.fasync = infrared_fasync,
};

/**
 * @brief 註冊字元裝置驅動
 * 
 * @return 0,成功;其他負值,失敗
*/
static int infrared_register(void)
{
    int ret = -1; 						// 儲存錯誤狀態碼

	/* GPIO 中斷初始化 */
	ret = infrared_init();

	/* 1、建立裝置號 */
	/* 採用動態分配的方式,獲取裝置編號,次裝置號為0 */
	/* 裝置名稱為 infrared_NAME,可通過命令 cat /proc/devices 檢視 */
	/* INFRARED_CNT 為1,只申請一個裝置編號 */
	ret = alloc_chrdev_region(&infrared_dev.devid, 0, INFRARED_CNT, INFRARED_NAME);
	if (ret < 0)
	{
		pr_err("%s Couldn't alloc_chrdev_region, ret = %d \r\n", INFRARED_NAME, ret);
		goto fail_region;
	}

	/* 2、初始化 cdev */
	/* 關聯字元裝置結構體 cdev 與檔案操作結構體 file_operations */
	infrared_dev.cdev.owner = THIS_MODULE;
	cdev_init(&infrared_dev.cdev, &infrared_ops);

	/* 3、新增一個 cdev */
	/* 新增裝置至cdev_map雜湊表中 */
	ret = cdev_add(&infrared_dev.cdev, infrared_dev.devid, INFRARED_CNT);
	if (ret < 0)
	{
		pr_err("fail to add cdev \r\n");
		goto del_unregister;
	}

	/* 4、建立類 */
	infrared_dev.class = class_create(THIS_MODULE, INFRARED_NAME);
	if (IS_ERR(infrared_dev.class)) 
	{
		pr_err("Failed to create device class \r\n");
		goto del_cdev;
	}

	/* 5、建立裝置,裝置名是 INFRARED_NAME */
	/*建立裝置 INFRARED_NAME 指定裝置名,*/
	infrared_dev.device = device_create(infrared_dev.class, NULL, infrared_dev.devid, NULL, INFRARED_NAME);
	if (IS_ERR(infrared_dev.device)) {
		goto destroy_class;
	}

	return 0;

destroy_class:
	device_destroy(infrared_dev.class, infrared_dev.devid);
del_cdev:
	cdev_del(&infrared_dev.cdev);
del_unregister:
	unregister_chrdev_region(infrared_dev.devid, INFRARED_CNT);
fail_region:
	/* 釋放個人初始化申請的資源,如del_init(); */
	free_irq(infrared_dev.irq_num, NULL);
	gpio_free(infrared_dev.gpio);
	return -EIO;
}

/**
 * @brief 登出字元裝置驅動
 * 
 * @return 0,成功;其他負值,失敗
*/
static void infrared_unregister(void)
{
	/* 1、刪除 cdev */
	cdev_del(&infrared_dev.cdev);
	/* 2、登出裝置號 */
	unregister_chrdev_region(infrared_dev.devid, INFRARED_CNT);
	/* 3、登出裝置 */
	device_destroy(infrared_dev.class, infrared_dev.devid);
	/* 4、登出類 */
	class_destroy(infrared_dev.class);

	/* 釋放中斷 */
	free_irq(infrared_dev.irq_num, NULL);
	/* 釋放 IO */
	gpio_free(infrared_dev.gpio);
}

/**
 * @brief 驅動入口函數
 * 
 * @return 0,成功;其他負值,失敗
*/
static int __init infrared_driver_init(void)
{
	pr_info("infrared_driver_init\n");
	return infrared_register();
}

/**
 * @brief 驅動出口函數
 * 
 * @return 0,成功;其他負值,失敗
*/
static void __exit infrared_driver_exit(void)
{
	pr_info("infrared_driver_exit\n");
	infrared_unregister();
}

/* 將上面兩個函數指定為驅動的入口和出口函數 */
module_init(infrared_driver_init);
module_exit(infrared_driver_exit);

/* LICENSE 和作者資訊 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("JIAOZHU");
MODULE_INFO(intree, "Y");

五、完整的驅動程式

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.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/semaphore.h>
#include <linux/of_irq.h>
#include <linux/irq.h>


/***************************************************************
檔名 : infrared.c
作者 : jiaozhu
版本 : V1.0
描述 : 紅外接收器驅動
其他 : 無
紀錄檔 : 初版 V1.0 2023/3/3
***************************************************************/

/* 紅外接收器的資料引腳 59 */
#define INFRARED_GPIO 59

#define PRINTK_GRADE KERN_INFO

/*------------------ 字元裝置內容 ----------------------*/
#define INFRARED_NAME 	"infrared"
#define INFRARED_CNT	(1)

static unsigned char infrared_receive_data[4];

/*------------------ 裝置資料結構體 ----------------------*/
struct infrared_dev_t 
{
	dev_t devid; 						// 裝置號
	struct cdev cdev; 					// cdev
	struct class *class; 				// 類
	struct device *device; 				// 裝置
	struct device_node *nd;				// 裝置節點
	int irq_num; 						// 中斷號
	int gpio;							// 資料接收引腳
	struct fasync_struct *fasync_queue; // 非同步相關結構體
};

struct infrared_dev_t infrared_dev;		// 裝置資料結構體 

/*------------------ 紅外波形過濾結構體 ----------------------*/
struct infrared_pwm_t
{
	long long previous;					// 記錄上一次的時間,64bit
	int flag;               			// 表示每個方波週期的開始
	long long start_time;				// 週期的起始時間
	int low_time;						// 低電平時間
	int high_time;						// 高電平時間
};

struct infrared_pwm_t infrared_pwm =		// 紅外波形採集
{
	.flag = 0,
	.previous = 0,
	.start_time = 0,
	.low_time = 0,
	.high_time = 0,
};

/*------------------ 紅外 NEC 資料解析結構體 ------------------*/
struct nec_decode_buf_t
{
	int flag;               			// 表示 NEC 資料開始
	unsigned  times[128];				// 記錄每幀的時間
	int num;							// 表示第幾幀
};

struct nec_decode_buf_t nec_buf =
{
	.flag = 0,
	.num = 0,
};


/**
 * @brief 紅外 NEC 資料解析
 * 
 * @param period 一個方波週期
*/
static void infrared_nec_decode(int period)
{
	int i, j;
	unsigned char temp;

	if ((period > 13000) && (period < 14000))
	{
		nec_buf.flag = 1;
		nec_buf.num = 0;
		return;
	}

	if (nec_buf.num < 32)
	{
		nec_buf.times[nec_buf.num ++] = period;
	}

	if ((period > 10500) && (period < 13500))
	{
		if (nec_buf.flag)
		{
			for(i = 0; i < 4; i++)		// 一共4個位元組
			{
            	temp = 0;
 
				for(j = 0; j < 8; j++)
				{
					if ((nec_buf.times[i * 8 + j] > 2100) && (nec_buf.times[i * 8 + j] < 2400) )
					{
						temp |= 1 << j;
					}
				}
				// printk("%02x	", temp);
				infrared_receive_data[i] = temp;
			}
			// printk("\n");
			nec_buf.flag  = 0;
			
		}
		else
		{
			// printk(PRINTK_GRADE "Repetitive signal\n");
			memset(infrared_receive_data, 0xFF, sizeof(infrared_receive_data));
		}
		
		/* 傳送非同步通知 */
		kill_fasync(&infrared_dev.fasync_queue, SIGIO, POLL_IN);
	}

}

/**
 * @brief 紅外中斷響應函數
 * 
 * @param irq
 * @param dev_id
 * @return 0,成功;其他負值,失敗
*/
static irqreturn_t infrared_interrupt(int irq, void *dev_id)
{
	unsigned previous_offset;		// 上一次的時間
	unsigned start_offset;			// 波型的起始時間差
	long long now = ktime_to_us(ktime_get());
    
	/* 由於紅外接收感測器在接收到紅外訊號時產生一個38KHz的訊號,所以接收時需要過濾,使訊號變為一個低電平訊號 */
	/*-------------------------------- 濾波 --------------------------------*/

	/* 從當前時刻開始接收一個下降沿開始的方波週期 */
	if (0 == infrared_pwm.flag )
	{
		infrared_pwm.start_time = now;
		infrared_pwm.flag  = 1;
	}
	
	/* 計算兩次下降沿的時差 */
	previous_offset = now - infrared_pwm.previous;
	infrared_pwm.previous = now;

	/* 過濾紅外接收器自生產生38KHz的訊號,週期大約是 26us */
	if (previous_offset < 60)
	{
		return IRQ_HANDLED;
	}

	/* 下降沿開始的時差,也就是一個週期的時間 */
	start_offset = now - infrared_pwm.start_time;
	/* 消除上次持續的訊號 */
	if (start_offset == 0)
	{
		return IRQ_HANDLED;
	}

	/* 完成一個週期的資料採集 */
	infrared_pwm.flag = 0;

	// infrared_pwm.low_time = start_offset - previous_offset + 52;		// 低電平時間
	// infrared_pwm.high_time = previous_offset - 52;					// 高電平時間
	
	/* NEC 解碼 */
	infrared_nec_decode(start_offset);

	return IRQ_HANDLED;
}

/**
 * @brief 紅外接收器初始化函數
 * 
 * @return 0,成功;其他負值,失敗
*/
static int infrared_init(void)
{
	int res;

	/* 申請 GPIO 資源 */
	infrared_dev.gpio = INFRARED_GPIO;
	res = gpio_request(infrared_dev.gpio, "infrared");
	if (res) 
	{
		pr_err("infrared dev: Failed to request gpio\n");
		return res;
	}

	/* 將 GPIO 設定為輸入模式 */
	gpio_direction_input(infrared_dev.gpio);

	/* 申請中斷 */
	infrared_dev.irq_num = gpio_to_irq(infrared_dev.gpio);
	res = request_irq(infrared_dev.irq_num, infrared_interrupt, IRQF_TRIGGER_FALLING, "infrared", NULL);
	if (res) 
	{
		gpio_free(infrared_dev.gpio);
		return res;
	}

	return 0;
}

/**
 * @brief 開啟裝置
 * 
 * @param inode 傳遞給驅動的 inode
 * @param filp 裝置檔案,file 結構體有個叫做 private_data 的成員變數
 * 一般在 open 的時候將 private_data 指向裝置結構體。
 * @return 0 成功;其他 失敗
 */
static int infrared_open(struct inode *inode, struct file *filp)
{
	/* 將裝置資料設定為私有資料 */
	filp->private_data = &infrared_dev;

	printk(PRINTK_GRADE "infrared_open\n");
    return 0;
}

/**
 * @brief 從裝置讀取資料
 * 
 * @param filp 要開啟的裝置檔案(檔案描述符)
 * @param buf 返回給使用者空間的資料緩衝區
 * @param count 要讀取的資料長度
 * @param offt 相對於檔案首地址的偏移
 * @return 0 成功;其他 失敗
 */
static ssize_t infrared_read(struct file *filp, char __user *buf, size_t count, loff_t *offt)
{
	int res = 0;
	// struct infrared_dev_t *infrared_dev = filp->private_data;

	res = copy_to_user(buf, infrared_receive_data, count);
    if(res != 0) {
		printk(PRINTK_GRADE "111111111111111\n");
		return -1;
    }

	// printk(PRINTK_GRADE "infrared_read\n");
    return 0;
}

/**
 * @brief 向裝置寫資料
 * @param filp 裝置檔案,表示開啟的檔案描述符
 * @param buf 要寫給裝置寫入的資料
 * @param count 要寫入的資料長度
 * @param offt 相對於檔案首地址的偏移
 * @return 寫入的位元組數,如果為負值,表示寫入失敗
*/
static ssize_t infrared_write(struct file *filp, const char __user *buf, size_t count, loff_t *offt)
{
    int res = 0;
	// struct infrared_dev_t *infrared_dev = filp->private_data;

	char write_buf[1024] = {"0"};
	res = copy_from_user(write_buf, buf, count);
    if(res != 0) {
        return -1;
    }
	printk("kernel recevdata:%s\r\n", write_buf);
    return 0;
}

static int infrared_fasync(int fd, struct file *filp, int on)
{
	struct infrared_dev_t *infrared_dev = filp->private_data;

	printk(PRINTK_GRADE "infrared_fasync\n");
	/* 非同步通知初始化 */
    return fasync_helper(fd, filp, on, &infrared_dev->fasync_queue);
}

/**
 * @brief 關閉/釋放裝置
 * @param filp 要關閉的裝置檔案(檔案描述符)
 * @return 0 成功;其他 失敗
*/
static int infrared_release(struct inode *inode, struct file *filp)
{
	int res = 0;
	printk(PRINTK_GRADE "infrared_release\n");

	/* 刪除非同步通知 */
	infrared_fasync(-1, filp, 0);
    return res;
}

/* 裝置操作函數結構體 */
static struct file_operations infrared_ops = {
    .owner = THIS_MODULE, 
    .open = infrared_open,
    .read = infrared_read,
    .write = infrared_write,
    .release = infrared_release,
	.fasync = infrared_fasync,
};

/**
 * @brief 註冊字元裝置驅動
 * 
 * @return 0,成功;其他負值,失敗
*/
static int infrared_register(void)
{
    int ret = -1; 						// 儲存錯誤狀態碼

	/* GPIO 中斷初始化 */
	ret = infrared_init();

	/* 1、建立裝置號 */
	/* 採用動態分配的方式,獲取裝置編號,次裝置號為0 */
	/* 裝置名稱為 infrared_NAME,可通過命令 cat /proc/devices 檢視 */
	/* INFRARED_CNT 為1,只申請一個裝置編號 */
	ret = alloc_chrdev_region(&infrared_dev.devid, 0, INFRARED_CNT, INFRARED_NAME);
	if (ret < 0)
	{
		pr_err("%s Couldn't alloc_chrdev_region, ret = %d \r\n", INFRARED_NAME, ret);
		goto fail_region;
	}

	/* 2、初始化 cdev */
	/* 關聯字元裝置結構體 cdev 與檔案操作結構體 file_operations */
	infrared_dev.cdev.owner = THIS_MODULE;
	cdev_init(&infrared_dev.cdev, &infrared_ops);

	/* 3、新增一個 cdev */
	/* 新增裝置至cdev_map雜湊表中 */
	ret = cdev_add(&infrared_dev.cdev, infrared_dev.devid, INFRARED_CNT);
	if (ret < 0)
	{
		pr_err("fail to add cdev \r\n");
		goto del_unregister;
	}

	/* 4、建立類 */
	infrared_dev.class = class_create(THIS_MODULE, INFRARED_NAME);
	if (IS_ERR(infrared_dev.class)) 
	{
		pr_err("Failed to create device class \r\n");
		goto del_cdev;
	}

	/* 5、建立裝置,裝置名是 INFRARED_NAME */
	/*建立裝置 INFRARED_NAME 指定裝置名,*/
	infrared_dev.device = device_create(infrared_dev.class, NULL, infrared_dev.devid, NULL, INFRARED_NAME);
	if (IS_ERR(infrared_dev.device)) {
		goto destroy_class;
	}

	return 0;

destroy_class:
	device_destroy(infrared_dev.class, infrared_dev.devid);
del_cdev:
	cdev_del(&infrared_dev.cdev);
del_unregister:
	unregister_chrdev_region(infrared_dev.devid, INFRARED_CNT);
fail_region:
	/* 釋放個人初始化申請的資源,如del_init(); */
	free_irq(infrared_dev.irq_num, NULL);
	gpio_free(infrared_dev.gpio);
	return -EIO;
}

/**
 * @brief 登出字元裝置驅動
 * 
 * @return 0,成功;其他負值,失敗
*/
static void infrared_unregister(void)
{
	/* 1、刪除 cdev */
	cdev_del(&infrared_dev.cdev);
	/* 2、登出裝置號 */
	unregister_chrdev_region(infrared_dev.devid, INFRARED_CNT);
	/* 3、登出裝置 */
	device_destroy(infrared_dev.class, infrared_dev.devid);
	/* 4、登出類 */
	class_destroy(infrared_dev.class);

	/* 釋放中斷 */
	free_irq(infrared_dev.irq_num, NULL);
	/* 釋放 IO */
	gpio_free(infrared_dev.gpio);
}

/**
 * @brief 驅動入口函數
 * 
 * @return 0,成功;其他負值,失敗
*/
static int __init infrared_driver_init(void)
{
	pr_info("infrared_driver_init\n");
	return infrared_register();
}

/**
 * @brief 驅動出口函數
 * 
 * @return 0,成功;其他負值,失敗
*/
static void __exit infrared_driver_exit(void)
{
	pr_info("infrared_driver_exit\n");
	infrared_unregister();
}

/* 將上面兩個函數指定為驅動的入口和出口函數 */
module_init(infrared_driver_init);
module_exit(infrared_driver_exit);

/* LICENSE 和作者資訊 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("JIAOZHU");
MODULE_INFO(intree, "Y");


六、測試程式

#include <fcntl.h>
#include <stdio.h>
#include <unistd.h> 
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <signal.h>


/***************************************************************
檔名 : drive_read_app.c
作者 : jiaozhu
版本 : V1.0
描述 : 驅動讀取測試
其他 : 使用方法:./drive_read_app [/dev/xxx]
argv[1] 需要讀取的驅動
紀錄檔 : 初版 V1.0 2023/3/3
***************************************************************/


int fd;
char *filename;

/**
 * @brief 訊號響應函數,用於讀取紅外接收的資料
 *
 * @param num 號誌
*/
void infrared_handler(int num)
{
    int res;
    unsigned char data_buf[4];
    /* 從驅動檔案讀取資料 */
    res = read(fd, data_buf, sizeof(data_buf));
    if (res == 0)       
    {
        printf("infrared data: %02x %02x %02x %02x\n", data_buf[0], data_buf[1], data_buf[2], data_buf[3]);
    }
    else
    {
        printf("read file %s failed!\r\n", filename);
    }
}

/**
 * @brief main 主程式
 * @param argc argv 陣列元素個數
 * @param argv 具體引數
 * @return 0 成功;其他 失敗
*/
int main(int argc, char *argv[])
{
    int flags = 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;
    }

    signal(SIGIO, infrared_handler);
    /* 設定當前程序接收訊號 */
    fcntl(fd, F_SETOWN, getpid());
    flags =fcntl(fd, F_GETFL);
    /* 開啟非同步通知 */
    fcntl(fd, F_SETFL, flags | FASYNC);

    while (1);

    close(fd);

    return 0;
}


參考連結

幾種常用的紅外線訊號傳輸協定:https://tech.hqew.com/news_1050217
小米紅外遙控器如何適配到其他應用裝置之上:<https://blog.csdn.net/qq_40001346/article/details/108639243