按键驱动和LED驱动原理上来讲基本都是一样的,都是操作GPIO,只不过一个是读取GPIO的高低电平,一个是从GPIO输出高低电平给LED。
本实验使用的开发板是IMX6DL,实验中使用的按键是通过使用下图中P1的EIM_A20和VCC3.3V两个引脚实现的,P1是开发板上60脚的BUS。
同样地,要确保复用为按键GPIO的EIM_A20脚没有被其他设备复用,在设备树文件imx6dl-c-sabresd.dts中添加pinctrl引脚信息如下。
pinctrl_key:key_bus{
fsl,pins = <
MX6QDL_PAD_EIM_A20__GPIO2_IO18 0x80000000 //将EIM_A20复用为GPIO2_IO18
>;
};
然后在设备树文件imx6dl-c-sabresd.dts根节点下添加一个key节点,其内容如下。
key{
#address-cells = <1>;
#size-cells = <1>;
pinctrl-names = "default";
compatible = "gpio_bus_key";
pinctrl-0 = <&pinctrl_key>; //设置KEY所使用的PIN对应的pinctrl节点
key-gpio = <&gpio2 18 GPIO_ACTIVE_LOW>; //P1,即开发板上bus的EIM_A20号口复用
status = "okay";
};
上面这些内容的添加要根据自己的开发板情况而定,添加完成后设备树就修改完成了,然后编译一下设备树,将最新的dtb文件拷贝到/tftp文件夹,然后在开发板启动时,最新编译的设备树文件就加载到开发板中了。
本实验中的按键驱动源代码如下,该代码来自正点原子,本人只做了一点改动!
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define KEY_CNT 1 /* 设备号个数 */
#define KEY_NAME "gpio_key" /* 在/dev目录下生成的设备名字*/
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; /* key所使用的GPIO编号*/
atomic_t keyvalue; /*按键值 原子操作,互斥访问*/
};
struct key_dev keydev; /* key设备 */
static int key_open(struct inode *inode, struct file *filp)
{
filp->private_data = &keydev; /* 设置私有数据 */
return 0;
}
static ssize_t key_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
int ret = 0;
char kbuf[1]; //定义为char类型
struct key_dev *dev = filp->private_data;
if (gpio_get_value(dev->key_gpio) == 0) /* key按下 */
{
while(!gpio_get_value(dev->key_gpio)); /* 等待按键释放 */
atomic_set(&dev->keyvalue,1);
}
else
atomic_set(&dev->keyvalue,0);
kbuf[0] = atomic_read(&dev->keyvalue); //读出按键值
ret = copy_to_user(buf,kbuf,sizeof(kbuf));
printk("Kernel: read value = %d\n",kbuf[0]);
return ret;
}
static struct file_operations key_fops = {
.owner = THIS_MODULE,
.open = key_open,
.read = key_read
};
const struct of_device_id of_match_table_key[] = {
{.compatible = "gpio_bus_key"}, //与设备树中的compatible属性匹配
{}
};
struct platform_driver dts_device = {
.driver = {
.owner = THIS_MODULE,
.name = "keygpio",
.of_match_table = of_match_table_key
}
};
static int __init gpio_key_init(void)
{
atomic_set(&keydev.keyvalue,0);
keydev.nd = of_find_node_by_path("/key");
if (keydev.nd == NULL)
return -EINVAL;
printk("Find node key!\n");
keydev.key_gpio = of_get_named_gpio(keydev.nd,"key-gpio",0);
if (keydev.key_gpio < 0)
{
printk("of_get_named_gpio failed!\r\n");
return -EINVAL;
}
printk("key_gpio = %d\r\n", keydev.key_gpio);
gpio_request(keydev.key_gpio,KEY_NAME); //申请gpio
gpio_direction_input(keydev.key_gpio); //将gpio设置为输入
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("gpiokey major=%d,minor=%d\r\n",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);
platform_driver_register(&dts_device);
return 0;
}
static void __exit gpio_key_exit(void)
{
gpio_free(keydev.key_gpio);
cdev_del(&keydev.cdev);
unregister_chrdev_region(keydev.devid,KEY_CNT);
device_destroy(keydev.class, keydev.devid);
class_destroy(keydev.class);
platform_driver_unregister(&dts_device);
printk("driver exit!\n");
}
module_init(gpio_key_init);
module_exit(gpio_key_exit);
MODULE_LICENSE("GPL");
代码中定义的变量既可以分开逐个定义,像LED驱动实验一样,这样代码看着简单;当然也可以像上面这样将代码中用到的变量都定义在一个结构体里面,这样代码看着更具象。
之后编写Makefile文件,将上面的代码文件编译成驱动模块,Makefile的写法和前面实验的一样,这里不在赘述。
下面的文件就是测试文件,该文件写好后通过交叉编译器编译,然后和驱动文件一起发到开发板上验证。
#include "stdio.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "unistd.h"
int main(int argc, char *argv[])
{
int fd,ret;
int count = 0;
char buf[1];
fd = open("/dev/gpio_key", O_RDWR);
if(fd < 0)
{
perror("open error!\n");
return fd;
}
printf("open device gpio_key!\n");
while(1)
{
ret = read(fd, &buf, sizeof(buf)); //循环读取按键值数据
if(ret < 0)
{
printf("read file failed!");
break;
}
else
{
if(buf[0] == 1)
{
printf("User: key up, value is %d\r\n",buf[0]);
count++;
}
else
{
printf("User: key down, value is %d\r\n",buf[0]);
count++;
}
if(count == 10) //读取十次键值就退出循环
break;
sleep(1);
}
}
close(fd);
return 0;
}
注意上面的测试文件中,为了不让其一直在循环中出不来,我这里设置了按键按下10次后自动退出,因为在测试文件运行后,按Ctrl+C是不管用的,而且我也设置了按键的键值读取至少间隔1秒。
最终在开发板上的运行结果如下图所示。
按键每按下一次,就打印1;按键一直按下不放,就打印0,而且每隔1秒打印一次。
本文参考文档:
I.MX6U嵌入式Linux驱动开发指南V1.5——正点原子