Ubuntu Weekly Recipe

第684回UbuntuからRaspberry Pi Picoを使う

2021年の1月にRaspberry PiシリーズにRaspberry Pi Picoが追加されました。これは「マイコンボード」と呼ばれるカテゴリーの製品です。今回はUbuntu上でPico用のファームウェアをビルドし、Picoに書き込み、起動する手順を説明します。

Raspberry Pi Pico

Raspberry Pi Picoは、これまでのRaspberry Piとは異なり「マイコンボード」とも呼ばれる、省電力・小サイズ・低コストなデバイスです。プロセッサーとしてデュアルコアのCortex-M0+(最大133MHz)に加えて、264KBのSRAMや2MBのFlash Memoryが搭載されています。結果的に、Debian/Ubuntuのような一般的なLinux OSは動作しません[1]⁠。

Raspberry Pi Picoのマイコンボードとしての特徴のひとつが余計な専用ツールが不要なことです。実行イメージのコンパイルは一般的なGCCコンパイラーでできますし、イメージの書き込みに至っては普通のUSBケーブルを接続して、USBマスストレージとして見えるフォルダーにドラッグアンドドロップするだけです。必要な情報のほとんどはRaspberry Piの公式サイトに資料としてまとまっているため、ちょっと使ってみるだけなら苦労することもないでしょう[2]⁠。

必要な機材は次のとおりです。

  • Raspberry Pi Pico本体
  • USB 2.0 Micro-Bのプラグが付いたUSBケーブル[3]
  • 上記を接続するPC

なおPicoはピン有り・無しの両方が売られています。いろんなオプションボードを試してみたいものの、はんだ付けは苦手という人はピン有りを選んでおきましょう。

PCもUSB給電ができ、USBマスストレージクラスに対応しているるなら種類は問いません。Picoの場合、Raspberry Piのみを用いて開発する方法も想定しているようですし、ドキュメントの解説もそちらがメインです。

上記を用意したら最初にサンプルプログラムを動かしましょう。正式な由来は不明なのですが、この手のデバイスは「まずLEDを光らせる」のが社会人としてのマナーなんだそうです[4]⁠。そして幸いにもサンプルページにはビルド済みのLEDを光らせるファームウェアが用意されています。

購入直後はおそらく、Flashは空のはずです。この場合、PicoをUSBケーブルでPCに接続するとUSBマスストレージとして認識し、自動的にマウントされます。dmesgとlsusbの出力結果は次のとおりです。

$ sudo dmesg
(中略)
[  753.792803] usb 1-1: new full-speed USB device number 8 using xhci_hcd
[  753.942280] usb 1-1: New USB device found, idVendor=2e8a, idProduct=0003, bcdDevice= 1.00
[  753.942293] usb 1-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[  753.942299] usb 1-1: Product: RP2 Boot
[  753.942303] usb 1-1: Manufacturer: Raspberry Pi
[  753.942307] usb 1-1: SerialNumber: E0C912D24340
[  753.975352] usb-storage 1-1:1.0: USB Mass Storage device detected
[  753.975457] scsi host0: usb-storage 1-1:1.0
[  753.975571] usbcore: registered new interface driver usb-storage
[  753.977555] usbcore: registered new interface driver uas
[  754.981713] scsi 0:0:0:0: Direct-Access     RPI      RP2              1    PQ: 0 ANSI: 2
[  754.982348] sd 0:0:0:0: Attached scsi generic sg0 type 0
[  754.982926] sd 0:0:0:0: [sda] 262144 512-byte logical blocks: (134 MB/128 MiB)
[  754.983245] sd 0:0:0:0: [sda] Write Protect is off
[  754.983254] sd 0:0:0:0: [sda] Mode Sense: 03 00 00 00
[  754.983561] sd 0:0:0:0: [sda] No Caching mode page found
[  754.983574] sd 0:0:0:0: [sda] Assuming drive cache: write through
[  755.005984]  sda: sda1
[  755.037747] sd 0:0:0:0: [sda] Attached SCSI removable disk

$ lsusb | grep Rasp
Bus 001 Device 008: ID 2e8a:0003 Raspberry Pi RP2 Boot

