Raspberry Pi Picoを買いました

今更ながら620円でRaspberry Pi Picoを買いました!PCのUSBに繋ぐと…

  • LED光ります!
  • (ボードの)温度測れます!

(電子工作をしない場合)それしか出来ない小さな基板です。

今回はこの基板のLED光らせて、電子工作なしにデバッグしてみたりします。

Raspberry Pi Picoとは

https://www.raspberrypi.com/products/raspberry-pi-pico/

およそ2cm x 5cmの小さな基板にUSBがついて、ARMが乗ってるRAM264KB/ROM2MBのマイコン基板です。Wifiがついてるモデルもあるのですが、今回購入したのはついてない初代のやつになります。

Raspberry Piシリーズと言えば、電子工作も出来るものの、Linuxを搭載してて普通にディスプレイ繋いだりもする高機能モバイル機器的なイメージがあります。しかし、この商品は他のとは一線を画すOS不要なマイコン基板(家電などに組み込まれる制御用の基板)で、スペックが大幅にダウンしてる代わりに$4という格安な値段設定になっています。同じ分野の競合だとArduino辺りで、実際にArduinoもこの基板に対応しています。比較するとRasberry Pi Picoは電力消費はやや高いものの、CPUパワーやメモリ容量は値段の割に高く、機能的にWifiやBluetoothがないという弱点はあるものの(Wifiはついてるモデルが出ています)、USBケーブル一本で電子工作なしにシリアル通信でMicroPythonを動かせるなど学習用に最適な商品になってる気がします。

本記事ではPythonは使いません。C/C++のみです。

接続

USBはmicro usb type Bです。仕様は1.1なのでBでも3.0はささりません。これでPCとPicoを繋ぎます。繋いでもせいぜい光るだけですが、、、

開発環境の準備

Raspberry Pi Picoの公式による開発開始用ドキュメントはこちらにあります。

https://datasheets.raspberrypi.com/pico/getting-started-with-pico.pdf

LinuxやWindowsやMac、IDEもVisualStudioCodeやEclipseなどいろいろな製品が使用可能なようですが、公式でもっともオーソドックスな組み合わせだと、Linux+VisualStudioCodeになるようです(LinuxはRaspberry Piの他のシリーズが想定されている)。

今回はWindows 10マシンのVirtualBox上で動いているUbuntu20.04に開発環境を構築します。制御系ということで環境構築でこけやすいため、リモートではなくUbuntu上に直接構築し、dockerも使用していません。そういう意味では本当はvirtualboxでなくnativeなLinuxの方がいいのですが、諸々の事情でこうなっています。

OS編

母艦はWindows10ですが、VMでLinuxを使います。

VirtualBox 6.1.40

https://www.virtualbox.org/wiki/Downloads

今回は6.1.40を使用しました。すでに7.0出てますけど、面倒でバージョンアップしてません。Windows上のUSBをLinuxから使うため、同じバージョンのExtension Packも入っています。WSLでも出来るんだと思いますが、GUIも使いたいのでVirtualBoxにしています。

Ubuntu 20.04 Desktop

Linuxとして今回はUbuntu 20.04を使用しています。すでに22.04出てますけど、面倒でバージョンアップしてません。入れ方などは省略です。Windows10上のVirtualBoxの中で動かしています。

アプリ編

Ubuntu上にインストールするアプリです。

SDKとexample

開発用SDKとそのexampleが本家で用意されているのでgitでcloneします。SDKを使用するときは環境変数PICO_SDK_PATHにgitで展開したSDKのパスを設定してください。

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

pico-examplesはこの記事でも動作確認に使われています。

Raspberry Pi Picoで使用可能なSDKは本家SDKの他にArduinoのSDKがありますが、今回は本家のSDKを選択してます。

ビルドツール

PCはx86_64でPicoはarmなので、PCでビルドするにはarm用のクロスのビルドツールが必要になりますが、こちらはubuntuの方でパッケージが用意されているのでそれを使います。

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

Visual Studio Code

https://code.visualstudio.com/Download

コーディングとビルドまではこれを使用しています。デバッグにも使いたかったのですが、今回使用する特殊なデバッガではうまく動きませんでした。本記事でのデバッグはgdbのcliになります。なお、Rasberry Pi Pico本家が想定しているデバッグ方法ならVisual Studio Codeも使えるのだと思います(未確認ですが)。

なお、SDKにArduino SDKを使った場合は、Arduino IDEも使えるようです。今回は使用していません。

Visual Studio Codeの拡張機能

