
字符设备硬件控制应用ioctl到 Jetson Nano GPIO 点灯课程位置驱动初级 → 字符设备 → 实现定制文件操作 → 定制操作命令、硬件控制、顺序申请逆序释放。依据2026-10-05 19:11《硬件控制》录音转写学习系统中的“实现定制文件操作/应用程序/验证测试/硬件控制”笔记老师资料10.内核驱动/led/step3.实现文件操作ioctl/{led.c,app.c,Makefile}Tegra X1 芯片手册与 Jetson Nano J41 载板规格。转写里的同音字按老师源码、寄存器截图和手册校正。适用范围本例针对Jetson Nano / Tegra X1、J41 物理 12 脚GPIO3_PJ.07内核示例为板端4.9.253-tegra。其他 Jetson 型号不能直接复用这些物理寄存器地址。课堂截图是老师的验证结果文末代码尚未在你的板上实测。一、先把整条链路串起来上一节已经做到应用open(/dev/led)内核能找到驱动的.open。这节新增一个定制命令入口.unlocked_ioctl再把命令变成 GPIO 输出电平。老师先只打印收到的命令证明“应用 → 驱动”通路成立再按板卡引脚、载板原理图、Tegra X1 手册找到 GPIO 寄存器完成“驱动 → 硬件”通路。录音约 00:03—21:29、21:29—01:02:17A[app.c: open /dev/led] -- B[/dev/led: 字符设备 500:0] B -- C[led.c: cdev file_operations] C -- D[app.c: ioctl LED_ON / LED_OFF] D -- E[led_ioctl: 判断命令] E -- F[led_on / led_off: readl writel] F -- G[GPIO3_PJ.07: J41 物理 12 脚高/低电平] G -- H[外接 LED 亮/灭]应用每隔100000微秒发一次开灯、关灯命令约亮 0.1 秒、灭 0.1 秒每秒 5 次完整闪烁。这里的闪烁节奏由用户态程序控制内核驱动只接收命令并改电平不是在内核里写无限循环。二、先纠正接线39 脚是 GND40 脚不是课堂文字笔记写“地线接 40 脚”但同节的 J41 引脚截图和载板规格图都标明物理 39 脚是 GND物理 40 脚是 I²S 数据输出/GPIO 信号。本实验应以 39 脚接地接线前断电并按板上 J41 的 1 脚标记数脚位。本地 NVIDIA《Jetson Nano Developer Kit Carrier Board Specification》I:\Linux内核驱动\嵌入式AI班资料\4.官方文档\jetson nano的官方资料\SP-09732-001_v1.1.pdf第 21 页的引脚表已核对Jetson Nano 用户指南 介绍了 J41 接口。J41 物理脚位本例用途代码中的对应关系12GPIO 输出信号接 LED 电路控制端。GPIO3_PJ.07位 7Pinmux 寄存器0x70003150。39地线 GND。外接电路的公共地。40不是地线不接本例的 GND。是另一路信号不能拿来代替 39。老师的接线思路是“12 脚输出高电平串限流的 LED 接向 GND”所以代码约定高电平亮、低电平灭。课堂直接接法的方向是J41-12 → 限流电阻 → LED 正极(长脚) → LED 负极(短脚) → J41-39。若使用裸 LED电阻要按该 LED 和载板的输出能力选Jetson Nano J41 信号为 3.3V载板上有电平转换器驱动能力有限。先用低电流负载若直连 LED 很暗或电平被拉低应改用高阻输入的缓冲/驱动级不要靠减小电阻强行取电。NVIDIA Jetson Nano J41 说明若手头有逻辑电平 N 沟道 MOSFET可让 12 脚只驱动栅极J41-12 → 栅极源极到J41-39漏极接 LED 负极J41-1(3.3V) → 约 1kΩ 限流电阻 → LED 正极栅极再用约100kΩ下拉到 39 脚。这样 LED 电流由 3.3V 电源脚提供代码仍保持“高亮低灭”。选用元件前仍应核对实际 LED、MOSFET 和载板资料。三、命令如何从应用传到驱动老师在应用和驱动两侧都定义同一组值#defineLED_MAGICL#defineLED_ON_IOW(LED_MAGIC,1,int)#defineLED_OFF_IOW(LED_MAGIC,2,int)L是命令编码中的类型字段1/2区分点亮与熄灭两侧必须逐字一致。课程文字笔记中也出现过0/1编号但老师原始step3源码实际用1/2配套代码按原始源码保留。_IOW是按类型、序号、方向和参数大小编码命令值不是加密也不是安全认证类型相同的其他驱动仍可能发生编码冲突。Linux ioctl 编码文档本节驱动只看cmd没有使用arg老师的_IOW(..., int)因而有语义不一致的地方。为少改老师示例配套代码保留_IOW命令值应用调用时提供一个有效的int地址作第三参数驱动暂时忽略它。若以后正式设计“无参数开/关”接口两侧应同时换成_IO(L, 1/2)只改一侧会得到“不认识命令”。Linux ioctl 接口文档在驱动的file_operations中.unlocked_ioctl led_ioctl让应用的ioctl(fd, LED_ON, ...)进入led_ioctl()。switch (cmd)命中LED_ON调led_on()命中LED_OFF调led_off()其他命令返回-ENOTTY。老师原始源码的LED_OFF分支少了break会在灭灯后继续落到default返回-1本例改为每个合法分支直接return 0同时在应用侧检查ioctl()是否失败。四、从 12 脚找到寄存器老师采用的查找顺序是J41 物理 12 脚 → 载板规格/原理图 → Tegra X1 的 DAP4_SCLK → GPIO3_PJ.07 → 寄存器基地址和偏移量。NVIDIA 开发者论坛中也明确核对了“Jetson Nano 12 脚 GPIO3_PJ.07”J 属于 GPIO3 控制器的第二个 port因此其寄存器相对GPIO3_BASE0x6000D200有0x04等偏移。NVIDIA GPIO 映射讨论代码常量地址/偏移本例要改的位效果PINMUX_AUX_DAP4_SCLK_00x70003150PM[1:0]选非 I²S 功能TRISTATE[4]0。管脚复用允许作 GPIO 输出。GPIO3_BASE CNF0x6000D200 0x04位 7 1。GPIO3_PJ.07 选 GPIO 模式。GPIO3_BASE OE0x6000D200 0x14位 7 1。GPIO 输出使能。GPIO3_BASE OUT0x6000D200 0x24位 7 1 或 0。高电平亮、低电平灭前提是外部 LED 按本例接法。ioremap(物理地址, 大小)得到内核可用于 MMIO 的__iomem地址再用readl()读 32 位寄存器改目标位用writel()写回不能把物理地址当普通 C 指针直接解引用。卸载时iounmap()与两次映射配对。Linux MMIO 文档/* 老师的核心方法只改变目标位 7保留同组其他 GPIO 的位。 */writel(readl(gpio_baseOUT)|(1U7),gpio_baseOUT);/* 亮 */writel(readl(gpio_baseOUT)~(1U7),gpio_baseOUT);/* 灭 */老师还在初始化末尾写了MSK_CNF、MSK_OE并把它们解释为“取消屏蔽”。芯片手册截图说明这类掩码写寄存器的高 8 位控制哪些位允许被写低 8 位是要写入的值。老师的| (1 7)只涉及低位不能按注释理解为“解除屏蔽”本例既然已经用普通CNF/OE/OUT做读改写就省去这两句不改变课程的主流程。五、完整代码沿用老师的led.c和app.c结构下载或复制同目录的三个文件到 Ubuntu 虚拟机的一个独立工程目录led.c注册设备号、映射寄存器、准备 GPIO、添加cdev并用ioctl控制高低电平。app.c打开/dev/led循环发送LED_ON/LED_OFF按CtrlC后先灭灯再关闭文件。Makefile把模块编译目录指向你此前使用的/home/yhai/bsp/Linux_for_Tegra/source/public/kernel/kernel-4.9目标架构是 arm64。代码保留了老师的固定主次号500:0、L、编号1/2、DAP4_SCLK/GPIO3 物理寄存器和100000微秒间隔。为使示例可重复加载、可检查错误做了以下有限修正先完成两处ioremap()和 GPIO 准备最后才cdev_add()因为cdev_add()一成功旧的/dev/led节点可能立即打开并调用回调不能让回调看到未映射的 GPIO 地址。Linux 字符设备 API映射失败明确返回-ENOMEM并按“成功取得什么就释放什么”的顺序清理。老师的部分错误路径未设置负错误码可能让模块初始化错误地返回成功。LED_OFF正常返回0未知命令返回-ENOTTY应用检查每次ioctl()的返回值并提供与_IOW(..., int)匹配的第三参数。初始化先把 OUT 位 7 置低再使能输出卸载先删除cdev、把 LED 关掉再释放映射和设备号。这样执行insmod时灯保持灭运行app后才开始闪烁。**课程示例的边界**这是为了学习寄存器、ioremap()和字符设备调用链的直接寄存器实验没有接入 Linux GPIO/pinctrl 的资源管理。运行前确保 12 脚未被 I²S 或其他驱动占用卸载后示例会拉低输出但不会恢复加载前的全部 pinmux/CNF/OE 配置。其他板卡、不同载板或产品驱动应使用与硬件描述和 GPIO 子系统匹配的实现。六、你这台板子的复现步骤0. 板端确认对象与接线先在板子执行cat/proc/device-tree/modeluname-runame-m只有确认是Jetson Nano / Tegra X1并与此前的4.9.253-tegra、aarch64相符才使用本例固定地址。接线按上面表格核对12 为控制信号、39 为 GND不要按文字笔记把地线接 40。若你的实际 LED 电路为“低电平亮”需反转led_on()/led_off()的 OUT 位逻辑本文以老师“高亮低灭”接法为准。1. Ubuntu 虚拟机编译在复制好led.c、app.c、Makefile的工程目录执行makecleanmakeaarch64-linux-gnu-gcc-Wall-Wextra-O2app.c-oappfileled.ko app modinfo-Fvermagic led.ko预期led.ko为ARM aarch64 relocatableapp为AArch64 用户程序模块vermagic以4.9.253-tegra开头并与板上运行内核匹配。若从 VMware 共享目录编译出现“文件修改时间在未来”可把源码复制到 Ubuntu 自身文件系统的目录再make clean make。不要把之前 x86_64 的led.ko或旧版应用当成新文件传板。2. 传到板子老师使用cp led.ko a.out /nfs/rootfs前提是板上已经挂载并能访问同一个 NFS 根目录。你的 Ubuntu/Jetson 已用过文件传输时也可用 SSH# 仅在板端地址仍为此前的 10.20.3.91 时才照抄这条scpled.ko app aidb10.20.3.91:/home/aidb/地址若变动先在板子执行ip -br addr查当前 IP再替换scp目标。这里的app是新交叉编译的应用老师默认输出名a.out功能对应但文件名不同。3. 板端加载、创建节点、闪烁、卸载在板子执行先停掉仍运行的旧版app并确认旧led模块已卸载cd/home/aidb lsmod|grep^led # 若上一行确实显示旧 led 模块再执行sudo rmmod ledsudoinsmod ./led.kogrepyhai_led/proc/devicesif[!-e/dev/led];thensudomknod/dev/led c5000fils-l/dev/ledsudo./appls -l /dev/led应显示它是字符设备主号500、次号0。若已有/dev/led却不是c 500, 0先查清原节点来源不能把错误节点当成通过。运行后观察外接 LED 闪烁按CtrlC程序发最后一个关灯命令并关闭文件。随后再查日志和卸载sudodmesg|tail-n30sudormmod ledgrepyhai_led/proc/devicessudodmesg|tail-n104. 验收判据观察应得结论insmod成功/proc/devices出现500 yhai_led。设备号取得成功模块已加载。/dev/led是c 500, 0app无open或ioctl错误。应用找到正确节点命令与驱动定义匹配。dmesg反复出现led on、led offLED 实际明暗交替。“应用 → 驱动 → GPIO → 外接灯”整条链路验证成功。仅有日志不能替代实际闪烁。CtrlC 后灭灯rmmod后/proc/devices不再有yhai_led。用户程序、字符设备、映射和设备号按示例退出。七、遇到故障先按现象定位现象先查什么insmod: File existslsmodInvalid module formatfile led.ko、modinfo -F vermagic led.ko与板子的uname -r、uname -m。open /dev/led报错/dev/led是否存在、是否为c 500, 0、模块是否加载、设备节点权限。ioctl ... Inappropriate ioctl for device两侧的L和命令序号是否同为1/2板上是否还是旧led.ko。日志有led on/offLED 不闪先核对12/39 接线、LED 极性、限流电路再用万用表测 J41-12 相对 J41-39 的高低变化。若 GPIO 电平未变化再检查板型、管脚复用和是否被其他驱动占用。卸载提示模块仍在使用先让应用正常退出并关闭/dev/led再rmmod led。八、这节课要记住的四句话ioctl让应用发送课程自定义的LED_ON/LED_OFF命令file_operations.unlocked_ioctl让命令进入驱动。ioremap负责把已确认的寄存器物理地址映射为内核可访问的 I/O 地址readl/writel负责读改写目标是 GPIO3_PJ.07 的第 7 位。GPIO 12 脚只是物理接线点主设备号 500 是/dev/led的软件编号两个“编号 12/500”不是一回事。老师的验证路径是“应用操作 → 内核日志 → 引脚电平 → 灯实际闪烁”你此前的open led ok只完成到应用能打开设备文件运行本节代码并看到真实闪烁才算完成硬件控制。