ad-矩阵-io口多按键驱动
按键实现思路
多按键驱动的思路和之前的io单按键,思路类似单按键是判断按键是按下,多按键判断是否有按键按下,按下的是哪个按键号,下面以最近工作用使用的ad按键为例
整个驱动一共4个c文件(键值、扫描、任务、驱动),6个h文件
核心思路是定时器中每10ms扫描一次按键,将按键值传入扫描函数,获得有效按键的按键号和按键状态,抄表将按键号按键状态两个变量转换成一个变量,将这个有效值传入队列,在主函数中出队列,将按键有效值作为参数哦传递给函数指针数组调用对应的任务函数,整个驱动没有使用一个与ic芯片有关的头文件,函数等,十分方便移植(整个驱动的思路来源于之前的几个项目,抄表是杰里的按键驱动,按键扫描代码是自己撸的,队列做数据透传的时候写的),可能程序的效率和代码量不够理想,但整体功能能比较好实现,最后面将会把10个文件全部贴出,附带完整工程下载
驱动分析:
一、有效键值
主要文件key_ad.c .h
h文件将每个按键对应电压的ad转换值算出来,选用两个键值的中值做作为有效键值判断
c文件,将h文件中的键值作为数组的元素,将获取的按键值和表对比获取获得按键号
#define R_UP 220L //22K
/* 不同电压对应的ad值 12bit adc */
#define ADC_33 (4096) //VCC
#define ADC_30 (3680) //220K
#define ADC_27 (3357) //100K
#define ADC_23 (2860) //51K
#define ADC_20 (2450) //33K
#define ADC_17 (2120) //24K
#define ADC_13 (1610) //15K
#define ADC_10 (1210) //9.1K
#define ADC_07 (915) //6.2K
#define ADC_03 (490) //3K
#define ADC_00 (0)
/* key voltage threshold */
#define AD_NOKEY ((ADC_33 + ADC_30)/2)
#define AD_KEY_0 ((ADC_30 + ADC_27)/2)
#define AD_KEY_1 ((ADC_27 + ADC_23)/2)
#define AD_KEY_2 ((ADC_23 + ADC_20)/2)
#define AD_KEY_3 ((ADC_20 + ADC_17)/2)
#define AD_KEY_4 ((ADC_17 + ADC_13)/2)
#define AD_KEY_5 ((ADC_13 + ADC_10)/2)
#define AD_KEY_6 ((ADC_10 + ADC_07)/2)
#define AD_KEY_7 ((ADC_07 + ADC_03)/2)
#define AD_KEY_8 ((ADC_03 + ADC_00)/2)
void get_key_adc_address(u16* _address);
KEY_NUMBER_U get_current_key_number(void);
/
/* c文件 */
#define KEY_SIZE (KEY_NUMBER - 1)
const static u16 ad_key_table[KEY_SIZE] = {
AD_KEY_0, AD_KEY_1, AD_KEY_2,
AD_KEY_3, AD_KEY_4, AD_KEY_5,
AD_KEY_6, AD_KEY_7, AD_KEY_8,
};
static u16* s_ad_key_value;
/* 初始化时将按键的ad值地址传进来,用指针变量存起来,就可以不用包含ad值的头文件了,方便移植 */
void get_key_adc_address(u16* _address)
{
s_ad_key_value = _address;
}
KEY_NUMBER_U get_current_key_number(void)
{
u8 l_number = KEY_NONE;
if (*s_ad_key_value > AD_NOKEY){
return KEY_NONE;
}
for (l_number = 0; l_number < KEY_SIZE; l_number++){
if (*s_ad_key_value > ad_key_table[l_number]){
break;
}
}
return (KEY_NUMBER_U)(KEY_SIZE - l_number);
}
二、键值扫描
这部分较为简单,主要文件key_scan.c .h 。将上一步扫描的键值传入key_scan中,基本是在io单按键的基础稍作修改的
void key_scan(KEY_SCAN_S *_key)
{
_key->value = NO_KEY;
_key->type = KEY_NO_PRESS;
if (_key->cur_key == NO_KEY){
switch (_key->status){
case KEY_NO_PRESS:
break;
case KEY_DOWN:
break;
case KEY_SHORT:
_key->type = KEY_SHORT;
break;
case KEY_LONG:
break;
case KEY_HOLD:
_key->type = KEY_HOLD_UP;
break;
default:
break;
}
_key->cnt = 0;
_key->status = KEY_NO_PRESS;
}else{
/* 上次的按值有效,但和这次不同 */
if ((NO_KEY != _key->last_key)&&(_key->last_key != _key->cur_key)){
/* 这次按键比上次小,认为是一个误触按键*/
if (_key->cur_key <= _key->last_key){
_key->last_key = _key->cur_key;
_key->cnt = 0;
_key->status = KEY_NO_PRESS;
return; //异常退出
}
/* 实际测试的时候抬手的一瞬间内,按键抖动,导致最后一次传入一个比当前键值大的键值
如果认为是异常则按键扫描识别率低,所以这次按键比上次大,认为是一个松手按键 */
if (KEY_SHORT == _key->status){
_key->type = KEY_SHORT;
}else if (KEY_HOLD == _key->status){
_key->type = KEY_HOLD_UP;
}
_key->cnt = 0;
_key->status = KEY_NO_PRESS;
}
_key->cnt++;
switch (_key->status){
case KEY_NO_PRESS:
_key->status = KEY_DOWN;
break;
case KEY_DOWN:
if (_key->cnt >= KEY_BASE_CNT) {
//长按
_key->status = KEY_SHORT;
}
break;
case KEY_SHORT:
if (_key->cnt >= KEY_LONG_CNT) {
//长按
_key->status = KEY_LONG;
_key->type = KEY_LONG;
}
break;
case KEY_LONG:
if (_key->cnt >= (KEY_LONG_CNT + KEY_HOLD_CNT)) {
//连按
_key->status = KEY_HOLD;
_key->type = KEY_HOLD;
_key->cnt = KEY_LONG_CNT;
}
break;
case KEY_HOLD:
if (_key->cnt >= (KEY_LONG_CNT + KEY_HOLD_CNT)) {
//连按
_key->type = KEY_HOLD;
_key->cnt = KEY_LONG_CNT;
}
break;
default:
break;
}
}
if (KEY_NO_PRESS != _key->type){
_key->number = _key->last_key;
_key->value = ((_key->type << 5)|(_key->number & 0x1f)); //高3位代表按键状态,低5位代表键值
}
_key->last_key = _key->cur_key;
}
三、按键处理
按键处理经过上一步,就已经获得了有效的键值和按键类型,开始按键处理 主要文件 key_device.c .h
按键初始化要传入按键ad值的地址,初始化一个fifo队列,不想用队列也是可以的,但是要扫描完立马进行按键任务处理
/* 为了方便抄表,NO_PRESS不是0,初始化赋值 */
static KEY_SCAN_S s_key = {
KEY_NO_PRESS,0,0,0,0,0,0,0};
static QUEUE_ELEMENT_S s_queue;
/* 传入adc地址,初始化队列 */
static void task_key_init(u16* _key_adc_address)
{
get_key_adc_address(_key_adc_address);
init_queue(&s_queue);
}
/*
按键扫描,把消息传入队列
*/
static void task_key_scan(void)
{
u8 l_msg = NO_MSG;
s_key.cur_key = get_current_key_number(); /* 获得按键值 */
key_scan(&s_key); /* 传入扫描,获取按键结果 */
if(KEY_NONE != s_key.value){
/* 将按键结果传入队列 */
/* 抄表获取对应的按键变量 */
l_msg = key_msg_buff[s_key