shimodashのアイコン画像
shimodash 2022年09月17日作成 (2022年09月19日更新)
製作品 製作品 閲覧数 430
shimodash 2022年09月17日作成 (2022年09月19日更新) 製作品 製作品 閲覧数 430

SPRESENSEをつかった、ゴミ拾いトングの機能拡張ースプレットングー

SPRESENSEをつかった、ゴミ拾いトングの機能拡張ースプレットングー

はじめに

私の地域では、月に一度市長を中心にクリーン作戦を朝に開催しています。
朝起きることが出来た場合は参加させて頂いています。
クリーン作戦は、駅前の集合場所から出発し、ボランティアメンバーが
まちのゴミを拾いながら、ゴミの収集所まで、様々なルートを通りながら
向かいます。
せっかく、実施しているゴミ拾いなので、まちのどの場所がゴミがあったのか
(溜まりやすい)のか、わかる方法があればと思い考えたのがこちらです。
SPRESENSEのGPS機能とELTRESの通信機能を使って、メンバーの場所を
送信し、かつトングにつけた測距センサをもとに、ゴミの数(正確にはトングの把持
回数)をモニタできるものを作ってみました。

なお、本情報を参考に製作した際のトラブル等については責任を負えないので
ご了承下さい。

部品

購入先が古く、不明なものもありますが以下が購入部品になります。

No 部品名 購入先orURL
1 SPRESENSEメインボード 貸出品
2 SPRESENSE用ELTRESアドオンボード 貸出品
3 uFL接続 15mm GPS用アンテナ https://www.switch-science.com/catalog/2641/
4 SPRESENSE用ELTRESアドオンボード対応 LPWAアンテナ https://www.switch-science.com/catalog/7908/
5 VL530X 測距センサ Aliexpress
6 ゴミ拾いトング セリア
7 Cube Pocket4.5(マグネットで付きプラケース) ダイソー
8 シール付きファスナーテープ cando
9 単4 3本 4.5V電池ボックス -

システム図/設計図

システム構成図はとても単純ではありますが、以下になります。
システム構成図

ELTRESデータのデータ送信はクレスコ様の提供している。
CLIP Viewer Lite を使わせて頂きました。
貸出品と合わせて、ユーザー登録しログインをすると
使い方のドキュメントやサンプルコードがダウンロードでき
簡単データをつかう事が可能かと思います。
なお、後述のプログラムはほぼ、クレスコ様の測距センサのサンプルコードそのままです。

SPRESENSE+ELTRESよりメンバーの位置と、トングの把持回数をクラウドに送信します。
なお、トングの把持回数は、ELTRESの下記のGPSペイロードフォーマットの15byte目拡張領域
を使用しました。
GPSペイロードデータ抜粋

SPRESENSEメインボードにELTRESアドオンボードを接続します。
VC530Xセンサは、I2C通信の為、メインボードから見えるピンヘッダを経由して
接続する事が出来ました。
下記はVC530Xセンサとの接続図です。
キャプションを入力できます

また、分解の写真をこちらに記載します。
上からの写真

電池交換の為マジックテープで固定

電池ケース、メインボード、センサの収納

上部アンテナの蓋取り外した状態

ソースコード

基本クレスコ様のVL53L0X搭載Time-of-Flight距離センサの
サンプルコードと同じです。

#include <EltresAddonBoard.h>

// VL53L0X用のライブラリのインクルード
#include <VL53L0X.h>


// PIN定義:LED(プログラム状態)
#define LED_RUN PIN_LED0
// PIN定義:LED(GNSS電波状態)
#define LED_GNSS PIN_LED1
// PIN定義:LED(ELTRES状態)
#define LED_SND PIN_LED2
// PIN定義:LED(エラー状態)
#define LED_ERR PIN_LED3

// プログラム内部状態:初期状態
#define PROGRAM_STS_INIT      (0)
// プログラム内部状態:起動中
#define PROGRAM_STS_RUNNING   (1)
// プログラム内部状態:終了
#define PROGRAM_STS_STOPPED   (3)

// プログラム内部状態
int program_sts = PROGRAM_STS_INIT;
// GNSS電波受信タイムアウト(GNSS受信エラー)発生フラグ
bool gnss_recevie_timeout = false;
// 点滅処理で最後に変更した時間
uint64_t last_change_blink_time = 0;
// イベント通知での送信直前通知(5秒前)受信フラグ
bool event_send_ready = false;
// ペイロードデータ格納場所
uint8_t payload[16];
// 最新のGGA情報
eltres_board_gga_info last_gga_info;

