Seeed XIAO RP2040をArduino-Picoで動かす

2026年3月29日技術開発Arduino,Seeed XIAO RP2040

概要

Raspberry Pi PicoをArduino-Picoで動かすという記事は多く見かけたのですが、Seeed XIAO RP2040を使う記事は少なかったので、調べて色々試した結果を紹介します。
Raspberry Pi PicoとSeeed XIAO RP2040は配線や周辺部品が異なるだけでマイコンは同じものなので、この記事の内容はRaspberry Pi Picoでも応用できると思います。

◆念のためArduino-Picoの説明
Raspberry Pi Picoは公式でArduino IDEに対応しています。ただし、下記のような一部の機能に対応していません。

  • 一部のライブラリ
  • FLASHメモリを疑似EEPROMのように使う
  • 2コアでの並列処理
  • I2C、SPIを複数ポート使ったり、チャンネルを変えて使う

    ※他にもありそうなので随時追加します。

上記の点を改良したArduino Coreを、Earle F. Philhower, III氏が新たに作成して無料で公開されています。
これが「Arduino-Pico」です。私個人としては公式ではなく個人が作成したものだと保守性で問題はないのか…?と感じているのですが、日本語の資料の多くがArduino-Picoを使用した環境のため、様子を見つつ使用しています。
この記事を書いている時点では更新は頻繁にされているようです。

I2CやSPIのポートについては公式のものでも何とかなるケースが多いのですが、2コアでの並列処理が使えるのは大きいですね。

実行環境

MCU: Seeed XIAO RP2040
IDE: Arduino IDE 1.8.19 windows(portable)

参考資料

Arduino-Pico 公式ページ https://arduino-pico.readthedocs.io/en/latest/#
Arduino-Pico Github https://github.com/earlephilhower/arduino-pico

Seeed XIAO RP2040のピンアサイン

ピンの定義

以下はpins_arduino.hの抜粋です。

// LEDs
#define PIN_LED        (17u)
#define PIN_LED_R      (17u)
#define PIN_LED_G      (16u)
#define PIN_LED_B      (25u)
#define LED_BUILTIN    PIN_LED

// Digital pins
static const uint8_t D0 = (26u);
static const uint8_t D1 = (27u);
static const uint8_t D2 = (28u);
static const uint8_t D3 = (29u);
static const uint8_t D4 = (6u);
static const uint8_t D5 = (7u);
static const uint8_t D6 = (0u);
static const uint8_t D7 = (1u);
static const uint8_t D8 = (2u);
static const uint8_t D9 = (4u);
static const uint8_t D10 = (3u);

// Analog pins
static const uint8_t A0  = (26u);
static const uint8_t A1  = (27u);
static const uint8_t A2  = (28u);
static const uint8_t A3  = (29u);
#define ADC_RESOLUTION 12

// NeoPixel
#define PIN_NEOPIXEL   (12u)
#define NEOPIXEL_POWER (11u)

// Serial1
#define PIN_SERIAL1_TX (0u)
#define PIN_SERIAL1_RX (1u)

// Serial2 not pinned out
#define PIN_SERIAL2_TX (31u)
#define PIN_SERIAL2_RX (31u)

// SPI
#define PIN_SPI0_MISO  (4u)
#define PIN_SPI0_MOSI  (3u)
#define PIN_SPI0_SCK   (2u)
#define PIN_SPI0_SS    (31u) // not pinned out
static const uint8_t SS   = PIN_SPI0_SS;   // SPI Slave SS not used. Set here only for reference.
static const uint8_t MOSI = PIN_SPI0_MOSI;
static const uint8_t MISO = PIN_SPI0_MISO;
static const uint8_t SCK  = PIN_SPI0_SCK;

// Not pinned out
#define PIN_SPI1_MISO  (31u)
#define PIN_SPI1_MOSI  (31u)
#define PIN_SPI1_SCK   (31u)
#define PIN_SPI1_SS    (31u)
#define SPI_MISO       (PIN_SPI1_MISO)
#define SPI_MOSI       (PIN_SPI1_MOSI)
#define SPI_SCK        (PIN_SPI1_SCK)

// Wire
#define __WIRE0_DEVICE (i2c1)
#define PIN_WIRE0_SDA  (6u)
#define PIN_WIRE0_SCL  (7u)
#define SDA            PIN_WIRE0_SDA
#define SCL            PIN_WIRE0_SCL
#define I2C_SDA        (SDA)
#define I2C_SCL        (SCL)

// Wire1 not pinned out
#define __WIRE1_DEVICE (i2c0)
#define PIN_WIRE1_SDA  (31u)
#define PIN_WIRE1_SCL  (31u)

