編集履歴一覧に戻る
jksoftのアイコン画像

jksoft が 2025年01月31日17時03分31秒 に編集

初版

タイトルの変更

+

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

タグの変更

+

SPRESENSE

+

ロボット

メイン画像の変更

メイン画像が設定されました

記事種類の変更

+

製作品

ライセンスの変更

+

(Apache-2.0) Apache License 2.0

本文の変更

+

## 概要 私は、今まで壁を走るロボット「うおーるぼっと」シリーズを開発してきました。 うおーるぼっとは、ホワイトボードに貼り付けてリモコンやスマートフォンで動かしたり、ペンで描いたコースに触れないように動かしたりする楽しむためのロボットです。 うおーるぼっとはホワイトボードを走れるので、折角ならばホワイトボードに書いたものを自動的に消せるようにしたいと思いました。 自動的に消せるホワイトボードはあるものの、専用のものだったり、故障が多かったりと一般的にはなかなか見かけません。 うおーるぼっとであれば、手軽にホワイトボードに貼り付けて使えるので、一般的なホワイトボードでも後づけで利用できます。 今回は、Spresenseを使用し、音声認識や画像認識を使いつつ試作を作ってみました。 ## 構成 構成は以下のとおりです。 ![キャプションを入力できます](https://camo.elchika.com/44f3f8a0cea95aad4556027d577a5afe7f7b907d/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f65306661366265332d633431372d346430352d613764372d3532633938646565633766662f36363865366363342d323536332d343330622d626266662d613066363837383562393031/) センサーとして、Spresense用のHDRカメラボード、コンデンサマイク、三軸の加速度センサーを使用してます。 HDRカメラは、ホワイトボード上に書かれたものやホワイトボードの端を検出するのに使用しています。 コンデンサマイクは、音声のキーワードで動作開始ができるように搭載しました。 三軸の加速度センサーは向いている方向がわかるように搭載しています。垂直に張り付いているので、重力加速度を検出するだけで方向がわかります。 ![キャプションを入力できます](https://camo.elchika.com/cc1e79d4497b881612eeaf9964cb233c9219dc5a/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f65306661366265332d633431372d346430352d613764372d3532633938646565633766662f65623564616438322d353738642d343364372d396634662d313865316439636134373965/) ![キャプションを入力できます](https://camo.elchika.com/eb8c280fe1642f39292ae9d3d254909740e0c340/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f65306661366265332d633431372d346430352d613764372d3532633938646565633766662f62613332663766652d323632322d343162642d393431342d343830643862613734656264/) ## 部品 | 品名 | 個数 | 購入先 | |:---:|:---|:---| | 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ベースで信号線とともに接続しています。 次の動画は、姿勢制御の確認です。ホワイトボードを回転させても常に右方向を向き続けます。 @[youtube](https://www.youtube.com/watch?v=rl3agEoOyI4) ### 画像認識 取り付けられたカメラにより、ホワイトボードの端や書かれたものを認識して制御します。 ![キャプションを入力できます](https://camo.elchika.com/fdd6be9083f13fc2abc0f90f078aa459e309b5ff/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f65306661366265332d633431372d346430352d613764372d3532633938646565633766662f65383739653031372d346366392d343535382d396365312d383831343063376263343133/) ### キーワード音声認識 「開始」と「終了」という音声のキーワードで動作を開始・停止できるようにしました。 キーワードの音声と環境音を検出して、キーワードのパターンを区別しています。 ![キャプションを入力できます](https://camo.elchika.com/4d63f7d049887c22a2a1d574ae20a99610047f56/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f65306661366265332d633431372d346430352d613764372d3532633938646565633766662f31643032373964382d313333632d343364662d386466362d323764346635353837356535/) ## 動作 組み合わせた動作の様子です @[youtube](https://www.youtube.com/watch?v=0cFZw_wb34s) ## ソースコード ### 画像認識と音声認識 メインコアのコード Main.ino ```C++ #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 ```C++ #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); } ``` ## 最後に 電源にモバイルバッテリーを利用したため、重量が重く、自重による負荷でなかなか水平に直進できませんでした。 そのため、姿勢制御が過剰に働いてしまうことが多かったので、バッテリーを見直した方が良いかもしれません。 あと、サーボモーターも必要以上にパワーがあるものだったので、これも変更すれば軽量化できそうです。 画像認識も誤認が多いので、もうちょっと作りこんでいきたいと思っています。