2024年10月1日火曜日

GPSロボットカー ESP32 自動走行

2019年に製作したESP32によるGPSロボットカー自動走行について記事にしました。
複数のウェイポイント(走行位置)をGPSデータの緯度・経度から方位、距離を計算して順番に走行します。
●GPS処理プログラムは当時、入江 博樹(熊本高等専門学校)公開プログラムを参考にしました。 





●ESP-32 NodeMCU開発ボード(技適製品)
●OLED SSD1306 0.96インチ  128 x 64 ディスプレイモジュール
●4WD L298N DC モータードライバ
●3.7V充電電池x2
  ESP32・モーター給電










GPS・センサー
●GPSモジュール(秋月電子)
●QMC5883L 3軸磁気センサー
   コンパス センサー
●VL53L0Xレーザー距離センサー
 障害物を検知すると一旦バック      して再走行します。




OLED表示
Lat:緯度
wy:目標緯度
Lon:経度
wx:目標経度
direct:方向
dr:距離の差分
dd:方向の差分
wp:ウエイポイント







図面



















Arduino IDE











プログラム
#include <math.h>                       // atn,pow関数用
#include <TinyGPS++.h>
#include <DFRobot_QMC5883.h>
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <ESP_Adafruit_SSD1306.h>
#include <VL53L0X.h>
VL53L0X gVL530X;
int dist;

#define OLED_RESET 4
#define RXD2 16 
#define TXD2 17

TinyGPSPlus gps;

Adafruit_SSD1306 display(OLED_RESET);
DFRobot_QMC5883 compass;


float headingDegrees;
int maxX = 2550;
int maxY = 7480;
int minX = -10150;
int minY = -5060;
//int offX = -6466;
//int offY = 4349;
int offX = 0;
int offY = 0;

//モータードライバDRV8833の接続

#define ENA 14
#define ENB 27
#define IN1 25
#define IN2 26
#define IN3 32
#define IN4 33
#define LEDPin   19

// ウェイポイント設定(基準点からのN,E方向への座標)、最高16ポイント
double wy[16];  
double wx[16];
int wp=0;                              // wp:0-15まで,ウェイポイントの数 -1
int wps=15;                             // wp ストップ ポイント
double wr=0.05;                         // ウェイポイントの半径
double ks=5;                           // もーたー切れ角比例係数
int spd=255;                           // car スピード
int spc;                               // スピード制御
int spcmin=0;                          // 最小値
int spcmax=spd;                        // 最大値
int led=0;
int gmap=0;                            //google map wp

// 走行処理関係変数
double gy, gx;                          // 北緯,東経を原点からの距離(メートル)に数値化
double ry, rx, rv, rd;                  // 北緯(正規化),東経(正規化),速度(ノット),方向(単位:度)の数値化
double dy, dx, dr, dd;                  // ウェイポイントに対する北緯,東経の差分(m),距離の差分,方位の差分



int pmode=0;                            // プログラムモード(メインメニューで分岐)
int dsp=1;                              // oled 1-GPS 2-WP
int deg_lat,deg_lon;
 
 
// GPS受信用変数,ポインタ
float latitude, longitude, mps, direct;   
double latit, longit, kn;               // 緯度,経度,ノット保存用の変数
double latitB, longitB, rdB;            // 差分で方位計算する時に前の座標保持用, 方位保管用
double dy2, dx2, rd2;                   // 一つ前の値との北緯,東経の差分(m),差分で求めた方位


 void map2() {
    digitalWrite(LEDPin,HIGH);
    gmap=1;     // google map wp mode
    wp=5;       // wp start point      
    wps=13;     // wp stop point
    wy[5] = 38.**4281;   //運動場駐車場2
    wx[5] = 141.**0814; 
    wy[6] = 38.**4187;  
    wx[6] = 141.**0833;
    wy[7] = 38.**4185; 
    wx[7] = 141.**0661;
    wy[8] = 38.**4118; 
    wx[8] = 141.**0676;
    wy[9] = 38.**4142; 
    wx[9] = 141.**0845;
    wy[10] = 38.**4024;  
    wx[10] = 141.**0865;
    wy[11] = 38.**4018;  
    wx[11] = 141.**0686;
    wy[12] = 38.**4276;  
    wx[12] = 141.**0635;
    wy[13] = 38.**4306; 
    wx[13] = 141.**0821;
    wy[14] = 0; 
    wx[14] = 0;
    wy[15] = 0; 
    wx[15] = 0; 
    delay(2000);  
    digitalWrite(LEDPin,LOW);   
 }
 

