mosaic X5 (mosaic HAT)で RTK 測位

少し使い方もわかってきたところで、RTK接続を。 でも、場所的には最悪なんだが、実現するかどうか。。。 ここ数年、自宅ではなかなかFixしなかったり、Fixしてもすぐに落ちたりしてており、結局屋外に出かけていたので正直設定と1度でもFixして動作確認ができればいいと思っていた。

しかし、結果はそれなりのFIXを得られて満足。 測位精度は屋外での実験を待ちたいが、現状では期待以上の結果が出ている。

さて、まず環境の紹介。

環境としては最悪かも。 次にシステム構成は下記。 PCを除くと写真の通り。

Wifi-Routerとmosaic HATとM5Stack Core2

今回の目的は、M5Stack Core2とmosaic X5をつかったRTKの実現である。

まずは、Core2が20km以上離れた場所のNtrip Serverに接続し、RTCM3の情報を得て、mosaicへ送信。mosaicからは、RTKのFix状態と位置情報を確認する。

まずは、RTKの設定をしない状態。

SBASが1衛星だけ補足していたが、単独測位との表示で±80cmといったところか。

衛星を半球しか捕まえていないところを見ると、かなり良い。 もしかしたらSBASが効いている可能性があるのだろうか。

従来の受信機ではこの状態だと軽く10mぐらいずれてしまっていた。次に、RTK測位を行っている場合、下記を見ると特にmosaic側での設定は不要のようである。

見ると、COM2にRTCM3が毎秒1kB程度づつ送られているのがわかる。

RTKもFloat解が出だした。

その後、Fixになり、それなりにほおっておいた状態が下の図である。

半球しか見えていないことと、これまでの実績を加味すると、このデータは結構よさそう。

その後、NMEAの出力を100mSに変えたが、Fix率が悪くなった。また、トレースで見切れていないのだが、(ここはu-centerが便利)RTKの送信が間に合わないのではないかという想像もしている。

どちらにせよ、個々のあたりはほかのアプリも組み込んだうえで調整が必要である。

また、少し気になるところとしては、Fixed RTKからStand-Aloneに落ち、Float解を経由してFix解となったときに、アンテナの位置は移動していないのだが、同じ場所を示さないという現象がある。

±15cm程度の誤差ではあるが、さらに良い環境で確かめる必要がある。

半球というところと、Ntrip Serverまでの距離が長いことと、Ntrip Serverの位置情報を入れていない事も十分考えられる。

今回は、M5がWifi-Router経由でNtrip Serverからのデータを受信し、mosaic X5に転送し、ちゃんとFix解が出るかどうかという実験だったので、それに関しては完了!

MOSAIC HAT (MOSAIC X5)の接続

さて、今度は、Septentrio社 の mosaic を試してみる。 X5という話なのだが、シルクにはmosaicとしかかない。

でも、ここに二次元バーコードが付いているので、読んでみると(URLかと期待した。) MOSAIC-X5GRB….

となっており、ちょっと安心。 mosaic-X5として扱っていける。
Webをみると、結構情報がある。 これまでubloxを使っているのはu-centerがわかりやすいという面がおおきかったが、どちらかというとマニア向けか。

ちょっと見たところでは、

データシートやマニュアル類、最新バージョンのファームの情報などは下記ある。

https://www.septentrio.com/en/products/gnss-receivers/receivers-module/mosaic#resources

そのほか、githubにもかなりの情報が公開されている。

https://github.com/septentrio-gnss/mosaicHAT

また、各種コンフィグレーションなどはRxToolsによって行われるが、下記からDownloadができる。

https://www.septentrio.com/en/products/software/rxtools#resources

ドライバも一緒にインストールされるので、あまり困ることはない。

さて、私のWindows環境にRxToolsをダウンロードし、ドライバと一緒にインストール。

そしてボードを見てみて、ANTとFTDI(USB/シリアル変換)で電源設定があるので、とりあえず両方とも3.3Vに指定して二周波のアンテナとUSBを接続。

一応動き出したが、場所の関係もあり、1GPS,1GLONASS.1BeiDou,1NavICしか入らない。 

ちょっと少し移動せざるを得ないか。。。

部屋の反対側に来て、作業性は悪いものの、半球は開放。さすがにあっという間に衛星を確保。

4GPS,7GLONASS,4Galileo,7BeiDou,1QZSSとなった。

方位が半円しか見えないためか、Main Signalの評価は半分以下しかあてにできない。 まぁ。しょうがないか。

まぁ、ほとんど設定も必要なくここまで来た。

そういえば、入江先生がWebで設定していたような雰囲気で話していたので、もしかしてと思い、pcの設定を確かめてみると、USB経由でLAN接続されているみたい。 また、192.168.3.1が怪しいので、さっそくChromeで接続してみると、これで十分設定できそうな画面が出てくる。

なんとなく感触はつかめた。 ふむ。 これは面白い。 

では次にGPSロボットカーのGPS部に使えるかどうか考えてみる。

まず、素材としてやらなけてばならないのは、

MOSAICの立ち上げ (この投稿部分が該当)

ロボットカー CPUへのNMEA送信(接続含む)

