CIVIC(シビック)FL1のOBD2実践編4:OBD2データロガーVer2

このページの目的と目次

前回までの作業で、OBD2経由でのデータモニター&データロガーの作成は完結したはずだった...のだが、モデルチェンジを実施してみよう。

要改善点[2023-08-06]

まず、前ユニットの不満点と要改善点を整理してみよう。特に3番が致命的な問題となっているぞww

  1. 液晶が白黒で解像度が低い(納得の選択だったはずだが)
  2. 偏向サングラスをかけた状態だと液晶が見えづらい
  3. どういった状況(いつ?どこで?)で取得したデータだったか覚えられない(人間の記録容量の問題だ)
  4. 筐体とケーブルが一体なので、プログラム変更の際には中身を取り出さないとダメ(筐体を貼り付けたから)
  5. そういえば、輝度自動調整の実装は見送っていたな

▲目次に戻る

いつ?どこで?[2023-08-06]

えーと、「いつ?」を明確にするためには時計を実装しなけりゃならないんだよね。RTCを組み込む?スマホとかと連動する? なんかめんどくさそう。

じゃぁ、「どこで?」ってのはどうしよう...あれ? そういや世の中にはGPSって素晴らしい技術があるじゃないか! それにGPSってグリニッジ標準時も取得できるので「いつ?」の問題も同時に解決する! これがいいんじゃないの?! で、いつもお世話になってる秋月のネットショップで探すと、アンテナ一体型のいいのがある。これにしよう!

秋月 GPS受信機器キット
AE-GYSFDMAXB(販売終了?)
GPS受信機器キット

ただし、それなりのサイズのモジュールなので筐体が大きくなって、ステアリングコラムの上に付けると邪魔になりそう...だったらいっそのこと、取付場所を一般的な追加メーターみたいにダッシュボードとかにして液晶もでっかくすればいいんじゃないの?

▲目次に戻る

Ver2製作仕様と使用部品[2023-08-06]

以上から、ODB2データロガーVer2の製作仕様を決定する。

仕様

今回仕様の注目点は下記だ。

部品

ODB2データロガーVer2回路図製作仕様と使用部品
項目メーカー機器備考
制御EsspressifESP32-DevKitC
表示不明3.5カラー液晶(タッチパネル付)MSP3520SPI制御、SDカードスロット付、480x320x65Kcolor
電源日清紡マイクロデバイスNJM7805SDL1-TE112V->5V
CANトランシーバーMicrochip TechnologyMCP2562FD-E/P
GPS受信機器キット秋月電子(太陽誘電)AE-GYSFDMAXB販売終了したかも。電池バックアップはしない。
電子ブザーDB ProductsUDB-05LFPNいちおう、警告音を出せるように
CdSセル不明パーツボックスに転がってた。暗抵抗15kΩ程度照度制御用
タクトスイッチZHEJIANG JIANFU ELECTRONICSTVDP01G73ABハードウェアリセット用
タクトスイッチキャップCOSLANDKTSC-61-R
3.5mm4極ミニジャック基板取付用AVVICON ELECTRONICMJ-4PP-9用途:12V、GND、CAN-H、CANL-L
4極ミニプラグケーブル不明1m品。片側はバラケーブル
OBD2オスケーブル不明ケーブル長20cm程度。端部バラケーブル
抵抗不明各種
コンデンサー不明各種
ピンヘッダー不明適量

▲目次に戻る

回路図・基板図・実装[2023-08-06]

回路図

さくさくっと設計を進めよう。だけど、毎回のことながら、回路図は見栄えよくするのに苦労する。部品配置は実際の形に似せて配置したうえで配線は美しく...って無理ww わかりゃいいのよ。

ODB2データロガーVer2回路図
ODB2データロガーVer2回路図

基板図

今回は液晶が大きくなったため、基板面積も大きくできる。前回は2階建てになってけど、今回は1枚のみにして、かつ、組立後も筐体が薄くできるような部品配置を心がけよう。

ODB2データロガーVer2基板図
ODB2データロガーVer2基板図

実装

ODB2データロガーVer2基板オモテ面
ODB2データロガーVer2基板オモテ面
ODB2データロガーVer2基板ウラ面
ODB2データロガーVer2基板ウラ面

