RK3568 Android 15驱动开发实战:从环境搭建到HAL集成
2026/7/29 2:23:52 网站建设 项目流程

在嵌入式开发领域,RK3568 作为一款性能均衡、接口丰富的通用型 SoC,被广泛应用于智能终端、工业控制和边缘计算设备。随着 Android 15 的发布,开发者面临新的系统特性和驱动适配需求。正点原子推出的 RK3568 开发板为学习和实战提供了稳定的硬件平台,而驱动开发作为连接硬件与操作系统的核心环节,其规范性和可调试性直接决定了项目的成败。

本文将以正点原子 RK3568 开发板为硬件基础,基于 Android 15 系统,完整演示一个可实操的驱动开发流程。内容涵盖环境搭建、内核配置、驱动模块编写、编译调试、HAL 层对接以及常见问题排查,目标是让读者掌握从零开始为 RK3568 开发定制外设驱动的能力,并理解 Android 系统下驱动开发的完整链路。

1. RK3568 与 Android 15 驱动开发环境准备

1.1 硬件与软件基础要求

在开始驱动开发前,需确保具备以下基础环境:

  • 硬件平台:正点原子 RK3568 开发板(至少配备 4GB RAM、32GB eMMC)、Type-C 数据线、12V 电源适配器、串口调试工具(如 USB 转 TTL 模块)。
  • 主机环境:Ubuntu 20.04 LTS 或 22.04 LTS(推荐 16GB 内存、200GB 可用磁盘空间)。
  • 工具链:RK3568 官方提供的 GCC 交叉编译工具链(aarch64-linux-gnu-)。
  • 源码准备:Android 15 系统源码、RK3568 内核源码(版本需与 Android 系统匹配)。

以下为环境依赖的详细清单:

组件版本/型号获取方式备注
开发板正点原子 RK3568官方渠道确认板载外设接口(如 GPIO、I2C、SPI)
主机系统Ubuntu 20.04/22.04官网下载需安装必备软件包(git、curl、tar、make)
工具链aarch64-linux-gnu-gcc 10.3RK 官网或 Linaro注意与内核编译配置一致
内核源码kernel-5.10Rockchip GitHub 仓库对应 Android 15 的基线版本
Android 源码android-15.0.0_r1AOSP 镜像站可仅下载设备相关部分

1.2 开发环境配置步骤

1. 安装基础依赖包
在 Ubuntu 终端中执行以下命令,安装编译所需的软件包:

sudo apt update sudo apt install -y git curl wget make gcc g++ libssl-dev libncurses-dev \ bison flex libelf-dev bc device-tree-compiler lz4 liblz4-tool

2. 配置交叉编译工具链
下载并解压工具链,将其路径加入环境变量:

# 下载工具链(以 Linaro 为例) wget https://releases.linaro.org/components/toolchain/binaries/10.3-2021.07/aarch64-linux-gnu/gcc-linaro-10.3.1-2021.07-x86_64_aarch64-linux-gnu.tar.xz tar -xf gcc-linaro-10.3.1-2021.07-x86_64_aarch64-linux-gnu.tar.xz sudo mv gcc-linaro-10.3.1-2021.07-x86_64_aarch64-linux-gnu /opt/ # 永久添加环境变量 echo 'export PATH=/opt/gcc-linaro-10.3.1-2021.07-x86_64_aarch64-linux-gnu/bin:$PATH' >> ~/.bashrc source ~/.bashrc # 验证工具链 aarch64-linux-gnu-gcc --version

3. 获取内核源码并切换分支
使用 git 克隆 Rockchip 内核仓库,并切换到与 Android 15 匹配的分支:

git clone https://github.com/rockchip-linux/kernel.git cd kernel git checkout origin/kernel-5.10 -b android15-rk3568

注意:内核版本与 Android 系统的兼容性至关重要。若使用不匹配的分支,可能导致编译错误或运行时异常。

2. RK3568 内核配置与驱动框架理解

2.1 内核配置与编译验证

RK3568 的内核配置需针对具体硬件进行定制。正点原子开发板的设备树文件通常已由厂商提供,但开发者仍需掌握如何调整配置以适应外设需求。