#define SERIAL_HOWMANY (1u)
#define SPI_HOWMANY    (1u)
#define WIRE_HOWMANY   (1u)

Arduino IDEの設定

Raspberry Pi Picoと同じ手順になります。
詳細は参考サイトを参照ください。
※英語が読める方は公式ページ、もしくはGithubを参考にするのが確実です。日本語で読みたい方は↓こちらの「Earle Philhowerコアのインストール」が説明が丁寧です。

Seeed XIAO RP2040をPCに接続する

初回はBOOTSELボタン(下図の赤枠部のボタン)を押したままUSBケーブルでPCに接続します。
この方法で接続すると大容量記憶デバイス(USBメモリ等と同じ扱い)として認識されます。

スイッチサイエンス・購入画面から引用

Arduino IDEを起動し、ツール → ボード → Raspberry Pi RP2040 Boards(X.X.X) → Seeed XIAO RP2040 をクリックしてボードを設定します。
Flash Sizeなどの設定項目が増えますが、とりあえず動かすだけならデフォルトのままでOKです。
後ほど必要に応じて変更します。

Raspberry PiやXIAO RP2040などRP2040搭載ボードは普通のArduinoと違い、PCと接続してもシリアルポートは認識されません。
普通のArduinoはシリアル通信でプログラムを書き込むのでシリアルポートの設定が必要ですが、RP2040は大容量記憶メモリとして認識され、USBメモリのように.uf2ファイルを保存する形でプログラムが書き込めれるためです。
おそらくですが、「RPI-RP2」「RP2-Boot」という名前のデバイスを自動認識して書き込むようです。(詳しくは確認中)

※参考画像ではシリアル通信でデバッグ情報が見れるよう設定変更しています。元に戻すのが面倒で…

ここまで出来たら通常のArduinoと同じく、コードを書いて「マイコンボードに書き込む」ボタンを押せばプログラムが書き込めます。
検証も書き込みもかなり時間がかかるので気長に待ちましょう。
↓このようなメッセージが出てきたら書き込み完了です。(コードやPCの環境によってメッセージが少し変わります)

Resetting COM7
Converting to uf2, output size: 116736, start address: 0x2000
Flashing D: (RPI-RP2)
Wrote 116736 bytes to D:/NEW.UF2

2回目以降の書き込みは「BOOTSELボタンを押しながらPCとUSBに接続」する必要はありません。
ただし、たまに書き込みが上手くいかない時があるようで、その時は初回の書き込みと同じ手順が必要です。

Lチカ

ファイル → スケッチ例 → 01.Basics → Blink の順にクリックしてLチカ用のサンプルコードを開きます。

これを一切変更せずに書き込むと、USBコネクタ横の赤LEDが点滅します。

NeoPixel

Seed XIAO RP2040にはNeoPixelが実装されています。
引き出されているピンとは干渉しないので、いつでも使用できます。

NeoPixelのDATAは12ピンに接続されており、PIN_NEOPIXELで定義されています。
またNeoPixelの電源は11ピンに接続されており、使用する前にHIGHにする必要があります。ピン名はNEOPIXEL_POWERで定義されています。

以下、1秒ごとに赤 → 緑 → 青 → 白と切り替わるプログラムとなります。

#include <Adafruit_NeoPixel.h>

const int NUMPIXELS = 1;

Adafruit_NeoPixel pixels(NUMPIXELS, PIN_NEOPIXEL, NEO_GRB + NEO_KHZ800);

void setup() {
  pixels.begin();
  pinMode(NEOPIXEL_POWER, OUTPUT);
  digitalWrite(NEOPIXEL_POWER, HIGH);
}

void loop() { //core 0
  /* 赤色で点灯 */
  pixels.setPixelColor(0, pixels.Color(255, 0, 0));
  pixels.show();
  
  delay(200);
  
  /* 消灯 */
  pixels.clear();
  pixels.show();
  
  delay(1000);

  /* 緑色で点灯 */
  pixels.setPixelColor(0, pixels.Color(0, 255, 0));
  pixels.show();
  
  delay(200);

  /* 消灯 */
  pixels.clear();
  pixels.show();
  
  delay(1000);

  /* 青色で点灯 */
  pixels.setPixelColor(0, pixels.Color(0, 0, 255));
  pixels.show();
  
  delay(200);
  
  /* 消灯 */
  pixels.clear();
  pixels.show();
  
  delay(1000);

  /* 白色で点灯 */
  pixels.setPixelColor(0, pixels.Color(85, 85, 85));
  pixels.show();
  
  delay(200);
  
  /* 消灯 */
  pixels.clear();
  pixels.show();
  
  delay(1000);
}