出来上がった基板に部品を実装していくよ。今回は、以下の方針だ。

ODB2データロガーVer2部品実装
ODB2データロガーVer2部品実装
ODB2データロガーVer2部品実装スペーサー
ODB2データロガーVer2部品実装スペーサー

OBD2で取得するデータ[2023-08-06]

どんなデータが取れるのか?

以前の調査では、「とにかくサポートしてるパラメータIDを教えて」への回答から取得できるデータを整理したが、今回は念のため、同じ方法で、ECU10(CAN-ID=18DA10F1)に絞り込んだ問い合わせしてみよう。

18DA10F1に、コマンド:0x02 0x01 ???? 0x00 0x00 0x00 0x00 0x00 を投げた際の回答
レスポンス後に続く
実データ
バイト数
モードパラメータID実データ各ビット
左からパラメータIDのアドレス0x01-0x20
ダミー
064100B63CA813550641001011 0110 0011 1100 1010 1000 0001 001155
064120B005A011550641201011 0000 0000 0101 1010 0000 0001 000155
06414072C28C01550641400111 0010 1100 0010 1000 1100 0000 000155
06416007114001550641600000 0111 0001 0001 0100 0000 0000 000155
06418000000002550641800000 0000 0000 0000 0000 0000 0000 001055

これを整理すると取得できるデータは下記となる。なんか増えてるが、最初から取得できるものだったのか、FLASHPROでプログラムを書き換えたから取得できるようになったのかはよくわからないが、よしとしておこう。(参考url:https://en.wikipedia.org/wiki/OBD-II_PIDs

FL1シビックのECU10から取得できるパラメータ
パラメータID
16進
データの種類バイト数換算式
01Monitor status4
03Fuel system status2
04エンジン負荷[%]1値*100/255
06Short term fuel trim[%]1値*100/255-100
07Long term fuel trim[%]1値*100/255-100
0BIntake manifold absolute pressure[kPa]1そのまま
0Cエンジン回転数[rpm]2(256*[バイト1]+[バイト2])/4
0D車速[km/hr]1そのまま
0E点火時期(上死点前)[度]1値/2-64
11アクセル開度[%]1値*100/255
13O2センサ位置1各ビットが1のところ
15O2センサ2電圧V、燃料調整%2電圧:[バイト1]/200
燃料調整:100*[バイト2]/128-100
1COBD standards1
1Fエンジン始動後経過時間[sec]2256*[バイト1]+[バイト2]
21走行距離[km]2256*[バイト1]+[バイト2]
23インジェクター燃料圧力[kPa]210*(256*[バイト1]+[バイト2])
24空燃比λ、センサー電圧V4λ:(256*[バイト1]+[バイト2])/32768
電圧:(256*[バイト3]+[バイト4])/8192
2ECommanded evaporative purge[%]1100*[バイト1]/255
30Warm-ups since codes cleared1[バイト1]
31Distance traveled since codes cleared[km]2256*[バイト1]+[バイト2]
33気圧(絶対値)[kPa]1[バイト1]
3C触媒温度[℃]2(256*[バイト1]+[バイト2])/10-40
42制御モジュール電圧[V]2(256*[バイト1]+[バイト2])/1000
43絶対的な負荷率[%]2100*(256*[バイト1]+[バイト2])/255
44空燃比λの狙い値2(256*[バイト1]+[バイト2])/32768
47スロットル開度絶対値B[%]1100*[バイト1]/255
49アクセルペダル位置D「%]1100*[バイト1]/255
4Aアクセルペダル位置E[%]1100*[バイト1]/255
4F空燃比最大値、センサ電圧最大値[V]、センサ電流最大値[mA]、吸気圧最大値[kPa]4空燃比:10*[バイト1」
電圧:10*[バイト2」
電流:10*[バイト1」
圧力:10*[バイト4」
51燃料のタイプ(ガソリン、軽油、ハイブリッドガソリン...とか)1
55排ガス酸素センサ(?)調整値(短期)バンク1・32100*[バイト1]/128-100
100*[バイト1]/128-100
56排ガス酸素センサ(?)調整値(長期)バンク1・32100*[バイト1]/128-100
100*[バイト1]/128-100
66空気流量[g/s]5バイト1:ビットでセンサA/Bの有効無効
センサA:(256*「バイト2]+[バイト3])/32
センサB:(256*「バイト4]+[バイト5])/32
67冷却水温度[℃]3バイト1:ビットでセンサ1/2の有効無効
センサ1:「バイト2]-40
センサ2:「バイト3]-40
68吸気温度[℃]
※問い合わせではデータを出せると言われたが実際にはデータを出してくれない
3バイト1:ビットでセンサ1/2の有効無効
センサ1:「バイト2]-40
センサ2:「バイト3]-40
6Cスロットル制御狙い値とスロットル位置5
70加給圧制御10
72ウェストゲート制御5
9FFuel System Percentage Use

