Development steps

Project planning and development stages

Step 4 - Add Piezo buzzer

Back to project development

Add Piezo buzzer

  • Make buzzer play music on fixed Coordinates;

In this step I need to add a buzzer and make it play something if the device is within a virtual fence as it's called a Geofence. The Geofencing is of course determined by GPS coordinates.

A Geofence is a virtual fence or an imaginary border drawn around an area.

The buzzer will be connected to GPIO 1.

The connection Schematic:

The Geofence is going to be circular as it's easier.

Th ecenter of the circle if defined with the initial Latitude and longitude values, there are then compare to the latitude and longitude values of the GPS module.

The distance between any two GPS coordinates are calculated using the Haversine Formula.

The distance is compared with the pre-defined distance value. Uf the calculated distance is greater than the pre-defined distance then it means the person is outside the Geofence.

To get the coordinates for the geofence we are going to use Google Maps as it's the easier way to get the coordinates and because I don't really need a high level of definition.

I will use Praza do Obradoiro in Santiago de Compostela as the center of the circle, but for testing purposes I will use Fab Lab León.

Praza do Obradoiro in Santiago de Compostela

  • Latitude: 42.88062323683849
  • Longitude: -8.545571997470367

Fab Lab León

  • Latitude: 42.61127351795643
  • Longitude: -5.59774217193631

I will use 80 meters as a reference. If a person is within 80 meter os the point of the circle the buzzer will beep.

I will have to add a tune later on.

The code:


#include <TinyGPS++.h>
#include <SoftwareSerial.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
/*
* Pedro Candeias  GPS Data logger
*/

#include <TinyGPS++.h>
#include <SoftwareSerial.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include "melody.h"

//Libraries for microSD card
#include "FS.h"
#include "SD.h"
#include "SPI.h"

#define SCREEN_WIDTH 128  // OLED display width, in pixels
#define SCREEN_HEIGHT 64  // OLED display height, in pixels
static const int RXPin = 20, TXPin = 21;
static const uint32_t GPSBaud = 9600;
const int SDPin = 5;
const int BuzzerPin = 3;
const int LedPin = 2;

// Geofence
const float maxDistance = 20;
float initialLatitude = 42.59854582665766;
float initialLongitude = -5.567326426073929;

// display I2C (SDA, SCL pins)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

// The TinyGPS++ object
TinyGPSPlus gps;

// String to hold GPS data
String gpstext;

// GPS write delay counter variables
// Change gpsttlcount as required
int gpscount = 0;
int gpsttlcount = 30;

// The serial connection to the GPS device
SoftwareSerial ss(RXPin, TXPin);