もし認識しないようなら、一旦ケーブルを抜いてからPico上にあるBOOTSELボタンを押しながらUSBケーブルを接続してください。慣れるまでは腕が3本ぐらいほしくなる手順です。USBコネクタはPC側を挿抜したほうがやりやすいと思います。とにかくUSBマスストレージとして認識されるはずです。

ちなみにディレクトリの中身を見るとINDEX.HTMINFO_UF2.TXTというファイルが見えるかと思います。前者はRaspberry Piのマイクロコントローラーのページ?version=シリアルナンバーのパラメーター付きでアクセスするだけです。後者は次のようなファームウェアの情報が表示されます。

shibata@nuc:~$ cat /media/shibata/RPI-RP2/INFO_UF2.TXT
UF2 Bootloader v1.0
Model: Raspberry Pi RP2
Board-ID: RPI-RP2

どちらも気にすることはないでしょう。

デスクトップ版のUbuntuなら、Picoは「/media/shibata/RPI-RP2/」以下にマウントされています。よって次のようにファームウェアを書き込みましょう。

$ wget https://rptl.io/pico-blink -O blink.uf2
$ cp blink.uf2 /media/shibata/RPI-RP2/

これだけでPicoは自動的に再起動し、おもむろにLEDの点滅が始まります。やりました。

ここではCLIからコピーしましたが、もちろんファイルアプリからドラッグアンドドロップでコピーしても問題ありません。

SDKのインストールのサンプルのビルド

これだけだと味気ないので、開発環境を用意して、自分でサンプルコードをビルドしてみましょう。これに関してはRaspberry Pi PicoのGetting started with Raspberry Pi PicoというPDFドキュメントにより詳細な手順が記載されています。

最初にビルドに必要なパッケージ一式をインストールしておきます。今回はUbuntu 20.04 LTSで確認しましたが、サポートされているUbuntuならどのリリースでも同じはずです。

$ sudo apt install gcc cmake gcc-arm-none-eabi \
  libnewlib-arm-none-eabi build-essential

Picoのドキュメントだと「libstdc++-arm-none-eabi-newlib」を手作業でインストールするようにとの指示がありますが、Ubuntu 20.04 LTSだとlibnewlib-arm-none-eabiパッケージのRecommendsに指定されているため、Ubuntu側の設定を変えていなければ一緒にインストールされるはずです。

次のpico-sdkとpico-examplesのコードをそれぞれcloneします。場所はどこでもかまいません。また、submoduleも更新しておきます。

$ git clone -b master https://github.com/raspberrypi/pico-sdk.git
$ cd pico-sdk
$ git submodule update --init
$ cd ..
$ git clone -b master https://github.com/raspberrypi/pico-examples.git

pico-sdkはPico用のファームウェアを構築する際に必要なコード一式とそれをビルドするためのcmakeファイルから成り立っています。よっていわゆる「SDKをビルドする」必要はありません。基本的に、ファームウェア本体をビルドするときに参照されて、一緒にビルドされるだけです。

言い換えるとファームウェアのビルド時にpico-sdkのパスを参照できるようにしておかなくてはなりません。一番手っ取り早いのは環境変数PICO_SDK_PATHを指定しておくことです。pico-sdkのclone先を固定化できるのであれば、~/.bashrcに書いておくのもひとつの手でしょう。

$ export PICO_SDK_PATH=$PWD/pico-sdk
$ ls -1 $PICO_SDK_PATH
CMakeLists.txt
LICENSE.TXT
README.md
cmake
docs
external
lib
pico_sdk_init.cmake
pico_sdk_version.cmake
src
test
tools

次にPico用ファームウェアのサンプルコードをビルドします。サンプルページのREADME.mdにもあるように、このリポジトリには大量のサンプルコードが用意されています。よってPicoでできる基本的なことは、まずはここのリポジトリを参考にすると良いでしょう。

先ほど利用したblink.uf2を自前でビルドしてみます。

