PR

Raspberry Pi Picoの開発をVSCodeで行う方法

Raspberry Pi Pico
本記事はプロモーションが含まれています。

こんにちは、ENGかぴです。

Raspberry Pi PicoはC/C++、Arduino、MicroPythonなど開発環境が充実しているためマイコン学習が手軽に行えます。VSCodeでRaspberry Pi Pico SDKを追加して開発する方法をまとめました。

本記事はVSCodeでRaspberry Pi Pico SDK開発環境を構築し、C/C++のプロジェクトを作成する方法をまとめています。GPIOピンを制御してLEDを点灯/消灯させて動作確認を行います。

VSCodeのダウンロードとインストールについては下記記事を参考にしてください。

VSCodeをインストールしてC/C++の開発環境を作る

Raspberry Pi Pico(以下Picoとする)と拡張基板のGrove Shield for Pi Picoを使用しています。

Picoを使用してArduino IDEやVSCodeで動作確認したことをまとめています。

Raspberry Pi Picoで学べるソフト開発

VSCodeにPicoの開発環境を追加する

VSCodeのインストールが済んでいる事を前提とします。VSCodeを起動して拡張機能を開きます。

VSCodeの拡張機能でRaspberry Pi Picoを追加
VSCodeの拡張機能でRaspberry Pi Picoを追加

VSCodeの左端の欄にある拡張機能(赤枠)をクリックすると拡張機能の一覧が表示されます。候補を絞るために「raspberry pi pico」を入力するとRaspberry Pi Picoが表示されるのでインストールします。

この拡張機能はRaspberry.comが公式に提供しているVSCode用のPico SDKです。インストールが終わると左端の欄にRaspberryのアイコンとプロジェクトのアイコンが追加されます。

PR:わからないを放置せず、あなたにあった最低限のスキルを身に着けるコツを教える テックジム 「書けるが先で、理解が後」を体験しよう!

Picoのプロジェクトを作成する

Picoのプロジェクトを生成する方法1
Picoのプロジェクトを生成する方法1

Picoの開発をC/C++で行うためプロジェクトを生成します。左端の欄のプロジェクトのアイコンをクリックとプロジェクトツールが表示されます。General欄から「New C/C++Project」を選択します。

初期起動時はproject欄がグレー色で選択できませんが、プロジェクトを生成する際に必要なツールを自動でインストールするため最初のプロジェクトを生成した後で使用できるようになります。

プロジェクト名、ボードタイプ、保存先を選択
プロジェクト名、ボードタイプ、保存先を選択

Name欄にファイル名を入力するとユーザー任意のソースコード用のフォルダが生成されます。Exampleはサンプルコードを選択する場合に使用します。今回は動作確認用のソースコードを作成するため「pico-vscode-gpio」を指定します。

Board typeはPicoを指定します。Locationはフォルダを保存する場所を指定します。

Select Pico SDK versionはデフォルトが最新のバージョンになっているので変更する必要はありません。

Featuresはプロジェクトで使用する周辺機器やライブラリを事前に選択する設定です。例えばI2CやUARTを使用する場合はチェックを入れます。チェックすることでプロジェクトに必要なライブラリが自動的に組み込まれCMakeLists.txtの設定に反映されます。

本記事ではGPIOを使用してLEDを操作するため周辺機器を追加する必要がないためFeaturesのチェックは行っていません。

後で追加する可能性があるものについてはチェックを入れておいても問題ありませんが、追加するとライブラリが追加されるためプログラム容量が重たくなります。手動でライブラリを追加する例を示します。

target_link_libraries(test 
        hardware_spi
        hardware_i2c
        )

CMakeLists.txtを開いて上記のように追加します。次にC/C++ファイルでインクルードします。

#include "hardware/spi.h"
#include "hardware/i2c.h"

インクルードしただけでは使用できないので初期化処理を行うAPIを使用します。Featuresであらかじめチェックを入れておくと上記の記述が自動で生成されます。

手動で追加すると設定ミスによってコンパイルエラーなどが発生することがあるので、ライブラリを追加する場合は新たにプロジェクトを生成して編集中のソースコードをコピーしてファイルを作り直したほうがよさそうです。

Stdio support、Code generation options、Debuggerの設定
Stdio support、Code generation options、Debuggerの設定