取得するデータ

いろんな面白そうなデータもあるが、今回は以下のデータを取得し、表示・ロギングできるようにする。まぁ、気が向いたらその他のデータも取得できるしね。

ODB2データロガーVer2で取得するデータ
項目備考
インマニ圧力[kPa_abs]OBD2より。PID:0x0B。CAN-ID:0x18DA10F1
エンジン回転数[rpm]OBD2より。PID:0x0C。CAN-ID:0x18DA10F1
スロットル開度[%]OBD2より。PID:0x11。CAN-ID:0x18DA10F1
緯度・経度[deg]GPSより。
時刻GPSより。

▲目次に戻る

動作イメージ[2023-08-06]

今回はさくっと動いた。やはり先人の資料とライブラリが整っていたのが大きいところだ。

データ表示イメージ

▲目次に戻る

完成

ODB2データロガーVer2_3Dモデル
ODB2データロガーVer2_3Dモデル
ODB2データロガーVer2_自在角度ブラケット構造
ODB2データロガーVer2__自在角度ブラケット構造
ODB2データロガーVer2_完成品
ODB2データロガーVer2_完成品
ODB2データロガーVer2_裏面
ODB2データロガーVer2_裏面
ODB2データロガーVer2_取付
ODB2データロガーVer2_取付

完成だ。筐体に関する今回の注目点は以下の2点だ。

自在角度ブラケット
球凹面と球体を面接触させ、それをねじで密着させることにより自由な角度で固定する。
筐体の厚さを抑えるためには几帳面に囲わない
裏面右下のベージュ色の部品はGPSアンテナだ。受信感度としては筐体内に収めても問題ないが、収めることで3mmくらい筐体厚さが増えてしまうので筐体を切り欠いて露出させている。
そのイメージは、SC57初代(愛車。2004年モデル)とSC57マイナーチェンジ後(2006年モデル)のアンダーカウルの違いから発想を得ている(実は、結構悔しい思いをしているぞww。几帳面に囲わなくなっただけでカウル全体がスリムになって恰好よくなったじゃないか)
CBR1000RR_SC57_2004
CBR1000RR_SC57_2004
CBR1000RR_SC57_2006
CBR1000RR_SC57_2006

▲目次に戻る

ソースコード[2023-08-13]

#include <FS.h>
#include <SD.h>
#include <SPI.h>
#include <time.h>
#include "CAN.h"        // "CAN by Sandeep Mistry"
#include "Free_Fonts.h" // "TFT_eSPI by Bodmer" 
#include "TFT_eSPI.h"   // "TFT_eSPI by Bodmer"
#include "TinyGPS.h"    // "TinyGPS by Mikal Hart"

//use Dual Core
TaskHandle_t thp[1];

#define pi 3.1415927

//io pin
// LCD&TouchPanel = HSPI
// Modified "User_Setup.h" in "TFT_eSPI"
// #define TFT_MISO 12(LCD = no wire, Touch = wired)
// #define TFT_MOSI 13
// #define TFT_SCLK 14
// #define TFT_CS   15
// #define TFT_DC    4
// #define TFT_RST   2
// #define TOUCH_CS 27
// #define SPI_FREQUENCY  40000000
// #define USE_HSPI_PORT
const byte SDmiso  = 19;  //SD card @VSPI
const byte SDmosi  = 23;  //SD
const byte SDsck   = 18;  //SD
const byte SDcs    =  5;  //SD
const byte CANrx   = 32;  //CAN
const byte CANtx   = 33;  //CAN
const byte LCDled  = 25;  //LCD backlight PWM
const byte LUM     = 35;  //luminance sensor
const byte BZR1    = 26;  //Buzzer pin
const int  BZR2    = 5800; // rpm for buzzer

