3、GPIO驱动调试:从寄存器到按键响应
GPIO调试,说白了就是让芯片的引脚听话。你想让它输出高电平它就高,想让它检测按键它就检测。听起来简单,但实际坑不少。我这些年调过的GPIO问题,少说也有几十个了。
3.1 GPIO控制器寄存器映射
每个SoC的GPIO控制器,本质上就是一堆寄存器。你要做的第一件事,就是把物理地址映射到虚拟地址空间。
在Linux里,我们通常用ioremap或者devm_ioremap_resource来做这件事。我个人习惯用后者,因为它自带资源管理,省得你忘了释放。
static void __iomem *gpio_base;
gpio_base = devm_ioremap_resource(dev, res);
if (IS_ERR(gpio_base))
return PTR_ERR(gpio_base);
映射完之后,你就能通过偏移量访问各个寄存器了。比如数据寄存器、方向寄存器、上拉下拉寄存器等等。每个芯片的偏移量都不一样,一定要看数据手册。
3.2 sysfs接口使用
调试GPIO最快的方式是什么?我个人觉得是sysfs。不用写驱动,直接在命令行就能操作。
首先,你要确认GPIO是否已经导出:
# 查看GPIO编号,比如GPIO3_5 = 3*32 + 5 = 101
echo 101 > /sys/class/gpio/export
# 设置方向
echo out > /sys/class/gpio/gpio101/direction
# 输出高电平
echo 1 > /sys/class/gpio/gpio101/value
# 读取电平
cat /sys/class/gpio/gpio101/value
嗯,这里要注意:有些内核版本把sysfs接口标记为deprecated了,改用chardev。但说实话,调试阶段sysfs还是最方便的。你想想看,一条命令就能测引脚,多省事。
3.3 gpiolib框架分析
gpiolib是Linux内核提供的GPIO抽象层。它把底层的寄存器操作封装起来,让你用统一的API干活。
核心结构体就两个:
| 结构体 | 作用 |
|---|---|
gpio_chip |
代表一个GPIO控制器,包含操作函数指针 |
gpio_desc |
代表一个具体的GPIO引脚,包含状态信息 |
写驱动时,你只需要实现gpio_chip里的几个回调函数:
static struct gpio_chip my_gpio_chip = {
.label = "my-gpio",
.direction_input = my_gpio_direction_input,
.direction_output = my_gpio_direction_output,
.get = my_gpio_get,
.set = my_gpio_set,
.base = -1, // 动态分配
.ngpio = 32,
};
注册的时候调用gpiochip_add_data就行了。内核会自动帮你处理设备树匹配、中断映射这些杂事。
3.4 中断触发方式配置
GPIO中断,说白了就是引脚电平变化时通知CPU。触发方式有几种:
- 上升沿触发:从低到高跳变时触发
- 下降沿触发:从高到低跳变时触发
- 双边沿触发:两种跳变都触发
- 电平触发:保持高或低电平期间一直触发
配置方式很简单,用request_irq或者devm_request_irq:
static irqreturn_t my_gpio_irq_handler(int irq, void *dev_id)
{
// 处理中断
printk("GPIO interrupt triggered!\n");
return IRQ_HANDLED;
}
// 注册中断,下降沿触发
ret = devm_request_irq(dev, gpio_to_irq(gpio_num),
my_gpio_irq_handler,
IRQF_TRIGGER_FALLING,
"my-gpio-key", NULL);
这里有个坑:电平触发容易产生多次中断。我曾经遇到过按键按下时,中断触发了十几次。后来加了防抖处理才解决。
3.5 实际案例:按键中断响应调试
好了,理论说完了,咱们来个实战。调试一个按键中断,从零开始。
第一步:确认硬件连接
按键一端接GPIO,另一端接地。按下时引脚拉低,所以用下降沿触发。
第二步:设备树配置
gpio-keys {
compatible = "gpio-keys";
pinctrl-0 = <&key_pins>;
power-key {
label = "Power Key";
gpios = <&gpio3 5 GPIO_ACTIVE_LOW>;
linux,code = <KEY_POWER>;
gpio-key,wakeup;
};
};
第三步:驱动实现
static int __init key_drv_init(void)
{
int ret;
int irq;
// 获取GPIO描述符
key_gpio = gpiod_get(&pdev->dev, NULL, GPIOD_IN);
if (IS_ERR(key_gpio)) {
pr_err("Failed to get GPIO\n");
return PTR_ERR(key_gpio);
}
// 获取中断号
irq = gpiod_to_irq(key_gpio);
// 注册中断
ret = devm_request_irq(&pdev->dev, irq,
key_irq_handler,
IRQF_TRIGGER_FALLING,
"power-key", NULL);
if (ret) {
pr_err("Failed to request IRQ\n");
return ret;
}
return 0;
}
第四步:调试过程
我调试时一般这么干:
- 先用sysfs确认GPIO能正常读写
- 用示波器看按键波形,确认没有严重抖动
- 注册中断后,按一下按键,看
/proc/interrupts里中断计数有没有增加 - 如果中断没触发,检查设备树里的GPIO编号对不对
最后,别忘了在中断处理函数里加防抖。最简单的办法是加一个定时器,中断触发后延迟10-20ms再读一次引脚电平。如果还是有效电平,才认为是真正的按键事件。
嗯,GPIO调试大概就这些。说白了就是三板斧:确认硬件、配好设备树、写好中断处理。但每个环节都有坑,多踩几次就熟了。