こんにちは、ENGかぴです。
Raspberry Pi PicoはRP2040マイコンを搭載したモジュールです。C/C++、Arduino、MicroPythonなど開発環境が充実しているためマイコン学習が手軽に行えるメリットがあります。Arduino環境で開発を行う方法をまとめました。
Arduino IDEのダウンロードとインストールについては下記記事を参考にしてください。
Raspberry Pi Pico(以下Picoとする)と拡張基板のGrove Shield for Pi Picoを使用しています。またGrove Button(P)を使用しています。
Picoの開発環境を作る
冒頭で紹介しているArduino IDEをインストールが済んでいる事を前提とします。Arduino IDEを起動してボードマネージャからPico(RP2040)のボードを追加します。
Arduino Mbed OS RP2040 Boardsを追加する

次にArduino IDEの側面のアイコン(上から2番目)をクリックするかツール欄からボード:XXXX(XXXXは現在選択しているボード)を選択して現れるタブからボードマネージャをクリックします。
ボードマネージャで検索欄に「pico」や「rp2040」と入力して検索するとArduino Mbed OS RP2040 Boardsが候補として表示されるのでインストールします。
PR:わからないを放置せず、あなたにあった最低限のスキルを身に着けるコツを教える テックジム 「書けるが先で、理解が後」を体験しよう!
ボードとポートの設定

ボードの選択欄から「Raspberry Pi Pico」を選択します。上の検索欄に「raspberry」や「pico」などを入力すると候補を絞ることができます。PicoをUSBで接続して反応したCOMポートを選択します。
例ではCOM1が表示されていますが、デスクトップパソコンに実装されているRS232CのポートでPicoのものでありません。初期起動ではPicoをUSBに接続してもCOMポートが応答しないことがあります。その場合は、次項目に従って一度サンプルソースを書き込んで再起動します。
PicoのCOMポートが反応している場合は、COMポートを選択するとソースコートを書き込むことができるようになります。
COMポートが表示されない場合
Picoのボードの「BOOT SEL」のボタンを押した状態でUSBを接続してBOOTモードで起動します。起動するとエクスプローラーにRPI-RP2ドライブが認識されます。

RPI-RP2ドライブには2種のファイルがあります。INDEX.HTMはRaspberry Piの公式ページのリンクになっています。クリックしてページを表示します。

ページを開くとRaspberry Piの公式ページに遷移しドキュメントに関する情報が表示されます。Arduino環境はC/C++を使用するためMicrocontrollers欄から「The C/C++SDK」を選択します。

The C/C++SDKのページではサンプルソースの説明があります。1.~4.の手順に従ってサンプルソースをPicoに書き込みます。1.の「for Raspberry Pi Pico」をクリックするとサンプルソースがダウンロードできます。2.と3.はRPI-RP2ドライブで開くことが手順の記載です。
4.はダウンロードしたファイルをRPI-RP2ドライブにドラッグアンドドロップして追加するとPicoが再起動することが記載されています。

ダウンロードしたファイルをエクスプローラーで表示しているRPI-RP2ドライブ内にドラッグアンドドロップして追加するとエクスプローラーを閉じてPicoが再起動します。
再起動するとLEDが点灯/消灯するサンプルソースで動作を開始します。このときPico専用のCOMポートが生成されて反応するようになります。

ポート欄にCOM7(COM番号はお使いの環境によって異なります。)が生成されています。ボードとポートを選択するとArduino環境でソールコードを書き込めるようになります。
Picoの動作を確認

