前不久接触到红外NEC编码,闲来无事,就想在Android上面实现红外NEC编码的解析(如果不了解NEC编码的同学,可以找度娘,相关资料很多很详细)。由于接收管的原因,收到的红外波形和发射的红外波形是一样的,比如:接收到的引导码是9ms的高电平和4.5ms的低电平,重复码是9ms高电平和2.25ms低电平,0是0.56ms高电平和0.56ms低电平,1是0.56高电平和1.69ms低电平,另外每个编码发送完成后会有一个高电平表示结束(可以用示波器查看)。

具体的思路:

              1、数据采集,中断采用边沿触发的方式,两次中断间隔时间相减就是高或者低电平持续时间,然后把记录到的数据放入一个queue里面,queue使用的是kernel自带的kfifo,这样就获得数据,然后唤醒下面2中的kernel thread处理queue里面的数据

              2、数据处理,创建一个kernel thread,在线程里面不断的读取queue里面的数据,先通过 process_ir_rx_cmd 判断是引导码还是重复码,如果是引导码接下来使用 process_ir_rx_data 解析处理相关数据,如果是重复码就上报之前的数据即可。

 

具体的代码如下:

#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>
#include <linux/pinctrl/consumer.h>
#include <linux/time.h>
#include <linux/kfifo.h>
#include <linux/kthread.h>
#include <linux/wait.h>
#include <linux/input.h>


static struct platform_device *ir_rx_dev = NULL;
static int irq_num;
static unsigned int trigger_level;
static struct task_struct *ir_rx_ts;
static struct input_dev *ir_rx_input_dev=NULL;

//定义相关命令和数据解析的状态
enum ir_rx_state{
	IR_RX_OK,
	IR_RX_CMD_START,
	IR_RX_CMD_REPEAT,
	IR_RX_CMD_ERROR,
	IR_RX_DATA_0,
	IR_RX_DATA_1,
	IR_RX_DATA_ERR,
};

//用一个二维数组保存接收到的红外命令和底层键值得对应关系
static unsigned int data_key_map[18][2]={
	{66,KEY_0},
	{22,KEY_1},
	{25,KEY_2},
	{13,KEY_3},
	{12,KEY_4},
	{24,KEY_5},
	{94,KEY_6},
	{8,KEY_7},
	{28,KEY_8},
	{90,KEY_9},
	{7,KEY_VOLUMEDOWN},
	{9,KEY_VOLUMEUP},
	{70,KEY_UP},
	{21,KEY_DOWN},
	{68,KEY_LEFT},
	{67,KEY_RIGHT},
	{64,KEY_PAUSE},
	{74,KEY_BACK},
};

//用来保存数据 time和高低电平的状态
typedef struct{
    struct timespec tv;
	int level;//1 falling 2 rasing
}ir_rx_data;

//see kfifo.h 这个是DEFINE_KFIFO展开后的,是kernel3.18里面的
/*ir_rx_fifo type is 
struct {
	union {
		struct _kfifo kfifo;
		ir_rx_data *type;
		const ir_rx_data *const_type;
		char (*rectype)[0];
		ir_rx_data *ptr;
		ir_rx_data const *ptr_const;
	};
	ir_rx_data buf[512];
}ir_rx_fifo = 
{
	{
		{
			.in	= 0, 
			.out	= 0, 
			.mask	= __is_kfifo_ptr(&(fifo)) ?  : ARRAY_SIZE((fifo).buf) - 1,
			.esize	= sizeof(*(fifo).buf),
			.data	= __is_kfifo_ptr(&(fifo)) ?NULL :(fifo).buf,
		}
	}
}
*/


//定义kfifo 类型是ir_rx_data大小是512
static DEFINE_KFIFO(ir_rx_fifo,ir_rx_data,512);//queue len =512 data type ir_rx_data
//INIT_KFIFO(ir_rx_fifo);

//创建一个wait_queue_head_t
DECLARE_WAIT_QUEUE_HEAD(ir_rx_wait);



//中断处理函数
static irqreturn_t ir_rx_handler(int irq,void *data){
	ir_rx_data cur_ird,store_ird;
    static ir_rx_data prv_ird={0};
	int ret = 0;

//如果当前是falling触发,设置level是1,否则设置level是2,并记录时间和修改触发方式相反
	getrawmonotonic(&cur_ird.tv);//time 
	if(trigger_level==IRQF_TRIGGER_FALLING){//falling trigger
	    cur_ird.level = 1 ;//level 
		trigger_level = IRQF_TRIGGER_RISING;
	}else{//rising trigger
		cur_ird.level = 2 ;//level
		trigger_level = IRQF_TRIGGER_FALLING;
	}
	irq_set_irq_type(irq_num,trigger_level);


//当前的触发时间减去前一次的触发时间就是电平的持续时间,根据前一次level状态设置是高电平还是低电平
	if(prv_ird.level!=0){
		if(prv_ird.level==1){
			store_ird.tv = ns_to_timespec(timespec_to_ns(&cur_ird.tv)-timespec_to_ns(&prv_ird.tv));
			store_ird.level = 0;//1 mean low level
		}else{
			store_ird.tv = ns_to_timespec(timespec_to_ns(&cur_ird.tv)-timespec_to_ns(&prv_ird.tv));
			store_ird.level = 1;//high level
		}

//把数据放入queue里面
	    ret = kfifo_put(&ir_rx_fifo,store_ird);
	    if(!ret){
		    printk("error the ir_rx_fifo is full\n");
	    }

//唤醒kernel thread执行
		wake_up(&ir_rx_wait);
	}

//保存本次的数据
	prv_ird = cur_ird;
	return IRQ_HANDLED;
}

