2019-09-25

大正池

めっちゃいい天気やないですか。今日走らないでいつ走るの!
久しぶりに京都方面に向かいます。清滝を登って同志社方面へ、そこから木津川を渡って大正池へ向かいます。十三峠に比べて緩やかな坂が続きますが、やっぱり30分以上かけてユーックリと登ります。山頂に和束町の看板が立っていました。最も美しい村だそうです。
 山頂から秋の空
セブンでお腹を満たします。安納芋羊羹うんまー!ねっとりしてパンに塗っても良さそう。
エネルギーをチャージして再び清滝峠を超えて帰ります。

19.09.25の走行コース
行距離:81.08km

2019-09-19

十三峠〜ぶどう坂

十三峠詣が続きます。しかしどんどん登頂までの時間が遅くなっていくのはなぜ?頑張らないと決めたのでダラダラ登るからほぼ徒歩並みのスピードです。山頂からの眺めはベスト。
もう少し足を伸ばして信貴山へ向かいます。バンジーの橋で標高を測ってみます。
国土地理院のデータでは287mですが、測定値は245m。これに下界で測定したマイナス40mを加えるとほぼ一致します。獲得高度としての表示はほぼ正確と言えるでしょう。
ぶどう坂を下ってKOGさんによってひとしきり話こんでから帰りました。

19.09.19の走行コース
行距離:47.92km

2019-09-14

穴虫峠で高度_勾配計を試す

夏バテで寝込んだり台風が来たり猛暑だったりで暫く走っていなかったのですが、今日こそ走りに行きます。とはいえ、体が鈍っているのでかるーく行っときます。先日作った高度_勾配計をバイクに取り付けて出かけます。
リビエール前の河川敷では何やらブラスバンドの練習をしています。
石川に入るところは草ぼうぼうです。すれ違いもままならない状態。皆譲り合って通っていますが、草刈りして欲しいですね。
竹内街道から穴虫峠を登ります。大阪側からは初めてですが、奈良に抜けるには緩くていいですね。さて頂上の状態は?

 気温27.9℃、湿度51%、気圧998hpa、高度125mです。国土地理院のデータでは139mなので14mの差があります。気圧と気温から高度を算出しているのですが、本来なら海面の気圧も計算に入れなければなりませんが、それは無理なので1気圧として計算しているからでしょうか。センサーの誤差も含まれます。なんにせよ獲得高度はほぼ正確に出るので良しとします。問題の勾配ですが、やはり動いていると無茶苦茶な値を出します。止まっていると当然ですが正しい値になります。これを解決するには更なる勉強が必要なので、暫し保留です。
暫く振りの走行で足は残ってないしあちこち痛いし、でも今日はいい天気で気持ち良かったぁ。


19.09.14の走行コース
行距離:52.03km

2019-09-12

高度_勾配計を作る(不具合編)

いつもの十三峠を試走してきました。と言っても車でです。先日GPSで高度等が表示できるものを作ったのでそれも持って行きました。結論から言って勾配表示は使い物になりません。走行加速度を考慮してなかったので、止まっている時は正しく表示できますが走り出すとめちゃくちゃな値を叩き出します。
山頂は海抜413mですがほぼ一致しました。GPSを使用した高度計は安定せずフラフラして信用できませんでした。
しばらくは高度計と停止時の勾配計として使用する事にします。あと、本体はプラウダ仕様にしました。
走行による加速度や車体の高向きも考慮する必要がありますが、先の宿題とします。

2019-09-11

高度_勾配計を作る(スケッチ紹介)

フリスクケースの中身はこんな感じ。
作り方を教えて欲しいという方がいらっしゃったのでスケッチを貼っておきます。いらないコードがいっぱいくっついていますが、将来性を考えて残してあります。

注:includeの<>は半角の<>に書き換える事。
// MPU-6050 Accelerometer + Gyro
#include <Wire.h>
#include "SparkFunBME280.h"
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SSD1306_I2C_ADDRESS   0x3C  // 011110+SA0+RW - 0x3C or 0x3D
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET     4 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

BME280 sensor;

unsigned long ttime;
unsigned long stime;
unsigned long old_stime;
int interval_time = 600; // second

