RX62NでUSBキーボードを作る


![1649094980671.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/369631/e41e969c-ca30-54a8-92f4-d6665080de57.jpeg)


　USB PHID（Peripheral HID）のドライバとして，Renesas提供のサンプルアプリケーションを流用する（an_r01an0401xx0233_usb）．
　従って，自前で実装する処理は，
1. ダイナミックスキャンでキー押下を読み取る
1. 押下されたキーに応じてReport Descriptorを設定
1. ドライバで用意されたUSB_Write処理を行う（実際には定期的に呼ばなければならないPHIDの処理`usb_loop`内で送信される．この関数は，キー入力の有無によらず定期的に呼ぶ．送信するキャラクタが設定されている場合，その文字を送信する処理を入れてある）
となる．

　ソフトウェアでチャタリング除去するのに加え，キーを押しっぱなしにした場合のリピート処理も入れる．


### 環境
* Windows 10
* e2studio
* ccrx v3.02.00

### キーコードはReport Descriptorで設定
　Report Descriptorは数バイトで構成される．必要なのは，
* Modifier keyを設定する第0バイト
* Key Codeを設定する第2バイト
だけである．
　Modifier キー（例えばシフトキー）だけを押した場合，Modifier keyを設定した後，Usage IDには`0x00`を設定して送信する．この処理は無くても大概問題ないが，`Shift`＋`Control`＋`Alt`などに何らかの機能を割り当てたい場合に，Modifier Keyを押下したという情報が必要なためである．

#### Modifier key

Bit | Value | Key
---|---|---
0 | 0x01 | LEFT CTRL
1 | 0x02 | LEFT SHIFT
2 | 0x04 | LEFT ALT
3 | 0x08 | LEFT GUI(Windows)
4 | 0x10 | RIGHT CTRL
5 | 0x20 | RIGHT SHIFT
6 | 0x40 | RIGHT ALT
7 | 0x80 | RIGHT GUI(Windows)

#### Usage ID
　USB HIDでは各キーに対応するコードをUsage IDとして規定している．PC（WindowsならHID.dllによって）では，Usage IDがスキャンコードに変換される<sup>（3）</sup>．

Value | Key
---|---
0x00 | データなし
0x04～0x1d | a～z
0x1F | 2 及び @
0x20 | 3 及び #
0x21 | 4 及び $
0x22 |5 及び %
0x23 |6 及び ^
0x24 |7 及び &
0x25 |8 及び *
0x26 |9 及び (
0x27 |0 及び )
0x28 | Return (ENTER)
0x29 | ESCAPE	
0x2A | DELETE (Backspace)
0x2B | Tab
0x2C | （スペースバー）
0x2D | - 及び _
0x2E | = 及び +
0x3A～0x45 | F1～F12
0x59～0x62 | テンキー1～テンキー0

<small>※通常テンキー（key pad）のキーは別なキーとして扱う</small>

### 回路図
　RX62Nマイコンボードと，転がっていたキーボード基板とを組み合わせて使う．あまり手を加えたくないので（面倒なので）小変更で済むように検討した結果，次のような配線となった．
![rx62n.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/369631/88b9cd39-109b-6b3f-9548-a35d8094b4a0.png)

### プログラム
　ルネサスのサンプル・アプリケーションPHIDを利用する．このサンプル・アプリケーションは，echoとkeyboardというサンプルを含む．keyboardとして利用するため，`r_usb_phid_apl.h`で設定する．
　付属のドキュメントにPHIDデバイスとして動作する流れが（状態遷移）詳しく書いてあるので読んでおくとよい．
　そして，幾つかのソースコードを書き換える．


まずはメインのソースコード．必要な処理を書く．

