rk3568安卓13驱动适配-红外遥控
2026/8/19 12:53:18 网站建设 项目流程

本文记录工作中,在rk3568安卓13的SDK中适配板载外设红外遥控功能时具体的驱动修改和遇到的问题。


目录

前言

一.红外遥控介绍

二. 驱动适配

1. 硬件原理

2.设备树修改

二、按键功能实现

1.实现按下MENU键,弹出设置界面

2.按下HOME健,返回自应用APK

3.遥控器操作软键盘输入文本框

总结


前言

在嵌入式Android设备开发中,红外遥控器适配是一个绕不开的基础功能。无论是智能电视、机顶盒、广告机还是工业平板,用户都期望能用遥控器像操作电视一样自然地控制设备。

然而,在实际开发中,遥控器适配往往没有想象中那么简单。尤其是在RK3568这样的平台搭配Android 13系统时,从硬件配置到系统框架,再到应用层的键值映射,涉及多个知识领域和代码层级。一个按键没反应,可能原因有十几种;一个焦点问题,可能需要跨越驱动、框架、应用三个层面去排查。本文将从驱动配置 → 键值映射 → 系统框架修改 → 应用适配四个层面,系统性地讲解如何在RK3568 Android 13 SDK中完成红外遥控器的完整适配,并解决过程中可能遇到的各种“坑”。

一.红外遥控介绍

一个完整的红外遥控系统由发射端接收端两部分构成。

  • 发射端(遥控器):核心是红外发光二极管,它能发出波长通常在940nm左右的近红外光。当你按下按键时,内部的编码芯片会生成代表特定功能的二进制指令码,并将其调制到38kHz的载波信号上,通过驱动电路由红外发射管发射出去。

  • 接收端(设备端):核心是一体化红外接收头,它集成了光电转换、放大、解调等功能。接收头将收到的红外光信号转换为电信号,解调出原始的编码脉冲,再由设备的解码电路解读并执行对应的操作。

在众多红外通信协议中,NEC Protocol是目前最广泛使用的标准之一,很多家电遥控器都采用此协议。

  • 载波与调制:使用38kHz的载波频率,通过脉宽调制(PWM)的方式来表示二进制数据“0”和“1”。

  • 数据帧结构:一帧完整的NEC数据包含引导码、地址码、地址反码、命令码和命令反码,总共32位数据。

    • 引导码:由9ms低电平+4.5ms高电平组成,用于通知接收设备“要开始传输数据了”。

    • 地址码:用于区分不同的设备,防止不同遥控器互相干扰。

    • 命令码:代表具体的控制指令,如“音量+”、“频道+”等。

    • 反码:地址码和命令码的逐位取反,用于接收端校验数据,增强抗干扰能力。

  • 连发码:当按键被持续按住时,遥控器会发射重复码(连发码),使设备可以连续响应,例如持续调节音量。

RK的PWM有三种工作模式, referencemode,one-shot mode 和 continuousmode. 红外遥 控器就采用referencemode,这种模式下PWM可以捕获输入高低电平的宽度,并产生中断,CPU 接收到中断后去相应的寄存器读取。按下遥控的时候,红外接收头会产生一系列的高低电平,PWM就会产生相应的中断,CPU读 取相应的寄存器就知道这些高低电平的时间,根据协议就可以解码出红外的用户码和键值码出来。

二. 驱动适配

1. 硬件原理

红外遥控设备使用rk3568的pwm功能,底层驱动rk官方都写好了,我们只需将引脚和按键解码16进制数据写入到设备树中即可,原理图如下,

从原理图上能看到这里用的rk3568的pwm3,且PWM3_IR对应rk3568的GPIO0_C2引脚,然后到rk3568-pinctrl.dtsi这个文件中查找pwm3,可以看到没有引脚复用,芯片级设备树文件已定义好引脚,不需要我们修改。