詳細な使い方は以下のRaspberry Pi Picoでの解説ページを参照してください。

GPIO

使い方は普通のArduinoと全く同じです。ただし、ピン番号はD0、D1のように「D」をつける必要があります。

次に、Arduinoで運用すると問題になるdigitalWrite()の処理速度を測定しました。

digitalWrite(D0, HIGH);
digitalWrite(D0, LOW);
ロジックアナライザでの測定結果

2回実行した時の処理時間が708.333[ns]なので、1回当たりの処理時間は約354.2[ns]となります。
Arduinoを使っているのでCPUクロックの割には遅いですが、普通に使う分には十分ですね。

GPIOで流せる電流

RP2040のデータシート2.19.4. Padsに記載があります。

<原文>
Each GPIO is connected to the off-chip world via a "pad". Pads are the electrical interface between the chip’s internal
logic and external circuitry. They translate signal voltage levels, support higher currents and offer some protection
against electrostatic discharge (ESD) events. Pad electrical behaviour can be adjusted to meet the requirements of the
external circuitry. The following adjustments are available:
• Output drive strength can be set to 2mA, 4mA, 8mA or 12mA
• Output slew rate can be set to slow or fast
• Input hysteresis (schmitt trigger mode) can be enabled
• A pull-up or pull-down can be enabled, to set the output signal level when the output driver is disabled
• The input buffer can be disabled, to reduce current consumption when the pad is unused, unconnected or
connected to an analogue signal.

<Google翻訳>
各GPIOは「パッド」を介して外部回路に接続されています。パッドは、チップ内部のロジックと外部回路間の電気的インターフェースです。信号電圧レベルを変換し、より大きな電流をサポートし、静電気放電(ESD)に対する保護機能も備えています。パッドの電気的特性は、外部回路の要件に合わせて調整できます。以下の調整が可能です。
• 出力駆動強度を2mA、4mA、8mA、または12mAに設定できます。
• 出力スルーレートを低速または高速に設定できます。
• 入力ヒステリシス(シュミットトリガモード)を有効にできます。
• 出力ドライバが無効になっている場合の出力信号レベルを設定するために、プルアップまたはプルダウンを有効にできます。
• パッドが未使用、未接続、または
アナログ信号に接続されている場合の消費電流を削減するために、入力バッファを無効にできます。

Output slew rateUser Bank Pad Control registerPADS_BANK0: GPIO0, GPIO1, …, GPIO28, GPIO29 RegistersDRIVEで設定できるようで、デフォルトでは4mAに設定されているようです。

◆参考サイト

ADC (アナログ入力)

通常のArduinoと同じ標準関数で実行可能で、A0~A3の4chが使えます。

またADCの出力レンジを設定する関数が用意されています。RP2040のADCの分解能は12bitなので、4096段階まで引き上げることが出来ます。

以下、D0の電圧をシリアル通信で出力するコードです。

#define VOLT_PIN D0

void setup() {
  /* シリアル通信の設定 */
  Serial.begin(115200);
  /* ADCの出力レンジを設定*/
  analogReadResolution(12);
  /* ピンをインプットに設定 */
  pinMode(VOLT_PIN, INPUT);
}

void loop() {
  /* 電圧を測定 */
  int volt = analogRead(VOLT_PIN);
  /* 測定した値をシリアル通信で出力 */
  Serial.println(volt);
  
  delay(500);
}

PWM (アナログ出力)

通常のArduinoと同じ標準関数で実行可能で、すべてのピンが対応しています。
またPWM周波数やレンジを設定する関数が追加されているので状況に応じて変更が可能です。

以下、D0からDuty25%・周波数25kHzのPWM信号を出力するコードです。

#define PWM_PIN D0

void setup() {
  /* ピンモードを設定 */
  pinMode(PWM_PIN, OUTPUT);
  /* PWMの周波数を設定 */
  analogWriteFreq(25000);
  /* Duty100%の値を設定 */
  analogWriteRange(4096);
  /* Duty50%のPWM信号を出力 */
  analogWrite(PWM_PIN, 1024);
}

void loop() {

}

パルス幅・周波数の測定

通常のArduinoと同じ標準関数で実行可能です。

以下、GPIO28に入力された信号の周波数を0.5秒ごとに出力するコードです。
パルス幅の測定結果はマイクロ秒で出力されること、1周期の半分の値が出力されることを考慮して周波数に換算します。

const int TACHO_PIN = 28;

