简介:这份资源是面向 Jetson Nano 开发者的 C/C++ GPIO 编程资料包,聚焦嵌入式 Linux 下通过 GPIO 库控制传感器、LED、按钮等外设的完整流程,适合有一定 C++ 语法基础、想从零上手 Jetson Nano 硬件编程的爱好者与工程师。压缩包共 68 个文件,以 cpp/h 源码为主体(29 个 cpp、17 个 h),包含可编译的库实现、PWM/中断/事件等示例程序;同时附带 md 文档说明安装步骤、库 API 与项目链接方法,并有 CMake 配置、Dockerfile 和 udev 规则文件,便于在不同环境中构建部署。整个包仅 106KB,精简无冗余。资源已有 852 人学习。通过学习可掌握 nvgpio_open、gpio_request、读写电平、事件回调等核心 API,参考 simple_out、button_interrupt、simple_pwm 等用例能直观理解引脚配置、输入输出模式切换及线程同步思路,有效缩短 Jetson Nano 外设项目开发的上手周期。 直接说结论:Jetson Nano的GPIO库,官方叫Jetson.GPIO,和树莓派的RPi.GPIO在API上高度相似,但底层实现、引脚映射、权限管理完全是另一套逻辑。这篇文章我把从烧录系统到点亮第一颗LED、再到踩坑排查的完整过程写清楚,面向第一次接触Jetson Nano的嵌入式开发者,也适合从树莓派转过来的朋友——你以为你会的,其实不一定是你以为的那样。
1. 为什么Jetson Nano的GPIO库值得单独写一篇
1.1 它和树莓派RPi.GPIO是“表亲”,但别当亲兄弟用
最早拿到Jetson Nano的人,大概率都有树莓派的使用背景。NVIDIA官方也的确是这么设计的——Jetson.GPIO的接口风格、命名方式、甚至示例代码都刻意往RPi.GPIO上靠,目的是降低迁移门槛。但实际用起来你会发现有几个本质区别:
- 引脚编号方式完全不同。树莓派有 BOARD(物理引脚号)和 BCM(博通芯片GPIO号)两套编号;Jetson Nano 虽然有 BOARD 和 BCM 两种模式,但这里的“BCM”只是把Jetson的引脚重新映射了一遍,跟树莓派的
GPIO17、GPIO27那套编号没有半毛钱关系。 - 硬件层面。树莓派的GPIO直连SoC的BCM2835,而Jetson Nano的GPIO是通过板载的PCA9539 GPIO扩展芯片和Tegra SoC内部GPIO混合管理的,这就导致部分引脚不支持中断、部分引脚有上拉/下拉的硬件限制。
- 权限机制。Jetson.GPIO 底层依赖
libgpiod和内核的gpiochip接口,默认情况下需要 root 权限,或者把用户加入gpio组、配置 udev 规则。新手最容易在这儿卡住。
一句话总结:API长得像,但驱动链路完全不同。你要是直接照搬树莓派的代码,大概率能跑,但性能、可靠性、引脚功能选择上会有很多隐性坑。
1.2 40针排针不是所有引脚都能随便用
Jetson Nano 的 40-pin 扩展排针是 2x20 布局,和树莓派物理上一致,但引脚功能分配完全是英伟达自己的。拿到板子后第一件事应该是去NVIDIA官网下载Jetson Nano Pinmux 表格(引脚复用配置表),而不是凭树莓派经验猜功能。
排针上有三类引脚你要心里有数:
- 供电脚:3.3V、5V、GND,共8个左右,这部分和树莓派一致,但5V引脚直接取自USB或DC电源,没有稳压保护,电流不大但足够烧坏传感器。
- 数字GPIO:大部分引脚默认是GPIO功能,但部分引脚默认复用为
I2C、UART、SPI、PWM、I2S等外设功能,必须通过 Pinmux 配置切换。 - 特殊功能脚:比如
PWM0、PWM2这类带硬件PWM的引脚,数量比树莓派少,而且默认映射的芯片通道也不同。
我见过不少人在这一步翻车——拿GPIO9当普通输入用,结果发现它默认是 I2C 的数据线,怎么读都是高电平。这不是代码问题,是引脚复用没配。
2. 环境准备与Jetson.GPIO库的安装
2.1 烧录系统时就要考虑GPIO库的适配性
Jetson Nano 支持多个系统版本,从 JetPack 4.2 到 4.6.4(Nano 系列的尽头),不同的 JetPack 版本对应的内核版本不同,而 Jetson.GPIO 库对内核版本是有要求的。我的建议是:
- 新手直接用JetPack 4.6.4 + Ubuntu 18.04,这是Nano最后也是最稳定的官方镜像,GCC、Python、pip 版本都不算太老,能装最新版 Jetson.GPIO。
- 不要为了尝鲜去刷 JetPack 5.x 或更高版本给 Nano(那是给 Orin 系列用的),驱动兼容性有风险。
- 烧录用SDK Manager或balenaEtcher + 官方镜像都行,我习惯直接用 SDK Manager 刷,一步到位还能顺手装 CUDA、cuDNN 这些加速库。
烧录完成后,先用ls /dev/gpiochip*确认内核有暴露GPIO设备节点。正常情况下应该能看到gpiochip0、gpiochip1等多个芯片,其中gpiochip0对应 Tegra SoC 内部GPIO,gpiochip1对应 PCA9539 扩展芯片。
2.2 安装Jetson.GPIO的完整流程
官方库在GitHub上,名字叫NVIDIA/jetson-gpio。安装步骤我整理成下面这条链:
# 1. 更新系统并安装依赖 sudo apt update sudo apt install -y python3-pip git # 2. 克隆官方仓库 git clone https://github.com/NVIDIA/jetson-gpio.git # 3. 进入仓库目录直接安装Python库 cd jetson-gpio sudo python3 setup.py install # 4. 配置用户组和udev规则(关键一步!) sudo groupadd -f gpio sudo usermod -aG gpio $USER sudo cp lib/python/Jetson/GPIO/99-gpio.rules /etc/udev/rules.d/ sudo udevadm control --reload-rules sudo udevadm trigger # 5. 注销重新登录,让用户组生效这里有个细节值得多说一句:很多教程只让你pip install Jetson.GPIO,但 PyPI 上的包版本可能滞后,而且不会自动帮你装 udev 规则。从源码装能保证库版本和驱动匹配,同时把权限文件一起搞定。建议按上面的流程走。
装完后打开 Python 验证一下:
import Jetson.GPIO as GPIO print(GPIO.VERSION) print(GPIO.gpiochip_find)如果输出版本号且不报错,说明库装好了。如果报ModuleNotFoundError,检查是不是装到了 Python 2 的环境里(Nano 自带 Python 2 和 Python 3,pip 默认可能指向 python2)。
2.3 权限配置是90%新手的第一道坎
JetPack 系统默认情况下,普通用户访问/dev/gpiochip*是会被拒绝的。你会看到这样的报错:
PermissionError: [Errno 13] Permission denied: '/dev/gpiochip0'解决办法就是上面第4步的组和udev规则。99-gpio.rules 这个文件内容本质上就是给/dev/gpiochip*设备赋予gpio用户组的读写权限。配置完成后,重新登录执行groups确认你已经在gpio组里。
有个小坑:SDK Manager 刷机时创建的用户名如果是nvidia,那你sudo usermod -aG gpio $USER加的是当前登录用户,如果用 SSH 远程登录后切换过用户,$USER会变,务必确认加对人了。
3. 核心接口与实操编码
3.1 基本流程:和RPi.GPIO神似但细节不同
先看一个标准程序骨架,我加了些注释,方便对照树莓派的使用习惯找差异:
import Jetson.GPIO as GPIO import time # 1. 设置引脚编号模式:BOARD 或 BCM # 注意:Jetson的BOARD编号 = 物理排针号;BCM编号 = 英伟达自定义逻辑号 GPIO.setmode(GPIO.BOARD) # 2. 设置warnings(默认开启,建议关闭避免刷屏) GPIO.setwarnings(False) # 3. 设置引脚功能:输出 # 物理排针第11脚(对应BCM编号18) GPIO.setup(11, GPIO.OUT, initial=GPIO.HIGH) # 4. 控制输出 GPIO.output(11, GPIO.LOW) time.sleep(1) GPIO.output(11, GPIO.HIGH) # 5. 清理 GPIO.cleanup()代码结构和树莓派基本一致,但有几个细节:
GPIO.setmode(GPIO.BOARD)和GPIO.setmode(GPIO.BCM)都可以用,但 BOARD 模式更直观——因为它直接对应排针上的物理位置,查引脚容易,不容易搞混。GPIO.setwarnings(False)在树莓派上可能只是避免“通道已被使用”的提示,但在 Jetson 上经常会出现重复初始化 PWM 或其他外设的警告,建议默认关掉。GPIO.cleanup()不能随意调用,它会把所有GPIO通道重置为输入模式,如果你在代码中间调用会导致程序后面的引脚状态全部失效。更合理的做法是在程序退出前调用。
3.2 GPIO的八种工作模式到底是什么
热搜词里出现过“GPIO八种工作模式”,这个词在STM32圈子里最常见,但在 Jetson/Linux 语境下得打个折扣理解。Linux 内核的gpiochip接口从功能上支持的模式主要分为:
| 模式 | 对应英文 | 说明 |
|---|---|---|
| 浮空输入 | Input Floating | 默认输入模式,无上下拉 |
| 上拉输入 | Input Pull-up | 输入默认高电平 |
| 下拉输入 | Input Pull-down | 输入默认低电平 |
| 推挽输出 | Output Push-pull | 可输出高低电平 |
| 开漏输出 | Output Open-drain | 只能拉低,需要外接上拉电阻 |
| 复用功能 | Alternate Function | 切换为I2C、SPI、UART等 |
| 模拟输入 | Analog Input | Jetson不常用,绝大多数引脚无ADC |
| 外部中断 | Interrupt Falling/Rising | 边沿触发事件 |
Jetson.GPIO 库能直接操作的是前六种,其中模拟输入是不支持的,Jetson Nano 没有板载ADC,需要外接ADS1115之类芯片。中断模式也不是所有引脚都支持,具体看硬件版本,PCA9539 扩展芯片的引脚对中断支持很差,经常要在软件里轮询替代。
3.3 实操案例:按键输入与外部中断
GPIO输出控制相对简单,难的是输入和中断。下面这个例子展示按键检测,分别用轮询和中断两种方式:
import Jetson.GPIO as GPIO import time GPIO.setmode(GPIO.BOARD) BUTTON_PIN = 13 # 物理第13脚 LED_PIN = 11 # 物理第11脚 GPIO.setup(LED_PIN, GPIO.OUT, initial=GPIO.LOW) GPIO.setup(BUTTON_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP) # 启用内部上拉 # 方式一:轮询(简单但浪费CPU) try: while True: if GPIO.input(BUTTON_PIN) == GPIO.LOW: # 按下为低 GPIO.output(LED_PIN, GPIO.HIGH) else: GPIO.output(LED_PIN, GPIO.LOW) time.sleep(0.05) # 消抖 except KeyboardInterrupt: pass finally: GPIO.cleanup()轮询的方式代码直观,但CPU占用高,而且50ms的延时会导致响应迟滞。更优雅的方式是事件回调:
# 定义回调函数 def button_callback(channel): print(f"按键触发,通道: {channel}") state = GPIO.input(LED_PIN) GPIO.output(LED_PIN, not state) # 注册事件监听 GPIO.add_event_detect(BUTTON_PIN, GPIO.FALLING, callback=button_callback, bouncetime=200) try: print("等待按键事件,按Ctrl+C退出...") time.sleep(60) except KeyboardInterrupt: pass finally: GPIO.remove_event_detect(BUTTON_PIN) GPIO.cleanup()使用事件回调时,bouncetime=200这个参数很关键。按键在物理上抖动会造成电平在几毫秒内反复跳变,如果不开消抖,一次按键会被当成多次触发。200ms 是机械按键的保守值,具体看你的按键质量,有些轻触开关 50ms 就够,有些劣质的要 300ms 才能稳住。
3.4 PWM与舵机控制
Jetson Nano 的 PWM 功能很让人纠结:40-pin 排针上只有两个硬件PWM引脚——物理第32脚(PWM0)和第33脚(PWM2)。而且底层用的是内核的pwm子系统,Jetson.GPIO 库对PWM的支持不像树莓派那么透明,需要额外指定设备树配置。
直接给你能跑的代码:
import Jetson.GPIO as GPIO import time GPIO.setmode(GPIO.BOARD) PWM_PIN = 32 # 物理第32脚,硬件PWM0 GPIO.setup(PWM_PIN, GPIO.OUT, initial=GPIO.LOW) pwm = GPIO.PWM(PWM_PIN, 50) # 50Hz,标准舵机频率 pwm.start(7.5) # 7.5%占空比,舵机中位 try: while True: pwm.ChangeDutyCycle(2.5) # 约0度 time.sleep(1) pwm.ChangeDutyCycle(7.5) # 约90度 time.sleep(1) pwm.ChangeDutyCycle(12.5) # 约180度 time.sleep(1) except KeyboardInterrupt: pass finally: pwm.stop() GPIO.cleanup()代码层面完全没有问题,但有两个细节必须提醒:
- PWM频率限制。
GPIO.PWM(PIN, freq)里的 freq 不是任意值都能生效,它取决于内核里pwmchip的时钟源频率,一般最高能到 100kHz 左右,但不保证占空比的分辨率。做电机控制用 50Hz 或 20kHz 比较安全。 - GPIO.setup 后立刻 start 会报错。在 Jetson.GPIO 的某些版本里,PWM 引脚必须先设为
GPIO.OUT,然后再调用GPIO.PWM,中间要加一行time.sleep(0.1)等待设备节点稳定。如果上来就GPIO.PWM(PIN, 50)报OSError: [Errno 8] Exec format error,大概率就是这个时序问题。
4. 常见问题与排查技巧实录
4.1 权限报错与“引脚没反应”类问题
我汇总了这段时间实际遇到的频率最高的问题,列一个速查表:
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
PermissionError: [Errno 13] | 用户不在gpio组或udev规则没生效 | 执行 usermod 和 udevadm 刷新,重新登录 |
OSError: [Errno 8] Exec format error | PWM引脚时序问题 / 设备树未配置 | 在 GPIO.setup 和 GPIO.PWM 之间加延时,检查 /sys/class/pwm |
| 引脚电平一直不变 | 引脚功能被复用成I2C/SPI | 查Pinmux表格,改用纯GPIO引脚 |
| 外部中断不触发 | 用了PCA9539扩展引脚或设置边沿错误 | 换成SoC内置GPIO引脚,确认边沿模式 |
| 读取输入值跳变 | 没有配置上下拉或硬件浮空 | 用 pull_up_down 参数或外接10k电阻 |
| 程序卡死无响应 | 事件回调里做了阻塞操作 | 回调函数只做标志位赋值,不要在回调里延时或打印太多 |
其中“引脚被复用”这个问题最容易排查又最容易忽略。我的经验是:开发前先用sudo cat /sys/kernel/debug/gpio查看每个引脚的当前状态,确认你打算用的引脚是不是已经绑定到某个外设。如果显示gpio-xxx (i2c-0 | i2c ),说明它被I2C占用了,要么改Pinmux,要么换引脚。
4.2 和热词里的RK平台报错做一个对照
热词里出现了一个很有意思的报错:rk gpio set mux failed, please check group info——这是Rockchip平台上的常见错误,意思是GPIO的mux(复用)配置失败。Jetson平台虽然不会直接弹这句话,但背后的逻辑是一样的:芯片的引脚资源有限,同一个物理引脚在不同时刻只能做一种功能。你可能在切换功能时把某个引脚从GPIO切到UART,结果管脚复用表写错,功能就乱套了。
在Jetson上对应的情况是:直接操作/sys/class/gpio或使用config-pin类似工具时,如果功能和当前Pinmux配置冲突,内核会静默失败,或者你的程序读到不稳定的电平。解决思路固定:查硬件原理图确认引脚用途 → 查Pinmux配置表 → 修改设备树或使用官方配置工具 → 重启生效。
另外热词里还提到“stm32f103的GPIO作为输入能不能承受5V电压”,这类耐压问题在Jetson上同样存在。Jetson Nano的GPIO是3.3V逻辑,不是5V容忍的。如果你接了一个5V输出的传感器到GPIO输入引脚,轻则读数异常,重则烧毁SoC的GPIO控制器。一定要接5V设备时,中间串一个电平转换模块(如BSS138四路电平转换板,或者简单的分压电阻)。
4.3 Jetson Orin Nano带来的兼容性差异
最近不少人在问 Orin Nano 能不能复用这套代码。答案是:大部分能跑,但细节有差异。Orin Nano 在40-pin排针上增加了更多的I2C、SPI和UART复用选项,但 Jeston.GPIO 官方库对 Orin Nano 的支持是通过libgpiod的新版驱动做的,引脚编号和旧的 Nano 不一致。
如果你拿着 Nano 的 BOARD 模式代码直接跑在 Orin Nano 上,大概率引脚错乱。建议 Orin 系列用户直接用gpiod命令行工具来确定gpiochip的线路偏移量,或者改用python3-gpiod库,而不是 Jetson.GPIO。我之前测过一次,Orin Nano 的gpiochip0偏移量和 Nano 完全不同,BMC映射也对不上,代码改起来不比重写省事。
4.4 排查硬件层面的坑
软件排查完了,有些问题其实出在硬件层面。我遇到过的:
- 杜邦线接触不良。Jetson Nano 的排针间距是标准2.54mm,但工业级的杜邦线公头尺寸有时偏小,插上去松松垮垮,电平读数随机跳变。后来我全部换成带锁扣的杜邦头,问题消失。
- 外部电源不够。GPIO驱动LED没问题,但如果同时驱动多个继电器或舵机,3.3V和5V的电流会捉襟见肘。Jetson Nano 的5V引脚从USB Type-C口取电时最大只有3A,带多个电机基本不可能。正确做法是外部单独供电,GPIO只做信号控制。
- 共地问题。外接传感器和Jetson板卡必须共地,否则电平参考点漂移,I2C、UART全都会出奇奇怪怪的问题。这个问题很基础,但真的是高发问题,每次排查都排到最后才发现是GND没接。
5. 一些值得坚持的工程习惯
最后分享几个我实际写GPIO程序时的习惯,算不上多高深,但能省不少调试时间。
第一,每个程序开头写清楚引脚编号模式。Jetson.GPIO 的 BOARD 和 BCM 模式虽然可以随时切换,但混用会让你心态爆炸。我见过有人前半段用BOARD按物理引脚控制,后半段用BCM控制另一个引脚,两个引脚在代码里看起来很近,实际上一个在天上一个在地下。
第二,优先用/sys/class/gpio或gpiod工具做快速验证。写Python程序之前,先用命令行确认引脚能不能正常输出、输入方向对不对。直接跑代码出了问题,很难判断是硬件还是库的问题:
# 用gpiod快速读取引脚状态 sudo gpiodetect sudo gpioinfo gpiochip0第三,错误处理里务必释放资源。Jetson运行的是Linux,如果程序异常退出前没有调GPIO.cleanup(),再次运行时会发现引脚状态是上一次残留的,有时甚至需要重启才能恢复。我的做法是在程序开头加atexit.register(GPIO.cleanup)保证异常退出也能清理。
做Jetson Nano的GPIO开发,表面上是在写Python操作引脚,实际上是和Linux内核的GPIO子系统、设备树、Pinmux配置打交道。遇到问题时,多往底层查一层,往往能找到答案。如果你也是从树莓派转过来的,建议忘了树莓派的那套习惯,老老实实从硬件手册开始——这板子的脾气,和树莓派不太一样。
本文还有配套的精品资源,点击获取