Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: обработка повторных нажатий клавиатуры
Форум разработчиков электроники ELECTRONIX.ru > Сайт и форум > В помощь начинающему > Программирование
GoodNews
Драйвер клавиатуры построен на базе атмеловского апноута AVR245. Есть одно но - повторные нажатия клавиш не могу реализовать. Т.е. теоретически представляю как это должно выглядеть, но на практике не получается. Я думал вначале использовать сравнение состояний порта клавиатуры. Т.е. при инициализации драйвера берём объявляем некоторую статическую переменную, в которую загружаем значение порта (все клавиши отпущены), затем в функции обработки клавиш считываем текущее состояние порта и сравниваем со значением по-умолчанию. Если не совпадает, то выводим значение, если совпадает - пропускаем. Также по идее должно проверяться была ли физически отпущена клавиша. Посоветуйте плиз как можно выйти из данного положения.
Алекс1981
Насколько я понимаю: требуется отсеять повторные нажатия клавиш? Если да, то есть вариант такой: после обработчика нажатия клавиши ставить задержку 100-200 мс, тем самым не нужно будет думать об отпускании клавиш. Если вариантов нажатий клавиш немного - можно ввести переменную состояния девайса и менять ее при нажатии определенной клавиши, если много, то запоминать нажатие последней клавиши и действительно как вы указали - проверять на соответствие и отсеивать
GoodNews
Цитата(Алекс1981 @ Feb 21 2010, 20:55) *
после обработчика нажатия клавиши ставить задержку 100-200 мс, тем самым не нужно будет думать об отпускании клавиш

Хочется без лишних задержек, так как кроме этого ещё реализовано динамическое обновление дисплея, так что эта задержка не будет удачным решением. В драйвере используются флаги для проверки состояний клавиш, однако физически они не проверяются на момент фактического "поднятия" клавиши, только програмно, т.е. выполнил кусок кода, выставил флаг в 1 или 0.
sigmaN
Сам я не видел никогда апноута AVR245, так что мой совет может оказаться совсем не в тему.
Как насчёт сделать так, чтобы нажатия и отпускания клавиш приводили к возникновению прерывания?
Ежели это будет весьма накладно и неудобно - нужно сделать чтобы клавиатура опрашивалась по таймеру(т.е. опять таки прерывание).
Сохраняя каждый раз last state и сравнивая его с только что считанным значением можно всегда понять было ли что-то отпущено или нажато....

Вопрос задан как-то не совсем ясно...приходится заниматься телепатией sad.gif
Цитата
Есть одно но - повторные нажатия клавиш не могу реализовать.
это что значит? Как в компе, нажал кнопку и держишь, сначала печатеается одна буква, потом некоторая задержка, а потом буковка начинает повторятся, пока кнопочку не отпустишь? Это имеется ввиду?

Или же нужно что-то типа подавления дребезга контактов?
Нужно чтобы топикстартер всё это прояснил.

Клавиатура с точки зрения железа как построена?

Лень мне искать аппноут smile.gif
rezident
Цитата(GoodNews @ Feb 21 2010, 23:50) *
Я думал вначале использовать сравнение состояний порта клавиатуры. Т.е. при инициализации драйвера берём объявляем некоторую статическую переменную,
Все правильно вы мыслите. Только статических переменных нужно две. В одной переменной хранится предыдущее состояние матрицы кнопок, а в другой - устойчивое (после устранения дребезга) состояние матрицы кнопок. Для формирования кодов повтора используйте вторую переменную. XORите новое устойчивое состояние и старое. Если результат XORа равен нулю, но состояние отличается от того, где ни одна кнопка не нажата, то используете ветвление программы для формирования автоповтора. Автоповтор требует отсчета двух временных интервалов. Первый интервал задает задержку начала автоповтора. Второй интервал формирует период автоповтора. Все просто как суп с лапшой smile.gif
GoodNews
Цитата(sigmaN @ Feb 21 2010, 21:57) *
Клавиатура с точки зрения железа как построена?

Лень мне искать аппноут smile.gif

AVR245: Code Lock with 4x4 Keypad and I2C™ LCD
Сорри. Неправильно написал. Имел ввиду, что не могу понять, как реализовать обработку повторных нажатий (чтобы их избежать).
Вот как код выглядит.
kbd.c
CODE
#include <avr/io.h>
#include <avr/interrupt.h>
#include "kbd.h"

unsigned char KBD_KeyPressed;
unsigned char KBD_LastKey;
unsigned char KBD_ScanRow;
unsigned char KBD_KeyDown;
//Потом убрать
unsigned char KBD_LastRow;
volatile unsigned char column;

