コインサイズ Arduino互換機 Seeeduino XIAO を使ってみた

![title.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30370/aaf94397-0c7f-0f0e-3d32-6885b3218269.jpeg)

[**Seeeduino XIAO**](https://www.seeedstudio.com/Seeeduino-XIAO-Arduino-Microcontroller-SAMD21-Cortex-M0+-p-4426.html) の実機を入手したのでスペックや特徴を紹介していきます。

各店商品ページ: [秋月電子](https://akizukidenshi.com/catalog/g/g115178/) / [はんだ付け済みの完成品](https://akizukidenshi.com/catalog/g/gM-16135/) / [SwitchScience](https://www.switch-science.com/catalog/6335/) / [Amazon](https://www.amazon.co.jp/dp/B086KXY929) (3個セット)

# 特徴

- **ARM Cortex M0+ CPU (SAMD21G18) 48MHz 搭載**
- **256 KB フラッシュ、32 KB SRAM**
- **USB Type-C**
- **SPI, I<sup>2</sup>C, UART, DMA が利用可**
- **20mm x 17.5mm とコインサイズ**
- **ロジックレベルは 3.3V**

基板背面には突起物がなく、さらにすべてのピンが **端面スルーホール加工** となっており別の基板上へのはんだ付けが容易になっています。

|<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30370/65443a20-c82c-09b8-10e9-6b067e055cb3.jpeg"/>|<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30370/b4f21419-898a-1830-6820-92d3517bccc8.jpeg"/>|
|:---:|:---:|
|**内容物**: 太ピンヘッダとシールが付属|**側面と背面**|

# スペック

[**公式Wiki**](https://wiki.seeedstudio.com/Seeeduino-XIAO/#specification) から抜粋・補足

||諸元|
|----|----|
|**CPU**|ARM Cortex-M0+ 32 bit (SAMD21G18)<br/>動作周波数 最大 48 MHz (32.768 kHz から逓倍)|
|**ストレージ**| Flash 256KB、SRAM 32 KB|
|**ピン**| GPIO 14 ピン、デジタル入出力 11 ピン、アナログ入力 11 ピン、DAC出力 1 ピン|
|**ピン機能**| SPI、I<sup>2</sup>C、UART、PWM、外部割り込み、SWD（背面パッド）|
|**ロジックレベル**| 3.3V|
|**内蔵LED**| プログラム可能 1 個、電源 1 個、シリアルポート 2 個|
|**電源**| USB Type-C から 5V （スケッチ書き込みと端子共用）、背面 VIN パッド 5V|
|**サイズ**|長辺 20 mm、短辺 17.5 mm、高さ 3.5 mm|

いわゆる **SAMD系 Arduino** です。

Arduino MKR シリーズと同じ系列となり、それら向けに書かれたライブラリであれば使用できます。一方 Arduino Uno のような ATmega系 Arduino とは別物で、ATmega の特定レジスタに依存しているライブラリは使用できません。

GPIO 14 ピンは側面の 11 ピンと、リセットピン、背面の SWD (SWCLK, SWDIO) の合計 14 ピンを指しています。
背面パッドの使用に関しては [**こちら**](https://qiita.com/nanase/items/37ecca24aee8fbd4492e) をご参照ください。

ピン機能にある UART は USB 経由のシリアルとは別物で、`Serial1` で操作が可能なものです。

# スケッチ書き込みとリセット

[**こちら**](https://wiki.seeedstudio.com/Seeeduino-XIAO/#software) を参考に Arduino IDE へボードを追加してください。

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30370/d8ff0409-7841-0a07-f9aa-ee49fef757b3.jpeg"/>

Seeeduino XIAO はリセットボタンがありません。代わりに **リセットパッド** が存在します。
リセット時はこのリセットパッドを短絡させて GND を印加してください。

## bootloaderモード

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30370/9a0107ab-2584-1251-c3c3-184ef2a216ee.gif"/>

動作中にハングアップしたり、スケッチを書き込めなくなったときは素早く2回リセットをかけると **bootloader モード** に入ります。
このモードでは LED がゆっくりと点滅し、Seeeduino XIAO が USB メモリデバイスと認識され通常モードとはシリアルポートが別になり、ボードに書き込まれているプログラムを起動せずに常時スケッチの書き込みモードになります。

bootloader モードから通常のモードに復帰させるにはスケッチを書き込むか、再度素早く2回リセットをかけてください。

PC から Seeeduino XIAO が USB デバイスとして認識されていない状態で bootloader モードに入ると、LED が速く点滅します。

# Lチカ

本家の Arduino と同じようにサンプルスケッチから Basics > Blink を選んで Upload してください。

```c++:blink.ino
void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  digitalWrite(LED_BUILTIN, HIGH);
  delay(1000);
  digitalWrite(LED_BUILTIN, LOW);
  delay(1000);
}
```

本家 Arduino とは逆に **LOW で点灯、HIGH で消灯** になっています。

## 他にも2つ点滅可能な内蔵LEDがある

ところで、公式サイトにはその他 2 つの内蔵 LED について `two LEDs for serial port downloading` と記載しています。ところが回路図を見るとこれらの RX, TX の LED と繋がっている物理ピンがまったく存在しません。

ここで [USBCore.cpp](https://github.com/Seeed-Studio/ArduinoCore-samd/blob/1.7.2/cores/arduino/USB/USBCore.cpp#L622-L627) を見ると、SerialUSB の送受信が発生する度に `digitalWrite` で点灯させていることがわかります。つまりこの 2 つの LED は自由に操作できます。

```cpp:USBCore.cpp(抜粋)
uint32_t USBDeviceClass::recv(uint32_t ep, void *_data, uint32_t len)
{
	if (!_usbConfiguration)
		return -1;

#ifdef PIN_LED_RXL
	if (rxLEDPulse == 0)
		digitalWrite(PIN_LED_RXL, LOW);

	rxLEDPulse = TX_RX_LED_PULSE_MS;
#endif
```

具体的なピン番号は SAMD 系 Arduino と同様に [variant.h/variant.cpp](https://github.com/Seeed-Studio/ArduinoCore-samd/blob/1.7.2/variants/XIAO_m0/variant.h#L66-L76) に記述されており、Seeeduino XIAO の場合、以下のように 11, 12 に割り当てられています。

```cpp:variant.h
#define PIN_LED_13  (13u)
#define PIN_LED     PIN_LED_13
#define LED_BUILTIN PIN_LED

#define PIN_LED_RXL          (12u)
#define PIN_LED_TXL          (11u)
#define PIN_LED2             PIN_LED_RXL
#define PIN_LED3             PIN_LED_TXL
```

以下に 3 つの LED を点滅させるスケッチを示します。RX, TX 用のLEDは青色のようです。

```c++:blink3.ino
void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(PIN_LED2, OUTPUT);
  pinMode(PIN_LED3, OUTPUT);
}

void loop() {
  digitalWrite(LED_BUILTIN, HIGH);
  digitalWrite(PIN_LED2, HIGH);
  digitalWrite(PIN_LED3, HIGH);
  delay(1000);
  digitalWrite(LED_BUILTIN, LOW);
  digitalWrite(PIN_LED2, LOW);
  digitalWrite(PIN_LED3, LOW);
  delay(1000);
}
```

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30370/94756423-5b1b-e4af-1dac-5fe238689b0a.gif"/>

# インタフェース

## Serial over USB CDC

Arduino MKR シリーズでも同様ですが ATmega系Arduino とは違い、シリアル通信の実態は **USB CDC** です。つまり通常のシリアル通信よりも高速にシリアル通信ができます。

したがって `Serial.begin(speed)` によるボーレートの指定は意味をなしませんが、それ以外は従来通りの Serial として使えます。

### 速度の計測

<img alt="Seeeduino XIAO to PC (Serial over USB CDC)" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30370/c18063f3-999d-6d2a-a5ad-01a137309f9a.png" width="600"/>

今回は [**こちら**](https://gist.github.com/nanase/6cc57e14a572196baefd893ade67d32e) のスケッチを使って、Seeeduino XIAO から PC への転送速度を測定しました。片方向のみですが PC からの転送速度も同じになるはずです。

横軸はバッファサイズで `Serial.write(buf, len)` で一度に送るサイズです。
1 バイトずつ送った場合は 0.11 Mbps (14.53 KB/s) しか速度が出ませんが、64 バイトずつだと **6.30 Mbps** (805.86 KB/s) とかなり速くなります。64 バイトの整数倍に速度の飛びがあるのがわかるように、内部バッファのサイズが 64 バイトであると推測できます。

前述の通りシリアル通信では LED の点滅が行われますが、これによる速度の低下はほとんどありませんでした。

## SPI (microSDカード)

Seeeduino XIAO のロジックレベルは 3.3V です。つまりレベルシフタなしに SPI を介して microSD カードが扱えます。
SPI については ATmega系 Arduino とは一部異なる挙動がありますので、注意点などは [**こちら**](https://qiita.com/nanase/items/406a8a848d1b259d3af4) をご確認ください。

今回は秋月電子の [**マイクロSDカードスロットDIP化キット**](https://akizukidenshi.com/catalog/g/g105488/) を使用して microSD カードを読みます。

スケッチは Arduino のサンプルコードそのままですが、`SD.begin(cs_pin)` の引数には `SS` を指定しておきます。variant.h によると `SS = 4` となっており、これは D4/A4/SDA のピンと同じです。もちろんこれ以外のピンも指定できます。

|![microsd_photo.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30370/ba2f4257-e0ca-a525-4afd-793296235f7b.jpeg)|![microsd_schematic.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30370/b10e707a-949d-e937-95c3-8c5b3d7f56bc.jpeg)|
|:---:|:---:|
|**配線例**|**回路図**|

```cpp:microsd.ino
#include <SPI.h>
#include <SD.h>

File myFile;

void setup() {
  Serial.begin(9600);
  while (!Serial) ;

  Serial.print("Initializing SD card... ");

  if (!SD.begin(SS)) { // <-------------------------------- SS = D4/A4/SDA pin
    Serial.println("initialization failed!");
    while (1) ;
  }
  Serial.println("initialization done.");

  myFile = SD.open("test.txt", FILE_WRITE);

  if (myFile) {
    Serial.print("Writing to test.txt...");
    myFile.println("testing 1, 2, 3.");

    myFile.close();
    Serial.println("done.");
  }
  else
    Serial.println("error opening test.txt");

  myFile = SD.open("test.txt");
  if (myFile) {
    Serial.println("test.txt:");

    while (myFile.available())
      Serial.write(myFile.read());

    myFile.close();
  }
  else
    Serial.println("error opening test.txt");
}

void loop() { }
```

```plaintext:microsd.inoの出力
Initializing SD card...initialization done.
Writing to test.txt...done.
test.txt:
testing 1, 2, 3.
```

## I<sup>2</sup>C

I<sup>2</sup>C も使えます。3.3V 系デバイスはレベルシフタなしで直接接続できます。

今回は [**BME280**](https://akizukidenshi.com/catalog/g/g109421/) を使って温湿度と気圧を測定しました。BME280 は 3.3V で動作するため、レベルシフタなしで接続できます。Arduino と BME280 の接続については [**こちらの記事**](https://qiita.com/nanase/items/f34e03c29410add9c4d0) にて詳しく解説します。

|![bme280_photo.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30370/19f35ca3-f9a6-33d6-124b-a37cf9d1344c.jpeg)|![bme280_schematic.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30370/9fc6d103-1dce-e367-7ef4-ba1be6ee50b3.jpeg)|
|:---:|:---:|
|**配線例**|**回路図**|

```cpp:bme280.ino
#include <Wire.h>
#include "SparkFunBME280.h"

BME280 sensor;

void setup() {
  Serial.begin(9600);
  Wire.begin();
  sensor.beginI2C(); // Wire を用いて I2C 接続開始
}

void loop() {
  Serial.print("Temp: ");
  Serial.print(sensor.readTempC(), 2);

  Serial.print(" °C, Humidity: ");
  Serial.print(sensor.readFloatHumidity(), 2);

  Serial.print(" %, Pressure: ");
  Serial.print(sensor.readFloatPressure() / 100.0, 1);
  Serial.println(" hPa");

  delay(5000);
}
```

```plaintext:bme280.inoの出力
Temp: 22.05 °C, Humidity: 44.99 %, Pressure: 1009.0 hPa
Temp: 22.05 °C, Humidity: 44.72 %, Pressure: 1008.9 hPa
Temp: 22.06 °C, Humidity: 44.81 %, Pressure: 1008.9 hPa
```

## UART

前述したように、物理ピンで出ている UART ピンは USB CDC のものとは異なります。TX, RX ピンを使ったシリアル通信は `Serial1` を使います。

今回は [**GPS受信機キット**](https://akizukidenshi.com/catalog/g/g109991/) を接続して PC から NMEA 情報を取得します。Seeeduino XIAO で行うことは非常にシンプルで、GPS 受信機キットとシリアルの中継だけです。

|![gps_photo.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30370/f304a688-f887-a7f4-5cd1-17be3f39815b.jpeg)|![gps_schematic.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30370/6fbef634-ae29-3a9b-1760-d3419524df31.jpeg)|
|:---:|:---:|
|**配線例**|**回路図**|

```cpp:gps.ino
void setup() {
  Serial.begin(9600);
  Serial1.begin(9600);
}

void loop() {
  if (Serial.available()) {
    char c = (char)Serial.read();
    Serial1.write(c);
  }

  if (Serial1.available()) {
    char c = (char)Serial1.read();
    Serial.write(c);
  }
}
```

今回は NMEA 情報の閲覧に GPSFox を使っています。画像の通り問題なく座標が測位できています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30370/76361d26-4036-aea5-bcdd-d38f30cf16e3.png" width="600"/>



# 発展

## DMA

SAMD系 Arduino の特徴のひとつである DMA の使用が可能です。DMA についての詳細は [**こちらの記事**](https://qiita.com/nanase/items/406a8a848d1b259d3af4#dma) をご参照ください。

## Single Cycle IOBUS

Cortex M0+ はクロックのワンサイクルで GPIO の出力を操作できる [**Single Cycle IOBUS**](https://microchipdeveloper.com/32arm:samd21-iobus-overview) という機能を持っています。特定のレジスタに書き込むと、論理の反転、ピンの無効化、ピンのドライブ電流を変更できます。

### digitalWrite のとき

まずは愚直に `digitalWrite` を使ってパルスを作ります。どの Arduino でも使える方法です。`loop` 関数の呼び出しにかかるオーバヘッドをへらすため反復しています。

```cpp:step1.ino
void setup() {
  pinMode(PIN_A7, OUTPUT);
}

#define P                     \
  digitalWrite(PIN_A7, HIGH); \
  digitalWrite(PIN_A7, LOW);

#define W P P P P P P P P P P P P P P P P
void loop() { W W W W W W W W W W W W W W W W }
```

### レジスタを直接操作するとき

`digitalWrite` を使わずレジスタを直接操作してパルスを作ります。

```cpp:step2.ino
void setup() {
  pinMode(PIN_A7, OUTPUT);
}

#define P                                                             \
  digitalPinToPort(PIN_A7)->OUTSET.reg = digitalPinToBitMask(PIN_A7); \
  digitalPinToPort(PIN_A7)->OUTCLR.reg = digitalPinToBitMask(PIN_A7);

#define W P P P P P P P P P P P P P P P P
void loop() { W W W W W W W W W W W W W W W W }
```

### Single Cycle IOBUS を使うとき

本題の機能を使います。今回は [**ここ**](https://lab.sasapea.mydns.jp/2020/03/16/seeeduino-xiao/) で紹介されている **IOBUS.h** を使います。ピン番号の指定が特殊です。

```cpp:step3.ino
#include "IOBUS.h"

#define digitalPinToIOPin(P) ((g_APinDescription[P].ulPort << 5) + g_APinDescription[P].ulPin)
#define PIN_NUM digitalPinToIOPin(PIN_A7)

void setup() {
  IOBUS::pinMode(PIN_NUM, OUTPUT, true);
}

#define P IOBUS::toggleOutput(PIN_NUM);

#define W P P P P P P P P P P P P P P P P
void loop() { W W W W W W W W W W W W W W W W }
```

### 結果

| |digitalWrite|レジスタ操作|Single Cycle IOBUS|
|---|---|---|---|
|**波形**|![step1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30370/9a269f4e-5bd0-8eb4-127e-14b8d239a6a3.png)|![step2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30370/84a04db3-c854-8862-d253-6ff8fc94aa8f.png)|![step3_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30370/4358d74f-285c-6d16-1cc3-809946c42125.png)|
|**周波数**| 333 kHz | 6 MHz | 24 MHz |
|**1周期にかかるサイクル数**| 144 サイクル | 8 サイクル | 2 サイクル |

サイクル数は LOW → HIGH → LOW の 1 周期で示したので、確かに 1 サイクル (48MHz) で論理が反転できています。

# Tips

- [**Seeeduino XIAO 背面パッドをGPIOとして使う**](https://qiita.com/nanase/items/37ecca24aee8fbd4492e)