一、GPIO子系统
1.1 引脚编号
在硬件上如何确定GPIO引脚?它属于哪组GPIO?它是这组GPIO里的哪个引脚?需要2个参数。但是在Linux软件上,可以使用引脚编号来表示。以100ask_ imx6ull为例在开发板上执行如下命令查看已经在使用的GPIO状态:可以看到在Linux系统中可以使用编号来访问某个GPIO。
cat /sys/kernel/debug/gpio
1.2 基于sysfs操作引脚
以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
作用分别是:申请引脚、设置成输入、获取电平状态、释放引脚
1.3 在驱动程序中调用GPIO子系统
在设备树中指定了GPIO引脚,在驱动代码中如何使用?也就是GPIO子系统的接口函数是什么?GPIO子系统有两套接口:基于描述符的(descriptor-based)、老的(legacy)。前者的函数都有前缀“ gpiod_”,它使用 gpio_desc 结构体来表示一个引脚;后者的函数都有前缀“ gpio_”,它使用一个整数来表示一个引脚。要操作一个引脚,首先要get引脚,然后设置方向,读值、写值。
驱动程序中要包含头文件,选用一个即可:
include <linux/gpio/consumer.h> // descriptor-based
include <linux/gpio.h> // legacy
下表列出常用的函数:
二、中断
2.1 使用中断的流程
- 确定中断号
- 注册中断处理函数,函数原型如下:
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev);
- 在中断处理函数里:分辨中断、处理中断、清楚中断
2.2 函数细节
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个参数是给中断处理函数使用的(用户自行决定是否要传给中断函数的参数)。
三、定时器
定时器作用:超时后调用超时函数(相当于闹钟,时间到后你就要做某些事)。
两个要素:超时时间和超时函数。
应用:如按键消抖。
3.1 定时器函数
在内核中使用定时器很简单,涉及这些函数(参考内核源码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);
- 设置定时器:定时器结构体,定时器超时函数,传给超时函数的参数。
- 向内核添加定时器。 timer->expires 表示超时时间。当超时时间到达,内核就会调用这个函数:timer->function(timer->data)。
- 修改定时器的超时时间。
- 删除定时器。
3.2 使用定时器处理按键抖动
在实际的按键操作中,可能会有机械抖动:按下或松开一个按键,它的 GPIO 电平会反复变化,最后才稳定。一般是几十毫秒才会稳定。如果不处理抖动的话,用户只操作一次按键,中断程序可能会上报多个数据。怎么处理?
核心在于:在GPIO中断中并不立刻记录按键值,而是修改定时器超时时间,10ms 后再处理。
- 如果 10ms 内又发生了 GPIO 中断,那就认为是抖动,这时再次修改超时时间为 10ms。
- 只有 10ms 之内再无 GPIO 中断发生,那么定时器的函数才会被调用。在定时器函数中记录按键值。