//中断相关初始化
static int ir_rx_irq_init(struct platform_device *dev){
	struct pinctrl *ir_rx_pinxtrl;
	struct pinctrl_state *ir_rx_pinctrl_state;
	int ret = 0;
	
	ir_rx_pinxtrl = devm_pinctrl_get(&dev->dev);
	if(!IS_ERR(ir_rx_pinxtrl)){
		ir_rx_pinctrl_state = pinctrl_lookup_state(ir_rx_pinxtrl,"ir_rx_int");
	}
	if((!IS_ERR(ir_rx_pinxtrl))&&(!IS_ERR(ir_rx_pinctrl_state))){
		pinctrl_select_state(ir_rx_pinxtrl,ir_rx_pinctrl_state);
	}
	irq_num = irq_of_parse_and_map(dev->dev.of_node,0);
	trigger_level = IRQF_TRIGGER_RISING;
	ret = request_irq(irq_num,ir_rx_handler,trigger_level,"ir-rx-int",NULL);
	if(!ret){
		printk("ir_rx_irq_init request_irq ok \n");
	}
	return ret;
}

//读取queue里面的数据,有数据才读,没有就堵塞在这
static void get_data(ir_rx_data *data){
	int ret = 0;
	wait_event(ir_rx_wait, kfifo_len(&ir_rx_fifo));
	ret = kfifo_get(&ir_rx_fifo,data);
	if(!ret){
		printk("get_data empty data\n");
	}
}


//解析是引导码还是重复码
static enum ir_rx_state process_ir_rx_cmd(void){
	ir_rx_data data;
	enum ir_rx_state state;
	s64 time;
	get_data(&data);
	if(data.level == 1){
		time = timespec_to_ns(&data.tv);
		if(time> 8000000 && time < 11000000){
			get_data(&data);
			time = timespec_to_ns(&data.tv);
			if(data.level==0 && (time>3000000 && time <7000000)){
				state = IR_RX_CMD_START;
			}else if(data.level==0 && (time>1500000 && time <3100000)){
				state = IR_RX_CMD_REPEAT;
			}else{
				state = IR_RX_CMD_ERROR;
			}
		}else{
			state = IR_RX_CMD_ERROR;
		}
	}else{
		state = IR_RX_CMD_ERROR;
	}
	return state;
}

//解析数据 只解析一位的数据,1返回IR_RX_DATA_1,0返回IR_RX_DATA_0,没有解析出来返回IR_RX_DATA_ERR

static enum ir_rx_state get_bit(void){
	s64 time;
	ir_rx_data data;
	enum ir_rx_state state;
	
	get_data(&data);
	time = timespec_to_ns(&data.tv);
	if(data.level == 1){
		if(time> 300000 && time < 900000){
			get_data(&data);
			time = timespec_to_ns(&data.tv);
			if(data.level==0 && (time>1300000 && time <2000000)){
				state = IR_RX_DATA_1;
			}else if(data.level==0 && (time>300000 && time <900000)){
				state = IR_RX_DATA_0;
			}
		}else{
			state = IR_RX_DATA_ERR;
		}
	}else{
		state = IR_RX_DATA_ERR;
	}
	return state;
}

//解析一个字节的数据,由于低位在前,如果get_bit是1,只需要data |= 1<<i 即可,为0不用管
static enum ir_rx_state get_byte(u8 *byte){
	int i=0;
	u8 data = 0;
	enum ir_rx_state state = IR_RX_OK;
	for(i=0;i<8;i++){
		switch (get_bit()){
			case IR_RX_DATA_0:
			     break;
			case IR_RX_DATA_1:
			     data |= 1<<i;
			     break;
			case IR_RX_DATA_ERR:
			default:
			     state = IR_RX_DATA_ERR;
				 goto out;
			     break;
		};
	}
	*byte = data;
out:
	return state;
}


//处理数据,读取4字节的数据,通过反码判断接收到的数据是否正确,
static enum ir_rx_state process_ir_rx_data(u8 *data){
	u8 rev_data[4]={0};
	int i=0;
	enum ir_rx_state state = IR_RX_OK;
	
