GPS Remote Data
Collection
with Arduino
d. bodnar revised 02-21-2015
For the last few weeks I have been
experimenting with two interesting devices. The first is a small
(about 7/8" x 5/8" x 1/8") RF transceiver
that is described in some detail here.
I have found the range to be excellent for such a small device.
The other device is a small GPS receiver that can simultaneously track as many as 12 satellites. It has only 4 pins, VCC, GND, TX and RX. The data from the unit is sent via serial with a default speed of 9600 baud. This is described here. Note that the software uses the GPS at 4800 baud. Changing this rate is described here.
|
Objective My primary objective for this exercise it to place the GPS outside, with a clear view of the sky, and have the transceiver send the position data into the house for analysis. This will also allow me to develop routines to collect and transmit other data with the RF units. |
Schematic The schematic shows how I connected them together along with an LCD display to show the data that is received. This, of course, is not needed on the transmitter version. Pins A4 and A5 are not shown on the drawing as they are not on the two edges of the board. On most Arduino Mini's they are either at one of the board or just inside of A2 and A3. Two voltage regulators were used, one 3.3 volt and one 5 volt. This was done to accommodate the LCD display which will work at 3.3 volts, but without much contrast. Running it from 5 volts resolved the issue. The LEDs are optional. One blinks when packets are received while the other lights when 3 or more satellites are being tracked. The antenna is a 3.2" piece of wire. It works best if a like-sized wire is connected to ground and aligned opposite to it (i.e. the antenna wire goes up, the ground wire goes down below it).
|
The completed receive unit is shown here.
The transceiver is circled in red. The LCD shows data from the
transmitter that is sending GPS info and temperature. Alt = altitude, Avg = average altitude, Sat = # of satellites being tracked, hdop is horizontal dilution of precision (accuracy) in 1/100ths, the next line is longitude and latitude, count is the number of sent packets and temp is temperature.
|
Software - Transmitter
GPS_Receive_RF-pair-v4.ino#include <RFM69.h> // for some reason this needs to be first or will not compile! #include <TinyGPS++.h> #include <SoftwareSerial.h> #include <Wire.h> #include <LCD.h> #include <LiquidCrystal_I2C.h> #include <SPI.h> #include <SPIFlash.h> #define I2C_ADDR 0x27 // <<----- Add your address here. Find it from I2C Scanner #define BACKLIGHT_PIN 3 #define En_pin 2 #define Rw_pin 1 #define Rs_pin 0 #define D4_pin 4 #define D5_pin 5 #define D6_pin 6 #define D7_pin 7 #define NODEID 99 #define NETWORKID 100 #define GATEWAYID 1 #define FREQUENCY RF69_915MHZ //Match this with the version of your Moteino! (others: RF69_433MHZ, RF69_868MHZ) #define KEY "thisIsEncryptKey" //has to be same 16 characters/bytes on all nodes, not more not less! #define LED 9 #define LED2 8 #define SERIAL_BAUD 9600 #define ACK_TIME 30 // # of ms to wait for an ack #include <OneWire.h> #include <DallasTemperature.h> // Data wire is plugged into port 3 on the Arduino #define ONE_WIRE_BUS 5 // Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs) OneWire oneWire(ONE_WIRE_BUS); float ftemp = 0; // Pass our oneWire reference to Dallas Temperature. DallasTemperature sensors(&oneWire); int TRANSMITPERIOD = 300; //transmit a packet to gateway so often (in ms) byte sendSize = 0; boolean requestACK = false; //SPIFlash flash(8, 0xEF40); //was 30 EF40 for 16mbit windbond chip RFM69 radio; typedef struct { int nodeId; //store this nodeId unsigned long uptime; //uptime in ms long temp; //float temp; //temperature maybe? int AAltitude; float LLat; float LLon; float TTemperature; int SendCount; } Payload; Payload theData; int CCounter = 0; int potPin = A3; // select the input pin for the potentiometer int potValue = 0; // variable to store the value coming from the pot byte buffer[10]; LiquidCrystal_I2C lcd(I2C_ADDR, En_pin, Rw_pin, Rs_pin, D4_pin, D5_pin, D6_pin, D7_pin); /* This sample code demonstrates the normal use of a TinyGPS++ (TinyGPSPlus) object. It requires the use of SoftwareSerial, and assumes that you have a 4800-baud serial GPS device hooked up on pins 4(rx) and 3(tx). */ static const int RXPin = 3, TXPin = 4; static const uint32_t GPSBaud = 4800; // The TinyGPS++ object TinyGPSPlus gps; // The serial connection to the GPS device SoftwareSerial ss(RXPin, TXPin); void setup() { pinMode(LED2, OUTPUT); digitalWrite(LED2, LOW); lcd.begin (20, 4); // <<----- My LCD was 16x2 lcd.setBacklightPin(BACKLIGHT_PIN, POSITIVE); // Switch on the backlight lcd.setBacklight(HIGH); lcd.home (); // go home lcd.print("GPS Test Display"); lcd.setCursor(0, 1); lcd.print("d. bodnar 02-17-15"); lcd.setCursor(0, 2); lcd.print("Units = MPH & Feet"); Serial.begin(115200); ss.begin(GPSBaud); radio.initialize(FREQUENCY, NODEID, NETWORKID); radio.setHighPower(); //uncomment only for RFM69HW! radio.encrypt(KEY); char buff[50]; sprintf(buff, "\nTransmitting at %d Mhz...", FREQUENCY == RF69_433MHZ ? 433 : FREQUENCY == RF69_868MHZ ? 868 : 915); Serial.println(buff); sensors.begin(); // if (flash.initialize()) // Serial.println("SPI Flash Init OK!"); // else // Serial.println("SPI Flash Init FAIL! (is chip present?)"); Serial.println(F("FullExample.ino")); Serial.println(F("An extensive example of many interesting TinyGPS++ features")); Serial.print(F("Testing TinyGPS++ library v. ")); Serial.println(TinyGPSPlus::libraryVersion()); Serial.println(F("by Mikal Hart")); Serial.println(); Serial.println(F("Sats HDOP Latitude Longitude Fix Date Time Date Alt Course Speed Card Distance Course Card Chars Sentences Checksum")); Serial.println(F(" (deg) (deg) Age Age (m) --- from GPS ---- ---- to London ---- RX RX Fail")); Serial.println(F("---------------------------------------------------------------------------------------------------------------------------------------")); } long lastPeriod = -1; void loop() { static const double LONDON_LAT = 51.508131, LONDON_LON = -0.128002; printInt(gps.satellites.value(), gps.satellites.isValid(), 5); printInt(gps.hdop.value(), gps.hdop.isValid(), 5); printFloat(gps.location.lat(), gps.location.isValid(), 11, 6); printFloat(gps.location.lng(), gps.location.isValid(), 12, 6); printInt(gps.location.age(), gps.location.isValid(), 5); printDateTime(gps.date, gps.time); printFloat(gps.altitude.meters(), gps.altitude.isValid(), 7, 2); printFloat(gps.course.deg(), gps.course.isValid(), 7, 2); printFloat(gps.speed.kmph(), gps.speed.isValid(), 6, 2); printStr(gps.course.isValid() ? TinyGPSPlus::cardinal(gps.course.value()) : "*** ", 6); unsigned long distanceKmToLondon = (unsigned long)TinyGPSPlus::distanceBetween( gps.location.lat(), gps.location.lng(), LONDON_LAT, LONDON_LON) / 1000; printInt(distanceKmToLondon, gps.location.isValid(), 9); double courseToLondon = TinyGPSPlus::courseTo( gps.location.lat(), gps.location.lng(), LONDON_LAT, LONDON_LON); printFloat(courseToLondon, gps.location.isValid(), 7, 2); const char *cardinalToLondon = TinyGPSPlus::cardinal(courseToLondon); printStr(gps.location.isValid() ? cardinalToLondon : "*** ", 6); printInt(gps.charsProcessed(), true, 6); printInt(gps.sentencesWithFix(), true, 10); printInt(gps.failedChecksum(), true, 9); // Serial.println(); Serial.print(" 6 dig test "); Serial.println(gps.location.lat(), 6); smartDelay(1000); if (millis() > 5000 && gps.charsProcessed() < 10) Serial.println(F("No GPS data received: check wiring")); lcd.clear(); float Satellites = gps.satellites.value(); lcd.setCursor(0, 0); lcd.print("Sat.= "); lcd.print(Satellites, 0); lcd.setCursor(9, 0); float hdop = gps.hdop.value(); lcd.print("hdop= "); lcd.print(hdop, 0); lcd.setCursor(0, 1); float SpeedM = gps.speed.mps(); lcd.print("Speed= "); lcd.print(SpeedM, 1); lcd.setCursor(0, 2); float altitude = gps.altitude.feet(); lcd.print("alt.= "); lcd.print(altitude, 0); lcd.setCursor(0, 3); float latitude = gps.location.lat(); lcd.print(latitude, 6); lcd.setCursor(10, 3); float longitude = gps.location.lng(); lcd.print(longitude, 6); if (Satellites >= 3) digitalWrite(LED2, HIGH); else digitalWrite(LED2, LOW); if (Serial.available() > 0) { char input = Serial.read(); if (input >= 48 && input <= 57) //[0,9] { TRANSMITPERIOD = 100 * (input - 48); if (TRANSMITPERIOD == 0) TRANSMITPERIOD = 1000; Serial.print("\nChanging delay to "); Serial.print(TRANSMITPERIOD); Serial.println("ms\n"); } if (input == 'r') //d=dump register values radio.readAllRegs(); //if (input == 'E') //E=enable encryption // radio.encrypt(KEY); //if (input == 'e') //e=disable encryption // radio.encrypt(null); /* if (input == 'd') //d=dump flash area { Serial.println("Flash content:"); int counter = 0; while (counter <= 256) { Serial.print(flash.readByte(counter++), HEX); Serial.print('.'); } while (flash.busy()); Serial.println(); } if (input == 'e') { Serial.print("Erasing Flash chip ... "); flash.chipErase(); while (flash.busy()); Serial.println("DONE"); } if (input == 'i') { Serial.print("DeviceID: "); word jedecid = flash.readDeviceId(); Serial.println(jedecid, HEX); } */ } //check for any received packets if (radio.receiveDone()) { Serial.print('['); Serial.print(radio.SENDERID, DEC); Serial.print("] "); //for (byte i = 0; i < radio.DATALEN; i++) //These lines were removed to prevent a compile error // Serial.print((char)radio.DATA[i]); // due to LCD library incompatibility with the RF library Serial.print(" [RX_RSSI:"); Serial.print(radio.readRSSI()); Serial.print("]"); if (radio.ACKRequested()) { radio.sendACK(); Serial.print(" - ACK sent"); delay(10); } Blink(LED, 5); Serial.println(); } // Serial.print("Requesting temperatures..."); sensors.requestTemperatures(); // Send the command to get temperatures // Serial.println("DONE"); Serial.print("Temperature for the device 1 (index 0) is: "); Serial.print(sensors.getTempCByIndex(0)); ftemp = sensors.getTempCByIndex(0); ftemp = ftemp * 9; ftemp = ftemp / 5; ftemp = ftemp + 32; Serial.print(" F= "); Serial.println(ftemp); int currPeriod = millis() / TRANSMITPERIOD; if (currPeriod != lastPeriod) { //fill in the struct with new values theData.nodeId = Satellites; theData.uptime = Satellites; // working theData.temp = hdop ; //hdop works theData.AAltitude = altitude; theData.LLat = latitude; theData.LLon = longitude; theData.TTemperature = ftemp; CCounter = ++CCounter ; theData.SendCount = CCounter; // float LLat; // float LLon; // Serial.print("Sending struct ("); // Serial.print(sizeof(theData)); // Serial.print(" bytes) ... "); if (radio.sendWithRetry(GATEWAYID, (const void*)(&theData), sizeof(theData))) // Serial.print(" ok!"); // else Serial.print(" nothing..."); // Serial.println(); Blink(LED, 3); lastPeriod = currPeriod; } } // This custom version of delay() ensures that the gps object // is being "fed". static void smartDelay(unsigned long ms) { unsigned long start = millis(); do { while (ss.available()) gps.encode(ss.read()); } while (millis() - start < ms); } static void printFloat(float val, bool valid, int len, int prec) { if (!valid) { while (len-- > 1) Serial.print('*'); Serial.print(' '); } else { Serial.print(val, prec); int vi = abs((int)val); int flen = prec + (val < 0.0 ? 2 : 1); // . and - flen += vi >= 1000 ? 4 : vi >= 100 ? 3 : vi >= 10 ? 2 : 1; for (int i = flen; i < len; ++i) Serial.print(' '); } smartDelay(0); } static void printInt(unsigned long val, bool valid, int len) { char sz[32] = "*****************"; if (valid) sprintf(sz, "%ld", val); sz[len] = 0; for (int i = strlen(sz); i < len; ++i) sz[i] = ' '; if (len > 0) sz[len - 1] = ' '; Serial.print(sz); smartDelay(0); } static void printDateTime(TinyGPSDate &d, TinyGPSTime &t) { if (!d.isValid()) { Serial.print(F("********** ")); } else { char sz[32]; sprintf(sz, "%02d/%02d/%02d ", d.month(), d.day(), d.year()); Serial.print(sz); } if (!t.isValid()) { Serial.print(F("******** ")); } else { char sz[32]; sprintf(sz, "%02d:%02d:%02d ", t.hour(), t.minute(), t.second()); Serial.print(sz); } printInt(d.age(), d.isValid(), 5); smartDelay(0); } static void printStr(const char *str, int len) { int slen = strlen(str); for (int i = 0; i < len; ++i) Serial.print(i < slen ? str[i] : ' '); smartDelay(0); } void Blink(byte PIN, int DELAY_MS) { pinMode(PIN, OUTPUT); digitalWrite(PIN, HIGH); delay(DELAY_MS); digitalWrite(PIN, LOW); }
|
Software - Receiver
GPS_Receive_RF-pair-LCD-v5.ino/* Note that the LCD library for I2C is not the original one but from http://www.divshare.com/download/launch/24151116-7dc the original had a conflict with the RF library */ #include <RFM69.h> #include <LiquidCrystal_I2C.h>; //#include <LCD.h>// #include <SPI.h> #include <SPIFlash.h> #include <Wire.h> #define I2C_ADDR 0x27 // <<----- Add your address here. Find it from I2C Scanner #define BACKLIGHT_PIN 3 #define En_pin 2 #define Rw_pin 1 #define Rs_pin 0 #define D4_pin 4 #define D5_pin 5 #define D6_pin 6 #define D7_pin 7 #define NODEID 1 #define NETWORKID 100 #define FREQUENCY RF69_915MHZ //Match this with the version of your Moteino! (others: RF69_433MHZ, RF69_868MHZ) #define KEY "thisIsEncryptKey" //has to be same 16 characters/bytes on all nodes, not more not less! #define LED 9 #define LED2 8 #define SERIAL_BAUD 9600 #define ACK_TIME 30 // # of ms to wait for an ack RFM69 radio; //SPIFlash flash(8, 0xEF40); //was 30 EF40 for 16mbit windbond chip bool promiscuousMode = false; //set to 'true' to sniff all packets on the same network typedef struct { int nodeId; //store this nodeId unsigned long uptime; //uptime in ms long temp; //float temp; //temperature maybe? int AAltitude; float LLat; float LLon; float TTemperature; int SendCount; } Payload; Payload theData; long AvgAltitude = 0; int Readings = 0; int MINhdop = 999; LiquidCrystal_I2C lcd(0x27, 20, 4); // set the LCD address to 0x27 for a 16 chars and 2 line display void setup() { pinMode(LED2, OUTPUT); digitalWrite(LED2, LOW); lcd.init(); // initialize the lcd // Print a message to the LCD. lcd.backlight(); lcd.home (); // go home lcd.print("GPS Test Display"); lcd.setCursor(0, 1); lcd.print("d. bodnar 02-17-15"); lcd.setCursor(0, 2); lcd.print("Units = MPH & Feet");; Serial.begin(SERIAL_BAUD); delay(10); radio.initialize(FREQUENCY, NODEID, NETWORKID); radio.setHighPower(); //uncomment only for RFM69HW! radio.encrypt(KEY); radio.promiscuous(promiscuousMode); char buff[50]; sprintf(buff, "\nListening at %d Mhz...", FREQUENCY == RF69_433MHZ ? 433 : FREQUENCY == RF69_868MHZ ? 868 : 915); Serial.println(buff); // if (flash.initialize()) // Serial.println("SPI Flash Init OK!"); // else // Serial.println("SPI Flash Init FAIL! (is chip present?)"); } byte ackCount = 0; void loop() { //process any serial input if (Serial.available() > 0) { char input = Serial.read(); if (input == 'r') //d=dump all register values radio.readAllRegs(); if (input == 'E') //E=enable encryption radio.encrypt(KEY); if (input == 'e') //e=disable encryption radio.encrypt(null); if (input == 'p') { promiscuousMode = !promiscuousMode; radio.promiscuous(promiscuousMode); Serial.print("Promiscuous mode "); Serial.println(promiscuousMode ? "on" : "off"); } /* if (input == 'd') //d=dump flash area { Serial.println("Flash content:"); int counter = 0; while (counter <= 256) { Serial.print(flash.readByte(counter++), HEX); Serial.print('.'); } while (flash.busy()); Serial.println(); } if (input == 'D') { Serial.print("Deleting Flash chip content... "); flash.chipErase(); while (flash.busy()); Serial.println("DONE"); } if (input == 'i') { Serial.print("DeviceID: "); word jedecid = flash.readDeviceId(); Serial.println(jedecid, HEX); } */ } if (radio.receiveDone()) { // Serial.print('['); // Serial.print(radio.SENDERID, DEC); // Serial.print("] "); // Serial.print(" [RX_RSSI:"); // Serial.print(radio.readRSSI()); // Serial.print("]"); if (promiscuousMode) { Serial.print("to ["); Serial.print(radio.TARGETID, DEC); Serial.print("] "); } if (radio.DATALEN != sizeof(Payload)) Serial.print("Invalid payload received, not matching Payload struct!"); else { theData = *(Payload*)radio.DATA; //assume radio.DATA actually contains our struct and not something else // Serial.print(" nodeId="); // Serial.print(theData.nodeId); int Sats = theData.uptime; Serial.print(" Satellites="); Serial.print(theData.uptime); Serial.print(" hdop="); Serial.print(theData.temp); if (theData.temp <= MINhdop & theData.temp != 0) MINhdop = theData.temp; Serial.print(" MINhdop="); Serial.print(MINhdop); Serial.print(" Altitude="); Serial.print(theData.AAltitude); AvgAltitude = AvgAltitude + theData.AAltitude; Readings = ++Readings; int AvgAlt = AvgAltitude / Readings; Serial.print(" Av-alt "); Serial.print(AvgAlt); Serial.print(" Lat="); Serial.print(theData.LLat, 6); Serial.print(" Lon="); Serial.print(theData.LLon, 6); Serial.print(" Ftemp="); Serial.print(theData.TTemperature, 1); Serial.print(" Count="); Serial.print(theData.SendCount); if (Sats >= 3) digitalWrite(LED2, HIGH); else digitalWrite(LED2, LOW); lcd.clear(); lcd.print("Alt="); lcd.print(theData.AAltitude); lcd.print(" Avg="); lcd.print(AvgAlt); lcd.setCursor(0, 1); lcd.print("Sat= "); lcd.print(theData.uptime); lcd.print(" hdop= "); lcd.print(theData.temp); lcd.setCursor(0, 2); lcd.print(theData.LLat, 6); lcd.print(" "); lcd.print(theData.LLon, 6); lcd.setCursor(0, 3); lcd.print("Cnt "); lcd.print(theData.SendCount); lcd.print(" temp="); lcd.print(theData.TTemperature, 1); } if (radio.ACKRequested()) { byte theNodeID = radio.SENDERID; radio.sendACK(); // Serial.print(" - ACK sent."); // When a node requests an ACK, respond to the ACK // and also send a packet requesting an ACK (every 3rd one only) // This way both TX/RX NODE functions are tested on 1 end at the GATEWAY /* if (ackCount++%3==0) { Serial.print(" Pinging node "); Serial.print(theNodeID); Serial.print(" - ACK..."); delay(3); //need this when sending right after reception .. ? if (radio.sendWithRetry(theNodeID, "ACK TEST", 8, 0, 50)) // 0 = only 1 attempt, no retries; wait up to 50mS Serial.print("ok!"); else Serial.print("nothing"); } */ } Serial.println(); Blink(LED, 300); } } void Blink(byte PIN, int DELAY_MS) { pinMode(PIN, OUTPUT); digitalWrite(PIN, HIGH); delay(DELAY_MS); digitalWrite(PIN, LOW); }
|