$ cd pico-examples
$ mkdir build
$ cd build
$ cmake ..
Using PICO_SDK_PATH from environment ('/home/ubuntu/pico-sdk')
PICO_SDK_PATH is /home/ubuntu/pico-sdk
Defaulting PICO_PLATFORM to rp2040 since not specified.
Defaulting PICO platform compiler to pico_arm_gcc since not specified.
-- Defaulting build type to 'Release' since not specified.
PICO compiler is pico_arm_gcc
-- The C compiler identification is GNU 9.2.1
-- The CXX compiler identification is GNU 9.2.1
-- The ASM compiler identification is GNU
-- Found assembler: /usr/bin/arm-none-eabi-gcc
Defaulting PICO target board to pico since not specified.
Using board configuration from /home/ubuntu/pico-sdk/src/boards/include/boards/pico.h
-- Found Python3: /usr/bin/python3.8 (found version "3.8.10") found components: Interpreter
TinyUSB available at /home/ubuntu/pico-sdk/lib/tinyusb/src/portable/raspberrypi/rp2040; adding USB support.
-- Configuring done
-- Generating done
-- Build files have been written to: /home/ubuntu/pico-examples/build

$ cd blink
$ make -j $(nproc)
Scanning dependencies of target ELF2UF2Build
Scanning dependencies of target bs2_default
[  0%] Creating directories for 'ELF2UF2Build'
[  0%] Building ASM object pico-sdk/src/rp2_common/boot_stage2/CMakeFiles/bs2_default.dir/compile_time_choice.S.obj
[  0%] Linking ASM executable bs2_default.elf
(中略)
[100%] Linking CXX executable blink.elf
[100%] Built target blink

上記手順通り実施すれば現在のディレクトリpico-examples/build/blink「blink.uf2」が残っているはずです。これをマスストレージ経由で書き込んでみましょう。もしすでにPicoをPCに繋いでいるなら、一旦USBケーブルを取り外してください。Pico上にあるBOOTSELボタンを押しながらUSBケーブルを接続します。そうすればUSBマスストレージデバイスとして認識されるはずなので、そのトップディレクトリにblink.uf2をコピーします。コピーが終わったら自動的に再起動して、LEDが点滅するはずです。

ちなみにpico-sdkでそのまま作ったUF2ファイルなら、新しいファームウェアを書き込むと上書きされ、過去に書いたファームウェアは消えてしまいます。

USBシリアルコンソールを使ってみる

これだけだと最初の結果と同じなので味気ないですね。せっかくなのでPicoからなにか表示させてみましょう。Picoには複数のUARTピンがあります。さらにFlash書き込み・給電用に使っているUSBポートにつながっているコントローラーは、ホストとしてもデバイスとしても振る舞えます。そこで余計な配線を省いてテストするために、⁠USBポートからUSBシリアルコンソールとしてデータを出力するファームウェア」を作ってみましょう。

もちろんこれも、pico-examplesにすでに存在します。hello_world/usbがそれです。ちなみにhello_world/serialを使えば、USBではなくてUART0に出力します。

$ cd ../hello_world/usb/
$ make -j $(nproc)
(中略)
[ 50%] Building C object hello_world/usb/CMakeFiles/hello_usb.dir/home/ubuntu/pico-sdk/src/rp2_common/pico_stdio/stdio.c.obj
[ 50%] Building C object hello_world/usb/CMakeFiles/hello_usb.dir/home/ubuntu/pico-sdk/src/rp2_common/pico_stdio_usb/reset_interface.c.obj
[ 50%] Building C object hello_world/usb/CMakeFiles/hello_usb.dir/home/ubuntu/pico-sdk/src/rp2_common/pico_stdio_usb/stdio_usb.c.obj
[ 50%] Building C object hello_world/usb/CMakeFiles/hello_usb.dir/home/ubuntu/pico-sdk/src/rp2_common/pico_stdio_usb/stdio_usb_descriptors.c.obj
[ 50%] Building C object hello_world/usb/CMakeFiles/hello_usb.dir/home/ubuntu/pico-sdk/lib/tinyusb/src/portable/raspberrypi/rp2040/dcd_rp2040.c.obj
(中略)
[100%] Linking CXX executable hello_usb.elf
[100%] Built target hello_usb

pico-sdkのほうでstdio_usb.ctinyusbもセットでビルドされていることがわかりますね。実際、pico-examplesのほうのコードはシンプルでUSBシリアルコンソールを使うかどうかをビルドオプションで設定しているだけであり、USBシリアルコンソールに関するコードはpico-sdkのほうに存在します。

