NEC 遥控器 源码处理流程分析

NEC 协议的遥控器

代码位置在 \kernel\drivers\input\remotectl\rkxx_remotectl.c


关于NEC协议的东西主要来源于网上,出处太多,也就不一一列举了,感谢提供资源的各位网友。

根据我个人的理解,NEC遥控器工作流程应该是这样的:

遥控器端:

当遥控器按下一个键之后,会形成载波发送到接收端,载波信息依次是: 

起始码

用户码              区别不同厂商的遥控器相互干扰

键值                  判断用户是按了哪一个键

结束码

另外需要说明的是,如果遥控器在压下一个键之后不放手,后面就不会发送同一个键被按下的载波(起始码,用户码......)了,而是会发一个 REPEAT的载波信号


当收到一个bit之后如何判断是0还是1,还是哪个状态主要是通过类似:if ((TIME_BIT1_MIN < ddata->period) && (ddata->period < TIME_BIT1_MAX)) 代码判断,这个是怎么算出来的,我也不清楚。

接收机端:

依次解出载波,并识别出需要的信息


在接收端,一个 bit 会产生一个中断,这个 bit  所持续的时间(period)会成为判断这是哪一段数据(起始码?用户码?..具体原理我也不清楚),可参考

\kernel\arch\arm\mach-rk30\include\mach\remotectl.h

#ifndef __RKXX_REMOTECTL_H__
#define __RKXX_REMOTECTL_H__
#include <linux/input.h>

/********************************************************************
**                            宏定义                                *
********************************************************************/
#define TIME_BIT0_MIN  625  /*Bit0  1.125ms*/
#define TIME_BIT0_MAX  1625

#define TIME_BIT1_MIN  1650  /*Bit1  2.25ms*/
#define TIME_BIT1_MAX  2650

#define TIME_PRE_MIN   13000   /*4500*/
#define TIME_PRE_MAX   14000   /*5500*/           /*PreLoad 4.5+0.56 = 5.06ms*/

#define TIME_RPT_MIN   98100   /*101000*/
#define TIME_RPT_MAX   98300   /*103000*/         /*Repeat  105-2.81=102.19ms*/  //110-9-2.25-0.56=98.19ms

#define TIME_SEQ_MIN   11200   /*2650*/
#define TIME_SEQ_MAX   11300   /*3000*/           /*sequence  2.25+0.56=2.81ms*/ //11.25ms

#define TIME_SEQ1_MIN   10000   /*2650*/
#define TIME_SEQ1_MAX   12000   /*3000*/           /*sequence  2.25+0.56=2.81ms*/ //11.25ms

#define TIME_SEQ2_MIN   40000   /*101000*/
#define TIME_SEQ2_MAX   47000   /*103000*/         /*Repeat  105-2.81=102.19ms*/  //110-9-2.25-0.56=98.19ms

/********************************************************************
**                          结构定义                                *
********************************************************************/
typedef enum _RMC_STATE
{
    RMC_IDLE,
    RMC_PRELOAD,
    RMC_USERCODE,
    RMC_GETDATA,
    RMC_SEQUENCE
}eRMC_STATE;


struct RKxx_remotectl_platform_data {
	//struct rkxx_remotectl_button *buttons;
	int nbuttons;
	int rep;
	int gpio;
	int active_low;
	int timer;
	int wakeup;
	void (*set_iomux)(void);
};

#endif



这里我主要分析在 接收端 的 处理流程:


每当从遥控器接受到一个 bit 就产生一次中断,中断处理函数是:

static irqreturn_t remotectl_isr(int irq, void *dev_id)
{
    struct rkxx_remotectl_drvdata *ddata =  (struct rkxx_remotectl_drvdata*)dev_id;
    struct timeval  ts;
	struct timeval temp_time;
	long plustime = 0;

	do_gettimeofday(&ts);
	ddata->cur_time = ts;
	temp_time = ts;
	ddata->period = 0;
	{
		plustime = temp_time.tv_sec - ddata->pre_time.tv_sec;
		if(plustime >= 0 && plustime <= 1)
		{
			if(plustime == 1)
			{
				temp_time.tv_usec += 1000000;
			}
			ddata->period = temp_time.tv_usec - ddata->pre_time.tv_usec;
		}
	}
	ddata->pre_time = ddata->cur_time;

    tasklet_hi_schedule(&ddata->remote_tasklet); 

    return IRQ_HANDLED;
}
这个函数会计算出这个 bit 持续的时间,即  ddata->period 值,然后调度   ddata->remote_tasklet  对应的函数,这个函数在 模块初始化函数(如下)中声明

