關鍵詞: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\n");
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\n",
pressed ? "pressed" : "released", row, col);
//(1)、得到按鍵在矩陣中的位置;
val = MATRIX_SCAN_CODE(row, col, keypad->row_shift);
printk("key %s, row: %d, col: %d\n",pressed ? "pressed" : "released", row, col);
printk("test by xu_bin for val = %d,key = %d\n",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\n",pressed ? "pressed" : "released", row, col);
printk("test by xu_bin for val =%d,key = %d\n",val,keypad->keycodes[val]);
(2)、上報鍵值keypad->keycodes[val],這個值是對於我們這個驅動來說的最終值;
(3)、input上報後同步,這個和input子系統相關。
這樣就完成了驅動部分的上報。