	for(i=0;i<4;i++){
		state = get_byte(&rev_data[i]);
		if(state!=IR_RX_OK)
			goto out;
	}
	if((rev_data[0] == (u8)(~rev_data[1])) && (rev_data[2] == (u8)(~rev_data[3]))){
		*data = rev_data[2];
		state = IR_RX_OK;
	}
out:
	return state;
}

//上报数据
static void report_data(u8 data){
	int i=0;
	for(i=0;i<18;i++){
		if(data == data_key_map[i][0]){
			input_report_key(ir_rx_input_dev,data_key_map[i][1],1);
			input_sync(ir_rx_input_dev);
			input_report_key(ir_rx_input_dev,data_key_map[i][1],0);
			input_sync(ir_rx_input_dev);
		}
	}
}

static void process_ir_rx_repeat(u8 data){
	if(!data){
	    report_data(data);
	}
}

//kernel thread一直循环处理数据
static int ir_rx_handler_thread(void *data){
	struct sched_param ir_rx_sp = {
		.sched_priority = 10,
	};
	enum ir_rx_state state;
	u8 code = 0;
	
	sched_setscheduler(ir_rx_ts,SCHED_RR,&ir_rx_sp);	
	do{
		state = process_ir_rx_cmd();//判断是引导码还是重复码
		switch(state){
		    case IR_RX_CMD_START:
			     state = process_ir_rx_data(&code);//如果引导码接下来处理数据
				 if(state == IR_RX_OK){
				     report_data(code);
				 }
				 break;
			case IR_RX_CMD_REPEAT:
			     process_ir_rx_repeat(code);//重复码就重复上报
				 break;
			case IR_RX_CMD_ERROR:
			default:
			     break;
		}
	}while(!kthread_should_stop());
	
	return 0;
}


//input相关的初始化
static int ir_rx_input_dev_init(struct device *dev){
	int i=0,ret;
	
	ir_rx_input_dev = devm_input_allocate_device(dev);
	if(ir_rx_input_dev==NULL){
		return -EINVAL;
	}
	
	for(i=0;i<10;i++){
	    input_set_capability(ir_rx_input_dev,EV_KEY,i+2);//key_1 - key_9 key_0
	}
	input_set_capability(ir_rx_input_dev,EV_KEY,KEY_VOLUMEDOWN);
	input_set_capability(ir_rx_input_dev,EV_KEY,KEY_VOLUMEUP);
	input_set_capability(ir_rx_input_dev,EV_KEY,KEY_UP);
	input_set_capability(ir_rx_input_dev,EV_KEY,KEY_DOWN);
	input_set_capability(ir_rx_input_dev,EV_KEY,KEY_LEFT);
	input_set_capability(ir_rx_input_dev,EV_KEY,KEY_RIGHT);
	input_set_capability(ir_rx_input_dev,EV_KEY,KEY_PAUSE);
	input_set_capability(ir_rx_input_dev,EV_KEY,KEY_BACK);
	
	ret = input_register_device(ir_rx_input_dev);
	
	return ret;
}

static int ir_rx_probe(struct platform_device *dev){
	int ret = 0;
	
	ir_rx_dev = dev;
    ret = ir_rx_irq_init(dev);
	if(ret){
		goto ir_rx_irq_init_fail;
	}
	ret = ir_rx_input_dev_init(&dev->dev);
	if(ret){
		goto alloc_input_dev_fail;
	}
	ir_rx_ts = kthread_run(ir_rx_handler_thread,NULL,"ir-rx-thread");
	if(IS_ERR(ir_rx_ts)){
		goto create_thread_fail;
	}
	return 0;
create_thread_fail:
alloc_input_dev_fail:
    free_irq(irq_num,NULL);
ir_rx_irq_init_fail:
	return ret;
}

static struct of_device_id ir_match_node={
	.compatible = "test,ir_rx",
};

static struct platform_driver ir_rx_drv = {
	.probe = ir_rx_probe,
	.driver = {
		.name = "ir_rx",
		.of_match_table = &ir_match_node,
	},
};

static int __init ir_rx_init(void){
	if(platform_driver_register(&ir_rx_drv)){
		printk("register ir_rx_drv fail\n");
	}
	return 0;
}

static void __exit ir_rx_exit(void){
	
}

module_init(ir_rx_init);
module_exit(ir_rx_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("fangyun");

总结:

        实际数据处理中,精准度还行,能实现相关的功能。不足之处,由于系统不是只处理这个红外,中断也很多,很多代码会屏蔽中断,导致有些时候获得的时间误差比较大,尽管放大了电平判断的时间,但是还是存在误差。基本感觉正确率在95%以上,如果对这个判断精度要求高,还是搞个单片机专门来处理会更好。