跳到主要内容

GPIO

1. GPIO 子系统

GPIO(通用输入/输出)是一种通用引脚,可以由微控制器(MCU)或 CPU 控制,具有多种功能,包括高低电平输入检测和输出控制。在 Linux 中,GPIO 引脚可以被导出到用户空间,通过 sysfs 文件系统进行控制,使得 GPIO 引脚可以用于各种用途,如串口通信、I2C、网络通信、电压检测等。

在 Linux 中,有一个专门的 GPIO 子系统驱动框架,用于处理 GPIO 设备。通过这个框架,用户可以轻松地与 CPU 的 GPIO 引脚进行交互。这个驱动框架支持将引脚用于基本的输入和输出功能,同时输入功能还支持中断检测。这使得开发者可以利用 GPIO 子系统轻松地将 GPIO 引脚用于各种用途,实现了灵活性和可编程性。有关 GPIO 子系统的更多详细信息可以在 <Linux内核源码>/Documentation/gpio 目录中找到。

2.GPIO 控制(shell)

2.1 引脚分布

在将 GPIO 引脚导出到用户空间时,通常需要用到引脚编号,我们可以通过接口图进行确定。更多详细功能请参考《资料下载》部分的 40pin 引脚图或 Datasheet 手册。LuckFox Omni3576 引脚图:

2.2 GPIO 编号计算

GPIO对应的引脚编号均在引脚图中标出,您可以直接使用或按照下面方法进行计算。

  • GPIO 有5个 bank:GPIO0~GPIO4,每个 bank 分为4组,共有32个 pin:A0~A7, B0~B7, C0~C7, D0~D7

  • GPIO 的命名方式为 GPIO{bank}_{group}{X},如下所示:

    GPIO0_A0 ~ A7 
    GPIO0_B0 ~ B7
    GPIO0_C0 ~ C7
    GPIO0_D0 ~ D7

    GPIO1_A0 ~ A7
    ....
    GPIO1_D0 ~ D7
    ....
    GPIO4_D0 ~ D7
  • GPIO 编号计算方法如下:

    GPIO 引脚编号计算公式:pin = bank * 32 + number
    GPIO 组内编号计算公式:number = group * 8 + X
    综上:pin = bank * 32 + (group * 8 + X)

    以 GPIO2_D7_d 为例说明,其中:

    • bank :2
    • group :3 (A=0, B=1, C=2, D=3)
    • X :7

    所以 GPIO2_D7_d 的引脚编号为:2 x 32 + ( 3x 8 + 7) = 95

2.3 使用 GPIO sysfs 接口控制 I/O

  1. 将 GPIO 控制从内核空间导出到用户空间(只写)。

    sudo su 
    echo 95 > /sys/class/gpio/export
  2. 取消 GPIO 控制从内核空间到用户空间的导出(只写)。

    echo 95 > /sys/class/gpio/unexport  

2.3.1 设备目录和设备属性

  1. 当你写入 GPIO 编号到 /sys/class/gpio/export 时,内核会将这个 GPIO 导出到用户空间,使之可以通过 /sys/class/gpio/gpio<编号> 目录来进行后续操作(如配置方向、设置或读取电平)。

    root@luckfox:/home/luckfox# ls /sys/class/gpio
    export gpio95 gpiochip0 gpiochip128 gpiochip32 gpiochip509 gpiochip64 gpiochip96 unexport
  2. 在 /sys/class/gpio/gpioN(N=1,2,3,5,...)设备目录下有多种属性,可以通过这些属性控制,从而实现对 GPIO 的控制。

    cd /sys/class/gpio/gpio95
    root@luckfox:/sys/class/gpio/gpio95# ls
    active_low device direction edge power subsystem uevent value
    • direction属性:设置 GPIO 的输入(in)或输出(out)
    • value属性:用于读取输入电平或者控制输出电平,写入或者读取到1表示高电平,写入或者读取到0表示低电平
    • edge属性:用于设置触发电平,只有将 GPIO 设置为中断的时候才会出现和使用这个属性
      • 上升沿触发:rising
      • 下降沿触发:falling
      • 双边沿触发:both
      • 禁用中断:none

2.3.2 控制 GPIO 引脚电平

  1. 设置方向。

    root@luckfox:/sys/class/gpio/gpio95# pwd   # 确保自己在想控制的设备目录下
    /sys/class/gpio/gpio95

    echo out > direction # 设置 GPIO 为输出
    echo in > direction # 设置 GPIO 为输入
  2. 设置value属性来控制 GPIO 电平。

    #输出
    echo 1 > value
    echo 0 > value

    # 输入
    cat value
  3. 注意:可能遇到的错误:

    root@luckfox:~# echo 72 > /sys/class/gpio/export
    bash: echo: write error: Device or resource busy
    • Linux 驱动调试中可能遇到 gpio 无法申请的问题,需要查找 gpio 被哪个驱动占用
    mount -t  debugfs none /media
    cat /media/gpio

    root@luckfox:/# cat /media/gpio
    gpiochip0: GPIOs 0-31, parent: platform/27320000.gpio, gpio0:
    gpio-9 ( |bt_default_wake_host) in lo IRQ
    gpio-14 ( |reset ) out lo ACTIVE LOW

    gpiochip1: GPIOs 32-63, parent: platform/2ae10000.gpio, gpio1:
    gpio-50 ( |bt_default_rts ) out lo
    gpio-55 ( |vcc5v0-host ) out hi
    gpio-60 ( |bt_default_wake ) out hi
    gpio-61 ( |work ) out hi

    gpiochip2: GPIOs 64-95, parent: platform/2ae20000.gpio, gpio2:

    gpiochip3: GPIOs 96-127, parent: platform/2ae30000.gpio, gpio3:
    gpio-104 ( |GTP RST PORT ) in hi
    gpio-118 ( |led ) out hi ACTIVE LOW
    gpio-120 ( |led ) out hi ACTIVE LOW
    gpio-125 ( |vbus5v0-typec ) out lo
    gpio-127 ( |reset ) out lo ACTIVE LOW

    gpiochip4: GPIOs 128-159, parent: platform/2ae40000.gpio, gpio4:
    gpio-128 ( |vcc3v3-m2 ) out hi
    gpio-148 ( |sbu1-dc ) out lo
    gpio-149 ( |sbu2-dc ) out hi

    gpiochip5: GPIOs 509-511, parent: platform/rk806-pinctrl.1.auto, rk806-gpio, can sleep:

3. 使用 Python 程序控制 I/O

  1. 控制一颗 LED 交替闪烁。

    #!/usr/bin/python3
    from periphery import GPIO
    import time

    LED_Pin = 95

    LED_GPIO = GPIO(LED_Pin, "out")

    while True:
    try:
    LED_GPIO.write(True)
    time.sleep(0.5)
    LED_GPIO.write(False)
    time.sleep(0.5)
    except KeyboardInterrupt:
    LED_GPIO.write(False)
    break
    except IOError:
    print ("Error")

    LED_GPIO.close()
  2. 运行程序:

    chmod 777 gpio.py 
    ./gpio.py

4. 使用 C 程序控制 I/O

  1. 下载并解压 C 程序文件,将文件上传到 Omni3576 中(可以参考Samba文件共享)。

  2. 进入工程文件夹,在工程中有sysfs_gpio.c、sysfs_gpio.h、main.c 和 Makefile 四个文件:

    linaro@linaro-alip:~$ cd Luckfox_GPIO_C && ls
    main.c Makefile sysfs_gpio.c sysfs_gpio.h
    linaro@linaro-alip:~/Luckfox_GPIO_C$
  3. 在 sysfs_gpio.h 文件中定义了 Omni3576 的引脚物理编码,并且在主函数 main.c 中调用,可根据自己需求修改:

    #sysfs_gpio.h 
    ....
    #define I2C0_SDA 18 // 3,18
    #define I2C0_SCL 17 // 5,17
    #define UART8_TX 70 // 7,70
    #define GPIO17 98 //11,98
    #define GPIO27 99 //13,99
    #define GPIO22 13 //15.13
    #define SPI0_MOSI 24 //19,24
    #define SPI0_MISO 25 //21,25
    #define SPI0_CLK 23 //23,23
    #define I2C8_SDA 79 //27,79
    #define UART8_RX 71 //29,71
    #define GPIO6 151 //31,151
    #define GPIO13 97 //33,97
    #define SPI4_MISO 138 //35,138
    #define GPIO26 72 //37,72
    #define UART2_TX 141 //8,141
    #define UART2_RX 140 //10,140
    #define PWM2 27 //12,27
    #define GPIO23 12 //16,12
    #define GPIO24 21 //18,21
    #define GPIO25 77 //22,77
    #define SPI0_CS0 22 //24,22
    #define SPI0_CS1 19 //26,19
    #define I2C8_SCL 78 //28,78
    #define GPIO12 96 //32,96
    #define GPIO16 95 //36,95
    #define SPI4_MOSI 137 //38,137
    #define SPI4_CLK 136 //40,136
    ....

    #main.c
    ....
    int x[28] = {I2C0_SDA, I2C0_SCL, UART8_TX, GPIO17, GPIO27,
    GPIO22, SPI0_MOSI, SPI0_MISO, SPI0_CLK, I2C8_SDA,UART8_RX,
    GPIO6, GPIO13, SPI4_MISO, GPIO26,UART2_TX, UART2_RX, PWM2,
    GPIO23, GPIO24, GPIO25, SPI0_CS0, SPI0_CS1, I2C8_SCL, GPIO12,
    GPIO16, SPI4_MOSI, SPI4_CLK};
    ....
  4. 编译程序:

    root@luckfox:/home/luckfox/Luckfox_GPIO_C# make 
    cc -c sysfs_gpio.c -o sysfs_gpio.o
    cc -c main.c -o main.o
    cc sysfs_gpio.o main.o -o sysfs_gpio
  5. 运行程序,可以在终端上查看 GPIO信息,并逐个点亮和熄灭LED:

    root@luckfox:/home/luckfox/Luckfox_GPIO_C# ./sysfs_gpio
    Debug: Export: Pin18
    Debug: Pin18:Output
    Debug: Export: Pin17
    Debug: Pin17:Output
    Debug: Export: Pin70
    Debug: Pin70:Output
    Debug: Export: Pin98
    Debug: Pin98:Output
    Debug: Export: Pin99
    Debug: Pin99:Output
    Debug: Export: Pin13
    Debug: Pin13:Output
    Debug: Export: Pin24
    Debug: Pin24:Output
    Debug: Export: Pin25
    Debug: Pin25:Output
    Debug: Export: Pin23
    Debug: Pin23:Output
    Debug: Export: Pin79
    Debug: Pin79:Output
    Debug: Export: Pin71
    Debug: Pin71:Output
    ....