执行内核配置菜单

# 进入内核源码目录 cd kernel # 使用默认配置(正点原子提供) make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- rockchip_defconfig # 启动图形化配置 make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- menuconfig

在 menuconfig 中,重点关注以下选项:

  • Device DriversCharacter devices→ 启用动态设备号分配(CONFIG_DYNAMIC_DEVNO
  • Device DriversGPIO Support→ 启用 Rockchip GPIO 驱动(CONFIG_GPIO_ROCKCHIP
  • File systems→ 启用用于模块加载的CONFIG_MODULE_FORCE_LOAD
  • Kernel hacking→ 启用CONFIG_DEBUG_KERNELCONFIG_DEBUG_DRIVER以便调试

编译内核与模块

# 编译内核镜像 make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc) # 编译设备树二进制文件 make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- dtbs # 编译外部模块(准备驱动开发环境) make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- modules_prepare

编译成功后,会在arch/arm64/boot/下生成Image内核镜像,在arch/arm64/boot/dts/rockchip/下生成rk3568-evb.dtb等设备树文件。

2.2 Linux 驱动框架核心概念

在编写具体驱动前,需理解 Linux 内核驱动的几个基础框架:

1. 字符设备驱动
最基础的驱动类型,适用于 GPIO、串口等逐字节读写的外设。核心结构体包括:

  • struct file_operations:定义驱动支持的操作(open、read、write、ioctl 等)
  • struct cdev:表示一个字符设备
  • dev_t:设备号(主设备号+次设备号)

2. 平台设备与驱动
用于描述 SoC 内部集成的外设(如 I2C 控制器、SPI 总线)。通过设备树(Device Tree)描述硬件资源,驱动通过of_match_table匹配设备。

3. 设备树(Device Tree)
RK3568 使用设备树替代传统的板级文件(board file)描述硬件连接关系。设备树源文件(.dts)编译后生成二进制文件(.dtb),由内核解析。

一个典型的 GPIO LED 设备树节点示例如下:

// 在 rk3568-evb.dtsi 中添加 leds { compatible = "gpio-leds"; user_led: led-0 { label = "user_led"; gpios = <&gpio0 10 GPIO_ACTIVE_HIGH>; // GPIO0_A10 linux,default-trigger = "heartbeat"; default-state = "off"; }; };

设备树修改后需重新编译 dtb 文件,并更新到开发板的 boot 分区。

3. 实战:编写一个简单的字符设备驱动

3.1 驱动模块基础代码结构

以下以一个最简单的虚拟字符设备为例,演示完整的驱动编写流程。该设备通过/dev/hello_dev节点提供读写接口。

创建驱动源码文件hello_driver.c

#include <linux/module.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/uaccess.h> #include <linux/slab.h> #define DEVICE_NAME "hello_dev" #define CLASS_NAME "hello_class" static int major_num; static struct class *hello_class = NULL; static struct cdev hello_cdev; static char *buffer = NULL; static size_t buffer_size = 1024; // 设备打开函数 static int hello_open(struct inode *inode, struct file *file) { printk(KERN_INFO "Hello driver: device opened\n"); return 0; } // 设备释放函数 static int hello_release(struct inode *inode, struct file *file) { printk(KERN_INFO "Hello driver: device closed\n"); return 0; } // 设备读取函数 static ssize_t hello_read(struct file *file, char __user *user_buf, size_t count, loff_t *offset) { size_t len = min(count, buffer_size); if (copy_to_user(user_buf, buffer, len)) { return -EFAULT; } printk(KERN_INFO "Hello driver: read %zu bytes\n", len); return len; } // 设备写入函数 static ssize_t hello_write(struct file *file, const char __user *user_buf, size_t count, loff_t *offset) { size_t len = min(count, buffer_size); if (copy_from_user(buffer, user_buf, len)) { return -EFAULT; } printk(KERN_INFO "Hello driver: wrote %zu bytes\n", len); return len; } // 文件操作结构体 static struct file_operations hello_fops = { .owner = THIS_MODULE, .open = hello_open, .release = hello_release, .read = hello_read, .write = hello_write, }; // 模块初始化函数 static int __init hello_init(void) { dev_t dev_num; // 动态申请设备号 if (alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME) < 0) { printk(KERN_ALERT "Failed to allocate device number\n"); return -1; } major_num = MAJOR(dev_num); // 创建字符设备 cdev_init(&hello_cdev, &hello_fops); if (cdev_add(&hello_cdev, dev_num, 1) < 0) { unregister_chrdev_region(dev_num, 1); printk(KERN_ALERT "Failed to add cdev\n"); return -1; } // 创建设备类 hello_class = class_create(THIS_MODULE, CLASS_NAME); if (IS_ERR(hello_class)) { cdev_del(&hello_cdev); unregister_chrdev_region(dev_num, 1); printk(KERN_ALERT "Failed to create class\n"); return PTR_ERR(hello_class); } // 在 /dev 下创建设备节点 device_create(hello_class, NULL, dev_num, NULL, DEVICE_NAME); // 分配缓冲区 buffer = kzalloc(buffer_size, GFP_KERNEL); if (!buffer) { device_destroy(hello_class, dev_num); class_destroy(hello_class); cdev_del(&hello_cdev); unregister_chrdev_region(dev_num, 1); printk(KERN_ALERT "Failed to allocate buffer\n"); return -ENOMEM; } printk(KERN_INFO "Hello driver loaded with major number %d\n", major_num); return 0; } // 模块清理函数 static void __exit hello_exit(void) { dev_t dev_num = MKDEV(major_num, 0); device_destroy(hello_class, dev_num); class_destroy(hello_class); cdev_del(&hello_cdev); unregister_chrdev_region(dev_num, 1); kfree(buffer); printk(KERN_INFO "Hello driver unloaded\n"); } module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("A simple hello world character device driver");

3.2 编译驱动模块的 Makefile 配置

为驱动代码编写对应的 Makefile:

# 指定内核源码路径(根据实际路径修改) KDIR := /path/to/your/kernel # 指定架构和交叉编译工具链 ARCH := arm64 CROSS_COMPILE := aarch64-linux-gnu- # 模块目标名称 obj-m += hello_driver.o all: $(MAKE) -C $(KDIR) M=$(PWD) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules clean: $(MAKE) -C $(KDIR) M=$(PWD) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) clean .PHONY: all clean

编译驱动模块:

make

成功编译后,将生成hello_driver.ko文件,这就是可加载到 RK3568 内核的驱动模块。

3.3 在开发板上测试驱动模块

1. 将模块文件传输到开发板
使用 ADB 或 SCP 将编译好的模块文件传输到开发板:

adb push hello_driver.ko /data/

2. 加载驱动模块
在开发板的终端中执行:

# 切换到模块所在目录 cd /data # 加载模块 insmod hello_driver.ko # 检查模块是否加载成功 lsmod | grep hello_driver # 查看内核日志,确认初始化信息 dmesg | tail -10

3. 测试设备节点功能
驱动加载后,会在/dev下生成hello_dev设备节点:

# 检查设备节点是否存在 ls -l /dev/hello_dev # 测试写入功能 echo "test message" > /dev/hello_dev # 测试读取功能 cat /dev/hello_dev # 查看操作日志 dmesg | tail -5

4. 卸载驱动模块
测试完成后卸载模块:

rmmod hello_driver

4. Android 15 系统下的驱动集成与 HAL 适配

4.1 将驱动集成到 Android 系统镜像

在原型验证通过后,需要将驱动正式集成到 Android 系统构建流程中,使其随系统启动自动加载。

1. 将驱动源码放入内核目录
在内核源码的drivers/char/目录下创建hello_driver/文件夹,将hello_driver.cKconfigMakefile放入其中。

配置 Kconfig 文件

# drivers/char/hello_driver/Kconfig config HELLO_DRIVER tristate "Simple Hello World Driver" default n help This is a simple hello world character device driver for demonstration.

配置 Makefile 文件

# drivers/char/hello_driver/Makefile obj-$(CONFIG_HELLO_DRIVER) += hello_driver.o

2. 修改上层 Kconfig 和 Makefile
drivers/char/Kconfig中添加:

source "drivers/char/hello_driver/Kconfig"

drivers/char/Makefile中添加:

obj-$(CONFIG_HELLO_DRIVER) += hello_driver/

3. 配置内核并重新编译
menuconfig中启用该驱动:

make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- menuconfig

导航到Device Drivers → Character devices → Simple Hello World Driver,选择<M>编译为模块或<*>编译进内核。

重新编译内核和模块:

make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc) make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- modules

4.2 为驱动编写 Android HAL 层

在 Android 系统中,应用层通常不直接访问内核驱动,而是通过 HAL(Hardware Abstraction Layer)进行交互。以下为 hello_driver 创建一个简单的 HAL 服务。

1. 创建 HAL 接口定义文件
hardware/interfaces/hello/1.0/目录下创建IHello.hal

package android.hardware.hello@1.0; interface IHello { write(string data) generates (bool success); read() generates (string data); };

2. 生成 HAL 代码框架
使用hidl-gen工具生成代码框架:

hidl-gen -o hardware/interfaces/hello/1.0/default/ \ -Lc++-impl -randroid.hardware:hardware/interfaces \ -randroid.hidl:system/libhidl/transport android.hardware.hello@1.0 hidl-gen -o hardware/interfaces/hello/1.0/default/ \ -Landroidbp-impl -randroid.hardware:hardware/interfaces \ -randroid.hidl:system/libhidl/transport android.hardware.hello@1.0

3. 实现 HAL 服务
在生成的Hello.cpp中实现具体的读写逻辑:

#include <hidl/LegacySupport.h> #include <hidl/HidlSupport.h> #include "Hello.h" #include <fcntl.h> #include <unistd.h> #include <string> using android::hardware::hello::V1_0::IHello; using android::hardware::Return; using android::hardware::Void; struct Hello : public IHello { Return<bool> write(const hidl_string& data) override { int fd = open("/dev/hello_dev", O_WRONLY); if (fd < 0) return false; ssize_t ret = ::write(fd, data.c_str(), data.size()); close(fd); return (ret == (ssize_t)data.size()); } Return<hidl_string> read() override { int fd = open("/dev/hello_dev", O_RDONLY); if (fd < 0) return hidl_string(""); char buffer[1024]; ssize_t ret = ::read(fd, buffer, sizeof(buffer)-1); close(fd); if (ret > 0) { buffer[ret] = '\0'; return hidl_string(buffer); } return hidl_string(""); } }; int main() { return android::hardware::configureRpcThreadpool(1, true) | android::hardware::registerAsService(new Hello()); }

4. 配置 SELinux 策略
device/rockchip/rk3568/sepolicy/中添加策略文件,允许 HAL 服务访问设备节点:

# hello_driver.te type hello_device, dev_type; type hello_service, system_api_service_type, service_manager_type; allow hello_service hello_device:chr_file { open read write };

4.3 集成到 Android 构建系统

1. 修改设备配置
在设备 Makefile 中添加:

# device/rockchip/rk3568/device.mk PRODUCT_PACKAGES += \ android.hardware.hello@1.0-service

2. 编译并刷写系统
重新编译 Android 系统并刷写到开发板:

source build/envsetup.sh lunch rk3568-userdebug make -j$(nproc) # 刷写系统 fastboot flash boot out/target/product/rk3568/boot.img fastboot flash system out/target/product/rk3568/system.img fastboot reboot

5. 驱动开发中的常见问题与排查方法

5.1 编译阶段问题排查

问题 1:交叉编译工具链不匹配

  • 现象:编译时出现未定义的架构相关错误。
  • 排查:检查工具链版本与内核配置是否一致。
  • 解决:使用内核源码目录中scripts/diffconfig对比配置差异,或使用厂商推荐的工具链。

问题 2:内核头文件缺失

  • 现象:编译驱动时找不到头文件。
  • 排查:确认KDIR路径正确,且已执行make modules_prepare
  • 解决:完整编译一次内核,确保头文件生成。

5.2 运行时问题排查

问题 1:模块加载失败

  • 现象insmod返回 "Invalid module format" 或 "Unknown symbol"。
  • 排查步骤
    1. 检查内核版本是否匹配:uname -r与编译时内核版本一致
    2. 查看详细错误信息:dmesg | tail -20
    3. 检查符号依赖:modinfo hello_driver.ko

问题 2:设备节点权限不足

  • 现象:应用层无法打开设备节点。
  • 排查:检查/dev/hello_dev的权限和所属组。
  • 解决:在 init 脚本中设置正确权限,或配置 udev 规则。

5.3 调试技巧与工具使用

1. 内核日志分级查看
使用dmesg查看内核日志,可通过级别过滤重要信息:

# 只显示错误和警告信息 dmesg -l err,warn # 实时监控新日志 dmesg -w

2. 使用 printk 调试
在驱动代码中合理添加 printk 语句,注意日志级别:

printk(KERN_DEBUG "Debug: variable value = %d\n", var); // 调试信息 printk(KERN_INFO "Info: device opened\n"); // 普通信息 printk(KERN_WARNING "Warning: unusual condition\n"); // 警告 printk(KERN_ERR "Error: operation failed\n"); // 错误

3. 使用 strace 跟踪系统调用
在应用层调试时,使用 strace 跟踪对设备节点的访问:

strace -e open,read,write,ioctl cat /dev/hello_dev

6. 生产环境驱动开发最佳实践

6.1 代码质量与维护性

1. 错误处理要完整
每个可能失败的操作都要有错误处理,确保资源正确释放:

int example_probe(struct platform_device *pdev) { struct resource *res; void __iomem *base; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(&pdev->dev, "Failed to get memory resource\n"); return -ENODEV; } base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(base)) { dev_err(&pdev->dev, "Failed to map memory\n"); return PTR_ERR(base); } // ... 其他初始化代码 return 0; }

