嵌入式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/上一章我们讲了为什么要先学轮询方式现在我们深入到代码层面看看 GPIO 输入到底是怎么工作的。说实话第一次写 GPIO 输入驱动的时候我以为会很复杂。结果发现 Linux 的 GPIO 子系统把事情简化了不少大部分复杂操作都被封装好了。输入和输出的 API 对比我们先回顾一下输出设备怎么用 GPIO/* 输出模式LED/蜂鸣器 */structgpio_desc*ledgpiod_get(dev,NULL,GPIOD_OUT_LOW);gpiod_set_value(led,1);// 点亮 LED再看输入模式/* 输入模式按键 */structgpio_desc*keygpiod_get(dev,NULL,GPIOD_IN);intstategpiod_get_value(key);// 读取按键状态API 的命名很有规律gpiod_get()获取 GPIO 描述符GPIOD_OUT_LOW/GPIOD_IN指定方向gpiod_set_value()/gpiod_get_value()操作 GPIOPS 关于 Descriptor API你可能听说过还有 Legacy APIgpio_request、gpio_direction_input之类的。那些是老接口现在不推荐用了。Descriptor API 是新的标准功能更强也更安全。我们教程统一用 Descriptor API。配置 GPIO 为输入在我们的硬件抽象层里初始化函数是这样的intkey_hw_init(structdevice*dev,structgpio_desc**gpio){structgpio_desc*gpiod;/* GPIOD_IN 表示配置为输入 */gpiodgpiod_get(dev,NULL,GPIOD_IN);if(IS_ERR(gpiod)){returnPTR_ERR(gpiod);}*gpiogpiod;return0;}这个gpiod_get()做了几件事解析设备树——从gpios gpio1 18 GPIO_ACTIVE_LOW提取信息申请 GPIO——防止被其他驱动占用配置方向——设置为输入模式返回描述符——后续操作用这个描述符::: tip 错误处理要重视gpiod_get()可能失败比如 GPIO 已经被占用或者设备树配置错误。所以一定要检查返回值。IS_ERR()和PTR_ERR()是内核的错误处理模式。很多内核函数用指针返回结果成功时返回有效指针失败时返回错误码编码的错误指针。这个模式和普通的返回值判断不太一样一开始用的时候容易搞混。:::读取 GPIO 状态读取状态的代码也很简单intkey_get_state(structgpio_desc*gpio){intval;valgpiod_get_value(gpio);/* 返回 0按下1松开 */return!val;}等等为什么要!val反转一下这涉及到GPIO_ACTIVE_LOW的处理。逻辑值和物理值GPIO 有两个层面的值物理电平和逻辑值。物理电平是实际电压高电平3.3V对应物理 1低电平0V对应物理 0逻辑值是应用层的语义按键按下对应逻辑 1按键松开对应逻辑 0我们的硬件是低电平触发所以物理和逻辑是反着的物理电平 逻辑值 ──────────────────── 高松开 → 0 低按下 → 1gpiod_get_value()已经帮我们处理了一次转换/* 内核内部实现简化 */intgpiod_get_value(structgpio_desc*desc){intraw_valgpiod_get_raw_value(desc);// 读取物理电平/* 如果设置了 GPIO_ACTIVE_LOW反转逻辑值 */if(test_bit(GPIOD_FLAG_ACTIVE_LOW,desc-flags))return!raw_val;elsereturnraw_val;}所以如果设备树里写了GPIO_ACTIVE_LOW物理低 →gpiod_get_value()返回 1物理高 →gpiod_get_value()返回 0但我们的应用层约定是按键按下 → 返回 0按键松开 → 返回 1所以需要在key_get_state()里再反转一次return !val。这个约定是从 LED 驱动继承来的。LED 的约定是1亮0灭。按键我们就约定1松开高电平0按下低电平。实际上这个约定可以随便定只要驱动和应用层统一就行。但我们为了保持一致性沿用了 LED 的约定。深入内核源码如果你想看gpiod_get_value()的完整实现它在drivers/gpio/gpiolib.c里intgpiod_get_value(structgpio_desc*desc){/* 省略参数检查和锁操作 */if(test_bit(GPIOD_FLAG_ACTIVE_LOW,desc-flags))return!gpiod_get_raw_value(desc);elsereturngpiod_get_raw_value(desc);}EXPORT_SYMBOL(gpiod_get_value);gpiod_get_raw_value()就直接读 GPIO 控制器的寄存器了具体实现取决于硬件平台。对于 i.MX6ULL它会读写 GPIO 数据寄存器GPIO_DR。关于 GPIO 释放你可能注意到了我们的硬件抽象层没有释放 GPIO 的函数。这是因为我们用了devm_APIgpioddevm_gpiod_get(dev,NULL,GPIOD_IN);devm_前缀表示managed resource托管资源。当设备卸载时内核会自动释放这些资源。所以我们的代码里不需要显式调用gpiod_put()。PS托管资源的好处托管资源机制最大的好处是防止资源泄漏。你想想如果驱动在某个错误路径返回忘记了释放 GPIO这个 GPIO 就永远被占用了。托管资源自动处理这些清理工作少写代码还更安全。当然我们的教程代码为了演示完整流程会显示调用释放函数。但在实际工程里托管资源是更好的选择。小结一下GPIO 输入的核心就这么几个函数/* 1. 获取并配置为输入 */gpiodgpiod_get(dev,NULL,GPIOD_IN);/* 2. 读取状态 */valgpiod_get_value(gpiod);/* 3. 可选释放 */gpiod_put(gpiod);剩下的工作就是怎么用这些基本操作实现一个完整的按键驱动了。下一章我们看轮询方式的实现在read()函数里循环等待按键事件。看完这些代码你会发现GPIO 输入并没有想象中那么复杂。Linux 的 GPIO 子系统把硬件差异都封装好了我们用统一的高层 API 就能操作。这种抽象做得挺到位的。相关阅读现代Qt开发教程新手篇1.15——正则与文本处理 - 相似度 100%通用GUI编程技术——Win32 原生编程实战五十四——Hook 机制 - 相似度 100%通用GUI编程技术——图形渲染实战四十四——D3D12命令列表、队列与围栏GPU同步核心 - 相似度 100%
嵌入式Linux实战:GPIO 输入机制 - 配置和读取那些事儿
发布时间:2026/6/12 11:15:10
嵌入式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/上一章我们讲了为什么要先学轮询方式现在我们深入到代码层面看看 GPIO 输入到底是怎么工作的。说实话第一次写 GPIO 输入驱动的时候我以为会很复杂。结果发现 Linux 的 GPIO 子系统把事情简化了不少大部分复杂操作都被封装好了。输入和输出的 API 对比我们先回顾一下输出设备怎么用 GPIO/* 输出模式LED/蜂鸣器 */structgpio_desc*ledgpiod_get(dev,NULL,GPIOD_OUT_LOW);gpiod_set_value(led,1);// 点亮 LED再看输入模式/* 输入模式按键 */structgpio_desc*keygpiod_get(dev,NULL,GPIOD_IN);intstategpiod_get_value(key);// 读取按键状态API 的命名很有规律gpiod_get()获取 GPIO 描述符GPIOD_OUT_LOW/GPIOD_IN指定方向gpiod_set_value()/gpiod_get_value()操作 GPIOPS 关于 Descriptor API你可能听说过还有 Legacy APIgpio_request、gpio_direction_input之类的。那些是老接口现在不推荐用了。Descriptor API 是新的标准功能更强也更安全。我们教程统一用 Descriptor API。配置 GPIO 为输入在我们的硬件抽象层里初始化函数是这样的intkey_hw_init(structdevice*dev,structgpio_desc**gpio){structgpio_desc*gpiod;/* GPIOD_IN 表示配置为输入 */gpiodgpiod_get(dev,NULL,GPIOD_IN);if(IS_ERR(gpiod)){returnPTR_ERR(gpiod);}*gpiogpiod;return0;}这个gpiod_get()做了几件事解析设备树——从gpios gpio1 18 GPIO_ACTIVE_LOW提取信息申请 GPIO——防止被其他驱动占用配置方向——设置为输入模式返回描述符——后续操作用这个描述符::: tip 错误处理要重视gpiod_get()可能失败比如 GPIO 已经被占用或者设备树配置错误。所以一定要检查返回值。IS_ERR()和PTR_ERR()是内核的错误处理模式。很多内核函数用指针返回结果成功时返回有效指针失败时返回错误码编码的错误指针。这个模式和普通的返回值判断不太一样一开始用的时候容易搞混。:::读取 GPIO 状态读取状态的代码也很简单intkey_get_state(structgpio_desc*gpio){intval;valgpiod_get_value(gpio);/* 返回 0按下1松开 */return!val;}等等为什么要!val反转一下这涉及到GPIO_ACTIVE_LOW的处理。逻辑值和物理值GPIO 有两个层面的值物理电平和逻辑值。物理电平是实际电压高电平3.3V对应物理 1低电平0V对应物理 0逻辑值是应用层的语义按键按下对应逻辑 1按键松开对应逻辑 0我们的硬件是低电平触发所以物理和逻辑是反着的物理电平 逻辑值 ──────────────────── 高松开 → 0 低按下 → 1gpiod_get_value()已经帮我们处理了一次转换/* 内核内部实现简化 */intgpiod_get_value(structgpio_desc*desc){intraw_valgpiod_get_raw_value(desc);// 读取物理电平/* 如果设置了 GPIO_ACTIVE_LOW反转逻辑值 */if(test_bit(GPIOD_FLAG_ACTIVE_LOW,desc-flags))return!raw_val;elsereturnraw_val;}所以如果设备树里写了GPIO_ACTIVE_LOW物理低 →gpiod_get_value()返回 1物理高 →gpiod_get_value()返回 0但我们的应用层约定是按键按下 → 返回 0按键松开 → 返回 1所以需要在key_get_state()里再反转一次return !val。这个约定是从 LED 驱动继承来的。LED 的约定是1亮0灭。按键我们就约定1松开高电平0按下低电平。实际上这个约定可以随便定只要驱动和应用层统一就行。但我们为了保持一致性沿用了 LED 的约定。深入内核源码如果你想看gpiod_get_value()的完整实现它在drivers/gpio/gpiolib.c里intgpiod_get_value(structgpio_desc*desc){/* 省略参数检查和锁操作 */if(test_bit(GPIOD_FLAG_ACTIVE_LOW,desc-flags))return!gpiod_get_raw_value(desc);elsereturngpiod_get_raw_value(desc);}EXPORT_SYMBOL(gpiod_get_value);gpiod_get_raw_value()就直接读 GPIO 控制器的寄存器了具体实现取决于硬件平台。对于 i.MX6ULL它会读写 GPIO 数据寄存器GPIO_DR。关于 GPIO 释放你可能注意到了我们的硬件抽象层没有释放 GPIO 的函数。这是因为我们用了devm_APIgpioddevm_gpiod_get(dev,NULL,GPIOD_IN);devm_前缀表示managed resource托管资源。当设备卸载时内核会自动释放这些资源。所以我们的代码里不需要显式调用gpiod_put()。PS托管资源的好处托管资源机制最大的好处是防止资源泄漏。你想想如果驱动在某个错误路径返回忘记了释放 GPIO这个 GPIO 就永远被占用了。托管资源自动处理这些清理工作少写代码还更安全。当然我们的教程代码为了演示完整流程会显示调用释放函数。但在实际工程里托管资源是更好的选择。小结一下GPIO 输入的核心就这么几个函数/* 1. 获取并配置为输入 */gpiodgpiod_get(dev,NULL,GPIOD_IN);/* 2. 读取状态 */valgpiod_get_value(gpiod);/* 3. 可选释放 */gpiod_put(gpiod);剩下的工作就是怎么用这些基本操作实现一个完整的按键驱动了。下一章我们看轮询方式的实现在read()函数里循环等待按键事件。看完这些代码你会发现GPIO 输入并没有想象中那么复杂。Linux 的 GPIO 子系统把硬件差异都封装好了我们用统一的高层 API 就能操作。这种抽象做得挺到位的。相关阅读现代Qt开发教程新手篇1.15——正则与文本处理 - 相似度 100%通用GUI编程技术——Win32 原生编程实战五十四——Hook 机制 - 相似度 100%通用GUI编程技术——图形渲染实战四十四——D3D12命令列表、队列与围栏GPU同步核心 - 相似度 100%