#define MPU6050_ACCEL_XOUT_H       0x3B   // R
#define MPU6050_WHO_AM_I           0x75   // R
#define MPU6050_PWR_MGMT_1         0x6B   // R/W
#define MPU6050_I2C_ADDRESS 0x68

typedef union accel_t_gyro_union {
  struct {
    uint8_t x_accel_h;
    uint8_t x_accel_l;
    uint8_t y_accel_h;
    uint8_t y_accel_l;
    uint8_t z_accel_h;
    uint8_t z_accel_l;
    uint8_t t_h;
    uint8_t t_l;
    uint8_t x_gyro_h;
    uint8_t x_gyro_l;
    uint8_t y_gyro_h;
    uint8_t y_gyro_l;
    uint8_t z_gyro_h;
    uint8_t z_gyro_l;
  }
  reg;
  struct {
    int16_t x_accel;
    int16_t y_accel;
    int16_t z_accel;
    int16_t temperature;
    int16_t x_gyro;
    int16_t y_gyro;
    int16_t z_gyro;
  }
  value;
};

int DL = 500; //待ち時間
int dt = 150; //周期
int unsigned long time; //現在時間

float angle_x;
float angle_y;
float angle_z;

float kx = 1.0; //補正係数x
float ky = 1.0; //補正係数y
float kz = 1.0; //補正係数z

float dx = 0.0; //オフセットx
float dy = 0.0; //オフセットy
float dz = 0.0; //オフセットz
float d_alt = 0.0; //オフセットalt


float ave_tmp ;
float ave_hum ;
float ave_prs ;
float ave_alt ;
float ave_dp  ;
double ave_di ;

int ch;
int old_sw = 1;
int new_sw = 1;

void setup() {
  pinMode( 8, INPUT_PULLUP);
  pinMode( 7, INPUT_PULLUP);
  pinMode( 6, INPUT_PULLUP);

  Wire.begin();
  //Serial.begin(9600);

  int error;
  uint8_t c;
  error = MPU6050_read (MPU6050_WHO_AM_I, &c, 1);
  error = MPU6050_read (MPU6050_PWR_MGMT_1, &c, 1);
  MPU6050_write_reg (MPU6050_PWR_MGMT_1, 0);

  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    //   Serial.println(F("SSD1306 allocation failed"));
    for (;;); // Don't proceed, loop forever
  }

  sensor.setI2CAddress(0x76);
  sensor.beginI2C();  // Wire を用いて I2C 接続開始

  sensor.setFilter(1);              // フィルタ係数: 2
  sensor.setStandbyTime(0);         // スタンバイ時間: 0.5 ms
  sensor.setTempOverSample(1);      // オーバーサンプリング x1
  sensor.setPressureOverSample(1);  // オーバーサンプリング x1
  sensor.setHumidityOverSample(1);  // オーバーサンプリング x1

}

