TinyUSBをGR-ROSEで動かす

# TinyUSBをGR-ROSEで動かす

TinyUSBはArduino IDEでもサポートされるUSBスタックです。

https://github.com/hathach/tinyusb

GR-ROSEのCPU RX65Nもサポートデバイスに入っていますが、ボードとして直接の対応はありません。
また、ソースコードデバッグも行えるようIDEのe2studioで扱えるようにしましたので、その手順を残しておこうと思います。
この記事はWindowsが対象になります。

大まかな流れは、下記のようになります。

- TinyUSBを標準の方法でビルドする（GR-ROSE向けでは無い）
- ビルド結果から必要なソースコードだけをコピーする（e2studioはフォルダの下にあるファイルがビルド対象となるため、不要なファイルを省きたい）
- e2studioのプロジェクトをGR-ROSE向けに作成する
- GR-ROSE向けにIOレジスタの設定を変更する

## ビルド環境を構築する

TinyUSBのビルドには``make``を使うので、MSYS2環境でビルドを行います。
MSYS2など必要なソフトのリンクを書いておきました。

### MSYS2のインストール

下記のサイトからインストーラをダウンロードして、インストールします。

<https://www.msys2.org/>

パッケージをアップデートします。

```bash
$ pacman -Syu
$ pacman -Su
```

``make``コマンドをインストールします。

```bash
$ pacman -S make
```

``git``も必要なので、ない場合はインストールします。

```bash
$ pacman -S git
```

### RX向けGCCのインストール

下記のサイトからダウンロードし、インストールします。
ダウンロードには、CyberTHORのアカウントが必要です。

<https://llvm-gcc-renesas.com/ja>

### e2studioのインストール

下記のサイトから「統合開発環境e² studio 2021-10 Windows用インストーラ」を探してダウンロードし、インストールします。
ダウンロードには、ルネサスのアカウントが必要です。

<https://www.renesas.com/jp/ja/software-tool/e-studio#download>

## TinyUSBの標準の手順でビルドする

