6-key 롤 오버를 하더라도, 폴링 주기 동안 눌린 키를 보낸다고 하면, 지금처럼 usb를 통한 키 전송 방식에서 엄청 느리진 않을거다.
지금 내가 구현한 방식은 키 입력이 발생하면 바로 report를 보내는 방식이다.
이렇게 하는 것이 아니라, 폴링 주기 동안 눌려진 키를 어레이에 담아서, 그 어레이를 폴링 주기가 도래했을 때 모아서 report를 해야 한다. 그래야 씹히는 키가 발생하지 않게 된다.
이렇게 하려면, 폴링주기를 잡고, 그 폴링주기 동안 어레이에 키 입력을 순차적으로 저장하고 폴링 주기가 왔을 때 report를 해야한다.
우선 위 방식대로 코드를 수정하도록 하자.
----
무한 입력은 정말 짧은 시간내에 많은 키가 입력돼도 모두 report가 되도록 해야하는데, 이는 descriptor를 커스텀해서 6개의 키입력을 담아서 보내는게 아니라 30개, 50개 등 내가 원하는 키 입력갯수를 커스텀해서 보내는게 가능하다고 한다.
이를 위해서는 descriptor를 커스텀하는게 필수라고 한다.
우선 무한 입력을 하기 전에, 6kro를 폴링주기에 맞춰서 제대로 응용해보도록 하자.
폴링주기는 임의로 잡는 것 말고, row, column의 스캔이 모두 끝났을 때 보내는 걸로 해보자.
아래는 ChatGPT 대화내용
ChatGPT의 말:
무한 입력(N-Key Rollover, NKRO)을 소프트웨어적으로 구현할 때는 몇 가지 중요한 개념이 있어요. 일반적인 매트릭스 키보드에서 소프트웨어 구성 방식은 대략 다음과 같습니다.
- 스캔 루프:
- 키 매트릭스의 각 행을 순차적으로 활성화하고, 그 행에 연결된 열을 읽어 현재 눌린 키를 감지합니다. 이 과정을 매우 빠르게 반복하면서 모든 키 입력을 동시에 처리할 수 있습니다.
- 디바운싱:
- 스위치의 물리적인 특성으로 인해 눌릴 때와 뗄 때 전기적 노이즈(바운스)가 발생합니다. 디바운싱 코드를 통해 스위치가 실제 눌렸는지 안정적으로 확인합니다. 디바운싱을 하지 않으면 키 입력이 여러 번 인식될 수 있어요.
- 다중 키 입력(NKRO) 처리:
- 키를 누를 때마다 스캔 루프가 활성화된 행과 열을 조합하여 매트릭스에서 각 키의 상태를 기록합니다. 이를 통해 여러 개의 키 입력이 동시에 감지되면, 소프트웨어는 각 키를 구분하여 처리합니다.
- 이와 함께, 무한 입력 시 발생할 수 있는 "고스트 키(Ghost Key)"를 방지하기 위해 다이오드를 사용하는 것이 중요합니다. 고스트 키가 없다면 소프트웨어 측에서는 별다른 고스트 키 방지 코드가 필요 없을 수 있습니다.
- 키 매핑 및 보고:
- 매트릭스의 키 위치를 실제 키보드 레이아웃에 맞게 매핑합니다. ESP32와 같은 마이크로컨트롤러에서는 매핑된 키 상태를 HID(Human Interface Device) 프로토콜에 맞춰 PC에 전송할 수 있습니다.
- 다수의 키 입력을 처리하는 과정에서 모든 키 상태를 유지하고, 새로운 입력이 발생할 때마다 이를 업데이트해 전송하도록 합니다.
- 타이머 기반 폴링:
- 스캔 루프와 디바운싱을 효율적으로 처리하려면 타이머를 기반으로 폴링 주기를 설정하는 것이 좋습니다. 폴링 주기가 지나치게 짧으면 CPU 부하가 커지고, 너무 길면 반응 속도가 느려질 수 있습니다.
ESP32에서는 이 모든 작업을 빠르게 수행할 수 있는 충분한 성능이 있어, 매트릭스 스캔 루프와 디바운싱을 타이머와 함께 효과적으로 사용할 수 있을 겁니다.
나의 말:
ChatGPT의 말:
맞아요! USB 키보드의 HID (Human Interface Device) 프로토콜에서는 일반적으로 최대 6개의 키 입력을 동시에 전송할 수 있도록 배열로 보내게 되어 있어요. 이 방식은 **부트 모드(boot mode)**에서 사용되는 표준 방식으로, 6개의 키와 함께 모디파이어 키(Shift, Ctrl 등) 상태를 전송할 수 있습니다.
이 방식은 **6-Key Rollover (6KRO)**라고 하며, USB HID 프로토콜의 기본 동작 방식입니다. 하지만 만약 무한 입력(NKRO)이 필요한 경우에는, 프로토콜을 다르게 처리하거나 별도의 소프트웨어 프로토콜을 사용해 HID 보고서의 형식을 확장해야 합니다.
간단히 정리하면:
- 6-Key Rollover 방식:
- USB HID 표준에서는 6개의 키 코드만을 보고서에 담을 수 있습니다.
- 각 보고서에는 모디파이어(Shift, Ctrl 등)와 최대 6개의 키 코드 배열이 포함됩니다.
- N-Key Rollover (NKRO) 처리:
- 키보드가 무한 입력을 지원하도록 구성할 때는 USB HID 표준을 확장하거나 커스텀 HID 보고서를 구성하여, 더 많은 키 입력을 담을 수 있도록 구현하는 방식이 필요합니다.
ESP32에서도 이 방식을 사용해 최대 6개 키 입력까지는 일반 HID 규격을 따를 수 있으며, 무한 입력을 위해서는 추가적인 커스텀 처리가 필요할 수 있습니다.
나의 말:
ChatGPT의 말:
네, 맞습니다! 타이머 기반 폴링 주기 내에서 여러 키가 눌리면, 이 모든 입력이 6개의 키 코드 배열에 담겨서 보고됩니다.
구체적으로 설명하자면:
- 폴링 주기 동안 여러 키가 눌렸다면, 이때 감지된 키들은 배열에 순차적으로 기록됩니다. 예를 들어, a, b, c를 누른 경우, 키보드는 각 키의 키 코드를 보고서의 배열에 담습니다.
- 보고서 생성:
- 폴링 주기마다 현재 키 상태를 확인하고, 배열에 담긴 키 코드들을 USB HID 보고서에 포함해 전송합니다.
- 배열은 6개의 슬롯을 가지고 있으므로, 6개 이하의 키는 모두 담기고, 그보다 많은 입력이 있으면 일부 키는 전송되지 않게 됩니다 (6키까지만 전송).
- 타이머 기반 폴링의 장점:
- 폴링 주기 내에서 발생한 모든 입력을 수집하여 한 번에 보고할 수 있어, 한 키마다 별도로 보고하는 것보다 효율적입니다.
- 입력 속도에 따라 주기를 적절히 설정해 빠르고 일관된 응답성을 유지할 수 있습니다.
그래서 6-Key Rollover 방식과 타이머 기반 폴링이 결합되면, 매 주기마다 최대 6개의 키 입력을 안정적으로 수집하고, 이 상태를 보고할 수 있게 됩니다.
나의 말:
ChatGPT의 말:
USB HID 키보드 보고서에서 모디파이어 키는 일반 키 코드 배열과는 별도의 필드에 담겨서 전송됩니다. 일반적인 USB HID 키보드 보고서의 구조는 다음과 같습니다:
- 모디파이어 바이트 (1바이트):
- 보고서의 첫 번째 바이트에 모디파이어 키 상태가 담깁니다.
- 각 비트가 하나의 모디파이어 키에 해당하며, 이 비트가 1로 설정되면 해당 모디파이어 키가 눌린 상태임을 의미합니다.
- 예를 들어, Shift, Ctrl, Alt, GUI(윈도우 키) 같은 키가 모디파이어 바이트의 각 비트에 할당됩니다.
- 예약된 바이트 (1바이트):
- 두 번째 바이트는 보통 예약되어 있으며, 대부분의 경우 사용되지 않습니다.
- 키 코드 배열 (6바이트):
- 뒤의 6바이트가 일반 키 코드를 담는 배열입니다. 여기에는 눌린 일반 키 코드들이 최대 6개까지 들어갑니다.
간단히 정리하면:
- 보고서 구조: [모디파이어(1바이트), 예약(1바이트), 키 코드 배열(6바이트)]
- 모디파이어 위치: 첫 번째 바이트
- 일반 키 위치: 세 번째 바이트부터 시작하는 6개의 바이트 배열
따라서 모디파이어 키는 일반 키 코드 배열과는 별도로 첫 번째 바이트에 비트로 관리되므로, 6-Key Rollover 방식을 유지하면서도 모디파이어 상태를 쉽게 전송할 수 있습니다.
나의 말:
ChatGPT의 말:
무한키 입력(N-Key Rollover, NKRO)을 USB HID로 구현하려면, 표준 6키 제한을 넘는 추가적인 키 입력을 처리할 수 있도록 커스텀 HID 보고서를 만들어야 합니다. 여러 가지 방법이 있는데, 주요 방식은 다음과 같습니다:
1. 커스텀 HID 보고서 정의
- USB HID 표준을 확장하여, 일반적인 8바이트(1바이트 모디파이어 + 1바이트 예약 + 6바이트 키 배열) 대신 더 많은 바이트로 구성된 커스텀 보고서를 정의할 수 있습니다.
- 예를 들어, 16바이트로 정의된 보고서를 사용할 경우, 첫 1바이트를 모디파이어로, 나머지 15바이트를 키 코드로 사용할 수 있습니다.
- 이 방식은 HID 디스크립터에서 정의해줘야 하며, 이를 지원하는 소프트웨어가 필요할 수 있습니다. 일부 운영 체제나 소프트웨어에서는 기본적인 HID 디스크립터만 인식하기 때문에, 이를 지원하는 펌웨어와 드라이버가 필요할 수 있습니다.
2. 멀티 리포트 방식
- NKRO를 위해 HID 디스크립터에서 여러 개의 리포트(Report ID)를 생성하여, 각 리포트마다 다른 키 조합을 보내도록 할 수도 있습니다.
- 예를 들어, 첫 번째 리포트는 6개의 키만 보내고, 추가 리포트는 다음 6개의 키를 보내는 식으로 구성할 수 있습니다.
- 이러한 멀티 리포트 방식은 하나의 HID 디바이스에서 여러 개의 보고서를 동시에 전송하는 방식이므로, 일부 시스템에서 인식이 어려울 수 있습니다.
3. 신호 방식을 활용한 특수 보고서
- USB 인터페이스 외에 BLE(Bluetooth Low Energy)와 같은 무선 프로토콜을 활용하는 경우, 여러 개의 키 입력을 한번에 전송할 수 있는 방식으로 커스텀 프로토콜을 구축할 수 있습니다.
- ESP32는 BLE를 지원하므로, 무선으로 NKRO를 구현할 때 BLE 프로토콜에 맞춰 확장된 HID 보고서를 정의하여 무한 키 입력을 처리하는 것도 가능합니다.
4. 타임슬롯을 활용한 순차적 보고
- 커스텀 펌웨어를 작성하여 빠르게 반복 폴링하면서 모든 키 입력을 차례대로 전송하는 방법도 있습니다.
- 예를 들어, 무한 입력 상태가 발생할 경우 빠르게 여러 HID 보고서를 연속으로 전송해 모든 키 입력이 다 전달되도록 합니다.
- 이를 사용하면 어느 정도는 무한 입력에 가까운 성능을 낼 수 있지만, USB 폴링 주기나 전송 주기 제한으로 인해 완전한 무한 입력을 보장하지는 않습니다.
실용적인 선택
ESP32-S3로 구현할 때는 커스텀 HID 보고서 정의 방식이나 BLE를 통한 커스텀 NKRO 방식이 적합할 수 있습니다.
- USB로 구현할 경우, 커스텀 HID 보고서를 설정해 추가적인 키 입력을 전송하는 방법이 일반적입니다.
- 무선 BLE로 구현한다면 HID 보고서의 길이를 확장하여 NKRO를 위한 추가적인 키를 전송할 수 있습니다.
이 방식을 통해 무한 키 입력을 구현할 수 있으며, 운영체제가 지원하는 한 무한 키 입력에 가까운 성능을 확보할 수 있습니다.
나의 말:
ChatGPT의 말:
네, 맞습니다! 6-Key Rollover 방식이나 NKRO 방식 모두 폴링 주기 동안에 감지된 키 입력들을 배열에 담아서 보내는 방식은 동일하며, 차이점은 한번에 담을 수 있는 키 수의 제한에 있습니다.
공통점: 폴링 주기 동안 감지된 키를 배열에 담아 전송
- 폴링 주기 내에 눌린 키들은 배열에 차곡차곡 추가됩니다. 이 배열은 각 키 입력을 키 코드로 표현하여 순서대로 담습니다.
- 폴링 주기가 끝날 때마다 현재 배열에 담긴 키 코드들을 USB HID 보고서로 전송합니다.
차이점: 배열에 담을 수 있는 키 개수의 차이
- 6-Key Rollover 방식에서는 표준 USB HID 보고서가 제공하는 6개 키 슬롯 제한으로 인해, 최대 6개까지만 배열에 담아 전송할 수 있습니다. 6개 이상 키가 눌리면, 나머지 키는 해당 보고서에서 전송되지 않습니다.
- NKRO 방식에서는 HID 보고서의 배열 크기를 확장하거나 커스텀 방식으로 여러 키를 보낼 수 있게 하여, 실제로 눌린 모든 키를 배열에 담아 전송할 수 있습니다. 이때 배열 크기는 설정에 따라 달라질 수 있으며, 원하는 만큼 확장할 수 있습니다.
따라서, 폴링 주기 동안 키를 배열에 담고 전송하는 구조는 동일하지만, 한 번에 담을 수 있는 키 수의 차이가 6-Key Rollover와 NKRO 방식의 주요 차이점입니다.
나의 말:
ChatGPT의 말:
맞습니다! 일반적으로 폴링 주기는 모든 행(row)과 열(column)이 스캔을 완료하는 시점으로 설정하는 것이 기본입니다.
다만, 반응 속도와 정확도를 최적화하기 위해 몇 가지 기준을 고려하여 폴링 주기를 조정할 수 있습니다:
1. 폴링 주기 기준
- 타이밍 기준: 키보드에서는 일반적으로 1ms에서 10ms 사이의 폴링 주기를 사용합니다. 게이밍 키보드나 반응성이 중요한 경우에는 1ms 폴링 주기를 선호하고, 일반적인 경우 5~10ms도 무리가 없습니다.
- 스캔 루프 완료 시점: 모든 행과 열이 한 번씩 스캔되어 상태가 확인된 시점을 기준으로 폴링 주기를 설정합니다. 예를 들어, 매트릭스에 여러 행과 열이 있다면, 한 번의 루프가 끝나는 시점까지의 시간을 측정하여, 그 시간보다 약간 여유 있는 주기를 설정하는 것이 일반적입니다.
2. 최적 폴링 주기 설정을 위한 고려 사항
- 반응 속도: 짧은 폴링 주기는 빠른 반응을 제공하지만, 너무 짧게 설정하면 CPU 부하가 늘어날 수 있습니다.
- 디바운싱: 스위치 바운스를 안정적으로 처리할 수 있는 주기를 선택하는 것이 중요합니다. 디바운싱은 보통 5ms~10ms 내외로 충분하며, 폴링 주기를 너무 짧게 하면 디바운싱 과정에서 노이즈를 키 입력으로 오인할 수 있습니다.
- CPU 성능: ESP32 같은 마이크로컨트롤러에서는 빠른 폴링 주기도 무리 없이 지원할 수 있으므로, 키보드 반응성을 위해 1ms~5ms 정도의 주기를 설정해도 좋습니다.
3. ESP32에서의 폴링 주기 추천
- ESP32의 성능을 고려했을 때 5ms 이하의 폴링 주기에서도 충분히 스캔이 가능합니다. 게이밍처럼 반응 속도가 중요하다면 1ms, 일반적인 키보드 용도로는 5ms 정도를 추천합니다.
- 폴링 주기를 설정할 때, 타이머를 사용해 주기를 일정하게 유지하고, 각 스캔 루프에서 모든 행과 열이 완료되었는지 확인하는 방식이 효과적입니다.
정리하자면, 모든 행과 열이 스캔되는 완료 시점을 기준으로 하되, 1ms~5ms 범위 내에서 실험하며 최적의 반응성과 안정성을 얻을 수 있는 주기를 찾는 것이 좋습니다.
나의 말:
ChatGPT의 말:
키 스캔이 완료될 때마다 자동으로 보고하는 방식으로 코드를 구성하려면, 스캔 루프가 끝날 때 보고(전송) 함수를 호출하도록 하여 스캔이 완료되는 타이밍에 맞춰 전송하게끔 설정할 수 있습니다. 이렇게 하면 폴링 주기를 타이머로 고정하지 않고, 스캔 완료 시점마다 보고하도록 동작을 최적화할 수 있습니다.
코드 구성 방식
이 방식의 핵심은 스캔 루프와 보고 루프의 분리입니다. 스캔 루프가 각 키의 상태를 매트릭스에서 확인하고 모든 행과 열을 확인했을 때, 그 상태를 보고서에 담아 한 번에 전송하는 구조로 구성합니다.
1. 키 스캔 함수 작성
- 각 행을 활성화하고, 열을 읽어 키 상태를 확인합니다.
- 이 과정이 끝나면 키 상태를 배열에 저장해둡니다.
2. 상태 변화 감지 및 보고 함수
- 스캔 루프가 끝날 때마다 현재 키 상태 배열과 이전 키 상태 배열을 비교해 변경 사항이 있으면 이를 보고서로 만들어 전송합니다.
- 키 상태가 변경되었을 때에만 보고를 하도록 하면, 불필요한 보고를 줄일 수 있습니다.
3. 루프 구성 예시
- 아래는 스캔이 완료될 때마다 키 상태를 보고하는 코드의 예시입니다:
설명
- scanKeys() 함수:
- 키 매트릭스의 각 행을 순차적으로 활성화하고, 그에 연결된 열 상태를 확인해 keyState 배열에 기록합니다.
- reportKeys() 함수:
- keyState와 prevKeyState를 비교하여 키 상태가 변경되었는지 확인합니다.
- 변경이 있으면 sendHIDReport() 함수를 호출하여 키 상태를 USB HID 보고서로 전송합니다.
- 전송 후 keyState 값을 prevKeyState에 복사하여 다음 루프에서 변경 사항을 추적할 수 있도록 합니다.
- 메인 루프(loop):
- 스캔과 보고를 반복하여 스캔 완료 시마다 보고가 이루어지도록 합니다.
이렇게 하면 각 스캔 루프가 끝날 때마다 변경 사항을 즉시 보고할 수 있어 폴링 주기를 별도로 고정할 필요 없이 동작할 수 있습니다.