Arduinoの標準ライブラリを使ってLEDを点灯/消灯させ、ボタンを押すとシリアルモニターに文字列を表示させて動作確認を行います。
Picoのピン配置などの詳細情報はPico-series Microcontrollersを参考にしてください。Picoの拡張ボードはGPIOピンなどを延長して配線できるように構成されているため、回路図ではGrove端子に接続されるピン番号に直接接続するイメージで記載しています。
Grove端子にButton(P)を実装してボタンを押すと文字列「Grove-Button-ok」をシリアルモニターに表示させます。GPIO25ピンはボードのLEDに接続されているためDOで使用し500ms毎にLEDを点灯/消灯させます。
初期化処理
void setup() {
Serial.begin(115200);
pinMode( 18, INPUT_PULLUP);
pinMode( 25, OUTPUT);
delay(3000);
Serial.println("Raspberry PI Pico");
Serial.println("Arduino IDE");
}
void loop() {
mainApp();
}
新規ファイルを生成するとデフォルトでsetup()関数とloop()関数が実装されています。setup()関数は初期化時に一度だけコールされる関数です。標準ライブラリなど初期化が必要であれば処理を追加します。
2行目はSerialクラスのbegin()関数でシリアル通信の初期化を行っています。引数にシリアル通信のボーレートを指定します。ボーレート以外のデフォルトはデータ長8ビット、パリティなし、ストップビット1ビットです。
3行目はpinMode()関数でGPIOピンの設定を行います。第1引数にGPIOピンの番号、第2引数にGPIOの種別を指定します。DIであればINPUTやINPUT_PULLUPを指定します。DOであればOUTPUTを指定します。
6行目のdelay()関数はソフトウェアウェイトです。引数にウェイトを1msの分解能で指定します。例では3000ms(3秒)を指定しています。
7~8秒目はSerialクラスのprintfln()関数でシリアルモニターに文字を表示します。
loop()関数はメイン処理1周毎にコールされる関数です。ユーザーが任意で処理する内容を追加します。例ではmainApp()関数をコールしています。
PR:スキマ時間で自己啓発!スマホで学べる人気のオンライン資格講座【スタディング】まずは気になる講座を無料で体験しよう!
タイマの管理
#define BASE_CNT 10
uint32_t timer; //条件を満たしたときのタイマ値
/* タイマ管理 */
void mainTimer(void){
if( millis() - timer >= BASE_CNT ){ // BASE_CNT以上か
timer = millis(); //現在の値を保存
if( timled > TIME_UP ){ //タイマを更新
--timled;
}
}
}
タイマの管理の一例として、現在の経過時間msと前回条件を満たしたときの時間msの差が規定値のBASE_CNT(10)以上であればタイマ値の更新を行う方法があります。
差が規定値以上になった時点で現在の経過時間msを保存してif文内部のタイマの変数を更新します。例ではBASE_CNTを10にしているためtimledの更新は10ms毎に発生します。timledの分解能が10ms単位になるためtimledに50をセットするとタイムアップするまでの時間は500msになります。
millis()関数は1ms毎にカウントが+1した値を戻すArduinoライブラリです。電源がONしてタスクが動作開始した時点でカウントを+1するため電源ONからの時間経過の目安となります。
10ms毎にベースカウントを更新するようにしているためオーバーフローが発生したとしても最大誤差が10ms以下になります。delay()関数などを使用して意図的にウェイトを置いていない限り、通常メインループ1周の時間は長くても数msになるため誤差が気にならないことが多いです。
タイミングをシビアに取りたい場合はタイマ機能を使用して確実にタイミングを同期させる必要があります。その場合はタイマ割り込みを使用するなどタイミングの制御が必要です。
チャタリングの防止
void DiFilter(void){
if( timdifilt == TIME_UP ){
difilt.buf[difilt.wp] = digitalRead(18);
if( difilt.buf[0] == difilt.buf[1] &&
difilt.buf[1] == difilt.buf[2] &&
difilt.buf[2] == difilt.buf[3] ){ //4回一致を確認
difilt.di1 = difilt.buf[0];
}
if( ++difilt.wp >= DI_FILT_MAX ){
difilt.wp = 0;
}
timdifilt = FILT_MIN;
}
}
Buttonなどの接点がOFFからONになった場合など状態が変化するとデジタル信号の0と1が安定しない区間(チャタリング)が発生します。チャタリングしている区間で処理を判断すると動作が安定しないため対策が必要です。容量が大きいリレー接点などでは安定までの区間が長くなりますが通常では数十msで収束します。
チャタリング対策の為、ソフトウェアタイマ(timdifilt)を使用して10msごとにdigitalRead()関数で18ピンの状態を取得し4回分のデジタル信号が一致した時の状態を採用します。
動作確認の結果

