안녕하시므니까.


지난번 저항으로 만들기(1, 2, 3)에 이어 이번엔 키보드 매트릭스를 이용해서 아두이노로 키보드를 만들어봅니다.


키보드 매트릭스가 뭐죠?


불 켜는 스위치든 키보드 스위치든, 스위치에는 다리가 두 개 달려있습니다. 스위치를 누르면 두 다리가 서로 연결되고, 떼면 떨어집니다. 그래서 전압을 보내거나 보내지 않음을 제어할 수 있습니다. 전자회로에서는 보통 한 쪽에는 전압이 있고, 다른 쪽에는 전압이 없어서 스위치를 누르면 전압이 낮아지거나 높아짐을 감지하여 스위치가 눌렸는지를 검사합니다.


즉, 입력을 감지하는 한 쪽 다리에는 0볼트를, 다른쪽 다리에는 USB에서 나오는 5볼트를 연결하여 입력을 감지하는 다리가 0볼트 상태라면 스위치가 안눌림 / 5볼트 상태라면 눌림이 됩니다. 그 반대로, 입력을 감지하는 다리가 5볼트이고 다른쪽 다리에 0볼트를 끼워서 다리가 5볼트일 때 스위치가 안눌림 / 0볼트일때 눌림으로 할 수도 있습니다. 


이렇게 스위치 1개를 동작시키는데에는 입력감지 핀 1개 + 전압 핀 1개, 총 두 개의 핀이 필요합니다.


11.png


이러한 컨트롤러 칩을 보신적이 있을텐데요. 저기 달려있는 핀 중에 두 개가 필요하다는 이야기입니다. 여기서 문제가 발생합니다. 컨트롤러 칩에 다리의 갯수는 제한되어 있고 컨트롤러 칩 자체의 동작이나 USB/PS2 등 호스트와의 연결을 위해서 예약되어있는 핀들이 있기 때문에 다리 갯수의 제한이 있다는 것입니다. 다리 갯수가 더 많은 컨트롤러는 그만큼 설계와 제어도 복잡해지기 때문에 가격이 비싸서 사용이 어려워집니다. 실제로 다리가 20개 달린 아두이노는 2만 원을 안하지만 다리가 70개 달린 아두이노는 7만원이 넘습니다.


그러니까 앞서 말씀드린 방법으로 스위치를 연결하면, 스위치 1개당 두 개의 핀이 필요하므로 만약 컨트롤러의 다리 갯수가 20개(=일반적인 아두이노의 사용 가능한 다리 갯수)라면 스위치를 최대 10개 연결할 수 있다는 것이 됩니다.


하지만 자세히 보면 전압을 공급하는 쪽의 다리는 모든 스위치 공통으로 사용해도 문제가 없으므로, 입력을 감지하는 다리만 있어도 앞선 방법을 구현할 수 있습니다. 실제로 전압은 따로 넣어주면 되니까 다리가 20개라면 스위치도 20개를 사용할 수 있습니다.


그런데 이러한 다리 20개짜리 아두이노로 키보드를 만들고 싶은데요... 키보드엔 스위치가 20개보다도 훨씬 많이 붙어있습니다. 제가 만들고 싶은 미니 키보드에는 스위치가 66개 필요한데 그러면 어떻게 해야되나요? 아두이노를 4개 사서 20개씩 나눠서 써야되나요? 이러면 돈도 많이 들고 부피도 커지고 USB 선도 네 개가 꼽히니까 엄청 불편하겠죠. 다리가 70개 달린 아두이노를 쓸까요? 이쪽도 비싼건 마찬가지고, 아슬아슬하게 미니 키보드는 만들 수 있지만, 만약 스위치가 70개를 넘는 구성을 한다면 이쪽도 못 쓰기 때문에 뭔가 근본적으로 여러 개의 스위치를 연결할 때 더 효율적인 방법이 필요합니다.


스위치 다리 두 개 중에 입력을 감지하는 다리 하나와 전압을 넣어주는 다리 하나가 필요하다고 했는데요, 그러면 입력을 감지하는 다리 하나에 전압을 0볼트, 2.5볼트, 5볼트 이렇게 넣어주면 어떨까요? 그러면 총 세 가지의 상태 구분이 가능할테니 입력 감지용 핀 하나로 안눌림 / 1번째 키 눌림 / 2번째 키 눌림의 감지가 가능하겠죠? 전압을 좀 더 촘촘하게 나눠서 0.5볼트 간격으로 한다면 총 11 가지 상태 구분을 통해 10개의 키를 감지할 수 있을겁니다. 아얘 그냥 0.05볼트 간격으로 나눠서 입력감지용 핀 하나에 100키를 감지시켜버릴 수도 있겠네요.


