/*
* Project MotoidIoT_v1.02.1
* Description: Motorcycle Telemetry system using a Particle Photon,
the Bosch BNO055 Absolute IMU, Adafruit Ultimate GPS Breakout board,
Adafruit SD Card Breakout board, and a two position switch. Adapted from Rossi telemetry system for
Arduino Mega board using the same sensor set. Adapted to work with Particle
for MEGR-3171 IoT Project. This version wifi disabled to focus on datalogging alone by default but
can be switched on. Wifi and reporting can be enabled by momentarily pressing the setup button once.
Wifi can then be disabled by pressing and holding the wifi button until the LED turns white then released.
* Author: John Sansbury
* Date: Mar 30,2017
*Revised: April 13, 2017
Connections
===========
BNO055 SD Card GPS Switch LED
Connect SCL to D1 Connect CS to A2 Connect TX to RX GND to side one Long leg (+) to D4
Connect SDA to D0 Connect DI to A5 Connect RX to TX 3V3 to side two Short Leg (-) to GND
Connect Vin to 3V3 Connect DO to A4 Connect GND to GND D7 to middle Use a 330 Ohm resistor
Connect GND to GND Connect CLK to A3 Connect VIN to 3V3 in series to reduce voltage
Connect GND to GND
Connect 3V to 3V3
BNO055 Board layout
+----------+
| *| RST PITCH ROLL HEADING
ADR |* *| SCL
INT |* *| SDA ^ /->
PS1 |* *| GND | |
PS0 |* *| 3VO Y Z--> \-X
| *| VIN
+----------+
Note for SD Card: Two position switch was added to allow for the SD card to be removed with out causing data
corruption. Data is only written when switch is in high position (D7 LED will be lit) otherwise loop becomes
paused until reactivated. Begins a new data file everytime switch is activated.
Note for BNO055: Calibration for BNO055 must be done prior to running this sketch for optiumum performance.
Accelerometer and Gyrometer output will be based upon these calibration values. The magnetometer will depend
upon the calibration values as well but will not give a proper heading until the device has been moved enough
for it to locate true north.
*/
/****************************************************************************************************************/
/*
WARNING: REMOVING SD CARD WITH SWITCH IN HIGH POSITION (D7 LED ON) CAN CAUSE SD CARD CORRUPTION AND A LOSS OF DATA
*/
/******************************************************************************************************************/
#include "Particle.h"
SYSTEM_MODE(SEMI_AUTOMATIC)
//SD Card Pre Code----------------------------------------------------------------------------------------------
#define CLK A3 // FOR SPI 52 on Mega, 13 on UNO, 24 on Particle (A3)
#define SDO A4 // AKA MISO 50 on Mega, 12 on UNO, 25 on Particle (A4)
#define SDI A5 // AKA MOSI 51 on Mega, 11 on UNO, 23 on Particle (A5)
#define SD_card A2 //SS for SD (Whatever pin you select), 2 on Particle (A2)
#include <SdFat.h>
SdFat SD; File dataFile; char filename[15]; // for SD card datalogging name
void dataWrite(); void nameFile(); void SD_cardSetup(); //declare function prototypes
//GPS Pre Code-----------------------------------------------------------------------------------------------------
#include <Adafruit_GPS.h>
USARTSerial& mySerial = Serial1;
Adafruit_GPS GPS(&mySerial);
uint timeZone = 4; //Changes depending on time zone and day light savings. Without daylight savings EST=4, PST=7. Add one more when not DST.
#define GPSECHO false // Set GPSECHO to 'false' to turn off echoing the GPS data to the Serial console
boolean usingInterrupt = false;
String gpsRead(); void gpsParse(); void proxHome(); //Decleration of function prototype
uint gpsFix=0; /*uint sat;*/ float lastSpeed; uint g=0; //String gpsString;
//BNO055 Pre Code--------------------------------------------------------------------------------------------------
#define BNO055_SAMPLERATE_DELAY_MS (10) /* Set the delay between fresh samples for BNO055 */
#include "Adafruit_BNO055.h"
#include "Adafruit_Sensor.h"
#include "imumaths.h"
Adafruit_BNO055 bno = Adafruit_BNO055(55); //Create object for BNO055 sensor
String orientation(); void setCal();
uint iteration=0; uint32_t timer = millis(); uint32_t gpsTimer = millis(); uint32_t reportTimer=millis();
uint gpsCount; uint f=0; uint atHome; bool wStatus; uint parseError=0; uint period; uint32_t lastIteration;
bool lastWstatus=FALSE; bool noWifi=TRUE; void flashError(); uint fileTime=30;//fileTime is the length you want the file to be in minutes
void wifiConnect(); void report();
//SETUP---------------------------------------------------------------------------------------------------------------------
void setup() {
pinMode(D7,INPUT);
pinMode(D4,OUTPUT);
System.on(button_status, button_handler);
wifiConnect(); //Currently defaults to wifi off to focus on datalogging but can be turned on via setup button
Serial.begin(115200);
/*GPS Sensor Setup-----------------------------------------------------------------------------------------------*/
//GPS.sendCommand(PMTK_SET_BAUD_57600); //Can change the baud rate to a higher speed but is prone to errors which can cause a temporary lose of fix.
GPS.begin(9600);
delay(1000);
//GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCONLY); //Time, Date, Position, Speed
GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA); //Contains fix and altitude data
GPS.sendCommand(PMTK_SET_NMEA_UPDATE_5HZ);
GPS.sendCommand(PMTK_API_SET_FIX_CTL_5HZ);
delay(1000);
/* Initialise BNO055 sensor------------------------------------------------------------------------------------- */
while(!bno.begin()) {
if(!noWifi && Particle.connected()) Particle.publish("BNO055 Sensor Setup", "Ooops, no BNO055 detected ... Check your wiring or I2C ADDR!");
flashError();
}
setCal(); /* Display some basic information on this sensor */
bno.setExtCrystalUse(true); //This allows the heading to be zeroed to north rather than start up position. Ensure this comes after all set up and calibration for sensor
/*SD Card Setup--------------------------------------------------------------------------------------------------*/
pinMode(SD_card,OUTPUT);
if (wStatus=digitalRead(D7)) SD_cardSetup();
timer=millis(); gpsTimer=millis(); reportTimer=millis();
}
void loop() {
if(gpsTimer > millis()) gpsTimer=millis();
if (timer > millis()) timer = millis();
if (reportTimer > millis()) timer = millis();
int elapsedTime = millis()-gpsTimer;
gpsParse(); // run gpsParse outside of timer loop so that GPS can parse as soon as a new status is available
gpsFix=GPS.fix;
wStatus=digitalRead(D7);
if (wStatus==1 && lastWstatus==FALSE && gpsFix==1) SD_cardSetup();
if (wStatus==0 && lastWstatus==TRUE){
dataFile.close();
lastWstatus=FALSE;
}
if(gpsFix==0) digitalWrite(D4,HIGH);
else digitalWrite(D4,LOW);
elapsedTime = millis() - timer;
if(elapsedTime > 9 && gpsFix==1) {
timer = millis(); // reset the timer
String dataString=0;
dataString += String(iteration); dataString += ",";
dataString += String(elapsedTime); dataString += ",";
dataString+=gpsRead();
dataString += orientation();
dataString += String(parseError); dataString += ",";
elapsedTime= millis()-timer;
dataString += String(elapsedTime);
if (wStatus==1){
dataWrite(dataString);
lastWstatus=TRUE;
iteration++;
}
}
elapsedTime=millis()-reportTimer;
//Only allow the wifi to be turned on and off (voluntarily)
if (System.buttonPushed() < 500 && System.buttonPushed() >5 && wStatus==0) {//click and release. LED turns blue and wifi will attempt to connect
//Only allowed to turn wifi on if not logging data so as not to interrup logging
if (noWifi==TRUE){
noWifi=FALSE;
wifiConnect();
}
}
else if (System.buttonPushed() > 500 && System.buttonPushed() <1500) {// with wifi on click and hold until LED turns white release and wifi will be off
if (noWifi==FALSE){
noWifi=TRUE;
wifiConnect();
}
}
if(!noWifi && !Particle.connected()){// detects if wifi connection has been lost and shuts off the wifi
//to prioritize data collection and ensure it doesnt get stuck looking for a signal
noWifi=TRUE;
wifiConnect();
}
if(elapsedTime >1999){
reportTimer=millis();
report();
}
if (iteration >= lastIteration) nameFile();
}
/* Below function is a a testing function that was used for troubleshooting communication between
the particles when communicating proximity. This allowed the relay on the reciever unit to be activated
without having to leave my house and instead just use a switch.*/
/* void loop() {
wStatus=digitalRead(D7);
if (wStatus==true) {
if (timer > millis()) timer = millis();
elapsedTime = millis() - timer;
if (elapsedTime > 1999){
report();
timer = millis();
}
}
else {
if (timer > millis()) timer = millis();
elapsedTime = millis() - timer;
if (elapsedTime > 1999){
report();
timer = millis();
}
}
}*/
void SD_cardSetup(){
while(!SD.begin(SD_card)) { //if system is attempting to write but SD card is not inserted or broken wait until working SD card inserted.
if(!noWifi && Particle.connected()) Particle.publish("SD Card Setup","SD Card failed, or not present.");
wStatus=digitalRead(D7);
if(!wStatus) return; //if sd card is not working but write status changes break from loop and return to main script
flashError();
}
strcpy(filename, "GPSLOG00.csv");
SdFile::dateTimeCallback(dateTime);// Calls time stamp function for the creation of SD card files
nameFile();
dataFile = SD.open(filename, FILE_WRITE);
if( ! dataFile ) {
if(!noWifi && Particle.connected()) Particle.publish("Couldnt create file:", filename);
flashError();
}
// Places the Headers in the file
dataFile.print( "Iteration, Cycle Time, Time, Date, Satellites, Fix, Quality, Latitude Degrees, Longitude Degrees, Speed, Delta Speed , Angle, Altitude, Heading, Pitch, Roll, Lat-Accel, Long-Accel, Vert-Accel, X-Accel, Y-Accel, Z-Accel,AV Heading, AV Pitch, AV Roll, Parse Error, Collection Time");
SdFile::dateTimeCallbackCancel();//Cancels the time stamp function for SD card files so that the time stamp shows creation time and not last modified time.
}
void dataWrite(String dataString){
while(!dataFile.print("\n")) {
while(!SD.begin(SD_card)) { // attempt to restart card
if(!noWifi && Particle.connected()) Particle.publish("SD Card Failure","SD Card failed, or not present.");
flashError();
}
}
dataFile.print(dataString);
}
void nameFile(){
for (uint8_t i = 0; i < 100; i++) {
filename[6] = '0' + i/10;
filename[7] = '0' + i%10;
if (filename[6]==9 && filename[7]==9 && !SD.exists(filename)) flashError();
// create if does not exist, do not open existing, write, sync after write
if (!SD.exists(filename)) {
if(!noWifi && Particle.connected()){
Particle.publish("Now writing to file:",filename);
Particle.process();
}
break;
}
}
iteration = 0; f=0;
}
void gpsParse(){
if (!usingInterrupt) { // in case you are not using the interrupt above, you'll need to 'hand query' the GPS, not suggested :(
char c = GPS.read(); // read data from the GPS in the 'main loop'
if (GPSECHO)
if (c) Serial.print(c);
}
if (GPS.newNMEAreceived()) { // if a sentence is received, we can check the checksum, parse it...
if (!GPS.parse(GPS.lastNMEA())) {
if (millis() - gpsTimer > 4999) {
if(!noWifi && Particle.connected()){
gpsTimer=millis();
Particle.publish("GPS", "{ error: \"failed to parse\"}", 60, PRIVATE );
Particle.process();
}
}
gpsTimer=millis();
parseError++;
return;// we can fail to parse a sentence in which case we should just wait for another
}
}
}
String gpsRead(){
float floatBuffer; String gpsString;
//GPS CODE-----------------------------------------------------------------------------------
int year=GPS.year, month=GPS.month, day=GPS.day, hour=GPS.hour;
uint endMonth [12]={31,28,31,30,31,30,31,31,30,31,30,31};
if((year%4)==0)endMonth[2]=29;
hour-=timeZone;
if (hour < 0){
hour = (hour + 24); // Corrects time and day when day changes for GST but not for location
if((day-1)<1){
if((month-1)<1){
month=12;
year-=1;
}
day=endMonth[month];
}
}
gpsString = String(hour); gpsString += ":";
gpsString += String(GPS.minute); gpsString += ":";
gpsString += String(GPS.seconds); gpsString +=",";
gpsString += String(month); gpsString += "/";
gpsString+= String(day); gpsString += "/";
gpsString += String(year); gpsString +=",";
gpsString+=String(GPS.satellites); gpsString +=",";
gpsString+=gpsFix; gpsString +=",";
gpsString+=String(GPS.fixquality); gpsString +=",";
gpsString +=String(GPS.latitudeDegrees)+",";
gpsString +=String(GPS.longitudeDegrees)+",";
floatBuffer = ((GPS.speed) * 1.1508); if (floatBuffer < 1) floatBuffer = 0; // Speed is given in knots and converted to MPH
gpsString+=String(floatBuffer); gpsString +=",";
int deltaSpeed = (floatBuffer - lastSpeed); lastSpeed = floatBuffer; //requires lastSpeed to be a global variable which took up to much space on Arduino. Will re-implement if enough RAM.
gpsString+=String(deltaSpeed); gpsString +=",";
gpsString+=String(GPS.angle); gpsString +=",";
gpsString+=String(GPS.altitude); gpsString +=",";
return gpsString;
}
String orientation(){
String dataString;
//Saves absolute orientation to the dataString
sensors_event_t event; bno.getEvent(&event);
dataString=String(event.orientation.x); dataString+=","; // Heading
dataString+=String(event.orientation.y); dataString+=","; //Pitch
dataString+=String(event.orientation.z); dataString+=","; //Roll
//dataString+=String(bno.getTemp()); //dataString+=","; //IMU temp sensor. Not accurate for ambient temps/
//Saves linear acceleration to the dataString
imu::Vector<3> lAccel = bno.getVector(Adafruit_BNO055::VECTOR_LINEARACCEL);
dataString+=String(lAccel.x()); dataString+=",";
dataString+=String(lAccel.y()); dataString+=",";
dataString+=String(lAccel.z()); dataString+=",";
//Saves angular acceleration to the dataString
imu::Vector<3> Accel = bno.getVector(Adafruit_BNO055::VECTOR_ACCELEROMETER);
dataString+=String(Accel.x()); dataString+=",";
dataString+=String(Accel.y()); dataString+=",";
dataString+=String(Accel.z()); dataString+=",";
//Saves angular acceleration to the dataString
imu::Vector<3> vAngle = bno.getVector(Adafruit_BNO055::VECTOR_GYROSCOPE);
dataString+=String(vAngle.x()); dataString+=",";
dataString+=String(vAngle.y()); dataString+=",";
dataString+=String(vAngle.z()); dataString+=",";
return dataString;
}
void setCal(){
// Calibration sketch must be run for each individual sensor prior to upload and then calibration values copied into this sketch.
delay(25);
uint8_t* calibData;
calibData[0] = 245;
calibData[1] = 255;
calibData[2] = 229;
calibData[3] = 255;
calibData[4] = 255;
calibData[5] = 255;
calibData[6] = 253;
calibData[7] = 255;
calibData[8] = 255;
calibData[9] = 255;
calibData[10] = 0;
calibData[11] = 0;
calibData[12] = 2;
calibData[13] = 255;
calibData[14] = 5;
calibData[15] = 1;
calibData[16] = 246;
calibData[17] = 0;
calibData[18] = 232;
calibData[19] = 3;
calibData[20] = 68;
calibData[21] = 3;
bno.setSensorOffsets(calibData);
delay(25);
}
void button_handler(system_event_t event, int duration, void* )
{
//This function listens for the setup button being pressed and counts the time it is pressed.
//In the loop there are two if statements that use this information
//This is based off the built in system function from the Particle and was developed from code in the
//system documents.
}
void proxHome(){
/*
.1°=27,551.8 ft
.01°=2,755.18 ft
.001°=275.518 ft
.0001°=27.5518 ft
.00001°=2.75518 ft
*/
double homeLat=-------; double homeLon=---------;
double latHigh=homeLat+.0005 double latLow=homeLat-.0005; double lonHigh=homeLon+.0005; double lonLow=homeLon-.0005;
double lat=GPS.latitudeDegrees; double lon=GPS.longitudeDegrees;
if(lat<=latHigh && lat>=latLow){ //.001°=275.518 ft
if(lon<=lonHigh && lon>=lonLow && atHome!=1){
atHome=1;
digitalWrite(D4,HIGH);
}
else if(lon>=lonHigh || lon<=lonLow){
atHome=0;
digitalWrite(D4,LOW);
}
}
else{
atHome=0;
digitalWrite(D4,LOW);
}
}
void report(){
if(!noWifi && Particle.connected()){
String message= "Write:"+String(wStatus);
message+=", Fix:"+String(gpsFix);
message+=", Sat:"+String(GPS.satellites);
message+=", PE:"+String(parseError);
Particle.publish("Status Update",message); //Publish a status update
Particle.process();//Added so that the processor doesn't get stuck trying to publish and continues loop
if(atHome==1){
Particle.publish("Home3121","HIGH",60,PUBLIC);
Particle.process();
}
else{
Particle.publish("Home3121","LOW",60,PUBLIC);
Particle.process();
}
}
}
/* Below function is a a testing function that was used for troubleshooting communication between
the particles when communicating proximity. This allowed the relay on the reciever unit to be activated
without having to leave my house and instead just use a switch.*/
/*void report(){
if(!noWifi && Particle.connected()){
String message= "Write:"+String(wStatus);
message+=", Fix:"+String(gpsFix);
message+=", Sat:"+String(GPS.satellites);
message+=", PE:"+String(parseError);
Particle.publish("Status Update",message); //Publish a status update
Particle.process();//Added so that the processor doesn't get stuck trying to publish and continues loop
if(wStatus==1){
Particle.publish("Home3121","HIGH",60,PUBLIC);
Particle.process();
}
else{
Particle.publish("Home3121","LOW",60,PUBLIC);
Particle.process();
}
}
}*/
void wifiConnect(){
if(!noWifi){
if(!Particle.connected()){
WiFi.on(); //turn on wifi if it was turned off
WiFi.setCredentials("-------","-------"); //saved wifi credentials insert your known wifi networks here
WiFi.setCredentials("------","-------"); //saved wifi credentials insert your known wifi networks here
WiFi.setCredentials("---------"); //saved wifi credentials insert your known wifi networks here
WiFi.connect(); //will attempt to connect to one of the saved wifi networks
while(WiFi.connecting()){
if (timer > millis()) timer = millis();
int elapsedTime = millis() - timer;
if (elapsedTime > 14999) {
timer = millis(); // reset the timer
WiFi.off(); //if it takes more than 15 second to connect to WiFi turn off the module
noWifi=TRUE;
period=9; //Save data at 100Hz since wifi is off
break; //break from while loop
}
}
if(WiFi.ready()) Particle.connect(); //will attempt to connect to the cloud following wifi connection
if(!Particle.connected()){ //check to see if the particle was able to connect if not wait a set period of time
WiFi.off(); //turn off wifi to save power until next connect attempt is made
noWifi=TRUE; //temporarily set to true so that it won't try to connect to wifi until told to try again
period=9; //Save data at 100Hz since wifi is off
}
else period=49; //Save data at 20Hz since wifi is on, otherwise it will corrupt the GPS data
}
}
else{
WiFi.off();
noWifi=TRUE;
period=9; //Save data at 100Hz since wifi is off
}
lastIteration=(fileTime*60000)/(period+1);//this calculates how many iterations should be present in the file
//based on the period between iterations and the length of time you want a file to last.
}
void flashError(){
digitalWrite(D4,HIGH);
delay(500);
digitalWrite(D4,LOW);
delay(250);
digitalWrite(D4,HIGH);
delay(500);
digitalWrite(D4,LOW);
delay(250);
}
void dateTime(uint16_t* date, uint16_t* time) {
// Need to create corrections for time and date at end of months and year.
uint endMonth [12]={31,28,31,30,31,30,31,31,30,31,30,31};
int year=GPS.year, month=GPS.month, day =GPS.day, hour=GPS.hour, minute=GPS.minute, second=GPS.seconds;
if((year%4)==0)endMonth[1]=29;
hour-=timeZone;
if (hour < 0){
hour = (hour + 24); // Corrects time and day when day changes for GST but not for location
if((day-1)<1){
if((month-1)<1){
month=12;
year-=1;
}
day=endMonth[month];
}
}
year-=48;//literally no idea why I have to make this correction here but not when writing to the file, but otherwise it kept telling me it was 2065.
// return date using FAT_DATE macro to format fields
*date = FAT_DATE(year, month, day);
// return time using FAT_TIME macro to format fields
*time = FAT_TIME(hour, minute, second);
}
Comments