jksoftのアイコン画像
jksoft 2025年01月31日作成 © Apache-2.0
製作品 製作品 閲覧数 170
jksoft 2025年01月31日作成 © Apache-2.0 製作品 製作品 閲覧数 170

自動ホワイトボードクリーナー

自動ホワイトボードクリーナー

概要

私は、今まで壁を走るロボット「うおーるぼっと」シリーズを開発してきました。
うおーるぼっとは、ホワイトボードに貼り付けてリモコンやスマートフォンで動かしたり、ペンで描いたコースに触れないように動かしたりする楽しむためのロボットです。

うおーるぼっとはホワイトボードを走れるので、折角ならばホワイトボードに書いたものを自動的に消せるようにしたいと思いました。
自動的に消せるホワイトボードはあるものの、専用のものだったり、故障が多かったりと一般的にはなかなか見かけません。
うおーるぼっとであれば、手軽にホワイトボードに貼り付けて使えるので、一般的なホワイトボードでも後づけで利用できます。
今回は、Spresenseを使用し、音声認識や画像認識を使いつつ試作を作ってみました。

構成

構成は以下のとおりです。

キャプションを入力できます

センサーとして、Spresense用のHDRカメラボード、コンデンサマイク、三軸の加速度センサーを使用してます。
HDRカメラは、ホワイトボード上に書かれたものやホワイトボードの端を検出するのに使用しています。
コンデンサマイクは、音声のキーワードで動作開始ができるように搭載しました。
三軸の加速度センサーは向いている方向がわかるように搭載しています。垂直に張り付いているので、重力加速度を検出するだけで方向がわかります。

キャプションを入力できます

キャプションを入力できます

部品

品名 個数 購入先
SPRESENSEメインボード 1 https://www.switch-science.com/products/3900 (今回はコンテストで提供いただいたものを使用しています)
SPRESENSE拡張ボード 1 https://www.switch-science.com/products/3901
SPRESENSE HDRカメラボード 1 https://www.switch-science.com/products/8080 (今回はコンテストで提供いただいたものを使用しています)
コンデンサマイク(高感度用と環境音用) 2 秋月電子通商やAmazonなど
抵抗 2.2KΩ 1 秋月電子通商やAmazonなど
Adafruit MMA8451 三軸加速度センサ 1 https://www.switch-science.com/products/1868
360連続回転SPT5525-360 25KGデジタルサーボ 2 Amazonなど
サーボ用ホイール&タイヤ 2 Amazonなど
Arduino用バニラシールド基板ver.2 1 https://www.switch-science.com/products/991
Arduinoシールド用ピンソケットのセット 1 https://www.switch-science.com/products/995
cheero Energy Plus 5000mAh IoT機器対応 大容量 モバイルバッテリー 1 Amazonなど
USBケーブル 1 Amazonなど
タミヤ ユニバーサルプレート 1 Amazonなど
ホワイトボード消しゴム 2 Amazonなど
ネオジム磁石(ネジで取り付けられるもの) 1 Amazonなど

機能確認

姿勢制御

垂直に走行する場合、重力加速度を検出すれば、向いてる方向がわかります。
そのため、加速度センサを使って、上方向、右方向、左方向、下方向への正確に進めるようにコントロールします。
加速度センサは、SPRESENSEにI2Cで接続できるMMA8451を使用しました。
拡張ボードを使用したので、電圧は3.3Vベースで信号線とともに接続しています。

次の動画は、姿勢制御の確認です。ホワイトボードを回転させても常に右方向を向き続けます。

ここに動画が表示されます

画像認識

取り付けられたカメラにより、ホワイトボードの端や書かれたものを認識して制御します。

キャプションを入力できます

キーワード音声認識

「開始」と「終了」という音声のキーワードで動作を開始・停止できるようにしました。
キーワードの音声と環境音を検出して、キーワードのパターンを区別しています。

キャプションを入力できます

動作

組み合わせた動作の様子です

ここに動画が表示されます

ソースコード

画像認識と音声認識

メインコアのコード
Main.ino

