tktk360のアイコン画像
tktk360 2025年01月18日作成 (2025年02月28日更新) © MIT
製作品 製作品 閲覧数 129
tktk360 2025年01月18日作成 (2025年02月28日更新) © MIT 製作品 製作品 閲覧数 129

GPS内蔵マーカーデバイス

GPS内蔵マーカーデバイス

この作品は、「建築現場での測量時の位置を共有」ことを目的とした「緯度経度と方角を共有するGPS内蔵マーカーデバイス」です。
マーカーデバイスの情報をスマホなどのデバイスから読み取ることで、位置取りをするのに役立てます。
位置とコンパスの向きの情報は、QRコードで表示することで、端末間を通信レスで取得することを特徴です。
インターネットを利用せず、安定して読み出すことが期待できます。
2024年 SONY Spresenseコンテストで提供いただいたモニター品を活用し制作しています。

※コンテストに辺り、応募記事を該当システムに投稿し、コンテスト応募サイトから該当情報を記載し応募するのですが、
 応募後、期間中でも、記事を編集すると、応募がキャンセル扱いになるとのことで、
 再応募が必要になり、それが漏れていたようで、コンテスト対象外となりました。
 供養記事としての公開となります。

目的・方針

・対象者が、同一の緯度経度と方角を取得する手段があること
・上記のデータの取得に、インターネットを使わないこと
・取得したデータの解釈は、読み取り機器側に任せること

動画

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

動画内の読み取り機器は、スマホを使用しています。
スマホのアプリは、Unityで開発し、事前にスマホ内に、緯度経度に対応する3Dモデルを用意しています。
QRコードの読み取りで取得した移動経度の値と合致するデータを表示しています。
今回の趣旨はデバイス側になるため、アプリの詳細説明は割愛させていただきます。
以後は、機器側について、解説します。

GPS内臓マーカ―デバイス

GPS内臓マーカーデバイス

部品

作成に使用したパーツは下記となります。

NO 品目 価格
1 SONY SPRESENSE メインボード 6,050
2 SONY SPRESENSE 拡張ボード 3,850
3 ILI9341搭載2.8インチSPI制御タッチパネル付TFT液晶 MSP2807 1,450
4 BMM150: SEN0419 1,497
5 [100円ショップケースなど] 330
合計 13,177

設計図

部品を元に、下記配線を行います。

使用ライブラリ
Arduino Library Manager
・ボードマネージャ-Spresense Commuity 3.3.1

Include Library
-Adafruit_ILI9341-spresense
-Adafruit-GFX-Library-spresense
-Grove_3Axis_Compass_V2.0_BMM150

プログラム
プログラム全体を下記にのせています。
Github: SpresenseMarkerDevice からソースコードを取得してください。

以降は、中心となるコードについての抜粋となります。

送信機(SpresenseMarkerDevice.ino)