Stdio supportはUART通信をどのポートを使ってPCとやり取りするかを選択します。

Console over UARTはGPIO0(TX)とGPIO1(RX)ピンを使ったUART通信を行います。外部機器や他のマイコンと接続する場合に使用します。

Console over USBはPicoのUSBポートをCOMポートとして使用します。シリアルモニターを使用してログの確認や入力をする場合に使用します。

開発中のデバッグでログ確認するならUSBを使用し、外部機器とハード間で通信を行う場合はUARTを使用する使い分けができます。

本記事ではPico1台で開発したソフトをシリアルモニターで動作を行うためConsole over USBを選択しています。

Code Generation Optionsはプロジェクトの実行方法やオブジェクトの型情報や例外処理の有効かの設定を行います。

Run the program from RAM rather than flashはプロジェクト生成時のコードの事項方式の設定です。チェックを入れるとROM(Flash)の内容をRAMにコピーしてRAMでコードを実行します。RAMから実行するため高速になりますが、RAM容量に制限(264kB)があるためROM容量が大きくなるようなコードの場合は使用できません。

Flashの書き込み回数に上限があるためデバッグ時に書き込みを頻繁に行いたくない場合に使用するとよいですが、最近のFlashは環境にもよりますが約10万回書き込みが可能なので趣味で開発を行う場合は特に気にする必要はないと思います。

Use project name as entry point file nameは生成させるバイナリファイルなどにプロジェクト名を使用する設定です。本記事ではプロジェクト名を「pico-vscode-gpio」にしているので、バイナリファイルの名前がpico-vscode-gpio.binになります。

Generate C++ codeはC++のクラス・ネームスペースなどを使用する設定です。C++のクラスを使用できるようにするためチェックしています。チェックしない場合はC言語ファイルになります。

Enable C++ RTTIは実行時にオブジェクトの型情報を取得できるようにする設定です。ポインタの型判定やdynamic_castが使用できますがRAMの容量が増えます。

Enable C++ exceptionsはtyr/catchによる例外処理が使用できるようになりますがRAMの容量が増えます。

PicoなどRAM容量に制限がある環境ではGenerate C++ codeだけ有効にしておくことで、C++の構文だけ活かしつつ、RAM容量をおさえるのが効果的です。

Debuggerはデバッグ機能の設定です。デフォルトはDebugProbe (CMSIS-DAP)担っています。これはARMが定義した標準的なデバッグインターフェースです。

SWD (Pi host, on Pi 5 it requires Linux Kernel >= 6.6.47) CMake ToolsはPicoをホストPCとして使用する場合のSWD接続設定になりますが、Picoが2台必要となるため使用しません。Linuxカーネルに関するバージョンの記載がありますが、Windowsを使用しているため関係ありません。

Enable CMake-Tools extension integrationはVSCodeのCMake Tools拡張機能と連携するかどうかの設定です。有効にするとCMakeLists.txtの構成が自動認識されるためビルド・デバッグ・ターゲット選択がVSCodeで可能になります。

チェックしない場合でも手動で選択することでビルド・デバッグができますがCMakeベースのプロジェクトにおいて開発効率が向上するためチェックをお勧めします。

「Create」をクリックするとプロジェクトを生成します。初期生成の時は必要なコンパイラなどの環境がインストールさせるため数分時間がかかります。必要な拡張機能がインストールされている場合は、数秒で初期のプロジェクトが生成されます。

PR:スキマ時間で自己啓発!スマホで学べる人気のオンライン資格講座【スタディング】まずは気になる講座を無料で体験しよう!

ソースコードの開発と動作確認

プロジェクトが生成されると初期のソースコードに処理を追加して作成したソースコードをコンパイルしてバイナリファイルを生成し、Picoに書き込む方法を説明します。

ソフトをコンパイルする方法

ソースコードをコンパイルする方法
ソースコードをコンパイルする方法

検索欄をクリックすると選択肢が候補として表示されます。コマンドの表示と実行を選択すると複数のコマンドの候補が表示されます。「CMake:Build」を選択するとコンパイルが開始されます。

ショートカットで「F7」を押してもコンパイルができます。また画面下のCompileのアイコンをクリックしてもコンパイルを開始できます。コンパイルに失敗した場合はソースコードにエラーがあるため修正が必要です。