RTKの実現

というところか。

今回はMOSAICからシリアル通信でカーCPU ( M5 Stack Core2 ) へ通信。 

カーCPUというのもめんどくさいので、今使っているM5Stack Core2で実験。 ← ちょっとの遊びには結構高いんだよねぇ。。。 

まずは、Core2のアプリを作成。

要はMOSAICから受信したデータを画面に出すことと、USBでPCに送ること。

Core2上の画面では制御文字が出るとどうなるかわからないので、一か所にAscii CodeがHexで表示されるようにした。

また、Core2からPCにシリアルを送るのは、データを見たいだけではなく、トレースデータを見たいのだが、設定Webではそのような機能を探しきれなかったので、u-centerを使ってしまおうという考え。

Core2で作ったスケッチは下記。

#include <M5Core2.h>

#define RXD2 13 //HardwareSerial Serial2;
#define TXD2 14 //HardwareSerial Serial2;

static int i;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);  //USB to PC
  delay(100);
  Serial2.begin(115200, SERIAL_8N1, RXD2, TXD2);  // UART for GPS module
  delay(100);
  M5.begin(true, false, true, true); //LCD:Yes, SD:No, Seria:Yes, I2C:Yes
//  M5.Power.begin(); // Power ON for GPIO21,GPIO22 and I2C
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setTextColor(GREEN , BLACK);
  M5.Lcd.setTextSize(2);
  M5.Lcd.println("M5 Started");
}

void loop() {  
  i = i + 1;
  if(Serial2.available() > 0) { 
    int data = Serial2.read();     
    Serial.write(data); 
    M5.Lcd.setCursor(0,20);
    M5.Lcd.println(data,HEX);
    M5.Lcd.println(i); 
  } 
}

何をやっているかというと、端子の定義とシリアルの定義(USBと端子)。

SerialのBufferに何かデータが来たら、そのままUSBに送信。 M5にはAscii Codeの16進数表示とLoop Counterの表示。

次にMOSAICとCore2の接続は下記。

Jumperスイッチとして3V3とVCCを接続。 ESP32のUARTは3.3V系のため。

シルクをみると、PWR SRCとあるので、VCCの電圧設定だけかもしれないが、念のため。

(こんな時にジャンクでとってあるJumper Switchが役に立つ)

CORE2側は

MOSAICもCore2もたぶんどちらもDCE側(端末側)。 なので、TXとRXD、RXとTXDを接続する。

ここは、ベンダーによってさまざまなので、だめなら逆に。 というレベル。

GNDはGND同士で接続。 MOSAICにはRTS/CTSの制御線があり、設計者のきちんとした意図が感じる。

でも、ごめんなさい。 使わないです。 Core2に無いし。 (仕事だと使うんですけどね)

つぎはMOSAIC-X5の設定。 Core2とMOSAIC-X5をともにUSBにつなげて電源ON。

192.168.3.1にアクセスして

Serial Portを指定して「Next」で次に

COM2を指定して、

好きなメッセージを選択してFinish!

最後にOK!!!! うーん。 簡単。 簡単。 

さらに、この更新周期に関しては期待大!

では、Core2の画面。

よさげ。

IDEのシリアルモニタの画面。

Good!

さらに、シリアルモニタを終了してu-centerでの画面。

Very Good!

u-centerでの設定 (NEO-M8P-2)

ちょっとu-centerの設定方法という相談があったので、ちょっと思い出してみました。

まずは、u-centerを最新版へ。 昨年はv20でしたが、いまみるとv22なんですねぇ。

v20を惜しみなくアンインストールし、u-bloxのWebからダウンロードしてインストール。

さっそくM8P-2にusbとアンテナを接続し、teratermで認識しているポートを確認。

今回は、COM3でした。

ためしにOKを押して少し様子を見ると、

接続はされている。 文字化けしているが、この辺りはアスキーコードの若い番号も来てしまうので、

御愛嬌といったところです。 ポートが正しいことと、読めていることを無事に確認。

こんなことをしているうちにダウンロードが終了し、u-centerのインストールへ。

インストールとともに、u-centerが起動されたので、tera termを終了してu-centerに

まずは、受信機のコネクションでCOM3を設定。

Packet Consoleボタンを押して

まずは、通信を確認。 位置は把握できていないようですが、衛星はある程度捕まえているので、

ちょっと先に進む。 ここは、テストするには環境が悪く、一部しかダメなんですよね。。。。

こんな感じです。

位置測位を開始していないところが気になるが、どんどん設定に進んでしまいましょう。・

[View]-[Configuration View]-[MSG]

ここで必要とするMSGを定義します。 ここにはいくつかの流派はあるので、自分の思い通りに。。

私は以下の設定。

出力はUSBとシリアルの両方としています。 (一応、、、) UART-2は今回はないので、設定しようとしてもSendした段階で消されてしまいます。

Robot Carで使用するNMEA MessageはGxRMCなんですが、U-Centerで感度やほかいろいろ見るときもあるので、最初はGxGGA,GxVTG、GxGLLも出力するようにしています。 (本番走行前で余裕があったときは、これらを外しています。)

 なので、こんな感じ。 都度、Sendを忘れないように。 一応2-3回チェックしたほうが良いです。