2. 使用 devm_ 资源管理函数
自动管理资源生命周期,避免内存泄漏:

// 传统方式(需要手动释放) ptr = kmalloc(size, GFP_KERNEL); // ... 使用 ptr kfree(ptr); // 推荐方式(自动释放) ptr = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);

6.2 性能与稳定性考量

1. 避免在中断上下文中进行耗时操作
将耗时任务推送到工作队列或线程中执行:

static struct work_struct my_work; static void my_work_handler(struct work_struct *work) { // 耗时操作 msleep(100); printk(KERN_INFO "Work completed\n"); } // 在中断处理函数中调度工作 irqreturn_t my_interrupt_handler(int irq, void *dev_id) { schedule_work(&my_work); return IRQ_HANDLED; }

2. 合理使用互斥锁保护共享资源
确保多线程访问时的数据一致性:

static DEFINE_MUTEX(my_lock); static int shared_data; static ssize_t my_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { int ret; mutex_lock(&my_lock); // 读写共享数据 ret = copy_to_user(buf, &shared_data, sizeof(shared_data)); mutex_unlock(&my_lock); return ret; }

6.3 电源管理适配

为驱动添加电源管理支持,确保系统休眠时正确处理设备状态:

static int my_suspend(struct device *dev) { struct my_device *my_dev = dev_get_drvdata(dev); // 保存设备状态 my_dev->saved_reg = readl(my_dev->base + REG_OFFSET); // 关闭设备时钟或电源 clk_disable(my_dev->clk); return 0; } static int my_resume(struct device *dev) { struct my_device *my_dev = dev_get_drvdata(dev); // 恢复时钟和电源 clk_enable(my_dev->clk); // 恢复设备状态 writel(my_dev->saved_reg, my_dev->base + REG_OFFSET); return 0; } static const struct dev_pm_ops my_pm_ops = { .suspend = my_suspend, .resume = my_resume, };

RK3568 的 Android 15 驱动开发需要平衡功能实现、系统稳定性和长期维护成本。从最简单的字符设备开始,逐步掌握平台设备、中断处理、DMA 传输等复杂机制,最终能够为复杂外设编写完整的驱动栈。在实际项目中,建议先通过模块化开发验证基本功能,再集成到系统镜像,并建立完整的测试和调试流程。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询