ADC-KEY linux代码分析(附部分简单电路)

一、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 流程图

根据上述代码的分析,得出如下流程图(这里没有完全按照代码去画,只是描述了其大致思路)
在这里插入图片描述

ADC-Keys是一个Linux驱动程序,用于将ADC(模拟-数字转换器)输入转换为按键输入。 以下是adc-keys.c的详细注释: ```c /* * ADC-Keys driver * * This driver converts ADC input to key events. * * The driver is designed to work with input subsystem. It creates * an input device (/dev/input/eventX) that can be read by user-space * applications. * * Author: John Doe <johndoe@example.com> */ #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/platform_device.h> #include <linux/input.h> // 定义ADC输入的最小和最大 #define ADC_MIN_VALUE 0 #define ADC_MAX_VALUE 4095 // 定义每个按键对应的ADC输入范围 #define KEY1_ADC_MIN_VALUE 0 #define KEY1_ADC_MAX_VALUE 100 #define KEY2_ADC_MIN_VALUE 101 #define KEY2_ADC_MAX_VALUE 500 #define KEY3_ADC_MIN_VALUE 501 #define KEY3_ADC_MAX_VALUE 1000 #define KEY4_ADC_MIN_VALUE 1001 #define KEY4_ADC_MAX_VALUE 4095 // 定义输入设备的名称和ID #define INPUT_DEVICE_NAME "adc-keys" #define INPUT_DEVICE_ID BUS_HOST // 定义平台设备的名称和ID #define PLATFORM_DEVICE_NAME "adc-keys" #define PLATFORM_DEVICE_ID 0 // 定义设备结构体 struct adc_keys_data { struct input_dev *input_dev; int adc_value; }; // 处理输入事件的函数 static void adc_keys_input_event(struct adc_keys_data *data) { int code = 0, value = 0; // 根据ADC输入的范围确定按键事件 if (data->adc_value >= KEY1_ADC_MIN_VALUE && data->adc_value <= KEY1_ADC_MAX_VALUE) { code = KEY_1; value = 1; } else if (data->adc_value >= KEY2_ADC_MIN_VALUE && data->adc_value <= KEY2_ADC_MAX_VALUE) { code = KEY_2; value = 1; } else if (data->adc_value >= KEY3_ADC_MIN_VALUE && data->adc_value <= KEY3_ADC_MAX_VALUE) { code = KEY_3; value = 1; } else if (data->adc_value >= KEY4_ADC_MIN_VALUE && data->adc_value <= KEY4_ADC_MAX_VALUE) { code = KEY_4; value = 1; } // 发送按键事件到输入子系统 if (code && value) { input_report_key(data->input_dev, code, value); input_sync(data->input_dev); } } // 处理ADC输入的函数 static void adc_keys_adc_event(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct platform_device *pdev = to_platform_device(dev); struct adc_keys_data *data = platform_get_drvdata(pdev); // 将输入的字符串转换为整数 sscanf(buf, "%d", &data->adc_value); // 处理输入事件 adc_keys_input_event(data); } // 初始化设备的函数 static int adc_keys_probe(struct platform_device *pdev) { struct input_dev *input_dev; struct adc_keys_data *data; int ret; // 分配设备结构体的内存 data = devm_kzalloc(&pdev->dev, sizeof(struct adc_keys_data), GFP_KERNEL); if (!data) return -ENOMEM; // 初始化输入设备 input_dev = input_allocate_device(); if (!input_dev) { dev_err(&pdev->dev, "Failed to allocate input device\n"); return -ENOMEM; } // 设置输入设备的名称和ID input_dev->name = INPUT_DEVICE_NAME; input_dev->id.bustype = INPUT_DEVICE_ID; // 设置输入设备支持的按键事件 set_bit(EV_KEY, input_dev->evbit); set_bit(KEY_1, input_dev->keybit); set_bit(KEY_2, input_dev->keybit); set_bit(KEY_3, input_dev->keybit); set_bit(KEY_4, input_dev->keybit); // 注册输入设备 ret = input_register_device(input_dev); if (ret) { dev_err(&pdev->dev, "Failed to register input device\n"); input_free_device(input_dev); return ret; } // 将设备结构体存储到平台设备中 platform_set_drvdata(pdev, data); // 创建设备属性,用于接收ADC输入的 ret = device_create_file(&pdev->dev, &dev_attr_adc_value); if (ret) { dev_err(&pdev->dev, "Failed to create device file\n"); input_unregister_device(input_dev); return ret; } // 初始化设备结构体 data->input_dev = input_dev; data->adc_value = 0; return 0; } // 卸载设备的函数 static int adc_keys_remove(struct platform_device *pdev) { struct adc_keys_data *data = platform_get_drvdata(pdev); // 删除设备属性 device_remove_file(&pdev->dev, &dev_attr_adc_value); // 注销输入设备 input_unregister_device(data->input_dev); return 0; } // 设备属性,用于接收ADC输入的 static DEVICE_ATTR(adc_value, S_IWUSR, NULL, adc_keys_adc_event); // 定义平台驱动程序结构体 static struct platform_driver adc_keys_driver = { .probe = adc_keys_probe, .remove = adc_keys_remove, .driver = { .name = PLATFORM_DEVICE_NAME, .owner = THIS_MODULE, }, }; // 模块初始化函数 static int __init adc_keys_init(void) { int ret; // 注册平台驱动程序 ret = platform_driver_register(&adc_keys_driver); if (ret) return ret; return 0; } // 模块卸载函数 static void __exit adc_keys_exit(void) { // 注销平台驱动程序 platform_driver_unregister(&adc_keys_driver); } module_init(adc_keys_init); module_exit(adc_keys_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("John Doe <johndoe@example.com>"); MODULE_DESCRIPTION("ADC-Keys driver"); ``` 以上是adc-keys.c的详细注释。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值