Nuttx驱动开发指南
翻译自: https://nuttx.apache.org/docs/latest/guides/drivers.html
某些 NuttX 板并不完全支持所有片上外设。如果您需要此硬件的支持,则需要从另一个芯片移植驱动程序,或者自己编写一个驱动程序。本节讨论如何执行此作。
驱动移植
通常,对片上外设的支持存在于密切相关的芯片中,甚至存在于不同的系列或不同的制造商中。许多芯片由不同的知识产权 (IP) 块组成,这些块从 Cadence、Synopsys 等供应商那里获得许可。IP 块可能足够相似,只需稍作修改即可使用另一个芯片的驱动程序。
1. 在 NuttX 源码中寻找相似驱动
- 查看外设数据手册中的寄存器名称。
- 在 NuttX 代码库中搜索这些寄存器名称(尝试多个不同的名称)。
- 注意:需要对比数据手册与头文件、代码文件的差异,架构间可能存在显著差异。
2. 在其他开源项目中寻找相似驱动
在 U-Boot、Zephyr 或 BSD Unix(OpenBSD、FreeBSD、NetBSD)源代码中找到类似的驱动程序:
- 仅用于灵感,不能直接复制代码,因许可证不兼容(如 Apache 2.0 和 BSD 许可证需要原始作者的软件授权协议)。
- 可通过调试查看驱动的工作原理。
- U-Boot 的驱动通常比 BSD Unix 的驱动更简单,易于理解。
3. 理解驱动的工作原理
printf 调试
- 使用
custinfo()在代码中插入日志,以便在代码运行时查看执行路径并查看变量。使用custinfo()而不是其他日志记录快捷方式(mcinfo()等)的原因是,您可以打开和关闭其他日志记录,并且仍然可以看到您的自定义调试日志记录。有时,平息来自特定调试日志记录快捷方式的大量日志记录非常有用。 - 请注意,将信息打印到控制台将影响计时。
保留一个仅包含调试设置的文件, 如
CONFIG_DEBUG_CUSTOM_INFO=y
(etc..)
在运行 make menuconfig 后,将配置追加到 .config 文件中:
$ cat .config debugsettings > .config1 ; mv .config1 .config
使用多线程与中断调试
中断:检查 C 栈帧以确定当前执行环境。
uint32_t frame = 0; // 必须是函数的第一行
uint32_t p_frame;
frame++; // 防止优化
p_frame = (uint32_t)(&frame);
custinfo("p_frame: %08x\n", p_frame);
线程:获取线程 ID 以确定当前线程。
pthread_t thread_id = pthread_self();
custinfo("pthread_id: %08x\n", thread_id);
GDB 调试
GDB 是一个很棒的工具。在本指南中,我们已经使用它来加载和运行我们的程序。但它可以做的更多。您可以单步执行代码、检查变量和内存、设置断点等。我通常从命令行使用它,但也在 JetBrains 的 Clion 等 IDE 中使用它,这样更容易查看代码上下文。
我发现一个至关重要的调试扩展功能是检查内存块的能力,例如NuttX系统中用于读写存储介质或网络适配器的缓冲区。这在分析二进制协议数据或排查内存损坏问题时尤为关键。针对此需求,Stack Overflow上关于使用 GDB 检查内存的 Stack Overflow 问题提供了实用方法:
通过向.gdbinit配置文件添加自定义命令,可快速调用xxd工具以十六进制+ASCII格式转储内存内容
define xxd
if $argc < 2
set $size = sizeof(*$arg0)
else
set $size = $arg1
end
dump binary memory dump.bin $arg0 ((void *)$arg0)+$size
eval "shell xxd -o %d dump.bin; rm dump.bin", ((void *)$arg0)
end
document xxd
Dump memory with xxd command (keep the address as offset)
xxd addr [size]
addr -- expression resolvable as an address
size -- size (in byte) of memory to dump
sizeof(*addr) is used by default end
下面是一个简短的 GDB 会话,它展示了这在实践中是什么样子的。请注意,正在检查的内存位置(此处为 0x200aa9eo)是传递给 mmcsd_readsingle 的缓冲区:
Program received signal SIGTRAP, Trace/breakpoint trap.
0x200166e8 in up_idle () at common/arm_idle.c:78
78 }
(gdb) b mmcsd_readsingle
Breakpoint 1 at 0x2006ea70: file mmcsd/mmcsd_sdio.c, line 1371.
(gdb) c
Continuing.
Breakpoint 1, mmcsd_readsingle (priv=0x200aa8c0, buffer=0x200aa9e0 "WRTEST TXT \030", startblock=2432) at mmcsd/mmcsd_sdio.c:1371
1371 finfo("startblock=%d\n", startblock);
(gdb) xxd 0x200aa9e0 200
200aa9e0: 5752 5445 5354 2020 5458 5420 1800 0000 WRTEST TXT ....
200aa9f0: 0000 0000 0000 0000 0000 5500 1100 0000 ..........U.....
200aaa00: 5752 5445 5354 3520 5458 5420 1800 0000 WRTEST5 TXT ....
200aaa10: 0000 0000 0000 0000 0000 5800 1500 0000 ..........X.....
200aaa20: e552 5445 5854 3620 5458 5420 1800 0000 .RTEXT6 TXT ....
200aaa30: 0000 0000 0000 0000 0000 5600 1200 0000 ..........V.....
200aaa40: 5752 5445 5354 3620 5458 5420 1800 0000 WRTEST6 TXT ....
200aaa50: 0000 0000 0000 0000 0000 5600 1200 0000 ..........V.....
200aaa60: 0000 0000 0000 0000 0000 0000 0000 0000 ................
200aaa70: 0000 0000 0000 0000 0000 0000 0000 0000 ................
200aaa80: 0000 0000 0000 0000 0000 0000 0000 0000 ................
200aaa90: 0000 0000 0000 0000 0000 0000 0000 0000 ................
200aaaa0: 0000 0000 0000 0000 ........
NuttX驱动作为参考
若您并非从其他架构移植NuttX驱动,参考同类NuttX驱动仍有助益。
例如:实现以太网驱动时可参考其他NuttX以太网驱动,开发SD卡驱动时可查阅同类存储设备驱动。即使芯片特定代码不同,与NuttX系统交互的框架结构仍可复用。
使用芯片数据手册
要移植或写入驱动程序,您必须熟悉芯片数据表中的信息。一定要找到你的芯片的数据表,并阅读与你正在使用的外设相关的部分。提前这样做会在以后节省大量时间。
另一件通常有用的事情是参考制造商提供的示例代码,或者来自其他作系统(如 U-Boot、Zephyr 或 FreeBSD)的驱动程序代码,同时参考数据表 — 了解工作代码如何实现必要的算法通常有助于了解驱动程序需要如何工作。
如何使用数据表, 系统级芯片 (SoC) 数据表中的关键信息通常是:
- 芯片架构图:Chip Architecture Diagram — 显示芯片的子部分 (CPU、系统总线、外设、I/O 等) 如何相互连接。
- 内存映射:Memory Map— 显示 memory 中 peripheral registers 的位置。此信息通常进入头文件。
- DMA引擎:DMA Engine — if Direct Memory Access (DMA) is used, this may have info on how to use it.
- 外设:Peripheral — 数据表通常有一个关于外设如何工作的部分。其中的关键部分包括:
- 寄存器列表: Registers List — 外设基本内存地址的名称和偏移量。这需要进入头文件。
- 寄存器映射: Register Map — 每个寄存器的大小是多少,位是什么意思?您将需要创建
#defines在您的代码将用于作 registers 的头文件中。请参阅其他驱动程序头文件 例子。
逻辑分析仪应用
对于涉及输入和输出 (I/O) 的驱动程序,尤其是涉及 SD 卡、SPI、I2C 等复杂协议的驱动程序,实际查看进出芯片引脚的波形非常有帮助。 逻辑分析仪 可以捕获该信息并以图形方式显示它,让您看到驱动是否在链路上做正确的事情
DMA调试技巧
- 在 DMA传输 之前、期间和之后 dump寄存器。一些 NuttX 驱动程序(例如
sam_sdmmc.c或imxrt_sdmmc.c)具有用于调试寄存器状态的内置代码,可以在 DMA 传输之前、期间和之后立即对寄存器进行采样,以及可以将外设寄存器以格式良好的方式dump到控制台设备(可以是serial console, network console, or memory)。如果您尝试调试 DMA 代码,请考虑使用类似这样的东西来查看芯片内部发生的情况。 - 将 register 设置与数据表中确定的预期设置进行比较,或者从另一个作系统(U-Boot、Zephyr、FreeBSD 等)中的工作代码中dump registers 来确定。
- 使用上面提到的
xxdGDB 工具在传输之前、期间和之后dump NuttX 内存缓冲区,以查看数据是否正确传输,是否存在超载或欠载,或诊断数据存储在错误位置的情况。 - printf 调试寄存器状态在这里也可以提供帮助。
- 请记住,日志记录可以更改您可能使用的任何算法的时间,因此在添加或删除日志记录时,事情可能会启动或停止工作。绝对是在禁用日志记录的情况下进行测试。

浙公网安备 33010602011771号