嵌入式Linux驱动:GPIO 按键驱动 - 我们终于要从输出走向输入了
仓库已经开源!所有教程,主线内核移植,跑新版本imx-linux/uboot都在这里,或者一起来尝试跑7.0的Linux!欢迎各位大佬观摩!喜欢的话点个⭐!
仓库地址:https://github.com/Awesome-Embedded-Learning-Studio/imx-forge
静态网页:https://awesome-embedded-learning-studio.github.io/imx-forge/
前面几章我们都在折腾输出设备——LED、蜂鸣器,这些有个共同点:CPU 控制它们,想让它们干嘛就干嘛。说干就干,写个寄存器,LED 就亮了;再写一次,蜂鸣器就叫了。这种掌控感确实让人上瘾。
但现实世界不全是输出设备。我们还需要处理输入设备——按键、传感器、触摸屏,这些设备不听 CPU 的指挥,反过来是它们告诉 CPU 发生了什么。这种角色的转换一开始让人有点不适应,我承认刚开始学的时候确实折腾了好几天。
输入和输出的本质区别
输出设备和输入设备的区别说起来简单,但理解透彻不容易:
/* 输出设备:CPU 写 GPIO */writel(GPIO_DR,val);// LED 亮了/* 输入设备:CPU 读 GPIO */u32 val=readl(GPIO_DR);// 按键状态区别就在这一读一写。输出是 CPU 主动的,输入是 CPU 被动的。输出什么时候发生由代码决定,输入什么时候发生由用户决定(用户什么时候按键谁知道)。
这个"谁说了算"的问题,直接影响了驱动的设计。
按键驱动要解决的问题
第一次写按键驱动的时候,我以为很简单。不就是读个 GPIO 吗?能有多复杂。结果现实给了我一记响亮的耳光,坑真的不少。
第一个问题是:**怎么知道按键被按下了?**你说我读 GPIO 状态吧,什么时候读?一直读?那 CPU 不就空转了吗?间隔着读?那按键刚好在两次读取之间按下怎么办?
第二个问题是:按键会抖动。机械按键的触点在接触瞬间会抖动。你按一下,GPIO 电平可能会跳变好几次:
理想波形(以为): ┌─────────────────── ────┘ └───── 实际波形(真实): ┌─┬───┬─────┬──────── ────┘ │ │ │ └── └─┘ └─┬─┬─┘ └─┘ ←──── 5-50ms ────→我第一次测试驱动的时候,按一下按键,应用程序居然收到好多次事件。查了半天才发现是这个抖动问题。真的,这种硬件特性只有踩过坑才会印象深刻。
第三个问题是:CPU 占用的问题
如果你用轮询方式——就是不停地读 GPIO 状态——CPU 占用率会很高。这不像 LED,设置一下就完事了,按键驱动需要持续监控。
我们先学轮询方式
这些问题的终极解决方案是用中断。但中断有自己的复杂度,需要理解中断系统、中断处理函数、下半部机制这些概念。
所以我们决定分两步走:先学轮询方式,再学中断方式。
轮询方式虽然效率低,但有几个好处:
- 简单直接——代码逻辑一目了然,没有隐藏的魔法
- 容易调试——出问题了直接看循环里的状态就行
- 建立概念——先理解"等待事件"的概念,再学中断会轻松很多
说实话,我觉得如果一开始就学中断,很容易迷失在各种机制里。先跑通轮询,建立信心,再学中断,这条路更平滑。
按键的硬件连接
我们的 Alpha 开发板上有一个按键,连接方式是经典的低电平触发:
+3.3V | < > 10kΩ 上拉电阻 < | +---- GPIO1_IO18 | 按键开关 | GND这个电路的工作原理:
- 按键松开时,上拉电阻把 GPIO 拉到高电平(3.3V)
- 按键按下时,开关导通,GPIO 被拉到地(0V)
这是个设计选择问题。上拉的好处是功耗低——按键松开时几乎没有电流。下拉的话,按键按下时会有电流从 VCC 流到 GND。对于电池供电的设备,这个差异可能很重要。
另外,很多传统设计习惯用上拉,可能历史原因多一些。
设备树配置
设备树里这样描述这个按键:
imxaes_key_gpio: key-gpio { compatible = "imxaes-key-gpio"; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_key>; gpios = <&gpio1 18 GPIO_ACTIVE_LOW>; status = "okay"; };这里的GPIO_ACTIVE_LOW很关键。它告诉内核:这个按键是低电平触发的。当按键按下时,GPIO 物理上是低电平,但逻辑上应该解释为"按键按下"(1)。
这个反转逻辑在驱动层会自动处理,我们写代码的时候不用管。
好了,背景介绍差不多了。下一节我们开始看代码,先从 GPIO 输入的机制说起。