シリアルモニターを起動した状態でUSBで電源を入れると初期化処理の3秒遅延の後に「Raspberry PI Pico」「Arduino IDE」の文字列が表示されます。初期化処理が終わるとボードのLEDを500ms毎に点灯/消灯します。
Grove Button(P)のボタンを押すと「Grove-Button-ok」が表示されます。このときチャタリング防止処理によって文字列が複数回表示されることがないことが確認できます。
ソースコード全体
ソースコードは記事作成時点において動作確認できていますが、使用しているライブラリの更新により動作が保証できなくなる可能性があります。また、ソースコードを使用したことによって生じた不利益などの一切の責任を負いかねます。参考資料としてお使いください。
#define PIN_DI1 18
#define PIN_DO1 25
#define TIME_OFF -1 //タイマーを使用しない場合
#define TIME_UP 0 //タイムアップ
#define BASE_CNT 10 //ベースタイマカウント値
#define LED_ONOFF 50
#define FILT_MIN 1
#define DI_FILT_MAX 4
struct DIFILT_TYP{
uint8_t wp;
uint8_t buf[DI_FILT_MAX];
uint8_t di1;
};
uint32_t beforetimCnt = millis();
int8_t cnt10ms;
int8_t timled = LED_ONOFF;
int8_t timdifilt = TIME_OFF;
int8_t ledcnt;
DIFILT_TYP difilt;
bool btnflg1;
/* プロトタイプ宣言 */
void mainApp(void);
void mainTimer(void);
void DiFilter(void);
void setup() {
Serial.begin(115200,);
pinMode( PIN_DI1, INPUT_PULLUP);
pinMode( PIN_DO1, OUTPUT);
delay(3000);
Serial.println("Raspberry PI Pico");
Serial.println("Arduino IDE");
timdifilt = FILT_MIN;
for( uint8_t i=0; i < 10; i++ ){
mainTimer();
DiFilter();
delay(10);
}
}
void loop() {
mainTimer();
mainApp();
DiFilter();
}
/* メイン処理 */
void mainApp(void){
if( timled == TIME_UP ){
timled = LED_ONOFF;
++ledcnt;
if( ledcnt % 2 ){
digitalWrite(PIN_DO1, HIGH);
}
else{
digitalWrite(PIN_DO1, LOW);
}
}
if(difilt.di1 == 1){
if(btnflg1){
btnflg1 = false;
Serial.println("Grove-Button-ok");
}
}else{
btnflg1 = true;
}
}
/* タイマ管理 */
void mainTimer(void){
if( millis() - beforetimCnt > BASE_CNT ){
beforetimCnt = millis();
//10msごとにここに遷移する
if( timled > TIME_UP ){
timled--;
}
if( timdifilt > TIME_UP ){
timdifilt--;
}
}
}
/* DiFilter function add */
void DiFilter(void){
if( timdifilt == TIME_UP ){
difilt.buf[difilt.wp] = digitalRead(PIN_DI1);
if( difilt.buf[0] == difilt.buf[1] &&
difilt.buf[1] == difilt.buf[2] &&
difilt.buf[2] == difilt.buf[3] ){ //4回一致を確認
difilt.di1 = difilt.buf[0];
}
if( ++difilt.wp >= DI_FILT_MAX ){
difilt.wp = 0;
}
timdifilt = FILT_MIN;
}
}
メイン関数であるloop()に遷移する前にsetup()関数内でDIフィルタの値を確定するためにfor文でdelay(10)で遅延させながらDI値を確定しています。
関連リンク
Arduinoのライブラリを使って動作確認を行ったことを下記リンクにまとめています。
Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方
Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方
ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方
PR:企業で求められる即戦力技術を身に付ける テックキャンプエンジニア転職
最後まで、読んでいただきありがとうございました。