I am making a neural network that's supposed to be capable of identifying handwritten numbers using the Mnist database downloadable here. The network works perfectly with one to 5 examples but after 10 it starts to get a bit iffy. Using a standard of 5000 examples the program will stagnate at around 0.42 cost (it starts at around 1.2 costs). all the output of the 10 neurons in the last layer will also trend towards 0.1 and the network is noticeably never very certain of it's guess because of this (usually the output of the guess will be around 0.1 to 0.2 with some exceptions)
Example of guess and outputs of the last layer after training for 5000 iterations:
Example: 5000
index: 2435
Cost: 0.459006
Expected: 0
Out_value 0: 0.0900279
Out_value 1: 0.104657
Out_value 2: 0.0980369
Out_value 3: 0.0990471
Out_value 4: 0.101716
Out_value 5: 0.0937537
Out_value 6: 0.0933432
Out_value 7: 0.114351
Out_value 8: 0.10058
Out_value 9: 0.0924466
Guess: 7
Guess certainty: 0.114351
false
I've tried adjusting the number and size of the h-layers and the learning rate, but the result is always the same (constantly jumping around a cost of around 0.42). I, of course, theorized that my backprop or math just didn't check out, but testing this with a test network based on a guide on backprop, link here my weights adjusted themselves perfect to the decimal according to the article. So I'm not sure what to do to prevent my network from stagnating and to make it learn at this point. Does anyone have any idea why it might be stagnating like this?
Relevant code in the cpp-file for the neural network:
#define _USE_MATH_DEFINES
#include <cmath>
#include <utility>
#include "neural_network.h"
#include <ctime>
#include <string>
#include <iostream>
#include <cstdlib>
namespace nn{
double fRand(const double& f_min, const double& f_max){ //generate random double from f_min to f_max
const auto f = static_cast<double>(rand()) / RAND_MAX;
return f_min + f * (f_max - f_min);
}
double sigmoid(const double& net) { //sigmoid function for out value
const double result = 1.0 / static_cast<double>(1.0 + pow(M_E, -net));
return result;
}
double xavier(int layer_from_size) { //function used to initialize initial weights.
const double val = sqrt(1.0 / static_cast<double>(layer_from_size));
return val;
}
double out_net_derivative(const double& out){ //derviative of out-value with respect to the net-value
const double val = out * (1 - out);
return val;
}
double cost_out_derivative(const double& out, const double& target)
//derivative of the cost with respect to the out-value for the neurons in the last layer
{
const double val = out - target;
return val;
}
double calc_cost(const Layer& layer, std::vector<double> target){ //calculating the total cost mainly for logging
double cost = 0;
for(int i = 0; i < target.size(); i++){
cost += pow(target[i] - layer.get_neurons()[i].get_out(), 2) / 2;
}
return cost;
}
double delta(const double& cost_out_derivative, const double& out)
//derivative of the cost with respect to the current neurons out multiplied by out_net_derivative
{
const double val = cost_out_derivative * out_net_derivative(out);
return val;
}
Weight::Weight(double weight, int neuron_from_index)
:weight_{ weight }, neuron_from_index_{ neuron_from_index }
{}
Neuron::Neuron(int pos, int layer) //creating a empty neuron
: net_{ 0.0 }, out_{ 0.0 }, error_gradient_{ 0.0 }, pos_{ pos }, layer_{ layer }
{
}
Neuron::Neuron(int pos, double out) //creating a neuron in the first layer with a pre-assigned out-value
: net_{ 0.0 }, out_{ out }, error_gradient_{ 0.0 }, pos_{ pos }, layer_{ 0 }
{
}
void Neuron::update_weights(const Layer& layer_from, const double& learning_rate){
for (Weight& weight : weights_to_) {
//derivative of net with respect to weight
double neuron_from_out = layer_from.get_neurons()[weight.get_neuron_from_index()].get_out();
//derivative of cost with respect to weight
double val = delta(error_gradient_, out_) * neuron_from_out;
weight.update_weight(val, learning_rate);
}
}
void Layer::update_error_gradient(Layer& layer_from)
//update all the error gradients (derivative of the cost with respect to the neurons out-value) in the previous layer (layer_from)
{
for (Neuron& neuron : layer_from.neurons_) neuron.set_error_gradient(0); //resetting all previous error gradients
for (int i = 0; i < neurons_.size(); i++) {
for (int j = 0; j < layer_from.get_neurons().size(); j++) {
double delta_val = delta(neurons_[i].get_error_gradient(), neurons_[i].get_out());
//partial derivative of cost with respect to the last layers neuron in position j
double val = neurons_[i].get_weights_to()[j].get_weight() * delta_val;
layer_from.neurons_[j].update_error_gradient(val);
}
}
}
void Layer::update_bias(const double& learning_rate){
for(const Neuron& neuron: neurons_){
//derivative of the cost with respect to the layer-bias
double val = out_net_derivative(neuron.get_out()) * neuron.get_error_gradient();
bias_ -= learning_rate * val;
}
}
void Neuron::set_weights(const int& layer_from_size){ //set initial weights for neuron
for(int i = 0; i < layer_from_size; i++){
//get random weight using xavier weight initialization
double v_val = fRand(-xavier(layer_from_size), xavier(layer_from_size));
Weight weight{ v_val, i };
weights_to_.push_back(weight);
}
}
void Layer::set_weights(const int& layer_from_size){ //set initial weights for layer
for (Neuron& neuron : neurons_) neuron.set_weights(layer_from_size);
}
void Network::set_weights(){ //set initial weights for network
//srand(time(NULL));
for(int i = 1; i < layers_.size(); i++){
layers_[i].set_weights(layers_[i - 1].get_neurons().size());
}
}
Layer::Layer(int pos, int size) //make layer of any size
: pos_{ pos }, bias_{ 0.0 }
{
for (int i = 0; i < size; i++) //fill with neurons according to desired size
{
Neuron neuron{ i, pos };
neurons_.push_back(neuron);
}
}
Layer::Layer(std::vector<Neuron> first_layer) //set the first layer of the network according pre-acquired neurons
:pos_{ 0 }, bias_{ 0.0 }, neurons_{std::move(first_layer)}
{}
void Layer::forward_pass(const Layer& layer_from){ //calculate net, and out-value of each neuron in layer
for(Neuron& neuron : neurons_){
double val = calc_net(layer_from, neuron, bias_);
neuron.set_net(val);
neuron.set_out(sigmoid(val));
}
}
void Network::forward_pass(){ //calculate net, and out-value of each neuron in network
for (int i = 1; i < layers_.size(); i++)
layers_[i].forward_pass(layers_[i - 1]);
}
void Layer::backprop(const Layer& layer_from, const double& learning_rate){ //backprop and thus update weights in layer
for (Neuron& neuron : neurons_)
neuron.update_weights(layer_from, learning_rate);
}
void Network::backprop(const std::vector<double>& target){ //backprop entire network and thus update weights and biases
forward_pass();
set_last_layer_error_grads(target);
for(int i = layers_.size() - 1; i > 0; i--){
//update error gradients for the previous layer in the network
layers_[i].update_error_gradient(layers_[i - 1]);
layers_[i].backprop(layers_[i - 1], learning_rate_);
layers_[i].update_bias(learning_rate_);
}
}
Network::Network(std::vector<int> structure, double learning_rate) //create a network skeleton
:learning_rate_{learning_rate}
{
for(int i = 0; i < structure.size(); i++){ //fill network with layers of various sizes according to structure
Layer layer{ i, structure[i] };
layers_.push_back(layer);
}
}
void Network::set_last_layer_error_grads(std::vector<double> target){
for (int i = 0; i < layers_[layers_.size() - 1].get_neurons().size(); i++) {
double val = cost_out_derivative(layers_[layers_.size() - 1].get_neurons()[i].get_out(), target[i]);
layers_[layers_.size() - 1].set_neuron_error_grad(i, val);
}
}
int Network::get_guess() const{ //get the networks guess for each example (image)
int guess = 0;
for (int i = 0; i < layers_[layers_.size() - 1].get_neurons().size(); i++) {
if (layers_[layers_.size() - 1].get_neurons()[guess].get_out() < layers_[layers_.size() - 1].get_neurons()[i].get_out())
guess = i;
//std::cout << "Guess certainty " << i << ":\t" << layers[layers.size() - 1].get_neurons()[i].get_out_value() << '\n';
std::cout << "Out_value " << i << ":\t" << layers_[layers_.size() - 1].get_neurons()[i].get_out() << '\n';
}
std::cout << "Guess:\t" << guess << '\n'
<< "Guess certainty:\t" << layers_[layers_.size() - 1].get_neurons()[guess].get_out() << "\n\n";
return guess;
}
int Network::get_weight_amount() const //get number of weights
{
int amount = 0;
for (int i = 1; i < layers_.size(); i++) {
amount += layers_[i - 1].get_neurons().size() * layers_[i].get_neurons().size();
}
return amount;
}
double calc_net(const Layer& layer_from, const Neuron& neuron, const double& bias){ // calculate net-value for specific neuron
const std::vector<Neuron>& neurons_from = layer_from.get_neurons();
const std::vector<Weight>& weights = neuron.get_weights_to();
if (neurons_from.size() != weights.size())
throw std::exception("there is not strictly one weight for each neuron in layer from.");
double net = 0;
//calculate net value with respect to the previous layers neurons and weights connecting them
for (int i = 0; i < neurons_from.size(); i++)
net += neurons_from[i].get_out() * weights[i].get_weight();
net += bias;
return net;
}
void Network::train(std::ifstream& practice_file, const int& sample_size, const int& practise_loops)
//train network with a specific sample size a specific number of times according to practice loops,
//getting necessary data for the first layer from a practice file
{
//srand(time(NULL));
std::vector<Layer> images;
std::vector<std::vector<double>> targets;
for(int i = 0; i < sample_size; i++){ //get and store all images and targets for the images in different vectors
std::vector<double> image = get_image(practice_file);
images.push_back(get_flayer(image));
targets.push_back(get_target(image, layers_[layers_.size() - 1].get_neurons().size()));
}
//backprop through random examples taken from the sample
for(int i = 0; i < practise_loops; i++){
int index = rand() % images.size();
layers_[0] = images[index];
backprop(targets[index]);
std::cout << "Example:\t" << i << '\n' << //logging
"index:\t" << index << '\n'
<< "Cost:\t" << calc_cost(layers_[layers_.size() - 1], targets[index]) << '\n';
if (correct_guess(targets[index]))
std::cout << "true\n";
else std::cout << "false\n";
}
}
double Network::test(std::ifstream& test_file, const int& sample_size){ //test network accuracy
int correct = 0;
std::vector<Layer> images;
std::vector<std::vector<double>> targets;
for (int i = 0; i < sample_size; i++) {
std::vector<double> image = get_image(test_file);
images.push_back(get_flayer(image));
targets.push_back(get_target(image, layers_[layers_.size() - 1].get_neurons().size()));
}
for(int i = 0; i < sample_size; i++)
{
layers_[0] = images[i];
forward_pass();
if (correct_guess(targets[i])) correct++; //keep track of correct guesses
}
double accuracy = 100 * correct / sample_size; //calculate accuracy
return accuracy;
}
std::vector<double> get_image(std::ifstream& ifs) { //get an image data from a file (specifically from the mnist files
std::vector<double> values; //all data converted to relevant format
std::string value; //data in string format
std::string line; //all data in string format
std::getline(ifs, line); //get image
//convert image string to relevant grey scale and target doubles and store them to be returned
for (const char& ch : line) {
switch (ch) {
case '0': case '1':
case '2': case '3':
case '4': case '5':
case '6': case '7':
case '8': case '9':
case '.':
value += ch;
break;
default:
values.push_back(std::stod(value));
value.clear();
break;
}
}
values.push_back(std::stod(value)); //store last piece of data
return values;
}
std::vector<double> get_target(const std::vector<double>& image, int last_layer_size){ //get target for an image
std::vector<double> target(last_layer_size);
//make sure that every neuron that is not the correct answer isn't lit up and do the opposite for the correct answer neuron
for(int i = 0; i < last_layer_size; i++){
//according to the file setup the first piece of data in the image is the target, hence image[0]
if (i == static_cast<int>(image[0])) target[i] = 1.0; //0.99
}
return target;
}
Layer get_flayer(std::vector<double> image) { //get the first layer through image
std::vector<Neuron> neurons;
image.erase(image.begin()); //throw away the target
for (int i = 0; i < image.size(); i++) {
Neuron neuron{ i, image[i] };
neurons.push_back(neuron);
}
Layer layer{ neurons };
return layer;
}
bool Network::correct_guess( const std::vector<double>& target) const{ //confirm if a guess by the network is correct
int excpected = 0;
for (int i = 0; i < target.size(); i++)
if (target[i] == 1.0) excpected = i; //the correct guess is the neuron position of the neuron fully lit of the bunch
std::cout << "Excpected:\t" << excpected << "\n\n";
return excpected == get_guess();
}
}
Link to full code including some exta functions in cpp-file, h-file, and main-file on GitHub for more context: Full code
So it turned out that the mistake I had made was related to how I read the greyscale values (the input) from the CSV-file containing the Mnist images converted to greyscale. I had neglected to divide each greyscale value with 255 (the highest possible greyscale value) to convert the greyscale to values between 0 and 1. Because of this, the second layer received huge input values thus causing the outputs of the second layer to explode massively. I have now fixed this issue and the program has around 87% accuracy when training with a file batch of 5000 images after 10000 iterations with one h-layer of size 16 (layer sizes: 784, 16, 10). While still not being optimal it is, of course, a huge improvement.
I needed to make a meters counter for a work thing, so I decided to just Arduino for it. I found an old encoder, found/wrote a simple code and hacked it all together and encountered a unexpected problem.
For some reason my counter won't count past around 8 meters or 31991 encoder pulses. Once it reaches this 8m limit, the number turns negative and starts counting backwards like -7.9 > -7.8 (i.e. continues counting upward towards 0).
Then it reaches zero and again counts to 8...
This is very strange to me and my limited coding knowledge can't fix it.
Does anyone know how to fix this or what I could do to make it work?
#include <LiquidCrystal.h>
#define inputA_in 6
#define inputB_in 7
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
int inputA_V = 0;
int inputB_V = 0;
int inputA = 0;
int inputB = 0;
int counter = 0;
// smeni vrednost tuka pred run
int console_frequency_milliseconds = 200; /// edna sekunda
int aLastState = 0;
int bLastState = 0;
float meters = 0.0;
unsigned long lasttime = 0;
int move_positive = 0;
int move_negative = 0;
int maximum_input_digital_v = 300; //treba da citash od konzola i da gi setirash max i min
int minimum_input_digital_v = 0;
int logical_threshold_v = 150; //brojkive se random staveni
void setup() {
pinMode (inputA_in, INPUT);
pinMode (inputB_in, INPUT);
Serial.begin (9600);
lcd.begin(16, 2);
// Print a message to the LCD
lcd.print("Metraza [m]");
aLastState = inputA;
bLastState = inputB;
lasttime = 0;
}
void loop () {
inputA = digitalRead(inputA_in);
if (inputA != aLastState) {
if (digitalRead(inputB_in) != inputA) {
counter ++;
aLastState = inputA;
} else {
counter --;
aLastState = inputA;
}
}
if (millis() - console_frequency_milliseconds > lasttime)//Detect once every 150ms
{
meters = 0.50014 * counter / 2000;
Serial.print("Position: ");
Serial.println(meters);
lasttime = millis();
lcd.setCursor(0, 1);
//Print a message to second line of LCD
lcd.print(meters);
}
}
Your counter is a simple int,
int counter = 0;
It seems that on your system they are only 16bit wide (with a maximum value of 32767), not surprising.
Use
long int counter = 0;
to get wider variables.
You might also want to change the calculation from
meters = 0.50014 * counter / 2000;
to
meters = 0.50014 * counter / 2000.0;
to avoid losing precision and range. Even with an int that would extend your range from 31991 encoder pulses to 32757 encoder pulses; and analog for the wider range.
You might also like to try changing the counter to an unsigned int or unsigned long int. I did not analyse your whole code, but I think you do not have anything which relies on representation of negative numbers. So you probably could double the range again. But no guarantees, subject to testing.
I need a little help trying to make a timer a little fuzzy. I want to use a fixed interval of timing like this:
____|_______|____|_____|_________|_____|___|_______|
where the pipe is the event occurrence and the underscore is the delay in an array like this:
int myIntervals = { 1000, 2000, 750, 850, 1200, 850, 500, 1000};
but the values are arbitrary.
I would like to create a slight randomness to the event, but not allow the randomness to affect the overall timing:
___*|*_____*|*__*|*___*|*_______*|*___*|*_*|*_____*|
where the randomness is described as the time contained by asterisks.
So the event always happens at the interval +- a random delay:
int fuzzyPeriod = random(-75, 75);
I've experimented around this but to no avail... I'm finding myself in a recursion when the fuzzy period is negative, or like this I get a millis() overflow problem, obviously.
int sequence[] = {1000, 750, 950, 1150, 1200, 1500, 1000, 1900, 2000};
unsigned int index;
unsigned long startMillis = 0;
unsigned int fuzzy = sequence[0];
void setup()
{
Serial.begin(9600);
pinMode(13, OUTPUT);
}
void loop()
{
if (startMillis < millis()) // will have rollover issues <<<<<<<
{
if (millis() - startMillis >= fuzzy)
{
digitalWrite(13, !digitalRead(13));
startMillis += sequence[index]; // advance startMillis by the last interval used
index++;
if (index >= sizeof(sequence) / sizeof(sequence[0]))
{
index = 0;
Serial.println("Restarted Sequence");
}
fuzzy = sequence[index] + random(-75, 76); // arbitrary offset needs to be positive or negative
Serial.print("fuzzy = ");
Serial.println(fuzzy);
}
}
}
I hope I've done a good job explaining... I cannot for the life of me get this done and I know I'm to the point where I need a little help!
Something like this:
unsigned int index;
unsigned long nextMillis;
int prevFuzzy = 0;
int fuzzy = 0;
void setup()
{
//...
nextMillis = millis();
}
void loop()
{
if (millis() >= nextMillis)
{
fuzzy = random(-75, 76);
// compensate for previous deviation and add new one
nextMillis += sequence[index] - prevFuzzy + fuzzy;
fuzzy = prevFuzzy;
// or just:
// fuzzy = random(-75, 76) - fuzzy
// nextMillis += sequence[index] + fuzzy;
index++;
if (index >= sizeof(sequence) / sizeof(sequence[0]))
{
index = 0;
}
// Do stuff
}
}
First you have to use unsigned long for milliseconds to properly account for the overflow.
Then I also initialized the variables at different places and startMillis with a different value
int sequence[] = {1000, 750, 950, 1150, 1200, 1500, 1000, 1900, 2000};
unsigned int index = 0;
unsigned long startMillis;
unsigned int fuzzy = sequence[0] + random(-75, 76);
#include <limits.h>
void setup()
{
Serial.begin(9600);
pinMode(13, OUTPUT);
randomSeed(analogRead(0));
startMillis = millis();
}
void loop()
{
long MilliDiff;
if (startMillis <= millis())
MilliDiff = millis() - startMillis;
else
MilliDiff = ULONG_MAX - startMillis + millis();
if (MilliDiff >= fuzzy)
{
digitalWrite(13, !digitalRead(13));
startMillis += sequence[index]; // advance startMillis by the last interval used
index++;
if (index >= sizeof(sequence) / sizeof(sequence[0]))
{
index = 0;
Serial.println("Restarted Sequence");
}
fuzzy = sequence[index] + random(-75, 76); // arbitrary offset needs to be positive or negative
Serial.print("startMillis = ");
Serial.print(startMillis);
Serial.print(", sequence = ");
Serial.print(sequence[index]);
Serial.print(", fuzzy = ");
Serial.println(fuzzy);
}
}
This code seems to be working on my Uno
I would like to know how to stop music with a push button (to stop it exactly at the moment you press the button). Because at the moment my code does what I want(stop the music and turn on a light when I press the press button, but it waits untill the end of the song to stop.
There is my code:
int buttonState = 0;
int speakerOut = 10;
int buttonPin= 7;
int frequency = 500;
int ledPin = 13;
int length = 17; // the number of notes
char notes[] = "gcefgcefgcefgcefga "; // a space represents a rest
//int beats[] = {2,2,1,1,2,2,1,1,2,2,1,1,2,2,1,1};
//int tempo = 250;
int DEBUG = 1;
#define c 3830
#define d 3400
#define e 3038
#define f 2864
#define g 2550
#define a 2272
#define b 2028
#define C 1912
#define R 0
void setup() {
// put your setup code here, to run once:
pinMode(ledPin, OUTPUT);
pinMode(buttonPin,INPUT);
pinMode(speakerOut, OUTPUT);
if (DEBUG) {
Serial.begin(9600); // Set serial out if we want debugging
}
}
// MELODY and TIMING =======================================
// melody[] is an array of notes, accompanied by beats[],
// which sets each note's relative length (higher #, longer note)
int melody[] = { C, b, g, C, b, e, R, C, c, g, a, C };
int beats[] = { 16, 16, 16, 8, 8, 16, 32, 16, 16, 16, 8, 8 };
int MAX_COUNT = sizeof(melody) / 2; // Melody length, for looping.
// Set overall tempo
long tempo = 10000;
// Set length of pause between notes
int pause = 1000;
// Loop variable to increase Rest length
int rest_count = 100; //<-BLETCHEROUS HACK; See NOTES
// Initialize core variables
int tone_ = 0;
int beat = 0;
long duration = 0;
// PLAY TONE ==============================================
// Pulse the speaker to play a tone for a particular duration
void playTone() {
long elapsed_time = 0;
if (tone_ > 0) { // if this isn't a Rest beat, while the tone has
// played less long than 'duration', pulse speaker HIGH and LOW
while (elapsed_time < duration) {
digitalWrite(speakerOut,HIGH);
delayMicroseconds(tone_ / 2);
// DOWN
digitalWrite(speakerOut, LOW);
delayMicroseconds(tone_ / 2);
// Keep track of how long we pulsed
elapsed_time += (tone_);
}
}
else { // Rest beat; loop times delay
for (int j = 0; j < rest_count; j++) { // See NOTE on rest_count
delayMicroseconds(duration);
}
}
}
void loop() {
// put your main code here, to run repeatedly:
buttonState = digitalRead(buttonPin);
if (buttonState==HIGH){
digitalWrite(ledPin, HIGH);
noTone(speakerOut);
}else {
digitalWrite(ledPin, LOW);
digitalWrite(speakerOut,HIGH);
for (int i=0; i<MAX_COUNT; i++) {
tone_ = melody[i];
beat = beats[i];
duration = beat * tempo; // Set up timing
playTone();
// A pause between notes...
delayMicroseconds(pause);
}
}
}
You have 2 options, before playTone() check button and, if pressed, end the for loop.
for (int i=0; i<MAX_COUNT; i++) {
tone_ = melody[i];
beat = beats[i];
duration = beat * tempo; // Set up timing
if (digitalRead(buttonPin)==LOW){
playTone();
} else {
break; //End for loop
}
// A pause between notes...
delayMicroseconds(pause);
}
Or the same but using void loop() as a for loop. A global variable, count between 0 and MAX_COUNT and do the same check. If pressed, count=0; and if not, continue playing the next note. Now, I can't code write this but has no difficult.
I know this has been asked many times but I really can't find what I am really searching.
I am using an Arduino Uno and a GPS Shield that shows GPS data through serial.
Here is the code I am uploading to my Arduino to interface the GPS Shield:
void loop() // run over and over
{
while(!(mySerial.available())){}
Serial.write(mySerial.read());
}
That is just the code. Nevertheless, as it continuously loops, on a Serial Monitor, it also output GPS data every second.
Here is its output every second:
$GPGGA,013856.000,000.9090,N,9090.90,E,1,09,1.1,316.97,M,0.00,M,,*66
$GPGSA,A,3,07,08,11,1ÿ3,16,19,23,27,42,,,,2.8,1.1,2.5*3F
$GPRMC,013856.000,A,000.9090,N,9090.90,E,0.0,038.1,310814,,,A*62
$GPGSV,ÿ3,1,12,16,26,059,33,27,33,025,44,08,30,330,32,07,31,326,34*7A
$GPGSV,3,2,12,19,58,354,31,01,33,186,18,23,32,221,24,11,5ÿ9,198,31*70
$GPGSV,3,3,12,42,60,129,32,13,38,253,27,32,06,161,,31,01,140,*7E
As it updates every second, the coordinates changes to minimal, which means the GPS Shield is working.
The problem here is, I wanted to parse the GPS data, especially on the GPGGA line only, and ignore the other lines. I would like to parse the Status, Latitude, N/S Indicator, Longitude, and E/W Indicator.
I have searched for the NMEA Library (http://nmea.sourceforge.net/), but I have no idea how to use it.
Can someone please help me here? Thank you.
NMEA data is in a GPS-style (ddmm.ssss) format, Google wants it in Decimal Style (dd.mmssss), there is a coversion function at the bottom of the code for this step.
I wrote this because I don't like the large, complicated libraries to do simple little things, especially when I am trying to figure out how it works.
This parses the GLL sentence, but you can change the sentence it's looking for and rearrange the sections if needed.
String ReadString;
void setup() {
Serial.begin(9600); //Arduino serial monitor thru USB cable
Serial1.begin(9600); // Serial1 port connected to GPS
}
void loop() {
ReadString=Serial1.readStringUntil(13); //NMEA data ends with 'return' character, which is ascii(13)
ReadString.trim(); // they say NMEA data starts with "$", but the Arduino doesn't think so.
// Serial.println(ReadString); //All the raw sentences will be sent to monitor, if you want them, maybe to see the labels and data order.
//Start Parsing by finding data, put it in a string of character array, then removing it, leaving the rest of thes sentence for the next 'find'
if (ReadString.startsWith("$GPGLL")) { //I picked this sentence, you can pick any of the other labels and rearrange/add sections as needed.
Serial.println(ReadString); // display raw GLL data in Serial Monitor
// mine looks like this: "$GPGLL,4053.16598,N,10458.93997,E,224431.00,A,D*7D"
//This section gets repeated for each delimeted bit of data by looking for the commas
//Find Lattitude is first in GLL sentence, other senetences have data in different order
int Pos=ReadString.indexOf(','); //look for comma delimetrer
ReadString.remove(0, Pos+1); // Remove Pos+1 characters starting at index=0, this one strips off "$GPGLL" in my sentence
Pos=ReadString.indexOf(','); //looks for next comma delimetrer, which is now the first comma because I removed the first segment
char Lat[Pos]; //declare character array Lat with a size of the dbit of data
for (int i=0; i <= Pos-1; i++){ // load charcters into array
Lat[i]=ReadString.charAt(i);
}
Serial.print(Lat); // display raw latitude data in Serial Monitor, I'll use Lat again in a few lines for converting
//repeating with a different char array variable
//Get Lattitude North or South
ReadString.remove(0, Pos+1);
Pos=ReadString.indexOf(',');
char LatSide[Pos]; //declare different variable name
for (int i=0; i <= Pos-1; i++){
LatSide[i]=ReadString.charAt(i); //fill the array
Serial.println(LatSide[i]); //display N or S
}
//convert the variable array Lat to degrees Google can use
float LatAsFloat = atof (Lat); //atof converts the char array to a float type
float LatInDeg;
if(LatSide[0]==char(78)) { //char(69) is decimal for the letter "N" in ascii chart
LatInDeg= ConvertData(LatAsFloat); //call the conversion funcion (see below)
}
if(LatSide[0]==char(83)) { //char(69) is decimal for the letter "S" in ascii chart
LatInDeg= -( ConvertData(LatAsFloat)); //call the conversion funcion (see below)
}
Serial.println(LatInDeg,15); //display value Google can use in Serial Monitor, set decimal point value high
//repeating with a different char array variable
//Get Longitude
ReadString.remove(0, Pos+1);
Pos=ReadString.indexOf(',');
char Longit[Pos]; //declare different variable name
for (int i=0; i <= Pos-1; i++){
Longit[i]=ReadString.charAt(i); //fill the array
}
Serial.print(Longit); //display raw longitude data in Serial Monitor
//repeating with a different char array variable
//Get Longitude East or West
ReadString.remove(0, Pos+1);
Pos=ReadString.indexOf(',');
char LongitSide[Pos]; //declare different variable name
for (int i=0; i <= Pos-1; i++){
LongitSide[i]=ReadString.charAt(i); //fill the array
Serial.println(LongitSide[i]); //display raw longitude data in Serial Monitor
}
//convert to degrees Google can use
float LongitAsFloat = atof (Longit); //atof converts the char array to a float type
float LongInDeg;
if(LongitSide[0]==char(69)) { //char(69) is decimal for the letter "E" in ascii chart
LongInDeg=ConvertData(LongitAsFloat); //call the conversion funcion (see below
}
if(LongitSide[0]==char(87)) { //char(87) is decimal for the letter "W" in ascii chart
LongInDeg=-(ConvertData(LongitAsFloat)); //call the conversion funcion (see below
}
Serial.println(LongInDeg,15); //display value Google can use in Serial Monitor, set decimal point value high
//repeating with a different char array variable
//Get TimeStamp - GMT
ReadString.remove(0, Pos+1);
Pos=ReadString.indexOf(',');
char TimeStamp[Pos]; //declare different variable name
for (int i=0; i <= Pos-1; i++){
TimeStamp[i]=ReadString.charAt(i); //fill the array
}
Serial.print(TimeStamp); //display raw longitude data in Serial Monitor, GMT
Serial.println("");
}
}
//Conversion function
float ConvertData(float RawDegrees)
{
float RawAsFloat = RawDegrees;
int firstdigits = ((int)RawAsFloat)/100; // Get the first digits by turning f into an integer, then doing an integer divide by 100;
float nexttwodigits = RawAsFloat - (float)(firstdigits*100);
float Converted = (float)(firstdigits + nexttwodigits/60.0);
return Converted;
}
I wrote this decent code, and it works up to two decimal places.
Code:
String gpsData;
String LATval = "######";
String LNGval = "######";
char inChar;
String gpsData;
String latt;
String la;
String lonn;
String lo;
float lattt;
float lonnn;
int latDeg;
int lonDeg;
float latMin;
float lonMin;
float latttt;
float lonnnn;
String sGPRMC;
void setup() {
Serial.begin(9600);
}
void loop() {
while (Serial.available()) {
inChar = Serial.read();
gpsData += inChar;
if (inChar == '$') {
gpsData = Serial.readStringUntil('\n');
break;
}
}
Serial.println(gpsData);
sGPRMC = gpsData.substring(0, 5);
if (sGPRMC == "GPRMC") {
Serial.flush();
latt = gpsData.substring(18, 28);
la = gpsData.substring(29, 30);
lonn = gpsData.substring(31, 42);
lo = gpsData.substring(43, 44);
Serial.print("latt:");
Serial.println(latt);
Serial.print("la:");
Serial.println(la);
Serial.print("lonn:");
Serial.println(lonn);
Serial.print("lo:");
Serial.println(lo);
lattt = latt.toFloat();
lonnn = lonn.toFloat();
Serial.print("lattt:");
Serial.println(lattt);
Serial.print("lonnn:");
Serial.println(lonnn);
if (la == "N" and lo == "E") {
latDeg = float(int(lattt / 100));
latMin = float(lattt - (latDeg * 100));
latMin = latMin / 60;
lonDeg = float(int(lonnn / 100));
lonMin = float(lonnn - (lonDeg * 100));
lonMin = lonMin / 60;
latttt = latDeg + latMin;
lonnnn = lonDeg + lonMin;
LATval = String(latttt);
LNGval = String(lonnnn);
Serial.print("latDeg:");
Serial.println(latDeg);
Serial.print("latMin:");
Serial.println(latMin);
Serial.print("lonDeg:");
Serial.println(lonDeg);
Serial.print("lonMin:");
Serial.println(lonMin);
Serial.print("LATval:");
Serial.println(LATval);
Serial.print("LNGval:");
Serial.println(LNGval);
}
}
}
I have searched the Internet, and the best answer would be using the "TinyGPS++" library for Arduino. Almost all GPS-related codes are already included on the Library.
You can use TinyGPS to parse the NMEA strings. If you are interested in only 1 sentence. You can write a custom parser as below for that sentence only.
int handle_byte(int byteGPS) {
buf[counter1] = byteGPS;
//Serial.print((char)byteGPS);
counter1++;
if (counter1 == 300) {
return 0;
}
if (byteGPS == ',') {
counter2++;
offsets[counter2] = counter1;
if (counter2 == 13) {
return 0;
} } if (byteGPS == '*') {
offsets[12] = counter1; }
// Check if we got a <LF>, which indicates the end of line if (byteGPS == 10) {
// Check that we got 12 pieces, and that the first piece is 6 characters
if (counter2 != 12 || (get_size(0) != 6)) {
return 0;
}
// Check that we received $GPRMC
// CMD buffer contains $GPRMC
for (int j=0; j<6; j++) {
if (buf[j] != cmd[j]) {
return 0;
}
}
// Check that time is well formed
if (get_size(1) != 10) {
return 0;
}
// Check that date is well formed
if (get_size(9) != 6) {
return 0;
}
SeeedOled.setTextXY(7,0);
for (int j=0; j<6; j++) {
SeeedOled.putChar(*(buf+offsets[1]+j));
}
SeeedOled.setTextXY(7,7);
for (int j=0; j<6; j++) {
SeeedOled.putChar(*(buf+offsets[9]+j));
}
// TODO: compute and validate checksum
// TODO: handle timezone offset
return 0; }
return 1; }
Try this which can help you
#include <SoftwareSerial.h>
#include <TinyGPS.h>
TinyGPS gps;
SoftwareSerial ss(3,4);
static void smartdelay(unsigned long ms);
static void print_float(float val, float invalid, int len, int prec);
static void print_int(unsigned long val, unsigned long invalid, int len);
static void print_date(TinyGPS &gps);
static void print_str(const char *str, int len);
void setup()
{
Serial.begin(9600);
ss.begin(9600);
}
void loop()
{
float flat, flon;
unsigned short sentences = 0, failed = 0;
gps.f_get_position(&flat, &flon);
Serial.print("LATITUDE: ");
print_float(flat, TinyGPS::GPS_INVALID_F_ANGLE, 10, 6);
Serial.println(" ");
Serial.print("LONGITUDE: ");
print_float(flon, TinyGPS::GPS_INVALID_F_ANGLE, 11, 6);
Serial.println(" ");
Serial.print("altitude: ");
print_float(gps.f_altitude(), TinyGPS::GPS_INVALID_F_ALTITUDE, 7, 2);
Serial.println(" ");
Serial.print("COURSE:");
print_float(gps.f_course(), TinyGPS::GPS_INVALID_F_ANGLE, 7, 2);
Serial.println("");
Serial.print("DIRECTION: ");
int d;
print_str(gps.f_course() == TinyGPS::GPS_INVALID_F_ANGLE ? "*** " : TinyGPS::cardinal(gps.f_course()), 6);
d=gps.f_course();
Serial.println();
Serial.println();
smartdelay(1000);
}
static void smartdelay(unsigned long ms)
{
unsigned long start = millis();
do
{
while (ss.available())
gps.encode(ss.read());
} while (millis() - start < ms);
}
static void print_float(float val, float invalid, int len, int prec)
{
if (val == invalid)
{
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 print_int(unsigned long val, unsigned long invalid, int len)
{
char sz[32];
if (val == invalid)
strcpy(sz, "*******");
else
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 print_str(const char *str, int len)
{
int slen = strlen(str);
for (int i=0; i<len; ++i)
Serial.print(i<slen ? str[i] : ' ');
smartdelay(0);
}