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

tktk360 が 2025年01月18日12時22分20秒 に編集

初版

タイトルの変更

+

GPS内蔵マーカーデバイス

タグの変更

+

SPRESENSE

+

GPS

+

QRコード

メイン画像の変更

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

記事種類の変更

+

製作品

ライセンスの変更

+

(MIT) The MIT License

本文の変更

+

この作品は、「建築現場での測量時の位置を共有」ことを目的とした「緯度経度と方角を共有するGPS内蔵マーカーデバイス」です。 マーカーデバイスの情報をスマホなどのデバイスから読み取ることで、位置取りをするのに役立てます。 位置とコンパスの向きの情報は、QRコードで表示することで、端末間を通信レスで取得することを特徴です。 インターネットを利用せず、安定して読み出すことが期待できます。 2024年 SONY Spresenseコンテストで提供いただいたモニター品を活用し制作しています。 ![利用イメージ図](https://camo.elchika.com/2911637cd69d200f3865f9eedcd9fd5c764a6c29/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f62386637346233302d313666652d346464342d623336312d6463653831333164303134392f37333765303363622d636333322d346434612d616237342d376365343261643864346238/) # 目的・方針 ・対象者が、同一の緯度経度と方角を取得する手段があること ・上記のデータの取得に、インターネットを使わないこと ・取得したデータの解釈は、読み取り機器側に任せること # 動画 @[youtube](https://youtube.com/shorts/0ArtVUwTMKc) 動画内の読み取り機器は、スマホを使用しています。 スマホのアプリは、Unityで開発し、事前にスマホ内に、緯度経度に対応する3Dモデルを用意しています。 QRコードの読み取りで取得した移動経度の値と合致するデータを表示しています。 今回の趣旨はデバイス側になるため、アプリの詳細説明は割愛させていただきます。 以後は、機器側について、解説します。 ![GPS内臓マーカーデバイス](https://camo.elchika.com/3d4b007cec35426833c3ed5d9069d6ca1a90ed2d/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f62386637346233302d313666652d346464342d623336312d6463653831333164303134392f36313830333031392d313538632d343565392d623530642d396363393234393432353236/) # 部品 作成に使用したパーツは下記となります。 | NO | 品目 | 価格 | |:---:|:---:|:---| | 1 | [SONY SPRESENSE メインボード](https://akizukidenshi.com/catalog/g/gM-14584/) | 6,050 | | 2 | [SONY SPRESENSE 拡張ボード](https://akizukidenshi.com/catalog/g/gM-14585/) | 3,850 | | 3 | [ILI9341搭載2.8インチSPI制御タッチパネル付TFT液晶 MSP2807](https://akizukidenshi.com/catalog/g/gM-16265/) | 1,450 | | 4 | [BMM150: SEN0419](https://www.marutsu.co.jp/pc/i/41700318/?srsltid=AfmBOopPp6CWbF6WKb-UgGIFmFXDvEXfvV67rvmLkYvtjmW7Hl5DwpHx) | 1,497 | | 5 | [100円ショップケースなど] | 330 | | | 合計| 13,177 | # 設計図 部品を元に、下記配線を行います。 ![](https://camo.elchika.com/3f1fbb4488d7d7a6483244cf3b7c229ce40d6b04/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f62386637346233302d313666652d346464342d623336312d6463653831333164303134392f30326563303164632d663235372d346464662d386432362d623866376562653364303164/) ==使用ライブラリ== Arduino Library Manager ・ボードマネージャ-Spresense Commuity 3.3.1 Include Library -[Adafruit_ILI9341-spresense](https://github.com/kzhioki/Adafruit_ILI9341) -[Adafruit-GFX-Library-spresense](https://github.com/kzhioki/Adafruit-GFX-Library) -[Grove_3Axis_Compass_V2.0_BMM150](https://github.com/Seeed-Studio/Grove_3_Axis_Compass_V2.0_BMM150) ==プログラム== プログラム全体を下記にのせています。 [Github: SpresenseFamilyMonitor](https://github.com/TKTK360/SpresenseFamilyMonitor) からソースコードを取得してください。 以降は、中心となるコードについての抜粋となります。 ```arduino:送信機(Spresense_MarkerDevice.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モデルをダウンロードさせる仕組みを追加するなどが 考えられる