资讯详情

RK安卓驱动开发入门:设备树、平台驱动与HAL/JNI链路

📅 2026/10/21 17:58:16 | 华诺云谱 👁 阅读
RK安卓驱动开发入门:设备树、平台驱动与HAL/JNI链路
第一次接触 RK 平台的安卓驱动开发时我最大的感受是“没人把最关键的前置知识说清楚”。大家上来就讲设备树、讲内核模块、讲 HAL 接口但小白连“要编译什么、烧什么、去哪看日志”都没概念课程自然就断片了。这篇就是专门解决这个问题的急救包把 RK 安卓驱动开发真正绕不开的知识底线一次性补齐。它不是书上的目录而是按我实际带人、实际调板子的顺序整理的。看完这篇你至少不会再被一个 probe 没进、一个 log 打不出来这种问题卡住三天。要学 RK 安卓驱动这几个底座概念必须稳稳立住内核怎么描述硬件、驱动怎么注册接口、上层怎么拿到数据、日志怎么看、环境怎么搭。1. 先拆清楚整个链路RK 安卓驱动开发到底在动哪一块很多新手接到任务时一脸懵是因为不知道“驱动开发”这四个字在 RK 平台上到底覆盖了哪几层。这里先看一条完整的软硬件链路App → Android Framework → HAL 层 → JNI/Binder → 内核驱动 → 硬件设备。安卓驱动开发并不是让你天天写内核代码至少一半以上的时间花在 HAL 层和设备树配置上。把这条链路拆明白了你才知道手里的需求该从哪一层下手。以常见的 RK 平台整机方案为例一个外设比如触摸屏或者传感器从硬件上电到 App 收到事件过程大致是这样硬件通过 GPIO、I2C、SPI 等总线连到主控内核驱动负责初始化设备并注册一个接口HAL 层的动态库加载这个接口把原始数据转换成安卓系统标准的数据格式再往上就是 Framework 服务和应用层。开发者的工作就是保证每一环都能接得上。这条链路上有一个容易误导新手的点驱动开发不等于只能动 kernel 目录。实际上在 RK 的 SDK 里菜单menuconfig改配置、dts 设备树调整引脚、HAL 源码修改数据格式统称都叫“做驱动”。甚至你在系统服务里加了一个权限判断也会被项目经理算成“驱动工作量”。所以你在看课时不要只盯住内核模块编程那一章HAL 和 JNI 的连接知识占比可能更高而且直接影响你调试时能不能看到数据。1.1 RK 平台里我们日常操作的几个目录RK 的安卓 SDK 一般基于 Android 版本迭代常见的有 Android 11、12、13 等版本。目录结构很多但真正做驱动开发时你常打交道的区域很固定分别是device/rockchip板级配置、板级设备树文件、产品相关的 makefile 都在这里。换一个主板主要就是改这个目录下的内容。kernel/arch/arm64/boot/dts/rockchip实际生效的内核设备树源文件定义硬件资源比如用到了哪组 I2C、哪个中断脚、供电引脚。kernel/drivers各种外设驱动源码。你在网上搜到的按键驱动、LED 驱动、传感器驱动通常都丢在这个目录下。hardware/rockchipRK 对硬件抽象层做的适配。Camera、显示、音频等外设的 HAL 实现基本都在这里。先把这个目录感建立起来后续改代码不会像无头苍蝇。比如你在需求单上看到“把红外遥控的 GPIO 改一下”那基本只需要打开设备树找到对应节点改引脚编号和复用功能就能编译验证根本没到写驱动代码的程度。这类需求其实占了日常开发的很大比例。1.2 小白最容易忽视的分工关系RK 安卓驱动开发有一个明显不同于纯 Linux 驱动开发的点安卓系统有权限管控和进程隔离。你的驱动即使在内核里跑通了上层能不能访问还要过 SELinux 和 Binder 权限这两关。很多新手把 probe 调通了但 app 打开节点时报 Permission denied就是因为没有处理 SELinux 加域和权限标签。我建议新手一开始就建立这个分工意识内核层负责“通了没”HAL 层负责“对不对”服务层负责“给不给”。也就是说内核代码负责硬件吞吐HAL 层保证数据语义符合上层预期系统服务决定谁能访问。明白这个分工你在排除问题时就会按层过滤而不是在一次调试里把所有可能性揉在一起。2. 内核侧三个必备概念设备树、平台驱动、字符接口内核部分的知识是最吓人的但实际做 RK 平台外设驱动真正高频出现的概念其实很聚焦。把设备树、平台驱动、字符接口这三件事搞懂你就能读懂大部分 RK 外设驱动的源码。2.1 设备树硬件的自我介绍手册设备树 (Device Tree) 是一个描述硬件信息的树状数据结构。它解决的问题是过去内核用一堆 board 文件来描述硬件硬件一变就得改内核代码重新编译内核现在内核把硬件描述外置到 dts 文件里驱动代码保持通用硬件资源配置在设备树中独立管理。看一个经典例子RK 平台上某颗 I2C 传感器的描述i2c0 { status okay; clock-frequency 400000; light_sensor: light38 { compatible vendor,light-sensor; reg 0x38; interrupt-parent gpio0; interrupts RK_PA6 IRQ_TYPE_LEVEL_LOW; supply-supply vcc3v3_sys; }; };请注意这段描述的作用它告诉内核i2c0 总线处于可用状态频率 400K总线地址 0x38 挂着一个名为 light_sensor 的设备它的中断接到了 gpio0 的 PA6 脚供电由某路电源提供。驱动代码里不写地址不写中断号通过查找节点拿这些信息——这就是设备树解耦硬件配置与驱动逻辑的核心思想。新手要注意status属性。RK 平台上一个外设“死活不工作”很多时候不是驱动问题而是设备树里这个节点的 status 被写成了 disabled。因为 RK SDK 里一个节点经常会同时出现在几个 dtsi 文件中有的表示启用有的表示禁用最终生效规则以板级 dts 覆盖结果为准。排查时不看到这一点很容易在代码里白转圈。compatible是另一个重点。它是一把钥匙驱动通过它找到自己管理的设备节点。驱动端会用of_match_table定义能匹配的字符串列表。两边对不上probe 就不会执行。新手头几次丢这个东西十有八九是 dts 加了节点却忘了在驱动的 of_device_id 里写匹配字符串或者字符串拼写有大小写差异。2.2 平台驱动与 probe驱动代码的启动入口设备树描述好硬件后内核怎么知道该执行哪段驱动代码答案就是平台驱动 (Platform Driver)。RK 主流 SoC 的软硬件框架内核驱动大多基于 platform_driver 模型编写。一个最小平台驱动初始化流程static const struct of_device_id sensor_of_match[] { { .compatible vendor,light-sensor, }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, sensor_of_match); static int sensor_probe(struct platform_device *pdev) { struct device *dev pdev-dev; struct device_node *np dev-of_node; int irq; irq platform_get_irq(pdev, 0); if (irq 0) return irq; dev_info(dev, probe ok, irq%d\n, irq); return 0; } static struct platform_driver sensor_driver { .probe sensor_probe, .driver { .name light_sensor, .of_match_table sensor_of_match, }, }; module_platform_driver(sensor_driver);看代码时注意sensor_probe的触发机制内核在启动或运行时总线注册驱动与设备树节点进行匹配一旦 compatible 对上就调用 probe。如果你的驱动编译进了内核但启动日志里没打 probe 的打印不要先怀疑代码语法去查匹配表、status 和编译选项。对于新写驱动的人我的建议是先把 probe 和 remove 这两个基础回调写好在 probe 里用dev_info打上一条“probe ok”的日志。这是驱动开发里成本最低的观测点能帮你快速确认硬件资源有没有被内核真正接管。2.3 寄存器与中断是绕不开的基本功设备树和平台驱动能把驱动“挂起来”但驱动真正控制硬件时还要面对寄存器和中断。RK 芯片外设寄存器操作方式和传统 ARM 处理器一致一般通过 remap 后的基地址加偏移来访问。内核里常见做法是先用设备树描述寄存器范围然后在 probe 中利用devm_platform_ioremap_resource获取寄存器的虚拟地址基址再用readl和writel读写寄存器。写寄存器务必注意一个坑寄存器位宽。RK 平台多数寄存器是 32 位但个别总线上的设备是 16 位或 8 位寄存器。读写位宽搞错设备表现就会非常诡异比如配置中断触发了但实际上没生效。遇到这类问题可以先用寄存器的 dump 对比工具查看实际值而不是反复改代码。中断处理方面RK 平台外设中断主要在设备树里声明驱动通过request_threaded_irq或者devm_request_threaded_irq申请。新手容易忽略的一点是申请中断时设置标志位比如/proc/interrupts里看不到中断计数一般就是 IRQF_TRIGGER 没配对触发方式。硬件触发是上升沿你却申请了下降沿中断就永远不来看起来像是硬件坏了。字符接口是内核向用户空间提供访问途径的常见方案最常见的是miscdevice。它自动注册一个主设备号你用次设备号区分具体节点即可。这类设备注册起来简单适合外设驱动把读写接口抛给上层 HAL 使用。写字符接口时最好实现基本的open、read、write、unlocked_ioctl。像 RK 常见的外设控制都是通过 ioctl 命令码从用户空间传入参数在内核空间完成具体动作。不要小看copy_from_user和copy_to_user新手最常犯的错误是直接在内核里对用户指针做取值操作导致内核崩溃。无论如何都要用拷贝函数这个习惯越早建立越好。3. HAL 与 JNI数据怎么从内核走到 App很多单片机和 Linux 工程师转 RK 安卓驱动时会在内核部分畅通无阻但一做到系统层就卡住。因为在安卓驱动开发里你写完内核驱动只算完成“硬件访问”部分真正和 App 对接的数据链路要经过 HAL 层和 JNI。3.1 HAL 层解决什么问题HAL 的完整意思是 Hardware Abstraction Layer目的是把硬件差异封装在系统底层。安卓 Framework 不希望知道每家芯片厂商的具体实现于是约定一层稳定的接口厂商各自实现动态库。App 和 Framework 的调用不直接访问内核节点而是访问 HAL 提供的方法。以 RK 平台某个外设为例流程是这样的内核驱动在/dev下创建节点并等待用户空间打开HAL 模块实现标准接口比如hal_module_open、get_sensor_listHAL 内部封装了往/dev节点写数据和读数据的逻辑JNI 通过 HAL 动态库调用具体方法把结果传回 Java 层。这一套下来上层完全不知道底层用的是 GPIO 还是 I2C。HAL 层开发要点是区分“标准接口”和“扩展接口”。如果硬件厂商有特殊需求可以在标准 HAL 里加扩展函数但主流程必须符合 Framework 的预期。有些新手会自己发明一套调用流程结果系统服务根本不会去调用它。正确思路是先看上层服务到底调了哪个 HAL 符号再去实现它。3.2 JNI从 Java 到 Native 的桥梁JNI (Java Native Interface) 连接 Java 和 C/C。在安卓驱动开发语境下JNI 一般出现在 Framework 服务和 HAL 之间。一个典型的 JNI 函数注册流程涉及System.loadLibrary、本地方法的native声明、以及对应 C 函数的JNIEXPORT实现。JNI 层把上层传下来的参数转换后调用 HAL 层或直接打开节点操作。操作 JNI 时最容易出现的问题是没有处理返回值类型对应关系。Java 的int在 JNI 里是jintJava 的long是jlongJava 的String要转换成const char*。类型对应错了常见结果是某个参数变成 0。调试这种问题最简单的办法是先在 Native 方法里打印日志确认参数真的传进来了。JNI 层还会涉及 Java VM 相关对象引用。不要在线程里直接调用AttachCurrentThread忘记DetachCurrentThread会导致 VM 崩溃。这部分功能比较深入建议新手先把简单的数据传递跑通再去处理异步线程回调。3.3 了解一点 Binder 和 AIDL 的门道安卓系统服务要跨进程调用时通常会走 Binder。你写的驱动自己搞一个 HAL 接口后可能还要封装一个系统服务然后用 AIDL 定义服务接口这样 App 才能通过服务拿到数据。这个概念初学者不一定要立刻掌握但至少要听得懂Binder 是安卓跨进程通信机制AIDL 是描述接口的 IDL 语言。RK 平台里很多外设功能比如背光调节、传感器上报、音频路由都有对应的系统服务。你如果只需要临时调试可以先绕过它直接写一个小的测试 App用 JNI 访问节点。但你在阅读底层日志时经常会看到Binder关键字。所以建议至少熟悉dumpsys命令和服务注册表结构看不懂再查不用一步到位。4. 实操过一遍编译环境、日志查看与快速验证理论知识说完了接下来进入动手环节。RK 安卓驱动开发有一个特点就是开发验证通常需要真机或板卡不能只靠模拟器。动手能力要求很高但反过来这里的调试线索也都是实打实的。4.1 搭建开发环境与编译固件RK 平台的整体编译流程一般依赖 Linux 环境常见使用 Ubuntu 18.04 或 20.04。SDK 里的编译脚本一般会封装好比如编译内核、编译安卓系统、生成统一镜像。你第一次拿到 SDK 时不要急着改代码先完整编译一次原版固件确保环境没问题。编译前要注意不要用 root 用户直接编译部分脚本不要跨文件系统编译不要用 Windows 解压 SDK 包。这三个问题都会导致编译过程出现奇怪的符号错误或者权限错误。如果看到Too many open files多半是系统文件句柄限制可以在/etc/security/limits.conf中调高 4096 甚至更高。还有一处个人经验RK SDK 对磁盘剩余空间要求比较高建议预留 200GB 以上。之前有人只留了 50GB编译到一半提示磁盘满还误以为源码损坏白白重来了一遍。最后要把“编译镜像”、“烧录工具”和“分区块烧录”这几件事分开理解。RK 平台支持整体烧录也支持单独烧录 kernel 分区或 dtb 分区。调试驱动改内核时单独烧录 boot 分区通常比重烧整个固件快很多。不同屏幕配置烧写 screen 分区时注意分区表和实际镜像对应关系免得烧错。4.2 三种日志别搞混串口、dmesg、logcatRK 安卓驱动开发调试里我见到最多的问题就是日志在哪里看看哪一份日志。这是完全不一样的东西。串口日志是内核和 bootloader 输出开机时最有用可以观察到早期硬件初始化过程。dmesg 是内核环形缓冲区的日志可以在系统起来后看内核昨晚发生了什么。logcat 是安卓用户空间的日志HAL 和 App 的打印都在里面。日常调试中最佳实践是同时开着串口日志和 logcat。比如你怀疑驱动 probe 没执行先看 dmesg 或串口里有没有对应打印你怀疑 HAL 没拿到数据就看 logcat 里的对应 TAG。不要用 logcat 的过滤功能去筛内核日志这样容易漏东西。如果设备完全起不来只能看串口。不要在没接串口的情况下反复烧录抓不到 log。你要确认开发板串口通道是否被复用成其他功能。之前某块板子的 UART 调试口被设备树里配置成了蓝牙 HCI 口串口一直白屏所有人都怀疑代码异常后来才发现是引脚复用冲突。遇到板子完全无输出时先查引脚复用配置再排查代码。4.3 sysfs 和调试节点的使用在 RK 平台调试时内核驱动通常会在 sysfs挂载于 /sys 目录或者 debugfs/sys/kernel/debug下创建节点方便手动触发操作。比如某个传感器驱动可能会在 /sys/bus/i2c/devices/0-0038/ 下面暴露一些属性文件。你用 shell 读取和写入这些文件就能绕过上层应用直接测试硬件行为。操作 sysfs 时注意权限一般需要 root 权限或者修改文件属主。调试某些设备时直接用echo 1 /sys/.../enable是正常测试手段但不要在生产模式长期保持手工 echo否则上层逻辑可能冲突。调试节点是辅助手段不是业务运行路径。另外很多 RK 平台会有一些厂商提供的测试应用里面封装的设备自检逻辑非常实用优先级高于你自己写测试程序。我自己的习惯是先把寄存器读写能力封装成一个独立的命令行工具用它在板上随意读写 I2C 设备、GPIO 和寄存器地址。遇到问题时先用它验证硬件是否正常再回头查驱动状态。这在底层开发中节约的时间非常可观。5. 经验速查高频问题和排查思路这一部分把节奏放慢整理成可以直接对照使用的表格。RK 安卓驱动开发里高频出现的问题其实数量是固定的排除法用熟了很多“疑难杂症”两小时内就可以定位。5.1 常见问题速查表现象可能原因建议检查点probe 根本没执行compatible 不匹配 / dts status 为 disabled / 驱动没编进内核检查 dmesg 里有无 driver 注册查 of_match_table 字符串确认 menuconfig 开启对应驱动probe 执行了但读寄存器返回空时钟未开启 / 供电未打开 / 引脚复用冲突检查设备树 regulator、clock、pinctrl 配置用 io dump 确认寄存器基地址上层打开设备节点失败SELinux 权限限制 / 设备节点缺失 / 节点创建失败先手动查 /dev 节点是否存在再查 SELinux audit 日志数据能读但数值全为 0 或异常I2C 地址错误 / 位宽不对 / 寄存器地址错对照数据手册和驱动源码用 i2c-tools 手动读设备寄存器编译内核报头文件缺失SDK 环境变量或路径未正确加载检查 source build/envsetup.sh 是否执行目标 platform 是否选对设备睡眠后唤醒不了中断唤醒能力配置问题 / 电源域管理问题 / dts 中断触发标志不对检查设备树 interrupts 属性、驱动中 enable_irq_wake 是否调用这张表并不是标准答案但覆盖了 RK 平台上超过一半的日常故障。遇到问题先按表格中的检查点快速过一遍再深入代码细节能避免很多无效修改。5.2 我个人的排查路径如果你刚接手一个驱动 bug我建议按以下顺序排查不要一上来就改代码第一确认编译进镜像的驱动版本就是你正在看的源码版本。版本错位的问题在多人协作中极其常见是最隐蔽的时间黑洞。第二确认设备树里节点实际生效的状态。先dmesg | grep一下或者读取/proc/device-tree对应路径的 status。第三查看内核日志里有没有和此节点相关的报错或警告。有时候 probe 已经进了但资源申请失败只是日志刷得比较快。第四用命令行工具手动验证总线通信。比如用 i2cdetect 确认设备在总线上存在。如果这一步失败说明问题在前面不用往下查。第五检查上层访问路径和权限确认节点权限和 SELinux 标签。第六锁定问题后再做最小改动并重复验证。这个顺序的核心思路是从外围到内核、从配置到代码、从硬件到软件。先验证“有没有这个设备”再看“驱动为什么没响应”最后才去改源码。顺序反过来新手经常会把能用的代码改坏然后花更多时间修回来。5.3 写给小白的一段真心话我见过很多新人被驱动开发吓住因为网上资料太散而且大多数资料写的都是纯 Linux 驱动跟 RK 安卓平台的实际工作有差距。实际上入门阶段并不需要你把内核内存管理、调度器都搞懂。你只需要能读懂设备树、能看懂平台驱动框架、能分清 HAL 和 JNI 的作用、能熟练抓取日志就足以开始承担 RK 平台外设驱动的开发任务了。在此基础上再用真实项目反复打磨。每次遇到问题不要急着问人先按上文的排查路径走一遍。这里的成长速度取决于你独立定位问题的次数而不是你背了多少函数接口。内核驱动框架会随着 Linux 版本变化HAL 接口会随着安卓版本变化但“链路思维”和“分层排查”的方法是不会变的。把这两样东西刻在脑子里以后不管 SDK 怎么升级你都能很快适应。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