//Обработка переполнения Timer1
ISR(TIMER1_OVF_vect)
{
if (!KBD_KeyDown)
{
KBD_PORT |= (0x10<<KBD_ScanRow);
if (++KBD_ScanRow > 3)
KBD_ScanRow = 0;
KBD_PORT &= ~(0x10<<KBD_ScanRow);
asm("nop");
column = (~KBD_PIN) & 0x0f;
}
}

//Считываем значение Timer1
uint16_t read_TCNT1(void)
{
uint16_t i;
unsigned char sreg;

sreg = SREG; //запоминаем состояние бита I (остальные биты нам в общем то побоку)
asm("cli"); //убедимся что флаг I сброшен
i = TCNT1; //читаем TCNT1
SREG = sreg; //востанавливаем состояние бита I
//(если он был сброшен до входа в процедуру то эта операция не даст никакого эффекта)
return i;
}

unsigned char KBD_GetKey(void)
{
unsigned char sreg;
sreg = SREG;
asm("cli");
switch (column)
{
case 0x00:
KBD_KeyDown = 0;
break;
case 0x01:
KBD_LastKey = KBD_Array[KBD_ScanRow][0];
KBD_LastRow = KBD_ScanRow;
if (!KBD_KeyDown)
KBD_KeyPressed = 1;
KBD_KeyDown = 1;
break;
case 0x02:
KBD_LastKey = KBD_Array[KBD_ScanRow][1];
KBD_LastRow = KBD_ScanRow;
if (!KBD_KeyDown)
KBD_KeyPressed = 1;
KBD_KeyDown = 1;
break;
case 0x04:
KBD_LastKey = KBD_Array[KBD_ScanRow][2];
KBD_LastRow = KBD_ScanRow;
if (!KBD_KeyDown)
KBD_KeyPressed = 1;
KBD_KeyDown = 1;
break;
case 0x08:
KBD_LastKey = KBD_Array[KBD_ScanRow][3];
KBD_LastRow = KBD_ScanRow;
if (!KBD_KeyDown)
KBD_KeyPressed = 1;
KBD_KeyDown = 1;
break;
default:
KBD_LastKey = 0;
KBD_KeyPressed = 0;
}

SREG = sreg;
if (KBD_KeyPressed)
{
KBD_KeyDown = 0;
KBD_KeyPressed = 0;
return (KBD_LastKey);
} else return (NO_KEY);
}

//Конфигурирования контроллера для работы с клавиатурой
void KBD_init(void)
{
//Конфигурируем порт клаваиатуры
KBD_REG |= (X1|X2|X3|X4);
KBD_REG &= ~(Y1|Y2|Y3|Y4);

//Конфигурирем выводы клавиатуры
//X1-X4 - выходы, высокий уровень
//Y1-Y4 - входы, высокий уровень, pull-up активен
KBD_PORT |= (X1|X2|X3|X4|Y1|Y2|Y3|Y4);

//Конфигурация таймеров
TCNT0 = 0x00;
TCNT1 = 0x1FF;
TIMSK = (1<<TOIE1); //Включить прерывание по переполнению для Timer1
TCCR1A = 0x00;
TCCR1B = (1<<CS11); //Включаем делитель /8

//Включаем обработку прерываний
asm("sei");
}

kbd.h
CODE
#ifndef _KBD_H
#define _KBD_H
#endif
#include <avr/io.h>

//Столбцы клавиатуры
#define Y1 (1<<0)
#define Y2 (1<<1)
#define Y3 (1<<2)
#define Y4 (1<<3)

//Строки клавиатуры
#define X1 (1<<4)
#define X2 (1<<5)
#define X3 (1<<6)
#define X4 (1<<7)

//Порт клавиатуры
#define KBD_PORT PORTB
#define KBD_PIN PINB

//Конфигурация порта клавиатуры
#define KBD_REG DDRB

void KBD_init(void);
unsigned char KBD_GetKey(void);
uint16_t read_TCNT1(void);

//Если ни одна клавиша не была нажата, то возвращаем значение -1
static uint8_t NO_KEY = -1;

//Массив определяющий значения клавиш
static uint8_t KBD_Array[4][4]=
{
{'1','2','3','A'},
{'4','5','6','B'},
{'7','8','9','C'},
{'*','0','#','D'}
};
extern volatile unsigned char column;
extern unsigned char KBD_LastRow;

