TinyUSBをGR-ROSEで動かす
TinyUSBはArduino IDEでもサポートされるUSBスタックです。
GR-ROSEのCPU RX65Nもサポートデバイスに入っていますが、ボードとして直接の対応はありません。
また、ソースコードデバッグも行えるようIDEのe2studioで扱えるようにしましたので、その手順を残しておこうと思います。
この記事はWindowsが対象になります。
大まかな流れは、下記のようになります。
- TinyUSBを標準の方法でビルドする(GR-ROSE向けでは無い)
- ビルド結果から必要なソースコードだけをコピーする(e2studioはフォルダの下にあるファイルがビルド対象となるため、不要なファイルを省きたい)
- e2studioのプロジェクトをGR-ROSE向けに作成する
- GR-ROSE向けにIOレジスタの設定を変更する
ビルド環境を構築する
TinyUSBのビルドにはmake
を使うので、MSYS2環境でビルドを行います。
MSYS2など必要なソフトのリンクを書いておきました。
MSYS2のインストール
下記のサイトからインストーラをダウンロードして、インストールします。
パッケージをアップデートします。
$ pacman -Syu
$ pacman -Su
make
コマンドをインストールします。
$ pacman -S make
git
も必要なので、ない場合はインストールします。
$ 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向けのビルドをしてみます。
Getting Startedを参考に進めます。
適当な作業フォルダを作り、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を使っている人は、上記の設定を*.code-workspace
で用意しておくのもいいかもしれません。
以降はbash
のプロンプトでの作業です。TiinyUSBのコードをクローンします。
$ git clone https://github.com/hathach/tinyusb tinyusb
$ cd tinyusb
examples
の中にいくつかのお手本があります。
今回はマウスを作りたいので、HIDの中からhid_boot_interface
で説明していきます。
$ cd examples/device/hid_boot_interface
ビルドするにはターゲットボードを指定する必要があります。
この値の候補はhw/bsp
の下にあるフォルダ名を指定します。
RX65N向けにはhw/bsp/rx/boards/rx65n_target
があるので、rx65n_target
を指定します。
$ 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
のフォルダ構成と同じなので、フォルダ構成も収集できます。
ソースコードを収集する
クローンしたフォルダから、ビルドに必要なファイルだけ別のフォルダにコピーする手順を説明します。
コピーするファイル一覧を作り、コピーコマンドにしてバッチファイルを作り、そのバッチファイルを実行してコピーします。
バッチファイルの作成には、サクラエディタを使用します。
コピー先のフォルダは、クローンしたフォルダtinyusb
と同レベルのフォルダcopyed
として説明します。
まず、_build
の下の*.o
をエクスプローラで検索します。
検索結果のファイルをCtrl+A
で全選択し、Ctrl+C
でクリップボードにコピーします。
そしてサクラエディタの新規ファイルにCtrl+V
で貼り付けると、ファイルパスの一覧が貼り付きます。
文字列置換や矩形選択などで、tinyusb
フォルダ以前のパスを削除します。例えばD:\Github\tinyusb\src\tusb.c
をsrc\tusb.c
とします。
このファイル一覧を正規表現を使った文字列置換で、コピーコマンドに変換します。
置換前は^(.+)\\([^\\]+)\.o
で、置換後はxcopy ..\\tinyusb\\$1\\$2.c $1\\
としてすべて置換します。
これをコピー先のフォルダcopyed
にcopy_c.bat
として保存し、サクラエディタでCtrl+B
でバッチファイルを実行し*.c
をコピーします。
次は、*.h
を抽出します。
*.c
ファイルからインクルードされたファイルは、コンパイラによって*.d
に依存関係として保存されています。
Grepで*.d
を対象に、(.+)(\\|/)([^\\/]+)\.h
で検索します。
検索結果を編集して、*.h
のパスの一覧にします。文字列置換で^.+\([0-9]+,[0-9]+\) +\[\w+\]: *
でGrepしたファイルパスの部分を削除します。次に *\\$
で行最後の\
を削除します。
検索結果は重複しているので、Ctrl+A
で全選択して、メニューから「編集」→「整形」→「選択範囲の昇順ソート」を選択します。
続けて「編集」→「整形」→「連続した重複行の削除」を選択して、依存しているファイル一覧を得ます。
このうちTinyUSBのフォルダにある物だけ残して、GCCの標準ライブラリのパスなど他の行を削除します。
/
を\
に置換したり、絶対パスの部分を矩形選択で削除して、*.c
の時と同様に正規表現で^(.+)\\([^\\]+)\.h
をxcopy ..\\tinyusb\\$1\\$2.h $1\\
に置換します。
これを、コピー先のフォルダcopyed
にcopy_h.bat
として保存し、サクラエディタでCtrl+B
でバッチファイルを実行し*.h
をコピーします。
これで、e2studioのプロジェクトに使うファイルが収集できました。
e2studioのプロジェクト作成
e2studioを起動します。ワークスペースに適当なフォルダを入力します。このフォルダの中は空にしておきます。
次に、新規のプロジェクトを作成します。
GCC-RXの実行ファイルプロジェクトを選択します。
プロジェクト名はhid_boot_interface
としておきます。
ターゲットデバイスをR5F565NEHxFPにします。
今回はスマートコンフィギュレーターは使いません。
CPUオプションで必要があれば変更します。
C標準ライブラリは、NewlibのPre-Buildで大丈夫です。
必要なファイルが生成されます。
プロジェクトが開かれると下記の様になります。
上記で抽出したcopyed
フォルダをプロジェクトフォルダにコピーしてtinyusb
に名前を変更します。
追加したフォルダはビルド対象になっていないので、ビルド対象にします。
下の画面で、「全て選択を解除」ボタンを押します。
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
に書かれています。
GR-ROSEに書き込むには*.bin
ファイルが欲しいので、Objcopyの設定を「Raw binary」に変更します。
ソースコードの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 |
システムクロック設定が違っているので下記の箇所を、
SYSTEM.SOSCCR.BYTE = 1;
if (SYSTEM.HOCOCR.BYTE) {
SYSTEM.HOCOCR.BYTE = 0;
while (!SYSTEM.OSCOVFSR.BIT.HCOVF) ;
}
SYSTEM.PLLCR.WORD = 0x1D10u; /* HOCO x 15 */
次のように書き換えます。
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などの端子が違っているので、次の箇所を、
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;
}
下記のように変更します。
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と組み合わせて、Azure IoTに接続するアプリを作りましたので、紹介しておきます。※2021/11/25現在 まだAzure IoTへの接続が未完成です。
https://github.com/h7ga40/azure_iot_hub_rose
これは、下記のコンテストに応募した作品です。