근데 이 전압을 실제 손쉽게 구할 수 있는 전자부품을 써서 나눠보려고 하면 그렇게 정밀하게 나눌 수가 없습니다. 심지어 수십 수백만원짜리 컴퓨터만 하더라도 오버클럭을 해보신 분들이라면 아시겠지만 파워서플라이 5볼트 / 12볼트가 실제로는 5.423볼트라든지 12.726볼트라든지 하여 정확하게 5.000 / 12.000 볼트가 안나온다는 걸 알고계실겁니다. 수십만 원짜리 메인보드에도 CPU에 들어가는 전압을 아주 정밀하게 제어하기 위해 6페이즈니 8페이즈니 하는 "전원부"가 있고 그럼에도 불구하고 BIOS 에서 1.275볼트라고 설정해놔도 윈도우즈에서 보면 1.3볼트가 들어간다든지 하여 전압 제어가 쉽지 않다는걸 겪어보셨을겁니다. 근데 10원짜리 저항 쪼가리 몇 개를 써서 전압을 0.05볼트 간격으로 나누는건 무리지요.


그래서 현실적으로 대략 5볼트를 10~15등분 하여 전압이 지나가는 통로를 스위치마다 서로 다르게 하여, 입력감지 핀 하나에 키 10~15개를 묶어 달아서 입력감지 핀 여섯개로 미니 키보드를 만들 수가 있습니다. 이게 지난시간 저항 키보드의 원리입니다.


그런데 이렇게 하면 핀 하나에 연결된 많은 키들 서로는 동시입력이 불가능합니다. 물론 머리를 더 쓰면 저항 값을 미묘하게 잘 조정하여 1번 키를 누르면 1볼트가, 2번 키를 누르면 2볼트가, 1번과 2번 키를 동시에 누르면 0.8볼트가 들어가게 할 수 있는데 이러면 핀 하나로 조절할 수 있는 전압의 경우의 수가 상당히 많이 줄어들어서 큰 의미가 없게 됩니다. 또한 전압을 서로 다르게 하여 핀 하나가 여러 스위치를 구분한다는 방법의 전제조건은 입력 감지 핀이 전압을 감지할 수 있는 형태여야 한다는 것인데, 단순히 있음/없음을 구분하는 것 보다 당연히 복잡한 작업이므로 컨트롤러 칩(여기서는 아두이노)의 성능이 좋아야 합니다. 2만 원 짜리 입력감지 핀이 20개 달린 아두이노에서도 전압을 구분할 수 있는 다리는 여섯 개 밖에 없습니다. 시장에서 팔리는 멤브레인 키보드는 키보드 전체 가격이 몇 천원밖에 안하는 판인데 이런데에 사용되는 칩의 단가는 매우 저렴해야할 수 밖에 없고, 다른 이유도 있겠지만 종합적으로 대량생산에는 알맞지 않는 방법이라 저항으로 판매용 키보드를 구현한 사례는 잘 없습니다.


그럼 다른 방법은 없느냐, 하면 오늘 소개할 키보드 매트릭스 방법이 있습니다. 저항 방식의 원리는 스위치마다 서로 다른 전압을 감지해서 입력감지 핀 하나에 달린 많은 스위치를 서로 구분해서 감지한다는 겁니다. 키보드 매트릭스에서는 전압을 넣는 다리를 스위치마다 서로 다른걸 꼽아주고 시간에 차이를 두어 전압을 하나씩 넣었다 뺐다 하여 어느 순간에 감지되는지 시간을 통해 구분을 할 수 있습니다.


단 이렇게 하는 경우, 필요한 다리의 갯수는 저항을 이용하는 것 보다는 많아져서 스위치의 제곱근 개가 필요합니다. 다리 20개짜리 아두이노에서 10 * 10개의 스위치, 즉 100개의 스위치를 구분할 수 있습니다. 만약 100키를 초과하는 키보드를 만들어야 한다면, 여기에 앞서 설명한 저항방식을 1개 핀에만 곁들여서 쓰면 110키, 2개 핀에 쓰면 120키가 가능합니다.


그래서 이번에는 아두이노로 키보드 매트릭스를 만들어서 키보드를 제작해보았습니다.


설명이 길어졌습니다만, 저항에 비해 키보드 매트릭스를 쓰면 좋은점은

- 엔키 롤오버가 됩니다 (자세한 내용은 "엔키 롤오버"와 "고스트키"로 검색해주세요)

- 머리를 덜 써도 됩니다


나쁜점은

- 납땜이 약간 더 귀찮습니다

- 핀이 더 많이 필요합니다 (= 크고 비싼 아두이노를 써야합니다)


준비물 : 키캡, 스위치, 다이오드, 철사1미터, 전선1미터, 아두이노, 하우징 및 필요한 나사류


1. 키캡을 구합니다.


2. 키캡 배열과 사이즈를 잘 재어서 도면을 샤샥 그리고 아크릴 주문을 합니다. 저항때와 동일하니 설명은 생략.


3fa5123e8d7a5181265471eecc1b7343.png

3. 아크릴이 배달오면 스위치를 쏙쏙 끼웁니다. 떨어지지 말라고 글루건도 발라줬습니다.

efc5bc593d2a758479d09d1488e0fbe2.jpg


c2e52f7718ae0ffc4b9ec43c0cacb78e.jpg

4. 그 다음 엔키 롤오버를 위해서 다이오드를 달 차례인데요. 다이오드가 12원인데 고휘도 녹색 LED가 10원이길래 겸사겸사 저는 이걸 쓰기로 했습니다. 스위치에 있는 LED 자리에 끼울 수 있으니 뒷면도 깔끔하게 나오겠지요.

+ 수정 : LED를 쓰면 키를 눌렀을 때 불이 들어오게 할 수 있습니다. 하지만 다이오드에 비해 전압강하가 많이 되기 때문에 입력을 아날로그로 감지해야 해서, 현재 사진의 5행에 해당하는 입력감지용 -극성 행을 디지털이 아닌 아날로그 핀에 연결해야합니다. 다이오드에 비해 추천하지 않습니다.

f9906b6c9e5212b6830ec9e0ad5d00f2.jpg


제 경우 위 사진에서 LED 왼쪽이 +, 오른쪽이 -입니다.


ca932723547a19fa6fac0c07f127acc5.jpg

거기에서 +극은 그대로 다리 길이만 잘랐고, -극을 한 바퀴 감아서 스위치 다리 중에 하나에 묶었습니다.

5. 이제 땜질입니다.

자 여기가 유일하게 뇌를 써야하는 부분입니다.

제가 쓴 아두이노 보드는 이전에 소개해드린 '늘소미노 하나'인데요. 아날로그 6핀 + 디지털 14핀으로 총 20핀이 연결 가능합니다.

그러니까 이 상황에서 키보드 매트릭스 구조를 이용할 때에 기본적으로 가능한 최대 키 수는 10 * 10, 100키입니다.


만약에 100키 이상을 구현하시려면 아날로그 핀이 붙는 줄 중에 하나를 두 갈래로 나눠서 한쪽은 그냥 쓰시고 다른 쪽은 저항을 하나 달아서 쓰시면 한 줄이 늘어나는 효과가 있어서 11 * 10, 110키를 쓰실 수 있습니다. 하나 더 불리면 12 * 10, 두 개 더 불리면 13 * 10이 되겠지요.


제가 만들 것은 미니 키보드라서 키가 다섯 줄 있고, 1번과 2번째 줄에 키가 가장 많아서 각각 14개씩 있습니다.

배열로 봤을 때 정리하면 5행 14열이지요.

이러면 5+14 = 19니까 보이는 그대로 매트릭스를 만들 수가 있습니다! 야 신난다!

만약에 보이는 그대로 매트릭스를 할 수 없다면 골치가 아파집니다... 왜 그럴까요? 답은 여러분에게 맡기겠습니다.

아무튼 20핀이 있는 아두이노에서는 행*열이 필요한 키를 채우되, 행+열이 20 이하여야지 된다는 이야기입니다.


그러면 땜을 합니다. 저처럼 행*열이 비대칭인 경우, 더 적은 쪽을 다이오드의 + 다리쪽과 닿게 붙이시면 좋고 더 많은 쪽을 다이오드의 - 다리쪽과 닿게 붙이시면 좋습니다.


아래 사진에서는 앞서 말씀드린 것 처럼 다이오드의 - 다리를 스위치 한 쪽 다리에 감아두었는데요, 가로줄이 5줄 뿐이라 다이오드의 +다리에 붙였고 세로줄은 14줄이라 스위치의 남은 쪽 다리(즉 스위치를 눌렀을 때 다이오드의 -쪽)에 붙였습니다.


아래 사진을 보시면 의외로 납땜이 머리 안아프고 단순하게 나온다는걸 보실 수 있습니다. 이 점이 아두이노를 쓰는 첫 번째 장점입니다. 편한대로 때우고 프로그래밍을 통해 키를 지정하면 되기때문에 풀 와이어링처럼 정확히 특정 핀 두 개를 특정 키에 붙일 필요가 없습니다.