//LCD initialize
#define CALIBRATION_FILE "/TouchCalData2"  //touch caliblation data at internal Flash
TFT_eSPI tft = TFT_eSPI();
const float LUM0 = 900; //backlight luminance change level
const int   LUM1 = 255;  //backlight PWM @ day  
const int   LUM2 =  40;  //backlight PWM @ night

//circle meter
const int Gdata[2][2] = {
    {190, 150} // outer meter radius for IntakePress
  , {145, 105} // inner meter radius for Throttle
};
const int GdataX = 240;   //center x position
const int GdataY = 220;   //center y posision
const int GdataS =  80;   //start deg
const int GdataE = 280;   //end deg
const int Gstep  =   4;   //Graphic step at deg
int    GdataP[2] = {0, 0}; //graph position memory
unsigned long Gcolor[200]; //Graphic color in each deg

// on,off button data
TFT_eSPI_Button KEY[2];
const int KEYx[2] = {180, 300}; //Button x position
const int KEYy    = 285;        //Button Y position
const int KEYw    = 80;         //Button Wide
const int KEYh    = 60;         //Button Height
byte Status       = 0;          //OFF(initial)
char* KEYstr[2]   = {"ON", "OFF"};
const int KEYc[2][2][3] = {
  //status0:frame,backbround,string     status1  
   {{TFT_WHITE, TFT_BLACK, TFT_WHITE},{TFT_WHITE, TFT_ORANGE, TFT_BLACK}}
  ,{{TFT_WHITE, TFT_CYAN , TFT_BLACK},{TFT_WHITE, TFT_BLACK , TFT_WHITE}}
};

// SD parameter
SPIClass vspi(VSPI);
int  FileNum = 0;  //file name number
byte SDlog   = 0;  //logging status
File LogData;
String FileTemp;

// CAN parameter
unsigned long CANtrgt = 0x18DA10F1;    //CAN-ID
byte CANpid[3] = {0x0B , 0x11 , 0x0C}; //PID  IntakePress, Throttle, rpm
float CANdata;      //OBD2 data

// GPS parameter
TinyGPS GPS;
float GPSlat, GPSlon;  //latitude, longitude
unsigned long GPSage, GPSdate, GPStm;
int    GPSyear;
byte   GPSmon, GPSday, GPShour, GPSmin, GPSsec, GPShdrd;
byte   GPSrcv  = 0;
byte   GPSstat = 0;
char*  GPSstr  = "GPS";
char   timestr[20];
tm     GPStime;    // GPS time (tm_wday, tm_year, tm_mon, tm_mday, tm_hour, tm_min, tm_sec)
tm*    GPSltime;   // local time (tm_year, tm_mon+1, tm_mday, wd[tm_wday], tm_hour, tm_min, tm_sec)
time_t GPSutime;   // unix timestamp
const int GPSclr[2] = {TFT_BLACK, TFT_GREENYELLOW};
const int GPSx      = 240; //"GPS" x position
const int GPSy      = 232; //"GPS" Y position