Как я уже сказал, идея в том, что надо нажать кнопку, напечатается один символ. Второй символ напечатается после того, как кнопка будет отпущена. А вот уже этим вторым символом может быть эта же кнопка ну и т.д. Т.е. нужно пройти через факт "одно нажатие - один символ".

Цитата(rezident @ Feb 21 2010, 23:08) *
Все правильно вы мыслите.

smile.gif верной дорогой значит! Автоповтор в принципе я не планирую использовать. Клавиатура используется для прямого ввода значений частоты и управления меню (меню пока ещё на стадии планирования, поэтому пока даже и не рассматривается). Надо переварить супчик smile.gif Спасибо за наводку на мысль smile.gif
rezident
Цитата(GoodNews @ Feb 22 2010, 02:40) *
Т.е. нужно пройти через факт "одно нажатие - один символ".
Дык стандартный путь примените. Реализуйте клавиатуру так, как все делают. Ваш UI (User Interface - пользовательский интерфейс) должен работать не с состоянием матрицы кнопок напрямую, а с буфером клавиатуры. В буфер клавиатуры должны помещаться скан-коды клавиш по факту нажатия или отпускания или автоповтора. Реализуйте три функции: а) putkey, б) checkkey, в) getkey с помощью которых вы будете работать с буфером клавиатуры.
putkey - сканирует матрицу клавиатуры; устраняет дребезг; определяет соответствие нажатой (и/или отжатой) кнопки и скан-кода; помещает скан-код в буфер клавиатуры, увеличивая счетчик буфера; формирует автоповтор последней нажатой клавиши
getkey - извлекает текущий скан-код из буфера клавиатуры, уменьшая счетчик буфера
checkkey - извлекает текущий скан-код из буфера клавиатуры, но без изменения счетчика буфера.
getkey и checkkey возращают скан-код 'NUL', если буфер клавиатуры пуст.
DpInRock
Алгоритм устранения дребезга выглядит так.
Язык описания - около Си.

CODE
unsigned char time;
void Some_timer_interrupt(void) // every 1 ms
{
time++;
}

unsigned char KEY_EVENT=0;

unsigned char key_state=0,Prev_key;
void Keyboard_task(void)
{
unsigned char key;
switch (key_state)
{
//state 0 -- wait for key press
case 0:
if (PIN()==xFF) break;
key_state=1; Prev_key=PIN();time=0;
break;
//state 1 - Убираем дребезг
case 1:
key=PIN();if (key<>Prev_key) {time=0;Prev_key=key;break;}//значение клавиатуры изменилось....
if (time>40) {if (key==0xFF) key_state=0; // ни фига - помеха проскочила....
else
{KEY_EVENT=key|0x80;key_state=2;} // ага, 40 миллисекунд клава стоит как вкопанная
};
break;
//ждем отжатия
case 2:
if (PIN()!=0xFF) {time=0;break;}// клава все еще не отжата
if (time>40) {key_state=0;KEY_EVENT=0x40};//40 миллисекунд как стабильно отжата. Можем послать сообщение //
//отжатии, а можем и не посылать. Обычно этого не требуется.
break;

}

}


Ну эта процедура вызывается время от времени. В фоновом цикле. Времени она не отъедает нисколько.
GoodNews
Как переварю, реализую и отпишусь. Всем большое спасибо за ответы! С 23-м Февраля всех!
GoodNews
2DpInRock:
Как я понял, KEY_EVENT - что-то типа флага статуса и он в общем-то не обязателен. Что-то вроде feedback'а.
GoodNews
Итак, вот что получилось.
kbd.c
CODE

#include <avr/io.h>
#include <avr/interrupt.h>
#include "kbd.h"
#define READ_KEY_COL() ((~KBD_PIN) & 0x0f)
enum {
STATE_WAIT,
STATE_DEBOUNCE,
STATE_WAITKEYUP
};
static uint8_t KBD_PrevKey;
static uint8_t KBD_LastKey;
uint8_t KBD_ScanRow;
uint8_t KBD_KeyDown = 0;
uint8_t KEY_STATE = STATE_WAIT;
uint8_t COLUMN_DEFAULT_STATE = 0x00;
volatile uint16_t time = 0;
volatile unsigned char column;

volatile uint8_t isKeyInBuffer = 0;
volatile uint8_t columnInBuffer;
volatile uint8_t rowInBuffer;

//Массив определяющий значения клавиш
static uint8_t KBD_Array[4][4]=
{
{'1','2','3','A'},
{'4','5','6','B'},
{'7','8','9','C'},
{'*','0','#','D'}
};

