zoukankan      html  css  js  c++  java
  • 【转】android 物理按键

    关键词:android   按键  矩阵按键 AD按键 

    平台信息:

    内核:linux2.6/linux3.0

    系统:android/android4.0

    平台:S5PV310(samsung exynos4210)

    作者:xubin341719(欢迎转载,请注明作者)

    一、硬件部分:

    1、矩阵按键、IO按键、AD按键

        这个知识相对来说比较简单,不过上次真有一个网友不太清楚这个。所以这个基础部分我们在这里也说一下。

    (1)、矩阵按键


    记得上大学时学单片机时,这个矩阵按键还是个重点呢,上面的图还是AT89S52的片子,工作原理比较简单,通过行、列来确定是那个按键按下,比如说上图标号为1的键按下,IO(P1.7,P1.3)有电平变化,程序可以通过这里来判断是那一个键按下的,同理标号为2的按键按下IO(P1.4,P1.0)有电平变化。

        这样做程序上要从两个IO来判断是那个键按下,多了一个步骤,但是在硬件上有一个优势,就是如果按键比较多的时候比较节省IO口,比如说上面4x4 = 16,8个IO可以做16个按键,8x8=64,16个IO可以做64个按键。

    优点:可以用少的IO来做多个按键,判断按键比较准确;

    缺点:程序上相对IO按键来说多了一步。

    (2)、IO按键

            这个就比较简单了,用一个IO口的高低电平来判断按键是否按下。

    优点:程序、硬件电路都比较简单,判断按键比较准确;

    缺点:IO有限、按键多时不太合适。比如矩阵按键16个IO可以表示64个按键,IO的话只有16个。

    (3)、AD按键

            这个在之前在做电视的时候用的比较多一点。

            AD按键就是通过一个ADC接口,如下图所示,给一个VCC电压,比如说S1接地时AD接口得到的模拟电压值为ADC=0;当S2按下时,ADC= VCC/(R1+R2)*R2;这样就可以得到不同的ADC值,程序中在这里判断是那个按键按下。

    优点:程序、硬件电路都比较简单,一个IO可以做多个按键;

    缺点:AD按键有时候判断不准确,所以在程序中要多加检测AD值的次数。

    2、S5PV310的矩阵按键

    硬件原理图如下:

    硬件接口说明:vol+,vol-,back,home,menu为1*5的矩阵键盘,芯片接口信息如下:

    XGNSS_GPIO_3/KP_COL3

    XGNSS_GPIO_4/KP_COL4

    XGNSS_GPIO_5/KP_COL5

    XGNSS_GPIO_6/KP_COL6

    XGNSS_GPIO_7/KP_COL7

    XEINT17/KP_ROW1

    我们这里1*5= 5也没有节省多少IO呀?情况是这样的,我们的原理图是从三星开发板上参考过来的,开发板上按键本来多一点,可是我们用不了那么多,人家那样做比较合理。可是我们“偷懒”,硬件上不用改,软件上也不用改,从这一点也可以看出我们国内做技术这个行业的有点……不太深入呀,整天老板在催,可是我们在细节上做不太好呀。三星在IO矩阵也有专用接口,所以就“奢侈”一次,用1*5的矩阵来实现5个按键。

    3、S5PV310的矩阵按键接口

    看一下芯片上的专用接口,如下图,全用的话有点多。



    关于专用接口的寄存器,这些寄存器我们后面要用得到的,按键的行、列信息会在这里面暂存的。

    以S5PV310为例,驱动代码:samsung-keypad.c

    软件部分:

    总体流程图如下,这个是在触摸屏基础上改过来的,感觉流程都是这个样子的。中断触发,中断处理。

    一、矩阵键行、列设定,和上报键值设定

    在android-kernel-samsung-dev/arch/arm/mach-exynos/mach-smdkv310.c中

    static uint32_t smdkv310_keymap[] __initdata = {
        /* KEY(row, col, keycode) */
        KEY(0, 3, KEY_1), KEY(0, 4, KEY_2), KEY(0, 5, KEY_3),
        KEY(0, 6, KEY_4), KEY(0, 7, KEY_5),
        KEY(1, 3, KEY_A), KEY(1, 4, KEY_C), KEY(1, 5, KEY_E),
        KEY(1, 6, KEY_B), KEY(1, 7, KEY_D)//(1)、键值初始化;
    };
    
    static struct matrix_keymap_data smdkv310_keymap_data __initdata = {
        .keymap        = smdkv310_keymap,
        .keymap_size    = ARRAY_SIZE(smdkv310_keymap),
    };
    static struct samsung_keypad_platdata smdkv310_keypad_data __initdata = {
        .keymap_data    = &smdkv310_keymap_data,
        .rows        = 2,         //(2)、行、列设定,8行、2列,其实我们只用了5行、1列;
        .cols        = 8,
    };
    static void __init smdkv310_machine_init(void)
    {
        samsung_keypad_set_platdata(&smdkv310_keypad_data); //(3)、平台设备初始化;
    }

    (1)、KEY(row, col,keycode)

    KEY这个宏在android-kernel-samsung-dev/include/linux/input/Matrix_keypad.h中实现:

    #define MATRIX_MAX_ROWS        32
    #define MATRIX_MAX_COLS            32
    #define KEY(row, col, val)    ((((row) & (MATRIX_MAX_ROWS - 1)) << 24) |
                     (((col) & (MATRIX_MAX_COLS - 1)) << 16) |
                     ((val) & 0xffff))

    keycode的值在android-kernel-samsung-dev/include/linux/input.h中有定义,如下:

    #define KEY_RESERVED        0
    #define KEY_ESC            1
    #define KEY_1            2
    #define KEY_2            3
    #define KEY_3            4
    #define KEY_4            5
    #define KEY_5            6
    #define KEY_6            7
    #define KEY_7            8
    #define KEY_8            9
    #define KEY_9            10
    #define KEY_0            11
    #define KEY_MINUS        12
    #define KEY_EQUAL        13
    #define KEY_BACKSPACE        14
    #define KEY_TAB            15
    #define KEY_Q            16
    #define KEY_W            17
    #define KEY_E            18
    #define KEY_R            19
    #define KEY_T            20
    #define KEY_Y            21
    #define KEY_U            22

    (2)、行列设定;

        .rows       = 2,       
        .cols       = 8,

    (3)、平台设备初始化;

    samsung_keypad_set_platdata(&smdkv310_keypad_data)。

    二、上面设定的keycode键值和上层相对应

    4.0.3_r1/device/samsung/smdkv310/samsung-keypad.kl中

    key 2     DPAD_UP               WAKE_DROPPED
    key 3     DPAD_CENTER           WAKE_DROPPED
    key 4     DPAD_DOWN             WAKE_DROPPED
    key 5     DPAD_RIGHT            WAKE_DROPPED
    key 6     DPAD_LEFT             WAKE_DROPPED
    key 18    VOLUME_DOWN       WAKE
    key 30    HOME                      WAKE_DROPPED
    key 32    MENU                      WAKE_DROPPED
    key 46    VOLUME_UP             WAKE
    key 48    BACK                      WAKE_DROPPED
    key 10    POWER                     WAKE

    总体对应图:

    以KEY_A为例,KEY_A 30最终和上层的keypad.kl中的30 HOME相对应




    三、矩阵键盘驱动程序分析

    android-kernel-samsung-dev/drivers/input/keyboard/samsung-keypad.c

    1、probe函数分析:

    static int __devinit samsung_keypad_probe(struct platform_device *pdev)
    {
        const struct samsung_keypad_platdata *pdata;
        const struct matrix_keymap_data *keymap_data;
        struct samsung_keypad *keypad;
        struct resource *res;
        struct input_dev *input_dev;
        unsigned int row_shift;
        unsigned int keymap_size;
        int error;
        ………………
        keymap_size = (pdata->rows << row_shift) * sizeof(keypad->keycodes[0]);
    
        keypad = kzalloc(sizeof(*keypad) + keymap_size, GFP_KERNEL);
        input_dev = input_allocate_device();
        if (!keypad || !input_dev) {
            error = -ENOMEM;
            goto err_free_mem;
        }
    
        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        if (!res) {
            error = -ENODEV;
            goto err_free_mem;
        }
    
        keypad->base = ioremap(res->start, resource_size(res));
        if (!keypad->base) {
            error = -EBUSY;
            goto err_free_mem;
        }
        …………
    //(1)、input参数初始化;
        keypad->input_dev = input_dev;
        keypad->row_shift = row_shift;
        keypad->rows = pdata->rows;
        keypad->cols = pdata->cols;
        init_waitqueue_head(&keypad->wait);
    
        input_dev->name = pdev->name;
        input_dev->id.bustype = BUS_HOST;
        input_dev->dev.parent = &pdev->dev;
        input_set_drvdata(input_dev, keypad);
    //(2)、打开、关闭函数;
        input_dev->open = samsung_keypad_open;
        input_dev->close = samsung_keypad_close;
    
        input_dev->evbit[0] = BIT_MASK(EV_KEY);
        if (!pdata->no_autorepeat)
            input_dev->evbit[0] |= BIT_MASK(EV_REP);
    
        input_set_capability(input_dev, EV_MSC, MSC_SCAN);
    
        input_dev->keycode = keypad->keycodes;
        input_dev->keycodesize = sizeof(keypad->keycodes[0]);
        input_dev->keycodemax = pdata->rows << row_shift;
    
        matrix_keypad_build_keymap(keymap_data, row_shift,
                input_dev->keycode, input_dev->keybit);
    
        keypad->irq = platform_get_irq(pdev, 0);
        if (keypad->irq < 0) {
            error = keypad->irq;
            goto err_put_clk;
        }
    //(3)、中断函数注册; 
        error = request_threaded_irq(keypad->irq, NULL, samsung_keypad_irq,
                IRQF_ONESHOT, dev_name(&pdev->dev), keypad);
        if (error) {
            dev_err(&pdev->dev, "failed to register keypad interrupt
    ");
            goto err_put_clk;
        }
    //(4)、input驱动注册。
        error = input_register_device(keypad->input_dev);
        if (error)
            goto err_free_irq;
    
        device_init_wakeup(&pdev->dev, pdata->wakeup);
        platform_set_drvdata(pdev, keypad);
        return 0;
    
    ………………
    }

    (1)、input参数初始化;

    (2)、打开、关闭函数;

    input_dev->open = samsung_keypad_open;
    static int samsung_keypad_open(struct input_dev *input_dev)
    {
        struct samsung_keypad *keypad = input_get_drvdata(input_dev);
        samsung_keypad_start(keypad);
        return 0;
    }
    其实open函数调用samsung_keypad_start()函数,对按键的寄存器一些操作,如下面寄存器列表中的。
    static void samsung_keypad_start(struct samsung_keypad *keypad)
    {
        unsigned int val;
        /* Tell IRQ thread that it may poll the device. */
        keypad->stopped = false;
        clk_enable(keypad->clk);
        /* Enable interrupt bits. */
        val = readl(keypad->base + SAMSUNG_KEYIFCON);
        val |= SAMSUNG_KEYIFCON_INT_F_EN | SAMSUNG_KEYIFCON_INT_R_EN;
        writel(val, keypad->base + SAMSUNG_KEYIFCON);
        /* KEYIFCOL reg clear. */
        writel(0, keypad->base + SAMSUNG_KEYIFCOL);
    }

    (3)、中断函数注册;

        error = request_threaded_irq(keypad->irq,NULL, samsung_keypad_irq,IRQF_ONESHOT, dev_name(&pdev->dev), keypad);

    request_threaded_irq这个函数也许我们比较陌生,可是看下下面一个函数也许就不难理解了:

    static inline int __must_check
    request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
            const char *name, void *dev)
    {
        return request_threaded_irq(irq, handler, NULL, flags, name, dev);
    }

    这个函数跟中断的作用是一样的,keypad->irq= platform_get_irq(pdev, 0);于中段号,当有按键按下时,会跳到中断函数,samsung_keypad_irq中;

     (4)、input驱动注册,input驱动比较重要,触摸屏、按键、gsensor、battery等都是通过input子系统上报的。

     2、中断函数: samsung_keypad_irq分析,当有按键按下时,调用这个函数

    static irqreturn_t samsung_keypad_irq(int irq, void *dev_id)
    {
        struct samsung_keypad *keypad = dev_id;
    
        unsigned int row_state[SAMSUNG_MAX_COLS];
        unsigned int val;
        bool key_down;
        do {
            val = readl(keypad->base + SAMSUNG_KEYIFSTSCLR);
            /* Clear interrupt. */
    //(1)、清除中断;
            writel(~0x0, keypad->base + SAMSUNG_KEYIFSTSCLR); 
    //(2)、扫描行列值,写入寄存器;
        samsung_keypad_scan(keypad, row_state);
    //(3)、键值上报,这是函数的主要部分了;
            key_down = samsung_keypad_report(keypad, row_state);
    //(4)、延时去抖动; 
            if (key_down)
                wait_event_timeout(keypad->wait, keypad->stopped,
                           msecs_to_jiffies(50));
        } while (key_down && !keypad->stopped);
        return IRQ_HANDLED;
    }

    (1)、清除中断;

    (2)、扫描行列值,写入寄存器(后面分析);

    (3)、键值上报,这是函数的主要部分了(后面分析);

    (4)、延时去抖动,如果有按键按下,有一个段时间的延时,看是否真正有按键,这就是所说的去抖动;

    3、当按键按下时,行列值的扫描函数samsung_keypad_scan执行,写入相应行列寄存器

    上图我们知道,对于矩阵键盘,主控有专门的接口,也有相应的寄存器,

    static void samsung_keypad_scan(struct samsung_keypad *keypad,
                    unsigned int *row_state)
    {
        struct device *dev = keypad->input_dev->dev.parent;
        unsigned int col;
        unsigned int val;
        for (col = 0; col < keypad->cols; col++) {
            if (samsung_keypad_is_s5pv210(dev)) {
                val = S5PV210_KEYIFCOLEN_MASK;
                val &= ~(1 << col) << 8;
            } else {
                val = SAMSUNG_KEYIFCOL_MASK;
                val &= ~(1 << col);
            }
            writel(val, keypad->base + SAMSUNG_KEYIFCOL);
            mdelay(1);
            val = readl(keypad->base + SAMSUNG_KEYIFROW);
            row_state[col] = ~val & ((1 << keypad->rows) - 1);
        }
        /* KEYIFCOL reg clear */
        writel(0, keypad->base + SAMSUNG_KEYIFCOL);
    }

    4、通过扫描键值写入相应寄存器,然后通过

    static bool samsung_keypad_report(struct samsung_keypad *keypad,
                      unsigned int *row_state)
    {
        struct input_dev *input_dev = keypad->input_dev;
        unsigned int changed;
        unsigned int pressed;
        unsigned int key_down = 0;
        unsigned int val;
        unsigned int col, row;
    
        for (col = 0; col < keypad->cols; col++) {
            changed = row_state[col] ^ keypad->row_state[col];
            key_down |= row_state[col];
            if (!changed)
                continue;
            for (row = 0; row < keypad->rows; row++) {
                if (!(changed & (1 << row)))
                    continue;
                pressed = row_state[col] & (1 << row);
                dev_dbg(&keypad->input_dev->dev,
                    "key %s, row: %d, col: %d
    ",
                    pressed ? "pressed" : "released", row, col);
    //(1)、得到按键在矩阵中的位置;
                val = MATRIX_SCAN_CODE(row, col, keypad->row_shift);
    printk("key %s, row: %d, col: %d
    ",pressed ? "pressed" : "released", row, col);
    printk("test by xu_bin for val = %d,key = %d
    ",val,keypad->keycodes[val]);
                input_event(input_dev, EV_MSC, MSC_SCAN, val);
    //(2)、上报键值keypad->keycodes[val];
                input_report_key(input_dev,
                        keypad->keycodes[val], pressed);
            }
    //(3)、input上报后同步; 
            input_sync(keypad->input_dev);
        }
        memcpy(keypad->row_state, row_state, sizeof(keypad->row_state));
        return key_down;
    }

    (1)、#defineMATRIX_SCAN_CODE(row, col, row_shift) (((row)<< (row_shift)) + (col))

    row_shift = 3

    如:row = 1; col = 6; row_shift = 3

    val = MATRIX_SCAN_CODE(row, col,keypad->row_shift) = ((1)<<(3)+(6)) = 14;

    就相当于:(1,6)这个数组里面的值:48

    printk("key %s, row: %d, col:%d ",pressed ? "pressed" : "released", row, col);

    printk("test by xu_bin for val =%d,key = %d ",val,keypad->keycodes[val]);


    (2)、上报键值keypad->keycodes[val],这个值是对于我们这个驱动来说的最终值;

    (3)、input上报后同步,这个和input子系统相关。 

    这样就完成了驱动部分的上报。

  • 相关阅读:
    BZOJ 3251 树上三角形:LCA【构成三角形的结论】
    BZOJ 2442 [Usaco2011 Open]修剪草坪:单调队列优化dp
    2018湖南省赛选拔
    扩展BSGS-传送门
    倒数第N个字符串
    HDU-6070 Dirt Ratio(二分+线段树+分数规划)
    第一场多校
    HDU5923-Prediction-有继承味道的并查集
    POJ2516费用流
    POJ3436:ACM Computer Factory-最大流
  • 原文地址:https://www.cnblogs.com/cslunatic/p/3637795.html
Copyright © 2011-2022 走看看