CMake Toolsの拡張機能を有効にしていない場合は候補者に「Pico コンパイラの使用:xxxxxgcc.exe」など使用できるコンパイラが表示されていれば選択することでコンパイルができるようになります。

ソフトを書き込む方法

ソースコードを書き込む方法
ソースコードを書き込む方法

Picoのボードの「BOOT SEL」のボタンを押した状態でUSBを接続してBOOTモードで起動します。起動するとエクスプローラーにRPI-RP2ドライブが認識されます。BOOTモードでなければソフトの書き込みに失敗します。

検索欄をクリックすると選択肢が候補として表示されます。コマンドの表示と実行を選択すると複数のコマンドの候補が表示されます。「Raspberry Pi Pico:Run Pico Project(USB)」を選択するとソフトの書き込みがスタートします。

また、エディター画面の下側のRunアイコンをクリックするとソフトの書き込みがスタートします。

PR:RUNTEQ(ランテック )- マイベスト4年連続1位を獲得した実績を持つWebエンジニア養成プログラミングスクール

Picoの動作を確認

GPIOを操作してPicoのボード上のLEDを点灯/消灯するプログラムを作成して書き込みます。LEDを点灯/消灯の状態をシリアルモニターでも確認できるようにします。

Picoのピン配置などの詳細情報はPico-series Microcontrollersを参考にしてください。

int main()
{
    stdio_init_all();
    //ここに初期化処理を入れる
    while (true) {
     //ここに処理を入れる
    }
}

デフォルトではstdio_init_all()関数とwhile(true)による無限ループが実装されています。while(true)に入る前に初期化処理を追加します。while()内には処理を追加します。

初期化処理

gpio_init(PIN_DO1);    // ピン初期化
gpio_set_dir(PIN_DO1, GPIO_OUT);    //DOピン
sleep_ms(5000);
printf("Hello, world!\n");
beforetimCnt = to_ms_since_boot(get_absolute_time());

gpio_init()関数はGPIOピンの初期化を行います。引数に初期化するピン番号を指定します。

gpio_set_dir()関数はGPIOピンの入出力の方向を指定します。第1引数にGPIOピンの番号を指定し、第引数に入出力の方向を指定します。例ではDOピンにするためGPIO_OUTを指定しています。

sleep_ms()関数はソフトウェアウェイトをms単位で指定します。例では5000msを指定しているので5秒間処理が停滞します。

printf()関数はシリアルモニターに表示する文字列を指定します。例では「Hello World!\n」の文字列を指定しています。

to_ms_since_boot()関数は起動してからの経過msを返します。引数に絶対時間(基準となる時間)を指定します。get_absolute_time()関数で現在時刻を取得して、to_ms_since_boot()関数で起動からの経過時間に変更します。

Arduino環境のmillis()関数と同じイメージで使用できます。

PR:(即戦力のスキルを身に着ける:DMM WEBCAMP 学習コース(はじめてのプログラミングコース))

タイマの管理

/* タイマ管理 */
void mainTimer(void){

  if( to_ms_since_boot(get_absolute_time()) - beforetimCnt >= 10){
    beforetimCnt = to_ms_since_boot(get_absolute_time());
    //10msごとにここに遷移する
    if( timled > TIME_UP ){
      timled--;
    }
  }
}

sleep_ms()関数を使用するとソフトウェアウェイトなので処理が停滞します。処理が停滞すると他の処理が遅れるため問題になることがあります。

sleep_ms()関数を使用せずに起動からの経過時間と基準となる時刻を比較して経過時間を判断する方法を説明します。

to_ms_since_boot()関数で取得した現在の時間と前回の時間の変数beforetimCntを比較して10以上であれば10ms経過したことになるのでif文の内部の処理でタイマの変数(timled)を更新(7行目)します。

5行目はタイマを更新するタイミングでの時刻のカウントを格納して次の経過時間の判定に使用します。

timledの更新は10ms毎に発生するためtimledに50をセットするとタイムアップするまでの時間は500msのタイマになります。

to_ms_since_boot()関数は符号なしのuint32_tの値で宣言されており32ビットのカウント値分更新していきますが、オーバーフローするとカウント値が0に戻ってしまいます。そのため50日連続で動作させたときはタイマの判定がずれてしまいます。