static int __devinit remotectl_probe(struct platform_device *pdev)
{
    struct RKxx_remotectl_platform_data *pdata = pdev->dev.platform_data;
    struct rkxx_remotectl_drvdata *ddata;
    struct input_dev *input;
    int i, j;
    int irq;
    int error = 0;

    if(!pdata) 
        return -EINVAL;
	#ifdef CONFIG_RK30_KEYBOARD_LED_CTL	
	rk29_keyboard_led_init();
	rk29_standby_led_init();
	#endif
    ddata = kzalloc(sizeof(struct rkxx_remotectl_drvdata),GFP_KERNEL);
    memset(ddata,0,sizeof(struct rkxx_remotectl_drvdata));

    ddata->state = RMC_PRELOAD;
    input = input_allocate_device();
    
    if (!ddata || !input) {
        error = -ENOMEM;
        goto fail0;
    }

    platform_set_drvdata(pdev, ddata);

    input->name = pdev->name;
    input->phys = "gpio-keys/input0";
    input->dev.parent = &pdev->dev;

    input->id.bustype = BUS_HOST;
    input->id.vendor = 0x0001;
    input->id.product = 0x0001;
    input->id.version = 0x0100;

	/* Enable auto repeat feature of Linux input subsystem */
	if (pdata->rep)
		__set_bit(EV_REP, input->evbit);
    
	ddata->nbuttons = pdata->nbuttons;
	ddata->input = input;
  wake_lock_init(&ddata->remotectl_wake_lock, WAKE_LOCK_SUSPEND, "rk29_remote");
  if (pdata->set_iomux){
  	pdata->set_iomux();
  }
  error = gpio_request(pdata->gpio, "remotectl");
	if (error < 0) {
		printk("gpio-keys: failed to request GPIO %d,"
		" error %d\n", pdata->gpio, error);
		//goto fail1;
	}
	error = gpio_direction_input(pdata->gpio);
	if (error < 0) {
		pr_err("gpio-keys: failed to configure input"
			" direction for GPIO %d, error %d\n",
		pdata->gpio, error);
		gpio_free(pdata->gpio);
		//goto fail1;
	}
    irq = gpio_to_irq(pdata->gpio);
	if (irq < 0) {
		error = irq;
		pr_err("gpio-keys: Unable to get irq number for GPIO %d, error %d\n",
		pdata->gpio, error);
		gpio_free(pdata->gpio);
		goto fail1;
	}
	
	error = request_irq(irq, remotectl_isr,	IRQF_TRIGGER_FALLING , "remotectl", ddata);
	
	if (error) {
		pr_err("gpio-remotectl: Unable to claim irq %d; error %d\n", irq, error);
		gpio_free(pdata->gpio);
		goto fail1;
	}
    setup_timer(&ddata->timer,remotectl_timer, (unsigned long)ddata);
    
    tasklet_init(&ddata->remote_tasklet, remotectl_do_something, (unsigned long)ddata);
    
    for (j=0;j<sizeof(remotectl_button)/sizeof(struct rkxx_remotectl_button);j++){ 
    	printk("remotectl probe j=0x%x\n",j);
		for (i = 0; i < remotectl_button[j].nbuttons; i++) {
			unsigned int type = EV_KEY;
	        
			input_set_capability(input, type, remotectl_button[j].key_table[i].keyCode);
		}
  }
	error = input_register_device(input);
	if (error) {
		pr_err("gpio-keys: Unable to register input device, error: %d\n", error);
		goto fail2;
	}
    
    input_set_capability(input, EV_KEY, KEY_WAKEUP);

	device_init_wakeup(&pdev->dev, 1);

	return 0;

fail2:
    pr_err("gpio-remotectl input_allocate_device fail\n");
	input_free_device(input);
	kfree(ddata);
fail1:
    pr_err("gpio-remotectl gpio irq request fail\n");
    free_irq(gpio_to_irq(pdata->gpio), ddata);
    del_timer_sync(&ddata->timer);
    tasklet_kill(&ddata->remote_tasklet); 
    gpio_free(pdata->gpio);
fail0: 
    pr_err("gpio-remotectl input_register_device fail\n");
    platform_set_drvdata(pdev, NULL);

	return error;
}