作成されたhello_usb.uf2をUSBマスストレージ経由でPicoに書き込んでください。自動的に再起動し、今度はLEDは点滅しなくなるはずです。

PicoにUSBシリアルコンソールとして接続しましょう。hello_usb.uf2が動いている場合、PCからはUSBコミュニケーションデバイスクラスとして認識され、/dev/ttyACM0が作成されます。このデバイスはudevルールによって、rootとdialoutグループにのみ書き込み権限が与えられています[5]⁠。よって次のようにdialoutグループに所属するか、sudo経由でアクセスする必要があります。

$ sudo usermod -a -G dialout $(whoami)

dialoutグループへの参加は一度ログインしなおすことで、変更が反映されます。idコマンドで確認してみてください。ちなみにdialoutグループに追加したあと、次のようにnewgrpコマンドを実行すると、ログインし直さなくても一時的にプライマリーグループをdialoutに変更できます。

$ newgrp dialout

/dev/ttyACM0へのアクセス権限が得られたので、次はシリアルコンソール接続用のソフトウェアを動かします。minicom、ckermit、picocom、screenなどなど多種多様な選択肢が存在しますが、ここではpicocomを使ってみましょう。

$ sudo apt install picocom
$ picocom /dev/ttyACM0 -b 115200
picocom v3.1
(中略)
Type [C-a] [C-h] to see available commands
Terminal ready
Hello, world!
Hello, world!

(picocomを終了するためにはCtrl-a Ctrl-xを入力)

Terminating...
Thanks for using picocom

ひたすら「Hello, world!」と表示し続けていたら成功です。これによりPicoが、⁠その電力が続く限り世界に挨拶をし続ける」という、狂気のデバイスへと生まれ変わりました。

Picoを姿勢測定デバイスとして動かす

既存のRaspberry Piと比べるとPicoは、単体でできることが限られています。その真価は、各種IOポートにいろいろなデバイスを接続することで発揮できるのです。

すでにPico向けのさまざまなオプションボードが登場しています。そこで今回は最後にWaveshareのセンサーデバイスであるPico-10DOF-IMUを使ってみましょう。これはそれぞれ3軸の加速度センサー・ジャイロセンサー・地磁気センサーを備えたICM-20948と気圧・気温センサーであるLPS22HBを搭載したPico向けのボードで、ピンヘッダがついているPicoであれば接続してすぐ利用できます[6]⁠。

もちろんPico用のサンプルコードもあるので、⁠とりあえず試す」だけならファームウェアをビルドして書き込むだけで済みます。

$ sudo apt install unzip
$ wget https://www.waveshare.com/w/upload/3/3d/Pico-10dof-imu.zip
$ unzip Pico-10dof-imu.zip

サンプルコードはC言語向けとMicroPython向けが用意されています。Picoは公式にMicroPythonもサポートしており、Pythonスクリプトを実行するだけでなくインタープリター環境を持ったファームウェアを書き込むことも可能です。今回はC言語のほうを使うことにします。

$ cd pico-10dof-imu/c/build/
$ cmake ..
Using PICO_SDK_PATH from environment ('/home/ubuntu/pico-sdk')
PICO_SDK_PATH is /home/ubuntu/pico-sdk
Defaulting PICO_PLATFORM to rp2040 since not specified.
Defaulting PICO platform compiler to pico_arm_gcc since not specified.
-- Defaulting build type to 'Release' since not specified.
PICO compiler is pico_arm_gcc
-- The C compiler identification is GNU 9.2.1
-- The CXX compiler identification is GNU 9.2.1
-- The ASM compiler identification is GNU
-- Found assembler: /usr/bin/arm-none-eabi-gcc
Defaulting PICO target board to pico since not specified.
Using board configuration from /home/ubuntu/pico-sdk/src/boards/include/boards/pico.h
-- Found Python3: /usr/bin/python3.8 (found version "3.8.10") found components: Interpreter
TinyUSB available at /home/ubuntu/pico-sdk/lib/tinyusb/src/portable/raspberrypi/rp2040; adding USB support.
-- Configuring done
-- Generating done
-- Build files have been written to: /home/ubuntu/pico-10dof-imu/c/build

