Linux 按鍵輸入實驗
1、新增 pinctrl 節點
首先修改在裝置樹裡面新增關於按鍵的節點。I.MX6U-ALPHA 開發板上的 KEY 使用了 UART1_CTS_B 這個 PIN,開啟 imx6ull-alientekemmc.dts,在 iomuxc 節點的 imx6ul-evk 子節點下建立一個名為“pinctrl_key”的子節點,節點內容如下所示:
pinctrl_key: keygrp {
fsl,pins = <
MX6UL_PAD_UART1_CTS_B__GPIO1_IO18 0xF080 /* KEY0 */
>;
};
2、 新增key裝置節點:
在根節點“/”下建立 KEY 節點,節點名為“key”,節點內容如下:
key {
#address-cells = <1>;
#size-cells = <1>;
compatible = "atkalpha-key";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_key>;
key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>; /* KEY0 */
interrupt-parent = <&gpio1>;
interrupts = <18 IRQ_TYPE_EDGE_BOTH>; /* FALLING RISING */
status = "okay";
};
pinctrl-0 屬性設定 KEY 所使用的 PIN 對應的 pinctrl 節點。key-gpio 屬性指定了 KEY 所使用的 GPIO。
3、檢查 PIN 是否被其他外設使用
在本章實驗中蜂鳴器使用的 PIN 為 UART1_CTS_B,因此先檢查 PIN 為 UART1_CTS_B 這個 PIN 有沒有被其他的 pinctrl 節點使用,如果有使用的話就要遮蔽掉,然後再檢查 GPIO1_IO18這個 GPIO 有沒有被其他外設使用,如果有的話也要遮蔽掉。
按鍵驅動和 LED 驅動原理上來講基本都是一樣的,都是操作 GPIO,只不過一個是讀取GPIO 的高低電平,一個是從 GPIO 輸出高低電平。本章我們實現按鍵輸入,在驅動程式中使用一個整形變數來表示按鍵值,應用程式透過 read 函式來讀取按鍵值,判斷按鍵有沒有按下。
按鍵驅動程式為:
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define KEY_CNT 1 /* 裝置號個數 */
#define KEY_NAME "key" /* 名字 */
/* 定義按鍵值 */
#define KEY0VALUE 0XF0 /* 按鍵值 */
#define INVAKEY 0X00 /* 無效的按鍵值 */
struct key_dev
{
dev_t devid; /* 裝置號 */
struct cdev cdev; /* cdev */
struct class *class; /* 類 */
struct device *device; /* 裝置 */
int major; /* 主裝置號 */
int minor; /* 次裝置號 */
struct device_node *nd; /* 裝置節點 */
int key_gpio; /* led 所使用的 GPIO 編號 */
atomic_t keyvalue; /* 原子變數 */
};
struct key_dev keydev; /* led 裝置 */
static int keyio_init(void)
{
keydev.nd = of_find_node_by_path("/key");
if (keydev.nd == NULL)
{
return -EINVAL;
}
keydev.key_gpio = of_get_named_gpio(keydev.nd, "key-gpio", 0);
if (keydev.key_gpio < 0)
{
printk("can't get key0\r\n");
return -EINVAL;
}
printk("key_gpio=%d\r\n", keydev.key_gpio);
/* 初始化 key 所使用的 IO */
gpio_request(keydev.key_gpio, "key0"); /* 請求 IO */
gpio_direction_input(keydev.key_gpio); /* 設定為輸入 */
return 0;
}
static int key_open(struct inode *inode, struct file *filp)
{
int ret = 0;
filp->private_data = &keydev; /* 設定私有資料 */
ret = keyio_init(); /* 初始化按鍵 IO */
if (ret < 0)
{
return ret;
}
return 0;
}
/*
* @description : 從裝置讀取資料
* @param – filp : 要開啟的裝置檔案(檔案描述符)
* @param - buf : 返回給使用者空間的資料緩衝區
* @param - cnt : 要讀取的資料長度
* @param – offt : 相對於檔案首地址的偏移
* @return : 讀取的位元組數,如果為負值,表示讀取失敗
*/
static ssize_t key_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
int ret = 0;
unsigned char value;
struct key_dev *dev = filp->private_data;
if(gpio_get_value(dev->key_gpio) == 0)
{
while(!gpio_get_value(dev->key_gpio));
atomic_set(&dev->keyvalue,KEY0VALUE);
}
else{
atomic_set(&dev->keyvalue,INVAKEY);
}
value = atomic_read(&dev->keyvalue);
ret = copy_to_user(buf,&value,sizeof(value));
return ret;
}
/* 裝置操作函式 */
static struct file_operations key_fops = {
.owner = THIS_MODULE,
.open = key_open,
.read = key_read,
};
static int __init mykey_init(void)
{
int ret = 0;
atomic_set(&keydev.keyvalue, INVAKEY);
/*1、建立裝置號*/
if (keydev.major)
{
keydev.devid = MKDEV(keydev.major, 0);
register_chrdev_region(keydev.devid, KEY_CNT, KEY_NAME);
}
else
{
alloc_chrdev_region(&keydev.devid, 0, KEY_CNT, KEY_NAME);
keydev.major = MAJOR(keydev.devid);
keydev.minor = MINOR(keydev.devid);
}
printk("newcheled major: %d minor: %d", keydev.major, keydev.minor);
keydev.cdev.owner = THIS_MODULE;
cdev_init(&keydev.cdev, &key_fops);
cdev_add(&keydev.cdev, keydev.devid, KEY_CNT);
keydev.class = class_create(THIS_MODULE, KEY_NAME);
if (IS_ERR(keydev.class))
{
return PTR_ERR(keydev.class);
}
keydev.device = device_create(keydev.class, NULL, keydev.devid, NULL, KEY_NAME);
if (IS_ERR(keydev.device))
{
return PTR_ERR(keydev.device);
}
return 0;
}
static void __exit mykey_exit(void)
{
/* 登出字元裝置驅動 */
gpio_free(keydev.key_gpio);
cdev_del(&keydev.cdev); /* 刪除 cdev */
unregister_chrdev_region(keydev.devid,KEY_CNT);
device_destroy(keydev.class, keydev.devid);
class_destroy(keydev.class);
}
module_init(mykey_init);
module_exit(mykey_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wyw");
其實按鍵與LED相似的就是一個是讀一個是寫。按鍵則是讀取高低電平。也就是檢測這個GPIO編號時候,利用原子變數寫入key->value這個值。
key_open 函式透過呼叫 keyio_init 函式來始化按鍵所使用的 IO,應用程式每次開啟按鍵驅動檔案的時候都會初始化一次按鍵 IO。key_read 函式,應用程式透過 read 函式讀取按鍵值的時候此函式就會執行。讀取按鍵 IO 的電平,如果為 0 的話就表示按鍵按下了,如果按鍵按下的話就等待按鍵釋放。按鍵釋放以後標記按鍵值為 KEY0VALUE。
測試App程式為:
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#define KEY0VALUE 0XF0
#define INVAKEY 0X00
int main(int argc, char *argv[])
{
int fd, retvalue;
char *filename;
unsigned char keyvalue;
if (argc != 2)
{
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR);
if (fd < 0)
{
printf("file %s open failed!\r\n", argv[1]);
return -1;
}
while (1)
{
read(fd, &keyvalue, sizeof(keyvalue));
if(keyvalue == KEY0VALUE)
{
printf("KEY0 Press, value = %#X\r\n", keyvalue);/* 按下 */
}
}
printf("App running finished!");
retvalue = close(fd); /* 關閉檔案 */
if (retvalue < 0)
{
printf("file %s close failed!\r\n", argv[1]);
return -1;
}
return 0;
}
現在就是按鍵按下終端輸出如下圖所示的現象: