반응형

키보드를 활성화하고 키보드에서 전달된 데이터를 처리할 준비가 되었으니, 이제 남은 것은 수신된 데이터를 처리하여 ASCII 코드 형태로 변환하는 일이 남았습니다.

 

ASCII 코드American Standard Code for Information Interchange의 약자로, 영문자를 0 ~ 127 범위에 대응시킨 문자 체계입니다.

 

키보드가 ASCII 코드를 사용해서 데이터를 전달해주면 좋겠지만, 키보드는 스캔 코드(Scan Code)를 전달하기 때문에 두 코드 간에 변환이 필요합니다.

 


 

→ 키보드와 스캔 코드

 

키보드는 키가 눌리거나 떨어질 때마다 해당 키에 대한 고유 코드를 전송하며, 키 이벤트에 대한 고유 코드를 스캔 코드라고 부릅니다.

 

아래는 키보드의 키 이벤트에 대응하는 스캔 코드 값을 나타낸 것입니다.

 

 

키보드의 스캔 코드 테이블 1
키보드의 스캔 코드 테이블 2
키보드의 스캔 코드 테이블 3

 

 

키보드의 모든 키는 각자의 고유 코드를 가지고 있으며, 키보드는 키가 눌리거나 떨어질 때마다 그 상태에 해당하는 키 값을 키보드 컨트롤러로 전송하는데, 스캔 코드는 키가 눌렸을 때(Down)와 떨어졌을 때(Up)의 값이 다르며 일반적으로 떨어졌을 때의 키 값은 눌려졌을 때의 값에 최상위 비트(bit 7)를 1로 설정한 값과 같습니다.

 

최상위 비트를 1로 설정하는 것은 0x80을 더하는 것과 같기 때문눌려졌을 때의 키 값에 0x80을 더하는 방식으로 처리하면 떨어졌을 때의 키 값을 가지고 있지 않아도 됩니다.

 

 

위의 표에서 빨간색 박스로 된 부분은 확장키(Extended Key)의 스캔 코드를 표시한 것입니다.

 

확장 키는 AT PC 시절에 사용되었던 키보드 이후에 추가된 키를 의미합니다.

 

확장 키는 일반적으로 QWERTY 키 영역과 숫자 패드 사이에 있습니다.

 

확장 키는 일반 키와 달리 2개 이상의 코드로 구성되므로 처리할 때 주의해야 하는데, 다행스럽게도 확장 키는 다른 키와 달리 0xE0나 0xE1로 시작한다는 공통점이 있으므로, 0xE0나 0xE1에 해당하는 스캔 코드가 수신되었을 때 이를 판단해서 처리하면 됩니다.

 

하지만 위의 표의 문자 영역과 기호 영역을 자세히 보면 키보드의 스캔 코드와 현재 사용하는 ASCII 코드가 일치하지 않습니다.

 

이는 스캔 코드를 그대로 화면에 출력하거나 문자열 버퍼에 저장해서 사용할 수 없다는 것을 의미합니다.

 

그러므로 스캔 코드는 ASCII 문자로 변경해야 합니다.

 


 

→ 스캔 코드를 ASCII 문자로 변환

 

OS에서 A 키가 눌렸을 때 스캔 코드인 0x1E 대신 ASCII 문자의 A를 나타내는 0x41를 전달하면, 애플리케이션에서는 변환 할 필요 없이 그대로 사용할 수 있기 때문에 애플리케이션에서 가장 편리하게 사용할 수 있는 값은 ASCII 코드입니다.

 

스캔 코드에서 ASCII 코드로 쉽게 변환하는 방법은 테이블을 만드는 것입니다.

 

확장 키를 제외한 스캔 코드의 수는 0x58, 즉 88개이므로 스캔 코드의 값을 테이블 인덱스로 사용하여 ASCII 값을 구하는 변환 테이블을 만들어 사용합니다.

 

 

스캔 코드를 ASCII 코드로 매핑하는 테이블을 만들기 전에 테이블을 구성하는 엔트리 자료구조를 먼저 생성합니다.

 

엔트리 자료구조를 정의할 때 고려해야 할 점알파벳 키와 숫자 키와 몇몇 기호 키는 Shift나 Caps Lock 키의 상태에 따라서 두 가지의 의미로 사용된다는 것입니다.

 

