基于Yocto构建的树莓派4镜像无法从用户态读取按键状态
问题:gpio-keys驱动事件无法在用户态读取
现象
- 内核侧已通过自定义日志确认GPIO17上的微动开关触发时,gpio-keys驱动会上报事件:
[ 185.053346] <gpio_keys_gpio_isr> [ 185.076626] <gpio_keys_gpio_report_event> type: 1 | code: 777 | state: 0 [ 185.265685] <gpio_keys_gpio_isr> [ 185.268975] <gpio_keys_gpio_isr> [ 185.292253] <gpio_keys_gpio_report_event> type: 1 | code: 777 | state: 1
- 用户态应用调用
read()时挂起,无法获取按键事件。
用户态代码(原错误版本)
#include <stdio.h> #include <fcntl.h> #include <sys/ioctl.h> #include <linux/input.h> #include <string.h> #define DP_EVENT_INPUT "/dev/input/event0" int main(int argc, char **argv) { unsigned int key_states[2]; struct input_event evt; int fd; memset(key_states, 0, sizeof(key_states)); if ((fd = open(DP_EVENT_INPUT, O_RDONLY)) < 0) { if (errno == EACCES && getuid() != 0) { fprintf(stderr, "You do not have access to %s. Try running as root instead.\n", DP_EVENT_INPUT); exit(EXIT_FAILURE); } } while(1) { ioctl(fd, EVIOCGKEYCODE, key_states); if(read(fd, &evt, sizeof(struct input_event)) > 0) { if(evt.type == EV_KEY) { printf("EVENT type: EV_KEY\n"); } else { printf("No event!\n"); } } } }
设备树Overlay
/dts-v1/; /plugin/; #include <dt-bindings/pinctrl/bcm2835.h> #include <dt-bindings/gpio/gpio.h> #include "dt-bindings/input/linux-event-codes.h" / { compatible = "raspberrypi,4-model-b", "brcm,bcm2711"; fragment@0 { target = <&gpio>; __overlay__ { // Configure the gpio pin controller buttonpin: button-gpio { brcm,pins = <17>; brcm,function = <BCM2835_FSEL_GPIO_IN>; brcm,pull = <BCM2835_PUD_UP>; }; }; }; fragment@1 { target-path = "/"; __overlay__ { gpio-keys { compatible = "gpio-keys"; pinctrl-names = "default"; pinctrl-0 = <&buttonpin>; status = "okay"; sw1 { label = "GPIO Key USER1"; linux,code = <777>; linux,input-type = <EV_KEY>; debounce-interval = <20>; gpios = <&gpio 17 GPIO_ACTIVE_LOW>; }; }; }; }; };
问题分析与解决
1. 错误的ioctl调用
你误用了EVIOCGKEYCODE,这个ioctl的作用是查询键码与输入事件的映射关系,而非读取当前按键状态。如果要读取按键状态,应使用EVIOCGKEY,且需要传入足够大的缓冲区(按键状态按位存储,通常定义为unsigned char key_states[KEY_MAX/8 + 1])。
另外,你的场景是要读取按键的按下/释放事件,其实不需要调用ioctl,直接读取input_event结构体即可——gpio-keys驱动会主动上报EV_KEY类型的事件。
2. 设备节点可能不匹配
/dev/input/event0不一定是gpio-keys对应的设备节点。可以通过以下命令确认正确的节点:
cat /proc/bus/input/devices
找到包含GPIO Key USER1标签的设备,查看其Handlers字段(比如eventX),替换代码中的DP_EVENT_INPUT路径。
3. 代码修正
- 补充缺失的头文件(比如
stdlib.h用于exit,errno.h用于errno) - 移除错误的
EVIOCGKEYCODE调用 - 完善设备打开失败的错误处理
- 可选:添加非阻塞模式(如果不想
read挂起)
修正后的示例代码:
#include <stdio.h> #include <fcntl.h> #include <sys/ioctl.h> #include <linux/input.h> #include <string.h> #include <stdlib.h> #include <errno.h> // 替换为实际的event节点 #define DP_EVENT_INPUT "/dev/input/eventX" int main(int argc, char **argv) { struct input_event evt; int fd; // 可选:添加O_NONBLOCK以非阻塞方式打开 if ((fd = open(DP_EVENT_INPUT, O_RDONLY)) < 0) { if (errno == EACCES && getuid() != 0) { fprintf(stderr, "权限不足,请以root身份运行\n"); } else { perror("打开输入设备失败"); } exit(EXIT_FAILURE); } while(1) { ssize_t ret = read(fd, &evt, sizeof(struct input_event)); if (ret == sizeof(struct input_event)) { if(evt.type == EV_KEY) { printf("EV_KEY事件:code=%d,state=%d\n", evt.code, evt.value); // state=0表示按键释放,state=1表示按下,state=2表示长按重复 } } else if (ret == -1 && errno != EAGAIN) { perror("读取事件失败"); break; } } close(fd); return 0; }
4. 设备树优化(可选)
linux,input-type = <EV_KEY>可以省略,gpio-keys驱动默认使用EV_KEY类型- 当前的GPIO配置(上拉+GPIO_ACTIVE_LOW)是正确的,与微动开关的接线逻辑匹配
内容的提问来源于stack exchange,提问作者grandzello
相关产品推荐
相关产品推荐