設定したつもりが変わっていないということはよくあること。 気にしない。

そのほかのNMEA Messageは以下の感じで出力しないようにしておく。

ゆっくりした制御なら出力しなくてもよいのですが、できるだけ余分な処理はトラブルの元なの潔く削除。

いつも限界で設定しておくと、トラブったときに、何が、何に関係していたかということがわかるので、それはそれでよしとします。

これが終わったらポートの設定。

左側よりPRTを選択して、UARTとUSBにUBX,NMEA,RTCM3を送受信できるように設定します。

速度は葉や右方が良いので、115200です。 USBで通信するのであれば関係ない。

下記はUART-1の設定だが、UART-2,USBも同じ設定に。 、と、UART-2が設定できなかったので、UART2は無視。

そして、保存します。 (一応保存されるような気もするが)

Packet Consoleを見てみるとGxRMCも出始めました。 よしよしです。

さて、次は、これをどのくらいの周期で出すかという設定です。

毎秒取れればOkという人は不要ですが、早くデータ収集したいという人は、Rateというメニューで設定します。 保存を忘れずに。 この通りにデータが来ると保証されているわけではないです。

M5Stack Core2がリスタートできない。

最近使っていなかったが、考えれば手元にあるM5Stick Cは、ともかくM5Stack Core2はモータとステアリング操作に、I2C、GPSとの通信にUART、RTKにWi-Fi、アピールに音、操作関係にはタッチパネル。

ちょっとパフォーマンスの心配はあるが、サーボをI2Cでやってしまえば可能性ありかと。

本当はパフォーマンスを考慮してRaspberry pi PICO Wを使いたかったんですが、まだ技適前。自分で研究用で申請してしまうのもありだが、そのものが手に入らない。 Arduino IDEでアプリ組めるし。 と思っていたが、また次回のお遊び用に。

さて、M5StackCore2のリハビリを兼ねて数字を表示するスケッチを。

#include <M5Core2.h>

static int i;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);  //USB to PC
  delay(100);
  M5.begin(); 
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setTextColor(GREEN , BLACK);
  M5.Lcd.setTextSize(2);
  M5.Lcd.println("M9 Started");
  Serial.println("M9 Started");
}

void loop() {
  i = i + 1;
  M5.Lcd.setCursor(0,15);
  M5.Lcd.println(i);
  M5.Lcd.println("123456");
  Serial.println(i);
  delay(500);
}

あれ? うまくいくときもあるが、再書き込みで止まったり、リスタートボタンの横のLEDが消えていたり、動作がおかしい。 リスタートボタンをおしてもウンスンではないか。。。

もしや、ESP32やUnoで時々苦しめられたRSTの信号不安定か?

ググってみると。 Core2においては、同現象に悩まされている人もいるが、それほど多くはない。

過去のセオリー通りにRSTとGND間にコンデンサをつけている人も確かにいる。

幸いCore2には端子もある。。。 おもちゃオシロで見てみるべきか。。。

こんな時にはリビング行ったり、冷蔵庫空けたり、、、、そういえば、Fall Guysが無料になっているんですよね。 なんとなく、Fall Guysの名前が覚えられなくて、Whole Foodsが頭の中に出てくることこの頃。

ってなことを考えながら、再度PCの前に。。。 Delayを増やしたり、カーソル処理を外したり。。。 あれ? ダウンロードするときに Time Out起こすことも、ますますCore2がひねくれている。

こうゆうときは、一度工場出荷へ。 EasyLoader_M5Core2_FactoryTest.exe を使って、最初のアプリをロード。 あれ? 動くじゃん。 リスタートも何度やっても問題ない。 これのアプリ部分だけ入れ替えたいなぁ。。。

ん? 

OSが原因か? ボード情報を取得してみると、対象外? んん? M5StackのCore2のボードと設定しているが、中身がこっそり変わっていたか??? ファームのスタイルが変わっていてIDEと一致していなかったのかも。

さっそく、ボードマネージャでM5Stackを検索。 インストールは1.0.6 最新は2.0.3。 これかぁ。。

最新に上げて、ダウンロードして事なきを得た。 コンパイルが長くてたまらん。 PCの性能もあるが、何とかしてくれ!!! 昔もコンパイル間に仮眠をとっていた時代が懐かしい。 これが原因だったのね。

すると、これまでESP32でトラブっていたのもこれが原因だったのでは??? と疑ってしまう。

動作確認して良好を確かめ一安心。 念のためにライブラリのM5Core2も0.1.2から0.1.4に挙げておいた。

再度動作確認して問題のないことを再確認してComplete!

サーボとの組み合わせ

ローバーには大きく分けてスピードコントローラ、ステアリングサーボなど複数のPWM出力を使用する。

まだ最終採択ではないが、これらを制御するために PCA9685 搭載16チャネル PWM/サーボ ドライバーをテストしている。 < https://www.switch-science.com/catalog/961/ >

これを磁気センサと組み合わせて使ってみた。 結果から言うと特に問題はなさそう。

配線は以下の通り