tasklet_init(&ddata->remote_tasklet, remotectl_do_something, (unsigned long)ddata); 就是这一行


也即就是,每从红外线遥控器获取到一个 bit  之后就调用一次  remotectl_do_something() ,代码如下:

static void remotectl_do_something(unsigned long  data)
{
    struct rkxx_remotectl_drvdata *ddata = (struct rkxx_remotectl_drvdata *)data;
	mod_timer(&ddata->timer, jiffies + msecs_to_jiffies(250));
	if((TIME_PRE_MIN < ddata->period) && (ddata->period < TIME_PRE_MAX)){
		if(ddata->state == RMC_SEQUENCE){
			remotectl_timer_reload(data);
			ddata->state = RMC_PRELOAD;
		}
	}

    switch (ddata->state)
    {
        case RMC_IDLE:
        {
            ;
        }
        break;
        
        case RMC_PRELOAD:
        {
            if ((TIME_PRE_MIN < ddata->period) && (ddata->period < TIME_PRE_MAX)){
                
                ddata->scanData = 0;
                ddata->count = 0;
                ddata->state = RMC_USERCODE;
				mod_timer(&ddata->timer, jiffies + msecs_to_jiffies(120));
            }else{
                ddata->state = RMC_PRELOAD;
            }
            ddata->pre_time = ddata->cur_time;
        }
        break;
        
        case RMC_USERCODE:
        {
            ddata->scanData <<= 1;
            ddata->count ++;

            if ((TIME_BIT1_MIN < ddata->period) && (ddata->period < TIME_BIT1_MAX)){
                ddata->scanData |= 0x01;
            }
            if (ddata->count == 0x10){//16 bit user code
                if (remotectl_keybdNum_lookup(ddata)){
                    ddata->state = RMC_GETDATA;
                    ddata->scanData = 0;
                    ddata->count = 0;
                }else{                //user code error
                    ddata->state = RMC_PRELOAD;
                }
            }
        }
        break;
        
        case RMC_GETDATA:
        {
            ddata->count ++;
            ddata->scanData <<= 1;

          
            if ((TIME_BIT1_MIN < ddata->period) && (ddata->period < TIME_BIT1_MAX)){
                ddata->scanData |= 0x01;
            }
            if (ddata->count == 0x10){
                if ((ddata->scanData&0x0ff) == ((~ddata->scanData >> 8)&0x0ff)){
                    if (remotectl_keycode_lookup(ddata)){
                        ddata->press = 1;
                         if (get_suspend_state()==0){
                                input_event(ddata->input, EV_KEY, ddata->keycode, 1);
                                input_sync(ddata->input);
                            }else if ((get_suspend_state())&&(ddata->keycode==KEY_POWER)){
                                input_event(ddata->input, EV_KEY, KEY_WAKEUP, 1);
                                input_sync(ddata->input);
                            }
                        ddata->state = RMC_SEQUENCE;
                    }else{
                        ddata->state = RMC_PRELOAD;
                    }
                }else{
                    ddata->state = RMC_PRELOAD;
                }
            }
        }
        break;
             
        case RMC_SEQUENCE:{

            //printk( "S\n");
            
            if ((TIME_RPT_MIN < ddata->period) && (ddata->period < TIME_RPT_MAX)){
                ;
            }else if ((TIME_SEQ_MIN < ddata->period) && (ddata->period < TIME_SEQ_MAX)){
	            if (ddata->press == 1){
                    ddata->press = 3;
                }else if (ddata->press & 0x2){
                    ddata->press = 2;
                //input_event(ddata->input, EV_KEY, ddata->keycode, 2);
		            //input_sync(ddata->input);
                }
                mod_timer(&ddata->timer,jiffies + msecs_to_jiffies(110));
                //ddata->state = RMC_PRELOAD;
            }
        }
        break;
       
        default:
            break;
    } 
	return;
}
这个函数的处理流程,就是 完成一个状态到另一个状态的转换,比如初始化为  RMC_PRELOAD状态,当得到了正确的 起始码之后就进入了 RMC_USERCODE状态