void setup() {
  /* シリアル通信の設定 */
  Serial.begin(115200);
  /* ピンをインプットに設定 */
  pinMode(TACHO_PIN, INPUT);
}

void loop() {
  /* パルス幅(HIGHの時間)を測定 */
  int pulse_width = pulseIn(TACHO_PIN, HIGH);
  /* パルス幅を周波数に換算する */
  float freq = (1000000.0 / ((float)(pulse_width) * 2.0));
  /* 測定した値をシリアル通信で出力 */
  Serial.println(freq);

  delay(500);
}

動作確認のため、オシロスコープのテストピンの1kHzの信号を入力してみました。

外部割込み

全てのピンで外部割込みが使用可能です。
使用方法も通常のArduinoシリーズと同じです。

以下、D9とGNDが導通したら赤色LEDが点灯するプログラムです。
※個人的に割込みの中で色々処理をするのは好きじゃないので、割込み内ではフラグを立てる処理のみにしています。

bool interrupt_flag = false;

void setup() {
  pinMode(D9,INPUT_PULLUP);
  pinMode(LED_BUILTIN, OUTPUT);

  attachInterrupt(D9, Interrupt, FALLING);
}

void Interrupt(){
  interrupt_flag = true;
}

void loop() {
  if(interrupt_flag){
    interrupt_flag = false;
    digitalWrite(LED_BUILTIN, LOW);
    delay(200);
  }else{
    digitalWrite(LED_BUILTIN, HIGH);
  }
}

タイマー割込み

通常のArduinoで使用するTimerone等は使えませんでした。
その代わりに別の関数が用意されています。
※Timerの何番を使用しているかは確認中です。

以下、1秒(1,000,000[us])ごとにLEDのON/OFFを切り替えるプログラムです。

struct repeating_timer st_timer;
bool timerFlag = false;
int a;

/* タイマー割り込み処理 */
bool Timer(struct repeating_timer *t) {
  timerFlag = true;
  return true;
}

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);

  /* タイマーの初期化(割込み間隔はusで指定) */
  add_repeating_timer_us(1000000, Timer, NULL, &st_timer);
}

void loop() {
  /* timerFlagがtrueになる度にLEDをトグルする */
  if (timerFlag == true) {
    if (a == 0) {
      digitalWrite(LED_BUILTIN, HIGH);
    } else {
      digitalWrite(LED_BUILTIN, LOW);
    }
    a = 1 - a;
    timerFlag = false;
  }
}

シリアル通信(UART)

PCとの通信

シリアル通信は特にボードの設定変更なく使えます。

当方の環境では書き込みか何かが上手くいかなかったのか、最初はシリアルポートが認識されませんでした。
理由はわかりませんが、Debug Portを「Disable」から「Serial」に、Debug Levelを「なし」から「全て」に変更して書き込むとPC側に認識されるようになりました。
以降はデフォルト設定に戻しても普通にシリアルポートが認識されています。

ファイル → 01.Basics → DigitalReadSerial の順にクリックしてスケッチ例を開き、変更せずに書き込みます。

GPIOの電圧をHIGHなのかLOWなのか判断してシリアル出力するサンプルプログラムです。
デフォルトでは、2番ピン(D8)に3.3Vをつなぐと1、GNDをつなぐと0が出力されます。

PC以外との通信

※2022/11/20追記

rp2040のUARTは2チャンネルあり、PCと通信しつつ他の回路とシリアル通信をすることが出来ます。
Seeed XIAO RP2040ではUART0がピンアサインされています。

ピン名称Seeed XIAO RP2040
ピンアサイン
TXD6(GPIO0)
RXD7(GPIO1)

実際のプログラムでは、PC通信での「Serial」を「Serial1」に変更するだけです。
※なぜかUART0がSerial1に、UART1がSerial2に割り当てられています。Serial0じゃだめだったのか…?

以下、DigitalReadSerialを書き換えてみました。動作確認にはUSB-シリアル変換ケーブル等、シリアル通信を受信できる回路が必要です。

int pushButton = 2;

void setup() {
  Serial1.begin(9600);
  pinMode(pushButton, INPUT);
}

void loop() {
  int buttonState = digitalRead(pushButton);
  Serial1.println(buttonState);
  delay(1);
}

I2C通信

Seeed XIAO RP2040のI2C通信は、初期化がやや特殊です。
RP2040はI2Cが2チャンネルあり、割り当てピンはいくつかの選択肢から選ぶことが出来ます。しかし、デフォルトの割り当てピンとSeeed XIAO RP2040のI2Cピンが一致していないため、必ずWire.begin()の前にピン設定が必要になります。