void setup() {
  Serial.begin(115200); //for serial monitor
  Serial2.begin(9600);  //for GPS
  
  //init.buzzer pin
  pinMode(BZR1, OUTPUT);
  digitalWrite(BZR1, LOW);
  
  //LCD initialize
  tft.init();
  tft.setRotation(3);        //horizontal & upper side SD
  analogWrite(LCDled, 0);    //back light off
  tft.fillScreen(TFT_BLACK);

  //TouchPanel initialize
  uint16_t calData[5];
  uint8_t  calDataOK = 0;
  // check file system exists
  if (!SPIFFS.begin()) {
    Serial.println("Formating file system");
    SPIFFS.format();
    SPIFFS.begin();
  }
  // check if calibration file exists and size is correct
  if (SPIFFS.exists(CALIBRATION_FILE)) {
    File f = SPIFFS.open(CALIBRATION_FILE, "r");
    if (f) {
      if (f.readBytes((char *)calData, 14) == 14)
        calDataOK = 1;
      f.close();
    }
  }
  if (calDataOK) {
    // calibration data valid
    tft.setTouch(calData);
  }

  //Graph bar color initialize
  float ChangeStep = (GdataE-GdataS)/100; //value of 1%
  for (int i=0; i<GdataE-GdataS; i++) { // i : deg value
    int RED   = 255;
    int GREEN = 255;
    int BLUE  = 255;
    // 0 - 25% : #00FF00-#FAFFFA
    if (i<=25*ChangeStep) {
      RED  = floor(255/25 * i/ChangeStep);
      BLUE = floor(255/25 * i/ChangeStep);
    }
    // 25 - 40% : white
    // 40 - 70% : #FFFFFF-#FFFF05
    if (i>=40*ChangeStep && i<=70*ChangeStep) {
      BLUE = 255-floor(255/30 * (i-40*ChangeStep)/ChangeStep);
    }
    // 70% -  : #FFFA00-#FF0000
    if (i>70*ChangeStep) {
      BLUE = 0;
      GREEN = 255-floor(255/30 * (i-70*ChangeStep)/ChangeStep);
    }
    Gcolor[i] = tft.color565(RED, GREEN, BLUE);  //R:5bit+G:6bit+B:5bit
  }

  //draw outer meter frame
  for (int i=GdataS; i<=GdataE; i=i+10) {
    int X1 = floor(GdataX-(Gdata[0][0]+10)*sin((float)i/180 * pi)+0.5);
    int Y1 = floor(GdataY+(Gdata[0][0]+10)*cos((float)i/180 * pi)+0.5);
    tft.drawWideLine(X1, Y1, GdataX, GdataY, 3, TFT_WHITE);
  }
  tft.fillCircle(GdataX, GdataY, Gdata[0][0]+4, TFT_BLACK); //erace line
  tft.drawSmoothArc(GdataX, GdataY, Gdata[0][0]+5, Gdata[0][0]+4, GdataS, GdataE, TFT_WHITE, TFT_WHITE, false);
  tft.setTextDatum(MC_DATUM);  //text posision Middle Center defined in "TFT_eSPI.h"
  tft.setTextColor(TFT_WHITE, TFT_BLACK);  //font, background
	tft.setFreeFont(FF21);  // see \TFT_eSPI\Fonts\GFXFF\print.txt
  tft.drawString(     "0",  28, 252); //Press
  tft.drawString(    "50",  70,  82);
  tft.drawString("100kPa", 242,   8);
  tft.drawString(   "150", 412,  82);
  tft.drawString(   "200", 458, 252);
  
  //draw inner meter frame 
  for (int i=GdataS; i<=GdataE; i=i+20) {
    int X1 = floor(GdataX-(Gdata[1][1]-5)*sin((float)i/180 * pi)+0.5);
    int Y1 = floor(GdataY+(Gdata[1][1]-5)*cos((float)i/180 * pi)+0.5);
    tft.drawWideLine(X1, Y1, GdataX, GdataY, 3, TFT_WHITE);
  }
  tft.fillCircle(GdataX, GdataY, Gdata[1][1]-10, TFT_BLACK); // erace line
  tft.drawSmoothArc(GdataX, GdataY, Gdata[1][1]-4, Gdata[1][1]-5, GdataS, GdataE, TFT_WHITE, TFT_WHITE, false);
  tft.drawString(  "0", 157, 232);
  tft.drawString("50%", 240, 136);
  tft.drawString("100", 313, 232);

  //draw logging sw
  FuncDrawKeypad(); // Function drawkeypad()

  // backlight on day level
  analogWrite(LCDled, LUM1);

  //SDcard initialize
  vspi.end();
  vspi.begin(SDsck, SDmiso, SDmosi, SDcs);
  if (!SD.begin(SDcs, vspi, 12000000, "/sd", 5)) {
    Serial.println("SD init error!");
  }
  File dir = SD.open("/");
  while(1){
    File entry =  dir.openNextFile();
    if(!entry){break;}
    if (!entry.isDirectory()) {
      FileTemp = entry.name();
    }
    entry.close();
    FileNum = FileTemp.toInt()+1; // for file name by serial number
  }

  //CAN initialize
  CAN.setPins(CANrx, CANtx);
  if (!CAN.begin(500E3)) {
    Serial.println("CAN-BUS init error!");
  }
  //for ESP32 setup register bit of CAN speed factor
  volatile uint32_t* pREG_IER = (volatile uint32_t*)0x3ff6b010; //force bit4=0
  *pREG_IER &= ~(uint8_t)0x10;

  //LCD starting Effect
  for (int i=GdataS; i<GdataE; i=i+Gstep) {
    int COL = i-GdataS;
    tft.drawSmoothArc(GdataX, GdataY, Gdata[0][0], Gdata[0][1], i, i+Gstep, Gcolor[COL], Gcolor[COL], false);
    tft.drawSmoothArc(GdataX, GdataY, Gdata[1][0], Gdata[1][1], i, i+Gstep, Gcolor[COL], Gcolor[COL], false);
    delay(20);
  }
  for (int j=GdataE; j>GdataS; j=j-Gstep) {
    tft.drawSmoothArc(GdataX, GdataY, Gdata[0][0], Gdata[0][1], j-Gstep, j, TFT_BLACK, TFT_BLACK, false);
    tft.drawSmoothArc(GdataX, GdataY, Gdata[1][0], Gdata[1][1], j-Gstep, j, TFT_BLACK, TFT_BLACK, false);
    delay(50);
  }

  //core0a enable
  xTaskCreatePinnedToCore(Core0a, "Core0a", 4096, NULL, 3, &thp[0], 0);

}