まず標準の手順で、サポートのある[Renesas RX65N Target Board](https://www.renesas.com/us/en/products/microcontrollers-microprocessors/rx-32-bit-performance-efficiency-mcus/rtk5rx65n0c00000br-target-board-rx65n)向けのビルドをしてみます。

[Getting Started](https://github.com/hathach/tinyusb/blob/master/docs/reference/getting_started.rst)を参考に進めます。

適当な作業フォルダを作り、MSYS2のbashで開いて作業フォルダに移動します。

msys bashの起動方法は、コマンドプロンプトで次のコマンドを打ちます。``rx-elf-gcc``のパスを通しておく必要があります。

```
C:\Users\user>PATH=%PATH%;C:\ProgramData\GCC for Renesas RX 8.3.0.202102-GNURX-ELF\rx-elf\rx-elf\bin
C:\Users\user>set CHERE_INVOKING=1
C:\Users\user>set MSYS2_PATH_TYPE=inherit
C:\Users\user>set MSYSTEM=MINGW64
C:\Users\user>C:\msys64\usr\bin\bash.exe --login -i
```

[VSCode](https://code.visualstudio.com/)を使っている人は、上記の設定を``*.code-workspace``で用意しておくのもいいかもしれません。

以降は``bash``のプロンプトでの作業です。TiinyUSBのコードをクローンします。

```bash
$ git clone https://github.com/hathach/tinyusb tinyusb
$ cd tinyusb
```

``examples``の中にいくつかのお手本があります。
今回はマウスを作りたいので、HIDの中から``hid_boot_interface``で説明していきます。

```bash
$ cd examples/device/hid_boot_interface
```

ビルドするにはターゲットボードを指定する必要があります。
この値の候補は``hw/bsp``の下にあるフォルダ名を指定します。
RX65N向けには``hw/bsp/rx/boards/rx65n_target``があるので、``rx65n_target``を指定します。

```bash
$ make BOARD=rx65n_target
```

``examples/device/hid_boot_interface/_build/rx65n_target``に、``hid_boot_interface.bin``ファイルが出来ていれば成功です。

## e2studioでビルド出来るようにする

上記で出来た実行ファイルはGR-ROSE用ではないので動きませんが、ビルドに必要な情報が集まりました。
ビルド対象の``*.c``はコンパイルされ、``_build``の下に``*.o``として保存されるので、``*.o``の一覧を見ればビルド対象のソースコードが特定できます。
またTinyUSBでは、``*.o``が保存されるフォルダ構成は、``*.c``のフォルダ構成と同じなので、フォルダ構成も収集できます。

### ソースコードを収集する

クローンしたフォルダから、ビルドに必要なファイルだけ別のフォルダにコピーする手順を説明します。
コピーするファイル一覧を作り、コピーコマンドにしてバッチファイルを作り、そのバッチファイルを実行してコピーします。
バッチファイルの作成には、[サクラエディタ](https://sakura-editor.github.io/)を使用します。

コピー先のフォルダは、クローンしたフォルダ``tinyusb``と同レベルのフォルダ``copyed``として説明します。

まず、``_build``の下の``*.o``をエクスプローラで検索します。

![オブジェクトファイル収集.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242320/82474f6a-c1d8-3f42-8b05-edc636e3d437.png)

検索結果のファイルを``Ctrl+A``で全選択し、``Ctrl+C``でクリップボードにコピーします。
そしてサクラエディタの新規ファイルに``Ctrl+V``で貼り付けると、ファイルパスの一覧が貼り付きます。
文字列置換や矩形選択などで、``tinyusb``フォルダ以前のパスを削除します。例えば``D:\Github\tinyusb\src\tusb.c``を``src\tusb.c``とします。
このファイル一覧を正規表現を使った文字列置換で、コピーコマンドに変換します。
置換前は``^(.+)\\([^\\]+)\.o``で、置換後は``xcopy ..\\tinyusb\\$1\\$2.c $1\\``としてすべて置換します。

![バッチファイルの作成](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242320/5177c8c4-3434-9bd5-82b0-fae8fe5ac4ae.png)

これをコピー先のフォルダ``copyed``に``copy_c.bat``として保存し、サクラエディタで``Ctrl+B``でバッチファイルを実行し``*.c``をコピーします。

次は、``*.h``を抽出します。
``*.c``ファイルからインクルードされたファイルは、コンパイラによって``*.d``に依存関係として保存されています。
Grepで``*.d``を対象に、``(.+)(\\|/)([^\\/]+)\.h``で検索します。

![edit_batchfile_for_copy3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242320/58246296-e676-60af-fef3-5f5ab328aced.png)

検索結果を編集して、``*.h``のパスの一覧にします。文字列置換で``^.+\([0-9]+,[0-9]+\) +\[\w+\]: *``でGrepしたファイルパスの部分を削除します。次に`` *\\$``で行最後の``\``を削除します。

![edit_batchfile_for_copy4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242320/fe850516-61da-0d6e-2670-3688ccaee1be.png)

検索結果は重複しているので、``Ctrl+A``で全選択して、メニューから「編集」→「整形」→「選択範囲の昇順ソート」を選択します。

![edit_batchfile_for_copy7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242320/a9ff8687-568f-13a0-56d0-97adda905854.png)

続けて「編集」→「整形」→「連続した重複行の削除」を選択して、依存しているファイル一覧を得ます。

![edit_batchfile_for_copy8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242320/275d26fe-04a0-e0e4-a5a3-b5213310f95f.png)

このうちTinyUSBのフォルダにある物だけ残して、GCCの標準ライブラリのパスなど他の行を削除します。
``/``を``\``に置換したり、絶対パスの部分を矩形選択で削除して、``*.c``の時と同様に正規表現で``^(.+)\\([^\\]+)\.h``を``xcopy ..\\tinyusb\\$1\\$2.h $1\\``に置換します。

これを、コピー先のフォルダ``copyed``に``copy_h.bat``として保存し、サクラエディタで``Ctrl+B``でバッチファイルを実行し``*.h``をコピーします。

これで、e2studioのプロジェクトに使うファイルが収集できました。

## e2studioのプロジェクト作成

e2studioを起動します。ワークスペースに適当なフォルダを入力します。このフォルダの中は空にしておきます。

![e2studio1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242320/d88c1011-b7d1-bf35-4604-a0b9b1e56ed6.png)

次に、新規のプロジェクトを作成します。

![e2studio2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242320/8b472c54-32df-60bb-e1ee-f3658297b880.png)

GCC-RXの実行ファイルプロジェクトを選択します。

![e2studio3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242320/3158a6df-74b8-9c12-96f1-60d54d9d9fdf.png)

プロジェクト名は``hid_boot_interface``としておきます。

![e2studio4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242320/1aeed9ad-a886-a70e-f6d9-ba68081a4765.png)

ターゲットデバイスをR5F565NEHxFPにします。

![e2studio5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242320/83596471-d02f-caa5-c56c-45310bea583f.png)

今回はスマートコンフィギュレーターは使いません。

![e2studio6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242320/2379ab06-0b4b-195b-a8e1-f00b87c42df6.png)

CPUオプションで必要があれば変更します。

![e2studio7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242320/1d33f964-ea0d-2875-480c-ecc99f983feb.png)

C標準ライブラリは、NewlibのPre-Buildで大丈夫です。

![e2studio8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242320/b442299c-8b4c-dd7c-8726-834d15634682.png)

必要なファイルが生成されます。

![e2studio9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242320/c9d0e91b-f0b6-7d29-c2b8-a78978cd7610.png)

プロジェクトが開かれると下記の様になります。

![e2studio10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242320/84ba06ac-0a3c-981a-137d-ab4dfbd40f5f.png)

上記で抽出した``copyed``フォルダをプロジェクトフォルダにコピーして``tinyusb``に名前を変更します。

![tinyusb_copy1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242320/8a6db411-7db6-67ef-73c5-0a9cafb1d6c5.png)

追加したフォルダはビルド対象になっていないので、ビルド対象にします。

![e2studio11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242320/657cdbd6-719c-2a6d-6fb5-aa7f964ab84e.png)

下の画面で、「全て選択を解除」ボタンを押します。

![e2studio12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242320/96f0a19b-7a7a-813d-4eb1-6714bafc8690.png)

``generate/hwinit.c``はビルドから除外します。``tinyusb/hw/bsp/rx/boards/rx65n_target/rx65n_target.c``にある関数と同じ関数名で空の定義があるためです。
また、``hw/mcu/renesas/rx/rx65n/*.*``にあるファイルは``generate/*.*``にもあり、``generate``側を使うため``hw/mcu``フォルダをビルドから除外します。

インクルードパスの設定と、マクロ定義を設定します。マクロ定義は``hw/bsp/rx/boards/rx65n_target/board.mk``に書かれています。

![e2studio13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242320/9ace0486-eaf6-aed7-536c-59c45bc30d5a.png)

GR-ROSEに書き込むには``*.bin``ファイルが欲しいので、Objcopyの設定を「Raw binary」に変更します。

![e2studio15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242320/013b7ad1-7c49-d6a1-f025-d66deef2f402.png)

## ソースコードのGR-ROSE対応

ターゲットボード向け実装のある``tinyusb/hw/bsp/rx/boards/rx65n_target/rx65n_target.c``を編集します。
シリアル通信はGR-ROSEのPMOD端子にあるシリアルを使用するよう変更します。ここは``SCI1``となっていますので、元のファイルにある``SCI5``を``SCI1``に文字列置換します。その他関連する定義も置換します。

|置換前|置換後|
|-|-|
|``SCI5``|``SCI1``|
|``TXI5``|``TXI1``|
|``RXI5``|``RXI1``|
|``TEI5``|``TEI1``|

システムクロック設定が違っているので下記の箇所を、

```c
  SYSTEM.SOSCCR.BYTE = 1;

  if (SYSTEM.HOCOCR.BYTE) {
    SYSTEM.HOCOCR.BYTE = 0;
    while (!SYSTEM.OSCOVFSR.BIT.HCOVF) ;
  }
  SYSTEM.PLLCR.WORD  = 0x1D10u; /* HOCO x 15 */
```

次のように書き換えます。

```c
  SYSTEM.MOFCR.BYTE = 0x20;
  SYSTEM.MOSCWTCR.BYTE = 0x53;
  SYSTEM.MOSCCR.BYTE = 0x0;
  while (SYSTEM.MOSCCR.BIT.MOSTP) ;

  SYSTEM.PLLCR.WORD  = 0x2700u; /* HOCO x 20 */
```

シリアルの端子やLEDなどの端子が違っているので、次の箇所を、

```c
void board_init(void)
{
  /* setup software configurable interrupts */
  ICU.SLIBR_USBI0.BYTE = IRQ_USB0_USBI0;
  ICU.SLIPRCR.BYTE     = 1;

#if CFG_TUSB_OS == OPT_OS_NONE
  /* Enable CMT0 */
  SYSTEM.PRCR.WORD = SYSTEM_PRCR_PRKEY | SYSTEM_PRCR_PRC1;
  MSTP(CMT0)       = 0;
  SYSTEM.PRCR.WORD = SYSTEM_PRCR_PRKEY;
  /* Setup 1ms tick timer */
  CMT0.CMCNT      = 0;
  CMT0.CMCOR      = CMT_PCLK / 1000 / 128;
  CMT0.CMCR.WORD  = CMT_CMCR_CMIE | CMT_CMCR_CKS_DIV_128;
  IR(CMT0, CMI0)  = 0;
  IPR(CMT0, CMI0) = IRQ_PRIORITY_CMT0;
  IEN(CMT0, CMI0) = 1;
  CMT.CMSTR0.BIT.STR0 = 1;
#endif

  /* Unlock MPC registers */
  MPC.PWPR.BIT.B0WI  = 0;
  MPC.PWPR.BIT.PFSWE = 1;
  // SW PB1
  PORTB.PMR.BIT.B1 = 0U;
  PORTB.PDR.BIT.B1 = 0U;
  // LED PD6
  PORTD.PODR.BIT.B6 = 1U;
  PORTD.ODR1.BIT.B4 = 1U;
  PORTD.PMR.BIT.B6  = 0U;
  PORTD.PDR.BIT.B6  = 1U;
  /* UART TXD5 => PA4, RXD5 => PA3 */
  PORTA.PMR.BIT.B4 = 1U;
  PORTA.PCR.BIT.B4 = 1U;
  MPC.PA4PFS.BYTE  = 0b01010;
  PORTA.PMR.BIT.B3 = 1U;
  MPC.PA5PFS.BYTE  = 0b01010;
  /* USB VBUS -> P16 */
  PORT1.PMR.BIT.B6 = 1U;
  MPC.P16PFS.BYTE  = MPC_PFS_ISEL | 0b10001;
  /* Lock MPC registers */
  MPC.PWPR.BIT.PFSWE = 0;
  MPC.PWPR.BIT.B0WI  = 1;

  /* Enable SCI1 */
  SYSTEM.PRCR.WORD   = SYSTEM_PRCR_PRKEY | SYSTEM_PRCR_PRC1;
  MSTP(SCI1)         = 0;
  SYSTEM.PRCR.WORD   = SYSTEM_PRCR_PRKEY;
  SCI1.SEMR.BIT.ABCS = 1;
  SCI1.SEMR.BIT.BGDM = 1;
  SCI1.BRR           = (SCI_PCLK / (8 * 115200)) - 1;
  IR(SCI1,  RXI1)    = 0;
  IR(SCI1,  TXI1)    = 0;
  IS(SCI1,  TEI1)    = 0;
  IR(ICU, GROUPBL0)  = 0;
  IPR(SCI1, RXI1)    = IRQ_PRIORITY_SCI1;
  IPR(SCI1, TXI1)    = IRQ_PRIORITY_SCI1;
  IPR(ICU,GROUPBL0)  = IRQ_PRIORITY_SCI1;
  IEN(SCI1, RXI1)    = 1;
  IEN(SCI1, TXI1)    = 1;
  IEN(ICU,GROUPBL0)  = 1;
  EN(SCI1, TEI1)     = 1;

  /* setup USBI0 interrupt. */
  IR(USB0, USBI0)  = 0;
  IPR(USB0, USBI0) = IRQ_PRIORITY_USBI0;
}

//--------------------------------------------------------------------+
// Board porting API
//--------------------------------------------------------------------+

void board_led_write(bool state)
{
  PORTD.PODR.BIT.B6 = state ? 0 : 1;
}

uint32_t board_button_read(void)
{
  return PORTB.PIDR.BIT.B1 ? 0 : 1;
}
```

下記のように変更します。

```c
void board_init(void)
{
  /* setup software configurable interrupts */
  ICU.SLIBR_USBI0.BYTE = IRQ_USB0_USBI0;
  ICU.SLIPRCR.BYTE     = 1;

#if CFG_TUSB_OS == OPT_OS_NONE
  /* Enable CMT0 */
  SYSTEM.PRCR.WORD = SYSTEM_PRCR_PRKEY | SYSTEM_PRCR_PRC1;
  MSTP(CMT0)       = 0;
  SYSTEM.PRCR.WORD = SYSTEM_PRCR_PRKEY;
  /* Setup 1ms tick timer */
  CMT0.CMCNT      = 0;
  CMT0.CMCOR      = CMT_PCLK / 1000 / 128;
  CMT0.CMCR.WORD  = CMT_CMCR_CMIE | CMT_CMCR_CKS_DIV_128;
  IR(CMT0, CMI0)  = 0;
  IPR(CMT0, CMI0) = IRQ_PRIORITY_CMT0;
  IEN(CMT0, CMI0) = 1;
  CMT.CMSTR0.BIT.STR0 = 1;
#endif

  /* Unlock MPC registers */
  MPC.PWPR.BIT.B0WI  = 0;
  MPC.PWPR.BIT.PFSWE = 1;
  // SW PD7
  PORTD.PMR.BIT.B7 = 0U;
  PORTD.PDR.BIT.B7 = 0U;
  // LED PA0, PA1
  PORTA.PODR.BIT.B0 = 0U;
  PORTA.PODR.BIT.B1 = 0U;
  PORTA.PMR.BIT.B0  = 0U;
  PORTA.PMR.BIT.B1  = 0U;
  PORTA.PDR.BIT.B0  = 1U;
  PORTA.PDR.BIT.B1  = 1U;
  /* UART TXD1 => P26, RXD1 => P30 */
  PORT2.PMR.BIT.B6 = 1U;
  PORT2.PDR.BIT.B6 = 1U;
  PORT2.PCR.BIT.B6 = 1U;
  MPC.P26PFS.BYTE  = 0b01010;
  PORT3.PMR.BIT.B0 = 1U;
  PORT3.PDR.BIT.B0 = 0U;
  MPC.P30PFS.BYTE  = 0b01010;
  /* USB VBUS -> P16 */
  PORT1.PMR.BIT.B6 = 1U;
  MPC.P16PFS.BYTE  = MPC_PFS_ISEL | 0b10001;
  /* Lock MPC registers */
  MPC.PWPR.BIT.PFSWE = 0;
  MPC.PWPR.BIT.B0WI  = 1;

  /* Enable SCI1 */
  SYSTEM.PRCR.WORD   = SYSTEM_PRCR_PRKEY | SYSTEM_PRCR_PRC1;
  MSTP(SCI1)         = 0;
  SYSTEM.PRCR.WORD   = SYSTEM_PRCR_PRKEY;
  SCI1.BRR           = (SCI_PCLK / (32 * 115200)) - 1;
  IR(SCI1,  RXI1)    = 0;
  IR(SCI1,  TXI1)    = 0;
  IS(SCI1,  TEI1)    = 0;
  IR(ICU, GROUPBL0)  = 0;
  IPR(SCI1, RXI1)    = IRQ_PRIORITY_SCI1;
  IPR(SCI1, TXI1)    = IRQ_PRIORITY_SCI1;
  IPR(ICU,GROUPBL0)  = IRQ_PRIORITY_SCI1;
  IEN(SCI1, RXI1)    = 1;
  IEN(SCI1, TXI1)    = 1;
  IEN(ICU,GROUPBL0)  = 1;
  EN(SCI1, TEI1)     = 1;

  /* setup USBI0 interrupt. */
  IR(USB0, USBI0)  = 0;
  IPR(USB0, USBI0) = IRQ_PRIORITY_USBI0;
}

//--------------------------------------------------------------------+
// Board porting API
//--------------------------------------------------------------------+

void board_led_write(bool state)
{
  PORTA.PODR.BIT.B0 = state ? 0 : 1;
}

uint32_t board_button_read(void)
{
  return PORTD.PIDR.BIT.B7 ? 0 : 1;
}
```

## ビルドと実行

ビルドすると「multiple definition of 'xxx'」というエラーが出ます。``generate/inthandler.c``にあるエラーの出た関数名の定義をコメントアウトします。

再度ビルドすると成功すると思います。

実行するには``HardwareDebug/hid_boot_interface.bin``をGR-ROSEに書き込みます。Windowsにマウスとして認識されば完成です。

## 最後に

GR-ROSEにデバッガを接続すれば、e2studioでソースコードデバッグが可能です。
デバッガの接続方法は、下記のサイトにあります。

<https://www.renesas.com/eu/ja/products/gadget-renesas/boards/gr-rose/project-connect-debugger>

USBデバイスとしての通信動作は動くように出来ましたが、マウスとしての動作は出来ていませんので、下記のようなマウスセンサーを使って、自作マウスが作れそうです。

<https://btoshop.jp/products/b02140>

このマウスセンサーは、下記のコードでセンサー値を読むことが出来ます。

<https://github.com/okhiroyuki/ADNS5050>

今回の手順から進めていき[TOPPERSのRTOS](https://www.toppers.jp/hrp3-kernel.html)と組み合わせて、[Azure IoT](https://azure.microsoft.com/ja-jp/overview/iot/)に接続するアプリを作りましたので、紹介しておきます。※2021/11/25現在 まだAzure IoTへの接続が未完成です。

<https://github.com/h7ga40/azure_iot_hub_rose>

これは、下記のコンテストに応募した作品です。

<https://algyan.connpass.com/event/224849/>