以正点原子 NPX
开发板进行实验。
一、原理分析
通过原理图分析可以得到,当按键按下后 KEY0
为低电平,当按键释放后 KEY0
为高电平。通过原理图可以确定 KEY0
连接在 UART1_CTS
引脚上。
二、修改设备树
1、查找引脚是否被使用
按键使用引脚如下:
从设备树中查找是否 UART1_CTS
引脚:
打开 imx6ull-lq-evk.dts
,在 iomuxc
节点的 imx6ul-evk
子节点下创建一个名为 “pinctrl_key”
的子节点,节点 内容如下所示:
2、添加 pinctrl 节点
pinctrl_key: keygrp {
fsl,pins = <
MX6UL_PAD_UART1_CTS_B__GPIO1_IO18 0xF080
>;
};
将引脚复用为 GPIO1_IO18
,并将电气属性寄存器值设置为为 0xF080
。
3、添加 key 设备节点
在根节点 “/”
下创建 key
节点,节点名为 “key”
,节点内容如下:
lq-key {
compatible = "lq-key";
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_key>;
key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>;
};
4、编译设备树
onlylove@ubuntu:~/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga$ make dtbs
CHK include/config/kernel.release
CHK include/generated/uapi/linux/version.h
CHK include/generated/utsrelease.h
make[1]: 'include/generated/mach-types.h' is up to date.
CHK include/generated/bounds.h
CHK include/generated/asm-offsets.h
CALL scripts/checksyscalls.sh
DTC arch/arm/boot/dts/imx6ull-lq-evk.dtb
onlylove@ubuntu:~/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga$
5、测试
/sys/firmware/devicetree/base # ls
#address-cells lq-led
#size-cells memory
aliases model
backlight name
chosen pxp_v4l2
clocks regulators
compatible reserved-memory
cpus soc
gpioled sound
interrupt-controller@00a01000 spi4
lq-key
/sys/firmware/devicetree/base # cd lq-key/
/sys/firmware/devicetree/base/lq-key # ls
compatible name pinctrl-names
key-gpio pinctrl-0 status
/sys/firmware/devicetree/base/lq-key # ls
compatible name pinctrl-names
key-gpio pinctrl-0 status
/sys/firmware/devicetree/base/lq-key # cat compatible
lq-key
/sys/firmware/devicetree/base/lq-key #
三、驱动编写
1、配置工程
在 VSCode
中添加 Linux
源码中的头文件路径。打开 VSCode
,按下 “Crtl+Shift+P”
打开 VSCode
的控制台,然后输入 “C/C++: Edit configurations(JSON) ”
,打开 C/C++
编辑配置文件,添加 Linux
源码头文件路径,修改后内容如下:
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**",
"/home/onlylove/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga/include",
"/home/onlylove/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga/arch/arm/include",
"/home/onlylove/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga/arch/arm/include/generated"
],
"defines": [],
"compilerPath": "/usr/bin/gcc",
"cStandard": "gnu17",
"cppStandard": "gnu++14",
"intelliSenseMode": "linux-gcc-x64"
}
],
"version": 4
}
2、makefile
KERNELDIR := /home/onlylove/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga
CURRENT_PATH := $(shell pwd)
obj-m := key.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
3、驱动框架编写和测试
源码如下:
#include "linux/init.h"
#include "linux/module.h"
#include "linux/fs.h"
#include "linux/cdev.h"
#include "linux/device.h"
#define KEYDEV_MAJOR 0 // 主设备号
#define KEYDEV_MINOR 0 // 次设备号
#define KEYDEV_CONUT 1 // 设备号个数
#define KEYDEV_NAME "keydev" // 设备文件名
/* 驱动使用数据管理 */
typedef struct{
int major; // 主设备号
int minor; // 次设备号
dev_t devid; // 设备号
struct cdev dev;
struct class *class;
struct device *device;
}keydev_t;
keydev_t keydev;
static const struct file_operations keydevops = {
.owner = THIS_MODULE,
};
/* 驱动加载函数 */
static int __init key_init(void)
{
int ret = 0;
/* 1、分配设备号 */
keydev.major = KEYDEV_MAJOR;
if(keydev.major){
// 指定设备号
keydev.minor = KEYDEV_MINOR;
keydev.devid = MKDEV(keydev.major,keydev.minor);
ret = register_chrdev_region(keydev.devid,KEYDEV_CONUT,KEYDEV_NAME);
}else{
// 分配设备号
ret = alloc_chrdev_region(&keydev.devid,0,KEYDEV_CONUT,KEYDEV_NAME);
keydev.major = MAJOR(keydev.devid);
keydev.minor = MINOR(keydev.devid);
}
if(ret < 0){
printk("keydev xxx_chrdev_region failed!\r\n");
goto keydev_chrdev_region_failed;
}
printk("keydev major=%d,minor=%d\r\n",keydev.major,keydev.minor);
/* 2、注册字符设备 */
keydev.dev.owner = THIS_MODULE;
cdev_init(&keydev.dev,&keydevops);
ret = cdev_add(&keydev.dev,keydev.devid,KEYDEV_CONUT);
if(ret < 0){
printk("keydev cdev_add failed!\r\n");
goto keydev_cdev_add_failed;
}
/* 3、创建类 */
keydev.class = class_create(THIS_MODULE,KEYDEV_NAME);
if (IS_ERR(keydev.class)) {
printk("keydev class_create failed!\r\n");
goto keydev_class_create_failed;
}
/* 4、创建设备 */
keydev.device = device_create(keydev.class,NULL,keydev.devid,NULL,KEYDEV_NAME);
if(IS_ERR(keydev.device)){
printk("keydev device_create failed!\r\n");
goto keydev_device_creat_failed;
}
return 0;
keydev_device_creat_failed:
class_destroy(keydev.class);
keydev_class_create_failed:
cdev_del(&keydev.dev);
keydev_cdev_add_failed:
unregister_chrdev_region(keydev.devid,KEYDEV_CONUT);
keydev_chrdev_region_failed:
return ret;
}
/* 驱动卸载函数 */
static void __exit key_exit(void)
{
/* 4、删除设备 */
device_destroy(keydev.class,keydev.devid);
/* 3、删除类 */
class_destroy(keydev.class);
/* 2、注销字符设备 */
cdev_del(&keydev.dev);
/* 1、释放设备号 */
unregister_chrdev_region(keydev.devid,KEYDEV_CONUT);
}
module_init(key_init);
module_exit(key_exit);
MODULE_LICENSE("GPL");
编译:
onlylove@ubuntu:~/linux/driver/linux_driver/6_key$ make
make -C /home/onlylove/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga M=/home/onlylove/linux/driver/linux_driver/6_key modules
make[1]: Entering directory '/home/onlylove/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga'
CC [M] /home/onlylove/linux/driver/linux_driver/6_key/key.o
Building modules, stage 2.
MODPOST 1 modules
CC /home/onlylove/linux/driver/linux_driver/6_key/key.mod.o
LD [M] /home/onlylove/linux/driver/linux_driver/6_key/key.ko
make[1]: Leaving directory '/home/onlylove/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga'
onlylove@ubuntu:~/linux/driver/linux_driver/6_key$
测试:
/ # ls
bin gpio-led.ko led_dts.ko mnt sbin usr
dev key.ko lib proc sys
etc led linuxrc root tmp
/ # lsmod
Module Size Used by Tainted: G
/ # ls /dev/keydev -l
ls: /dev/keydev: No such file or directory
/ #
/ #
/ # insmod key.ko
keydev major=248,minor=0
/ # lsmod
Module Size Used by Tainted: G
key 1299 0
/ # ls /dev/keydev -l
crw-rw---- 1 0 0 248, 0 Jan 1 01:06 /dev/keydev
/ #
/ #
/ # rmmod key.ko
/ # lsmod key.ko
Module Size Used by Tainted: G
/ # ls /dev/keydev -l
ls: /dev/keydev: No such file or directory
/ #
4、key驱动完善
#include "linux/init.h"
#include "linux/module.h"
#include "linux/fs.h"
#include "linux/cdev.h"
#include "linux/device.h"
#include "linux/of.h"
#include "linux/of_gpio.h"
#include "linux/gpio.h"
#include "linux/atomic.h"
#include "asm/uaccess.h"
#define KEYDEV_MAJOR 0 // 主设备号
#define KEYDEV_MINOR 0 // 次设备号
#define KEYDEV_CONUT 1 // 设备号个数
#define KEYDEV_NAME "keydev" // 设备文件名
#define KEY0VALUE 0XF0 /* 按键值 */
#define INVAKEY 0X00 /* 无效的按键值 */
/* 驱动使用数据管理 */
typedef struct{
int major; // 主设备号
int minor; // 次设备号
dev_t devid; // 设备号
struct cdev dev;
struct class *class;
struct device *device;
struct device_node *nd;
int key_gpio;
atomic_t keyvalue;
}keydev_t;
keydev_t keydev;
/* key 通过读取设备树 */
static int lq_key_init(void)
{
keydev.nd = of_find_node_by_path("/lq-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);
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 = lq_key_init();
if (ret < 0) {
return ret;
}
return 0;
}
static ssize_t key_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
int ret = 0;
unsigned char value;
keydev_t *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 const struct file_operations keydevops = {
.owner = THIS_MODULE,
.open = key_open,
.read = key_read,
};
/* 驱动加载函数 */
static int __init key_init(void)
{
int ret = 0;
/* 1、分配设备号 */
keydev.major = KEYDEV_MAJOR;
if(keydev.major){
// 指定设备号
keydev.minor = KEYDEV_MINOR;
keydev.devid = MKDEV(keydev.major,keydev.minor);
ret = register_chrdev_region(keydev.devid,KEYDEV_CONUT,KEYDEV_NAME);
}else{
// 分配设备号
ret = alloc_chrdev_region(&keydev.devid,0,KEYDEV_CONUT,KEYDEV_NAME);
keydev.major = MAJOR(keydev.devid);
keydev.minor = MINOR(keydev.devid);
}
if(ret < 0){
printk("keydev xxx_chrdev_region failed!\r\n");
goto keydev_chrdev_region_failed;
}
printk("keydev major=%d,minor=%d\r\n",keydev.major,keydev.minor);
/* 2、注册字符设备 */
keydev.dev.owner = THIS_MODULE;
cdev_init(&keydev.dev,&keydevops);
ret = cdev_add(&keydev.dev,keydev.devid,KEYDEV_CONUT);
if(ret < 0){
printk("keydev cdev_add failed!\r\n");
goto keydev_cdev_add_failed;
}
/* 3、创建类 */
keydev.class = class_create(THIS_MODULE,KEYDEV_NAME);
if (IS_ERR(keydev.class)) {
printk("keydev class_create failed!\r\n");
goto keydev_class_create_failed;
}
/* 4、创建设备 */
keydev.device = device_create(keydev.class,NULL,keydev.devid,NULL,KEYDEV_NAME);
if(IS_ERR(keydev.device)){
printk("keydev device_create failed!\r\n");
goto keydev_device_creat_failed;
}
return 0;
keydev_device_creat_failed:
class_destroy(keydev.class);
keydev_class_create_failed:
cdev_del(&keydev.dev);
keydev_cdev_add_failed:
unregister_chrdev_region(keydev.devid,KEYDEV_CONUT);
keydev_chrdev_region_failed:
return ret;
}
/* 驱动卸载函数 */
static void __exit key_exit(void)
{
/* 4、删除设备 */
device_destroy(keydev.class,keydev.devid);
/* 3、删除类 */
class_destroy(keydev.class);
/* 2、注销字符设备 */
cdev_del(&keydev.dev);
/* 1、释放设备号 */
unregister_chrdev_region(keydev.devid,KEYDEV_CONUT);
}
module_init(key_init);
module_exit(key_exit);
MODULE_LICENSE("GPL");
四、应用程序编写
#include "stdio.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define KEY0VALUE 0XF0
int main(int argc, char *argv[])
{
int fd, ret;
char *filename;
unsigned char keyvalue;
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);
}
}
return 0;
}
五、测试
/ # ls
bin etc key_app linuxrc proc sbin tmp
dev key.ko lib mnt root sys usr
/ #
/ # lsmod
Module Size Used by Not tainted
/ # ls -l /dev/keydev
ls: /dev/keydev: No such file or directory
/ #
/ # insmod key.ko
keydev major=248,minor=0
/ #
/ # lsmod
Module Size Used by Tainted: G
key 1881 0
/ # ls -l /dev/keydev
crw-rw---- 1 0 0 248, 0 Jan 1 01:59 /dev/keydev
/ #
/ # rmmod key.ko
/ #
/ # lsmod
Module Size Used by Tainted: G
/ # ls -l /dev/keydev
ls: /dev/keydev: No such file or directory
/ #
/ # insmod key.ko
keydev major=248,minor=0
/ #
/ # lsmod
Module Size Used by Tainted: G
key 1881 0
/ # ls -l /dev/keydev
crw-rw---- 1 0 0 248, 0 Jan 1 02:00 /dev/keydev
/ #
/ # ./key_app /dev/keydev
key_gpio=18
KEY0 Press, value = 0XF0
KEY0 Press, value = 0XF0
KEY0 Press, value = 0XF0
KEY0 Press, value = 0XF0
KEY0 Press, value = 0XF0
KEY0 Press, value = 0XF0
KEY0 Press, value = 0XF0
KEY0 Press, value = 0XF0
KEY0 Press, value = 0XF0
KEY0 Press, value = 0XF0
KEY0 Press, value = 0XF0
KEY0 Press, value = 0XF0
KEY0 Press, value = 0XF0
KEY0 Press, value = 0XF0
KEY0 Press, value = 0XF0
^C
/ #
通过以上日志可以确定,驱动可以正常运行。
六、说明
驱动仅仅为了测试功能,不够完善。在实际开发中也不会使用如此方法。在实际开发中会使用 Linux
中 input
子系统完成。