前不久接触到红外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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
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<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%以上,如果对这个判断精度要求高,还是搞个单片机专门来处理会更好。