最近接触到的一个代码,这个代码看起来很简单,但是却蕴藏了人类的智慧与结晶。正是这些不断产生的智慧与结晶,让我们的电子产品越来越稳定,越来越智能。
周五了,评论文章,选两个同学赠送书籍《Linux内核完全剖析》基于0.12。
#功能
通过ADC值的不同来判断是哪个按键按下,这种方案应该是很常见了,而且这种方案可以节省GPIO口。实现起来的难度也不是特别大。
#硬件连接
#原来的旧代码
static void adc_key_poll(struct work_struct *work)
{
struct rk_keys_drvdata *ddata;
int i, result = -1;
ddata = container_of(work, struct rk_keys_drvdata, adc_poll_work.work);
if (!ddata->in_suspend) {
result = rk_key_adc_iio_read(ddata);/**读取SARADC值*/
if (result > INVALID_ADVALUE &&
┊ result < (EMPTY_DEFAULT_ADVALUE - ddata->drift_advalue))
ddata->result = result;
for (i = 0; i < ddata->nbuttons; i++) {
struct rk_keys_button *button = &ddata->button[i];
if (!button->adc_value)
continue;
if (result < button->adc_value + ddata->drift_advalue &&
┊ result > button->adc_value - ddata->drift_advalue)
button->adc_state = 1;
else
button->adc_state = 0;
if (button->state != button->adc_state)
mod_timer(&button->timer,
┊ jiffies + DEBOUNCE_JIFFIES);
}
}
schedule_delayed_work(&ddata->adc_poll_work, ADC_SAMPLE_JIFFIES);
}
#升级代码
staticvoidadc_keys_poll(structinput_polled_dev*dev)
{
structadc_keys_state*st=dev->private;
inti,value,ret;
u32diff,closest=0xffffffff;
intkeycode=0;
ret=iio_read_channel_processed(st->channel,&value);
if(unlikely(ret<0)){
/*Forciblyreleasekeyifanywaspressed*/
value=st->keyup_voltage;
}else{
/*先把电压值与所有按键列表的值比较,找出最接近的一个,以及找出最接近的差值。*/
for(i=0;i<st->num_keys;i++){
diff=abs(st->map[i].voltage-value);
if(diff<closest){
closest=diff;
keycode=st->map[i].keycode;
}
}
}
//printk("adc_keys_pollvalue:%dkeycode:%d\n",value,keycode);
/*
然后把如果发现当前电压值与按键弹起的电压值很接近,(标准是小于上面找出的最小差值),那么认为没有按键按下,所以设置keycode为0,不上报。
*/
if(abs(st->keyup_voltage-value)<closest)
keycode=0;
if(st->last_key&&st->last_key!=keycode)
input_report_key(dev->input,st->last_key,0);
if(keycode){
input_report_key(dev->input,keycode,1);
printk("adc_key_pollkeycode:%d\n",keycode);
}
/*他这么做有个好处,就是只要硬件设计合理的情况下,电阻不同批料有误差的情况下,也不至于需要驱动工程师去修改电压值或者误差范围才能识别按键。*/
/*这个做法肯定是在量产出现了很多问题后作出的改善。*/
input_sync(dev->input);
st->last_key=keycode;
}
完整的驱动代码可以看下面这段,我觉得这小段代码的意义非常大。很有意思,通过一个for循环找到一个最接近的数值,判断为按下的这个键值。而且如果按下和 抬起来的数值接近,就判断为没有按键按下。
这就不需要再去考虑一个问题,那就误差范围的问题了。之前的那套旧代码使用的是误差范围,如果ADC值设定为 2000 ,误差范围设定为100,那么读取的ADC值在1900~2100范围内都可以认为是这个按键按下的。
但是存在一个情况,就是不同批次,不同物料的硬件,误差总是不能令人满意,软件需要不断的调整这个误差范围。在不断的调整过程中,发现调整误差范围已经不能解决当下的问题了,所以就产生了新的算法。而我们现在看到的这个新的算法就这样应运而生了。当然,实际情况可能还要复杂化,可能需要加上一些滤波算法先过滤等等。
总之,各位可以保存下来这段代码,在需要的时候拿来使用。展现自己的码农魅力。
#完整升级代码
/*
*InputdriverforresistorladderconnectedonADC
*
*Copyright(c)AlexandreBelloni
*
*Thisprogramisfreesoftware;youcanredistributeitand/ormodifyit
*underthetermsoftheGNUGeneralPublicLicenseversion2aspublishedby
*theFreeSoftwareFoundation.
*/
#include<linux/err.h>
#include<linux/iio/consumer.h>
#include<linux/iio/types.h>
#include<linux/input.h>
#include<linux/input-polldev.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>
structadc_keys_button{
u32voltage;
u32keycode;
};
structadc_keys_state{
structiio_channel*channel;
u32num_keys;
u32last_key;
u32keyup_voltage;
conststructadc_keys_button*map;
};
staticvoidadc_keys_poll(structinput_polled_dev*dev)
{
structadc_keys_state*st=dev->private;
inti,value,ret;
u32diff,closest=0xffffffff;
intkeycode=0;
ret=iio_read_channel_processed(st->channel,&value);
if(unlikely(ret<0)){
/*Forciblyreleasekeyifanywaspressed*/
value=st->keyup_voltage;
}else{
/*先把电压值与所有按键列表的值比较,找出最接近的一个,以及找出最接近的差值。*/
for(i=0;i<st->num_keys;i++){
diff=abs(st->map[i].voltage-value);
if(diff<closest){
closest=diff;
keycode=st->map[i].keycode;
}
}
}
//printk("adc_keys_pollvalue:%dkeycode:%d\n",value,keycode);
/*
然后把如果发现当前电压值与按键弹起的电压值很接近,(标准是小于上面找出的最小差值),那么认为没有按键按下,所以设置keycode为0,不上报。
*/
if(abs(st->keyup_voltage-value)<closest)
keycode=0;
if(st->last_key&&st->last_key!=keycode)
input_report_key(dev->input,st->last_key,0);
if(keycode){
input_report_key(dev->input,keycode,1);
printk("adc_key_pollkeycode:%d\n",keycode);
}
/*他这么做有个好处,就是只要硬件设计合理的情况下,电阻不同批料有误差的情况下,也不至于需要驱动工程师去修改电压值或者误差范围才能识别按键。*/
/*这个做法肯定是在量产出现了很多问题后作出的改善。*/
input_sync(dev->input);
st->last_key=keycode;
}
staticintadc_keys_load_keymap(structdevice*dev,structadc_keys_state*st)
{
structadc_keys_button*map;
structfwnode_handle*child;
inti;
st->num_keys=device_get_child_node_count(dev);
if(st->num_keys==0){
dev_err(dev,"keymapismissing\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,"Keywithinvalidormissingvoltage\n");
fwnode_handle_put(child);
return-EINVAL;
}
map[i].voltage/=1000;
if(fwnode_property_read_u32(child,"linux,code",
&map[i].keycode)){
dev_err(dev,"Keywithinvalidormissinglinux,code\n");
fwnode_handle_put(child);
return-EINVAL;
}
i++;
}
st->map=map;
return0;
}
staticintadc_keys_probe(structplatform_device*pdev)
{
structdevice*dev=&pdev->dev;
structadc_keys_state*st;
structinput_polled_dev*poll_dev;
structinput_dev*input;
enumiio_chan_typetype;
inti,value;
interror;
st=devm_kzalloc(dev,sizeof(*st),GFP_KERNEL);
if(!st)
return-ENOMEM;
st->channel=devm_iio_channel_get(dev,"buttons");
if(IS_ERR(st->channel))
returnPTR_ERR(st->channel);
if(!st->channel->indio_dev)
return-ENXIO;
error=iio_get_channel_type(st->channel,&type);
if(error<0)
returnerror;
if(type!=IIO_VOLTAGE){
dev_err(dev,"Incompatiblechanneltype%d\n",type);
return-EINVAL;
}
if(device_property_read_u32(dev,"keyup-threshold-microvolt",
&st->keyup_voltage)){
dev_err(dev,"Invalidormissingkeyupvoltage\n");
return-EINVAL;
}
st->keyup_voltage/=1000;
error=adc_keys_load_keymap(dev,st);
if(error)
returnerror;
platform_set_drvdata(pdev,st);
poll_dev=devm_input_allocate_polled_device(dev);
if(!poll_dev){
dev_err(dev,"failedtoallocateinputdevice\n");
return-ENOMEM;
}
if(!device_property_read_u32(dev,"poll-interval",&value))
poll_dev->poll_interval=value;
poll_dev->poll=adc_keys_poll;
poll_dev->private=st;
input=poll_dev->input;
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;
__set_bit(EV_KEY,input->evbit);
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);
error=input_register_polled_device(poll_dev);
if(error){
dev_err(dev,"Unabletoregisterinputdevice:%d\n",error);
returnerror;
}
return0;
}
#ifdefCONFIG_OF
staticconststructof_device_idadc_keys_of_match[]={
{.compatible="adc-keys",},
{}
};
MODULE_DEVICE_TABLE(of,adc_keys_of_match);
#endif
staticstructplatform_driver__refdataadc_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);
MODULE_AUTHOR("AlexandreBelloni<alexandre.belloni@free->");
MODULE_DESCRIPTION("InputdriverforresistorladderconnectedonADC");
MODULE_LICENSE("GPLv2");
如果觉得《对比一段ADC键值读取的代码》对你有帮助,请点赞、收藏,并留下你的观点哦!