// buzzer
int tempo=88; 
int melody[] = {
NOTE_AS4,-2,  NOTE_F4,8,  NOTE_F4,8,  NOTE_AS4,8,//1
NOTE_GS4,16,  NOTE_FS4,16,  NOTE_GS4,-2,
NOTE_AS4,-2,  NOTE_FS4,8,  NOTE_FS4,8,  NOTE_AS4,8,
NOTE_A4,16,  NOTE_G4,16,  NOTE_A4,-2,
REST,1, 
NOTE_AS4,4,  NOTE_F4,-4,  NOTE_AS4,8,  NOTE_AS4,16,  NOTE_C5,16, NOTE_D5,16, NOTE_DS5,16,//7
NOTE_F5,2,  NOTE_F5,8,  NOTE_F5,8,  NOTE_F5,8,  NOTE_FS5,16, NOTE_GS5,16,
NOTE_AS5,-2,  NOTE_AS5,8,  NOTE_AS5,8,  NOTE_GS5,8,  NOTE_FS5,16,
NOTE_GS5,-8,  NOTE_FS5,16,  NOTE_F5,2,  NOTE_F5,4, 
NOTE_DS5,-8, NOTE_F5,16, NOTE_FS5,2, NOTE_F5,8, NOTE_DS5,8, //11
NOTE_CS5,-8, NOTE_DS5,16, NOTE_F5,2, NOTE_DS5,8, NOTE_CS5,8,
NOTE_C5,-8, NOTE_D5,16, NOTE_E5,2, NOTE_G5,8, 
NOTE_F5,16, NOTE_F4,16, NOTE_F4,16, NOTE_F4,16,NOTE_F4,16,NOTE_F4,16,NOTE_F4,16,NOTE_F4,16,NOTE_F4,8, NOTE_F4,16,NOTE_F4,8,
NOTE_AS4,4,  NOTE_F4,-4,  NOTE_AS4,8,  NOTE_AS4,16,  NOTE_C5,16, NOTE_D5,16, NOTE_DS5,16,//15
NOTE_F5,2,  NOTE_F5,8,  NOTE_F5,8,  NOTE_F5,8,  NOTE_FS5,16, NOTE_GS5,16,
NOTE_AS5,-2, NOTE_CS6,4,
NOTE_C6,4, NOTE_A5,2, NOTE_F5,4,
NOTE_FS5,-2, NOTE_AS5,4,
NOTE_A5,4, NOTE_F5,2, NOTE_F5,4,
NOTE_FS5,-2, NOTE_AS5,4,
NOTE_A5,4, NOTE_F5,2, NOTE_D5,4,
NOTE_DS5,-2, NOTE_FS5,4,
NOTE_F5,4, NOTE_CS5,2, NOTE_AS4,4,
NOTE_C5,-8, NOTE_D5,16, NOTE_E5,2, NOTE_G5,8, 
NOTE_F5,16, NOTE_F4,16, NOTE_F4,16, NOTE_F4,16,NOTE_F4,16,NOTE_F4,16,NOTE_F4,16,NOTE_F4,16,NOTE_F4,8, NOTE_F4,16,NOTE_F4,8
};

int notes=sizeof(melody)/sizeof(melody[0])/2; 
int wholenote = (60000 * 4) / tempo;
int divider = 0, noteDuration = 0;

// Comment this define out if not connected to PC
#define DEBUG 1

#ifdef DEBUG
#define DebugPrintln(str) Serial.println(str)
#define DebugPrint(str) Serial.print(str)
#define DebugPrintf(str, str2) Serial.printf(str, str2)
#else
#define DebugPrintln(str)
#define DebugPrint(str)
#define DebugPrintf(str, str2)
#endif

void initDisplay() {
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
DebugPrintln(F("SSD1306 allocation failed"));
for (;;)
  ;
}
delay(500);
display.clearDisplay();
display.setTextColor(WHITE);
}

// Initialize SD card
void initSDCard() {
if (!SD.begin(SDPin)) {
DebugPrintln("Card Mount Failed");
return;
}
}

// Write to the SD card
void writeFile(fs::FS &fs, const char *path, const char *message) {
DebugPrintf("Writing file: %sn", path);
File dataFile = fs.open(path, FILE_WRITE);
if (!dataFile) {
DebugPrintln("Failed to open file for writing");
return;
}
if (dataFile.print(message)) {
DebugPrintln("File written");
} else {
DebugPrintln("Write failed");
}
dataFile.close();
}

// Append data to the SD card
void appendFile(fs::FS &fs, const char *path, const char *message) {
DebugPrintf("Appending to file: %sn", path);

File dataFile = fs.open(path, FILE_APPEND);
if (!dataFile) {
DebugPrintln("Failed to open file for appending");
return;
}
if (dataFile.print(message)) {
DebugPrintln("Message appended");
} else {
DebugPrintln("Append failed");
}
dataFile.close();
}

void setup() {
Serial.begin(9600);
initDisplay();
initSDCard();
pinMode(BuzzerPin, OUTPUT);
pinMode(LedPin, OUTPUT);
ss.begin(GPSBaud);

File dataFile = SD.open("/gpslog.csv");
if (!dataFile) {
DebugPrintln("File doesn't exist");
DebugPrintln("Creating file...");
writeFile(SD, "/gpslog.csv", "Trail rn");
} else {
DebugPrintln("File already exists");
}
dataFile.close();
}

