はじめに
私はコンテストで制作する予定だった作品を完成することができませんでした。モニター提供品を受け取ったにも関わらず、私のタイムスケジュール管理が甘くハードウェアもブレッドボードとなってしまい、大変申し訳ございませんでした。ここからの内容はできていない制作する予定だったものも含みます。そのうえで考察の部分でできなかったところなどについて記述します。
謝辞
ソニーセミコンダクタソリューションズ様この度はモニター提供品を貸し出してくださり、ありがとうございました。私が今まで扱っていない機能に挑戦することができ、とても勉強になりました。また、私が制作した作品にはMSP2807を表示させるためのライブラリをつかわせていただきました。そのライブラリは次の通りです。
・Adafruit_GFX.h
・Adafruit_ILI9341.h
・GNSS.h
・SPI.h
URLを下記に掲載します。
https://github.com/adafruit/Adafruit-GFX-Library
https://github.com/adafruit/Adafruit_ILI9341
このような素晴らしいライブラリを提供していただき、本当にありがとうございました。
私はMSP2807のグラフィック液晶やGPSなどを今まで電子工作で取り扱ったことがなく、自分が思った通りに動かすことに苦戦しました。しかし、提供してくださったライブラリなどを参考に制作してみた結果MSP2807はある程度自分の思い通りに動作させることができ、とても勉強になりました。また、GPSは最後まで機能を完成させることはできませんでしたが、GPSの仕組みや、距離の計算方法などを学ぶ機会をいただき、改めて、ありがとうございました。
背景
私が長距離測定器の制作を始めようと思った理由はマラソンを楽しんでほしいからです。マラソンはどうしても苦手な人は嫌いになりやすくなってしまいます。そこでこの測定器を使うことで少しでもマラソンを楽しみ、苦手意識を克服してほしいという思いからこのようなものを制作しようと思いました。この私が制作しようとしている長距離測定器は自分でコースを設定することができ、さらに測定時間以外にも残りの距離、速度を測定するようにしたいと考えているため、楽しみながらマラソンをすることができると思います。
機能
###状態遷移図
状態遷移図を下記に掲載します。
図1全体的な状態遷移図
コースの設定方法
コースの設定を実現する方法を説明します。この測定器は緯度経度を使って、コースを設定します。設定するタイミングは状態遷移図の経過地点の設定です。図3の黒い丸は緯度経度で表した経過地点です。地図を見ながらボタンにより自分が好きなところに経過地点を設定し、コースを設定します。
コース設定手順
最初の画面です。
ボタンの配置は次のようになっています。左から順番にNext ボタン、設定1ボタン、設定2ボタン、初期化ボタンです。初期化ボタンは最初はつけたかったですが実装できませんでした。
設定1ボタンを押すと次のように画面が変わります。
このように経過地点数が1つづつインクリメントされます。
次にNextボタンを押すと次のような画面に変わります。
先程と同じように設定1ボタンを押すと次のようになります。
また、設定2ボタンを押すとインクリメントする桁が1つ右にずれます。
設定2ボタンを長押しすると緯度経度の設定が切り替わります。
さらに設定2ボタンを長押しすると設定している経過地点数がインクリメントされます。
まだ完成していませんがNextボタンをおすと次のような画面に切り替わります。
これは上から順に測定時間、次までの経過地点までの距離、速度、ゴールまでの距離を表しています。時間のところが動かなかったです。また、動作確認もできていません。また、写真だけではわかりずらいと思うので動画を載せておきます。ただし、現在カメラがなく、動画を投稿できないので、過去のものを載せます。プログラムは変わっています。失敗している動画なのでわかりずらかったらすみません。
https://youtu.be/9kHL8mm5rL0
経過地点の判定方法
経過地点を判定する際に現在いる緯度経度と設定した緯度経度が同じとき経過地点を通過したことにしてしまうと正確に通らなければなりません。それでもし、丁度その位置が一致せず、誤って通り過ぎてしまった場合、正しく測定されずモチベーションが下がってしまいます。そうならないようにするために設定した経過地点の半径30m以内に入れば経過地点を通過したことにするアルゴリズムを考えています。
距離測定方法
距離の測定方法はHaversineの公式を使用しました。このプログラムはの参考記事のURLを掲載しておきます。この記事はとても分かりやすく、距離の計算方法について説明していてとても分かりやすかったです。わかりやすい説明をありがとうございました。
https://qiita.com/Yuzu2yan/items/0f312954feeb3c83c70e
部品
次の材料で長距離測定器を制作しました。
部品表
部品名 | 個数 |
---|---|
spresenseメインボード | 1 |
spresense拡張ボード | 1 |
ジャンパー線 | 13 |
タクトスイッチ | 4 |
必要なツール
使用した機材
・ブレッドボード
・モバイルバッテリ
・micro USB Type-B の USB ケーブル(給電用)
使用機材の接続方法
spresenseメインボードと拡張ボードをBtoBコネクタより接続します。BtoBコネクタはメインボードの裏側、拡張ボードの表についているためこのコネクタ同士を接続します。また、この測定器は外で使用することを想定しているため、モバイルバッテリを使用します。
モバイルバッテリの接続方法について説明します。モバイルバッテリには次の写真のような穴があります。
それぞれUSBケーブルの口にあったところを接続します。
こちらの小さいほうはメインボードに給電するところに接続します。
開発環境
Spresense Arduino
回路図
長距離測定器を制作するための回路図は次のようになっています。
回路の接続を具体的にして行きます。
MSP2807の電源部分の回路です。
これだけで表示器が付きます。
次はSPI通信部です。
そしてリセット機能などのその他の機能を持ったとことを接続していきます。
次はボタン回路です。
ソースコード
#ifndef function
#define function
#include "SPI.h"
#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"
#include "GNSS.h"
#define TFT_DC 9
#define TFT_CS 10
#define TFT_MOSI 11
#define TFT_CLK 13
#define TFT_RST 8
#define TFT_MISO 12
#define SW1 7
#define SW2 6
#define SW3 5
#define SW4 4
#define POW 3
#define BLONG 100
#define EQATR 6378.137
//変数定義
int c = 0;
int c1 = 0;
int c2 = 0;
int tft_c = 0;
int c_1ms = 0;
int cou[2];
char mode = 0;
char str[20];
char strr[8][20];
char s1[2] = {1,1};
char s2[2] = {1,1};
int s3[2] ;
char s4[2] = {1,1};
int cb = 0;
int cshow = 0;
char pointm = 0;
char point = 0;
char whereshow[5][2][9];
double wherep[5][2];
String s;
char result = 0;
long timer = 0;
#endif
}
#include "function.h"
//#include "GPSDATA.h"
#include <math.h>
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_MOSI, TFT_CLK, TFT_RST, TFT_MISO);
SpNavData NavData;
SpGnss Gnss;
class GPSDATA{
//フィールド
private:
double lat_gps;
double lon_gps;
double lat_set[5];
double lon_set[5];
char nowpoint;
char point_max;
// コンストラクタ
public : GPSDATA(){
point_max = pointm;
for(char i = 0; i < pointm; i++){
lat_set[i] = wherep[i][0];
lon_set[i] = wherep[i][1];
}
};
//プロパティ
//残りの距離
public :double Remain_dis_geter(){
double dis = nextpoint(lat_gps,lon_gps,lat_set[nowpoint],lat_set[nowpoint]);
for(char i = nowpoint; (i + 1) < point_max; i++){
dis += nextpoint(lat_gps,lon_gps,lat_set[i],lon_set[i]);
}
return dis;
}
//次の経過地点までの距離
public : double nextpoint_dis_geter(){
return nextpoint(lat_gps,lon_gps,lat_set[nowpoint],lat_set[nowpoint]);
}
public: void position_gps(double a, double b, double c){
lat_gps = a;
lon_gps = b;
nowpoint = c;
}
//メソッド
//2地点間の距離を求める
private: double nextpoint(double gps_latitude,double gps_longitude,
double nextlatitude, double nextlongitude){
const double a = 4000 / 36 ;
double x = 0;
double y = 0;
double dis = 0;;
x = a * (gps_latitude - nextlatitude);
y = a * (gps_longitude - nextlongitude);
dis = sqrt(x * x + y * y);
return dis;
}
};
enum ParamSat {
eSatGps, /**< GPS World wide coverage */
eSatGlonass, /**< GLONASS World wide coverage */
eSatGpsSbas, /**< GPS+SBAS North America */
eSatGpsGlonass, /**< GPS+Glonass World wide coverage */
eSatGpsBeidou, /**< GPS+BeiDou World wide coverage */
eSatGpsGalileo, /**< GPS+Galileo World wide coverage */
eSatGpsQz1c, /**< GPS+QZSS_L1CA East Asia & Oceania */
eSatGpsGlonassQz1c, /**< GPS+Glonass+QZSS_L1CA East Asia & Oceania */
eSatGpsBeidouQz1c, /**< GPS+BeiDou+QZSS_L1CA East Asia & Oceania */
eSatGpsGalileoQz1c, /**< GPS+Galileo+QZSS_L1CA East Asia & Oceania */
eSatGpsQz1cQz1S, /**< GPS+QZSS_L1CA+QZSS_L1S Japan */
};
static enum ParamSat satType = eSatGps;
//関数定義
//カウンター生成
void Timer(void){
cou[0] = millis();
if(cou[0]-cou[1] > 10){
cou[1] = cou[0];
c++;
c2++;
tft_c++;
cshow++;
if(digitalRead(SW3) == 0){
cb++;
}
else{
cb = 0;
}
}
}
//tft設定
void tftset(void){
tft.begin();
tft.setTextColor(ILI9341_BLACK);
tft.setTextSize(3);
tft.setRotation(1);
// tft.readcommand8(ILI9341_RDMODE);
// tft.readcommand8(ILI9341_RDPIXFMT);
//tft.readcommand8(ILI9341_RDIMGMT);
tft.readcommand8(ILI9341_RDSELFDIAG);
tft.fillScreen(ILI9341_WHITE);
}
//tft表示
void tftprint(char a[20],int b, int c,const char e){
char d = 0;
char g[20];
for(char i = 0; i < 10; i++){
if(a[i] != strr[e][i] )
d = 1;
}
if(d == 1){
tft.setTextColor(ILI9341_WHITE);
tft.setCursor(b*19,c*24);
tft.setTextColor(ILI9341_WHITE);
tft.setCursor(b*19,c*24);
for(char i = 0; i < 20; i++){
g[i] = strr[e][i];
}
tft.print(g);
tft.setTextColor(ILI9341_BLACK);
tft.setCursor(b*19,c*24);
tft.print(a);
for(char i = 0; i < 20; i++){
strr[e][i] = a[i];
}
}
}
//tftのスクリーンクリア(改良)
void tft_myclean(int b, int c,int e){
char g[20];
for(char i = 0; i < 20; i++){
g[i] = strr[e][i];
}
tft.setTextColor(ILI9341_WHITE);
tft.setCursor(b*19,c*24);
tft.println(g);
}
//ボタン処理
char statu(void){
char f = 0;
if(c > 10){
c = 0;
s1[0] = digitalRead(SW1) ;
if(s1[0] == 0 && s1[1] == 1){
f = 1;
}
s1[1] = digitalRead(SW1);
s2[0] = digitalRead(SW2);
if(s2[0] == 0 && s2[1] == 1){
f = 2;
}
s2[1] = s2[0];
s3[0] = cb;
if(s3[1] != 0){
if(s3[0] == 0 && s3[1] <= BLONG){
f = 3;
}
else if(s3[0] == 0 && s3[1] > BLONG){
f = 4;
}
}
s3[1] = s3[0];
s4[0] = digitalRead(SW4);
if(s4[0] == 0 && s4[1] == 1){
f = 5;
}
s4[1] = s4[0];
return f;
}
}
//孤度法に変換
double arcchange(double a){
return a * M_PI / 180;
}
//2地点間の緯度経度から距離を計算
double dis_cal (double lat1, double lat2, double lon1, double lon2){
lat1 = arcchange(lat1);
lat2 = arcchange(lat2);
lon1 = arcchange(lon1);
lon2 = arcchange(lon2);
return EQATR * acos(sin(lat1) * sin(lat2) + cos(lat1) * cos(lat2) * cos(lon1 - lon2));
}
//残りの距離全体を計算
double overalldis_cal(double wherepoint[5][2],double gps_lat,double gps_lon, char nowpoint, char pointm){
static double dis = 0;
dis = dis_cal(gps_lat,wherepoint[point + 1][0],gps_lon,wherepoint[point + 1][1]);
for(char i = nowpoint + 1; i < (pointm - 1); i++){
// for(char i = 1; i <= 2; i++){
dis += dis_cal(wherepoint[i][0],wherepoint[i + 1][0],wherepoint[i][1],wherepoint[i + 1][1]);
}
return dis;
}
//最大値処理
void maxp(char *a, char b){
if(*a > b){
*a = 0;
}
}
//tftに緯度経度を表示させる用
void show(char a, char b, char d) {
if(a == 0){
sprintf(str,"%d%d.%d%d%d%d%d%d",
whereshow[b][d][0],whereshow[b][d][1],whereshow[b][d][2],
whereshow[b][d][3],whereshow[b][c][4],whereshow[b][c][5],
whereshow[b][d][6],whereshow[b][d][7]);
}
if(a == 1){
sprintf(str,"%d%d%d.%d%d%d%d%d%d",
whereshow[b][d][0],whereshow[b][d][1],whereshow[b][d][2],
whereshow[b][d][3],whereshow[b][c][4],whereshow[b][c][5],
whereshow[b][d][6],whereshow[b][d][7],whereshow[b][d][8]);
}
}
//表示させた緯度経度をdouble型に変換
void where_cal(double *a, char *b, char c){
if(c == 0){
*a = (*b * 10) + (*(b + 1) * 1) + (*(b + 2) * 0.1) + (*(b + 3) * 0.01) + (*(b + 4) * 0.001) +
(*(b + 5) * 0.0001) + (*(b + 6) * 0.00001) +(*(b + 7) * 0.000001);
}
if(c == 1){
*a = (*b * 100) +((*b + 1) * 10) + (*(b + 2) * 1) + (*(b + 3) * 0.1) + (*(b + 4) * 0.01) + (*(b + 5) * 0.001) +
(*(b + 6) * 0.0001) + (*(b + 7) * 0.00001) +(*(b + 8) * 0.000001);
}
}
//GPSの設定
void GPSset(void){
result = Gnss.begin();
Gnss.select(GPS);
result = Gnss.start(COLD_START);
}
void m0(void){
sprintf(str,"point : %d",pointm);
tftprint(str,4,4,0);
if(statu() == 2){
pointm++;
maxp(&pointm,5);
}
if(statu() == 1){
mode += 1;
//tft.fillScreen(ILI9341_WHITE);
tft_myclean(4,4,0);
}
}
void m1(void){
static char a = 0;
static char i = 0;
static char j = 0;
static char k = 0;
if(a == 0){
sprintf(str,"point%d ",point);
tftprint(str,0,4,0);
tftprint("latitude",0,5,1);
delay(500);
show(0,point,i);
tftprint(str,0,7,2);
a = 1;
}
switch(statu()){
case 1: //Nextボタンでの処理
mode++;
point = 0;
tft_myclean(0,4,0);
tft_myclean(0,5,1);
tft_myclean(0,7,2);
break;
case 2 : //設定1ボタンでの処理
whereshow[point][i][j]++;
if(whereshow[point][i][j] > 9)
whereshow[point][i][j] = 0;
show(i,point,i) ;
tftprint(str,0 ,7, 2);
where_cal(&wherep[point][i],&whereshow[point][i][0],i);
break;
case 3 : //設定2ボタンでの処理(短押し)
j++;
//
if(i == 0)
maxp(&j,7);
else if(i == 1)
maxp(&j,8);
break;
case 4 : //設定2ボタンでの処理(長押し)
j = 0;
if(i == 0){
i = 1;
}
else{
point++;
maxp(&point,pointm);
i = 0;
}
show(i,point,i);
tftprint(str,0,7,2);
sprintf(str,"point%d",point);
tftprint(str,0,4,0);
if(i == 0 )
sprintf(str,"latitude",point);
else if(i == 1)
sprintf(str,"longitude",point);
tftprint(str,0,5,1);
break;
}
}
double nextpoint(double gps_latitude,double gps_longitude,
double nextlatitude, double nextlongitude){
const double a = 4000 / 36 ;
double x = 0;
double y = 0;
double dis = 0;;
x = a * (gps_latitude - nextlatitude);
y = a * (gps_longitude - nextlongitude);
dis = sqrt(x * x + y * y);
return dis;
}
void m2(void){
static char f = 0;
static double dis = 0;
static double overrall_dis = 0;
GPSDATA myGPS;
//時間表示
if(c2 > 100){
c2 = 0;
timer++;
sprintf(str,"%dh%dm%ds",timer/3600,timer/60,timer%60);
tftprint(str,3,2,0);
}
//その他の表示
if (Gnss.waitUpdate(-1))
{
Gnss.getNavData(&NavData);
//次の経過地点までの距離
sprintf(str,"Nextpoint");
tftprint(str,3,3,4);
dis = dis_cal(NavData.latitude,wherep[point + 1][0],NavData.longitude,wherep[point + 1][1]);
char_change(dis,3);
tftprint(str,3,4,1);
// //速度表示
char_change(NavData.velocity,2);
tftprint(str,3,5,2);
//残りの距離表示
overrall_dis = overalldis_cal(wherep,NavData.latitude,NavData.longitude,point,pointm);
char_change(overrall_dis,3);
tftprint(str,3,6,3);
if(dis < 0.030){
point += 1;
sprintf(str,"%d",point);
tftprint(str,3,6,6);
}
// if(point == pointm){
// mode++;
// tft_myclean(3,3,0);
// tft_myclean(3,4,1);
// tft_myclean(3,5,2);
// }
}
}
void m3(void){
//未完成
sprintf(str,"result");
tftprint(str,3,3,0);
sprintf(str,"%dh%dm%ds",timer/3600,timer/60,timer%60);
tftprint(str,4,3,1);
}
void setup() {
//put your setup code here, to run once:
pinMode(SW1, INPUT_PULLUP);
pinMode(SW2, INPUT_PULLUP);
pinMode(SW3, INPUT_PULLUP);
pinMode(SW4, INPUT_PULLUP);
pinMode(1, OUTPUT);
pinMode(POW,OUTPUT);
digitalWrite(POW,LOW);
tftset();
GPSset();
}
void char_change(double a,char b){
s = String(a,b);
s.toCharArray(str,20);
}
void loop() {
// put your main code here, to run repeatedly:
Timer();
switch(mode){
case 0: m0(); break;
case 1: m1(); break;
case 2: m2(); break;
case 3: m3(); break;
default : mode = 0;
}
// sprintf(str,"SW test");
// tftprint(str,1,1,1);
// if(statu() == 3){
// sprintf(str,"OK");
// tftprint(str,2,2,0);
// }
}
考察
私ができなかった部分は主に測定開始の部分です。ソフトウェアはある程度考えることができたのですが、私の配線では少し動くと接触不良でMSP2807の電源が落ちてしまい、動作を確認することができていません。今回の結果を踏まえてハードウェアの見直しをして、次につなげることができるようにしたいと考えています。
-
akagaf
さんが
2024/01/31
に
編集
をしました。
(メッセージ: 初版)
ログインしてコメントを投稿する