9867a166d0dd35ad120c572b49f272df.jpg


(사진을 잘 못찍어서 가로줄과 세로줄이 겹친 것 같지만, 세로줄이 바닥에 붙어있고 가로줄은 위에 떠있어서 서로 닿지 않습니다. 서로 닿으면 안됩니다)


6. 아두이노에 붙여줍니다. 저는 가로 5행은 A0~A4핀에, 세로 14열은 D0~D13핀에 달았습니다.


c4698c7c82adbb2d9f9546c0a1f4aec8.jpg


(사진에선 생각없이 아두이노를 왼쪽 앞에 붙였는데 왼쪽 뒤로 옮겼습니다)


+ 수정 : LED를 쓸 경우 사진의 가로 5행(-극)을 디지털이 아닌 아날로그 핀에 연결해야합니다.

또한 LED를 켜지게 하려면 '풀업 저항'을 달아주어야 합니다. 아래 댓글에 첨부한 사진처럼 5볼트와 아날로그핀을 80옴 저항으로 연결해주세요.

일반 다이오드를 쓸 경우 이러한 작업이 필요 없으며, 아날로그 디지털 구분 없이 연결해도 됩니다.


7. 키캡을 끼우고 나사를 잠구고 하우징을 마무리합니다.


8. 다됐으면 아두이노 프로그래밍을 할 차례입니다.


여기서 지난시간 저항+아날로그입력 방법을 잠시 기억해보면, 한 줄로 엮인 키 들이 서로 저항값이 다르기 때문에 같은 전압을 흘려도 서로 다른 전압이 들어오는 원리를 이용하였습니다.


키보드 매트릭스의 경우는 가로 * 세로 격자를 짜서 둘 중 한 줄은 읽기 상태(+전압), 다른 한 줄은 보내기 상태(-전압)를 유지했다가 키를 누르면 둘이 맞붙어서 눌림을 감지하는 원리입니다.


그런데 하나의 읽기 줄에 여러 보내기 줄이 붙는데, 대체 어느 줄이 붙은 건질 어떻게 감지하는지가 매우 궁금했는데요... 사진갤러리의 yChan님께서 알려주셨습니다. 보내기 줄을 항상 켜두기(-전압) 하는게 아니라 한 줄만 켜서 입력이 들어오나 확인하고 -> 그 줄은 끄고 다음 줄을 켜서 입력이 들어오나 확인하고 -> 의 반복으로 체크를 하는거였습니다. 이럴수가.


아무튼 만약 키보드에 fn키가 없이 1키 = 1입력 으로 고정된다면 2차원 배열과 for 반복문을 이용해서 엄청 심플하게 프로그래밍을 할 수가 있습니다. 예를 들면


void setup() {
  pinMode(A0, INPUT_PULLUP);
  pinMode(A1, INPUT_PULLUP);
  pinMode(A2, INPUT_PULLUP);
  pinMode(A3, INPUT_PULLUP);
  pinMode(A4, INPUT_PULLUP);
  pinMode(0, OUTPUT);
  pinMode(1, OUTPUT);
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);
  pinMode(6, OUTPUT);
  pinMode(7, OUTPUT);
  pinMode(8, OUTPUT);
  pinMode(9, OUTPUT);
  pinMode(10, OUTPUT);
  pinMode(11, OUTPUT);
  pinMode(12, OUTPUT);
  pinMode(13, OUTPUT);

}