void KBD_TASK(void)
{
switch (KEY_STATE)
{
//case 0 - ожидание нажатия клавиши
case STATE_WAIT:
if (READ_KEY_COL() == COLUMN_DEFAULT_STATE)
{
KBD_KeyDown = 0;
break;
}
KBD_PrevKey = READ_KEY_COL();
time = 0;
KEY_STATE = STATE_DEBOUNCE;
break;
//Убираем дребезг
case STATE_DEBOUNCE:
KBD_LastKey = READ_KEY_COL();
if (KBD_LastKey != KBD_PrevKey)
{
time = 0;
KBD_KeyDown = 0;
KEY_STATE = STATE_WAIT;
break;
}
if (time > 40)
{
columnInBuffer = KBD_LastKey;
rowInBuffer = KBD_ScanRow;
isKeyInBuffer = 1;
KEY_STATE = STATE_WAITKEYUP; //Всё-таки нажатие не случайное
}
break;
//Ожидание отжатия клавиши
case STATE_WAITKEYUP:
if (READ_KEY_COL() != COLUMN_DEFAULT_STATE)
{
time = 0;
break; //Клавишу не отпустили
}
if (time > 1024)
{
KEY_STATE = STATE_WAIT; //Клавишу отпустили
KBD_KeyDown = 0;
}
}
}
unsigned char KBD_GetKey(void)
{
unsigned char KBD_PressedKey = NO_KEY;
if (isKeyInBuffer)
{
switch (columnInBuffer)
{
case 0x00:
KBD_PressedKey = NO_KEY;
break;
case 0x01:
KBD_PressedKey = KBD_Array[rowInBuffer][0];
break;
case 0x02:
KBD_PressedKey = KBD_Array[rowInBuffer][1];
break;
case 0x04:
KBD_PressedKey = KBD_Array[rowInBuffer][2];
break;
case 0x08:
KBD_PressedKey = KBD_Array[rowInBuffer][3];
break;
default:
KBD_PressedKey = NO_KEY;
}
isKeyInBuffer = 0;
}
return (KBD_PressedKey);
}

//Обработка переполнения Timer1
ISR(TIMER1_OVF_vect)
{
TCNT1 = TMR1_CNT;
time++;

if (!KBD_KeyDown)
{
KBD_PORT |= (0x10<<KBD_ScanRow);
if (++KBD_ScanRow > 3)
KBD_ScanRow = 0;
KBD_PORT &= ~(0x10<<KBD_ScanRow);
asm("nop");
column = READ_KEY_COL();
if (COLUMN_DEFAULT_STATE != READ_KEY_COL())
KBD_KeyDown = 1;
} else
KBD_TASK();
}

//Конфигурирования контроллера для работы с клавиатурой
void KBD_init(void)
{
//Конфигурируем порт клаваиатуры
KBD_REG |= (X1|X2|X3|X4);
KBD_REG &= ~(Y1|Y2|Y3|Y4);

//Конфигурирем выводы клавиатуры
//X1-X4 - выходы, высокий уровень
//Y1-Y4 - входы, высокий уровень, pull-up активен
KBD_PORT |= (X1|X2|X3|X4|Y1|Y2|Y3|Y4);

//Конфигурация таймеров
TCNT0 = 0x00;
TCNT1 = TMR1_CNT;
TIMSK = (1<<TOIE1); //Включить прерывание по переполнению для Timer1
TCCR1A = 0x00;
TCCR1B = (1<<CS12)|(1<<CS10); //Включаем делитель /1024

//Включаем обработку прерываний
asm("sei");
}

kbd.h
CODE

#ifndef _KBD_H
#define _KBD_H
#endif
#include

//Столбцы клавиатуры
#define Y1 (1<<0)
#define Y2 (1<<1)
#define Y3 (1<<2)
#define Y4 (1<<3)

//Строки клавиатуры
#define X1 (1<<4)
#define X2 (1<<5)
#define X3 (1<<6)
#define X4 (1<<7)

//Порт клавиатуры
#define KBD_PORT PORTB
#define KBD_PIN PINB

//Конфигурация порта клавиатуры
#define KBD_REG DDRB

//Если ни одна клавиша не была нажата, то возвращаем значение 0
#define NO_KEY 0

//Состояние выходов порта клавиатуры при отжатых клавишах
#define KBD_DEFAULT_STATE 0xff

//Timer1 стартует с позиции 0xffff (MAX)
#define TMR1_CNT 0xffff

void KBD_init(void);
unsigned char KBD_GetKey(void);
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.