2018年03月08日

MicroPythonでのUSBキーボード入力の調査

ここ1ヶ月ほど、Raspberry PiベアメタルMicroPythonにUSBキーボード入力を追加してみたいと思っていろいろ調べています。

用途として、
(1)USBキーボードを単純にREPLのための入力手段としてのみ使う
(2)モジュールを用意してMicroPythonのスクリプトから汎用的に使えるようにする
の2つが考えられ、当然のことながら後者のほうが応用が利きますが、APIを設計する必要があります。

考えてみると、そもそも私はこれまでMicroPythonでUSBを使ったことがありませんでした。
そこでまずはUSBデバイスモードでの利用方法を調べたのが前回の記事でした。

一方、USBホストモードについてフォーラムで情報を探してみたところ、実験的な実装がSTM32にはあることが分かりました。
元来MCU向けであるMicroPythonのUSB機能は、もっぱらUSBデバイスを作成するための機能が整備されており、USBホストはプライオリティが低いようです。

USB host mode ・ Issue #212 ・ micropython/micropython

ちょっといじってみましたが、STMHALの最新ライブラリに追随していないのか、リンクエラーになってしまってUSBホストモードを有効にした状態でビルドすることはできませんでした。

ですので、どのような実装になっているのか、ports/stm32配下のソースコードを調べてみた結果をメモしておきます。

●条件付きコンパイル
関連しそうなスイッチは以下の通りです。

mphalport.c:#ifdef USE_HOST_MODE
usb.c:#if defined(USE_HOST_MODE)
Makefile:#USBHOST_DIR=usbhost
boards/XXX/mpconfigboard.h:#define MICROPY_HW_ENABLE_USB (1)