void loop() {
    
  //GPS indicate
  if (GPSstat != GPSrcv) {
    tft.setFreeFont(FF21);
    tft.setTextColor(GPSclr[GPSrcv], TFT_BLACK);  //font, background
    tft.drawString(GPSstr, GPSx, GPSy);
    GPSstat = GPSrcv;
  }

  // Exec CAN command
  for (int i=0; i<=2; i++) {
    //send command
    CAN.beginExtendedPacket(CANtrgt, 8);
    CAN.write(0x02);      // number of additional bytes
    CAN.write(0x01);      // show current data
    CAN.write(CANpid[i]); // PID parameter
    CAN.endPacket();
    while (
         CAN.parsePacket() == 0
      || CAN.read() < 3           // correct length
      || CAN.read() != 0x41       // correct mode
      || CAN.read() != CANpid[i]  // correct PID
    );
    switch (i) {
      //Intake Pressure
      case 0:
        CANdata = CAN.read(); //0-255kPa
        FuncDrawMeter(floor((GdataE - GdataS)*CANdata/200), 0); //0-200 = 0-200kPa, outermeter
        break;
      //Throttle
      case 1:
        CANdata = (float)100*CAN.read()/255; //0-100%
        FuncDrawMeter(floor((GdataE - GdataS)*CANdata/85), 1); //0-200 = 0-85%max, innermeter
        break;
      //rpm
      case 2:
        CANdata = ((float)256*CAN.read() + CAN.read())/4;
        if (CANdata>=BZR2) {digitalWrite(BZR1, HIGH);  tft.fillRect(GdataX-55, GdataY-60, 110, 55, TFT_RED);}
        if (CANdata<BZR2)  {digitalWrite(BZR1, LOW);   tft.fillRect(GdataX-55, GdataY-60, 110, 55, TFT_BLACK);}
        break;
    }
    
    // start data logging
    if (SDlog == 0 && Status == 1) {
      //status: no logging & switch on
      String Fname = "/";
      Fname.concat(String(FileNum)); Fname.concat(".csv");
      LogData = SD.open(Fname, FILE_APPEND);   //file open
      SDlog = 1;     // data logging start.
      FuncGPSdata(); // output GPSposition,time
    }
    
    // data loging. output to SDcard
    if (SDlog == 1) {
      // output to SD
      LogData.print(millis());LogData.print(",");LogData.print(CANpid[i]);
      LogData.print(",");LogData.println(CANdata);
      // if push "STOP" bottun, to stop data logging
      if (Status == 0) { //switch off
        FuncGPSdata(); // output GPSposition,time
        LogData.close();
        SDlog = 0; // stop logging
        FileNum++;
      }
    }
  }

  //push touch key?
  uint16_t touchX = 0, touchY = 0;
  bool keyPress = tft.getTouch(&touchX, &touchY);
  for (int i=0; i<2; i++) {
    if (keyPress && KEY[i].contains(touchX, touchY)) {
      if (i==1 && Status==0) {Status = 1;} // start logging
      if (i==0 && Status==1) {Status = 0;} // end logging
      FuncDrawKeypad();
    }
  }

  unsigned long start1 = millis();
  while (millis() - start1 < 100) {}
}

