一、前言
按键输入检测方式由硬件决定了软件如何开发,一开始我设计的硬件没有考虑到这点,也正是因为这,迫使我不得不学习设备树的使用,因为我的硬件按键输入检测是按键按下经过电阻到地的方式,而不是上拉的模式,所以必须设置GPIO的引脚为上拉模式,但是普通的引脚驱动只能设置输入和输出模式,再有就是高低电平,如果想要设置上拉模式,必须使用设备树的方式,所以这边文章的主要目的也是介绍和分享一下如何使用设备树配置GPIO引脚的模式,和简单的按键输入应用介绍。全部源码下载在这里
二、环境
宿主机:window10,Ubuntu16.04
目标及:a40i,linux3.10
三、正文
本文采用设备树的方式,因为普通的GPIO操作无法设置上拉模式,我的硬件原理图如下所示
硬件未采用上拉电阻,所以需要软件中设置引脚上拉,只能使用设备树的方式,设备树配置如下
keyscan {
compatible = "key,scan";
gpios = <&pio PB 8 0 1 1 1>,<&pio PB 10 0 1 1 1>,<&pio PB 12 0 1 1 1>,<&pio PH 2 0 1 1 1>,<&pio PH 5 0 1 1 1>,<&pio PB 2 0 1 1 1>;
};
之后使用platform_driver去调用设备树资源,代码如下
struct of_device_id ids[] = {
{.compatible = "key,scan"},
{},
};
/* 1. 定义platform_driver */
static struct platform_driver chip_demo_gpio_driver = {
.probe = myprobe,
.remove = myremove,
.driver = {
.name = "mydrv",
.of_match_table = ids,
},
};
/* 2. 在入口函数注册platform_driver */
static int __init keyscan_init(void)
{
int err;
err = platform_driver_register(&chip_demo_gpio_driver);
return err;
}
之后就是将设备树中的引脚配置出来,代码如下
struct device_node *nd = pdev->dev.of_node;
struct gpio_config config;
unsigned long conf;
char pin_name[6][32];
printk("gpio count:%d\n", of_gpio_named_count(nd, "gpios"));
for (int i = 0; i < 6; i++)
{
key_gpio[i] = of_get_named_gpio_flags(nd, "gpios", i, (enum of_gpio_flags *)&config);
printk("gpio name%d:%s\n", i+1,sunxi_gpio_to_name(config.gpio, pin_name[i]));
if (!gpio_is_valid(key_gpio[i]))
printk("gpio isn't valid\n");
if (gpio_request(key_gpio[i], pdev->name) < 0)//申请注册gpio
printk("gpio request failed %d\n", key_gpio[i]);
gpio_direction_input(key_gpio[i]);//设置io输入模式
if(config.pull!=GPIO_PULL_DEFAULT){//判断设备树配置输入模式与默认是否相同,不同则配置设备树模式
conf=SUNXI_PINCFG_PACK(SUNXI_PINCFG_TYPE_PUD,config.pull);
pin_config_set(SUNXI_PINCTRL,pin_name[i],conf);
}
if(config.drv_level!=GPIO_DRVLVL_DEFAULT){//判断设备树配置驱动能力等级与默认是否相同,不同则配置设备树模式
conf=SUNXI_PINCFG_PACK(SUNXI_PINCFG_TYPE_DRV,config.drv_level);
pin_config_set(SUNXI_PINCTRL,pin_name[i],conf);
}
if(config.data!=GPIO_DATA_DEFAULT){//判断设备树配置有效电平与默认是否相同,不同则配置设备树模式
conf=SUNXI_PINCFG_PACK(SUNXI_PINCFG_TYPE_DAT,config.data);
pin_config_set(SUNXI_PINCTRL,pin_name[i],conf);
}
}
在其中注册了一个注册file_operations
其中只写了一个read函数,read代码和消抖功能如下所示
static ssize_t keyscan_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
char status[6];//第一次获取键值
char after_status[6];//延时后第二次获取键值
char result[6];//返回的数据结果,1有按键按下,0无按键按下
for (int i = 0; i < 6; i++)
status[i]=gpio_get_value(key_gpio[i]);//读取gpio电平
mdelay(10);//延时10ms,消抖
for (int i = 0; i < 6; i++)
after_status[i]=gpio_get_value(key_gpio[i]);//读取gpio电平
for (int i = 0; i < 6; i++){
if(status[i]==0 && after_status[i]==0)//判断有按键按下,硬件导通下拉电阻到地,所以按下按键数值为0
result[i]=1;
else//有抖动或者按键未按下,默认为按键未按下
result[i]=0;
}
if (copy_to_user(buf,&result, sizeof(result)))
return -EFAULT; //拷贝失败
return 0;
}
驱动写完了之后,测试驱动程序如下所示:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <poll.h>
#include <unistd.h>
#include <time.h>
#include <sys/time.h>
char *keyscan_PATH = "/dev/kbq_keyscan0";
int main(int argc, char **argv)
{
int fd;
char status[6];
/* 打开文件 */
fd = open(keyscan_PATH, O_RDWR);
if (fd == -1){
printf("can not open file %s\n", keyscan_PATH);
return -1;
}
while(1){
usleep(10000);//延时100ms
read(fd, &status, sizeof(status));//读取
printf("key1=%d,key2=%d,key3=%d,key4=%d,key5=%d,key6=%d\r\n",status[0],status[1],status[2],status[3],status[4],status[5]);
}
close(fd);
return 0;
}
测试效果如下所示:
四、结语
学习