ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Jetson Orin Nano GPIO配置避坑指南:从寄存器到设备树实战

Jetson Orin Nano GPIO配置避坑指南:从寄存器到设备树实战 1. 为什么 Jetson Orin Nano 的 GPIO 配置让人头疼拿到 Jetson Orin Nano 的第一天很多人都会觉得这板子跟树莓派差不多不就是拉高拉低一个引脚的事吗结果一上手就发现/sys/class/gpio这个在树莓派上玩得飞起的老朋友在 Orin Nano 上要么找不到对应的引脚号要么写进去毫无反应要么干脆报个Device or resource busy。更让人抓狂的是网上搜到的教程一半是 Xavier NX 的一半是 Nano 老款的引脚编号表对不上寄存器地址也对不上照着抄直接翻车。这个问题的根源在于Jetson Orin 系列包括 Orin Nano、Orin NX、AGX Orin的 GPIO 管理机制跟早期的 Jetson Nano 有了本质区别。早期 Nano 的引脚复用相对简单很多引脚默认就是 GPIO 功能你直接操作就行。而 Orin 系列引入了更复杂的Pinmux引脚复用控制器和GPIO 控制器的分层架构一个物理引脚能不能当 GPIO 用取决于三件事Pinmux 有没有把它配成 GPIO 功能、GPIO 控制器有没有使能对应的线、设备树里有没有正确声明。这三层任何一层没打通你在用户空间怎么折腾都是白费力气。我前后在 Orin Nano 上折腾了大概两周时间从最开始的devmem直接怼寄存器到后来老老实实改设备树、配 Pinmux中间踩的坑足够写一篇避坑指南了。这篇文章就是把这些经验整理出来面向的是需要在 Orin Nano 上做 GPIO 控制的嵌入式开发者不管你是要控制一个继电器、读一个按键、还是驱动一个自定义的外设模块这套流程都能直接复用。我会从最底层的寄存器操作讲起一路讲到设备树配置和用户空间验证把每一层的原理和实操都拆开说清楚。2. 先搞清楚 Orin Nano 的 GPIO 架构再动手2.1 三层架构Pinmux、GPIO 控制器、设备树Orin Nano 的 GPIO 体系可以类比成一栋三层楼的建筑。最底层是Pinmux 层它决定了每个物理引脚当前连接的是哪个功能模块——可能是 GPIO、可能是 I2C 的 SDA、可能是 PWM 输出。这一层由 Pinmux 寄存器控制每个引脚对应一组寄存器位写入不同的值就切换到不同的功能。中间层是GPIO 控制器Orin Nano 内部有多个 GPIO 控制器实例比如gpio2200000这类每个控制器管理一组 GPIO 线负责实际的电平读写和中断管理。最上层是设备树它把前两层的信息组织起来告诉内核哪个引脚是什么功能、属于哪个控制器、用户空间怎么访问。很多人一上来就想在用户空间用echo命令操作 GPIO结果发现/sys/class/gpio里根本没有想要的引脚。这是因为内核在启动时根据设备树的描述来注册 GPIO如果设备树里没声明某个引脚为 GPIO 功能内核就不会把它暴露到用户空间。所以正确的顺序是先确认硬件设计上这个引脚要做什么然后在设备树里配好 Pinmux 和 GPIO 控制器最后才是用户空间操作。2.2 为什么 /sys/class/gpio 在 Orin Nano 上不好使/sys/class/gpio这套接口是 Linux 内核早期提供的 GPIO 用户空间访问方式它的设计假设是 GPIO 编号是全局唯一的、固定的。但在 Orin Nano 上GPIO 编号是动态分配的取决于设备树里 GPIO 控制器的gpio-ranges属性和base值。而且 Orin 系列的 Pinmux 默认配置里很多引脚并没有被配成 GPIO 功能所以即使你找到了编号操作也可能无效。另外从 Linux 4.8 开始内核引入了新的GPIO character device接口/dev/gpiochipN这是官方推荐的用户空间访问方式。Orin Nano 的 L4T 内核默认就支持这个接口用libgpiod工具集操作比老的 sysfs 方式稳定得多。我实测下来用gpioset和gpioget命令操作 Orin Nano 的 GPIO成功率远高于直接写/sys/class/gpio。2.3 引脚编号的坑从物理引脚到 GPIO 编号的映射Orin Nano 的 40-pin 排针上每个物理引脚对应的 GPIO 编号并不是连续的也不是简单的线性映射。比如物理引脚 7 对应的是GPIO09在设备树里的标签可能是GPIO09但到了用户空间它的全局编号可能是 416 或者别的数字。这个映射关系取决于设备树里 GPIO 控制器的gpio-ranges定义。我建议的做法是不要死记硬背编号而是通过gpioinfo命令查看当前系统里所有 GPIO 控制器的线和它们的名字。gpioinfo会列出每个gpiochip下的所有线包括线的名字、方向、使用状态。你可以根据线的名字比如GPIO09来定位到具体的gpiochip和线偏移然后用gpioset操作。这样即使换了内核版本或者改了设备树只要线的名字不变你的操作逻辑就不用改。3. 从 devmem 直接操作寄存器开始理解底层3.1 devmem 是什么为什么先用它devmem是一个用户空间工具可以直接读写物理内存地址。在嵌入式开发里它常被用来快速验证寄存器操作不需要写内核模块也不需要改设备树。对于 Orin Nano 的 GPIO 来说用devmem可以直接操作 Pinmux 寄存器和 GPIO 控制器的寄存器看到最底层的效果。我一开始就是用devmem来验证硬件是否正常的。比如我想确认某个引脚能不能被拉高就直接找到对应的 GPIO 控制器的输出寄存器地址用devmem写进去然后用万用表量电压。如果能拉高说明硬件通路没问题问题出在软件配置上如果拉不高那可能是 Pinmux 没配对或者硬件本身有问题。这种方法虽然原始但排查问题非常直接。3.2 找到正确的寄存器地址Orin Nano 的 GPIO 控制器寄存器地址在技术参考手册TRM里有详细说明。以常见的gpio2200000控制器为例它的基地址是0x02200000每个 GPIO 端口有一组寄存器包括输出使能、输出值、输入值、中断配置等。每个端口通常管理 8 个引脚寄存器的偏移量根据端口号计算。具体来说假设你要操作GPIO09首先要在 TRM 里找到它属于哪个端口比如PORT_A然后找到PORT_A的基地址偏移。输出使能寄存器的偏移通常是0x00输出值寄存器是0x04输入值寄存器是0x08。你要先把对应位在输出使能寄存器里置 1然后在输出值寄存器里写 1 或 0 来控制电平。这里有个细节Orin Nano 的 GPIO 寄存器是 32 位的但每个端口只用了低 8 位对应 8 个引脚。写的时候要注意不要影响其他位最好用读-改-写的方式而不是直接写一个绝对值。我一开始就是直接写0xFFFFFFFF结果把其他引脚的配置也改了导致系统行为异常。3.3 devmem 操作的实操步骤和风险用devmem操作寄存器的步骤大概是这样的先确认引脚对应的 GPIO 控制器和端口然后计算寄存器的物理地址接着用devmem读取当前值修改目标位再写回去。比如# 读取输出使能寄存器 devmem 0x02200000 # 假设返回 0x00000000表示所有引脚都是输入 # 设置 bit 0 为输出 devmem 0x02200000 32 0x00000001 # 设置输出值为高 devmem 0x02200004 32 0x00000001但这里有个大坑Pinmux 必须先配好。如果 Pinmux 没有把该引脚配成 GPIO 功能你写 GPIO 控制器的寄存器是没用的因为引脚根本没连到 GPIO 模块上。我一开始不知道这一点对着 GPIO 寄存器折腾了半天万用表量出来电压一直是 0后来才发现是 Pinmux 的问题。注意devmem操作的是物理地址权限要求是 root。而且直接操作寄存器有风险写错地址可能导致系统崩溃或者硬件损坏。建议只在调试阶段用正式产品一定要通过设备树和标准 GPIO 接口来操作。4. Pinmux 配置让引脚变成 GPIO 的关键一步4.1 Pinmux 寄存器的作用和查找方法Pinmux 寄存器决定了每个物理引脚的功能选择。Orin Nano 的 Pinmux 寄存器分布在0x02430000到0x02440000这个地址范围内具体范围以 TRM 为准每个引脚对应一个 32 位寄存器寄存器的值决定了该引脚的功能。比如值0x00可能是 GPIO0x01可能是 I2C0x02可能是 PWM具体定义要看 TRM 里的 Pinmux 表格。查找某个引脚的 Pinmux 寄存器地址最可靠的方法是查 TRM 的 Pinmux 章节。TRM 里会有一个大表格列出每个引脚的名字、默认功能、可选的复用功能、对应的寄存器地址和偏移。比如GPIO09可能对应PADCTL_GPIO09_0这个寄存器地址是0x02434000之类的。你需要根据硬件设计确定这个引脚要做什么功能然后写入对应的值。4.2 用 devmem 临时修改 Pinmux 验证功能在正式改设备树之前我建议先用devmem临时改一下 Pinmux 寄存器验证硬件通路是否正常。比如# 读取 GPIO09 的 Pinmux 寄存器 devmem 0x02434000 # 假设返回 0x00000001表示当前是 I2C 功能 # 改成 GPIO 功能具体值查 TRM devmem 0x02434000 32 0x00000000改完之后再用devmem操作 GPIO 控制器的寄存器看能不能拉高拉低。如果能说明硬件没问题接下来就可以放心地改设备树了。如果改了 Pinmux 还是不行那可能是 GPIO 控制器没使能或者引脚被其他驱动占用了。这里有个经验Orin Nano 的 Pinmux 寄存器有些是带锁的写之前需要先解锁。解锁的方法通常是往一个特定的寄存器写一个魔术数字具体要看 TRM。我遇到过几次写 Pinmux 没反应的情况后来发现是没解锁折腾了好久。4.3 Pinmux 配置的持久化设备树才是正解用devmem改 Pinmux 只是临时的重启就没了。要让配置持久化必须在设备树里声明。Orin Nano 的设备树里Pinmux 配置通常在pinmux2430000这个节点下每个引脚有一个子节点比如pinmux: pinmux2430000 { gpio09_pinmux: gpio09 { nvidia,pins gpio09; nvidia,function gpio; nvidia,pull TEGRA_PIN_PULL_NONE; nvidia,tristate TEGRA_PIN_DISABLE; nvidia,enable-input TEGRA_PIN_ENABLE; }; };然后在 GPIO 控制器节点里引用这个 Pinmux 配置声明这个引脚是 GPIO 功能。这样内核启动时就会自动配置好 Pinmux你就不需要手动用devmem了。5. 设备树配置实战从零添加一个 GPIO5.1 找到正确的设备树文件Orin Nano 的设备树文件在 L4T 内核源码的hardware/nvidia/platform/t23x/目录下具体路径取决于你的 L4T 版本。比如 L4T 35.x 的路径可能是hardware/nvidia/platform/t23x/p3768-0000-a0/。你需要找到跟你的载板对应的设备树文件通常是tegra234-p3767-0000-*.dts这类命名。如果你用的是官方的 Orin Nano 开发套件设备树文件可能是tegra234-p3767-0003-p3768-0000-a0.dts。如果你用的是第三方载板设备树文件可能不同需要跟载板厂商确认。找到文件后你可以直接修改也可以用overlay的方式添加自己的配置后者更干净升级 L4T 时不会冲突。5.2 添加 Pinmux 配置节点在设备树里添加 Pinmux 配置首先要找到pinmux2430000节点然后在里面添加你的引脚配置。比如你要把GPIO09配成 GPIO 功能pinmux2430000 { gpio09_pinmux: gpio09 { nvidia,pins gpio09; nvidia,function gpio; nvidia,pull TEGRA_PIN_PULL_NONE; nvidia,tristate TEGRA_PIN_DISABLE; nvidia,enable-input TEGRA_PIN_ENABLE; }; };这里的nvidia,function要填gpionvidia,tristate要填TEGRA_PIN_DISABLE表示输出使能nvidia,enable-input填TEGRA_PIN_ENABLE表示输入使能。如果你只需要输出输入使能可以关掉但一般建议都打开方便调试。5.3 在 GPIO 控制器里声明引脚Pinmux 配好后还要在 GPIO 控制器节点里声明这个引脚。Orin Nano 的 GPIO 控制器节点通常是gpio2200000你需要在它的子节点里添加你的 GPIOgpio2200000 { my_gpio: my-gpio { gpio-hog; gpios TEGRA234_MAIN_GPIO(A, 0) 0; output-high; line-name my-gpio; }; };这里的TEGRA234_MAIN_GPIO(A, 0)是宏定义表示端口 A 的第 0 个引脚。output-high表示默认输出高电平line-name是用户空间看到的名字。这样配置后内核启动时就会自动把这个引脚配成输出高并且在/dev/gpiochipN里能看到名为my-gpio的线。5.4 编译和部署设备树改完设备树后需要重新编译。在 L4T 内核源码目录下用make dtbs命令编译设备树生成的.dtb文件在arch/arm64/boot/dts/目录下。然后把.dtb文件拷贝到板子的/boot/目录修改extlinux.conf里的FDT行指向新的.dtb文件重启即可。如果你用的是overlay方式需要把.dtbo文件放到/boot/目录然后在extlinux.conf里添加OVERLAYS行。overlay的好处是不用改原始设备树升级 L4T 时只需要重新编译 overlay 就行。注意编译设备树时要用跟板子上运行的内核版本匹配的源码否则可能出现兼容性问题。我遇到过用旧版源码编译的设备树在新内核上启动失败的情况排查了好久才发现是版本不匹配。6. 用户空间验证gpioset、gpioget 和 libgpiod6.1 安装 libgpiod 工具集libgpiod是 Linux 官方推荐的 GPIO 用户空间库提供了一组命令行工具包括gpiodetect、gpioinfo、gpioset、gpioget等。在 Orin Nano 上你可以用apt安装sudo apt update sudo apt install gpiod libgpiod-dev安装完成后先用gpiodetect看看系统里有哪些 GPIO 控制器gpiodetect输出可能是这样的gpiochip0 [2200000.gpio] (32 lines) gpiochip1 [2210000.gpio] (32 lines) ...每个gpiochip对应一个 GPIO 控制器括号里是控制器的设备树路径后面的数字是线的数量。6.2 用 gpioinfo 查看引脚状态gpioinfo命令会列出每个gpiochip下的所有线包括线的名字、方向、使用状态。比如gpioinfo gpiochip0输出可能是line 0: my-gpio my-gpio output active-high [used] line 1: unnamed unused input active-high ...这里line 0的名字是my-gpio方向是output状态是used说明这个引脚已经被设备树里的gpio-hog占用了。如果你想在用户空间操作这个引脚需要先把gpio-hog去掉或者用gpioset直接操作。6.3 用 gpioset 和 gpioget 操作 GPIOgpioset用来设置 GPIO 的输出值gpioget用来读取输入值。比如# 设置 gpiochip0 的第 0 线为高电平 gpioset gpiochip0 01 # 设置 gpiochip0 的第 0 线为低电平 gpioset gpiochip0 00 # 读取 gpiochip0 的第 0 线 gpioget gpiochip0 0gpioset默认会在命令执行后释放 GPIO如果你需要保持电平可以用-m参数让它在后台保持gpioset -m time -s 10 gpiochip0 01这表示保持 10 秒后释放。如果你需要一直保持可以用-m wait让它等待信号。6.4 常见问题权限和占用用gpioset操作 GPIO 时可能会遇到权限问题。默认情况下只有 root 用户才能操作 GPIO。你可以把用户加到gpio组或者用sudo运行。另外如果 GPIO 已经被其他驱动占用比如被gpio-hog占用gpioset会报Device or resource busy。这时候你需要先释放占用或者用gpioinfo确认哪个驱动占用了它。我遇到过一种情况设备树里配了gpio-hog但用户空间又想操作同一个引脚结果一直报 busy。后来把gpio-hog去掉改成在用户空间用gpioset操作问题就解决了。所以如果你需要在用户空间灵活控制建议不要在设备树里配gpio-hog而是让内核只注册 GPIO 线不自动占用。7. 常见问题与排查技巧实录7.1 引脚无反应从 Pinmux 到 GPIO 控制器的排查链路引脚操作没反应是最常见的问题排查思路应该是从底层往上查。先用万用表量引脚电压如果电压不变说明 Pinmux 可能没配对。用devmem读一下 Pinmux 寄存器确认功能选择是否正确。如果 Pinmux 对了再读 GPIO 控制器的输出使能寄存器确认引脚是否被配成输出。如果输出使能也对了再读输出值寄存器确认值是否写进去了。如果值写进去了但电压还是不变那可能是硬件问题比如引脚被外部电路拉死或者引脚损坏。我整理了一个排查速查表现象可能原因排查方法引脚电压不变Pinmux 未配成 GPIO用 devmem 读 Pinmux 寄存器引脚电压不变GPIO 控制器未使能读输出使能寄存器引脚电压不变引脚被外部电路拉死断开外部电路再测gpioset 报 busy引脚被驱动占用用 gpioinfo 查看占用者gpioset 报权限错误用户不在 gpio 组加 sudo 或加组设备树改了没生效dtb 未更新检查 extlinux.conf 的 FDT 行7.2 设备树编译报错常见语法和引用问题设备树编译报错通常是因为语法错误或者引用未定义。比如nvidia,pins的值写错了或者引用了不存在的宏定义。编译时用make dtbs会给出具体的错误行和错误信息根据提示修改就行。常见的错误包括缺少分号、括号不匹配、引用了未定义的节点、宏定义拼写错误。我遇到过一种情况在overlay里引用了一个在主设备树里定义的标签但编译时提示找不到。后来发现是overlay编译时需要先编译主设备树生成符号表然后overlay才能引用。解决方法是在编译overlay之前先make dtbs编译主设备树。7.3 系统启动黑屏设备树改错后的恢复方法改设备树最怕的就是改错了导致系统启动黑屏。Orin Nano 启动黑屏的原因可能是设备树里的 Pinmux 配置影响了显示相关的引脚或者 GPIO 配置冲突导致内核崩溃。如果你遇到黑屏不要慌可以用以下方法恢复把板子进入恢复模式用 SDK Manager 重新刷机。如果你有串口调试线可以通过串口看到启动日志定位到具体是哪个设备树节点出了问题。如果你用的是overlay可以把extlinux.conf里的OVERLAYS行注释掉重启后系统会用原始设备树启动。我建议在改设备树之前先备份原始的.dtb文件和extlinux.conf这样出问题了可以快速回滚。另外改设备树时最好一次只改一个引脚验证通过后再改下一个避免一次性改太多导致问题难以定位。7.4 实操心得几个容易忽略的细节第一个细节是Pinmux 的默认值。Orin Nano 的很多引脚在出厂时 Pinmux 默认不是 GPIO而是其他功能比如 I2C、UART。如果你直接操作 GPIO 控制器可能没反应因为引脚根本没连到 GPIO 模块。所以第一步永远是确认 Pinmux。第二个细节是GPIO 控制器的 base 值。不同的 L4T 版本GPIO 控制器的 base 值可能不同导致用户空间的全局编号变化。所以不要硬编码编号而是用gpioinfo动态查找。第三个细节是电平极性。有些引脚的默认电平是高有些是低配置的时候要注意active-high和active-low的区别。如果你发现gpioset设 1 但实际输出是低可能是极性配反了。第四个细节是上拉和下拉。Orin Nano 的 Pinmux 支持配置内部上拉和下拉如果你读一个按键不配上拉可能会读到浮空值。配置的时候根据外部电路选择合适的上拉或下拉。8. 从寄存器到设备树的完整流程回顾整个流程走下来其实核心就是三层Pinmux 决定引脚功能GPIO 控制器决定电平读写设备树把这两层组织起来并持久化。调试的时候可以从底层往上查先用devmem验证硬件通路再改设备树做持久化配置最后用libgpiod在用户空间操作。我个人的习惯是拿到一个新板子先用devmem快速验证几个关键引脚的 Pinmux 和 GPIO 寄存器确认硬件没问题。然后写一个最小的设备树overlay只配一个引脚编译部署后验证。验证通过后再批量添加其他引脚。这样即使出问题也能快速定位是哪个引脚的配置有问题。另外Orin Nano 的 TRM 是必读的虽然文档很长但 Pinmux 和 GPIO 章节一定要仔细看。很多网上教程的寄存器地址和宏定义都是针对旧版 Jetson 的直接抄会翻车。以 TRM 为准结合gpioinfo和devmem的实际输出才能确保配置正确。最后分享一个小技巧如果你不确定某个引脚的 Pinmux 寄存器地址可以在内核源码里搜索引脚名字比如grep -r gpio09 hardware/nvidia/platform/t23x/通常能找到对应的设备树配置和宏定义比翻 TRM 快得多。
返回列表