文章

在 jetson orin nx 上 使用 sysfs 操作 gpio

在 jetson orin nx 上 使用 sysfs 操作 gpio

在前面我介绍了如何查早资料并通过 libgpiod 来操作 GPIO。本篇主要是记录一下使用 sysfs 操作 GPIO,下面就以 jetson orin nx 为例讲述一下如何使用。

怎样计算 GPIO index?

在 “在 jetson orin nx 上 使用 libgpiod 库操作 gpio“ 中,通过 libgpiod 的命令行工具 gpioinfo 是可以输出 GPIO port 对应的 line , 然后通过 line 来控制 GPIO;但是在旧的系统中是没有 libgpiod 的支持的,所以只能通过公式换算 gpio index, 用来通过 sysfs 控制 GPIO;下面是换算的方法:

  1. Orin GPIO 以 bank(组) 分组,每个 bank 最多包含 8 个引脚。GPIO 的 bank 和引脚信息是计算其索引的基础。GPIO 表示为X.Y,其中:
    • X 表示对 bank 进行编号,可以具有以下值:PA、PB、…、PY 或 PAA、…PFF,如下表所列。
    • 对于采用 P* 命名方案的 bank,必须忽略“P”,即“PA”指的是 bank A。
    • Y 表示 bank 内引脚的编号。例如,00 表示引脚 0,01 表示引脚 1,……,07 表示引脚 7。
  2. 这些 bank 与一个 base 相关联,也可以按顺序转换为 bank_index,如下表所示:

如果在内核中进行了更改,则 base 编号可能会发生变化,可以使用 dmesg 日志进行识别:

1
2
3
nvidia@tegra-ubuntu:~$ cat /sys/kernel/debug/gpio | grep gpiochip
[ 9.965908] gpiochip0: registered GPIOs 348 to 511 on tegra234-gpio
[ 9.970024] gpiochip1: registered GPIOs 316 to 347 on tegra234-gpio-aon

从上面的日志可以看出,tegra234-gpio 的 base 为 348,tegra234-gpio-aon 的 base 为 316。

  1. GPIO index 可以计算为:GPIO_index = base + bank_start_index + pin。

以下是计算 PX.01 的 GPIO index 的示例。

PX.01 代表 bank X 和引脚 1。 Bank X 的 bank_index 为 114,base 为 348(tegra-gpio base) GPIO index 为 463(463 = 348 + 114 + 1)

怎样控制 GPIO ?

下面是使用 c++ 实现的控制接口

gpio.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#ifndef __GPIO_H__
#define __GPIO_H__

#include <cstdint>
#include <string>

class Gpio {
public:
    enum class Mode 
        : uint8_t {
        OUTPUT,  // 输出
        INPUT    // 输入
    };
    enum class Value
        : uint8_t {
        LOW  = 0, // 低电平
        HIGH = 1  // 高电平
    };
    enum class Trigger
        : uint8_t {
        NONE,
        RISING,   // 上升沿
        FALLING,  // 下降沿
        BOTH      // 双边沿
    };
    Gpio(const std::string &port, int gpio_num);
    // 初始化, export
    bool init();
    // 反初始化,unexport
    bool deinit();
    // 设置方向
    bool set_direction(const Mode &mode);
    // 设置触发方式
    bool set_trigger(const Trigger& trigger);
    // 设置电平
    bool set_value(const Value &value);
    // 获取电平
    auto get_value() -> Value;

private:
    int m_gpio_num;
    std::string m_port;
};

#endif //__GPIO_H__

gpio.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
#include "gpio.h"
#include "logger.h" // 我在其他文章中提到过,自己封装的 spdlog
#include "spdlog/fmt/fmt.h"
#include <fstream>
#include <string>
#include <sys/stat.h>
#include <unistd.h>

Gpio::Gpio(const std::string &port, int gpio_num)
    : m_port(port)
    , m_gpio_num(gpio_num)
    {}

bool Gpio::init() {
    struct stat st;
    auto gpio_dir = fmt::format("/sys/class/gpio/{}", m_port);
    // 检查 GPIO 是否已导出
    if (stat(gpio_dir.c_str(), &st) == 0) {
        return true;
    }
    std::ofstream export_file("/sys/class/gpio/export");
    if (!export_file) {
        LOGW("Failed to open export file");
        return false;
    }
    export_file << m_gpio_num;
    if (export_file.fail()) {
        LOGW("Failed to write export file");
        return false;
    }
    // 等待导出完成
    usleep(200000);
    return true;
}

bool Gpio::deinit() {
    struct stat st;
    auto gpio_dir = fmt::format("/sys/class/gpio/{}", m_port);
    // 检查 GPIO 是否已取消导出
    if (stat(gpio_dir.c_str(), &st) != 0) {
        return true;
    }
    std::ofstream unexport_file("/sys/class/gpio/unexport");
    if (!unexport_file) {
        LOGW("Failed to open unexport file");
        return false;
    }
    unexport_file << m_gpio_num;
    if (unexport_file.fail()) {
        LOGW("Failed to write unexport file");
        return false;
    }
    usleep(100000);
    return true;
}

bool Gpio::set_direction(const Mode &mode) {
    auto path = fmt::format("/sys/class/gpio/{}/direction", m_port);
    std::ofstream file(path);
    if (!file) {
        LOGW("Failed to open {}", path);
        return false;
    }
    switch (mode) {
        case Mode::OUTPUT:
            file << "out";
            break;
        case Mode::INPUT:
            file << "in";
            break;
        default:
            return false;
    }
    return !file.fail();
}

bool Gpio::set_trigger(const Trigger &trigger) {
    auto path = fmt::format("/sys/class/gpio/{}/edge", m_port);
    std::ofstream file(path);
    if (!file) {
        LOGW("Failed to open {}", path);
        return false;
    }
    switch (trigger) {
        case Trigger::NONE:
            file << "none";
            break;
        case Trigger::RISING:
            file << "rising";
            break;
        case Trigger::FALLING:
            file << "falling";
            break;
        case Trigger::BOTH:
            file << "both";
            break;
        default:
            return false;
    }
    return !file.fail();
}

bool Gpio::set_value(const Value &value) {
    auto path = fmt::format("/sys/class/gpio/{}/value", m_port);
    std::ofstream file(path);
    if (!file) {
        LOGW("Failed to open {}", path);
        return false;
    }

    file << static_cast<int>(value);
    return !file.fail();
}

Gpio::Value Gpio::get_value() {
    auto path = fmt::format("/sys/class/gpio/{}/value", m_port);
    std::ifstream file(path);
    if (!file) {
        LOGW("Failed to open {}", path);
        return Value::LOW; // 默认返回低电平
    }
    int val;
    file >> val;
    return (val == 0) ? Value::LOW : Value::HIGH;
}

使用

1
2
3
4
5
6
7
8
9
10
11
12
// 注意:此代码只在米尔的 STM32MP257 开发板上验证过,此板运行基于 debian 的衍生 linux 系统;
// 由于每块板子的分组习惯以及命名习惯略有差异,所以,代码不一定完全适配,但是思路是一样的,按照
// 同样的思路更改即可。
#include "gpio.h"

int main() {
    Gpio gpio("PX.01", 463);
    gpio.init();
    gpio.set_direction(Gpio::Mode::OUTPUT);
    gpio.set_value(Gpio::Value::LOW);
    return 0;
}
本文由作者按照 CC BY 4.0 进行授权