サーボとの組み合わせ

ローバーには大きく分けてスピードコントローラ、ステアリングサーボなど複数の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もついてる! ふーむ 初期インストールソフトができすぎ!

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

サンプルプログラム公開

GPS・QZSSロボットカーコンテスト2020で使用した測位とWaypoint算出に関するソースコードを参考用にUpします。
本ソースコードは「測位航法学会特製 F9P搭載受信機」にも対応しているはずです。

最適ではないのですが、参考になる方もいるかと思い公開に踏み切りました。 もちろん無保証なのと、これによる問題障害については一切責任は負えないので、そのあたりを理解してみてもらえば幸いです。 また、本業が忙しく、コメント・質問などに、答えられない場合があります。


2019年大会はFix率が悪く、走行テストが難航し2回目の走行をキャンセルせざるを得なかったのですが、今年は参考までに以下の対策を講じてみた。 (どれが効果的かというのは感触です。)

-F9PのFarmwareのUpgrade
これはかなり効果があったと思います。
-QZSSの無効化
これも効果があったように感じますが、FarmのUpdate後にL1Sに関する記事を読みL1Sを無効化して再登録。 Fix率は下がらないことを確認した。 L1Sの無効化が聞いたのかFarmware Updateが効いたのかは不明。
-アンテナ背面の銅箔テープによるマルチパス防止
これは気持ちの問題だった気がする。
-ntrip Serverとのアクセス手法
これは効果大だった。制御周期も早いので、制御の合間に数文字程度を読み取って制御側の処理をやっていたが、メッセージ終了までは制御は行わず一気に処理を行った。
-F9P更新周期
F9Pはカタログスペック上20Hzとなっており、51ms(50msが定義できなかった)で動作させた。Logを見るとデータが変更しないときもあるが、最速で見るれ可能性が高いと想定し使ったが、Fixしたのは1-2度のみですぐに設定を戻した。(100mS~300ms)

そのほか2019年では悩んでいたBluetooth通信であったが、ESP32 DevkitをV4にすることで同じソースコードで動作した。試しにV2に戻すと動作しない。 昨年はこれでだいぶん時間を食ってしまったが。 ただ、PC側は毎回削除し、登録している。 この辺はかなり改善の余地がある。 また、Bluetoothを使っていると、たまに処理が止まったり遅くなったりすることがある。情報取得時以外はPC側から接続しないで使っていた。

—–
本ソースコードは無保証の条件で、無償提供し、個人・商用を問わず利用は自由です。ただし、内部で使用されているライブラリなどは、そのライセンス条項に従ってください。

Sample.zip

—–

実際の土壌の湿度と地温の測定

本来の目的であった、地中の水分(土壌湿度)と地温の測定を行う。

使用したセンサーは(購入先は違うが)
DS18B20 防水型センサ
https://www.robotshop.com/jp/ja/ds18b20-waterproof-digital-temperature-sensor.html
土壌湿度センサ
https://www.amazon.co.jp/gp/product/B07K1DP211/ref=ppx_yo_dt_b_asin_title_o02_s01?ie=UTF8&psc=1

これらをDS18B20はOneWireで通信するために以下のライブラリをインストールした。
https://github.com/PaulStoffregen/OneWire
https://github.com/milesburton/Arduino-Temperature-Control-Library

配線としては両方のセンサとも3.3VとGNDに配線した後に、
温度計はD3を使用
土壌計はA1を使用
した。

温度は温度の値がそのまま出るが、湿度は出てくる電圧をanalogReadのレンジに対しての%に修正した。
今回は、モバイルバッテリを使って、連続動作時間や変化を知るためにブレッドボードで配線し、配線部にはホットボンドで仮止め、そして全体をビニール袋で覆った。

モバイルバッテリと合わせたところ

ビニール袋に入れたところ

地面に刺したところ

これで、受信できていることを確認した。
これによって、例えば数100mはなれた畑から自宅にLPWANで接続して地温や水分状態を監視できるプロトタイプが出来上がった。