以下の3つを入れておきます。

  • C/C++(Pico関係なく普通に使うと思う)
  • CMake Tools(Pico関係なく普通に使うと思う)
  • Cortex-Debug(PicoのCPU用のデバッグツール)

拡張機能としては、PlatformIOを使うと、pico-sdkとArduino SDKを選んで使うことができるそうですが、今回は使用していません。

デバッガ

Raspberry Pi Pico本来のデバッグは、Pico本体の他にもう一つprobeとして使用するハードを用意し(典型的には他のRaspberry Piシリーズか、もう一つのPico本体)、それらをシリアル入出力を電子工作で繋いで行います(SWD=Serial Wired Debug)。従ってRaspberry Pi Pico本来のデバッガもこの仕組みを前提としたものになっており、この記事のデバイス構成では実現できません。

この記事でのデバッグはそれとは違う特殊な方法なので、環境準備も含めて別途章立てして解説します。

pico上で動作するアプリの開発

開発と言っても、この章ではpico-examplesのアプリをビルドし、そのいくつかを実行するだけです。

pico-examplesのビルド

pico-examplesには多くのアプリの実装例がありますが、それらをまとめてビルドします。以下では2通りのビルド方法を示します。

コマンドラインからビルド

端末からは以下のようにビルドできます。

$ cd pico-example
$ mkdir build
$ cd build
$ cmake ..
$ make

debug情報を入れる場合はcmake ..の代わりにcmake -DCMAKE_BUILD_TYPE=Debug ..に変更してください。

Visual Studio Codeからビルド

  1. Visual Studio Codeからpico-exampleフォルダを開きます
  2. 開いたフォルダの直下に.vscodeディレクトリを作成します
  3. ide/vscode/*を.vscodeディレクトリにコピーします
  4. Visual Studio Codeの下部の青いバーにある小さなCMake用のボタンを押下し、Debug/Releaseなど必要な構成を選択します
  5. Visual Studio Codeの下部の青いバーにある小さなToolkit選択用のボタンを押下し、PicoのCPU用にGCC 9.2.1 arm-none-eabiを選択します
  6. メニューからターミナル>ビルドタスクの実行を選択して、CMake: buildを選択します

pico-examplesを実行

アプリは複数ビルドされていますが、まずはLEDを点滅させるアプリを実行させてみます。

LED光らせる!

俗に言うLチカってやつです。

ソースコード

ビルドしたpico-examplesのうち、LEDを光らせるアプリはpico-examples/blinkの下にソースがあります。誰でも分かる簡単そうなコードですよね。

/**
 * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include "pico/stdlib.h"

int main() {
#ifndef PICO_DEFAULT_LED_PIN
#warning blink example requires a board with a regular LED
#else
    const uint LED_PIN = PICO_DEFAULT_LED_PIN;
    gpio_init(LED_PIN);
    gpio_set_dir(LED_PIN, GPIO_OUT);
    while (true) {
        gpio_put(LED_PIN, 1);
        sleep_ms(250);
        gpio_put(LED_PIN, 0);
        sleep_ms(250);
    }
#endif
}

メモリイメージファイル

ソースコードをビルドして出来上がる.exeなどの実行可能ファイルは、実行すると、シェルがRAMなどのメモリにロードして、エントリポイントから実行されるわけですが、マイコン基板での実行可能ファイルはシェルから実行されるのではなく、典型的にはROMに焼き付けられてからリセットされ、BOOTプログラムから(OSがないので)直接実行されます。なので実行可能ファイルというよりは、より原始的なメモリイメージファイルに近いです。

LEDを光らせるアプリをビルドして出来たメモリイメージファイルは、pico-examples/build/blink/blink.uf2になります。.uf2ファイルの中身はUSB Flashing Formatと呼ばれるもので、仕様は以下にあります。

https://github.com/Microsoft/uf2

フラッシュ(ROM焼き)と実行

ビルドして出来た.uf2ファイルをROMに焼くには、Raspberry Pi PicoをUSBマスストレージ(USBメモリ)として起動しないといけません。このモードで起動するためには基板にある小さな灰色の丸いボタン、BOOTSELを押しながらUSBを差し込まないといけません。このUSBメモリに先のバイナリを放り込むと自動的にROMをuf2ファイルの内容に書き換えて再起動してくれます。

具体的には以下のとおりになります。

  1. PicoにUSBが刺さってたら外す
  2. BOOTSELを押しながらUSBケーブルのコネクタを差し込む
  3. WindowsでUSBデバイスの取り外しメニューからRPI-PR2を外す
  4. VirtualBox上のメニューから、Raspberry Pi RP2 Boot[0100]を選択してチェックを入れる
  5. Ubuntu上でファイルマネージャー相当が起動するので、そこに当該ファイル(blink.uf2)を放り込む

マウント後はコマンドから普通にコピーしてもいいです。コピーが終わるとROMを書き換えて再起動し、LEDを点滅してくれます。

温度を測る!

基板の温度を標準出力にダラダラ出力します。pico-examples/adc/onboard_temperature/onboard_temperature.cがソースになります。

/**
 * Copyright (c) 2021 Raspberry Pi (Trading) Ltd.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 *
 */