本プログラムでは、先の磁気センサのアプリに追加したものである。

そこで気になるのがサーボのビビりである。 原因は磁気センサの値が揺れているためにおこるものであり、その値をフィルタリングすることで逃げている。

フィルタリング手法はいくつかあるのだが、なんとなくコーディングの楽なRCフィルタリングを用いた

参考にこの部分である。

  new_angle =k_rc*new_angle+(1.0-k_rc)*rad;  // RC filter

k_rcはフィルタリングの強さで0~1の値を入れるが、1に近くなるほどフィルタリングが強く反応も遅くなる。0.8程度を入れたが、チューニング要素である。

// This software is released under the MIT License.
// See license.txt at http://moon.robots.jp/ehp/ 
// The software is provided "AS IS", without warranty of any kind, 
// express or implied, including but not limited to the warranties
// of merchantability. (c) 2021 Team Katy
// 
// 2021/5/23 ver 1.0 first released by S.K
//

#include <Wire.h>
#include <Servo.h>
#include <PCA9685.h>     


////// Other Variables
static float k_rc = 0.8;           // RC filter factor

////// Servo
#define SERVOMIN_VAL 110     // minimum pulse width for servo
#define SERVOMAX_VAL 460     // maximum pulse width for servo
#define SERVO_CH 1           // channel for servo
PCA9685 pwm = PCA9685(0x40); // No address jumper connection in PCA9685

////// AK8963 register 
const int   I2C_AK8963_addr=0x0c;   // i2c: ak8963 slave address
const int AK_ID= 0x00;  //  00H READ  Device ID; ID is 0x48
const int AK_ST1= 0x02; //  02H READ  Status 1  DataStatus; D0:True; data ready D1:True data overrun
const int AK_HXL= 0x03; //  03H READ  X-axis data 8190:4912uT -8190:-4912uT
const int AK_CNTL1= 0x0A; //  0AH READ/WRITE  Control1  Function Control
                          // see data sheet D0:3 mode; D4:0 is 14 bit, 1 is 16bit
const int AK_ASAX= 0x10;  //  10H READ  X-axis sensitivity adjustment value Fuse ROM
const int   AK_WAIT=100;     // Wait for mode change [us]
const int   AK_MOD_PWRDWN=0x0;   // Power down mode 
const int   AK_MOD_READ_METHOD=0x1;   
                // Read method:0x1:Single
                //             0x2:Continus 8Hz
                //             0x3:Continus 100Hz
const int   AK_MOD_FUSE=0x0f;    // mode change to read FUSE
const int   AK_MOD_BIT=0x10;   // # of read bits: 0x10:16 bit, 0x00:14 bit
const int   AK_I2C_ADD=0x48;   // I2C address for AK8963
static float x_bias = -122; // calibration value
static float x_gain = 1; // calibration value
static float y_bias = -45.5; // calibration value
static float y_gain = 1; // calibration value
static float z_bias = -122; // calibration value
static float z_gain = 1; // calibration value

static int  i2c_buf[8];    // read buffer for I2C
const int   max_char=8;    // max read characters from serial monitor

// convert to pulse and write to servo 
void write_servo(int ch, int ang){ 
  int pulse = map(ang, 0, 180, SERVOMIN_VAL, SERVOMAX_VAL); // convert angle to servo pulse value
  Serial.print("Pulse width = ");
  Serial.println(pulse);  
  pwm.setPWM(ch, 0, pulse);
}


//********** i2c_read **********//
int i2c_read(int addr, int cnt= 1, int *buf=NULL)
{
  if (cnt<1 or 128<=cnt) return(-1);
  int   rtn_code, i, dat;

  Wire.beginTransmission(I2C_AK8963_addr);
  Wire.write(addr);
  rtn_code= Wire.endTransmission(false);
  if (rtn_code!=0) return(-1);

  Wire.requestFrom(I2C_AK8963_addr, cnt);
  for (i=0; i<cnt; i++)
  {
    dat=Wire.read();
    if (buf==NULL)
      break;
    else
      buf[i]=dat;
  }

  while (Wire.available()) Wire.read();

  if (buf == NULL)
    return(dat);
  else 
    return(buf[0]);
}


//********** i2c_write **********//
int i2c_write(int addr, int dat)
{
  int rtn_code;

  Wire.beginTransmission(I2C_AK8963_addr);
  Wire.write(addr);
  Wire.write(dat);
  rtn_code=Wire.endTransmission();
  if (rtn_code!=0) 
    return (-1);
  else 
    return(0);
}

//********** read_serial **********//
int read_serial()
{
  if (Serial.available()){
    char ch = Serial.read();
    return((int) ch );
  }
  else return(0);
}



