1. 程式人生 > >NEC 遙控器 原始碼處理流程分析

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 遙控器 原始碼處理流程分析

NEC 協議的遙控器 程式碼位置在 \kernel\drivers\input\remotectl\rkxx_remotectl.c 關於NEC協議的東西主要來源於網上,出處太多,也就不一一列舉了,感謝提供資源的各位網友。 根據我個人的理解,NEC遙控器工作流程應該是這樣

Spark修煉之道(高階篇)——Spark原始碼閱讀:第十二節 Spark SQL 處理流程分析

作者:周志湖 下面的程式碼演示了通過Case Class進行表Schema定義的例子: // sc is an existing SparkContext. val sqlContext = new org.apache.spark.sql.SQLConte

大資料入門環境搭建整理、大資料入門系列教程合集、大資料生態圈技術整理彙總、大資料常見錯誤合集、大資料的離線和實時資料處理流程分析

本篇文章主要整理了筆者學習大資料時整理的一些文章,文章是從環境搭建到整個大資料生態圈的常用技術整理,環境希望可以幫助到剛學習大資料到童鞋,大家在學習過程中有問題可以隨時評論回覆! 大資料生態圈涉及技術: Hadoop、MapReduce、HDFS、Hive、Hbase、Spark、Scala

Spark Join處理流程分析

為了更好的分析Spark Join處理流程,我們選擇具有Shuffle操作的示例來進行說明,這比沒有Shuffle操作的處理流程要複雜一些。本文主要通過實現一個Join操作的Spark程式,提交執行該程式,並通過Spark UI上的各種執行資訊來討論Spark Join處理流程。 Spa

SpringBoot原始碼---啟動流程分析

 既然看到這篇文章了,那麼預設讀者已經很熟悉SpringBoot的使用的。  第一步,啟動一個SpringBoot應用: @ComponentScan(basePackages = {""}) @MapperScan("") @SpringBootApplication

Servlet處理流程分析

當Servlet被裝載和例項化過後,容器會首先呼叫init()方法對Servlet進行初始化,只有在init()方法呼叫成功後,Servlet才能處於服務狀態接收客戶端的請求並進行處理.在整個Servlet的宣告週期中init()方法只會被呼叫一次 當Servlet執行完初始化操作之後就會呼叫service

Spring MVC請求處理流程分析

一、簡介 Spring MVC框架在工作中經常用到,配置簡單,使用起來也很方便,很多書籍和部落格都有介紹其處理流程,但是,對於

rest_framework框架之認證功能的使用和原始碼實現流程分析

rest_framework框架之認證的使用和原始碼實現流程分析 一、認證功能的原始碼流程 建立檢視函式 Note 建立檢視函式後,前端發起請求,url分配路由,執行檢視類,檢視類中執行對應方法必須經過dispatch()即排程方法 from rest_framework.views import

EGADS框架處理流程分析

最近在搞異常檢測相關的工作,因此調研了業界常用的異常檢測系統。通過查閱相關資料,發現業界對雅虎開源的EGADS系統評價比較高,其git專案已有980個star。這周閱讀了專案的原始碼,梳理了系統框架的基本處理流程,整理成這篇文章。現分享給大家,希望對想了解EGADS系統工作原理的同學有所幫助。 ## 1.

SpringMVC原始碼學習之request處理流程 springMVC原始碼學習地址 springMVC原始碼學習之addFlashAttribute原始碼分析 java reflect反射呼叫方法invoke

目的:為看原始碼提供呼叫地圖,最長呼叫邏輯深度為8層,反正我是springMVC原始碼學習地址看了兩週才理出來的。 1.處理流程(版本為4.3.18) 入口為spring-webmvc-4.3.18.RELEASE.jar中org.springframework.web.servlet.Dispatche

Android8.0 TV焦點處理流程原始碼分析

前言 現在基本上都是觸控式螢幕手機,導致很多開發者對焦點並沒有深刻認識,但仍然存在一些非觸屏手機,還有Android TV平臺都與焦點息息相關,這裡就總結下自己看過多篇文章和自己的實踐經驗。先看兩張焦點圖。 焦點基礎及原始碼分析通過兩篇來帶大家深入認識。

hadoop原始碼解析之hdfs寫資料全流程分析---客戶端處理

DFSOutputStream介紹 DFSOutputStream概況介紹 這一節我們介紹hdfs寫資料過程中,客戶端的處理部分。客戶端的處理主要是用到了DFSOutputStream物件,從名字我們可以看出,這個是對dfs檔案系統輸出流的一個

WebRTC原始碼分析三:視訊處理流程

 文字介紹視訊的處理流程。圖1中顯示了兩路視訊會話視訊訊號流過程。 圖1 視訊流程示意圖 以一路視訊會話為例,主要分為以下幾個執行緒: 1)視訊源產生執行緒:Camera生產視訊畫面,封裝成視訊幀,以一定幀率投遞到下一個模組。; 2)採集執行緒:由Capturer負責採集視訊幀,並對視訊幀進行一定處理,如

twemproxy原始碼分析之四:處理流程

很讚的註釋: * nc_connection.[ch] * Connection (struct conn) * + + + * |

小夥伴們的ceph原始碼分析三——monitor訊息處理流程

筆者在讀程式碼初期非常想理清楚的就是ceph這麼個系統在服務端與客戶端是怎麼響應與發起請求的。 本人主要負責monitor部分,而且追了一會cephx認證的程式碼,所以拿這塊舉例,後續osd部分主要是對同事分享的學習。 本篇會講到src/mon/monitor.cc中 c

Mybatis工作機制原始碼分析—一次insert請求處理流程

      本文從原始碼分析的角度分析Mybatis一次insert請求處理流程。 insert整體處理流程 時序圖 相關原始碼 /** SqlSessionTemplate.java */ public int insert(String statement, Obj

【MyBatis源碼分析】insert方法、update方法、delete方法處理流程(上篇)

times database connect 環境 enable clas 它的 java對象 ace 打開一個會話Session 前文分析了MyBatis將配置文件轉換為Java對象的流程,本文開始分析一下insert方法、update方法、delete方法處理的流程,至

線上服務mcelog負載異常分析處理流程

線上服務mcelog負載異常分析處理流程一、問題概述:Nginx服務器,HP,有冗余,其中一臺服務器mcelog負載比較高,日誌秒級別,已經影響了此服務器業務。tail -f /var/log/mcelog#註意看此信息是不斷循環,註意看Transaction:Memory scrubbing error M

SpringMVC源碼分析-400異常處理流程及解決方法

defining count ror error this 設計模式 進入 如何 16px 本文設計SpringMVC異常處理體系源碼分析,SpringMVC異常處理相關類的設計模式,實際工作中異常處理的實踐。 問題場景 假設我們的SpringMVC應用中有如下控制器: 代

Oracle_RAC宕機和hang分析處理流程

reply 事件分析 宕機 等待 nal 故障 zab ali 手動 目的:分享一下公司的db故障處理流程,主要是思想。事件描述及影響:2018年9月30日04:43點,zabbix告警odsdb2數據庫疑似宕機,機房值班人員通過堡壘機無法登錄數據庫服務器,從其他機器也無法