GPIO 子系统与 pinctrl

发布时间:2026/7/10 7:14:13
GPIO 子系统与 pinctrl GPIO 子系统与 pinctrlGPIO 是嵌入式最常用的外设。Linux 有 gpiolib 子系统统一管理 GPIO还有 pinctrl 管理引脚复用。这篇讲 GPIO 子系统的用法设备树怎么描述、驱动里怎么申请设置 GPIO、怎么申请中断以及 pinctrl 的作用。大家好我是黒漂技术佬。GPIO 是嵌入式里最基础的外设LED、按键、继电器、片选信号……都用 GPIO。Linux 有 gpiolib 子系统统一管理所有 GPIO不用自己直接读写寄存器。还有 pinctrl 子系统管引脚复用一个引脚可以是 GPIO也可以是 I2C、SPI、UART 的功能脚和电气属性上下拉、驱动能力。这篇讲 GPIO 和 pinctrl 的使用。一、GPIO 子系统概述gpiolib 的作用统一的 GPIO 操作接口不用管底层是哪个控制器申请/释放 GPIO防止冲突设置方向、读值、写值GPIO 映射到中断sysfs 用户空间接口现在推荐用 libgpiod两种编号方式全局编号老方式每个 GPIO 有个全局数字号比如 GPIO1_3 就是 32335。现在不推荐用全局号不同芯片编号不一样不通用。GPIO 描述符推荐用struct gpio_desc *指针表示从设备树获取更通用。二、设备树中的 GPIOGPIO 控制器SoC 的 GPIO 控制器在设备树里描述gpio0: gpio10000000 { compatible vendor,gpio-controller; reg 0x10000000 0x1000; gpio-controller; #gpio-cells 2; interrupt-controller; #interrupt-cells 2; };gpio-controller标记这是个 GPIO 控制器#gpio-cells引用这个控制器的 GPIO 需要几个 cell一般 2 个第一个引脚号第二个标志极性、上下拉等使用 GPIO 的设备节点用gpios属性引用my_led { compatible vendor,my-led; gpios gpio0 5 GPIO_ACTIVE_HIGH; // 意思用 gpio0 控制器的第 5 号引脚高电平有效 };GPIO 标志常用的GPIO_ACTIVE_HIGH 高电平有效 GPIO_ACTIVE_LOW 低电平有效 GPIO_PUSH_PULL 推挽 GPIO_OPEN_DRAIN 开漏 GPIO_OPEN_SOURCE 开源命名 GPIO多个 GPIO 的时候可以命名my_device { compatible vendor,my-dev; reset-gpios gpio0 3 GPIO_ACTIVE_LOW; led-gpios gpio0 5 GPIO_ACTIVE_HIGH; key-gpios gpio0 7 GPIO_ACTIVE_LOW; };名字随便起后缀-gpios驱动里按名字取。三、驱动里操作 GPIO描述符方式推荐头文件#includelinux/gpio/consumer.h获取 GPIO按名字获取structgpio_desc*gpio;gpiodevm_gpiod_get(dev,reset,GPIOD_OUT_LOW);if(IS_ERR(gpio))returnPTR_ERR(gpio);第二个参数是名字对应设备树里的reset-gpios自动加 -gpios 后缀。第三个参数是初始方向和值GPIOD_ASIS不变GPIOD_IN输入GPIOD_OUT_LOW输出初始低GPIOD_OUT_HIGH输出初始高还可以或上GPIOD_ACTIVE_LOW等标志设置方向gpiod_direction_input(gpio);// 设为输入gpiod_direction_output(gpio,1);// 设为输出初始值1读写值// 读intvalgpiod_get_value(gpio);// 写gpiod_set_value(gpio,1);// 输出高gpiod_set_value(gpio,0);// 输出低释放gpiod_put(gpio);用 devm_gpiod_get 的话自动释放不用手动 put。多个 GPIOstructgpio_descs*gpios;gpiosdevm_gpiod_get_array(dev,led,GPIOD_OUT_LOW);// gpios-ndescs 是数量// gpios-desc[0], desc[1]... 分别操作四、旧的 GPIO 编号方式了解老驱动用全局编号的方式#includelinux/gpio.hintgpio_num123;gpio_request(gpio_num,my_dev);gpio_direction_input(gpio_num);gpio_direction_output(gpio_num,1);gpio_get_value(gpio_num);gpio_set_value(gpio_num,0);gpio_free(gpio_num);不推荐了新驱动都用 gpiod 描述符方式和设备树配合更好。五、GPIO 中断GPIO 可以配置成中断源。把 GPIO 转成中断号intirqgpiod_to_irq(gpio);拿到 irq 号之后就可以用 request_irq 注册中断处理函数了跟普通中断一样。设备树里配置触发方式key-gpios gpio0 7 GPIO_ACTIVE_LOW;中断触发方式上升沿/下降沿/电平在 request_irq 时指定request_irq(irq,handler,IRQF_TRIGGER_FALLING,my_key,dev);完整示例按键中断structgpio_desc*key_gpio;intirq;// 获取 GPIOkey_gpiodevm_gpiod_get(dev,key,GPIOD_IN);if(IS_ERR(key_gpio))returnPTR_ERR(key_gpio);// 转中断号irqgpiod_to_irq(key_gpio);if(irq0)returnirq;// 申请中断devm_request_irq(dev,irq,key_irq_handler,IRQF_TRIGGER_FALLING,my_key,dev);按键按下触发下降沿中断在中断处理函数里做防抖、上报事件等。六、pinctrl 子系统作用pinctrlpin control子系统负责引脚复用mux一个引脚可以配置成 GPIO、I2C、SPI、UART 等不同功能引脚配置上下拉、驱动强度、开漏、施密特触发等电气属性为什么需要SoC 的引脚是复用的比如某个引脚可以当普通 GPIO可以当 I2C 的 SDA可以当 SPI 的 MOSIpinctrl 负责切换引脚的功能模式和电气属性。设备树中的 pinctrl控制器端定义引脚配置pinctrl20000000 { uart0_pins: uart0 { mux { pins PA0, PA1; function uart0; }; config { pins PA0, PA1; bias-pull-up; drive-strength 8; }; }; gpio_key_pins: gpio-key { pins PA2; function gpio; bias-pull-up; }; };设备节点引用uart0: uart10000000 { pinctrl-names default; pinctrl-0 uart0_pins; };pinctrl-names是状态名pinctrl-0对应第一个状态的配置。多个状态比如休眠的时候引脚换个配置pinctrl-names default, sleep; pinctrl-0 uart0_default; pinctrl-1 uart0_sleep;驱动里可以切换状态pinctrl_pm_select_default_state(dev);pinctrl_pm_select_sleep_state(dev);驱动里一般不用手动操作大部分情况设备树配好 pinctrl驱动框架会自动设置 default 状态。驱动不用直接操作 pinctrl。只有需要动态切换引脚功能的时候才手动操作。七、常见引脚配置属性设备树 pinctrl 里常用的配置不同厂商命名可能略有差异属性说明bias-disable不上拉不下拉bias-pull-up上拉bias-pull-down下拉drive-push-pull推挽输出drive-open-drain开漏输出drive-strength mA驱动能力毫安input-enable使能输入input-disable禁止输入slew-rate n压摆率具体支持哪些要看 pinctrl 驱动的文档不同 SoC 不一样。八、用户空间操作 GPIOsysfs 方式旧逐渐淘汰# 导出 GPIO 123echo123/sys/class/gpio/export# 设方向echoout/sys/class/gpio/gpio123/direction# 写值echo1/sys/class/gpio/gpio123/value# 读值cat/sys/class/gpio/gpio123/value# 取消导出echo123/sys/class/gpio/unexportlibgpiod 方式新推荐用户空间 C 库通过字符设备操作 GPIO# 列出所有 GPIO 控制器gpiodetect# 查看某个 bank 的信息gpioinfo gpiochip0# 读值gpioget gpiochip05# 写值gpioset gpiochip051# 监控中断gpiomon gpiochip07应用开发推荐用 libgpiod比 sysfs 好用。九、完整示例LED 驱动设备树my_led { compatible vendor,my-led; led-gpios gpio0 5 GPIO_ACTIVE_HIGH; status okay; };驱动#includelinux/module.h#includelinux/platform_device.h#includelinux/gpio/consumer.h#includelinux/of.hstructled_dev{structgpio_desc*gpio;};staticconststructof_device_idled_of_match[]{{.compatiblevendor,my-led},{}};MODULE_DEVICE_TABLE(of,led_of_match);staticssize_tled_show(structdevice*dev,structdevice_attribute*attr,char*buf){structled_dev*leddev_get_drvdata(dev);returnsprintf(buf,%d\n,gpiod_get_value(led-gpio));}staticssize_tled_store(structdevice*dev,structdevice_attribute*attr,constchar*buf,size_tcount){structled_dev*leddev_get_drvdata(dev);intval;if(kstrtoint(buf,10,val))return-EINVAL;gpiod_set_value(led-gpio,val?1:0);returncount;}staticDEVICE_ATTR(brightness,0644,led_show,led_store);staticintled_probe(structplatform_device*pdev){structled_dev*led;intret;leddevm_kzalloc(pdev-dev,sizeof(*led),GFP_KERNEL);if(!led)return-ENOMEM;// 获取 GPIO默认输出低led-gpiodevm_gpiod_get(pdev-dev,led,GPIOD_OUT_LOW);if(IS_ERR(led-gpio))returnPTR_ERR(led-gpio);platform_set_drvdata(pdev,led);// 创建 sysfs 属性retdevice_create_file(pdev-dev,dev_attr_brightness);if(ret)returnret;return0;}staticintled_remove(structplatform_device*pdev){device_remove_file(pdev-dev,dev_attr_brightness);return0;}staticstructplatform_driverled_driver{.probeled_probe,.removeled_remove,.driver{.namemy-led,.of_match_tableled_of_match,},};module_platform_driver(led_driver);MODULE_LICENSE(GPL);MODULE_AUTHOR(Heipiao);十、常见坑坑 1GPIO 被占用GPIO 已经被别的驱动申请了再申请会失败。检查设备树里有没有其他地方用了同一个引脚。坑 2pinctrl 没配对引脚没切到 GPIO 功能操作 GPIO 没反应。先确认 pinctrl 配置正确引脚复用对了。坑 3有效极性搞反设备树里 GPIO_ACTIVE_LOW驱动里写 1 实际输出低电平。gpiod 接口会自动处理极性按逻辑值操作就行不用关心物理电平。坑 4开漏输出没上拉开漏模式外部要接上拉电阻不然输出高电平是浮空的。坑 5中断触发方式和电平不匹配按键上拉的话按下是低电平应该用下降沿触发。搞反了会一直进中断或者不进中断。坑 6防抖机械按键按下会抖动直接读值会跳变。要么硬件防抖加电容要么软件防抖定时器延时再读。十一、本篇小结gpiolibLinux 统一 GPIO 子系统推荐用 gpiod 描述符方式devm_gpiod_get设备树用xxx-gpios属性描述 GPIO驱动按名字获取GPIO 操作gpiod_direction_input/output、gpiod_get_value/set_valueGPIO 中断gpiod_to_irq 转中断号然后 request_irq 注册pinctrl 子系统管引脚复用mux和电气属性上下拉、驱动强度等pinctrl 一般在设备树配置好驱动框架自动设置 default 状态用户空间操作 GPIO新的用 libgpiod旧的 sysfs 逐渐淘汰注意 GPIO 极性ACTIVE_LOW/HIGH、防抖、引脚复用配置下一篇讲I2C 设备驱动开发I2C 子系统、i2c_driver、设备树匹配、读写寄存器。我是黒漂技术佬。