Используем клавиатуру от BlackBerry Q10 в своих проектах
1 октября 2018
Однажды, во время утреннего полистывания Twitter, мое внимание привлек проект господина @arturo182 (a.k.a Artur Pacholec). Проект представляет собой DIY смартфон на базе экранчика ILI9341 и qwerty-клавиатуры с подсветкой от BlackBerry Q10. Особенно меня впечатлила идея использования клавиатуры от смартфона. Было решено незамедлительно обзавестись такой клавиатурой и научиться с ней работать.
Вот как она выглядит:
Клавиатуру можно приобрести на eBay менее чем за 3$. Подключение к ней осуществляется через маленький коннектор с замысловатым названием BM14B(0.8)-24DS-0.4V(53). Быстрее и дешевле всего оказалось купить такие коннекторы в Чип-и-Дипе. Нам также понадобится переходник с этого разъема на обычные пины с шагом 2.54 мм. К счастью, @arturo182 заботливо выложил такой переходник на GitHub’е. Платы были заказаны на OSH Park.
Припаять коннектор не особо сложно, главное иметь под рукой хороший флюс и лупу с сильным увеличением. Лично я сначала залудил пады на плате, припаял коннектор за два пина, расположенных по диагонали, а затем, придавливая его к плате пинцетом, припаял все остальные пины. Если визуально плата в порядке, отмываем в УЗ-ванне (при таком расстоянии между пинами зубная щетка плохо справляется) и перепроверяем мультиметром.
Используя получившийся переходник, я подключил клавиатуру к отладочной плате LimeSTM32:
Для питания клавиатуре нужно 3.3 В. Подсветка не такая яркая, чтобы ее было хорошо видно на фото. Однако в темноте свою функцию она выполняет прекрасно. Кнопки соединены по матричной схеме, 7 строк на 5 столбцов. Подавая высокое напряжение на один столбец и считывая напряжение со строк, мы можем определить, какая кнопка сейчас нажата.
Пример кода, использующего таймер и прерывания:
HAL_GPIO_WritePin(Col1_GPIO_Port, Col1_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(Col2_GPIO_Port, Col2_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(Col3_GPIO_Port, Col3_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(Col4_GPIO_Port, Col4_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(Col5_GPIO_Port, Col5_Pin, GPIO_PIN_RESET);
}
char* buttons[][5] = {
/* ROW 1 */ {"q", "e", "r", "u", "o"},
/* ROW 2 */ {"w", "s", "g", "h", "l"},
/* ROW 3 */ {"SYM", "d", "t", "y", "i"},
/* ROW 4 */ {"a", "p", "RSHIFT", "ENTER", "DEL"},
/* ROW 5 */ {"ALT", "x", "v", "b", "$"},
/* ROW 6 */ {" ", "z", "c", "n", "m"},
/* ROW 7 */ {"MIC", "LSHIFT", "f", "j", "k"},
};
char* buttons_alt[][5] = {
/* ROW 1 */ {"#", "2", "3", "_", "+"},
/* ROW 2 */ {"1", "4", "/", ":", "\""},
/* ROW 3 */ {NULL, "5", "(", ")", "-"},
/* ROW 4 */ {"*", "@", NULL, NULL, NULL},
/* ROW 5 */ {NULL, "8", "?", "!", NULL},
/* ROW 6 */ {NULL, "7", "9", ",", "."},
/* ROW 7 */ {"0", NULL, "6", ";", "'"},
};
// button should be released before
// pressing next button
bool btn_released = true;
bool shift_pressed = false;
bool alt_pressed = false;
uint32_t line_length = 0;
void button_pressed(uint8_t row, uint8_t col) {
char buff[16];
if(alt_pressed && (buttons_alt[row-1][col-1] != NULL)) {
strncpy(buff, buttons_alt[row-1][col-1], sizeof(buff));
} else {
strncpy(buff, buttons[row-1][col-1], sizeof(buff));
}
if((strcmp(buff, "LSHIFT") == 0) ||
(strcmp(buff, "RSHIFT") == 0)) {
shift_pressed = !shift_pressed;
alt_pressed = false;
} else if(strcmp(buff, "DEL") == 0) {
if(line_length > 0) {
UART_Printf("\x08 \x08");
line_length--;
shift_pressed = false;
alt_pressed = false;
}
} else if(strcmp(buff, "ALT") == 0) {
alt_pressed = true;
shift_pressed = false;
// ULED will blink once. Since shift_pressed is false it will
// be turned off shortly.
HAL_GPIO_WritePin(ULED_GPIO_Port, ULED_Pin, GPIO_PIN_SET);
} else if((strcmp(buff, "MIC") == 0) ||
(strcmp(buff, "SYM") == 0)) {
// ignore
} else if(strcmp(buff, "ENTER") == 0) {
UART_Printf("\r\n");
line_length = 0;
shift_pressed = false;
alt_pressed = false;
} else { // regular button
if(shift_pressed && isalpha(buff[0])) {
buff[0] = (char)toupper(buff[0]);
}
UART_Printf("%s", buff);
line_length++;
shift_pressed = false;
alt_pressed = false;
}
}
void set_current_column(uint8_t column) {
switch(column) {
case 1:
HAL_GPIO_WritePin(Col5_GPIO_Port, Col5_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(Col1_GPIO_Port, Col1_Pin, GPIO_PIN_SET);
break;
case 2:
HAL_GPIO_WritePin(Col1_GPIO_Port, Col1_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(Col2_GPIO_Port, Col2_Pin, GPIO_PIN_SET);
break;
case 3:
HAL_GPIO_WritePin(Col2_GPIO_Port, Col2_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(Col3_GPIO_Port, Col3_Pin, GPIO_PIN_SET);
break;
case 4:
HAL_GPIO_WritePin(Col3_GPIO_Port, Col3_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(Col4_GPIO_Port, Col4_Pin, GPIO_PIN_SET);
break;
default:
HAL_GPIO_WritePin(Col4_GPIO_Port, Col4_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(Col5_GPIO_Port, Col5_Pin, GPIO_PIN_SET);
break;
}
}
uint8_t current_column = 1;
bool some_button_pressed_during_scan = false;
bool button_was_released = true;
// called from HAL_TIM_PeriodElapsedCallback
void change_column() {
current_column++;
if(current_column > 5) {
button_was_released = !some_button_pressed_during_scan;
current_column = 1;
some_button_pressed_during_scan = false;
}
set_current_column(current_column);
}
// called from HAL_GPIO_EXTI_Callback
void row_selected(uint16_t pin) {
if(some_button_pressed_during_scan) {
// process only one button per complete scan
return;
}
some_button_pressed_during_scan = true;
if(!button_was_released) {
// if user holds a button don't process it multiple times
return;
}
if(pin == GPIO_PIN_0) {
button_pressed(1, current_column);
} else if(pin == GPIO_PIN_1) {
button_pressed(2, current_column);
} else if(pin == GPIO_PIN_2) {
button_pressed(3, current_column);
} else if(pin == GPIO_PIN_3) {
button_pressed(4, current_column);
} else if(pin == GPIO_PIN_4) {
button_pressed(5, current_column);
} else if(pin == GPIO_PIN_5) {
button_pressed(6, current_column);
} else if(pin == GPIO_PIN_6) {
button_pressed(7, current_column);
}
}
void init() {
keyboard_prepare();
HAL_TIM_Base_Start_IT(&htim3);
UART_Printf("Ready!\r\n");
HAL_Delay(1);
}
void loop() {
HAL_GPIO_WritePin(ULED_GPIO_Port, ULED_Pin,
shift_pressed ? GPIO_PIN_SET : GPIO_PIN_RESET);
HAL_Delay(100);
}
Прошу принять во внимание, что здесь в обработчике прерывания используется блокирующая передача по UART. В боевом коде вы этого, вероятно, не хотите. Также в полной версии исходников к этому посту вы найдете пример кода без прерываний, который просто крутится в цикле.
Ну вот, теперь берем ILI9341, добавляем слот для SD-карты, GSM-модуль, микрофон, динамик, и получаем DIY мобильный телефон! А какие потрясающие применения данной клавиатуре приходят вам на ум?
Дополнение: В продолжение темы — Как я паял маленькую qwerty-клавиатуру.
Метки: STM32, Электроника.
Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.