ちなみにArduinoのソースの改造部分は下記となる。
( ” /// Modify” 部分を追加”)

定義部分への追加

// for Temperatures Sensor and Moisture Sensors  /// Modify
#include <OneWire.h>             /// Modify
#include <DallasTemperature.h>   /// Modify

#define dht_dpin A0 // Use A0 pin as Data pin for DHT11. 
#define DHTTYPE DHT11   // DHT 11 

// for Temperatures Sensor and Moisture Sensors   /// Modify
#define ONE_WIRE_BUS 3 // データ(黄)で使用するポート番号  /// Modify
#define SENSER_BIT    9      // 精度の設定bit     /// Modify
OneWire oneWire(ONE_WIRE_BUS);                    /// Modify
DallasTemperature sensors(&oneWire);              /// Modify

“void do_send(osjob_t* j){ “の中

// for Temperatures Sensor and Moisture Sensors    /// Modify
    sensors.requestTemperatures();              // 温度取得要求 /// Modify
    float Mo01,Ti01;                               /// Modify
    Mo01 = 100.0 - (((float) analogRead(1) + 1.0) / 1024.0 * 100.0 );  /// Modify
    Ti01 = (float) sensors.getTempCByIndex(0);     /// Modify
    Serial.print("Soil temp =");                   /// Modify
    Serial.println(Ti01); //温度の取得&シリアル送信  /// Modify
    Serial.print("Moisture =");                     /// Modify
    Serial.println(Mo01); //土壌湿度のシリアル送信  /// Modify

    CayenneLPP lpp(51);                    // create a buffer of 51 bytes to store the payload

    lpp.reset();                           // clear the buffer
//    lpp.addTemperature(1, t);       // on channel 1, add temperature, value 22.5°C   /// Modify
//    lpp.addRelativeHumidity(2, h);  // on channel 2, Hudimidity   /// Modify
    lpp.addTemperature(1, Ti01);      // on channel 1, add temperature, value 22.5°C   /// Modify
    lpp.addRelativeHumidity(2, Mo01); // on channel 2, Hudimidity   /// Modify

以上

HATについているGPSについて

このLoRa WAN GatewayにはGPSが搭載されている。このホームページでGPSについてを記載しておく。

日本での主な入手元の一つであるRSコンポーネンツでの仕様は、結構さみしい。
ほとんどがLoRa WANのテスト用で使っているためと思われる。
***************************
https://jp.rs-online.com/web/p/products/1875121
とくにデータシートはなく、記載として下記の通り
DGPS 、 SBA をサポート( WAAS/EGNOS / MSAS / GAGAN )
内部パッチアンテナと外部アクティブアンテナの GPS 自動切り替え
PPS 対NMEA は、タイムサービスで使用できます
SDK コマンドをサポートします
内蔵LNAによって感度を最適化
EASY™は外部メモリを使わない先進のAGPS技術
AlwaysLocate™は周期モードのインテリジェントコントローラ
GPS FLP モードでは、通常モードの消費電力が約 50 % です
短絡保護及びアンテナ検出をサポート
**********************
ただ、もう少し見てみると、
GPSモジュールの写真を見ると、

ここでQUECELのWebで確認して特徴的な項目を挙げると。
https://www.quectel.com/UploadFile/Product/Quectel_L89_GNSS_Specification_V1.1.pdf
*L1信号だけでなくL5信号も受信!
昨年のGPSロボットカーではL1/L2の二周波受信を行った。これはL2はないものの、最近の日本の衛星である”みちびき”には対応。 ここ10年くらいの設計。
ただ、L2がないのは残念だが、この価格では仕方ない。
*RTCMには未対応らしく、RTKという単語も見当たらない。 これはちょっとマイナスポイント。 RTCMを利用して1-2cm位置測定という目的には使えない。みちびき信号で頑張るか。
*データ更新周期1秒。 (0.1秒は開発中)
歩行や通常のトラッキングには何も問題ないのだが、GPSロボットカーに使うにしてはちょっと役不足か。。。

参考になれば。

屋外からの接続結果

試しに部屋の机の上に無造作に置いて、どこまで受信できるかを見てみた。
環境の悪い状態でテストした例である。
時期にチャンピョンデータも取得したいと思っている。
先に断っておくが、この辺りの専門家というわけではないので、出てくる数字の目安というものがわからない。また、名称についても、これまでの経験上から想像しているものが多く、LPWANに適応していないものも多く、そもそも間違って覚えているものも多いと思われるので、そこはご容赦願いたい。

まず、直線距離は約500m。これ以上はなれると受信できなくなったので、受信限界時のRSSIとSNRに注目。

用語として
RSSIとは受信信号強度(Received Signal Strength Indicator)であるが、単位が明確についている絶対値ではない。チップメーカで決められた値のため比較はできない。同じ構造を持つ機器での比較や移動時の比較には良い。
SNRとは信号対雑音比(Signal-to-Noise Ratio)であり、信号とノイズの強度差分である。 ノイズが大きくなれば信号は聞き取りにくくなる。小さい電力で少ないデータを使うことを前提に、信号とノイズの差分をどれだけ少なくても通信を確立させるかというところに醍醐味がある。

RSSIから見ていくとする。 1mと500mでの比較を行う。

1mでは大体-41から-45を示しているが、500mでは-107を示した。それ以下の数値の時は通信できなかった。単位としてdBmで表示されているもののMQTTのプロトコルの特性としてデータの種類の定義で単位が固定されてしまうので、ここは信じないほうが良い可能性がある。 これからほかの人の報告を見ながら考えていく。
ここでは、数値が-90程度までは使える。
(そのうち距離のトレンドも取ってみたい。)
また、気になったのが朝8時前は受信強度が安定していたことである。数値が動いているので、正しくデータを読み取っているのだが、ちょうどこの時間はいくつかのPCに電源が入ったころである。屋内の影響とは限らないが、’I’との戦いはここに始まったことではないので、将来のネタとして残しておきたい。

SNRの比較である。

1mでは大体10から12dbを示している。これ自体はLPWAN自体がノイズと信号比率がそれほど大きくなくても大丈夫なものとして考えていたが、500mでは-17dbを観測しつつも連続受信をしていた。 マイナス? どれほど信号を抜き出せるのか? と思いながら、この値は正常なものかどうか考えあぐねている。

どちらにせよ、この両者の数値を見て経験を積んでいくこととなりそうである。

********************************番外1*****
番外としてこのLoRa GatewayのSpecの一部は下記であり、さらに初感を付しておく。
*168 dB maximum link budget.
Link Budjetは損失と通信距離の関係である、この数値でシミュレータにかけると
理論上の到達距離を算出できるのだが、値の指標がわからない。
LPWANは長距離とうたっている関係上良い数値なのでは?

*+20 dBm – 100 mW constant RF output vs.
これは、ただ単に空中線電力の 100mWとみてよいのか?
(内部レジスタで設定できるようなので、実際の出力は不明)

*Programmable bit rate up to 300 kbps.
LPWAは遅いというイメージがあったので、この数値であれば結構使える。
実力を測りたいところだが、プロトコル上簡単に実測するのは難しそう。

*High sensitivity: down to -148 dBm.
さすがLPWAN用ともいうべきか、GPS受信機並みの受信感度といったところか

*FSK, GFSK, MSK, GMSK, LoRaTM and OOK modulation.
こんなにたくさんの変調方式をサポートしている? これが普通?

*127 dB Dynamic Range RSSI.
受信感度が高いためかダイナミックレンジも大きい。

*Packet engine up to 256 bytes with CRC
LoRa WAN使用をフルサポートとみてよいと信じる。

*Built-in temperature sensor and low battery indicator.
LEDが見つけられない。。。。