
本文记录工作中在rk3568安卓13的SDK中适配板载外设红外遥控功能时具体的驱动修改和遇到的问题。目录前言一.红外遥控介绍二. 驱动适配1. 硬件原理2.设备树修改二、按键功能实现1.实现按下MENU键弹出设置界面2.按下HOME健返回自应用APK3.遥控器操作软键盘输入文本框总结前言在嵌入式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: pwmfdd70030 { 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_key2ir_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] USERCODE0xf [ 4279.117280_1] RMC_GETDATA2a按键的宏定义可以去以下路径看相关头文件中的宏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与用于“返回桌面”的HOMEkeycode3在 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-builder436b1153169c:~/sdk/rk3568-sdk-test$ grep -rn LatinIME ./ --include*.mk --exclude-dirout ./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-builder436b1153169c:~/sdk/rk3568-sdk-test$修改为以下即可build/make/target/product/handheld_product.mk PRODUCT_PACKAGES \ BoxLatinIME \总结遥控器适配这件事说大不大说小不小。它不像音视频解码那样需要复杂的算法也不像图形渲染那样需要深入 GPU 优化。但它恰恰是用户接触设备最直接的方式一个按键没反应用户就会觉得“这东西不好用”。我们不仅让遥控器“能用”更让它“好用”。希望这篇博客能够帮助到正在做类似工作的开发者让你们少走一些弯路。