macOSからPi Picoを使う [Pi Pico]
Raspberry Pi Picoが日本ではショップによって手に入るところが出てきた。でも一瞬で無くなったりする。僕はついこないだやっと2個だけ確保できた。
Raspberry Pi PicoをmacOSから使いたいんだけど、vendor specificなクラスのUSBデバイスを扱う必要がある。
それもめんどくさいなあ、とりあえずlibusbを使って、あとからIOUSBHostで書き換えるか、Pi Pico側はTinyUSBなんだけど、これはドキュメントが整備されてなくて厳しいなあ、cmakeって慣れてなくて難しいなあ、などと思っていた。
pico_sdkを落としていろいろいじっているうちにいろんなことがわかってきた...
それでもGetting_started(pdf)のChapter9.1 Building on Apple macOSに従って設定するとexampleはmacOSでビルドできるようになった。まずhomebrewで必要なライブラリやコマンドをインストールする。それからsdkをcloneするんだけど、サブモジュールもインストールしておかないとtinyUSBがロードされない。
あとはPICO_SDK_PATH変数をpico-sdkディレクトリに設定する。さて、これでexampleはビルドできるようになったんだけど、自分のプロジェクトにするとクロスコンパイラでなくネイティブのclangが呼ばれてしまう。CMakeLists.txtのどこかに設定があるんだろうけど見つからない。色々試すとなぜかpico-sdkと同じレベルのディレクトリにすると設定される。全然わからん。
誰か教えて。
まあ、でもそうすることで、cmakeが動いてビルドできるようになる。
また、Pi Pico側はTinyUSBを使うんだけど、なぜかいまだにexampleさえビルドすることができない。pico_sdkは簡単にできるように工夫されてるようなんだけど、その構造は複雑で難しい。勉強が必要である。
どうしようか悩んでるうちにあることに気がついた。
とする。これはstdin、stdoutをUARTではなくUSBにする、という宣言らしくて、こうするとTinyUSBが自動的にリンク(というかソースからコンパイルするmakefileが生成される。OS前提に慣れた身には抵抗を感じるけど、組み込みでは「ライブラリをリンク」と「.oファイルを一緒にビルド」の区別はないわな)される。
pico_stdioはCランタイムとformat引数記述の上ではコンパチのprintfが実装されている。これを使えば文字列の出力はかなり自由にできる。ただし、入力はあまり用意されていない。ただひとつ
はstdinからunsigned charをひとつだけ読み込むことができる。引数はμsec単位のtimeoutで、これを超えるとPICO_ERROR_TIMEOUTを返すことになっている。0を指定するとunixのfcntl()関数にO_NONBLOCKを指定したときの同じような動作をする。
でヘッダを読んでmain()関数の初めのほうに
で初期化をする。これでstdinとstdoutが使えるようになる。
macOS側だけど、これも簡単な方法がみつかった。
実はPi Picoでpico_stdioをUSBに設定すると、Pi PicoのUSBはdeviceモードになり、CDCクラスとしてhostから見えるようにdescriptorが設定される。
これはそうやったhello_worldを走らしたPi PicoをMacに接続してUSBProberで見たところ。 vendorID、productIDは正しいようである。シリアルNo.は0になっている。デバイスクラスは239(=0xEF)はMiscellaneousでサブクラスが2、プロトコルが1になっている。これはIAD(微妙にシンプルなpdf仕様書)という複数の機能を持ってるデバイス用の特別な値の組み合わせらしい。
残りを見てみると、interfaceが3つあって
複雑な書き方だけど、これでmacOSは認識するので一般的なデスクリプタ記述のようである(macOSでは、もともとはWindowsに固有だけど今ではデファクトになってるような規格を無視することがあったので、警戒心が養われてしまった)。
CDCクラスはRS232CやISDNなんかをUSB経由で動かすことを想定したもので、USB-シリアルコンバータなんかがこのクラスになっている。実際には別にRS232Cが繋がっていなくて、デバイスの中で閉じていてもホストからはわからない。vendor specificなクラスとして実装するとキャラクタ以外のデータも通信できるけど、しょせんPi PicoはUSB1.1だし、Pi Picoを使って動画を送ろうと言う人はなかなかないだろうし(僕はそんなことはやらない)、ということでこれで十分である。
これは便利で、macOS側は汎用のキャラクタデバイスとして見えるはずである。
/devの中をさぐるとPi Picoの抜き差しで
というデバイスファイルができたり無くなったりする。かならずこの名前になるのかどうかわからないし、さらにはCDCデバイスが未来永劫macOSでサポートされるかどうかわからないけど、もしこれがあればmacOSはPOSIX互換だということになってるので、このデバイスファイルを通してキャラクタをやりとりすることができる。
このおかげで一気に簡単になった。
ためしにこんなのを書いた。test.cに
これはstdinに入力があるとそのキャラクタをstdoutに返して、1秒以上入力がないとチップ温度を表示する、というだけのもの。
CMakeLists.txtに
としてビルドする。つまりGetting_startedにあるように
でうまくいけばbuildディレクトリは
みたいになるはずで、Pi PicoのBOOTSELボタンを押しながらUSBを挿して、マウントされたボリュームにこのtest.uf2をコピーすればいい。
こうするとPi Pico側からかってにアンマウントするので、必ず「ディスクの不正な取り出し」という通知がOSから出る。もちろんPi Pico側のファイルシステムは壊れたりはしていないので通知は無視していい。とはいうものの、毎回なので煩わしい。
これでUSBを引っこ抜いて挿し直すとこのプログラムが動き出すはずである。
とりあえずmacOS側でさっきのキャラクタデバイスとやりとりできるように
として(最後の引数はボーレートだけど今回は関係ない。ちなみになんでscreenという名前なのかと言うとVT100のエミュレータだからだそうである。懐かしい)適当にキーを打つと
やめると1秒ごとにタイムアウトして温度を表示する。
確認できた。
これでとりあえずのPi PicoとmacOSとの通信手段が手に入った。あとはZeroWだとOSにおんぶにだっこできた部分をどこまでpico-sdkで肩代わりできるか、というのをみていく。
Raspberry Pi PicoをmacOSから使いたいんだけど、vendor specificなクラスのUSBデバイスを扱う必要がある。
それもめんどくさいなあ、とりあえずlibusbを使って、あとからIOUSBHostで書き換えるか、Pi Pico側はTinyUSBなんだけど、これはドキュメントが整備されてなくて厳しいなあ、cmakeって慣れてなくて難しいなあ、などと思っていた。
pico_sdkを落としていろいろいじっているうちにいろんなことがわかってきた...
2 macOSへのsdkの整備
まず、pico_sdkはcmakeで統一的にビルドできるようになってるんだけど、依存関係が複雑で、僕がcmakeに慣れてないせいもあって(いかにXcodeにスポイルされているかよくわかる)、Getting_startedからちょっと違うことをしようとするとうまくいかない。TinyUSBにいたってはexamplesのビルドさえうまくいかない。それでもGetting_started(pdf)のChapter9.1 Building on Apple macOSに従って設定するとexampleはmacOSでビルドできるようになった。まずhomebrewで必要なライブラリやコマンドをインストールする。それからsdkをcloneするんだけど、サブモジュールもインストールしておかないとtinyUSBがロードされない。
$ git clone -b master https://github.com/raspberrypi/pico-sdk.git $ cd pico-sdk $ git submodule update --init --recursive $ cd .. $ git clone -b master https://github.com/raspberrypi/pico-examples.git
誰か教えて。
まあ、でもそうすることで、cmakeが動いてビルドできるようになる。
3 macOSとの通信
macOSとはUSBを経由して通信したいんだけど、vendor specificなUSBデバイスと通信するには、macOS側はI/O Kitを使うか、USBDriverKitか、IOUSBHostか、はたまたlibusbか、と色々選択肢があって(というか、どれだと近未来のobsolete地雷を踏まずに済むか)、どれを使えばいいのかよくわからない。また、Pi Pico側はTinyUSBを使うんだけど、なぜかいまだにexampleさえビルドすることができない。pico_sdkは簡単にできるように工夫されてるようなんだけど、その構造は複雑で難しい。勉強が必要である。
どうしようか悩んでるうちにあることに気がついた。
3.1 pico_stdio
pico_examplesにあるhello_worldはGetting_startedのblinkの次に紹介される基本的なサンプルで、これはCランタイムのstdioのサブセットであるpico_stdioというモジュールを経由して文字列を出力する。出力先はUART(シリアルポート)とUSBのふたつが使える。USBのためにはTinyUSBがインストールされてないといけない。さらにCMakeLists.txtの中にpico_enable_stdio_uart(hello_world 0) pico_enable_stdio_usb(hello_world 1)
pico_stdioはCランタイムとformat引数記述の上ではコンパチのprintfが実装されている。これを使えば文字列の出力はかなり自由にできる。ただし、入力はあまり用意されていない。ただひとつ
int getchar_timeout_us(uint32_t timeout_us);
3.2 pico_stdioを使う
pico_stdioを使うためにはまず#include <stdio.h>
stdio_init_all();
3.3 macOS側
さてこれで、Pi Pico側はUSB経由でキャラクタを入出力できるようになる。macOS側だけど、これも簡単な方法がみつかった。
実はPi Picoでpico_stdioをUSBに設定すると、Pi PicoのUSBはdeviceモードになり、CDCクラスとしてhostから見えるようにdescriptorが設定される。
これはそうやったhello_worldを走らしたPi PicoをMacに接続してUSBProberで見たところ。 vendorID、productIDは正しいようである。シリアルNo.は0になっている。デバイスクラスは239(=0xEF)はMiscellaneousでサブクラスが2、プロトコルが1になっている。これはIAD(微妙にシンプルなpdf仕様書)という複数の機能を持ってるデバイス用の特別な値の組み合わせらしい。
残りを見てみると、interfaceが3つあって
.... Interface Association Communications-Control First Interface 0 Interface Count 2 Function Class 2 (Communications-Control) Function Subclass 2 Interface Protocol 0 Function String 0 (none) Interface #0 - Communications-Control ............................ "Board CDC" Alternate Setting 0 Number of Endpoints 1 Interface Class: 2 (Communications-Control) Interface Subclass; 2 Interface Protocol: 0 Comm Class Header Functional Descriptor .... Address: 0x81 (IN) Attributes: 0x03 (Interrupt) Max Packet Size: 8 Polling Interval: 16 ms Interface #1 - Communications-Data/Unknown Comm Class Model Alternate Setting 0 Number of Endpoints 2 Interface Class: 10 (Communications-Data) Interface Subclass; 0 (Unknown Comm Class Model) Interface Protocol: 0 Endpoint 0x02 - Bulk Output Address: 0x02 (OUT) Attributes: 0x02 (Bulk) Max Packet Size: 64 Polling Interval: 0 ms Endpoint 0x82 - Bulk Input Address: 0x82 (IN) Attributes: 0x02 (Bulk) Max Packet Size: 64 Polling Interval: 0 ms Interface #2 - Vendor-specific ....................................... "Reset" Alternate Setting 0 Number of Endpoints 0 Interface Class: 255 (Vendor-specific) Interface Subclass; 0 (Vendor-specific) Interface Protocol: 1となっている。詳しくはわからないんだけど、function classがCDC(Communication Data Class)になっている。interface#0がなんらかの制御用らしくてIN方向(デバイス→ホスト)のendpointだけ持っていて、interface#1がbulk transactionの入出力になっている。これが実際にデータをやり取りするpipeを持っているらしい。残ったinterface#2はResetの名前になっているけどVendor specificでendpointもないので、このままでは機能はわからない。
複雑な書き方だけど、これでmacOSは認識するので一般的なデスクリプタ記述のようである(macOSでは、もともとはWindowsに固有だけど今ではデファクトになってるような規格を無視することがあったので、警戒心が養われてしまった)。
CDCクラスはRS232CやISDNなんかをUSB経由で動かすことを想定したもので、USB-シリアルコンバータなんかがこのクラスになっている。実際には別にRS232Cが繋がっていなくて、デバイスの中で閉じていてもホストからはわからない。vendor specificなクラスとして実装するとキャラクタ以外のデータも通信できるけど、しょせんPi PicoはUSB1.1だし、Pi Picoを使って動画を送ろうと言う人はなかなかないだろうし(僕はそんなことはやらない)、ということでこれで十分である。
これは便利で、macOS側は汎用のキャラクタデバイスとして見えるはずである。
/devの中をさぐるとPi Picoの抜き差しで
crw-rw-rw- 1 root wheel 9, 8 5 7 10:06 /dev/tty.usbmodem0000000000001
このおかげで一気に簡単になった。
ためしにこんなのを書いた。test.cに
#include <stdio.h> #include "pico/stdlib.h" #include "hardware/adc.h" int main() { stdio_init_all(); adc_init(); adc_select_input(4); while (true) { const float conversion_factor = 3.3f / (1 << 12); int c = getchar_timeout_us(1000000); if (c != PICO_ERROR_TIMEOUT) printf("received character [%c]\n", c); else { uint16_t result = adc_read(); float adcVoltage = result * conversion_factor; float celsius = 225.126 - 322.81 * adcVoltage; printf("timeout.\n"); printf("%s 0x%03x, %s %5.3f V, %s %5.2f degree\n", "Raw value:", result, "voltage:", adcVoltage, "temperature:", celsius); } } return 0; }
CMakeLists.txtに
cmake_minimum_required(VERSION 3.13) include(pico_sdk_import.cmake) project(test_project C CXX ASM) set(CMAKE_C_STANDARD 11) set(CMAKE_CXX_STANDARD 17) pico_sdk_init() add_executable(test test.c ) pico_enable_stdio_usb(test 1) pico_enable_stdio_uart(test 0) pico_add_extra_outputs(test) target_link_libraries(test pico_stdlib hardware_adc)
$ export PICO_SDK_PATH=../pico-sdk $ mkdir build $ cd build $ cmake .. $ make
$ ls CMakeCache.txt pico-sdk CMakeDoxyfile.in test.bin CMakeDoxygenDefaults.cmake test.dis CMakeFiles test.elf Makefile test.elf.map cmake_install.cmake test.hex elf2uf2 test.uf2 generated $
$ cp test.uf2 /Volumes/RPI-RP2/
これでUSBを引っこ抜いて挿し直すとこのプログラムが動き出すはずである。
とりあえずmacOS側でさっきのキャラクタデバイスとやりとりできるように
$ screen /dev/tty.usbmodem0000000000001 9600
received character [a] received character [s] received character [d] received character [f] timeout. Raw value: 0x2f7, voltage: 0.611 V, temperature 27.73 degree timeout. Raw value: 0x2f6, voltage: 0.611 V, temperature 27.99 degree
確認できた。
これでとりあえずのPi PicoとmacOSとの通信手段が手に入った。あとはZeroWだとOSにおんぶにだっこできた部分をどこまでpico-sdkで肩代わりできるか、というのをみていく。
2021-05-12 21:36
nice!(0)
コメント(0)
コメント 0