void loop() {
boolean newData = false;
for (unsigned long start = millis(); millis() - start < 1000;) {
while (ss.available() > 0) {
  if (gps.encode(ss.read())) {
    
    // start gps file
    // See if we have a complete GPS data string
    if (displayInfo() != "0") {
      // Get GPS string
      gpstext = displayInfo();
      // Check GPS Count
      DebugPrintln(gpscount);
      if (gpscount == gpsttlcount) {
        DebugPrintln(gpstext);
        appendFile(SD, "/gpslog.csv", gpstext.c_str());
      }
      // Increment GPS Count
      gpscount = gpscount + 1;
      if (gpscount > gpsttlcount) {
        gpscount = 0;
      }
    }
    // End GPS file
    newData = true;
    float distance = getDistance(gps.location.lat(), gps.location.lng(), initialLatitude, initialLongitude);
      DebugPrint("Distance:");
      DebugPrintln(distance);
    if(distance > maxDistance) {
      DebugPrintln("outside");
    } else {
      DebugPrintln("inside");
      display.clearDisplay();
      display.setTextColor(SSD1306_WHITE);
      display.setCursor(0, 0);
      display.setTextSize(2);
      display.print("YOU HAVE");
      display.setCursor(0, 20);
      display.setTextSize(2);
      display.print("ARRIVED!");
      display.display();
      playBuzzer();
      digitalWrite(LedPin, HIGH);  // turn the LED on (HIGH is the voltage level)
      delay(1000);                      // wait for a second
      digitalWrite(LedPin, LOW);   // turn the LED off by making the voltage LOW
      delay(1000);
    }

  }
}
}

//If newData is true
if (newData == true) {
newData = false;
DebugPrint("Satellites: ");
DebugPrintln(gps.satellites.value());
displayCoordinates();
} else {
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.setTextSize(3);
display.print("No Data");
display.display();
}
}

void displayCoordinates() {
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
if (gps.location.isValid() == 1) {
//String gps_speed = String(gps.speed.kmph());
display.setTextSize(1);
display.setCursor(25, 5);
display.print("Lat: ");
display.setCursor(50, 5);
display.print(gps.location.lat(), 6);
display.setCursor(25, 20);
display.print("Lng: ");
display.setCursor(50, 20);
display.print(gps.location.lng(), 6);
display.setCursor(25, 35);
display.print("Speed: ");
display.setCursor(65, 35);
display.print(gps.speed.kmph());
display.setTextSize(1);
display.setCursor(0, 50);
display.print("SAT:");
display.setCursor(25, 50);
display.print(gps.satellites.value());
display.setTextSize(1);
display.setCursor(70, 50);
display.print("ALT:");
display.setCursor(95, 50);
display.print(gps.altitude.meters(), 0);
display.display();
} else {
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.setTextSize(3);
display.print("No Data");
display.display();
}
}

// Function to return GPS string
String displayInfo() {
// Define empty string to hold 
// Libraries for microSD card
#include "FS.h"
#include "SD.h"
#include "SPI.h"

#define SCREEN_WIDTH 128  // OLED display width, in pixels
#define SCREEN_HEIGHT 64  // OLED display height, in pixels
static const int RXPin = 20, TXPin = 21;
static const uint32_t GPSBaud = 9600;
const int SDPin = 5;
const int buzzerPin = 1;  // GPIO 1

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

// The TinyGPS++ object
TinyGPSPlus gps;

// String to hold GPS data
String gpstext;

// GPS write delay counter variables
// Change gpsttlcount as required
int gpscount = 0;
int gpsttlcount = 30;

// The serial connection to the GPS device
SoftwareSerial ss(RXPin, TXPin);

// Comment this define out if not connected to PC
#define DEBUG 1

#ifdef DEBUG
#define DebugPrintln(str) Serial.println(str)
#define DebugPrint(str) Serial.print(str)
#define DebugPrintf(str, str2) Serial.printf(str, str2)
#else
#define DebugPrintln(str)
#define DebugPrint(str)
#define DebugPrintf(str, str2)
#endif

void initDisplay() {
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
  DebugPrintln(F("SSD1306 allocation failed"));
  for (;;) {}
  }
  delay(500);
  display.clearDisplay();
  display.setTextColor(WHITE);
}