犹如一个自动状态机:

RMC_PRELOAD--------------得到正确的起始码------------------------->>>>>>RMC_USERCODE

RMC_USERCODE-----------得到能识别的用户名---------------------->>>>>>RMC_GETDATA

RMC_GETDATA---------------得到能识别的键值------------------------->>>>>>RMC_SEQUECE

RMC_SEQUECE-------------得到正确的结束码-------------------------->>>>>>RMC_PRELOAD

其中,RMC_USERCODE,RMC_GETDATA,RMC_SEQUECE在识别出错的时候都返回到RMC_PRELOAD状态,结束解析。意思就是:比如,当收到的用户码不能识别,也即就是不是我们厂生产的遥控器在遥控我们的接收器,当然不能让它得到解析;又比如,用户码正确了,但是键值查不到,这可能是凑巧用户码一致但是确实不是这个遥控板,因为不同遥控板发送的键的扫描码可能是不一致的,当然也要结束本轮识别。

每当识别到了 键值 (即 RMC_GETDATA状态正确识别到了键值)就会向上层应用发送这个键值

                                input_event(ddata->input, EV_KEY, ddata->keycode, 1);
                                input_sync(ddata->input);

注意input_event函数的最后一个参数是 1 ,查资料得到,如果是 1  代表某个键按下去了,如果事件驱动器在收到input_event(...,1)之后的额定的时间内没有收到依然没有收到input_event(...,0)那么将产生连击事件。比如,系统在收到input_event(...,1)后如果200ms没收到input_event(...,0)就会认为键是压下去了,但还没弹起来,就是每隔Xms向上层应用发送 压下去的键的键值。

那么remotectl_do_something()又是如何实现连击的效果的呢?

首先,我们知道了每来一个电平我们就产生一次中断,中断处理程序会调用remotectl_do_something(),如果系统在收到一个 XX 键按下去之后再收到 REPEAT电平会做如处理:

        case RMC_SEQUENCE:{
            if ((TIME_RPT_MIN < ddata->period) && (ddata->period < TIME_RPT_MAX)){
                ;
            }else if ((TIME_SEQ_MIN < ddata->period) && (ddata->period < TIME_SEQ_MAX)){
	            if (ddata->press == 1){
                    ddata->press = 3;
                }else if (ddata->press & 0x2){
                    ddata->press = 2;
                }
                mod_timer(&ddata->timer,jiffies + msecs_to_jiffies(110));
            }
        }
对,就是什么也不做,但是在收到这个REPEAT (TIME_SEQ_MIN < ddata->period) && (ddata->period < TIME_SEQ_MAX)电平的时候,remotectl_do_something()先是做了如下一件事,再去判断是否连击的,就是在它的前几行,代码是:

mod_timer(&ddata->timer, jiffies + msecs_to_jiffies(250));

这句代码是什么意思呢?mod_timer()就是改变一个已经激活的定时器,重新设置它的超时时间。也就是说,如果收到REPEAT重复电平,但250ms之后没有收到任何电平了,250ms之后就会超时,超时就会执行注册这个定时器所对应的函数,这个函数在模块初始化函数remotectl_probe()中做了说明:

setup_timer(&ddata->timer,remotectl_timer, (unsigned long)ddata);

也即超时就会执行remotectl_timer(),该函数的代码是:

static void remotectl_timer(unsigned long _data)
{
    struct rkxx_remotectl_drvdata *ddata =  (struct rkxx_remotectl_drvdata*)_data;
    if(ddata->press != ddata->pre_press) {
        ddata->pre_press = ddata->press = 0;

        if (get_suspend_state()==0){//没有挂起
            input_event(ddata->input, EV_KEY, ddata->keycode, 0);
		    input_sync(ddata->input);
        }else if ((get_suspend_state())&&(ddata->keycode==KEY_POWER)){
            input_event(ddata->input, EV_KEY, KEY_WAKEUP, 0);
            input_sync(ddata->input);
        }
    }
#ifdef CONFIG_PM
    remotectl_wakeup(_data);
#endif
    ddata->state = RMC_PRELOAD;
}

这个函数就是向事件驱动发送input_event(...,0),也就是通知事件驱动说,这个键弹起来了。

归纳一下,当连续收到重复电平的时候,在CASE重复电平分支里什么也不做,但是会不断地去更新 定时器,每次都设置超时时间为250ms,直到收到其他电平(后面会介绍)或者没收到任何电平(250ms之后执行默认弹起动作)。

应当说明一点,前面介绍了重复电平,是先收到一个正确的键值之后才会在下一个周期发送重复电平,所以收到重复电平的状态应该是 RMC_SEQUECE 状态,所以case这这个分支里做了是否是重复电平判断。

那么一个正常按键情况又是如何工作的呢?

收到起始码bit之后就会调用remotectl_do_something(),现在就来着重分析它:

	mod_timer(&ddata->timer, jiffies + msecs_to_jiffies(250));
	if((TIME_PRE_MIN < ddata->period) && (ddata->period < TIME_PRE_MAX)){
		if(ddata->state == RMC_SEQUENCE){
			remotectl_timer_reload(data);
			ddata->state = RMC_PRELOAD;
		}
	}
第一句,前面介绍了,不讲,接下来的这个判断作用是处理这个情况:

当用户按下去之后,不松,又突然松了重按一下键的情况。

不松时,状态为 RMC_SEQUENCE,突然又重按一下,得到了一个 TIME_PRE_MIN~TIME_PRE_MAX即得到一个起始码,这种情况下,模块不能等到REPEAT电平置的250ms超时时自动弹起,而是要马上弹起(),并把状态置为 RMC_PRELOAD,马上弹起的动作是由 remotectl_timer_reload()实现的,它的代码是:

static void remotectl_timer_reload(unsigned long _data)
{
	struct rkxx_remotectl_drvdata *ddata = (struct rkxx_remotectl_drvdata*)_data;
	if(ddata->press != ddata->pre_press){
		ddata->pre_press = ddata->press = 0;
		input_event(ddata->input, EV_KEY, ddata->keycode, 0);
		input_sync(ddata->input);
	}
}
判断press=?pre_press后面会介绍,两个不等就代表 没有弹起,因为弹起是通过调用 remotectl_timer()实现的, 就会设置他们ddata->pre_press = ddata->press = 0;

继续分析remotectl_do_something(),接下来就进入了switch..case...这段代码,它依次识别遥控器发来的   起始码---用户码---键值----结束码 ,其中用户码16位,键值本身是8位,但是却发了两次 前8位为正常的键值,后8位是正常值得反码。这都是为了保证键值没错,键值错了比用户码识别错了还严重,因为用户码识别错了 ,大不了是本次没按灵,但是键值如果错了,可能导致不可预料的操作。

        case RMC_IDLE:
        {
            ;
        }
        break;

无关状态,直接跳过

        case RMC_PRELOAD:
        {
            if ((TIME_PRE_MIN < ddata->period) && (ddata->period < TIME_PRE_MAX)){
                
                ddata->scanData = 0;
                ddata->count = 0;
                ddata->state = RMC_USERCODE;
				mod_timer(&ddata->timer, jiffies + msecs_to_jiffies(120));
            }else{
                ddata->state = RMC_PRELOAD;
            }
            ddata->pre_time = ddata->cur_time;
            //mod_timer(&ddata->timer,jiffies + msecs_to_jiffies(130));
        }
        break;
