【TINY4412】LINUX移植笔记:(19)设备树 KEY驱动
宿主机 : 虚拟机 Ubuntu 16.04 LTS / X64
目标板[底板]: Tiny4412SDK - 1506
目标板[核心板]: Tiny4412 - 1412
LINUX内核: 4.12.0
交叉编译器: arm-none-linux-gnueabi-gcc(gcc version 4.8.3 20140320)
日期: 2017-8-13 09:43:25
作者: SY
简介
ARM
支持矩阵键盘接口,参考手册:P55 Keypad Interface
- 行:0~13
- 列:0~7
最多支持 14 X 8 = 112 个按键
默认被上拉为高电平,按键按下检测到低电平。
由于开发板在 行
设计了按键,在 列
没有设计,看了源码samsung-keypad.c
,并不支持 列
为0的情况,因此使用gpio_keys.c
设备树
参考文档:./Documentation/devicetree/bindings/input/gpio_keys.txt
在 exynos4.dtsi
/ {
interrupt-parent = <&gic>;
gic: interrupt-controller@10490000 {
compatible = "arm,cortex-a9-gic";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0x10490000 0x10000>, <0x10480000 0x10000>;
};
};
作为中断控制器的父类神一般的存在,对他的子类 interrupt
字段中的元素个数做了说明,包含3个元素<中断类型 中断号 触发方式>
在 exynos4412-pinctrl.dtsi
pinctrl_1: pinctrl@11000000 {
gpx3: gpx3 {
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};
};
对他的子类 interrupt
字段中的元素个数做了说明,包含2个元素<引脚号 触发方式>
在自己的 exynos4412-tiny4412.dts
配置按键
gpio_keys {
compatible = "gpio-keys";
pinctrl-names = "default";
key1 {
interrupt-parent = <&gpx3>;
interrupts = <2 IRQ_TYPE_EDGE_FALLING>;
gpios = <&gpx3 2 GPIO_ACTIVE_LOW>;
linux,code = <KEY_1>;
label = "key1";
debounce-interval = <10>;
wakeup-source;
};
key2 {
interrupt-parent = <&gpx3>;
interrupts = <3 IRQ_TYPE_EDGE_FALLING>;
gpios = <&gpx3 3 GPIO_ACTIVE_LOW>;
linux,code = <KEY_2>;
label = "key2";
debounce-interval = <10>;
wakeup-source;
};
key3 {
interrupt-parent = <&gpx3>;
interrupts = <4 IRQ_TYPE_EDGE_FALLING>;
gpios = <&gpx3 4 GPIO_ACTIVE_LOW>;
linux,code = <KEY_3>;
label = "key3";
debounce-interval = <10>;
wakeup-source;
};
key4 {
interrupt-parent = <&gpx3>;
interrupts = <5 IRQ_TYPE_EDGE_FALLING>;
gpios = <&gpx3 5 GPIO_ACTIVE_LOW>;
linux,code = <KEY_4>;
label = "key4";
debounce-interval = <10>;
wakeup-source;
};
};
menuconfig
Device Drivers --->
Input device support --->
[*] Keyboards --->
<*> GPIO Buttons
应用程序
/*
* beep driver for tiny4412
*
* Copyright (c) 2017
* Author: SY <1530454315@qq.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <stdbool.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/epoll.h>
#define KEY_1 2
#define KEY_2 3
#define KEY_3 4
#define KEY_4 5
struct input_event {
struct timeval time;
unsigned short int type;
unsigned short int code;
signed int value;
};
#if 0
static void help(void)
{
printf("Usage: ./key <id>\n");
}
#endif
bool esc = false;
static void sigint_handler(int dunno)
{
switch (dunno) {
case SIGINT:
esc = true;
printf("< Ctrl+C > Press.\n");
break;
default:
break;
}
}
static void* read_handler(void* data)
{
printf("thread run.\n");
int epfd = epoll_create1(0);
if (epfd < 0) {
perror("epoll_create1");
return NULL;
}
int evfd = open("/dev/input/event1", O_RDONLY);
if (evfd < 0) {
perror("[open]");
esc = true;
}
struct epoll_event epoll_event;
epoll_event.events = EPOLLIN;
epoll_event.data.fd = evfd;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, evfd, &epoll_event) < 0) {
perror("[epoll_ctl]");
esc = true;
}
printf("start epoll...\n");
struct input_event event;
const int MAX_EVENT_NUMS = 10;
const int TIMEOUT = 100;
struct epoll_event *events = calloc(MAX_EVENT_NUMS, sizeof(struct epoll_event));
if (!events) {
perror("mem calloc");
esc = true;
}
while (esc == false) {
int nums = epoll_wait(epfd, events, MAX_EVENT_NUMS, TIMEOUT);
for (int i=0; i<nums; ++i) {
if (events[i].events & (EPOLLERR | EPOLLHUP)) {
perror("epoll");
continue;
} else if ((events[i].data.fd == evfd) && (events[i].events & EPOLLIN)) {
int ret = read(evfd, &event, sizeof(event));
if (ret < 0) {
break;
}
//printf("[key] nums=%d code=%d value=%d\n", nums, event.code, event.value);
switch (event.code) {
case KEY_1:
if (event.value) {
printf("[KEY1] Press.\n");
} else {
printf("[KEY1] Release.\n");
}
break;
case KEY_2:
if (event.value) {
printf("[KEY2] Press.\n");
} else {
printf("[KEY2] Release.\n");
}
break;
case KEY_3:
if (event.value) {
printf("[KEY3] Press.\n");
} else {
printf("[KEY3] Release.\n");
}
break;
case KEY_4:
if (event.value) {
printf("[KEY4] Press.\n");
} else {
printf("[KEY4] Release.\n");
}
break;
default:
break;
}
}
}
}
if (events) {
free(events);
}
close(epfd);
close(evfd);
printf("thread exit.\n");
pthread_exit(NULL);
return NULL;
}
int main(int argc, char **argv)
{
pthread_t thread_read;
int ret = pthread_create(&thread_read, NULL, read_handler, NULL);
if (ret) {
perror("[thread_create]");
return 1;
}
/* Register signal */
signal(SIGINT, sigint_handler);
pthread_join(thread_read, NULL);
printf("done!\n");
return 0;
}
Makefile
# Linux modules compile
# Author : SY
# Time : 2017-8-10 22:29:24
#############################################################################
CC = arm-none-linux-gnueabi-gcc
CFLAGS = -Wall -std=gnu99 -pthread
TARGET = key
OBJS = $(TARGET).o
INSTALL_PATH = /opt/fs/rootfs/rootfs/tmp/
$(TARGET) : $(OBJS)
$(CC) $(CFLAGS) -o $@ $<
install:
chmod 755 $(TARGET)
cp $(TARGET) $(INSTALL_PATH)
clean:
rm -rf *.o $(TARGET)
编译
第一次编译出现警告
key.o: In function `main':
key.c:(.text+0x94): undefined reference to `pthread_create'
key.c:(.text+0xd0): undefined reference to `pthread_join'
collect2: error: ld returned 1 exit status
Makefile:15: recipe for target 'key' failed
make: *** [key] Error 1
解决:Undefined reference to pthread_create in Linux
在 Makefile
中加入 -pthread
再次编译
key.c: In function 'read_handler':
key.c:63:2: warning: implicit declaration of function 'epoll_createl' [-Wimplicit-function-declaration]
int epfd = epoll_createl(0);
老是提示变量未声明,找来找去始终找不到原因,我们使用的编译器:arm-none-linux-gnueabi-gcc
,我们使用 #include <sys/epoll.h>
,编译器会去路径:/opt/arm-2014.05/arm-none-linux-gnueabi/libc/usr/include/
找到指定文件
/* Creates an epoll instance. Returns an fd for the new instance.
The "size" parameter is a hint specifying the number of file
descriptors to be associated with the new instance. The fd
returned by epoll_create() should be closed with close(). */
extern int epoll_create (int __size) __THROW;
/* Same as epoll_create but with an FLAGS parameter. The unused SIZE
parameter has been dropped. */
extern int epoll_create1 (int __flags) __THROW;
明明已经包含进去,却提示未声明,只可能拼写错误。
修改源文件:
int epfd = epoll_create1(0);
再次编译 OK
了,错误的原因是把:1
写成了 l
,妈蛋,找了两个晚上原因。
烧录
使用 NFS
方式进入 Linux
[root@TINY4412:~]# cd tmp/
[root@TINY4412:/tmp]# ls
beep gdbserver key
[root@TINY4412:/tmp]# ./key
thread run.
start epoll...
[KEY1] Press.
[KEY1] Release.
[KEY2] Press.
[KEY2] Release.
[KEY3] Press.
[KEY3] Release.
[KEY4] Press.
[KEY4] Release.
^C< Ctrl+C > Press.
thread exit.
done!
代码分析
在 APP
中使用了 2
个线程,本来只使用主线程也可以实现,但是在按下 Ctrl + C
时,进程会被操作系统终结,导致不能清理占用的资源,造成内存泄漏。
增加 Ctrl + C
重写
默认在按下 Ctrl + C
时,Linux
向我们的进程发送 SIGINT
信号。
下面是 Linux
的标准信号,是不可靠的,没有放进队列,因此有可能丢失信号。后来增加 32~63
信号为扩展信号,也是实时信号,支持队列,为可靠信号。当标准信号和扩展信号同时挂起时,优先响应标准信号。
#define SIGHUP 1
#define SIGINT 2
#define SIGQUIT 3
#define SIGILL 4
#define SIGTRAP 5
#define SIGABRT 6
#define SIGIOT 6
#define SIGBUS 7
#define SIGFPE 8
#define SIGKILL 9
#define SIGUSR1 10
#define SIGSEGV 11
#define SIGUSR2 12
#define SIGPIPE 13
#define SIGALRM 14
#define SIGTERM 15
#define SIGSTKFLT 16
#define SIGCHLD 17
#define SIGCONT 18
#define SIGSTOP 19
#define SIGTSTP 20
#define SIGTTIN 21
#define SIGTTOU 22
#define SIGURG 23
#define SIGXCPU 24
#define SIGXFSZ 25
#define SIGVTALRM 26
#define SIGPROF 27
#define SIGWINCH 28
#define SIGIO 29
#define SIGPOLL SIGIO
/*
#define SIGLOST 29
*/
#define SIGPWR 30
#define SIGSYS 31
#define SIGUNUSED 31
SIGINT
:程序终止信号,按下 Ctrl + C
时发出
SIGHUP
:终止终端连接信号,当用户退出终端时发出
上述的信号都有默认的行为,例如:SIGINT
信号默认就是关闭当前进程。当然,我们可以重写方法,改变信号发生后,程序的行为。其实类似于修改函数指针,让其指向我们的自定义方法。
/* Register signal */
signal(SIGINT, sigint_handler);
static void sigint_handler(int dunno)
{
switch (dunno) {
case SIGINT:
esc = true;
printf("< Ctrl+C > Press.\n");
break;
default:
break;
}
}
添加上述语句可以捕获 SIGINT
信号,执行 sigint_handler
函数,虽然我设置 esc == false
退出 main
方法,但是测试按下 Ctrl + C
并没有任何反应,只有按下任意一个按键后,才能退出进程。
发现原因是: read()
为阻塞函数,只有按下按键才能退出函数。
顺便说一句,使用 signal()
函数重定向后,在按下 Ctrl + C
后,你的进程不会被杀死了!
多线程
如果使用单线程的话, read()
阻塞后,就不能进行其他操作,因此开辟一个新的线程用来执行 read()
int main(int argc, char **argv)
{
pthread_t thread_read;
int ret = pthread_create(&thread_read, NULL, read_handler, NULL);
if (ret) {
perror("[thread_create]");
return 1;
}
pthread_join(thread_read, NULL);
}
static void* read_handler(void* data)
{
...
pthread_exit(NULL);
}
上述就是一个多线程的框架:
pthread_create
:用于创建一个新的线程
pthread_join
:用于主线程等待子线程执行结束,是一个阻塞函数。
read_handler
:为新开的线程,和其他线程共享地址空间,但是有自己的堆栈。
按下 Ctrl + C
后,新开的线程也没有自动结束,这样主线程仍然阻塞。这就强迫你想到使用异步 IO
方式。
异步 IO
查找资料,发现大家用的最多的是:select
poll
epoll
对于 read()
等类似的阻塞型函数,你不能使用该函数测试是否数据可读写,因为一旦不可读写,将导致当前线程阻塞,那么异步 IO
无非是提供一种方法,帮你做这个工作。
select
#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
对于形参:readfds
writefds
exceptfds
表示三种监事类型,一旦执行该函数,要么有相应事件就绪函数返回,要么超时返回,要么参数错误返回。可以根据返回值做相应操作。
nfds
:表示监视的设备描述符的个数
参考:Linux Programmer’s Manual SELECT(2)
select
当然也有缺点:
- 监视的
fd
数量有限 - 对于
fd
采用线性扫描的方式,当监控的fd
较多时,占用的资源会很多,而且遍历速度会变慢
poll
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
缺点:
poll
返回后,需要遍历并比对所有fd
的revents
,才能判断哪些fd
可以操作,耗时
epoll
看名字就知道是 poll
的改良版。
#include <sys/epoll.h>
typedef union epoll_data
{
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event
{
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
} __EPOLL_PACKED;
extern int epoll_create1 (int __flags) __THROW;
extern int epoll_ctl (int __epfd, int __op, int __fd,
struct epoll_event *__event) __THROW;
extern int epoll_wait (int __epfd, struct epoll_event *__events,
int __maxevents, int __timeout);
优点:
当收到可读写事件时,只返回有效的事件的个数,这样可以节省大量遍历的时间。
总结
在监视的描述符较少时,select
比较合适,因为简单。
在监视的描述符较多时,epoll
比较合适,效率比较高。