// Initialize SD card
void initSDCard() {
  if (!SD.begin(SDPin)) {
  DebugPrintln("Card Mount Failed");
  return;
  }
}

// Write to the SD card
void writeFile(fs::FS &fs, const char *path, const char *message) {
  DebugPrintf("Writing file: %s\n", path);
  File dataFile = fs.open(path, FILE_WRITE);
  if (!dataFile) {
  DebugPrintln("Failed to open file for writing");
  return;
  }
  if (dataFile.print(message)) {
  DebugPrintln("File written");
  } else {
  DebugPrintln("Write failed");
  }
  dataFile.close();
}

// Append data to the SD card
void appendFile(fs::FS &fs, const char *path, const char *message) {
  DebugPrintf("Appending to file: %s\n", path);

  File dataFile = fs.open(path, FILE_APPEND);
  if (!dataFile) {
  DebugPrintln("Failed to open file for appending");
  return;
  }
  if (dataFile.print(message)) {
  DebugPrintln("Message appended");
  } else {
  DebugPrintln("Append failed");
  }
  dataFile.close();
}

void setup() {
  Serial.begin(9600);
  initDisplay();
  initSDCard();
  ss.begin(GPSBaud);

  // Set buzzer pin as OUTPUT
  pinMode(buzzerPin, OUTPUT);

  File dataFile = SD.open("/gpslog.csv");
  if (!dataFile) {
  DebugPrintln("File doesn't exist");
  DebugPrintln("Creating file...");
  writeFile(SD, "/gpslog.csv", "Trail\n");
} else {
  DebugPrintln("File already exists");
}
dataFile.close();
}

// Function to calculate distance between two coordinates
double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
// Convert latitude and longitude to radians
lat1 = radians(lat1);
lon1 = radians(lon1);
lat2 = radians(lat2);
lon2 = radians(lon2);

// Haversine formula
double dlon = lon2 - lon1;
double dlat = lat2 - lat1;
double a = pow(sin(dlat / 2), 2) + cos(lat1) * cos(lat2) * pow(sin(dlon / 2), 2);
double c = 2 * atan2(sqrt(a), sqrt(1 - a));
double distance = 6371 * c * 1000;  // multiply by 6371 (radius of Earth in kilometers) to get distance in meters
return distance;
}

void loop() {
boolean newData = false;
for (unsigned long start = millis(); millis() - start < 1000;) {
  while (ss.available() > 0) {
  if (gps.encode(ss.read())) {
      // Start GPS file
      // See if we have a complete GPS data string
      if (displayInfo() != "0") {
      // Get GPS string
      gpstext = displayInfo();
      // Check GPS Count
      DebugPrintln(gpscount);
      if (gpscount == gpsttlcount) {
          DebugPrintln(gpstext);
          appendFile(SD, "/gpslog.csv", gpstext.c_str());

          // Check if within 80 meters of target coordinates
          double targetLat = 42.61127351795643;
          double targetLon = -5.59774217193631;
          double currentLat = gps.location.lat();
          double currentLon = gps.location.lng();
          double distance = calculateDistance(currentLat, currentLon, targetLat, targetLon);
          if (distance <= 80) {
          // Beep the buzzer
          digitalWrite(buzzerPin, HIGH);
          delay(500);  // Beep duration
          digitalWrite(buzzerPin, LOW);
          }
      }
      // Increment GPS Count
      gpscount = gpscount + 1;
      if (gpscount > gpsttlcount) {
          gpscount = 0;
      }
      }
      // End GPS file
      newData = true;
  }
  }
}

// If newData is true
if (newData == true) {
  newData = false;

  DebugPrint("Satellites: ");
  DebugPrintln(gps.satellites.value());
  displayCoordinates();
} else {
  display.clearDisplay();
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 0);
  display.setTextSize(3);
  display.print("No Data");
  display.display();
}
}

void displayCoordinates() {
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);

