背景
Arduinoとはプログラムを書き込んで電子工作に利用できる開発ボードの総称です。開発ボードの回路図や、開発環境であるArduinoIDEなど、多くの設計がオープンソースとして公開されています。
そのためArduinoIDEを利用して公式・非公式含めて様々な種類のボードを開発できます。
シリアル通信とは、情報を1ビットずつ送信する通信形式の名前です。
Arduino Unoなどの場合、Serialという定義済みの変数を利用することでシリアル通信を行なえます。
シリアル通信の変数は、型を指定すれば下記のように関数の引数として渡せます。
#include <HardwareSerial.h>
void printHello(HardwareSerial *serial) {
serial->print("hello");
}
void setup() {
Serial.begin(115200);
}
void loop() {
printHello(&Serial); // print hello
delay(1000);
}
Unoなどの代表的なArduinoでは、上記のプログラムのようにHardwareSerialをシリアルの型とすればをビルドして動かせます。
しかし、USB通信機能を備えるLeonardoやARM系のArduinoは、Serialに別の型が使われているためビルドに失敗します。
adafruitのTravisCIスクリプトで検査対象となっているボード全てに対応するシリアル通信プログラムを書くのに時間がかかったため、備忘録を兼ねて方法を共有します。
使ったもの
Arduino IDE
Arduino IDEとは、Arduino向けのプログラムの開発ができるソフトウェアです。下記のページからダウンロードできます。
Download the Arduino IDE
Arduino IDEで利用できるボード情報
Arduino IDEの設定にjsonを追加してボードマネージャーからダウンロードすることで、標準設定のArduino以外のボードをArduino IDEでビルド出来るようになります。使ったボードの読み込み先はそれぞれこちらです。
esp8266/Arduino
ESP8266が使えるようになります。
espressif/arduino-esp32
ESP32が使えるようになります。
arduino/ArduinoCore-sam
ArduinoのSAM系(Due)が使えるようになります。
arduino/ArduinoCore-samd
ArduinoのSAMD系(Zeroなど)が使えるようになります。
Adafruit boards
Adafruitのボード全般が使えるようになります。
一覧
解説するボードとシリアルの種類を表にまとめます。| ボード | プログアム用シリアル | 自由なシリアル | SoftwareSerial | ||
|---|---|---|---|---|---|
| 型 | 変数名 | 型 | 変数名 | ||
| Arduino Uno | HardwareSerial | Serial | - | - | 利用可能 |
| ESP8266 | HardwareSerial | Serial | - | - | 利用可能 |
| ESP32 | HardwareSerial | Serial | HardwareSerial | Serial1, Serial2 | 利用できない |
| Arduino Mega | HardwareSerial | Serial | HardwareSerial | Serial1, Serial2, Serial3 | 利用可能 |
| Arduino Leonardo | Serial_ (USBAPI.h) | Serial | HardwareSerial | Serial1 | 利用可能 |
| Arduino Due | Serial_ (USB/USBAPI.h), UARTClass (HardwareSerialとして使える) | SerialUSB, Serial |
USARTClass (HardwareSerialとして使える) | Serial1, Serial2, Serial3 | 利用できない |
| Arduino Zero | Serial_ (USB/USBAPI.h), Uart (HardwareSerialとして使える) | SerialUSB, Serial | Uart (HardwareSerialとして使える) | Serial1 | 利用できない |
| Adafruit M0 | Serial_ (USB/USBAPI.h) | Serial | Uart (HardwareSerialとして使える) | Serial1 | 利用できない |
| Adafruit M4 | Serial_ (USB/USBAPI.h) | Serial | Uart (HardwareSerialとして使える) | Serial1, SERCOMで定義できるUart | 利用できない |
それぞれ解説します。
Streamで受け取れば、printを使う関数を記述可能
Streamというクラスを介してserialを受け取れば、大体のserialを引数として受け取れます。#include <HardwareSerial.h>
#include <SoftwareSerial.h>
void streamHello(Stream *serial) { serial->println("hello"); }
void setup() {
Serial.begin(115200);
softSerial.begin(115200);
}
void loop() {
streamHello(&Serial);
streamHello(&softSerial);
delay(1000);
}
twitterで教えていただきました。
@asukiaaa
— 🧽 (@INOUETAICHI) August 14, 2021
これ読みました。https://t.co/jDafsv2TMD
Serial_クラスもSerialクラス同様にStreamクラスを継承してるので、
void printHello(Stream *serial) {
serial->print("hello");
}
と書けば全部動くんじゃないでしょか
Arduino Uno(atmega328p系)
Arduino Unoはプログラミングポートを兼ねたHardwareSerialを1つ持っています。SoftwareSerialを利用できます。
PassUno.ino
#include <HardwareSerial.h>
#include <SoftwareSerial.h>
SoftwareSerial softSerial(8, 9);
void hardHello(HardwareSerial *serial) {
serial->println("hello");
}
void softHello(SoftwareSerial *serial) {
serial->println("hello");
}
void setup() {
Serial.begin(115200);
softSerial.begin(115200);
}
void loop() {
hardHello(&Serial);
softHello(&softSerial);
delay(1000);
}
ESP8266
ESP8266はプログラミングポートを兼ねたHardwareSerialを1つ持っています。SoftwareSerialを利用できます。
つまり、Unoと同じコードが動きます。
PassESP32.ino
#include <HardwareSerial.h>
#include <SoftwareSerial.h>
SoftwareSerial softSerial(8, 9);
void hardHello(HardwareSerial *serial) {
serial->println("hello");
}
void softHello(SoftwareSerial *serial) {
serial->println("hello");
}
void setup() {
Serial.begin(115200);
softSerial.begin(115200);
}
void loop() {
hardHello(&Serial);
softHello(&softSerial);
delay(1000);
}
ESP32
ESP32はプログラミングポートを兼ねたHardwareSerialを1つ、自由に使えるHardwareSerialを2つ持っています。SoftwareSerialは利用できません。
下記のコードはビルドはできますが、ESP32のSerial1は利用できないピンが割り当てられているため、Serial1を利用する場合はピンの配置を変える必要があります。
PassESP32.ino
#include <HardwareSerial.h>
void hardHello(HardwareSerial *serial) {
serial->println("hello");
}
void setup() {
Serial.begin(115200);
Serial1.begin(115200);
Serial2.begin(115200);
}
void loop() {
hardHello(&Serial);
hardHello(&Serial1);
hardHello(&Serial2);
delay(1000);
}
Arduino Mega(atmega2560系)
Arduino Megaはプログラミングポートを兼ねたHardwareSerialを1つ、自由に使えるHardwareSerialを3つ持っています。SoftwareSerialを利用できます。
自分が調査した中では、SoftwareSerialとHardwareSerialを合わせると、最も多くのシリアルを利用でるボードです。
PassMega.ino
#include <HardwareSerial.h>
#include <SoftwareSerial.h>
SoftwareSerial softSerial(10, 11);
void hardHello(HardwareSerial *serial) {
serial->println("hello");
}
void softHello(SoftwareSerial *serial) {
serial->println("hello");
}
void setup() {
Serial.begin(115200);
Serial1.begin(115200);
Serial2.begin(115200);
Serial3.begin(115200);
softSerial.begin(115200);
}
void loop() {
hardHello(&Serial);
hardHello(&Serial1);
hardHello(&Serial2);
hardHello(&Serial3);
softHello(&softSerial);
delay(1000);
}
Arduino Leonardo(atmega32u4系)
Arduino LeonardoはUSB接続用のUSBAPIを1つ、自由に使えるHardwareSerialを1つ持っています。SoftwareSerialを利用できます。
PassLeonardo.ino
#include <HardwareSerial.h>
#include <SoftwareSerial.h>
#include <USBAPI.h>
SoftwareSerial softSerial(8, 9);
void usbHello(Serial_ *serial) {
serial->println("hello");
}
void hardHello(HardwareSerial *serial) {
serial->println("hello");
}
void softHello(SoftwareSerial *serial) {
serial->println("hello");
}
void setup() {
Serial.begin(115200);
Serial1.begin(115200);
softSerial.begin(115200);
}
void loop() {
usbHello(&Serial);
hardHello(&Serial1);
softHello(&softSerial);
delay(1000);
}
Arduino Due(Arduino SAM系)
Arduino DueはNativeポートとしてUSBAPIを1つ、プログラミングポートを兼ねたUARTClassを1つ、自由に使えるUSARTClassを3つ持っています。UARTClassとUSARTClassはHardwareSerialとして利用できます。
SoftwareSerialは利用できません。
PassDue.ino
#include <HardwareSerial.h>
#include <USB/USBAPI.h>
void usbHello(Serial_ *serial) {
serial->println("hello");
}
void hardHello(HardwareSerial *serial) {
serial->println("hello");
}
void setup() {
SerialUSB.begin(115200);
Serial.begin(115200);
Serial1.begin(115200);
Serial2.begin(115200);
Serial3.begin(115200);
}
void loop() {
usbHello(&SerialUSB);
hardHello(&Serial);
hardHello(&Serial1);
hardHello(&Serial2);
hardHello(&Serial3);
delay(1000);
}
Arduino Zero(Arduino SAMD系)
Arduino ZeroはNativeポートとしてUSBAPIを1つ、プログラミングポートとして使えるUartを1つ、自由に使えるUartを1つ持っています。UartはHardwareSerialとして利用できます。
SoftwareSerialは利用できません。
PassZero.ino
#include <HardwareSerial.h>
#include <USB/USBAPI.h>
void usbHello(Serial_ *serial) {
serial->println("hello");
}
void hardHello(HardwareSerial *serial) {
serial->println("hello");
}
void setup() {
SerialUSB.begin(115200); // native port
Serial.begin(115200); // programming port
Serial1.begin(115200); // 0: RX, 1: TX
}
void loop() {
usbHello(&SerialUSB);
hardHello(&Serial);
hardHello(&Serial1);
delay(1000);
}
Adafruit M0(Adafruit SAMD系)
Adafruitが出しているM0系のボードは、USBAPIを1つ、自由に使えるUartを1つ持っています。UartはHardwareSerialとして利用できます。
SoftwareSerialは利用できません。
PassAdafruitM0.ino
#include <HardwareSerial.h>
#include <USB/USBAPI.h>
void usbHello(Serial_ *serial) {
serial->println("hello");
}
void hardHello(HardwareSerial *serial) {
serial->println("hello");
}
void setup() {
Serial.begin(115200);
Serial1.begin(115200);
}
void loop() {
usbHello(&Serial);
hardHello(&Serial1);
delay(1000);
}
Adafruit M4(Adafruit SAMD系)
AdafruitのM4系ボードは、USBAPIを1つ、自由に使えるUartを1つ、定義によってUartやI2Cなどに利用できるポートを3つくらい持っています。UartはHardwareSerialとして利用できます。
Uartの定義方法は、Adafruitの解説ページを参考にしてください。
Creating a new Serial | Using ATSAMD21 SERCOM for more SPI, I2C and Serial ports | Adafruit Learning System
SoftwareSerialは利用できません。
PassAdafruitM4.ino
#include <HardwareSerial.h>
#include <Uart.h>
#include <USB/USBAPI.h>
void usbHello(Serial_ *serial) {
serial->println("hello");
}
void hardHello(HardwareSerial *serial) {
serial->println("hello");
}
// https://learn.adafruit.com/using-atsamd21-sercom-to-add-more-spi-i2c-serial-ports/creating-a-new-serial
Uart uartSerial(&sercom1, 11, 10, SERCOM_RX_PAD_0, UART_TX_PAD_2);
void setup() {
Serial.begin(115200);
Serial1.begin(115200);
uartSerial.begin(115200);
}
void loop() {
usbHello(&Serial);
hardHello(&Serial1);
hardHello(&uartSerial);
delay(1000);
}
全部の環境に向けてビルドできるプログラム
Arduinoはc言語やc++の記述をプログラムで利用できます。c言語にはコンパイル前にプログラムの内容を処理するマクロという機能があり、これを利用することで環境ごとにプログラムの内容を変えられます。
全部の環境に向けてビルドできるプログラムをマクロを利用して作ってみました。
PassAll.ino
#include <HardwareSerial.h>
#if !defined(__arm__) && !defined(ESP32)
#include <SoftwareSerial.h>
void softHello(SoftwareSerial *serial) {
serial->println("hello");
}
#ifdef ARDUINO_MEGA
SoftwareSerial softSerial(10, 11);
#else
SoftwareSerial softSerial(8, 9);
#endif
#endif
// define hardHello before including SoftwareSerial cause error
void hardHello(HardwareSerial *serial) {
serial->println("hello");
}
#if ADAFRUIT_METRO_M4_EXPRESS
#include <Uart.h>
Uart uartSerial(&sercom1, 11, 10, SERCOM_RX_PAD_0, UART_TX_PAD_2);
#endif
#ifdef USBCON
#ifdef __arm__
#include <USB/USBAPI.h>
#else
#include <USBAPI.h>
#endif
void usbHello(Serial_ *serial) {
serial->println("hello");
}
#endif
// define flag to detect using SerialUSB
#if defined(__arm__) && !defined(ADAFRUIT_FEATHER_M0) && !defined(ADAFRUIT_METRO_M4_EXPRESS)
// adafruit boards don't use SerialUSB
#define USING_SERIAL_USB
#endif
void setup() {
#if !defined(__arm__) && !defined(ESP32)
softSerial.begin(115200);
#endif
#ifdef USING_SERIAL_USB
usbHello(&SerialUSB);
#endif
Serial.begin(115200);
#if defined(USBCON) && !defined(USING_SERIAL_USB)
Serial1.begin(115200);
#endif
#ifdef ESP32
Serial1.begin(115200);
Serial2.begin(115200);
#endif
#if defined(ARDUINO_MEGA) || defined(ARDUINO_DUE)
Serial1.begin(115200);
Serial2.begin(115200);
Serial3.begin(115200);
#endif
#if ADAFRUIT_METRO_M4_EXPRESS
uartSerial.begin(115200);
#endif
}
void loop() {
#if !defined(__arm__) && !defined(ESP32)
softHello(&softSerial);
#endif
#ifdef USING_SERIAL_USB
usbHello(&SerialUSB);
#endif
#if defined(USBCON) && !defined(USING_SERIAL_USB)
usbHello(&Serial);
#else
hardHello(&Serial);
#endif
#if defined(USBCON) && !defined(USING_SERIAL_USB)
hardHello(&Serial1);
#endif
#ifdef ESP32
hardHello(&Serial1);
hardHello(&Serial2);
#endif
#if defined(ARDUINO_MEGA) || defined(ARDUINO_DUE)
hardHello(&Serial1);
hardHello(&Serial2);
hardHello(&Serial3);
#endif
#if ADAFRUIT_METRO_M4_EXPRESS
hardHello(&uartSerial);
#endif
delay(1000);
}
マクロで切り替えている内容はこちらです。
SoftwareSerial関係
- ESP32とSAMとSAMDのボード(ARM系のボード)ではSoftwareSerialを利用しない
- Arduino MegaのSoftwareSerialはRXポートとして10を利用する
#if !defined(__arm__) && !defined(ESP32)
#include <SoftwareSerial.h>
void softHello(SoftwareSerial *serial) {
serial->println("hello");
}
#ifdef ARDUINO_MEGA
SoftwareSerial softSerial(10, 11);
#else
SoftwareSerial softSerial(8, 9);
#endif
#endif
void setup() {
#if !defined(__arm__) && !defined(ESP32)
softHello(&softSerial);
#endif
}
void loop() {
#if !defined(__arm__) && !defined(ESP32)
softHello(&softSerial);
#endif
}
USBAPI関係
- USBCONが定義されている場合、USBAPIを利用する
- SAMとSAMDのボード(ARM系のボード)ではSerial_をUSB/USBAPI.hでincludeする
- ArduinoのSAMとSAMDボード(ARM系のボード)ではSerialUSBを利用する
- AdafruitのSAMDボードではSerialUSBではなくSerialを利用する
(このプログラムはAdafruitのFeather M0とMetro M4 Expressだけ除外する処理にしています。Adafruitの他のSAMDのボード情報はAdafruitのsamdのboards.txtに載っています。)
#ifdef USBCON
#ifdef __arm__
#include <USB/USBAPI.h>
#else
#include <USBAPI.h>
#endif
void usbHello(Serial_ *serial) {
serial->println("hello");
}
#endif
// define flag to detect using SerialUSB
#if defined(__arm__) && !defined(ADAFRUIT_FEATHER_M0) && !defined(ADAFRUIT_METRO_M4_EXPRESS)
// adafruit boards don't use SerialUSB
#define USING_SERIAL_USB
#endif
void setup() {
#ifdef USING_SERIAL_USB
usbHello(&SerialUSB);
#endif
Serial.begin(115200);
#if defined(USBCON) && !defined(USING_SERIAL_USB)
Serial1.begin(115200);
#endif
}
void loop() {
#ifdef USING_SERIAL_USB
usbHello(&SerialUSB);
#endif
#if defined(USBCON) && !defined(USING_SERIAL_USB)
usbHello(&Serial);
#else
hardHello(&Serial);
#endif
#if defined(USBCON) && !defined(USING_SERIAL_USB)
hardHello(&Serial1);
#endif
}
複数のHardwareSerial関係
- Mega、DueはSerial1、Serial2、Serial3を利用する
- ESP32はSerial1とSerial2を利用する
- USBAPIをSerialUSBではなくSerialとして利用するボードは、Serial1をHardwareSerialとして利用する
void setup() {
#ifdef ESP32
Serial1.begin(115200);
Serial2.begin(115200);
#endif
#if defined(ARDUINO_MEGA) || defined(ARDUINO_DUE)
Serial1.begin(115200);
Serial2.begin(115200);
Serial3.begin(115200);
#endif
}
void loop() {
#ifdef ESP32
hardHello(&Serial1);
hardHello(&Serial2);
#endif
#if defined(ARDUINO_MEGA) || defined(ARDUINO_DUE)
hardHello(&Serial1);
hardHello(&Serial2);
hardHello(&Serial3);
#endif
}
Adafruit M4の機能
- Adafruit M4はUartでポートを定義して利用する
#if ADAFRUIT_METRO_M4_EXPRESS
#include <Uart.h>
Uart uartSerial(&sercom1, 11, 10, SERCOM_RX_PAD_0, UART_TX_PAD_2);
#endif
void setup() {
#if ADAFRUIT_METRO_M4_EXPRESS
uartSerial.begin(115200);
#endif
void loop() {
#if ADAFRUIT_METRO_M4_EXPRESS
hardHello(&uartSerial);
#endif
}
まとめ
USB通線専用ポートを持つボードのUSBAPIがSerialだったりSerialUSBだったりして切り替えに手間取りましたが、Arduinoで定義されている複数のシリアルを関数の引数として使う方法が分かりました。動作確認に利用したプログラムは下記のリポジトリで公開します。
asukiaaa/TravisPracticeArduinoLib/examples
「この記述はこうにると、より簡潔になるよ」など、改善案などありましたら、コメントやメッセージをもらえるとありがたいです。
「複数種類のシリアルをサポートしたいけどマクロを多用してまで同じ記述を増やしたくないよ」という方は、シリアルをラップするライブラリを公開してますので、良かったらそちらをお使いください。
asukiaaa/SomeSerial
ArduinoでHardwareSerialもSoftwareSerialもサポートする関数やクラスを作る方法
この情報が何かの参考になれば嬉しいです。
参考
Serial - Arduino ReferenceAdapting Sketches to M0 - Adafruit
ArduinoCore-samd/boards.txt
変更履歴
2021.12.31Streamクラスを使えば大体のシリアルクラスを扱えるという情報共有があったので、更新しました。

0 件のコメント :
コメントを投稿