```main.c
#include "platform.h"
#include "stdio.h"
#include "r_usb_basic_if.h"
#include "r_usb_phid_apl.h"

extern int8_t usb_loop(void);
extern void usb_phid_init(void);
extern uint8_t usb_send_char;
extern uint8_t usb_send_modifier;

#define ROW_NUMBER  4
#define COLUMN_NUMBER  10
#define KEY_REPEAT_FIRST_INTERVAL 32000
#define KEY_REPEAT_SECOND_INTERVAL 12000
#define ANTI_CHATTER 40

const uint8_t charReference[2][ROW_NUMBER][COLUMN_NUMBER] = { { //without Fn key
		{ KBD_CODE_p, KBD_CODE_o, KBD_CODE_i, KBD_CODE_u, KBD_CODE_y,
				KBD_CODE_t, KBD_CODE_r, KBD_CODE_e, KBD_CODE_w, KBD_CODE_q }, {
				KBD_CODE_ENTER, KBD_CODE_RSF, KBD_CODE_SPACE, KBD_CODE_m,
				KBD_CODE_n, KBD_CODE_b, KBD_CODE_v, KBD_CODE_c, KBD_CODE_x,
				KBD_CODE_z }, { KBD_CODE_0, KBD_CODE_9, KBD_CODE_8, KBD_CODE_7,
				KBD_CODE_6, KBD_CODE_5, KBD_CODE_4, KBD_CODE_3, KBD_CODE_2,
				KBD_CODE_1 }, { KBD_CODE_BS, KBD_CODE_l, KBD_CODE_k, KBD_CODE_j,
				KBD_CODE_h, KBD_CODE_g, KBD_CODE_f, KBD_CODE_d, KBD_CODE_s,
				KBD_CODE_a } }, {  //with Fn key
				{ KBD_CODE_F1, KBD_CODE_F2, KBD_CODE_F3, KBD_CODE_F4,
省略
						KBD_CODE_0, KBD_CODE_0, KBD_CODE_0, KBD_CODE_0 } }, };

void kbd_init(void) {
	usb_loop();	usb_loop();	usb_loop();	usb_loop();

	PORTD.DDR.BYTE = 0x0F;
	PORTE.DDR.BYTE = 0x00;
	PORTD.PCR.BYTE = 0xF0;
	PORTE.PCR.BYTE = 0xFF;  //pull up
	PORT0.DDR.BIT.B7 = 0;
}

uint16_t keysCount[ROW_NUMBER][COLUMN_NUMBER]; //to cut chuttering & to key repeating
uint16_t keyCountFn = 0;
uint8_t fnKeyState = 0;

uint8_t sendKeyCodes[8];  //this is ring buffer for storing key codes up to 8
int8_t keyInputPos = 0;
int8_t keySendPos = 0;

void kbdScan(void) {
	uint8_t row_port = 0x01;

	if (PORT0.PORT.BIT.B7 == 0) {
		keyCountFn++;
		if (keyCountFn == ANTI_CHATTER) {
			fnKeyState = 1;
		}
	} else {
		keyCountFn = 0;
		fnKeyState = 0;
	}

	for (int i = 0; i < ROW_NUMBER; i++) {
		row_port = 0x01 << i;
		row_port ^= 0x0F;
		PORTD.DR.BYTE = row_port;
		for (uint16_t k = 0; k < 100; k++)
			;
		uint16_t keyMatrixValue = ((PORTD.PORT.BIT.B7) << 9)
				| ((PORTD.PORT.BIT.B4) << 8) | PORTE.PORT.BYTE;
		keyMatrixValue ^= 0xFFFF;
		uint16_t mask_bit = 0;
		for (int8_t j = 0; j < COLUMN_NUMBER; j++) {
			mask_bit = 0x01 << j;

			if (keyMatrixValue & mask_bit) {
				keysCount[i][j]++;
				if (charReference[fnKeyState][i][j] > 128) { //checking modifier key or not
					usb_send_modifier |= 0x20;
					continue;
				}
				if (keysCount[i][j] == ANTI_CHATTER) {
					sendKeyCodes[keyInputPos++] = charReference[fnKeyState][i][j];
					keyInputPos %= 8;
					PORT1.DR.BIT.B5 ^= 1;  //on board LED blink

				} else if (keysCount[i][j] > KEY_REPEAT_SECOND_INTERVAL) { // for typing continuously
					keysCount[i][j] = KEY_REPEAT_FIRST_INTERVAL;
					sendKeyCodes[keyInputPos++] = charReference[fnKeyState][i][j];
					keyInputPos %= 8;

				} else if (keysCount[i][j] > KEY_REPEAT_FIRST_INTERVAL) { // for typing continuously
					sendKeyCodes[keyInputPos++] = charReference[fnKeyState][i][j];
					keyInputPos %= 8;
				}
			} else {
				keysCount[i][j] = 0;
				if (charReference[fnKeyState][i][j] > 128) { //checking modifier key or not
					switch (charReference[fnKeyState][i][j]) {
					case 0xE5:  //shift detouch
						usb_send_modifier = usb_send_modifier & 0xDF;
						break;
					}
				}
				continue;
			}
		}
	}
}

void sendKeyProcess(void) {
	if (usb_loop() != USB_STS_NONE)
		return;
	if (keySendPos == keyInputPos)
		return;

	uint8_t key = sendKeyCodes[keySendPos++];
	keySendPos %= 8;

	usb_send_char = key;
	usb_loop();
}

void main(void) {
	usb_phid_init();
	PORT1.DDR.BIT.B5 = 1;  //On Board LED
	PORT1.DR.BIT.B5 = 0;
	kbd_init();

	while (1) {
		kbdScan();
		sendKeyProcess();
	}
}
```

