
Arduino Plug and Make Kitをさわって、まなんで、たのしもう
こんにちは、アルバイトの田中です。
今回は、Arduino Plug and Make Kitをご紹介します。
早速Kitのご紹介に入ろうと思いましたが、まずはArduinoについてご存じない方のために簡単な紹介をします。「知ってるよ!」という方は次のセクション「 2. Arduino Plug and Make Kitとは?」 まで読み飛ばしてください。
目次
- Arduinoとは?
- Arduino Plug and Make Kitとは?
- 開封
- チュートリアルを進めてみよう!
4.1. 環境構築
4.2. プロジェクトの結果
- キットを使ってIoTなものを作ってみた
- 最後に
- サンプルコード
1. Arduinoとは?
弊社のこちらのページから引用しますと
Arduino(アルデュイーノ)はフィジカルコンピューティングのためのオープンソースのプラットフォームです。電子工作の経験がない人でも簡単にインタラクティブなものを作りはじめられます。世界中で使われているので、インターネットで検索すればたくさんの作例を見つけることができます
つまり、初心者でも簡単にプログラムを書いてものづくりが始められる、世界中で人気なツールなのです。
では、今回紹介するArduino Plug and Make Kitは、Arduinoに加えてどのような特徴があるのでしょうか?
2. Arduino Plug and Make Kitとは?
商品ページから引用しますと
Arduino UNO R4 WiFiを使って簡単にIoTデバイスを制作できるキットです。同梱のQwiicベース センサモジュール Modulinoを接続するだけで簡単に使用できます。ブレッドボードの用意やハンダづけ、複雑なコーディングの必要がなく、モジュールを接続していくだけで使用できるため、特に教育分野での使用に適しています
なんと、センサモジュール同士をコネクタでつなぎ、センサモジュール用のライブラリを使ってArduinoにプログラムを書くだけで簡単にIoTデバイスを製作できるのです!
上の写真のようなKit内のセンサモジュールはModulinoと呼ばれていて、Groveコネクタで配線するだけでArduinoと接続できます。
電子工作初心者のうちは、「Arduinoから始めたいけど、何をすればよいか分からない...」と初手で躓くことが非常に多いと思います。Arduino Plug and Make Kitは、丁寧なドキュメントや、IoTな電子工作でよく使うパーツなどが揃っているので学習目的に最適なのです。
3. 開封
前置きの紹介が長くなりましたが、ここから実際に商品を触ってみます。
まずは段ボールを開封!
そして、商品の外箱を開けると「DIYの世界へようこそ!IoTなデバイスを作りましょう!」と記されたメッセージカードが入っています。メッセージカードでは、「QRコードをスキャンしてドキュメントに飛ぶびましょう!」と教えてくれています。指示に従ってQRコードを読み取ってみましょう。
スマートフォンでQRコードを読み取ると、Arduinoのサイトへアカウント登録するよう促されるので、登録をします。Google、Apple、Meta、GitHubのアカウントが連携できるのが素晴らしいですね。アカウントが多くなると管理が大変ですから連携機能はありがたいです。
![]() |
![]() |
登録が完了すると、すぐに以下のようなコンテンツが無料で閲覧できます。Kit専用のコンテンツだけで約5時間分あります。多くの場合、教育用のコンテンツ代は非常に高価ですが無料で良いのでしょうか。
こちらのコンテンツは、Kitがなくてもhttps://courses.arduino.cc/plugandmake/から閲覧できるので、ぜひ覗いてみてください。
そして、キットの内容物は以下の通りです。
- 1 x Arduino UNO R4 WiFi
- 7 x Modulinoセンサおよびアクチュエータ
- 1 x KNOB(プッシュボタン付きエンコーダ)
- 1 x PIXELS(8 x RGB LED)
- 1 x DISTANCE(ToF近接センサ)
- 1 x MOVEMENT(6軸IMU)
- 1 x BUZZER
- 1 x BUTTONS(3 x ボタン)
- 1 x THERMO(温湿度センサ)
- ケーブル(USB-C、Qwiic)
- 組み立てフレーム(ネジ、ナットを含む)
写真中心の黄色いボードは、ModulinoとArduinoを固定する台座です。
内容物を確認したところでKit専用のコンテンツを読み、チュートリアルを進めていきましょう!
4. チュートリアルを進めてみよう!
チュートリアルは、丁寧すぎるくらいに事細かく手順を示してくれます。言語はすべて英語なので、日本人には多少の読みにくさがありますが、最近はwebの翻訳機能が充実しているので、ツールを駆使して適宜日本語訳して読んでいきましょう。翻訳を使えば小学校高学年くらいから読めると思います。
4.1. 環境構築
まずは環境構築です。環境構築の項目は全部で3つあり、合計23分のコンテンツがあります。指示通りに読むことで、構築は終わるので詳細は割愛します。トラブルが起きた際のトラブルシューティングが細かに書いてあるので、上手くいかないときは参考にすると良いと思います。
本Kitに付属しているArduino UNO R4 WiFiは、オンライン上のコンパイルビルドツール、監視ツール、制御ツール等を含むIoTサービスである「Arduino Cloud」に対応しています。学習コンテンツの初めは、このArduino Cloud周りの設定がメインです。
ちなみに私は、初めてArduino UNO R4 WiFiを触ったので、オンライン上のエディターでコンパイルし、書き込みを行えることに衝撃を受けました。R3からこんなに発展していたのですね...。R3でArduinoが止まってる方々は、R4 WiFi単体だけでもぜひお手に取って遊んでみてください。変化に衝撃を受けるかと思います。Arduino Uno R4 WiFiの商品ページはこちら。
本筋から逸れましたので軌道修正です。チュートリアルを終えると、早速プロジェクトごとの実践の章が始まります。
私は、リストの上から3番目の「8ビットの電子音楽を作成できるファンキーなシンセサイザーを構築」と書いてあるSonic Synthのプロジェクトに興味を持ったので、これを進めていきます。
プロジェクトのボタンを押下すると、コンテンツが表示されます。導入の項目では、Sonic SynthプロジェクトはModulinoボタン、ブザー、ノブ、ピクセルを使用してシンセサイザーを作るプロジェクトですと説明がありました。以下の写真のモジュールたちですね。モジュールの4隅にはネジ穴が開いており、黄色の台座にネジで固定していきます。
各学習項目には、プロジェクトを進めた先にどのような学習成果が見込めるかリストされています。今回のプロジェクトでは、以下の結果が得られるそうです。
和訳をすると以下の通りです。
学習成果
このプロジェクトでは、次の方法を学習します。
- Modulino Buttonsのボタンを押すたびに異なるトーンを生成します。
- Arduino Cloudからトーンの持続時間と周波数を制御します。
- Modulino Knobを使用してトーンの特性を操作します。
- Modulino Pixelsを視覚的なフィードバックとして使用します。
また、次のような優れたプログラミング概念も学習します。
- 算術演算子(加算、乗算)
- 自作関数
- for文
- インクリメント(増分)
- if文
学習成果が分かったところで、実際にプロジェクトを進めていきます。
4.2. プロジェクトの結果
プログラムを終えて無事シンセサイザーを完成させると以下の動画のようになります。ボタンを押す位置とノブの回転量でスピーカーの音程を変えることができました。また、ノブの位置に応じてLEDの点灯位置を上下できました。
プロジェクトの最後には、改良の手がかりが書いており、今回のプロジェクトの場合は以下のように書いてありました。
「今回のプロジェクトで使用しなかったModulinoを追加したり、既存のModulinoの挙動を変えみたりして機能を拡張してみよう」とのことです。各プロジェクトの最後には必ず改良の手がかりが書かれていて、学んだ内容を活かして、説明書なしで自ら考えて手を動かす練習ができます。
5. キットを使ってIoTなものを作ってみた
キットの内容だけでは少々遊び足りないので、実際にIoTな工作をしてみます。
都市部に住む会社員/学生の方は、毎朝電車で通勤通学をしているわけですが、大幅な遅延により、間に合わないことがありますよね。そんなとき、上司/教員は、なぜ来ていないか心配になると思います。
そこで今回は、一目で電車の遅延状況が分かる端末を作ってみました。弊社オフィスが大江戸線の牛込神楽坂駅近くで、都営地下鉄の利用者が多いので、都営線の遅延表示を行います。
撮影時は三田線と荒川線が遅れていたので、端末右下の三田線と荒川線を示すLEDが消えています。フルカラーLEDは左から
- 浅草線
- 三田線
- 新宿線
- 大江戸線
- 荒川線
- 日暮里舎人線
の順で並んでいます。
フルカラーLEDでの遅延状況の表示に加え、左下のボタンでモードを切り替えて、東京都営の電車8路線の遅延状況を文字でも表示できます。
ボタン上のオレンジLEDの2進数表示で路線モードが表示されていて、モードの順番はフルカラーLEDの路線と同じ順番です。
例えば下の写真は、オレンジLEDが2進数で「2」を指しているので新宿線表示のモードです。右のフルカラーLEDが全点灯しているので電車の遅延はなさそうですね。
以下の写真は、2進数で「5」を指しているので日暮里舎人ライナー表示のモードです。
動画ではボタンを押して浅草線から三田線へ路線モードが変わり、三田線の遅れた理由がスクロールで表示されています(撮影角度の問題で少々見づらくてすみません...)。
こちらの制作のサンプルコードは本ブログの最下部に掲載しています。バグが残っているかもしれませんが、ぜひ参考にしてみてください。
余談
Arduino Cloudで作業を進めていたのですが、Free版は一日の最大書き込み数が25回でした。Free版のままでの開発は厳しそうなので、途中からローカル環境に変更しました。Modulinoのライブラリはローカル環境のArduino IDEでも使用できます。クラウド環境で開発したくない場合はすぐにローカル版へ変更できるため、お金に余裕のある方は、Arduino Cloudのサブスクリプションを契約して、クラウド環境で開発することを勧めます。
6.最後に
今回はArduino Plug and Make Kitをご紹介しました。ご紹介したキットは、コネクタ配線のみで基本的なパーツの使い方が学べるので、電子工作初心者の方が電子工作やIoT工作の基礎を勉強するには最適だと思います。もちろん、玄人の方でもModulino縛りでアイデア勝負の工作をしてみるのも楽しいかと思います。PCBを製作して、自作Modulinoがあっても楽しいかもしれないですね。
このブログを見て、興味を持った方はぜひ以下のリンクから販売ページを覗いてみてください。
https://www.switch-science.com/products/9760
※付属のArduino Uno R4 WiFiは、スイッチサイエンスの「Arduino Uno永久保証」の対象です。詳しくは特集ページをご覧ください。
最後まで読んでいただきありがとうございました。
7. サンプルコード
本ブログの5章で紹介した制作物のコードを掲載します。
使用したライブラリ
- ArduinoJson 7.2.1
- WiFiS3(Arduino Uno R4標準ライブラリ)
- WiFiSSLClient(Arduino Uno R4標準ライブラリ)
- Arduino_LED_Matrix(Arduino Uno R4標準ライブラリ)
- Modulino(Arduino Plug and Make Kit用ライブラリ)
- Arduino-misakiUTF16 v1.2(https://github.com/Tamakichi/Arduino-misakiUTF16)
- string(C言語標準ライブラリ)
API取得先
公共交通オープンデータセンター開発者サイト: https://developer.odpt.org/
※本ブログ公開時(2025年1月)での取得先となります。
※本コード使用の際は、公共交通オープンデータセンター開発者サイトで利用登録の上、コード上の「公共交通オープンデータセンター開発者サイト上の東京都交通局APIのリンク」を置き換えてください。
※本ブログでは、公共交通オープンデータセンター開発者サイト上の東京都交通局様公開のデータを利用しています。
※個人の趣味の工作の範囲で利用をお願いします。
注意点
arduino_secrets.hでSSIDやパスワードのdefineをしています。
バグが残っている点、ご了承ください。
個人の趣味の工作の範囲で利用をお願いします。
#include <ArduinoJson.h>
#include <WiFiS3.h>
#include <WiFiSSLClient.h>
#include <Modulino.h>
#include <Arduino_LED_Matrix.h>
#include "arduino_secrets.h"
#include "misakiUTF16.h"
#include "string.h"
ModulinoPixels leds;
ModulinoButtons buttons;
ArduinoLEDMatrix matrix;
WiFiSSLClient client;
char ssid[] = SECRET_SSID;
char pass[] = SECRET_PASS;
int status = WL_IDLE_STATUS;
const char* host = "api-public.odpt.org";
const char* path = "「公共交通オープンデータセンター開発者サイト上の東京都交通局APIのリンク」";
const int httpsPort = 443;
const uint32_t REQUEST_INTERVAL = 300000; // 300000 ms = 5分
int brightness = 10;
uint32_t lastRequestTime = 0;
uint32_t WebGet_LastTime = 0;
bool start_flag = true;
// JSONパーサ用バッファ
StaticJsonDocument<1536> doc;
// 12x8ドット フレームバッファ
unsigned long frame[] = {0, 0, 0};
static uint32_t lastScrollTime = 0;
const uint32_t SCROLL_INTERVAL = 60; // スクロール更新間隔
// 路線ごとの遅延情報を保存するバッファ
char trainInfo[6][128] = {
"情報が取得されていません",
"情報が取得されていません",
"情報が取得されていません",
"情報が取得されていません",
"情報が取得されていません",
"情報が取得されていません"
};
// 路線ごとの色設定
uint8_t color[6][3] = {
{232, 82, 152}, // 浅草
{0, 121, 194}, // 三田
{108, 187, 90}, // 新宿
{182, 0, 122}, // 大江戸
{183, 155, 131}, // 荒川
{255, 255, 255} // 日暮里舎人
};
// 路線名を配列に入れておく
const char* routeNames[6] = {
"浅草",
"三田",
"新宿",
"大え戸", //江の字がMisakiフォントにないので、ひらがなで代用
"あら川", //荒の字がMisakiフォントにないので、ひらがなで代用
"日暮里舎人"
};
// スクロール表示関連
void displayScrollingText(const char* text);
void scrollIn(uint8_t* font, uint8_t fw, uint8_t ypos, int wt);
void scroll(void);
void clearFrame(void);
void setAt(uint8_t x, uint8_t y, uint8_t color);
// テキスト処理
void replaceText(char* str, const char* target, const char* replacement);
// 通信処理
bool fetchTrainInfo(void);
bool ensureWiFiConnected(void);
int8_t mode;
void setup() {
Serial.begin(115200);
Modulino.begin();
leds.begin();
matrix.begin();
buttons.begin();
buttons.setLeds(true, true, true);
mode = 0; //mode初期化
// WiFi接続
while (status != WL_CONNECTED) {
Serial.print("Connecting to: ");
Serial.println(ssid);
status = WiFi.begin(ssid, pass);
delay(5000);
if (WiFi.status() == WL_CONNECTED) {
break;
}
}
Serial.println("WiFi接続成功!");
}
void loop() {
// Wi-Fi が切れていたら再接続を試みる
ensureWiFiConnected();
// ボタン押下判定
if (buttons.update()) {
if (buttons.isPressed(0)) {
mode++;
if (mode >= 6) mode = 0;
replaceText(trainInfo[mode], "遅延", "ちえん");
replaceText(trainInfo[mode], "頃", "ごろ");
replaceText(trainInfo[mode], "江", "え");
replaceText(trainInfo[mode], "荒", "あら");
Serial.print("Button A pressed! mode : ");
Serial.println(mode);
}
else if (buttons.isPressed(2)) {
mode--;
if (mode < 0) mode = 5;
// Misakiフォントにない特定の漢字をひらがな等に置き換え
replaceText(trainInfo[mode], "遅延", "ちえん");
replaceText(trainInfo[mode], "頃", "ごろ");
replaceText(trainInfo[mode], "江", "え");
replaceText(trainInfo[mode], "荒", "あら");
Serial.print("Button C pressed! mode : ");
Serial.println(mode);
}
}
// ボタンLEDを現在のmodeに応じて二進数で点灯
buttons.setLeds((mode / 4) % 2, (mode / 2) % 2, mode % 2);
// 一定時間ごと&初回で最新情報を取得
if ((millis() - WebGet_LastTime) > REQUEST_INTERVAL || start_flag) {
Serial.println("\nStarting connection to server...");
if (fetchTrainInfo()) {
// ログ出し&色設定
for (uint8_t i = 0; i < 6; i++) {
Serial.print(routeNames[i]);
Serial.print(" の情報: ");
Serial.println(trainInfo[i]);
// 遅延なしの場合は路線色に点灯
if (strcmp(trainInfo[i], "現在、15分以上の遅延はありません。") == 0) {
leds.set(i, color[i][0], color[i][1], color[i][2], brightness);
}
}
leds.show();
// 取得直後のモード文字も置き換え
replaceText(trainInfo[mode], "遅延", "ちえん");
replaceText(trainInfo[mode], "頃", "ごろ");
replaceText(trainInfo[mode], "江", "え");
replaceText(trainInfo[mode], "荒", "あら");
}
WebGet_LastTime = millis();
start_flag = false;
}
// テキストをスクロール表示
if (millis() - lastScrollTime > SCROLL_INTERVAL) {
// 表示する文字列を作成。routeNames[mode]とtrainInfo[mode]を結合して表示
char scrollBuffer[256];
snprintf(scrollBuffer, sizeof(scrollBuffer), "%s線 : %s", routeNames[mode], trainInfo[mode]);
displayScrollingText(scrollBuffer);
lastScrollTime = millis();
}
}
// -------------------- Wi-Fi再接続 --------------------
bool ensureWiFiConnected(void) {
if (WiFi.status() == WL_CONNECTED) return true;
Serial.println("Wi-Fi切断を検出、再接続中...");
WiFi.disconnect();
delay(1000);
WiFi.begin(ssid, pass);
unsigned long startAttemptTime = millis();
while (WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < 10000) {
delay(500);
Serial.print(".");
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\nWi-Fi再接続成功");
return true;
} else {
Serial.println("\nWi-Fi再接続失敗");
return false;
}
}
// -------------------- 列車情報取得 --------------------
bool fetchTrainInfo(void) {
if (!client.connect(host, httpsPort)) {
Serial.println("サーバー接続失敗");
return false;
}
// HTTPリクエスト送信
client.print(String("GET ") + path + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"Connection: close\r\n\r\n");
Serial.println("HTTPリクエスト送信完了");
// HTTPレスポンスからヘッダを読み飛ばす
while (client.connected()) {
String line = client.readStringUntil('\n');
if (line == "\r") {
// 空行を検出したら終了
break;
}
}
DeserializationError error = deserializeJson(doc, client);
client.stop(); // 切断
if (error) {
Serial.print("JSON解析失敗: ");
Serial.println(error.f_str());
return false;
}
uint8_t docSize = doc.size();
for (uint8_t i = 0; i < 6; i++) {
if (i < docSize) {
const char* info = doc[i]["odpt:trainInformationText"]["ja"] | "情報なし";
strncpy(trainInfo[i], info, sizeof(trainInfo[i]) - 1);
trainInfo[i][sizeof(trainInfo[i]) - 1] = '\0';
} else {
strncpy(trainInfo[i], "情報なし", sizeof(trainInfo[i]) - 1);
trainInfo[i][sizeof(trainInfo[i]) - 1] = '\0';
}
}
return true;
}
// -------------------- スクロール表示関連 --------------------
void displayScrollingText(const char* text) {
static char* ptr = nullptr;
static uint8_t font[8];
static uint8_t width = 0;
if (!ptr || *ptr == '\0') {
ptr = (char*)text;
}
uint16_t ucode;
ptr += charUFT8toUTF16(&ucode, ptr);
width = isZenkaku(ucode) ? 8 : 4;
getFontDataByUTF16(font, ucode);
scrollIn(font, width, 1, SCROLL_INTERVAL);
}
// 1文字分を左スクロールで挿入
void scrollIn(uint8_t* font, uint8_t fw, uint8_t ypos, int wt) {
for (int8_t wx = 0; wx < fw; wx++) {
scroll(); // 左に1ドットスクロール
for (int8_t wy = 0; wy < 8; wy++) {
bool pixelOn = (font[wy] & (0x80 >> wx)) != 0;
setAt(11, wy + ypos, pixelOn ? 1 : 0);
}
matrix.loadFrame(frame);
delay(wt);
}
}
// フレームバッファクリア
void clearFrame(void) {
for (uint8_t i = 0; i < 3; i++) {
frame[i] = 0;
}
}
void scroll() {
uint8_t mlb[3];
for (uint8_t i = 0; i < 3; i++) {
mlb[i] = (frame[i] & 0x80000000UL) ? 1 : 0;
frame[i] <<= 1;
}
// 右端の不要なビットをクリア
frame[0] &= 0b11111111111011111111111011111111;
frame[1] &= 0b11101111111111101111111111101111;
frame[2] &= 0b11111110111111111110111111111110;
frame[0] = mlb[1] ? (frame[0] | 1UL) : (frame[0] & ~1UL);
frame[1] = mlb[2] ? (frame[1] | 1UL) : (frame[1] & ~1UL);
}
// LED座標に点を描画
void setAt(uint8_t x, uint8_t y, uint8_t c) {
if (x >= 12 || y >= 8) return;
uint8_t bitno = y * 12 + x; // 96ビット連番
uint8_t index = bitno / 32;
uint8_t pos = bitno % 32;
if (c) {
frame[index] |= (0x80000000UL >> pos);
} else {
frame[index] &= ~(0x80000000UL >> pos);
}
}
// -------------------- 文字列置換 --------------------
void replaceText(char* str, const char* target, const char* replacement) {
char buffer[128];
char* pos = strstr(str, target);
if (!pos) return;
size_t lenBefore = pos - str;
snprintf(buffer, sizeof(buffer),
"%.*s%s%s",
(int)lenBefore, str, replacement, pos + strlen(target));
strncpy(str, buffer, sizeof(buffer) - 1);
str[sizeof(buffer) - 1] = '\0';
}