#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/adc.h"

/* Choose 'C' for Celsius or 'F' for Fahrenheit. */
#define TEMPERATURE_UNITS 'C'

/* References for this implementation:
 * raspberry-pi-pico-c-sdk.pdf, Section '4.1.1. hardware_adc'
 * pico-examples/adc/adc_console/adc_console.c */
float read_onboard_temperature(const char unit) {
    
    /* 12-bit conversion, assume max value == ADC_VREF == 3.3 V */
    const float conversionFactor = 3.3f / (1 << 12);

    float adc = (float)adc_read() * conversionFactor;
    float tempC = 27.0f - (adc - 0.706f) / 0.001721f;

    if (unit == 'C') {
        return tempC;
    } else if (unit == 'F') {
        return tempC * 9 / 5 + 32;
    }

    return -1.0f;
}

int main() {
    stdio_init_all();
#ifdef PICO_DEFAULT_LED_PIN
    gpio_init(PICO_DEFAULT_LED_PIN);
    gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT);
#endif

    /* Initialize hardware AD converter, enable onboard temperature sensor and
     *   select its channel (do this once for efficiency, but beware that this
     *   is a global operation). */
    adc_init();
    adc_set_temp_sensor_enabled(true);
    adc_select_input(4);

    while (true) {
        float temperature = read_onboard_temperature(TEMPERATURE_UNITS);
        printf("Onboard temperature = %.02f %c\n", temperature, TEMPERATURE_UNITS);

#ifdef PICO_DEFAULT_LED_PIN
        gpio_put(PICO_DEFAULT_LED_PIN, 1);
        sleep_ms(10);

        gpio_put(PICO_DEFAULT_LED_PIN, 0);
#endif
        sleep_ms(990);
    }

    return 0;
}

CPU内蔵の温度センサで測定し、A/D変換された値から温度を計算してprintfしてるだけです。しかしRaspberry Pi Pico上でprintfしても画面がないのでどこに出力されるのか分かりません。デフォルトだと特定のピンにシリアル出力するだけなので、そのピンの信号を実際のシリアルケーブルに流すための電子工作をして、それをPCまで繋げないと出力内容を見ることができません。

しかし、Raspberry Pi PicoのSDKではprintfをUSBシリアル(CDC)に流すことが出来る設定があり、こうするとUSBをPC側からシリアルポートとして使用できるようになります。この設定はCMakefileで変更する事ができます。

コードの修正

--- a/adc/onboard_temperature/CMakeLists.txt
+++ b/adc/onboard_temperature/CMakeLists.txt
@@ -3,8 +3,8 @@ add_executable(onboard_temperature onboard_temperature.c)
 target_link_libraries(onboard_temperature pico_stdlib hardware_adc)
 
 # enable uart output, disable usb output
-pico_enable_stdio_uart(onboard_temperature 1)
-pico_enable_stdio_usb(onboard_temperature 0)
+pico_enable_stdio_uart(onboard_temperature 0)
+pico_enable_stdio_usb(onboard_temperature 1)
 
 # create map/bin/hex file etc.
 pico_add_extra_outputs(onboard_temperature)

uartというのが本物のシリアルで、usbというのがUSB CDCです。

https://raspberrypi.github.io/pico-sdk-doxygen/group__pico__stdio.html

ビルド方法とフラッシュ方法はblinkと同じなので省略します(.uf2は別のファイルになりますが)。

(USB)シリアルポートの存在確認

再起動したPicoはUSB経由でシリアルデータ(温度)をprintfしてるはずですが、これはどのように受け取ればいいのでしょうか?

  1. まずは再起動して外れたことになってしまってるUSBをVirtualBoxに戻します
  2. VirtualBoxのメニューからデバイス>USB>Raspberry Pi Pico[0100]を選択してチェックを入れます

こうすることで、Raspberry Pi PicoのUSB CDC機能で作られたUSBシリアルポートが、WindowsからVirtialBoxのUbuntuに繋がります。Linuxではコマンドラインからlsusbをすることで、USBの接続状態を確認できます。