5. 设备树简介

  1. GPIO DTS 源文件在kernel-6.10/arch/arm64/boot/dts/rockchip/rk3576.dtsi已经定义,我们可以直接调用。

    gpio2: gpio@2ae20000 {
    compatible = "rockchip,gpio-bank";
    reg = <0x0 0x2ae20000 0x0 0x200>;
    interrupts = <GIC_SPI 161 IRQ_TYPE_LEVEL_HIGH>;
    clocks = <&cru PCLK_GPIO2>, <&cru DBCLK_GPIO2>;

    gpio-controller;
    #gpio-cells = <2>;
    gpio-ranges = <&pinctrl 0 64 32>;
    interrupt-controller;
    #interrupt-cells = <2>;
    };
  2. 如果想要设置GPIO输入输出,以控制 LED 为例,可以在设备文件kernel-6.1/arch/arm64/boot/dts/rockchip/luckfox-omni3576.dts文件中定义:

    / {
    model = "Luckfox Omni3576";
    compatible = "luckfox,omni3576", "rockchip,rk3576";
    gpio2pb0:gpio2pb0 {
    pinctrl-names = "default";
    pinctrl-0 = <&gpio2_pb0>;
    gpios = <&gpio2 RK_PB0 GPIO_ACTIVE_HIGH>;
    };
    };

    &pinctrl {
    gpio2-pb0 {
    gpio2_pb0:gpio2-pb0 {
    rockchip,pins = <2 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>;
    };
    };
    };

  3. 如果想要设置GPIO中断,可以在设备文件kernel-6.1/arch/arm64/boot/dts/rockchip/luckfox-omni3576.dts文件中定义:

    / {
    model = "Luckfox Omni3576";
    compatible = "luckfox,omni3576", "rockchip,rk3576";
    gpio2pb0:gpio2pb0 {
    compatible = "rockchip,gpio-bank";
    pinctrl-names = "default";
    pinctrl-0 = <&gpio2_pb0>;
    interrupt-parent = <&gpio2>;
    interrupts = <RK_PB0 IRQ_TYPE_EDGE_RISING>;
    };
    };

    &pinctrl {
    gpio2-pb0 {
    gpio2_pb0:gpio2-pb0 {
    rockchip,pins = <2 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>;
    };
    };
    };
    • IRQ_TYPE_NONE :无定义中断触发类型

    • IRQ_TYPE_EDGE_RISING:上升沿触发

    • IRQ_TYPE_EDGE_FALLING:下降沿触发

    • IRQ_TYPE_EDGE_BOTH:上升沿和下降沿都触发

    • IRQ_TYPE_LEVEL_HIGH:高电平触发

    • IRQ_TYPE_LEVEL_LOW :低电平触发