void loop() {
  ttime = millis();
  stime = ttime / 1000;

  int error;
  float dT;
  accel_t_gyro_union accel_t_gyro;
  error = MPU6050_read (MPU6050_ACCEL_XOUT_H, (uint8_t *) &accel_t_gyro, sizeof(accel_t_gyro));

  uint8_t swap;
#define SWAP(x,y) swap = x; x = y; y = swap
  SWAP (accel_t_gyro.reg.x_accel_h, accel_t_gyro.reg.x_accel_l);
  SWAP (accel_t_gyro.reg.y_accel_h, accel_t_gyro.reg.y_accel_l);
  SWAP (accel_t_gyro.reg.z_accel_h, accel_t_gyro.reg.z_accel_l);
  SWAP (accel_t_gyro.reg.t_h, accel_t_gyro.reg.t_l);
  SWAP (accel_t_gyro.reg.x_gyro_h, accel_t_gyro.reg.x_gyro_l);
  SWAP (accel_t_gyro.reg.y_gyro_h, accel_t_gyro.reg.y_gyro_l);
  SWAP (accel_t_gyro.reg.z_gyro_h, accel_t_gyro.reg.z_gyro_l);

  float acc_x = accel_t_gyro.value.x_accel / 16384.0; //FS_SEL_0 16,384 LSB / g
  float acc_y = accel_t_gyro.value.y_accel / 16384.0;
  float acc_z = accel_t_gyro.value.z_accel / 16384.0;

  float acc_angle_x = atan2(acc_x, acc_z) * 360 * kx / 2.0 / PI;
  float acc_angle_y = atan2(acc_y, acc_z) * 360 * ky / 2.0 / PI;
  float acc_angle_z = atan2(acc_x, acc_y) * 360 * kz / 2.0 / PI;

  float gyro_x = accel_t_gyro.value.x_gyro / 131.0;  //FS_SEL_0 131 LSB / (°/s)
  float gyro_y = accel_t_gyro.value.y_gyro / 131.0;
  float gyro_z = accel_t_gyro.value.z_gyro / 131.0;

  angle_x = (angle_x * 0.9) + (acc_angle_x * 0.1) ;
  angle_y = (angle_y * 0.9) + (acc_angle_y * 0.1) ;
  angle_z = (angle_z * 0.9) + (acc_angle_z * 0.1) ;

  //BME280
  float tmp = sensor.readTempC();
  ave_tmp = (ave_tmp * 0.9) + (tmp * 0.1) ;
  float hum = sensor.readFloatHumidity();
  ave_hum = (ave_hum * 0.9) + (hum * 0.1) ;
  float prs = sensor.readFloatPressure() / 100.0 ;
  ave_prs = (ave_prs * 0.9) + (prs * 0.1) ;
  float alt = sensor.readFloatAltitudeMeters();
  ave_alt = (ave_alt * 0.9) + (alt * 0.1) ;
  float dp  = sensor.dewPointC();
  ave_dp  = (ave_dp  * 0.9) + (dp * 0.1) ;
  double di = 0.81 * tmp + 0.01 * hum * (0.99 * tmp - 14.3) + 46.3;
  ave_di  = (di * 0.9) + (di * 0.1) ;

  //offset
  if (digitalRead(6) == LOW ) {
    dx = 0.0 ;
    dy = 0.0 ;
    dz = 0.0 ;
    d_alt = 0.0 ;
  }
  if (digitalRead(7) == LOW ) {
    dx = angle_x ;
    dy = angle_y ;
    dz = angle_z ;
    d_alt = ave_alt ;
  }
  float a_x = angle_x - dx ;
  float a_y = angle_y - dy ;
  float a_z = angle_z - dz ;
  float slope_x = tan(a_x * ( PI / 180) ) * 100.0 ;
  float slope_y = tan(a_y * ( PI / 180) ) * 100.0 ;
  float slope_z = tan(a_z * ( PI / 180) ) * 100.0 ;
  float a_alt = ave_alt - d_alt ;

  new_sw = digitalRead(8) ;
  if (new_sw == 0 & old_sw == 1) {
    ch = !ch;
  }
  old_sw = new_sw ;

  if (ch == 1) {
    display.clearDisplay();
    display.setTextSize(3);
    display.setTextColor(WHITE);
    display.setCursor(0, 0);
    display.print(a_alt, 0);
    display.println("m");
    display.print(slope_y, 1);
    //display.setTextSize(1);
    display.print("%");
    //display.setTextSize(3);
    display.display();  // ディスプレイへの表示
  } else {
    display.clearDisplay();
    display.setTextSize(2);
    display.setTextColor(WHITE);
    display.setCursor(0, 0);
    display.print(ave_tmp, 1);
    display.print("C ");
    display.print(ave_hum, 0);
    display.println("%");
    display.print(ave_prs, 0);
    display.println("hpa");
    display.print(a_alt, 0);
    display.print("m ");
    display.print(a_y, 1);
    display.println("");
    display.print(ave_dp, 1);
    display.print("  ");
    display.print(ave_di, 1);
    display.display();  // ディスプレイへの表示
  }

}

// MPU6050_read
int MPU6050_read(int start, uint8_t *buffer, int size) {
  int i, n, error;
  Wire.beginTransmission(MPU6050_I2C_ADDRESS);
  n = Wire.write(start);
  if (n != 1)
    return (-10);
  n = Wire.endTransmission(false);    // hold the I2C-bus
  if (n != 0)
    return (n);
  // Third parameter is true: relase I2C-bus after data is read.
  Wire.requestFrom(MPU6050_I2C_ADDRESS, size, true);
  i = 0;
  while (Wire.available() && i < size) {
    buffer[i++] = Wire.read();
  }
  if ( i != size)
    return (-11);
  return (0);  // return : no error
}