그렇기에 엔트리는 두 가지 ASCII 코드를 모두 포함할 수 있도록 정의합니다.

 

아래는 두 가지 코드를 모두 고려한 매핑 테이블의 엔트리입니다.

 

 

스캔 코드 매핑 테이블을 구성하는 엔트리

 

 

KEYMAPPINGENTRY가 준비되었으니 변환 테이블을 구성합니다.

 

변환 테이블을 구성할 때 유의할 점ASCII 문자로 대응되지 않는 값들이 있다는 것입니다.

 

기능 키들 중에 HOME 키나 F1 키는 별도로 할당된 ASCII 문자가 없기 때문에 FS64 OS에서는 이러한 키에 0x80 이상의 값을 할당하고, 매크로를 정의하여 애플리케이션에서 사용하게 합니다.

 

아래는 KEYMAPPINGENTRY를 이용하여 구성한 변환 테이블을 나타낸 것입니다.

 

일단 테이블의 크기가 커서 일부만 표현했습니다.

 

 

스캔 코드를 ASCII 코드로 매핑하는 테이블

 

 

이제 테이블이 준비되었으니 실제로 키 값을 입력받아 ASCII 문자로 변환할 때 필요한 데이터를 정의합니다.

 

키 값을 ASCII 문자로 변환하려면 스캔 코드를 ASCII 코드로 매핑할 때 일반 키를 반환할지 혹은 조합 키를 반환할지를 판단해야 하기 때문Shift 키와 Caps Lock 키, Num Lock 키, 즉 조합 키의 상태를 알고 있어야 합니다.

 

그렇기에 키보드 상태를 저장하는 자료구조를 정의하여 조합 키의 상태를 추가합니다.

 

 

사실 별도의 처리가 필요한 키는 조합 키뿐만 아니라 스캔 코드 2개 이상이 조합된 확장(Extended Key)도 처리해야 합니다.

 

확장 키 중에서 Pause를 제외한 나머지 키들은 확장 키임을 알리는 0xE0 코드를 먼저 전송하므로 0xE0를 수신했을 때 다음에 오는 키 값을 기다렸다가 처리하면 됩니다.

 

다른 확장 키와 달리 Pause는 0xE1 0x1D 0x45처럼 3개의 코드가 조합되고 Up Code가 없으므로 0xE1이 수신되었을 때 Pause를 미리 처리하고 나머지 키 코드 2개를 무시하는 방법으로 처리할 수 있습니다.

 

그렇기에 확장키가 수신되었는지 여부를 나타내는 필드Pause가 입력되었을 때 이후에 무시해야 하는 키 값을 저장하는 필드도 추가합니다.

 

아래는 이러한 부분들을 반영한 키보드 자료구조입니다.

 

조합 키의 입력 상태와 확장 키 입력 상태를 저장하는 필드가 있습니다.

 

(Scroll Lock 키에 대한 상태를 넣은 이유는 조합 키로 사용하지는 않지만, 키보드 LED 상태를 저장하려면 필요하기 때문입니다.)

 

키보드의 키 상태를 관리하는 자료구조

 

지금까지 키 값 변환에 필요한 자료구조와 테이블을 알아보았으므로 본격적으로 스캔 코드를 ASCII 코드로 변환합니다.

 

스캔 코드를 ASCII 코드로 정확히 변환하려면 조합 키의 상태에 따라 영향을 받는지 여부를 알아야 하므로 조합 키의 상태에 따라 영향을 받는 키들을 먼저 구분합니다.

 

아래는 키를 세 그룹으로 묶어 해당 그룹에 영향을 주는 조합 키를 나타낸 것입니다.

 

 

키 그룹과 관련된 조합 키

 

 

위에서 볼 수 있듯이 각 그룹별로 영향을 받는 조합 키가 다르기 때문에 키를 정확하게 변환하려면 스캔 코드를 그룹으로 분류하고 관련된 조합 키의 상태에 따라 적절한 코드로 변환해야 합니다.

 

키가 특정 그룹에 포함되는지 판단하려면 스캔 코드 테이블을 참조하면 되므로 그리 어렵지 않습니다.

 

