KursyPoradnikiInspirujące DIYForum

Klawiatura skrótów z Arduino, która ułatwia codzienną pracę

Klawiatura skrótów z Arduino, która ułatwia codzienną pracę

Czy pamiętacie niedawny artykuł o własnej klawiaturze rozwiązującej problem nieporęcznych skrótów klawiszowych w programie Eagle? Dzięki niej do jednego przycisku można było przypisać nawet bardzo długą sekwencję kliknięć.

W tym artykule przedstawiam moją klawiaturę, która ułatwia pracę z różnymi programami komputerowymi!

Jakiś czas temu pisałem o ciekawym projekcie klawiatury do programu Eagle, która ułatwiała używanie nieporęcznych skrótów klawiszowych. Projekt bardzo mi się spodobał, gdyż program Corel Draw X5, którego codziennie używam do tworzenia ilustracji, posiada wiele narzędzi z przypisanymi iście "pianistycznymi" skrótami klawiszowymi, np. Shift+Alt+P. Po chwili namysłu zdecydowałem się na wykonanie swojej wersji klawiatury!

Na początek zakupiłem niezbędne elementy:

  • klawiaturę numeryczną posiadającą 18 przycisków,
  • płytkę Arduino Pro Micro z interfejsem USB 2.0 HID.

Zdecydowałem się na taką klawiaturę, gdyż posiada wygodne przyciski oraz gotową matrycę połączeń, więc w teorii wystarczy podłączyć się do gotowego złącza z sygnałami od klawiszy.

Matrycowanie przycisków jest wygodnym rozwiązaniem, ponieważ zmniejsza liczbę potrzebnych połączeń. Dla przykładu klawiatura 16 przyciskowa (4x4) wymaga jedynie 8 przewodów, zaś 20 przyciskowa (4x5) wymaga 9 przewodów. Tak jest w teorii...

W praktyce okazało się, że złącze zakupionej klawiatury posiada 12 wyprowadzeń...

Używając multimetru nastawionego na pomiar najniższego zakresu rezystancji, spisałem wszystkie połączenia pomiędzy stykami gniazda. Następnie zebrane informacje rozrysowałem do postaci matrycy połączeń, gdzie w miejscach węzłów są przyciski.

W tym miejscu wyjaśniła się zagadka dlaczego jest aż 12 linii. Niektóre linie (patrz komórka F-1) odpowiadają za matrycowanie tylko 1 przycisku! Najwyraźniej w procesie technologicznym bardziej opłacalne było dodanie większej liczby linii - jeśli jest jakieś inne "łatwiejsze" wyjaśnienie tej zagadki, to zwyczajnie go wcześniej nie zauważyłem i przyjąłem poniższy schemat połączeń.

Matryca połączeń przycisków i opis złącza.

Matryca połączeń przycisków i opis złącza.

Na podstawie schematu połączeń w matrycy podzieliłem sygnały na te które mają być wejściami i te które będą wyjściami. Następnie przylutowałem odpowiednie przewody poprzez rezystory ograniczające prąd do Arduino.

Obudowa z drukarki 3D

Następnie zabrałem się za obudowę. Do schowania sterownika konieczna była dodatkowa podstawka. Model stworzyłem w  Autodesk Fusion 360 i wydrukowałem na drukarce 3D.

Model dostawki do klawiatury z gniazdem na Arduino.

Model dostawki do klawiatury z gniazdem na Arduino.

Dostawka posiada gniazdo na Arduino Pro Micro w miejscu dobrym do podłączenia kabla USB. Dzięki temu nie ma potrzeby montowania w obudowie dodatkowego złącza USB.

Klawiatura jest zaokrąglona i ciężko było ją zwymiarować. Mimo to, podstawka pasuje dość dobrze zarówno kształtem jak i kolorem.

Ostatnim etapem było wykonanie ikonek klawiszy (oczywiście w Corelu używając nowiutkiej klawiatury). Wydruk zalaminowałem i przykleiłem taśmą dwustronną do przycisków.

