Используем клавиатуру от BlackBerry Q10 в своих проектах

1 октября 2018

Однажды, во время утреннего полистывания Twitter, мое внимание привлек проект господина @arturo182 (a.k.a Artur Pacholec). Проект представляет собой DIY смартфон на базе экранчика ILI9341 и qwerty-клавиатуры с подсветкой от BlackBerry Q10. Особенно меня впечатлила идея использования клавиатуры от смартфона. Было решено незамедлительно обзавестись такой клавиатурой и научиться с ней работать.

Вот как она выглядит:

Qwerty-клавиатура от BlackBerry Q10

Клавиатуру можно приобрести на eBay менее чем за 3$. Подключение к ней осуществляется через маленький коннектор с замысловатым названием BM14B(0.8)-24DS-0.4V(53). Быстрее и дешевле всего оказалось купить такие коннекторы в Чип-и-Дипе. Нам также понадобится переходник с этого разъема на обычные пины с шагом 2.54 мм. К счастью, @arturo182 заботливо выложил такой переходник на GitHub’е. Платы были заказаны на OSH Park.

Припаять коннектор не особо сложно, главное иметь под рукой хороший флюс и лупу с сильным увеличением. Лично я сначала залудил пады на плате, припаял коннектор за два пина, расположенных по диагонали, а затем, придавливая его к плате пинцетом, припаял все остальные пины. Если визуально плата в порядке, отмываем в УЗ-ванне (при таком расстоянии между пинами зубная щетка плохо справляется) и перепроверяем мультиметром.

Используя получившийся переходник, я подключил клавиатуру к отладочной плате LimeSTM32:

Подключение qwerty-клавиатуры к отладочной плате

Для питания клавиатуре нужно 3.3 В. Подсветка не такая яркая, чтобы ее было хорошо видно на фото. Однако в темноте свою функцию она выполняет прекрасно. Кнопки соединены по матричной схеме, 7 строк на 5 столбцов. Подавая высокое напряжение на один столбец и считывая напряжение со строк, мы можем определить, какая кнопка сейчас нажата.

Пример кода, использующего таймер и прерывания:

void keyboard_prepare() {
    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-клавиатуру.

Метки: , .


Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.