kernel-5.10/arch/arm64/boot/dts/rockchip/rk3568-pinctrl.dtsi pwm3 { /omit-if-no-ref/ pwm3_pins: pwm3-pins { rockchip,pins = /* pwm3_ir */ <0 RK_PC2 1 &pcfg_pull_none>; }; };

2.设备树修改

可以先看下芯片级设备树文件中关于pwm3节点的定义,可以看到引脚确实定义好了。

kernel-5.10/arch/arm64/boot/dts/rockchip/rk3568.dtsi pwm3: pwm@fdd70030 { compatible = "rockchip,rk3568-pwm", "rockchip,rk3328-pwm"; reg = <0x0 0xfdd70030 0x0 0x10>; interrupts = <GIC_SPI 82 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI 86 IRQ_TYPE_LEVEL_HIGH>; #pwm-cells = <3>; pinctrl-names = "active"; pinctrl-0 = <&pwm3_pins>; clocks = <&pmucru CLK_PWM0>, <&pmucru PCLK_PWM0>; clock-names = "pwm", "pclk"; status = "disabled"; };

在参考板设备树文件中没有搜到pwm3,但是有pwm7。我们可以仿pwm7来写pwm3,在板级设备树文件中编写pwm3,设备树配置如下,其中ir_key1表示第一款遥控器,若适配多款继续往下写即可ir_key2,ir_key3等。

kernel-5.10/arch/arm64/boot/dts/rockchip/rk3568-evb2-lp4x-v10.dtsi &pwm3 { status = "okay"; compatible = "rockchip,remotectl-pwm"; remote_pwm_id = <3>; //指定使用哪个 PWM 通道来接收红外信号 handle_cpu_id = <1>; //指定处理红外中断的 CPU 核心编号 remote_support_psci = <0>; //是否支持 PSCI电源管理协议。0 表示不支持 rockchip,usercode = <0x000f>; rockchip,key_table = <0x06 KEY_BACK>, <0x59 KEY_UP>, <0x58 KEY_DOWN>, <0x56 KEY_LEFT>, <0x57 KEY_RIGHT>, <0x2a KEY_MUTE>, <0xd5 KEY_POWER>, <0x08 KEY_HOME>, <0x2f KEY_VOLUMEUP>, <0x2e KEY_VOLUMEDOWN>, <0xcf KEY_SEARCH>, <0xf4 KEY_ENTER>, <0x9d KEY_MENU>; }; ir_key2{ rockchip,usercode = <0xf708>; rockchip,key_table = <0xe3 KEY_BACK>, <0xff KEY_UP>, <0xfe KEY_DOWN>, <0xfc KEY_LEFT>, <0xfd KEY_RIGHT>, <0x99 KEY_MENU>, <0xf5 KEY_POWER>, <0xf3 KEY_HOME>, <0xa2 KEY_VOLUMEDOWN>, <0xa7 KEY_VOLUMEUP>; }; };

按键值打印需要打开开关,在安卓板子上用串口或者adb shell输入如下命令

echo 1 > /sys/module/rockchip_pwm_remotectl/parameters/code_print

然后操作遥控器按键比如按下静音键,会有类似如下打印,注意都是16进制,直接输入设备树对应位置即可

[ 4279.080603][C1] USERCODE=0xf [ 4279.117280_1] RMC_GETDATA=2a

按键的宏定义可以去以下路径看相关头文件中的宏

kernel-5.10/include/dt-bindings/input/linux-event-codes.h

下面是列举我用到的按键及中文含义

// 导航类 KEY_BACK // 返回 KEY_HOME // 主页 KEY_MENU // 菜单 KEY_UP // 上 KEY_DOWN // 下 KEY_LEFT // 左 KEY_RIGHT // 右 KEY_ENTER // 确认,也是ok键 // 音量类 KEY_VOLUMEUP // 音量+ KEY_VOLUMEDOWN // 音量- KEY_MUTE // 静音 // 系统类 KEY_MENU // 菜单 KEY_POWER // 电源 KEY_SEARCH // 语音输入

三、按键功能实现

做项目的时候,我们大多是将自己的apk作为桌面应用,屏蔽官方的桌面,就需要用到按键与自己的应用结合,下面介绍如何自定义按键功能。

1.实现按下MENU键,弹出设置界面

修改以下文件的interceptKeyBeforeDispatching函数部分,在case KeyEvent.KEYCODE_MENU:下添加以下代码拦截

frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java //interceptKeyBeforeDispatching函数中 case KeyEvent.KEYCODE_MENU: // Hijack modified menu keys for debugging features final int chordBug = KeyEvent.META_SHIFT_ON; // ==================新增,拦截 MENU 键,打开系统设置 Slog.d(TAG, "=== MENU key pressed, launching Settings ==="); if (!keyguardOn && down && repeatCount == 0) { mHandler.post(() -> { Intent intent = new Intent(android.provider.Settings.ACTION_SETTINGS); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); try { mContext.startActivityAsUser(intent, UserHandle.CURRENT); Slog.d(TAG, "=== Settings launched ==="); } catch (Exception e) { Slog.e(TAG, "Failed to launch Settings", e); } }); return -1; // 消费事件,不让应用层处理 } //================================================ if (down && repeatCount == 0) { if (mEnableShiftMenuBugReports && (metaState & chordBug) == chordBug) { Intent intent = new Intent(Intent.ACTION_BUG_REPORT); mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null, null, null, 0, null, null); return key_consumed; } } break;

2.按下HOME健,返回自应用APK

屏蔽官方的launch,桌面应用后,想要实现按下home键返回自己的apk应用,和上面menu按键类似修改,劫持home按键添加业务处理逻辑即可。

frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java //interceptKeyBeforeDispatching函数中 case KeyEvent.KEYCODE_HOME: // First we always handle the home key here, so applications // can never break it, although if keyguard is on, we do let // it handle it, because that gives us the correct 5 second // timeout. //新增代码============================================= if (!keyguardOn && down && repeatCount == 0) { // 直接操作 user APK:存在则切到前台,不存在则启动 Slog.d(TAG, "=== Launching user APK ==="); mHandler.post(() -> { Intent intent = mContext.getPackageManager().getLaunchIntentForPackage("自己的应用名"); if (intent != null) { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | Intent.FLAG_ACTIVITY_SINGLE_TOP); mContext.startActivityAsUser(intent, UserHandle.CURRENT); Slog.d(TAG, "=== user apk launched successfully ==="); } else { // 如果找不到包,回退到标准 Home launchHomeFromHotKey(displayId); Slog.d(TAG, "=== user apk not found, fallback to home ==="); } }); return -1; // 消费事件,不让应用层处理 } //================================================ DisplayHomeButtonHandler handler = mDisplayHomeButtonHandlers.get(displayId); if (handler == null) { handler = new DisplayHomeButtonHandler(displayId); mDisplayHomeButtonHandlers.put(displayId, handler); } return handler.handleHomeButton(focusedToken, event); break;

调试时,按下home键没反应,在interceptKeyBeforeDispatching函数中添加日志打印按键值才发现按键值打印的122,对应KEYCODE_HOME_PAGE宏,驱动和安卓层的按键解析没有对应起来。需要去修改Generic.kl文件,修改如下

frameworks/base/data/keyboards/Generic.kl //将key 102 MOVE_HOME改为 key 102 HOME WAKE

查了下具体原因为:我这款遥控器在物理层面发出信号,被驱动接收后,向系统上报了一个事件。这个事件包含的扫描码 是102。这个102是一个硬件层面的编号,本身没有功能含义。系统收到扫描码102后,需要去查一个“翻译表”把它转换成 Android 能识别的keycode。系统在 Generic.kl这个回退文件里找到了扫描码102对应的配置:key 102 MOVE_HOME,这个配置并不意味着系统理解错了。MOVE_HOME是 Android 里一个非常古老、标准的键码常量,它的值恰好就是122。它的设计初衷是用于全尺寸PC键盘的“Home”键,功能是将光标移动到行首或文档开头。因此,MOVE_HOME与用于“返回桌面”的HOMEkeycode=3),在 Android 系统里是两个完全不同的功能。这里强制翻译成HOME键即可。

3.遥控器操作软键盘输入文本框

一开始使用安卓自带的软键盘,发现遥控器没反应,光标都切不到软键盘上面去。查资料得出安卓自带的软键盘LatinIME只支持触摸或者鼠标输入,对于遥控器需要更换TV版专用软键盘,在RK官方问题库中找到,RK官方推荐vendor\rockchip\common\apps\BoxLatinIME路径下的软键盘。需要将该路径下Android.mk文件中的rk30board和box的条件判断屏蔽,修改后如下,

vendor\rockchip\common\apps\BoxLatinIME\Android.mk ############################################################################### # PinyinIME LOCAL_PATH := $(call my-dir) #ifeq ($(strip $(TARGET_BOARD_HARDWARE)), rk30board) #修改屏蔽 #ifeq ($(strip $(TARGET_BOARD_PLATFORM_PRODUCT)), box) #修改屏蔽 include $(CLEAR_VARS) #LOCAL_MULTILIB := 32 LOCAL_MODULE := BoxLatinIME LOCAL_MODULE_CLASS := APPS LOCAL_MODULE_TAGS := optional LOCAL_BUILT_MODULE_STEM := package.apk LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX) LOCAL_CERTIFICATE := shared LOCAL_SRC_FILES := $(LOCAL_MODULE).apk ifeq ($(strip $(TARGET_ARCH)), arm) LOCAL_PREBUILT_JNI_LIBS := \ lib/arm/libjni_latinime.so else ifeq ($(strip $(TARGET_ARCH)), arm64) LOCAL_PREBUILT_JNI_LIBS := \ lib/arm64/libjni_latinime.so endif include $(BUILD_PREBUILT) #endif #endif

在SDK根目录下搜索LatinIME这个工具是怎么添加打包的,在同路径下文件中替换为BoxLatinIME即可,执行以下命令搜索,能看出build/make/target/product/handheld_product.mk这个路径下的编译文件最像打包处,修改这里即可。或者可以继续搜索哪里包含handheld_product.mk验证下,最终这个文件是不是被参与编译了。

android-builder@436b1153169c:~/sdk/rk3568-sdk-test$ grep -rn "LatinIME" ./ --include="*.mk" --exclude-dir=out ./device/generic/armv7-a-neon/mini_common.mk:35: LatinIME \ ./device/generic/common/gsi_product.mk:25: LatinIME \ ./device/generic/common/mgsi/mgsi_product.mk:23: LatinIME \ ./device/rockchip/common/tv/tv_base.mk:81: BoxLatinIME \ ./packages/services/Car/car_product/build/car_base.mk:40: LatinIME \ ./packages/services/Car/car_product/build/car.mk:157: CarLatinIME \ ./packages/inputmethods/LatinIME/CleanSpec.mk:47:$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/LatinIME*) ./packages/inputmethods/LatinIME/CleanSpec.mk:48:$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/app/LatinIME.apk) ./packages/inputmethods/LatinIME/CleanSpec.mk:55:$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/app/LatinIME) ./vendor/rockchip/common/apps/BoxLatinIME/Android.mk:9:LOCAL_MODULE := BoxLatinIME ./build/make/target/product/handheld_product.mk:31: LatinIME \ android-builder@436b1153169c:~/sdk/rk3568-sdk-test$

修改为以下即可,

build/make/target/product/handheld_product.mk PRODUCT_PACKAGES += \ BoxLatinIME \

总结

遥控器适配这件事,说大不大,说小不小。它不像音视频解码那样需要复杂的算法,也不像图形渲染那样需要深入 GPU 优化。但它恰恰是用户接触设备最直接的方式,一个按键没反应,用户就会觉得“这东西不好用”。我们不仅让遥控器“能用”,更让它“好用”。希望这篇博客能够帮助到正在做类似工作的开发者,让你们少走一些弯路。

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

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

立即咨询