//-------------------------------------------------------------------------
// Swimming beacon for M5StickC Plus
//-------------------------------------------------------------------------
//-------------------------------------------------------------------------
// Declarations
//-------------------------------------------------------------------------
// ----- Libraries
#include <M5StickCPlus.h>
#include <TinyGPSPlus.h>
#include <HardwareSerial.h>
#include <QMC5883LCompass.h>
#include <Math3D.h>
#include <Wire.h>
#include <OneButton.h>
// ----- Communication parameters
static const int RXPin = 33, TXPin = 32;
static const int HATSDAPin = 0, HATSCLPin = 26;
static const uint32_t GPSBaud = 9600;
static const int n_avg = 4;
// ----- Variables UI
int ledPin = G10;
OneButton btnA = OneButton(M5_BUTTON_HOME, true);
OneButton btnB = OneButton(M5_BUTTON_RST, true);
// ----- Sensor settings
QMC5883LCompass compass; // QMC5883L compass on HAT
TinyGPSPlus gps; // TinyGPSPlus object
HardwareSerial ss(2); // Serial communication to the GPS device
// ----- Variables GPS
int currentYear, currentMonth, currentDay;
int currentHour, currentMinute, currentSecond, currentCentisecond;
double currentLat, currentLng;
double destinationLat, destinationLng;
// ----- Variables compass
double offset = 0; // offset for compass
double declination = 13.9167; // declination for Edmonton, AB, Canada
float magnetometerX;
float magnetometerY;
float magnetometerZ;
float compassX;
float compassY;
float compassZ;
float bearing;
// ----- Variables accelerometer
float accelerometerX;
float accelerometerY;
float accelerometerZ;
// ----- Variables gyroscope
float gyroscopeX;
float gyroscopeY;
float gyroscopeZ;
// ----- Variables AHRS
float pitch;
float roll;
float yaw;
// ----- Variables beacon navigation
double goal_distance = 0.10; //
int earthRadius = 6371; // mean radius of the earth
double current_course = 0; // GPS based course
double destination_course = 0; // GPS based course
double distance_to_destination = 0; // GPS based distance to destination
double bearing_to_destination; // difference between bearing and target direction
double course_to_destination; // difference between course and target direction
//-------------------------------------------------------------------------
// Setup
//-------------------------------------------------------------------------
void setup() {
// ----- M5StickC
M5.begin();
M5.Lcd.setRotation(0);
M5.Lcd.fillScreen(BLACK);
M5.Lcd.setTextSize(2);
M5.Lcd.setCursor(0, 15);
M5.Lcd.setTextColor(RED);
M5.Lcd.println("Loading...");
delay(500);
// ----- IMU
M5.IMU.Init();
M5.Lcd.println("IMU ok");
delay(500);
// ----- GPS
ss.begin(GPSBaud, SERIAL_8N1, RXPin, TXPin);
M5.Lcd.println("GPS ok");
delay(500);
// ----- Compass
Wire.begin(HATSDAPin, HATSCLPin);
compass.init();
M5.Lcd.println("Compass ok");
delay(500);
// ----- LED
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, HIGH);
M5.Lcd.println("LED ok");
delay(500);
// ----- Button handles
btnA.attachClick(btnAClick);
btnA.setDebounceTicks(40);
btnB.attachClick(btnBClick);
btnB.setDebounceTicks(25);
M5.Lcd.println("Buttons ok");
delay(500);
}
//-------------------------------------------------------------------------
// Main loop
// run >> set destination >> run
//-------------------------------------------------------------------------
void loop() {
delay(100);
digitalWrite (ledPin, HIGH);
// ----- Poll for button press
btnA.tick();
btnB.tick();
// ----- Update data
getMagnetometerValues();
getAccelerometerValues();
while (ss.available() > 0) {
gps.encode(ss.read());
readGPS();
}
// ----- Heading calculation
headingFromCompass();
headingFromGPS();
// ----- Update navigation
current_course = gps.course.deg();
course_to_destination = gps.courseTo(currentLat, currentLng, destinationLat, destinationLng);
distance_to_destination = gps.distanceBetween(currentLat, currentLng, destinationLat, destinationLng);
course_to_destination = current_course - course_to_destination;
bearing_to_destination = bearing - course_to_destination;
// ----- Update data
displayInfo_LCD();
}
//-------------------------------------------------------------------------
// Data acquisition functions
//-------------------------------------------------------------------------
// ----- Read GPS data
void readGPS() {
//location
if (gps.location.isValid()) {
currentLat = gps.location.lat();
currentLng = gps.location.lng();
} else {
currentLat = 0;
currentLng = 0;
}
}
// ----- Read magnetometer data
void getMagnetometerValues() {
//magnetometer data
compass.read();
magnetometerX = compass.getX();
magnetometerY = compass.getY();
magnetometerZ = compass.getZ();
//calibrated data
//MAG_calibrated = (MAG - b) * A;
Vec3 b; // b = [-338.8024 -203.7892 116.4398];
b.x = -338.8024; b.y = -203.7892; b.z = 116.4398;
M3x3 A; // A = [0.9609 -0.0038 0.0084; -0.0038 0.9451 0.0017; 0.0084 0.0017 1.1012];
A.a11 = 0.9609; A.a12 = -0.0038; A.a13 = 0.0084;
A.a21 = -0.0038; A.a22 = 0.9451; A.a23 = 0.0017;
A.a31 = 0.0084; A.a32 = 0.0017; A.a33 = 1.1012;
Vec3 MAG;
MAG.x = magnetometerX; MAG.y = magnetometerY; MAG.z = magnetometerZ;
Vec3 MAG_calibrated = Normalize(Rotate(Sub(MAG, b), A));
//compass data
compassX = MAG_calibrated.y;
compassY = MAG_calibrated.x;
compassZ = -MAG_calibrated.z;
}
// ----- Read accelerometer data
void getAccelerometerValues() {
M5.IMU.getAccelData(&accelerometerX, &accelerometerY, &accelerometerZ);
}
//-------------------------------------------------------------------------
// Functions to perform calculations
//-------------------------------------------------------------------------
// ----- Destination calculator (based on http://www.efalk.org/Navigation/math.html#toc0)
void calculateDestination(double latitude_start, double longitude_start, double distance, double course, double &latitude_end, double &longitude_end) {
double Bs = radians(latitude_start); // Latitude at start, radians
double Ls = radians(longitude_start); // Longitude at start, radians
double D = distance / earthRadius; // Distance to destination, radians
double hdg = radians(course); // Heading to destination, radians
double Dx = D * sin(hdg);
double Dy = D * cos(hdg);
double B = Bs + Dy;
double L = Ls + Dx / cos(B);
latitude_end = degrees(B);
longitude_end = degrees(L);
}
// ----- Compass heading with tilt compensation
void headingFromCompass() {
Vec3 CMP; // calibrated compass
CMP.x = compassX;
CMP.y = compassY;
CMP.z = compassZ;
Vec3 ACC;
ACC.x = accelerometerX;
ACC.y = accelerometerY;
ACC.z = accelerometerZ;
Vec3 ACCn = Normalize(ACC);
float d = DotProd(CMP, ACCn);
Vec3 MAGw = Sub(CMP, Mul(d, ACCn));
bearing = degrees(atan2(MAGw.y, MAGw.x)) - offset - declination;
}
// ----- GPS heading
void headingFromGPS() {
// to destination
distance_to_destination = gps.distanceBetween(gps.location.lat(), gps.location.lng(), destinationLat, destinationLng);
course_to_destination = gps.courseTo(gps.location.lat(), gps.location.lng(), destinationLat, destinationLng);
}
//-------------------------------------------------------------------------
// Display info
//-------------------------------------------------------------------------
void displayInfo_LCD() {
M5.Lcd.fillScreen(BLACK);
M5.Lcd.setTextSize(2);
//directions
M5.Lcd.setCursor(0, 15);
M5.Lcd.setTextColor(YELLOW);
M5.Lcd.println(F("Direction: "));
M5.Lcd.setTextColor(WHITE);
M5.Lcd.print(bearing_to_destination,1);
M5.Lcd.println(F(" deg"));
M5.Lcd.print(course_to_destination,1);
M5.Lcd.println(F(" deg"));
//distance
M5.Lcd.setCursor(0, 200);
M5.Lcd.setTextColor(YELLOW);
M5.Lcd.println(F("Distance: "));
M5.Lcd.setTextColor(WHITE);
M5.Lcd.print(distance_to_destination,1);
M5.Lcd.println(F(" m"));
//vectors
drawVector_angle(course_to_destination, RED, 50);
drawVector_angle(bearing_to_destination, DARKGREY, 30);
}
//-------------------------------------------------------------------------
// Drawing functions
//-------------------------------------------------------------------------
// ----- Draw vector using ANGLE
void drawVector_angle(double angle, uint32_t color, int size) {
int centerX = M5.Lcd.width()/2;
int centerY = M5.Lcd.height()/2 + 10;
float angleA = angle;
float angleB = angle - 150;
float angleC = angle + 150;
Vec3 A;
A.x = centerX - sin(radians(angleA)) * size;
A.y = centerY - cos(radians(angleA)) * size;
A.z = 0;
Vec3 B;
B.x = centerX - sin(radians(angleB)) * size;
B.y = centerY - cos(radians(angleB)) * size;
B.z = 0;
Vec3 C;
C.x = centerX - sin(radians(angleC)) * size;
C.y = centerY - cos(radians(angleC)) * size;
C.z = 0;
M5.Lcd.fillTriangle(A.x, A.y, B.x, B.y, C.x, C.y, color);
}
// -----------------------------------
// Button functions
// -----------------------------------
// ----- RST button > power off
void btnBClick() {
M5.Axp.PowerOff();
digitalWrite (ledPin, HIGH);
M5.Lcd.fillScreen(BLACK);
M5.Lcd.setTextSize(3);
M5.Lcd.setTextColor(WHITE);
M5.Lcd.setCursor(0, 20);
M5.Lcd.println("home");
}
// ----- HOME button > change state
void btnAClick() {
digitalWrite (ledPin, LOW);
delay(200);
calculateDestination(currentLat, currentLng, goal_distance, bearing, destinationLat, destinationLng);
destination_course = current_course;
delay(500);
M5.Beep.beep();
delay(500);
M5.Beep.end();
digitalWrite (ledPin, HIGH);
}
Comments