●REPLでのUSBキーボード入力
以下のように、コードはありますが無効化されています。
int mp_hal_stdin_rx_chr(void) {
for (;;) {
#if 0
#ifdef USE_HOST_MODE
pyb_usb_host_process();
int c = pyb_usb_host_get_keyboard();
if (c != 0) {
return c;
}
#endif
#endif

●USBホストモードへの変更
pyb.usb_mode()の実装(usb.c)で以下のように処理されています。
#if defined(USE_HOST_MODE)
if (strcmp(mode_str, "host") == 0) {
pyb_usb_host_init();
}


●USBホストモードの実装
usb.cの中に
// code for experimental USB OTG support                                        

#ifdef USE_HOST_MODE

で始まる部分があり、
void pyb_usb_host_init(void);
void pyb_usb_host_process(void);
uint pyb_usb_host_get_keyboard(void);

の3つの関数が定義されていますが、実際にはpyb_usb_host_get_keyboardはTODO扱いになっています。

というわけで、現状の仮の実装としては

・pyb.usb_mode("host") を実行するとUSBホストモードが有効に(pyb_usb_host_init()が呼ばれる)
・以降は、REPLループの中でpyb_usb_host_process()とpyb_usb_host_get_keyboard()が呼ばれる

となっています。
usb.cの中に書かれているコメントに、MicroPythonにおけるUSBの実装方針があったので引用しておきます。
// MicroPython bindings for USB

/*
Philosophy of USB driver and Python API: pyb.usb_mode(...) configures the USB
on the board. The USB itself is not an entity, rather the interfaces are, and
can be accessed by creating objects, such as pyb.USB_VCP() and pyb.USB_HID().
We have:
pyb.usb_mode() # return the current usb mode
pyb.usb_mode(None) # disable USB
pyb.usb_mode('VCP') # enable with VCP interface
pyb.usb_mode('VCP+MSC') # enable with VCP and MSC interfaces
pyb.usb_mode('VCP+HID') # enable with VCP and HID, defaulting to mouse protocol
pyb.usb_mode('VCP+HID', vid=0xf055, pid=0x9800) # specify VID and PID
pyb.usb_mode('VCP+HID', hid=pyb.hid_mouse)
pyb.usb_mode('VCP+HID', hid=pyb.hid_keyboard)
pyb.usb_mode('VCP+HID', pid=0x1234, hid=(subclass, protocol, max_packet_len, polling_interval, report_desc))
vcp = pyb.USB_VCP() # get the VCP device for read/write
hid = pyb.USB_HID() # get the HID device for write/poll
Possible extensions:
pyb.usb_mode('host', ...)
pyb.usb_mode('OTG', ...)
pyb.usb_mode(..., port=2) # for second USB port
*/


USBホストモードのAPIとしてはusb_mode('host')が唯一のものであり、接続されたUSBデバイスを使うためのAPIはありません。
ですので、APIは独自に設計せざるを得ない感じですが、USB_HID()などの既存のUSBデバイス向けAPIと衝突するのは望ましくないので、例えば

(1)usb_mode() は共通。現在のモードを返せるようにしておく(pyb.usb_mode()を呼ぶと'host'が返る)
(2)USB_HCD()でホストコントローラのオブジェクトを返す
(3)ホストコントローラオブジェクト経由でデバイスとの通信を行う

みたいな感じかなあと思います。

各種デバイスクラスをどこまでCで実装するかは考えどころですが、Low SpeedのHIDの場合はMicroPythonで様々なデバイスを実装できるほうが、応用が利く気がします。
MicroPythonで実装した場合に、マウスのように比較的更新速度が高いデバイスで速度が追いつくかどうかは気になりますが、これは実験してみないとなんとも言えないですね。
ラベル:MicroPython USB
posted by boochow at 20:00| Comment(0) | stm32 | このブログの読者になる | 更新情報をチェックする

2018年03月06日

MicroPythonでUSBマウス

pybmouse.gif

MicroPythonの動作しているSTM32のボードをUSBマウスとして動作させ、PCに接続してみました。
ボードは以前も使ったNUCLEO-F767ZIです。
これは以下のページのサンプルをそのまま使ったもので、上のように、MicroPythonのコードでPCのマウスポインタを動かすことができます。
(画像はLICEcapでキャプチャしました。)

8. Making the pyboard act as a USB mouse − MicroPython 1.9.3 documentation

USBデバイスとして動作するUSBポートは、プログラムを書き込むST-LINKではなく、ボードの反対側についているUSB端子のほうです。
PCへの接続は、以下のようにマウス用とプログラミング用の2本のUSBケーブルが必要になります。

pyb-usb-test.jpg


マウス用のUSBポートは、MicroPythonを動かしている場合、初期状態ではPCからはUSBマスストレージとして認識されます。
このストレージでアクセスできるのは、REPLからosモジュールでアクセスできるものと同じファイルシステムです。
以下のように「PYBFLASH」というドライブ名で見えています。

pybcdc.png


先に、このストレージにある「pybcdc.inf」をPCにコピーしておいてください。
マウスのデバイスドライバをインストールする際に必要になります。
次のステップ以降では、このストレージへのアクセスができなくなります。

コピーしたら、ストレージのboot.pyをメモ帳などで開いて修正します。
コメントアウトされている
#pyb.usb_mode('VCP+HID') # act as a serial device and a mouse

の先頭の#を削除して、この行を有効にして保存します。
これでリセットすると、MicroPythonはマスストレージではなくHIDデバイスとして起動します。

初回起動時はWindowsからマウスとして認識されないので、さきほどコピーしたpybcdc.infを使ってデバイスをインストールする必要があります。

以下のようなコードを入れて、osc(200,20)等と入力すると、マウスカーソルが水平方向に動きます。


USBマスストレージとしてアクセスできるように戻したい場合は、ST-LINK側からboot.pyをuPyCraftなどを使って書き換えてください。
ラベル:USB MicroPython
posted by boochow at 23:01| Comment(0) | stm32 | このブログの読者になる | 更新情報をチェックする

2018年02月03日

STM32のバイナリをQEMUで動かしてみた

qemu-stm32.png
Raspberry PiのベアメタルプログラミングではQEMUを使うことができましたが、同様にしてSTM32のソフトウェアもエミュレータで動かすことができます。
オリジナルのQEMUはSTM32のボードをサポートしていませんが、ここからフォークしたGNU MCU Eclipse用のQEMUでは、いくつかのSTM32ベースのボード(と、Cortex-m7)をサポートしています。

The GNU MCU Eclipse QEMU

Windows版のQEMUをダウンロードして、SW4STM32でビルドしたバイナリを動作させてみました。
最新版では現在Windows版がありませんが、以前のバージョン(私はv2.8.0-20161227を使いました)で問題ありません。

サポートされているボードの中から、NUCLEO-F411REを使ってみることにしました。
CubeMXを使って、Lチカを作成します。
F411REではオンボードLEDがPA5に接続されていますので、メインループを
  while (1)
{
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
HAL_Delay(500);
}
としました。

ビルドして、QEMUで起動します。オプションは

C:\>"C:\Program Files\GNU ARM Eclipse\QEMU\2.8.0-201612271623-dev\bin\qemu-system-nuarmeclipse.exe" -verbose -board NUCLEO-F411RE --image qemu-test-f411re.bin
のように指定します。最後のbinファイルはビルドしたバイナリファイルです。

すると、
GNU ARM Eclipse 64-bits QEMU v2.8.0 (C:\Program Files\GNU ARM Eclipse\QEMU\2.8.0
-201612271623-dev\bin\qemu-system-gnuarmeclipse.exe).
Board: 'NUCLEO-F411RE' (ST Nucleo Development Board for STM32 F4 series).
Device: 'STM32F411RE' (Cortex-M4 r0p0, MPU, 4 NVIC prio bits, 86 IRQs), Flash: 5
12 kB, RAM: 128 kB.
Image: 'qemu-test-f411re.bin'.
Command line: (none).
Cortex-M4 r0p0 core initialised.
Cortex-M4 r0p0 core reset.

C:\Program Files\GNU ARM Eclipse\QEMU\2.8.0-201612271623-dev\bin\qemu-system-gnu
armeclipse.exe: Attempt to set CP10/11 in SCB->CPACR, but FP is not supported ye
t.

というようにエラーで止まってしまいました。
エミュレータがサポートしていないFPUを利用しようとしたため、ということのようです。
そこで、ソースコード中でこの処理を行っている部分(system_stm32f4xx.c)を以下のようにコメントアウトして再度ビルドしたところ、うまく動作しました。
void SystemInit(void)
{
/* FPU settings ------------------------------------------------------------*/
#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
// SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2)); /* set CP10 and CP11 Full Access */
#endif

本当はこのあと、GDBに接続させてQEMU上のバイナリをデバッグしてみたいところですが、SW4STM32にGNU MCU EclipseのQEMU Debuggingプラグインを入れただけではうまく動作しませんでした。
posted by boochow at 14:25| Comment(0) | stm32 | このブログの読者になる | 更新情報をチェックする

2018年01月28日

TrueSTUDIO for STM32を動かしてみた

最近STMicro社が買収して、無料で配布されるようになったIDE「Atollic TrueSTUDIO for STM32」を動かしてみました。
買収により、「Pro Versionが無料で使用できるようになる」そうです。

TrueSTUDIO Developers using STM32 devices get an early holiday gift from STMicroelectronics

このIDEはEclipseをベースとしていますので、基本的にはSW4STM32とよく似ています。
以下、とりあえずインストールして動かしてみた、という記録です。

ダウンロードは以下から行えます。Linux版とWindows版があります。
Mac版はありません。SW4STM32にはMac版があるのに、何故でしょうね。

TrueSTUDIO - Atollic

また、サポートされているボードの一覧がこちらにあります。
ほぼ全てのボードがサポートされているようですので、困ることは無いと思います。

STMicroelectronics - Atollic

デバッガには、インストール時にST-LinkとSEGGER J-Linkのいずれも選択することができます。

truestudio01.png


起動すると、以下のようなおなじみのEclipseの画面が現れます。
SW4STM32を使ったことがあれば、基本的な使い方にはあまり迷うことはないと思われます。

truestudio02.png


新規のプロジェクトを作成するには、ファイル→新規→C Projectを選択します。
するとプロジェクト作成Wizardが起動されます。

truestudio03.png


Project TypeにはEmbedded C Projectを選びます。

truestudio04.png


ボードの選択画面になりますので、使用するボードを選びます。
とりあえず手元のNUCLEO-F767ZIを選びました。

truestudio05.png


ランタイムライブラリはnewlibと機能簡略版newlib-nanoが選べます。
とりあえず機能簡略版でいいでしょう。

truestudio06.png


デバッグプローブは、今回はST-Link内蔵のボードを使うのでST-Linkを選びます。

truestudio07.png


これで必要なファイル一式がセットアップされ、ワークスペースに表示されます。

truestudio08.png


この時点で、ビルドまで終わっています。mainには、
while(1){ i++; }
というコードが入っています。

メニューから「実行」→デバッグを選択すると、ウインドウ配置がデバッグモードになり、mainの最初のところで一時停止状態になります。
ここから先はSW4STM32とあまり違いません。


TrueSTUDIOをCubeMXと一緒に使用することも可能です。
CubeMXでは、Project SettingsでTrueSTUDIOをIDEに指定することもできるようになっています。
また、TrueSTUDIOからCubeMXを呼び出すことができます。そのためのプラグインは以下にあります。

STSW-STM32095 - STM32CubeMX Eclipse plug in for STM32 configuration and initialization C code generation - STMicroelectronics

TrueSTUDIOからは、「ヘルプ」メニューから「新規ソフトウェアのインストール」を選択します。
そして、「作業対象」へ上記からダウンロードしたZIPファイルを指定するとインストールできます。

CubeMXを開くには、「ウインドウ」メニューから「Perspective」→「パースペクティブを開く」→「その他…」でSTM32CubeMXを選択します。

もっとも、CubeMXを使用して生成されるコードはどのIDE向けでも大差ないので、CubeMX+SW4STM32に慣れているのであれば、あえてTrueSTUDIOを使う意味は大きくは無いかもしれません。
posted by boochow at 15:22| Comment(0) | stm32 | このブログの読者になる | 更新情報をチェックする

2017年12月17日

NUCLEO-F767ZIでST-Link経由のシリアル通信をprintfで使う

NUCLEOはUSB経由ST-Linkでボード本体と接続されていますが、同時にVitual Portという機能でST-Link上でシリアルポートを使うことができます。
Arduinoでは「Serial」でホスト側とUART通信を行えるようになっていますが、Virtal Portが使えれば、STM32用のソフトウェアを開発するときもArduinoのSerialと同様に使えて便利そうです。

この記事は、Virtal Portを使用するプロジェクトをCubeMXで作成する方法のメモです。
こちらにSTM32F7 Discovery用の解りやすいチュートリアルがYouTubeありましたので、この記事はほぼこのビデオそのままの内容です。


(1)CubeMXで開発ボードを選択したら、まずPinout→Clear Pinoutsで全てリセットします。
uart01.png

次に、先にプロジェクトの設定をしておきます。
Project→Settingsで設定ウインドウを開き、
・プロジェクト名
・Toolchain/IDE
を設定します。

(2)シリアルポートの設定をしていきます。
UM1974の26ページに、ST-LinkにはUSART3がPD8とPD9で接続されていることが記載されています。
uart02.png

そこで、CubeMXでUSART3を使えるように設定します。

このとき、USART3を有効にしてはいけません。USART3という「機能」をどのピンに割り当てるかは可変なのですが、初期状態からUSART3を有効にするとPD8とPD9ではない別のピンに自動でこの機能が割り当てられてしまいます。

まずPD8とPD9を探します。
CubeMxにはピンを検索する機能がありますので、そこに「PD8」と入力します。
uart03.png

すると、ピン配置図の上で該当のピンが分かりますので、そのピンをクリックします。
uart04.png

そのピンに指定できる機能(Alternate Function)のリストが出ますので、その中から「USART3_TX」を選択します。
uart05.png

PD9についても同様に「USART3_RX」を指定します。最終的に図のようにPD8とPD9の機能が指定できればOKです。
uart06.png

ちなみに、どのピンにどんなAlternate Functionが割り当てられているのかは、CPUのデータシートを見ると一覧表があります。データシートは何をするにも必携です。

ここでようやくUSART3の機能を設定します。
ModeはAsynchronousにします。Hardware Flow ControlはDisabledのままです。
uart07.png

SYSセクションでは、DebugをSerial Wireにしておきます。
uart08.png

(3)Clock Configurationタブに移ります。
uart09.png

ここではHCLKを最高値(NUCLEO-F767ZIでは216MHz)に設定します。
uart10.png

以下のようなダイアログが出た場合にはOKを押してください。
uart11.png

(4)Configurationタブに移り、UARTの設定をします。
uart12.png

「USART3」のボックスをクリックすると設定ウインドウが開きます。
Word Lengthは8bitsにしておくのが良いでしょう。
uart13.png


(5)最後にProject→Generate Codeでスケルトンコードが生成されます。
uart14.png



シリアルポートに読み書きするには、
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);

を使います。
第1引数はUARTを表す構造体です。生成されたスケルトンコードでは、UART構造体が
UART_HandleTypeDef huart3;
として定義されています。
第2引数はバッファ、第3引数はバッファ長、第4引数はタイムアウト(msec)です。
第2引数の型はuint8_tへのポインタです。

main()関数からメッセージを表示させるには、こんな感じで書きます。
  HAL_UART_Transmit(&huart3,(uint8_t *)"Hello\r\n", 7, 100);


しかしこれは、ちょっと使い勝手が悪いですね。
CubeMXのサンプルには、printfでUARTに出力できるようにするサンプルが付属していますので、これを自分のプロジェクトでも使ってみましょう。

該当するサンプルは、CubeMXのライブラリ(STM32CubeF7)では
STM32Cube\Repository\STM32Cube_FW_F7_V1.8.0\Projects\STM32F767ZI-Nucleo\Examples\UART\UART_Printf\SW4STM32

にあります。

まず、上記のフォルダにある「syscalls.c」を自分のプロジェクトにコピーしてください。
このファイルにはprintfから呼ばれる_write()の実装が入っています。

このファイル自体は変更する必要はありません。
その代わりに、printfを使いたいファイル(今回はmain.c)の中で、
int __io_putchar(int ch)
を再定義します。

これも上記のプロジェクトのmain.cにあるとおりにすればよいのですが、該当部分を抜き出すと以下のようになります。

ここでは、1文字出力する関数をUSART3へ1文字出力するように書き換えています。
printf()は_write()を経由して最終的に__io_putchar()を呼ぶように実装されているので、上記を追加することでprintfでST-Link経由でPC側へ文字列を送ることができるようになります。

ちなみに読み込みのほうは、syscalls.cにある_read()の実装が、バッファ分のデータを読み込むまで終わらないようになっているので、こちらにも手を入れる必要がありそうです。
posted by boochow at 10:57| Comment(0) | stm32 | このブログの読者になる | 更新情報をチェックする
人気記事