void Core0a(void *args) {
  while (1) {
    delay(1);
    
    //backlight controll
    float LUMread = analogRead(LUM);
    if (LUMread>LUM0) {analogWrite(LCDled, LUM1);} else {analogWrite(LCDled, LUM2);}

    //GPS
    unsigned long start2 = millis();
    while (Serial2.available() == 0 && millis()-start2<2000) {}
    if (millis()-start2>=2000) {
      // time out
      GPSrcv =0;
    } else if (GPS.encode(Serial2.read())) {
      GPSrcv = 1;
      GPS.f_get_position(&GPSlat, &GPSlon, &GPSage); //latitude longitude age
      GPS.crack_datetime(&GPSyear, &GPSmon, &GPSday, &GPShour, &GPSmin, &GPSsec, &GPShdrd, &GPSage);
      GPStime.tm_year = GPSyear-1900; //1900 is 0
      GPStime.tm_mon  = GPSmon-1;     //jan is 0
      GPStime.tm_mday = GPSday;
      GPStime.tm_hour = GPShour+9;    //japan
      GPStime.tm_min  = GPSmin;
      GPStime.tm_sec  = GPSsec;
      GPSutime = mktime(&GPStime);      //create unix timestamp
      GPSltime = localtime(&GPSutime);  //create local time
      sprintf(
          timestr, "%4d-%02d-%02d %02d:%02d:%02d"
        , GPSltime->tm_year+1900, GPSltime->tm_mon+1, GPSltime->tm_mday
        , GPSltime->tm_hour,      GPSltime->tm_min,   GPSltime->tm_sec
      );
    }
  }
}

//Function draw button
void FuncDrawKeypad() {
  tft.setFreeFont(FF22);
  for(int i=0; i<2; i++) {
    KEY[i].initButton(
       &tft, KEYx[i], KEYy, KEYw, KEYh, KEYc[i][Status][0]
      ,KEYc[i][Status][1], KEYc[i][Status][2], KEYstr[i], 1
    );
    KEY[i].drawButton();
  }
}

//Function Draw Meter
// DM1 : data value   0-max
// DM2 : data channel 0,1
void FuncDrawMeter(int DM1, byte DM2) {
  if (DM1 != GdataP[DM2]) {
    int DMdeg0 = GdataS + GdataP[DM2];
    int DMdeg1 = GdataS + DM1;
    if (DMdeg1 > DMdeg0) {
      //increase graph bar
      for (int DMi= DMdeg0; DMi<DMdeg1; DMi=DMi+Gstep) {
        int COL = DMi-GdataS;
        tft.drawSmoothArc(
            GdataX, GdataY, Gdata[DM2][0], Gdata[DM2][1]
          , DMi, DMi+Gstep, Gcolor[COL], Gcolor[COL], false
        );
      }
    } else {
      //decrease graph bar
      tft.drawSmoothArc(
          GdataX, GdataY, Gdata[DM2][0], Gdata[DM2][1]
        , DMdeg1, DMdeg0+Gstep, TFT_BLACK, TFT_BLACK, false
      );
    }
    GdataP[DM2] = DM1;  //store position of GraphBar 0-200
  }
}

void FuncGPSdata() {
  //if GPS enable, output location and time
  if (GPSstat == 1) {
    LogData.print(GPSlat, 5); LogData.print(","); LogData.print(GPSlon, 5);
    LogData.print(","); LogData.println(timestr);
  }
}

▲目次に戻る