// MPU6050_write
int MPU6050_write(int start, const uint8_t *pData, int size) {
  int n, error;
  Wire.beginTransmission(MPU6050_I2C_ADDRESS);
  n = Wire.write(start);        // write the start address
  if (n != 1)
    return (-20);
  n = Wire.write(pData, size);  // write data bytes
  if (n != size)
    return (-21);
  error = Wire.endTransmission(true); // release the I2C-bus
  if (error != 0)
    return (error);
  return (0);         // return : no error
}

// MPU6050_write_reg
int MPU6050_write_reg(int reg, uint8_t data) {
  int error;
  error = MPU6050_write(reg, &data, 1);
  return (error);
}

高度_勾配計を作る

ロードバイク乗りには坂を毛嫌いする筋とその逆、坂とあれば登っちゃう筋があります。私は後者。ところで、登っている最中にこの坂の勾配はどのくらい?とか、何メートル登った?と思うことが良くあります。高価なサイコンなら見れるのでしょうが、残念ながら私のにはついていません。無いなら作る・・・という事で作っちゃいました。
高度は気圧センサと気温から計算します。角度はGセンサを使います。これら全てをフリスクのケースに入れます。


全て配線した様子がこれ。ちょっと汚くなりました。
バッテリを入れたところ。なんとか治りましたが、充電回路までは治らなかったのが少々残念です。なので、充電する時はバッテリを取り外します。

 バッテリを繋ぐと以下が表示されます。

  • 1行目:温度、湿度
  • 2行目:気圧
  • 3行目:高度、傾斜角度
  • 4行目:結露温度、不快指数
自転車に乗ってる時に情報量が多すぎるのと、文字が小さくて見えないのでボタンを押すと高度を傾斜のみを表示する様にしました。4行表示時は角度(°)ですが、2行表示時は勾配(%)で表示する様にしました。
ボタンは上部に三つついています。
中:表示切り替え(4行←→2行)
右:ゼロセット(ボタンを押した時の高度と角度をゼロにセット)
左:表示を実測値に戻す

角度が45°の時に勾配は100%になります。
坂の下の平坦なところで右ボタンを押してゼロにセットした後に登れば、獲得高度が表示されます。
あとは実際に走行してどんな感じか見てみる事にします。しかし九月に入ったというのに暑いのでしばしの間バイクはお休みです。

2019-09-04

気圧計で高度を測定する

BME280という気圧センサーを入手したのでこれを使って高度を計測してみました。BME280には温湿度センサーも入っています。高度は気圧と気温から算出できますがライブラリがやってくれるので読み出して表示するだけです。ライブラリはAdafruitのものを使いました。高度の他、結露ポイント、不快指数も読み出せます。これらをまとめてOLEDに表示してみました。
 1行目:気温、湿度
 2行目:気圧
 3行目:高度、(テスト用)
 4行目:結露ポイント、不快指数
高度を確かめる為に自宅駐車場と十三峠山頂で測定してみました。比較の為に以前作ったGPSモニターを一緒に持って行きました。
まずは自宅駐車場です。ここは国土地理院の地図によると海抜1.7mですが、マイナス16mと海に沈んでしまっています。GPSモニターでは42mと高すぎます。
十三峠山頂です。国土地理院の地図によると413.2mのところ374mです。GPSでは209mとかなり低いです。
気圧から高度を算出する際に海面の気圧が必要になりますが、同時に測れませんのでライブラリでは1013hpaで計算されている様です。相対高度(獲得高度)は国土地理院411.5mに対し実測390mと近ずいてきます。これに対しGPSはかなり差があり信用できないことがわかりました。
ところでいつもは自転車で登るところを車で登ったんですが、よくこんな坂を自転車で登れるもんだね、と自分に感心してしまうのでした。
空には厚い雲が立ち込めていますが、この後大雨に見舞われます。