// VL53L0X制御用インスタンス
static VL53L0X distance_sensor;
// 最新値(距離)
uint16_t last_distance;

//掴んでいるときの期間のパラメーター値
#define CATCH_DURATION   (10)

bool      gps = true;
bool      hand_catch = false;
uint8_t   close_dist_cnt=0;
uint8_t  catch_cnt=0;


/**
 * @brief イベント通知受信コールバック
 * @param event イベント種別
 */
void eltres_event_cb(eltres_board_event event) {
  switch (event) {
  case ELTRES_BOARD_EVT_GNSS_TMOUT:
    // GNSS電波受信タイムアウト
    Serial.println("gnss wait timeout error.");
    gnss_recevie_timeout = true;
    break;
  case ELTRES_BOARD_EVT_IDLE:
    // アイドル状態
    Serial.println("waiting sending timings.");
    digitalWrite(LED_SND, LOW);
    break;
  case ELTRES_BOARD_EVT_SEND_READY:
    // 送信直前通知(5秒前)
    Serial.println("Shortly before sending, so setup payload if need.");
    event_send_ready = true;
    break;
  case ELTRES_BOARD_EVT_SENDING:
    // 送信開始
    Serial.println("start sending.");
    digitalWrite(LED_SND, HIGH);
    break;
  case ELTRES_BOARD_EVT_GNSS_UNRECEIVE:
    // GNSS電波未受信
    Serial.println("gnss wave has not been received.");
    digitalWrite(LED_GNSS, LOW);
    break;
  case ELTRES_BOARD_EVT_GNSS_RECEIVE:
    // GNSS電波受信
    Serial.println("gnss wave has been received.");
    digitalWrite(LED_GNSS, HIGH);
    gnss_recevie_timeout = false;
    break;
  case ELTRES_BOARD_EVT_FAULT:
    // 内部エラー発生
    Serial.println("internal error.");
    break;
  }
}


/**
 * @brief GGA情報受信コールバック
 * @param gga_info GGA情報のポインタ
 */
void gga_event_cb(const eltres_board_gga_info *gga_info) {
  Serial.print("[gga]");
  last_gga_info = *gga_info;
  if (gga_info->m_pos_status) {
    // 測位状態
    // GGA情報をシリアルモニタへ出力
    Serial.print("utc: ");
    Serial.println((const char *)gga_info->m_utc);
    Serial.print("lat: ");
    Serial.print((const char *)gga_info->m_n_s);
    Serial.print((const char *)gga_info->m_lat);
    Serial.print(", lon: ");
    Serial.print((const char *)gga_info->m_e_w);
    Serial.println((const char *)gga_info->m_lon);
    Serial.print("pos_status: ");
    Serial.print(gga_info->m_pos_status);
    Serial.print(", sat_used: ");
    Serial.println(gga_info->m_sat_used);
    Serial.print("hdop: ");
    Serial.print(gga_info->m_hdop);
    Serial.print(", height: ");
    Serial.print(gga_info->m_height);
    Serial.print(" m, geoid: ");
    Serial.print(gga_info->m_geoid);
    Serial.println(" m");
  } else {
    // 非測位状態
    // "invalid data"をシリアルモニタへ出力
    Serial.println("invalid data.");
  }
}

/**
 * @brief setup()関数
 */
void setup() {
  // シリアルモニタ出力設定
  Serial.begin(115200);
  
  // LED初期設定
  pinMode(LED_RUN, OUTPUT);
  digitalWrite(LED_RUN, HIGH);
  pinMode(LED_GNSS, OUTPUT);
  digitalWrite(LED_GNSS, LOW);
  pinMode(LED_SND, OUTPUT);
  digitalWrite(LED_SND, LOW);
  pinMode(LED_ERR, OUTPUT);
  digitalWrite(LED_ERR, LOW);

  // ELTRES起動処理
//  eltres_board_result ret = EltresAddonBoard.begin(ELTRES_BOARD_SEND_MODE_1MIN,eltres_event_cb, NULL);
  eltres_board_result ret = EltresAddonBoard.begin(ELTRES_BOARD_SEND_MODE_1MIN,eltres_event_cb, gga_event_cb);
  if (ret != ELTRES_BOARD_RESULT_OK) {
    // ELTRESエラー発生
    digitalWrite(LED_RUN, LOW);
    digitalWrite(LED_ERR, HIGH);
    program_sts = PROGRAM_STS_STOPPED;
    Serial.print("cannot start eltres board (");
    Serial.print(ret);
    Serial.println(").");
    return;
  }

  // センサ初期設定
  Wire.begin();
  distance_sensor.setTimeout(500);
  if (distance_sensor.init() == false) {
    // センサ初期設定エラー
    EltresAddonBoard.end();
    digitalWrite(LED_RUN, LOW);
    digitalWrite(LED_ERR, HIGH);
    program_sts = PROGRAM_STS_STOPPED;
    Serial.println("cannnot start a distance sensor.");
    return;
  }
  // 測定開始
  distance_sensor.startContinuous();
  // 正常
  program_sts = PROGRAM_STS_RUNNING;
}

