この作品は、「建築現場での測量時の位置を共有」ことを目的とした「緯度経度と方角を共有するGPS内蔵マーカーデバイス」です。
マーカーデバイスの情報をスマホなどのデバイスから読み取ることで、位置取りをするのに役立てます。
位置とコンパスの向きの情報は、QRコードで表示することで、端末間を通信レスで取得することを特徴です。
インターネットを利用せず、安定して読み出すことが期待できます。
2024年 SONY Spresenseコンテストで提供いただいたモニター品を活用し制作しています。
※コンテストに辺り、応募記事を該当システムに投稿し、コンテスト応募サイトから該当情報を記載し応募するのですが、
応募後、期間中でも、記事を編集すると、応募がキャンセル扱いになるとのことで、
再応募が必要になり、それが漏れていたようで、コンテスト対象外となりました。
供養記事としての公開となります。
目的・方針
・対象者が、同一の緯度経度と方角を取得する手段があること
・上記のデータの取得に、インターネットを使わないこと
・取得したデータの解釈は、読み取り機器側に任せること
動画
動画内の読み取り機器は、スマホを使用しています。
スマホのアプリは、Unityで開発し、事前にスマホ内に、緯度経度に対応する3Dモデルを用意しています。
QRコードの読み取りで取得した移動経度の値と合致するデータを表示しています。
今回の趣旨はデバイス側になるため、アプリの詳細説明は割愛させていただきます。
以後は、機器側について、解説します。
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モデルをダウンロードさせる仕組みを追加するなどが
考えられる
投稿者の人気記事
-
tktk360
さんが
2025/01/18
に
編集
をしました。
(メッセージ: 初版)
-
tktk360
さんが
2025/01/18
に
編集
をしました。
-
tktk360
さんが
2025/01/18
に
編集
をしました。
-
tktk360
さんが
2025/01/18
に
編集
をしました。
-
tktk360
さんが
2025/01/18
に
編集
をしました。
-
tktk360
さんが
2025/02/28
に
編集
をしました。
ログインしてコメントを投稿する