$ make -j $(nproc)
(中略)
[100%] Linking CXX executable imu.elf
[100%] Built target imu

このあたりはpico-examplesと手順は同じですね。最終的にimu.uf2が作られるため、これをPicoに書き込んでください。

このファームウェアはUSBシリアルとUART0の両方に出力する設定になっています。具体的にはpico-10dof-imu/c/CMakeLists.txtの次の記述が該当します。

pico_enable_stdio_usb(imu 1)
pico_enable_stdio_uart(imu 1)

そこで「USBシリアルを使ってみる」と同じように、/dev/ttyACM0にpicocomで接続してみます。

$ picocom /dev/ttyACM0 -b 115200
picocom v3.1
(中略)
 /-------------------------------------------------------------/

 Roll: 2.62     Pitch: -1.49     Yaw: -158.66
Pressure = 999.34 hPa , Temperature =  31.46 °C

 /-------------------------------------------------------------/

 Roll: 2.57     Pitch: -1.44     Yaw: -158.57
Pressure = 999.33 hPa , Temperature =  31.45 °C

100ms周期で、⁠各軸の回転角度と気圧・気温」を表示してくれていることがわかります。

ロール(Roll)はX軸の回転角度で、ピッチ(Pitch)がY軸、ヨー(Yaw)がZ軸になります。デバイスに対してXYZ軸がどちらを向いているのかはPico-10DOF-IMUのボード上に記載されています。ただしPicoに接続してしまうと隠れてしまうので注意が必要です。回路図のPDFのPDFにも記載されているため、そちらを参照すると良いでしょう。USBコネクタのシルクがある面を見て、USBコネクタのシルクが上にある状態にしたときに、向かって右方向がX軸の正の向き、上方向がY軸の正の向き、手前方向がZ軸の正の向きになるようです(いわゆる右手系です⁠⁠。

実際に上記の出力を眺めながら個々の軸を回転させるとイメージがつかめると思います。

pico-10dof-imu/c/main.cが本体のコードです。ここから中身を見ていくと良いでしょう。たとえば、メインループの中では次のように、各センサーの生データの出力がコメントアウトされています。

                printf("\r\n /-------------------------------------------------------------/ \r\n");
                printf("\r\n Roll: %.2f     Pitch: %.2f     Yaw: %.2f \r\n",stAngles.fRoll, stAngles.fPitch, stAngles.fYaw);
           printf("Pressure = %6.2f hPa , Temperature = %6.2f °C\r\n", PRESS_DATA, TEMP_DATA);
                //printf("\r\n Acceleration: X: %d     Y: %d     Z: %d \r\n",stAccelRawData.s16X, stAccelRawData.s16Y, stAccelRawData.s16Z);
                //printf("\r\n Gyroscope: X: %d     Y: %d     Z: %d \r\n",stGyroRawData.s16X, stGyroRawData.s16Y, stGyroRawData.s16Z);
                //printf("\r\n Magnetic: X: %d     Y: %d     Z: %d \r\n",stMagnRawData.s16X, stMagnRawData.s16Y, stMagnRawData.s16Z);
                sleep_ms(100);

これをコメントアウトを外して//を削除して⁠⁠、再ビルドし(makeを実行し⁠⁠、ファームウェアを書き込むと次のような結果が得られます。

 Roll: 1.45     Pitch: 0.38     Yaw: -174.00
Pressure = 1004.02 hPa , Temperature =  26.06 °C

 Acceleration: X: -234     Y: -198     Z: 16970

 Gyroscope: X: 8     Y: 2     Z: -9

 Magnetic: X: 97     Y: -41     Z: -504

Pioc-10DOF-IMUのコードは本当にサンプルと言うべきシンプルなものなので、これをとっかかりにしていろいろカスタマイズすると良いでしょう。サンプルコードはクォータニオンの計算をソフトウェア側で行っています。ICM-20948にはDMP(Digital Motion Processor)と呼ばれる、このあたりの計算を行ってくれるプロセッサーが搭載されています。DMPを使うようにするのもおもしろいかもしれませんね[7]⁠。

おすすめ記事

記事・ニュース一覧