Nie będę tłumaczył, co robią poszczególne ikonki, bo każdy i tak może dostosować klawiaturę do własnych potrzeb. W moim przypadku przyspiesza ona wykonywanie wielu operacji w Corelu!

Oczywiście klawiatura może również generować dłuższe ciągi znaków:

Program w Arduino - dla dociekliwych

W programie fizyczne połączenia tworzone są stosując funkcję setSymbol, która za argumenty przyjmuje numer linii wejściowej, numer linii wyjściowej oraz unikatowy symbol. Funkcja posługuje się przy tym wcześniej zdefiniowanymi tablicami:

  • OUTPUTS - tablica 6 linii wyjściowych zdefiniowana na stałe,
  • INPUTS - tablica 6 linii wejściowych zdefiniowana na stałe,
  • symbols - tablica 36 możliwych konfiguracji linii (przy czym używamy tylko 18) wiążących ze sobą wejście, wyjście oraz symbol jako przechowywaną wartość.
Funkcja setSymbol
void setSymbol(int lineOutput, int lineInput, char symbol){
  // zmienne odpowiedajace indeksom w tablicy dla fizycznych linii
  // wpisanych przez użytkownika.
  int iRow, iColumn;
  
  // odszukaj indeks wiersza czyli lini wyjsc w macierzy
  for(iRow = 0; iRow < MATRIX_SIZE; ++iRow){
    if(OUTPUTS[iRow] == lineOutput)
      break;
  }
  
  // odszukaj indeks kolumny czyli lini wejsc w maciery
  for(iColumn = 0; iColumn < MATRIX_SIZE; ++iColumn){
    if(INPUTS[iColumn] == lineInput)
      break;
  }

  // zapisz symbol w komorce macierzy 1-wymiarowej
  // przy pomocy 2 współrzędnych
  symbols[iRow * MATRIX_SIZE + iColumn] = symbol;
}

Następnie tak odszukane indeksy w tablicach są zamieniane na indeks w tablicy 36 możliwych symboli. Jak to się dzieje? Numer wiersza iRow wymnożony przez rozmiar macierzy połączeń (MATRIX_SIZE) niejako przenosi indeks pomiędzy wierszami. Zaś dodanie numeru kolumny przesuwa go o zadaną wartość w obrębie kolumn.

Następnie do tak wybranego indeksu wpisywany jest symbol:

Indeksowanie w obrębie tablicy symboli
symbols[iRow * MATRIX_SIZE + iColumn] = symbol;

Zasada działania sterownika matrycy jest prosta i została omówiona w kursie Arduino poziom 2. W danej chwili tylko jedna linia wyjściowa przyjmuje stan niski, a wszystkie linie wejściowe są sprawdzane. Przełączanie pomiędzy wyjściami jest na tyle szybkie, że efekt matrycowania jest niezauważalny.

Sprawdzanie zatrzasków
void loop() {
  for(int iOutput = 0; iOutput < MATRIX_SIZE; ++iOutput){
    // aktywuj sprawdzanie wiersza o indeksie iOutput
    digitalWrite(OUTPUTS[iOutput], LOW);
    for(int iInput = 0; iInput < MATRIX_SIZE; ++iInput){
      // oblicz numer indeksu w macierzy wszystkich przycisków
      index = iOutput * MATRIX_SIZE + iInput;
      
      // jeżeli obecny stan jest LOW (przycisk wciśnięty)
      if((digitalRead(INPUTS[iInput]) == LOW)){
        if(states[index] == KEY_RELESED){
          // to wywołaj kliknięcie
          onPress(index);
        }
        // ustaw flagę żeby zadziałał zatrzask
        states[index] = KEY_PRESSED;
      } else if(states[index] == KEY_PRESSED)
        // zresetuj zatrzask w momencie puszczenia przycisku
        states[index] = KEY_RELESED;
    }
    // opuść linię aby przejść do następnej
    digitalWrite(OUTPUTS[iOutput], HIGH);
  }
}