/**
 * @brief loop()関数
 */
void loop() {

  switch (program_sts) {
    case PROGRAM_STS_RUNNING:
      // プログラム内部状態:起動中
      if (gnss_recevie_timeout) {
        // GNSS電波受信タイムアウト(GNSS受信エラー)時の点滅処理
        uint64_t now_time = millis();
        if ((now_time - last_change_blink_time) >= 1000) {
          last_change_blink_time = now_time;
          bool set_value = digitalRead(LED_ERR);
          bool next_value = (set_value == LOW) ? HIGH : LOW;
          digitalWrite(LED_ERR, next_value);
        }
      } else {
        digitalWrite(LED_ERR, LOW);
      }

      if (event_send_ready) {
        // 送信直前通知時の処理
        event_send_ready = false;

        setup_payload_gps();
        
        // 送信ペイロードの設定
        EltresAddonBoard.set_payload(payload);
      }

      // 距離センサから測定値を取得し、最新値を更新
      measure_distance();
      break;
     
    case PROGRAM_STS_STOPPED:
      // プログラム内部状態:終了
      break;
  }

  // 次のループ処理まで100ミリ秒待機
  delay(100);
}

/**
 * @brief 気圧圧力照度距離ペイロード設定(距離のみ利用)
 * @param distance 距離
 */
void setup_payload_distance(int16_t distance) {

  // 設定情報をシリアルモニタへ出力
  Serial.print("[setup_payload_distance]");
  Serial.print("distance:");
  Serial.print(distance);
  Serial.println();

  // ペイロード領域初期化
  memset(payload, 0x00, sizeof(payload));
  // ペイロード種別[気圧圧力照度距離ペイロード]設定
  payload[0] = 0x85;
  // 距離設定
  payload[13] = (uint8_t)((distance >> 8) & 0xff);
  payload[14] = (uint8_t)(distance & 0xff);
}

/**
 * @brief 距離センサから対象物との距離を取得し、最新値を更新
 */
void measure_distance() {
  uint16_t distance;
  // 距離センサから測定値を取得
  distance = distance_sensor.readRangeContinuousMillimeters();
  // 最新値の更新
  last_distance = distance;
  //距離が40mm未満だったらトングが閉まっていると判断
  if (distance < 40 ){
  // 最新値をシリアルモニタへ出力
    Serial.print("[measure]distance: ");
   Serial.print(last_distance);
   Serial.println(" mm");
   close_dist_cnt++;
    // CATCH_DURATIONより長い期間トングが閉まっていたら掴んでいると判断
   if (close_dist_cnt > CATCH_DURATION){
    hand_catch = true;
   }else{
    hand_catch = false;
   }
   
  }else{
   Serial.print("[measure]distance over: ");
   Serial.print(last_distance);
   Serial.println(" mm");
   //トングが開いて、掴んでいると判断されていたら掴んだ回数をインクリメント
   if (hand_catch == true){
    catch_cnt++;
    }
   hand_catch = false;
   close_dist_cnt=0;
  }

  Serial.print("[measure]catch_cnt: ");
  Serial.print(catch_cnt);
  Serial.println(" times");
    

}

/**
 * @brief GPSペイロード設定
 */