//********** mag_read **********//
int mag_read(float &x, float &y, float &z)
{
  int   rtn_code;
  delayMicroseconds(AK_WAIT);
  rtn_code=i2c_write(AK_CNTL1, AK_MOD_READ_METHOD | AK_MOD_BIT);
  if (rtn_code!=0) {
    Serial.println("Error in configuration setting for AK8963");
    return(-1);
  }

  while ((i2c_read(AK_ST1) and 0x01)==0); // wait until ready.

  i2c_read(AK_HXL, 7,i2c_buf); // read measure data and status2 
  if (i2c_buf[6] & 0x08)        // check sensor overflow in status2
  {
    Serial.println(" Sensor overflow ");
    return (-1);
  }

  x=int ((i2c_buf[1]<<8) + i2c_buf[0]);
  y=int ((i2c_buf[3]<<8) + i2c_buf[2]);
  z=int ((i2c_buf[5]<<8) + i2c_buf[4]);

  if (x>32767) x= x-65536;
  if (y>32767) y= y-65536;
  if (z>32767) z= z-65536;
  
  x+=x_bias;
  y+=y_bias;
  z+=z_bias;

  return (0);
}

//********** calib_mag **********//
void calib_mag()
{
  int   i, pre_cnt = 10;
  float xmin=9999,xmax=0,ymin=9999,ymax=0,zmin=9999,zmax=0;
  float x,y,z;
  int   calib_count = 150;
  float cx[calib_count];
  float cy[calib_count];
  float cz[calib_count];
  x_bias =0;
  y_bias =0;
  z_bias =0;
 
  Serial.println("Entering Mag");

  for (i=0; i<=pre_cnt; i++){
    delay(1000);
    Serial.print(pre_cnt-i);
    Serial.println(" sec reamin for calibration.");
  }
  
  Serial.println(" Start calibration, please rotate the sensor.");
  for (i=0; i<calib_count-1; i++){
    mag_read(x,y,z);
    Serial.print(calib_count-i);
    Serial.print("/");
    Serial.print(calib_count);
    Serial.print(": x="); Serial.print(x);
    Serial.print(": y="); Serial.print(y);
    Serial.print(": z="); Serial.println(z);
    cx[i]=x;
    cy[i]=y;
    cz[i]=z;
    if(xmin>x) xmin = x;
    if(ymin>y) ymin = y;
    if(zmin>z) zmin = z;
    if(xmax<x) xmax = x;
    if(ymax<y) ymax = y;
    if(zmax<z) zmax = z;
    delay(100);
  }
  x_bias = 0 - (xmax + xmin) / 2;
  y_bias = 0 - (ymax + ymin) / 2;
  z_bias = 0 - (zmax + zmin) / 2;
  x_gain = 1 / ((xmax - xmin) / 2);
  y_gain = 1 / ((ymax - ymin) / 2);
  z_gain = 1 / ((zmax - zmin) / 2);
  Serial.print("Calibrated Values are X Bias ="); Serial.print(x_bias);
  Serial.print(", Y Bias "); Serial.print(y_bias,3);
  Serial.print(", Z Bias "); Serial.print(x_bias,3);
  Serial.print(", X Gain "); Serial.print(x_gain,5);
  Serial.print(", Y Gain "); Serial.print(y_gain,5);
  Serial.print(", Z Gain "); Serial.println(z_gain,5);
  
  Serial.println("Wait a 15 seconds");
  delay(15000);
}

//********** setup **********//
void  setup (void)
{
  Serial.begin(115200);

  pwm.begin();                // no connection for address on PCA9685 0x40
  pwm.setPWMFreq(50);         // pwm period
 
  Wire.begin();
  if (i2c_read(AK_ID) != AK_I2C_ADD){
     Serial.println("NOT FOUND AK8963");
  }
  delayMicroseconds(AK_WAIT);
  
}

//********** loop **********//
void  loop(void)
{
  int ch_i = read_serial();
  if ( ch_i >= 49){ // ch_i = '1' 
    Serial.println("Calib Start");
    calib_mag();
  }
  float x,y,z;
  int rad;
  mag_read(x,y,z);
  rad=atan(y/x)*(180/3.1416) + ((x>=0)? 0: (y<0)? -180: 180);
  Serial.print(rad); 
  Serial.print(": "); 
  Serial.print(x,5); 
  Serial.print(","); 
  Serial.print(y,5); 
  Serial.print(","); 
  Serial.println(z,5); 

// control 
static float new_angle;

  if (rad < -90 or rad > 175){
    rad = 175;
  }
  else{
    if (rad < 5){
      rad = 5;
    }
  }

  new_angle =k_rc*new_angle+(1.0-k_rc)*rad;  // RC filter

// Servo
  write_servo(SERVO_CH, new_angle);
  Serial.print("Angle = ");
  Serial.println(new_angle);  
}

磁気センサの校正について (キャリブレーション)

今回使用したAK8963でそのまま読み取ると以下の感じである。

(マンションの中で、15cm程度の近くにPCや汎用電源、スマホがある状態で使っている。)

まず、目的はローバーへの搭載のためなので、基本水平として想定しZ軸は無視。

とは言いつつも、地軸は斜めになっているので以下のイメージとなる。

でも、欲しいのは方位なので、実際にはX軸、Y軸さえあればよい。ローバーがその場で360度回転するとすると地軸の動作はぐるっと回る。 まぁ。 結局X-Yでいいのだが。

では、実際にサンプルデータとして水平に置いて水平旋回させて読み取ったデータを散布図に表してみると。

思った以上に円になってる。中心がずれているのはしょうがない。