if (gps.location.isValid() == 1) {
  display.setTextSize(1);

  display.setCursor(25, 5);
  display.print("Lat: ");
  display.setCursor(50, 5);
  display.print(gps.location.lat(), 6);

  display.setCursor(25, 20);
  display.print("Lon: ");
  display.setCursor(50, 20);
  display.print(gps.location.lng(), 6);

  display.setCursor(25, 35);
  display.print("Alt: ");
  display.setCursor(50, 35);
  display.print(gps.altitude.meters());

  display.setCursor(25, 50);
  display.print("Satellites: ");
  display.setCursor(85, 50);
  display.print(gps.satellites.value());
}

display.display();
}

String displayInfo() {
String tmp = "";
if (gps.location.isValid() == 1) {
  tmp = "LAT: " + String(gps.location.lat(), 6) + ", LON: " + String(gps.location.lng(), 6) + ", ALT: " + String(gps.altitude.meters(), 2) + ", SAT: " + String(gps.satellites.value());
} else {
  tmp = "0";
}
return tmp;
}
output
String gpsdata = "";
// Get latitude and longitude
if (gps.location.isValid()) {
gpsdata = String(gps.location.lat(), 6);
gpsdata += (",");
gpsdata += String(gps.location.lng(), 6);
gpsdata += (",");
} else {
return "0";
}

// Get Date
if (gps.date.isValid()) {
gpsdata += String(gps.date.year());
gpsdata += ("-");
if (gps.date.month() < 10) gpsdata += ("0");
gpsdata += String(gps.date.month());
gpsdata += ("-");
if (gps.date.day() < 10) gpsdata += ("0");
gpsdata += String(gps.date.day());
} else {
return "0";
}

// Space between date and time
gpsdata += (" ");

// Get time
if (gps.time.isValid()) {
if (gps.time.hour() < 10) gpsdata += ("0");
gpsdata += String(gps.time.hour());
gpsdata += (":");
if (gps.time.minute() < 10) gpsdata += ("0");
gpsdata += String(gps.time.minute());
gpsdata += (":");
if (gps.time.second() < 10) gpsdata += ("0");
gpsdata += String(gps.time.second());
} else {
return "0";
}

// Return completed string
return gpsdata;
}

// Calculate distance between two points
float getDistance(float flat1, float flon1, float flat2, float flon2) {
// Variables
float dist_calc=0;
float dist_calc2=0;
float diflat=0;
float diflon=0;
// Calculations
diflat  = radians(flat2-flat1);
flat1 = radians(flat1);
flat2 = radians(flat2);
diflon = radians((flon2)-(flon1));
dist_calc = (sin(diflat/2.0)*sin(diflat/2.0));
dist_calc2 = cos(flat1);
dist_calc2*=cos(flat2);
dist_calc2*=sin(diflon/2.0);
dist_calc2*=sin(diflon/2.0);
dist_calc +=dist_calc2;
dist_calc=(2*atan2(sqrt(dist_calc),sqrt(1.0-dist_calc)));
dist_calc*=6371000.0; //Converting to meters
return dist_calc;
}

void playBuzzer(){
for (int thisNote = 0; thisNote < notes * 2; thisNote = thisNote + 2) {
// calculates the duration of each note
divider = melody[thisNote + 1];
if (divider > 0) {
  // regular note, just proceed
  noteDuration = (wholenote) / divider;
} else if (divider < 0) {
  // dotted notes are represented with negative durations!!
  noteDuration = (wholenote) / abs(divider);
  noteDuration *= 1.5; // increases the duration in half for dotted notes
}
// we only play the note for 90% of the duration, leaving 10% as a pause
tone(BuzzerPin, melody[thisNote], noteDuration*0.9);
// Wait for the specief duration before playing the next note.
delay(noteDuration);
// stop the waveform generation before the next note.
noTone(BuzzerPin);
}
}


The buzzer is controlled using the digitalWrite() function. It is set to HIGH for a duration of 500 milliseconds when the user is within an 80-meter radius of the target coordinates, and then set back to LOW to stop the beep.

The final code will use the coordinates in Santiago de Compostela.

Links

Geofencing: GPS, GSM, Geofence