void Stop(){  
    ledcWrite(1, 0);  // stop   
    ledcWrite(2, 0);  
    
}

void R_forward(){     
    ledcWrite(1, spd - spc);    
    ledcWrite(2, spd );
    digitalWrite(IN1,LOW);
    digitalWrite(IN2,HIGH);
    digitalWrite(IN3,LOW);
    digitalWrite(IN4,HIGH);     
     
void L_forward(){     
    ledcWrite(1, spd );    
    ledcWrite(2, spd - spc);
    digitalWrite(IN1,LOW);
    digitalWrite(IN2,HIGH);
    digitalWrite(IN3,LOW);
    digitalWrite(IN4,HIGH);     
}   

void led_flash(){
  if(led==1){  
    led=0;
    digitalWrite(LEDPin,LOW);      
  }
  else{
    led=1;
    digitalWrite(LEDPin,HIGH);
  }
}


void gsp_oled(){
  if(dsp==0){
  display.clearDisplay();
  display.setTextSize(1); 
  display.setCursor(0,0); 
  display.print("Latitude  :");
  display.println(gps.location.lat(), 6);
  display.print("Longitude :");
  display.println(gps.location.lng(), 5);
  display.print("Satellites:");
  display.println(gps.satellites.value());
  display.print("Elevation :");
  display.print(gps.altitude.feet());
  display.println("ft"); 
  display.print("Time UTC  :");
  display.print(gps.time.hour());                       // GPS time UTC 
  display.print(":");
  display.print(gps.time.minute());                     // Minutes
  display.print(":");
  display.println(gps.time.second());                   // Seconds
  //display.print("course    :");
  //display.println(gps.course.deg());
  display.print("direct    :");
  display.println(direct);
  display.print("Speed km/h:");
  display.println(gps.speed.kmph());
  display.print("distance  :");
  display.println(dist);
  display.display();
  
  }
  if(dsp==1){
  display.clearDisplay();
  display.setTextSize(1);  
  display.setCursor(0,0); 
  display.print("Lat   : ");
  display.println(latitude, 6);
  display.print("wy[wp]: ");
  display.println(wy[wp], 6);  
  display.print("Lon   : ");
  display.println(longitude, 6);
  display.print("wx[wp]: ");
  display.println(wx[wp], 6); 
  display.print("direct: "); // 方向
  display.println(direct); 
  display.print("dr  : "); // 目標地点までのと距離
  display.println(dr*64);
  display.print("dd  : "); // 北に対する角度
  display.println(dd);  
  display.print("wp  : "); // 目標地点
  display.print(wp);                      
  display.display();
}
}

//--------------------------------------------------------------------------
//  GPS受信                                                     
//-------------------------------------------------------------------
void  recvGPS(){
      latitude=gps.location.lat(); //get latitude      
      longitude=gps.location.lng(); //get longtude            
      kn=gps.speed.knots();     // 速度 get
      //direct=gps.course.deg();   // 進行方向 get 
       
      gps_val();        //  GPS信号の数値返還             
      gps_cal();       // 現在地とウェイポイントとの関係を計算  
      delay(1);
    }


//-------------------------------------------------------------------------- 
//  GPS信号の数値返還       
//--------------------------------------------------------------------------
void gps_val(){
      latitB=latit; longitB=longit;  // 一つ前の値を保管
      latit=latitude;        
      longit=longitude;    
      deg_lat=(int)( latitude/100);        // 
      deg_lon=(int)( longitude/100);      // 
      
      rv=mps;      
      rdB=rd;                        // 一つ前の値を保管
      
      rd=direct;               // 方位を数値変換 
}

void gps_cal(){
      // 緯度経度の数値変換,目標地点との差分を計算
      dy=(wy[wp] - latit )*1860; 
      dx=(wx[wp] - longit)*1560;
      
      // 目標地点までのと距離を計算
      dr=sqrt(pow(dy,2)+pow(dx,2));
      
      // 目標地点への方向を計算 
      dd = atan(dx / dy);                 // 北に対する角度を求める(±π/2範囲)
      dd=dd*57;                           // ラジアン->度に変換 dd*(180/pai)
      // 0-360度に変換
      if (dx > 0 && dy < 0)     dd = 180 + dd;
      else if(dx < 0 && dy < 0) dd = 180 + dd;
      else if(dx < 0 && dy > 0) dd = 360 + dd;
      else;
 
      // GPSが正しい方位を出力しない場合は、前の座標との差分で計算
      dy2=(latit -latitB) *1860; 
      dx2=(longit-longitB)*1560;
      
      if (dy2==0 || dx2==0){                  // 緯度または経度のどちらかの変化が0の時
        if (dy2==0){ if (dx2<0) rd2= 270; else rd2=90; } // rd2= 0, 90, 180,270 のいずれかを直接与える.
        if (dx2==0){ if (dy2<0) rd2=-180; else rd2=0; } // dy2,dx2のどちらも0の時はrd2=0
      } else {
        // 目標地点への方向を計算
        rd2 = atan(dx2 / dy2);                 // 北に対する角度を求める(±π/2範囲)
        rd2=rd2*57;                           // ラジアン->度に変換 dd*(180/pai)
        // 0-360度に変換
        if (dx2 > 0 && dy2 < 0)     rd2 = 180 + rd2;
        else if(dx2 < 0 && dy2 < 0) rd2 = 180 + rd2;
        else if(dx2 < 0 && dy2 > 0) rd2 = 360 + rd2;
        else;
      }
      
      // 移動距離が小さくて使用するGPSが正しい速度と方向を出さない時のみ
      // 座標差から計算した値と置き換える(速度=0,方位前の値のまま,GPSの仕様により異なる)
      if (kn==0 && dx2!=0 && dy2!=0){  
        rd=rd2; 
      } 
      
      // 方位偏差の計算し,現在の進行方向から±180度の範囲に直す
      dd=dd-rd;
      if (dd>180) dd=dd-360;
      else if (dd<-180) dd=dd+360;  
}

//--------------------------------------------------------------------------
//  GPSによる走行
//  runMode=0:GPSにより方位が取れなくても偏差で計算
//  runMode=1:方位がとれない場合はニュートラルに戻す                                                     
//--------------------------------------------------------------------------
void runGPS(){  
      led_flash();    
      spc=abs(dd)*ks;
      spc=constrain(spc,spcmin,spcmax);
      if (dr < wr){
        if(wp>4){
          if(wp < wps){
          wp=wp+1;
        }
        }
      Stop();
    }
    else{  
      if(dd >= 0){
      R_forward();
       }
      else if(dd < 0){ 
      L_forward();      
      }      
      }   
    } 

void recvCompass(){
   Vector mag = compass.readRaw();
//  Serial.print(mag.XAxis);
//  Serial.print(",");
//  Serial.print(mag.YAxis);
//  Serial.print(",");
//  Serial.print(mag.ZAxis);
//  Serial.println(",");

  if (mag.XAxis > maxX)  maxX = mag.XAxis;
  if (mag.YAxis > maxY)  maxY = mag.YAxis;

  if (mag.XAxis < minX)  minX = mag.XAxis;
  if (mag.YAxis < minY)  minY = mag.YAxis;

//  Serial.print(maxX);
//  Serial.print(",");
//  Serial.print(maxY);
//  Serial.print(",");
//  Serial.print(minX);
//  Serial.print(",");
//  Serial.println(minY);

  Vector norm = compass.readNormalize();
  // Calculate heading
  float heading = atan2(norm.YAxis, norm.XAxis);
  float declinationAngle = (4.0 + (26.0 / 60.0)) / (180 / PI);
  heading += declinationAngle;
  // Correct for heading < 0deg and heading > 360deg
  if (heading < 0) {
    heading += 2 * PI;
  }
  if (heading > 2 * PI) {
    heading -= 2 * PI;
  }
  // Convert to degrees
  float headingDegrees = heading * 180 / PI;
  // Output
  ////  Serial.print(" Heading = ");
  //Serial.print(heading);
  //Serial.print(",");
  ////  Serial.print(" Degress = ");
  //Serial.print(headingDegrees);
  //Serial.println();  
  direct=headingDegrees;   // 進行方向 get      
}

void vl53(){
  dist = gVL530X.readRangeContinuousMillimeters();  
  if (gVL530X.timeoutOccurred()){ 
  }    
  if(dist < 400 ){
    ledcWrite(1, spd);   // back   
    ledcWrite(2, spd);           
    digitalWrite(IN1,HIGH);
    digitalWrite(IN2,LOW);
    digitalWrite(IN3,HIGH);
    digitalWrite(IN4,LOW);
    delay(1000);
    ledcWrite(1, spd);   // left  
    ledcWrite(2, spd);  
    digitalWrite(IN1,HIGH);
    digitalWrite(IN2,LOW);
    digitalWrite(IN3,LOW);
    digitalWrite(IN4,HIGH);     
    delay(500); 
    ledcWrite(1, spd);  // go    
    ledcWrite(2, spd); 
    digitalWrite(IN1,LOW);
    digitalWrite(IN2,HIGH);
    digitalWrite(IN3,LOW);
    digitalWrite(IN4,HIGH); 
    delay(500);       
    Stop();    
}
void setup() {   
  pinMode(ENA,OUTPUT);
  pinMode(ENB,OUTPUT);    
  pinMode(IN1,OUTPUT);
  pinMode(IN2,OUTPUT);  
  pinMode(IN3,OUTPUT);  
  pinMode(IN4,OUTPUT);  
  pinMode(LEDPin, OUTPUT);  
  map2();  
  digitalWrite(LEDPin,HIGH);  
  ledcSetup(1,490,8);     // モータ 0CH 490Hz 8bit=0-255(PWM)  
  ledcSetup(2,490,8);   
  ledcAttachPin(ENA, 1); // モータ 0CH
  ledcAttachPin(ENB, 2);   
  
  delay(1000); 
  Wire.begin(21,22);  // (SDA,SCL):ESP8266(IO_0)-OLED(SDA),(IO_1)-OLED(SCL)
  display.begin(SSD1306_SWITCHCAPVCC, 0x78>>1); // OLED ADDRESS  
  display.clearDisplay();
  display.setTextSize(1);  
  display.setTextColor(WHITE);                          // Set OLED color to White
  display.setCursor(0,0);                               // Set cursor to 0,0
  display.println("GPS example");  
  display.println(TinyGPSPlus::libraryVersion());
  display.display();
  delay(1500);                                          // Pause 1.5 seconds  
  gVL530X.init();
  gVL530X.setTimeout(1000);
  gVL530X.startContinuous(100);
  //Serial.begin(9600);
  Serial2 . begin ( 9600 ,   SERIAL_8N1 ,   RXD2 ,   TXD2 ) ;  
   while (!compass.begin()) {
    //Serial.println("Could not find a valid QMC5883 sensor, check wiring!");
    delay(500);
    //yield();
  }
  if(compass.isHMC()){    
    compass.setRange(HMC5883L_RANGE_1_3GA);
    compass.setMeasurementMode(HMC5883L_CONTINOUS);
    compass.setDataRate(HMC5883L_DATARATE_15HZ);
    compass.setSamples(HMC5883L_SAMPLES_8);
    }
  
  if (compass.isQMC()) {
    compass.setRange(QMC5883_RANGE_2GA);
    compass.setMeasurementMode(QMC5883_CONTINOUS);
    compass.setDataRate(QMC5883_DATARATE_50HZ);
    compass.setSamples(QMC5883_SAMPLES_8);
  }
}

void loop() {
   while (Serial2.available() > 0) {
    char c = Serial2.read();
    gps.encode(c);
    if (gps.location.isUpdated()) {   
    recvCompass();                   //direct コンパス方向検出                
    recvGPS(); 
    gsp_oled();    
    runGPS(); 
    vl53();   
  }     
  }   
}


0 件のコメント:

コメントを投稿

Raspberry Pi Donkey Car スマートカー

  2020年に製作したDonkey Car スマートカー について記事にしました。 Donkey CarはRaspberry Pi のカメラからコースを ディープラーニングさせ自動走行を行います。(動画は白線上を自動走行)   動画