$ lsusb
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 002 Device 024: ID 2e8a:000a Raspberry Pi Pico
Bus 002 Device 002: ID 80ee:0021 VirtualBox USB Tablet
Bus 002 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
$

Rasberry Pi Picoが表示されてれば接続されてます。そしてUSB CDCとして認識されていれば、シリアルデバイス/dev/ttyACM0が出来ています。

(USB)シリアル通信

picocomというソフトをaptでinstallします。

$ sudo apt install picocom

シリアルデバイスへのアクセス権を付与します。

$ sudo usermod -a -G dialout $USER
$ sudo usermod -a -G plugdev $USER

一旦ログインし直してから、以下のコマンドでprintf出力を見る事ができます。

$ picocom /dev/ttyACM0 -b 115200
picocom v3.1

port is        : /dev/ttyACM0
flowcontrol    : none
baudrate is    : 115200
parity is      : none
databits are   : 8
stopbits are   : 1
escape is      : C-a
local echo is  : no
noinit is      : no
noreset is     : no
hangup is      : no
nolock is      : no
send_cmd is    : sz -vv
receive_cmd is : rz -vv -E
imap is        : 
omap is        : 
emap is        : crcrlf,delbs,
logfile is     : none
initstring     : none
exit_after is  : not set
exit is        : no

Type [C-a] [C-h] to see available commands
Terminal ready
Onboard temperature = 14.03 C
Onboard temperature = 14.03 C
Onboard temperature = 14.03 C
Onboard temperature = 14.03 C
Onboard temperature = 13.56 C
Onboard temperature = 14.03 C
Onboard temperature = 14.03 C
Onboard temperature = 13.56 C
Onboard temperature = 14.03 C
Onboard temperature = 14.03 C

Terminating...
Thanks for using picocom
$

終了はCtrl+A Ctrl+Xです。温度表示行以外は基板から来たメッセージではありません。

Windowsで見たい!

わざわざUbuntuで見なくても、Windowsにteratermなどを入れれば見れます。シリアルを選んで、USBっぽいCOMポートを選べばOKです。

gdbでデバッグ

既に説明したとおり、普通はRaspberry Pi Picoを2台用意するか、他のRaspberry Piシリーズを用意してシリアルで繋いでデバッグ(Serial Wired Debug=SWD)します。片側のPico、もしくは他のPiシリーズはprobeとしての扱いです。必ず存在する外部との通信ポートであるUSBをちゃんと残すために、デバッガは別の線(Serial)を使って実機とやり取りするわけです。

しかし学習用かつ電子工作なしで使いたい場合は必ずしもUSBを残す必要がありません。そこでUSBをデバッグ用に使ってしまい、さらに未使用のCPUコアをプローブに使ってしまおうというコンセプトで作られたのが、pico-debugというソフトです。

pico-debug

プロジェクトのGithubは以下にあります。

https://github.com/majbthrd/pico-debug

使い方

書いてあるとおりです。

https://github.com/majbthrd/pico-debug/blob/master/howto/README.md

デバッグ対象のビルド

デバッグ対象のソースをデバッグ構成でビルドするだけです。今回ビルド結果として使用するバイナリファイルは.uf2ではなく、デバッグ情報が付加された.elfになります。

pico-debugを起動

今回.uf2にしてアップロードするのはpico-debugです。少しコードは見たのですが、このコードをどうビルドするのかは分かりませんでした。配布されているバイナリをそのまま使用したため、気になる人は使用を控えた方がいいかもしれません。

https://github.com/majbthrd/pico-debug/releases

使用したのはpico-debug-gimmecache.uf2です。BOOTSELスイッチと.uf2の意味するところは、実際にはROM焼きではなくUSBブート(通常はフラッシュROMブート)なので、このプログラムはRAM上にロードされ、リセット(完全な初期化)もされません。

USBから起動したらvirtualboxのメニューからデバイス>USB>pico-debug CMSIS-DAP[1005]を選択してvirtualboxに繋ぎます。これで別コアでprobeが動作してるRaspberry Pi Picoが起動し、UbuntuからUSBのCMSIS-DAPでprobeに指示を飛ばせる状態になりました。

openocdを起動

probeに指示を飛ばすのはopenocdというソフトが行います。先にpico-debugで配布バイナリをそのまま使ったこともあり、これも配布のものをそのまま使ってしまいました(こちらは普通のUbuntuアプリなのでビルドは簡単そうでしたが)。

