一、ADC按键简单电路分析
上图是网上找的,实际的电路还需要做一些防静电+消抖的设计,1k电阻附近的按键按下,以此类推,ADC检测到的电压如下
//假设下列是这些按键
未按下 3.3V
1K => (1K / (3K+1K)) * 3.3V = 0.825V //音量+
2K => 1.32V //音量-键
3K => 1.65V //menu键
4K => 1.88V //home键
5K => 2.06V //esc键
基本思路:对根据ADC采集到的电压进行区分,就可以判断是哪个按键按下
二、linux中的ADC按键
1.设备树中的配置
如果要根据《一、ADC按键简单电路分析》中的电路,需要对设备树中的参数进行重新设置
adc-keys1 {
compatible = "adc-keys";
io-channels = <&saradc 1>; //这里申请的是 SARADC(逐次逼近ADC) 通道1。
io-channel-names = "buttons";
poll-interval = <100>; //adc检查是采用轮询的方式检查,间隔时间
//keyup-threshold-microvolt = <1800000>; //按键抬起后的电压阈值1.8V,只要超过1.8V,认为没有按键按下
keyup-threshold-microvolt = <3300000>;
esc-key {
linux,code = <KEY_MICMUTE>; //键值,按键按下,会触发KEY_MICMUTE事件
label = "micmute";
press-threshold-microvolt = <2060000>; //检测到2.06V,认为esc键按下
};
home-key {
linux,code = <KEY_MODE>; //键值,按键按下,会触发KEY_MODE事件
label = "mode";
press-threshold-microvolt = <1880000>; //检测到1.88V,认为home键按下
};
menu-key {
linux,code = <KEY_PLAY>; //键值,按键按下,会触发KEY_PLAY事件
label = "play";
press-threshold-microvolt = <1650000>; //检测到1.65V,认为menu键按下
};
vol-down-key {
linux,code = <KEY_VOLUMEDOWN>; //键值,按键按下,会触发KEY_VOLUMEDOWN事件
label = "volume down";
press-threshold-microvolt = <1320000>; //检测到1.32V,认为音量-键按下
};
vol-up-key {
linux,code = <KEY_VOLUMEUP>; //键值,按键按下,会触发KEY_VOLUMEUP事件
label = "volume up";
press-threshold-microvolt = <825000>; //检测到0.825V,认为音量+键按下
};
};
2.linux代码adc-keys.c分析
kernel-5.15/drivers/input/keyboard/adc-keys.c
// SPDX-License-Identifier: GPL-2.0-only
/*
* Input driver for resistor ladder connected on ADC
*
* Copyright (c) 2016 Alexandre Belloni
*/
#include <linux/err.h>
#include <linux/iio/consumer.h>
#include <linux/iio/types.h>
#include <linux/input.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/slab.h>
struct adc_keys_button {
u32 voltage; //按键电压
u32 keycode; //按键 键值
};
struct adc_keys_state {
struct iio_channel *channel; //ADC通道
u32 num_keys; //按键数量
u32 last_key; //上一次按键
u32 keyup_voltage; //按键抬起电压
const struct adc_keys_button *map; //adc keys映射 集合
};
//这个是poll处理函数
static void adc_keys_poll(struct input_dev *input)
{
//先获取之前probe中 input私有数据st,存放了各个按键的信息
struct adc_keys_state *st = input_get_drvdata(input);
int i, value, ret;
u32 diff, closest = 0xffffffff;
int keycode = 0;
//读取ADC电压值
ret = iio_read_channel_processed(st->channel, &value);
if (unlikely(ret < 0)) {
/* Forcibly release key if any was pressed */
//这里默认没有按键按下
value = st->keyup_voltage;
} else {
//检测到按键按下
for (i = 0; i < st->num_keys; i++) {
//计算 |各个按键电压阈值 - ADC采集到的电压| 的大小
diff = abs(st->map[i].voltage - value);
/**
* 这个判断的意义在于,选择一个与ADC采样差异最小 按键
* 1.先从第一个按键开始判断,必定赋值
* 2.第二个按键电压阈值 与 ADC采样值差异如果小于 第一个按键,则赋值,否则不赋值
* 3.以此类推,找出diff最小的按键,那么就可以判断是这个按键被按下
*/
if (diff < closest) {
closest = diff;
keycode = st->map[i].keycode;
}
}
}
//这里是做一个无按键按下的 keycode赋值处理
if (abs(st->keyup_voltage - value) < closest)
keycode = 0;
//input_report_key第三个值,0表示按键释放,1表示按键按下
//即检查上一次按键是否被释放,如果释放则执行if里内容,没有释放则往下执行
if (st->last_key && st->last_key != keycode)
input_report_key(input, st->last_key, 0);
//这里上报当前按键被按下的时间
if (keycode)
input_report_key(input, keycode, 1);
input_sync(input);
st->last_key = keycode;
}
//各个按键的电压参数解析,键值的映射,保存
static int adc_keys_load_keymap(struct device *dev, struct adc_keys_state *st)
{
struct adc_keys_button *map;
struct fwnode_handle *child;
int i;
//这个func是统计设备子节点的个数,从设备树来看,这里是按键的数量
st->num_keys = device_get_child_node_count(dev);
if (st->num_keys == 0) {
dev_err(dev, "keymap is missing\n");
return -EINVAL;
}
map = devm_kmalloc_array(dev, st->num_keys, sizeof(*map), GFP_KERNEL);
if (!map)
return -ENOMEM;
i = 0;
//这里开始解析子节点并赋值,就是获取各个按键的信息
device_for_each_child_node(dev, child) {
//获取各个按键按下的电压大小
if (fwnode_property_read_u32(child, "press-threshold-microvolt",
&map[i].voltage)) {
dev_err(dev, "Key with invalid or missing voltage\n");
fwnode_handle_put(child);
return -EINVAL;
}
map[i].voltage /= 1000;
//获取各个按键对应的linux key-code
if (fwnode_property_read_u32(child, "linux,code",
&map[i].keycode)) {
dev_err(dev, "Key with invalid or missing linux,code\n");
fwnode_handle_put(child);
return -EINVAL;
}
i++;
}
//存放这些按键的映射
st->map = map;
return 0;
}
static int adc_keys_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct adc_keys_state *st;
struct input_dev *input;
enum iio_chan_type type;
int i, value;
int error;
/*********下面部分是ADC的电压参数解析,各个按键对应的linux键值*********/
st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL);
if (!st)
return -ENOMEM;
st->channel = devm_iio_channel_get(dev, "buttons"); //获取adc通道name
if (IS_ERR(st->channel))
return PTR_ERR(st->channel);
if (!st->channel->indio_dev)
return -ENXIO;
error = iio_get_channel_type(st->channel, &type); //获取adc通道类型
if (error < 0)
return error;
if (type != IIO_VOLTAGE) {
dev_err(dev, "Incompatible channel type %d\n", type);
return -EINVAL;
}
//获取按键抬起属性,这里是3.3V
if (device_property_read_u32(dev, "keyup-threshold-microvolt",
&st->keyup_voltage)) {
dev_err(dev, "Invalid or missing keyup voltage\n");
return -EINVAL;
}
//这个值计算后:3300
st->keyup_voltage /= 1000;
//这里做各个按键的电压参数解析,键值的映射,保存
error = adc_keys_load_keymap(dev, st);
if (error)
return error;
/*********下面的部分就是将上述按键接入input子系统中,需要做按键信息的上报*********/
//分配一个input_dev结构体
input = devm_input_allocate_device(dev);
if (!input) {
dev_err(dev, "failed to allocate input device\n");
return -ENOMEM;
}
//设置结构体的私有数据,st里保存了各个按键的电压参数,键值的映射
input_set_drvdata(input, st);
//一些参数的设置
input->name = pdev->name;
input->phys = "adc-keys/input0";
input->id.bustype = BUS_HOST;
input->id.vendor = 0x0001;
input->id.product = 0x0001;
input->id.version = 0x0100;
//这里设置事件类型未EV_KEY,即按键事件
__set_bit(EV_KEY, input->evbit);
//这个for是对各个按键设置对应的键值
for (i = 0; i < st->num_keys; i++)
__set_bit(st->map[i].keycode, input->keybit);
//这个在设备树中没有设置
if (device_property_read_bool(dev, "autorepeat"))
__set_bit(EV_REP, input->evbit);
//按键轮询poll的设置,adc_keys_poll
error = input_setup_polling(input, adc_keys_poll);
if (error) {
dev_err(dev, "Unable to set up polling: %d\n", error);
return error;
}
//这个设置轮询的时间
if (!device_property_read_u32(dev, "poll-interval", &value))
input_set_poll_interval(input, value);
//将这些按键注册到input设备中
error = input_register_device(input);
if (error) {
dev_err(dev, "Unable to register input device: %d\n", error);
return error;
}
return 0;
}
#ifdef CONFIG_OF
static const struct of_device_id adc_keys_of_match[] = {
{ .compatible = "adc-keys", },
{ }
};
MODULE_DEVICE_TABLE(of, adc_keys_of_match);
#endif
static struct platform_driver adc_keys_driver = {
.driver = {
.name = "adc_keys",
.of_match_table = of_match_ptr(adc_keys_of_match),
},
.probe = adc_keys_probe,
};
module_platform_driver(adc_keys_driver);
...
三、Linux中 adc-key 流程图
根据上述代码的分析,得出如下流程图(这里没有完全按照代码去画,只是描述了其大致思路)