#if (SUBCORE != 1) #error "Core selection is wrong!!" #endif #include <MP.h> #include <Servo.h> #include <Wire.h> #include <Adafruit_MMA8451.h> #include <Adafruit_Sensor.h> // サーボの停止位置の値 // 個体差で中心値を微調整 #define SERVO1_CENTRE_VALUE 93 #define SERVO2_CENTRE_VALUE 90 // サーボの最大スピード #define SERVO_MAX_SPEED 40 enum Mode { MODE_STOP, MODE_RIGHT_RUN, MODE_LEFT_RUN, MODE_UP_RUN, MODE_DOWN_RUN }; Servo servo1; Servo servo2; Adafruit_MMA8451 mma = Adafruit_MMA8451(); Mode currentMode = MODE_STOP; void setServo(int16_t right,int16_t left) { int16_t servo1_speed = right; int16_t servo2_speed = left; if (servo1_speed > SERVO_MAX_SPEED) servo1_speed = SERVO_MAX_SPEED; if (servo2_speed > SERVO_MAX_SPEED) servo2_speed = SERVO_MAX_SPEED; servo1.write(SERVO1_CENTRE_VALUE+servo1_speed); servo2.write(SERVO2_CENTRE_VALUE-servo2_speed); } void setup() { MP.begin(); Serial.begin(115200); servo1.attach(6); servo2.attach(5); mma.begin(); mma.setRange(MMA8451_RANGE_2_G); } void loop() { int ret; int *buff; ret = MP.Recv(&msgid, &buff); if (ret >= 0) { currentMode = buff; } mma.read(); if (currentMode != MODE_STOP && mma.z > -3000 && mma.z < 3000){ int16_t position = 0; if (currentMode == MODE_RIGHT_RUN || currentMode == MODE_LEFT_RUN) position = mma.y; if (currentMode == MODE_UP_RUN || currentMode == MODE_DOWN_RUN) position = mma.x; int target = 0; // 目標値(Yを0に合わせる) float Kp = 0.05; // 比例ゲイン(調整可能) int controlSignal = Kp * (position - target); // 制御信号を計算 // 制御信号をm1とm2に反映 int16_t m1 = constrain(-controlSignal, -SERVO_MAX_SPEED, SERVO_MAX_SPEED); // m1の範囲を制限 if (m1 > 7 || m1 < -7) { int16_t m2 = -m1; // m2はm1と逆向きに設定 if (currentMode == MODE_RIGHT_RUN || currentMode == MODE_UP_RUN) setServo(m1,m2); if (currentMode == MODE_LEFT_RUN || currentMode == MODE_DOWN_RUN) setServo(m2,m1); } else { setServo(-SERVO_MAX_SPEED,-SERVO_MAX_SPEED); } } else { setServo(0,0); } Serial.println(); delay(100); }

足回りの制御

サブコアのコード
Sub.ino

#if (SUBCORE != 1) #error "Core selection is wrong!!" #endif #include <MP.h> #include <Servo.h> #include <Wire.h> #include <Adafruit_MMA8451.h> #include <Adafruit_Sensor.h> // サーボの停止位置の値 // 個体差で中心値を微調整 #define SERVO1_CENTRE_VALUE 93 #define SERVO2_CENTRE_VALUE 90 // サーボの最大スピード #define SERVO_MAX_SPEED 40 enum Mode { MODE_STOP, MODE_RIGHT_RUN, MODE_LEFT_RUN, MODE_UP_RUN, MODE_DOWN_RUN }; Servo servo1; Servo servo2; Adafruit_MMA8451 mma = Adafruit_MMA8451(); Mode currentMode = MODE_STOP; void setServo(int16_t right,int16_t left) { int16_t servo1_speed = right; int16_t servo2_speed = left; if (servo1_speed > SERVO_MAX_SPEED) servo1_speed = SERVO_MAX_SPEED; if (servo2_speed > SERVO_MAX_SPEED) servo2_speed = SERVO_MAX_SPEED; servo1.write(SERVO1_CENTRE_VALUE+servo1_speed); servo2.write(SERVO2_CENTRE_VALUE-servo2_speed); } void setup() { MP.begin(); Serial.begin(115200); servo1.attach(6); servo2.attach(5); mma.begin(); mma.setRange(MMA8451_RANGE_2_G); } void loop() { int ret; int *buff; ret = MP.Recv(&msgid, &buff); if (ret >= 0) { currentMode = buff; } mma.read(); if (currentMode != MODE_STOP && mma.z > -3000 && mma.z < 3000){ int16_t position = 0; if (currentMode == MODE_RIGHT_RUN || currentMode == MODE_LEFT_RUN) position = mma.y; if (currentMode == MODE_UP_RUN || currentMode == MODE_DOWN_RUN) position = mma.x; int target = 0; // 目標値(Yを0に合わせる) float Kp = 0.05; // 比例ゲイン(調整可能) int controlSignal = Kp * (position - target); // 制御信号を計算 // 制御信号をm1とm2に反映 int16_t m1 = constrain(-controlSignal, -SERVO_MAX_SPEED, SERVO_MAX_SPEED); // m1の範囲を制限 if (m1 > 7 || m1 < -7) { int16_t m2 = -m1; // m2はm1と逆向きに設定 if (currentMode == MODE_RIGHT_RUN || currentMode == MODE_UP_RUN) setServo(m1,m2); if (currentMode == MODE_LEFT_RUN || currentMode == MODE_DOWN_RUN) setServo(m2,m1); } else { setServo(-SERVO_MAX_SPEED,-SERVO_MAX_SPEED); } } else { setServo(0,0); } Serial.println(); delay(100); }

最後に

電源にモバイルバッテリーを利用したため、重量が重く、自重による負荷でなかなか水平に直進できませんでした。
そのため、姿勢制御が過剰に働いてしまうことが多かったので、バッテリーを見直した方が良いかもしれません。
あと、サーボモーターも必要以上にパワーがあるものだったので、これも変更すれば軽量化できそうです。

画像認識も誤認が多いので、もうちょっと作りこんでいきたいと思っています。

1
jksoftのアイコン画像
Raspberry Pi Picoの電子書籍のシリーズを書いてます。 本職ではIoT向けアプリ開発実行基盤のenebularや地図アプリのelcompathの開発に携わっています。 最近はフロントエンドアプリやローコードツールのNode-REDを仕事でいじってます。
  • jksoft さんが 2025/01/31 に 編集 をしました。 (メッセージ: 初版)
ログインしてコメントを投稿する