void loop() {

  char* keys[5][14] {

    {"96", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "45", "61", "KEY_BACKSPACE"},

    {"KEY_TAB", "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "91", "93", "92"},

    {"KEY_CAPSLOCK", "A", "S", "D", "F", "G", "H", "J", "K", "L", "59", "39", "KEY_RETURN", "0"},

    {"KEY_LEFT_SHIFT", "Z", "X", "C", "V", "B", "N", "M", "44", "46", "47", "KEY_RIGHT_SHIFT", "0", "0"},

    {"KEY_LEFT_CTRL", "KEY_LEFT_ALT", "KEY_LEFT_GUI", "32", "KEY_RIGHT_GUI", "KEY_RIGHT_ALT", "KEY_RIGHT_CTRL", "0", "0", "0", "0", "0", "0", "0"}

  }

  for (i=0, i<=4, i++) {

    for (j=0, j<=13, j++ {

      digitalWrite(j, LOW);

      if (digitalRead(i)) Keyboard.release(keys[i][j]);

      else Keyboard.press(keys[i][j]);

      digitalWrite(j, HIGH);

    }

  }

}


심지어 생략된 코드도 없이 위에꺼가 전체 코드입니다. 물론 배열마다 다르니 위 코드를 복붙해서 쓰실수는 없겠지만 대충 이렇게 2차원 배열로 키 위치를 잡아놓고 2중 for로 스캔을 해버리면 되는겁니다.


다만, 제 경우는 미니 키보드다보니 fn키가 있을뿐더러 왼쪽 alt키를 누르자마자 떼면 윈도우키고 누르고 1초 지나면 alt가 된다든지, 오른쪽 쉬프트를 윗방향키와 전환되게 쓴다든지 할 것이므로 for반복으로는 좀 무리가 있기에 전부 풀어서 동작하게 하기로 합니다.


void setup() {
  pinMode(A0, INPUT_PULLUP);
  pinMode(A1, INPUT_PULLUP);
  pinMode(A2, INPUT_PULLUP);
  pinMode(A3, INPUT_PULLUP);
  pinMode(A4, INPUT_PULLUP);
  pinMode(0, OUTPUT);
  pinMode(1, OUTPUT);
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);
  pinMode(6, OUTPUT);
  pinMode(7, OUTPUT);
  pinMode(8, OUTPUT);
  pinMode(9, OUTPUT);
  pinMode(10, OUTPUT);
  pinMode(11, OUTPUT);
  pinMode(12, OUTPUT);
  pinMode(13, OUTPUT);

}


void loop() {

  digitalWrite(0, LOW);

  if (digitalRead(A0)) Keyboard.release(96);

  else Keyboard.press(96);

  if (digitalRead(A1)) Keyboard.release(KEY_TAB);

  else Keyboard.press(KEY_TAB);

  if (digitalRead(A2)) Keyboard.release(KEY_CAPSLOCK);

  else Keyboard.press(KEY_CAPSLOCK);

  if (digitalRead(A3)) Keyboard.release(KEY_LEFT_SHIFT);

  else Keyboard.press(KEY_LEFT_SHIFT);

  if (digitalRead(A4)) Keyboard.release(KEY_LEFT_CTRL);

  else Keyboard.press(KEY_LEFT_CTRL);

  digitalWrite(0, HIGH);


  digitalWrite(1, LOW);

  if (digitalRead(A0)) Keyboard.release('1');

  else Keyboard.press('1');

  if (digitalRead(A1)) Keyboard.release('Q');

  else Keyboard.press('Q');

  if (digitalRead(A2)) Keyboard.release('A');

  else Keyboard.press('A');

  if (digitalRead(A3)) Keyboard.release('Z');

  else Keyboard.press('Z');

  if (digitalRead(A4)) Keyboard.release(KEY_LEFT_ALT);

  else Keyboard.press(KEY_LEFT_ALT);

  digitalWrite(1, HIGH);


  // 일단 두 줄만 써봤는데 이런식으로 세로줄 핀을 1개 넘기고 가로줄 다섯개를 체크하여 14개 세로줄 전체를 체크합니다

}


각 키별 아두이노 고유 이름은 이전시간 짜깁기해둔 아래 그림을 참고하시면 되겠습니다. 저장해서 보시면 커집니다.


3244362880126702cd490d94e0a03e6b.png


도움이 되실지 모르겠으나 아무튼 제가 작성한 코드를 첨부합니다. sketch_apr21a.ino


제 경우 왼쪽 컨트롤을 fn과 합쳐서 사용합니다. 예를 들어 Ctrl + 1을 하면 F1이 입력됩니다. Ctrl + Caps Lock + 1을 하면 Ctrl + F1이 입력됩니다. 마찬가지로 Ctrl + Backspace = Delete, Ctrl + Caps Lock + Backspace = Ctrl + Delete.


이런식으로 왼쪽 알트는 왼쪽 윈도우와 합쳐서, 오른쪽 쉬프트는 윗방향키와 합쳐서 사용합니다.


아주 자주 이용되는 Alt + F4는 그냥 Alt + 4를 누르면 되게 했습니다.


미니 배열이지만 풀 사이즈 사용자 경험 그대로 손 가는대로 쓰면 되게 맞춘거지요. 이게 아두이노를 쓸 때의 두 번째 장점입니다. 키 배열은 물론이고 각 키의 매크로를 완전 맘대로 정할 수 있습니다.


9. 완성


fa48ae3a4eede733cc13b488152d87ff.jpg


431bdc0e526bba64cbe177f0042ff4ec.jpg


328B6146.jpg