　次に，ルネサスがサンプルとして用意しているキーボード・ドライバのソースコードを書き換える．
　書き換えるのは，元々usb_main()という名称だった関数ですが，関数名は変更した．そしてHIDキーボードとしてPCに文字を送れるように，この関数に処理を追加した．アプリケーションのメインループからは，`uint8_t usb_send_char`に送りたい文字を設定してから，`usb_loop()`を呼ぶ．
　送る文字がない場合でも定期的に`usb_loop()`を呼ばないとPCとの接続が切られてしまう．

```r_usb_phid_keyboard_apl.c
省略
int8_t usb_loop(void) {  //旧usb_main()
	int8_t status;
	switch (status = R_USB_GetEvent(&ctrl)) {
	case USB_STS_CONFIGURED:
		g_status = NO_WRITING;
		break;

	case USB_STS_WRITE_COMPLETE:
		if (DATA_WRITING == g_status) {
			g_status = ZERO_WRITING;
			R_USB_Write(&ctrl, (uint8_t*) g_zero_data, DATA_LEN); /* Sending the zero data (8 bytes) */
		} else {
			g_status = NO_WRITING;
			status = USB_STS_SECOND_WRITE;
		}
		break;

	case USB_STS_REQUEST: /* Receive Class Request */
		if (USB_SET_REPORT == (ctrl.setup.type & USB_BREQUEST)) {
			R_USB_Read(&ctrl, (uint8_t*) &g_numlock, 2); /* Get the NumLock data (NumLock data is not used) */
		}
		if (USB_GET_IDLE == (ctrl.setup.type & USB_BREQUEST)) {
			R_USB_Write(&ctrl, &g_idle, 1);
		} else if (USB_GET_DESCRIPTOR == (ctrl.setup.type & USB_BREQUEST)) {
			if (USB_GET_REPORT_DESCRIPTOR == ctrl.setup.value) {
				R_USB_Write(&ctrl, (uint8_t*) g_apl_report, 76);
			} else if (USB_GET_HID_DESCRIPTOR == ctrl.setup.value) {
				/* Configuration Discriptor address set. */
				R_USB_Write(&ctrl, (uint8_t*) &g_apl_configuration[18], 9);
			} else {
			}
		} else {
		}
		break;

	case USB_STS_REQUEST_COMPLETE: /* Complete Class Request */
		if (USB_SET_IDLE == (ctrl.setup.type & USB_BREQUEST)) {
			p_idle_value = (uint8_t*) &ctrl.setup.value;
#if USB_CFG_ENDIAN == USB_CFG_LITTLE
			g_idle = p_idle_value[1];
#else   /* USB_CFG_ENDIAN == USB_CFG_LITTLE */
                    g_idle = p_idle_value[0];
#endif  /* USB_CFG_ENDIAN == USB_CFG_LITTLE */
		} else if (USB_SET_PROTOCOL == (ctrl.setup.type & USB_BREQUEST)) {
		} else {
		}
		break;

	case USB_STS_SUSPEND:
	case USB_STS_DETACH:
#if defined(USE_LPW)
		low_power_mcu();
#endif /* defined(USE_LPW) */
		break;

	case USB_STS_NONE: /* No event */

#if !defined(USE_LPW)
                if (0 != (sw_data & SW1_PUSH))
                {
                    ctrl.module = USE_USBIP;
                    ret_code = R_USB_GetInformation(&ctrl, &info);
                    if (USB_SUCCESS == ret_code)
                    {
                        if (USB_STS_SUSPEND == info.status)
                        {
                            ctrl.type   = USB_PHID;
                            R_USB_Resume(&ctrl);
                        }
                    }
                }
#endif /* !defined(USE_LPW) */

		if (usb_send_char) {
			if (NO_WRITING == g_status) {
				//get_hid_data(sw_data, g_data);
				*(g_data + 2) = usb_send_char;
				usb_send_char = 0;
				g_status = DATA_WRITING;
				ctrl.type = USB_PHID;
				R_USB_Write(&ctrl, g_data, DATA_LEN);
			}
		}
		break;
	}
	return status;
}
省略
```

