目前的情况是这样, 自己在外设上面 搞了四个按键 ,充当遥控器的 音量加 音量减 还有 灯光亮 灯光暗 这4个功能,
按照最简单的方法就是, 弄4个IO出来,
可以自己写一个驱动文件,
一个一个获取引脚,然后设置成为中断引脚,然后关联中断函数,设置一个定时器函数在中断函数的下半部进行消抖,一般是10ms。 在定时器中断函数调用输入子系统的接口函数,这样很简单就可以实现,不过有点浪费资源。目前很多管脚都是复用的。
考虑了之后还是用
/kernel/drivers/input/keyboard/rk_keys.c 这个模块已经写好了,自己调一下就可以用了
自己的电路是将一个模块电路接入ADCIN1
不同按键按下去,传入ADCIN1引脚的电压 0V 1.4V 2.0V 2.4V 区分开来 不按的时候是3 V
rk_keys.c 按键驱动有两种模式 ,
一种是GPIO中断 , 比如 Power 引脚的中断
另外一种是 定时器扫描按键的方式,每100ms读取一下ADC寄存器的值,如果ADC的值发生改变的时候,就可以通过ADC值判断按键。
1、先说一下键,键值
在输入子系统中,比如一个音量加的这个信号要发送到上层,其实只有一个接口函数就可以了,
input_event(input, EV_KEY, 键, 键值);
键值就是 0 和 1 ,就是开或者关
键就是输入子系统定义的宏 从0 ~ 100 ~ 200 每个键代表的意义不一样 ,比如音量减就是 114
这个可以看 dts/include/dt-budling/input 里面的input.h 这个BSP已经定义好了对应的键 ,自己可以查,遥控器用IR也是用的这里的键
那么调用
input_event(input, EV_KEY, 114, 1);
input_event(input, EV_KEY, 114, 0);
input_sync(input);
调用运行一下,音量条就可以看到减了,具体要了解更详细可以去看输入子系统。
现在怎么做呢?妈妈从厨房哭到厕所。因为税扣太多了。
2、分析一下rk_keys.c:
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);
if(result > INVALID_ADVALUE && result < EMPTY_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 + DRIFT_ADVALUE &&
result > button->adc_value - DRIFT_ADVALUE)
button->adc_state = 1;
else
button->adc_state = 0;
if(button->state != button->adc_state)
mod_timer(&button->timer,
jiffies + msecs_to_jiffies(DEFAULT_DEBOUNCE_INTERVAL));
}
}
schedule_delayed_work(&ddata->adc_poll_work,
msecs_to_jiffies(ADC_SAMPLE_TIME));
}
adc_key_poll这个函数 每100ms 会运行一次 ,
内核里面的中断一般都是分成两半,前一半很直接触发的中断,很快运行一些函数,另外一半会放到 定时器工作队列。
或者定时器中断上半部,定时器队列下半部,而且队列机制都是只运行一次。要想运行多次就要像成语接龙一样运行一次然后再中断函数里面设置一次。
if (ddata->chan) {
INIT_DELAYED_WORK(&ddata->adc_poll_work, adc_key_poll);
schedule_delayed_work(&ddata->adc_poll_work,
msecs_to_jiffies(ADC_SAMPLE_TIME));
}
这个就是工作队列 + 定时器
adc_key_poll就是定时器中断函数,
运行的最后再次调用了
schedule_delayed_work(&ddata->adc_poll_work,
msecs_to_jiffies(ADC_SAMPLE_TIME));
msecs_to_jiffies将毫秒转化成为jiffies, jiffies就是系统的心脏,一开机运行人家就开始计算,心脏跳了多少次,jiffies就是多少,jiffies可以清零,人清零了就是死了。
比如我准备心脏跳到某个地方在运行,jiffies + 2 *HZ
while(time_before(jiffies,jiffies + 2 *HZ))
while(time_before(jiffies,jiffies + HZ/10))
这样就可以长延时2S了或者延时100ms ,系统都都心脏的概念,所以jiffies 特别重要。
里面有个函数是
result = rk_key_adc_iio_read(ddata);
这个是ADC模块的接口,读出ADC转换出来的值,
然后有一个for循环
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 + DRIFT_ADVALUE &&
result > button->adc_value - DRIFT_ADVALUE)
button->adc_state = 1;
else
button->adc_state = 0;
if(button->state != button->adc_state)
mod_timer(&button->timer,
jiffies + msecs_to_jiffies(DEFAULT_DEBOUNCE_INTERVAL));
}
这个for循环是核心,这个i 的循环次数就是dts 上面的按键节点
/kernel/arc/arm/boot/dts/rk312x-sdk.dtsi
&adc {
status = "okay";
key: key {
compatible = "rockchip,key";
io-channels = <&adc 1>;
vol-up-key {
linux,code = <115>;
label = "volume up";
rockchip,adc_value = <523>;
};
vol-down-key {
linux,code = <114>;
label = "volume down";
rockchip,adc_value = <255>;
};
power-key {
gpios = <&gpio0 GPIO_A5 GPIO_ACTIVE_LOW>;
linux,code = <116>;
label = "power";
gpio-key,wakeup;
};
up-key {
linux,code = <57>;
label = "menu";
rockchip,adc_value = <446>;
};
down-key {
linux,code = <58>;
label = "menu";
rockchip,adc_value = <640>;
};
};
io-channels = <&adc 1>;
通道是1 ADCIN1 如果你想接到ADCIN0 就改成0
linux,code = <115>;
这个就是键,对应是音量的加
rockchip,adc_value = <523>; 这个就是要调试的东西,按键的adc对应值。
这个数值是怎么知道的呢?
可以自己打印出来,前面不是有一个 result = rk_key_adc_iio_read(ddata); 的地方吗? 直接在这个后面加一个printk
每按下一个按键看看打印出来的ADC转换值是多少,然后在dts上面就设置多少。
回到这个for循环
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 + DRIFT_ADVALUE &&
result > button->adc_value - DRIFT_ADVALUE)
button->adc_state = 1;
else
button->adc_state = 0;
if(button->state != button->adc_state)
mod_timer(&button->timer,
jiffies + msecs_to_jiffies(DEFAULT_DEBOUNCE_INTERVAL));
}
C
这个函数意思就是会去将读取出来的值 和 设置的判断值做比较 ,
比如 我按下音量时是 253 dts上面设置的值是300
vol-up-key {
linux,code = <115>;
label = "volume up";
rockchip,adc_value = <300>;
};
if(253 < 300 + DRIFT_ADVALUE &&
253 > 300 - DRIFT_ADVALUE)
DRIFT_ADVALUE 这个东西就是波动值 rk设置为 70 要看你的按键有多少个 或者 adc的灵敏度 随便设置。在文件开头有定义
if(button->state != button->adc_state)
mod_timer(&button->timer,
jiffies + msecs_to_jiffies(DEFAULT_DEBOUNCE_INTERVAL));
如果值对应的话,哦,有按键按下了, 那么就会开启定时器的下半部,
mod_timer(&button->timer,
jiffies + msecs_to_jiffies(DEFAULT_DEBOUNCE_INTERVAL));
static void keys_timer(unsigned long _data)
{
struct rk_keys_drvdata *pdata = rk_key_get_drvdata();
struct rk_keys_button *button = (struct rk_keys_button *)_data;
struct input_dev *input = pdata->input;
int state;
if(button->type == TYPE_GPIO)
state = !!((gpio_get_value(button->gpio) ? 1 : 0) ^ button->active_low);
else
state = !!button->adc_state;
if(button->state != state) {
button->state = state;
input_event(input, EV_KEY, button->code, button->state);
key_dbg(pdata, "%skey[%s]: report event[%d] state[%d]\n",
(button->type == TYPE_ADC)?"adc":"gpio", button->desc, button->code, button->state);
input_event(input, EV_KEY, button->code, button->state);
input_sync(input);
}
if(state)
mod_timer(&button->timer,
jiffies + msecs_to_jiffies(DEFAULT_DEBOUNCE_INTERVAL));
}
DEFAULT_DEBOUNCE_INTERVAL 为10 10毫秒
keys_timer就是最后实现接口调用的函数,
那个state 就是一个套子,意思进去说按下去的时候,我会取反,弹起来的时候也会取反 ,这样 按下去会触发一次 mod_timer 弹起来也会触发一次
当你连续按着的时候,就会不断的发送,直到套子弹起来,就这样,还有这个套子有一个bug,就是当ADC读到0的时候弹不起来,我已经解决了,哎哟我去,好几把烦,不想说了。
原作者:rom酱