Jetson Nano GPIO 点灯:从 J41 插针查到寄存器
对象:你目前使用的 Jetson Nano / Tegra X1、J41 物理 12 脚 、板端内核
4.9.253-tegra。这是一份查资料与理解课程代码 的流程;实际板型、设备树版本和灯的亮灭仍须在你的板上核对。配套代码见 led.c 和 app.c。
先抓住与 STM32 相同、不同的地方
两者都要回答三件事:选哪个管脚功能、设输入还是输出、输出高还是低 。STM32 常从芯片封装脚直接查到 GPIO 端口和 MODER/ODR/BSRR;Jetson Nano 还隔着载板 J41、模组 SODIMM、SoC 管脚命名、可能的电平转换器,以及 Linux 已有的 pinctrl/GPIO 驱动。
| 你熟悉的 STM32 思路 | 此例在 Jetson Nano 上对应什么 |
|---|---|
| 开发板排针 → MCU 封装脚 | J41-12 → 载板信号 I2S0_SCLK → 模组第 199 脚 → SoC 管脚 DAP4_SCLK |
| 选 GPIO/复用功能 | Pinmux 管脚设置 + GPIO 控制器的 CNF 位;这是两层 |
| 配置输出方向 | Pinmux 的 TRISTATE 放行输出,再设 GPIO 的 OE |
| 写高/低电平 | GPIO 的 OUT 位 7 |
| 裸机直接写寄存器 | 课程的 led.ko 在内核 中 ioremap/readl/writel;用户程序只能经 /dev/led 的 ioctl 调用它 |
#mermaid-svg-Z0sERM13Ylu8QE7U{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Z0sERM13Ylu8QE7U .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Z0sERM13Ylu8QE7U .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Z0sERM13Ylu8QE7U .error-icon{fill:#552222;}#mermaid-svg-Z0sERM13Ylu8QE7U .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Z0sERM13Ylu8QE7U .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Z0sERM13Ylu8QE7U .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Z0sERM13Ylu8QE7U .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Z0sERM13Ylu8QE7U .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Z0sERM13Ylu8QE7U .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Z0sERM13Ylu8QE7U .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Z0sERM13Ylu8QE7U .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Z0sERM13Ylu8QE7U .marker.cross{stroke:#333333;}#mermaid-svg-Z0sERM13Ylu8QE7U svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Z0sERM13Ylu8QE7U p{margin:0;}#mermaid-svg-Z0sERM13Ylu8QE7U .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Z0sERM13Ylu8QE7U .cluster-label text{fill:#333;}#mermaid-svg-Z0sERM13Ylu8QE7U .cluster-label span{color:#333;}#mermaid-svg-Z0sERM13Ylu8QE7U .cluster-label span p{background-color:transparent;}#mermaid-svg-Z0sERM13Ylu8QE7U .label text,#mermaid-svg-Z0sERM13Ylu8QE7U span{fill:#333;color:#333;}#mermaid-svg-Z0sERM13Ylu8QE7U .node rect,#mermaid-svg-Z0sERM13Ylu8QE7U .node circle,#mermaid-svg-Z0sERM13Ylu8QE7U .node ellipse,#mermaid-svg-Z0sERM13Ylu8QE7U .node polygon,#mermaid-svg-Z0sERM13Ylu8QE7U .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Z0sERM13Ylu8QE7U .rough-node .label text,#mermaid-svg-Z0sERM13Ylu8QE7U .node .label text,#mermaid-svg-Z0sERM13Ylu8QE7U .image-shape .label,#mermaid-svg-Z0sERM13Ylu8QE7U .icon-shape .label{text-anchor:middle;}#mermaid-svg-Z0sERM13Ylu8QE7U .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Z0sERM13Ylu8QE7U .rough-node .label,#mermaid-svg-Z0sERM13Ylu8QE7U .node .label,#mermaid-svg-Z0sERM13Ylu8QE7U .image-shape .label,#mermaid-svg-Z0sERM13Ylu8QE7U .icon-shape .label{text-align:center;}#mermaid-svg-Z0sERM13Ylu8QE7U .node.clickable{cursor:pointer;}#mermaid-svg-Z0sERM13Ylu8QE7U .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Z0sERM13Ylu8QE7U .arrowheadPath{fill:#333333;}#mermaid-svg-Z0sERM13Ylu8QE7U .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Z0sERM13Ylu8QE7U .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Z0sERM13Ylu8QE7U .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Z0sERM13Ylu8QE7U .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Z0sERM13Ylu8QE7U .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Z0sERM13Ylu8QE7U .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Z0sERM13Ylu8QE7U .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Z0sERM13Ylu8QE7U .cluster text{fill:#333;}#mermaid-svg-Z0sERM13Ylu8QE7U .cluster span{color:#333;}#mermaid-svg-Z0sERM13Ylu8QE7U div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Z0sERM13Ylu8QE7U .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Z0sERM13Ylu8QE7U rect.text{fill:none;stroke-width:0;}#mermaid-svg-Z0sERM13Ylu8QE7U .icon-shape,#mermaid-svg-Z0sERM13Ylu8QE7U .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Z0sERM13Ylu8QE7U .icon-shape p,#mermaid-svg-Z0sERM13Ylu8QE7U .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Z0sERM13Ylu8QE7U .icon-shape .label rect,#mermaid-svg-Z0sERM13Ylu8QE7U .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Z0sERM13Ylu8QE7U .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Z0sERM13Ylu8QE7U .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Z0sERM13Ylu8QE7U :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} J41 物理 12 脚
载板 I2S0_SCLK
模组脚 199
SoC DAP4_SCLK
Pinmux 0x70003150
GPIO3_PJ.07
CNF / OE / OUT 的 bit 7
输出通路
LED 电路
一、打开哪份资料、查哪里
下列文件都在 I:\Linux内核驱动\嵌入式AI班资料\7.硬件控制资料。页码是 PDF 阅读器显示的页码;有印刷页码时也写出。
| 顺序 | 资料与位置 | 本例查到什么 |
|---|---|---|
| 1 | 2.1 载板标注说明,PDF 第 21--22 页、印刷第 17--18 页,Figure 3-1 / Table 3-3 | 找 J41 Header Pin #12 ;表格同一行给出模组信号 I2S0_SCLK、模组脚 199、Tegra 管脚 DAP4_SCLK、GPIO 口 PJ.07。J41-39 是 GND,J41-40 是信号。 |
| 2 | 2.3 载板电路图,PDF 第 19 页 | 用 J41、I2S0_SCLK 搜网名,并看到 J41 信号经过载板电平转换器;排针不是直接焊到 SoC 焊球。 |
| 3 | 4.1 名称指南,PDF 第 15 页、印刷第 11 页 | 复核模组第 199 脚:模组名 I2S0_SCLK,SoC 名 DAP4_SCLK。 |
| 4 | 4.2 开发板 40PIN 表,Jetson_Nano_DevKit 工作表第 145 行 |
一行同时看到 J41 脚位、SoC 名、GPIO 名、设备树 pin 名,以及当前用途/方向。 |
| 5 | 3.1 Tegra X1 TRM,第 9 章:PDF 第 331 页 §9.15.82;第 247--252 页 §9.13/Table 32 | 查 PINMUX_AUX_DAP4_SCLK_0、GPIO Controller 3 --- Port J、CNF/OE/OUT 的偏移和位定义。 |
| 6 | 4.4 NVIDIA 40-pin 配置说明,PDF 第 4--8 页 | 理解表格生成 pinmux 设备树文件、启动阶段应用配置的流程;这是持久配置路线,与老师的运行时直接写寄存器实验不同。 |
本例暂时用不到 ARM Cortex-A57/GIC 手册。查"J41 第几脚"先看载板资料 ;查"寄存器偏移和每个位"才进入 Tegra X1 TRM ,不要在 2977 页 TRM 中盲搜 12。

