MovingBaseロガーは、昨年のRTK21で、M5Atom2個とM5Stick1個とM5Core2でまとめてあるのですが、
2022年は、これに、もう1セットF9Pを加えてF9P3個、F9H2個ひずみゲージアンプ4ch,加速度センサ2個と大がかりなシステムを構想しております。そのため、ESP32系では、処理能力が不足するので、超高速マイコンのTeensy4.1ベースでシステムをゼロから作りだしました。正月からいじりだしたTeensy4.1の試運転が終わったので、実際の各機能を同時に埋め込んだ場合どうなるのか、未知なので、これから何週間もはまるかもしれませんので、シリーズ化して備忘録していきます。
※2022年11月8日追記 10か月間学んだ事のTIPSをまとめました
Teensy4.1を前提にしてます。SDログが不安定で泣きまくった果てに悟ったTIPSです。
さらに、バグが見つかりました。ハードウェアシリアルのバッファ設定をイレギュラーにしていたのが原因でSDカードが不安定になったのが原因でした。
●RTK22で得られるデータ
得られるのデータは、RTK22は、スキーの横滑り角と姿勢、高速(20Hz)でのPVTとスキーリアルタイムの曲率変化、足の傾斜角です。これらを春先から夏にかけて、グラフィックプログラムで表現することで、自分の滑りがどこでどうなっているのか、見えるようになると思います。
●プログラム開発の各デバイスのデータログ機能
①MovingBaseのデータが100msec間隔で入ってくるのをSDカードに書き込む
②MovingBaseのデータを実数化して、無線で、モニターのM5Core2に送信して、リアルタイムでモニターする
③MovingBaseが10Hzと遅いので、それを補間するために20HzでF9Pを単独RTKしてPVT値をSDログする。
④スキーの曲がりをひずみゲージセンサでセンシングして、アンプから10-50msec周期でSDログする。
⑤スキー靴横に固定した加速度センサで、I2Cで3軸の加速度を測定してSDログする。
⑥NTRIP受信用にM5Atom2個使って左右と20Hz用F9PへRTCMを供給する(Teensyが忙しいのでM5Atom使う)
●使うインターフェース
USBポート5個 :右が3口のUSBポートをHUBでまとめてUSB HOST受信、左が2個
シリアル4ポート:左右で無線送信用モジュールとしてM5Atomliteを2個、ひずみアンプ2個
I2C2ポート:左右で加速度センサ用のI2C2個
データモニター用のUSBシリアル2個
●rev042で使っているライブラリー
Teensy用のArduinoIDE用ツールTeesuduino内の各種ライブラリを使ってプログラム作成していきます。
①USB HOST用ライブラリ
USBHOST用ライブラリーUSBHost_t36:https://github.com/PaulStoffregen/USBHost_t36
このライブラリーは、Teensy3.6用ですが、teensy4.1も同じUSBHOST構成なので使えます。これのExampleのSerialを改造してつかいます。
USB接続なら何でも複数組み合わせて受信できます。HUBとDeviceとUSBSerial機器はなんでもござれです。まるでPC、ラズパイみたいです。
F9PからNAV-PVT(100byte),F9HからNAV-RELPOSNED(72byte)のUBXデータをそれぞれのUSBポートからHUB経由で受信してデータを合体させてSDカードへログしながら、Ucenterへリアルタイムで流して観察できるようにします。
②SDライブラリで大ハマり
Arduinoで昔から使われているSD.hは、USBHOSTライブラリと相性が悪くてOPENできなくなったり、100msec周期でOPEN-CLOSEしながらLOOPを回すのがぎりぎりでどきどきハング。=>この現象のおかげで1週間くらいかかってしまいました。
■そこで、Teensy用に開発されたsdFAT.hライブラリに交換すると物凄く速くなりました。
10msec周期でOPEN-CLOSEしながら、OKでした。
実際のデータ書き込み時間は、172Byteで33μsecとSPIの最高速(5MBPS)でてました。
●SD高速化 sdFATライブラリ
世間一般でSDカード付製品に使われているSDカードシステムは、SPIではなくSDIOが使われてます。
4bitのSDIOは、データ線が4本あるので、SPIの1本の4倍以上高速です。
だいたい、SPIで5MBPSですが、SDIOなら20MBPS以上でます。
今回は、GNSSチップなのでSPIでも十分ですが、音楽録音とか映像などマルチメディアだとSDIOが
必須となります。マイコンでSDIOに対応しているのはTeensyしか知りません。
Greimanさん作のライブラリGITはここです。https://github.com/greiman/SdFat
●信州MAKERS流のプログラムコピペ開発
素人プログラムでいかに難しいことを避けて、所望の機能を得るかということです。
①目的の単機能に近いExampleプログラムをかたっぱしからコンパイル動作させてみる。
②機能を複数化した場合に相性問題がないかExampleプログラムを2個合体させて様子をみる
③相性がわるければ、①に戻って、できるだけ新しいライブラリを探す。
④FORUMを検索して同じ課題をかかえた人の相談結果を探す。
①SDカードのサンプルプログラム
ArduinoIDEのスケッチ例にたくさんあるのですが、sdFATは、デフォルトでなく、カスタムライブラリのスケッチ例
にありましたが、シンプルな書き込みのサンプルが無くて、いろいろな機能がはいっていて難解で困ってしまいました。そこで、TeensyForumにいって検索しまくって探しあてました。
SDFAT read/write example ここのWWATSON氏のサンプルププログラムがシンプルで判り易かったです。
GITにはないので、ここからそのままコピーしてArduinoIDEに貼り付けてコンパイルして即動作OKでした。
外付けSDと内蔵SDでコメントが多いですが実際は数行でできてます。
/* | |
SD card read/write with SdFat-beta | |
This example shows how to read and write data to and from an SD card file | |
The circuit: | |
* SD card attached to SPI bus as follows: | |
** MOSI – pin 11 | |
** MISO – pin 12 | |
** CLK – pin 13 | |
created Nov 2010 | |
by David A. Mellis | |
modified 9 Apr 2012 | |
by Tom Igoe | |
Modified 12-15-20 | |
by Warren Watson | |
This example code is in the public domain. | |
*/ | |
#include “SdFat.h” | |
// Uncomment ‘USE_EXTERNAL_SD’ define to use an external SD card adapter or leave | |
// it commented to use the built in sd card. | |
//#define USE_EXTERNAL_SD | |
#ifdef USE_EXTERNAL_SD | 外付けか内蔵か選択 |
const uint8_t SD_CS_PIN = SS; | 内蔵ならいらない |
#define SPI_CLOCK SD_SCK_MHZ(10) | |
#define SD_CONFIG SdSpiConfig(SD_CS_PIN, SHARED_SPI, SPI_CLOCK) | |
#else // Use built in SD card. | |
// Why does this #ifdef have to be here? I do not understand. If it is defined | |
// then why does the compiler give this error without the #ifdef/#endif: | |
// “ReadWrite:39: error: ‘SDCARD_SS_PIN’ was not declared in this scope” | |
// Either it is defined previously or it is not right??? | |
// This one has me stumped. Uncomment the #ifdef and #endif and you will see what | |
// happens. I am probably missing something again! | |
#ifdef SDCARD_SS_PIN | |
const uint8_t SD_CS_PIN = SDCARD_SS_PIN; | |
#endif // SDCARD_SS_PIN | |
// Setup for built in SD card. | |
#define SPI_CLOCK SD_SCK_MHZ(50) | |
#define SD_CONFIG SdioConfig(FIFO_SDIO) | |
#endif // USE_EXTERNAL_SD | |
// SdFat-beta usage | |
SdFs sd; | sdsdFs インスタンス定義 |
FsFile myFile; | FsFile インスタンス定義 |
void setup() { | セットアップ |
// Open serial communications and wait for port to open: | |
Serial.begin(9600); | 後で115200に変更する |
while (!Serial) { | シリアルポートチェック |
; // wait for serial port to connect. Needed for native USB port only | |
} | |
Serial.print(“Initializing SD card…”); | イニシャライズ開始 |
if (!sd.begin(SD_CONFIG)) { | SD_CONFIGを使う |
Serial.println(“initialization failed!”); | 失敗した場合 |
return; | |
} | |
Serial.println(“initialization done.”); | イニシャライズ成功 |
// open the file. note that only one file can be open at a time, | |
// so you have to close this one before opening another. | |
// NOTE: With SdFat-Beta this is not the case anymore. | |
myFile = sd.open(“test.txt”, FILE_WRITE); | ファイルOPEN |
// Comment out the following line to append text to test.txt. | |
myFile.truncate(0); | 追記しないで重ね書きする |
// if the file opened okay, write to it: | |
if (myFile) { | myFile Okなら書き込む |
#ifdef USE_EXTERNAL_SD | |
Serial.print(“Writing test.txt to external SD card…”); | |
#else | |
Serial.print(“Writing test.txt to built in SD card…”); | |
#endif | |
myFile.println(“testing 1, 2, 3.”); | myFileへ書き込み |
// close the file: | |
myFile.close(); | Fileを閉じる |
Serial.println(“done.”); | |
} else { | |
// if the file didn’t open, print an error: | |
Serial.println(“error opening test.txt”); | エラーなら |
} | |
// re-open the file for reading: | |
myFile = sd.open(“test.txt”); | 再度OPEN |
if (myFile) { | myFile OKなら |
Serial.println(“test.txt:”); | |
// read from the file until there’s nothing else in it: | |
while (myFile.available()) { | ファイル読み込み可能なら |
Serial.write(myFile.read()); | あるだけ読みこむ |
} | |
// close the file: | |
myFile.close(); | ファイルを閉じる |
} else { | |
// if the file didn’t open, print an error:} | |
Serial.println(“error opening test.txt”); | |
} | |
} | |
void loop() { | |
// nothing happens after setup | |
このサンプルを自分流に改造ししたが、
●TIPS
ファイルをOPENしてから、CLOSEが確実にされないとデータが記録保存されないリスクあります。
途中で電源が切れたりしたら、それまでのデータが紛失したら大変です。そこで、データの1セット書いたら
毎回CLOSEするようにするのですが、Teensyの書き込みループが速すぎて(30μsecで172byte書き込んでしまう)ので、OPENしてからCLOSまでが短すぎて暴走します。そこで、10msecdelay(10)いれて、暴走をとめました。これで、計測途中で電源が切れても、大丈夫です。ESP32系だと書き込みが遅いので、こんな心配はいらないのですが速すぎるマイコンだと速いということを念頭においてプログラムを描かないといけません。
●本プログラムの備忘録
Teensy4.1用ソースはGISTに置いてあります。https://gist.github.com/dj1711572002/f472a0f45c404c904bb97526359b7b41
下記黒塗りコードはテーブルなのでコピーしても使えません
工夫点1:USB HOS経由でBaseとRoverからPVTとRELPOSNEDが入ってくるがタイミングがばらばらと入ってくる可能性がある
そこで、総バイト数172Byteを読み込んだら、PVTとRELPOSNEDに分類するアルゴリズムを作った。
その部分ソース
//*********************************************************** | |
//****************DATA ACQUSITION From USB HOST************** | |
//*********************************************************** | |
//—USB HOST Ports read————————– | |
//2ポートのデータをBuf172[]にまとめて収納1周期トータル172byteなので順不同で配列に格納して後で、ばらす。 | |
while(userial.available()){ | userialがOKなら読む |
Buf172[Num1]=userial.read(); | Buf172[配列に読み込む |
Num1++; | データ数インクリメント |
}//—USERIAL read finished——————— | |
//—USERIALB Port read——————- | |
while(userialb.available()){ | useriabがOKなら読む |
Buf172[Num1]=userialb.read(); | Buf172[]に追加していく |
Num1++; | データ数インクリメント |
}//useralb read finished | |
//:::::::::: Logging & Monitoring data process 172bytes:::::::::::::::::::::: | |
//1周期分のデータ取得終了であと90msecあるのでその間に各種処理を行う | 172byte読んだら次まで100msecあるからゆっくりと処理できる |
if(Num1>=172){ //a MB data read finished | 上記読み込みをLOOPさせて 172byteになったら処理開始 |
Num1=0; | |
//—-PVT RELPOS Deviding— 172byteのデータの先頭がPVTかRELPSOか判別して、PVTdata[]RELPOSdata[]に仕分ける—————————————– | 順序が逆転している場合を直す |
if(Buf172[0] == 0xB5 && Buf172[1] == 0x62 && Buf172[2] == 0x01 && Buf172[3] == 0x07){//1st PVT Header | PVTのヘッダー4文字を検出した |
for (i=0;i<100;i++){ | 以後100byte1がPVTdata収納 |
PVTdata[i]=Buf172[i]; | |
} | |
for (i=100;i<172;i++){ | |
RELPOSdata[i-100]=Buf172[i]; | 以後72byteRELPOSdata収納 |
} | |
}//if end | |
else if(Buf172[0] == 0xB5 && Buf172[1] == 0x62 && Buf172[2] == 0x01 && Buf172[3] == 0x3C){//1st RELPOS Header | 先頭がRELPOSの場合 |
for(i=0;i<72;i++){ | |
RELPOSdata[i]=Buf172[i]; | RELPOS72byteをPVTdataに収納 |
} | |
for(i=72;i<172;i++){ | |
PVTdata[i-72]=Buf172[i]; | PVT100byteをPVTdataへ収納 |
} | |
}//else end |
工夫点2:MovingBaseバイナリーデータをフレームデータに変換した
teensy処理能力に余裕があるので、NAV-PVTとNAV-RELPOSNEDのフレームデータに換算
//==============PVTcnv,RELPOScnv バイナリを実数データ変換====== |
int result=PVTcnv(PVTdata,PVTval); |
result=RELPOScnv(RELPOSdata,RELPOSval); |
PVTcnv()は、100バイトのバイナリを33個のlongに変換してます。
B2L(上、中上、中下、下)の順にバイナリを並べて掛け算してます。
//+++++++++++++4byte Binary to Long +++++++++++++++++++++++++++++++ |
long B2L(uint8_t b4 , uint8_t b3 ,uint8_t b2 , uint8_t b1 ) { |
//pc.printf(“B2L IN=%s,%x,%x,%x,%x,b4&0x80=%d\n\r”,sen,b4,b3,b2,b1,b4 &0x80); |
//pc.printf(“B2L IN=b4&0x80=%d\n\r”,b4 & 0x80); |
long su; |
if ((b4 & 0x80) && 0x80){//最上位ビットたっていればマイナス |
su = -(256-(int)b1)+(255-(int)b2)*256+(255-(int)b3)*65536+(255-(int)b4)*256*256*256; |
//pc.printf(“B2L-:sen=%s,%d,%d,%d,%d,%d\n\r”,sen,b4,b3,b2,b1,su); |
} |
else { |
su=(int)b1+(int)b2*256+(int)b3* 65536+(int)b4*256*256*256; |
//pc.printf(“B2L+:sen=%s,%d,%d,%d,%d,%d,%d\n\r”,sen,b4,b3,b2,b1,su); |
} |
//^^^^^^^^^^^^^^^^Binary Conversion^^^^^^^^^^^^^^^^^^^ |
int PVTcnv(uint8_t d[100],long pvt[33]){ |
//PVT header[0-6] |
//0:itow[6-9] |
pvt[0]=B2L(d[9],d[8],d[7],d[6]); |
//1:year[10-12] |
pvt[1]=d[10]+d[11]*256; |
//2:month[12] |
pvt[2]=d[12]; |
//3:day[13] |
pvt[3]=d[13]; |
//4:hour[14] |
pvt[4]=d[14]; |
//5:min[15] |
pvt[5]=d[15]; |
//6:sec[16] |
pvt[6]=d[16]; |
//7:valid[17] |
pvt[7]=d[17]; |
//8:tAcc[18-21] |
pvt[8]=B2L(d[21],d[20],d[19],d[18]); |
//9:nano[22-25] |
pvt[9]=B2L(d[25],d[24],d[23],d[22]); |
//10:fixType[26] |
pvt[10]=d[26]; |
//11:flags[27] This is Fix status 131 |
pvt[11]=d[27]; |
//12:flags2[28] |
pvt[12]=d[28]; |
//13:numSV[29] |
pvt[13]=d[29]; |
//14:lon[30-33] |
pvt[14]=B2L(d[33],d[32],d[31],d[30]); |
//15:lat[34-37] |
pvt[15]=B2L(d[37],d[36],d[35],d[34]); |
//16:height[38-41] |
pvt[16]=B2L(d[41],d[40],d[39],d[38]); |
//17:hMSL[42-45] |
pvt[17]=B2L(d[45],d[44],d[43],d[42]); |
//18:hAcc[46-49] |
pvt[18]=B2L(d[49],d[48],d[47],d[46]); |
//19:vAcc[50-53] |
pvt[19]=B2L(d[53],d[52],d[51],d[50]); |
//20:velN[54-57] |
pvt[20]=B2L(d[57],d[56],d[55],d[54]); |
//21:velE[58-61] |
pvt[21]=B2L(d[61],d[60],d[59],d[58]); |
//22:velD[62-65] |
pvt[22]=B2L(d[65],d[64],d[63],d[62]); |
//23:gSpeed[66-69] |
pvt[23]=B2L(d[69],d[68],d[67],d[66]); |
//24:headMot[70-73] |
pvt[24]=B2L(d[73],d[72],d[71],d[70]); |
//25:sAcc[74-77] |
pvt[25]=B2L(d[77],d[76],d[75],d[74]); |
//26:headAcc[78-81] |
pvt[26]=B2L(d[81],d[80],d[79],d[78]); |
//27:pDOP[82-83] |
pvt[27]=d[82]+d[83]*256; |
//28:flags3[84] |
pvt[28]=d[84]; |
//29:reserved1[85] |
pvt[29]=d[85]; |
//30:headVeh[86-89] |
pvt[30]=B2L(d[89],d[88],d[87],d[86]); |
//31:magDec[90-91] |
pvt[31]=d[90]+d[91]*256; |
//32:magAcc[92-93] |
pvt[32]=d[92]+d[93]*256; |
}//PVTcnv() end |
RELPOSNEDは21個のフレームデータがあります
//–RELPOScnv————————— |
int RELPOScnv(uint8_t d[72],long relpos[20]){ |
//RELPOS header[0-5] |
int s=0; |
//0:ver[6] |
relpos[0]=d[s+6]; |
//1:reserved1[7] |
relpos[1]=d[s+7]; |
//2:refStationId[8] |
relpos[2]=d[s+8]; |
//3:itow[10-13] |
relpos[3]=B2L(d[s+13],d[s+12],d[s+11],d[s+10]); |
//4:relposN[14-17] |
relpos[4]=B2L(d[s+17],d[s+16],d[s+15],d[s+14]); |
//5:relposE[18-21] |
relpos[5]=B2L(d[s+21],d[s+20],d[s+19],d[s+18]); |
//6:relposD[22-25] |
relpos[6]=B2L(d[s+25],d[s+24],d[s+23],d[s+22]); |
//7:relposLength[26-29] |
relpos[7]=B2L(d[s+29],d[s+28],d[s+27],d[s+26]); |
//8:relposHeading[30-33] |
relpos[8]=B2L(d[s+33],d[s+32],d[s+31],d[s+30]); |
//9:reserved2[34] |
relpos[9]=B2L(d[s+34],d[s+33],d[s+32],d[s+31]); |
//10:relposHPN[35] |
relpos[10]=127-int(d[s+35]); |
//11:relposHPE[36] |
relpos[11]=127-int(d[s+36]); |
//12:relposHPD[37] |
relpos[12]=127-int(d[s+37]); |
//13:relposHPLength[38] |
relpos[13]=127-int(d[s+38]); |
//14:accN[38-41] |
relpos[14]=B2L(d[s+41],d[s+40],d[s+39],d[s+38]); |
//15:accE[42-45] |
relpos[15]=B2L(d[s+45],d[s+44],d[s+43],d[s+42]); |
//16:accD[46-49] |
relpos[16]=B2L(d[s+49],d[s+48],d[s+47],d[s+46]); |
//17:accLength[50-53] |
relpos[17]=B2L(d[s+53],d[s+52],d[s+51],d[s+50]); |
//18:accHeading[54-57] |
relpos[18]=B2L(d[s+57],d[s+56],d[s+55],d[s+54]); |
//19:reserved[57-60] |
relpos[19]=B2L(d[s+60],d[s+59],d[s+58],d[s+57]); |
//20:flags[60-63] |
relpos[20]=B2L(d[s+63],d[s+62],d[s+61],d[s+60]); |
} |
工夫点3;SDファイル名を時刻データからタイムスタンプ生成
年月日からログファイル名をタイムスタンプとしてYY-MM-DD-mm.ubxとする
//*********ファイル名をタイムスタンプで作ってファイルOPEN**************************************************** |
if (PVTval[11]==131 && Sflag==0){//start |
Sflag=1; |
//—PVTval[1]=Year/[2]=Month/[3]=Day/[4]=Hour/[5]=Min/[6]=sec— |
int JST=(PVTval[4]+9)%24;//UTC hourをJSTに変換 |
String stime=String(PVTval[2],DEC)+”-“+String(PVTval[3],DEC)+”-“+String(JST,DEC)+”-“+String(PVTval[5],DEC);//MMDDHHMM |
String stimeB=”/”+stime+”.ubx”;//UBX Binary File |
String stimeV=”/”+stime+”.txt”;//UBX Value Text File |
int slenB=stimeB.length()+1; |
int slenV=stimeV.length()+1; |
//ファイル名はchar配列なのでStringからchar配列変換 fname[]を得る |
stimeB.toCharArray(fnameB,slenB);//stimeB to fnameB[] chara Array |
stimeV.toCharArray(fnameV,slenV);//stimeV to fnameV[] chara Array |
Serial.println(); |
Serial.print(“fnameB=”); |
Serial.print(stimeB); |
Serial.print(“/fnameV=”); |
Serial.println(stimeV); |
Serial.print(“TimeStamp:Sflag=”); |
Serial.println(Sflag); |
//myFile = sd.open(fnameB, FILE_WRITE); |
//myFile.truncate(0);//Appned |
Sflag=1; |
}//timeStamp making end |
工夫点4:PVTのflags=131ならBaseがFIXしたということなので、検知した時点からSDログを開始します。OPENーCloseをデータ受信毎回で行うことで突発電源断でもデータを保持する。OPEN-CLOSE間にdelay(10)をいれて暴走防止した。
//*****************************SD Write********************************** |
if (PVTval[11]==131 ){//start |
n=0; |
int stime; |
stime=micros(); |
myFile = sd.open(fnameB, FILE_WRITE); |
while(n<172){//PVT write to SD card |
if(myFile){//myFileチェックは、書き込み直前で行う。 |
if(n<100){ |
myFile.write(PVTdata[n]); |
} |
if(n>=100 && n<172){ |
myFile.write(RELPOSdata[n-100]); |
} |
}else{ |
Serial.print(“myFile Error”); |
} |
n++; |
}//while write finished |
delay(10);//OPEN-CLOSE時間をあける |
myFile.close(); //毎回クローズして万一のアクシデントに備える |
//if(Serial.available() && Serial.read() >= 0) { |
// Sflag=0; |
// } |
/* |
if(kaisu>1000){//デバッグ用に千回でとめる |
Serial.print(“SD closed time=”); |
Serial.print(millis()); |
exit(0);//loop exit |
} |
//Serial.println(“SD closed write Finished”); |
//kaisu++; |
//Serial.print(kaisu); |
//Serial.print(“:SD write time=”); |
//Serial.print(micros()-stime); |
*/ |
}//SD Write end |
工夫点5:PCへの出力をバイナリでUCENTER用、シリアルモニター用にフレームデータをSerial.printするようにして長時間ログでUcenterでも監視できるようにした。
//========Ucenter OUTPUT======================= |
for(i=0;i<172;i++){ |
if(i<100){ |
Serial.write(PVTdata[i]); |
} |
if(i>=100 && i<172){ |
Serial.write(RELPOSdata[i-100]); |
} |
} |
/* |
//======Serialprint Monitor PVTval RELPOSval========== |
Serial.println(); |
Serial.print(“PVTval:”); |
for(i=0;i<33;i++){ |
Serial.print(PVTval[i]); |
Serial.print(“,”); |
} |
Serial.println(); |
Serial.println(); |
Serial.print(“RELPOSval:”); |
for(i=0;i<21;i++){ |
Serial.print(RELPOSval[i]); |
Serial.print(“,”); |
} |
Serial.println(); |
*/ |
}// if 172 end |
}//loop finished |
●RTk22の目的とRTK21との違いンネル
昨年のRT21では、Base(F9P)-Rover(F9P)だったので、RoverからNAV-PVTとNAV-RELPOSNEDは、順に出力されてUART1でマイコンで受信してましたので、それをUSBシリアルでUcenterに接続するだけで、単純でよかったのですが、RTK22は、USBポートですべてデータをハンドリングすることにしました。そうすることで、多チャンネルのF9Pシステムを連結することが容易になります。RTK22では、高速化を目標としてます。そのために、Movingbase10Hzを補完するためにもう1個F9Pシステムを追加して、20HzでRTK測位させます。つまりMovingBaseの測位10Hzの倍の速度で測位データを追加することで、位置データを2倍にできるので、倍速にできるということです。Headingは10Hzですが、スキーの動作の特性上、Headdingの角度範囲が狭いので、10Hzでも20Hzでも大差ないだろうという判断です。位置データの場合、スキー速度によって、10Hzと20Hzで大きな差(50-2m前後)の差がでるので、効果が大きいという判断です。この判断が正しいかどうかという実験確認します。ダメだったら、RTK23でF9Pをやめて、SeptentrioのMISAICチップを搭載したSimpleRTK3Bボードへ乗り換えて、RTKシステム全体を大変更します。