W programie zatrzask reprezentuje tablica flag o nazwie states. Jeżeli w danym momencie przycisk jest wciśnięty (przyjmuje stan LOW) i wiemy, że ostatni stan był puszczony (KEY_RELEASE) to wywołaj metodę onPress(index), gdzie index odpowiada znakowi w tablicy symbols lub ostatniemu stanowi w tablicy states.

Funkcja onPress
void onPress(int index){
  //załącz diodę umieszczoną na przodzie klawiatury
  digitalWrite(LED_PIN, HIGH);
  // pobierz symbol spod konkretnego indeksu
  switch(symbols[index]){
    //wierz 1
    case KEY_NUM:
      Keyboard.write(KEY_ESC);
      break;
    case KEY_DOT:
      Keyboard.press(KEY_LEFT_CTRL);
      Keyboard.write('u');
      Keyboard.releaseAll();
      break;
  }
  delay(200);
  digitalWrite(LED_PIN, LOW);
}

Metoda onPress łączy w sobie fizyczne kliknięcia klawiatury z komendą wysłaną do podłączonego urządzenia. Stosując blok switch-case sprawdzany jest symbol klikniętego przycisku i wysyłany zostaje zdefiniowany tekst. W powyższym przypadku są to:

  • Wciśnięcie samego klawisza Escape,
  • Wciśnięcie kombinacji Ctrl+U.

Dla zainteresowanych pełny kod programu. Program jest napisany na tyle elastycznie, że można go dostosować do własnych potrzeb:

  1. ustalić rozmiar matrycy MATRIX_SIZE,
  2. zmienić piny reprezentujące połączenia w blokach #define,
  3. zdefiniować własne symbole w metodzie mapKeys,
  4. napisać co klawiatura ma wypisywać w metodzie onPress.
Pełen kod programu
#include "Keyboard.h"

// led
#define LED_PIN A2

// liczby
#define MATRIX_SIZE 6

// stany
#define KEY_RELESED 0
#define KEY_PRESSED 1

// klawisze
#define KEY_NONE 'x'
#define KEY_ENTER 'e'
#define KEY_NUM 'n'
#define KEY_BACK 'B'
#define KEY_0 '0'
#define KEY_1 '1'
#define KEY_2 '2'
#define KEY_3 '3'
#define KEY_4 '4'
#define KEY_5 '5'
#define KEY_6 '6'
#define KEY_7 '7'
#define KEY_8 '8'
#define KEY_9 '9'
#define KEY_DOT '.'
#define KEY_PLUS '+'
#define KEY_MINUS '-'
#define KEY_MUL '*'
#define KEY_DIV '/'

// wyjscia - wiersze
#define OUT_LINE_1 15
#define OUT_LINE_2 14
#define OUT_LINE_6 8
#define OUT_LINE_A 7
#define OUT_LINE_B 6
#define OUT_LINE_C 5
const int OUTPUTS[] = {OUT_LINE_1, OUT_LINE_2, OUT_LINE_6, OUT_LINE_A, OUT_LINE_B, OUT_LINE_C};

// wejscia - kolumny
#define IN_LINE_3 16
#define IN_LINE_4 10
#define IN_LINE_5 9
#define IN_LINE_D 4
#define IN_LINE_E 3
#define IN_LINE_F 2
const int INPUTS[] = {IN_LINE_3, IN_LINE_4, IN_LINE_5, IN_LINE_D, IN_LINE_E, IN_LINE_F};

// macierze reprezentujące fizyczne symbole i stan zatrzasku
char symbols[MATRIX_SIZE * MATRIX_SIZE];
int states[MATRIX_SIZE * MATRIX_SIZE];
int index = 0;