https://github.com/earlephilhower/pico-quick-toolchain/releases/

使用したのはx86_64-linux-gnu.openocd-e3428fadb.220714.tar.gzです。展開して起動します。

$ tar xvfz x86_64-linux-gnu.openocd-e3428fadb.220714.tar.gz
$ cd openocd/bin
$ ./openocd -f ../share/openocd/scripts/board/pico-debug.cfg

openocdはローカルホストのポート3333でlistenするサービスとして動作し、gdbから使用されます。

gdbを起動

イメージとしては、serialで繋がるdebug targetとprobeがあって(SWD)、そのprobeがpico-debug、このprobeはUSBのCMSIS/DAPというSWDをUSBから使えるプロトコルを話せるので、そのUSB操作をopenocdが受け持ち、gdbはTCP/IPでopenocdとお話する、という感じです。ユーザーはgdbを使いますが、図にするとこんな感じでしょうか。

user <=> [ gdb <=TCP/IP=> openocd ] <=USB=> [ pico-debug <=> ターゲットプログラム ]

では別端末から、gdbをインストールします

$ sudo apt install gdb-multiarch

pico-exampleのblinkyをデバッグターゲットとして指定してgdbを起動後、openocdと繋ぎます。

$ cd pico-examples/build/picoboard/blinky
$ gdb-multiarch picoboard_blinky.elf
...
(gdb) target remote localhost:3333
Remote debugging using localhost:3333
0x2003bd12 in ?? ()
(gdb)

これで[gdb]-[openocd]-[pico-debug]が繋がり、CLIながらデバッグ操作できる状態になりました。

ターゲットプログラムをロードしてデバッグ

まずはgdbの引数で指定したプログラム(blinky)をRaspberry Pi Picoにロードさせます。

(gdb) load
Loading section .boot2, size 0x100 lma 0x10000000
Loading section .text, size 0x40e8 lma 0x10000100
Loading section .rodata, size 0xf64 lma 0x100041e8
Loading section .binary_info, size 0x20 lma 0x1000514c
Loading section .data, size 0x20c lma 0x1000516c
Start address 0x100001e8, load size 21368
Transfer rate: 2 KB/sec, 3561 bytes/write.
(gdb)

次にリセットして止めます。このリセットはpico-debugが消えないように行われます。

(gdb) monitor reset init
target halted due to debug-request, current mode: Thread 
xPSR: 0xf1000000 pc: 0x000000ea msp: 0x20041f00
(gdb)

blinkyのmain関数にbreak pointを張って、実行再開します。

(gdb) b main
Breakpoint 1 at 0x100003f8: file ../picoboard/blinky/blinky.c, line 67.
(gdb) c
Continuing.
Note: automatically using hardware breakpoints for read-only addresses.

するとすぐにmain関数で止まります。後は通常のgdb操作で

Breakpoint 1, main () at ../picoboard/blinky/blinky.c:67
67      int main() {
(gdb) n
72          gpio_init(LED_PIN);
(gdb) n
73          gpio_set_dir(LED_PIN, GPIO_OUT);
(gdb) n
75              put_morse_str(LED_PIN, "Hello world");
(gdb) n
76              sleep_ms(1000);
(gdb) n
74          while (true) {
(gdb) n
75              put_morse_str(LED_PIN, "Hello world");
(gdb) s
put_morse_str (led_pin=led_pin@entry=25, str=str@entry=0x100041e8 "Hello world") at ../picoboard/blinky/blinky.c:56
56          for (; *str; ++str) {
(gdb) c
Continuing.
^C
Program received signal SIGINT, Interrupt.
time_reached (t=...) at /home/user/rpp/pico-sdk/src/rp2_common/hardware_timer/include/hardware/timer.h:116
116         uint32_t hi_target = (uint32_t)(target >> 32u);
(gdb) detach
Detaching from program: /home/user/rpp/pico-examples/build/picoboard/blinky/picoboard_blinky.elf, Remote target
Ending remote debugging.
[Inferior 1 (Remote target) detached]
(gdb) q
$

一応ちゃんと動いているようです。Visual Studio Codeで出来たらもっといいんでしょうけど。

まとめ

Raspberry Pi PicoをVirtualBox上のUbuntuから操作し、ビルド&デバッグ可能な状態にしてみました。

Raspberry Pi Pico自体は$4にしては遊べるマイコン基板だと思いました。この手の機器はユーザーの多さが情報の多さや品質の高さに直結するように感じるので、ネームバリューと価格性能比でそれなりに売れてるこの基板は学習用に最適な気がしました。