ピン名称デフォルト割り当てSeeed XIAO RP2040
ピンアサイン
SDAGPIO4(I2C0)GPIO6(I2C1)
SCLGPIO5(I2C0)GPIO7(I2C1)

↓こちらのブログでSeeed XIAO RP2040を使ったI2Cscannerのコードを紹介されているので、こちらを参考にさせていただきました。

https://aloseed.com/it/xiao_rp2040/#toc18

ピン設定で「SDA」「SCL」という文字列を代入していますが、これはSeeed XIAO RP2040のボード情報の部分でそれぞれ「6」「7」と定義されています。
また当方の環境では「Wire1」を使用したプログラムを書き込むと、PCに認識されなくなってしまいました。(BOOTSELボタンで起動して別のプログラムを書き込めば直ります)
以下、動作確認出来たプログラムです。

#include <Wire.h>

#define XIAO_RP2040_NEW_BOARD (defined(ARDUINO_SEEED_XIAO_RP2040) || (defined(ARDUINO_SEEED_XAIO_RP2040) && defined(__WIRE0_DEVICE)))

#if XIAO_RP2040_NEW_BOARD
#define WIRE Wire
#else
#define WIRE Wire1
#endif

void setup()
{
#if !XIAO_RP2040_NEW_BOARD
  WIRE.setSDA(SDA);
  WIRE.setSCL(SCL);
#endif
  WIRE.begin();
 
  Serial.begin(115200);
  while (!Serial);             // Leonardo: wait for serial monitor
  Serial.println("\nI2C Scanner");
}
 
 
void loop()
{
  byte error, address;
  int nDevices;
 
  Serial.println("Scanning...");
 
  nDevices = 0;
  for(address = 1; address < 127; address++ ) 
  {
    // The i2c_scanner uses the return value of
    // the Write.endTransmisstion to see if
    // a device did acknowledge to the address.
    WIRE.beginTransmission(address);
    error = WIRE.endTransmission();
 
    if (error == 0)
    {
      Serial.print("I2C device found at address 0x");
      if (address<16) 
        Serial.print("0");
      Serial.print(address,HEX);
      Serial.println("  !");
 
      nDevices++;
    }
    else if (error==4) 
    {
      Serial.print("Unknown error at address 0x");
      if (address<16) 
        Serial.print("0");
      Serial.println(address,HEX);
    }    
  }
  if (nDevices == 0)
    Serial.println("No I2C devices found\n");
  else
    Serial.println("done\n");
 
  delay(5000);           // wait 5 seconds for next scan
}

SPI通信

SPI通信については、CS以外はデフォルトの割当と一致しているためピン設定は不要です。

ピン名称デフォルト割当Seeed XIAO RP2040
ピンアサイン
CSGPIO31(SPI0)GPIO1(SPI0)
SCKGPIO2(SPI0)GPIO2(SPI0)
TX(MOSI)GPIO3(SPI0)GPIO3(SPI0)
RX(MISO)GPIO4(SPI0)GPIO4(SPI0)

以下、MCP3002(A/Dコンバータ)での動作確認用サンプルコードです。18~20行目のピン設定は無くても動作しますが、変更用のメソッドを忘れそうなのでメモのため記述しています。

#include <SPI.h>

const int CS = 1;
const float v_ref = 3.3;

// 通信速度1Mbps、MSBファースト、モード0
SPISettings settings(1000000, MSBFIRST, SPI_MODE0);

void setup() {
  // シリアル通信の初期化
  Serial.begin(115200);

  // GPIOの初期化
  pinMode(CS, OUTPUT);
  digitalWrite(CS, HIGH);

  // SPI通信の初期化
  SPI.setSCK(SCK);
  SPI.setTX(MOSI);
  SPI.setRX(MISO);
  SPI.begin();
}

void loop() {
  // ADC値の取得
  SPI.beginTransaction(settings);            //通信開始
  digitalWrite(CS, LOW);
  byte highByte = SPI.transfer(0b01101000);  // コマンド送信(シングルエンド:CH0)、上位バイト受信
  byte lowByte = SPI.transfer(0x00);         // ダミーデータ送信、下位バイト受信
  digitalWrite(CS, HIGH);
  SPI.endTransaction();                      //通信終了

  // 取得した値を物理量に変換して出力
  unsigned int dataCh0 = ((highByte & 0x03) << 8) + lowByte;
  float volts = (float)dataCh0 * v_ref / 1024.0;
  Serial.println("CH0 " + String(volts, 3) + "V");

  delay(500);
}