// funkcja wewnatrz której definiujemy fizyczne połączenia klawiszy i ich symbole
void mapKeys(){
  setSymbol(OUT_LINE_1, IN_LINE_3, KEY_9);
  setSymbol(OUT_LINE_1, IN_LINE_4, KEY_3);
  setSymbol(OUT_LINE_1, IN_LINE_5, KEY_MUL);
  setSymbol(OUT_LINE_1, IN_LINE_D, KEY_6);
  setSymbol(OUT_LINE_1, IN_LINE_E, KEY_DOT);
  setSymbol(OUT_LINE_1, IN_LINE_F, KEY_MINUS);

  setSymbol(OUT_LINE_2, IN_LINE_3, KEY_7);
  setSymbol(OUT_LINE_2, IN_LINE_4, KEY_1);
  setSymbol(OUT_LINE_2, IN_LINE_D, KEY_4);
  
  setSymbol(OUT_LINE_6, IN_LINE_5, KEY_NUM);
  
  setSymbol(OUT_LINE_A, IN_LINE_3, KEY_PLUS);
  setSymbol(OUT_LINE_A, IN_LINE_4, KEY_ENTER);
  
  setSymbol(OUT_LINE_B, IN_LINE_3, KEY_8);
  setSymbol(OUT_LINE_B, IN_LINE_4, KEY_2);
  setSymbol(OUT_LINE_B, IN_LINE_5, KEY_DIV);
  setSymbol(OUT_LINE_B, IN_LINE_D, KEY_5);
  setSymbol(OUT_LINE_B, IN_LINE_E, KEY_0);
  
  setSymbol(OUT_LINE_C, IN_LINE_D, KEY_BACK);
}

// funckja w której wyzwalane są zdarzenia od kliknięć fizycznych przycisków
void onPress(int index){
  //załącz diodę umieszczoną na przodzie klawiatury
  digitalWrite(LED_PIN, HIGH);
  // pobierz symbol spod konkretnego indeksu
  switch(symbols[index]){
    //wierz 1
    case KEY_NUM:
      Keyboard.write(KEY_ESC);
      break;
    case KEY_DIV:
      Keyboard.write(KEY_PAGE_UP);
      break;
    case KEY_MUL:
      Keyboard.write(KEY_PAGE_DOWN);
      break;
    case KEY_MINUS:
      Keyboard.press(KEY_LEFT_CTRL);
      Keyboard.write(' ');
      Keyboard.releaseAll();
      break;

    //wiersz 2
    case KEY_7:
      Keyboard.write(KEY_F10);
      break;
    case KEY_8:
      Keyboard.write(KEY_F5);
      break;
    case KEY_9:
      Keyboard.press(KEY_LEFT_SHIFT);
      Keyboard.press(KEY_LEFT_ALT);
      Keyboard.write('q');
      Keyboard.releaseAll();
      break;
    case KEY_PLUS:
      Keyboard.press(KEY_LEFT_SHIFT);
      Keyboard.press(KEY_LEFT_ALT);
      Keyboard.write('o');
      Keyboard.releaseAll();
      break;

    //wiersz 3
    case KEY_4:
      Keyboard.write(KEY_F8);
      break;
    case KEY_5:
      Keyboard.press(KEY_LEFT_CTRL);
      Keyboard.write(KEY_PAGE_UP);
      Keyboard.releaseAll();
      break;
    case KEY_6:
      Keyboard.press(KEY_LEFT_SHIFT);
      Keyboard.write(KEY_PAGE_UP);
      Keyboard.releaseAll();
      break;
    case KEY_BACK:
      Keyboard.write(KEY_F9);
      break;

    //wiersz 4,5
    case KEY_1:
      Keyboard.press(KEY_LEFT_CTRL);
      Keyboard.write('q');
      Keyboard.releaseAll();
      break;
    case KEY_2:
      Keyboard.press(KEY_LEFT_CTRL);
      Keyboard.write(KEY_PAGE_DOWN);
      Keyboard.releaseAll();
      break;
    case KEY_3:
      Keyboard.press(KEY_LEFT_SHIFT);
      Keyboard.write(KEY_PAGE_DOWN);
      Keyboard.releaseAll();
      break;
    case KEY_0:
      Keyboard.print(":)");
      break;
    case KEY_DOT:
      Keyboard.press(KEY_LEFT_CTRL);
      Keyboard.write('u');
      Keyboard.releaseAll();
      break;
    case KEY_ENTER:
      Keyboard.write(KEY_ENTER);
      break;
  }

  delay(200);
  digitalWrite(LED_PIN, LOW);
}