在准备状态(RMC_PRELOAD)如果收到了起始码,就意味着本轮识别开始,准备接受用户码了,因为NEC遥控器常常采用 38K Hz的载波,一个周期约 108ms,所以这设置自动弹起的时候使用了 120ms这个值,120ms之后应该把本次识别完成了,无论如何也要弹起,免得产生连击。

        case RMC_USERCODE:
        {
            ddata->scanData <<= 1;
            ddata->count ++;

            if ((TIME_BIT1_MIN < ddata->period) && (ddata->period < TIME_BIT1_MAX)){
                ddata->scanData |= 0x01;
            }
            if (ddata->count == 0x10){//16 bit user code
                if (remotectl_keybdNum_lookup(ddata)){
                    ddata->state = RMC_GETDATA;
                    ddata->scanData = 0;
                    ddata->count = 0;
                }else{                //user code error
                    ddata->state = RMC_PRELOAD;
                }
            }
        }
        break;
接受用户码,把 scanData左移一位,说白了把最低位置 0 ,然后判断收到的是1还是0 ,通过  if ((TIME_BIT1_MIN < ddata->period) && (ddata->period < TIME_BIT1_MAX))判断,如果条件为真,表示收到了一个 1  这个时候   scanData |= 0x01; 说白了 就是把最低位置为 1 ,重复16次就收到了一个完整的用户码。

收到用户码之后,调用remotectl_keybdNum_lookup(),就是判断接收机是否支持这种遥控器,这里面代码很简单,就是一个键值映射,就不说了

        case RMC_GETDATA:
        {
            ddata->count ++;
            ddata->scanData <<= 1;
          
            if ((TIME_BIT1_MIN < ddata->period) && (ddata->period < TIME_BIT1_MAX)){
                ddata->scanData |= 0x01;
            }
            if (ddata->count == 0x10){
                if ((ddata->scanData&0x0ff) == ((~ddata->scanData >> 8)&0x0ff)){
                    if (remotectl_keycode_lookup(ddata)){
                        ddata->press = 1;
                         if (get_suspend_state()==0){
                                input_event(ddata->input, EV_KEY, ddata->keycode, 1);
                                input_sync(ddata->input);
                            }else if ((get_suspend_state())&&(ddata->keycode==KEY_POWER)){
                                input_event(ddata->input, EV_KEY, KEY_WAKEUP, 1);
                                input_sync(ddata->input);
                            }
                        ddata->state = RMC_SEQUENCE;
                    }else{
                        ddata->state = RMC_PRELOAD;
                    }
                }else{
                    ddata->state = RMC_PRELOAD;
                }
            }
        }
        break;
case RMC_USERCODE:一样,先收下 16 之后判断 前8位和其反码(后8位)是否一致,也就是判断键值在传输过程中是否出错。

如果没有出错,就去查找 usercode对应的遥控器有没有这个键,有这个键,表示按对了,就把这个按键的键值发给上层应用

        case RMC_SEQUENCE:{
            if ((TIME_RPT_MIN < ddata->period) && (ddata->period < TIME_RPT_MAX)){
                ;
            }else if ((TIME_SEQ_MIN < ddata->period) && (ddata->period < TIME_SEQ_MAX)){
	            if (ddata->press == 1){
                    ddata->press = 3;
                }else if (ddata->press & 0x2){
                    ddata->press = 2;
                }
                mod_timer(&ddata->timer,jiffies + msecs_to_jiffies(110));
            }
        }
这段代码比起前面的就有点复杂,不解释后面介绍。

总结起来,remotectl_do_something干的工作就是:

收到起始码--->接受用户码----->用户码判定---->接受键值----->键值传输查错控制----->判断键值---->向上层应用发送键值------>接受结束码 

press,pre_press 意思

在程序中多次判断 这两个值是否相等,这两个值究竟又是什么意思呢?

据我分析,press有4个值 分别是 0,1,2,3,其中:

0  代表  初始化状态

1  代表  识别到了一个按键

3   代表  完完整整识别了一个载波,也就是 结束码也得到了