ここでの構成としては、150個のデータを取得して各軸の最大値/最小値を取得して中点を求めるもの。

至極簡単だが、校正した結果は下記となる。

校正自体より磁気センサとの通信に時間がかかったというのが正直なところ。 このプログラムはシリアルモニターで見ていれば、校正結果がモニターに一定時間表示するので、その値をプログラムの中にハードコーディングしておくことを進める。

また、キャリブレーション指令としてはシリアルモニタに’1’を入力してください。

約10秒間の待ち時間があり、ここでセンサを水平旋回する準備。
測定は約15秒間で150センシングします。 この間に2-3周回するとよいでしょう。

すると 測定されたBIASが表示されるので参考としてください。

Gainは正規可用途で作っていましたが、今のところどこでも使っていません。

この測定されたBIASは自動的に磁気センサからの読み込みデータの補正に使用されますが、

電源を切ってしまうと無くなってしまうので、この値を初期値として入れておくことを薦めます。

磁気センサについて MPU-9250 (AK8963)

これまで、方位についてはGPSから入力される信号を用いていたのだが、

利点として

・移動時の測定においては移動方向の方位が取得できる。

・GPSの精度が良ければ方位も十分な精度が期待できる。

 ここでいう精度とは再現性である。

欠点として

・移動していないと方位の取得が取れない

これまでは、基本的に移動している状態で方位を算出していたので、GPSでの方位取得を行ってきた。

しかし、今回は必要性に迫られた部分もあり磁気センサから方位算出を行うこととした。

使用したのは、 MPU9250 と呼ばれる9軸センサ。
ジャイロ、加速度、磁気センサが入って9軸というわけだが、今後の遊びも含めてということでこれを選定。

値段もまぁ許される程度です。

しかし、中身はちょっと素直ではなさそう。

いくつかのサンプルを試してみたが、高速で読もうとすると時々0が戻ってきたりする。 サンプルが悪い気もしなくはないのだが、データシートを見ると、磁気センサはSDA/SCLというよりも AUX_CL,AUX_DAに接続されているので、そちらをI2Cと想定して試してみた。 というわけであくまで保証はできなく参考程度にしてください。 また、この磁気センサは旭化成の AK8963 であり、使い方などはAK8963 のデータシートが参考になる。 下記はMPU-925のブロック図です。

ESP32との配線は、以下の感じ。

MPU-9250のSCL/SDAには接続せずEDA/ECLに配線することによって磁気センサに直接アクセスが可能となる。

参考のためにソースコードを記載する。 転用などは特に連絡不要ですが無保証です。

また、勝手な想像によるキャリブレーション 機能も搭載しています。

// This software is released under the MIT License.
// See license.txt at http://moon.robots.jp/ehp/ 
// The software is provided "AS IS", without warranty of any kind, 
// express or implied, including but not limited to the warranties
// of merchantability. (c) 2021 Team Katy
// 
// 2021/5/23 ver 1.0 first released by S.K
//
//

#include <Wire.h>

const int   I2C_AK8963_addr=0x0c;   // i2c: ak8963 slave address

////// AK8963 register 
const int AK_ID= 0x00;  //  00H READ  Device ID; ID is 0x48
const int AK_ST1= 0x02; //  02H READ  Status 1  DataStatus; D0:True; data ready D1:True data overrun
const int AK_HXL= 0x03; //  03H READ  X-axis data 8190:4912uT -8190:-4912uT
const int AK_CNTL1= 0x0A; //  0AH READ/WRITE  Control1  Function Control
                          // see data sheet D0:3 mode; D4:0 is 14 bit, 1 is 16bit
const int AK_ASAX= 0x10;  //  10H READ  X-axis sensitivity adjustment value Fuse ROM
const int   AK_WAIT=100;     // Wait for mode change [us]
const int   AK_MOD_PWRDWN=0x0;   // Power down mode 
const int   AK_MOD_READ_METHOD=0x1;   
                // Read method:0x1:Single
                //             0x2:Continus 8Hz
                //             0x3:Continus 100Hz
const int   AK_MOD_FUSE=0x0f;    // mode change to read FUSE
const int   AK_MOD_BIT=0x10;   // # of read bits: 0x10:16 bit, 0x00:14 bit
const int   AK_I2C_ADD=0x48;   // I2C address for AK8963
static float x_bias = 0; // calibration value
static float x_gain = 0; // calibration value
static float y_bias = 0; // calibration value
static float y_gain = 1; // calibration value
static float z_bias = 1; // calibration value
static float z_gain = 1; // calibration value

static int  i2c_buf[8];    // read buffer for I2C
const int   max_char=8;    // max read characters from serial monitor

//********** i2c_read **********//
int i2c_read(int addr, int cnt= 1, int *buf=NULL)
{
  if (cnt<1 or 128<=cnt) return(-1);
  int   rtn_code, i, dat;

  Wire.beginTransmission(I2C_AK8963_addr);
  Wire.write(addr);
  rtn_code= Wire.endTransmission(false);
  if (rtn_code!=0) return(-1);

  Wire.requestFrom(I2C_AK8963_addr, cnt);
  for (i=0; i<cnt; i++)
  {
    dat=Wire.read();
    if (buf==NULL)
      break;
    else
      buf[i]=dat;
  }

  while (Wire.available()) Wire.read();

  if (buf == NULL)
    return(dat);
  else 
    return(buf[0]);
}

