目录
[1. 核心功能需求](#1. 核心功能需求)
[2. 硬件方案选型(基于 RK3568)](#2. 硬件方案选型(基于 RK3568))
[3. 软件整体架构](#3. 软件整体架构)
[1. 完整设备树配置](#1. 完整设备树配置)
[2. 驱动编译与验证](#2. 驱动编译与验证)
[三、第二步:HAL 层封装,统一硬件控制接口](#三、第二步:HAL 层封装,统一硬件控制接口)
[1. HAL 层目录与文件结构](#1. HAL 层目录与文件结构)
[2. 头文件door_access_hal.h](#2. 头文件door_access_hal.h)
[3. 接口实现door_access_hal.c](#3. 接口实现door_access_hal.c)
[4. 编译脚本Android.bp](#4. 编译脚本Android.bp)
[5. 编译 HAL 层模块](#5. 编译 HAL 层模块)
[四、第三步:JNI 封装与安卓门禁 App 开发](#四、第三步:JNI 封装与安卓门禁 App 开发)
[1. JNI 接口封装](#1. JNI 接口封装)
[2. 安卓门禁 App 开发](#2. 安卓门禁 App 开发)
大家好,我是黒漂技术佬。前面 17 篇内容,我们从安卓驱动基础架构、设备树,到 GPIO、中断、I2C、SPI、显示、音频、摄像头等全系列外设驱动,再到全套调试方法,已经把 RK 安卓驱动开发的核心技能全部讲透了。
很多兄弟后台说:"佬,单个外设的驱动我都会了,但是怎么把这些东西整合到一个完整的项目里?有没有一个全流程的实战项目,能把所有知识点串起来?"
安排!今天这篇,我们就做一个工业级的落地项目 ------基于 RK3568 的安卓智能门禁系统,把前面所有学到的知识点全部整合起来,从需求分析、硬件方案选型、全外设驱动适配、HAL 层封装、JNI 接口开发,到最终的安卓门禁 App 全栈开发,一步不落,小白跟着走,就能做出一个可直接落地的智能门禁产品。
一、项目需求与整体方案设计
1. 核心功能需求
我们做的智能门禁系统,要实现工业级场景的完整功能,覆盖我们前面所有的知识点:
- 人脸识别开锁:通过摄像头采集人脸,本地 NPU 做人脸识别,匹配成功后驱动继电器开锁;
- 密码 / 刷卡开锁:支持电容按键输入密码、IC 卡刷卡开锁,适配门禁常用场景;
- 音视频对讲:支持门口机和室内机的音视频对讲,用到摄像头、音频编解码、屏幕显示;
- 门禁状态显示:7 寸 MIPI 屏实时显示摄像头画面、开锁状态、时间、提示信息;
- 开锁执行机构:继电器控制电磁锁,PWM 驱动蜂鸣器做按键 / 开锁提示音;
- 事件存储与上报:所有开锁、报警事件本地存储,支持网络上报到后台。
2. 硬件方案选型(基于 RK3568)
表格
| 功能模块 | 硬件选型 | 对应前面的知识点 |
|---|---|---|
| 核心主控 | RK3568 核心板 + 底板 | 全系列驱动开发基础 |
| 显示模块 | 7 寸 MIPI LCD 屏(800*1280) | 第 14 篇 MIPI 显示驱动、DRM 框架 |
| 摄像头 | GC2053 1080P MIPI 摄像头 | 第 16 篇 摄像头驱动、V4L2 子系统 |
| 音频模块 | ES8388 Codec + 咪头 + 喇叭 | 第 15 篇 音频驱动、ALSA 架构 |
| 开锁执行 | 5V 继电器 + 电磁锁 | 第 8 篇 GPIO 输出驱动 |
| 按键输入 | 6 路电容触摸按键 | 第 13 篇 input 子系统、按键中断驱动 |
| 提示音 | 无源蜂鸣器 | 第 10 篇 PWM 驱动 |
| 刷卡模块 | MFRC522 RFID 模块(SPI 接口) | 第 12 篇 SPI 驱动开发 |
| 存储 | 8GB EMMC | 系统与事件存储 |
| 网络 | 双网口 + WiFi/BT 模块 | 系统联网与事件上报 |
3. 软件整体架构
我们严格遵循安卓系统的标准分层架构,和前面的单外设驱动开发完全一致,整体分为 5 层,从上到下依次是:
- 应用层:安卓门禁 App,实现 UI 交互、人脸识别逻辑、事件管理、音视频对讲;
- Framework 层:安卓系统 API + 自定义 JNI 接口,连接 Java 和 Native 层;
- HAL 层:硬件抽象层,封装所有外设的控制接口,给 JNI 层提供标准 C/C++ 接口;
- 内核驱动层:基于 Linux 内核,实现所有外设的标准驱动,给 HAL 层提供设备文件操作接口;
- 硬件层:RK3568 核心板 + 所有外设硬件。
二、第一步:全外设驱动适配与设备树配置
这个项目的核心基础,就是所有外设的驱动适配,我们基于前面的单外设驱动知识,完成整个项目的设备树配置和驱动适配,所有外设的驱动,内核都已经自带,我们只需要修改设备树,不用写一行驱动代码,就能完成所有外设的适配。
1. 完整设备树配置
打开你的 RK3568 板级.dts 文件,添加下面的完整设备树配置,所有模块都加了详细注释,直接对应我们的硬件方案:
dts
/ {
// ====================== 1. 门禁按键input驱动(6路电容按键) ======================
gpio_keys: gpio-keys {
compatible = "gpio-keys";
status = "okay";
autorepeat;
pinctrl-names = "default";
// 数字按键0-9、确认、取消,对应标准键码
key_0 {
label = "key-0";
gpios = <&gpio1 RK_PA0 GPIO_ACTIVE_LOW>;
linux,code = <KEY_0>;
debounce-interval = <20>;
};
key_1 {
label = "key-1";
gpios = <&gpio1 RK_PA1 GPIO_ACTIVE_LOW>;
linux,code = <KEY_1>;
debounce-interval = <20>;
};
key_2 {
label = "key-2";
gpios = <&gpio1 RK_PA2 GPIO_ACTIVE_LOW>;
linux,code = <KEY_2>;
debounce-interval = <20>;
};
key_3 {
label = "key-3";
gpios = <&gpio1 RK_PA3 GPIO_ACTIVE_LOW>;
linux,code = <KEY_3>;
debounce-interval = <20>;
};
key_enter {
label = "key-enter";
gpios = <&gpio1 RK_PA4 GPIO_ACTIVE_LOW>;
linux,code = <KEY_ENTER>;
debounce-interval = <20>;
wakeup-source;
};
key_cancel {
label = "key-cancel";
gpios = <&gpio1 RK_PA5 GPIO_ACTIVE_LOW>;
linux,code = <KEY_ESC>;
debounce-interval = <20>;
};
};
// ====================== 2. 继电器开锁控制 ======================
door_lock: door-lock {
compatible = "gpio-leds";
status = "okay";
lock {
label = "door-lock";
gpios = <&gpio1 RK_PB0 GPIO_ACTIVE_HIGH>;
default-state = "off"; // 默认断开,锁关闭
};
};
// ====================== 3. 蜂鸣器PWM控制 ======================
beeper: beeper {
compatible = "pwm-beeper";
status = "okay";
pwms = <&pwm0 0 500000 0>; // 2kHz频率,适合蜂鸣器
amp-supply = <&vcc3v3_sys>;
beeper-hz = <2000>;
};
};
// ====================== 4. MIPI 7寸屏显示驱动 ======================
&dsi0 {
status = "okay";
rockchip,lane-count = <2>;
rockchip,max-bandwidth = <800>;
rockchip,format = <MIPI_DSI_FMT_RGB888>;
rockchip,mode = <MIPI_DSI_MODE_VIDEO>;
rockchip,video-mode = <MIPI_DSI_VIDEO_MODE_BURST>;
panel@0 {
compatible = "simple-mipi-dsi-panel";
reg = <0>;
status = "okay";
// 7寸屏800*1280时序参数,根据屏幕规格书修改
display-timings {
native-mode = <&timing0>;
timing0: timing0 {
clock-frequency = <78000000>;
hactive = <800>;
hfront-porch = <20>;
hback-porch = <20>;
hsync-len = <10>;
vactive = <1280>;
vfront-porch = <10>;
vback-porch = <10>;
vsync-len = <2>;
hsync-active = <0>;
vsync-active = <0>;
de-active = <1>;
pixelclk-active = <0>;
};
};
power-supply = <&vcc3v3_lcd0>;
reset-gpios = <&gpio4 RK_PA0 GPIO_ACTIVE_LOW>;
reset-delay-ms = <10>;
prepare-delay-ms = <10>;
init-delay-ms = <120>;
// 屏幕初始化命令,根据屏幕规格书添加
dsi,init-commands = [
15 00 02 78 00
05 00 01 29 00
];
};
};
&route_dsi0 {
status = "okay";
connect = <&vopb_out_dsi0>;
};
&vopb {
status = "okay";
};
&vopb_out_dsi0 {
status = "okay";
};
&backlight {
status = "okay";
pwms = <&pwm1 0 50000 0>;
brightness-levels = <0 20 40 60 80 100 120 140 160 180 200 220 240 255>;
default-brightness = <200>;
};
// ====================== 5. GC2053摄像头驱动 ======================
&i2c2 {
status = "okay";
clock-frequency = <400000>;
gc2053: gc2053@37 {
compatible = "galaxycore,gc2053";
reg = <0x37>;
status = "okay";
clocks = <&cru CLK_CAM_OUT0>;
clock-names = "xvclk";
assigned-clocks = <&cru CLK_CAM_OUT0>;
assigned-clock-rates = <24000000>;
pwdn-gpios = <&gpio4 RK_PA1 GPIO_ACTIVE_HIGH>;
reset-gpios = <&gpio4 RK_PA2 GPIO_ACTIVE_LOW>;
rotation = <0>;
orientation = <0>;
data-lanes = <1 2>;
max-fps = <30>;
port {
gc2053_out: endpoint {
remote-endpoint = <&mipi_csi2_in>;
data-lanes = <1 2>;
};
};
};
};
&csi2_dphy0 {
status = "okay";
ports {
#address-cells = <1>;
#size-cells = <0>;
port@0 {
reg = <0>;
#address-cells = <1>;
#size-cells = <0>;
mipi_csi2_in: endpoint@0 {
reg = <0>;
remote-endpoint = <&gc2053_out>;
data-lanes = <1 2>;
};
};
port@1 {
reg = <1>;
#address-cells = <1>;
#size-cells = <0>;
csiphy_out: endpoint@0 {
reg = <0>;
remote-endpoint = <&isp_in>;
};
};
};
};
&rkcif_mipi_lvds {
status = "okay";
port {
isp_in: endpoint {
remote-endpoint = <&csiphy_out>;
};
};
};
&rkcif_mipi_lvds_sditf {
status = "okay";
};
&rkisp_vir0 {
status = "okay";
};
// ====================== 6. ES8388音频驱动 ======================
&i2c1 {
status = "okay";
clock-frequency = <400000>;
es8388: es8388@10 {
compatible = "everest,es8388";
reg = <0x10>;
status = "okay";
#sound-dai-cells = <0>;
clocks = <&cru CLK_I2S1_OUT>;
clock-names = "mclk";
hp-det-gpios = <&gpio0 RK_PB6 GPIO_ACTIVE_LOW>;
};
};
&i2s1_8ch {
status = "okay";
#sound-dai-cells = <0>;
rockchip,clk-trcm = <1>;
};
sound {
compatible = "simple-audio-card";
status = "okay";
model = "rk3568-door-access";
simple-audio-card,format = "i2s";
simple-audio-card,bitclock-master = <&dailink_master>;
simple-audio-card,frame-master = <&dailink_master>;
simple-audio-card,mclk-fs = <256>;
dailink_master: simple-audio-card,cpu {
sound-dai = <&i2s1_8ch>;
};
simple-audio-card,codec {
sound-dai = <&es8388>;
};
};
// ====================== 7. MFRC522 RFID模块(SPI) ======================
&spi0 {
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
pinctrl-names = "default";
pinctrl-0 = <&spi0_clk &spi0_mosi &spi0_miso &spi0_cs0>;
mfrc522: mfrc522@0 {
compatible = "nxp,mfrc522";
reg = <0>;
spi-max-frequency = <10000000>;
status = "okay";
reset-gpios = <&gpio2 RK_PA0 GPIO_ACTIVE_LOW>;
interrupt-parent = <&gpio2>;
interrupts = <RK_PA1 IRQ_TYPE_EDGE_FALLING>;
};
};
// ====================== 8. 外设时钟使能 ======================
&pwm0 {
status = "okay";
};
&pwm1 {
status = "okay";
};
2. 驱动编译与验证
-
设备树修改完成后,编译内核和设备树: bash
运行
./build.sh -CKu -
烧录 boot.img 到开发板,重启后,按下面的步骤验证所有外设驱动是否正常: bash
运行
# 1. 验证按键input设备 cat /proc/bus/input/devices | grep gpio-keys getevent # 按下按键,能看到对应的事件上报 # 2. 验证MIPI屏 # 开机屏幕正常点亮,显示安卓桌面,说明显示驱动正常 # 3. 验证摄像头 v4l2-ctl --list-devices # 能看到GC2053摄像头设备 # 4. 验证音频 aplay -l # 能看到声卡设备,tinyplay播放测试音频正常 # 5. 验证继电器 echo 1 > /sys/class/leds/door-lock/brightness # 继电器吸合,锁打开 echo 0 > /sys/class/leds/door-lock/brightness # 继电器断开,锁关闭 # 6. 验证蜂鸣器 echo 2000 > /sys/class/input/eventX/device/beeper/hz # 蜂鸣器响
所有外设验证通过后,我们的驱动层就全部完成了,接下来进入 HAL 层的封装。
三、第二步:HAL 层封装,统一硬件控制接口
我们把所有外设的控制逻辑,封装成标准的 HAL 层动态库,给上层 JNI 提供统一的 C/C++ 接口,避免上层直接操作设备文件,符合安卓系统的架构规范。
1. HAL 层目录与文件结构
在 SDK 的hardware/rockchip/目录下,创建door_access_hal目录,创建以下文件:
plaintext
door_access_hal/
├── include
│ └── door_access_hal.h // 头文件,对外接口声明
├── door_access_hal.c // 接口实现
└── Android.bp // 编译脚本
2. 头文件door_access_hal.h
定义所有对外的控制接口,覆盖门禁系统的所有硬件操作:
c
运行
#ifndef DOOR_ACCESS_HAL_H
#define DOOR_ACCESS_HAL_H
#ifdef __cplusplus
extern "C" {
#endif
// ====================== 门锁控制 ======================
// 开锁:1=开锁,0=关锁
int door_lock_set(int enable);
// 获取门锁状态
int door_lock_get_status(void);
// ====================== 蜂鸣器控制 ======================
// 蜂鸣器响:duration_ms=持续时间,0=一直响
int beeper_play(int duration_ms);
// 停止蜂鸣器
int beeper_stop(void);
// ====================== 按键事件读取 ======================
// 初始化按键事件监听
int key_init(void);
// 读取按键事件,阻塞等待
int key_read_event(int *key_code, int *state);
// 释放按键资源
int key_release(void);
// ====================== RFID读卡 ======================
// 初始化RFID模块
int rfid_init(void);
// 读取IC卡卡号,阻塞等待
int rfid_read_card(unsigned char *card_id, int *id_len);
// 释放RFID资源
int rfid_release(void);
// ====================== 音频播放 ======================
// 播放提示音,比如"请刷卡""密码错误"
int audio_play_tip(const char *audio_file);
// 开始录音
int audio_start_record(const char *record_file);
// 停止录音
int audio_stop_record(void);
// ====================== 系统初始化与释放 ======================
// 所有硬件初始化
int door_access_hal_init(void);
// 所有资源释放
int door_access_hal_release(void);
#ifdef __cplusplus
}
#endif
#endif // DOOR_ACCESS_HAL_H
3. 接口实现door_access_hal.c
基于我们前面的驱动设备文件,实现所有接口,核心代码片段如下:
c
运行
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <pthread.h>
#include <linux/input.h>
#include "door_access_hal.h"
// 全局设备文件描述符
static int lock_fd = -1;
static int key_fd = -1;
static int beeper_fd = -1;
// ====================== 门锁控制实现 ======================
int door_lock_set(int enable)
{
if (lock_fd < 0) {
lock_fd = open("/sys/class/leds/door-lock/brightness", O_RDWR);
if (lock_fd < 0) {
printf("【HAL】打开门锁设备失败\n");
return -1;
}
}
if (enable) {
write(lock_fd, "1", 1);
printf("【HAL】门锁已打开\n");
} else {
write(lock_fd, "0", 1);
printf("【HAL】门锁已关闭\n");
}
return 0;
}
int door_lock_get_status(void)
{
char buf[2] = {0};
if (lock_fd < 0) return -1;
lseek(lock_fd, 0, SEEK_SET);
read(lock_fd, buf, 1);
return atoi(buf);
}
// ====================== 蜂鸣器控制实现 ======================
int beeper_play(int duration_ms)
{
// 蜂鸣器控制实现,通过sysfs节点控制PWM
system("echo 2000 > /sys/class/leds/beeper/brightness");
if (duration_ms > 0) {
usleep(duration_ms * 1000);
beeper_stop();
}
return 0;
}
int beeper_stop(void)
{
system("echo 0 > /sys/class/leds/beeper/brightness");
return 0;
}
// 剩余的按键、RFID、音频接口实现,基于前面的单外设驱动知识完成
// 音频部分基于tinyalsa库实现,RFID基于SPI设备文件操作
4. 编译脚本Android.bp
json
cc_library_shared {
name: "libdooraccess",
srcs: ["door_access_hal.c"],
local_include_dirs: ["include"],
vendor: true,
shared_libs: [
"libtinyalsa",
"libcutils",
"libutils",
],
cflags: [
"-Wall",
"-Werror",
],
}
5. 编译 HAL 层模块
bash
运行
# 进入SDK根目录,设置编译环境
source build/envsetup.sh
lunch rk3568_r-userdebug
# 编译HAL模块
mmm hardware/rockchip/door_access_hal
编译完成后,会生成libdooraccess.so动态库,放到开发板的/vendor/lib64/目录下,给 JNI 层调用。
四、第三步:JNI 封装与安卓门禁 App 开发
1. JNI 接口封装
在 Android Studio 项目中,创建DoorAccessJni.java类,声明 native 方法,对应 HAL 层的接口:
java
运行
package com.heipiao.dooraccess;
public class DoorAccessJni {
static {
System.loadLibrary("dooraccess");
System.loadLibrary("door_jni");
}
public native int halInit();
public native int halRelease();
public native int doorLockSet(int enable);
public native int beeperPlay(int durationMs);
public native int readKeyEvent(int[] keyInfo);
public native int readRfidCard(byte[] cardId);
public native int playTipAudio(String audioFile);
}
然后编写对应的 JNI C++ 代码,调用 HAL 层的接口,完成 Java 和 Native 层的桥接。
2. 安卓门禁 App 开发
基于 JNI 接口,开发完整的门禁 App,核心功能包括:
- 实时预览:通过 Camera2 API,预览摄像头画面,做人脸识别;
- 人脸识别:集成 RK NPU 的人脸识别 SDK,本地完成人脸匹配,匹配成功自动开锁;
- 密码开锁:监听 input 按键事件,接收用户输入的密码,验证成功开锁;
- 刷卡开锁:监听 RFID 读卡事件,读取卡号,验证白名单后开锁;
- 事件记录:所有开锁、报警事件,本地存储到 SQLite 数据库,支持查询和导出;
- 音视频对讲:基于 WebRTC,实现门口机和室内机的音视频对讲。
App 的核心开锁逻辑代码片段:
java
运行
// 人脸识别匹配成功回调
@Override
public void onFaceMatchSuccess(float similarity) {
// 开锁
doorAccessJni.doorLockSet(1);
// 播放提示音
doorAccessJni.beeperPlay(500);
doorAccessJni.playTipAudio("/sdcard/door_open.mp3");
// 记录事件
EventManager.getInstance().addEvent("人脸识别开锁", "匹配度:" + similarity);
// 3秒后关锁
new Handler(Looper.getMainLooper()).postDelayed(() -> {
doorAccessJni.doorLockSet(0);
}, 3000);
}
// 按键事件监听
new Thread(() -> {
int[] keyInfo = new int[2];
while (isRunning) {
int ret = doorAccessJni.readKeyEvent(keyInfo);
if (ret == 0) {
int keyCode = keyInfo[0];
int state = keyInfo[1];
// 处理按键输入,密码验证逻辑
runOnUiThread(() -> handleKeyInput(keyCode, state));
}
}
}).start();
五、项目优化与落地注意事项
- 系统裁剪与开机优化:裁剪安卓系统不必要的服务,优化开机速度,实现开机自启门禁 App,工业级产品要求开机后 10 秒内进入工作状态;
- 稳定性优化:添加看门狗功能,系统异常时自动重启;所有硬件操作添加异常处理,避免 App 崩溃;
- 功耗优化:无人操作时,关闭屏幕背光,降低 CPU 频率,进入低功耗模式,有人按按键时唤醒;
- 安全加固:人脸数据、密码、卡号加密存储,安卓系统 root 权限关闭,SELinux 开启,防止恶意破解;
- 网络冗余:支持有线网和 WiFi 双网冗余,断网时本地正常工作,联网后自动上报事件。
结尾说两句
这篇文章,我们把前面 17 篇的所有知识点,全部整合到了一个完整的智能门禁项目里,从硬件方案、驱动适配、HAL 层封装,到安卓 App 开发,完成了全栈的落地实战。到这里,你已经具备了独立完成 RK 平台安卓工业级项目的全流程开发能力,再也不是只会写单外设驱动的脚本小子了。
下一篇,我们进入进阶优化内容,驱动性能优化与功耗优化实战,教你怎么让你的驱动运行更快、占用资源更少、功耗更低,满足工业级产品的严苛要求。
我是黒漂技术佬,关注我,带你零基础入门 RK 安卓驱动开发,不踩坑。有任何项目开发的问题,评论区留言,我都会一一回复。