void setup_payload_gps() {
  String lat_string = String((char*)last_gga_info.m_lat);
  String lon_string = String((char*)last_gga_info.m_lon);
  int index;
  uint32_t gnss_time;
  uint32_t utc_time;

  // GNSS時刻(epoch秒)の取得
  EltresAddonBoard.get_gnss_time(&gnss_time);
  // UTC時刻を計算(閏秒補正)
  utc_time = gnss_time - 18;

  // 設定情報をシリアルモニタへ出力
  Serial.print("[setup_payload_gps]");
  Serial.print("lat:");
  Serial.print(lat_string);
  Serial.print(",lon:");
  Serial.print(lon_string);
  Serial.print(",utc:");
  Serial.print(utc_time);
  Serial.print(",pos:");
  Serial.print(last_gga_info.m_pos_status);
  Serial.println();

  // ペイロード領域初期化
  memset(payload, 0x00, sizeof(payload));
  // ペイロード種別[GPSペイロード]設定
  payload[0] = 0x81;
  // 緯度設定
  index = 0;
  payload[1] = (uint8_t)(((lat_string.substring(index,index+1).toInt() << 4)
                + lat_string.substring(index+1,index+2).toInt()) & 0xff);
  index += 2;
  payload[2] = (uint8_t)(((lat_string.substring(index,index+1).toInt() << 4)
                + lat_string.substring(index+1,index+2).toInt()) & 0xff);
  index += 2;
  index += 1;   // skip "."
  payload[3] = (uint8_t)(((lat_string.substring(index,index+1).toInt() << 4)
                + lat_string.substring(index+1,index+2).toInt()) & 0xff);
  index += 2;
  payload[4] = (uint8_t)(((lat_string.substring(index,index+1).toInt() << 4)
                + lat_string.substring(index+1,index+2).toInt()) & 0xff);
  // 経度設定
  index = 0;
  payload[5] = (uint8_t)(lon_string.substring(index,index+1).toInt() & 0xff);
  index += 1;
  payload[6] = (uint8_t)(((lon_string.substring(index,index+1).toInt() << 4)
                + lon_string.substring(index+1,index+2).toInt()) & 0xff);
  index += 2;
  payload[7] = (uint8_t)(((lon_string.substring(index,index+1).toInt() << 4)
                + lon_string.substring(index+1,index+2).toInt()) & 0xff);
  index += 2;
  index += 1;   // skip "."
  payload[8] = (uint8_t)(((lon_string.substring(index,index+1).toInt() << 4)
                + lon_string.substring(index+1,index+2).toInt()) & 0xff);
  index += 2;
  payload[9] = (uint8_t)(((lon_string.substring(index,index+1).toInt() << 4)
                + lon_string.substring(index+1,index+2).toInt()) & 0xff);
  // 時刻(EPOCH秒)設定
  payload[10] = (uint8_t)((utc_time >> 24) & 0xff);
  payload[11] = (uint8_t)((utc_time >> 16) & 0xff);
  payload[12] = (uint8_t)((utc_time >> 8) & 0xff);
  payload[13] = (uint8_t)(utc_time & 0xff);
  // 拡張用領域(0固定)設定に掴んだカウントを追加
//  payload[14] = 0x00;
    payload[14] = catch_cnt;
  // 品質設定
  payload[15] = last_gga_info.m_pos_status;
}

製作時の課題

製作した際の検討や課題点について記載します。

  • ケース作成
     私自身は、3Dプリンタでのケース作成のスキルがないので、ケース選びは基本100円ショップもしくは
     インターネットで購入出来るものにしました。
     今回は、運良くダイソーで丁度いいサイズのものが見つかりましたので、それを流用する事としました。
     もう少し、薄く、小さく作成したものをトングに装着できたら良かったと思っています。

  • 電源
     ケースの作成とも関連しますが、当初モバイルバッテリーを使おうと思っていましたが、モバイルバッテリーの
     自動電源offの対策が必要であること、小さいサイズのものが見つからなかったので、乾電池からの給電としました。
     小さくするために、ボタン電池で動かしたかった所はありあますが、ソフトウェアを検討してボタン電池で動く
     様にチャレンジしたいです。

さいごに

製作をするにあたり、まずは機材を貸出頂いた企業の皆様ありがとうございました。
新しいデバイスを使った製作にチャレンジすることができました。
この場を借りて御礼申し上げます。
また、完成までものづくりの仲間から、沢山のアドバイスを頂き助けられました。

SPRESENSEは低消費電力だと思うので、その能力を引き出せる物ができたら
とても良いと思います。
私も、低消費電力化や3Dプリンタでの造形など、引き続きチャレンジできたらと思います。

shimodashのアイコン画像
会社員をしています。 Myms-techという活動名で、子供向けの電子工作ワークショップを しています。 URLは https://shimodash.github.io/Myms-tech facebookは https://www.facebook.com/mymstech twitterは https://twitter.com/shimodash_home もしよかったらSNSフォローを ※発言は個人的なものです。
  • shimodash さんが 2022/09/17 に 編集 をしました。 (メッセージ: 初版)
  • shimodash さんが 2022/09/17 に 編集 をしました。 (メッセージ: さいごにの章をついか)
  • shimodash さんが 2022/09/19 に 編集 をしました。 (メッセージ: 分解の写真と説明文追加)
ログインしてコメントを投稿する