#include <Arduino.h> #include <vector> #include <pthread.h> #include "SPI.h" // GNSS #include <GNSS.h> #include <GNSSPositionData.h> #include "gnss_logger.h" #include "gnss_nmea.h" // TFT #include "Adafruit_GFX.h" #include "Adafruit_ILI9341.h" // QR #include "qrcode.h" // BMM150 #include "bmm150.h" #include "bmm150_defs.h" // TFT #define TFT_DC 9 #define TFT_CS 10 #define TFT_RST 8 Adafruit_ILI9341 tft = Adafruit_ILI9341(&SPI, TFT_DC, TFT_CS, TFT_RST); // QR #define X_BASE_PIXLE 60 #define Y_BASE_PIXLE 90 #define SCALE_SIZE 4 // x3 // BMM150 BMM150 bmm = BMM150(); bmm150_mag_data value_offset; float g_headingDegrees; // GPS #define POSITIONING_INTERVAL 10 // positioning interval in seconds SpGnss Gnss; // SpGnss objectString g_time; String g_time; String g_lat; String g_lot; // Button #define buttonPin 7 bool g_isFirst = true; bool g_isBtn = false; int g_waitGns = 0; int g_cntCompass = 0; int g_cntGns = 0; //------------------------------------------------------------- // Turn on / off the LED1 for positioning state notification. // [in] state Positioning state //------------------------------------------------------------- static void Led_isPosfix(bool state) { if (state == 1){ ledOn(PIN_LED0); } else { ledOff(PIN_LED0); } } //------------------------------------------------------------- //StrSplit //------------------------------------------------------------- int StrSplit(String data, char delimiter, String *dst) { int index = 0; int datalength = data.length(); for (int i = 0; i < datalength; i++) { char tmp = data.charAt(i); if ( tmp == delimiter ) { index++; } else { dst[index] += tmp; } } return (index + 1); } //------------------------------------------------------------- //ConvertGps //------------------------------------------------------------- float ConvertGps(String data) { float f = atof(data.c_str()); float deg = (int)f / 100; float min = f - deg * 100; return deg + min / 60; } void floatValuesToCommaString(float* values, char* resultString) { String floatStrings[3]; for (int i = 0; i < 3; ++i) { if (i==2) { floatStrings[i] = String((int)values[i]); } else { floatStrings[i] = String(values[i], 5); // Adjust precision as needed } } sprintf(resultString, "%s,%s,%s\0", floatStrings[0].c_str(), floatStrings[1].c_str(), floatStrings[2].c_str()); } //------------------------------------------------------------- // DrawQR // [in] draw qr //------------------------------------------------------------- void DrawQR(String data) { Serial.print("QR="); Serial.println(data); // Create the QR code QRCode qrcode; uint8_t qrcodeData[qrcode_getBufferSize(3)]; qrcode_initText(&qrcode, qrcodeData, 3, 0, data.c_str()); //tft.fillScreen(ILI9341_WHITE); tft.fillRect(0, 0, tft.width(), tft.height() - 240, ILI9341_WHITE); for (unsigned int y = 0; y < qrcode.size; y++) { for (unsigned int x = 0; x < qrcode.size; x++) { if (qrcode_getModule(&qrcode, x, y)) { tft.writeFillRect( X_BASE_PIXLE + (x * SCALE_SIZE), Y_BASE_PIXLE + (y * SCALE_SIZE), SCALE_SIZE, SCALE_SIZE, ILI9341_BLACK); } } } } //------------------------------------------------------------- // DrawMessage // [in] draw string //------------------------------------------------------------- void DrawMessage(String txt) { tft.fillRect(0, 240, tft.width(), 240, ILI9341_WHITE); tft.setCursor(5, 240); tft.setTextColor(ILI9341_BLACK); tft.setTextSize(2); tft.println(txt); } //------------------------------------------------------------- // CreateMessage //------------------------------------------------------------- String CreateMessage(String time, String lat, String lot, float conpass) { //float values[4] = { atof(time.c_str()) + 90000, ConvertGps(lat), ConvertGps(lot), conpass }; float values[3] = { ConvertGps(lat), ConvertGps(lot), conpass }; char resultString[50]; // Adjust the size as needed floatValuesToCommaString(values, resultString); //Serial.print("result = "); Serial.println(resultString); return resultString; } //------------------------------------------------------------- // calibrate // [in] time //------------------------------------------------------------- void calibrate(uint32_t timeout) { int16_t value_x_min = 0; int16_t value_x_max = 0; int16_t value_y_min = 0; int16_t value_y_max = 0; int16_t value_z_min = 0; int16_t value_z_max = 0; uint32_t timeStart = 0; bmm.read_mag_data(); value_x_min = bmm.raw_mag_data.raw_datax; value_x_max = bmm.raw_mag_data.raw_datax; value_y_min = bmm.raw_mag_data.raw_datay; value_y_max = bmm.raw_mag_data.raw_datay; value_z_min = bmm.raw_mag_data.raw_dataz; value_z_max = bmm.raw_mag_data.raw_dataz; delay(100); timeStart = millis(); int cnt = 0; String txt = ""; while((millis() - timeStart) < timeout) { bmm.read_mag_data(); // Update x-Axis max/min value if(value_x_min > bmm.raw_mag_data.raw_datax) { value_x_min = bmm.raw_mag_data.raw_datax; // Serial.print("Update value_x_min: "); // Serial.println(value_x_min); } else if(value_x_max < bmm.raw_mag_data.raw_datax) { value_x_max = bmm.raw_mag_data.raw_datax; // Serial.print("update value_x_max: "); // Serial.println(value_x_max); } // Update y-Axis max/min value if(value_y_min > bmm.raw_mag_data.raw_datay) { value_y_min = bmm.raw_mag_data.raw_datay; // Serial.print("Update value_y_min: "); // Serial.println(value_y_min); } else if(value_y_max < bmm.raw_mag_data.raw_datay) { value_y_max = bmm.raw_mag_data.raw_datay; // Serial.print("update value_y_max: "); // Serial.println(value_y_max); } // Update z-Axis max/min value if(value_z_min > bmm.raw_mag_data.raw_dataz) { value_z_min = bmm.raw_mag_data.raw_dataz; // Serial.print("Update value_z_min: "); // Serial.println(value_z_min); } else if(value_z_max < bmm.raw_mag_data.raw_dataz) { value_z_max = bmm.raw_mag_data.raw_dataz; // Serial.print("update value_z_max: "); // Serial.println(value_z_max); } Serial.print("."); txt += "."; cnt++; if (cnt %2 == 0) { DrawMessage(txt); } delay(100); } value_offset.x = value_x_min + (value_x_max - value_x_min)/2; value_offset.y = value_y_min + (value_y_max - value_y_min)/2; value_offset.z = value_z_min + (value_z_max - value_z_min)/2; } //------------------------------------------------------------- // commpassFunction //------------------------------------------------------------- bool commpassFunction() { bmm150_mag_data value; bmm.read_mag_data(); value.x = bmm.raw_mag_data.raw_datax - value_offset.x; value.y = bmm.raw_mag_data.raw_datay - value_offset.y; value.z = bmm.raw_mag_data.raw_dataz - value_offset.z; float xyHeading = atan2(value.x, value.y); float zxHeading = atan2(value.z, value.x); float heading = xyHeading; if(heading < 0) heading += 2*PI; if(heading > 2*PI) heading -= 2*PI; g_headingDegrees = heading * 180/M_PI; //float xyHeadingDegrees = xyHeading * 180 / M_PI; //float zxHeadingDegrees = zxHeading * 180 / M_PI; return true; } //------------------------------------------------------------- // Setup positioning. // return 0 if success, 1 if failure //------------------------------------------------------------- int gnsSetup() { int error_flag = 0; // Set Gnss debug mode. Gnss.setDebugMode(PrintNone); if (Gnss.begin(Serial) != 0) { Serial.println("Gnss begin error!!"); error_flag = 1; } else { Serial.println("Gnss begin OK."); // GPS + QZSS(L1C/A) + QZAA(L1S) Gnss.select(GPS); Gnss.select(QZ_L1CA); Gnss.select(QZ_L1S); Gnss.setInterval(POSITIONING_INTERVAL); if (Gnss.start(HOT_START) != OK) { Serial.println("Gnss start error!!"); error_flag = 1; } } return error_flag; } //------------------------------------------------------------- // gnsLoop //------------------------------------------------------------- bool gnsLoop() { // static static bool PosFixflag = false; // Check update. if (Gnss.waitUpdate()) { //if (Gnss.waitUpdate(POSITIONING_INTERVAL * 1000)) { // Get NavData. SpNavData NavData; Gnss.getNavData(&NavData); // Position Fixed bool LedSet = ((NavData.posDataExist) && (NavData.posFixMode != 0)); if (PosFixflag != LedSet) { Led_isPosfix(LedSet); PosFixflag = LedSet; } if (PosFixflag) { //unsigned short sat = NavData.posSatelliteType; unsigned short sat = NavData.satelliteType; // GPS, QZ_L1CA --> LED1 if (0 != (sat & (GPS | QZ_L1CA))) { ledOn(PIN_LED1); } else { ledOff(PIN_LED1); } // QZ_L1S --> LED2 if (0 != (sat & QZ_L1S)) { ledOn(PIN_LED2); } else { ledOff(PIN_LED2); } } // Convert Nav-Data to Nmea-String. String NmeaString = getNmeaGga(&NavData); if (strlen(NmeaString.c_str()) == 0) { Serial.println("getNmea error"); } else { Serial.print(NmeaString); // 分割数 = 分割処理(文字列, 区切り文字, 配列) String cmds[25] = {"\0"}; // 分割された文字列を格納する配列 int index = StrSplit(NmeaString, ',', cmds); // 結果表示 //for(int i = 0; i < index; i++){ // Serial.println(cmds[i]); //} if (4 < index) { g_time = cmds[1]; g_lat = cmds[2]; g_lot = cmds[4]; if (strlen(g_lat.c_str()) != 0 && strlen(g_lot.c_str()) != 0) { //Serial.print("lat="); Serial.println(g_lat); //Serial.print("lot="); Serial.println(g_lot); //SendGnssLoraData(g_time, g_lat, g_lot); return true; } } } } return false; } //------------------------------------------------------------- // buttonLoop //------------------------------------------------------------- bool buttonLoop() { int buttonState = digitalRead(buttonPin); // 押下 if (buttonState == LOW) { if (!g_isBtn) { g_isBtn = true; Serial.println("btn push"); } } else { g_isBtn = false; } return g_isBtn; } //------------------------------------------------------------- // DrawData //------------------------------------------------------------- void DrawData(bool isQR, bool isConpass) { String result = CreateMessage(g_time, g_lat, g_lot, g_headingDegrees); if (isConpass) DrawMessage(result); if (isQR) DrawQR(result); } // pthread static void gps0_thread(void *arg) { while(true) { if (gnsLoop()) { g_cntGns++; // 最初だけ表示 if (g_isFirst) { g_isFirst = false; g_cntGns = 100; } } delay(100); } } //------------------------------------------------------------- // setup //------------------------------------------------------------- void setup() { Serial.begin(115200); // Turn on all LED:Setup start. ledOn(PIN_LED0); ledOn(PIN_LED1); ledOn(PIN_LED2); ledOn(PIN_LED3); // button pinMode(buttonPin, INPUT_PULLUP); // tft tft.begin(40000000); uint8_t x = tft.readcommand8(ILI9341_RDMODE); Serial.print("Display Power Mode: 0x"); Serial.println(x, HEX); x = tft.readcommand8(ILI9341_RDMADCTL); Serial.print("MADCTL Mode: 0x"); Serial.println(x, HEX); x = tft.readcommand8(ILI9341_RDPIXFMT); Serial.print("Pixel Format: 0x"); Serial.println(x, HEX); x = tft.readcommand8(ILI9341_RDIMGFMT); Serial.print("Image Format: 0x"); Serial.println(x, HEX); x = tft.readcommand8(ILI9341_RDSELFDIAG); Serial.print("Self Diagnostic: 0x"); Serial.println(x, HEX); // GNS int error_flag = gnsSetup(); // bmm150 if(bmm.initialize() == BMM150_E_ID_NOT_CONFORM) { Serial.println("Chip ID can not read!"); while(1); } else { Serial.println("Initialize done!"); } Serial.println("Start figure-8 calibration after 3 seconds."); tft.fillScreen(ILI9341_WHITE); delay(3000); calibrate(10000); Serial.println("\n\rCalibrate done.."); delay(500); pthread_t gps0; pthread_create(&gps0, NULL, gps0_thread, NULL); // Turn off all LED:Setup done. ledOff(PIN_LED0); ledOff(PIN_LED1); ledOff(PIN_LED2); ledOff(PIN_LED3); } //------------------------------------------------------------- // loop //------------------------------------------------------------- void loop(void) { if (commpassFunction()) { g_cntCompass++; if (g_cntCompass % 2) { Serial.print("Heading: "); Serial.println(g_headingDegrees); } } bool cFlag = false, gFlag = false; if (g_cntCompass >= 10) { g_cntCompass = 0; cFlag = true; } if (g_cntGns >= 10) { g_cntGns = 0; gFlag = true; } if (gFlag || cFlag) { DrawData(gFlag, cFlag); } delay(100); }

最後に

  • 今後の発展として、読み込み端末側で3Dモデルを用意する以外に、
    SpresenseにESP32を追加し、マイコンで、Webサーバー上で3Dモデルをダウンロードさせる仕組みを追加するなどが
    考えられる
ログインしてコメントを投稿する