Picoの開発にPlatformIOを使おう(PicoでJPEGデコードサンプル付き)
Raspberry Pi Pico(以下、Pico 1)やRaspberry Pi Pico 2(以下、Pico 2)の開発にPlatformIOを使おうよ、という啓蒙記事です。
PlatformIOを選ぶ理由は、Arduino Frameworkを利用しつつVisual Studio Code(以下、VSCode)で開発したいからというシンプルなものです。VSCodeは拡張機能であれこれできるし、CopilotやGoogle Code AssistantやらAIの手助けも受けられる。Arduino IDEも2.0からかなりいい感じになってますが、VSCodeに慣れてるとやっぱりねえというのがありますよね。
このアーティクルでは、Pico 2を含めたPlatformIOのセットアップや、こんな風にできるよというサンプルとしてカラーLCDにJPEG画像を表示する例を紹介します。
PicoシリーズとPlatformIO
PlatformIOについて多くの説明は不要と思いますが、主としてマイクロコントローラ(マイコン)をターゲットにした統合開発環境です。
かつてのマイコンの開発は、A社のマイコンならこのIDEやツールキットとこのフレームワーク、B社ならこれとこれという感じで、開発ターゲットが変われば開発環境が変わるのが割と普通な感じでした。環境構築の手間や環境が変わる負担が開発効率を下げていた面があったことは否めないでしょう。
PlatformIOはホームページにあるとおり多数のマイコンと開発ボード、そして多数のフレームワークに対応しています。PlatformIOという統一された環境で、さまざまなマイコンを使用した開発を行うことができることが、PlatformIOを利用する大きなメリットです。
PlatformIO自体はコマンドラインのツールですが、公式のGUIフロントエンドとしてVSCodeの拡張機能が提供されています。VSCodeにPlatformIO ExtensionをインストールすればVSCodeで、様々なマイコンの開発が完結でき非常に便利なものです。
PlatformIOはPlatformIO Labsという営利企業が開発しています。つい最近になってPlatform Labの有力なスポンサーのひとつだったEspressifとPlatformIO Labsの間に揉め事が起き、Espressifの新しい製品はPlatformIOでサポートされないことが決まるといったようなことが起きました。
じつはRaspberry Pi Foundation/LTDとの関係もあまり良好ではないようです。RP2040/Pico 1はいちおう対応プラットフォームに入っていますが、PlatformIOの公式サポートの対象ではありません。また、RP2350/Pico 2は対応に入っていないので、開発を行うにはコミュニティが作成したサードパーティのプラットフォームツリーをインストールする必要があります。
PlatformIO Labsからは、RP2040/Pico 1は人気があるため対応プラットフォームに入れているもののRaspberry Pi Foundation/LTDの協力が得られず積極的なサポートが行えないこと、RP2350/Pico 2への対応も未定ということが明らかにされています。Raspberry Pi Foundation/LTDのスタンスが変わらない限り、PlatformIOでPicoシリーズが積極的にサポートされることはなさそうな情勢ではあります。
とはいえ、コミュニティ版を利用すればRP2350/Pico 2の開発も行える状況なので、PlatformIO Labsの姿勢がどうあれ利用に支障はありません。利用する人が増えれば、Raspberry Pi Foundation/LTD.もスタンスを変えるかもしれないので、どんどん使っていったほうがいいでしょう。
PlatformIOをPico 2対応にする
PlatformIOを使ってPico 2/Pico 1の開発を行うためにPCに必要なのは下記の2つです。
- Git
- VSCode
- デバッグプローブ
VSCodeが必要なのは当然として、PlatformIOにサードパーティのプラットフォームサポートをインストールするときにGitをバックエンドで駆動するので、Gitも必ず必要です。Windowsな人はGit for Windowsをインストールしておきましょう。
また、Windowsは長いパス名が有効になっていないとエラーが発生する場合があるようです。ローカルグループポリシーエディタ(gpedit.msc)を起動して左ペインで「管理用テンプレート」→「ファイルシステム」を選び「Win32の長いパスを有効にする」を標準の「いいえ」から「有効」に変更してください。変更できたらWindowsを再起動させます。
図1: Windowsは長いパス名を有効に
デバッグプローブも必須です。ネイティブコード開発なのでデバッグプローブがないとデバッグができません。Raspberry Pi Debug ProbeやPicoprobeを用意します。使い方はリンク先を参考にしてください。
PlatformIOの入手は簡単で、VSCodeの拡張機能をインストールするだけです。これですべてのダウンロードとセットアップが行われます。インストールにはしばらく時間がかかるほか、インストールが終わったらVSCodeの再起動を求められます。
図2: 拡張機能PlatformIO IDEをインストールする
PlatformIO標準だと前述のようにPico 1/RP2040対応しかインストールされません。コミュニティ版のPico 2/RP2350のプラットフォームサポートを利用する方法はいくつかありますが、執筆時点(2025年4月)でいちばん良さげなのはFork of Platformio Raspberry Pi RP2040: development platform for PlatformIOを使わせて貰う方法です。割と活発に開発が行われていて、最近になりArduino Frameworkに加え純正のPico C SDKがフレームワークに追加されました。
もっとも、Pico C SDKはRaspberry Pi Foundation公式の拡張機能が良く出来ているので、わざわざPlatformIOで開発する必要はないかもしれません。CMakeのレシピとPlatformIOの設定ファイルplatform.iniとどちらがお好みか、くらいな感じでしょうか。
PlatformIOをインストールしたVSCodeで、PlatformIO→Platformsを開き、「Advanced Installation」をクリックします。Advanced Platform installationダイアログにhttps://github.com/maxgerhardt/platform-raspberrypiのURLを入力して「Install」ボタンをクリックします。
図3: PlatformIO IDEにサードパーティのプラットフォームを追加
システムにGitがないと、ここでエラーになりますので注意してください。また、インストールにはかなりの時間がかかります。放置しておきましょう。
Pico 2プロジェクトの作成
VSCodeを再起動してPlatformIOのHomeから、New Projectをクリックすると、Project Wizardダイアログが開きます。Board欄にraspberryと入力してみましょう。例のようにPico 2やPico 2WなどPlatformIO単独ではサポートされていないプラットフォームが選択できるようになっていることが確認できます。
図4: Board:でPico 2などが選択できる
Framework欄ではArduinoと純正のPico C SDKの選択ができます。前出のようにPico C SDKならRaspberry Pi Foundation純正拡張機能を使ったほうがいいように思うので、ここではArduinoに限定して話を進めていきます。
Board欄でPico 2を選び、FrameworkにArduinoを選択し、Name欄に適当なプロジェクト名を入れてプロジェクトを新規作成します。しばらく時間が掛かりますが、作成が終わるとPlatformIOの標準的なプロジェクトディレクトリツリーと、プロジェクト設定ファイルplatform.iniが生成されます。src/main.cppがメインのソースコードです。
Pico 2を選択した場合に作成されるデフォルトのplatform.iniは次のような形です。
[env:rpipico2]
platform = raspberrypi
board = rpipico2
framework = arduino
[env:]
で始まるセクションが環境設定です。platform.iniには柔軟性があり、複数の環境を設定して切り替えると言ったことが可能になっています。たとえば、デバッグ環境とリリース環境を設定しておくというのが典型的な例でしょう。詳しくは公式のドキュメントを参照してください。
Picoシリーズの開発にデバッグプローブを用いる場合は、次の2行をplatform.iniの環境設定の末尾に追加し保存します。
upload_protocol = cmsis-dap
debug_tool = cmsis-dap
これでボードへのアップロードやデバッグにCMSIS-DAPが利用されるようになります。
開発中のほとんどの作業は、下ツールバーに並んでいるアイコンで行えます。
図5: ツールバーのPlatformIOアイコン
左から、PlatformIOのホームを開く、ビルド、アップロード&実行、クリーン、ユニットテスト、高機能シリアル端末を開く、新規ターミナル、環境切り替えメニューです。
デバッグは左アクティビティバーのデバッグアイコンをクリックしてデバッグ画面に切り替えて行います。「デバッグと実行」が「PIO DEBUG」になっていることを確認して実行ボタンをクリックすればデバッグ実行が始まります[1]。もちろん、デバッグプローブと開発ターゲット(Pico 2など)が正しく接続されていないとエラーになるので注意しましょう。
図6: デバッグ実行
Pico 2でJPEGデコード
こんなことが実に簡単にできるよ、という例としてカラーLCDにJPEG画像を表示する例を最後に載せておきます。
Picoシリーズの標準SDKでJPEGをデコードするとなると、自分でデコーダーを書くか、あるいは使えそうなデコーダーを頂いてきて移植するという感じになろうかと思いますが、いずれの場合も若干の手間と労力が発生します。
それに対してArduino Frameworkは長い歴史と膨大なユーザーに支えられた豊富なライブラリがあり、すべてではないにせよ多くがPicoシリーズで利用できます。標準SDKに比べて少ない労力で同じようなことができる、これがArduino Frameworkを使う利点と思われます。
Arduino Frameworkで使えるJPEGデコーダーはいくつかあるようですが、JPEGDECというライブラリが良さそうでした。後述するように簡単にDMAを使って表示(LCDへのデータ転送)とデコードを並列化できるように作られていて、DMAを使うとPico 2でも高速にJPEGを表示できます。
PlatformIOには、簡単にプロジェクトにライブラリを追加する機能があります。PlatformIOのLibrariesを開き、利用したいライブラリ名や機能を入力して検索ボタンを押すと、リポジトリに存在する該当するライブラリの一覧できます。その中から良さそうなライブラリをクリックし、「Add Project」ボタンで既存のプロジェクトのそのライブラリを追加できます。
図7: ライブラリの検索や追加ができる
ライブラリを追加すると、プロジェクトのplatform.iniの環境設定に次のような行が追加され、ライブラリがリポジトリからダウンロードされます。
lib_deps =
bitbank2/JPEGDEC@^1.7.0
このようにバージョンも固定されるので、ライブラリのバージョンが変わってビルドできなくなるとか動かなくなるといったこともありません。
利用する機材
サンプルでは機材はPico 2に加えて、ポピュラーなコントローラST7789を使ったSPI接続のLCDモジュールを利用します。表示ライブラリには、こちらもポピュラーなAdafruit_ST7789を利用します。
このライブラリは汎用なので、ST7789を使ったSPI接続のLCDモジュールなら対応が可能ですが、よく知られているようにモジュールの初期化は解像度や製品によって変える必要があります。サンプルでは解像度240×240ドットのこの製品を使っていますが、異なるモジュールを使用する場合は初期化を変える必要があります。
具体的には、main.cppの次の行を適宜モジュールに合わせて変更してください。最初の2つのパラメータが解像度、3つめはSPIモードです。
// 解像度240×240、SPIモード3
st7789_lcd.init(240, 240, SPI_MODE3);
異なる解像度のLCDを利用する場合は、サンプル画像include/constructor.jpgをLCD解像度と同じサイズの異なるJPEG画像に差し替えます。サンプル画像はmain.cppでインラインアセンブラを使い、GNUアセンブラの疑似命令incbin
を使って埋め込んでいるので、その部分を変更すればファイル名も変更できます。適宜、変更してください。
// サンプル画像を埋め込む
__asm(\
".section \".rodata\" \n"\
".balign 4\n"\
".global _sample_picture\n"\
".global _picture_size\n"\
"_sample_picture:\n"\
".incbin \"include/constructor.jpg\"\n"\
".set _picture_size, . - _sample_picture\n"\
".section \".text\"\n"\
);
Adafruit_ST7789は、PicoシリーズではデフォルトのSPIポートを使うので、LCDとPico 2は次のように接続します。
LCD側 | Pico側 | 機能 |
---|---|---|
BLK | 未接続 | バックライトコントロール端子 |
DC | GP20(26番) | データ/コマンド切り替えピン |
RES | GP21(27番) | リセット端子 |
SDA | GP19(25番) | シリアルデータ入力 |
SCL | GP18(24番) | SPIクロック入力 |
VCC | 3V3(36番) | 電源 |
GND | GND(23番など) | GND |
できるだけ短く長さの揃った線で接続します。できればフラットケーブルが望ましいでしょう。
接続の品質が悪い場合、SPIクロックを下げないと表示が乱れるかもしれません。表示が乱れる場合は次の行で設定しているクロックを下げてください。
// SPIクロック100MHz
st7789_lcd.setSPISpeed(100*000*000);
なお、Pico 1でも動作しますが、platform.iniの変更が必要になります。
実行しよう
サンプルコードをリポジトリからGitを使って入手します。
git clone https://future.quake4.jp/gogs/yoneda/jpg2lcd.git
PlatformIOのホーム画面から、「Open Project」でjpg2lcdフォルダをプロジェクトとして開きます。
Pico 2、LCDモジュール、デバッグプローブの接続を確認したらビルド、アップロードを実行します。次のようにLCDにサンプル画像が表示されます。
図8: 実行例
画像は2秒に1回の割で更新され、JPEGデコードおよび表示にかかった時間をシリアルポート(UART0)に出力しています。PicoprobeやRaspberry Pi Debug Probeを利用しているのならば、シリアル端末を開いてデコードおよび表示にかかった時間を確認できます。
図9: シリアル端末の時間表示
このようにおおむね46.5ミリ秒前後で表示できています。ちなみに、incbin
を使ってピクセルフォーマットRGB565の非圧縮バイナリをLCDにDMA転送した場合でも38ミリ秒ほどかかりますから、非圧縮に対してJPEG展開で10ミリ秒ほどしか悪化していません。画像データサイズが、非圧縮の112.5KiBから、JPEGならば12KiBほどと1/10くらいになっていることを考えれば、10ミリ秒は十分に許容可能な時間です。
検証していない推測ですが、incbin
を使って.rodata
として埋め込んだデータはeSPIフラッシュメモリ上ですから、DMA転送の速度はeSPIの帯域で頭打ちになります。JPEGデコードの場合、eSPI領域からの読み取りサイズが減る一方で、DMA転送は高速なRAM上のバッファからLCDになるので非圧縮バイナリよりも高速になるはずです。10ミリ秒の差しかないのは、そのあたりが関連しているのだろうと推測します。
JPEGDECでJPEGデコード
最後に簡単にサンプルコードについて軽く説明しておきます。JPEGDECは、JPEGをMCU(Minimum Coded Unit、マイクロコントローラではありません)と呼ばれるブロック単位で行い、1つのMCUのデコードが終わるたびに、コールバック関数を呼び出す仕組みになっています。
JPEGDEC jpeg;
// MCUが展開されるたびに呼び出されるコールバック関数
int mcu_draw(JPEGDRAW *draw)
{
ここでMCUをLCDに描画する
return 1;
}
...
jpeg.openFLASH(先頭アドレス, サイズ, コールバック関数);
jpeg.setPixelType(LCDのピクセル形式);
jpeg.decode(x, y, オプション);
jpeg.close();
mcu_draw()
がJPEGDECから呼び出されるコールバック関数で、引数のJPEDRAWのメンバx
、y
にMCUの左上座標、メンバiWidth
、iHeight
にMCUの幅と高さ、iBpp
に色深度、そしてメンバpPixels
にMCUのビットマップが格納されています。mcu_draw()
では、この情報に基づいてLCDにMCUを描画します。
展開するJPEGデータはJPEGDEC.openXXX()
で指定します。メンバ関数JPEGDEC.openXXX()
は多数用意されていて、メモリ上にあるJPEGデータならjpeg.openFLASH()
やjpeg.openRAM()
を使用します。そのほか、SDカード上のファイルシステムに対応できるJPEGDEC.openXXX()
が用意されています。
JPEGDEC.setPixelType()
で展開先のピクセル形式を設定します。デフォルトはRGB565のリトリエンディアンですが、DMA転送を用いる場合はビッグエンディアンにする必要がある点に注意が必要です。
JPEGDEC.decode()
を呼び出すとデコードが始まります。引数x、yは画像左上のLCD画面上における始点座標を指定します。サンプルでは240×240ドットの画像をLCDパネル全面に表示しているので0,0を設定しています。
オプションでは縮小や回転、ディザといった指定が可能になっています。画像サイズがLCD解像度より大きかったり小さかったりする場合は、それらを指定することもできます。
DMA転送を用いる場合は、オプションJPEG_USES_DMA
を指定しなければなりません。このオプションを指定しないと、コールバック関数に渡すバッファ(pPixels
)が使い回されるので、DMA転送中に次のMCUのデータが上書きされる結果、正常な表示が行われません。注意してください。
DMA転送を用いたLCD表示
JPEGDECはコールバック関数におけるMCUの描画にDMA転送を用いることで、JPEGデータのデコードと表示の並列化を行うことができ、高速な表示ができるように設計されています。サンプルではinclude/dma_st7789.hppに定義したdma_st7789
クラスにDMA転送による表示を実装しました。
dma_st7789
クラスはAdafruit_ST7789
クラスを継承してDMA転送機能を付け加えたサブクラスです。dma_st7789
クラスのメンバtransfer()
がPicoシリーズのDMAを使ってLCDの表示を行う関数です。
dma_st7789.transfer(x,y,width,height,buffer);
x
、y
,width
,height
が画像の左上座標とサイズ、そしてbuffer
がフレームバッファにDMA転送するビットマップです。transfer()
はDMA転送を設定すると、終了を待たずに返るノンブロックの関数です。ただし、呼び出されたときに他のDMA転送の最中であった場合、そのDMA転送が終わるまで待ってから新たなDMA転送を設定します。
サンプルでは、JPEGDECのコールバック関数でdma_st7789.transfer()
を呼ぶことで、表示とJPEGデコードの並列化を行っています。詳細はinclude/dma_st7789.hppを見てもらえばいいと思いますが、Pico C SDKのDMAをAdafruit_ST7789
クラスに追加しているだけです。
関数名としてAdafruit_ST7789
クラスのcamelCase式と、Pico C SDKのsnake_case式が混在していて非常に気持ちが悪いコードになっていますが、ともかく機能はします。おそらく、JPEGDECのPicoの実装例としては、この方法がもっとも高速ではないのかなと思います。
もちろん、サブCPUを援用したJPEGのデコードを自力で実装すればもっと速いかもしれませんが、その場合Arduino Frameworkを使うかどうかは悩ましいところです。Arduino Frameworkのライブラリは多くがスレッドセーフではないですから、PicoシリーズのサブCPUの活用には制限が生じる可能性があるからです。そうであれば、Pico C SDKを使って実装してしまったほうがスッキリするかもしれません。
Arduino Frameworkは、MicroPythonよりは圧倒的に高速なアプリケーションが作成でき、同時にほどほどに楽をしたいというケースには最適です。是非活用してください。
-
初回デバッグ実行時にはOpenOCDなどのインストールが行われるので少し時間がかかります。 ↩︎
Discussion