// funckja wiążąca symbol z numerami (indeksami w tablicach)
// linii wejścia i wyjścia
void setSymbol(int lineOutput, int lineInput, char symbol){
  // zmienne odpowiedajace indeksom w tablicy dla fizycznych linii
  // wpisanych przez użytkownika.
  int iRow, iColumn;
  
  // odszukaj indeks wiersza czyli lini wyjsc w macierzy
  for(iRow = 0; iRow < MATRIX_SIZE; ++iRow){
    if(OUTPUTS[iRow] == lineOutput)
      break;
  }
  
  // odszukaj indeks kolumny czyli lini wejsc w maciery
  for(iColumn = 0; iColumn < MATRIX_SIZE; ++iColumn){
    if(INPUTS[iColumn] == lineInput)
      break;
  }

  // zapisz symbol w komorce macierzy 1-wymiarowej
  // przy pomocy 2 współrzędnych
  symbols[iRow * MATRIX_SIZE + iColumn] = symbol;
}

// wstępne ustawienia peryferiów i mapowanie przycisków
void setup() {
  //LED
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);
  
  for(int i = 0; i < MATRIX_SIZE; ++i){
    pinMode(OUTPUTS[i], OUTPUT);
    digitalWrite(OUTPUTS[i], HIGH);

    //WEJSCIA 
    pinMode(INPUTS[i], INPUT);
    digitalWrite(INPUTS[i], HIGH);
  }

  // domyślnie nie ma żadnych połączeń w klawiaturze
  for(int i = 0; i < MATRIX_SIZE * MATRIX_SIZE; ++i){
    symbols[i] = KEY_NONE;
    states[i] = KEY_RELESED;
  }
  
  // użyj połączeń zdefiniownaych przez użytkownika
  mapKeys();
  Keyboard.begin();
}

// główna pętla w której sprawdzane są kliknięcia
void loop() {
  for(int iOutput = 0; iOutput < MATRIX_SIZE; ++iOutput){
    // aktywuj sprawdzanie wiersza o indeksie iOutput
    digitalWrite(OUTPUTS[iOutput], LOW);
    for(int iInput = 0; iInput < MATRIX_SIZE; ++iInput){
      // oblicz numer indeksu w macierzy wszystkich przycisków
      index = iOutput * MATRIX_SIZE + iInput;
      
      // jeżeli obecny stan jest LOW (przycisk wciśnięty)
      if((digitalRead(INPUTS[iInput]) == LOW)){
        if(states[index] == KEY_RELESED){
          // to wywołaj kliknięcie
          onPress(index);
        }
        // ustaw flagę żeby zadziałał zatrzask
        states[index] = KEY_PRESSED;
      } else if(states[index] == KEY_PRESSED)
        // zresetuj zatrzask w momencie puszczenia przycisku
        states[index] = KEY_RELESED;
    }
    // opuść linię aby przejść do następnej
    digitalWrite(OUTPUTS[iOutput], HIGH);
  }
}

Podsumowanie

Projekt bardzo praktyczny i ciekawy - polecam każdemu zrobić swoją wersję klawiatury. Warto jednak zastanowić się, czy jest sens przerabiać gotową klawiaturę numeryczną. Zdecydowanie łatwiej byłoby odtworzyć projekt z własnymi, "łatwiej" podłączonymi przyciskami!

arduino, corel, klawiatura

Trwa ładowanie komentarzy...