　ヘッダも書き換える．

```r_usb_phid_apl.h
#include <string.h>
#include "r_usb_basic_if.h"
#include "r_usb_phid_if.h"
#include "r_usb_rsk_keydriver.h"
#include "r_usb_rsk_lowpower.h"

/******************************************************************************
Macro definitions
******************************************************************************/
#define NUM_STRING_DESCRIPTOR (7u)

#define USB_ECHO                (0)                 /* Loop back(Echo) mode */
#define USB_KEYBOARD            (1)                 /* Keyboard mode */

#define NO_WRITING              (0)
#define DATA_WRITING            (1)
#define ZERO_WRITING            (2)

#define KBD_CODE_a              (0x04)      /* a */
#define KBD_CODE_b				(0x05)
#define KBD_CODE_c              (0x06)  
省略
#define KBD_CODE_y              (0x1C)      /*  */
#define KBD_CODE_z              (0x1D)      /* a */
#define KBD_CODE_1				(0x1E)
#define KBD_CODE_2              (0x1F)      /*  */
#define KBD_CODE_3              (0x20)      /*  */
省略
#define KBD_CODE_9              (0x26)      /*  */
#define KBD_CODE_0              (0x27)      /* a */

#define KBD_CODE_ENTER			(0x28)

#define KBD_CODE_BS				(0x2A)
#define KBD_CODE_SPACE			(0x2C)

#define KBD_CODE_bsla			(0x31)
//#define KBD_CODE_kamma		(0x32) //NON-US
#define KBD_CODE_semi			(0x33)
#define KBD_CODE_quote			(0x34)


/******************************************************************************
Exported global variables
******************************************************************************/
extern  const   uint8_t g_apl_device[];
extern  const   uint8_t g_apl_configuration[];
extern  const   uint8_t *g_apl_string_table[];
extern  const   uint8_t g_apl_report[];
```

参考文献
（1）USB規格の情報；USB Implementers Forum
https://www.usb.org/developers
（2）
https://www.usb.org/sites/default/files/hid1_11.pdf
（3）USB HID Usage ID；capyBaral - bsakatu.net
https://bsakatu.net/doc/usb-hid-to-scancode/
（4）オープンドレイン；組込みソフトウェア研究室
https://www.haljion.net/index.php/2019-11-13-03-27-18/119-2019-11-13-03-22-16/rx-62n/223-led-i-o