2   代表 我现在没搞懂,在 case RMC_SEQUENC 里面有一段诡异的代码:

	        if (ddata->press == 1){
                    ddata->press = 3;
                }else if (ddata->press & 0x2){
                    ddata->press = 2;
                }

这段代码至今未懂,恳请能够分析出这段代码的网友能不吝指点一二。

再分析一段代码:关于遥控器在系统深度睡眠状态的唤醒代码  有BUG

#ifdef CONFIG_PM
void remotectl_wakeup(unsigned long _data)
{
    struct rkxx_remotectl_drvdata *ddata =  (struct rkxx_remotectl_drvdata*)_data;
    long *time;
    int i;

    time = ddata->remotectl_suspend_data.scanTime;

    if (get_suspend_state()){
        
        static int cnt;
       
        ddata->remotectl_suspend_data.suspend_flag = 0;
        ddata->count = 0;
        ddata->state = RMC_USERCODE;
        ddata->scanData = 0;
        
        for (i=0;i<ddata->remotectl_suspend_data.cnt;i++){
            if (((TIME_BIT1_MIN<time[i])&&(TIME_BIT1_MAX>time[i]))||((TIME_BIT0_MIN<time[i])&&(TIME_BIT0_MAX>time[i]))){
                cnt = i;
                break;;
            }
        }
        
        for (;i<cnt+32;i++){
            ddata->scanData <<= 1;
            ddata->count ++;

            if ((TIME_BIT1_MIN < time[i]) && (time[i] < TIME_BIT1_MAX)){
                ddata->scanData |= 0x01;
            }
            
            if (ddata->count == 0x10){//16 bit user code
                          
                if (ddata->state == RMC_USERCODE){
                    if (remotectl_keybdNum_lookup(ddata)){
                        ddata->scanData = 0;
                        ddata->count = 0;
                        ddata->state = RMC_GETDATA;
                    }else{
                        ddata->state = RMC_PRELOAD;
                    }
                }else if (ddata->state == RMC_GETDATA){
                    if ((ddata->scanData&0x0ff) == ((~ddata->scanData >> 8)&0x0ff)){
                        if (remotectl_keycode_lookup(ddata)){
                             if (ddata->keycode==KEY_POWER){
                                input_event(ddata->input, EV_KEY, KEY_WAKEUP, 1);
                                input_sync(ddata->input);
                                input_event(ddata->input, EV_KEY, KEY_WAKEUP, 0);
                                input_sync(ddata->input);
                            }
                            ddata->state = RMC_PRELOAD;
                        }else{
                            ddata->state = RMC_PRELOAD;
                        }
                    }else{
                        ddata->state = RMC_PRELOAD;
                    }
                }else{
                    ddata->state = RMC_PRELOAD;
                }
            }
        }
    }
    memset(ddata->remotectl_suspend_data.scanTime,0,50*sizeof(long));
    ddata->remotectl_suspend_data.cnt= 0; 
    ddata->state = RMC_PRELOAD;
    
}

#endif

这段代码的意思是,从scanTime中取出 32个 我们需要的 用户码+键值,如果他们能通过验证,并且是键值是 POWER 键,那么我就向上层应用发送一个 电源键被按下和弹起的动作,这样就能唤醒系统了系统睡眠之后遥控器的键值 之所以要从 scanTime[] 里面取,是因为这段代码。请看代码,在函数  irqreturn_t remotectl_isr()  的最后

#ifdef CONFIG_PM
   wake_lock_timeout(&ddata->remotectl_wake_lock, HZ);
   if ((get_suspend_state())&&(ddata->remotectl_suspend_data.cnt<50))
       ddata->remotectl_suspend_data.scanTime[ddata->remotectl_suspend_data.cnt++] = ddata->period;
#endif
如果系统进入休眠状态,那么就把收到的 bit 持续时间存入到   scanTime  中,并且用 cnt  进行计数

但取出scanTime 值的这段代码有问题:

首先,它是过滤掉无关的码,因为用户码和键值的 bit持续时间 都是在 最小0/1 ~最大0/1 之间,所以找到第一个 相关的bit所在位置,代表从这 ( i 值) 开始读用户码和键值

        for (i=0;i<ddata->remotectl_suspend_data.cnt;i++){
            if (((TIME_BIT1_MIN<time[i])&&(TIME_BIT1_MAX>time[i]))||((TIME_BIT0_MIN<time[i])&&(TIME_BIT0_MAX>time[i]))){
                cnt = i;
                break;;
            }
        }
接下来是,开始取值:

        for (;i<cnt+32;i++){
            ddata->scanData <<= 1;
            ddata->count ++;

            if ((TIME_BIT1_MIN < time[i]) && (time[i] < TIME_BIT1_MAX)){
                ddata->scanData |= 0x01;
            }
在for开始执行前,i 值和 cnt 相等的,所以这个 for 循环需要运行 32 次,错就错在这。试想,如果在睡眠状态,没有收到完整的32位用户码+键值,那么势必要从  scanTime[cnt]取得一些非存入的不可预料的值。也就是说,如果存入scanTime  的值的个数是 cnt 个, cnt 小于32,我们又必须从scanTime 中取得32个,那么32-cnt这些值是不可预料的。 

而正好,这个错误正好发生在目前公司的RK3188的板子上了。

通过实验,发现如果 cnt 大于36 (2位起始码+16位用户码+16位键值 +2位结束码)那么系统正常唤醒,反之就不能唤醒。

通过实验,在睡眠状态,连续快速按 两次开机键就能正常唤醒,因为第二次可能存入了完整的 36 位值了

更改代码:

        for (i=0;i<ddata->remotectl_suspend_data.cnt;i++){
        	if (ddata->count>=32)
        		break;

           if ((TIME_BIT1_MIN < time[i]) && (time[i] < TIME_BIT1_MAX)){
                ddata->scanData <<= 1;
		ddata->scanData |= 0x01;
                ddata->count ++;;
            }else if ((TIME_BIT0_MIN < time[i]) && (time[i] < TIME_BIT0_MAX)){
            	  ddata->scanData <<= 1;
            	  ddata->count ++;;
            }
        }
这样,保证取到的数据都是需要的数据,直到把  scanTime  取完也取不出32 个,那么循环也要结束,与其取错误数据倒不如不取。
这段代码,在板子上依然存在问题,就是在待机状态开机键只能捕捉到正确的键值,用户码最多能正确捕捉到后7位

在厂家给的遥控器驱动里面,我发现,对于唤醒,并没有对用户码进行验证,直接判断是否是电源键而发生动作,这正好被我们测试出来了,通过电视机的遥控器居然能把我们的板子唤醒,蛋都碎了~~~哎,目前系统工程师说,可能在深度睡眠状态 板子工作电压都会变,导致收不到前面几位用户码,于是,只好折衷下,只判断后7位,通过了4个遥控器测试。代码如下:

			for (i=0;i<sizeof(remotectl_button)/sizeof(struct rkxx_remotectl_button);i++)
			{
				//Note: just test the last 7 bit of usercode(16bit).
				if( (remotectl_button[i].usercode&0x7F) == ((ddata->scanData>>16)&0x7F) )
				{
					remotectl_get_pwr_scanData(ddata,&power_scanData,i);
					if ( (ddata->scanData&0xFFFF) == (power_scanData&0xFFFF) )
					{
					    input_event(ddata->input, EV_KEY, KEY_WAKEUP, 1);
						input_sync(ddata->input);
						input_event(ddata->input, EV_KEY, KEY_WAKEUP, 0);
						input_sync(ddata->input);
						break;
					}
				}
				
			}


希望解决这个问题网友,能联系我,谢谢  [email protected]

继续更新,瑞芯微的专家已经回复,因为android在深度睡眠的状态CPU频率会调整到很低,导致收不到完整的用户码。

此问题在很多板子上都有出现,要彻底解决就只有改遥控器的power键,每次按的时候发送两次一样的波,等于是按了两次power键,程序按一次处理。

你可能感兴趣的:(NEC 遥控器 源码处理流程分析)