//********** i2c_write **********//
int i2c_write(int addr, int dat)
{
  int rtn_code;

  Wire.beginTransmission(I2C_AK8963_addr);
  Wire.write(addr);
  Wire.write(dat);
  rtn_code=Wire.endTransmission();
  if (rtn_code!=0) 
    return (-1);
  else 
    return(0);
}

//********** mag_read **********//
int mag_read(float &x, float &y, float &z)
{
  int   rtn_code;
  delayMicroseconds(AK_WAIT);
  rtn_code=i2c_write(AK_CNTL1, AK_MOD_READ_METHOD | AK_MOD_BIT);
  if (rtn_code!=0) {
    Serial.println("Error in configuration setting for AK8963");
    return(-1);
  }

  while ((i2c_read(AK_ST1) and 0x01)==0); // wait until ready.

  i2c_read(AK_HXL, 7,i2c_buf); // read measure data and status2 
  if (i2c_buf[6] & 0x08)        // check sensor overflow in status2
  {
    Serial.println(" Sensor overflow ");
    return (-1);
  }

  x=int ((i2c_buf[1]<<8) + i2c_buf[0]);
  y=int ((i2c_buf[3]<<8) + i2c_buf[2]);
  z=int ((i2c_buf[5]<<8) + i2c_buf[4]);

  if (x>32767) x= x-65536;
  if (y>32767) y= y-65536;
  if (z>32767) z= z-65536;
  
  x+=x_bias;
  y+=y_bias;
  z+=z_bias;

  return (0);
}

//********** calib_mag **********//
void calib_mag()
{
  int   i, pre_cnt = 10;
  float xmin=9999,xmax=0,ymin=9999,ymax=0,zmin=9999,zmax=0;
  float x,y,z;
  int   calib_count = 150;
  float cx[calib_count];
  float cy[calib_count];
  float cz[calib_count];
  x_bias =0;
  y_bias =0;
  z_bias =0;
 
  Serial.println("Entering Mag");

  for (i=0; i<=pre_cnt; i++){
    delay(1000);
    Serial.print(pre_cnt-i);
    Serial.println(" sec reamin for calibration.");
  }
  
  Serial.println(" Start calibration, please rotate the sensor.");
  for (i=0; i<calib_count-1; i++){
    mag_read(x,y,z);
    Serial.print(calib_count-i);
    Serial.print("/");
    Serial.print(calib_count);
    Serial.print(": x="); Serial.print(x);
    Serial.print(": y="); Serial.print(y);
    Serial.print(": z="); Serial.println(z);
    cx[i]=x;
    cy[i]=y;
    cz[i]=z;
    if(xmin>x) xmin = x;
    if(ymin>y) ymin = y;
    if(zmin>z) zmin = z;
    if(xmax<x) xmax = x;
    if(ymax<y) ymax = y;
    if(zmax<z) zmax = z;
    delay(100);
  }
  x_bias = 0 - (xmax + xmin) / 2;
  y_bias = 0 - (ymax + ymin) / 2;
  z_bias = 0 - (zmax + zmin) / 2;
  x_gain = 1 / ((xmax - xmin) / 2);
  y_gain = 1 / ((ymax - ymin) / 2);
  z_gain = 1 / ((zmax - zmin) / 2);
  Serial.print("Calibrated Values are X Bias ="); Serial.print(x_bias);
  Serial.print(", Y Bias "); Serial.print(y_bias,3);
  Serial.print(", Z Bias "); Serial.print(x_bias,3);
  Serial.print(", X Gain "); Serial.print(x_gain,5);
  Serial.print(", Y Gain "); Serial.print(y_gain,5);
  Serial.print(", Z Gain "); Serial.println(z_gain,5);
  
  Serial.println("Wait a 15 seconds");
  delay(15000);
}

//********** read_serial **********//
int read_serial()
{
  if (Serial.available()){
    char ch = Serial.read();
    return((int) ch );
  }
  else return(0);
}

//********** setup **********//
void  setup (void)
{
  Serial.begin(115200);
  Wire.begin();
  if (i2c_read(AK_ID) != AK_I2C_ADD) Serial.println("NOT FOUND AK8963");
  delayMicroseconds(AK_WAIT);
  
}

//********** loop **********//
void  loop(void)
{
  int ch_i = read_serial();
  Serial.print("ch = ");   Serial.print(ch_i); 
  
  if ( ch_i >= 49){ // ch_i = '1' 
    Serial.println("Calib Start");
    calib_mag();
  }
  float x,y,z;
  int rad;
  mag_read(x,y,z);
  rad=atan(y/x)*(180/3.1416) + ((x>=0)? 0: (y<0)? -180: 180);
  Serial.print(rad); 
  Serial.print(": "); 
  Serial.print(x,5); 
  Serial.print(","); 
  Serial.print(y,5); 
  Serial.print(","); 
  Serial.println(z,5); 
}