어떤 그룹에 속하는지 확인했다면 그룹에 관련된 조합 키의 상태에 따라 일반 코드 또는 조합 코드를 반환하면 되므로 쉽게 처리할 수 있습니다.

 

아래는 키가 속하는 그룹과 조합 키의 상태를 이용하여 일반 키를 반환할지 혹은 조합된 키를 반환할지 여부를 반환하는 함수의 코드입니다.

 

위의 키 그룹과 관련된 조합 키 그림과 같이 키 그룹을 알파벳 키, 숫자와 기호 키, 숫자 패드로 나누어 처리합니다.

 

 

조합된 키를 선택해야 하는지 여부를 반환하는 함수의 코드 1

 

 

 

조합된 키를 선택해야 하는지 여부를 반환하는 함수의 코드 2

 

 

조합 키의 상태 처리 역시 조합 키에 대한 스캔 코드가 수신될 때마다 갱신하는 방식으로 처리할 수 있습니다.

 

아래는 스캔 코드가 조합 키나 LED 상태 변경이 필요한 키일 때 이를 처리하는 함수의 코드입니다.

 

조합 키이면 키보드 상태를 관리하는 자료구조에 정보를 갱신하고 LED 상태 변경이 필요하다면 이전에 작성한 kChangeKeyboardLED() 함수를 호출하도록 작성되었습니다.

 

 

조합 키의 상태를 갱신하는 함수의 코드

 

 

조합된 키를 선택해야 할지 여부를 판단하는 것이나 조합 키의 상태를 갱신하는 작업은 그리 복잡하지 않습니다.

 

정말 중요하고 복잡한 부분은 확장키를 처리하는 것입니다.

 

확장키는 2개 이상의 스캔 코드로 구성되며 두 번째 또는 세 번째까지 키를 확인해야 정확한 키 값을 찾을 수 있습니다.

 

그나마 다행인 점은 Pause 키는 다른 확장 키와 달리 0xE1으로 시작하여 0x1D, 0x45가 전송되고, Pause 키를 제외한 다른 확장 키는 0xE0와 일반 키의 스캔 코드가 전송되기 때문에 Pause 키의 경우는 0xE1을 수신한 뒤 나머지 0x1D, 0x45 무시하게 처리하고, 다른 확장 키는 0xE0을 수신했을 때 확장 키임을 저장해둔 후 다음 키가 수신되었을 때 ASCII 코드와 함께 확장 키임을 알려주는 방법으로 처리할 수 있습니다.

 

아래는 이러한 처리 알고리즘을 간략하게 표현한 것입니다.

스캔 코드를 ASCII 코드로 변환하는 알고리즘

 

아래는 위의 알고리즘대로 작성된 스캔 코드에서 ASCII 코드로 변환하는 함수입니다.

 

알고리즘을 그대로 옮겨놓았으므로 알고리즘만 이해하면 크게 어렵지 않습니다.

 

스캔 코드를 ASCII 코드로 변환하는 함수의 코드 1
스캔 코드를 ASCII 코드로 변환하는 함수의 코드 2

 


 

→ 간단한 셸 구현

 

키보드 처리를 위한 내부 코드는 전부 구현됐지만, 겉으로 보이는 모습은 예전과 다를 바가 없습니다.

 

키보드 디바이스 드라이버가 구현되었으니 이를 활용하여 입력된 키 값을 화면에 출력하는 기능을 추가해야 하는데, 이러한 기능은 키보드 디바이스 드라이버의 테스트를 위해서도 꼭 필요한 부분입니다.

 

셸이란 사용자에게서 명령을 받아 작업을 수행하는 프로그램으로 OS와 유저를 연결해주는 중요한 역할을 맡고 있습니다.

OS의 기능이 다양하면 다양할수록 셸의 기능도 그만큼 복잡해집니다.

 

지금은 키보드 디바이스 드라이버를 테스트하는 것이 주목적이므로 입력된 스캔 코드를 변환하여 화면에 순차적으로 출력하는 기능만 구현합니다.

 

아래는 키보드로부터 값을 입력받아 화면에 출력하는 코드입니다.

 

 

입력된 키를 화면에 출력하는 간단한 셸 코드

 

 

반응형

+ Recent posts