10ms毎にベースカウントを更新するようにしているためオーバーフローが発生したとしても最大誤差が10ms以下になります。sleep_ms()関数などを使用して意図的にウェイトを置いていない限り、通常メインループ1周の時間は長くても数msになるため誤差が気にならないことが多いです。

タイミングをシビアに取りたい場合はタイマ機能を使用して確実にタイミングを同期させる必要があります。その場合はタイマ割り込みを使用するなどタイミングの制御が必要です。

GPIOの出力を操作する

/* メイン処理 */
void mainApp(void){
   
  if( timled == TIME_UP ){
    timled = LED_ONOFF;
    printf("LEDTEST:%d, \n",gpio_get(PIN_DO1));
    gpio_put(PIN_DO1, !gpio_get(PIN_DO1));
  }
}

timledがタイムアップしたときにGPIOを操作してLEDを点灯/消灯を切り替えます。

gpio_get()関数は引数で指定したピン番号の状態を取得します。LOWであれば0、HIGHであれば1になります。例ではprintf()関数で現在のポートの状態をシリアルモニターに表示するようにしています(6行目)。

gpio_put()関数はDOポートを操作します。第1引数に操作するDOポート番号、第2引数に出力する値(LOW/HIGH)を指定します。例ではgpio_get()関数で呼び出したDOピンの状態を否定(!)して指定しています。これにより処理を行う毎に出力が反転するトグル出力になります。

動作確認の結果

ソースコード全体のコードをPicoに書き込みます。Picoの電源を入れてから5秒経過してシリアルモニターに「Hello World!」を表示するため、ポートが認識されたらすぐに監視の開始をクリックしてシリアルモニター開始します。

動作確認の結果(シリアルモニター)
動作確認の結果(シリアルモニター)

「Hello World!」を表示した後は無限ループの処理を行います。500ms毎にDOポートの状態を「LEDTEST:X」で表示し、LEDを点灯/消灯を切り替えます。シリアルモニターのDOの状態が0であれば消灯、1であれば点灯していることが確認できました。

スポンサーリンク

ソースコード全体

ソースコードは記事作成時点において動作確認できていますが、使用しているライブラリの更新により動作が保証できなくなる可能性があります。また、ソースコードを使用したことによって生じた不利益などの一切の責任を負いかねます。参考資料としてお使いください。

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

#define PIN_DO1 25
#define TIME_OFF -1 //タイマーを使用しない場合
#define TIME_UP 0 //タイムアップ
#define BASE_CNT 10 //ベースタイマカウント値
#define LED_ONOFF 50

uint32_t beforetimCnt;
int8_t timled = LED_ONOFF;

void mainApp(void);
void mainTimer(void);

int main()
{
    stdio_init_all();
    gpio_init(PIN_DO1);                  // ピン初期化
    gpio_set_dir(PIN_DO1, GPIO_OUT);    //DOピン
    sleep_ms(5000);

    printf("Hello, world!\n");
    beforetimCnt = to_ms_since_boot(get_absolute_time());

    while (true) {
        mainTimer();
        mainApp();
    }
}

/* タイマ管理 */
void mainTimer(void){

  if( to_ms_since_boot(get_absolute_time()) - beforetimCnt > BASE_CNT ){
    beforetimCnt = to_ms_since_boot(get_absolute_time());
    //10msごとにここに遷移する
    if( timled > TIME_UP ){
      timled--;
    }
  }
}

/* メイン処理 */
void mainApp(void){
   
  if( timled == TIME_UP ){
    timled = LED_ONOFF;
    printf("LEDTEST:%d, \n",gpio_get(PIN_DO1));
    gpio_put(PIN_DO1, !gpio_get(PIN_DO1));
  }
}

メインのファイルの内容をコピーして置き換えることで使用できます。

関連リンク

Arduinoのライブラリを使って動作確認を行ったことを下記リンクにまとめています。

Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方

Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方

ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方

Raspberry Pi Picoで学べるソフト開発

 

PR:企業で求められる即戦力技術を身に付ける テックキャンプエンジニア転職

最後まで、読んでいただきありがとうございました。

タイトルとURLをコピーしました