拓冰建站拓冰建站
首页 / 资讯中心 / 正文

OpenHarmony ADC按键开发实战:从分压电路到SARADC驱动全解析

1. 从板载按键说起为什么ADC能读按键板载按键在嵌入式开发板上属于最不起眼、但出场率最高的外设之一。绝大多数开发板的按键电路设计得非常朴素一个机械按键一端接地另一端接GPIO配合一颗上拉电阻按下时电平拉低松开时电平拉高。这种方案简单可靠写代码也直观轮询或者中断都能处理。但当你手里的开发板按键数量变多、GPIO引脚又已经被屏幕、串口、传感器占得差不多的时候问题就来了——引脚不够用了。这时候ADC按键方案就派上用场了。它的核心思路是把多个按键接到同一个ADC通道上每个按键按下时通过不同的分压电阻网络让ADC引脚读到一个独一无二的电压值。MCU或者SoC只需要一路SARADC通道就能识别出好几个按键。在OpenHarmony系统实战开发中板载按键的ADC转换和信号控制是一个非常适合入门的综合案例它同时涉及设备树配置、SARADC驱动、GPIO复用、内核态与用户态的数据交互以及上层应用的事件响应。这篇文章面向的是正在用OpenHarmony做开发、手里有一块带ADC按键的开发板、想搞清楚从硬件电路到系统服务整条链路的工程师。不管你是刚接触OpenHarmony设备开发的新手还是从裸机STM32转过来的老手只要你想弄明白“一个按键按下去系统到底经历了什么”这篇内容都能给你一条清晰的路径。我会从电路原理讲到设备树从SARADC驱动讲到信号控制逻辑把踩过的坑和实测有效的配置一并分享出来。2. 整体设计思路与方案选型2.1 为什么不用GPIO中断而选ADC方案先说清楚一个前提ADC按键方案并不是要替代所有GPIO按键。如果你的板子上只有一两个按键GPIO中断依然是最优解——响应快、功耗低、代码简单。ADC按键真正的价值场景是按键数量在3个以上且GPIO资源紧张。我拿到的这块开发板上有5个板载按键音量加、音量减、电源、复位、功能键。其中电源和复位是硬件电路直接处理的不经过SoC。剩下三个按键如果各占一个GPIO加上上下拉配置和中断申请至少吃掉3个引脚。而这块板子的40Pin扩展接口上可用GPIO本来就不宽裕。所以硬件设计上直接把这三个按键做成了ADC分压网络接到SARADC的一个通道上。从系统设计的角度看ADC按键方案有几个明显优势。第一节省引脚N个按键只需要1路ADC。第二硬件成本低只需要几个精度尚可的电阻。第三在OpenHarmony的HDF驱动框架下SARADC有标准化的驱动模型配置起来比自定义GPIO中断服务更规范。缺点也很明显不能同时按下多个按键否则电压叠加会导致识别错误按键按下时ADC需要持续采样功耗比中断唤醒高对电阻精度和ADC参考电压稳定性有要求。2.2 OpenHarmony下SARADC驱动的整体架构OpenHarmony的HDF驱动框架把外设驱动分成了三层HDI接口层、驱动适配层、硬件抽象层。SARADC驱动也不例外。上层应用通过HDI接口调用AdcOpen、AdcRead等标准接口中间层由HDF驱动框架负责设备管理和电源控制底层则是SoC厂商提供的SARADC控制器操作函数。在这个项目里数据流向是这样的应用层发起读取请求 → HDI接口 → HDF SARADC核心层 → SoC SARADC驱动 → 硬件控制器 → 读取到原始ADC值 → 逐层返回 → 应用层根据电压阈值判断哪个按键被按下。这里有一个关键设计决策按键识别逻辑放在哪一层放在内核驱动层响应快但调试麻烦放在用户态应用层灵活但延迟稍高。我选择放在用户态原因有三一是OpenHarmony的应用开发调试更方便改阈值不用重新编译内核二是按键识别本身不要求微秒级响应毫秒级完全够用三是用户态可以直接对接OpenHarmony的事件分发机制把按键事件送给UI框架。2.3 设备树在其中的角色设备树Device Tree在OpenHarmony的Linux内核分支中承担着硬件描述的角色。对于SARADC来说设备树需要描述清楚用的是哪个SARADC控制器、通道号是多少、参考电压是多少、采样速率怎么配、引脚复用怎么设。很多从STM32裸机转过来的工程师会不习惯设备树觉得直接在C代码里写寄存器更直接。但设备树的好处在于同一份内核镜像可以适配不同的板级硬件只需要换设备树文件。对于OpenHarmony这种要适配多种开发板的系统来说设备树是必须掌握的技能。3. 核心细节解析与实操要点3.1 硬件电路分压网络的设计与计算先看硬件。板载三个按键分别对应三个不同的分压电阻值。电路结构是ADC引脚通过一个上拉电阻接到参考电压Vref每个按键通过一个不同的下拉电阻接地。没有按键按下时ADC引脚被上拉到Vref读到的值接近满量程。按下某个按键时该按键的下拉电阻与上拉电阻形成分压ADC引脚读到一个中间电压值。假设上拉电阻R_pull10kΩ参考电压Vref1.8V三个按键的下拉电阻分别是R110kΩ、R24.7kΩ、R32.2kΩ。那么按下不同按键时ADC引脚的电压分别是按键1按下V 1.8 × 10/(1010) 0.9V按键2按下V 1.8 × 4.7/(104.7) ≈ 0.575V按键3按下V 1.8 × 2.2/(102.2) ≈ 0.325V无按键V ≈ 1.8V这三个电压值之间的间隔分别是0.325V和0.25V对于10位ADC分辨率1.8/1024≈1.76mV来说间隔足够大不会误判。但如果你用的是12位ADC分辨率更高间隔可以设计得更小从而支持更多按键。注意电阻精度直接影响电压间隔。建议使用1%精度的金属膜电阻不要用5%的碳膜电阻。另外按键的接触电阻也会影响分压比劣质按键的接触电阻可能达到几百欧姆足以让电压偏移几十毫伏。3.2 设备树节点配置详解在OpenHarmony的Linux内核中SARADC控制器的设备树节点通常由SoC厂商提供比如瑞芯微RK3568的SARADC节点在rk3568.dtsi中已经定义好了。我们需要做的是在板级设备树文件比如rk3568-evb.dts中引用这个节点并添加自己的通道配置。一个典型的SARADC设备树配置如下saradc { status okay; vref-supply vcc_1v8_s0; }; adc_keys { compatible adc-keys; io-channels saradc 0; io-channel-names buttons; keyup-threshold-microvolt 1800000; poll-interval 100; button-vol-up { label volume_up; linux,code KEY_VOLUMEUP; press-threshold-microvolt 900000; }; button-vol-down { label volume_down; linux,code KEY_VOLUMEDOWN; press-threshold-microvolt 575000; }; button-function { label function; linux,code KEY_FUNCTION; press-threshold-microvolt 325000; } };这里有几个关键点需要解释。io-channels指定了SARADC控制器和通道号saradc 0表示使用saradc控制器的第0通道。vref-supply引用了1.8V的稳压源这个必须和硬件实际参考电压一致否则所有阈值都会偏移。poll-interval是轮询间隔单位毫秒100ms意味着每秒采样10次对于按键来说响应足够快又不会太耗电。press-threshold-microvolt是按键按下的电压阈值。内核的adc-keys驱动会读取ADC原始值转换成微伏然后和这些阈值比较。如果读到的电压在某个阈值的±容差范围内就上报对应的按键事件。提示keyup-threshold-microvolt必须设置为参考电压值表示没有按键按下时的状态。如果这个值设错了系统会一直认为有按键被按住。3.3 GPIO复用与引脚配置SARADC的引脚通常是复用的需要确认引脚复用寄存器配置正确。在RK3568上SARADC通道0对应的引脚是GPIO1_PC0这个引脚默认功能就是SARADC不需要额外配置复用。但如果你用的是其他SoC比如全志或晶晨的芯片可能需要通过pinctrl子系统配置引脚功能。在设备树中pinctrl配置通常长这样pinctrl { saradc { saradc_pins: saradc-pins { rockchip,pins 1 RK_PC0 1 pcfg_pull_none; }; }; };这里的1 RK_PC0 1 pcfg_pull_none表示GPIO1组的PC0引脚功能选择1即SARADC功能不上拉不下拉。注意ADC引脚不能配置内部上拉或下拉否则会干扰外部电阻分压网络。外部已经有上拉电阻了内部再上拉会导致分压比完全错误。3.4 内核驱动配置与编译选项要让adc-keys驱动工作内核配置中需要打开以下选项CONFIG_ADCy CONFIG_SARADC_ROCKCHIPy CONFIG_INPUT_ADC_KEYSy CONFIG_INPUT_EVDEVy CONFIG_KEYBOARD_ADCy其中CONFIG_INPUT_ADC_KEYS是核心它提供了通用的ADC按键驱动。CONFIG_KEYBOARD_ADC是另一种实现两者选其一即可。我实测下来adc-keys更稳定因为它直接对接输入子系统不需要额外的键盘映射层。编译内核后系统启动时会在/dev/input/下生成一个event设备节点比如event2。你可以用evtest工具测试按键事件是否正常上报。如果evtest能看到事件但应用层收不到那问题就出在OpenHarmony的事件分发层而不是内核驱动。4. 实操过程与核心环节实现4.1 从零开始设备树修改与内核编译第一步找到你的板级设备树文件。以RK3568为例路径通常是kernel/linux/linux-5.10/arch/arm64/boot/dts/rockchip/rk3568-evb.dts。在这个文件中找到saradc节点确认状态是okay并且vref-supply指向正确的稳压源。第二步添加adc-keys节点。你可以直接写在板级dts文件的根节点下也可以写在一个独立的dtsi文件中再包含进来。我习惯写在独立文件中方便管理。文件命名为rk3568-adc-keys.dtsi然后在板级dts中#include进来。第三步确认引脚复用。查看SoC的datasheet找到SARADC通道0对应的引脚确认pinctrl配置正确。如果不确定可以在系统启动后查看/sys/kernel/debug/pinctrl/下的引脚状态。第四步编译内核。在OpenHarmony的代码根目录下执行./build.sh --product-name rk3568 --ccache --build-target kernel编译完成后生成的boot.img会包含新的设备树。烧录到开发板后系统启动时内核会打印SARADC和adc-keys的初始化日志。你可以用dmesg | grep adc查看。4.2 验证ADC原始值读取在写应用层代码之前先确认ADC能读到正确的原始值。有两种方法。第一种是通过sysfs接口cat /sys/bus/iio/devices/iio:device0/in_voltage0_raw这个命令会返回ADC的原始值比如0到4095之间的一个数。按下不同按键观察数值变化。如果数值不变或者跳变剧烈说明硬件电路或设备树配置有问题。第二种方法是用iio工具iio_readdev -b 1 -s 1 iio:device0 voltage0这个工具会以二进制格式输出采样值适合批量测试。我实测下来sysfs方式最简单但每次读取都有文件操作开销不适合高频采样。iio工具更底层但需要额外安装。注意如果读到的原始值一直是0或者一直是满量程先检查vref-supply是否配置正确再检查引脚是否被其他驱动占用。用cat /sys/kernel/debug/gpio可以查看GPIO占用情况。4.3 OpenHarmony HDI接口调用OpenHarmony的SARADC HDI接口定义在drivers/peripheral/adc/目录下。核心接口有三个int32_t AdcOpen(int32_t number); int32_t AdcRead(int32_t number, uint32_t *value); int32_t AdcClose(int32_t number);AdcOpen打开指定通道的ADC设备AdcRead读取一次采样值AdcClose关闭设备。在用户态应用中你需要先通过AdcOpen获取设备句柄然后循环调用AdcRead读取电压值。一个典型的读取循环如下int32_t adcHandle AdcOpen(0); if (adcHandle 0) { printf(AdcOpen failed\n); return -1; } uint32_t value 0; while (1) { int32_t ret AdcRead(adcHandle, value); if (ret ! 0) { printf(AdcRead failed\n); break; } float voltage value * 1.8f / 4096.0f; printf(ADC raw: %u, voltage: %.3fV\n, value, voltage); usleep(100000); } AdcClose(adcHandle);这段代码每100ms读取一次ADC把原始值转换成电压值打印出来。你可以根据电压值判断哪个按键被按下。4.4 按键识别逻辑与阈值调优理论阈值和实际读到的值往往有偏差因为电阻精度、参考电压精度、ADC线性度都会影响结果。所以阈值调优是必须的。我的做法是先打印出每个按键按下时的实际ADC原始值记录20组数据取平均值作为中心阈值然后根据数据的标准差确定容差范围。比如实测数据如下按键理论电压实测平均电压标准差建议阈值范围音量加0.900V0.892V0.008V0.870V - 0.915V音量减0.575V0.568V0.006V0.550V - 0.586V功能键0.325V0.319V0.005V0.305V - 0.333V无按键1.800V1.795V0.003V 1.750V根据这个表我在应用层设置阈值判断逻辑如果电压在0.870V到0.915V之间判定为音量加在0.550V到0.586V之间判定为音量减在0.305V到0.333V之间判定为功能键高于1.750V判定为无按键。其他电压值视为无效可能是按键抖动或同时按下多个按键。提示按键抖动会导致电压在阈值边缘跳变。建议在软件中加一个简单的去抖逻辑连续3次采样都在同一阈值范围内才确认按键按下。4.5 信号控制从按键事件到系统响应识别到按键后下一步是触发系统响应。在OpenHarmony中最规范的做法是通过输入子系统上报按键事件让系统的事件分发机制处理。但如果你只是想快速验证也可以直接在应用层调用系统接口。比如音量加按键按下时调用音频服务接口提高音量#include audio_system_manager.h void handleVolumeUp() { AudioSystemManager *manager AudioSystemManager::GetInstance(); int32_t volume manager-GetVolume(AudioSystemManager::STREAM_MUSIC); manager-SetVolume(AudioSystemManager::STREAM_MUSIC, volume 1); }功能键按下时可以触发一个自定义动作比如启动某个应用或者切换屏幕亮度。OpenHarmony提供了Want机制来启动Ability你可以构造一个Want对象并调用StartAbility。如果你希望按键事件能被系统全局捕获那就需要把adc-keys驱动上报的事件接入OpenHarmony的输入子系统。这需要在HDF层配置输入设备把/dev/input/eventX注册到输入框架中。具体做法是在vendor/目录下的HDF配置文件中添加输入设备节点指定设备路径和事件类型。5. 常见问题与排查技巧实录5.1 ADC读数不稳定或跳变严重这是最常见的问题。可能的原因有四个参考电压不稳、电阻精度不够、引脚受到干扰、采样速率设置不当。参考电压不稳是最容易被忽略的。如果你用的是开发板上的LDO输出作为Vref而LDO同时给其他外设供电负载变化会导致Vref波动。解决办法是给Vref加一个100nF到1uF的滤波电容或者使用独立的基准电压源。电阻精度问题前面提过了这里补充一点分压电阻的温漂也会影响读数。如果开发板工作环境温度变化大建议使用低温漂电阻比如25ppm/°C。引脚干扰通常来自相邻的高速信号线。ADC引脚走线要尽量短远离时钟线和数据线。如果无法避免可以在ADC引脚和地之间加一个10nF的电容滤掉高频干扰。采样速率设置不当也会导致读数跳变。SARADC的采样速率太高采样保持电容来不及充满读数会偏低且不稳定。RK3568的SARADC默认采样率是1MHz对于按键应用来说太快了。可以在设备树中降低采样率或者在驱动中增加采样保持时间。5.2 按键识别错误或误触发按键识别错误通常表现为没按按键却上报了按键事件或者按了A键却识别成B键。没按按键却上报事件最常见的原因是keyup-threshold-microvolt设置错误。这个值必须等于或略高于无按键时的实际电压。如果设低了系统会把无按键状态误判为某个按键按下。按A键识别成B键说明两个按键的电压间隔太小或者阈值范围重叠了。解决办法是重新设计分压电阻增大电压间隔。一般来说相邻按键的电压间隔至少要是ADC分辨率的10倍以上。对于10位ADC和1.8V参考电压分辨率约1.76mV10倍就是17.6mV。但实际应用中考虑到噪声和温漂建议间隔至少50mV。还有一种情况是按键抖动导致连续触发。机械按键在按下和松开的瞬间会产生几十毫秒的抖动ADC会读到一系列跳变的电压值。软件去抖是必须的最简单的做法是连续采样3到5次只有所有采样值都在同一阈值范围内才确认按键状态。5.3 设备树配置不生效设备树修改后不生效通常有三个原因编译时没有包含修改的文件、烧录时没有更新设备树分区、设备树节点被其他节点覆盖。第一个原因最好排查。编译完成后在out/目录下找到生成的dtb文件用dtc工具反编译成dts搜索你的节点名称确认修改已经包含进去。第二个原因也常见。有些开发板的boot.img和dtb是分开的分区烧录时只烧了boot.img没有烧dtb。确认烧录工具的配置确保dtb分区也被更新。第三个原因比较隐蔽。如果你的节点名称和SoC厂商定义的节点冲突内核会选择其中一个可能不是你想要的那个。解决办法是给节点起一个独特的名称比如my-adc-keys而不是adc-keys。5.4 OpenHarmony HDI接口调用失败HDI接口调用失败通常返回负值。常见的错误码和原因如下错误码含义排查方向-1设备未打开确认AdcOpen是否成功-2参数错误检查通道号是否超出范围-3设备忙是否有其他进程占用了ADC-4读取超时检查硬件连接和采样速率-5权限不足确认应用是否有ADC设备访问权限权限问题在OpenHarmony中比较常见。默认情况下只有系统应用才能访问ADC设备。如果你是普通应用需要在config.json中申请ohos.permission.ADC权限。如果这个权限不存在可能需要修改系统权限配置文件把ADC设备节点加入可访问列表。5.5 常见问题速查表现象可能原因解决方法ADC读数始终为0引脚未复用为ADC功能检查pinctrl配置ADC读数始终为满量程上拉电阻未焊接或虚焊检查硬件焊接读数跳变超过10%参考电压不稳或干扰加滤波电容检查Vref按键识别错误阈值范围重叠重新计算分压电阻按键无响应驱动未加载检查内核配置和dmesg日志HDI调用返回-5权限不足申请ADC权限或修改权限配置设备树不生效dtb未更新确认烧录分区配置6. 实操心得与进阶建议6.1 电阻选型的经验法则分压电阻的选型直接决定了ADC按键方案的可靠性。我踩过的坑是一开始用了10kΩ上拉和10kΩ、4.7kΩ、2.2kΩ下拉理论计算没问题但实测发现按键2和按键3的电压间隔只有0.25V在12位ADC下虽然够用但噪声一大就容易误判。后来我把下拉电阻改成10kΩ、3.3kΩ、1kΩ电压间隔拉大到0.4V以上稳定性明显提升。另一个经验是上拉电阻不要太大。上拉电阻越大ADC引脚的驱动能力越弱采样保持电容充电越慢读数越容易偏低。一般来说上拉电阻在4.7kΩ到10kΩ之间比较合适。如果追求低功耗可以适当增大但要相应降低采样速率。6.2 软件去抖的简单实现硬件去抖需要加电容但电容会延长按键响应时间。我更喜欢软件去抖灵活且不增加硬件成本。最简单的软件去抖是计数法#define DEBOUNCE_COUNT 3 int lastKey -1; int stableCount 0; int detectKey(float voltage) { int currentKey -1; if (voltage 1.750f) currentKey -1; else if (voltage 0.870f voltage 0.915f) currentKey 0; else if (voltage 0.550f voltage 0.586f) currentKey 1; else if (voltage 0.305f voltage 0.333f) currentKey 2; if (currentKey lastKey) { stableCount; } else { stableCount 0; lastKey currentKey; } if (stableCount DEBOUNCE_COUNT) { return currentKey; } return -1; }这段代码的逻辑是只有连续3次采样都识别到同一个按键才认为按键有效。DEBOUNCE_COUNT可以根据实际抖动情况调整一般3到5次就够了。如果采样间隔是100ms3次就是300ms响应稍慢但很稳。如果希望响应更快可以把采样间隔降到20msDEBOUNCE_COUNT设为5总去抖时间还是100ms。6.3 多按键同时按下的处理ADC按键方案的一个固有缺陷是无法处理多按键同时按下。如果用户同时按下音量加和音量减ADC读到的电压是两个下拉电阻并联后的分压值这个值可能落在某个按键的阈值范围内导致误判。我的处理策略是在阈值表中增加一个“无效区间”的概念。如果读到的电压不在任何按键的阈值范围内就判定为无效输入忽略这次采样。同时在应用层加一个互斥逻辑如果当前已经有按键被按下且未释放忽略新的按键事件。如果你确实需要支持多按键同时按下那就只能回到GPIO方案或者使用矩阵键盘方案。ADC按键方案不适合这种场景。6.4 低功耗场景的优化ADC按键方案在低功耗场景下需要特别注意。如果系统进入休眠ADC采样也会停止按键无法唤醒系统。解决办法是把ADC按键同时接到一个GPIO中断上休眠时关闭ADC靠GPIO中断唤醒唤醒后再启动ADC读取具体按键值。这个方案需要硬件上把ADC引脚同时接到一个支持中断的GPIO上。有些SoC的ADC引脚本身就支持中断功能那就更方便了。在设备树中配置中断触发方式为下降沿触发因为按键按下时电压从高变低。6.5 从ADC按键到其他ADC应用掌握了ADC按键之后同样的SARADC接口可以用来做很多其他事情。比如读取光敏电阻的电压来判断环境亮度读取热敏电阻的电压来测量温度读取电位器的电压来获取旋钮位置。这些应用的硬件电路和ADC按键类似都是分压网络区别只在于传感器电阻是随环境变化的。在OpenHarmony中这些应用都可以复用同一套SARADC HDI接口。你只需要修改设备树中的通道配置和阈值判断逻辑就能快速实现新的功能。这也是OpenHarmony HDF驱动框架的优势所在硬件抽象层统一了接口上层应用不需要关心底层是哪个SoC的ADC。我在实际项目中发现ADC按键方案在成本敏感的消费电子产品中非常受欢迎。一个典型的遥控器如果有10个按键用GPIO方案需要10个引脚用ADC方案只需要1个引脚加10个电阻。对于引脚资源紧张的MCU来说这是巨大的优势。当然ADC方案也有代价需要软件去抖、无法多键同时按、对电源稳定性要求高。选择哪种方案取决于你的具体需求和成本预算。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门