せっかくなのでUIFLOWを使う設定を行ってみる

M5StackのホームページからUIFlowを選択する。
https://docs.m5stack.com/#/en/core/core2
Burning toolをインストール。IDEと同時にできないのはしょうがない。。。
Zip形式がダウンロードされるので、解凍してM5Burner.exeを実行。
すると、M5Burnerが起動され、UIFLOW(CORE2)なるものができてきたので、これを実行。
よく見るとゲームみたいなものもある。。。 まぁ。 これは後でのお楽しみ(^^;
COMポートを合わせこみ、Downloadボタンを押すとBurnボタンも出てきたので押す!
ん? Wifi Setting? USB経由ではないのか? まぁ。いいや。 何とでもなる。
押したら、ダウンロード始まった。 あれ? CORE2にはWifi設定していないぞ?
あぁ。 ダウンロードイメージにWifi情報を組み込んだということか。
なるほど。 無事にBurn終了。 CORE2の画面も変わった。

次のAPIKEYでのペアリング。
CORE2でUIFLOWを選択するとUSBかWi-Fiの選択が出てくるのでWi-Fiを選択。
API KEYが出てきた。 これをメモメモ
今度は下記にアクセスすると、
https://flow.m5stack.com/
Api Key、言語、デバイス対応(CORE2を選択)、テーマ色、を聞いてくる。
M5BunerでWindowsを選ぶとそちらに飛ぼうとしたので、そこはキャンセルし。
まずはOkを押させてもらう。
お。 接続した。 スクラッチに近い感触の絵が出てきたが、デモもある。
これはわかりやすくて最初のとっかかりにはよさそうだ。
ふむ。 どちらかというとWebをエンジニアリングツールとして使うだけであり、CORE2とはWi-fi経由で行うということみたいであり、特にInternet経由というわけではなさそうだ。






IDEの設定とM5Stack Core2の工場出荷 への戻し方確認

ここでは、基本として覚えておきたい部分のメモ
(このページでは保証できないので、各自の責任において実施してください。)

M5StackのHomePage
https://docs.m5stack.com/#/
上記からCORE2をクリックする。 チュートリアル、機器仕様、ピンアサインなどなど

工場出荷のソフトに戻す方法
1.上記URLからEASYLOADERを選択し、WINDOWSを選択。
インストーラがダウンロードされるので、セキュリティを気にしつつ実行!
Comポートを選んで Burn ボタンを押す。

2.Arduino IDEの設定方法
私の使っているIDEは1.8.13ということを前提に置いておいて。
先のM5StackのHomePageからQUICK-STARTを選択しArduino IDEを選択。
ここで、ボードマネージャに指定するURLをコピーペースとしておく。
IDEのファイルメニューー>環境設定から
追加のボードマネージャに先ほど指定したURLをペースト。
ただし、すでに追加されている場合は、右のボタンを押してURLを追加する。
念のためIDEを再起動し、ツールメニューのボードのボードマネージャを選択。
ここでInternetから情報を集めてきているはずなので、少し待ってから検索バーのようなところにM5Stackと入力すると、インストールできる状態になる。 ここで何も出てこなかったらネットワーク接続や、先ほど設定したURLを疑うのが妥当。
インストールが完了したらダイアログを閉じて、再度ツールメニューのボードマネージャから
M5Stack ArduinoからM5Stack-Core2を選択。
次にライブラリとしてスケッチメニューからライブラリをインクルードのライブラリマネージャを選択しM5Core2で検索。
私の場合、M5Core2とESP32-Chimera-Coreが出てきたがM5Core2のみインストールした。

動作確認としてスケッチ例でM5StackCore2のTouchを選択してダウンロード。
無事にダウントードが終了し、タッチをするとしたところが白くなることを確認できた。

試しに ここでOFF/ON。 OFFは電源ボタンを3-4秒押し続けてリセットボタン横のLEDが消えることを確認。
再度ここでON。 画面変化がなく寂しいがタッチすると白くなることを確認。

本題はここから、ここでEASYLOADRをつかってBURNしてみる。

おぉぉ 買った状態に戻った。 これで一安心!







M5Stack Core2

また、増えてしまった。 M5Stackとしてはまだ2個目。 でもたぶんこの部屋には動かなくなったものも含めESP32は10台以上。。。 Beagle Boneから始まってあちこちに手を出してしまった気もする。 それはさておき、進化の激しさをかじる。digital divideの解消にも一役買えそうである。

さて、送られてきたM5Stack Core2の口開け式も無事終了。 特に輸送などの問題はなさそう。
さっそく電源ボタンみたいなものを押してみると、おぉ。 なんかいっぱい文字が、、、表示があるって新鮮。 SDCard Find failed ん? そりゃそうだ。 CLass10のが一枚余っているのだが、まぁ あとで考えるか。 Touch to Skipふむふむ とりあえず画面をタッチ。

おぉ こんな絵が出てきた。

ご立派。 SOUNDのFFTもついてる! ふーむ 初期インストールソフトができすぎ!

もったいないので、元に戻す方法はきちんと押さえておこう。