二、把五种名字串成同一个管脚
按 4.2 开发板表 → Jetson_Nano_DevKit → 第 7 行列标题 → 第 145 行读:
| 单元格 | 值 | 它是什么 |
|---|---|---|
C145 |
12 |
J41 物理插针号。 |
A145 / B145 |
I2S0_SCLK / 199 |
模组对外信号名 / 模组 SODIMM 脚号,199 不是 J41 脚号。 |
D145 |
DAP4_SCLK |
Tegra SoC 的管脚名称;用它去 TRM 搜 pinmux 寄存器。 |
H145 |
GPIO3_PJ.07 |
GPIO 控制器 3、J 口、口内第 7 位。用它去 GPIO 寄存器表查。 |
I145 |
I2S4B_SCLK |
同一 SoC 管脚的一个 SFIO 复用功能 。载板写 I2S0_SCLK,SoC 功能表写 I2S4B_SCLK,编号体系不同。 |
W145 / AE145 |
dap4_sclk_pj7 / GPIO_PJ7 |
设备树 pinctrl 查找名 / GPIO pin group 名;拿 dap4_sclk_pj7 去 BSP 的 .dtsi 搜。 |
AS145 / AT145 |
GPIO3_PJ.07 / Input |
这份表当前设为 GPIO 用途、输入方向;要在启动配置中设输出,方向也要改变。 |
**查找动作:**先在载板 PDF 搜 J41 并定位 12;然后在表格 C 列搜 12,找到第 145 行;接着用 DAP4_SCLK 搜 TRM 的 Pinmux 小节,用 GPIO3_PJ.07 找 Controller 3 / Port J。改查别的引脚时,重新走这条链,不复制 PJ.07 的地址。
4.3 模组表 的同一管脚也在第 145 行,但没有 4.2 的"40 Pin Header"列,所以相应字段整体左移:SoC 名在 C145、GPIO 名在 G145、设备树名在 V145、用途/方向在 AR145/AS145。NVIDIA 的 4.4 说明提到 AR/AS,与你手中 4.2 表的 AS/AT 不同。总以当前工作表第 7 行的列标题为准。
三、两处"复用"到底怎么配
**第一处:SoC 管脚的 Pinmux 寄存器。**在 TRM §9.15.82 搜 PINMUX_AUX_DAP4_SCLK_0:偏移 0x3150,本例物理地址 0x70003150。该寄存器至少要分清:
| 位 | 含义 | 对输出灯的关系 |
|---|---|---|
PM[1:0] |
选择 SFIO 编码:0=I2S4B,1=RSVD1,2/3=RSVD2/3。 |
不能把 PM=1 翻译成"GPIO 编号 1" 。板级 GPIO 配置可能采用 rsvd1 来避开 I2S;最终 GPIO/SFIO 还要看下方 CNF。 |
TRISTATE[4] |
1 为高阻,0 为允许输出通路。 |
老师代码清零 bit 4。只清这里还不会自动输出高电平。 |
PUPD[3:2] |
无上下拉、下拉或上拉。 | 决定输入/空闲时的默认偏置;不是控制亮灭的 OUT。 |
E_INPUT[6]、PARK[5] |
输入缓冲、深睡眠停放状态。 | 本例代码没有系统处理这些字段,不能说它完成了全部管脚配置。 |
老师代码为 (readl(gpio_pinmux) & ~(1 << 4)) | 1:它清 bit 4、置 bit 0 ,没有清 PM 的 bit 1,因此并不保证最终 PM[1:0]=01;而 01 在手册中标为 RSVD1。理解代码时应按位核对,后续正式配置应使用与当前 BSP 对应的 pinctrl/设备树方案,不把这句推广给其他管脚。
第二处:GPIO 控制器本身。 GPIO3_PJ.07 中的 J 决定寄存器位置,07 决定寄存器内的位:
CNF bit 7 = 1:选择 GPIO,而非 SFIO。OE bit 7 = 1:GPIO 作为输出;0是输入/高阻。OUT bit 7 = 1/0:输出高/低。课程接线约定高电平亮、低电平灭。
所以Pinmux 放行输出 → CNF 选 GPIO → OE 选输出 → OUT 写电平,四个动作服务于同一根管脚,不能只做其中一个。TRM §9.13.1--9.13.3 分别说明了这三个 GPIO 寄存器的条件。
四、偏移怎么算:为什么是 0x04 / 0x14 / 0x24
先从 Linux 4.9 的 Tegra210 SoC 设备树看两块 MMIO 范围:GPIO 控制器为 0x6000D000 起,Pinmux 的 mux 寄存器范围为 0x70003000 起。这是核对地址来源的参考;你自己的 NVIDIA BSP 和板上实际设备树仍要再对照。
TRM Table 32 的 GPIO Controller 3 --- Port I J K L 一行里,J 是第二列:
| 目标 | 相对整个 GPIO 块 0x6000D000 |
相对老师取的 GPIO3 基址 0x6000D200 |
实际物理地址 |
|---|---|---|---|
J 口 CNF |
+0x204 |
+0x04 |
0x6000D204 |
J 口 OE |
+0x214 |
+0x14 |
0x6000D214 |
J 口 OUT |
+0x224 |
+0x24 |
0x6000D224 |
text
GPIO 块 0x6000D000 + 控制器 3 的 0x200 + J 口 CNF 的 0x04
= 0x6000D204
GPIO3 基址 0x6000D200 + J 口 OUT 的 0x24
= 0x6000D224
目标位 = 1 << 7 = 0x80
0x04 是 J 口相对于 I 口的寄存器位置 ,7 是 J 口内的位号 ,二者不能相加。上表是 TRM 的 Lower / Read-Modify-Write 地址;右侧另有 0x284/0x294/0x2A4 等 Upper / Per-Pin Mask Write 地址。老师代码使用 readl → 改位 → writel 的 Lower 地址,没有使用 Mask Write。不要把两列混用。
Pinmux 的 0x70003150 也可写成 0x70003000 + 0x150;TRM 写的 Offset: 0x3150 可理解为相对 0x70000000。不能把 0x3150 再加到 0x70003000 上。这里的 Pinmux 地址与上面的 GPIO 地址属于不同 MMIO 区域。
五、对照你现在的 led.c 看执行顺序
register_chrdev_region(MKDEV(500,0), ...)占用字符设备号 。这里的500:0只负责把/dev/led关联到驱动,和 J41-12、模组脚 199、GPIO bit 7 没有数值关系。ioremap(0x70003150, 8)获得 Pinmux MMIO 的内核映射地址;readl/writel修改该管脚的复用/三态字段。ioremap(0x6000D200, 0xFF)获得 GPIO3 寄存器窗口;用+0x04设置 J 口CNFbit 7,用+0x14设置OEbit 7,再用+0x24设置OUTbit 7。cdev_init/cdev_add把file_operations接入字符设备;板端/dev/led必须是字符节点500,0。- 用户态
open("/dev/led") → ioctl(LED_ON/LED_OFF);内核进入led_ioctl() → led_on()/led_off(),最终只改OUT的 bit 7。用户态循环的usleep(100000)决定闪烁频率。
c
/* 代码实际要找的三个地址:都在 GPIO3 的 J 口,只改第 7 位。 */
#define GPIO3 0x6000D200
#define CNF 0x04
#define OE 0x14
#define OUT 0x24
/* GPIO3 + OUT = 0x6000D224;bit 7 = 0x80。 */
ioremap 后的指针是内核虚拟地址,不是应用可直接解引用的物理地址;卸载时要 iounmap。同一 J 口还有其他位,读改写是为保留其他位,但它没有解决与 Linux GPIO/pinctrl 驱动并发使用同一寄存器的资源归属问题。课程直接写寄存器是理解硬件的实验。
六、表格配置与老师代码是两条不同时间线
- **老师当前实验:运行时设置。**板子已经启动 Linux,
insmod led.ko才执行上述 MMIO 写入;模块卸载或重启后,这些运行时写入不等于持久的板级 Pinmux 配置。 - NVIDIA 板级流程:启动时设置。在 4.2 表第 145 行按列标题确认
Customer Usage、Pin Direction、初始状态,再由表格生成 pinmux/gpio 的设备树文件,与匹配当前 L4T/BSP、板型版本 的启动配置一起部署。你这份 4.2 表目前显示 GPIO 用途、Input;若要上电就是输出,需要把方向及安全初始状态一同设计,并核对生成的配置。NVIDIA 的说明指出 CBoot 会应用这部分 pinmux 配置,Linux 不会自动重新应用 CBoot 那一份。NVIDIA Jetson Nano 适配文档
**现在学习这份 MD 时只读资料、核对当前板子即可。**4.4 文档中的 U-Boot/CBoot、刷机命令和文件名针对其发布版本,不应直接复制到你现在的 4.9.253-tegra 系统。以后进入 Platform/设备树/GPIO 子系统,再做匹配 BSP 的持久配置和驱动资源管理。
七、板上只读核对与常见误认
在 Jetson Linux 中查看板型和实际加载的设备树信息,不写寄存器:
sh
uname -r
uname -m
cat /proc/device-tree/model
tr '\0' '\n' < /proc/device-tree/compatible
test -r /proc/device-tree/nvidia,dtsfilename && tr -d '\0' < /proc/device-tree/nvidia,dtsfilename
在 Ubuntu 虚拟机 的 NVIDIA BSP 中用上一步的板型/文件名找相应 .dts/.dtsi,再搜索 dap4_sclk_pj7、GPIO_PJ7、gpio@6000d000。看到名字和配置后,才判断当前板实际由谁占用这根脚。没有板端结果之前,本笔记的地址链是手册与课程代码的静态核对。
| 常见误认 | 正确区分 |
|---|---|
| "12、199、7 是同一个 GPIO 编号。" | 12=J41 物理插针;199=模组 SODIMM 脚;7=PJ 口内位号。 |
"GPIO3_PJ.07 的 3 是高电平 3V。" |
3 指第 3 组 GPIO 控制器;J 指端口,07 指位号。排针电平由载板规格确认。 |
"PM=1 就是 GPIO。" |
TRM 把 1 标为 RSVD1;GPIO_CNF 决定 GPIO/SFIO。 |
"设置 OUT=1 灯就一定亮。" |
还要 Pinmux 放行、CNF=GPIO、OE=输出、设备节点/驱动正常,外接电路也要正确。 |
| "J41-40 是地。" | J41-39 是 GND;40 是 I2S0_DOUT 信号。 |
**接灯留意载板能力:**2.1 的 J41 表为 12 脚标出很弱的 20 µA / -20 µA 驱动,并注明这类信号经过 TXB0108 电平转换器。课程"串电阻直连 LED"的思路适合解释高低电平,但不保证裸 LED 能明显发光;做可见亮灭测试宜让 12 脚驱动高阻缓冲/晶体管,LED 电流由合适电源与限流电阻提供。J41-12 到 J41-39 的信号/地关系先确认,不要把 40 脚当地线。
最后给自己做一个闭卷追踪:任选 J41 的另一个可用 GPIO,能否从载板 Table 3-3 找到模组/SoC 名,在 4.2 表找到设备树名,在 TRM 找到 Controller/Port/位,再分别算出 Pinmux 与 GPIO 的地址?能走通这一遍,就真正掌握了老师找 PJ.07 的方法。