我想使用基于 GPIO 的按钮。假设按下输入低电平,松开是高电平。硬件有做抖动消除(树莓派可能没有,但是应该不影响我这个比较宽泛的问题的讨论)。在写驱动的时候,脑海里浮现了一个奇怪的问题。
正常流程:
按键 ->硬件抖动消除 -> 中断产生,cpu 收到中断 ->中断处理程序读取 GPIO 的值(高还是低电平)判断按键的动作是什么,读到的值是 0 ,然后做相应的处理
但是我不知道是否在理论上存在这样一种可能:
按键 ->硬件抖动消除 -> 中断产生,cpu 收到中断 ->键被快速释放(假设理论上有个足够快的按键工具可以快速释放) -> 硬件抖动消除 ->由第一次中断引起的中断处理程序读取 GPIO 的值,读到的值是 1 。与正常流程不一样。
其实这个问题的本质我感觉是 GPIO 没有硬件 cache 来存储数据(也就是每次中断对应的 GPIO 值),后来的中断可能在理论上覆盖前面的值。但是像其他设备,比如网卡就有 cache 会存储数据,虽然 cache 的容量是有限制的,但是至少可以保证前几次中断来的数据不会被覆盖。cache 满了的时候,会丢弃后面的数据。请问这样理解对吗?
另外,正常流程中,读取 GPIO 值在中断的 top half 里面读比较好呢,还是在 bottom half 里面读也可以?是不是在 bottom half 里面读也无所谓,因为 GPIO 按键是个慢中断,在实践上没有我后面所说的 GPIO 值被后来的中断覆盖的问题。
1
crysislinux 2023-07-31 12:19:08 +08:00
听起来会有你说的这种现象。结果就是按键按了没反应,跟没按一样,感觉日常也遇到过这种情况,就是不知道是不是同一种原因. 发生的概率太小外加发生了也没什么严重后果,就不需要去投入额外成本解决了。
|
2
xiri 2023-07-31 12:28:12 +08:00 via Android
不是很熟悉 linux 的处理,不过我之前折腾过一些单片机做串口协议转换,设置中断函数的时候是可以指定在低电平变高电平时触发还是高电平变低电平时触发。
也就是说你提到的按下/松开按钮的两个场景最终可以直接去调用不同的中断处理函数,而不用再去读一下 gpio 才能知道当前触发的模式 |
4
huangya OP @xiri
>也就是说你提到的按下/松开按钮的两个场景最终可以直接去调用不同的中断处理函数,而不用再去读一下 gpio 才能知道当前触发的模式 嗯,看起来是一种思路。嵌入式 linux 有的。可以使用 IRQF_TRIGGER_RISING 和 IRQF_TRIGGER_FALLING 来指定。但是我感觉这样会增加一些冗余代码,不是很简洁。 |
5
huangya OP |
6
billlee 2023-07-31 12:50:21 +08:00
足够快的释放等于抖动,会直接被过滤掉。只要中断处理不是慢得很离谱,就没有什么问题。但树莓派上拿 python 写的所谓“中断处理程序”并不是真的,确实可能出现慢得离谱的情况。
|
7
Alexsen 2023-07-31 13:36:33 +08:00
在设计 GPIO 驱动时,通常不需要担心这些极端情况。在中断的 top half 读取 GPIO 值,加上硬件抖动消除,通常就足够确保可靠的按键检测了。
|
8
villivateur 2023-07-31 13:47:07 +08:00
个人见解,对于树莓派这种比较高级的计算设备,最好自己写个驱动用轮询的方式去读 GPIO ,不要用中断了。这样编程很方便,而且能实现软件消抖
|
9
huangya OP @Alexsen 请允许我再问个细节问题。按照你的说法,不需要担心这种极端情况,那是不是也意味着我在 top half 读取 GPIO 值可以放到一个 static 全局变量,然后在 bottom half 读。我也不需要担心这个 static 全局变量被第二次中断覆盖。是吗?谢谢!
|
10
huangya OP @billlee
>足够快的释放等于抖动,会直接被过滤掉。 好像也是。 >但树莓派上拿 python 写的所谓“中断处理程序”并不是真的,确实可能出现慢得离谱的情况。 我不是,我是用 C 写一个 kernel module. |
11
elechi 2023-07-31 16:05:01 +08:00
足够快就是抖动了,本身就会过滤了。过滤的作用就是把认为非正常时间的抖动消除。
|
12
dalabenba 2023-07-31 18:41:10 +08:00 via Android
linux kernel 的 bottom half 是放在一个内核线程里跑的,时间不定。本身 linux 也不是实时系统,内核运行时有好多关中断的操作
|
14
duke807 2023-07-31 19:37:23 +08:00 via Android
我的做法是,需要区分长短按的话:
只要来中断就关闭按键中断,然后启动一个比较短时间的定时器来读按键,定时器是循环多次的,通过检查第几次按键释放来判断是什么事件 如果释放的太早,认为是干扰 适中是短按 超过一定次数,直接输出长按事件 如果按键没有释放,定时器持续循环,直到连续检测到很多次已释放的值,则重新打开按键中断 如果只用支持短按: 只要有中断,就输出按键事件,同时关闭按键中断,启动一个单次定时器 半秒后定时器中断时再打开按键中断即可 或者按键上加一个 100nf 滤波电容(按键不要直接短路电容,要串一个小电阻),根本就不用考虑按键抖动问题 |
15
sujin190 2023-07-31 22:01:33 +08:00 via Android
如果是嵌入式程序的话你想多了,中断要么被屏蔽,触发了执行权就给你了,你的手不可能有这么快,而且大多数中断都是低到高或者高到低触发中断,如果恰好那一刻中断被屏蔽那就不会触发中断了,不存在调度逻辑,而且就算有防抖也不可能那么长,手按操作再快也要几百毫秒一次了,这时间对 cpu 来说可是非常长的
|
16
huangya OP @sujin190 嗯,能帮忙看下看下我在第 9 楼的回复中提到的细节问题吗?这个我也不需要担心吗? 12 和 13 楼也是 9 楼相关问题。
|
17
sujin190 2023-08-01 10:35:26 +08:00 1
@huangya #16 其实你只要理解硬件的中断优先级是手动管理的,硬件编程和软件编程不一样,如何调度何时调度都是固定的,所以每秒留给按钮处理程序多少时间一般就是提前设计好的,几乎不会有明显波动,所以一般来说就人的反应来说并不会出现来不及处理的情况,而且吧就算是嵌入式程序,设计合理严谨的程序也不会直接在中断里直接完成逻辑处理,一般都只是获取完状态然后创建任务交给主程序完成业务逻辑,那么这种情况下,每个中断的处理时间是完全精确到了具体的时钟周期,提前就可以完全精确的算出了每个中断耗时和冲突情况,既然如此按钮能否正常处理提前就能知道啊
而对于有操作系统的像树莓派这种,一般来说应用程序层面接收到的都是内核给的软中断,一般不是驱动程序的话并不能直接接收到硬件中断吧,否则岂不是应用程序处理异常就会导致内核直接崩溃,换句话说内核负责管理了中断优先级和调度延时,同样一般内核也能保证硬件中断能得到及时处理,毕竟硬件中断并不会受到系统负载的影响,而对于网卡、USB 之类需要处理大量数据的这种设备,其实硬件已经通过 DMA 把数据直接存到内存了,硬件中断只是告诉操作系统有新数据已经保存到内存,之后同样转有软中断处理,所以硬件中断所做的事情是非常少的,不能及时处理的可能性很小,软中断自然收到操作系统调度影响但是此时程序设计本身就能支持不确定延时了吧 |
18
dalabenba 2023-08-01 10:55:33 +08:00 via Android
@huangya 可以这么做,但即使这样也可能出现中断响应比实际触发晚比较多的情况,因为可能别人把中断关掉了。最好直接把不同按键逻辑绑定到不同中断响应程序里
|
19
TerryRobles 2023-08-01 11:24:18 +08:00
为什么我没懂你们在讨论什么
OP 的意思是 CPU 读取到的是第一次的电平吗? 按键一般不都是读取多次的吗? 中断的话我一般都是用下降沿触发 |
20
huangya OP @TerryRobles
在我的使用场景下, 1.上升沿和下降沿都需要触发中断,因为我要用软件来计算按键持续时间的长度。区分是长按还是短按。 2.我现在是使用读 gpio 的值来区分是按下还是松开。 >按键一般不都是读取多次的吗? 3.在我的环境下,假设使用了硬件消除抖动,只需要读取一次。 基于上述我所说的几点,OP 在讨论一种可能的理论情况,第一次按键产生中断,然后程序读取 GPIO 的值的时候,有没有可能这个值由于键快速释放导致 GPIO 值变化了。这里还牵扯一个问题,就是什么时候读取。如果放到中断的 top half 这个概率就更小了,如果放到中断的 bottom half ,会不会概率增大很大。 |