在硬件上如何确定GPIO引脚?它属于哪组GPIO?它是这组GPIO里的哪个引脚?需要2个参数。但是在Linux软件上,可以使用引脚编号来表示。以100ask_ imx6ull为例在开发板上执行如下命令查看已经在使用的GPIO状态:可以看到在Linux系统中可以使用编号来访问某个GPIO。
cat /sys/kernel/debug/gpio
以100ask_imx6ull为例,它有一个按键,原理图如下:
那么GPIO5_3的号码是4*32 + 3 =131,可以如下操作读取按键值:
echo 131 > /sys/class/gpio/export // gpio_request
echo in > /sys/class/gpio/gpio110/direction // gpio_direction_input
cat /sys/class/gpio/gpio110/value // gpio_get_value
echo 131 > /sys/class/gpio/unexport // gpio_free
作用分别是:申请引脚、设置成输入、获取电平状态、释放引脚
在设备树中指定了GPIO引脚,在驱动代码中如何使用?也就是GPIO子系统的接口函数是什么?GPIO子系统有两套接口:基于描述符的(descriptor-based)、老的(legacy)。前者的函数都有前缀“ gpiod_”,它使用 gpio_desc 结构体来表示一个引脚;后者的函数都有前缀“ gpio_”,它使用一个整数来表示一个引脚。要操作一个引脚,首先要get引脚,然后设置方向,读值、写值。
驱动程序中要包含头文件,选用一个即可:
include // descriptor-based
include // legacy
下表列出常用的函数:
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev);
request_irq函数的第1个参数是中断号,可以根据GPIO函数获得中断号:
int gpio_to_irq(unsigned int gpio); //使用该函数传入一个gpio的编码得到中断号
request_irq函数的第2个参数是函数指针: 中断函数会被内核调用
enum irqreturn {
IRQ_NONE = (0 << 0),
IRQ_HANDLED = (1 << 0),
IRQ_WAKE_THREAD = (1 << 1),
};
typedef enum irqreturn irqreturn_t;
typedef irqreturn_t (*irq_handler_t)(int irq, void *dev);
request_irq函数的第3个参数flag表示中断的触发类型:上升沿触发、下降沿触发、上升下降同时触发、高电平触发、低电平触发,有如下取值:
#define IRQF_TRIGGER_NONE 0x00000000
#define IRQF_TRIGGER_RISING 0x00000001
#define IRQF_TRIGGER_FALLING 0x00000002
#define IRQF_TRIGGER_HIGH 0x00000004
#define IRQF_TRIGGER_LOW 0x00000008
#define IRQF_SHARED 0x00000080
request_irq函数的第4个参数是中断的名字,可以在执行cat /proc/interrupts的结果里查看。
request_irq函数的第5个参数是给中断处理函数使用的(用户自行决定是否要传给中断函数的参数)。
定时器作用:超时后调用超时函数(相当于闹钟,时间到后你就要做某些事)。
两个要素:超时时间和超时函数。
应用:如按键消抖。
在内核中使用定时器很简单,涉及这些函数(参考内核源码include\linux\timer.h):
setup_timer(timer, fn, data);
void add_timer(struct timer_list *timer);
int mod_timer(struct timer_list *timer, unsigned long expires);
int del_timer(struct timer_list *timer);
在实际的按键操作中,可能会有机械抖动:按下或松开一个按键,它的 GPIO 电平会反复变化,最后才稳定。一般是几十毫秒才会稳定。如果不处理抖动的话,用户只操作一次按键,中断程序可能会上报多个数据。怎么处理?
核心在于:在GPIO中断中并不立刻记录按键值,而是修改定时器超时时间,10ms 后再处理。