This commit is contained in:
2018-02-26 21:20:38 -08:00
299 changed files with 86868 additions and 49839 deletions

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 812 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 814 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 KiB

View File

@@ -0,0 +1,13 @@
\section{Goals}
The summary of goals for the the Mars Rover Ground Station project are as follows:
\begin{itemize}
\item Create a stable, dual-monitor, full-screen GUI that runs on an Ubuntu 16.04 computer.
\item In the GUI, display video streams, Rover arm joint positions, and status information from the Rover.
\item Also in the GUI, provide a user-interactable map that includes the ability to plot and edit multiple kinds of way-points.
\item Provide control elements to enable the Rover's autonomous mode for the appropriate event during the URC competition.
\item Via various user input devices, allow the user to remotely drive and control the arm on the Rover.
\item Maintain software stability even when radio systems are dropping packets or completely disconnecting and reconnecting.
\item Keep the GUI intuitive enough that the user can focus on performing competition tasks rather than fighting the ground station software.
\item Provide enough documentation so that future Rover years may more easily build off of the code foundation.
\end{itemize}

View File

@@ -0,0 +1,59 @@
\lstset{ %
backgroundcolor=\color{white}, % choose the background color
basicstyle=\footnotesize, % size of fonts used for the code
breaklines=true, % automatic line breaking only at whitespace
captionpos=b, % sets the caption-position to bottom
commentstyle=\color{gray}, % comment style
escapeinside={\%*}{*)}, % if you want to add LaTeX within your code
keywordstyle=\color{blue}, % keyword style
stringstyle=\color{purple}, % string literal style
}
\section{Interesting Code}
\subsection{Joystick ROS Drive Test}
\subsubsection{Code}
\begin{lstlisting}[language=python]
rospy.init_node("drive_tester")
self.pub = rospy.Publisher("/drive/motoroneandtwo", RoverMotorDrive, queue_size=1)
def __get_controller_data(self):
if (self.controller_aquired):
events = self.gamepad.read()
for event in events:
if event.code in self.raw_mapping_to_class_mapping:
self.controller_states[self.raw_mapping_to_class_mapping[event.code]] = event.state
# print "Logitech: %s" % self.controller_states
def __broadcast_if_ready(self):
drive = RoverMotorDrive()
axis = self.controller_states["left_stick_y_axis"]
drive.first_motor_direction = 1 if axis <= 512 else 0
drive.first_motor_speed = min(abs(self.controller_states["left_stick_y_axis"] - 512) * 128, 65535)
self.pub.publish(drive)
\end{lstlisting}
\subsubsection{Description}
These two methods and supporting lines above, taken from the testing class LogitechJoystick, contained in the file joystick\_drive\_test.py are the core of what is needed to get joystick data and broadcast it to the rover over a ROS topic.
These two methods are called on after another in a QThread. \_\_get\_controller\_data() reacts to motion events from the joystick and stores the current value of all axes and buttons in self.controller\_states. Then, in \_\_broadcast\_if\_ready(), and instantiation of the custom ROS message type, RoverMotorDrive, is made and values set to a scaled version of the raw values provided by the joystick. Finally, this data is published to the motor drive node and causes the ROS receiving node to see the data, send a message to the motor driver, and cause the motor to spin.
\subsection{Video Test}
\subsubsection{Code}
\begin{lstlisting}[language=python]
def toggle_video_display(self):
if self.video_enabled:
if self.video_subscriber:
self.video_subscriber.unregister()
self.new_frame = True
self.video_enabled = False
else:
new_topic = self.camera_topics[self.current_camera_settings["resolution"]]
self.video_subscriber = rospy.Subscriber(new_topic, CompressedImage, self.__image_data_received_callback)
self.video_enabled = True
\end{lstlisting}
\subsubsection{Description}
This very simple snippet is in the VideoReceiver class in VideoSystems. It is a demonstration of what is needed to properly disable the receiving of video data on a stream. Looking at the Subscriber line, you can see that there is an image callback associated with the subscription to a topic in ROS. This means that if you don't actually unsubscribe (or in this case, unregister) from a topic, as can be seen a few lines above, the data will continue being received even if you are not actively using it. Not doing this would cause unwanted bandwidth to be used.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,31 @@
# This config is copied from overleaf so output there will match compiled output here
# support for the glossaries package:
add_cus_dep('glo', 'gls', 0, 'makeglossaries');
add_cus_dep('acn', 'acr', 0, 'makeglossaries');
sub makeglossaries {
system("makeglossaries \"$_[0]\"");
}
# support for the nomencl package:
add_cus_dep('nlo', 'nls', 0, 'makenlo2nls');
sub makenlo2nls {
system("makeindex -s nomencl.ist -o \"$_[0].nls\" \"$_[0].nlo\"");
}
# from the documentation for V. 2.03 of asymptote:
sub asy {return system("asy \"$_[0]\"");}
add_cus_dep("asy","eps",0,"asy");
add_cus_dep("asy","pdf",0,"asy");
add_cus_dep("asy","tex",0,"asy");
# metapost rule from http://tex.stackexchange.com/questions/37134
add_cus_dep('mp', '1', 0, 'mpost');
sub mpost {
my $file = $_[0];
my ($name, $path) = fileparse($file);
pushd($path);
my $return = system "mpost $name";
popd();
return $return;
}

View File

@@ -0,0 +1,18 @@
# Makefile created by Corwin Perren
# Generic makefile for all LaTeX projects downloaded from overleaf
#
# All this makefile does is call perl against latexmkrc, which is
# the latex equivalent of make
LATEXMK_COMPILE_FLAGS = -pdf
LATEXMK_CLEAN_FLAGS = -c
.DEFAULT_GOAL := all
all: latexmk_output clean
latexmk_output:
perl latexmk.pl $(LATEXMK_COMPILE_FLAGS)
clean:
perl latexmk.pl $(LATEXMK_CLEAN_FLAGS)

View File

@@ -0,0 +1,7 @@
\section{Problems / Solutions}
\subsection{Mars Rover Electrical/Software Team Development Progress}
As the Mars Rover project is a very large multi-disciplinary project run by undergraduate volunteers, the team has historically had problems with being behind schedule, and this year is no different. The team lost their electrical lead last term and has been catching up since the team lead, Nick McComb, took over this position. This, plus normal delays in development have meant that many of the core systems that the capstone team needs to test against are not present, not ready to interact with in their current state, or in a few cases still in a research state. Corwin has attempted to alleviate some of this by helping assemble hardware for the electrical team and prioritizing some ground station development time for the Rover systems themselves. If need be, our capstone team could help with these other systems more if need be to help facilitate forward progress.
\subsection{ROS / QT Learning Curves}
The Robot Operating System and QT frameworks that are at the core of the ground station project are large, involved tools that require extensive practice and training to effectively use. As these are relatively new technologies to even the main Rover software team, the learning curve has slowed initial development. Overall, we don't think this will continue to be a problem as the team gets more and more familiar with them, but it definitely impacted development time up to this point.

View File

@@ -0,0 +1,157 @@
\subsection{Chris Pham}
\subsubsection{Week 1}
This first week of classes for the three of us was mostly planning on what to exactly doing for the next 10 weeks and what exactly our working times are.
We planned for Tuesdays, Thursdays, and Saturdays because that's when Ken is supposed to be down and everyone seems to be free on those days.
Another thing that did come up was that the expectation that we should go to the OSU Robotics Club's Mars Rover Meetings on Saturday at 12 in the afternoon.
Another bonus of Saturday's is that that is when the team has working hours and we can interact with the people creating the systems on the rover side.
After deciding our times, we decided on who should do what task using our Asana and dictated by the club and our client.
It was decided that I would be doing Mapping and the others doing video and connection statuses.
\\
I was first prototyping using the Google Maps Static API and Python 2.7 to grab the tile at the given GPS location.
It worked, but I ended up thinking about how to stitch this object correctly.
I was also contemplating zooming in on the image instead of using stitching to remove complexity.
I do not have my code now from testing out the API because I reformatted this computer completely to install the requirements for the project, Ubuntu 16.04 and more importantly ROS.
\subsubsection{Week 2}
On Saturday, I started working on trying to search how to stitch images using Google Images.
I was told that last year they used this class package called GooMPy that took care of that for them and I spent my time looking at this code and trying to understand what it does.
We also took a group photo and I was introduced to the software team of the Rover.
It turned out they're mostly Freshman and they did not finish their Python reviews yet, so they could not start on the rover software that we need to interact with.
\\
On Tuesday, we met up in our club room and did some basic things with Qt Designer so we would be caught up on designing that.
I had some questions about some things like the API calling limit we have for Google Maps, and it turns out we will almost never hit the API limit.
Another thing was multi-threaded performance because how this file worked was that it did a bunch of Google Maps calls and then merged them with PIL (Python Image Library) and this looked doable to multi-thread.
However, one issue we have is the effect of Qt's QThreads and the multi-threading process for Python playing nicely with each other.
After talking about that, I then made created a branch in git and then built my files based upon GooMPy and made it more class based so it makes more sense for grouping and private variables.
The troubles with the structured format of the old file makes it hard to understand with the lack of comments and weird structures that is not expected in Python.
All progress during this week was during January 18, 2018:\\
\begin{center}
\begin{tabular}{l l l l} \textbf{Detail} & \textbf{Author} & \textbf{Date} &\textbf{Description}\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/bb8ed586e278eaa18b1861a0949afa53a743c876}{bb8ed58} & Chris Pham & 2018-01-18 10:37:41 -0800 &Make Mapping Core (mapping.py)\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/47942ffd1dcc2b3a1e29776d00a005f5a4247ea3}{47942ff} & Chris Pham & 2018-01-18 10:40:47 -0800 &Added Imports\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/c8c8f857cd2de7d01a5c790a22ac643a6b962436}{c8c8f85} & Chris Pham & 2018-01-18 10:47:56 -0800 &Add Constants\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/a9f36af40d4c3a00bb2336852d0db329f3b5905e}{a9f36af} & Chris Pham & 2018-01-18 10:53:39 -0800 &Read key from file\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/7e593097eab330b71a54b56251a1bea11e31a72c}{7e59309} & Chris Pham & 2018-01-18 10:55:42 -0800 &Made apikey file, and fixed open error\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/b82a39d3bf666abd8ed38839d6c5730b09e6ad04}{b82a39d} & Chris Pham & 2018-01-18 10:58:24 -0800 &fixed fp naming conventions\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/3570c4b5735bc2dbdf39bb5f52d34ccaa5bbee7e}{3570c4b} & Chris Pham & 2018-01-18 11:09:09 -0800 &write file intro\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/a9bf793bfc0db615d7d62bef7ac1d56f81c38d6c}{a9bf793} & Chris Pham & 2018-01-18 11:21:05 -0800 &add gmaps obj and init\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/c2bb5f6d31e6457d59e207c5f87edeeb2ac50e11}{c2bb5f6} & Chris Pham & 2018-01-18 12:14:06 -0800 &Added pixel\_to\_degrees helper\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/f203cee53e0a60c4dc8eed40dfae0a07e4649e59}{f203cee} & Chris Pham & 2018-01-18 12:24:01 -0800 &rebuild Google Maps API link\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/d09dbb1a02bc8883bc16e68aa8de74ee1b6ecfe6}{d09dbb1} & Chris Pham & 2018-01-18 12:43:03 -0800 &Addded \_grab\_tile helper\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/16f76fad49699b346e15c0fdcfc2edcb814a0123}{16f76fa} & Chris Pham & 2018-01-18 13:00:53 -0800 &added \_pixels\_to\_lon helper\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/b7348ee897a3bbff349d2d04d7c2084bc60c46f0}{b7348ee} & Chris Pham & 2018-01-18 13:07:21 -0800 &Fix self in pixel\_to\_lon helper\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/400410cea4af92cb25fd1f686809f320c9cc102c}{400410c} & Chris Pham & 2018-01-18 13:21:38 -0800 &Added \_pixels\_to\_lat\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/d2f4fc38fecf903ed35d731dafe566709707601c}{d2f4fc3} & Chris Pham & 2018-01-18 13:23:48 -0800 &Add gitignore for API key\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/b319118e1d32c8d655afe7a6cd084916d16f8db3}{b319118} & Chris Pham & 2018-01-18 13:26:03 -0800 &remove key\\\hline
\end{tabular}
\end{center}
\subsubsection{Week 3}
During the meeting on Saturday, I was informed that there is a API key and signature that I should use so I don't get throttled nor limited on calls.
I was finishing up some of my helper functions for my class object and then fixed my code to fit PEP8 conventions.
Then to implement my code, I made a new file called sign.py to take in a string and then return me the correct signature to append to the string for Google to accept.
Once that was done, I built a testing script to call the object and then save the big image somewhere on my computer.
Turns out my laptop hates that and I froze my computer for 20 minutes and once that was done, I edited the .gitignore to ignore the .jpg files I was downloading from the API.
\\
On Thursday, I was trying to get my zooming to work correctly on this image, but I can't do it from the API and I would have to do it from Qt or from OpenGL.
I also made a smaller display image to display it onto the display space on the GUI and for my computer to run.
\begin{center}
\begin{tabular}{l l l l}
\textbf{Detail} & \textbf{Author} & \textbf{Date} &\textbf{Description}\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/995386ee89319296165900b1466b6cfc5cf060d5}{995386e} & Chris Pham & 2018-01-20 13:06:28 -0800 &Made pixels\_to\_meters helper\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/fee5b5537bbdb496791f7b7f323aae6995844afc}{fee5b55} & Chris Pham & 2018-01-20 13:24:32 -0800 &Added logic for num\_sqares\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/3565e7b57f1f19a81b9b34b88a8c9ef527e7c7d6}{3565e7b} & Chris Pham & 2018-01-20 13:43:47 -0800 &Rebuild grab\_tile\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/e955442131978ffd1d24aa6ced9ff2c9f899ed59}{e955442} & Chris Pham & 2018-01-20 13:57:11 -0800 &Fixed to fit PEP\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/332c751758c837cb52b3a9008ba830c5755fbf76}{332c751} & Chris Pham & 2018-01-20 14:14:44 -0800 &Append signature\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/a0601abb8db35bf4a7ebcdba81f702814c73fdf2}{a0601ab} & Chris Pham & 2018-01-20 16:32:58 -0800 &Add testing file with updated gitignore for jpgs\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/1c16cb48a1faec2e8dc157748d0f870d1253fde7}{1c16cb4} & Chris Pham & 2018-01-25 10:49:36 -0800 &Creates Big Image\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/b12f8fc986912d75d018e8da9912eaa1c93b9e4b}{b12f8fc} & Chris Pham & 2018-01-25 15:01:35 -0800 &Working updates and fetching, starting on zoom functionality\\\hline
\end{tabular}
\end{center}
\subsubsection{Week 4}
This week, I had a bunch of troubles trying to get most of my stuff working correctly.
One big issue with my code was the latitude and longitude values to pixel coordinates. For some reason my values would be pointing to the wrong locations.
From the research I did about this issue, I can explain better why these equations are used at least.
I spent around 4 hours working on this and I had to leave to do something else on the side.
\\
I was thinking it could be the skew from the center of the map towards the outsides compared to just getting the outside values at the start.
I tried to get the boundaries when generating map and then cutting the value in half to get the approximate centering.
That did not work at all and I just gave up on it for a bit and went ahead and tried to program something else like putting icons on the map correctly.
Somehow, when I copied the code from the previous function, and then used it, it provided the correct locations and I was really confused.
Then I used the function that I just made and used that to center my map to see if that worked and it did.
Once I got that part working, I then made some way of adding icons to the image to quickly prototype my way-point system.
Here are some examples of my code via images, before and after:
\begin{figure}[ht!]
\begin{minipage}[t]{.45\linewidth}\centering
\includegraphics[width=\linewidth]{figures/unzoomed}
\caption{Before, Centered at Kelly}
\end{minipage}\hfill
\begin{minipage}[t]{.45\linewidth}\centering
\includegraphics[width=\linewidth]{figures/centered}
\caption{After, Centered at Kelly and points at MU and Valley}
\end{minipage}
\end{figure}
\newline
One of the next issues I thought about was trying to reduce computation time and I was thinking of using a way-point later to control and manage any drawing on the screen.
I can make a transparent layer/image with alpha levels with icons I can inject via PIL that I can draw each time instead of using disk time to get the images again
That can be done using Qt I think, or even PIL can do it, but it might take a hit on computation times.
\begin{center}
\begin{tabular}{l l l l} \textbf{Detail} & \textbf{Author} & \textbf{Date} &\textbf{Description}\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/3ed2c5977e41dc8f4baee7b10d7c3114793e29d7}{3ed2c59} & Chris Pham & 2018-02-01 19:25:54 -0800 &created and fixed centering\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/5f12d8074f8fcfb799f6ac3493cb39081bb7f7c3}{5f12d80} & Chris Pham & 2018-02-01 19:55:57 -0800 &starting on map updatiing\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/75adf70d5bfba0e4b7b48bf7cefbc88feb68f4de}{75adf70} & Chris Pham & 2018-02-02 11:30:16 -0800 &define \_\_str\_\_\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/d012360da50b074efb9a1cc1f7c1e0dacda207e7}{d012360} & Chris Pham & 2018-02-02 13:11:06 -0800 &remove prints\\\hline
\hline\end{tabular}
\end{center}
\subsubsection{Week 5}
This week was building and refactoring my object class so it had a helper class to do work for it too.
I rearranged it because I needed to use some of those function myself for building the way-point overlay system I going to implement for my system.
On Saturday, I merged my branch into the Master, but that took a bit of time because I had to rebase my branch into fitting the current master branch.
Once it was rebased to fit the master branch, I then had to do some 'git mv' to move some files around go the correct locations.
Once that was finished, I could finish my pull request and I could go on with some other tasks.
I started commenting and documenting my progress using docstrings and then applying PEP8 standard for the rest of my code, which meant that I needed to decrease the amount of characters were on a line most of the time.
After finishing up all of that, I started on making the coordinator between the class and the QObject that I needed to use to get Qt to work correctly with this object.
\begin{center}
\begin{tabular}{l l l l}\textbf{Detail} & \textbf{Author} & \textbf{Date} & \textbf{Description}\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/15154f912ff4204e8a3bcf48299592d1fb8bc2ca}{15154f9} & Chris Pham & 2018-02-03 12:49:43 -0800 &Merge branch 'Mapping\_Core' of https://github.com/OSURoboticsClub/Rover\_2017\_2018 into Mapping\_Core\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/07534f8487ba8a04d63e73c22385158479c3c6cc}{07534f8} & Chris Pham & 2018-02-03 13:45:51 -0800 &Starting of mapping class refactor\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/06c605ebd5780e65f1f7ad077ef5cf7a136d2a18}{06c605e} & Chris Pham & 2018-02-03 13:46:46 -0800 &remove idea file\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/e65d4e5067d4b3f9b1223a57f7666131e2c2cf6f}{e65d4e5} & Chris Pham & 2018-02-03 13:50:32 -0800 &Move new\_image function to helper\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/37303e3a95020e41aadea474e2d6e95c543f5af0}{37303e3} & Chris Pham & 2018-02-03 13:53:21 -0800 &Moved fast\_round to helper\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/0cbd593380d86ca3d479b7e40167273641c8c04d}{0cbd593} & Chris Pham & 2018-02-03 13:56:15 -0800 &Add pixels\_to\_degrees to helper\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/10c9dc8451a582e67e8475e2bf161054191f1d02}{10c9dc8} & Chris Pham & 2018-02-03 14:02:23 -0800 &Make static functions and self reference for helper\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/4a66dbc0b5954672d4377a1c9e07c53ccd1d7323}{4a66dbc} & Chris Pham & 2018-02-03 14:06:46 -0800 &Moved pixels\_to\_meters to Helper\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/d17aaf5ef8343be017032c46fa391b9b27470bc2}{d17aaf5} & Chris Pham & 2018-02-03 14:37:43 -0800 &Added Docstrings for mapping.py\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/b402cd12d3ab294d280cddf85c28ac263f62ce91}{b402cd1} & Chris Pham & 2018-02-03 14:40:46 -0800 &created docstrings for maphelper\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/ad51c3d7940eb1c5baa8f4ac0b72fe115c7eb776}{ad51c3d} & Chris Pham & 2018-02-03 14:51:32 -0800 &Finished move\_latlon function\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/31ad2185cb708d66cdb29dc23597a8c241622b4f}{31ad218} & Chris Pham & 2018-02-03 14:52:20 -0800 &Comment out waypoints\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/23f3554af37f18f17b1ad9c79378f5fc185ebf1a}{23f3554} & Chris Pham & 2018-02-03 15:28:45 -0800 &complying with PEP8 standard\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/89e7739f54adcf9317261f4741d1860d9c41a838}{89e7739} & Chris Pham & 2018-02-08 12:58:45 -0800 &Start of RoverMapCoordinator\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/33694343176eaedff94334df4083b831ce1bc006}{3369434} & Chris Pham & 2018-02-08 13:33:32 -0800 &Changed file names and started initer for MapCoordinator\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/f4d6835e85304f1dd43cea14ac4eb17587b14606}{f4d6835} & Chris Pham & 2018-02-08 13:50:12 -0800 &create label for left xml for map location, and remove \_get\_map.\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/4455e5769389aa18ea396a3981cf3a3b989c6271}{4455e57} & Chris Pham & 2018-02-08 13:57:49 -0800 &fixed wrong size references\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/cdfc76789173155e7f6fdc8d27137149e6f41fc6}{cdfc767} & Chris Pham & 2018-02-08 14:02:43 -0800 &Corrcted bool and created the thread for the main class\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/bb3b47977b1818ff9d45c6144d134e6a34a7d316}{bb3b479} & Chris Pham & 2018-02-08 14:06:12 -0800 &Moved file to software/ground\_station\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/e9f03deaa25624aeec6509c72e782cc801fef521}{e9f03de} & Chris Pham & 2018-02-08 14:08:26 -0800 &Merge pull request \#9 from OSURoboticsClub/Mapping\_Core\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/f71bcba2b080bbc51f9d5eda2f2ae3010d332f68}{f71bcba} & Chris Pham & 2018-02-08 14:13:53 -0800 &Change the include to correct path and remove test file\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/fe53c5d5a73ac253acaef11fef448995f519cca8}{fe53c5d} & Chris Pham & 2018-02-08 14:36:12 -0800 &added init for mapping\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/79325aefdf731ab4fcadc9dee58e3359e6c3b289}{79325ae} & Chris Pham & 2018-02-08 14:39:46 -0800 &Added parenthesis\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/345c75886dfaf76865e56aedc130ec83f9dd8352}{345c758} & Chris Pham & 2018-02-08 14:40:48 -0800 &remove bang\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/8b903d1299d30611ca22aaebc5267e874de4c004}{8b903d1} & Chris Pham & 2018-02-08 14:44:20 -0800 &changed import calls and another bang\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/e64c42c45fa51556a9e4e2fba3f2e0450f75a134}{e64c42c} & Chris Pham & 2018-02-08 14:51:35 -0800 &correct super \_\_init\_\_ for map coord\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/cba5bc9b39738a42df87b09cea98ffc9599b9617}{cba5bc9} & Chris Pham & 2018-02-08 14:59:56 -0800 &Create setup\_signals for Map Coords\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/c50675b30154dfe9d87e9b7e2cb8d02caafc7397}{c50675b} & Chris Pham & 2018-02-08 15:05:31 -0800 &Add connect signals and slots to map coords\\\hline
\end{tabular}
\end{center}
\subsubsection{Week 6}
On Saturday, I created most of the Qt interfaces by emulation from Corwin's work on his Video threading and QObject, but I still was having troubles trying to get everything else to work correctly like correct placement of calls, signals, and emitters for the system.
On Tuesday, we went to class for our Elevator Pitch class and then we had our normal work day doing the Expo Poster and it looks nice from what I think.
I also got help from Corwin on that day to fix my coordinator.
The coordinator needs to be setup correctly because the wrong line before a call would halt the system and freeze the GUI or even the computer it is running on.
The coding was done on the NUC itself, and will be a part of Corwin's Commit that I will list below.
I also was tasked to validate and see how accurate the GPS location on the map for the system.
The reason for that task is for the systems assurance test and paperwork that needs to be submitted to the competition committee to allow the club to go to the Utah event.
\begin{center}
\begin{tabular}{l l l l}\textbf{Detail} & \textbf{Author} & \textbf{Date} & \textbf{Description}\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/bd4deb1b2afef1159783ac5775884bccb595b245}{bd4deb1} & Chris Pham & 2018-02-10 13:37:16 -0800 &Fix QObject thread for Map Coordinator\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/c90247643f816b5add089c604804ead6e8a73b3e}{c902476} & Corwin Perren & 2018-02-12 21:03:34 -0800 &workspace\\\hline
\end{tabular}
\end{center}

View File

@@ -0,0 +1,73 @@
\subsection{Corwin Perren}
\subsubsection{Week 1}
The first week Chris, Ken, and I met in Graf 306 after the first capstone class of the term. During this meeting I showed them the Rover hardware, ground station hardware, and the general workspace that we'd be spending much time in for the next two terms. We then also came up with a work schedule consisting of roughly five hours or more a day on Tuesdays, Thursdays, and Saturdays. Initial tasks we're divvied up and we began work on our respective parts. My task was to continue work on the video display systems that I'd written testing code for the previous term. The primary item I got working this week was adding camera name labels to the stream so that it would be easy to tell what camera was being viewed, even if the user changed where the stream was displayed. I also added FPS counters to my development streams so I could ensure they were displaying at 30 FPS or above, and made some code adjustments for efficiency. Plans were made to begin development of the auto quality adjustment systems for subsequent weeks. During this week, only a single video stream was being displayed.
\subsubsection{Week 2}
During week 2, I continued work on the video receiving and display class, starting by testing its reliability with network disconnections and running the software for an extended period of time. Once semi-verified, I began work refactoring my prototype class into two new classes, a VideoReceiver class and VideoCoordinator class. The VideoReceiver class will handle the low level grabbing of video frames from the ROS topic, adjusting which resolution it's subscribed to, and automatic quality adjustments based on changes to frame rates. It will also allow each instance's stream to be disabled, so that network bandwidth is not being used when the stream is not being displayed.
\\
The VideoCoordinator class determines which video streams are available upon launch, and creates instantiations of the VideoReceiver classes as necessary. It also handles displaying the frames provided by the VideoReceiver classes in the QLabels on the GUI that are being used to view the streams. Users can also left-click on the stream windows to tell the VideoCoordinator to change what stream is being displayed. A right-click will allow a particular stream to be disabled to save on network bandwidth.
\\
This week I also began refactoring the core program code structure and launch file so that we would have a solid foundation to work off of, and to allow us to more easily integrate our classes as the terms progress.
\subsubsection{Week 3}
I finished work on the core program structure during the beginning of this week, and added an additional feature to ensure the Rover itself was present on the network before starting, as the GUI is useless without it. It will also make it easy to tell if the radios are functioning down the line. As part of this rework, I added a shared object to be passed around to the classes we make so they have access to GUI elements, other instances of classes, and each other. This is mainly needed to make the signal/slot connections that Qt is based around.
\\
I made significant progress on writing the VideoReceiver and VideoCoordinator classes this week. Namely, the VideoCoordinator class could successfully dynamically adjust to the number of video streams present on launch, as opposed to hard coding the cameras in. To go along with this, changing which stream was displayed became possible on all three video displays, as well as disabling streams with the right-click described before. When disabled, the video now completely kills that streams connection to the Rover, so its bandwidth usage drops to zero. In a similar vein, if the user is cycling video streams and a particular stream has not been explicitly disabled, but is not active on a video display, the stream is "sleeped" by disabling the stream. Once it is being viewed again, the stream automatically re-activates.
\\
To finish off the week, I made placeholder blocks in the GUI design files that matched our design document layout. This was so that it would be easier to drop in actually useful GUI elements as they are designed.
\begin{figure}[h!]
\centering
\captionsetup{justification=centering}
\includegraphics[width=0.5\textwidth]{figures/video_working}
\caption{Three streams working}
\end{figure}
\subsubsection{Week 4}
This week, I began a slight departure from pure ground station work as it was becoming apparent that progress on the main Rover software and electrical systems were behind. Talking with the team lead, Nick McComb, it was determined that my time for a week or two would be best spent helping write ROS nodes for the Rover or firmware for micro-controllers. With this in mind, I worked on getting the GPS and Inertial Measurement Unit attached to a Teensy 3.2 sending data to the Rover. This involved making modifications to a Modbus RTU library so that it would be compatible with the Teensys. Once the firmware was working and properly sending out data, I then wrote a receiving node for ROS to take in the GPS and IMU data over serial and broadcast it to the rest of the ROS systems. This step was particularly helpful as our team now has working GPS data to use for mapping and waypoint systems on the ground station.I also began minimal work on a mock motor driver node for the Rover, so that we could more quickly be able to test remote driving.
\subsubsection{Week 5}
Week 5 was a particularly slow week as I had midterms. Very similarly to the previous week, I decided that my time was best spent trying to help with other aspects of the team, in this case electrical. The Rover's main control board, Iris, needed to be assembled and I have extensive soldering experience so I spent 17 hours over two days assembling, testing, and fixing problems with this main control board. As everything on the Rover has to communicate through this board, it is essential to have it working both for the main software team and capstone. I wrote a simple motor driving ROS node that communicated with another Teensy microcontroller attached to a motor driver and test motor.
\begin{figure}[h!]
\centering
\captionsetup{justification=centering}
\includegraphics[width=0.5\textwidth]{figures/iris}
\caption{Iris after assembly}
\end{figure}
\subsubsection{Week 6}
The final week, ending in this report, was mostly spent working on the rough draft of the Expo poster. Our team filled out the poster with all the major information and graphics and should only require minimal modification for the final revision. Due to having gotten a simple motor driver node working the previous week, I did write a simple example python script to read in joystick data on the ground station and broadcast it to the Rover, properly moving the motor. This was a very simple demo however, and needs a finalized version of the ROS motor driving nodes on the Rover in order to be integrated into the actual ground station software.
\subsubsection{Github Commits}
\begin{center}
\begin{tabular}{l l l l} \textbf{Detail} & \textbf{Author} & \textbf{Date} &\textbf{Description}\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/0f8103eb2dc990541e7f181bd4ac8afa3c5b06a1}{0f8103e} & Corwin Perren & 2018-01-12 00:43 &Changed the launch file to have the correct camera names.\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/b2c495e6b69c4f38cd2d4e6e1ac0a48703fac9f1}{b2c495e} & Corwin Perren & 2018-01-11 16:45 &Added names to the testing images and fps counters\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/53d8de2ea80d6034f0906da0aa5e2d3df0dd6aae}{53d8de2} & Corwin Perren & 2018-01-13 21:50 &Changed one of the camera names\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/2e40a51e830b5ffffd97820d597b73360b716d12}{2e40a51} & Corwin Perren & 2018-01-14 03:01 &Updated UDEV rules for camera names\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/c49b90b038ad2b31b2b1643f9f55f86c5cdfa658}{c49b90b} & Corwin Perren & 2018-01-20 17:16 &Added thrid resolution for cameras\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/08fc436fa277f839826840774823bae37c9bc45e}{08fc436} & Corwin Perren & 2018-01-22 22:42 &Updated UI Files\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/4d071d304ed3781c89f4a5067644981131a33226}{4d071d3} & Corwin Perren & 2018-01-23 09:07 &Updated layout of the main launcher, as well as ui files.\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/60ea2d9339e11a6ec749b47d5b5487ccb39d8f09}{60ea2d9} & Corwin Perren & 2018-01-23 09:27 &Added a dark stylesheet so things look nice\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/6270f7380f6fd9b73c99193c93860d4042816e7b}{6270f73} & Corwin Perren & 2018-01-23 10:22 &Launching RoverVideeoCoordinator. Added working logging... \\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/03706408d374a025982071ab5414694a2742d8ca}{0370640} & Corwin Perren & 2018-01-23 12:54 &Added check for ros master on startup. More work on video...\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/05c65645e9c4a87a6571797f7dd8680858f095f2}{05c6564} & Corwin Perren & 2018-01-23 14:44 &RoverVideoCoordinator now kind of working with VideoReceiver...\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/b85319665e7fa439f4834266e504547d630671eb}{b853196} & Corwin Perren & 2018-01-23 17:08 &Got changing video sources working, along with showing the...\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/6297a2af444c42c48861fc4852042402748274e5}{6297a2a} & Corwin Perren & 2018-01-25 15:46 &Video modifications and last changes\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/fc1b41178cea41bbf1d846e0178cee1cd1d800f9}{fc1b411} & Corwin Perren & 2018-01-25 15:49 &Changed layout for organizational coherence.\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/006f11af653ba82b0984a2de92f99dc3f240cdd0}{006f11a} & Corwin Perren & 2018-01-25 16:11 &Auto-sleep of background cameras works. Dynamic changing of...\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/0e72b84a930ab4a58539d535a9a177c3a0cb9f8b}{0e72b84} & Corwin Perren & 2018-01-25 16:17 &Removed workspace files\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/71aca163d60f6ea6bacbde141f42a9d693305c5d}{71aca16} & Corwin Perren & 2018-01-25 16:20 &Fixing things.\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/219bc0956cbec27066b3f25de0edcc7397a7ef12}{219bc09} & Corwin Perren & 2018-01-25 17:55 &Everything ready to do sensing of frame rates to adjust quality...\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/bb8bae2f55caca5f45d0ed77e9ab8ad933ccb8fc}{bb8bae2} & Corwin Perren & 2018-02-03 15:09 &Removed ublox. Added rover\_drive\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/940d2782c361acee060488d8b07636eb3fc2eb7f}{940d278} & Corwin Perren & 2018-02-03 17:12 &Added beginnings of getting motor control working from within...\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/ad4bf7d74c408d05a6d1788f1b981c0c8380865d}{ad4bf7d} & Corwin Perren & 2018-02-11 21:18 &Some new drive code. Firmware too, but that's not up yet.\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/c90247643f816b5add089c604804ead6e8a73b3e}{c902476} & Corwin Perren & 2018-02-12 21:03 &workspace\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/b5be2a6f2472909ed06ee3535c4eec78f6c601ab}{b5be2a6} & Corwin Perren & 2018-02-12 21:04 &Merge branch 'video\_testing'\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/aca4c0627c3ce4ed2f6b3c4513e8ffccbb2b2277}{aca4c06} & Corwin Perren & 2018-02-13 13:20 &Map now loads. Changed launcher to match monitors.\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/d13a0d2d69b27b816e33b2eade6f1e9f102bc0ef}{d13a0d2} & Corwin Perren & 2018-02-14 18:35 &Updated UI Files. Getting closer to actual layout.\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/f009383b3eb6f0868cc842b70d3430cd41be3816}{f009383} & Corwin Perren & 2018-02-14 18:39 &Added missing compass image\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/4698723bb0e747fb52d420056f2822ff04005f9d}{4698723} & Corwin Perren & 2018-02-14 18:51 &Changed right screen so it color matches\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/9b3fd50188b3a7504998bac4456ce3717f8a827b}{9b3fd50} & Corwin Perren & 2018-02-14 19:52 &Very rough joystick driving working\\\hline
\href{https://github.com/OSURoboticsClub/Rover_2017_2018/commit/b54c8c38566fa1de3f41249c2f4ac9f58b08f4ab}{b54c8c3} & Corwin Perren & 2018-02-15 18:33 &Updated README for ground station.\\\hline
\end{tabular}
\end{center}

View File

@@ -0,0 +1,63 @@
\subsection{Ken Steinfeldt}
\subsubsection{Week 1}
On the first day of class the entire group met in the OSURC Mars Rover team lab space in Graf 306.
During this meeting we familiarized ourselves with the equipment that we would be working with in the coming weeks.
This equipment included what currently existed of the mars rover hardware stack, the ground station hardware, and general hardware available around the lab space environment.
After becoming familiar with the hardware and lab environment we sat down to plan the future of the project.
The first order of business was to decide on regular, weekly, meeting times for us to work together.
It was decided that we would meet on Tuesdays and Thursdays from about 9AM to around 3PM, weekly.
It was also the expectation that the capstone team would meet with the Mars Rover club on Saturdays at noon.
Regularly meeting in the lab and with the rest of the Mars Rover team allows us to regularly interact with the hardware stack, keep tabs on rover development, and socialize with other members of the team who are not in the capstone project.
Finally, we reviewed the tasks that needed to be completed and discussed dependencies.
After this discussion tasks were assigned to each member.
Corwin would start with video feeds, Chris with mapping, and I with system statuses.
With this decided we set about familiarizing ourselves with our tasks and made plans to complete them.
I spent the remaining time that week setting up my development environment and learning about ROS, through the official ROS tutorial, and PyQt development.
\subsubsection{Week 2}
During week 2 I started working on the StatusCore module for the first time.
The purpose of the StatusCore module is to retrieve rover status information and display it to the user via the ground station UI.
This is done utilizing some ROS abstractions called publishers and subscribers.
In this case the SystemStatus module on the rover broadcasts via publishers, and the StatusCore module in the ground station software receives these broadcasts via subscribers.
My plan is to pick up the broadcasted statuses from the rover through a series of callback functions in the StatusCore module.
I hope to then push the results directly to the GUI after performing whatever minimal logic is necessary.
I spent the week learning about publishers and subscribers.
At this time the rover software team was creating their status publishers, so I was able to work with them in order to better understand the process and formulate a prototype for my StatusCore module.
\subsubsection{Week 3}
In the beginning of week 3 I was naive enough to believe that I would finish up my first iteration of the StatusCore module.
I was unable to do so.
However, at the end of this week, and with the help of the rover software team, I had a very simple template that could use going forward.
Though not all rover publishers had yet been built, this template includes callback functions with subscribers to each publisher had been built.
With this template in place I was confident with the foundation of the StatusCore module and determined that I would spend the next week creating the necessary GUI elements.
This plan would allow me to move forward with my work while the rover software team finished their SystemStatus module.
Once the SystemStatus module is complete I can simply add the necessary subscribers, implement some simple bounds-checking logic and wrap up StatusCore.
It is worth noting that during week 3 the mars rover repository was completely reorganized by Corwin at the request of many people within the overall project.
\subsubsection{Week 4}
As noted previously, my plan for week four was to create the necessary GUI elements for StatusCore.
The beginning of the week was spent learning the PyQt framework, which proved to be somewhat more complicated than I had anticipated.
However, by midweek I felt that I had a tenuous grasp of it and proceeded to move forward.
The system status portion of the GUI calls for a clock, a very necessary tool during competition, so I set about to implement one.
Implementing the clock was somewhat trivial but did require a small amount of time to write the logic and output the time.
After completing the clock I moved onto the stopwatch.
Building the stopwatch was, for me, a nontrivial process, and took some time to complete.
However, by the end of the week I had a working stopwatch module.
Currently the stopwatch uses two pushbuttons; start and reset, to operate.
In the future I plan to remove the pushbuttons and operate only on right and left mouse clicks.
\subsubsection{Week 5}
Week five I did not anticipate accomplishing much as I had many midterm assignments in other classes to complete.
Project time this week was spent on finishing the visual framework for the system statuses.
By the end of the week I had completed a very basic and very ugly GUI for StatusCore.
However, it was later determined that we would use a different style for the GUI that is focused more on boxes and 'lights' than text, and the the UI was quickly redesigned.
\subsubsection{Week 6}
On week six the group went the the elevator pitch meeting and participated and then spend the remaining time working on the midterm report.
A poster was rough draft was completed and done in such a way that only minimal changes will be required for the final poster.
Then, of course, the midterm report was written and the video report recorded.

View File

@@ -0,0 +1,6 @@
\section{Progress}
\input{progress/progress_summary}
\input{progress/chris}
\input{progress/ken}
\input{progress/corwin}
\input{progress/progress_remaining}

View File

@@ -0,0 +1,12 @@
\subsection{Remaining Work}
\begin{itemize}
\item Finish mapping
\item Finish system statuses
\item Finish video systems
\item HID Integration
\item Drive Coordinator
\item Waypoints Coordinator
\item Arm Visualizer
\item Arm Coordinator
\item Logging/Recording Coordinator
\end{itemize}

View File

@@ -0,0 +1,32 @@
\subsection{Current Progress}
\subsubsection{General}
\begin{itemize}
\item Program layout and launcher file refactored for clean organization, easy integration
\item Program will not launch and shows error if Rover not connected
\item UI files have placeholders for all GUI elements from design document
\end{itemize}
\subsubsection{Mapping}
\begin{itemize}
\item Can display a map on the main system
\item Need a GUI element to change the location
\item Embedded way-points work, but cannot remove them
\item Centering Works, need to build a toggle button
\end{itemize}
\subsubsection{System Statuses}
\begin{itemize}
\item Basic StatusCore module that subscribes to existing ROS publishers
\item Basic GUI visuals stubbed out
\item GUI stopwatch module
\end{itemize}
\subsubsection{Video Systems}
\begin{itemize}
\item Can view one or more video streams, that dynamically change depending on how many are broadcasting
\item GUI elements can change what stream is being viewed with a left click
\item GUI elements can disable a stream with a right click
\item Resolution can be adjusted, though it is being done manually for now
\item Stream quality adjustment is ready for FPS data to do automatic adjustment
\item Video streams stop network traffic when not being viewed
\end{itemize}

View File

@@ -0,0 +1,151 @@
\documentclass[onecolumn, draftclsnofoot, 10pt, compsoc]{IEEEtran}
\usepackage{graphicx}
\graphicspath{{./figures/}}
\usepackage{url}
\usepackage{setspace}
\usepackage{multicol}
\usepackage{pdflscape}
\usepackage{pdfpages}
\usepackage[british]{babel}
\usepackage{listings}
\usepackage{xcolor}
\usepackage{listings}
\usepackage{hyperref}
\usepackage{subfig}
\usepackage{geometry}
\geometry{textheight=9.5in, textwidth=7in}
% \overfullrule=2in
% 1. Fill in these details
\def \CapstoneTeamName{ Ground Station Software Team}
\def \CapstoneTeamNumber{ 30}
\def \GroupMemberOne{ Kenneth Steinfeldt}
\def \GroupMemberTwo{ Christopher Pham}
\def \GroupMemberThree{ Corwin Perren}
\def \CapstoneProjectName{ OSU Robotics Club\\Mars Rover Ground Station}
\def \CapstoneSponsorCompany{ OSU Robotics Club}
\def \CapstoneSponsorPerson{ Nick McComb}
%Personal \newcommands
\newcommand{\functRequ}[4]{
\item #1%
\par
\begin{itemize}
\item \textit{Description:} #2.%
\item \textit{Rationale:} #3.%
\item \textit{Dependencies:} #4%
\end{itemize}
}
\definecolor{backcolor}{rgb}{0.95,0.95,0.92}
\lstset{basicstyle=\ttfamily,
backgroundcolor=\color{backcolor},
showstringspaces=false,
commentstyle=\color{red},
keywordstyle=\color{blue},
columns=fullflexible,
breaklines=true,
postbreak=\mbox{\textcolor{red}{$\hookrightarrow$}\space},
}
% 2. Uncomment the appropriate line below so that the document type works
\def \DocType{ %Problem Statement
%Requirements Document
%Technology Review
%Design Document
Progress Report
}
\newcommand{\NameSigPair}[1]{
\par
\makebox[2.75in][r]{#1}
\hfill
\makebox[3.25in]{
\makebox[2.25in]{\hrulefill}
\hfill
\makebox[.75in]{\hrulefill}
}
\par\vspace{-12pt}
\textit{
\tiny\noindent
\makebox[2.75in]{}
\hfill
\makebox[3.25in]{
\makebox[2.25in][r]{Signature}
\hfill
\makebox[.75in][r]{Date}
}
}
}
% 3. If the document is not to be signed, uncomment the command below
\renewcommand{\NameSigPair}[1]{#1}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\begin{document}
\begin{titlepage}
\pagenumbering{gobble}
\begin{singlespace}
% 4. If you have a logo, use this includegraphics command to put it on the coversheet.
\begin{minipage}{7in}
\centering
\hspace*{-.7in}
$\vcenter{\hbox{\includegraphics[height=4cm]{Oregon_State_College_of_Engineering_Logo}}}$
\hspace*{.2in}
$\vcenter{\hbox{\includegraphics[height=2.5cm]{OSURCLogoOrange}}}$
\end{minipage}
\par\vspace{.35in}
\centering
\scshape{
\huge CS Capstone \DocType \par
{\large\today}\par
\vspace{.5in}
\textbf{\Huge\CapstoneProjectName}\par
\vfill
{\large Prepared for}\par
\Huge \CapstoneSponsorCompany\par
\vspace{5pt}
{\Large\NameSigPair{\CapstoneSponsorPerson}\par}
{\large Prepared by }\par
Group\CapstoneTeamNumber\par
% 5. comment out the line below this one if you do not wish to name your team
\CapstoneTeamName\par
\vspace{5pt}
{\Large
\NameSigPair{\GroupMemberOne}\par
\NameSigPair{\GroupMemberTwo}\par
\NameSigPair{\GroupMemberThree}\par
}
\vspace{20pt}
\begin{abstract}
% 6. Fill in your abstract
This document contains the summary of the purpose and goals of the ground station software, the progress our team has made during the first half of winter term, as well as problems and solutions to those problems we've encountered so far. Additionally, it contains snippets of useful code and images of our progress so far. Overall, this document provides a good overview of everything our team has accomplished up to this point in Winter term.
\end{abstract}
}
\end{singlespace}
\end{titlepage}
\newpage
\pagenumbering{arabic}
\tableofcontents
\clearpage
% Write stuff here....
\input{purpose/purpose}
\input{goals/goals}
\input{progress/progress}
\input{problems_solutions/problems_solutions}
\input{interesting_code/interesting_code}
\end{document}

View File

@@ -0,0 +1,6 @@
\section{Purpose}
The purpose of this project is to create ground station control software that will interface with the OSU Robotics Club's Mars Rover.
This software will take in controls via user input devices such as joysticks, a SpaceNav mouse, as well as traditional keyboard and mouse in order to send remote control commands.
These commands will allow a user to drive the Rover and control the Rover arm.
Additionally, this software will allow for viewing of video streams and status information being sent back to the ground station from the Rover.
The point of writing this software is to complete the package that is the Mars Rover competition robot so that it may compete in the University Rover Challenge taking place in June of 2018.

View File

@@ -0,0 +1,25 @@
int epin = 6;
void setup() {
Serial.begin(9600);
Serial1.begin(9600);
Serial.println("listening");
pinMode(epin,OUTPUT);
}
void loop() {
if(Serial1.available() > 0 ){
while(Serial1.available() >0 ){
Serial.write(Serial1.read());
//Serial.println();
}
}
digitalWrite(epin,HIGH);
delay(100);
Serial1.write("///SN=?\r\n");
delay(10);
digitalWrite(epin,LOW);
delay(500);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

View File

@@ -43,3 +43,5 @@ https://docs.google.com/spreadsheets/d/1TkVeK_GaS78QLqv8NcE_QGGE-NSj-3agn10lKnIS
- Missing 3v3 rail connection to VBAT pin on Teensy. Will program without it, but will not boot without it.
- For R46 vs R47 selection, only R47 is needed.
- No invert needed for S.BUS connection (bridge pins)
- No serial number spot on silkscreen
- Likely that this should use a high-speed USB hub instead of the current full-speed implementation. It is easy, without deliberate planning, to hit the bottleneck of the USB hub.

View File

@@ -37,4 +37,7 @@ https://docs.google.com/spreadsheets/d/1CobSEg-5mzBy_F1_ASbbnYLLLra0shLwDUG4rKD0
### Known Issues
#### Version 1
None (yet)
- Switching regulator that was purchased is not compatible with the design. A pin-compatible new one was specc'd and ordered, it is included in the BOM as "alternative" parts. Only U1, L1, C2, and R1 are changed. An 1N4148 diode also needs to be added per LT1933's datasheet. See below schematic:
<img src="files/motor_v1_bodge_1.jpg" width="600px">
- Missing 3v3 rail connection to VBAT on Teensy. Will program without it, but will not boot without it. Connect VBat (pin 21) to the 3v3 side of C12 (closest to the XTAL)
- MCU mistakenly connected to the 5v_SYS, needs to be connected to the 3v3 net (as there is no 5v net on this board)

View File

@@ -0,0 +1,9 @@
void setup() {
// put your setup code here, to run once:
}
void loop() {
// put your main code here, to run repeatedly:
}

View File

@@ -0,0 +1,9 @@
void setup() {
// put your setup code here, to run once:
}
void loop() {
// put your main code here, to run repeatedly:
}

View File

@@ -0,0 +1,131 @@
////////// Includes //////////
#include "SBUS.h"
#include <ModbusRtu.h>
#include "Arduino.h" // Needed so I can nicely define things
////////// Hardware / Data Enumerations //////////
enum HARDWARE {
TELEM_24V = A0,
TELEM_5V = A1,
USB_TELEM_5V = A2,
TELEM_3V3 = A3,
BAD_1 = 3,
BAD_2 = 4,
BAD_3 = 23,
BAD_4 = 24,
LED_RED = 1,
LED_GREEN = 32,
LED_BLUE = 6,
LED_BLUE_EXTRA = 13
};
enum MODBUS_REGISTERS {
// 0-15 are directly written from SBUS Class
VOLTAGE_24 = 16,
VOLTAGE_5 = 17,
USB_VOLTAGE_5 = 18,
VOLTAGE_3V3 = 19
};
////////// Global Variables //////////
const uint8_t node_id = 1;
const uint8_t mobus_serial_port_number = 2;
#define SBUS_HARDWARE_PORT Serial3
uint16_t modbus_data[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
uint8_t num_modbus_registers = 0;
int8_t poll_state = 0;
bool communication_good = false;
uint8_t message_count = 0;
uint8_t failSafe;
uint16_t lostFrames = 0;
uint16_t telem_24v_scalar = 37500;
////////// Class Instantiations //////////
SBUS x8r(SBUS_HARDWARE_PORT);
Modbus slave(node_id, mobus_serial_port_number, 0); // 0 in thrid param means regular UART
void setup() {
setup_hardware();
x8r.begin();
num_modbus_registers = sizeof(modbus_data) / sizeof(modbus_data[0]);
slave.begin(115200);
Serial.begin(9600);
}
void loop() {
poll_sbus();
poll_modbus();
set_leds();
poll_sensors();
}
void setup_hardware(){
// Setup pins as inputs / outputs
pinMode(HARDWARE::TELEM_24V, INPUT);
pinMode(HARDWARE::TELEM_5V, INPUT);
pinMode(HARDWARE::USB_TELEM_5V, INPUT);
pinMode(HARDWARE::TELEM_3V3, INPUT);
pinMode(HARDWARE::BAD_1, INPUT);
pinMode(HARDWARE::BAD_2, INPUT);
pinMode(HARDWARE::BAD_3, INPUT);
pinMode(HARDWARE::BAD_4, INPUT);
pinMode(HARDWARE::LED_RED, OUTPUT);
pinMode(HARDWARE::LED_GREEN, OUTPUT);
pinMode(HARDWARE::LED_BLUE, OUTPUT);
pinMode(HARDWARE::LED_BLUE_EXTRA, OUTPUT);
// Set default pin states
digitalWrite(HARDWARE::LED_RED, LOW);
digitalWrite(HARDWARE::LED_GREEN, HIGH);
digitalWrite(HARDWARE::LED_BLUE, HIGH);
digitalWrite(HARDWARE::LED_BLUE_EXTRA, LOW);
// Set teensy to increased analog resolution
analogReadResolution(13);
}
void poll_sbus(){
x8r.read(&modbus_data[0], &failSafe, &lostFrames);
}
void poll_sensors(){
modbus_data[MODBUS_REGISTERS::VOLTAGE_24] = telem_24v_scalar * (analogRead(HARDWARE::TELEM_24V) / 8192.0);
}
void poll_modbus(){
poll_state = slave.poll(modbus_data, num_modbus_registers);
communication_good = !slave.getTimeOutState();
}
void set_leds(){
if(poll_state > 4){
message_count++;
if(message_count > 2){
digitalWrite(HARDWARE::LED_BLUE_EXTRA, !digitalRead(HARDWARE::LED_BLUE_EXTRA));
message_count = 0;
}
digitalWrite(HARDWARE::LED_GREEN, LOW);
digitalWrite(HARDWARE::LED_RED, HIGH);
}else if(!communication_good){
digitalWrite(HARDWARE::LED_BLUE_EXTRA, LOW);
digitalWrite(HARDWARE::LED_GREEN, HIGH);
digitalWrite(HARDWARE::LED_RED, LOW);
}
}

View File

@@ -0,0 +1,718 @@
/***************************************************************************
This is a library for the BNO055 orientation sensor
Designed specifically to work with the Adafruit BNO055 Breakout.
Pick one up today in the adafruit shop!
------> http://www.adafruit.com/products
These sensors use I2C to communicate, 2 pins are required to interface.
Adafruit invests time and resources providing this open source code,
please support Adafruit andopen-source hardware by purchasing products
from Adafruit!
Written by KTOWN for Adafruit Industries.
!!!!!MODIFIED VERSION TO USE I2C_T3 LIBRARY FOR TEENSY INSTEAD OF WIRE
IN ORDER TO USE TEENSY'S BOTH I2C PORTS
Roald Baudoux - 2016-03
Updated 2016-03-15: i2c_bus type defined to set choice of Wire or Wire1
Updated 2016-08-17: added delay of 30 ms after function call "write8(BNO055_SYS_TRIGGER_ADDR, 0x20);"
MIT license, all text above must be included in any redistribution
***************************************************************************/
#if ARDUINO >= 100
#include "Arduino.h"
#else
#include "WProgram.h"
#endif
#include <math.h>
#include <limits.h>
#include "Adafruit_BNO055_t3.h" // MODIFIED RB
/***************************************************************************
CONSTRUCTOR
***************************************************************************/
/**************************************************************************/
/*!
@brief Instantiates a new Adafruit_BNO055 class
!!!!! MODIFIED TO BE COMPATIBLE WITH I2C_T3 library instead of Wire
// Params : I2C bus, sensor ID, address, I2C mode (master/slave), pins, pullups, freq, opMode (interrupt/DMA/immediate)
*/
/**************************************************************************/
// OLD : Adafruit_BNO055::Adafruit_BNO055(int32_t sensorID, uint8_t address)
Adafruit_BNO055::Adafruit_BNO055(i2c_bus iBus, int32_t sensorID, uint8_t aAddress, i2c_mode iMode, i2c_pins pins, i2c_pullup pullup, i2c_rate iRate, i2c_op_mode opeMode)
{
_iBus = iBus; // ADDED RB 0 for Wire() and 1 for Wire1();
_sensorID = sensorID;
_aAddress = aAddress;
_iMode = iMode; // ADDED RB
_pins = pins; // ADDED RB
_pullup = pullup; // ADDED RB
_iRate = iRate; // ADDED RB
_opeMode = opeMode; // ADDED RB
}
/***************************************************************************
PUBLIC FUNCTIONS
***************************************************************************/
/**************************************************************************/
/*!
@brief Sets up the HW
*/
/**************************************************************************/
bool Adafruit_BNO055::begin(adafruit_bno055_opmode_t aMode)
{
/* Enable I2C */
if (_iBus == WIRE_BUS)
{
Wire.begin(_iMode, _aAddress, _pins, _pullup, _iRate, _opeMode);
}
else
{
Wire1.begin(_iMode, _aAddress, _pins, _pullup, _iRate, _opeMode);
}
/* Make sure we have the right device */
uint8_t id = read8(BNO055_CHIP_ID_ADDR);
if(id != BNO055_ID)
{
delay(1000);
id = read8(BNO055_CHIP_ID_ADDR);
if(id != BNO055_ID) {
return false; // still not? ok bail
}
}
/* Switch to config mode (just in case since this is the default) */
setMode(OPERATION_MODE_CONFIG);
/* Reset */
write8(BNO055_SYS_TRIGGER_ADDR, 0x20);
delay(30); // ADDED RB
while (read8(BNO055_CHIP_ID_ADDR) != BNO055_ID)
{
delay(10);
}
delay(50);
/* Set to normal power mode */
write8(BNO055_PWR_MODE_ADDR, POWER_MODE_NORMAL);
delay(10);
write8(BNO055_PAGE_ID_ADDR, 0);
/* Set the output units */
/*
uint8_t unitsel = (0 << 7) | // Orientation = Android
(0 << 4) | // Temperature = Celsius
(0 << 2) | // Euler = Degrees
(1 << 1) | // Gyro = Rads
(0 << 0); // Accelerometer = m/s^2
write8(BNO055_UNIT_SEL_ADDR, unitsel);
*/
/* Configure axis mapping (see section 3.4) */
/*
write8(BNO055_AXIS_MAP_CONFIG_ADDR, REMAP_CONFIG_P2); // P0-P7, Default is P1
delay(10);
write8(BNO055_AXIS_MAP_SIGN_ADDR, REMAP_SIGN_P2); // P0-P7, Default is P1
delay(10);
*/
write8(BNO055_SYS_TRIGGER_ADDR, 0x0);
delay(10);
/* Set the requested operating mode (see section 3.3) */
setMode(aMode);
delay(20);
return true;
}
/**************************************************************************/
/*!
@brief Puts the chip in the specified operating mode
*/
/**************************************************************************/
void Adafruit_BNO055::setMode(adafruit_bno055_opmode_t aMode)
{
_aMode = aMode;
write8(BNO055_OPR_MODE_ADDR, _aMode);
delay(30);
}
/**************************************************************************/
/*!
@brief Use the external 32.768KHz crystal
*/
/**************************************************************************/
void Adafruit_BNO055::setExtCrystalUse(boolean usextal)
{
adafruit_bno055_opmode_t modeback = _aMode;
/* Switch to config mode (just in case since this is the default) */
setMode(OPERATION_MODE_CONFIG);
delay(25);
write8(BNO055_PAGE_ID_ADDR, 0);
if (usextal) {
write8(BNO055_SYS_TRIGGER_ADDR, 0x80);
} else {
write8(BNO055_SYS_TRIGGER_ADDR, 0x00);
}
delay(10);
/* Set the requested operating mode (see section 3.3) */
setMode(modeback);
delay(20);
}
/**************************************************************************/
/*!
@brief Gets the latest system status info
*/
/**************************************************************************/
void Adafruit_BNO055::getSystemStatus(uint8_t *system_status, uint8_t *self_test_result, uint8_t *system_error)
{
write8(BNO055_PAGE_ID_ADDR, 0);
/* System Status (see section 4.3.58)
---------------------------------
0 = Idle
1 = System Error
2 = Initializing Peripherals
3 = System Iniitalization
4 = Executing Self-Test
5 = Sensor fusio algorithm running
6 = System running without fusion algorithms */
if (system_status != 0)
*system_status = read8(BNO055_SYS_STAT_ADDR);
/* Self Test Results (see section )
--------------------------------
1 = test passed, 0 = test failed
Bit 0 = Accelerometer self test
Bit 1 = Magnetometer self test
Bit 2 = Gyroscope self test
Bit 3 = MCU self test
0x0F = all good! */
if (self_test_result != 0)
*self_test_result = read8(BNO055_SELFTEST_RESULT_ADDR);
/* System Error (see section 4.3.59)
---------------------------------
0 = No error
1 = Peripheral initialization error
2 = System initialization error
3 = Self test result failed
4 = Register map value out of range
5 = Register map address out of range
6 = Register map write error
7 = BNO low power mode not available for selected operat ion mode
8 = Accelerometer power mode not available
9 = Fusion algorithm configuration error
A = Sensor configuration error */
if (system_error != 0)
*system_error = read8(BNO055_SYS_ERR_ADDR);
delay(200);
}
/**************************************************************************/
/*!
@brief Gets the chip revision numbers
*/
/**************************************************************************/
void Adafruit_BNO055::getRevInfo(adafruit_bno055_rev_info_t* info)
{
uint8_t a, b;
memset(info, 0, sizeof(adafruit_bno055_rev_info_t));
/* Check the accelerometer revision */
info->accel_rev = read8(BNO055_ACCEL_REV_ID_ADDR);
/* Check the magnetometer revision */
info->mag_rev = read8(BNO055_MAG_REV_ID_ADDR);
/* Check the gyroscope revision */
info->gyro_rev = read8(BNO055_GYRO_REV_ID_ADDR);
/* Check the SW revision */
info->bl_rev = read8(BNO055_BL_REV_ID_ADDR);
a = read8(BNO055_SW_REV_ID_LSB_ADDR);
b = read8(BNO055_SW_REV_ID_MSB_ADDR);
info->sw_rev = (((uint16_t)b) << 8) | ((uint16_t)a);
}
/**************************************************************************/
/*!
@brief Gets current calibration state. Each value should be a uint8_t
pointer and it will be set to 0 if not calibrated and 3 if
fully calibrated.
*/
/**************************************************************************/
void Adafruit_BNO055::getCalibration(uint8_t* sys, uint8_t* gyro, uint8_t* accel, uint8_t* mag) {
uint8_t calData = read8(BNO055_CALIB_STAT_ADDR);
if (sys != NULL) {
*sys = (calData >> 6) & 0x03;
}
if (gyro != NULL) {
*gyro = (calData >> 4) & 0x03;
}
if (accel != NULL) {
*accel = (calData >> 2) & 0x03;
}
if (mag != NULL) {
*mag = calData & 0x03;
}
}
/**************************************************************************/
/*!
@brief Gets the temperature in degrees celsius
*/
/**************************************************************************/
int8_t Adafruit_BNO055::getTemp(void)
{
int8_t temp = (int8_t)(read8(BNO055_TEMP_ADDR));
return temp;
}
/**************************************************************************/
/*!
@brief Gets a vector reading from the specified source
*/
/**************************************************************************/
imu::Vector<3> Adafruit_BNO055::getVector(adafruit_vector_type_t vector_type)
{
imu::Vector<3> xyz;
uint8_t buffer[6];
memset (buffer, 0, 6);
int16_t x, y, z;
x = y = z = 0;
/* Read vector data (6 bytes) */
readLen((adafruit_bno055_reg_t)vector_type, buffer, 6);
x = ((int16_t)buffer[0]) | (((int16_t)buffer[1]) << 8);
y = ((int16_t)buffer[2]) | (((int16_t)buffer[3]) << 8);
z = ((int16_t)buffer[4]) | (((int16_t)buffer[5]) << 8);
/* Convert the value to an appropriate range (section 3.6.4) */
/* and assign the value to the Vector type */
switch(vector_type)
{
case VECTOR_MAGNETOMETER:
/* 1uT = 16 LSB */
xyz[0] = ((double)x)/16.0;
xyz[1] = ((double)y)/16.0;
xyz[2] = ((double)z)/16.0;
break;
case VECTOR_GYROSCOPE:
/* 1rps = 900 LSB */
xyz[0] = ((double)x)/900.0;
xyz[1] = ((double)y)/900.0;
xyz[2] = ((double)z)/900.0;
break;
case VECTOR_EULER:
/* 1 degree = 16 LSB */
xyz[0] = ((double)x)/16.0;
xyz[1] = ((double)y)/16.0;
xyz[2] = ((double)z)/16.0;
break;
case VECTOR_ACCELEROMETER:
case VECTOR_LINEARACCEL:
case VECTOR_GRAVITY:
/* 1m/s^2 = 100 LSB */
xyz[0] = ((double)x)/100.0;
xyz[1] = ((double)y)/100.0;
xyz[2] = ((double)z)/100.0;
break;
}
return xyz;
}
/**************************************************************************/
/*!
@brief Gets a quaternion reading from the specified source
*/
/**************************************************************************/
imu::Quaternion Adafruit_BNO055::getQuat(void)
{
uint8_t buffer[8];
memset (buffer, 0, 8);
int16_t x, y, z, w;
x = y = z = w = 0;
/* Read quat data (8 bytes) */
readLen(BNO055_QUATERNION_DATA_W_LSB_ADDR, buffer, 8);
w = (((uint16_t)buffer[1]) << 8) | ((uint16_t)buffer[0]);
x = (((uint16_t)buffer[3]) << 8) | ((uint16_t)buffer[2]);
y = (((uint16_t)buffer[5]) << 8) | ((uint16_t)buffer[4]);
z = (((uint16_t)buffer[7]) << 8) | ((uint16_t)buffer[6]);
/* Assign to Quaternion */
/* See http://ae-bst.resource.bosch.com/media/products/dokumente/bno055/BST_BNO055_DS000_12~1.pdf
3.6.5.5 Orientation (Quaternion) */
const double scale = (1.0 / (1<<14));
imu::Quaternion quat(scale * w, scale * x, scale * y, scale * z);
return quat;
}
/**************************************************************************/
/*!
@brief Provides the sensor_t data for this sensor
*/
/**************************************************************************/
void Adafruit_BNO055::getSensor(sensor_t *sensor)
{
/* Clear the sensor_t object */
memset(sensor, 0, sizeof(sensor_t));
/* Insert the sensor name in the fixed length char array */
strncpy (sensor->name, "BNO055", sizeof(sensor->name) - 1);
sensor->name[sizeof(sensor->name)- 1] = 0;
sensor->version = 1;
sensor->sensor_id = _sensorID;
sensor->type = SENSOR_TYPE_ORIENTATION;
sensor->min_delay = 0;
sensor->max_value = 0.0F;
sensor->min_value = 0.0F;
sensor->resolution = 0.01F;
}
/**************************************************************************/
/*!
@brief Reads the sensor and returns the data as a sensors_event_t
*/
/**************************************************************************/
bool Adafruit_BNO055::getEvent(sensors_event_t *event)
{
/* Clear the event */
memset(event, 0, sizeof(sensors_event_t));
event->version = sizeof(sensors_event_t);
event->sensor_id = _sensorID;
event->type = SENSOR_TYPE_ORIENTATION;
event->timestamp = millis();
/* Get a Euler angle sample for orientation */
imu::Vector<3> euler = getVector(Adafruit_BNO055::VECTOR_EULER);
event->orientation.x = euler.x();
event->orientation.y = euler.y();
event->orientation.z = euler.z();
return true;
}
/**************************************************************************/
/*!
@brief Reads the sensor's offset registers into a byte array
*/
/**************************************************************************/
bool Adafruit_BNO055::getSensorOffsets(uint8_t* calibData)
{
if (isFullyCalibrated())
{
adafruit_bno055_opmode_t lastMode = _aMode;
setMode(OPERATION_MODE_CONFIG);
readLen(ACCEL_OFFSET_X_LSB_ADDR, calibData, NUM_BNO055_OFFSET_REGISTERS);
setMode(lastMode);
return true;
}
return false;
}
/**************************************************************************/
/*!
@brief Reads the sensor's offset registers into an offset struct
*/
/**************************************************************************/
bool Adafruit_BNO055::getSensorOffsets(adafruit_bno055_offsets_t &offsets_type)
{
if (isFullyCalibrated())
{
adafruit_bno055_opmode_t lastMode = _aMode;
setMode(OPERATION_MODE_CONFIG);
delay(25);
offsets_type.accel_offset_x = (read8(ACCEL_OFFSET_X_MSB_ADDR) << 8) | (read8(ACCEL_OFFSET_X_LSB_ADDR));
offsets_type.accel_offset_y = (read8(ACCEL_OFFSET_Y_MSB_ADDR) << 8) | (read8(ACCEL_OFFSET_Y_LSB_ADDR));
offsets_type.accel_offset_z = (read8(ACCEL_OFFSET_Z_MSB_ADDR) << 8) | (read8(ACCEL_OFFSET_Z_LSB_ADDR));
offsets_type.gyro_offset_x = (read8(GYRO_OFFSET_X_MSB_ADDR) << 8) | (read8(GYRO_OFFSET_X_LSB_ADDR));
offsets_type.gyro_offset_y = (read8(GYRO_OFFSET_Y_MSB_ADDR) << 8) | (read8(GYRO_OFFSET_Y_LSB_ADDR));
offsets_type.gyro_offset_z = (read8(GYRO_OFFSET_Z_MSB_ADDR) << 8) | (read8(GYRO_OFFSET_Z_LSB_ADDR));
offsets_type.mag_offset_x = (read8(MAG_OFFSET_X_MSB_ADDR) << 8) | (read8(MAG_OFFSET_X_LSB_ADDR));
offsets_type.mag_offset_y = (read8(MAG_OFFSET_Y_MSB_ADDR) << 8) | (read8(MAG_OFFSET_Y_LSB_ADDR));
offsets_type.mag_offset_z = (read8(MAG_OFFSET_Z_MSB_ADDR) << 8) | (read8(MAG_OFFSET_Z_LSB_ADDR));
offsets_type.accel_radius = (read8(ACCEL_RADIUS_MSB_ADDR) << 8) | (read8(ACCEL_RADIUS_LSB_ADDR));
offsets_type.mag_radius = (read8(MAG_RADIUS_MSB_ADDR) << 8) | (read8(MAG_RADIUS_LSB_ADDR));
setMode(lastMode);
return true;
}
return false;
}
/**************************************************************************/
/*!
@brief Writes an array of calibration values to the sensor's offset registers
*/
/**************************************************************************/
void Adafruit_BNO055::setSensorOffsets(const uint8_t* calibData)
{
adafruit_bno055_opmode_t lastMode = _aMode;
setMode(OPERATION_MODE_CONFIG);
delay(25);
/* A writeLen() would make this much cleaner */
write8(ACCEL_OFFSET_X_LSB_ADDR, calibData[0]);
write8(ACCEL_OFFSET_X_MSB_ADDR, calibData[1]);
write8(ACCEL_OFFSET_Y_LSB_ADDR, calibData[2]);
write8(ACCEL_OFFSET_Y_MSB_ADDR, calibData[3]);
write8(ACCEL_OFFSET_Z_LSB_ADDR, calibData[4]);
write8(ACCEL_OFFSET_Z_MSB_ADDR, calibData[5]);
write8(GYRO_OFFSET_X_LSB_ADDR, calibData[6]);
write8(GYRO_OFFSET_X_MSB_ADDR, calibData[7]);
write8(GYRO_OFFSET_Y_LSB_ADDR, calibData[8]);
write8(GYRO_OFFSET_Y_MSB_ADDR, calibData[9]);
write8(GYRO_OFFSET_Z_LSB_ADDR, calibData[10]);
write8(GYRO_OFFSET_Z_MSB_ADDR, calibData[11]);
write8(MAG_OFFSET_X_LSB_ADDR, calibData[12]);
write8(MAG_OFFSET_X_MSB_ADDR, calibData[13]);
write8(MAG_OFFSET_Y_LSB_ADDR, calibData[14]);
write8(MAG_OFFSET_Y_MSB_ADDR, calibData[15]);
write8(MAG_OFFSET_Z_LSB_ADDR, calibData[16]);
write8(MAG_OFFSET_Z_MSB_ADDR, calibData[17]);
write8(ACCEL_RADIUS_LSB_ADDR, calibData[18]);
write8(ACCEL_RADIUS_MSB_ADDR, calibData[19]);
write8(MAG_RADIUS_LSB_ADDR, calibData[20]);
write8(MAG_RADIUS_MSB_ADDR, calibData[21]);
setMode(lastMode);
}
/**************************************************************************/
/*!
@brief Writes to the sensor's offset registers from an offset struct
*/
/**************************************************************************/
void Adafruit_BNO055::setSensorOffsets(const adafruit_bno055_offsets_t &offsets_type)
{
adafruit_bno055_opmode_t lastMode = _aMode;
setMode(OPERATION_MODE_CONFIG);
delay(25);
write8(ACCEL_OFFSET_X_LSB_ADDR, (offsets_type.accel_offset_x) & 0x0FF);
write8(ACCEL_OFFSET_X_MSB_ADDR, (offsets_type.accel_offset_x >> 8) & 0x0FF);
write8(ACCEL_OFFSET_Y_LSB_ADDR, (offsets_type.accel_offset_y) & 0x0FF);
write8(ACCEL_OFFSET_Y_MSB_ADDR, (offsets_type.accel_offset_y >> 8) & 0x0FF);
write8(ACCEL_OFFSET_Z_LSB_ADDR, (offsets_type.accel_offset_z) & 0x0FF);
write8(ACCEL_OFFSET_Z_MSB_ADDR, (offsets_type.accel_offset_z >> 8) & 0x0FF);
write8(GYRO_OFFSET_X_LSB_ADDR, (offsets_type.gyro_offset_x) & 0x0FF);
write8(GYRO_OFFSET_X_MSB_ADDR, (offsets_type.gyro_offset_x >> 8) & 0x0FF);
write8(GYRO_OFFSET_Y_LSB_ADDR, (offsets_type.gyro_offset_y) & 0x0FF);
write8(GYRO_OFFSET_Y_MSB_ADDR, (offsets_type.gyro_offset_y >> 8) & 0x0FF);
write8(GYRO_OFFSET_Z_LSB_ADDR, (offsets_type.gyro_offset_z) & 0x0FF);
write8(GYRO_OFFSET_Z_MSB_ADDR, (offsets_type.gyro_offset_z >> 8) & 0x0FF);
write8(MAG_OFFSET_X_LSB_ADDR, (offsets_type.mag_offset_x) & 0x0FF);
write8(MAG_OFFSET_X_MSB_ADDR, (offsets_type.mag_offset_x >> 8) & 0x0FF);
write8(MAG_OFFSET_Y_LSB_ADDR, (offsets_type.mag_offset_y) & 0x0FF);
write8(MAG_OFFSET_Y_MSB_ADDR, (offsets_type.mag_offset_y >> 8) & 0x0FF);
write8(MAG_OFFSET_Z_LSB_ADDR, (offsets_type.mag_offset_z) & 0x0FF);
write8(MAG_OFFSET_Z_MSB_ADDR, (offsets_type.mag_offset_z >> 8) & 0x0FF);
write8(ACCEL_RADIUS_LSB_ADDR, (offsets_type.accel_radius) & 0x0FF);
write8(ACCEL_RADIUS_MSB_ADDR, (offsets_type.accel_radius >> 8) & 0x0FF);
write8(MAG_RADIUS_LSB_ADDR, (offsets_type.mag_radius) & 0x0FF);
write8(MAG_RADIUS_MSB_ADDR, (offsets_type.mag_radius >> 8) & 0x0FF);
setMode(lastMode);
}
bool Adafruit_BNO055::isFullyCalibrated(void)
{
uint8_t system, gyro, accel, mag;
getCalibration(&system, &gyro, &accel, &mag);
if (system < 3 || gyro < 3 || accel < 3 || mag < 3)
return false;
return true;
}
/***************************************************************************
PRIVATE FUNCTIONS
***************************************************************************/
/**************************************************************************/
/*!
@brief Writes an 8 bit value over I2C
*/
/**************************************************************************/
bool Adafruit_BNO055::write8(adafruit_bno055_reg_t reg, byte value)
{
if (_iBus == WIRE_BUS)
{
Wire.beginTransmission(_aAddress); //// MODIFIED RB
#if ARDUINO >= 100
Wire.write((uint8_t)reg); /// TO BE MODIFIED? NO
Wire.write((uint8_t)value); /// TO BE MODIFIED? NO
#else
Wire.send(reg); /// TO BE MODIFIED? NO
Wire.send(value); /// TO BE MODIFIED? NO
#endif
Wire.endTransmission(); /// TO BE MODIFIED? NO
/* ToDo: Check for error! */
return true;
}
else
{
Wire1.beginTransmission(_aAddress); //// MODIFIED RB
#if ARDUINO >= 100
Wire1.write((uint8_t)reg); /// TO BE MODIFIED? NO
Wire1.write((uint8_t)value); /// TO BE MODIFIED? NO
#else
Wire1.send(reg); /// TO BE MODIFIED? NO
Wire1.send(value); /// TO BE MODIFIED? NO
#endif
Wire1.endTransmission(); /// TO BE MODIFIED? NO
/* ToDo: Check for error! */
return true;
}
}
/**************************************************************************/
/*!
@brief Reads an 8 bit value over I2C
*/
/**************************************************************************/
byte Adafruit_BNO055::read8(adafruit_bno055_reg_t reg )
{
byte value = 0;
if (_iBus == WIRE_BUS)
{
Wire.beginTransmission(_aAddress); /// TO BE MODIFIED? NO
#if ARDUINO >= 100
Wire.write((uint8_t)reg); /// TO BE MODIFIED? NO
#else
Wire.send(reg);
#endif
Wire.endTransmission();
Wire.requestFrom(_aAddress, (byte)1); /// TO BE MODIFIED? NO
#if ARDUINO >= 100
value = Wire.read(); /// TO BE MODIFIED? NO
#else
value = Wire.receive(); //// MODIFIED RB receive -> readByte
//value = Wire.readByte(); //// MODIFIED RB receive -> readByte
#endif
return value;
}
else
{
Wire1.beginTransmission(_aAddress); /// TO BE MODIFIED? NO
#if ARDUINO >= 100
Wire1.write((uint8_t)reg); /// TO BE MODIFIED? NO
#else
Wire1.send(reg);
#endif
Wire1.endTransmission();
Wire1.requestFrom(_aAddress, (byte)1); /// TO BE MODIFIED? NO
#if ARDUINO >= 100
value = Wire1.read(); /// TO BE MODIFIED? NO
#else
value = Wire1.readByte(); //// MODIFIED RB receive -> readByte
#endif
return value;
}
}
/**************************************************************************/
/*!
@brief Reads the specified number of bytes over I2C
*/
/**************************************************************************/
bool Adafruit_BNO055::readLen(adafruit_bno055_reg_t reg, byte * buffer, uint8_t len)
{
if (_iBus == WIRE_BUS)
{
Wire.beginTransmission(_aAddress); /// TO BE MODIFIED? NO
#if ARDUINO >= 100
Wire.write((uint8_t)reg); /// TO BE MODIFIED? NO
#else
Wire.send(reg); /// TO BE MODIFIED? NO
#endif
Wire.endTransmission();
Wire.requestFrom(_aAddress, (byte)len); /// TO BE MODIFIED? NO
for (uint8_t i = 0; i < len; i++)
{
#if ARDUINO >= 100
buffer[i] = Wire.read(); /// TO BE MODIFIED? NO
#else
buffer[i] = Wire.readByte(); //// MODIFIE RB receive -> readByte
#endif
}
/* ToDo: Check for errors! */
return true;
}
else
{
Wire1.beginTransmission(_aAddress); /// TO BE MODIFIED? NO
#if ARDUINO >= 100
Wire1.write((uint8_t)reg); /// TO BE MODIFIED? NO
#else
Wire1.send(reg); /// TO BE MODIFIED? NO
#endif
Wire1.endTransmission();
Wire1.requestFrom(_aAddress, (byte)len); /// TO BE MODIFIED? NO
for (uint8_t i = 0; i < len; i++)
{
#if ARDUINO >= 100
buffer[i] = Wire1.read(); /// TO BE MODIFIED? NO
#else
buffer[i] = Wire1.readByte(); //// MODIFIE RB receive -> readByte
#endif
}
/* ToDo: Check for errors! */
return true;
}
}

View File

@@ -0,0 +1,344 @@
/***************************************************************************
This is a library for the BNO055 orientation sensor
Designed specifically to work with the Adafruit BNO055 Breakout.
Pick one up today in the adafruit shop!
------> http://www.adafruit.com/products
These sensors use I2C to communicate, 2 pins are required to interface.
Adafruit invests time and resources providing this open source code,
please support Adafruit andopen-source hardware by purchasing products
from Adafruit!
Written by KTOWN for Adafruit Industries.
!!!!!MODIFIED VERSION TO USE I2C_T3 LIBRARY FOR TEENSY INSTEAD OF WIRE
IN ORDER TO USE TEENSY'S BOTH I2C PORTS
Roald Baudoux - 2016-03
Updated 2016-03-15: i2c_bus type defined to set choice of Wire or Wire1
MIT license, all text above must be included in any redistribution
***************************************************************************/
#ifndef __ADAFRUIT_BNO055_H__
#define __ADAFRUIT_BNO055_H__
#if (ARDUINO >= 100)
#include "Arduino.h"
#else
#include "WProgram.h"
#endif
// code for AT_TINY85 deleted (no AT_TINY85 on Teensy boards)
#include <i2c_t3.h> // MODIFIED FROM Wire.h to i2c_t3.h
#include <Adafruit_Sensor.h>
#include <utility/imumaths.h>
#define BNO055_ADDRESS_A (0x28)
#define BNO055_ADDRESS_B (0x29)
#define BNO055_ID (0xA0)
#define NUM_BNO055_OFFSET_REGISTERS (22)
enum i2c_bus {WIRE_BUS, WIRE1_BUS}; // ADDED RB
typedef struct
{
uint16_t accel_offset_x;
uint16_t accel_offset_y;
uint16_t accel_offset_z;
uint16_t gyro_offset_x;
uint16_t gyro_offset_y;
uint16_t gyro_offset_z;
uint16_t mag_offset_x;
uint16_t mag_offset_y;
uint16_t mag_offset_z;
uint16_t accel_radius;
uint16_t mag_radius;
} adafruit_bno055_offsets_t;
class Adafruit_BNO055 : public Adafruit_Sensor
{
public:
typedef enum
{
/* Page id register definition */
BNO055_PAGE_ID_ADDR = 0X07,
/* PAGE0 REGISTER DEFINITION START*/
BNO055_CHIP_ID_ADDR = 0x00,
BNO055_ACCEL_REV_ID_ADDR = 0x01,
BNO055_MAG_REV_ID_ADDR = 0x02,
BNO055_GYRO_REV_ID_ADDR = 0x03,
BNO055_SW_REV_ID_LSB_ADDR = 0x04,
BNO055_SW_REV_ID_MSB_ADDR = 0x05,
BNO055_BL_REV_ID_ADDR = 0X06,
/* Accel data register */
BNO055_ACCEL_DATA_X_LSB_ADDR = 0X08,
BNO055_ACCEL_DATA_X_MSB_ADDR = 0X09,
BNO055_ACCEL_DATA_Y_LSB_ADDR = 0X0A,
BNO055_ACCEL_DATA_Y_MSB_ADDR = 0X0B,
BNO055_ACCEL_DATA_Z_LSB_ADDR = 0X0C,
BNO055_ACCEL_DATA_Z_MSB_ADDR = 0X0D,
/* Mag data register */
BNO055_MAG_DATA_X_LSB_ADDR = 0X0E,
BNO055_MAG_DATA_X_MSB_ADDR = 0X0F,
BNO055_MAG_DATA_Y_LSB_ADDR = 0X10,
BNO055_MAG_DATA_Y_MSB_ADDR = 0X11,
BNO055_MAG_DATA_Z_LSB_ADDR = 0X12,
BNO055_MAG_DATA_Z_MSB_ADDR = 0X13,
/* Gyro data registers */
BNO055_GYRO_DATA_X_LSB_ADDR = 0X14,
BNO055_GYRO_DATA_X_MSB_ADDR = 0X15,
BNO055_GYRO_DATA_Y_LSB_ADDR = 0X16,
BNO055_GYRO_DATA_Y_MSB_ADDR = 0X17,
BNO055_GYRO_DATA_Z_LSB_ADDR = 0X18,
BNO055_GYRO_DATA_Z_MSB_ADDR = 0X19,
/* Euler data registers */
BNO055_EULER_H_LSB_ADDR = 0X1A,
BNO055_EULER_H_MSB_ADDR = 0X1B,
BNO055_EULER_R_LSB_ADDR = 0X1C,
BNO055_EULER_R_MSB_ADDR = 0X1D,
BNO055_EULER_P_LSB_ADDR = 0X1E,
BNO055_EULER_P_MSB_ADDR = 0X1F,
/* Quaternion data registers */
BNO055_QUATERNION_DATA_W_LSB_ADDR = 0X20,
BNO055_QUATERNION_DATA_W_MSB_ADDR = 0X21,
BNO055_QUATERNION_DATA_X_LSB_ADDR = 0X22,
BNO055_QUATERNION_DATA_X_MSB_ADDR = 0X23,
BNO055_QUATERNION_DATA_Y_LSB_ADDR = 0X24,
BNO055_QUATERNION_DATA_Y_MSB_ADDR = 0X25,
BNO055_QUATERNION_DATA_Z_LSB_ADDR = 0X26,
BNO055_QUATERNION_DATA_Z_MSB_ADDR = 0X27,
/* Linear acceleration data registers */
BNO055_LINEAR_ACCEL_DATA_X_LSB_ADDR = 0X28,
BNO055_LINEAR_ACCEL_DATA_X_MSB_ADDR = 0X29,
BNO055_LINEAR_ACCEL_DATA_Y_LSB_ADDR = 0X2A,
BNO055_LINEAR_ACCEL_DATA_Y_MSB_ADDR = 0X2B,
BNO055_LINEAR_ACCEL_DATA_Z_LSB_ADDR = 0X2C,
BNO055_LINEAR_ACCEL_DATA_Z_MSB_ADDR = 0X2D,
/* Gravity data registers */
BNO055_GRAVITY_DATA_X_LSB_ADDR = 0X2E,
BNO055_GRAVITY_DATA_X_MSB_ADDR = 0X2F,
BNO055_GRAVITY_DATA_Y_LSB_ADDR = 0X30,
BNO055_GRAVITY_DATA_Y_MSB_ADDR = 0X31,
BNO055_GRAVITY_DATA_Z_LSB_ADDR = 0X32,
BNO055_GRAVITY_DATA_Z_MSB_ADDR = 0X33,
/* Temperature data register */
BNO055_TEMP_ADDR = 0X34,
/* Status registers */
BNO055_CALIB_STAT_ADDR = 0X35,
BNO055_SELFTEST_RESULT_ADDR = 0X36,
BNO055_INTR_STAT_ADDR = 0X37,
BNO055_SYS_CLK_STAT_ADDR = 0X38,
BNO055_SYS_STAT_ADDR = 0X39,
BNO055_SYS_ERR_ADDR = 0X3A,
/* Unit selection register */
BNO055_UNIT_SEL_ADDR = 0X3B,
BNO055_DATA_SELECT_ADDR = 0X3C,
/* Mode registers */
BNO055_OPR_MODE_ADDR = 0X3D,
BNO055_PWR_MODE_ADDR = 0X3E,
BNO055_SYS_TRIGGER_ADDR = 0X3F,
BNO055_TEMP_SOURCE_ADDR = 0X40,
/* Axis remap registers */
BNO055_AXIS_MAP_CONFIG_ADDR = 0X41,
BNO055_AXIS_MAP_SIGN_ADDR = 0X42,
/* SIC registers */
BNO055_SIC_MATRIX_0_LSB_ADDR = 0X43,
BNO055_SIC_MATRIX_0_MSB_ADDR = 0X44,
BNO055_SIC_MATRIX_1_LSB_ADDR = 0X45,
BNO055_SIC_MATRIX_1_MSB_ADDR = 0X46,
BNO055_SIC_MATRIX_2_LSB_ADDR = 0X47,
BNO055_SIC_MATRIX_2_MSB_ADDR = 0X48,
BNO055_SIC_MATRIX_3_LSB_ADDR = 0X49,
BNO055_SIC_MATRIX_3_MSB_ADDR = 0X4A,
BNO055_SIC_MATRIX_4_LSB_ADDR = 0X4B,
BNO055_SIC_MATRIX_4_MSB_ADDR = 0X4C,
BNO055_SIC_MATRIX_5_LSB_ADDR = 0X4D,
BNO055_SIC_MATRIX_5_MSB_ADDR = 0X4E,
BNO055_SIC_MATRIX_6_LSB_ADDR = 0X4F,
BNO055_SIC_MATRIX_6_MSB_ADDR = 0X50,
BNO055_SIC_MATRIX_7_LSB_ADDR = 0X51,
BNO055_SIC_MATRIX_7_MSB_ADDR = 0X52,
BNO055_SIC_MATRIX_8_LSB_ADDR = 0X53,
BNO055_SIC_MATRIX_8_MSB_ADDR = 0X54,
/* Accelerometer Offset registers */
ACCEL_OFFSET_X_LSB_ADDR = 0X55,
ACCEL_OFFSET_X_MSB_ADDR = 0X56,
ACCEL_OFFSET_Y_LSB_ADDR = 0X57,
ACCEL_OFFSET_Y_MSB_ADDR = 0X58,
ACCEL_OFFSET_Z_LSB_ADDR = 0X59,
ACCEL_OFFSET_Z_MSB_ADDR = 0X5A,
/* Magnetometer Offset registers */
MAG_OFFSET_X_LSB_ADDR = 0X5B,
MAG_OFFSET_X_MSB_ADDR = 0X5C,
MAG_OFFSET_Y_LSB_ADDR = 0X5D,
MAG_OFFSET_Y_MSB_ADDR = 0X5E,
MAG_OFFSET_Z_LSB_ADDR = 0X5F,
MAG_OFFSET_Z_MSB_ADDR = 0X60,
/* Gyroscope Offset register s*/
GYRO_OFFSET_X_LSB_ADDR = 0X61,
GYRO_OFFSET_X_MSB_ADDR = 0X62,
GYRO_OFFSET_Y_LSB_ADDR = 0X63,
GYRO_OFFSET_Y_MSB_ADDR = 0X64,
GYRO_OFFSET_Z_LSB_ADDR = 0X65,
GYRO_OFFSET_Z_MSB_ADDR = 0X66,
/* Radius registers */
ACCEL_RADIUS_LSB_ADDR = 0X67,
ACCEL_RADIUS_MSB_ADDR = 0X68,
MAG_RADIUS_LSB_ADDR = 0X69,
MAG_RADIUS_MSB_ADDR = 0X6A
} adafruit_bno055_reg_t;
typedef enum
{
POWER_MODE_NORMAL = 0X00,
POWER_MODE_LOWPOWER = 0X01,
POWER_MODE_SUSPEND = 0X02
} adafruit_bno055_powermode_t;
typedef enum
{
/* Operation mode settings*/
OPERATION_MODE_CONFIG = 0X00,
OPERATION_MODE_ACCONLY = 0X01,
OPERATION_MODE_MAGONLY = 0X02,
OPERATION_MODE_GYRONLY = 0X03,
OPERATION_MODE_ACCMAG = 0X04,
OPERATION_MODE_ACCGYRO = 0X05,
OPERATION_MODE_MAGGYRO = 0X06,
OPERATION_MODE_AMG = 0X07,
OPERATION_MODE_IMUPLUS = 0X08,
OPERATION_MODE_COMPASS = 0X09,
OPERATION_MODE_M4G = 0X0A,
OPERATION_MODE_NDOF_FMC_OFF = 0X0B,
OPERATION_MODE_NDOF = 0X0C
} adafruit_bno055_opmode_t;
typedef enum
{
REMAP_CONFIG_P0 = 0x21,
REMAP_CONFIG_P1 = 0x24, // default
REMAP_CONFIG_P2 = 0x24,
REMAP_CONFIG_P3 = 0x21,
REMAP_CONFIG_P4 = 0x24,
REMAP_CONFIG_P5 = 0x21,
REMAP_CONFIG_P6 = 0x21,
REMAP_CONFIG_P7 = 0x24
} adafruit_bno055_axis_remap_config_t;
typedef enum
{
REMAP_SIGN_P0 = 0x04,
REMAP_SIGN_P1 = 0x00, // default
REMAP_SIGN_P2 = 0x06,
REMAP_SIGN_P3 = 0x02,
REMAP_SIGN_P4 = 0x03,
REMAP_SIGN_P5 = 0x01,
REMAP_SIGN_P6 = 0x07,
REMAP_SIGN_P7 = 0x05
} adafruit_bno055_axis_remap_sign_t;
typedef struct
{
uint8_t accel_rev;
uint8_t mag_rev;
uint8_t gyro_rev;
uint16_t sw_rev;
uint8_t bl_rev;
} adafruit_bno055_rev_info_t;
typedef enum
{
VECTOR_ACCELEROMETER = BNO055_ACCEL_DATA_X_LSB_ADDR,
VECTOR_MAGNETOMETER = BNO055_MAG_DATA_X_LSB_ADDR,
VECTOR_GYROSCOPE = BNO055_GYRO_DATA_X_LSB_ADDR,
VECTOR_EULER = BNO055_EULER_H_LSB_ADDR,
VECTOR_LINEARACCEL = BNO055_LINEAR_ACCEL_DATA_X_LSB_ADDR,
VECTOR_GRAVITY = BNO055_GRAVITY_DATA_X_LSB_ADDR
} adafruit_vector_type_t;
// Arduino Zero board check removed (No Zero in Teensy range)
/*
#ifdef ARDUINO_SAMD_ZERO
#error "On an arduino Zero, BNO055's ADR pin must be high. Fix that, then delete this line."
Adafruit_BNO055 ( int32_t sensorID = -1, uint8_t address = BNO055_ADDRESS_B );
#else
Adafruit_BNO055 ( int32_t sensorID = -1, uint8_t address = BNO055_ADDRESS_A );
#endif
*/
Adafruit_BNO055 (i2c_bus iBus = WIRE_BUS, int32_t sensorID = -1, uint8_t aAddress = BNO055_ADDRESS_A, i2c_mode iMode = I2C_MASTER, i2c_pins pins = I2C_PINS_18_19, i2c_pullup pullup = I2C_PULLUP_INT, i2c_rate iRate = I2C_RATE_100, i2c_op_mode opeMode = I2C_OP_MODE_ISR); // MODIFIED
bool begin ( adafruit_bno055_opmode_t aMode = OPERATION_MODE_NDOF );
void setMode ( adafruit_bno055_opmode_t aMode );
void getRevInfo ( adafruit_bno055_rev_info_t* );
void displayRevInfo ( void );
void setExtCrystalUse ( boolean usextal );
void getSystemStatus ( uint8_t *system_status,
uint8_t *self_test_result,
uint8_t *system_error);
void displaySystemStatus ( void );
void getCalibration ( uint8_t* system, uint8_t* gyro, uint8_t* accel, uint8_t* mag);
imu::Vector<3> getVector ( adafruit_vector_type_t vector_type );
imu::Quaternion getQuat ( void );
int8_t getTemp ( void );
/* Adafruit_Sensor implementation */
bool getEvent ( sensors_event_t* );
void getSensor ( sensor_t* );
/* Functions to deal with raw calibration data */
bool getSensorOffsets(uint8_t* calibData);
bool getSensorOffsets(adafruit_bno055_offsets_t &offsets_type);
void setSensorOffsets(const uint8_t* calibData);
void setSensorOffsets(const adafruit_bno055_offsets_t &offsets_type);
bool isFullyCalibrated(void);
private:
byte read8 ( adafruit_bno055_reg_t );
bool readLen ( adafruit_bno055_reg_t, byte* buffer, uint8_t len );
bool write8 ( adafruit_bno055_reg_t, byte value );
// bool _i2cBus; // ADDED : O for Wire and 1 for Wire1
uint8_t _aAddress;
int32_t _sensorID;
adafruit_bno055_opmode_t _aMode;
// ADDED RB :
i2c_bus _iBus; // Wire or Wire 1
i2c_mode _iMode; // MASTER OR SLAVE MODE
i2c_pins _pins;
i2c_pullup _pullup;
i2c_rate _iRate;
i2c_op_mode _opeMode;
};
#endif

View File

@@ -0,0 +1,30 @@
/*
Inertial Measurement Unit Maths Library
Copyright (C) 2013-2014 Samuel Cowen
www.camelsoftware.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef IMUMATH_H
#define IMUMATH_H
#include "vector.h"
#include "matrix.h"
#include "quaternion.h"
#endif

View File

@@ -0,0 +1,243 @@
/*
Inertial Measurement Unit Maths Library
Copyright (C) 2013-2014 Samuel Cowen
www.camelsoftware.com
Bug fixes and cleanups by Gé Vissers (gvissers@gmail.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef IMUMATH_MATRIX_HPP
#define IMUMATH_MATRIX_HPP
#include <string.h>
#include <stdint.h>
#include "vector.h"
namespace imu
{
template <uint8_t N> class Matrix
{
public:
Matrix()
{
memset(_cell_data, 0, N*N*sizeof(double));
}
Matrix(const Matrix &m)
{
for (int ij = 0; ij < N*N; ++ij)
{
_cell_data[ij] = m._cell_data[ij];
}
}
~Matrix()
{
}
Matrix& operator=(const Matrix& m)
{
for (int ij = 0; ij < N*N; ++ij)
{
_cell_data[ij] = m._cell_data[ij];
}
return *this;
}
Vector<N> row_to_vector(int i) const
{
Vector<N> ret;
for (int j = 0; j < N; j++)
{
ret[j] = cell(i, j);
}
return ret;
}
Vector<N> col_to_vector(int j) const
{
Vector<N> ret;
for (int i = 0; i < N; i++)
{
ret[i] = cell(i, j);
}
return ret;
}
void vector_to_row(const Vector<N>& v, int i)
{
for (int j = 0; j < N; j++)
{
cell(i, j) = v[j];
}
}
void vector_to_col(const Vector<N>& v, int j)
{
for (int i = 0; i < N; i++)
{
cell(i, j) = v[i];
}
}
double operator()(int i, int j) const
{
return cell(i, j);
}
double& operator()(int i, int j)
{
return cell(i, j);
}
double cell(int i, int j) const
{
return _cell_data[i*N+j];
}
double& cell(int i, int j)
{
return _cell_data[i*N+j];
}
Matrix operator+(const Matrix& m) const
{
Matrix ret;
for (int ij = 0; ij < N*N; ++ij)
{
ret._cell_data[ij] = _cell_data[ij] + m._cell_data[ij];
}
return ret;
}
Matrix operator-(const Matrix& m) const
{
Matrix ret;
for (int ij = 0; ij < N*N; ++ij)
{
ret._cell_data[ij] = _cell_data[ij] - m._cell_data[ij];
}
return ret;
}
Matrix operator*(double scalar) const
{
Matrix ret;
for (int ij = 0; ij < N*N; ++ij)
{
ret._cell_data[ij] = _cell_data[ij] * scalar;
}
return ret;
}
Matrix operator*(const Matrix& m) const
{
Matrix ret;
for (int i = 0; i < N; i++)
{
Vector<N> row = row_to_vector(i);
for (int j = 0; j < N; j++)
{
ret(i, j) = row.dot(m.col_to_vector(j));
}
}
return ret;
}
Matrix transpose() const
{
Matrix ret;
for (int i = 0; i < N; i++)
{
for (int j = 0; j < N; j++)
{
ret(j, i) = cell(i, j);
}
}
return ret;
}
Matrix<N-1> minor_matrix(int row, int col) const
{
Matrix<N-1> ret;
for (int i = 0, im = 0; i < N; i++)
{
if (i == row)
continue;
for (int j = 0, jm = 0; j < N; j++)
{
if (j != col)
{
ret(im, jm++) = cell(i, j);
}
}
im++;
}
return ret;
}
double determinant() const
{
// specialization for N == 1 given below this class
double det = 0.0, sign = 1.0;
for (int i = 0; i < N; ++i, sign = -sign)
det += sign * cell(0, i) * minor_matrix(0, i).determinant();
return det;
}
Matrix invert() const
{
Matrix ret;
double det = determinant();
for (int i = 0; i < N; i++)
{
for (int j = 0; j < N; j++)
{
ret(i, j) = minor_matrix(j, i).determinant() / det;
if ((i+j)%2 == 1)
ret(i, j) = -ret(i, j);
}
}
return ret;
}
double trace() const
{
double tr = 0.0;
for (int i = 0; i < N; ++i)
tr += cell(i, i);
return tr;
}
private:
double _cell_data[N*N];
};
template<>
inline double Matrix<1>::determinant() const
{
return cell(0, 0);
}
};
#endif

View File

@@ -0,0 +1,272 @@
/*
Inertial Measurement Unit Maths Library
Copyright (C) 2013-2014 Samuel Cowen
www.camelsoftware.com
Bug fixes and cleanups by Gé Vissers (gvissers@gmail.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef IMUMATH_QUATERNION_HPP
#define IMUMATH_QUATERNION_HPP
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <math.h>
#include "matrix.h"
namespace imu
{
class Quaternion
{
public:
Quaternion(): _w(1.0), _x(0.0), _y(0.0), _z(0.0) {}
Quaternion(double w, double x, double y, double z):
_w(w), _x(x), _y(y), _z(z) {}
Quaternion(double w, Vector<3> vec):
_w(w), _x(vec.x()), _y(vec.y()), _z(vec.z()) {}
double& w()
{
return _w;
}
double& x()
{
return _x;
}
double& y()
{
return _y;
}
double& z()
{
return _z;
}
double w() const
{
return _w;
}
double x() const
{
return _x;
}
double y() const
{
return _y;
}
double z() const
{
return _z;
}
double magnitude() const
{
return sqrt(_w*_w + _x*_x + _y*_y + _z*_z);
}
void normalize()
{
double mag = magnitude();
*this = this->scale(1/mag);
}
Quaternion conjugate() const
{
return Quaternion(_w, -_x, -_y, -_z);
}
void fromAxisAngle(const Vector<3>& axis, double theta)
{
_w = cos(theta/2);
//only need to calculate sine of half theta once
double sht = sin(theta/2);
_x = axis.x() * sht;
_y = axis.y() * sht;
_z = axis.z() * sht;
}
void fromMatrix(const Matrix<3>& m)
{
double tr = m.trace();
double S;
if (tr > 0)
{
S = sqrt(tr+1.0) * 2;
_w = 0.25 * S;
_x = (m(2, 1) - m(1, 2)) / S;
_y = (m(0, 2) - m(2, 0)) / S;
_z = (m(1, 0) - m(0, 1)) / S;
}
else if (m(0, 0) > m(1, 1) && m(0, 0) > m(2, 2))
{
S = sqrt(1.0 + m(0, 0) - m(1, 1) - m(2, 2)) * 2;
_w = (m(2, 1) - m(1, 2)) / S;
_x = 0.25 * S;
_y = (m(0, 1) + m(1, 0)) / S;
_z = (m(0, 2) + m(2, 0)) / S;
}
else if (m(1, 1) > m(2, 2))
{
S = sqrt(1.0 + m(1, 1) - m(0, 0) - m(2, 2)) * 2;
_w = (m(0, 2) - m(2, 0)) / S;
_x = (m(0, 1) + m(1, 0)) / S;
_y = 0.25 * S;
_z = (m(1, 2) + m(2, 1)) / S;
}
else
{
S = sqrt(1.0 + m(2, 2) - m(0, 0) - m(1, 1)) * 2;
_w = (m(1, 0) - m(0, 1)) / S;
_x = (m(0, 2) + m(2, 0)) / S;
_y = (m(1, 2) + m(2, 1)) / S;
_z = 0.25 * S;
}
}
void toAxisAngle(Vector<3>& axis, double& angle) const
{
double sqw = sqrt(1-_w*_w);
if (sqw == 0) //it's a singularity and divide by zero, avoid
return;
angle = 2 * acos(_w);
axis.x() = _x / sqw;
axis.y() = _y / sqw;
axis.z() = _z / sqw;
}
Matrix<3> toMatrix() const
{
Matrix<3> ret;
ret.cell(0, 0) = 1 - 2*_y*_y - 2*_z*_z;
ret.cell(0, 1) = 2*_x*_y - 2*_w*_z;
ret.cell(0, 2) = 2*_x*_z + 2*_w*_y;
ret.cell(1, 0) = 2*_x*_y + 2*_w*_z;
ret.cell(1, 1) = 1 - 2*_x*_x - 2*_z*_z;
ret.cell(1, 2) = 2*_y*_z - 2*_w*_x;
ret.cell(2, 0) = 2*_x*_z - 2*_w*_y;
ret.cell(2, 1) = 2*_y*_z + 2*_w*_x;
ret.cell(2, 2) = 1 - 2*_x*_x - 2*_y*_y;
return ret;
}
// Returns euler angles that represent the quaternion. Angles are
// returned in rotation order and right-handed about the specified
// axes:
//
// v[0] is applied 1st about z (ie, roll)
// v[1] is applied 2nd about y (ie, pitch)
// v[2] is applied 3rd about x (ie, yaw)
//
// Note that this means result.x() is not a rotation about x;
// similarly for result.z().
//
Vector<3> toEuler() const
{
Vector<3> ret;
double sqw = _w*_w;
double sqx = _x*_x;
double sqy = _y*_y;
double sqz = _z*_z;
ret.x() = atan2(2.0*(_x*_y+_z*_w),(sqx-sqy-sqz+sqw));
ret.y() = asin(-2.0*(_x*_z-_y*_w)/(sqx+sqy+sqz+sqw));
ret.z() = atan2(2.0*(_y*_z+_x*_w),(-sqx-sqy+sqz+sqw));
return ret;
}
Vector<3> toAngularVelocity(double dt) const
{
Vector<3> ret;
Quaternion one(1.0, 0.0, 0.0, 0.0);
Quaternion delta = one - *this;
Quaternion r = (delta/dt);
r = r * 2;
r = r * one;
ret.x() = r.x();
ret.y() = r.y();
ret.z() = r.z();
return ret;
}
Vector<3> rotateVector(const Vector<2>& v) const
{
return rotateVector(Vector<3>(v.x(), v.y()));
}
Vector<3> rotateVector(const Vector<3>& v) const
{
Vector<3> qv(_x, _y, _z);
Vector<3> t = qv.cross(v) * 2.0;
return v + t*_w + qv.cross(t);
}
Quaternion operator*(const Quaternion& q) const
{
return Quaternion(
_w*q._w - _x*q._x - _y*q._y - _z*q._z,
_w*q._x + _x*q._w + _y*q._z - _z*q._y,
_w*q._y - _x*q._z + _y*q._w + _z*q._x,
_w*q._z + _x*q._y - _y*q._x + _z*q._w
);
}
Quaternion operator+(const Quaternion& q) const
{
return Quaternion(_w + q._w, _x + q._x, _y + q._y, _z + q._z);
}
Quaternion operator-(const Quaternion& q) const
{
return Quaternion(_w - q._w, _x - q._x, _y - q._y, _z - q._z);
}
Quaternion operator/(double scalar) const
{
return Quaternion(_w / scalar, _x / scalar, _y / scalar, _z / scalar);
}
Quaternion operator*(double scalar) const
{
return scale(scalar);
}
Quaternion scale(double scalar) const
{
return Quaternion(_w * scalar, _x * scalar, _y * scalar, _z * scalar);
}
private:
double _w, _x, _y, _z;
};
} // namespace
#endif

View File

@@ -0,0 +1,227 @@
/*
Inertial Measurement Unit Maths Library
Copyright (C) 2013-2014 Samuel Cowen
www.camelsoftware.com
Bug fixes and cleanups by Gé Vissers (gvissers@gmail.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef IMUMATH_VECTOR_HPP
#define IMUMATH_VECTOR_HPP
#include <string.h>
#include <stdint.h>
#include <math.h>
namespace imu
{
template <uint8_t N> class Vector
{
public:
Vector()
{
memset(p_vec, 0, sizeof(double)*N);
}
Vector(double a)
{
memset(p_vec, 0, sizeof(double)*N);
p_vec[0] = a;
}
Vector(double a, double b)
{
memset(p_vec, 0, sizeof(double)*N);
p_vec[0] = a;
p_vec[1] = b;
}
Vector(double a, double b, double c)
{
memset(p_vec, 0, sizeof(double)*N);
p_vec[0] = a;
p_vec[1] = b;
p_vec[2] = c;
}
Vector(double a, double b, double c, double d)
{
memset(p_vec, 0, sizeof(double)*N);
p_vec[0] = a;
p_vec[1] = b;
p_vec[2] = c;
p_vec[3] = d;
}
Vector(const Vector<N> &v)
{
for (int x = 0; x < N; x++)
p_vec[x] = v.p_vec[x];
}
~Vector()
{
}
uint8_t n() { return N; }
double magnitude() const
{
double res = 0;
for (int i = 0; i < N; i++)
res += p_vec[i] * p_vec[i];
return sqrt(res);
}
void normalize()
{
double mag = magnitude();
if (isnan(mag) || mag == 0.0)
return;
for (int i = 0; i < N; i++)
p_vec[i] /= mag;
}
double dot(const Vector& v) const
{
double ret = 0;
for (int i = 0; i < N; i++)
ret += p_vec[i] * v.p_vec[i];
return ret;
}
// The cross product is only valid for vectors with 3 dimensions,
// with the exception of higher dimensional stuff that is beyond
// the intended scope of this library.
// Only a definition for N==3 is given below this class, using
// cross() with another value for N will result in a link error.
Vector cross(const Vector& v) const;
Vector scale(double scalar) const
{
Vector ret;
for(int i = 0; i < N; i++)
ret.p_vec[i] = p_vec[i] * scalar;
return ret;
}
Vector invert() const
{
Vector ret;
for(int i = 0; i < N; i++)
ret.p_vec[i] = -p_vec[i];
return ret;
}
Vector& operator=(const Vector& v)
{
for (int x = 0; x < N; x++ )
p_vec[x] = v.p_vec[x];
return *this;
}
double& operator [](int n)
{
return p_vec[n];
}
double operator [](int n) const
{
return p_vec[n];
}
double& operator ()(int n)
{
return p_vec[n];
}
double operator ()(int n) const
{
return p_vec[n];
}
Vector operator+(const Vector& v) const
{
Vector ret;
for(int i = 0; i < N; i++)
ret.p_vec[i] = p_vec[i] + v.p_vec[i];
return ret;
}
Vector operator-(const Vector& v) const
{
Vector ret;
for(int i = 0; i < N; i++)
ret.p_vec[i] = p_vec[i] - v.p_vec[i];
return ret;
}
Vector operator * (double scalar) const
{
return scale(scalar);
}
Vector operator / (double scalar) const
{
Vector ret;
for(int i = 0; i < N; i++)
ret.p_vec[i] = p_vec[i] / scalar;
return ret;
}
void toDegrees()
{
for(int i = 0; i < N; i++)
p_vec[i] *= 57.2957795131; //180/pi
}
void toRadians()
{
for(int i = 0; i < N; i++)
p_vec[i] *= 0.01745329251; //pi/180
}
double& x() { return p_vec[0]; }
double& y() { return p_vec[1]; }
double& z() { return p_vec[2]; }
double x() const { return p_vec[0]; }
double y() const { return p_vec[1]; }
double z() const { return p_vec[2]; }
private:
double p_vec[N];
};
template <>
inline Vector<3> Vector<3>::cross(const Vector& v) const
{
return Vector(
p_vec[1] * v.p_vec[2] - p_vec[2] * v.p_vec[1],
p_vec[2] * v.p_vec[0] - p_vec[0] * v.p_vec[2],
p_vec[0] * v.p_vec[1] - p_vec[1] * v.p_vec[0]
);
}
} // namespace
#endif

View File

@@ -0,0 +1,154 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software< /span>
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* Update by K. Townsend (Adafruit Industries) for lighter typedefs, and
* extended sensor support to include color, voltage and current */
#ifndef _ADAFRUIT_SENSOR_H
#define _ADAFRUIT_SENSOR_H
#if ARDUINO >= 100
#include "Arduino.h"
#include "Print.h"
#else
#include "WProgram.h"
#endif
/* Intentionally modeled after sensors.h in the Android API:
* https://github.com/android/platform_hardware_libhardware/blob/master/include/hardware/sensors.h */
/* Constants */
#define SENSORS_GRAVITY_EARTH (9.80665F) /**< Earth's gravity in m/s^2 */
#define SENSORS_GRAVITY_MOON (1.6F) /**< The moon's gravity in m/s^2 */
#define SENSORS_GRAVITY_SUN (275.0F) /**< The sun's gravity in m/s^2 */
#define SENSORS_GRAVITY_STANDARD (SENSORS_GRAVITY_EARTH)
#define SENSORS_MAGFIELD_EARTH_MAX (60.0F) /**< Maximum magnetic field on Earth's surface */
#define SENSORS_MAGFIELD_EARTH_MIN (30.0F) /**< Minimum magnetic field on Earth's surface */
#define SENSORS_PRESSURE_SEALEVELHPA (1013.25F) /**< Average sea level pressure is 1013.25 hPa */
#define SENSORS_DPS_TO_RADS (0.017453293F) /**< Degrees/s to rad/s multiplier */
#define SENSORS_GAUSS_TO_MICROTESLA (100) /**< Gauss to micro-Tesla multiplier */
/** Sensor types */
typedef enum
{
SENSOR_TYPE_ACCELEROMETER = (1), /**< Gravity + linear acceleration */
SENSOR_TYPE_MAGNETIC_FIELD = (2),
SENSOR_TYPE_ORIENTATION = (3),
SENSOR_TYPE_GYROSCOPE = (4),
SENSOR_TYPE_LIGHT = (5),
SENSOR_TYPE_PRESSURE = (6),
SENSOR_TYPE_PROXIMITY = (8),
SENSOR_TYPE_GRAVITY = (9),
SENSOR_TYPE_LINEAR_ACCELERATION = (10), /**< Acceleration not including gravity */
SENSOR_TYPE_ROTATION_VECTOR = (11),
SENSOR_TYPE_RELATIVE_HUMIDITY = (12),
SENSOR_TYPE_AMBIENT_TEMPERATURE = (13),
SENSOR_TYPE_VOLTAGE = (15),
SENSOR_TYPE_CURRENT = (16),
SENSOR_TYPE_COLOR = (17)
} sensors_type_t;
/** struct sensors_vec_s is used to return a vector in a common format. */
typedef struct {
union {
float v[3];
struct {
float x;
float y;
float z;
};
/* Orientation sensors */
struct {
float roll; /**< Rotation around the longitudinal axis (the plane body, 'X axis'). Roll is positive and increasing when moving downward. -90<39><=roll<=90<39> */
float pitch; /**< Rotation around the lateral axis (the wing span, 'Y axis'). Pitch is positive and increasing when moving upwards. -180<38><=pitch<=180<38>) */
float heading; /**< Angle between the longitudinal axis (the plane body) and magnetic north, measured clockwise when viewing from the top of the device. 0-359<35> */
};
};
int8_t status;
uint8_t reserved[3];
} sensors_vec_t;
/** struct sensors_color_s is used to return color data in a common format. */
typedef struct {
union {
float c[3];
/* RGB color space */
struct {
float r; /**< Red component */
float g; /**< Green component */
float b; /**< Blue component */
};
};
uint32_t rgba; /**< 24-bit RGBA value */
} sensors_color_t;
/* Sensor event (36 bytes) */
/** struct sensor_event_s is used to provide a single sensor event in a common format. */
typedef struct
{
int32_t version; /**< must be sizeof(struct sensors_event_t) */
int32_t sensor_id; /**< unique sensor identifier */
int32_t type; /**< sensor type */
int32_t reserved0; /**< reserved */
int32_t timestamp; /**< time is in milliseconds */
union
{
float data[4];
sensors_vec_t acceleration; /**< acceleration values are in meter per second per second (m/s^2) */
sensors_vec_t magnetic; /**< magnetic vector values are in micro-Tesla (uT) */
sensors_vec_t orientation; /**< orientation values are in degrees */
sensors_vec_t gyro; /**< gyroscope values are in rad/s */
float temperature; /**< temperature is in degrees centigrade (Celsius) */
float distance; /**< distance in centimeters */
float light; /**< light in SI lux units */
float pressure; /**< pressure in hectopascal (hPa) */
float relative_humidity; /**< relative humidity in percent */
float current; /**< current in milliamps (mA) */
float voltage; /**< voltage in volts (V) */
sensors_color_t color; /**< color in RGB component values */
};
} sensors_event_t;
/* Sensor details (40 bytes) */
/** struct sensor_s is used to describe basic information about a specific sensor. */
typedef struct
{
char name[12]; /**< sensor name */
int32_t version; /**< version of the hardware + driver */
int32_t sensor_id; /**< unique sensor identifier */
int32_t type; /**< this sensor's type (ex. SENSOR_TYPE_LIGHT) */
float max_value; /**< maximum value of this sensor's value in SI units */
float min_value; /**< minimum value of this sensor's value in SI units */
float resolution; /**< smallest difference between two values reported by this sensor */
int32_t min_delay; /**< min delay in microseconds between events. zero = not a constant rate */
} sensor_t;
class Adafruit_Sensor {
public:
// Constructor(s)
Adafruit_Sensor() {}
virtual ~Adafruit_Sensor() {}
// These must be defined by the subclass
virtual void enableAutoRange(bool enabled) {};
virtual bool getEvent(sensors_event_t*) = 0;
virtual void getSensor(sensor_t*) = 0;
private:
bool _autoRange;
};
#endif

View File

@@ -0,0 +1,221 @@
# Adafruit Unified Sensor Driver #
Many small embedded systems exist to collect data from sensors, analyse the data, and either take an appropriate action or send that sensor data to another system for processing.
One of the many challenges of embedded systems design is the fact that parts you used today may be out of production tomorrow, or system requirements may change and you may need to choose a different sensor down the road.
Creating new drivers is a relatively easy task, but integrating them into existing systems is both error prone and time consuming since sensors rarely use the exact same units of measurement.
By reducing all data to a single **sensors\_event\_t** 'type' and settling on specific, **standardised SI units** for each sensor family the same sensor types return values that are comparable with any other similar sensor. This enables you to switch sensor models with very little impact on the rest of the system, which can help mitigate some of the risks and problems of sensor availability and code reuse.
The unified sensor abstraction layer is also useful for data-logging and data-transmission since you only have one well-known type to log or transmit over the air or wire.
## Unified Sensor Drivers ##
The following drivers are based on the Adafruit Unified Sensor Driver:
**Accelerometers**
- [Adafruit\_ADXL345](https://github.com/adafruit/Adafruit_ADXL345)
- [Adafruit\_LSM303DLHC](https://github.com/adafruit/Adafruit_LSM303DLHC)
- [Adafruit\_MMA8451\_Library](https://github.com/adafruit/Adafruit_MMA8451_Library)
**Gyroscope**
- [Adafruit\_L3GD20\_U](https://github.com/adafruit/Adafruit_L3GD20_U)
**Light**
- [Adafruit\_TSL2561](https://github.com/adafruit/Adafruit_TSL2561)
- [Adafruit\_TSL2591\_Library](https://github.com/adafruit/Adafruit_TSL2591_Library)
**Magnetometers**
- [Adafruit\_LSM303DLHC](https://github.com/adafruit/Adafruit_LSM303DLHC)
- [Adafruit\_HMC5883\_Unified](https://github.com/adafruit/Adafruit_HMC5883_Unified)
**Barometric Pressure**
- [Adafruit\_BMP085\_Unified](https://github.com/adafruit/Adafruit_BMP085_Unified)
- [Adafruit\_BMP183\_Unified\_Library](https://github.com/adafruit/Adafruit_BMP183_Unified_Library)
**Humidity & Temperature**
- [DHT-sensor-library](https://github.com/adafruit/DHT-sensor-library)
**Orientation**
- [Adafruit_BNO055](https://github.com/adafruit/Adafruit_BNO055)
## How Does it Work? ##
Any driver that supports the Adafruit unified sensor abstraction layer will implement the Adafruit\_Sensor base class. There are two main typedefs and one enum defined in Adafruit_Sensor.h that are used to 'abstract' away the sensor details and values:
**Sensor Types (sensors\_type\_t)**
These pre-defined sensor types are used to properly handle the two related typedefs below, and allows us determine what types of units the sensor uses, etc.
```
/** Sensor types */
typedef enum
{
SENSOR_TYPE_ACCELEROMETER = (1),
SENSOR_TYPE_MAGNETIC_FIELD = (2),
SENSOR_TYPE_ORIENTATION = (3),
SENSOR_TYPE_GYROSCOPE = (4),
SENSOR_TYPE_LIGHT = (5),
SENSOR_TYPE_PRESSURE = (6),
SENSOR_TYPE_PROXIMITY = (8),
SENSOR_TYPE_GRAVITY = (9),
SENSOR_TYPE_LINEAR_ACCELERATION = (10),
SENSOR_TYPE_ROTATION_VECTOR = (11),
SENSOR_TYPE_RELATIVE_HUMIDITY = (12),
SENSOR_TYPE_AMBIENT_TEMPERATURE = (13),
SENSOR_TYPE_VOLTAGE = (15),
SENSOR_TYPE_CURRENT = (16),
SENSOR_TYPE_COLOR = (17)
} sensors_type_t;
```
**Sensor Details (sensor\_t)**
This typedef describes the specific capabilities of this sensor, and allows us to know what sensor we are using beneath the abstraction layer.
```
/* Sensor details (40 bytes) */
/** struct sensor_s is used to describe basic information about a specific sensor. */
typedef struct
{
char name[12];
int32_t version;
int32_t sensor_id;
int32_t type;
float max_value;
float min_value;
float resolution;
int32_t min_delay;
} sensor_t;
```
The individual fields are intended to be used as follows:
- **name**: The sensor name or ID, up to a maximum of twelve characters (ex. "MPL115A2")
- **version**: The version of the sensor HW and the driver to allow us to differentiate versions of the board or driver
- **sensor\_id**: A unique sensor identifier that is used to differentiate this specific sensor instance from any others that are present on the system or in the sensor network
- **type**: The sensor type, based on **sensors\_type\_t** in sensors.h
- **max\_value**: The maximum value that this sensor can return (in the appropriate SI unit)
- **min\_value**: The minimum value that this sensor can return (in the appropriate SI unit)
- **resolution**: The smallest difference between two values that this sensor can report (in the appropriate SI unit)
- **min\_delay**: The minimum delay in microseconds between two sensor events, or '0' if there is no constant sensor rate
**Sensor Data/Events (sensors\_event\_t)**
This typedef is used to return sensor data from any sensor supported by the abstraction layer, using standard SI units and scales.
```
/* Sensor event (36 bytes) */
/** struct sensor_event_s is used to provide a single sensor event in a common format. */
typedef struct
{
int32_t version;
int32_t sensor_id;
int32_t type;
int32_t reserved0;
int32_t timestamp;
union
{
float data[4];
sensors_vec_t acceleration;
sensors_vec_t magnetic;
sensors_vec_t orientation;
sensors_vec_t gyro;
float temperature;
float distance;
float light;
float pressure;
float relative_humidity;
float current;
float voltage;
sensors_color_t color;
};
} sensors_event_t;
```
It includes the following fields:
- **version**: Contain 'sizeof(sensors\_event\_t)' to identify which version of the API we're using in case this changes in the future
- **sensor\_id**: A unique sensor identifier that is used to differentiate this specific sensor instance from any others that are present on the system or in the sensor network (must match the sensor\_id value in the corresponding sensor\_t enum above!)
- **type**: the sensor type, based on **sensors\_type\_t** in sensors.h
- **timestamp**: time in milliseconds when the sensor value was read
- **data[4]**: An array of four 32-bit values that allows us to encapsulate any type of sensor data via a simple union (further described below)
**Required Functions**
In addition to the two standard types and the sensor type enum, all drivers based on Adafruit_Sensor must also implement the following two functions:
```
bool getEvent(sensors_event_t*);
```
Calling this function will populate the supplied sensors\_event\_t reference with the latest available sensor data. You should call this function as often as you want to update your data.
```
void getSensor(sensor_t*);
```
Calling this function will provide some basic information about the sensor (the sensor name, driver version, min and max values, etc.
**Standardised SI values for sensors\_event\_t**
A key part of the abstraction layer is the standardisation of values on SI units of a particular scale, which is accomplished via the data[4] union in sensors\_event\_t above. This 16 byte union includes fields for each main sensor type, and uses the following SI units and scales:
- **acceleration**: values are in **meter per second per second** (m/s^2)
- **magnetic**: values are in **micro-Tesla** (uT)
- **orientation**: values are in **degrees**
- **gyro**: values are in **rad/s**
- **temperature**: values in **degrees centigrade** (Celsius)
- **distance**: values are in **centimeters**
- **light**: values are in **SI lux** units
- **pressure**: values are in **hectopascal** (hPa)
- **relative\_humidity**: values are in **percent**
- **current**: values are in **milliamps** (mA)
- **voltage**: values are in **volts** (V)
- **color**: values are in 0..1.0 RGB channel luminosity and 32-bit RGBA format
## The Unified Driver Abstraction Layer in Practice ##
Using the unified sensor abstraction layer is relatively easy once a compliant driver has been created.
Every compliant sensor can now be read using a single, well-known 'type' (sensors\_event\_t), and there is a standardised way of interrogating a sensor about its specific capabilities (via sensor\_t).
An example of reading the [TSL2561](https://github.com/adafruit/Adafruit_TSL2561) light sensor can be seen below:
```
Adafruit_TSL2561 tsl = Adafruit_TSL2561(TSL2561_ADDR_FLOAT, 12345);
...
/* Get a new sensor event */
sensors_event_t event;
tsl.getEvent(&event);
/* Display the results (light is measured in lux) */
if (event.light)
{
Serial.print(event.light); Serial.println(" lux");
}
else
{
/* If event.light = 0 lux the sensor is probably saturated
and no reliable data could be generated! */
Serial.println("Sensor overload");
}
```
Similarly, we can get the basic technical capabilities of this sensor with the following code:
```
sensor_t sensor;
sensor_t sensor;
tsl.getSensor(&sensor);
/* Display the sensor details */
Serial.println("------------------------------------");
Serial.print ("Sensor: "); Serial.println(sensor.name);
Serial.print ("Driver Ver: "); Serial.println(sensor.version);
Serial.print ("Unique ID: "); Serial.println(sensor.sensor_id);
Serial.print ("Max Value: "); Serial.print(sensor.max_value); Serial.println(" lux");
Serial.print ("Min Value: "); Serial.print(sensor.min_value); Serial.println(" lux");
Serial.print ("Resolution: "); Serial.print(sensor.resolution); Serial.println(" lux");
Serial.println("------------------------------------");
Serial.println("");
```

View File

@@ -0,0 +1,9 @@
name=Adafruit Unified Sensor
version=1.0.2
author=Adafruit <info@adafruit.com>
maintainer=Adafruit <info@adafruit.com>
sentence=Required for all Adafruit Unified Sensor based libraries.
paragraph=A unified sensor abstraction layer used by many Adafruit sensor libraries.
category=Sensors
url=https://github.com/adafruit/Adafruit_Sensor
architectures=*

View File

@@ -0,0 +1,17 @@
# Auto detect text files and perform LF normalization
* text=auto
# Custom for Visual Studio
*.cs diff=csharp
# Standard to msysgit
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain

View File

@@ -0,0 +1,43 @@
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
# =========================
# Operating System Files
# =========================
# OSX
# =========================
.DS_Store
.AppleDouble
.LSOverride
# Thumbnails
._*
# Files that might appear on external disk
.Spotlight-V100
.Trashes
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk

View File

@@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
{one line to give the program's name and a brief idea of what it does.}
Copyright (C) {year} {name of author}
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
{project} Copyright (C) {year} {fullname}
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

View File

@@ -0,0 +1,79 @@
NeoGPS
======
This fully-configurable Arduino library uses _**minimal**_ RAM, PROGMEM and CPU time,
requiring as few as _**10 bytes of RAM**_, **866 bytes of PROGMEM**, and **less than 1mS of CPU time** per sentence.
It supports the following protocols and messages:
#### NMEA 0183
* GPGGA - System fix data
* GPGLL - Geographic Latitude and Longitude
* GPGSA - DOP and active satellites
* GPGST - Pseudo Range Error Statistics
* GPGSV - Satellites in View
* GPRMC - Recommended Minimum specific GPS/Transit data
* GPVTG - Course over ground and Ground speed
* GPZDA - UTC Time and Date
The "GP" prefix usually indicates an original [GPS](https://en.wikipedia.org/wiki/Satellite_navigation#GPS) source. NeoGPS parses *all* Talker IDs, including
* "GL" ([GLONASS](https://en.wikipedia.org/wiki/Satellite_navigation#GLONASS)),
* "BD" or "GB" ([BeiDou](https://en.wikipedia.org/wiki/Satellite_navigation#BeiDou)),
* "GA" ([Galileo](https://en.wikipedia.org/wiki/Satellite_navigation#Galileo)), and
* "GN" (mixed)
This means that GLRMC, GBRMC or BDRMC, GARMC and GNRMC from the latest GPS devices (e.g., ublox M8N) will also be correctly parsed. See discussion of Talker IDs in [Configurations](extras/doc/Configurations.md#enabledisable-the-talker-id-and-manufacturer-id-processing).
Most applications can be fully implemented with the standard NMEA messages above. They are supported by almost all GPS manufacturers. Additional messages can be added through derived classes.
Most applications will use this simple, familiar loop structure:
```
NMEAGPS gps;
gps_fix fix;
void loop()
{
while (gps.available( gps_port )) {
fix = gps.read();
doSomeWork( fix );
}
}
```
For more information on this loop, see the [Usage](extras/doc/Data%20Model.md#usage) section on the [Data Model](extras/doc/Data%20Model.md) page.
(This is the plain Arduino version of the [CosaGPS](https://github.com/SlashDevin/CosaGPS) library for [Cosa](https://github.com/mikaelpatel/Cosa).)
Goals
======
In an attempt to be reusable in a variety of different programming styles, this library supports:
* resource-constrained environments (e.g., ATTINY targets)
* sync or async operation (reading in `loop()` vs interrupt processing)
* event or polling (deferred handling vs. continuous calls in `loop()`)
* coherent fixes (merged from multiple sentences) vs. individual sentences
* optional buffering of fixes
* optional floating point
* configurable message sets, including hooks for implementing proprietary NMEA messages
* configurable message fields
* multiple protocols from same device
* any kind of input stream (Serial, [NeoSWSerial](https://github.com/SlashDevin/NeoSWSerial), I2C, PROGMEM arrays, etc.)
Inconceivable!
=============
Don't believe it? Check out these detailed sections:
Section | Description
-------- | ------------
[License](LICENSE) | The Fine Print
[Installing](extras/doc/Installing.md) | Copying files
[Data Model](extras/doc/Data%20Model.md) | How to parse and use GPS data
[Configurations](extras/doc/Configurations.md) | Tailoring NeoGPS to your needs
[Performance](extras/doc/Performance.md) | 37% to 72% faster! Really!
[RAM requirements](extras/doc/RAM.md) | Doing it without buffers!
[Program Space requirements](extras/doc/Program.md) | Making it fit
[Examples](extras/doc/Examples.md) | Programming styles
[Troubleshooting](extras/doc/Troubleshooting.md) | Troubleshooting
[Extending NeoGPS](extras/doc/Extending.md) | Using specific devices
[ublox](extras/doc/ublox.md) | ublox-specific code
[Tradeoffs](extras/doc/Tradeoffs.md) | Comparing to other libraries
[Acknowledgements](extras/doc/Acknowledgements.md) | Thanks!

View File

@@ -0,0 +1,168 @@
#include <NMEAGPS.h>
//======================================================================
// Program: NMEA.ino
//
// Description: This program uses the fix-oriented methods available() and
// read() to handle complete fix structures.
//
// When the last character of the LAST_SENTENCE_IN_INTERVAL (see NMEAGPS_cfg.h)
// is decoded, a completed fix structure becomes available and is returned
// from read(). The new fix is saved the 'fix' structure, and can be used
// anywhere, at any time.
//
// If no messages are enabled in NMEAGPS_cfg.h, or
// no 'gps_fix' members are enabled in GPSfix_cfg.h, no information will be
// parsed, copied or printed.
//
// Prerequisites:
// 1) Your GPS device has been correctly powered.
// Be careful when connecting 3.3V devices.
// 2) Your GPS device is correctly connected to an Arduino serial port.
// See GPSport.h for the default connections.
// 3) You know the default baud rate of your GPS device.
// If 9600 does not work, use NMEAdiagnostic.ino to
// scan for the correct baud rate.
// 4) LAST_SENTENCE_IN_INTERVAL is defined to be the sentence that is
// sent *last* in each update interval (usually once per second).
// The default is NMEAGPS::NMEA_RMC (see NMEAGPS_cfg.h). Other
// programs may need to use the sentence identified by NMEAorder.ino.
// 5) NMEAGPS_RECOGNIZE_ALL is defined in NMEAGPS_cfg.h
//
// 'Serial' is for debug output to the Serial Monitor window.
//
// License:
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// NeoGPS is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//
//======================================================================
//-------------------------------------------------------------------------
// The GPSport.h include file tries to choose a default serial port
// for the GPS device. If you know which serial port you want to use,
// edit the GPSport.h file.
#include <GPSport.h>
//------------------------------------------------------------
// For the NeoGPS example programs, "Streamers" is common set
// of printing and formatting routines for GPS data, in a
// Comma-Separated Values text format (aka CSV). The CSV
// data will be printed to the "debug output device".
// If you don't need these formatters, simply delete this section.
#include <Streamers.h>
//------------------------------------------------------------
// This object parses received characters
// into the gps.fix() data structure
static NMEAGPS gps;
//------------------------------------------------------------
// Define a set of GPS fix information. It will
// hold on to the various pieces as they are received from
// an RMC sentence. It can be used anywhere in your sketch.
static gps_fix fix;
//----------------------------------------------------------------
// This function gets called about once per second, during the GPS
// quiet time. It's the best place to do anything that might take
// a while: print a bunch of things, write to SD, send an SMS, etc.
//
// By doing the "hard" work during the quiet time, the CPU can get back to
// reading the GPS chars as they come in, so that no chars are lost.
static void doSomeWork()
{
// Print all the things!
trace_all( DEBUG_PORT, gps, fix );
} // doSomeWork
//------------------------------------
// This is the main GPS parsing loop.
static void GPSloop()
{
while (gps.available( gpsPort )) {
fix = gps.read();
doSomeWork();
}
} // GPSloop
//--------------------------
void setup()
{
DEBUG_PORT.begin(9600);
while (!DEBUG_PORT)
;
DEBUG_PORT.print( F("NMEA.INO: started\n") );
DEBUG_PORT.print( F(" fix object size = ") );
DEBUG_PORT.println( sizeof(gps.fix()) );
DEBUG_PORT.print( F(" gps object size = ") );
DEBUG_PORT.println( sizeof(gps) );
DEBUG_PORT.println( F("Looking for GPS device on " GPS_PORT_NAME) );
#ifndef NMEAGPS_RECOGNIZE_ALL
#error You must define NMEAGPS_RECOGNIZE_ALL in NMEAGPS_cfg.h!
#endif
#ifdef NMEAGPS_INTERRUPT_PROCESSING
#error You must *NOT* define NMEAGPS_INTERRUPT_PROCESSING in NMEAGPS_cfg.h!
#endif
#if !defined( NMEAGPS_PARSE_GGA ) & !defined( NMEAGPS_PARSE_GLL ) & \
!defined( NMEAGPS_PARSE_GSA ) & !defined( NMEAGPS_PARSE_GSV ) & \
!defined( NMEAGPS_PARSE_RMC ) & !defined( NMEAGPS_PARSE_VTG ) & \
!defined( NMEAGPS_PARSE_ZDA ) & !defined( NMEAGPS_PARSE_GST )
DEBUG_PORT.println( F("\nWARNING: No NMEA sentences are enabled: no fix data will be displayed.") );
#else
if (gps.merging == NMEAGPS::NO_MERGING) {
DEBUG_PORT.print ( F("\nWARNING: displaying data from ") );
DEBUG_PORT.print ( gps.string_for( LAST_SENTENCE_IN_INTERVAL ) );
DEBUG_PORT.print ( F(" sentences ONLY, and only if ") );
DEBUG_PORT.print ( gps.string_for( LAST_SENTENCE_IN_INTERVAL ) );
DEBUG_PORT.println( F(" is enabled.\n"
" Other sentences may be parsed, but their data will not be displayed.") );
}
#endif
DEBUG_PORT.print ( F("\nGPS quiet time is assumed to begin after a ") );
DEBUG_PORT.print ( gps.string_for( LAST_SENTENCE_IN_INTERVAL ) );
DEBUG_PORT.println( F(" sentence is received.\n"
" You should confirm this with NMEAorder.ino\n") );
trace_header( DEBUG_PORT );
DEBUG_PORT.flush();
gpsPort.begin( 9600 );
}
//--------------------------
void loop()
{
GPSloop();
}

View File

@@ -0,0 +1,107 @@
#include <NMEAGPS.h>
NMEAGPS gps; // This parses the GPS characters
gps_fix fix; // This holds on to the latest values
//======================================================================
// Program: NMEAGSV.ino
//
// Description: Display satellites in view, as reported by the GSV sentences.
//
// Prerequisites:
// 1) NMEA.ino works with your device (correct TX/RX pins and baud rate)
// 2) NMEAGPS_PARSE_SATELLITES and NMEAGPS_PARSE_SATELLITE_INFO are
// enabled in NMEAGPS_cfg.h
// 3) The GSV sentence has been enabled in NMEAGPS_cfg.h.
// 4) Your device emits the GSV sentence (use NMEAorder.ino to confirm).
// 5) LAST_SENTENCE_IN_INTERVAL has been set to GSV (or any other enabled sentence)
// in NMEAGPS_cfg.h (use NMEAorder.ino).
//
// 'Serial' is for debug output to the Serial Monitor window.
//
// License:
// Copyright (C) 2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// NeoGPS is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//
//======================================================================
#include <GPSport.h>
//-----------------
// Check configuration
#ifndef NMEAGPS_PARSE_GSV
#error You must define NMEAGPS_PARSE_GSV in NMEAGPS_cfg.h!
#endif
#ifndef NMEAGPS_PARSE_SATELLITES
#error You must define NMEAGPS_PARSE_SATELLITE in NMEAGPS_cfg.h!
#endif
#ifndef NMEAGPS_PARSE_SATELLITE_INFO
#error You must define NMEAGPS_PARSE_SATELLITE_INFO in NMEAGPS_cfg.h!
#endif
//-----------------
void setup()
{
DEBUG_PORT.begin(9600);
while (!Serial)
;
DEBUG_PORT.print( F("NeoGPS GSV example started\n") );
gpsPort.begin(9600);
} // setup
//-----------------
void loop()
{
while (gps.available( gpsPort )) {
fix = gps.read();
displaySatellitesInView();
}
} // loop
//-----------------
void displaySatellitesInView()
{
DEBUG_PORT.print( gps.sat_count );
DEBUG_PORT.print( ',' );
for (uint8_t i=0; i < gps.sat_count; i++) {
DEBUG_PORT.print( gps.satellites[i].id );
DEBUG_PORT.print( ' ' );
DEBUG_PORT.print( gps.satellites[i].elevation );
DEBUG_PORT.print( '/' );
DEBUG_PORT.print( gps.satellites[i].azimuth );
DEBUG_PORT.print( '@' );
if (gps.satellites[i].tracked)
DEBUG_PORT.print( gps.satellites[i].snr );
else
DEBUG_PORT.print( '-' );
DEBUG_PORT.print( F(", ") );
}
DEBUG_PORT.println();
} // displaySatellitesInView

View File

@@ -0,0 +1,433 @@
#include <Arduino.h>
#include <NMEAGPS.h>
//======================================================================
// Program: NMEASDlog.ino
//
// Description: This program is an interrupt-driven GPS logger.
// It uses the alternative serial port libraries NeoHWSerial,
// NeoSWSerial, or NeoICSerial.
//
// Prerequisites:
// 1) You have completed the requirements for NMEA_isr.ino
// 2) You have connected an SPI SD card and verified it is working
// with other SD utilities.
// 3) For logging faster than the default 1Hz rate, you have
// identified the required commands for your GPS device.
//
// 'Serial' is for debug output to the Serial Monitor window.
//
// License:
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// NeoGPS is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//
//======================================================================
#include <GPSport.h>
//#include <Streamers.h>
//----------------------------------------------------------------
// Check configuration
#if !defined( GPS_FIX_TIME ) || !defined( GPS_FIX_LOCATION )
#error You must define TIME and LOCATION in GPSfix_cfg.h
#endif
#if !defined( NMEAGPS_PARSE_RMC )
#error You must define NMEAGPS_PARSE_RMC in NMEAGPS_cfg.h!
#endif
#ifndef NMEAGPS_INTERRUPT_PROCESSING
#error You must define NMEAGPS_INTERRUPT_PROCESSING in NMEAGPS_cfg.h!
#endif
static const int LED = 13;
static NMEAGPS gps;
//----------------------------------------------------------------
// SD card includes and declarations
#include <SPI.h>
#include <SdFat.h>
SdFat SD;
const int chipSelect = 8;
//----------------------------------------------------------------
// For testing, it may be more convenient to simply print the
// GPS fix fields to the Serial Monitor. Simply uncomment
// this define to skip all SD operations. An SD card module
// does not have to be connected.
#define SIMULATE_SD
#ifdef SIMULATE_SD
auto &logfile = DEBUG_PORT;
#else
File logfile;
#endif
//----------------------------------------------------------------
// Utility to print a long integer like it's a float
// with 9 significant digits.
void printL( Print & outs, int32_t degE7 )
{
// Extract and print negative sign
if (degE7 < 0) {
degE7 = -degE7;
outs.print( '-' );
}
// Whole degrees
int32_t deg = degE7 / 10000000L;
outs.print( deg );
outs.print( '.' );
// Get fractional degrees
degE7 -= deg*10000000L;
// Print leading zeroes, if needed
if (degE7 < 10L)
outs.print( F("000000") );
else if (degE7 < 100L)
outs.print( F("00000") );
else if (degE7 < 1000L)
outs.print( F("0000") );
else if (degE7 < 10000L)
outs.print( F("000") );
else if (degE7 < 100000L)
outs.print( F("00") );
else if (degE7 < 1000000L)
outs.print( F("0") );
// Print fractional degrees
outs.print( degE7 );
}
//----------------------------------------------------------------
// Because the SD write can take a long time, GPSisr will store
// parsed data in the NMEAGPS 'buffer' array until GPSloop can get to it.
//
// The number of elements you should have in the array depends on
// two things: the speed of your SD card, and the update rate you
// have chosen.
//
// For an update rate of 10Hz (100ms period), two fixes are probably
// enough. Most cards take ~100ms to complete a write of 512 bytes.
// With FIX_MAX=2, two complete fixes can be stored, which were
// received in 200ms. A third fix can be started, giving a little
// more time before an overrun occurs, a total of about 250ms.
//
// If your card is slower or your update rate is faster, you should
// first build and run this program to determine the speed of your
// card. The time it takes to log one record is printed to the log
// file.
//
// After the program has run for a minute or two, remove the
// card and examine the loggingTimes. You may see that an interval
// was skipped, and you will also see an OVERRUN message on the
// DEBUG_PORT (usually Serial).
//
// You should calculate a new FIX_MAX from the maximum loggingTime
// you see in the log file:
//
// FIX_MAX = (max loggingTime)/(update period) + 1;
//
// For example, if the max loggingTime is 160ms, and the update period is
// 100ms (10Hz), then FIX_MAX = 160/100 + 1 = 2.
//
// Change the FIX_MAX value, build and run the program again. The
// SD log file should now contain all records, and no OVERRUN
// messages should have been printed on the DEBUG_PORT.
//
// If you do see an OVERRUN message, examine the loggingTime to see
// if it exceeded the maximum value from the previous build, and
// increase FIX_MAX.
//
// If you are also printing data to a Serial device, you could be
// printing too much information. In general, you must print less than
// (baudrate/10) characters per second. For example, if your baudrate
// is 9600, you must print less than 960 characters per second. And if
// the update rate is 10Hz, you must print no more than 96 characters
// per update.
//
// There are also restrictions on how much you should print to Serial in one
// section of code. If you print more than 64 characters (the output buffer
// size), then some prints will block until all characters can be stored in the
// output buffer.
//
// For example, if you try to print 80 characters, the first 64 characters
// will be immediately stored in the output buffer. However, the last 16
// characters must wait until the output buffer has room for 16 more
// characters. That takes 16 * (10/baudrate) milliseconds. At 9600 baud
// that will take 17ms. The loggingTimes will show no less than 17ms per
// record, and will occasionally include the longer SD write time of ~100ms.
//----------------------------------------------------------------
static void GPSloop()
{
if (gps.available()) {
gps_fix fix = gps.read();
// Log the fix information if we have a location and time
if (fix.valid.location && fix.valid.time) {
static uint16_t lastLoggingTime = 0;
uint16_t startLoggingTime = millis();
// If you like the CSV format implemented in Streamers.h,
// you could replace all these prints with
// trace_all( logFile, fix ); // uncomment include Streamers.h
printL( logfile, fix.latitudeL() );
logfile.print( ',' );
printL( logfile, fix.longitudeL() );
logfile.print(',');
if (fix.dateTime.hours < 10)
logfile.print( '0' );
logfile.print(fix.dateTime.hours);
logfile.print( ':' );
if (fix.dateTime.minutes < 10)
logfile.print( '0' );
logfile.print(fix.dateTime.minutes);
logfile.print( ':' );
if (fix.dateTime.seconds < 10)
logfile.print( '0' );
logfile.print(fix.dateTime.seconds);
logfile.print( '.' );
if (fix.dateTime_cs < 10)
logfile.print( '0' ); // leading zero for .05, for example
logfile.print(fix.dateTime_cs);
logfile.print(',');
logfile.print( lastLoggingTime ); // write how long the previous logging took
logfile.println();
// flush() is used to empty the contents of the SD buffer to the SD.
// If you don't call flush, the data will fill up the SdFat buffer
// of 512bytes and flush itself automatically.
//
// To avoid losing data or corrupting the SD card file system, you must
// call flush() at least once (or close()) before powering down or pulling
// the SD card.
//
// It is *strongly* recommended that you use some external event
// to close the file. For example, staying within 50m of the moving
// average location for 1 minute, or using a switch to start and stop
// logging. It would also be good to provide a visual indication
// that it is safe to power down and/or remove the card, perhaps via
// the LED.
//
// To reduce the amount of data that may be lost by an abnormal shut down,
// you can call flush() periodically.
//
// Depending on the amount of data you are printing, you can save
// *a lot* of CPU time by not flushing too frequently. BTW, flushing
// every time at 5Hz is too frequent.
// This shows how to flush once a second.
static uint16_t lastFlushTime = 0;
if (startLoggingTime - lastFlushTime > 1000) {
lastFlushTime = startLoggingTime; // close enough
logfile.flush();
}
#ifdef SIMULATE_SD
// Simulate the delay of writing to an SD card. These times are
// very long. This is intended to show (and test) the overrun detection.
//
// On a 1Hz GPS, delaying more than 2 seconds here, or more than
// 2 seconds in two consecutive updates, will cause OVERRUN.
//
// Feel free to try different delays to simulate the actual behavior
// of your SD card.
uint16_t t = random(0,5); // 0..4
t += 3; // 3..7
t = t*t*t*t; // 81..2401ms
delay( t ); // cause an OVERRUN
#endif
// All logging is finished, figure out how long that took.
// This will be written in the *next* record.
lastLoggingTime = (uint16_t) millis() - startLoggingTime;
}
}
} // GPSloop
//----------------------------------------------------------------
void GPSisr( uint8_t c )
{
gps.handle( c );
} // GPSisr
//----------------------------------------------------------------
// This routine waits for GPSisr to provide
// a fix that has a valid location.
//
// The LED is slowly flashed while it's waiting.
static void waitForFix()
{
DEBUG_PORT.print( F("Waiting for GPS fix...") );
uint16_t lastToggle = millis();
for (;;) {
if (gps.available()) {
if (gps.read().valid.location)
break; // Got it!
}
// Slowly flash the LED until we get a fix
if ((uint16_t) millis() - lastToggle > 500) {
lastToggle += 500;
digitalWrite( LED, !digitalRead(LED) );
DEBUG_PORT.write( '.' );
}
}
DEBUG_PORT.println();
digitalWrite( LED, LOW );
gps.overrun( false ); // we had to wait a while...
} // waitForFix
//----------------------------------------------------------------
void setup()
{
DEBUG_PORT.begin(9600);
while (!DEBUG_PORT)
; // wait for serial port to connect.
DEBUG_PORT.println( F("NMEASDlog.ino started!") );
DEBUG_PORT.print( F("fix size = ") );
DEBUG_PORT.println( sizeof(gps_fix) );
DEBUG_PORT.print( NMEAGPS_FIX_MAX );
DEBUG_PORT.println( F(" GPS updates can be buffered.") );
if (gps.merging != NMEAGPS::EXPLICIT_MERGING)
DEBUG_PORT.println( F("Warning: EXPLICIT_MERGING should be enabled for best results!") );
gpsPort.attachInterrupt( GPSisr );
gpsPort.begin( 9600 );
// Configure the GPS. These are commands for MTK GPS devices. Other
// brands will have different commands.
gps.send_P( &gpsPort, F("PMTK314,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0") ); // RMC only for MTK GPS devices
gps.send_P( &gpsPort, F("PMTK220,100") ); // 10Hz update rate for MTK GPS devices
// Enable the LED for blinking feedback
pinMode( LED, OUTPUT );
initSD();
waitForFix();
} // setup
//----------------------------------------------------------------
void loop()
{
GPSloop();
if (gps.overrun()) {
gps.overrun( false );
DEBUG_PORT.println( F("DATA OVERRUN: fix data lost!") );
}
}
//----------------------------------------------------------------
void initSD()
{
#ifdef SIMULATE_SD
DEBUG_PORT.println( F(" Simulating SD.") );
#else
DEBUG_PORT.println( F("Initializing SD card...") );
// see if the card is present and can be initialized:
if (!SD.begin(chipSelect)) {
DEBUG_PORT.println( F(" SD card failed, or not present") );
// don't do anything more:
// Flicker the LED
while (true) {
digitalWrite(LED,HIGH);
delay(75);
digitalWrite(LED,LOW);
delay(75);
}
}
DEBUG_PORT.println( F(" SD card initialized.") );
// Pick a numbered filename, 00 to 99.
char filename[15] = "data_##.txt";
for (uint8_t i=0; i<100; i++) {
filename[5] = '0' + i/10;
filename[6] = '0' + i%10;
if (!SD.exists(filename)) {
// Use this one!
break;
}
}
logfile = SD.open(filename, FILE_WRITE);
if (!logfile) {
DEBUG_PORT.print( F("Couldn't create ") );
DEBUG_PORT.println(filename);
// If the file can't be created for some reason this leaves the LED on
// so I know there is a problem
digitalWrite(LED,HIGH);
while (true) {}
}
DEBUG_PORT.print( F("Writing to ") );
DEBUG_PORT.println(filename);
// GPS Visualizer requires a header to identify the CSV fields.
// If you are saving other data or don't need this, simply remove/change it
logfile.println( F("latitude,longitude,time,loggingTime") );
//trace_header( logfile ); // and uncomment #include Streamers.h
#endif
} // initSD

View File

@@ -0,0 +1,90 @@
#include <NMEAGPS.h>
//======================================================================
// Program: NMEA_isr.ino
//
// Prerequisites:
// 1) NMEA.ino works with your device
//
// Description: This minimal program parses the GPS data during the
// RX character interrupt. The ISR passes the character to
// the GPS object for parsing. The GPS object will add gps_fix
// structures to a buffer that can be later read() by loop().
//
// License:
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// NeoGPS is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//
//======================================================================
#include <GPSport.h>
#include <Streamers.h>
// Check configuration
#ifndef NMEAGPS_INTERRUPT_PROCESSING
#error You must define NMEAGPS_INTERRUPT_PROCESSING in NMEAGPS_cfg.h!
#endif
static NMEAGPS gps;
//--------------------------
static void GPSisr( uint8_t c )
{
gps.handle( c );
} // GPSisr
//--------------------------
void setup()
{
DEBUG_PORT.begin(9600);
while (!DEBUG_PORT)
;
DEBUG_PORT.print( F("NMEA_isr.INO: started\n") );
DEBUG_PORT.print( F("fix object size = ") );
DEBUG_PORT.println( sizeof(gps.fix()) );
DEBUG_PORT.print( F("NMEAGPS object size = ") );
DEBUG_PORT.println( sizeof(gps) );
DEBUG_PORT.println( F("Looking for GPS device on " GPS_PORT_NAME) );
trace_header( DEBUG_PORT );
DEBUG_PORT.flush();
gpsPort.attachInterrupt( GPSisr );
gpsPort.begin( 9600 );
}
//--------------------------
void loop()
{
if (gps.available()) {
// Print all the things!
trace_all( DEBUG_PORT, gps, gps.read() );
}
if (gps.overrun()) {
gps.overrun( false );
DEBUG_PORT.println( F("DATA OVERRUN: took too long to print GPS data!") );
}
}

View File

@@ -0,0 +1,265 @@
#include <NMEAGPS.h>
//======================================================================
// Program: NMEAaverage.ino
//
// Description: This program averages locations over time to compute
// a higher-accuracy *static* location. It also shows
// how to use the distance functions in the Location class.
//
// Prerequisites:
// 1) NMEA.ino works with your device (correct TX/RX pins and baud rate)
// 2) At least once sentence with a location field has been enabled
//
// 'Serial' is for debug output to the Serial Monitor window.
//
// License:
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// NeoGPS is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//
//======================================================================
#include <GPSport.h>
//------------------------------------------------------------
// Check that the config files are set up properly
#if !defined( NMEAGPS_PARSE_GGA ) && \
!defined( NMEAGPS_PARSE_GLL ) && \
!defined( NMEAGPS_PARSE_RMC )
#error You must uncomment at least one of NMEAGPS_PARSE_GGA, GGL or RMC in NMEAGPS_cfg.h!
#endif
#if !defined( GPS_FIX_LOCATION )
#error You must uncomment GPS_FIX_LOCATION in GPSfix_cfg.h!
#endif
#if !defined( GPS_FIX_TIME )
#error You must uncomment GPS_FIX_TIME in GPSfix_cfg.h!
#endif
#if !defined( GPS_FIX_DATE )
#error You must uncomment GPS_FIX_DATE in GPSfix_cfg.h!
#endif
//------------------------------------------------------------
static NMEAGPS gps; // This parses the GPS characters
static gps_fix fix; // This holds the latest GPS fix
//------------------------------------------------------------
using namespace NeoGPS;
static gps_fix first; // good GPS data
static clock_t firstSecs; // cached dateTime in seconds since EPOCH
static Location_t avgLoc; // gradually-calculated average location
static uint16_t count; // number of samples
static int32_t sumDLat, sumDLon; // accumulated deltas
static bool doneAccumulating; // accumulation completed
//------------------------------------------------------------
const char nCD [] PROGMEM = "N";
const char nneCD[] PROGMEM = "NNE";
const char neCD [] PROGMEM = "NE";
const char eneCD[] PROGMEM = "ENE";
const char eCD [] PROGMEM = "E";
const char eseCD[] PROGMEM = "ESE";
const char seCD [] PROGMEM = "SE";
const char sseCD[] PROGMEM = "SSE";
const char sCD [] PROGMEM = "S";
const char sswCD[] PROGMEM = "SSW";
const char swCD [] PROGMEM = "SW";
const char wswCD[] PROGMEM = "WSW";
const char wCD [] PROGMEM = "W";
const char wnwCD[] PROGMEM = "WNW";
const char nwCD [] PROGMEM = "NW";
const char nnwCD[] PROGMEM = "NNW";
const char * const dirStrings[] PROGMEM =
{ nCD, nneCD, neCD, eneCD, eCD, eseCD, seCD, sseCD,
sCD, sswCD, swCD, wswCD, wCD, wnwCD, nwCD, nnwCD };
const __FlashStringHelper *compassDir( uint16_t bearing ) // degrees CW from N
{
const int16_t directions = sizeof(dirStrings)/sizeof(dirStrings[0]);
const int16_t degreesPerDir = 360 / directions;
int8_t dir = (bearing + degreesPerDir/2) / degreesPerDir;
while (dir < 0)
dir += directions;
while (dir >= directions)
dir -= directions;
return (const __FlashStringHelper *) pgm_read_ptr( &dirStrings[ dir ] );
} // compassDir
//------------------------------------------------------------
static void doSomeWork()
{
static bool warned = false; // that we're waiting for a valid location
if (fix.valid.location && fix.valid.date && fix.valid.time) {
if (count == 0) {
// Just save the first good fix
first = fix;
firstSecs = (clock_t) first.dateTime;
count = 1;
} else {
// After the first fix, accumulate locations until we have
// a good average. Then display the offset from the average.
if (warned) {
// We were waiting for the fix to be re-acquired.
warned = false;
DEBUG_PORT.println();
}
DEBUG_PORT.print( count );
if (!doneAccumulating) {
// Enough time?
if (((clock_t)fix.dateTime - firstSecs) > 2 * SECONDS_PER_HOUR)
doneAccumulating = true;
}
int32_t dLat, dLon;
if (!doneAccumulating) {
// Use deltas from the first location
dLat = fix.location.lat() - first.location.lat();
sumDLat += dLat;
int32_t avgDLat = sumDLat / count;
dLon = fix.location.lon() - first.location.lon();
sumDLon += dLon;
int32_t avgDLon = sumDLon / count;
// Then calculated the average location as the first location
// plus the averaged deltas.
avgLoc.lat( first.location.lat() + avgDLat );
avgLoc.lon( first.location.lon() + avgDLon );
count++;
}
DEBUG_PORT.print( ',' );
DEBUG_PORT.print( avgLoc.lat() );
DEBUG_PORT.print( ',' );
DEBUG_PORT.print( avgLoc.lon() );
DEBUG_PORT.print( ',' );
dLat = avgLoc.lat() - fix.location.lat();
DEBUG_PORT.print( dLat );
DEBUG_PORT.print( ',' );
dLon = avgLoc.lon() - fix.location.lon();
DEBUG_PORT.print( dLon );
// Calculate the distance from the current fix to the average location
float avgDistError = avgLoc.DistanceKm( fix.location );
DEBUG_PORT.print( ',' );
DEBUG_PORT.print( avgDistError * 100000.0 ); // cm
// Calculate the bearing from the current fix to the average location.
// NOTE: other libraries will have trouble with this calculation,
// because these coordinates are *VERY* close together. Naive
// floating-point calculations will not have enough significant
// digits.
float avgBearingErr = fix.location.BearingTo( avgLoc );
float bearing = avgBearingErr * Location_t::DEG_PER_RAD;
DEBUG_PORT.print( ',' );
DEBUG_PORT.print( bearing, 6 );
DEBUG_PORT.print( ',' );
DEBUG_PORT.print( compassDir( bearing ) );
// Calculate a point that is 10km away from the average location,
// at the error bearing
Location_t tenKmAway( avgLoc );
tenKmAway.OffsetBy( 10.0 / Location_t::EARTH_RADIUS_KM, avgBearingErr );
DEBUG_PORT.print( ',' );
DEBUG_PORT.print( tenKmAway.lat() );
DEBUG_PORT.print( ',' );
DEBUG_PORT.print( tenKmAway.lon() );
// Calculate the bearing from the average location to that point.
// This should be very close to the avgBearingErr, and will
// reflect the calculation error. This is because the
// current fix is *VERY* close to the average location.
float tb = avgLoc.BearingToDegrees( tenKmAway );
DEBUG_PORT.print( ',' );
DEBUG_PORT.print( tb, 6 );
DEBUG_PORT.println();
}
} else {
if (!warned) {
warned = true;
DEBUG_PORT.print( F("Waiting for fix...") );
} else {
DEBUG_PORT.print( '.' );
}
}
} // doSomeWork
//------------------------------------------------------------
static void GPSloop()
{
while (gps.available( gpsPort )) {
fix = gps.read();
doSomeWork();
}
} // GPSloop
//------------------------------------------------------------
void setup()
{
DEBUG_PORT.begin(9600);
while (!DEBUG_PORT)
;
DEBUG_PORT.print( F("NMEAaverage.INO: started\n") );
DEBUG_PORT.print( F(" fix object size = ") );
DEBUG_PORT.println( sizeof(gps.fix()) );
DEBUG_PORT.print( F(" gps object size = ") );
DEBUG_PORT.println( sizeof(gps) );
DEBUG_PORT.println( F("Looking for GPS device on " GPS_PORT_NAME) );
DEBUG_PORT.println( F("Comparing current fix with averaged location.\n"
"count,avg lat,avg lon,dlat,dlon,distance(cm),"
"bearing deg,compass,lat/lon 10km away & recalc bearing") );
DEBUG_PORT.flush();
gpsPort.begin( 9600 );
}
//------------------------------------------------------------
void loop()
{
GPSloop();
}

View File

@@ -0,0 +1,105 @@
#include <NMEAGPS.h>
//======================================================================
// Program: NMEAbenchmark.ino
//
// Prerequisites:
//
// Description: Use GPGGA and GPRMC sentences to test
// the parser's performance.
//
// GSV sentences are tested if enabled.
//
// 'Serial' is for debug output to the Serial Monitor window.
//
// License:
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// NeoGPS is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//
//======================================================================
#include <Streamers.h>
static NMEAGPS gps;
//--------------------------
static uint32_t time_it( const char *data )
{
const uint16_t ITERATIONS = 1024;
uint32_t start, end;
Serial.flush();
start = micros();
for (uint16_t i=ITERATIONS; i > 0; i--) {
char *ptr = (char *) data;
while (*ptr)
gps.decode( *ptr++ );
}
end = micros();
return (end-start)/ITERATIONS;
}
//--------------------------
void setup()
{
Serial.begin(9600);
Serial.println( F("NMEAbenchmark: started") );
Serial.print( F("fix object size = ") );
Serial.println( sizeof(gps.fix()) );
Serial.print( F(" gps object size = ") );
Serial.println( sizeof(gps) );
trace_header( Serial );
Serial.flush();
const char *gga =
"$GPGGA,092725.00,4717.11399,N,00833.91590,E,"
"1,8,1.01,499.6,M,48.0,M,,0*5B\r\n";
const char *gga_no_lat =
"$GPGGA,092725.00,,,00833.91590,E,"
"1,8,1.01,499.6,M,48.0,M,,0*0D\r\n";
Serial << F("GGA time = ") << time_it( gga ) << '\n';
trace_all( Serial, gps, gps.fix() );
Serial << F("GGA no lat time = ") << time_it( gga_no_lat ) << '\n';
trace_all( Serial, gps, gps.fix() );
const char *rmc =
"$GPRMC,083559.00,A,4717.11437,N,00833.91522,E,"
"0.004,77.52,091202,,,A*57\r\n";
Serial << F("RMC time = ") << time_it( rmc ) << '\n';
trace_all( Serial, gps, gps.fix() );
#ifdef NMEAGPS_PARSE_GSV
const char *gsv =
"$GPGSV,3,1,10,23,38,230,44,29,71,156,47,07,29,116,41,08,09,081,36*7F\r\n"
"$GPGSV,3,2,10,10,07,189,,05,05,220,,09,34,274,42,18,25,309,44*72\r\n"
"$GPGSV,3,3,10,26,82,187,47,28,43,056,46*77\r\n";
Serial << F("GSV time = ") << time_it( gsv ) << '\n';
trace_all( Serial, gps, gps.fix() );
#endif
}
//--------------------------
void loop() {}

View File

@@ -0,0 +1,69 @@
#include <NMEAGPS.h>
//======================================================================
// Program: NMEAblink.ino
//
// Prerequisites:
// 1) NMEA.ino works with your device
//
// Description: This program will toggle the LED once per second,
// when the LAST_SENTENCE_IN_INTERVAL is received.
//
// Because no actual GPS data is used, you could disable all
// messages (except the LAST_SENTENCE) and all gps_fix members.
// It would still receive a 'fix' oncer per second, without
// without using any RAM or CPU time to parse or save
// the (unused) values. Essentially, this app uses the LAST_SENTENCE
// as a 1PPS signal.
//
// Note: Because this example does not use 'Serial', you
// could use 'Serial' for the gpsPort, like this:
//
// #define gpsPort Serial
//
// See GPSport.h for more information.
//
// License:
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// NeoGPS is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//
//======================================================================
#include <GPSport.h>
static NMEAGPS gps;
static const int led = 13;
//--------------------------
void setup()
{
gpsPort.begin(9600);
pinMode(led, OUTPUT);
}
//--------------------------
void loop()
{
if (gps.available( gpsPort)) {
gps.read(); // don't really do anything with the fix...
digitalWrite( led, !digitalRead(led) ); // toggle the LED
}
}

View File

@@ -0,0 +1,367 @@
#include <NMEAGPS.h>
//======================================================================
// Program: NMEAdiagnostic.ino
//
// Description: This program tries different baud rates until
// valid NMEA sentences are detected. Some GPS devices may
// have a binary mode that does not emit NMEA sentences. You
// may have to send a special command or use a utility program
// to configure it to emit NMEA sentences instead of binary messages.
//
// Prerequisites:
// 1) Your GPS device has been correctly powered.
// Be careful when connecting 3.3V devices.
// 2) Your GPS device is correctly connected to an Arduino serial port.
// See GPSport.h for the default connections.
//
// 'Serial' is for debug output to the Serial Monitor window.
//
// License:
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// NeoGPS is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//
//======================================================================
#include <GPSport.h>
#include <Streamers.h>
// Check configuration
#ifndef NMEAGPS_RECOGNIZE_ALL
#error You must define NMEAGPS_RECOGNIZE_ALL in NMEAGPS_cfg.h!
#endif
#ifdef NMEAGPS_IMPLICIT_MERGING
#error You must *undefine* NMEAGPS_IMPLICIT_MERGING in NMEAGPS_cfg.h! \
Please use EXPLICIT or NO_MERGING.
#endif
#ifdef NMEAGPS_INTERRUPT_PROCESSING
#error You must *NOT* define NMEAGPS_INTERRUPT_PROCESSING in NMEAGPS_cfg.h!
#endif
static NMEAGPS gps ; // This parses received characters
static gps_fix all_data ; // A composite of all GPS data fields
static uint32_t last_rx = 0UL; // The last millis() time a character was
// received from GPS.
static uint32_t valid_sentence_received = 0UL;
static bool last_sentence_received = false;
static uint32_t baudStartTime = 0UL;
static uint8_t warnings = 0;
static uint8_t errors = 0;
//--------------------------
static void hang()
{
DEBUG_PORT.println( F("\n** NMEAdiagnostic completed **\n") );
if (warnings) {
DEBUG_PORT.print( warnings );
DEBUG_PORT.print( F(" warnings") );
}
if (warnings && errors)
DEBUG_PORT.print( F(" and ") );
if (errors) {
DEBUG_PORT.print( errors );
DEBUG_PORT.print( F(" errors") );
}
if (warnings || errors)
DEBUG_PORT.println();
DEBUG_PORT.flush();
for (;;)
;
} // hang
//--------------------------
// Baud rates to check
static long baud_table[] =
{ 1200, 2400, 4800, 9600, 14400, 19200, 28800, 31250, 38400,
57600, 115200 };
static const uint8_t num_bauds = sizeof(baud_table)/sizeof(baud_table[0]);
static const uint8_t INITIAL_BAUD_INDEX = 3; // 9600
static uint8_t baud_index = INITIAL_BAUD_INDEX;
static bool triedDifferentBaud = false;
//--------------------------
static void tryBaud()
{
long baud = baud_table[baud_index];
DEBUG_PORT.print( F("\n____________________________\n\nChecking ") );
DEBUG_PORT.print( baud );
DEBUG_PORT.print( F(" baud...\n") );
DEBUG_PORT.flush();
//if (baud == 9600) baud = 17000;
gpsPort.begin( baud );
baudStartTime = millis();
} // tryBaud
//--------------------------
static void tryAnotherBaudRate()
{
gpsPort.end();
while (gpsPort.available())
gpsPort.read();
if (baud_index == INITIAL_BAUD_INDEX) {
baud_index = 0;
} else {
baud_index++;
if (baud_index == INITIAL_BAUD_INDEX)
baud_index++; // skip it, we already tried it
if (baud_index >= num_bauds) {
baud_index = INITIAL_BAUD_INDEX;
DEBUG_PORT.print( F("\n All baud rates tried!\n") );
hang();
}
}
tryBaud();
triedDifferentBaud = true;
} // tryAnotherBaudRate
//------------------------------------
static const uint16_t MAX_SAMPLE = 256;
static uint8_t someChars[ MAX_SAMPLE ];
static uint16_t someCharsIndex = 0;
static void dumpSomeChars()
{
if (someCharsIndex > 0) {
DEBUG_PORT.print( F("Received data:\n") );
const uint16_t bytes_per_line = 32;
char ascii[ bytes_per_line ];
uint8_t *ptr = &someChars[0];
for (uint16_t i=0; i<someCharsIndex; ) {
uint16_t j;
for (j=0; (i<someCharsIndex) && (j<bytes_per_line); i++, j++) {
uint8_t c = *ptr++;
if (c < 0x10)
DEBUG_PORT.print('0');
DEBUG_PORT.print( c, HEX );
if ((' ' <= c) && (c <= '~'))
ascii[ j ] = c;
else
ascii[ j ] = '.';
}
uint16_t jmax = j;
while (j++ < bytes_per_line)
DEBUG_PORT.print( F(" ") );
DEBUG_PORT.print( ' ' );
for (j=0; j<jmax; j++)
DEBUG_PORT.print( ascii[ j ] );
DEBUG_PORT.print( '\n' );
}
DEBUG_PORT.flush();
someCharsIndex = 0;
}
} // dumpSomeChars
//----------------------------------------------------------------
void displaySentences()
{
// We received one or more sentences, display the baud rate
DEBUG_PORT.print( F("\n\n**** NMEA sentence(s) detected! ****\n") );
dumpSomeChars();
DEBUG_PORT << F("\nDevice baud rate is ") <<
baud_table[ baud_index ] << '\n';
DEBUG_PORT.print( F("\nGPS data fields received:\n\n ") );
trace_header( DEBUG_PORT );
DEBUG_PORT.print( F(" ") );
trace_all( DEBUG_PORT, gps, all_data );
if (!last_sentence_received) {
warnings++;
DEBUG_PORT.print( F("\nWarning: LAST_SENTENCE_IN_INTERVAL defined to be ") );
DEBUG_PORT.print( gps.string_for( LAST_SENTENCE_IN_INTERVAL ) );
DEBUG_PORT.println( F(", but was never received.\n"
" Please use NMEAorder.ino to determine which sentences your GPS device sends, and then\n"
" use the last one for the definition in NMEAGPS_cfg.h.") );
}
} // displaySentences
//----------------------------------------------------------------
// Listen to see if the GPS device is correctly
// connected and functioning.
static void listenForSomething()
{
uint32_t current_ms = millis();
uint32_t ms_since_last_rx = current_ms - last_rx;
bool waited_long_enough = (current_ms - baudStartTime) > 1000UL;
if ((ms_since_last_rx > 5) && waited_long_enough) {
// The GPS device has not sent any characters for at least 5ms.
// See if we've been getting chars sometime during the last second.
// If not, the GPS may not be working or connected properly.
bool getting_chars = (someCharsIndex > 0);
// Try to diagnose the problem
static uint8_t tries = 1;
bool tryNext = false;
if (!getting_chars) {
if (tries++ >= 3) {
errors++;
DEBUG_PORT.println( F("\nCheck GPS device and/or connections. No data received.\n") );
tryNext = true;
}
} else if (valid_sentence_received) {
uint8_t s = valid_sentence_received/1000;
uint8_t ms = valid_sentence_received - s*1000;
DEBUG_PORT.print( F("Valid sentences were received ") );
DEBUG_PORT.print( s );
DEBUG_PORT.print( '.' );
if (ms < 100)
DEBUG_PORT.print( '0' );
if (ms < 10)
DEBUG_PORT.print( '0' );
DEBUG_PORT.print( ms );
DEBUG_PORT.println(
F(" seconds ago.\n"
" The GPS update rate may be lower than 1Hz,\n"
" or the connections may be bad." ) );
displaySentences();
hang();
} else {
DEBUG_PORT.println(
F("No valid sentences, but characters are being received.\n"
" Check baud rate or device protocol configuration.\n" ) );
dumpSomeChars();
delay( 2000 );
tryNext = true;
}
if (tryNext) {
tries = 1;
tryAnotherBaudRate();
valid_sentence_received = 0UL;
}
}
} // listenForSomething
//------------------------------------
static void GPSloop()
{
while (gpsPort.available()) {
last_rx = millis();
uint8_t c = gpsPort.read();
if (someCharsIndex < MAX_SAMPLE)
someChars[ someCharsIndex++ ] = c;
if (gps.decode( c ) == NMEAGPS::DECODE_COMPLETED) {
all_data |= gps.fix();
valid_sentence_received = last_rx;
if (gps.nmeaMessage == LAST_SENTENCE_IN_INTERVAL)
last_sentence_received = true;
DEBUG_PORT.print( F("Received ") );
DEBUG_PORT.println( gps.string_for( gps.nmeaMessage ) );
static uint8_t sentences_printed = 0;
bool long_enough = (millis() - baudStartTime > 3000);
if (long_enough ||
(
(sentences_printed++ >= 20) &&
(someCharsIndex >= MAX_SAMPLE)
) ) {
displaySentences();
hang();
}
}
}
if (!valid_sentence_received ||
(millis() - valid_sentence_received > 3000UL))
listenForSomething();
} // GPSloop
//--------------------------
void setup()
{
DEBUG_PORT.begin(9600);
while (!DEBUG_PORT)
;
DEBUG_PORT.print( F("NMEAdiagnostic.INO: started\n") );
DEBUG_PORT.println( F("Looking for GPS device on " GPS_PORT_NAME) );
if (sizeof(gps_fix) <= 2) {
warnings++;
DEBUG_PORT.print( F("\nWarning: no fields are enabled in GPSfix_cfg.h.\n Only the following information will be displayed:\n ") );
trace_header( DEBUG_PORT );
}
#if !defined( NMEAGPS_PARSE_GGA ) & !defined( NMEAGPS_PARSE_GLL ) & \
!defined( NMEAGPS_PARSE_GSA ) & !defined( NMEAGPS_PARSE_GSV ) & \
!defined( NMEAGPS_PARSE_RMC ) & !defined( NMEAGPS_PARSE_VTG ) & \
!defined( NMEAGPS_PARSE_ZDA ) & !defined( NMEAGPS_PARSE_GST )
warnings++;
DEBUG_PORT.println( F("\nWarning: no messages are enabled for parsing in NMEAGPS_cfg.h.\n No fields will be valid, including the 'status' field.") );
#endif
DEBUG_PORT.flush();
tryBaud();
} // setup
//--------------------------
void loop()
{
GPSloop();
}

View File

@@ -0,0 +1,82 @@
#include <NMEAGPS.h>
//======================================================================
// Program: NMEAdistance.ino
//
// Description: Display distance from a base location.
//
// Prerequisites:
// 1) NMEA.ino works with your device (correct TX/RX pins and baud rate)
// 2) GPS_FIX_LOCATION has been enabled.
// 3) A sentence that contains lat/long has been enabled (GGA, GLL or RMC).
// 4) Your device sends at least one of those sentences.
//
// 'Serial' is for debug output to the Serial Monitor window.
//
// License:
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// NeoGPS is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//
//======================================================================
#include <GPSport.h>
//------------------------------------------------------------
// Check that the config files are set up properly
#if !defined( NMEAGPS_PARSE_RMC ) & \
!defined( NMEAGPS_PARSE_GGA ) & \
!defined( NMEAGPS_PARSE_GLL )
#error You must uncomment at least one of NMEAGPS_PARSE_RMC, NMEAGPS_PARSE_GGA or NMEAGPS_PARSE_GLL in NMEAGPS_cfg.h!
#endif
#if !defined( GPS_FIX_LOCATION )
#error You must uncomment GPS_FIX_LOCATION in GPSfix_cfg.h!
#endif
NMEAGPS gps;
// The base location, in degrees * 10,000,000
NeoGPS::Location_t base( -253448688L, 1310324914L ); // Ayers Rock, AU
void setup()
{
DEBUG_PORT.begin(9600);
DEBUG_PORT.println( F("NMEAdistance.ino started.") );
DEBUG_PORT.println( F("Looking for GPS device on " GPS_PORT_NAME) );
gpsPort.begin(9600);
} // setup
void loop()
{
while (gps.available( gpsPort )) {
gps_fix fix = gps.read(); // save the latest
// When we have a location, calculate how far away we are from the base location.
if (fix.valid.location) {
float range = fix.location.DistanceMiles( base );
DEBUG_PORT.print( F("Range: ") );
DEBUG_PORT.print( range );
DEBUG_PORT.println( F(" Miles") );
} else
// Waiting...
DEBUG_PORT.print( '.' );
}
} // loop

View File

@@ -0,0 +1,183 @@
#include <NMEAGPS.h>
//======================================================================
// Program: NMEAloc.ino
//
// Description: This program only parses an RMC sentence for the lat/lon.
//
// Prerequisites:
// 1) NMEA.ino works with your device (correct TX/RX pins and baud rate)
// 2) The RMC sentence has been enabled.
// 3) Your device sends an RMC sentence (e.g., $GPRMC).
//
// 'Serial' is for debug output to the Serial Monitor window.
//
// License:
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// NeoGPS is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//
//======================================================================
#include <GPSport.h>
//------------------------------------------------------------
// Check that the config files are set up properly
#if !defined( NMEAGPS_PARSE_RMC )
#error You must uncomment NMEAGPS_PARSE_RMC in NMEAGPS_cfg.h!
#endif
#if !defined( GPS_FIX_TIME )
#error You must uncomment GPS_FIX_TIME in GPSfix_cfg.h!
#endif
#if !defined( GPS_FIX_LOCATION )
#error You must uncomment GPS_FIX_LOCATION in GPSfix_cfg.h!
#endif
#if !defined( GPS_FIX_SPEED )
#error You must uncomment GPS_FIX_SPEED in GPSfix_cfg.h!
#endif
#if !defined( GPS_FIX_SATELLITES )
#error You must uncomment GPS_FIX_SATELLITES in GPSfix_cfg.h!
#endif
#ifdef NMEAGPS_INTERRUPT_PROCESSING
#error You must *NOT* define NMEAGPS_INTERRUPT_PROCESSING in NMEAGPS_cfg.h!
#endif
//------------------------------------------------------------
static NMEAGPS gps; // This parses the GPS characters
//----------------------------------------------------------------
// Print the 32-bit integer degrees *as if* they were high-precision floats
static void printL( Print & outs, int32_t degE7 );
static void printL( Print & outs, int32_t degE7 )
{
// Extract and print negative sign
if (degE7 < 0) {
degE7 = -degE7;
outs.print( '-' );
}
// Whole degrees
int32_t deg = degE7 / 10000000L;
outs.print( deg );
outs.print( '.' );
// Get fractional degrees
degE7 -= deg*10000000L;
// Print leading zeroes, if needed
int32_t factor = 1000000L;
while ((degE7 < factor) && (factor > 1L)){
outs.print( '0' );
factor /= 10L;
}
// Print fractional degrees
outs.print( degE7 );
}
static void doSomeWork( const gps_fix & fix );
static void doSomeWork( const gps_fix & fix )
{
// This is the best place to do your time-consuming work, right after
// the RMC sentence was received. If you do anything in "loop()",
// you could cause GPS characters to be lost, and you will not
// get a good lat/lon.
// For this example, we just print the lat/lon. If you print too much,
// this routine will not get back to "loop()" in time to process
// the next set of GPS data.
if (fix.valid.location) {
if ( fix.dateTime.seconds < 10 )
DEBUG_PORT.print( '0' );
DEBUG_PORT.print( fix.dateTime.seconds );
DEBUG_PORT.print( ',' );
// DEBUG_PORT.print( fix.latitude(), 6 ); // floating-point display
// DEBUG_PORT.print( fix.latitudeL() ); // integer display
printL( DEBUG_PORT, fix.latitudeL() ); // prints int like a float
DEBUG_PORT.print( ',' );
// DEBUG_PORT.print( fix.longitude(), 6 ); // floating-point display
// DEBUG_PORT.print( fix.longitudeL() ); // integer display
printL( DEBUG_PORT, fix.longitudeL() ); // prints int like a float
DEBUG_PORT.print( ',' );
if (fix.valid.satellites)
DEBUG_PORT.print( fix.satellites );
DEBUG_PORT.print( ',' );
DEBUG_PORT.print( fix.speed(), 6 );
DEBUG_PORT.print( F(" kn = ") );
DEBUG_PORT.print( fix.speed_mph(), 6 );
DEBUG_PORT.print( F(" mph") );
} else {
// No valid location data yet!
DEBUG_PORT.print( '?' );
}
DEBUG_PORT.println();
} // doSomeWork
//------------------------------------
static void GPSloop();
static void GPSloop()
{
while (gps.available( gpsPort ))
doSomeWork( gps.read() );
} // GPSloop
//--------------------------
void setup()
{
DEBUG_PORT.begin(9600);
while (!DEBUG_PORT)
;
DEBUG_PORT.print( F("NMEAloc.INO: started\n") );
DEBUG_PORT.print( F("fix object size = ") );
DEBUG_PORT.println( sizeof(gps.fix()) );
DEBUG_PORT.print( F("NMEAGPS object size = ") );
DEBUG_PORT.println( sizeof(gps) );
DEBUG_PORT.println( F("Looking for GPS device on " GPS_PORT_NAME) );
#ifdef NMEAGPS_NO_MERGING
DEBUG_PORT.println( F("Only displaying data from xxRMC sentences.\n Other sentences may be parsed, but their data will not be displayed.") );
#endif
DEBUG_PORT.flush();
gpsPort.begin(9600);
}
//--------------------------
void loop()
{
GPSloop();
}

View File

@@ -0,0 +1,117 @@
#include <Arduino.h>
#include <NMEAGPS.h>
//======================================================================
// Program: NMEAlocDMS.ino
//
// Description: This program only parses an RMC sentence for the lat/lon.
//
// Prerequisites:
// 1) NMEA.ino works with your device (correct TX/RX pins and baud rate)
// 2) The RMC sentence has been enabled.
// 3) Your device sends an RMC sentence (e.g., $GPRMC).
//
// Serial is for trace output to the Serial Monitor window.
//
// License:
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// NeoGPS is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//
//======================================================================
#include <GPSport.h>
//------------------------------------------------------------
// Check that the config files are set up properly
#if !defined( NMEAGPS_PARSE_RMC )
#error You must uncomment NMEAGPS_PARSE_RMC in NMEAGPS_cfg.h!
#endif
#if !defined( GPS_FIX_LOCATION_DMS )
#error You must uncomment GPS_FIX_LOCATION_DMS in GPSfix_cfg.h!
#endif
#ifdef NMEAGPS_INTERRUPT_PROCESSING
#error You must *NOT* define NMEAGPS_INTERRUPT_PROCESSING in NMEAGPS_cfg.h!
#endif
//------------------------------------------------------------
static NMEAGPS gps; // This parses the GPS characters
static void doSomeWork( const gps_fix & fix );
static void doSomeWork( const gps_fix & fix )
{
// This is the best place to do your time-consuming work, right after
// the RMC sentence was received. If you do anything in "loop()",
// you could cause GPS characters to be lost, and you will not
// get a good lat/lon.
// For this example, we just print the lat/lon. If you print too much,
// this routine will not get back to "loop()" in time to process
// the next set of GPS data.
if (fix.valid.location) {
DEBUG_PORT << fix.latitudeDMS;
DEBUG_PORT.print( fix.latitudeDMS.NS() );
DEBUG_PORT.write( ' ' );
if (fix.longitudeDMS.degrees < 100)
DEBUG_PORT.write( '0' );
DEBUG_PORT << fix.longitudeDMS;
DEBUG_PORT.print( fix.longitudeDMS.EW() );
} else {
// No valid location data yet!
DEBUG_PORT.print( '?' );
}
DEBUG_PORT.println();
} // doSomeWork
//------------------------------------
static void GPSloop();
static void GPSloop()
{
while (gps.available( gpsPort ))
doSomeWork( gps.read() );
} // GPSloop
//--------------------------
void setup()
{
DEBUG_PORT.begin(9600);
while (!DEBUG_PORT)
;
DEBUG_PORT.print( F("NMEAlocDMS.INO: started\n") );
DEBUG_PORT.println( F("Looking for GPS device on " GPS_PORT_NAME) );
DEBUG_PORT.flush();
gpsPort.begin(9600);
}
//--------------------------
void loop()
{
GPSloop();
}

View File

@@ -0,0 +1,247 @@
#include <NMEAGPS.h>
//======================================================================
// Program: NMEAorder.ino
//
// Description: This example program records the order of sentences
// received in each 1-second interval. The last sentence is
// important to know, as that will be used to determine when the
// GPS quiet time is starting (see NMEA.ino). It is safe to perform
// time-consuming operations at that point, and the risk of losing
// characters will be minimized (see comments in 'GPSloop').
//
// Prerequisites:
// 1) Your GPS device has been correctly powered.
// Be careful when connecting 3.3V devices.
// 2) Your GPS device is correctly connected to an Arduino serial port.
// See GPSport.h for the default connections.
// 3) You know the default baud rate of your GPS device
// Use NMEAdiagnostic.ino to scan for the correct baud rate.
// 4) NMEAGPS_RECOGNIZE_ALL must be enabled in NMEAGPS_cfg.h
//
// 'Serial' is for debug output to the Serial Monitor window.
//
// License:
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// NeoGPS is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//
//======================================================================
#include <GPSport.h>
//------------------------------------------------------------
// Check configuration
#ifndef NMEAGPS_RECOGNIZE_ALL
#error You must define NMEAGPS_RECOGNIZE_ALL in NMEAGPS_cfg.h!
#endif
#ifdef NMEAGPS_INTERRUPT_PROCESSING
#error You must *NOT* define NMEAGPS_INTERRUPT_PROCESSING in NMEAGPS_cfg.h!
#endif
static NMEAGPS gps ; // This parses received characters
static uint32_t last_rx = 0L; // The last millis() time a character was
// received from GPS. This is used to
// determine when the GPS quiet time begins.
//------------------------------------------------------------
static NMEAGPS::nmea_msg_t last_sentence_in_interval = NMEAGPS::NMEA_UNKNOWN;
static uint8_t prev_sentence_count = 0;
static uint8_t sentence_count = 0;
static const uint8_t MAX_SENTENCES = 20; // per second
static NMEAGPS::nmea_msg_t sentences[ MAX_SENTENCES ];
static void recordSentenceTypes()
{
// Always save the last sentence, even if we're full
sentences[ sentence_count ] = gps.nmeaMessage;
if (sentence_count < MAX_SENTENCES-1)
sentence_count++;
} // recordSentenceTypes
//-----------------------------------------------------------
static void printSentenceOrder()
{
DEBUG_PORT.println( F("\nSentence order in each 1-second interval:") );
for (uint8_t i=0; i<sentence_count; i++) {
DEBUG_PORT.print( F(" ") );
DEBUG_PORT.println( gps.string_for( sentences[i] ) );
}
if (sentences[sentence_count-1] == LAST_SENTENCE_IN_INTERVAL) {
DEBUG_PORT.print( F("\nSUCCESS: LAST_SENTENCE_IN_INTERVAL is correctly set to NMEAGPS::NMEA_") );
} else {
DEBUG_PORT.print( F("\nERROR: LAST_SENTENCE_IN_INTERVAL is incorrectly set to NMEAGPS::NMEA_") );
DEBUG_PORT.print( gps.string_for( LAST_SENTENCE_IN_INTERVAL ) );
DEBUG_PORT.print( F("!\n You must change this line in NMEAGPS_cfg.h:\n"
" #define LAST_SENTENCE_IN_INTERVAL NMEAGPS::NMEA_") );
}
DEBUG_PORT.println( gps.string_for( sentences[sentence_count-1] ) );
DEBUG_PORT.println();
} // printSentenceOrder
//------------------------------------
static void GPSloop()
{
while (gpsPort.available()) {
last_rx = millis();
if (gps.decode( gpsPort.read() ) == NMEAGPS::DECODE_COMPLETED) {
if (last_sentence_in_interval == NMEAGPS::NMEA_UNKNOWN) {
// Still building the list
recordSentenceTypes();
DEBUG_PORT.print( '.' );
}
}
}
} // GPSloop
//----------------------------------------------------------------
// Determine whether the GPS quiet time has started.
//
// This is only needed in the example programs, which must work
// for *any* GPS device.
//
// It also "pretends" to have a quiet time once per
// second so that some debug messages are emitted. This allows
// beginners to see whether the GPS device is correctly
// connected and functioning.
static bool quietTimeStarted()
{
uint32_t current_ms = millis();
uint32_t ms_since_last_rx = current_ms - last_rx;
if (ms_since_last_rx > 5) {
// The GPS device has not sent any characters for at least 5ms.
// See if we've been getting chars sometime during the last second.
// If not, the GPS may not be working or connected properly.
bool getting_chars = (ms_since_last_rx < 1000UL);
static uint32_t last_quiet_time = 0UL;
bool just_went_quiet =
(((int32_t) (last_rx - last_quiet_time)) > 10L);
bool next_quiet_time =
((current_ms - last_quiet_time) >= 1000UL);
if ((getting_chars && just_went_quiet)
||
(!getting_chars && next_quiet_time)) {
last_quiet_time = current_ms; // Remember for next loop
// If we're not getting good data, make some suggestions.
bool allDone = false;
if (!getting_chars) {
DEBUG_PORT.println( F("\nCheck GPS device and/or connections. No characters received.\n") );
allDone = true;
} else if (sentence_count == 0) {
DEBUG_PORT.println( F("No valid sentences, but characters are being received.\n"
"Check baud rate or device protocol configuration.\n" ) );
allDone = true;
}
if (allDone) {
DEBUG_PORT.print( F("\nEND.\n") );
for (;;)
; // hang!
}
// No problem, just a real GPS quiet time.
return true;
}
}
return false;
} // quietTimeStarted
//----------------------------------------------------------------
// Figure out what sentence the GPS device sends
// as the last sentence in each 1-second interval.
static void watchForLastSentence()
{
if (quietTimeStarted()) {
if (prev_sentence_count != sentence_count) {
// We have NOT received two full intervals of sentences with
// the same number of sentences in each interval. Start
// recording again.
prev_sentence_count = sentence_count;
sentence_count = 0;
} else if (sentence_count > 0) {
// Got it!
last_sentence_in_interval = sentences[ sentence_count-1 ];
}
}
} // watchForLastSentence
//--------------------------
void setup()
{
DEBUG_PORT.begin(9600);
while (!DEBUG_PORT)
;
DEBUG_PORT.print( F("NMEAorder.INO: started\n") );
DEBUG_PORT.print( F("fix object size = ") );
DEBUG_PORT.println( sizeof(gps.fix()) );
DEBUG_PORT.print( F("NMEAGPS object size = ") );
DEBUG_PORT.println( sizeof(gps) );
DEBUG_PORT.println( F("Looking for GPS device on " GPS_PORT_NAME) );
DEBUG_PORT.flush();
gpsPort.begin( 9600 );
}
//--------------------------
void loop()
{
GPSloop();
if (last_sentence_in_interval == NMEAGPS::NMEA_UNKNOWN)
watchForLastSentence();
else {
printSentenceOrder();
for (;;)
; // All done!
}
}

View File

@@ -0,0 +1,259 @@
#include <NMEAGPS.h>
//======================================================================
// Program: NMEArevGeoCache.ino
//
// Description: Activates a servo when the current location is
// close enough to the target location.
//
// Prerequisites:
// 1) NMEA.ino works with your device (correct TX/RX pins and baud rate)
// 2) The RMC sentence has been enabled.
// 3) Your device sends an RMC sentence (e.g., $GPRMC).
//
// Additional Hardware examples:
// 16x2 Character LCD: https://www.adafruit.com/products/181)
// Servo : TPro Micro SG90 https://www.adafruit.com/products/169
//
// 'Serial' is for debug output to the Serial Monitor window.
//
// Credits:
// This is simplified version of bnordlund9's Geobox:
// http://www.youtube.com/watch?v=g0060tcuofg
//
// Engagement Box by Kenton Harris 11/12/2012
//
// Reverse Geocache idea by Mikal Hart of http://arduiniana.org/
//
// License:
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// NeoGPS is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//
//======================================================================
#include <GPSport.h>
//------------------------------------------------------------
// Check that the config files are set up properly
#if !defined( NMEAGPS_PARSE_RMC ) & \
!defined( NMEAGPS_PARSE_GGA ) & \
!defined( NMEAGPS_PARSE_GLL )
#error You must uncomment at least one of NMEAGPS_PARSE_RMC, NMEAGPS_PARSE_GGA or NMEAGPS_PARSE_GLL in NMEAGPS_cfg.h!
#endif
#if !defined( GPS_FIX_LOCATION )
#error You must uncomment GPS_FIX_LOCATION in GPSfix_cfg.h!
#endif
//------------------------------------------------------------
// Additional Hardware includes
#include <LiquidCrystal.h>
//#include <NewLiquidCrystal_emul.h>
#include <PWMServo.h>
PWMServo servoLatch;
const int servoLock = 180; // angle (deg) of "locked" servo
const int servoUnlock = 105; // angle (deg) of "unlocked" servo
using namespace NeoGPS;
NMEAGPS gps;
float range; // current distance from HERE to THERE
const float CLOSE_ENOUGH = 300.0; // miles, change for your needs
LiquidCrystal lcd(7, 8, 6, 10, 11, 12);
const int fixLEDPin = 4;
const int servoPin = 9;
// The target location, in degrees * 10,000,000
Location_t there( 384602500, -1098191667L );
// 38°27'36.2"N 109°49'15.4"W
void setup()
{
servoLatch.attach(SERVO_PIN_A);
servoLatch.write(servoLock);
delay(50);
lcd.begin(16, 2);
Serial.begin(9600);
Serial.println( F("Debug GPS Test:") );
gpsPort.begin(9600);
// Configure GPS (these are Adafruit GPS commands - your brand may be different)
gpsPort.print
( F( "$PMTK314,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*29\r\n" // RMC only...
"$PMTK220,1000*1F\r\n" ) ); // ...and 1 Hz update rate
} // setup
void loop()
{
static uint8_t warningState = 0;
static uint32_t lastFixTime, lastDotTime;
while (gps.available( gpsPort )) {
gps_fix fix = gps.read(); // save the latest
// Set the "fix" LED to on or off
bool gpsWasFixed = fix.valid.status && (fix.status >= gps_fix::STATUS_STD);
digitalWrite( fixLEDPin, gpsWasFixed );
// When we have a location, calculate how far away we are from "there".
if (fix.valid.location) {
lastFixTime = millis();
range = fix.location.DistanceMiles( there );
//for GPS debug
DEBUG_PORT.print( F("Here: ") );
//printDMS( DEBUG_PORT, fix.location );
printAsFloat( DEBUG_PORT, fix.location );
DEBUG_PORT.print( F(" There: ") );
//printDMS( DEBUG_PORT, there );
printAsFloat( DEBUG_PORT, there );
DEBUG_PORT.print( F("Range: ") );
DEBUG_PORT.print( range );
DEBUG_PORT.println( F(" Miles") );
// Are we there?
if (range < CLOSE_ENOUGH) {
lcd.setCursor(0,1);
lcd.println( F("Box Unlocking...") );
delay(500);
servoLatch.write(servoUnlock);
lcd.clear();
lcd.setCursor(0,1);
lcd.print( F("Happy Birthday") );
for (;;); // hang here
}
// Nope, just give a little feedback...
lcd.clear();
lcd.setCursor(0,0);
lcd.print( F("Signal Locked") );
lcd.setCursor(0,1);
lcd.print( range );
lcd.print( F(" Miles") );
warningState = 0; // restart in case we lose the fix later
}
}
// Display warnings when the GPS hasn't had a fix for a while
if (millis() - lastFixTime > 2000UL) {
if (warningState == 0) {
// First, show the warning message...
lcd.clear();
lcd.setCursor(0,0);
lcd.print( F("Acquiring Signal") );
warningState = 1;
} else if (warningState < 10) {
// ...then some dots, two per second...
if (millis() - lastDotTime > 500UL ) {
lastDotTime = millis();
lcd.setCursor( warningState-1, 1 );
lcd.print( '.' );
warningState++;
}
} else if (warningState == 10) {
// ... then tell them what to do.
lcd.setCursor(0,1);
lcd.println( F("Take box outside") );
// Don't display anything else until location is valid
warningState++; // 11
}
}
} // loop
//----------------------------------------------------------------
#include "DMS.h"
void printDMS( Print & outs, const Location_t & loc )
{
DMS_t dms;
dms.From( loc.lat() );
outs.print( dms.NS() );
outs << dms;
dms.From( loc.lon() );
outs.print( dms.EW() );
outs << dms;
} // printDMS
//----------------------------------------------------------------
void printL( Print & outs, int32_t degE7 )
{
// Extract and print negative sign
if (degE7 < 0) {
degE7 = -degE7;
outs.print( '-' );
}
// Whole degrees
int32_t deg = degE7 / 10000000L;
outs.print( deg );
outs.print( '.' );
// Get fractional degrees
degE7 -= deg*10000000L;
// Print leading zeroes, if needed
int32_t factor = 1000000L;
while ((degE7 < factor) && (factor > 1L)){
outs.print( '0' );
factor /= 10L;
}
// Print fractional degrees
outs.print( degE7 );
} // printL
//----------------------------------------------------------------
void printAsFloat( Print & outs, const Location_t &loc )
{
printL( outs, loc.lat() ); // display decimal degrees or...
// printDMS( outs, loc.lat() ); // ... display degrees minutes seconds
outs.print( F(", ") );
printL( outs, loc.lat() );
// printDMS( outs, loc.lat() );
}

View File

@@ -0,0 +1,72 @@
#include <NMEAGPS.h>
//======================================================================
// Program: NMEAsimple.ino
//
// Description: This program shows simple usage of NeoGPS
//
// Prerequisites:
// 1) NMEA.ino works with your device (correct TX/RX pins and baud rate)
// 2) At least one of the RMC, GGA or GLL sentences have been enabled in NMEAGPS_cfg.h.
// 3) Your device at least one of those sentences (use NMEAorder.ino to confirm).
// 4) LAST_SENTENCE_IN_INTERVAL has been set to one of those sentences in NMEAGPS_cfg.h (use NMEAorder.ino).
// 5) LOCATION and ALTITUDE have been enabled in GPSfix_cfg.h
//
// 'Serial' is for debug output to the Serial Monitor window.
//
// License:
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// NeoGPS is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//
//======================================================================
#include <GPSport.h>
NMEAGPS gps; // This parses the GPS characters
gps_fix fix; // This holds on to the latest values
void setup()
{
DEBUG_PORT.begin(9600);
while (!Serial)
;
DEBUG_PORT.print( F("NMEAsimple.INO: started\n") );
gpsPort.begin(9600);
}
//--------------------------
void loop()
{
while (gps.available( gpsPort )) {
fix = gps.read();
DEBUG_PORT.print( F("Location: ") );
if (fix.valid.location) {
DEBUG_PORT.print( fix.latitude(), 6 );
DEBUG_PORT.print( ',' );
DEBUG_PORT.print( fix.longitude(), 6 );
}
DEBUG_PORT.print( F(", Altitude: ") );
if (fix.valid.altitude)
DEBUG_PORT.print( fix.altitude() );
DEBUG_PORT.println();
}
}

View File

@@ -0,0 +1,771 @@
#include <NMEAGPS.h>
using namespace NeoGPS;
//======================================================================
// Program: NMEAtest.ino
//
// Prerequisites:
// 1) All NMEA standard messages and Satellite Information
// are enabled.
// 2) All 'gps_fix' members are enabled.
// 3) All validation options are enabled.
//
// Description: This test program uses one GPGGA sentence
// to test the parser's:
// 1) robustness WRT dropped, inserted, and mangled characters
// 2) correctness WRT values extracted from the input stream
//
// Some care in testing must be taken because
// 1) The XOR-style checksum is not very good at catching errors.
// 2) The '*' is a special character for delimiting the CRC. If
// it is changed, a CR/LF will allow the sentence to pass.
//
// 'Serial' is for debug output to the Serial Monitor window.
//
// License:
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// NeoGPS is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//
//======================================================================
#include <Streamers.h>
//------------------------------------------------------------
// Check that the config files are set up properly
#if !defined(NMEAGPS_PARSE_GGA) | \
!defined(NMEAGPS_PARSE_GLL) | \
!defined(NMEAGPS_PARSE_GSA) | \
!defined(NMEAGPS_PARSE_GST) | \
!defined(NMEAGPS_PARSE_GSV) | \
!defined(NMEAGPS_PARSE_RMC) | \
!defined(NMEAGPS_PARSE_VTG) | \
!defined(NMEAGPS_PARSE_ZDA)
#error NMEAGPS_PARSE_GGA, GLL, GSA, GSV, RMC, VTG and ZDA must be defined in NMEAGPS_cfg.h!
#endif
#ifndef GPS_FIX_DATE
#error GPS_FIX_DATE must be defined in GPSfix_cfg.h!
#endif
#ifndef GPS_FIX_TIME
#error GPS_FIX_TIME must be defined in GPSfix_cfg.h!
#endif
#ifndef GPS_FIX_LOCATION
#error GPS_FIX_LOCATION must be defined in GPSfix_cfg.h!
#endif
#ifndef GPS_FIX_LOCATION_DMS
#error GPS_FIX_LOCATION_DMS must be defined in GPSfix_cfg.h!
#endif
#ifndef GPS_FIX_ALTITUDE
#error GPS_FIX_ALTITUDE must be defined in GPSfix_cfg.h!
#endif
#ifndef GPS_FIX_SPEED
#error GPS_FIX_SPEED must be defined in GPSfix_cfg.h!
#endif
#ifndef GPS_FIX_HEADING
#error GPS_FIX_HEADING must be defined in GPSfix_cfg.h!
#endif
#ifndef GPS_FIX_SATELLITES
#error GPS_FIX_SATELLITES must be defined in GPSfix_cfg.h!
#endif
#ifndef GPS_FIX_HDOP
#error GPS_FIX_HDOP must be defined in GPSfix_cfg.h!
#endif
#ifndef GPS_FIX_GEOID_HEIGHT
#error GPS_FIX_GEOID_HEIGHT must be defined in GPSfix_cfg.h!
#endif
static NMEAGPS gps;
//--------------------------
// Example sentences
struct LocVector_t
{
float range;
float bearing;
};
static Location_t AyersRock( -253448688L, 1310324914L );
// -25.3448688,131.0324914
// 2520.692128,S,13101.949484,E
// 25 20' 41.528" S 131 1' 56.969" E
static const LocVector_t NiihauToAyersRock = { 9078.681, 238.33972 };
const char validRMC[] __PROGMEM =
"$GPRMC,092725.00,A,2520.69213,S,13101.94948,E,"
"0.004,77.52,091202,,,A*43\r\n";
//...........................
static Location_t ubloxHQ( 472852369L, 85630763L ); // near Zurich, Switzerland
// 47.2852369, 8.5630763
// 47 17' 6.840" N 008 33' 54.954" E
static const LocVector_t NiihauToUblox = { 12248.67, 8.0625 };
const char validGGA[] __PROGMEM =
"$GPGGA,092725.00,4717.113993,N,00833.915904,E,"
"1,8,1.01,499.6,M,48.0,M,,0*5C\r\n";
//...........................
static Location_t MacchuPicchu( -131628050L, -725455080L );
// -13.162805, -72.545508
// 13.162805,S,72.545508,W
// 13 09' 46.098" S 72 32' 43.830" W
static const LocVector_t NiihauToMacchu = { 10315.93, 103.07306 };
const char validGGA2[] __PROGMEM =
"$GPGGA,162254.00,1309.7683,S,7232.7305,W,1,03,2.36,2430.2,M,-25.6,M,,*7E\r\n";
//...........................
static Location_t DexterMO( 367944050L, -899586550L );
// 36.794405, -89.958655
// 36.794405,N,89.958655,W
// 36 47' 39.858" N 89 57' 31.158" W
static const LocVector_t NiihauToDexter = { 6865.319, 58.85472 };
const char validRMC2[] __PROGMEM =
"$GPRMC,162254.00,A,3647.6643,N,8957.5193,W,0.820,188.36,110706,,,A*49\r\n";
//...........................
static Location_t NiihauHI( 218276210L, -1602448760L );
// 21.827621, -160.244876
// 21.827621,N,160.244876,W
// 21 49' 39.4356" N 160 14' 41.5536 W
static const LocVector_t NiihauToNiihau = { 0.0, 90.0 };
const char validRMC3[] __PROGMEM =
"$GPRMC,235959.99,A,2149.65726,N,16014.69256,W,8.690,359.99,051015,9.47,E,A*26\r\n";
// 218276212L, -1602448757L
// 21 49.65727' N 160 14.69254' W
// 21 49' 39.4362" N 160 14' 41.5524" W
const char validRMC4[] __PROGMEM =
"$GPRMC,235959.99,A,2149.65727,N,16014.69254,W,8.690,359.99,051015,9.47,E,A*25\r\n";
static const LocVector_t NiihauToNiihau2 = { 0.00003812513, 54.31585 };
//...........................
static Location_t JujaKenya( -10934552L, 370261835L );
// -1.0934552, 37.0261835
// 01 05' 36.458" S 037 01' 42.140" E
static const LocVector_t NiihauToJuja = { 17046.24, 318.6483 };
const char validGLL[] __PROGMEM =
"$GNGLL,0105.60764,S,03701.70233,E,225627.00,A,A*6B\r\n";
//--------------------------------
const char mtk1[] __PROGMEM =
"$GPGGA,064951.000,2307.1256,N,12016.4438,E,1,8,0.95,39.9,M,17.8,M,,*63\r\n";
const char mtk2[] __PROGMEM =
"$GPRMC,064951.000,A,2307.1256,N,12016.4438,E,0.03,165.48,260406,3.05,W,A*2C\r\n";
const char mtk3[] __PROGMEM =
"$GPVTG,165.48,T,,M,0.03,N,0.06,K,A*36\r\n";
const char mtk4[] __PROGMEM =
"$GPGSA,A,3,29,21,26,15,18,09,06,10,,,,,2.32,0.95,2.11*00\r\n";
const char mtk5[] __PROGMEM =
"$GPGSV,3,1,09,29,36,029,42,21,46,314,43,26,44,020,43,15,21,321,39*7D\r\n";
const char mtk6[] __PROGMEM =
"$GPGSV,3,2,09,18,26,314,40,09,57,170,44,06,20,229,37,10,26,084,37*77\r\n";
const char mtk7[] __PROGMEM =
"$GPGSV,3,3,09,07,,,26*73\r\n";
const char mtk7a[] __PROGMEM =
"$GLGSV,1,1,4,29,36,029,42,21,46,314,43,26,44,020,43,15,21,321,39*5E\r\n";
const char mtk8[] __PROGMEM =
"$GNGST,082356.00,1.8,,,,1.7,1.3,2.2*60\r\n";
const char mtk9[] __PROGMEM =
"$GNRMC,083559.00,A,4717.11437,N,00833.91522,E,0.004,77.52,091202,,,A,V*33\r\n";
const char mtk10[] __PROGMEM =
"$GNGGA,092725.00,4717.11399,N,00833.91590,E,1,08,1.01,499.6,M,48.0,M,,*45\r\n";
const char mtk11[] __PROGMEM =
"$GLZDA,225627.00,21,09,2015,00,00*70\r\n";
const char fpGGA00[] __PROGMEM = "$GPGGA,092725.00,3242.9000,N,11705.816900,W,"
"1,8,1.01,499.6,M,48.0,M,,0*49\r\n";
const char fpGGA01[] __PROGMEM = "$GPGGA,092725.00,3242.9000,N,11705.816901,W,"
"1,8,1.01,499.6,M,48.0,M,,0*48\r\n";
const char fpGGA02[] __PROGMEM = "$GPGGA,092725.00,3242.9000,N,11705.816902,W,"
"1,8,1.01,499.6,M,48.0,M,,0*4B\r\n";
const char fpGGA03[] __PROGMEM = "$GPGGA,092725.00,3242.9000,N,11705.816903,W,"
"1,8,1.01,499.6,M,48.0,M,,0*4A\r\n";
const char fpGGA04[] __PROGMEM = "$GPGGA,092725.00,3242.9000,N,11705.816904,W,"
"1,8,1.01,499.6,M,48.0,M,,0*4D\r\n";
const char fpGGA05[] __PROGMEM = "$GPGGA,092725.00,3242.9000,N,11705.816905,W,"
"1,8,1.01,499.6,M,48.0,M,,0*4C\r\n";
const char fpGGA06[] __PROGMEM = "$GPGGA,092725.00,3242.9000,N,11705.816906,W,"
"1,8,1.01,499.6,M,48.0,M,,0*4F\r\n";
const char fpGGA07[] __PROGMEM = "$GPGGA,092725.00,3242.9000,N,11705.816907,W,"
"1,8,1.01,499.6,M,48.0,M,,0*4E\r\n";
const char fpGGA08[] __PROGMEM = "$GPGGA,092725.00,3242.9000,N,11705.816908,W,"
"1,8,1.01,499.6,M,48.0,M,,0*41\r\n";
const char fpGGA09[] __PROGMEM = "$GPGGA,092725.00,3242.9000,N,11705.816909,W,"
"1,8,1.01,499.6,M,48.0,M,,0*40\r\n";
const char fpGGA10[] __PROGMEM = "$GPGGA,092725.00,3242.9000,N,11705.816910,W,"
"1,8,1.01,499.6,M,48.0,M,,0*48\r\n";
//--------------------------
static bool parse_P( const char *ptr )
{
bool decoded = false;
char c;
gps.fix().init();
while ( (c = pgm_read_byte( ptr++ )) != '\0' ) {
if (NMEAGPS::DECODE_COMPLETED == gps.decode( c )) {
decoded = true;
}
}
return decoded;
}
//--------------------------
static void traceSample( const char *ptr, bool init = true )
{
Serial << F("Input: ") << (const __FlashStringHelper *) ptr;
if (init)
gps.data_init();
bool decoded = parse_P( ptr );
if (decoded)
Serial << F("Results: ");
else
Serial << F("Failed to decode! ");
trace_all( Serial, gps, gps.fix() );
Serial << '\n';
}
//--------------------------
static uint8_t passed = 0;
static uint8_t failed = 0;
static void checkFix
( const char *msg, NMEAGPS::nmea_msg_t msg_type, gps_fix::status_t status,
int32_t lat, int32_t lon,
uint8_t latDeg, uint8_t latMin, uint8_t latSec, uint16_t latSecFrac, Hemisphere_t ns,
uint8_t lonDeg, uint8_t lonMin, uint8_t lonSec, uint16_t lonSecFrac, Hemisphere_t ew,
const LocVector_t & to )
{
const char *ptr = msg;
for (;;) {
char c = pgm_read_byte( ptr++ );
if (!c) {
Serial.print( F("FAILED to parse \"") );
Serial.print( (const __FlashStringHelper *) msg );
Serial.println( F("\"\n") );
failed++;
break;
}
if (NMEAGPS::DECODE_COMPLETED == gps.decode( c )) {
bool ok = true;
const gps_fix & fix = gps.fix();
if (gps.nmeaMessage != msg_type) {
Serial.print( F("FAILED wrong message type ") );
Serial.println( gps.nmeaMessage );
failed++;
ok = false;
}
if (fix.status != status ) {
Serial.print( F("FAILED wrong status ") );
Serial.print( fix.status );
Serial.print( F(" (expected ") );
Serial.print( status );
Serial.println( ')' );
failed++;
ok = false;
}
if (fix.latitudeL() != lat) {
Serial.print( F("FAILED wrong latitude ") );
Serial.print( fix.latitudeL() );
Serial.print( F(" (expected ") );
Serial.print( lat );
Serial.println( ')' );
failed++;
ok = false;
}
if (fix.longitudeL() != lon) {
Serial.print( F("FAILED wrong longitude ") );
Serial.print( fix.longitudeL() );
Serial.print( F(" (expected ") );
Serial.print( lon );
Serial.println( ')' );
failed++;
ok = false;
}
if (fix.latitudeDMS.degrees != latDeg) {
Serial.print( F("FAILED wrong latitude degrees ") );
Serial.print( fix.latitudeDMS.degrees );
Serial.print( F(", expected ") );
Serial.println( latDeg );
failed++;
ok = false;
}
if (fix.latitudeDMS.minutes != latMin) {
Serial.print( F("FAILED wrong latitude minutes ") );
Serial.print( fix.latitudeDMS.minutes );
Serial.print( F(", expected ") );
Serial.println( latMin );
failed++;
ok = false;
}
if (fix.latitudeDMS.seconds_whole != latSec) {
Serial.print( F("FAILED wrong latitude seconds ") );
Serial.print( fix.latitudeDMS.seconds_whole );
Serial.print( F(", expected ") );
Serial.println( latSec );
failed++;
ok = false;
}
int8_t fracDiff = (int8_t)(fix.latitudeDMS.seconds_frac - latSecFrac);
const int8_t ieps = 1;
if (abs(fracDiff) > ieps) {
Serial.print( F("FAILED wrong latitude seconds fraction ") );
Serial.print( fix.latitudeDMS.seconds_frac );
Serial.print( F(", expected ") );
Serial.println( latSecFrac );
failed++;
ok = false;
}
if (fix.latitudeDMS.hemisphere != ns) {
Serial.print( F("FAILED wrong latitude NS ") );
Serial.println( fix.latitudeDMS.NS() );
failed++;
ok = false;
}
if (fix.longitudeDMS.degrees != lonDeg) {
Serial.print( F("FAILED wrong longitude degrees ") );
Serial.print( fix.longitudeDMS.degrees );
Serial.print( F(", expected ") );
Serial.println( lonDeg );
failed++;
ok = false;
}
if (fix.longitudeDMS.minutes != lonMin) {
Serial.print( F("FAILED wrong longitude minutes ") );
Serial.print( fix.longitudeDMS.minutes );
Serial.print( F(", expected ") );
Serial.println( lonMin );
failed++;
ok = false;
}
if (fix.longitudeDMS.seconds_whole != lonSec) {
Serial.print( F("FAILED wrong longitude seconds ") );
Serial.print( fix.longitudeDMS.seconds_whole );
Serial.print( F(", expected ") );
Serial.println( lonSec );
failed++;
ok = false;
}
fracDiff = (int8_t)(fix.longitudeDMS.seconds_frac - lonSecFrac);
if (abs(fracDiff) > ieps) {
Serial.print( F("FAILED wrong longitude seconds fraction ") );
Serial.print( fix.longitudeDMS.seconds_frac );
Serial.print( F(", expected ") );
Serial.println( lonSecFrac );
failed++;
ok = false;
}
if (fix.longitudeDMS.hemisphere != ew) {
Serial.print( F("FAILED wrong longitude EW ") );
Serial.println( fix.longitudeDMS.EW() );
failed++;
ok = false;
}
char floatChars[16];
float distance = NiihauHI.DistanceKm( fix.location );
float diff = abs( distance - to.range );
if ( (diff/to.range) > 0.000001 ) {
Serial.print( F("FAILED distance ") );
dtostre( distance, floatChars, 6, DTOSTR_PLUS_SIGN );
Serial.print( floatChars );
Serial.print( F(" != ") );
dtostre( to.range, floatChars, 6, DTOSTR_PLUS_SIGN );
Serial.println( floatChars );
failed++;
ok = false;
}
float courseTo = NiihauHI.BearingToDegrees( fix.location );
diff = abs( courseTo - to.bearing );
if ( diff > 0.005 ) {
Serial.print( F("FAILED bearing ") );
dtostre( courseTo, floatChars, 6, DTOSTR_PLUS_SIGN );
Serial.print( floatChars );
Serial.print( F(" != ") );
dtostre( to.bearing, floatChars, 6, DTOSTR_PLUS_SIGN );
Serial.print( floatChars );
failed++;
ok = false;
}
if (ok)
passed++;
break;
}
}
}
//--------------------------
void setup()
{
// Start the normal trace output
Serial.begin(9600);
Serial.print( F("NMEA test: started\n") );
Serial.print( F("fix object size = ") );
Serial.println( sizeof(gps_fix) );
Serial.print( F("gps object size = ") );
Serial.println( sizeof(NMEAGPS) );
// Some basic rejection tests
Serial.println( F("Test rejection of all byte values") );
for (uint16_t c=0; c < 256; c++) {
if (c != '$') {
if (NMEAGPS::DECODE_CHR_INVALID != gps.decode( (char)c )) {
Serial.print( F("FAILED to reject single character ") );
Serial.println( c );
failed++;
return;
}
}
}
passed++;
Serial.println( F("Test rejection of multiple $") );
for (uint16_t i=0; i < 256; i++) {
if (NMEAGPS::DECODE_COMPLETED == gps.decode( '$' )) {
Serial.print( F("FAILED to reject multiple '$' characters\n") );
failed++;
return;
}
}
passed++;
uint16_t validGGA_len = 0;
// Insert a ' ' at each position of the test sentence
Serial.println( F("Insert ' '") );
uint16_t insert_at = 1;
do {
const char *ptr = validGGA;
uint8_t j = 0;
for (;;) {
if (j++ == insert_at) {
NMEAGPS::decode_t result = gps.decode( ' ' );
if (gps.validateChars() || gps.validateFields()) {
if (result == NMEAGPS::DECODE_COMPLETED) {
Serial.print( F("FAILED incorrectly decoded ' ' @ pos ") );
Serial.println( insert_at );
failed++;
} else if (gps.nmeaMessage != NMEAGPS::NMEA_UNKNOWN) {
Serial.print( F("FAILED incorrectly accepted ' ' @ pos ") );
Serial.println( insert_at );
failed++;
}
}
}
char c = pgm_read_byte( ptr++ );
if (!c) {
if (validGGA_len == 0) {
validGGA_len = j-1;
Serial.print( F("Test string length = ") );
Serial.println( validGGA_len );
}
break;
}
if (gps.decode( c ) == NMEAGPS::DECODE_COMPLETED) {
Serial.print( F("FAILED incorrectly decoded @ pos ") );
Serial.println( insert_at );
failed++;
//return;
}
}
} while (++insert_at < validGGA_len-2);
passed++;
// Drop one character from each position in example sentence
Serial.println( F("Drop character") );
for (uint16_t i=0; i < validGGA_len-3; i++) {
const char *ptr = validGGA;
uint8_t firstInvalidPos = 0;
char dropped = 0;
for (uint8_t j = 0;; j++) {
char c = pgm_read_byte( ptr++ );
if (!c || (c == '*')) break;
if (j == i) {
dropped = c;
} else {
NMEAGPS::decode_t result = gps.decode( c );
if (result == NMEAGPS::DECODE_COMPLETED) {
Serial.print( F("FAILED decoded after dropping '") );
Serial << dropped;
Serial.print( F("' at pos ") );
Serial.println( i );
failed++;
} else if (gps.nmeaMessage == NMEAGPS::NMEA_UNKNOWN) {
if (firstInvalidPos != 0)
firstInvalidPos = j;
}
}
}
if (firstInvalidPos != 0) {
Serial.print( F(" /*") );
Serial.print( i );
Serial.print( F("*/ ") );
Serial.println( firstInvalidPos );
}
}
passed++;
// Mangle one character from each position in example sentence
Serial.println( F("Mangle one character") );
for (uint16_t i=0; i < validGGA_len-3; i++) {
const char *ptr = validGGA;
uint8_t j = 0;
char replaced = 0;
for (;;) {
char c = pgm_read_byte( ptr++ );
if (!c || (c == '*')) break;
if (j++ == i)
replaced = c++; // mangle means increment
if (NMEAGPS::DECODE_COMPLETED == gps.decode( c )) {
Serial.print( F("FAILED replacing '") );
Serial << (uint8_t) replaced;
Serial.print( F("' with '") );
Serial << (uint8_t) (replaced+1);
Serial.print( F("' at pos ") );
Serial.println( i );
failed++;
break;
//return;
}
}
}
passed++;
// Verify that exact values are extracted
Serial.println( F("Verify parsed values") );
{
const char *ptr = validGGA;
for (;;) {
char c = pgm_read_byte( ptr++ );
if (!c) {
Serial.print( F("FAILED to parse \"") );
Serial.print( (str_P) validGGA );
Serial.println( F("\"\n") );
failed++;
break;
}
if (NMEAGPS::DECODE_COMPLETED == gps.decode( c )) {
gps_fix expected;
expected.dateTime.parse( PSTR("2002-12-09 09:27:25") );
expected.dateTime_cs = 0;
if (gps.nmeaMessage != NMEAGPS::NMEA_GGA) {
Serial.print( F("FAILED wrong message type ") );
Serial.println( gps.nmeaMessage );
failed++;
break;
}
if ((gps.fix().dateTime.hours != expected.dateTime.hours ) ||
(gps.fix().dateTime.minutes != expected.dateTime.minutes) ||
(gps.fix().dateTime.seconds != expected.dateTime.seconds) ||
(gps.fix().dateTime_cs != expected.dateTime_cs)) {
Serial << F("FAILED wrong time ") << gps.fix().dateTime << '.' << gps.fix().dateTime_cs << F(" != ") << expected.dateTime << '.' << expected.dateTime_cs << '\n';
failed++;
break;
}
if (gps.fix().latitudeL() != 472852332L) {
Serial.print( F("FAILED wrong latitude ") );
Serial.println( gps.fix().latitudeL() );
failed++;
break;
}
if (gps.fix().longitudeL() != 85652651L) {
Serial.print( F("FAILED wrong longitude ") );
Serial.println( gps.fix().longitudeL() );
failed++;
break;
}
if (gps.fix().status != gps_fix::STATUS_STD) {
Serial.print( F("FAILED wrong status ") );
Serial.println( gps.fix().status );
failed++;
break;
}
if (gps.fix().satellites != 8) {
Serial.print( F("FAILED wrong satellites ") );
Serial.println( gps.fix().satellites );
failed++;
break;
}
if (gps.fix().hdop != 1010) {
Serial.print( F("FAILED wrong HDOP ") );
Serial.println( gps.fix().hdop );
failed++;
break;
}
if (gps.fix().altitude_cm() != 49960) {
Serial.print( F("FAILED wrong altitude ") );
Serial.println( gps.fix().longitudeL() );
failed++;
break;
}
break;
}
}
}
passed++;
checkFix( validRMC , NMEAGPS::NMEA_RMC, gps_fix::STATUS_STD,
-253448688L, 1310324913L,
25, 20, 41, 528, SOUTH_H, 131, 1, 56, 969, EAST_H,
NiihauToAyersRock );
checkFix( validGGA , NMEAGPS::NMEA_GGA, gps_fix::STATUS_STD,
472852332L, 85652651L,
47, 17, 6, 840, NORTH_H, 8, 33, 54, 954, EAST_H,
NiihauToUblox );
if (gps.fix().geoidHeight_cm() != 4800) {
Serial.print( F("FAILED wrong geoid height for 48.00") );
Serial.println( gps.fix().geoidHeight_cm() );
failed++;
}
checkFix( validGGA2, NMEAGPS::NMEA_GGA, gps_fix::STATUS_STD,
-131628050L, -725455083L,
13, 9, 46, 98, SOUTH_H, 72, 32, 43, 830, WEST_H,
NiihauToMacchu );
if (gps.fix().geoidHeight_cm() != -2560) {
Serial.print( F("FAILED wrong geoid height for -25.60") );
Serial.println( gps.fix().geoidHeight_cm() );
failed++;
}
checkFix( validRMC2, NMEAGPS::NMEA_RMC, gps_fix::STATUS_STD,
367944050L, -899586550L,
36, 47, 39, 858, NORTH_H, 89, 57, 31, 158, WEST_H,
NiihauToDexter );
checkFix( validRMC3, NMEAGPS::NMEA_RMC, gps_fix::STATUS_STD,
218276210L, -1602448760L,
21, 49, 39, 436, NORTH_H, 160, 14, 41, 554, WEST_H,
NiihauToNiihau );
checkFix( validRMC4, NMEAGPS::NMEA_RMC, gps_fix::STATUS_STD,
218276212L, -1602448757L,
21, 49, 39, 436, NORTH_H, 160, 14, 41, 552, WEST_H,
NiihauToNiihau2 );
checkFix( validGLL , NMEAGPS::NMEA_GLL, gps_fix::STATUS_STD,
-10934607L, 370283722L,
1, 5, 36, 458, SOUTH_H, 37, 1, 42, 140, EAST_H,
NiihauToJuja );
}
//--------------------------
void loop()
{
Serial.print( F("PASSED ") );
Serial << passed;
Serial.println( F(" tests.") );
if (failed) {
Serial.print( F("FAILED ") );
Serial << failed;
Serial.println( F(" tests.") );
} else {
Serial << F("------ Samples ------\nResults format:\n ");
trace_header( Serial );
Serial << '\n';
#ifdef NMEAGPS_STATS
gps.statistics.init();
#endif
traceSample( validGGA );
traceSample( validGGA2 );
traceSample( validRMC );
traceSample( validRMC2 );
traceSample( validRMC3 );
traceSample( validGLL );
traceSample( mtk1 );
traceSample( mtk2 );
traceSample( mtk3 );
traceSample( mtk4 );
traceSample( mtk5 );
traceSample( mtk6, false );
traceSample( mtk7, false );
traceSample( mtk7a, false );
traceSample( mtk8 );
traceSample( mtk9 );
traceSample( mtk10 );
traceSample( mtk11 );
if (!gps.fix().valid.date ||
(gps.fix().dateTime.date != 21) ||
(gps.fix().dateTime.month != 9) ||
(gps.fix().dateTime.year != 15) ||
!gps.fix().valid.time ||
(gps.fix().dateTime.hours != 22) ||
(gps.fix().dateTime.minutes != 56) ||
(gps.fix().dateTime.seconds != 27))
Serial << F("******** ZDA not parsed correctly **********\n");
/**
* This next section displays incremental longitudes.
* If you have defined USE_FLOAT in Streamers.cpp, this will show
* how the conversion to /float/ causes loss of accuracy compared
* to the /uint32_t/ values.
*/
Serial << F("--- floating point conversion tests ---\n\n");
traceSample( fpGGA00 );
traceSample( fpGGA01 );
traceSample( fpGGA02 );
traceSample( fpGGA03 );
traceSample( fpGGA04 );
traceSample( fpGGA05 );
traceSample( fpGGA06 );
traceSample( fpGGA07 );
traceSample( fpGGA08 );
traceSample( fpGGA09 );
traceSample( fpGGA10 );
}
for (;;);
}

View File

@@ -0,0 +1,188 @@
#include <NMEAGPS.h>
//======================================================================
// Program: NMEAtimezone.ino
//
// Description: This program shows how to offset the GPS dateTime member
// into your specific timezone. GPS devices do not know which
// timezone they are in, so they always report a UTC time. This
// is the same as GMT.
//
// Prerequisites:
// 1) NMEA.ino works with your device
// 2) GPS_FIX_TIME is enabled in GPSfix_cfg.h
// 3) NMEAGPS_PARSE_RMC is enabled in NMEAGPS_cfg.h. You could use
// any sentence that contains a time field. Be sure to change the
// "if" statement in GPSloop from RMC to your selected sentence.
//
// 'Serial' is for debug output to the Serial Monitor window.
//
// License:
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// NeoGPS is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//
//======================================================================
static NMEAGPS gps; // This parses received characters
static gps_fix fix; // This contains all the parsed pieces
//--------------------------
// CHECK CONFIGURATION
#if !defined(GPS_FIX_TIME) | !defined(GPS_FIX_DATE)
#error You must define GPS_FIX_TIME and DATE in GPSfix_cfg.h!
#endif
#if !defined(NMEAGPS_PARSE_RMC) & !defined(NMEAGPS_PARSE_ZDA)
#error You must define NMEAGPS_PARSE_RMC or ZDA in NMEAGPS_cfg.h!
#endif
#include <GPSport.h>
//--------------------------
// Set these values to the offset of your timezone from GMT
static const int32_t zone_hours = -5L; // EST
static const int32_t zone_minutes = 0L; // usually zero
static const NeoGPS::clock_t zone_offset =
zone_hours * NeoGPS::SECONDS_PER_HOUR +
zone_minutes * NeoGPS::SECONDS_PER_MINUTE;
// Uncomment one DST changeover rule, or define your own:
#define USA_DST
//#define EU_DST
#if defined(USA_DST)
static const uint8_t springMonth = 3;
static const uint8_t springDate = 14; // latest 2nd Sunday
static const uint8_t springHour = 2;
static const uint8_t fallMonth = 11;
static const uint8_t fallDate = 7; // latest 1st Sunday
static const uint8_t fallHour = 2;
#define CALCULATE_DST
#elif defined(EU_DST)
static const uint8_t springMonth = 3;
static const uint8_t springDate = 31; // latest last Sunday
static const uint8_t springHour = 1;
static const uint8_t fallMonth = 10;
static const uint8_t fallDate = 31; // latest last Sunday
static const uint8_t fallHour = 1;
#define CALCULATE_DST
#endif
//--------------------------
void adjustTime( NeoGPS::time_t & dt )
{
NeoGPS::clock_t seconds = dt; // convert date/time structure to seconds
#ifdef CALCULATE_DST
// Calculate DST changeover times once per reset and year!
static NeoGPS::time_t changeover;
static NeoGPS::clock_t springForward, fallBack;
if ((springForward == 0) || (changeover.year != dt.year)) {
// Calculate the spring changeover time (seconds)
changeover.year = dt.year;
changeover.month = springMonth;
changeover.date = springDate;
changeover.hours = springHour;
changeover.minutes = 0;
changeover.seconds = 0;
changeover.set_day();
// Step back to a Sunday, if day != SUNDAY
changeover.date -= (changeover.day - NeoGPS::time_t::SUNDAY);
springForward = (NeoGPS::clock_t) changeover;
// Calculate the fall changeover time (seconds)
changeover.month = fallMonth;
changeover.date = fallDate;
changeover.hours = fallHour - 1; // to account for the "apparent" DST +1
changeover.set_day();
// Step back to a Sunday, if day != SUNDAY
changeover.date -= (changeover.day - NeoGPS::time_t::SUNDAY);
fallBack = (NeoGPS::clock_t) changeover;
}
#endif
// First, offset from UTC to the local timezone
seconds += zone_offset;
#ifdef CALCULATE_DST
// Then add an hour if DST is in effect
if ((springForward <= seconds) && (seconds < fallBack))
seconds += NeoGPS::SECONDS_PER_HOUR;
#endif
dt = seconds; // convert seconds back to a date/time structure
} // adjustTime
//--------------------------
static void GPSloop()
{
while (gps.available( gpsPort )) {
fix = gps.read();
// Display the local time
if (fix.valid.time && fix.valid.date) {
adjustTime( fix.dateTime );
DEBUG_PORT << fix.dateTime;
}
DEBUG_PORT.println();
}
} // GPSloop
//--------------------------
#ifdef NMEAGPS_INTERRUPT_PROCESSING
static void GPSisr( uint8_t c )
{
gps.handle( c );
}
#endif
//--------------------------
void setup()
{
DEBUG_PORT.begin(9600);
while (!DEBUG_PORT)
;
DEBUG_PORT.print( F("NMEAtimezone.INO: started\n") );
DEBUG_PORT.println( F("Looking for GPS device on " GPS_PORT_NAME ) );
DEBUG_PORT.println( F("Local time") );
DEBUG_PORT.flush();
gpsPort.begin( 9600 );
#ifdef NMEAGPS_INTERRUPT_PROCESSING
gpsPort.attachInterrupt( GPSisr );
#endif
}
//--------------------------
void loop()
{
GPSloop();
}

View File

@@ -0,0 +1,156 @@
#include <NeoGPS_cfg.h>
#include <ublox/ubxNMEA.h>
//======================================================================
// Program: PUBX.ino
//
// Description: This program parses NMEA proprietary messages from
// ublox devices. It is an extension of NMEA.ino.
//
// Prerequisites:
// 1) You have a ublox GPS device
// 2) NMEA.ino works with your device
// 3) You have installed ubxNMEA.H and ubxNMEA.CPP
// 4) At least one NMEA standard or proprietary sentence has been enabled.
// 5) Implicit Merging is disabled.
//
// Serial is for debug output to the Serial Monitor window.
//
// License:
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// NeoGPS is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//
//======================================================================
#include <GPSport.h>
#include <Streamers.h>
//------------------------------------------------------------
// Check that the config files are set up properly
#if !defined( NMEAGPS_PARSE_GGA ) & !defined( NMEAGPS_PARSE_GLL ) & \
!defined( NMEAGPS_PARSE_GSA ) & !defined( NMEAGPS_PARSE_GSV ) & \
!defined( NMEAGPS_PARSE_RMC ) & !defined( NMEAGPS_PARSE_VTG ) & \
!defined( NMEAGPS_PARSE_ZDA ) & !defined( NMEAGPS_PARSE_GST ) & \
!defined( NMEAGPS_PARSE_PUBX_00 ) & !defined( NMEAGPS_PARSE_PUBX_04 )
#error No NMEA sentences enabled: no fix data available for fusing.
#endif
#if !defined( NMEAGPS_PARSE_PUBX_00 ) & !defined( NMEAGPS_PARSE_PUBX_04 )
#error No PUBX messages enabled! You must enable one or more in ubxNMEA.h!
#endif
#ifndef NMEAGPS_DERIVED_TYPES
#error You must "#define NMEAGPS_DERIVED_TYPES" in NMEAGPS_cfg.h!
#endif
#ifndef NMEAGPS_EXPLICIT_MERGING
#error You must define NMEAGPS_EXPLICIT_MERGING in NMEAGPS_cfg.h
#endif
#ifdef NMEAGPS_INTERRUPT_PROCESSING
#error You must *NOT* define NMEAGPS_INTERRUPT_PROCESSING in NMEAGPS_cfg.h!
#endif
//------------------------------------------------------------
static ubloxNMEA gps; // This parses received characters
static gps_fix merged;
//----------------------------------------------------------------
static void poll()
{
#if defined( NMEAGPS_PARSE_PUBX_00 )
gps.send_P( &gpsPort, F("PUBX,00") );
#endif
#if defined( NMEAGPS_PARSE_PUBX_04 )
gps.send_P( &gpsPort, F("PUBX,04") );
#endif
}
//----------------------------------------------------------------
static void doSomeWork()
{
// Print all the things!
trace_all( DEBUG_PORT, gps, merged );
// Ask for the proprietary messages again
poll();
} // doSomeWork
//------------------------------------
static void GPSloop()
{
while (gps.available( gpsPort )) {
merged = gps.read();
doSomeWork();
}
} // GPSloop
//--------------------------
void setup()
{
DEBUG_PORT.begin(9600);
while (!DEBUG_PORT)
;
DEBUG_PORT.print( F("PUBX: started\n") );
DEBUG_PORT.print( F("fix object size = ") );
DEBUG_PORT.println( sizeof(gps.fix()) );
DEBUG_PORT.print( F("ubloxNMEA object size = ") );
DEBUG_PORT.println( sizeof(gps) );
DEBUG_PORT.println( F("Looking for GPS device on " GPS_PORT_NAME) );
#ifndef NMEAGPS_PARSE_PUBX_00
if (LAST_SENTENCE_IN_INTERVAL == (NMEAGPS::nmea_msg_t) ubloxNMEA::PUBX_00) {
DEBUG_PORT.println( F("ERROR! LAST_SENTENCE_IN_INTERVAL PUBX_00 not enabled!\n"
" Either change LAST_SENTENCE or enable PUBX_00") );
for(;;);
}
#endif
#ifndef NMEAGPS_PARSE_PUBX_04
if (LAST_SENTENCE_IN_INTERVAL == (NMEAGPS::nmea_msg_t) ubloxNMEA::PUBX_04) {
DEBUG_PORT.println( F("ERROR! LAST_SENTENCE_IN_INTERVAL PUBX_04 not enabled!\n"
" Either change LAST_SENTENCE or enable PUBX_04") );
for(;;);
}
#endif
trace_header( DEBUG_PORT );
DEBUG_PORT.flush();
gpsPort.begin(9600);
// Ask for the special PUBX sentences
poll();
}
//--------------------------
void loop()
{
GPSloop();
}

View File

@@ -0,0 +1,137 @@
#include <NMEAGPS.h>
//======================================================================
// Program: SyncTime.ino
//
// Description: This program shows how to update the sub-second
// part of a clock's seconds. You can adjust the clock update
// interval to be as small as 9ms. It will calculate the
// correct ms offset from each GPS second.
//
// Prerequisites:
// 1) NMEA.ino works with your device
// 2) GPS_FIX_TIME is enabled in GPSfix_cfg.h
// 3) NMEAGPS_PARSE_RMC is enabled in NMEAGPS_cfg.h. You could use
// any sentence that contains a time field. Be sure to change the
// "if" statement in GPSloop from RMC to your selected sentence.
//
// 'Serial' is for debug output to the Serial Monitor window.
//
// License:
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// NeoGPS is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//
//======================================================================
#include <GPSport.h>
static NMEAGPS gps;
static gps_fix fix;
#if !defined(GPS_FIX_TIME)
#error You must define GPS_FIX_TIME in GPSfix_cfg.h!
#endif
#if !defined(NMEAGPS_PARSE_RMC) & \
!defined(NMEAGPS_PARSE_GLL) & \
!defined(NMEAGPS_PARSE_GGA) & \
!defined(NMEAGPS_PARSE_GST)
#error You must define NMEAGPS_PARSE_RMC, GLL, GGA or GST in NMEAGPS_cfg.h!
#endif
#if !defined(NMEAGPS_TIMESTAMP_FROM_INTERVAL) & \
!defined(NMEAGPS_TIMESTAMP_FROM_PPS)
#error You must define NMEAGPS_TIMESTAMP_FROM_INTERVAL or PPS in NMEAGPS_cfg.h!
#endif
#if defined(NMEAGPS_TIMESTAMP_FROM_PPS)
#warning You must modify this sketch to call gps.UTCsecondStart at the PPS rising edge (see NMEAGPS.h comments).
#endif
#ifdef NMEAGPS_INTERRUPT_PROCESSING
#error You must *NOT* define NMEAGPS_INTERRUPT_PROCESSING in NMEAGPS_cfg.h!
#endif
//----------------------------------------------------------------
static const uint32_t CLOCK_INTERVAL_MS = 100UL;
static uint32_t lastShowTime = CLOCK_INTERVAL_MS+1; // First time never matches
//----------------------------------------------------------------
static void showTime( uint16_t subs, uint16_t factor = 100 /* hundredths */ )
{
uint8_t showSeconds = fix.dateTime.seconds;
// Step by seconds until we're in the current UTC second
while (subs >= 1000UL) {
subs -= 1000UL;
if (showSeconds < 59)
showSeconds++;
else
showSeconds = 0;
//DEBUG_PORT.print( '+' );
}
DEBUG_PORT.print( showSeconds );
DEBUG_PORT.print( '.' );
// Leading zeroes
for (;;) {
factor /= 10;
if ((factor < 10) || (subs >= factor))
break;
DEBUG_PORT.print( '0' );
}
DEBUG_PORT.println( subs );
} // showTime
//--------------------------
void setup()
{
DEBUG_PORT.begin(9600);
while (!DEBUG_PORT)
;
DEBUG_PORT.print( F("SyncTime.INO: started\n") );
DEBUG_PORT.println( F("Looking for GPS device on " GPS_PORT_NAME) );
DEBUG_PORT.println( F("Local time seconds.milliseconds") );
DEBUG_PORT.flush();
gpsPort.begin( 9600 );
}
//--------------------------
void loop()
{
while (gps.available( gpsPort )) {
fix = gps.read();
}
if (fix.valid.time) {
uint32_t UTCms = gps.UTCms();
if (((UTCms % CLOCK_INTERVAL_MS) == 0) && (UTCms != lastShowTime)) {
showTime( UTCms, 1000 );
lastShowTime = UTCms;
}
}
}

View File

@@ -0,0 +1,198 @@
#include <NMEAGPS.h>
NMEAGPS gps;
//======================================================================
// Program: SyncTime.ino
//
// Description: This program displays all GPS fields in the default configuration
// in a tabular display. To be comparable to other libraries' tabular displays,
// you must also enable HDOP in GPSfix_cfg.h.
//
// Most NeoGPS examples display *all* configured GPS fields in a CSV format
// (e.g., NMEA.ino).
//
// Prerequisites:
// 1) NMEA.ino works with your device (correct TX/RX pins and baud rate)
// 2) GPS_FIX_HDOP is defined in GPSfix_cfg.h
//
// 'Serial' is for debug output to the Serial Monitor window.
//
// License:
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// NeoGPS is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//
//======================================================================
#include <GPSport.h>
//-----------------
// Check configuration
#ifndef GPS_FIX_HDOP
#error You must uncomment GPS_FIX_HDOP in GPSfix_cfg.h!
#endif
//-----------------
static const NeoGPS::Location_t London( 51.508131, -0.128002 );
void setup()
{
DEBUG_PORT.begin(9600);
DEBUG_PORT.println
(
F( "Testing NeoGPS library\n\n"
"Sats HDOP Latitude Longitude Date Time Alt Speed Heading -- To London -- Chars Sentences Errors\n"
" (deg) (deg) (m) Dist Dir\n" )
);
repeat( '-', 133 );
gpsPort.begin(9600);
}
//-----------------
void loop()
{
if (gps.available( gpsPort )) {
gps_fix fix = gps.read();
float bearingToLondon = fix.location.BearingToDegrees( London );
bool validDT = fix.valid.date & fix.valid.time;
print( fix.satellites , fix.valid.satellites, 3 );
print( fix.hdop/1000.0 , fix.valid.hdop , 6, 2 );
print( fix.latitude () , fix.valid.location , 10, 6 );
print( fix.longitude() , fix.valid.location , 11, 6 );
print( fix.dateTime , validDT , 20 );
print( fix.altitude () , fix.valid.altitude , 7, 2 );
print( fix.speed_kph() , fix.valid.speed , 7, 2 );
print( fix.heading () , fix.valid.heading , 7, 2 );
print( compassDir( fix.heading () ) , fix.valid.heading , 4 );
print( fix.location.DistanceKm( London ), fix.valid.location , 5 );
print( bearingToLondon , fix.valid.location , 7, 2 );
print( compassDir( bearingToLondon ) , fix.valid.location , 4 );
print( gps.statistics.chars , true, 10 );
print( gps.statistics.ok , true, 6 );
print( gps.statistics.errors, true, 6 );
DEBUG_PORT.println();
}
}
//-----------------
// Print utilities
static void repeat( char c, int8_t len )
{
for (int8_t i=0; i<len; i++)
DEBUG_PORT.write( c );
}
static void printInvalid( int8_t len )
{
DEBUG_PORT.write( ' ' );
repeat( '*', abs(len)-1 );
}
static void print( float val, bool valid, int8_t len, int8_t prec )
{
if (!valid) {
printInvalid( len );
} else {
char s[16];
dtostrf( val, len, prec, s );
DEBUG_PORT.print( s );
}
}
static void print( int32_t val, bool valid, int8_t len )
{
if (!valid) {
printInvalid( len );
} else {
char s[16];
ltoa( val, s, 10 );
repeat( ' ', len - strlen(s) );
DEBUG_PORT.print( s );
}
}
static void print( const __FlashStringHelper *str, bool valid, int8_t len )
{
if (!valid) {
printInvalid( len );
} else {
int slen = strlen_P( (const char *) str );
repeat( ' ', len-slen );
DEBUG_PORT.print( str );
}
}
static void print( const NeoGPS::time_t & dt, bool valid, int8_t len )
{
if (!valid) {
printInvalid( len );
} else {
DEBUG_PORT.write( ' ' );
Serial << dt; // this "streaming" operator outputs date and time
}
}
//------------------------------------------------------------
// This snippet is from NMEAaverage. It keeps all the
// compass direction strings in FLASH memory, saving RAM.
const char nCD [] PROGMEM = "N";
const char nneCD[] PROGMEM = "NNE";
const char neCD [] PROGMEM = "NE";
const char eneCD[] PROGMEM = "ENE";
const char eCD [] PROGMEM = "E";
const char eseCD[] PROGMEM = "ESE";
const char seCD [] PROGMEM = "SE";
const char sseCD[] PROGMEM = "SSE";
const char sCD [] PROGMEM = "S";
const char sswCD[] PROGMEM = "SSW";
const char swCD [] PROGMEM = "SW";
const char wswCD[] PROGMEM = "WSW";
const char wCD [] PROGMEM = "W";
const char wnwCD[] PROGMEM = "WNW";
const char nwCD [] PROGMEM = "NW";
const char nnwCD[] PROGMEM = "NNW";
const char * const dirStrings[] PROGMEM =
{ nCD, nneCD, neCD, eneCD, eCD, eseCD, seCD, sseCD,
sCD, sswCD, swCD, wswCD, wCD, wnwCD, nwCD, nnwCD };
const __FlashStringHelper *compassDir( uint16_t bearing ) // degrees CW from N
{
const int16_t directions = sizeof(dirStrings)/sizeof(dirStrings[0]);
const int16_t degreesPerDir = 360 / directions;
int8_t dir = (bearing + degreesPerDir/2) / degreesPerDir;
while (dir < 0)
dir += directions;
while (dir >= directions)
dir -= directions;
return (const __FlashStringHelper *) pgm_read_ptr( &dirStrings[ dir ] );
} // compassDir

View File

@@ -0,0 +1,403 @@
#include <NeoGPS_cfg.h>
#include <ublox/ubxGPS.h>
//======================================================================
// Program: ublox.ino
//
// Prerequisites:
// 1) You have a ublox GPS device
// 2) PUBX.ino works with your device
// 3) You have installed the ubxGPS.* and ubxmsg.* files.
// 4) At least one UBX message has been enabled in ubxGPS.h.
// 5) Implicit Merging is disabled in NMEAGPS_cfg.h.
//
// Description: This program parses UBX binary protocal messages from
// ublox devices. It shows how to acquire the information necessary
// to use the GPS Time-Of-Week in many UBX messages. As an offset
// from midnight Sunday morning (GPS time), you also need the current
// UTC time (this is *not* GPS time) and the current number of GPS
// leap seconds.
//
// Serial is for debug output to the Serial Monitor window.
//
// License:
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// NeoGPS is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//
//======================================================================
#include <GPSport.h>
#include <Streamers.h>
//------------------------------------------------------------
// Check that the config files are set up properly
#ifndef NMEAGPS_DERIVED_TYPES
#error You must "#define NMEAGPS_DERIVED_TYPES" in NMEAGPS_cfg.h!
#endif
#if !defined(UBLOX_PARSE_STATUS) & !defined(UBLOX_PARSE_TIMEGPS) & \
!defined(UBLOX_PARSE_TIMEUTC) & !defined(UBLOX_PARSE_POSLLH) & \
!defined(UBLOX_PARSE_VELNED) & !defined(UBLOX_PARSE_SVINFO) & \
!defined(UBLOX_PARSE_DOP)
#error No UBX binary messages enabled: no fix data available for fusing.
#endif
#if defined(UBLOX_PARSE_DOP) & \
( !defined(GPS_FIX_HDOP) & \
!defined(GPS_FIX_VDOP) & \
!defined(GPS_FIX_PDOP) )
#warning UBX DOP message is enabled, but all GPS_fix DOP members are disabled.
#endif
#ifndef NMEAGPS_RECOGNIZE_ALL
// Resetting the messages with ublox::configNMEA requires that
// all message types are recognized (i.e., the enum has all
// values).
#error You must "#define NMEAGPS_RECOGNIZE_ALL" in NMEAGPS_cfg.h!
#endif
//-----------------------------------------------------------------
// Derive a class to add the state machine for starting up:
// 1) The status must change to something other than NONE.
// 2) The GPS leap seconds must be received
// 3) The UTC time must be received
// 4) All configured messages are "requested"
// (i.e., "enabled" in the ublox device)
// Then, all configured messages are parsed and explicitly merged.
class MyGPS : public ubloxGPS
{
public:
enum
{
GETTING_STATUS,
GETTING_LEAP_SECONDS,
GETTING_UTC,
RUNNING
}
state NEOGPS_BF(8);
MyGPS( Stream *device ) : ubloxGPS( device )
{
state = GETTING_STATUS;
}
//--------------------------
void get_status()
{
static bool acquiring = false;
if (fix().status == gps_fix::STATUS_NONE) {
static uint32_t dotPrint;
bool requestNavStatus = false;
if (!acquiring) {
acquiring = true;
dotPrint = millis();
DEBUG_PORT.print( F("Acquiring...") );
requestNavStatus = true;
} else if (millis() - dotPrint > 1000UL) {
dotPrint = millis();
DEBUG_PORT << '.';
static uint8_t requestPeriod;
if ((++requestPeriod & 0x07) == 0)
requestNavStatus = true;
}
if (requestNavStatus)
// Turn on the UBX status message
enable_msg( ublox::UBX_NAV, ublox::UBX_NAV_STATUS );
} else {
if (acquiring)
DEBUG_PORT << '\n';
DEBUG_PORT << F("Acquired status: ") << (uint8_t) fix().status << '\n';
#if defined(GPS_FIX_TIME) & defined(GPS_FIX_DATE) & \
defined(UBLOX_PARSE_TIMEGPS)
if (!enable_msg( ublox::UBX_NAV, ublox::UBX_NAV_TIMEGPS ))
DEBUG_PORT.println( F("enable TIMEGPS failed!") );
state = GETTING_LEAP_SECONDS;
#else
start_running();
state = RUNNING;
#endif
}
} // get_status
//--------------------------
void get_leap_seconds()
{
#if defined(GPS_FIX_TIME) & defined(GPS_FIX_DATE) & \
defined(UBLOX_PARSE_TIMEGPS)
if (GPSTime::leap_seconds != 0) {
DEBUG_PORT << F("Acquired leap seconds: ") << GPSTime::leap_seconds << '\n';
if (!disable_msg( ublox::UBX_NAV, ublox::UBX_NAV_TIMEGPS ))
DEBUG_PORT.println( F("disable TIMEGPS failed!") );
#if defined(UBLOX_PARSE_TIMEUTC)
if (!enable_msg( ublox::UBX_NAV, ublox::UBX_NAV_TIMEUTC ))
DEBUG_PORT.println( F("enable TIMEUTC failed!") );
state = GETTING_UTC;
#else
start_running();
#endif
}
#endif
} // get_leap_seconds
//--------------------------
void get_utc()
{
#if defined(GPS_FIX_TIME) & defined(GPS_FIX_DATE) & \
defined(UBLOX_PARSE_TIMEUTC)
lock();
bool safe = is_safe();
NeoGPS::clock_t sow = GPSTime::start_of_week();
NeoGPS::time_t utc = fix().dateTime;
unlock();
if (safe && (sow != 0)) {
DEBUG_PORT << F("Acquired UTC: ") << utc << '\n';
DEBUG_PORT << F("Acquired Start-of-Week: ") << sow << '\n';
start_running();
}
#endif
} // get_utc
//--------------------------
void start_running()
{
bool enabled_msg_with_time = false;
#if (defined(GPS_FIX_LOCATION) | \
defined(GPS_FIX_LOCATION_DMS) | \
defined(GPS_FIX_ALTITUDE)) & \
defined(UBLOX_PARSE_POSLLH)
if (!enable_msg( ublox::UBX_NAV, ublox::UBX_NAV_POSLLH ))
DEBUG_PORT.println( F("enable POSLLH failed!") );
enabled_msg_with_time = true;
#endif
#if (defined(GPS_FIX_SPEED) | defined(GPS_FIX_HEADING)) & \
defined(UBLOX_PARSE_VELNED)
if (!enable_msg( ublox::UBX_NAV, ublox::UBX_NAV_VELNED ))
DEBUG_PORT.println( F("enable VELNED failed!") );
enabled_msg_with_time = true;
#endif
#if defined(UBLOX_PARSE_DOP)
if (!enable_msg( ublox::UBX_NAV, ublox::UBX_NAV_DOP ))
DEBUG_PORT.println( F("enable DOP failed!") );
else
DEBUG_PORT.println( F("enabled DOP.") );
enabled_msg_with_time = true;
#endif
#if (defined(GPS_FIX_SATELLITES) | defined(NMEAGPS_PARSE_SATELLITES)) & \
defined(UBLOX_PARSE_SVINFO)
if (!enable_msg( ublox::UBX_NAV, ublox::UBX_NAV_SVINFO ))
DEBUG_PORT.println( F("enable SVINFO failed!") );
enabled_msg_with_time = true;
#endif
#if defined(UBLOX_PARSE_TIMEUTC)
#if defined(GPS_FIX_TIME) & defined(GPS_FIX_DATE)
if (enabled_msg_with_time &&
!disable_msg( ublox::UBX_NAV, ublox::UBX_NAV_TIMEUTC ))
DEBUG_PORT.println( F("disable TIMEUTC failed!") );
#elif defined(GPS_FIX_TIME) | defined(GPS_FIX_DATE)
// If both aren't defined, we can't convert TOW to UTC,
// so ask for the separate UTC message.
if (!enable_msg( ublox::UBX_NAV, ublox::UBX_NAV_TIMEUTC ))
DEBUG_PORT.println( F("enable TIMEUTC failed!") );
#endif
#endif
state = RUNNING;
trace_header( DEBUG_PORT );
} // start_running
//--------------------------
bool running()
{
switch (state) {
case GETTING_STATUS : get_status (); break;
case GETTING_LEAP_SECONDS: get_leap_seconds(); break;
case GETTING_UTC : get_utc (); break;
}
return (state == RUNNING);
} // running
} NEOGPS_PACKED;
// Construct the GPS object and hook it to the appropriate serial device
static MyGPS gps( &gpsPort );
#ifdef NMEAGPS_INTERRUPT_PROCESSING
static void GPSisr( uint8_t c )
{
gps.handle( c );
}
#endif
//--------------------------
static void configNMEA( uint8_t rate )
{
for (uint8_t i=NMEAGPS::NMEA_FIRST_MSG; i<=NMEAGPS::NMEA_LAST_MSG; i++) {
ublox::configNMEA( gps, (NMEAGPS::nmea_msg_t) i, rate );
}
}
//--------------------------
static void disableUBX()
{
gps.disable_msg( ublox::UBX_NAV, ublox::UBX_NAV_TIMEGPS );
gps.disable_msg( ublox::UBX_NAV, ublox::UBX_NAV_TIMEUTC );
gps.disable_msg( ublox::UBX_NAV, ublox::UBX_NAV_VELNED );
gps.disable_msg( ublox::UBX_NAV, ublox::UBX_NAV_POSLLH );
gps.disable_msg( ublox::UBX_NAV, ublox::UBX_NAV_DOP );
}
//--------------------------
void setup()
{
// Start the normal trace output
DEBUG_PORT.begin(9600);
while (!DEBUG_PORT)
;
DEBUG_PORT.print( F("ublox binary protocol example started.\n") );
DEBUG_PORT << F("fix object size = ") << sizeof(gps.fix()) << '\n';
DEBUG_PORT << F("ubloxGPS object size = ") << sizeof(ubloxGPS) << '\n';
DEBUG_PORT << F("MyGPS object size = ") << sizeof(gps) << '\n';
DEBUG_PORT.println( F("Looking for GPS device on " GPS_PORT_NAME) );
DEBUG_PORT.flush();
// Start the UART for the GPS device
#ifdef NMEAGPS_INTERRUPT_PROCESSING
gpsPort.attachInterrupt( GPSisr );
#endif
gpsPort.begin(9600);
// Turn off the preconfigured NMEA standard messages
configNMEA( 0 );
// Turn off things that may be left on by a previous build
disableUBX();
#if 0
// Test a Neo M8 message -- should be rejected by Neo-6 and Neo7
ublox::cfg_nmea_v1_t test;
test.always_output_pos = false; // invalid or failed
test.output_invalid_pos = false;
test.output_invalid_time= false;
test.output_invalid_date= false;
test.use_GPS_only = false;
test.output_heading = false; // even if frozen
test.__not_used__ = false;
test.nmea_version = ublox::cfg_nmea_v1_t::NMEA_V_4_0;
test.num_sats_per_talker_id = ublox::cfg_nmea_v1_t::SV_PER_TALKERID_UNLIMITED;
test.compatibility_mode = false;
test.considering_mode = true;
test.max_line_length_82 = false;
test.__not_used_1__ = 0;
test.filter_gps = false;
test.filter_sbas = false;
test.__not_used_2__= 0;
test.filter_qzss = false;
test.filter_glonass= false;
test.filter_beidou = false;
test.__not_used_3__= 0;
test.proprietary_sat_numbering = false;
test.main_talker_id = ublox::cfg_nmea_v1_t::MAIN_TALKER_ID_GP;
test.gsv_uses_main_talker_id = true;
test.beidou_talker_id[0] = 'G';
test.beidou_talker_id[1] = 'P';
DEBUG_PORT << F("CFG_NMEA result = ") << gps.send( test );
#endif
while (!gps.running())
if (gps.available( gpsPort ))
gps.read();
}
//--------------------------
void loop()
{
if (gps.available( gpsPort ))
trace_all( DEBUG_PORT, gps, gps.read() );
// If the user types something, reset the message configuration
// back to a normal set of NMEA messages. This makes it
// convenient to switch to another example program that
// expects a typical set of messages. This also saves
// putting those config messages in every other example.
if (DEBUG_PORT.available()) {
do { DEBUG_PORT.read(); } while (DEBUG_PORT.available());
DEBUG_PORT.println( F("Stopping...") );
configNMEA( 1 );
disableUBX();
gpsPort.flush();
gpsPort.end();
DEBUG_PORT.println( F("STOPPED.") );
for (;;);
}
}

View File

@@ -0,0 +1,369 @@
#include <NMEAGPS.h>
#include <NeoTeeStream.h>
//======================================================================
// Program: NMEA.ino
//
// Description: This program sends ublox commands to enable and disable
// NMEA sentences, set the update rate to 1Hz, 5Hz or 10Hz, and set the
// baud rate to 9600 or 115200.
//
// Enter the following commands through the Serial Monitor window:
//
// '1' - send NMEA PUBX text command to enable all sentences
// '0' - send NMEA PUBX text command to disable all sentences except GLL
// 'd' - send UBX binary command to disable all sentences except GLL
// 'r1' - send UBX binary command to set update rate to 1Hz
// 'r5' - send UBX binary command to set update rate to 5Hz
// 'r0' - send UBX binary command to set update rate to 10Hz
// 'r6' - send UBX binary command to set update rate to 16Hz
// '5' - send NMEA PUBX text command to set baud rate to 115200
// '7' - send NMEA PUBX text command to set baud rate to 57600
// '3' - send NMEA PUBX text command to set baud rate to 38400
// '9' - send NMEA PUBX text command to set baud rate to 9600
// 'e' - toggle echo of all characters received from GPS device.
//
// CAUTION: If your Serial Monitor window baud rate is less than the GPS baud
// rate, turning echo ON will cause the sketch to lose some or all
// GPS data and/or fixes.
//
// NOTE: All NMEA PUBX text commands are also echoed to the debug port.
//
// Prerequisites:
// 1) Your GPS device has been correctly powered.
// Be careful when connecting 3.3V devices.
// 2) Your GPS device is correctly connected to an Arduino serial port.
// See GPSport.h for the default connections.
// 3) You know the default baud rate of your GPS device.
// If 9600 does not work, use NMEAdiagnostic.ino to
// scan for the correct baud rate.
// 4) LAST_SENTENCE_IN_INTERVAL is defined to be the following in NMEAGPS_cfg.h:
//
// #include <stdint.h>
// extern uint8_t LastSentenceInInterval; // a variable!
// #define LAST_SENTENCE_IN_INTERVAL \
// ((NMEAGPS::nmea_msg_t) LastSentenceInInterval)
//
// This is a replacement for the typical
//
// #define LAST_SENTENCE_IN_INTERVAL NMEAGPS::NMEA_GLL
//
// This allows the sketch to choose the last sentence *at run time*, not
// compile time. This is necessary because this sketch can send
// configuration commands that change which sentences are enabled at run
// time. The storage for the "externed" variable is below.
//
// 'Serial' is for debug output to the Serial Monitor window.
//
// License:
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// NeoGPS is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//
//======================================================================
#include <GPSport.h>
#include <Streamers.h>
static NMEAGPS gps;
static gps_fix fix_data;
uint8_t LastSentenceInInterval = 0xFF; // storage for the run-time selection
static char lastChar; // last command char
static bool echoing = false;
// Use NeoTee to echo the NMEA text commands to the Serial Monitor window
Stream *both[2] = { &DEBUG_PORT, &gpsPort };
NeoTeeStream tee( both, sizeof(both)/sizeof(both[0]) );
//-------------------------------------------
// U-blox UBX binary commands
const unsigned char ubxRate1Hz[] PROGMEM =
{ 0x06,0x08,0x06,0x00,0xE8,0x03,0x01,0x00,0x01,0x00 };
const unsigned char ubxRate5Hz[] PROGMEM =
{ 0x06,0x08,0x06,0x00,200,0x00,0x01,0x00,0x01,0x00 };
const unsigned char ubxRate10Hz[] PROGMEM =
{ 0x06,0x08,0x06,0x00,100,0x00,0x01,0x00,0x01,0x00 };
const unsigned char ubxRate16Hz[] PROGMEM =
{ 0x06,0x08,0x06,0x00,50,0x00,0x01,0x00,0x01,0x00 };
// Disable specific NMEA sentences
const unsigned char ubxDisableGGA[] PROGMEM =
{ 0x06,0x01,0x08,0x00,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0x01 };
const unsigned char ubxDisableGLL[] PROGMEM =
{ 0x06,0x01,0x08,0x00,0xF0,0x01,0x00,0x00,0x00,0x00,0x00,0x01 };
const unsigned char ubxDisableGSA[] PROGMEM =
{ 0x06,0x01,0x08,0x00,0xF0,0x02,0x00,0x00,0x00,0x00,0x00,0x01 };
const unsigned char ubxDisableGSV[] PROGMEM =
{ 0x06,0x01,0x08,0x00,0xF0,0x03,0x00,0x00,0x00,0x00,0x00,0x01 };
const unsigned char ubxDisableRMC[] PROGMEM =
{ 0x06,0x01,0x08,0x00,0xF0,0x04,0x00,0x00,0x00,0x00,0x00,0x01 };
const unsigned char ubxDisableVTG[] PROGMEM =
{ 0x06,0x01,0x08,0x00,0xF0,0x05,0x00,0x00,0x00,0x00,0x00,0x01 };
const unsigned char ubxDisableZDA[] PROGMEM =
{ 0x06,0x01,0x08,0x00,0xF0,0x08,0x00,0x00,0x00,0x00,0x00,0x01 };
//--------------------------
void sendUBX( const unsigned char *progmemBytes, size_t len )
{
gpsPort.write( 0xB5 ); // SYNC1
gpsPort.write( 0x62 ); // SYNC2
uint8_t a = 0, b = 0;
while (len-- > 0) {
uint8_t c = pgm_read_byte( progmemBytes++ );
a += c;
b += a;
gpsPort.write( c );
}
gpsPort.write( a ); // CHECKSUM A
gpsPort.write( b ); // CHECKSUM B
} // sendUBX
//-------------------------------------------
// U-blox NMEA text commands
const char disableRMC[] PROGMEM = "PUBX,40,RMC,0,0,0,0,0,0";
const char disableGLL[] PROGMEM = "PUBX,40,GLL,0,0,0,0,0,0";
const char disableGSV[] PROGMEM = "PUBX,40,GSV,0,0,0,0,0,0";
const char disableGSA[] PROGMEM = "PUBX,40,GSA,0,0,0,0,0,0";
const char disableGGA[] PROGMEM = "PUBX,40,GGA,0,0,0,0,0,0";
const char disableVTG[] PROGMEM = "PUBX,40,VTG,0,0,0,0,0,0";
const char disableZDA[] PROGMEM = "PUBX,40,ZDA,0,0,0,0,0,0";
const char enableRMC[] PROGMEM = "PUBX,40,RMC,0,1,0,0,0,0";
const char enableGLL[] PROGMEM = "PUBX,40,GLL,0,1,0,0,0,0";
const char enableGSV[] PROGMEM = "PUBX,40,GSV,0,1,0,0,0,0";
const char enableGSA[] PROGMEM = "PUBX,40,GSA,0,1,0,0,0,0";
const char enableGGA[] PROGMEM = "PUBX,40,GGA,0,1,0,0,0,0";
const char enableVTG[] PROGMEM = "PUBX,40,VTG,0,1,0,0,0,0";
const char enableZDA[] PROGMEM = "PUBX,40,ZDA,0,1,0,0,0,0";
const char baud9600 [] PROGMEM = "PUBX,41,1,3,3,9600,0";
const char baud38400 [] PROGMEM = "PUBX,41,1,3,3,38400,0";
const char baud57600 [] PROGMEM = "PUBX,41,1,3,3,57600,0";
const char baud115200[] PROGMEM = "PUBX,41,1,3,3,115200,0";
//--------------------------
const uint32_t COMMAND_DELAY = 250;
void changeBaud( const char *textCommand, unsigned long baud )
{
gps.send_P( &tee, (const __FlashStringHelper *) disableRMC );
delay( COMMAND_DELAY );
// gps.send_P( &tee, (const __FlashStringHelper *) disableGLL );
// delay( COMMAND_DELAY );
gps.send_P( &tee, (const __FlashStringHelper *) disableGSV );
delay( COMMAND_DELAY );
gps.send_P( &tee, (const __FlashStringHelper *) disableGSA );
delay( COMMAND_DELAY );
gps.send_P( &tee, (const __FlashStringHelper *) disableGGA );
delay( COMMAND_DELAY );
gps.send_P( &tee, (const __FlashStringHelper *) disableVTG );
delay( COMMAND_DELAY );
gps.send_P( &tee, (const __FlashStringHelper *) disableZDA );
delay( 500 );
gps.send_P( &tee, (const __FlashStringHelper *) textCommand );
gpsPort.flush();
gpsPort.end();
DEBUG_PORT.print( F("All sentences disabled for baud rate ") );
DEBUG_PORT.print( baud );
DEBUG_PORT.println( F(" change. Enter '1' to reenable sentences.") );
delay( 500 );
gpsPort.begin( baud );
} // changeBaud
//------------------------------------
static void doSomeWork()
{
// Print all the things!
trace_all( DEBUG_PORT, gps, fix_data );
} // doSomeWork
//--------------------------
void setup()
{
DEBUG_PORT.begin(9600);
while (!DEBUG_PORT)
;
DEBUG_PORT.print( F("ubloxRate.INO: started\n") );
DEBUG_PORT.println( F("Looking for GPS device on " GPS_PORT_NAME) );
#ifdef NMEAGPS_INTERRUPT_PROCESSING
#error You must *NOT* define NMEAGPS_INTERRUPT_PROCESSING in NMEAGPS_cfg.h!
#endif
#if !defined( NMEAGPS_PARSE_GGA ) & !defined( NMEAGPS_PARSE_GLL ) & \
!defined( NMEAGPS_PARSE_GSA ) & !defined( NMEAGPS_PARSE_GSV ) & \
!defined( NMEAGPS_PARSE_RMC ) & !defined( NMEAGPS_PARSE_VTG ) & \
!defined( NMEAGPS_PARSE_ZDA ) & !defined( NMEAGPS_PARSE_GST )
DEBUG_PORT.println( F("\nWARNING: No NMEA sentences are enabled: no fix data will be displayed.") );
#else
if (gps.merging == NMEAGPS::NO_MERGING) {
DEBUG_PORT.print ( F("\nWARNING: displaying data from ") );
DEBUG_PORT.print ( gps.string_for( LAST_SENTENCE_IN_INTERVAL ) );
DEBUG_PORT.print ( F(" sentences ONLY, and only if ") );
DEBUG_PORT.print ( gps.string_for( LAST_SENTENCE_IN_INTERVAL ) );
DEBUG_PORT.println( F(" is enabled.\n"
" Other sentences may be parsed, but their data will not be displayed.") );
}
#endif
if (LastSentenceInInterval != LAST_SENTENCE_IN_INTERVAL) {
DEBUG_PORT.println(
F("LAST_SENTENCE_IN_INTERVAL is not properly defined in NMEAGPS_cfg.h!\n"
" See Prerequisite 4 above") );
}
LastSentenceInInterval = NMEAGPS::NMEA_GLL;
trace_header( DEBUG_PORT );
DEBUG_PORT.flush();
gpsPort.begin( 9600 );
}
void loop()
{
// Check for commands
if (DEBUG_PORT.available()) {
char c = DEBUG_PORT.read();
switch (c) {
case '0':
if (lastChar == 'r') {
sendUBX( ubxRate10Hz, sizeof(ubxRate10Hz) );
} else {
gps.send_P( &tee, (const __FlashStringHelper *) disableRMC );
delay( COMMAND_DELAY );
gps.send_P( &tee, (const __FlashStringHelper *) enableGLL );
delay( COMMAND_DELAY );
gps.send_P( &tee, (const __FlashStringHelper *) disableGSV );
delay( COMMAND_DELAY );
gps.send_P( &tee, (const __FlashStringHelper *) disableGSA );
delay( COMMAND_DELAY );
gps.send_P( &tee, (const __FlashStringHelper *) disableGGA );
delay( COMMAND_DELAY );
gps.send_P( &tee, (const __FlashStringHelper *) disableVTG );
delay( COMMAND_DELAY );
gps.send_P( &tee, (const __FlashStringHelper *) disableZDA );
LastSentenceInInterval = NMEAGPS::NMEA_GLL;
}
break;
case '1':
if (lastChar == 'r') {
sendUBX( ubxRate1Hz, sizeof(ubxRate1Hz) );
} else {
gps.send_P( &tee, (const __FlashStringHelper *) enableRMC );
delay( COMMAND_DELAY );
gps.send_P( &tee, (const __FlashStringHelper *) enableGLL );
delay( COMMAND_DELAY );
gps.send_P( &tee, (const __FlashStringHelper *) enableGSV );
delay( COMMAND_DELAY );
gps.send_P( &tee, (const __FlashStringHelper *) enableGSA );
delay( COMMAND_DELAY );
gps.send_P( &tee, (const __FlashStringHelper *) enableGGA );
delay( COMMAND_DELAY );
gps.send_P( &tee, (const __FlashStringHelper *) enableVTG );
delay( COMMAND_DELAY );
gps.send_P( &tee, (const __FlashStringHelper *) enableZDA );
LastSentenceInInterval = NMEAGPS::NMEA_ZDA;
}
break;
case '3':
changeBaud( baud38400, 38400UL );
break;
case '5':
if (lastChar == 'r') {
sendUBX( ubxRate5Hz, sizeof(ubxRate5Hz) );
} else
changeBaud( baud115200, 115200UL );
break;
case '6':
if (lastChar == 'r') {
sendUBX( ubxRate16Hz, sizeof(ubxRate16Hz) );
}
break;
case '7':
changeBaud( baud57600, 57600UL );
break;
case '9':
changeBaud( baud9600, 9600UL );
break;
case 'd':
sendUBX( ubxDisableRMC, sizeof(ubxDisableRMC) );
delay( COMMAND_DELAY );
//sendUBX( ubxDisableGLL, sizeof(ubxDisableGLL) );
sendUBX( ubxDisableGSV, sizeof(ubxDisableGSV) );
delay( COMMAND_DELAY );
sendUBX( ubxDisableGSA, sizeof(ubxDisableGSA) );
delay( COMMAND_DELAY );
sendUBX( ubxDisableGGA, sizeof(ubxDisableGGA) );
delay( COMMAND_DELAY );
sendUBX( ubxDisableVTG, sizeof(ubxDisableVTG) );
delay( COMMAND_DELAY );
sendUBX( ubxDisableZDA, sizeof(ubxDisableZDA) );
LastSentenceInInterval = NMEAGPS::NMEA_GLL;
break;
case 'e':
echoing = !echoing;
break;
default: break;
}
lastChar = c;
}
// Check for GPS data
if (echoing) {
// Use advanced character-oriented methods to echo received characters to
// the Serial Monitor window.
if (gpsPort.available()) {
char c = gpsPort.read();
DEBUG_PORT.write( c );
gps.handle( c );
if (gps.available()) {
fix_data = gps.read();
doSomeWork();
}
}
} else {
// Use the normal fix-oriented methods to display fixes
if (gps.available( gpsPort )) {
fix_data = gps.read();
doSomeWork();
}
}
}

View File

@@ -0,0 +1,35 @@
#ifndef GPS_FIX_CFG
#define GPS_FIX_CFG
/**
* Enable/disable the storage for the members of a fix.
*
* Disabling a member prevents it from being parsed from a received message.
* The disabled member cannot be accessed or stored, and its validity flag
* would not be available. It will not be declared, and code that uses that
* member will not compile.
*
* DATE and TIME are somewhat coupled in that they share a single `time_t`,
* but they have separate validity flags.
*
* See also note regarding the DOP members, below.
*
*/
#define GPS_FIX_DATE
#define GPS_FIX_TIME
#define GPS_FIX_LOCATION
//#define GPS_FIX_LOCATION_DMS
//#define GPS_FIX_ALTITUDE
//#define GPS_FIX_SPEED
//#define GPS_FIX_HEADING
//#define GPS_FIX_SATELLITES
//#define GPS_FIX_HDOP
//#define GPS_FIX_VDOP
//#define GPS_FIX_PDOP
//#define GPS_FIX_LAT_ERR
//#define GPS_FIX_LON_ERR
//#define GPS_FIX_ALT_ERR
//#define GPS_FIX_GEOID_HEIGHT
#endif

View File

@@ -0,0 +1,286 @@
#ifndef NMEAGPS_CFG_H
#define NMEAGPS_CFG_H
//------------------------------------------------------
// Enable/disable the parsing of specific sentences.
//
// Configuring out a sentence prevents it from being recognized; it
// will be completely ignored. (See also NMEAGPS_RECOGNIZE_ALL, below)
//
// FYI: Only RMC and ZDA contain date information. Other
// sentences contain time information. Both date and time are
// required if you will be doing time_t-to-clock_t operations.
//#define NMEAGPS_PARSE_GGA
//#define NMEAGPS_PARSE_GLL
//#define NMEAGPS_PARSE_GSA
//#define NMEAGPS_PARSE_GSV
//#define NMEAGPS_PARSE_GST
#define NMEAGPS_PARSE_RMC
//#define NMEAGPS_PARSE_VTG
//#define NMEAGPS_PARSE_ZDA
//------------------------------------------------------
// Select which sentence is sent *last* by your GPS device
// in each update interval. This can be used by your sketch
// to determine when the GPS quiet time begins, and thus
// when you can perform "some" time-consuming operations.
#define LAST_SENTENCE_IN_INTERVAL NMEAGPS::NMEA_RMC
// If the NMEA_LAST_SENTENCE_IN_INTERVAL is not chosen
// correctly, GPS data may be lost because the sketch
// takes too long elsewhere when this sentence is received.
// Also, fix members may contain information from different
// time intervals (i.e., they are not coherent).
//
// If you don't know which sentence is the last one,
// use NMEAorder.ino to list them. You do not have to select
// the last sentence the device sends if you have disabled
// it. Just select the last sentence that you have *enabled*.
//------------------------------------------------------
// Enable/Disable coherency:
//
// If you need each fix to contain information that is only
// from the current update interval, you should uncomment
// this define. At the beginning of the next interval,
// the accumulating fix will start out empty. When
// the LAST_SENTENCE_IN_INTERVAL arrives, the valid
// fields will be coherent.
//#define NMEAGPS_COHERENT
// With IMPLICIT merging, fix() will be emptied when the
// next sentence begins.
//
// With EXPLICIT or NO merging, the fix() was already
// being initialized.
//
// If you use the fix-oriented methods available() and read(),
// they will empty the current fix for you automatically.
//
// If you use the character-oriented method decode(), you should
// empty the accumulating fix by testing and clearing the
// 'intervalComplete' flag in the same way that available() does.
//------------------------------------------------------
// Choose how multiple sentences are merged:
// 1) No merging
// Each sentence fills out its own fix; there could be
// multiple sentences per interval.
// 2) EXPLICIT_MERGING
// All sentences in an interval are *safely* merged into one fix.
// NMEAGPS_FIX_MAX must be >= 1.
// An interval is defined by NMEA_LAST_SENTENCE_IN_INTERVAL.
// 3) IMPLICIT_MERGING
// All sentences in an interval are merged into one fix, with
// possible data loss. If a received sentence is rejected for
// any reason (e.g., a checksum error), all the values are suspect.
// The fix will be cleared; no members will be valid until new
// sentences are received and accepted. This uses less RAM.
// An interval is defined by NMEA_LAST_SENTENCE_IN_INTERVAL.
// Uncomment zero or one:
#define NMEAGPS_EXPLICIT_MERGING
//#define NMEAGPS_IMPLICIT_MERGING
#ifdef NMEAGPS_IMPLICIT_MERGING
#define NMEAGPS_MERGING NMEAGPS::IMPLICIT_MERGING
// When accumulating, nothing is done to the fix at the
// beginning of every sentence...
#ifdef NMEAGPS_COHERENT
// ...unless COHERENT is enabled and a new interval is starting
#define NMEAGPS_INIT_FIX(m) \
if (intervalComplete()) { intervalComplete( false ); m.valid.init(); }
#else
#define NMEAGPS_INIT_FIX(m)
#endif
// ...but we invalidate one part when it starts to get parsed. It *may* get
// validated when the parsing is finished.
#define NMEAGPS_INVALIDATE(m) m_fix.valid.m = false
#else
#ifdef NMEAGPS_EXPLICIT_MERGING
#define NMEAGPS_MERGING NMEAGPS::EXPLICIT_MERGING
#else
#define NMEAGPS_MERGING NMEAGPS::NO_MERGING
#define NMEAGPS_NO_MERGING
#endif
// When NOT accumulating, invalidate the entire fix at the
// beginning of every sentence
#define NMEAGPS_INIT_FIX(m) m.valid.init()
// ...so the individual parts do not need to be invalidated as they are parsed
#define NMEAGPS_INVALIDATE(m)
#endif
#if ( defined(NMEAGPS_NO_MERGING) + \
defined(NMEAGPS_IMPLICIT_MERGING) + \
defined(NMEAGPS_EXPLICIT_MERGING) ) > 1
#error Only one MERGING technique should be enabled in NMEAGPS_cfg.h!
#endif
//------------------------------------------------------
// Define the fix buffer size. The NMEAGPS object will hold on to
// this many fixes before an overrun occurs. This can be zero,
// but you have to be more careful about using gps.fix() structure,
// because it will be modified as characters are received.
#define NMEAGPS_FIX_MAX 1
#if defined(NMEAGPS_EXPLICIT_MERGING) && (NMEAGPS_FIX_MAX == 0)
#error You must define FIX_MAX >= 1 to allow EXPLICIT merging in NMEAGPS_cfg.h
#endif
//------------------------------------------------------
// Enable/Disable interrupt-style processing of GPS characters
// If you are using one of the NeoXXSerial libraries,
// to attachInterrupt, this must be defined.
// Otherwise, it must be commented out.
//#define NMEAGPS_INTERRUPT_PROCESSING
#ifdef NMEAGPS_INTERRUPT_PROCESSING
#define NMEAGPS_PROCESSING_STYLE NMEAGPS::PS_INTERRUPT
#else
#define NMEAGPS_PROCESSING_STYLE NMEAGPS::PS_POLLING
#endif
//------------------------------------------------------
// Enable/disable the talker ID, manufacturer ID and proprietary message processing.
//
// First, some background information. There are two kinds of NMEA sentences:
//
// 1. Standard NMEA sentences begin with "$ttccc", where
// "tt" is the talker ID, and
// "ccc" is the variable-length sentence type (i.e., command).
//
// For example, "$GPGLL,..." is a GLL sentence (Geographic Lat/Long)
// transmitted by talker "GP". This is the most common talker ID. Some
// devices may report "$GNGLL,..." when a mix of GPS and non-GPS
// satellites have been used to determine the GLL data.
//
// 2. Proprietary NMEA sentences (i.e., those unique to a particular
// manufacturer) begin with "$Pmmmccc", where
// "P" is the NMEA-defined prefix indicator for proprietary messages,
// "mmm" is the 3-character manufacturer ID, and
// "ccc" is the variable-length sentence type (it can be empty).
//
// No validation of manufacturer ID and talker ID is performed in this
// base class. For example, although "GP" is a common talker ID, it is not
// guaranteed to be transmitted by your particular device, and it IS NOT
// REQUIRED. If you need validation of these IDs, or you need to use the
// extra information provided by some devices, you have two independent
// options:
//
// 1. Enable SAVING the ID: When /decode/ returns DECODE_COMPLETED, the
// /talker_id/ and/or /mfr_id/ members will contain ID bytes. The entire
// sentence will be parsed, perhaps modifying members of /fix/. You should
// enable one or both IDs if you want the information in all sentences *and*
// you also want to know the ID bytes. This adds two bytes of RAM for the
// talker ID, and 3 bytes of RAM for the manufacturer ID.
//
// 2. Enable PARSING the ID: The virtual /parse_talker_id/ and
// /parse_mfr_id/ will receive each ID character as it is parsed. If it
// is not a valid ID, return /false/ to abort processing the rest of the
// sentence. No CPU time will be wasted on the invalid sentence, and no
// /fix/ members will be modified. You should enable this if you want to
// ignore some IDs. You must override /parse_talker_id/ and/or
// /parse_mfr_id/ in a derived class.
//
//#define NMEAGPS_SAVE_TALKER_ID
//#define NMEAGPS_PARSE_TALKER_ID
//#define NMEAGPS_PARSE_PROPRIETARY
#ifdef NMEAGPS_PARSE_PROPRIETARY
//#define NMEAGPS_SAVE_MFR_ID
#define NMEAGPS_PARSE_MFR_ID
#endif
//------------------------------------------------------
// Enable/disable tracking the current satellite array and,
// optionally, all the info for each satellite.
//
//#define NMEAGPS_PARSE_SATELLITES
//#define NMEAGPS_PARSE_SATELLITE_INFO
#ifdef NMEAGPS_PARSE_SATELLITES
#define NMEAGPS_MAX_SATELLITES (20)
#ifndef GPS_FIX_SATELLITES
#error GPS_FIX_SATELLITES must be defined in GPSfix.h!
#endif
#endif
#if defined(NMEAGPS_PARSE_SATELLITE_INFO) & \
!defined(NMEAGPS_PARSE_SATELLITES)
#error NMEAGPS_PARSE_SATELLITES must be defined!
#endif
//------------------------------------------------------
// Enable/disable gathering interface statistics:
// CRC errors and number of sentences received
#define NMEAGPS_STATS
//------------------------------------------------------
// Configuration item for allowing derived types of NMEAGPS.
// If you derive classes from NMEAGPS, you *must* define NMEAGPS_DERIVED_TYPES.
// If not defined, virtuals are not used, with a slight size (2 bytes) and
// execution time savings.
//#define NMEAGPS_DERIVED_TYPES
#ifdef NMEAGPS_DERIVED_TYPES
#define NMEAGPS_VIRTUAL virtual
#else
#define NMEAGPS_VIRTUAL
#endif
//-----------------------------------
// See if DERIVED_TYPES is required
#if (defined(NMEAGPS_PARSE_TALKER_ID) | defined(NMEAGPS_PARSE_MFR_ID)) & \
!defined(NMEAGPS_DERIVED_TYPES)
#error You must define NMEAGPS_DERIVED_TYPES in NMEAGPS.h in order to parse Talker and/or Mfr IDs!
#endif
//------------------------------------------------------
// Some devices may omit trailing commas at the end of some
// sentences. This may prevent the last field from being
// parsed correctly, because the parser for some types keep
// the value in an intermediate state until the complete
// field is received (e.g., parseDDDMM, parseFloat and
// parseZDA).
//
// Enabling this will inject a simulated comma when the end
// of a sentence is received and the last field parser
// indicated that it still needs one.
//#define NMEAGPS_COMMA_NEEDED
//------------------------------------------------------
// Some applications may want to recognize a sentence type
// without actually parsing any of the fields. Uncommenting
// this define will allow the nmeaMessage member to be set
// when *any* standard message is seen, even though that
// message is not enabled by a NMEAGPS_PARSE_xxx define above.
// No valid flags will be true for those sentences.
#define NMEAGPS_RECOGNIZE_ALL
//------------------------------------------------------
// Sometimes, a little extra space is needed to parse an intermediate form.
// This config items enables extra space.
//#define NMEAGPS_PARSING_SCRATCHPAD
#endif

View File

@@ -0,0 +1,85 @@
#ifndef NEOGPS_CFG
#define NEOGPS_CFG
/**
* Enable/disable packed data structures.
*
* Enabling packed data structures will use two less-portable language
* features of GCC to reduce RAM requirements. Although it was expected to slightly increase execution time and code size, the reverse is true on 8-bit AVRs: the code is smaller and faster with packing enabled.
*
* Disabling packed data structures will be very portable to other
* platforms. NeoGPS configurations will use slightly more RAM, and on
* 8-bit AVRs, the speed is slightly slower, and the code is slightly
* larger. There may be no choice but to disable packing on processors
* that do not support packed structures.
*
* There may also be compiler-specific switches that affect packing and the
* code which accesses packed members. YMMV.
**/
#ifdef __AVR__
#define NEOGPS_PACKED_DATA
#endif
//------------------------------------------------------------------------
// Based on the above define, choose which set of packing macros should
// be used in the rest of the NeoGPS package. Do not change these defines.
#ifdef NEOGPS_PACKED_DATA
// This is for specifying the number of bits to be used for a
// member of a struct. Booleans are typically one bit.
#define NEOGPS_BF(b) :b
// This is for requesting the compiler to pack the struct or class members
// "as closely as possible". This is a compiler-dependent interpretation.
#define NEOGPS_PACKED __attribute__((packed))
#else
// Let the compiler do whatever it wants.
#define NEOGPS_PACKED
#define NEOGPS_BF(b)
#endif
/*
* Accommodate C++ compiler and IDE changes.
*
* Declaring constants as class data instead of instance data helps avoid
* collisions with #define names, and allows the compiler to perform more
* checks on their usage.
*
* Until C++ 10 and IDE 1.6.8, initialized class data constants
* were declared like this:
*
* static const <valued types> = <constant-value>;
*
* Now, non-simple types (e.g., float) must be declared as
*
* static constexpr <nonsimple-types> = <expression-treated-as-const>;
*
* The good news is that this allows the compiler to optimize out an
* expression that is "promised" to be "evaluatable" as a constant.
* The bad news is that it introduces a new language keyword, and the old
* code raises an error.
*
* TODO: Evaluate the requirement for the "static" keyword.
* TODO: Evaluate using a C++ version preprocessor symbol for the #if.
*
* The CONST_CLASS_DATA define will expand to the appropriate keywords.
*
*/
#if ARDUINO < 10606
#define CONST_CLASS_DATA static const
#else
#define CONST_CLASS_DATA static constexpr
#endif
#endif

View File

@@ -0,0 +1,35 @@
#ifndef GPS_FIX_CFG
#define GPS_FIX_CFG
/**
* Enable/disable the storage for the members of a fix.
*
* Disabling a member prevents it from being parsed from a received message.
* The disabled member cannot be accessed or stored, and its validity flag
* would not be available. It will not be declared, and code that uses that
* member will not compile.
*
* DATE and TIME are somewhat coupled in that they share a single `time_t`,
* but they have separate validity flags.
*
* See also note regarding the DOP members, below.
*
*/
#define GPS_FIX_DATE
#define GPS_FIX_TIME
#define GPS_FIX_LOCATION
#define GPS_FIX_LOCATION_DMS
#define GPS_FIX_ALTITUDE
#define GPS_FIX_SPEED
#define GPS_FIX_HEADING
#define GPS_FIX_SATELLITES
#define GPS_FIX_HDOP
#define GPS_FIX_VDOP
#define GPS_FIX_PDOP
#define GPS_FIX_LAT_ERR
#define GPS_FIX_LON_ERR
#define GPS_FIX_ALT_ERR
#define GPS_FIX_GEOID_HEIGHT
#endif

View File

@@ -0,0 +1,286 @@
#ifndef NMEAGPS_CFG_H
#define NMEAGPS_CFG_H
//------------------------------------------------------
// Enable/disable the parsing of specific sentences.
//
// Configuring out a sentence prevents it from being recognized; it
// will be completely ignored. (See also NMEAGPS_RECOGNIZE_ALL, below)
//
// FYI: Only RMC and ZDA contain date information. Other
// sentences contain time information. Both date and time are
// required if you will be doing time_t-to-clock_t operations.
#define NMEAGPS_PARSE_GGA
#define NMEAGPS_PARSE_GLL
#define NMEAGPS_PARSE_GSA
#define NMEAGPS_PARSE_GSV
#define NMEAGPS_PARSE_GST
#define NMEAGPS_PARSE_RMC
#define NMEAGPS_PARSE_VTG
#define NMEAGPS_PARSE_ZDA
//------------------------------------------------------
// Select which sentence is sent *last* by your GPS device
// in each update interval. This can be used by your sketch
// to determine when the GPS quiet time begins, and thus
// when you can perform "some" time-consuming operations.
#define LAST_SENTENCE_IN_INTERVAL NMEAGPS::NMEA_RMC
// If the NMEA_LAST_SENTENCE_IN_INTERVAL is not chosen
// correctly, GPS data may be lost because the sketch
// takes too long elsewhere when this sentence is received.
// Also, fix members may contain information from different
// time intervals (i.e., they are not coherent).
//
// If you don't know which sentence is the last one,
// use NMEAorder.ino to list them. You do not have to select
// the last sentence the device sends if you have disabled
// it. Just select the last sentence that you have *enabled*.
//------------------------------------------------------
// Enable/Disable coherency:
//
// If you need each fix to contain information that is only
// from the current update interval, you should uncomment
// this define. At the beginning of the next interval,
// the accumulating fix will start out empty. When
// the LAST_SENTENCE_IN_INTERVAL arrives, the valid
// fields will be coherent.
//#define NMEAGPS_COHERENT
// With IMPLICIT merging, fix() will be emptied when the
// next sentence begins.
//
// With EXPLICIT or NO merging, the fix() was already
// being initialized.
//
// If you use the fix-oriented methods available() and read(),
// they will empty the current fix for you automatically.
//
// If you use the character-oriented method decode(), you should
// empty the accumulating fix by testing and clearing the
// 'intervalComplete' flag in the same way that available() does.
//------------------------------------------------------
// Choose how multiple sentences are merged:
// 1) No merging
// Each sentence fills out its own fix; there could be
// multiple sentences per interval.
// 2) EXPLICIT_MERGING
// All sentences in an interval are *safely* merged into one fix.
// NMEAGPS_FIX_MAX must be >= 1.
// An interval is defined by NMEA_LAST_SENTENCE_IN_INTERVAL.
// 3) IMPLICIT_MERGING
// All sentences in an interval are merged into one fix, with
// possible data loss. If a received sentence is rejected for
// any reason (e.g., a checksum error), all the values are suspect.
// The fix will be cleared; no members will be valid until new
// sentences are received and accepted. This uses less RAM.
// An interval is defined by NMEA_LAST_SENTENCE_IN_INTERVAL.
// Uncomment zero or one:
#define NMEAGPS_EXPLICIT_MERGING
//#define NMEAGPS_IMPLICIT_MERGING
#ifdef NMEAGPS_IMPLICIT_MERGING
#define NMEAGPS_MERGING NMEAGPS::IMPLICIT_MERGING
// When accumulating, nothing is done to the fix at the
// beginning of every sentence...
#ifdef NMEAGPS_COHERENT
// ...unless COHERENT is enabled and a new interval is starting
#define NMEAGPS_INIT_FIX(m) \
if (intervalComplete()) { intervalComplete( false ); m.valid.init(); }
#else
#define NMEAGPS_INIT_FIX(m)
#endif
// ...but we invalidate one part when it starts to get parsed. It *may* get
// validated when the parsing is finished.
#define NMEAGPS_INVALIDATE(m) m_fix.valid.m = false
#else
#ifdef NMEAGPS_EXPLICIT_MERGING
#define NMEAGPS_MERGING NMEAGPS::EXPLICIT_MERGING
#else
#define NMEAGPS_MERGING NMEAGPS::NO_MERGING
#define NMEAGPS_NO_MERGING
#endif
// When NOT accumulating, invalidate the entire fix at the
// beginning of every sentence
#define NMEAGPS_INIT_FIX(m) m.valid.init()
// ...so the individual parts do not need to be invalidated as they are parsed
#define NMEAGPS_INVALIDATE(m)
#endif
#if ( defined(NMEAGPS_NO_MERGING) + \
defined(NMEAGPS_IMPLICIT_MERGING) + \
defined(NMEAGPS_EXPLICIT_MERGING) ) > 1
#error Only one MERGING technique should be enabled in NMEAGPS_cfg.h!
#endif
//------------------------------------------------------
// Define the fix buffer size. The NMEAGPS object will hold on to
// this many fixes before an overrun occurs. This can be zero,
// but you have to be more careful about using gps.fix() structure,
// because it will be modified as characters are received.
#define NMEAGPS_FIX_MAX 1
#if defined(NMEAGPS_EXPLICIT_MERGING) && (NMEAGPS_FIX_MAX == 0)
#error You must define FIX_MAX >= 1 to allow EXPLICIT merging in NMEAGPS_cfg.h
#endif
//------------------------------------------------------
// Enable/Disable interrupt-style processing of GPS characters
// If you are using one of the NeoXXSerial libraries,
// to attachInterrupt, this must be defined.
// Otherwise, it must be commented out.
//#define NMEAGPS_INTERRUPT_PROCESSING
#ifdef NMEAGPS_INTERRUPT_PROCESSING
#define NMEAGPS_PROCESSING_STYLE NMEAGPS::PS_INTERRUPT
#else
#define NMEAGPS_PROCESSING_STYLE NMEAGPS::PS_POLLING
#endif
//------------------------------------------------------
// Enable/disable the talker ID, manufacturer ID and proprietary message processing.
//
// First, some background information. There are two kinds of NMEA sentences:
//
// 1. Standard NMEA sentences begin with "$ttccc", where
// "tt" is the talker ID, and
// "ccc" is the variable-length sentence type (i.e., command).
//
// For example, "$GPGLL,..." is a GLL sentence (Geographic Lat/Long)
// transmitted by talker "GP". This is the most common talker ID. Some
// devices may report "$GNGLL,..." when a mix of GPS and non-GPS
// satellites have been used to determine the GLL data.
//
// 2. Proprietary NMEA sentences (i.e., those unique to a particular
// manufacturer) begin with "$Pmmmccc", where
// "P" is the NMEA-defined prefix indicator for proprietary messages,
// "mmm" is the 3-character manufacturer ID, and
// "ccc" is the variable-length sentence type (it can be empty).
//
// No validation of manufacturer ID and talker ID is performed in this
// base class. For example, although "GP" is a common talker ID, it is not
// guaranteed to be transmitted by your particular device, and it IS NOT
// REQUIRED. If you need validation of these IDs, or you need to use the
// extra information provided by some devices, you have two independent
// options:
//
// 1. Enable SAVING the ID: When /decode/ returns DECODE_COMPLETED, the
// /talker_id/ and/or /mfr_id/ members will contain ID bytes. The entire
// sentence will be parsed, perhaps modifying members of /fix/. You should
// enable one or both IDs if you want the information in all sentences *and*
// you also want to know the ID bytes. This adds two bytes of RAM for the
// talker ID, and 3 bytes of RAM for the manufacturer ID.
//
// 2. Enable PARSING the ID: The virtual /parse_talker_id/ and
// /parse_mfr_id/ will receive each ID character as it is parsed. If it
// is not a valid ID, return /false/ to abort processing the rest of the
// sentence. No CPU time will be wasted on the invalid sentence, and no
// /fix/ members will be modified. You should enable this if you want to
// ignore some IDs. You must override /parse_talker_id/ and/or
// /parse_mfr_id/ in a derived class.
//
//#define NMEAGPS_SAVE_TALKER_ID
//#define NMEAGPS_PARSE_TALKER_ID
//#define NMEAGPS_PARSE_PROPRIETARY
#ifdef NMEAGPS_PARSE_PROPRIETARY
//#define NMEAGPS_SAVE_MFR_ID
#define NMEAGPS_PARSE_MFR_ID
#endif
//------------------------------------------------------
// Enable/disable tracking the current satellite array and,
// optionally, all the info for each satellite.
//
#define NMEAGPS_PARSE_SATELLITES
#define NMEAGPS_PARSE_SATELLITE_INFO
#ifdef NMEAGPS_PARSE_SATELLITES
#define NMEAGPS_MAX_SATELLITES (20)
#ifndef GPS_FIX_SATELLITES
#error GPS_FIX_SATELLITES must be defined in GPSfix.h!
#endif
#endif
#if defined(NMEAGPS_PARSE_SATELLITE_INFO) & \
!defined(NMEAGPS_PARSE_SATELLITES)
#error NMEAGPS_PARSE_SATELLITES must be defined!
#endif
//------------------------------------------------------
// Enable/disable gathering interface statistics:
// CRC errors and number of sentences received
#define NMEAGPS_STATS
//------------------------------------------------------
// Configuration item for allowing derived types of NMEAGPS.
// If you derive classes from NMEAGPS, you *must* define NMEAGPS_DERIVED_TYPES.
// If not defined, virtuals are not used, with a slight size (2 bytes) and
// execution time savings.
//#define NMEAGPS_DERIVED_TYPES
#ifdef NMEAGPS_DERIVED_TYPES
#define NMEAGPS_VIRTUAL virtual
#else
#define NMEAGPS_VIRTUAL
#endif
//-----------------------------------
// See if DERIVED_TYPES is required
#if (defined(NMEAGPS_PARSE_TALKER_ID) | defined(NMEAGPS_PARSE_MFR_ID)) & \
!defined(NMEAGPS_DERIVED_TYPES)
#error You must define NMEAGPS_DERIVED_TYPES in NMEAGPS.h in order to parse Talker and/or Mfr IDs!
#endif
//------------------------------------------------------
// Some devices may omit trailing commas at the end of some
// sentences. This may prevent the last field from being
// parsed correctly, because the parser for some types keep
// the value in an intermediate state until the complete
// field is received (e.g., parseDDDMM, parseFloat and
// parseZDA).
//
// Enabling this will inject a simulated comma when the end
// of a sentence is received and the last field parser
// indicated that it still needs one.
//#define NMEAGPS_COMMA_NEEDED
//------------------------------------------------------
// Some applications may want to recognize a sentence type
// without actually parsing any of the fields. Uncommenting
// this define will allow the nmeaMessage member to be set
// when *any* standard message is seen, even though that
// message is not enabled by a NMEAGPS_PARSE_xxx define above.
// No valid flags will be true for those sentences.
#define NMEAGPS_RECOGNIZE_ALL
//------------------------------------------------------
// Sometimes, a little extra space is needed to parse an intermediate form.
// This config items enables extra space.
#define NMEAGPS_PARSING_SCRATCHPAD
#endif

View File

@@ -0,0 +1,85 @@
#ifndef NEOGPS_CFG
#define NEOGPS_CFG
/**
* Enable/disable packed data structures.
*
* Enabling packed data structures will use two less-portable language
* features of GCC to reduce RAM requirements. Although it was expected to slightly increase execution time and code size, the reverse is true on 8-bit AVRs: the code is smaller and faster with packing enabled.
*
* Disabling packed data structures will be very portable to other
* platforms. NeoGPS configurations will use slightly more RAM, and on
* 8-bit AVRs, the speed is slightly slower, and the code is slightly
* larger. There may be no choice but to disable packing on processors
* that do not support packed structures.
*
* There may also be compiler-specific switches that affect packing and the
* code which accesses packed members. YMMV.
**/
#ifdef __AVR__
#define NEOGPS_PACKED_DATA
#endif
//------------------------------------------------------------------------
// Based on the above define, choose which set of packing macros should
// be used in the rest of the NeoGPS package. Do not change these defines.
#ifdef NEOGPS_PACKED_DATA
// This is for specifying the number of bits to be used for a
// member of a struct. Booleans are typically one bit.
#define NEOGPS_BF(b) :b
// This is for requesting the compiler to pack the struct or class members
// "as closely as possible". This is a compiler-dependent interpretation.
#define NEOGPS_PACKED __attribute__((packed))
#else
// Let the compiler do whatever it wants.
#define NEOGPS_PACKED
#define NEOGPS_BF(b)
#endif
/*
* Accommodate C++ compiler and IDE changes.
*
* Declaring constants as class data instead of instance data helps avoid
* collisions with #define names, and allows the compiler to perform more
* checks on their usage.
*
* Until C++ 10 and IDE 1.6.8, initialized class data constants
* were declared like this:
*
* static const <valued types> = <constant-value>;
*
* Now, non-simple types (e.g., float) must be declared as
*
* static constexpr <nonsimple-types> = <expression-treated-as-const>;
*
* The good news is that this allows the compiler to optimize out an
* expression that is "promised" to be "evaluatable" as a constant.
* The bad news is that it introduces a new language keyword, and the old
* code raises an error.
*
* TODO: Evaluate the requirement for the "static" keyword.
* TODO: Evaluate using a C++ version preprocessor symbol for the #if.
*
* The CONST_CLASS_DATA define will expand to the appropriate keywords.
*
*/
#if ARDUINO < 10606
#define CONST_CLASS_DATA static const
#else
#define CONST_CLASS_DATA static constexpr
#endif
#endif

View File

@@ -0,0 +1,35 @@
#ifndef GPS_FIX_CFG
#define GPS_FIX_CFG
/**
* Enable/disable the storage for the members of a fix.
*
* Disabling a member prevents it from being parsed from a received message.
* The disabled member cannot be accessed or stored, and its validity flag
* would not be available. It will not be declared, and code that uses that
* member will not compile.
*
* DATE and TIME are somewhat coupled in that they share a single `time_t`,
* but they have separate validity flags.
*
* See also note regarding the DOP members, below.
*
*/
//#define GPS_FIX_DATE
//#define GPS_FIX_TIME
//#define GPS_FIX_LOCATION
//#define GPS_FIX_LOCATION_DMS
//#define GPS_FIX_ALTITUDE
//#define GPS_FIX_SPEED
//#define GPS_FIX_HEADING
//#define GPS_FIX_SATELLITES
//#define GPS_FIX_HDOP
//#define GPS_FIX_VDOP
//#define GPS_FIX_PDOP
//#define GPS_FIX_LAT_ERR
//#define GPS_FIX_LON_ERR
//#define GPS_FIX_ALT_ERR
//#define GPS_FIX_GEOID_HEIGHT
#endif

View File

@@ -0,0 +1,286 @@
#ifndef NMEAGPS_CFG_H
#define NMEAGPS_CFG_H
//------------------------------------------------------
// Enable/disable the parsing of specific sentences.
//
// Configuring out a sentence prevents it from being recognized; it
// will be completely ignored. (See also NMEAGPS_RECOGNIZE_ALL, below)
//
// FYI: Only RMC and ZDA contain date information. Other
// sentences contain time information. Both date and time are
// required if you will be doing time_t-to-clock_t operations.
//#define NMEAGPS_PARSE_GGA
//#define NMEAGPS_PARSE_GLL
//#define NMEAGPS_PARSE_GSA
//#define NMEAGPS_PARSE_GSV
//#define NMEAGPS_PARSE_GST
#define NMEAGPS_PARSE_RMC
//#define NMEAGPS_PARSE_VTG
//#define NMEAGPS_PARSE_ZDA
//------------------------------------------------------
// Select which sentence is sent *last* by your GPS device
// in each update interval. This can be used by your sketch
// to determine when the GPS quiet time begins, and thus
// when you can perform "some" time-consuming operations.
#define LAST_SENTENCE_IN_INTERVAL NMEAGPS::NMEA_RMC
// If the NMEA_LAST_SENTENCE_IN_INTERVAL is not chosen
// correctly, GPS data may be lost because the sketch
// takes too long elsewhere when this sentence is received.
// Also, fix members may contain information from different
// time intervals (i.e., they are not coherent).
//
// If you don't know which sentence is the last one,
// use NMEAorder.ino to list them. You do not have to select
// the last sentence the device sends if you have disabled
// it. Just select the last sentence that you have *enabled*.
//------------------------------------------------------
// Enable/Disable coherency:
//
// If you need each fix to contain information that is only
// from the current update interval, you should uncomment
// this define. At the beginning of the next interval,
// the accumulating fix will start out empty. When
// the LAST_SENTENCE_IN_INTERVAL arrives, the valid
// fields will be coherent.
//#define NMEAGPS_COHERENT
// With IMPLICIT merging, fix() will be emptied when the
// next sentence begins.
//
// With EXPLICIT or NO merging, the fix() was already
// being initialized.
//
// If you use the fix-oriented methods available() and read(),
// they will empty the current fix for you automatically.
//
// If you use the character-oriented method decode(), you should
// empty the accumulating fix by testing and clearing the
// 'intervalComplete' flag in the same way that available() does.
//------------------------------------------------------
// Choose how multiple sentences are merged:
// 1) No merging
// Each sentence fills out its own fix; there could be
// multiple sentences per interval.
// 2) EXPLICIT_MERGING
// All sentences in an interval are *safely* merged into one fix.
// NMEAGPS_FIX_MAX must be >= 1.
// An interval is defined by NMEA_LAST_SENTENCE_IN_INTERVAL.
// 3) IMPLICIT_MERGING
// All sentences in an interval are merged into one fix, with
// possible data loss. If a received sentence is rejected for
// any reason (e.g., a checksum error), all the values are suspect.
// The fix will be cleared; no members will be valid until new
// sentences are received and accepted. This uses less RAM.
// An interval is defined by NMEA_LAST_SENTENCE_IN_INTERVAL.
// Uncomment zero or one:
#define NMEAGPS_EXPLICIT_MERGING
//#define NMEAGPS_IMPLICIT_MERGING
#ifdef NMEAGPS_IMPLICIT_MERGING
#define NMEAGPS_MERGING NMEAGPS::IMPLICIT_MERGING
// When accumulating, nothing is done to the fix at the
// beginning of every sentence...
#ifdef NMEAGPS_COHERENT
// ...unless COHERENT is enabled and a new interval is starting
#define NMEAGPS_INIT_FIX(m) \
if (intervalComplete()) { intervalComplete( false ); m.valid.init(); }
#else
#define NMEAGPS_INIT_FIX(m)
#endif
// ...but we invalidate one part when it starts to get parsed. It *may* get
// validated when the parsing is finished.
#define NMEAGPS_INVALIDATE(m) m_fix.valid.m = false
#else
#ifdef NMEAGPS_EXPLICIT_MERGING
#define NMEAGPS_MERGING NMEAGPS::EXPLICIT_MERGING
#else
#define NMEAGPS_MERGING NMEAGPS::NO_MERGING
#define NMEAGPS_NO_MERGING
#endif
// When NOT accumulating, invalidate the entire fix at the
// beginning of every sentence
#define NMEAGPS_INIT_FIX(m) m.valid.init()
// ...so the individual parts do not need to be invalidated as they are parsed
#define NMEAGPS_INVALIDATE(m)
#endif
#if ( defined(NMEAGPS_NO_MERGING) + \
defined(NMEAGPS_IMPLICIT_MERGING) + \
defined(NMEAGPS_EXPLICIT_MERGING) ) > 1
#error Only one MERGING technique should be enabled in NMEAGPS_cfg.h!
#endif
//------------------------------------------------------
// Define the fix buffer size. The NMEAGPS object will hold on to
// this many fixes before an overrun occurs. This can be zero,
// but you have to be more careful about using gps.fix() structure,
// because it will be modified as characters are received.
#define NMEAGPS_FIX_MAX 1
#if defined(NMEAGPS_EXPLICIT_MERGING) && (NMEAGPS_FIX_MAX == 0)
#error You must define FIX_MAX >= 1 to allow EXPLICIT merging in NMEAGPS_cfg.h
#endif
//------------------------------------------------------
// Enable/Disable interrupt-style processing of GPS characters
// If you are using one of the NeoXXSerial libraries,
// to attachInterrupt, this must be defined.
// Otherwise, it must be commented out.
//#define NMEAGPS_INTERRUPT_PROCESSING
#ifdef NMEAGPS_INTERRUPT_PROCESSING
#define NMEAGPS_PROCESSING_STYLE NMEAGPS::PS_INTERRUPT
#else
#define NMEAGPS_PROCESSING_STYLE NMEAGPS::PS_POLLING
#endif
//------------------------------------------------------
// Enable/disable the talker ID, manufacturer ID and proprietary message processing.
//
// First, some background information. There are two kinds of NMEA sentences:
//
// 1. Standard NMEA sentences begin with "$ttccc", where
// "tt" is the talker ID, and
// "ccc" is the variable-length sentence type (i.e., command).
//
// For example, "$GPGLL,..." is a GLL sentence (Geographic Lat/Long)
// transmitted by talker "GP". This is the most common talker ID. Some
// devices may report "$GNGLL,..." when a mix of GPS and non-GPS
// satellites have been used to determine the GLL data.
//
// 2. Proprietary NMEA sentences (i.e., those unique to a particular
// manufacturer) begin with "$Pmmmccc", where
// "P" is the NMEA-defined prefix indicator for proprietary messages,
// "mmm" is the 3-character manufacturer ID, and
// "ccc" is the variable-length sentence type (it can be empty).
//
// No validation of manufacturer ID and talker ID is performed in this
// base class. For example, although "GP" is a common talker ID, it is not
// guaranteed to be transmitted by your particular device, and it IS NOT
// REQUIRED. If you need validation of these IDs, or you need to use the
// extra information provided by some devices, you have two independent
// options:
//
// 1. Enable SAVING the ID: When /decode/ returns DECODE_COMPLETED, the
// /talker_id/ and/or /mfr_id/ members will contain ID bytes. The entire
// sentence will be parsed, perhaps modifying members of /fix/. You should
// enable one or both IDs if you want the information in all sentences *and*
// you also want to know the ID bytes. This adds two bytes of RAM for the
// talker ID, and 3 bytes of RAM for the manufacturer ID.
//
// 2. Enable PARSING the ID: The virtual /parse_talker_id/ and
// /parse_mfr_id/ will receive each ID character as it is parsed. If it
// is not a valid ID, return /false/ to abort processing the rest of the
// sentence. No CPU time will be wasted on the invalid sentence, and no
// /fix/ members will be modified. You should enable this if you want to
// ignore some IDs. You must override /parse_talker_id/ and/or
// /parse_mfr_id/ in a derived class.
//
//#define NMEAGPS_SAVE_TALKER_ID
//#define NMEAGPS_PARSE_TALKER_ID
//#define NMEAGPS_PARSE_PROPRIETARY
#ifdef NMEAGPS_PARSE_PROPRIETARY
//#define NMEAGPS_SAVE_MFR_ID
#define NMEAGPS_PARSE_MFR_ID
#endif
//------------------------------------------------------
// Enable/disable tracking the current satellite array and,
// optionally, all the info for each satellite.
//
//#define NMEAGPS_PARSE_SATELLITES
//#define NMEAGPS_PARSE_SATELLITE_INFO
#ifdef NMEAGPS_PARSE_SATELLITES
#define NMEAGPS_MAX_SATELLITES (20)
#ifndef GPS_FIX_SATELLITES
#error GPS_FIX_SATELLITES must be defined in GPSfix.h!
#endif
#endif
#if defined(NMEAGPS_PARSE_SATELLITE_INFO) & \
!defined(NMEAGPS_PARSE_SATELLITES)
#error NMEAGPS_PARSE_SATELLITES must be defined!
#endif
//------------------------------------------------------
// Enable/disable gathering interface statistics:
// CRC errors and number of sentences received
//#define NMEAGPS_STATS
//------------------------------------------------------
// Configuration item for allowing derived types of NMEAGPS.
// If you derive classes from NMEAGPS, you *must* define NMEAGPS_DERIVED_TYPES.
// If not defined, virtuals are not used, with a slight size (2 bytes) and
// execution time savings.
//#define NMEAGPS_DERIVED_TYPES
#ifdef NMEAGPS_DERIVED_TYPES
#define NMEAGPS_VIRTUAL virtual
#else
#define NMEAGPS_VIRTUAL
#endif
//-----------------------------------
// See if DERIVED_TYPES is required
#if (defined(NMEAGPS_PARSE_TALKER_ID) | defined(NMEAGPS_PARSE_MFR_ID)) & \
!defined(NMEAGPS_DERIVED_TYPES)
#error You must define NMEAGPS_DERIVED_TYPES in NMEAGPS.h in order to parse Talker and/or Mfr IDs!
#endif
//------------------------------------------------------
// Some devices may omit trailing commas at the end of some
// sentences. This may prevent the last field from being
// parsed correctly, because the parser for some types keep
// the value in an intermediate state until the complete
// field is received (e.g., parseDDDMM, parseFloat and
// parseZDA).
//
// Enabling this will inject a simulated comma when the end
// of a sentence is received and the last field parser
// indicated that it still needs one.
//#define NMEAGPS_COMMA_NEEDED
//------------------------------------------------------
// Some applications may want to recognize a sentence type
// without actually parsing any of the fields. Uncommenting
// this define will allow the nmeaMessage member to be set
// when *any* standard message is seen, even though that
// message is not enabled by a NMEAGPS_PARSE_xxx define above.
// No valid flags will be true for those sentences.
//#define NMEAGPS_RECOGNIZE_ALL
//------------------------------------------------------
// Sometimes, a little extra space is needed to parse an intermediate form.
// This config items enables extra space.
//#define NMEAGPS_PARSING_SCRATCHPAD
#endif

View File

@@ -0,0 +1,85 @@
#ifndef NEOGPS_CFG
#define NEOGPS_CFG
/**
* Enable/disable packed data structures.
*
* Enabling packed data structures will use two less-portable language
* features of GCC to reduce RAM requirements. Although it was expected to slightly increase execution time and code size, the reverse is true on 8-bit AVRs: the code is smaller and faster with packing enabled.
*
* Disabling packed data structures will be very portable to other
* platforms. NeoGPS configurations will use slightly more RAM, and on
* 8-bit AVRs, the speed is slightly slower, and the code is slightly
* larger. There may be no choice but to disable packing on processors
* that do not support packed structures.
*
* There may also be compiler-specific switches that affect packing and the
* code which accesses packed members. YMMV.
**/
#ifdef __AVR__
#define NEOGPS_PACKED_DATA
#endif
//------------------------------------------------------------------------
// Based on the above define, choose which set of packing macros should
// be used in the rest of the NeoGPS package. Do not change these defines.
#ifdef NEOGPS_PACKED_DATA
// This is for specifying the number of bits to be used for a
// member of a struct. Booleans are typically one bit.
#define NEOGPS_BF(b) :b
// This is for requesting the compiler to pack the struct or class members
// "as closely as possible". This is a compiler-dependent interpretation.
#define NEOGPS_PACKED __attribute__((packed))
#else
// Let the compiler do whatever it wants.
#define NEOGPS_PACKED
#define NEOGPS_BF(b)
#endif
/*
* Accommodate C++ compiler and IDE changes.
*
* Declaring constants as class data instead of instance data helps avoid
* collisions with #define names, and allows the compiler to perform more
* checks on their usage.
*
* Until C++ 10 and IDE 1.6.8, initialized class data constants
* were declared like this:
*
* static const <valued types> = <constant-value>;
*
* Now, non-simple types (e.g., float) must be declared as
*
* static constexpr <nonsimple-types> = <expression-treated-as-const>;
*
* The good news is that this allows the compiler to optimize out an
* expression that is "promised" to be "evaluatable" as a constant.
* The bad news is that it introduces a new language keyword, and the old
* code raises an error.
*
* TODO: Evaluate the requirement for the "static" keyword.
* TODO: Evaluate using a C++ version preprocessor symbol for the #if.
*
* The CONST_CLASS_DATA define will expand to the appropriate keywords.
*
*/
#if ARDUINO < 10606
#define CONST_CLASS_DATA static const
#else
#define CONST_CLASS_DATA static constexpr
#endif
#endif

View File

@@ -0,0 +1,35 @@
#ifndef GPS_FIX_CFG
#define GPS_FIX_CFG
/**
* Enable/disable the storage for the members of a fix.
*
* Disabling a member prevents it from being parsed from a received message.
* The disabled member cannot be accessed or stored, and its validity flag
* would not be available. It will not be declared, and code that uses that
* member will not compile.
*
* DATE and TIME are somewhat coupled in that they share a single `time_t`,
* but they have separate validity flags.
*
* See also note regarding the DOP members, below.
*
*/
#define GPS_FIX_DATE
#define GPS_FIX_TIME
#define GPS_FIX_LOCATION
//#define GPS_FIX_LOCATION_DMS
#define GPS_FIX_ALTITUDE
#define GPS_FIX_SPEED
#define GPS_FIX_HEADING
#define GPS_FIX_SATELLITES
//#define GPS_FIX_HDOP
//#define GPS_FIX_VDOP
//#define GPS_FIX_PDOP
//#define GPS_FIX_LAT_ERR
//#define GPS_FIX_LON_ERR
//#define GPS_FIX_ALT_ERR
//#define GPS_FIX_GEOID_HEIGHT
#endif

View File

@@ -0,0 +1,286 @@
#ifndef NMEAGPS_CFG_H
#define NMEAGPS_CFG_H
//------------------------------------------------------
// Enable/disable the parsing of specific sentences.
//
// Configuring out a sentence prevents it from being recognized; it
// will be completely ignored. (See also NMEAGPS_RECOGNIZE_ALL, below)
//
// FYI: Only RMC and ZDA contain date information. Other
// sentences contain time information. Both date and time are
// required if you will be doing time_t-to-clock_t operations.
#define NMEAGPS_PARSE_GGA
//#define NMEAGPS_PARSE_GLL
//#define NMEAGPS_PARSE_GSA
//#define NMEAGPS_PARSE_GSV
//#define NMEAGPS_PARSE_GST
#define NMEAGPS_PARSE_RMC
//#define NMEAGPS_PARSE_VTG
//#define NMEAGPS_PARSE_ZDA
//------------------------------------------------------
// Select which sentence is sent *last* by your GPS device
// in each update interval. This can be used by your sketch
// to determine when the GPS quiet time begins, and thus
// when you can perform "some" time-consuming operations.
#define LAST_SENTENCE_IN_INTERVAL NMEAGPS::NMEA_RMC
// If the NMEA_LAST_SENTENCE_IN_INTERVAL is not chosen
// correctly, GPS data may be lost because the sketch
// takes too long elsewhere when this sentence is received.
// Also, fix members may contain information from different
// time intervals (i.e., they are not coherent).
//
// If you don't know which sentence is the last one,
// use NMEAorder.ino to list them. You do not have to select
// the last sentence the device sends if you have disabled
// it. Just select the last sentence that you have *enabled*.
//------------------------------------------------------
// Enable/Disable coherency:
//
// If you need each fix to contain information that is only
// from the current update interval, you should uncomment
// this define. At the beginning of the next interval,
// the accumulating fix will start out empty. When
// the LAST_SENTENCE_IN_INTERVAL arrives, the valid
// fields will be coherent.
//#define NMEAGPS_COHERENT
// With IMPLICIT merging, fix() will be emptied when the
// next sentence begins.
//
// With EXPLICIT or NO merging, the fix() was already
// being initialized.
//
// If you use the fix-oriented methods available() and read(),
// they will empty the current fix for you automatically.
//
// If you use the character-oriented method decode(), you should
// empty the accumulating fix by testing and clearing the
// 'intervalComplete' flag in the same way that available() does.
//------------------------------------------------------
// Choose how multiple sentences are merged:
// 1) No merging
// Each sentence fills out its own fix; there could be
// multiple sentences per interval.
// 2) EXPLICIT_MERGING
// All sentences in an interval are *safely* merged into one fix.
// NMEAGPS_FIX_MAX must be >= 1.
// An interval is defined by NMEA_LAST_SENTENCE_IN_INTERVAL.
// 3) IMPLICIT_MERGING
// All sentences in an interval are merged into one fix, with
// possible data loss. If a received sentence is rejected for
// any reason (e.g., a checksum error), all the values are suspect.
// The fix will be cleared; no members will be valid until new
// sentences are received and accepted. This uses less RAM.
// An interval is defined by NMEA_LAST_SENTENCE_IN_INTERVAL.
// Uncomment zero or one:
#define NMEAGPS_EXPLICIT_MERGING
//#define NMEAGPS_IMPLICIT_MERGING
#ifdef NMEAGPS_IMPLICIT_MERGING
#define NMEAGPS_MERGING NMEAGPS::IMPLICIT_MERGING
// When accumulating, nothing is done to the fix at the
// beginning of every sentence...
#ifdef NMEAGPS_COHERENT
// ...unless COHERENT is enabled and a new interval is starting
#define NMEAGPS_INIT_FIX(m) \
if (intervalComplete()) { intervalComplete( false ); m.valid.init(); }
#else
#define NMEAGPS_INIT_FIX(m)
#endif
// ...but we invalidate one part when it starts to get parsed. It *may* get
// validated when the parsing is finished.
#define NMEAGPS_INVALIDATE(m) m_fix.valid.m = false
#else
#ifdef NMEAGPS_EXPLICIT_MERGING
#define NMEAGPS_MERGING NMEAGPS::EXPLICIT_MERGING
#else
#define NMEAGPS_MERGING NMEAGPS::NO_MERGING
#define NMEAGPS_NO_MERGING
#endif
// When NOT accumulating, invalidate the entire fix at the
// beginning of every sentence
#define NMEAGPS_INIT_FIX(m) m.valid.init()
// ...so the individual parts do not need to be invalidated as they are parsed
#define NMEAGPS_INVALIDATE(m)
#endif
#if ( defined(NMEAGPS_NO_MERGING) + \
defined(NMEAGPS_IMPLICIT_MERGING) + \
defined(NMEAGPS_EXPLICIT_MERGING) ) > 1
#error Only one MERGING technique should be enabled in NMEAGPS_cfg.h!
#endif
//------------------------------------------------------
// Define the fix buffer size. The NMEAGPS object will hold on to
// this many fixes before an overrun occurs. This can be zero,
// but you have to be more careful about using gps.fix() structure,
// because it will be modified as characters are received.
#define NMEAGPS_FIX_MAX 1
#if defined(NMEAGPS_EXPLICIT_MERGING) && (NMEAGPS_FIX_MAX == 0)
#error You must define FIX_MAX >= 1 to allow EXPLICIT merging in NMEAGPS_cfg.h
#endif
//------------------------------------------------------
// Enable/Disable interrupt-style processing of GPS characters
// If you are using one of the NeoXXSerial libraries,
// to attachInterrupt, this must be defined.
// Otherwise, it must be commented out.
//#define NMEAGPS_INTERRUPT_PROCESSING
#ifdef NMEAGPS_INTERRUPT_PROCESSING
#define NMEAGPS_PROCESSING_STYLE NMEAGPS::PS_INTERRUPT
#else
#define NMEAGPS_PROCESSING_STYLE NMEAGPS::PS_POLLING
#endif
//------------------------------------------------------
// Enable/disable the talker ID, manufacturer ID and proprietary message processing.
//
// First, some background information. There are two kinds of NMEA sentences:
//
// 1. Standard NMEA sentences begin with "$ttccc", where
// "tt" is the talker ID, and
// "ccc" is the variable-length sentence type (i.e., command).
//
// For example, "$GPGLL,..." is a GLL sentence (Geographic Lat/Long)
// transmitted by talker "GP". This is the most common talker ID. Some
// devices may report "$GNGLL,..." when a mix of GPS and non-GPS
// satellites have been used to determine the GLL data.
//
// 2. Proprietary NMEA sentences (i.e., those unique to a particular
// manufacturer) begin with "$Pmmmccc", where
// "P" is the NMEA-defined prefix indicator for proprietary messages,
// "mmm" is the 3-character manufacturer ID, and
// "ccc" is the variable-length sentence type (it can be empty).
//
// No validation of manufacturer ID and talker ID is performed in this
// base class. For example, although "GP" is a common talker ID, it is not
// guaranteed to be transmitted by your particular device, and it IS NOT
// REQUIRED. If you need validation of these IDs, or you need to use the
// extra information provided by some devices, you have two independent
// options:
//
// 1. Enable SAVING the ID: When /decode/ returns DECODE_COMPLETED, the
// /talker_id/ and/or /mfr_id/ members will contain ID bytes. The entire
// sentence will be parsed, perhaps modifying members of /fix/. You should
// enable one or both IDs if you want the information in all sentences *and*
// you also want to know the ID bytes. This adds two bytes of RAM for the
// talker ID, and 3 bytes of RAM for the manufacturer ID.
//
// 2. Enable PARSING the ID: The virtual /parse_talker_id/ and
// /parse_mfr_id/ will receive each ID character as it is parsed. If it
// is not a valid ID, return /false/ to abort processing the rest of the
// sentence. No CPU time will be wasted on the invalid sentence, and no
// /fix/ members will be modified. You should enable this if you want to
// ignore some IDs. You must override /parse_talker_id/ and/or
// /parse_mfr_id/ in a derived class.
//
//#define NMEAGPS_SAVE_TALKER_ID
//#define NMEAGPS_PARSE_TALKER_ID
//#define NMEAGPS_PARSE_PROPRIETARY
#ifdef NMEAGPS_PARSE_PROPRIETARY
//#define NMEAGPS_SAVE_MFR_ID
#define NMEAGPS_PARSE_MFR_ID
#endif
//------------------------------------------------------
// Enable/disable tracking the current satellite array and,
// optionally, all the info for each satellite.
//
//#define NMEAGPS_PARSE_SATELLITES
//#define NMEAGPS_PARSE_SATELLITE_INFO
#ifdef NMEAGPS_PARSE_SATELLITES
#define NMEAGPS_MAX_SATELLITES (20)
#ifndef GPS_FIX_SATELLITES
#error GPS_FIX_SATELLITES must be defined in GPSfix.h!
#endif
#endif
#if defined(NMEAGPS_PARSE_SATELLITE_INFO) & \
!defined(NMEAGPS_PARSE_SATELLITES)
#error NMEAGPS_PARSE_SATELLITES must be defined!
#endif
//------------------------------------------------------
// Enable/disable gathering interface statistics:
// CRC errors and number of sentences received
#define NMEAGPS_STATS
//------------------------------------------------------
// Configuration item for allowing derived types of NMEAGPS.
// If you derive classes from NMEAGPS, you *must* define NMEAGPS_DERIVED_TYPES.
// If not defined, virtuals are not used, with a slight size (2 bytes) and
// execution time savings.
//#define NMEAGPS_DERIVED_TYPES
#ifdef NMEAGPS_DERIVED_TYPES
#define NMEAGPS_VIRTUAL virtual
#else
#define NMEAGPS_VIRTUAL
#endif
//-----------------------------------
// See if DERIVED_TYPES is required
#if (defined(NMEAGPS_PARSE_TALKER_ID) | defined(NMEAGPS_PARSE_MFR_ID)) & \
!defined(NMEAGPS_DERIVED_TYPES)
#error You must define NMEAGPS_DERIVED_TYPES in NMEAGPS.h in order to parse Talker and/or Mfr IDs!
#endif
//------------------------------------------------------
// Some devices may omit trailing commas at the end of some
// sentences. This may prevent the last field from being
// parsed correctly, because the parser for some types keep
// the value in an intermediate state until the complete
// field is received (e.g., parseDDDMM, parseFloat and
// parseZDA).
//
// Enabling this will inject a simulated comma when the end
// of a sentence is received and the last field parser
// indicated that it still needs one.
//#define NMEAGPS_COMMA_NEEDED
//------------------------------------------------------
// Some applications may want to recognize a sentence type
// without actually parsing any of the fields. Uncommenting
// this define will allow the nmeaMessage member to be set
// when *any* standard message is seen, even though that
// message is not enabled by a NMEAGPS_PARSE_xxx define above.
// No valid flags will be true for those sentences.
#define NMEAGPS_RECOGNIZE_ALL
//------------------------------------------------------
// Sometimes, a little extra space is needed to parse an intermediate form.
// This config items enables extra space.
//#define NMEAGPS_PARSING_SCRATCHPAD
#endif

View File

@@ -0,0 +1,85 @@
#ifndef NEOGPS_CFG
#define NEOGPS_CFG
/**
* Enable/disable packed data structures.
*
* Enabling packed data structures will use two less-portable language
* features of GCC to reduce RAM requirements. Although it was expected to slightly increase execution time and code size, the reverse is true on 8-bit AVRs: the code is smaller and faster with packing enabled.
*
* Disabling packed data structures will be very portable to other
* platforms. NeoGPS configurations will use slightly more RAM, and on
* 8-bit AVRs, the speed is slightly slower, and the code is slightly
* larger. There may be no choice but to disable packing on processors
* that do not support packed structures.
*
* There may also be compiler-specific switches that affect packing and the
* code which accesses packed members. YMMV.
**/
#ifdef __AVR__
#define NEOGPS_PACKED_DATA
#endif
//------------------------------------------------------------------------
// Based on the above define, choose which set of packing macros should
// be used in the rest of the NeoGPS package. Do not change these defines.
#ifdef NEOGPS_PACKED_DATA
// This is for specifying the number of bits to be used for a
// member of a struct. Booleans are typically one bit.
#define NEOGPS_BF(b) :b
// This is for requesting the compiler to pack the struct or class members
// "as closely as possible". This is a compiler-dependent interpretation.
#define NEOGPS_PACKED __attribute__((packed))
#else
// Let the compiler do whatever it wants.
#define NEOGPS_PACKED
#define NEOGPS_BF(b)
#endif
/*
* Accommodate C++ compiler and IDE changes.
*
* Declaring constants as class data instead of instance data helps avoid
* collisions with #define names, and allows the compiler to perform more
* checks on their usage.
*
* Until C++ 10 and IDE 1.6.8, initialized class data constants
* were declared like this:
*
* static const <valued types> = <constant-value>;
*
* Now, non-simple types (e.g., float) must be declared as
*
* static constexpr <nonsimple-types> = <expression-treated-as-const>;
*
* The good news is that this allows the compiler to optimize out an
* expression that is "promised" to be "evaluatable" as a constant.
* The bad news is that it introduces a new language keyword, and the old
* code raises an error.
*
* TODO: Evaluate the requirement for the "static" keyword.
* TODO: Evaluate using a C++ version preprocessor symbol for the #if.
*
* The CONST_CLASS_DATA define will expand to the appropriate keywords.
*
*/
#if ARDUINO < 10606
#define CONST_CLASS_DATA static const
#else
#define CONST_CLASS_DATA static constexpr
#endif
#endif

View File

@@ -0,0 +1,35 @@
#ifndef GPS_FIX_CFG
#define GPS_FIX_CFG
/**
* Enable/disable the storage for the members of a fix.
*
* Disabling a member prevents it from being parsed from a received message.
* The disabled member cannot be accessed or stored, and its validity flag
* would not be available. It will not be declared, and code that uses that
* member will not compile.
*
* DATE and TIME are somewhat coupled in that they share a single `time_t`,
* but they have separate validity flags.
*
* See also note regarding the DOP members, below.
*
*/
#define GPS_FIX_DATE
#define GPS_FIX_TIME
#define GPS_FIX_LOCATION
//#define GPS_FIX_LOCATION_DMS
#define GPS_FIX_ALTITUDE
#define GPS_FIX_SPEED
#define GPS_FIX_HEADING
#define GPS_FIX_SATELLITES
//#define GPS_FIX_HDOP
//#define GPS_FIX_VDOP
//#define GPS_FIX_PDOP
//#define GPS_FIX_LAT_ERR
//#define GPS_FIX_LON_ERR
//#define GPS_FIX_ALT_ERR
//#define GPS_FIX_GEOID_HEIGHT
#endif

View File

@@ -0,0 +1,286 @@
#ifndef NMEAGPS_CFG_H
#define NMEAGPS_CFG_H
//------------------------------------------------------
// Enable/disable the parsing of specific sentences.
//
// Configuring out a sentence prevents it from being recognized; it
// will be completely ignored. (See also NMEAGPS_RECOGNIZE_ALL, below)
//
// FYI: Only RMC and ZDA contain date information. Other
// sentences contain time information. Both date and time are
// required if you will be doing time_t-to-clock_t operations.
//#define NMEAGPS_PARSE_GGA
//#define NMEAGPS_PARSE_GLL
//#define NMEAGPS_PARSE_GSA
//#define NMEAGPS_PARSE_GSV
//#define NMEAGPS_PARSE_GST
//#define NMEAGPS_PARSE_RMC
//#define NMEAGPS_PARSE_VTG
//#define NMEAGPS_PARSE_ZDA
//------------------------------------------------------
// Select which sentence is sent *last* by your GPS device
// in each update interval. This can be used by your sketch
// to determine when the GPS quiet time begins, and thus
// when you can perform "some" time-consuming operations.
#define LAST_SENTENCE_IN_INTERVAL (nmea_msg_t) NMEA_LAST_MSG+5 /* ubloxNMEA::PUBX_04 */
// If the NMEA_LAST_SENTENCE_IN_INTERVAL is not chosen
// correctly, GPS data may be lost because the sketch
// takes too long elsewhere when this sentence is received.
// Also, fix members may contain information from different
// time intervals (i.e., they are not coherent).
//
// If you don't know which sentence is the last one,
// use NMEAorder.ino to list them. You do not have to select
// the last sentence the device sends if you have disabled
// it. Just select the last sentence that you have *enabled*.
//------------------------------------------------------
// Enable/Disable coherency:
//
// If you need each fix to contain information that is only
// from the current update interval, you should uncomment
// this define. At the beginning of the next interval,
// the accumulating fix will start out empty. When
// the LAST_SENTENCE_IN_INTERVAL arrives, the valid
// fields will be coherent.
//#define NMEAGPS_COHERENT
// With IMPLICIT merging, fix() will be emptied when the
// next sentence begins.
//
// With EXPLICIT or NO merging, the fix() was already
// being initialized.
//
// If you use the fix-oriented methods available() and read(),
// they will empty the current fix for you automatically.
//
// If you use the character-oriented method decode(), you should
// empty the accumulating fix by testing and clearing the
// 'intervalComplete' flag in the same way that available() does.
//------------------------------------------------------
// Choose how multiple sentences are merged:
// 1) No merging
// Each sentence fills out its own fix; there could be
// multiple sentences per interval.
// 2) EXPLICIT_MERGING
// All sentences in an interval are *safely* merged into one fix.
// NMEAGPS_FIX_MAX must be >= 1.
// An interval is defined by NMEA_LAST_SENTENCE_IN_INTERVAL.
// 3) IMPLICIT_MERGING
// All sentences in an interval are merged into one fix, with
// possible data loss. If a received sentence is rejected for
// any reason (e.g., a checksum error), all the values are suspect.
// The fix will be cleared; no members will be valid until new
// sentences are received and accepted. This uses less RAM.
// An interval is defined by NMEA_LAST_SENTENCE_IN_INTERVAL.
// Uncomment zero or one:
#define NMEAGPS_EXPLICIT_MERGING
//#define NMEAGPS_IMPLICIT_MERGING
#ifdef NMEAGPS_IMPLICIT_MERGING
#define NMEAGPS_MERGING NMEAGPS::IMPLICIT_MERGING
// When accumulating, nothing is done to the fix at the
// beginning of every sentence...
#ifdef NMEAGPS_COHERENT
// ...unless COHERENT is enabled and a new interval is starting
#define NMEAGPS_INIT_FIX(m) \
if (intervalComplete()) { intervalComplete( false ); m.valid.init(); }
#else
#define NMEAGPS_INIT_FIX(m)
#endif
// ...but we invalidate one part when it starts to get parsed. It *may* get
// validated when the parsing is finished.
#define NMEAGPS_INVALIDATE(m) m_fix.valid.m = false
#else
#ifdef NMEAGPS_EXPLICIT_MERGING
#define NMEAGPS_MERGING NMEAGPS::EXPLICIT_MERGING
#else
#define NMEAGPS_MERGING NMEAGPS::NO_MERGING
#define NMEAGPS_NO_MERGING
#endif
// When NOT accumulating, invalidate the entire fix at the
// beginning of every sentence
#define NMEAGPS_INIT_FIX(m) m.valid.init()
// ...so the individual parts do not need to be invalidated as they are parsed
#define NMEAGPS_INVALIDATE(m)
#endif
#if ( defined(NMEAGPS_NO_MERGING) + \
defined(NMEAGPS_IMPLICIT_MERGING) + \
defined(NMEAGPS_EXPLICIT_MERGING) ) > 1
#error Only one MERGING technique should be enabled in NMEAGPS_cfg.h!
#endif
//------------------------------------------------------
// Define the fix buffer size. The NMEAGPS object will hold on to
// this many fixes before an overrun occurs. This can be zero,
// but you have to be more careful about using gps.fix() structure,
// because it will be modified as characters are received.
#define NMEAGPS_FIX_MAX 1
#if defined(NMEAGPS_EXPLICIT_MERGING) && (NMEAGPS_FIX_MAX == 0)
#error You must define FIX_MAX >= 1 to allow EXPLICIT merging in NMEAGPS_cfg.h
#endif
//------------------------------------------------------
// Enable/Disable interrupt-style processing of GPS characters
// If you are using one of the NeoXXSerial libraries,
// to attachInterrupt, this must be defined.
// Otherwise, it must be commented out.
//#define NMEAGPS_INTERRUPT_PROCESSING
#ifdef NMEAGPS_INTERRUPT_PROCESSING
#define NMEAGPS_PROCESSING_STYLE NMEAGPS::PS_INTERRUPT
#else
#define NMEAGPS_PROCESSING_STYLE NMEAGPS::PS_POLLING
#endif
//------------------------------------------------------
// Enable/disable the talker ID, manufacturer ID and proprietary message processing.
//
// First, some background information. There are two kinds of NMEA sentences:
//
// 1. Standard NMEA sentences begin with "$ttccc", where
// "tt" is the talker ID, and
// "ccc" is the variable-length sentence type (i.e., command).
//
// For example, "$GPGLL,..." is a GLL sentence (Geographic Lat/Long)
// transmitted by talker "GP". This is the most common talker ID. Some
// devices may report "$GNGLL,..." when a mix of GPS and non-GPS
// satellites have been used to determine the GLL data.
//
// 2. Proprietary NMEA sentences (i.e., those unique to a particular
// manufacturer) begin with "$Pmmmccc", where
// "P" is the NMEA-defined prefix indicator for proprietary messages,
// "mmm" is the 3-character manufacturer ID, and
// "ccc" is the variable-length sentence type (it can be empty).
//
// No validation of manufacturer ID and talker ID is performed in this
// base class. For example, although "GP" is a common talker ID, it is not
// guaranteed to be transmitted by your particular device, and it IS NOT
// REQUIRED. If you need validation of these IDs, or you need to use the
// extra information provided by some devices, you have two independent
// options:
//
// 1. Enable SAVING the ID: When /decode/ returns DECODE_COMPLETED, the
// /talker_id/ and/or /mfr_id/ members will contain ID bytes. The entire
// sentence will be parsed, perhaps modifying members of /fix/. You should
// enable one or both IDs if you want the information in all sentences *and*
// you also want to know the ID bytes. This adds two bytes of RAM for the
// talker ID, and 3 bytes of RAM for the manufacturer ID.
//
// 2. Enable PARSING the ID: The virtual /parse_talker_id/ and
// /parse_mfr_id/ will receive each ID character as it is parsed. If it
// is not a valid ID, return /false/ to abort processing the rest of the
// sentence. No CPU time will be wasted on the invalid sentence, and no
// /fix/ members will be modified. You should enable this if you want to
// ignore some IDs. You must override /parse_talker_id/ and/or
// /parse_mfr_id/ in a derived class.
//
//#define NMEAGPS_SAVE_TALKER_ID
//#define NMEAGPS_PARSE_TALKER_ID
#define NMEAGPS_PARSE_PROPRIETARY
#ifdef NMEAGPS_PARSE_PROPRIETARY
//#define NMEAGPS_SAVE_MFR_ID
#define NMEAGPS_PARSE_MFR_ID
#endif
//------------------------------------------------------
// Enable/disable tracking the current satellite array and,
// optionally, all the info for each satellite.
//
//#define NMEAGPS_PARSE_SATELLITES
//#define NMEAGPS_PARSE_SATELLITE_INFO
#ifdef NMEAGPS_PARSE_SATELLITES
#define NMEAGPS_MAX_SATELLITES (20)
#ifndef GPS_FIX_SATELLITES
#error GPS_FIX_SATELLITES must be defined in GPSfix.h!
#endif
#endif
#if defined(NMEAGPS_PARSE_SATELLITE_INFO) & \
!defined(NMEAGPS_PARSE_SATELLITES)
#error NMEAGPS_PARSE_SATELLITES must be defined!
#endif
//------------------------------------------------------
// Enable/disable gathering interface statistics:
// CRC errors and number of sentences received
//#define NMEAGPS_STATS
//------------------------------------------------------
// Configuration item for allowing derived types of NMEAGPS.
// If you derive classes from NMEAGPS, you *must* define NMEAGPS_DERIVED_TYPES.
// If not defined, virtuals are not used, with a slight size (2 bytes) and
// execution time savings.
#define NMEAGPS_DERIVED_TYPES
#ifdef NMEAGPS_DERIVED_TYPES
#define NMEAGPS_VIRTUAL virtual
#else
#define NMEAGPS_VIRTUAL
#endif
//-----------------------------------
// See if DERIVED_TYPES is required
#if (defined(NMEAGPS_PARSE_TALKER_ID) | defined(NMEAGPS_PARSE_MFR_ID)) & \
!defined(NMEAGPS_DERIVED_TYPES)
#error You must define NMEAGPS_DERIVED_TYPES in NMEAGPS.h in order to parse Talker and/or Mfr IDs!
#endif
//------------------------------------------------------
// Some devices may omit trailing commas at the end of some
// sentences. This may prevent the last field from being
// parsed correctly, because the parser for some types keep
// the value in an intermediate state until the complete
// field is received (e.g., parseDDDMM, parseFloat and
// parseZDA).
//
// Enabling this will inject a simulated comma when the end
// of a sentence is received and the last field parser
// indicated that it still needs one.
//#define NMEAGPS_COMMA_NEEDED
//------------------------------------------------------
// Some applications may want to recognize a sentence type
// without actually parsing any of the fields. Uncommenting
// this define will allow the nmeaMessage member to be set
// when *any* standard message is seen, even though that
// message is not enabled by a NMEAGPS_PARSE_xxx define above.
// No valid flags will be true for those sentences.
//#define NMEAGPS_RECOGNIZE_ALL
//------------------------------------------------------
// Sometimes, a little extra space is needed to parse an intermediate form.
// This config items enables extra space.
//#define NMEAGPS_PARSING_SCRATCHPAD
#endif

View File

@@ -0,0 +1,85 @@
#ifndef NEOGPS_CFG
#define NEOGPS_CFG
/**
* Enable/disable packed data structures.
*
* Enabling packed data structures will use two less-portable language
* features of GCC to reduce RAM requirements. Although it was expected to slightly increase execution time and code size, the reverse is true on 8-bit AVRs: the code is smaller and faster with packing enabled.
*
* Disabling packed data structures will be very portable to other
* platforms. NeoGPS configurations will use slightly more RAM, and on
* 8-bit AVRs, the speed is slightly slower, and the code is slightly
* larger. There may be no choice but to disable packing on processors
* that do not support packed structures.
*
* There may also be compiler-specific switches that affect packing and the
* code which accesses packed members. YMMV.
**/
#ifdef __AVR__
#define NEOGPS_PACKED_DATA
#endif
//------------------------------------------------------------------------
// Based on the above define, choose which set of packing macros should
// be used in the rest of the NeoGPS package. Do not change these defines.
#ifdef NEOGPS_PACKED_DATA
// This is for specifying the number of bits to be used for a
// member of a struct. Booleans are typically one bit.
#define NEOGPS_BF(b) :b
// This is for requesting the compiler to pack the struct or class members
// "as closely as possible". This is a compiler-dependent interpretation.
#define NEOGPS_PACKED __attribute__((packed))
#else
// Let the compiler do whatever it wants.
#define NEOGPS_PACKED
#define NEOGPS_BF(b)
#endif
/*
* Accommodate C++ compiler and IDE changes.
*
* Declaring constants as class data instead of instance data helps avoid
* collisions with #define names, and allows the compiler to perform more
* checks on their usage.
*
* Until C++ 10 and IDE 1.6.8, initialized class data constants
* were declared like this:
*
* static const <valued types> = <constant-value>;
*
* Now, non-simple types (e.g., float) must be declared as
*
* static constexpr <nonsimple-types> = <expression-treated-as-const>;
*
* The good news is that this allows the compiler to optimize out an
* expression that is "promised" to be "evaluatable" as a constant.
* The bad news is that it introduces a new language keyword, and the old
* code raises an error.
*
* TODO: Evaluate the requirement for the "static" keyword.
* TODO: Evaluate using a C++ version preprocessor symbol for the #if.
*
* The CONST_CLASS_DATA define will expand to the appropriate keywords.
*
*/
#if ARDUINO < 10606
#define CONST_CLASS_DATA static const
#else
#define CONST_CLASS_DATA static constexpr
#endif
#endif

View File

@@ -0,0 +1,35 @@
#ifndef GPS_FIX_CFG
#define GPS_FIX_CFG
/**
* Enable/disable the storage for the members of a fix.
*
* Disabling a member prevents it from being parsed from a received message.
* The disabled member cannot be accessed or stored, and its validity flag
* would not be available. It will not be declared, and code that uses that
* member will not compile.
*
* DATE and TIME are somewhat coupled in that they share a single `time_t`,
* but they have separate validity flags.
*
* See also note regarding the DOP members, below.
*
*/
//#define GPS_FIX_DATE
//#define GPS_FIX_TIME
//#define GPS_FIX_LOCATION
//#define GPS_FIX_LOCATION_DMS
//#define GPS_FIX_ALTITUDE
#define GPS_FIX_SPEED
//#define GPS_FIX_HEADING
//#define GPS_FIX_SATELLITES
//#define GPS_FIX_HDOP
//#define GPS_FIX_VDOP
//#define GPS_FIX_PDOP
//#define GPS_FIX_LAT_ERR
//#define GPS_FIX_LON_ERR
//#define GPS_FIX_ALT_ERR
//#define GPS_FIX_GEOID_HEIGHT
#endif

View File

@@ -0,0 +1,286 @@
#ifndef NMEAGPS_CFG_H
#define NMEAGPS_CFG_H
//------------------------------------------------------
// Enable/disable the parsing of specific sentences.
//
// Configuring out a sentence prevents it from being recognized; it
// will be completely ignored. (See also NMEAGPS_RECOGNIZE_ALL, below)
//
// FYI: Only RMC and ZDA contain date information. Other
// sentences contain time information. Both date and time are
// required if you will be doing time_t-to-clock_t operations.
//#define NMEAGPS_PARSE_GGA
//#define NMEAGPS_PARSE_GLL
//#define NMEAGPS_PARSE_GSA
//#define NMEAGPS_PARSE_GSV
//#define NMEAGPS_PARSE_GST
#define NMEAGPS_PARSE_RMC
//#define NMEAGPS_PARSE_VTG
//#define NMEAGPS_PARSE_ZDA
//------------------------------------------------------
// Select which sentence is sent *last* by your GPS device
// in each update interval. This can be used by your sketch
// to determine when the GPS quiet time begins, and thus
// when you can perform "some" time-consuming operations.
#define LAST_SENTENCE_IN_INTERVAL NMEAGPS::NMEA_RMC
// If the NMEA_LAST_SENTENCE_IN_INTERVAL is not chosen
// correctly, GPS data may be lost because the sketch
// takes too long elsewhere when this sentence is received.
// Also, fix members may contain information from different
// time intervals (i.e., they are not coherent).
//
// If you don't know which sentence is the last one,
// use NMEAorder.ino to list them. You do not have to select
// the last sentence the device sends if you have disabled
// it. Just select the last sentence that you have *enabled*.
//------------------------------------------------------
// Enable/Disable coherency:
//
// If you need each fix to contain information that is only
// from the current update interval, you should uncomment
// this define. At the beginning of the next interval,
// the accumulating fix will start out empty. When
// the LAST_SENTENCE_IN_INTERVAL arrives, the valid
// fields will be coherent.
//#define NMEAGPS_COHERENT
// With IMPLICIT merging, fix() will be emptied when the
// next sentence begins.
//
// With EXPLICIT or NO merging, the fix() was already
// being initialized.
//
// If you use the fix-oriented methods available() and read(),
// they will empty the current fix for you automatically.
//
// If you use the character-oriented method decode(), you should
// empty the accumulating fix by testing and clearing the
// 'intervalComplete' flag in the same way that available() does.
//------------------------------------------------------
// Choose how multiple sentences are merged:
// 1) No merging
// Each sentence fills out its own fix; there could be
// multiple sentences per interval.
// 2) EXPLICIT_MERGING
// All sentences in an interval are *safely* merged into one fix.
// NMEAGPS_FIX_MAX must be >= 1.
// An interval is defined by NMEA_LAST_SENTENCE_IN_INTERVAL.
// 3) IMPLICIT_MERGING
// All sentences in an interval are merged into one fix, with
// possible data loss. If a received sentence is rejected for
// any reason (e.g., a checksum error), all the values are suspect.
// The fix will be cleared; no members will be valid until new
// sentences are received and accepted. This uses less RAM.
// An interval is defined by NMEA_LAST_SENTENCE_IN_INTERVAL.
// Uncomment zero or one:
#define NMEAGPS_EXPLICIT_MERGING
//#define NMEAGPS_IMPLICIT_MERGING
#ifdef NMEAGPS_IMPLICIT_MERGING
#define NMEAGPS_MERGING NMEAGPS::IMPLICIT_MERGING
// When accumulating, nothing is done to the fix at the
// beginning of every sentence...
#ifdef NMEAGPS_COHERENT
// ...unless COHERENT is enabled and a new interval is starting
#define NMEAGPS_INIT_FIX(m) \
if (intervalComplete()) { intervalComplete( false ); m.valid.init(); }
#else
#define NMEAGPS_INIT_FIX(m)
#endif
// ...but we invalidate one part when it starts to get parsed. It *may* get
// validated when the parsing is finished.
#define NMEAGPS_INVALIDATE(m) m_fix.valid.m = false
#else
#ifdef NMEAGPS_EXPLICIT_MERGING
#define NMEAGPS_MERGING NMEAGPS::EXPLICIT_MERGING
#else
#define NMEAGPS_MERGING NMEAGPS::NO_MERGING
#define NMEAGPS_NO_MERGING
#endif
// When NOT accumulating, invalidate the entire fix at the
// beginning of every sentence
#define NMEAGPS_INIT_FIX(m) m.valid.init()
// ...so the individual parts do not need to be invalidated as they are parsed
#define NMEAGPS_INVALIDATE(m)
#endif
#if ( defined(NMEAGPS_NO_MERGING) + \
defined(NMEAGPS_IMPLICIT_MERGING) + \
defined(NMEAGPS_EXPLICIT_MERGING) ) > 1
#error Only one MERGING technique should be enabled in NMEAGPS_cfg.h!
#endif
//------------------------------------------------------
// Define the fix buffer size. The NMEAGPS object will hold on to
// this many fixes before an overrun occurs. This can be zero,
// but you have to be more careful about using gps.fix() structure,
// because it will be modified as characters are received.
#define NMEAGPS_FIX_MAX 1
#if defined(NMEAGPS_EXPLICIT_MERGING) && (NMEAGPS_FIX_MAX == 0)
#error You must define FIX_MAX >= 1 to allow EXPLICIT merging in NMEAGPS_cfg.h
#endif
//------------------------------------------------------
// Enable/Disable interrupt-style processing of GPS characters
// If you are using one of the NeoXXSerial libraries,
// to attachInterrupt, this must be defined.
// Otherwise, it must be commented out.
//#define NMEAGPS_INTERRUPT_PROCESSING
#ifdef NMEAGPS_INTERRUPT_PROCESSING
#define NMEAGPS_PROCESSING_STYLE NMEAGPS::PS_INTERRUPT
#else
#define NMEAGPS_PROCESSING_STYLE NMEAGPS::PS_POLLING
#endif
//------------------------------------------------------
// Enable/disable the talker ID, manufacturer ID and proprietary message processing.
//
// First, some background information. There are two kinds of NMEA sentences:
//
// 1. Standard NMEA sentences begin with "$ttccc", where
// "tt" is the talker ID, and
// "ccc" is the variable-length sentence type (i.e., command).
//
// For example, "$GPGLL,..." is a GLL sentence (Geographic Lat/Long)
// transmitted by talker "GP". This is the most common talker ID. Some
// devices may report "$GNGLL,..." when a mix of GPS and non-GPS
// satellites have been used to determine the GLL data.
//
// 2. Proprietary NMEA sentences (i.e., those unique to a particular
// manufacturer) begin with "$Pmmmccc", where
// "P" is the NMEA-defined prefix indicator for proprietary messages,
// "mmm" is the 3-character manufacturer ID, and
// "ccc" is the variable-length sentence type (it can be empty).
//
// No validation of manufacturer ID and talker ID is performed in this
// base class. For example, although "GP" is a common talker ID, it is not
// guaranteed to be transmitted by your particular device, and it IS NOT
// REQUIRED. If you need validation of these IDs, or you need to use the
// extra information provided by some devices, you have two independent
// options:
//
// 1. Enable SAVING the ID: When /decode/ returns DECODE_COMPLETED, the
// /talker_id/ and/or /mfr_id/ members will contain ID bytes. The entire
// sentence will be parsed, perhaps modifying members of /fix/. You should
// enable one or both IDs if you want the information in all sentences *and*
// you also want to know the ID bytes. This adds two bytes of RAM for the
// talker ID, and 3 bytes of RAM for the manufacturer ID.
//
// 2. Enable PARSING the ID: The virtual /parse_talker_id/ and
// /parse_mfr_id/ will receive each ID character as it is parsed. If it
// is not a valid ID, return /false/ to abort processing the rest of the
// sentence. No CPU time will be wasted on the invalid sentence, and no
// /fix/ members will be modified. You should enable this if you want to
// ignore some IDs. You must override /parse_talker_id/ and/or
// /parse_mfr_id/ in a derived class.
//
//#define NMEAGPS_SAVE_TALKER_ID
//#define NMEAGPS_PARSE_TALKER_ID
//#define NMEAGPS_PARSE_PROPRIETARY
#ifdef NMEAGPS_PARSE_PROPRIETARY
//#define NMEAGPS_SAVE_MFR_ID
#define NMEAGPS_PARSE_MFR_ID
#endif
//------------------------------------------------------
// Enable/disable tracking the current satellite array and,
// optionally, all the info for each satellite.
//
//#define NMEAGPS_PARSE_SATELLITES
//#define NMEAGPS_PARSE_SATELLITE_INFO
#ifdef NMEAGPS_PARSE_SATELLITES
#define NMEAGPS_MAX_SATELLITES (20)
#ifndef GPS_FIX_SATELLITES
#error GPS_FIX_SATELLITES must be defined in GPSfix.h!
#endif
#endif
#if defined(NMEAGPS_PARSE_SATELLITE_INFO) & \
!defined(NMEAGPS_PARSE_SATELLITES)
#error NMEAGPS_PARSE_SATELLITES must be defined!
#endif
//------------------------------------------------------
// Enable/disable gathering interface statistics:
// CRC errors and number of sentences received
//#define NMEAGPS_STATS
//------------------------------------------------------
// Configuration item for allowing derived types of NMEAGPS.
// If you derive classes from NMEAGPS, you *must* define NMEAGPS_DERIVED_TYPES.
// If not defined, virtuals are not used, with a slight size (2 bytes) and
// execution time savings.
//#define NMEAGPS_DERIVED_TYPES
#ifdef NMEAGPS_DERIVED_TYPES
#define NMEAGPS_VIRTUAL virtual
#else
#define NMEAGPS_VIRTUAL
#endif
//-----------------------------------
// See if DERIVED_TYPES is required
#if (defined(NMEAGPS_PARSE_TALKER_ID) | defined(NMEAGPS_PARSE_MFR_ID)) & \
!defined(NMEAGPS_DERIVED_TYPES)
#error You must define NMEAGPS_DERIVED_TYPES in NMEAGPS.h in order to parse Talker and/or Mfr IDs!
#endif
//------------------------------------------------------
// Some devices may omit trailing commas at the end of some
// sentences. This may prevent the last field from being
// parsed correctly, because the parser for some types keep
// the value in an intermediate state until the complete
// field is received (e.g., parseDDDMM, parseFloat and
// parseZDA).
//
// Enabling this will inject a simulated comma when the end
// of a sentence is received and the last field parser
// indicated that it still needs one.
//#define NMEAGPS_COMMA_NEEDED
//------------------------------------------------------
// Some applications may want to recognize a sentence type
// without actually parsing any of the fields. Uncommenting
// this define will allow the nmeaMessage member to be set
// when *any* standard message is seen, even though that
// message is not enabled by a NMEAGPS_PARSE_xxx define above.
// No valid flags will be true for those sentences.
//#define NMEAGPS_RECOGNIZE_ALL
//------------------------------------------------------
// Sometimes, a little extra space is needed to parse an intermediate form.
// This config items enables extra space.
//#define NMEAGPS_PARSING_SCRATCHPAD
#endif

View File

@@ -0,0 +1,85 @@
#ifndef NEOGPS_CFG
#define NEOGPS_CFG
/**
* Enable/disable packed data structures.
*
* Enabling packed data structures will use two less-portable language
* features of GCC to reduce RAM requirements. Although it was expected to slightly increase execution time and code size, the reverse is true on 8-bit AVRs: the code is smaller and faster with packing enabled.
*
* Disabling packed data structures will be very portable to other
* platforms. NeoGPS configurations will use slightly more RAM, and on
* 8-bit AVRs, the speed is slightly slower, and the code is slightly
* larger. There may be no choice but to disable packing on processors
* that do not support packed structures.
*
* There may also be compiler-specific switches that affect packing and the
* code which accesses packed members. YMMV.
**/
#ifdef __AVR__
#define NEOGPS_PACKED_DATA
#endif
//------------------------------------------------------------------------
// Based on the above define, choose which set of packing macros should
// be used in the rest of the NeoGPS package. Do not change these defines.
#ifdef NEOGPS_PACKED_DATA
// This is for specifying the number of bits to be used for a
// member of a struct. Booleans are typically one bit.
#define NEOGPS_BF(b) :b
// This is for requesting the compiler to pack the struct or class members
// "as closely as possible". This is a compiler-dependent interpretation.
#define NEOGPS_PACKED __attribute__((packed))
#else
// Let the compiler do whatever it wants.
#define NEOGPS_PACKED
#define NEOGPS_BF(b)
#endif
/*
* Accommodate C++ compiler and IDE changes.
*
* Declaring constants as class data instead of instance data helps avoid
* collisions with #define names, and allows the compiler to perform more
* checks on their usage.
*
* Until C++ 10 and IDE 1.6.8, initialized class data constants
* were declared like this:
*
* static const <valued types> = <constant-value>;
*
* Now, non-simple types (e.g., float) must be declared as
*
* static constexpr <nonsimple-types> = <expression-treated-as-const>;
*
* The good news is that this allows the compiler to optimize out an
* expression that is "promised" to be "evaluatable" as a constant.
* The bad news is that it introduces a new language keyword, and the old
* code raises an error.
*
* TODO: Evaluate the requirement for the "static" keyword.
* TODO: Evaluate using a C++ version preprocessor symbol for the #if.
*
* The CONST_CLASS_DATA define will expand to the appropriate keywords.
*
*/
#if ARDUINO < 10606
#define CONST_CLASS_DATA static const
#else
#define CONST_CLASS_DATA static constexpr
#endif
#endif

View File

@@ -0,0 +1,5 @@
Acknowledgements
==========
Mikal Hart's [TinyGPS](https://github.com/mikalhart/TinyGPS) for the generic `decode` approach.
tht's [initial implementation](http://forum.arduino.cc/index.php?topic=150299.msg1863220#msg1863220) of a Cosa `IOStream::Device`.

View File

@@ -0,0 +1,64 @@
Character-oriented methods
===========================
Sometimes, individual characters or sentences must be processed or filtered, long before a fix structure is completed (i.e., `available()`). In this advanced technique, your sketch should read each character and pass it to the character-oriented method `gps.decode()`:
```
void loop()
{
while (serial.available()) {
char c = serial.read();
if (gps.decode( c ) == DECODE_COMPLETED) {
... do something with gps.fix()...
}
```
As `gps` decodes those bytes, it will gradually fill out the pieces of its internal fix structure, `gps.fix()` (members described [here](Data%20Model.md). When you want to use some of the fix data, you can access it like this:
```
Serial.print( gps.fix().latitude() );
Serial.print( ',' );
Serial.println( gps.fix().longitude() );
```
However, you must wait for the sentence to be completely decoded. You can't access `gps.fix()` unless you know that it is COMPLETED. You must copy it to another fix variable if you need to access it at any time.
**IMPORTANT:** `gps.fix()` **IS ONLY VALID WHEN:**
- `gps.decode()` just returned `DECODE_COMPLETED`, or
- `gps.is_safe()`
This is because `gps.fix().speed` may be half-formed. You must either do all your accessing immediately after `gps.decode()` returns `DECODE_COMPLETED`:
```
void loop()
{
// At this point of the code, speed could be half-decoded.
if (gps.fix().speed <= 5) // NOT A GOOD IDEA!
Serial.println( F("Too slow!") );
while (serial.available()) {
char c = serial.read();
if (gps.decode( serial.read() ) == NMEAGPS::DECODE_COMPLETED) {
// Access any piece of gps.fix() in here...
if (gps.fix().speed <= 5) // OK!
Serial.println( F("Too slow!") );
if (gps.fix().lat ...
}
}
```
Or you must call `gps.is_safe()` before using `gps.fix()`. It is safest to copy `gps.fix()` into your own variable for use at any time:
```
gps_fix my_fix;
void loop()
{
while (serial.available()) {
char c = serial.read();
if (gps.decode( serial.read() ) == NMEAGPS::DECODE_COMPLETED) {
my_fix = gps.fix(); // save for later...
}
}
if (my_fix.speed <= 5) // OK
DigitalWrite( UNDERSPEED_INDICATOR, HIGH );
```
Although the character-oriented program structure gives you a finer granularity of control, you must be more careful when accessing `gps.fix()`.

View File

@@ -0,0 +1,231 @@
# Choosing your configuration
There are only a few configurations provided by examples. If your application needs something slightly different, here is a general configuration process.
## What number do you want?
First, decide which data members of `gps_fix` and `NMEAGPS` you need (see [Data Model](Data Model.md) for member descriptions). Those members **must** be enabled in `GPSfix_cfg.h`.
Next, figure out what messages can fill out those members, because those messages **must** be enabled in `NMEAGPS_cfg.h`. Here is a table of the NMEA messages parsed by NeoGPS, and which data members they affect:
<table>
<tr>
<td><p align="right"><b>Message</b></p><p><b>Data Member</b></p></td>
<td><p>GGA</p><p><br></p></td>
<td><p>GLL</p><p><br></p></td>
<td><p>GSA</p><p><br></p></td>
<td><p>GST</p><p><br></p></td>
<td><p>GSV</p><p><br></p></td>
<td><p>RMC</p><p><br></p></td>
<td><p>VTG</p><p><br></p></td>
<td><p>ZDA</p><p><br></p></td>
<td><p align="center">PUBX<br>00<sup>1<sup></p><p><br></p></td>
<td><p align="center">PUBX<br>04<sup>1<sup></p><p><br></p></td>
</tr>
<tr><td><p><b>class gps_fix</b></p></td></tr>
<tr>
<td><p align="right">status</p></td>
<td>*</td>
<td>*</td>
<td>*</td>
<td> </td>
<td> </td>
<td>*</td>
<td>*</td>
<td> </td>
<td>*</td>
<td> </td>
</tr>
<tr>
<td><p align="right">date<sup>2</sup></p></td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td>*</td>
<td> </td>
<td>*</td>
<td> </td>
<td>*</td>
</tr>
<tr>
<td><p align="right">time<sup>2</sup></p></td>
<td>*</td>
<td>*</td>
<td> </td>
<td>*</td>
<td> </td>
<td>*</td>
<td> </td>
<td>*</td>
<td>*</td>
<td>*</td>
</tr>
<tr>
<td><p align="right">lat/lon</p></td>
<td>*</td>
<td>*</td>
<td> </td>
<td> </td>
<td> </td>
<td>*</td>
<td> </td>
<td> </td>
<td>*</td>
<td> </td>
</tr>
<tr>
<td><p align="right">altitude</p></td>
<td>*</td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td>*</td>
<td> </td>
</tr>
<tr>
<td><p align="right">speed</p></td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td>*</td>
<td>*</td>
<td> </td>
<td>*</td>
<td> </td>
</tr>
<tr>
<td><p align="right">heading</p></td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td>*</td>
<td>*</td>
<td> </td>
<td>*</td>
<td> </td>
</tr>
<tr>
<td><p align="right">lat, lon, alt error</p></td>
<td> </td>
<td> </td>
<td> </td>
<td>*</td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
</tr>
<tr>
<td><p align="right">satellites</p></td>
<td>*</td>
<td> </td>
<td>*</td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td>*</td>
<td> </td>
</tr>
<tr>
<td><p align="right">HDOP</p></td>
<td>*</td>
<td> </td>
<td>*</td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td>*</td>
<td> </td>
</tr>
<tr>
<td><p align="right">VDOP</p></td>
<td> </td>
<td> </td>
<td>*</td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td>*</td>
<td> </td>
</tr>
<tr>
<td><p align="right">PDOP</p></td>
<td> </td>
<td> </td>
<td>*</td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
</tr>
<tr>
<td><p align="right">TDOP</p></td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td>*</td>
<td> </td>
</tr>
<tr><td><p><b>class NMEAGPS</b></p></td></tr>
<tr>
<td><p align="right">satellite IDs</p></td>
<td> </td>
<td> </td>
<td>*</td>
<td> </td>
<td>*</td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
</tr>
<tr>
<td><p align="right">satellite azimuth,<br>&nbsp;&nbsp;elevation and<br>&nbsp;&nbsp;signal strength</p></td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td>*</td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
</tr>
</table>
This table illustrates the poor design of the NMEA message set: it requires multiple messages to deliver a complete fix (i.e., all members of `gps_fix`). This also explains why many manufacturers provide proprietary messages that *are* more complete. Above, you can see that the `$PUBX,00`<sup>1</sup> message contains all members except `date`.
While the manufacturer's specification will document all sentences supported for your device, you can also find general descriptions of many NMEA sentences [here](http://www.gpsinformation.org/dale/nmea.htm), [here](http://aprs.gids.nl/nmea/) or [here](http://www.catb.org/gpsd/NMEA.txt).
<hr>
<sub><sup>1</sup> The NMEA proprietary messages "PUBX" are only availble in the `ubloxNMEA` class. See [ublox-specific instructions](ublox.md) for adding this class to your configuration.</sub>
<sub><sup>2</sup> Date and time are both stored in one member of `gps_fix`, called `dateTime`. The `fix.dateTime` member is a C++ class that has both date-oriented members (Date, Month and Year) and time-oriented members (Hours, Minutes and Seconds). See [NeoTime.h](/src/NeoTime.h) for the complete description and capabilities of the `dateTime` member, such as date/time arithmetic and conversion to/from seconds since the epoch. Hundredths of a second are stored in a separate member of `gps_fix`, called `dateTime_cs`, and can also be accessed with the functions `dateTime_ms()` and `dateTime_us()`.</sub>

View File

@@ -0,0 +1,13 @@
Coherency
==========
Coherency guarantees that all members of a fix are from the same GPS time. For example, lat/long members may have been set by the newest sentence, but the altitude may be from the previous time interval. Most applications do not care that the fix members are not coherent. However, if you are controlling a drone or other autonomous vehicle, you may need coherency.
NeoGPS achieves coherency by detecting the "quiet" time between batches of sentences. When new data starts coming in, the fix will get emptied or initialized, and all new sentences will be accumulated in the internal fix.
If coherency is desired, **you must choose the correct LAST_SENTENCE_IN_INTERVAL.** If you're not sure which sentence is sent last (and therefore, when the quiet time begins), use NMEAorder.ino to analyze your GPS device.
You must also use **EXPLICIT_MERGING**. Implicit merging cannot be used with coherency is because a sentence has to be parsed to know its timestamp. If it were implicitly merged, the old data would not have been invalidated. Invalidating data from a previous update period must be performed _before_ the sentence parsing begins. That can only be accomplished with a second 'safe' copy of the fix data and explicit merging (i.e., FIX_MAX >= 1). With implicit merging, new data has already been mixed with old data by the time DECODE_COMPLETED occurs and timestamps can be checked.
When you have correctly chosen the LAST_SENTENCE_IN_INTERVAL *and* EXPLICIT_MERGING, the fix-oriented methods `available` and `read()` will return a coherent fix.
NOTE: If you use the [character-oriented methods](CharOriented.md) `decode`, `is_safe` and `fix()` to handle individual sentences, you must check `intervalComplete()` to know when the GPS update interval is completed, and the GPS quiet time has started.

View File

@@ -0,0 +1,260 @@
Configuration
=============
All configuration items are conditional compilations: a `#define` controls an `#if`/`#endif` section.
Comment out any items to be disabled or excluded from your build.
Where possible, checks are performed to verify that you have chosen a "valid"
configuration: you may see `#error` messages in the build log. See also [Choosing Your Configuration](Choosing.md) and [Troubleshooting](Troubleshooting.md).
# class gps_fix
The following configuration items are near the top of GPSfix_cfg.h:
```
// Enable/Disable individual parts of a fix, as parsed from fields of a $GPxxx sentence
#define GPS_FIX_DATE
#define GPS_FIX_TIME
#define GPS_FIX_LOCATION
#define GPS_FIX_LOCATION_DMS
#define GPS_FIX_ALTITUDE
#define GPS_FIX_SPEED
#define GPS_FIX_HEADING
#define GPS_FIX_SATELLITES
#define GPS_FIX_HDOP
#define GPS_FIX_VDOP
#define GPS_FIX_PDOP
#define GPS_FIX_LAT_ERR
#define GPS_FIX_LON_ERR
#define GPS_FIX_ALT_ERR
#define GPS_FIX_GEOID_HEIGHT
```
See the [Data Model](Data%20Model.md) page and `GPSfix.h` for the corresponding members that are enabled or disabled by these defines.
========================
# class NMEAGPS
The following configuration items are near the top of NMEAGPS_cfg.h.
#### Enable/Disable parsing the fields of a $GPxxx sentence
```
#define NMEAGPS_PARSE_GGA
#define NMEAGPS_PARSE_GLL
#define NMEAGPS_PARSE_GSA
#define NMEAGPS_PARSE_GSV
#define NMEAGPS_PARSE_GST
#define NMEAGPS_PARSE_RMC
#define NMEAGPS_PARSE_VTG
#define NMEAGPS_PARSE_ZDA
```
#### Select the last sentence in an update interval
This is used to determine when the GPS quiet time begins and when a batch of coherent sentences have been merged. It is crucial to know when fixes can be marked as available, and when you can perform some time-consuming operations.
```
#define LAST_SENTENCE_IN_INTERVAL NMEAGPS::NMEA_GLL
```
You can use `NMEAorder.ino` to determine the last sentence sent by your device.
#### Enable/Disable No, Implicit, Explicit Merging
If you want NO merging, comment out both defines. Otherwise, uncomment the IMPLICIT or EXPLICIT define.
```
#define NMEAGPS_EXPLICIT_MERGING
//#define NMEAGPS_IMPLICIT_MERGING
```
See [Merging](Merging.md) for more information.
#### Define the fix buffer size.
The NMEAGPS object will hold on to this many fixes before an overrun occurs. The buffered fixes can be obtained by calling `gps.read()`. You can specify zero, but you have to be sure to call `gps.read()` before the next sentence starts.
```
#define NMEAGPS_FIX_MAX 1
```
#### Enable/Disable interrupt-style processing
Define how fixes are dropped when the fix buffer is full.
```
#define NMEAGPS_KEEP_NEWEST_FIXES true
```
true = the oldest fix will be dropped, and the new fix will be saved.
false = the new fix will be dropped, and all old fixes will be saved.
#### Enable/Disable interrupt-style processing
If you are using one of the NeoXXSerial libraries to `attachInterrupt`, this must be uncommented to guarantee safe access to the buffered fixes with `gps.read()`. For normal polling-style processing, it must be commented out.
```
//#define NMEAGPS_INTERRUPT_PROCESSING
```
#### Enable/Disable the talker ID and manufacturer ID processing.
There are two kinds of NMEA sentences:
1. Standard NMEA sentences begin with "$ttccc", where
"tt" is the talker ID (e.g., GP), and
"ccc" is the variable-length sentence type (e.g., RMC).
For example, "$GPGLL,..." is a GLL sentence (Geographic Lat/Long)
transmitted by talker "GP". This is the most common talker ID. Some devices
may report "$GNGLL,..." when a mix of GPS and non-GPS satellites have been
used to determine the GLL data (e.g., GLONASS+GPS).
2. Proprietary NMEA sentences (i.e., those unique to a particular manufacturer)
begin with "$Pmmmccc", where
"P" is the NMEA-defined prefix indicator for proprietary messages,
"mmm" is the 3-character manufacturer ID, and
"ccc" is the variable-length sentence type (it can be empty).
No validation of manufacturer ID and talker ID is performed in this
base class. For example, although "GP" is a common talker ID, it is not
guaranteed to be transmitted by your particular device, and it IS NOT REQUIRED.
If you need validation of these IDs, or you need to use the extra information
provided by some devices, you have two independent options:
1. Enable SAVING the ID: When `decode` returns DECODE_COMPLETED, the `talker_id`
and/or `mfr_id` members will contain ID bytes. The entire sentence will be
parsed, perhaps modifying members of `fix`. You should enable one or both IDs
if you want the information in all sentences *and* you also want to know the ID
bytes. This adds 2 bytes of RAM for the talker ID, and 3 bytes of RAM for the
manufacturer ID.
2. Enable PARSING the ID: The virtual `parse_talker_id` and `parse_mfr_id` will
receive each ID character as it is received. If it is not a valid ID, return
`false` to abort processing the rest of the sentence. No CPU time will be wasted
on the invalid sentence, and no `fix` members will be modified. You should
enable this if you want to ignore some IDs. You must override `parse_talker_id`
and/or `parse_mfr_id` in a derived class.
```
#define NMEAGPS_SAVE_TALKER_ID
#define NMEAGPS_PARSE_TALKER_ID
#define NMEAGPS_SAVE_MFR_ID
#define NMEAGPS_PARSE_MFR_ID
```
#### Enable/Disable parsing of NMEA proprietary messages
If you are deriving a class from NMEAGPS to parse proprietary messages, you must uncomment this define:
```
//#define NMEAGPS_PARSE_PROPRIETARY
```
#### Enable/Disable tracking the current satellite array
You can also enable tracking the detailed information for each satellite, and how many satellites you want to track.
Although many GPS receivers claim to have 66 channels of tracking, 16 is usually the maximum number of satellites
tracked at any one time.
```
#define NMEAGPS_PARSE_SATELLITES
#define NMEAGPS_PARSE_SATELLITE_INFO
#define NMEAGPS_MAX_SATELLITES (20)
```
#### Enable/disable gathering interface statistics:
Uncommenting this define will allow counting the number of sentences and characters received and the number of checksum (CS) errors. If the CS errors are increasing, you could be losing characters or the connection could be noisy.
```
#define NMEAGPS_STATS
```
#### Enable/disable UTC sub-second resolution
Fixes will be received at approximately 1-second intervals, but there can
be 30ms *or more* of variance in those intervals. If you need to know the exact UTC time at *any* time,
with sub-second accuracy, you must calculate the
offset between the Arduino micros() clock and the UTC time in a received fix.
There are two ways to do this:
**1. When the GPS quiet time ends and the new update interval begins.**
The timestamp will be set when the first character (the '$') of
the new batch of sentences arrives from the GPS device. This is fairly
accurate, but it will be delayed from the PPS edge by the GPS device's
fix calculation time (usually ~100us). There is very little variance
in this calculation time (usually < 30us), so all timestamps are
delayed by a nearly-constant amount.
```
#define NMEAGPS_TIMESTAMP_FROM_INTERVAL
```
NOTE: At update rates higher than 1Hz, the updates may arrive with
some increasing variance.
**2. From the PPS pin of the GPS module.**
It is up to the application developer to decide how to capture that event. For example, you could:
a) simply poll for it in loop and call UTCsecondStart(micros());
b) use attachInterrupt to call a Pin Change Interrupt ISR to save
the micros() at the time of the interrupt (see NMEAGPS.h), or
c) connect the PPS to an Input Capture pin. Set the
associated TIMER frequency, calculate the elapsed time
since the PPS edge, and add that to the current micros().
```
#define NMEAGPS_TIMESTAMP_FROM_PPS
```
#### Enable/Disable derived types
Although normally disabled, this must be enabled if you derive any classes from NMEAGPS.
```
//#define NMEAGPS_DERIVED_TYPES
```
The ublox-specific files require this define (see [ublox](ublox.md) section).
#### Enable/Disable guaranteed comma field separator
Some devices may omit trailing commas at the end of some sentences. This may prevent the last field from being parsed correctly, because the parser for some types keep the value in an intermediate state until the complete field is received (e.g., parseDDDMM, parseFloat and parseZDA).
Enabling this will inject a simulated comma when the end of a sentence is received and the last field parser indicated that it still needs one.
```
//#define NMEAGPS_COMMA_NEEDED
```
#### Enable/Disable recognizing all sentence types
Some applications may want to recognize a sentence type without actually parsing any of the fields. Uncommenting this define will allow the `gps.nmeaMessage` member to be set when *any* standard message is seen, even though that message is not enabled by a `#defineNMEAGPS_PARSE_xxx`. No valid flags will be true for disabled sentences.
```
#define NMEAGPS_RECOGNIZE_ALL
```
#### Enable/Disable parsing scratchpad
Sometimes, a little extra space is needed to parse an intermediate form. This define enables extra space. Parsers that require the scratchpad can either `#ifdef` an `#error` when the scratchpad is disabled, or let the compiler generate an error when attempting to use the `scratchpad` union (see NMEAGPS.h).
```
//#define NMEAGPS_PARSING_SCRATCHPAD
```
========================
# ublox-specific configuration items
See the [ublox](ublox.md) section.
========================
# Floating-point output.
Streamers.cpp is used by the example programs for printing members of `fix()`. It is not required for parsing the GPS data stream, and this file may be deleted. It is an example of checking validity flags and formatting the various members of `fix()` for textual streams (e.g., Serial prints or SD writes).
Streamers.cpp has one configuration item:
```
#define USE_FLOAT
```
This is local to this file, and is only used by the example programs. This file is _not_ required unless you need to stream one of these types: bool, char, uint8_t, int16_t, uint16_t, int32_t, uint32_t, F() strings, `gps_fix` or `NMEAGPS`.
Most example programs have a choice for displaying fix information once per day. (Untested!)
```
#define PULSE_PER_DAY
```
========================
# Platforms
The following configuration items are near the top of NeoGPS_cfg.h.
#### Enable/Disable packed bitfields
```
#define NEOGPS_PACKED_DATA
```
8-bit Arduino platforms can address memory by bytes or words. This allows passing data by reference or
address, as long as it is one or more bytes in length. The `gps_fix` class has some members which are
only one bit; these members cannot be passed by reference or address, only by value. NeoGPS uses an
appropriate passing technique, depending on the size of these members.
32-bit Arduino platforms require *all* memory accesses to be 32-bit aligned, which precludes passing
bitfield, byte, or word members by reference or address. Rather than penalize the 8-bit platforms with
unpacked classes and structs, the `NEOGPS_PACKED_DATA` can be disabled on 32-bit platforms. This
increases the RAM requirements, but these platforms typically have more available RAM.
========================
# Typical configurations
A few common configurations are defined as follows
**Minimal**: no fix members, no messages (pulse-per-second only)
**DTL**: date, time, lat/lon, GPRMC messsage only.
**Nominal**: date, time, lat/lon, altitude, speed, heading, number of
satellites, HDOP, GPRMC and GPGGA messages.
**Full**: Nominal plus talker ID, VDOP, PDOP, lat/lon/alt errors, satellite array with satellite info, all messages, and parser statistics.
**PUBX**: Nominal fix items, standard sentences _disabled_, proprietary sentences PUBX 00 and 04 enabled, and required PARSE_MFR_ID and DERIVED_TYPES
These configurations are available in the [configs](/extras/configs) subdirectory.
========================
# Configurations of other libraries
**TinyGPS** uses the **Nominal** configuration + a second `fix`.
**TinyGPSPlus** uses the **Nominal** configuration + statistics + a second fix + timestamps for each `fix` member.
**Adafruit_GPS** uses the **Nominal** configuration + geoid height and IGNORES CHECKSUM!

View File

@@ -0,0 +1,253 @@
Data Model
==========
Rather than holding onto individual fields, the concept of a **fix** is used to group data members of the GPS acquisition into a C structure (a `struct` type called `gps_fix`). This also facilitates merging pieces received at different times (i.e., in separate sentences) into a single easy-to-use structure.
The main `NMEAGPS gps;` object you declare in your sketch parses received characters, gradually assembling a `fix`. Most programs will call `gps.read()` to obtain the completed fix structure (see [Usage](#Usage) below).
Given a variable declaration of type `gps_fix`:
```
gps_fix fix;
```
...this `fix` variable (or any other variable of type `gps_fix`) contains the following members:
* `fix.status`, a status code
* `enum` values STATUS_NONE, STATUS_EST, STATUS_TIME_ONLY, STATUS_STD or STATUS_DGPS
* a [location](Location.md) structure (i.e., latitude and longitude), accessed with
* `fix.latitudeL()` and `fix.longitudeL()` for the higher-precision integer degrees, scaled by 10,000,000 (10 significant digits)
* `fix.latitude()` and `fix.longitude()` for the lower-precision floating-point degrees (~7 significant digits)
* NOTE: these lat/lon values are
* positive for North or East degrees and negative for South or West degrees.
* stored in a 'fix.location' structure, like a 2D coordinate. The `location_t` class provides additional methods for distance, bearing and offset calculations, as described [here](Location.md).
* `fix.latitudeDMS` and `fix.latitudeDMS` are structures (see DMS.h) that each contain
* `fix.longitudeDMS.degrees` in integer degrees
* `fix.latitudeDMS.degrees`, in integer minutes
* `fix.longitudeDMS.seconds_whole`, in integer seconds
* `fix.latitudeDMS.seconds_frac`, in integer thousandths of a second
* `fix.latitudeDMS.secondsF()`, in floating-point seconds
* hemisphere indicator, accessed with
* `fix.longitudeDMS.hemisphere` (enum values NORTH_H, SOUTH_H, EAST_H or WEST_H)
* `fix.longitudeDMS.EW()` (char values `E` or `W`)
* `fix.latitudeDMS.NS()` (char values `N` or `S`)
* NOTE: An integer degree value (scaled by 10<sup>7</sup> can be used to set the DMS structure by using `fix.latitudeDMS.From( otherLatitude );`
* an altitude (above ellipsoid, not Mean Sea Level), accessed with
* `fix.altitude_cm()`, in integer centimeters
* `fix.altitude()`, in floating-point meters
* `fix.alt.whole`, in integer meters
* `fix.alt.frac`, in integer centimeters, to be added to the whole part
* a speed, accessed with
* `fix.speed_kph()`, in floating-point kilometers per hour
* `fix.speed_mph()`, in floating-point miles per hour
* `fix.speed()`, in floating-point knots (nautical miles per hour)
* `fix.speed_mkn()`, in integer knots, scaled by 1000
* `fix.spd.whole`, in integer knots
* `fix.spd.frac`, in integer thousandths of a knot, to be added to the whole part
* a heading, accessed with
* `fix.heading_cd()`, in integer hundredths of a degree
* `fix.heading()`, in floating-point degrees
* `fix.hdop`, `fix.vdop` and `fix.pdop`, in integer thousandths of the DOP.
* [Dilution of Precision](https://en.wikipedia.org/wiki/Dilution_of_precision_(navigation)) is a unitless measure of the current satellite constellation geometry WRT how 'good' it is for determining a position. This is _independent_ of signal strength and many other factors that may be internal to the receiver. &nbsp;&nbsp;**It cannot be used to determine position accuracy in meters.** Instead, use the LAT/LON/ALT error in cm members, which are populated by GST sentences.
* latitude, longitude and altitude error, accessed with
* `fix.lat_err_cm`, `fix.lon_err_cm` and `fix.alt_err_cm`, in integer centimeters
* `fix.lat_err()`, `fix.lon_err()` and `fix.alt_err()`, in floating-point meters
* geoid height above ellipsoid (see [here](https://en.wikipedia.org/wiki/Geoid) for description), accessed with
* `fix.geoidHeight_cm`, in integer centimeters
* `fix.geoidHeight()`, in floating-point meters
* `fix.geoidHt.whole`, in integer meters
* `fix.geoidHt.frac`, in integer centimeters to be added to the whole part
* `fix.satellites`, a satellite count
* a date/time structure (see [Time.h](/src/Time.h)), accessed with
* `fix.dateTime.year`,
* `fix.dateTime.month`,
* `fix.dateTime.date`, the day-of-month,
* `fix.dateTime.hours`,
* `fix.dateTime.minutes`,
* `fix.dateTime.seconds`, and
* `fix.dateTime.day`, the day-of-the-week. This member is expensive to calculate, so it is *uninitialized* until you call the `set_day()` method. If you need the day-of-the-week, be sure to call `set_day` whenever the `year`, `month` or `date` members are changed. In general, call `fix.dateTime.set_day()` whenever `fix` is assigned (e.g., `fix = gps.read()`).<br><br>
`Time` operations allow converting to and from total seconds offset from a *de facto* starting time (e.g., an epoch date/time "origin"). There are constants in Time.h for NTP, POSIX and Y2K epochs. Simply change the `static` members `s_epoch_year` and `s_epoch_weekday` in Time.h, and all date/time operations will be based on that epoch. This does not affect GPS times, but it will allow you to easily convert a GPS time to/from an NTP or POSIX time value (seconds).<br><br>
The [NMEAtimezone.ino](/examples/NMEAtimezone/NMEAtimezone.ino) example program shows how to convert the GPS time (UTC) into a local time. Basically, a `Time` structure is converted to seconds (from the epoch start), then the time zone offset *in seconds* is added, and then the offset seconds are converted back to a time structure, with corrected day, month, year, hours and minutes members.
* `fix.dateTime_cs`, in integer hundredths of a second
* `fix.dateTime_ms()`, in milliseconds
* `fix.dateTime_us()`, in microseconds
* a collection of boolean `valid` flags for each of the above members, accessed with
* `fix.valid.status`
* `fix.valid.date` for year, month, day-of-month
* `fix.valid.time` for hours, minutes, seconds and centiseconds
* `fix.valid.location` for latitude and longitude
* `fix.valid.altitude`
* `fix.valid.speed`
* `fix.valid.heading`
* `fix.valid.hdop`, `fix.valid.vdop` and `fix.valid.hpop`
* `fix.valid.lat_err`, `fix.valid.lon_err` and `fix.valid.alt_err`
* `fix.valid.geoidHeight`
## Validity
Because the GPS device may *not* have a fix, each member of a `gps_fix` can be marked as valid or invalid. That is, the GPS device may not know the lat/long yet. To check whether the fix member has been received, test the corresponding `valid` flag (described above). For example, to check if lat/long data has been received:
```
if (my_fix.valid.location) {
Serial.print( my_fix.latitude() );
Serial.print( ',' );
Serial.println( my_fix.longitude() );
}
```
You should also know that, even though you have enabled a particular member (see [GPSfix_cfg.h](/src/GPSfix_cfg.h)), it **may not have a value** until the related NMEA sentence sets it. And if you have not enabled that sentence for parsing in `NMEAGPS_cfg.h`, it will **never** be valid.
## Other GPS-related information
There is additional information that is not related to a fix. Instead, it contains information about parsing or a [**G**lobal **N**avigation **S**atellite **S**ystem](https://en.wikipedia.org/wiki/Satellite_navigation). GNSS's currently include GPS (US), GLONASS (Russia), Beidou (China) and Galileo (EU). The main `NMEAGPS gps` object you declare in your sketch contains:
* `gps.UTCsecondStart()`, the Arduino `micros()` value when the current UTC second started
* `gps.UTCms()`, the number of milliseconds since the last received UTC time, calculated from `micros()` and `gps.UTCsecondStart`.
* `gps.UTCus()`, the number of microseconds since the last received UTC time, calculated from `micros()` and `gps.UTCsecondStart`.
* `gps.nmeaMessage`, the latest received message type. This is an ephemeral value, because multiple sentences are merged into one `fix` structure. If you only check this after a complete fix is received, you will only see the LAST_SENTENCE_IN_INTERVAL.
* enum values NMEA_GLL, NMEA_GSA, NMEA_GST, NMEA_GSV, NMEA_RMC, NMEA_VTG or NMEA_ZDA
* `gps.satellies[]`, an array of satellite-specific information, where each element contains
* `gps.satellies[i].id`, satellite ID
* `gps.satellies[i].elevation`, satellite elevation in 0-90 integer degrees
* `gps.satellies[i].azimuth`, satellite azimuth in 0-359 integer degrees
* `gps.satellies[i].snr`, satellite signal-to-noise ratio in 0-99 integer dBHz
* `gps.satellies[i].tracked`, satellite being tracked flag, a boolean
* `gps.talker_id[]`, talker ID, a two-character array (not NUL-terminated)
* `gps.mfr_id[]`, manufacturer ID, a three-character array (not NUL-terminated)
* an internal fix structure, `gps.fix()`. Most sketches **should not** use `gps.fix()` directly!
## Usage
First, declare an instance of `NMEAGPS`:
```
NMEAGPS gps;
```
Next, tell the `gps` object to handle any available characters on the serial port:
```
void loop()
{
while (gps.available( gps_port )) {
```
The `gps` object will check if there are any characters available, and if so, read them from the port and parse them into its internal fix. Many characters will have to be read before the current fix is complete, so `gps.available` will return `false` until the fix is complete; the body of `while` loop will be skipped many times, and the rest of `loop()` will be executed.
When a fix is finally completed, `gps.available` will return `true`. Now your sketch can "read" the completed fix structure from the `gps` object:
```
void loop()
{
while (gps.available( gps_port )) {
gps_fix fix = gps.read();
```
The local `fix` variable now contains all the GPS fields that were parsed from the `gps_port`. You can access them as described above:
```
void loop()
{
while (gps.available( gps_port )) {
gps_fix fix = gps.read();
if (fix.valid.time) {
...
```
Note that the `fix` variable is local to that `while` loop; it cannot be accessed elsewhere in your sketch. If you need to access the fix information elsewhere, you must declare a global fix variable:
```
gps_fix currentFix;
void loop()
{
while (gps.available( gps_port )) {
currentFix = gps.read();
if (currentFix.valid.time) {
...
```
Any part of your sketch can use the information in `currentFix`.
Please note that the fix structure is much smaller than the raw character data (sentences). A fix is nominally 1/4 the size of one sentence (~30 bytes vs ~120 bytes). If two sentences are sent during each update interval, a fix could be 1/8 the size required for buffering two sentences.
In this fix-oriented program structure, the methods `gps.available` and `gps.read` are manipulating entire `gps_fix` structures. Multiple characters and sentences are used internally to fill out a single fix: members are "merged" from sentences into one fix structure (described [here](Merging.md)).
That program structure is very similar to the typical serial port reading loop:
```
void loop()
{
while (serial.available()) {
char c = serial.read();
... do something with the character ...;
}
```
However, the fix-oriented methods operate on complete *fixes*, not individual characters, fields or sentences.
Note: If you find that you need to filter or merge data with a finer level of control, you may need to use a different [Merging option](Merging.md), [Coherency](Coherency.md), or the more-advanced [Character-Oriented methods](/doc/CharOriented.md).
## Examples
Some examples of accessing fix values:
```
gps_fix fix_copy = gps.read();
int32_t lat_10e7 = fix_copy.lat; // scaled integer value of latitude
float lat = fix_copy.latitude(); // float value of latitude
Serial.print( fix_copy.latDMS.degrees );
Serial.print( ' ' );
Serial.print( fix_copy.latDMS.minutes );
Serial.print( F("' " );
Serial.print( fix_copy.latDMS.seconds );
if (fix_copy.dateTime.month == 4) // test for the cruelest month
cry();
// Count how satellites are being received for each GNSS
for (uint8_t i=0; i < gps.sat_count; i++) {
if (gps.satellites[i].tracked) {
if (gps.satellites[i] . id <= 32)
GPS_satellites++;
if (gps.satellites[i] . id <= 64)
SBAS_satellites++;
if (gps.satellites[i] . id <= 96)
GLONASS_satellites++;
}
}
```
And some examples of accessing valid flags in a `fix` structure:
```
if (fix_copy.valid.location)
// we have a lat/long!
if (fix_copy.valid.time)
// the copy has hours, minutes and seconds
```
Here's an example for accessing the altitude
```
if (fix_copy.valid.altitude) {
z2 = fix_copy.altitude_cm();
vz = (z2 - z1) / dt;
z1 = z2;
// Note: if you only care about meters, you could also do this:
// z = fix_copy.alt.whole;
}
```
You can also check a collection of flags before performing a calculation involving
multiple members:
```
if (fix_copy.valid.altitude && fix_copy.valid.date && fix_copy.valid.time) {
dt = (clock_t) fix_copy.dateTime - (clock_t) fix_copy.dateTime;
dz = fix_copy.alt.whole - last_alt; // meters
vz = dz / dt; // meters per second vertical velocity
}
```
Bonus: The compiler will optimize this into a single bit mask operation.
The example printing utility file, [Streamers.cpp](/src/Streamers.cpp#L100) shows how to access each fix member and print its value.
## Options
Except for `status`, each of these `gps_fix` members is conditionally compiled; any, all, or *no* members can be selected for parsing, storing and merging. This allows you to configuring NeoGPS to use the minimum amount of RAM for the particular members of interest. See [Configurations](Configurations.md) for how to edit [GPSfix_cfg.h](/src/GPSfix_cfg.h) and [NMEAGPS_cfg.h](/src/NMEAGPS_cfg.h#L67), respectively.
## Precision
Integers are used for all members, retaining full precision of the original data.
```
gps_fix fix = gps.read();
if (fix.valid.location) {
// 32-bit ints have 10 significant digits, so you can detect very
// small changes in position:
d_lat = fix_copy.lat - last_lat;
}
```
Optional floating-point accessors are provided for many members.
```
if (fix_copy.valid.location) {
float lat = fix_copy.latitude();
// floats only have about 6 significant digits, so this
// computation is useless for detecting small movements:
foobar = (lat - target_lat);
```

View File

@@ -0,0 +1,168 @@
Examples
======
In an attempt to be reusable in a variety of different programming styles, this library supports:
* sync or async operation (main `loop()` vs interrupt processing)
* fused or not fused (multiple reports into one fix)
* optional buffering of fixes
* optional floating point
* configurable message sets, including hooks for implementing proprietary NMEA messages
* configurable message fields
* multiple protocols from same device
These example programs demonstrate how to use the classes in the different programming styles:
* [NMEA](/examples/NMEA/NMEA.ino) - sync, single fix, standard NMEA only (RMC sentence only)
* [NMEA_isr](/examples/NMEA_isr/NMEA_isr.ino) - **async**, single fix, standard NMEA only (RMC sentence only)
* [NMEAblink](/examples/NMEAblink/NMEAblink.ino) - sync, single fix, standard NMEA only, minimal example, only blinks LED
* [NMEAloc](/examples/NMEAloc/NMEAloc.ino) - sync, single fix, minimal example using only standard NMEA RMC sentence
* [NMEAlocDMS](/examples/NMEAlocDMS/NMEAlocDMS.ino) - same as NMEAloc.ino, but displays location in Degrees, Minutes and Seconds
* [NMEAaverage](/examples/NMEAaverage/NMEAaverage.ino) - sync, single fix, averages a high-accuracy location over time
* [NMEAtimezone](/examples/NMEAtimezone/NMEAtimezone.ino) - same as NMEAloc.ino, but displays local time instead of UTC (GMT)
* [NMEASDlog](/examples/NMEASDlog/NMEASDlog.ino) - **async**, buffered fixes, standard NMEA only (RMC sentence only), logging to SD card
* [PUBX](/examples/PUBX/PUBX.ino) - sync, coherent fix, standard NMEA + ublox proprietary NMEA
* [ublox](/examples/ublox/ublox.ino) - sync or **async**, coherent fix, ublox binary protocol UBX
Preprocessor symbol `USE_FLOAT` can be used in [Streamers.cpp](/src/Streamers.cpp) to select integer or floating-point output.
See also important information in `NMEAorder.ino` below, and the [Installing](Installing.md), [Configurations](Configurations.md) and [Troubleshooting](Troubleshooting.md) sections.
##Diagnostics
Several programs are provided to help diagnose GPS device problems:
### Connection and Baud Rate
* [NMEAdiagnostic](/examples/NMEAdiagnostic/NMEAdiagnostic.ino)
This program listens for sentences and, if none are detected, tries a different baud rate. When sentences are detected, the correct baud rate is displayed. The received data may help you determine the problem (e.g., dropped characters or binary protocol).
See the [Troubleshooting](Troubleshooting.md) section for more details.
### Sentence order
* [NMEAorder](/examples/NMEAorder/NMEAorder.ino)
This program determines the order of NMEA sentences sent during each 1-second interval:
```
NMEAorder.INO: started
fix object size = 44
NMEAGPS object size = 72
Looking for GPS device on Serial1
.....................
Sentence order in each 1-second interval:
RMC
VTG
GGA
GSA
GSV
GSV
GSV
GSV
GLL
```
The last sentence is of particular interest, as it is used to determine when the quiet time begins. All example programs **depend** on knowing the last sentence (see [Quiet Time Interval](Troubleshooting#quiet-time-interval)).
### Self-test Program
* [NMEAtest](/examples/NMEAtest/NMEAtest.ino)
For this program, **No GPS device is required**. Test bytes are streamed from PROGMEM character arrays. Various strings are passed to `decode` and the expected pass or fail results are displayed. If **NeoGPS** is correctly configured, you should see this on your SerialMonitor:
```
NMEA test: started
fix object size = 44
NMEAGPS object size = 72
Test string length = 75
PASSED 11 tests.
------ Samples ------
Results format:
Status,UTC Date/Time,Lat,Lon,Hdg,Spd,Alt,HDOP,VDOP,PDOP,Lat err,Lon err,Alt err,Sats,[sat],
Input: $GPGGA,092725.00,4717.11399,N,00833.91590,E,1,8,1.01,499.6,M,48.0,M,,0*5B
Results: 3,2000-01-01 09:27:25.00,472852332,85652650,,,49960,1010,,,,,,8,[],
Input: $GPRMC,092725.00,A,2520.69213,S,13101.94948,E,0.004,77.52,091202,,,A*43
Results: 3,2002-12-09 09:27:25.00,-253448688,1310324913,7752,4,,,,,,,,,[],
Input: $GPRMC,162254.00,A,3647.6643,N,8957.5193,W,0.820,188.36,110706,,,A*49
Results: 3,2006-07-11 16:22:54.00,367944050,-899586550,18836,820,,,,,,,,,[],
Input: $GPRMC,235959.99,A,2149.65726,N,16014.69256,W,8.690,359.99,051015,9.47,E,A*26
Results: 3,2015-10-05 23:59:59.99,218276210,-1602448760,35999,8690,,,,,,,,,[],
Input: $GNGLL,0105.60764,S,03701.70233,E,225627.00,A,A*6B
Results: 3,2000-01-01 22:56:27.00,-10934607,370283722,,,,,,,,,,,[],
Input: $GPGGA,064951.000,2307.1256,N,12016.4438,E,1,8,0.95,39.9,M,17.8,M,,*63
Results: 3,2000-01-01 06:49:51.00,231187600,1202740633,,,3990,950,,,,,,8,[],
Input: $GPRMC,064951.000,A,2307.1256,N,12016.4438,E,0.03,165.48,260406,3.05,W,A*2C
Results: 3,2006-04-26 06:49:51.00,231187600,1202740633,16548,30,,,,,,,,,[],
Input: $GPVTG,165.48,T,,M,0.03,N,0.06,K,A*36
Results: 3,,,,16548,30,,,,,,,,,[],
Input: $GPGSA,A,3,29,21,26,15,18,09,06,10,,,,,2.32,0.95,2.11*00
Results: 3,,,,,,,950,,2320,,,,,[],
Input: $GPGSV,3,1,09,29,36,029,42,21,46,314,43,26,44,020,43,15,21,321,39*7D
Results: ,,,,,,,,,,,,,9,[29,21,26,15,],
Input: $GPGSV,3,2,09,18,26,314,40,09,57,170,44,06,20,229,37,10,26,084,37*77
Results: ,,,,,,,,,,,,,9,[29,21,26,15,18,9,6,10,],
Input: $GPGSV,3,3,09,07,,,26*73
Results: ,,,,,,,,,,,,,9,[29,21,26,15,18,9,6,10,7,],
Input: $GNGST,082356.00,1.8,,,,1.7,1.3,2.2*60
Results: ,2000-01-01 08:23:56.00,,,,,,,,,170,130,,,[29,21,26,15,18,9,6,10,7,],
Input: $GNRMC,083559.00,A,4717.11437,N,00833.91522,E,0.004,77.52,091202,,,A,V*33
Results: 3,2002-12-09 08:35:59.00,472852395,85652537,7752,4,,,,,,,,,[29,21,26,15,18,9,6,10,7,],
Input: $GNGGA,092725.00,4717.11399,N,00833.91590,E,1,08,1.01,499.6,M,48.0,M,,*45
Results: 3,2000-01-01 09:27:25.00,472852332,85652650,,,49960,1010,,,,,,8,[29,21,26,15,18,9,6,10,7,],
Input: $GLZDA,225627.00,21,09,2015,00,00*70
Results: ,2015-09-21 22:56:27.00,,,,,,,,,,,,,[29,21,26,15,18,9,6,10,7,],
--- floating point conversion tests ---
Input: $GPGGA,092725.00,3242.9000,N,11705.8169,W,1,8,1.01,499.6,M,48.0,M,,0*49
Results: 3,2000-01-01 09:27:25.00,327150000,-1170969483,,,49960,1010,,,,,,8,[29,21,26,15,18,9,6,10,7,],
Input: $GPGGA,092725.00,3242.9000,N,11705.8170,W,1,8,1.01,499.6,M,48.0,M,,0*41
Results: 3,2000-01-01 09:27:25.00,327150000,-1170969500,,,49960,1010,,,,,,8,[29,21,26,15,18,9,6,10,7,],
Input: $GPGGA,092725.00,3242.9000,N,11705.8171,W,1,8,1.01,499.6,M,48.0,M,,0*40
Results: 3,2000-01-01 09:27:25.00,327150000,-1170969517,,,49960,1010,,,,,,8,[29,21,26,15,18,9,6,10,7,],
Input: $GPGGA,092725.00,3242.9000,N,11705.8172,W,1,8,1.01,499.6,M,48.0,M,,0*43
Results: 3,2000-01-01 09:27:25.00,327150000,-1170969533,,,49960,1010,,,,,,8,[29,21,26,15,18,9,6,10,7,],
Input: $GPGGA,092725.00,3242.9000,N,11705.8173,W,1,8,1.01,499.6,M,48.0,M,,0*42
Results: 3,2000-01-01 09:27:25.00,327150000,-1170969550,,,49960,1010,,,,,,8,[29,21,26,15,18,9,6,10,7,],
Input: $GPGGA,092725.00,3242.9000,N,11705.8174,W,1,8,1.01,499.6,M,48.0,M,,0*45
Results: 3,2000-01-01 09:27:25.00,327150000,-1170969567,,,49960,1010,,,,,,8,[29,21,26,15,18,9,6,10,7,],
Input: $GPGGA,092725.00,3242.9000,N,11705.8175,W,1,8,1.01,499.6,M,48.0,M,,0*44
Results: 3,2000-01-01 09:27:25.00,327150000,-1170969583,,,49960,1010,,,,,,8,[29,21,26,15,18,9,6,10,7,],
Input: $GPGGA,092725.00,3242.9000,N,11705.8176,W,1,8,1.01,499.6,M,48.0,M,,0*47
Results: 3,2000-01-01 09:27:25.00,327150000,-1170969600,,,49960,1010,,,,,,8,[29,21,26,15,18,9,6,10,7,],
```
### Benchmark
* [NMEAbenchmark](/examples/NMEAbenchmark/NMEAbenchmark.ino)
For this program, **No GPS device is required**. GGA, RMC and GSV sentences can be tested. Bytes are streamed from PROGMEM character arrays and parsed to determine execution times. All times are displayed in microseconds:
```
NMEAbenchmark: started
fix object size = 22
NMEAGPS object size = 29
GGA time = 844
GGA no lat time = 497
```

View File

@@ -0,0 +1,50 @@
Extending NeoGPS
=========
Using features that are unique to your device fall into three categories:
####1. Configuring the device with special commands
Many devices allow you to configure which standard messages are emitted, or the rate at which they are emitted. It may be as simple as sending a proprietary command to the device. Simply use the NMEAGPS `send` or `send_P` method.
For example, to set the baudrate of the ublox NEO-6M gps device, send it a
`UBX,41` message:
```
gps.send_P( F("PUBX,41,1,0007,0003,19200,0") );
```
####2. Parsing additional message types
Some devices provide additional messages with extra information, or more efficient groupings. This will require deriving a class from `NMEAGPS`. The derived class needs to
* declare a PROGMEM table of the new message types,
* point that table back to the NMEAGPS table
* override the `parseField` method to extract information from each new message type
Please see ubxNMEA.h and .cpp for an example of adding two ublox-proprietary messages.
####3. Handling new protocols
Some devices provide additional protocols. They are frequently binary, which requires
fewer bytes than NMEA 0183. Because they can both be transmitted on the same port, it is
very likely that they can be distinguished at the message framing level.
For example, NMEA messages always start with a '$' and end with CR/LF. ublox messages start
with 0xB5 and 0x62 bytes, a message class and id, and a 2-byte message length. There is no
terminating character; the message completed when `length` bytes have been received.
This will require deriving a class from `NMEAGPS`. The derived class needs
to
* define new `rxState` values for the protocol state machine. These should
be unique from the NMEA state values, but they should share the IDLE state
value.
* override the `decode` method to watch for its messages. As bytes are
received, it may transition out of the IDLE state and into its own set of
state values. If the character is not valid for this protocol, it should
delegate it to the NMEAGPS base clase, which may begin processing an NMEAGPS
message. If the rxState is not one of the derived states (i.e., it is in
one of the NMEAGPS states), the character should be delegated to
NMEAGPS::decode.
* implement something like the `parseField` method if parse-in-place
behavior is desirable. This is not necessarily `virtual`, as it will only
be called from the derived `decode`.
* You are free to add methods and data members as required for handling the
protocol. Only `decode` must be overridden.
Please see ubxGPS.h and .cpp for an example of implementing the
ublox-proprietary protocol, UBX. The derived `ubloxGPS` class provides both
parse-in-place *and* buffered messages. See the `send` and `poll` methods.

View File

@@ -0,0 +1,196 @@
Installing
==========
1. [Download the library](#1-download-the-library)
2. [Choose a serial port](#2-choose-a-serial-port)
3. [Connect the GPS device](#3-connect-the-gps-device)
4. [Review `GPSport.h`](#4-review-librariesneogpssrcgpsporth)
5. [Open the example](#5--open-the-example-sketch-nmeaino)
6. [Build and upload](#6--build-and-upload-the-sketch-to-your-arduino)
<hr>
### 1. Download the library
It is easiest to use the [Ardino IDE Library Manager](https://www.arduino.cc/en/Guide/Libraries#toc3) to automatically download and install NeoGPS. Select the menu **Sketch -> Include Library -> Manage Libraries**. Then type "NeoGPS" in the Search box.
If you need to perform a manual installation,:
* Download the [master ZIP file](https://github.com/SlashDevin/NeoGPS/archive/master.zip).
* Open the zip file and open the nested `NeoGPS-master` subdirectory.
* Select and copy all files in the `NeoGPS-master` subdirectory into a new `Arduino/Libraries/NeoGPS` directory, like most libraries. The `Arduino/Libraries/NeoGPS` directory should contain:<br>
```
extras
examples
src
library.properties
LICENSE
README.md
```
<hr>
### 2. Choose a serial port
**BEST**: The fastest, most reliable way to connect a GPS device is to use a HardwareSerial port.
On any Arduino board, you can connect the GPS device to the `Serial` pins (0 & 1). You can still print debug statements, and they will show up on the Serial Monitor window. The received GPS characters will not interfere with those prints, and you will not see those characters on the Serial Monitor window.
However, when you need to upload a new sketch to the Arduino over USB, **you must disconnect the GPS TX from the Arduino RX pin 0.** Otherwise, the GPS characters will interfere with the upload data. Some people put a switch in that connection to make it easy to upload without disturbing the wires.
For Mega, Due and Teensy boards, you can connect the GPS device to the `Serial1`, `Serial2` or `Serial3` pins.
For Micro and Leo (and other 32U4-based Arduinos), you can connect the GPS device to the `Serial1` pins.
**2nd Best**: If you can't connect the GPS device to a `HardwareSerial` port, you should download and install the [AltSoftSerial](https://github.com/PaulStoffregen/AltSoftSerial) or [NeoICSerial](https://github.com/SlashDevin/NeoICSerial) library. These libraries only work on two specific pins (8 & 9 on an UNO). This library is very efficient and reliable. It uses one of the hardware TIMERs, so it may conflict with libraries that use TIMERs or PWM output (e.g., servo).
**3rd Best**: If you can't use the pins required by `AltSoftSerial`, and your GPS device runs at 9600, 19200 or 38400 baud, you should download and install the [NeoSWSerial](https://github.com/SlashDevin/NeoSWSerial) library. This library is almost as efficient. It will help you avoid common timing problems caused by `SoftwareSerial`. It does not need an extra TIMER, so it can be used with most other libraries. It does use Pin Change Interrupts, but there is an option in the header file that allows you to coordinate other PCI usage with `NeoSWSerial`.
`NeoSWSerial` can be used with `AltSoftSerial` at the same time, allowing your sketch to have two extra serial ports.
**WORST**: `SoftwareSerial` is NOT RECOMMENDED, because it disables interrupts for long periods of time. This can interfere with other parts of your sketch, or with other libraries. It cannot transmit and receive at the same time, and your sketch can only receive from one `SoftwareSerial` instance at time.
<hr>
### 3. Connect the GPS device
Most GPS devices are 3.3V devices, and most Arduinos are 5V devices. Although many GPS modules are described as "3V & 5V compatible",
<p align=center><b>YOU SHOULD NOT CONNECT A 5V ARDUINO TRANSMIT PIN TO THE 3.3V GPS RX PIN</b></p>
This can damage the device, cause overheating, system power problems or decrease the lifetime of battery-operated systems. You must level-shift this connection with inexpensive level-shifting modules (best) or a resistor divider.
Connecting the 3.3V GPS TX pin to a 5V Arduino receive pin will not damage the GPS device, but it may not be reliable. This is because the GPS TX voltage is slightly lower than what the Arduino requires. It works in many situations, but if you are not able to receive GPS characters reliably, you probably need to use a level-shifting module (best) or a diode+resistor to "pull up" the GPS TX pin voltage.
<hr>
### 4. Review `Libraries/NeoGPS/src/GPSport.h`
This file declares a the serial port to be used for the GPS device. You can either:
* Use the default `GPSport.h` and connect your GPS device according to what it chooses; or
* Replace the entire contents of `GPSport.h` and insert your own declarations (see below and comments in `GPSport.h`).
#### Default choices for GPSport.h
By default, Mega, Leonardo, Due, Zero/MKR1000 and Teensy boards will use `Serial1`.
All other Boards will use [AltSoftSerial](https://github.com/PaulStoffregen/AltSoftSerial) on two specific pins (see table at linked page).
If you want to use a different serial port library (review step 2 above), you must edit these `#include` lines in `GPSport.h`:
```
//#include <NeoHWSerial.h> // NeoSerial or NeoSerial1 for INTERRUPT_PROCESSING
#include <AltSoftSerial.h> // <-- DEFAULT. Two specific pins required (see docs)
//#include <NeoICSerial.h> // AltSoftSerial with Interrupt-style processing
//#include <NeoSWSerial.h> // Any pins, only @ 9600, 19200 or 38400 baud
//#include <SoftwareSerial.h> // NOT RECOMMENDED!
```
Uncomment **one** of those include statements, and it will use that library for the GPS serial port.
If you uncomment the `NeoSWSerial.h` include, pins 3 and 4 will be used for the GPS. If your GPS is on different pins, you must edit these `#define` lines in `GPSport.h`:
#define RX_PIN 4
#define TX_PIN 3
#### Choosing your own serial port
If you know what serial port you want to use, you can **REPLACE EVERYTHING** in `GPSport.h' with the three declarations that are used by all example programs:
1. the `gpsPort` variable **(include its library header if needed)**;
2. the double-quoted C string for the `GPS_PORT_NAME` (displayed by all example programs); and
3. the `DEBUG_PORT` to use for Serial Monitor print messages (usually `Serial`).
All the example programs can use any of the following serial port types:
* HardwareSerial (built-in `Serial`, `Serial1` et al. STRONGLY recommended)
* [AltSoftSerial](https://github.com/PaulStoffregen/AltSoftSerial) **DEFAULT** (only works on one specific Input Capture pin)
* [NeoICSerial](https://github.com/SlashDevin/NeoICSerial) (only works on one specific Input Capture pin)
* [NeoHWSerial](https://github.com/SlashDevin/NeoHWSerial) (required for NMEA_isr and NMEASDlog on built-in serial ports)
* [NeoSWSerial](https://github.com/SlashDevin/NeoSWSerial) (works on most pins)
* SoftwareSerial (built-in, NOT recommended)
Be sure to download the library you have selected (NOTE: `HardwareSerial` and `SoftwareSerial` are pre-installed by the Arduino IDE and do not need to be downloaded).
For example, to make all examples use `Serial` for the GPS port **and** for Serial Monitor messages, `GPSport.h` should contain just these 3 declarations:
```
#ifndef GPSport_h
#define GPSport_h
#define gpsPort Serial
#define GPS_PORT_NAME "Serial"
#define DEBUG_PORT Serial
#endif
```
Or, to make all examples use `AltSoftSerial` for the GPS port and `Serial` for Serial Monitor messages, `GPSport.h` should contain just these statements:
```
#ifndef GPSport_h
#define GPSport_h
#include <AltSoftSerial.h>
AltSoftSerial gpsPort;
#define GPS_PORT_NAME "AltSoftSerial"
#define DEBUG_PORT Serial
#endif
```
<hr>
### 5. Open the example sketch NMEA.ino
In the Arduino IDE, select **File -> Examples -> NeoGPS -> NMEA**.
<hr>
### 6. Build and upload the sketch to your Arduino.
**Note:** If the sketch does not compile, please see the [Troubleshooting](Troubleshooting.md#configuration-errors) section.
When the sketch begins, you should see this:
```
NMEA.INO: started
fix object size = 31
gps object size = 84
Looking for GPS device on Serial1
GPS quiet time is assumed to begin after a RMC sentence is received.
You should confirm this with NMEAorder.ino
Status,UTC Date/Time,Lat,Lon,Hdg,Spd,Alt,Sats,Rx ok,Rx err,Rx chars,
3,2016-05-24 01:21:29.00,472852332,85652650,,138,,,1,0,66,
3,2016-05-24 01:21:30.00,472852311,85652653,,220,24040,7,9,0,557,
3,2016-05-24 01:21:31.00,472852315,85652647,,449,24080,7,17,0,1048,
etc.
```
The default NeoGPS configuration is **Nominal**, as described [here](Configurations.md#typical-configurations). If you do not see this output, please review the [Troubleshooting](Troubleshooting.md#gps-device-connection-problems) section.
This output can be copy & pasted into a into a text editor for saving as a CSV file, which can then be imported into a spreadsheet program for graphing or analysis.
<img src="images/example.png"/>
<hr>
### The NMEA.ino example works!
Once you have verified the GPS device connection and build process with this first example, you should also verify your device's behavior with `NMEAorder.ino` (see [this section](Troubleshooting.md#quiet-time-interval)). This can avoid problems later on, when you start adding/merging other functions to do your "work".
[Other examples](Examples.md) include `NMEAloc.ino`, which shows how to use just the location fields of a fix, or `NMEAtimezone.ino`, which shows how to adjust the GPS time for your local time zone.
If you are logging information to an SD card, you should next try `NMEA_isr.ino`. It is identical to `NMEA.ino`, except that it handles the GPS characters during the RX char interrupt. Interrupt handling will require one of the NeoXXSerial libraries to be installed (e.g. [NeoHWSerial](https://github.com/SlashDevin/NeoHWSerial)).
If you are working on a drone or other autonomous system, you should you should read about [Coherency](Coherency.md) and the interrupt-driven technique in [NMEA_isr](/examples/NMEA_isr/NMEA_isr.ino).
You can also try other configurations. Please see [Choosing Your Configuration](Choosing.md) for more information, and then simply edit `GPSfix_cfg.h` and/or `NMEAGPS_cfg.h`, or select an [example configuration](../configs) and copy these three files into your application directory: `NeoGPS_cfg.h`, `GPSfix_cfg.h`, and `NMEAGPS_cfg.h`.
You can review and edit each of the copied configuration files to add or remove messages or fields, at any time.
**Note:** Not all configurations will work with all example applications. Compiler error messages are emitted for incompatible settings, or if an example requires certain configurations.
### I have a ublox GPS device
After you have tried all the standard NMEA examples, and you need the ublox-specific capabilities of NeoGPS, please see the [ublox](ublox.md) section. Try `PUBX.ino` first, then try `ublox.ino` if you *really* need the binary protocol.

View File

@@ -0,0 +1,18 @@
### License:
**NeoGPS**
Copyright (C) 2014-2017, SlashDevin
NeoGPS is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
NeoGPS is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with NeoGPS. If not, see <http://www.gnu.org/licenses/>.

View File

@@ -0,0 +1,62 @@
Location
========
The `Location_t` class is a 2D point, containing a latitude and longitude in integer degrees * 10<sup>7</sup> (source [here](/src/Location.h)).
This class also provides geographic distance, bearing and offset functions. Furthermore, they all take advantage of the increased precision of the integer coordinates. Other libraries use floating-point coordinates, which have only 6 or 7 significant digits. By using integer math, calculations maintain their original accuracy as long as possible. For example, small distances can be calculated to millimeter accuracy.
The example program [NMEAaverage.ino](/examples/NMEAaverage/NMEAaverage.ino) shows several techniques for performing 2D calculations.
### Distance
To calculate the distance between a pre-determined point and the current fix,
```
NeoGPS::Location_t madrid( 404381311L, -38196229L ); // see https://www.google.com/maps/@40.4381311,-3.8196229,6z
gps_fix fix;
void loop()
{
while (gps.available( gps_port )) {
fix = gps.read();
float dist = fix.location.DistanceKm( madrid );
// or dist = NeoGPS::Location_t::DistanceKm( fix.location, madrid );
Serial.print( dist );
Serial.println( F(" km") );
```
`DistanceMiles` is also available
### Bearing
To calculate the bearing from one point to another (in radians, CW from North),
```
float bearing = fix.location.BearingToDegrees( madrid );
// or bearing = NeoGPS::Location_t::BearingToDegrees( fix.location, madrid );
```
Radians is returned by `BearingTo`.
### Offsetting a Location
To move a location by a specified distance, in a specified direction,
```
float bearing = fix.location.BearingToDegrees( madrid );
// or bearing = NeoGPS::Location_t::BearingToDegrees( fix.location, madrid );
// Step 10km closer to the destination
Location_t next_stop( fix.location );
next_stop.OffsetBy( bearing, 10 / NeoGPS::Location_t::EARTH_RADIUS_KM );
```
Notice that the distance is specified in *radians*. To convert from km to radians, divide by the Earth's radius in km. To convert from miles, divide the miles by the Earth's radius in miles.
### NeoGPS namespace
Because the `Location_t` is inside the `NeoGPS` namespace, any time you want to declare your own instance, use any of the constants in that class (anything that requires the `Location_t` name), you must prefix it with `NeoGPS::` (shown above). As with any C++ namespace, you can relax that requirement by putting this statement anywhere after the NeoGPS includes:
```
using namespace NeoGPS;
```
This technique is used in the **NMEAaverage.ino** sketch.
However, if you have any other libraries that declare their own `Location_t` (not likely), you could not use the `using` statement. `Time_t` is inside the `NeoGPS` namespace for the same reason: avoiding name collisions.

View File

@@ -0,0 +1,90 @@
Merging
===========
Because different NMEA sentences contain different pieces of a fix, they have to be "merged" to determine a complete picture. Some sentences contain only date and time. Others contain location and altitude, but not speed and heading (see table [here](Choosing.md)).
There are several ways to use the GPS fix data: without merging, implicit merging, and **explicit merging ([the default](#3-explicit-merging))**. NeoGPS allows you to choose how you want multiple sentences to be merged:
### 1. NO MERGING
In this mode, `gps.read()` will return a fix that is populated from just one sentence. You will probably receive multiple sentences per update interval, depending on your GPS device.
If you are interested in just a few pieces of information, and those pieces can be obtained from one or two sentences, you can wait for that specific sentence to arrive, and then use one or more members of the fix at that time. Make sure that "no merging" is selected in NMEAGPS_cfg.h by commenting out these lines:
```
//#define NMEAGPS_EXPLICIT_MERGING
//#define NMEAGPS_IMPLICIT_MERGING
```
Then write your loop to wait for the specific sentence:
```
void loop()
{
while (gps.available( gps_port )) {
const gps_fix & rmc = gps.read();
if (gps.nmeaMessage == NMEAGPS::NMEA_RMC) {
// All GPS-related work is performed inside this if statement
if (rmc.valid.speed && (rmc.speed <= 5))
Serial.println( F("Too slow!") );
if (rmc.valid.location && ... or any rmc member
}
}
// Can't access rmc out here...
```
If you are interested in pieces of information that are grouped by some detailed criteria (e.g., field values), you must select "no merging" and then manually merge the fixes of interest. The `merged` copy will be safe to access at any time:
```
gps_fix merged;
void loop()
{
while (gps.available( gps_port )) {
const gps_fix & fix = gps.read();
if ((gps.nmeaMessage == NMEAGPS::NMEA_RMC) &&
(fix.valid.heading &&
((4500 <= fix.heading_cd()) && (fix.heading_cd() < 9000)))){
merged |= fix;
if (merged.valid.satellites && (rmc.satellites > 5))
Serial.println( F("Moar sats!") );
}
}
// Can't access 'fix' out here, but 'merged' can be used...
```
### 2. IMPLICIT MERGING
If you are interested in more pieces of information, perhaps requiring more kinds of sentences to be decoded, but don't really care about what time the pieces were received, you could enable implicit merging:
```
//#define NMEAGPS_EXPLICIT_MERGING
#define NMEAGPS_IMPLICIT_MERGING
```
As sentences are received, they are accumulated internally. Previous field values are retained until they are overwritten by another sentence. When `gps.available`, the accumulated fix can be obtained with `gps.read()`.
Note: The members in an implicitly-merged fix may not be coherent (see [Coherency](Coherency.md). Also, checksum errors can cause the internal fix to be completely reset. Be sure your sketch checks the [valid flags](Data%20Model.md#validity) before using any fix data.
### 3. EXPLICIT MERGING
This is the default setting. To enable explicit merging, make sure this is in NMEAGPS_cfg.h:
```
#define NMEAGPS_EXPLICIT_MERGING
//#define NMEAGPS_IMPLICIT_MERGING
#define NMEAGPS_FIX_MAX 1
```
NMEAGPS_FIX_MAX must be at least one to use EXPLICIT merging.
As sentences are received, they are merged internally. When the LAST_SENTENCE_IN_INTERVAL is received, the internal fix is marked as "available", and it can be accessed by calling `gps.read()`. When the next sentence arrives, the internal fix structure will be emptied.
```
gps_fix_t currentFix;
void loop()
{
while (gps.available( gps_port ))
currentFix = gps.read();
check_position( currentFix );
```
Explicit merging is also required to implement [Coherency](Coherency.md).

View File

@@ -0,0 +1,48 @@
Performance
===========
#### **NeoGPS** is **40% to 70% faster**.
For comparison, the following sentences were parsed by various [Configurations](/doc/Configurations.md) of **NeoGPS**, **TinyGPS** and **TinyGPSPlus** on a 16MHz Arduino Mega2560.
```
$GPGGA,092725.00,4717.11399,N,00833.91590,E,1,8,1.01,499.6,M,48.0,M,,0*5B
$GPRMC,083559.00,A,4717.11437,N,00833.91522,E,0.004,77.52,091202,,,A*57
$GPGSV,3,1,10,23,38,230,44,29,71,156,47,07,29,116,41,08,09,081,36*7F
$GPGSV,3,2,10,10,07,189,,05,05,220,,09,34,274,42,18,25,309,44*72
$GPGSV,3,3,10,26,82,187,47,28,43,056,46*77
```
<table>
<tr><td>Configuration</td><td>Sentence</td><td>NeoGPS</td><td>TinyGPS<br>Time (% faster)</td><td>TinyGPS++<br>Time (% faster)</td><td>Adafrut_GPS<br>Time (%faster)</td></tr>
<tr><td>Minimal</td><td>GGA<br>RMC</td><td>329us<br>335us</td><td>- (78%)<br>- (77%)</td><td>- (78%)<br>- (77%)</td></tr>
<tr><td>DTL</td><td>GGA<br>RMC</td><td>780us<br>803us</td><td>- (46%)<br>- (44%)</td><td>- (47%)<br>- (44%)</td></tr>
<tr><td>Nominal</td><td>GGA<br>RMC</td><td>864us<br>883us</td><td>1448us (40%)<br>1435us (39%)</td><td>1473us (41%)<br>1442us (39%)</td><td>1358us (36%)<br>1535us (42%)</td></tr>
<tr><td>Full</td><td>GGA<br>RMC<br>GSV</td><td>908us<br>899us<br>2194us</td><td>- (37%)<BR>- (37%)<br>- (-)</td><td>1523us (40%)<br>1560us (42%)<br>6651us (67%)</td></tr>
</table>
#### Why is **NeoGPS** faster?
Most libraries use extra buffers to accumulate parts of the sentence so they
can be parsed all at once. For example, an extra field buffer may hold on
to all the characters between commas. That buffer is then parsed into a
single data item, like `heading`. Some libraries even hold on to the
*entire* sentence before attempting to parse it. In addition to increasing
the RAM requirements, this requires **extra CPU time** to copy the bytes and
index through them... again.
**NeoGPS** parses each character immediately into the data item. When the
delimiting comma is received, the data item has been fully computed *in
place* and is marked as valid.
Most libraries parse all fields of their selected sentences. Although most
people use GPS for obtaining lat/long, some need only time, or even just one
pulse-per-second.
**NeoGPS** configures each item separately. Disabled items are
conditionally compiled, which means they will not use any RAM, program space
or CPU time. The characters from those fields are simply skipped; they are
never copied into a buffer or processed.
While it is significantly faster and smaller than all NMEA parsers, these same improvements also make
NeoGPS faster and smaller than _binary_ parsers.

View File

@@ -0,0 +1,13 @@
Program Space requirements
=======
The **Minimal** configuration requires 866 bytes.
The **DTL** configuration requires 2072 bytes.
The **Nominal** configuration requires 2800 bytes. TinyGPS uses about 2400 bytes. TinyGPS++ uses about 2700 bytes.
The **Full** configuration requires 3462 bytes.
(All configuration numbers include 166 bytes PROGMEM.)

View File

@@ -0,0 +1,41 @@
RAM requirements
=======
#### **NeoGPS** requires **72% to 96% _less_ RAM, saving 140 to 1100 bytes.**
Because you can select what data members are stored, the RAM savings depends on the [configuration](Configurations.md):
<table>
<tr><td>Configuration</td><td>NeoGPS<br>Size</td><td>TinyGPS<br>Size (% smaller)</td><td>TinyGPS++<br>Size (% smaller)</td><td>Adafruit_GPS<br>Size (% smaller)</td></tr>
<tr><td>Minimal</td><td>10</td><td>- (95%)</td><td>- (96%)</td></tr>
<tr><td>DTL</td><td>25</td><td>- (86%)</td><td>- (90%)</td></tr>
<tr><td>Nominal</td><td>41</td><td>180 (72%)</td><td>240 (83%)</td><td>326 (87%)</td></tr>
<tr><td>Full</td><td>242</td><td>- (-)</td><td>~1400 (83%)</td></tr>
</table>
#### Why does **NeoGPS** use less RAM?
As data is received from the device, various portions of a `fix` are
modified. In fact, _**no buffering RAM is required**_. Each character
affects the internal state machine and may also contribute to a data member
(e.g., latitude).
If your application only requires an accurate one pulse-per-second, you
can configure it to parse *no* sentence types and retain *no* data members.
This is the **Minimal** configuration. Although the
`fix().status` can be checked, no valid flags are available. Even
though no sentences are parsed and no data members are stored, the
application will still receive an empty `fix` once per second:
```
while (gps.available( gpsPort )) {
gps_fix nothingButStatus = gps.read();
sentenceReceived();
}
```
The `ubloxNMEA` derived class doesn't use any extra bytes of RAM.
The `ubloxGPS` derived class adds 20 bytes to handle the more-complicated protocol,
plus 5 static bytes for converting GPS time and Time Of Week to UTC.

View File

@@ -0,0 +1,40 @@
Tradeoffs
=========
There's a price for everything, hehe...
#### Configurability means that the code is littered with #ifdef sections.
I've tried to increase white space and organization to make it more readable, but let's be honest...
conditional compilation is ugly.
#### Accumulating parts means knowing which parts are valid.
Before accessing a part, you must check its `valid` flag. Fortunately, this adds only one bit per member. See [Streamers.cpp](/src/Streamers.cpp#L100) for an example of accessing every data member. That file also shows how to accommodate different builds: all references to 'gps_fix' members are wrapped with conditional compilation `#ifdef`/`#endif` statements. If you do not plan to support multiple configurations, you do not need to use `#ifdef`/`#endif` statements.
#### Parsing without buffers, or *in place*, means that you must be more careful about when you access data items.
In general, using the fix-oriented methods `available` and `read` are atomically safe. You can access any parts of your `fix` structure, at any time.
If you are using the advanced [character-oriented methods](/extras/doc/CharOriented.md):
* You must wait to access the internal `gps.fix()` until after the entire sentence has been parsed.
* Only 3 example programs use these methods: NMEAblink, NMEAorder and NMEAdiagnostic. These examples simply `decode` until a sentence is COMPLETED. See `GPSloop()` in [NMEAdiagnostic.ino](/examples/NMEAdiagnostoc/NMEAdiagnostic.ino).
* Member function `gps.is_safe()` can also be used to determine when it is safe to access the internal `gps.fix()`.
* Received data errors can cause invalid field values to be set in the internal fix *before* the CRC is fully computed. The CRC will
catch most of those, and the internal fix members will then be marked as invalid.
#### Accumulating parts into *one* fix means less RAM but more complicated code
By enabling one of the [merging](/extras/doc/Merging.md) methods, fixes will accumulate data from all received sentences. The code required to implement those different techniques is more complicated than simply setting a structure member.
You are not restricted from having other instances of fix; you can copy or merge a some or all of a new fix into another copy if you want.
#### Full C++ OO implementation is more advanced than most Arduino libraries.
You've been warned! ;)
#### "fast, good, cheap... pick two."
Although most of the RAM reduction is due to eliminating buffers, some of it is from trading RAM
for additional code (see **Nominal** Program Space above). And, as I mentioned, the readabilty (i.e., goodness) suffers from its configurability.

View File

@@ -0,0 +1,275 @@
Troubleshooting
===============
Most problems are caused by these kinds of errors:
1. [Device connection](#gps-device-connection-problems) (use `NMEAdiagnostic.ino`)
2. [Poor reception](#poor-reception) (use `NMEA.ino`)
3. [Configuration](#configuration-errors) (use `NMEAtest.ino`)
4. [Quiet Time Interval](#quiet-time-interval) (use `NMEAorder.ino`)
5. [Trying to do too many things](#trying-to-do-too-many-things) at the same time
6. [When all else fails...](#when-all-else-fails)
__________________
## GPS device connection problems
The example program `NMEAdiagnostic.ino` can several kinds of problems:
1) The GPS device may not be correctly wired,
2) The GPS device is correctly wired but running at the wrong baud rate,
3) The GPS device is running at the correct baud rate, but it never sends the LAST_SENTENCE_IN_INTERVAL, as defined in NMEAGPS_cfg.h
To help diagnose the problem, use [NMEAdiagnostic.ino](/examples/NMEAdiagnostic/NMEAdiagnostic.ino). Create an NMEAdiagnostic subdirectory, copy the same NeoGPS files into that subdirectory, along with NMEAdiagnostic.ino. Build and upload the sketch.
##### 1) Not correctly wired
If the GPS device is not correctly connected, [NMEAdiagnostic.ino](/examples/NMEAdiagnostic/NMEAdiagnostic.ino) will display:
```
NMEAdiagnostic.INO: started
Looking for GPS device on Serial1
____________________________
Checking 9600 baud...
Check GPS device and/or connections. No data received.
```
The example program [NMEA.ino](/examples/NMEA/NMEA.ino) will display the following, stopping after printing the header:
```
NMEA.INO: started
fix object size = 31
gps object size = 84
Looking for GPS device on Serial1
GPS quiet time is assumed to begin after a GST sentence is received.
You should confirm this with NMEAorder.ino
Status,UTC Date/Time,Lat,Lon,Hdg,Spd,Alt,Sats,Rx ok,Rx err,Rx chars,
```
You may have the GPS device connected to the wrong pins (GPS RX should be connected to Arduino TX, and GPS TX should be connected to Arduino RX), or the .INO may be using the wrong serial object: review `GPSport.h` for the expected connections, or delete the include and declare your own serial port variable.
##### 2) Wrong baud rate
If the wrong baud rate is chosen, [NMEAdiagnostic.ino](/examples/NMEAdiagnostic/NMEAdiagnostic.ino) will try each baud rate to determine the correct baud rate for your GPS device. For each baud rate, it will display:
```
Checking 4800 baud...
No valid sentences, but characters are being received.
Check baud rate or device protocol configuration.
Received data:
884A0C4C8C480C8808884A0C8C480C4A4CC80A0C027759495995A5084C0A4C88 .J.L.H....J..H.JL....wYIY...L.L.
```
If the example program [NMEA.ino](/examples/NMEA/NMEA.ino) is using the wrong baud rate, it will print lines of empty data:
```
Local time,Status,UTC Date/Time,Lat,Lon,Hdg,Spd,Alt,HDOP,Rx ok,Rx err,Rx chars,
,,,,,,,,0,3,181,
,,,,,,,,0,1,422,
```
Notice how the received character count (Rx chars) is increasing, but nothing was decoded successfully (Rx ok always 0), and a few sentences had errors. You should review your GPS device's documentation to find the correct baud rate, or use [NMEAdiagnostic](/examples/NMEAdiagnostic/NMEAdiagnostic.ino) to auto-detect the correct baud rate.
##### 3) Wrong LAST_SENTENCE_IN_INTERVAL in NMEAGPS_cfg.h
It is very important that the correct LAST_SENTENCE_IN_INTERVAL is chosen. It is used to determine what sentences are in a particular interval (usually 1 second) and when the GPS quiet time begins (see below).
Choosing the wrong LAST_SENTENCE_IN_INTERVAL may cause GPS characters to be dropped (because the quiet time is assumed to begin at the wrong time), or prevent any update intervals from being completed (because the sketch waits for the wrong sentence to arrive).
If the wrong LAST_SENTENCE_IN_INTERVAL is chosen, [NMEAdiagnostic.ino](/examples/NMEAdiagnostic/NMEAdiagnostic.ino) will display:
```
NMEAdiagnostic.INO: started
Looking for GPS device on Serial1
____________________________
Checking 9600 baud...
Received GSV
Received GSV
Received GLL
Received RMC
Received VTG
Received GGA
Received GSA
Received GSV
Received GSV
Received GSV
Received GLL
Received RMC
Received VTG
Received GGA
Received GSA
Received GSV
Received GSV
Received GSV
Received GLL
Received RMC
Received VTG
**** NMEA sentence(s) detected! ****
Received data:
47504753562C332C312C31312C30322C37302C3330322C32372C30352C33352C GPGSV,3,1,11,02,70,302,27,05,35,
3139372C32342C30362C34392C3035332C33312C30392C31372C3037332C3230 197,24,06,49,053,31,09,17,073,20
2A37360D0A2447504753562C332C322C31312C31322C35352C3236362C32352C *76..$GPGSV,3,2,11,12,55,266,25,
Device baud rate is 9600
GPS data fields received:
Status,UTC Date/Time,Lat,Lon,Hdg,Spd,Alt,Sats,Rx ok,Rx err,Rx chars,
3,2016-05-24 01:56:45.00,456013948,-720168555,,816,25450,7,21,0,1317,
Warning: LAST_SENTENCE_IN_INTERVAL defined to be RMC, but was never received.
Please use NMEAorder.ino to determine which sentences your GPS device sends, and then
use the last one for the definition in NMEAGPS_cfg.h.
** NMEAdiagnostic completed **
1 warnings
```
As instructed, you should run the NMEAorder.ino sketch to determine which sentence is last. You may be able to see a slight pause in the "Received XXX" messages above, which would also identify the last sentence. Edit NMEAGPS_cfg.ino and change this line:
```
#define LAST_SENTENCE_IN_INTERVAL NMEAGPS::NMEA_GLL
```
__________________
## Poor reception
If the GPS device is correctly connected, and the sketch is receiving complete NMEA sentences, without data errors, [NMEA.ino](/examples/NMEA/NMEA.ino) displays "mostly" empty fields:
```
Local time,Status,UTC Date/Time,Lat,Lon,Hdg,Spd,Alt,HDOP,Rx ok,Rx err,Rx chars,
,0,,,,,,,3,0,259,
,0,,,,,,,6,0,521,
```
Notice how "Rx ok" and "Rx chars" increase each second, and "Rx err" is usually zero. This means that the sketch is receiving complete NMEA sentences, without data errors.
Because the fields are all empty, the GPS device is not receiving signals from any satellites. Check the antenna connection and orientation, and make sure you have an unobstructed view of the sky: go outside for the best reception, although getting close to a window may help.
Although different GPS devices behave differently when they do not have a fix, you may be able to determine how many satellites are being received from what is being reported.
As soon as the GPS device receives a signal from just one satellite, the status may change to "1" and the date and time will be reported:
```
Local time,Status,UTC Date/Time,Lat,Lon,Hdg,Spd,Alt,HDOP,Rx ok,Rx err,Rx chars,
2015-09-14 15:07:30,1,2015-09-14 19:07:30.00,,,,,,3,0,259,
2015-09-14 15:07:31,1,2015-09-14 19:07:30.00,,,,,,3,0,521,
```
If it continues to report only date and time, you do not have an unobstructed view of the sky: only one satellite signal is being received.
When signals from three satellites are being received, the GPS device will start reporting latitude and longitude.
When signals from four or more satellites are being received, the GPS device will start reporting altitude.
You can also enable other `gps_fix` fields or NMEA sentences. In **GPSfix_cfg.h**, uncomment this line:
```
#define GPS_FIX_SATELLITES
```
In **NMEAGPS_cfg.h** uncomment these lines:
```
#define NMEAGPS_PARSE_GGA
#define NMEAGPS_PARSE_GSA
#define NMEAGPS_PARSE_GSV
#define NMEAGPS_PARSE_SATELLITES
#define NMEAGPS_PARSE_SATELLITE_INFO
#define NMEAGPS_MAX_SATELLITES (20)
```
This will display additional fields for how many satellites are in view, whether they are being "tracked", and their individual signal strengths.
```
Local time,Status,UTC Date/Time,Lat,Lon,Hdg,Spd,Alt,HDOP,VDOP,PDOP,Lat err,Lon err,Alt err,Sats,[sat elev/az @ SNR],Rx ok,Rx err,Rx chars,
2015-09-14 16:03:07,3,2015-09-14 20:03:07.00,,,,,,,,,,,,2,[2 71/27@14,5 65/197@33,],8,0,476,
2015-09-14 16:03:08,3,2015-09-14 20:03:08.00,,,,,,,,,,,,2,[2 71/27@14,5 65/197@33,],16,0,952,
```
This shows that only two satellites are being tracked. You must move to a position with a better view of the sky.
__________________
## Quiet Time Interval
Because GPS devices send lots of data, it is possible for the Arduino input buffer to overflow. Many other libraries' examples will fail when modified to print too much information, or write to an SD card (see also [next section](#trying-to-do-too-many-things)).
NeoGPS examples are structured in a way that takes advantage of a "quiet time" in the data stream. GPS devices send a batch of sentences, once per second. After the last sentence in the batch has been sent, the GPS device will not send any more data until the next interval. The example programs watch for that last sentence. When it is received, it is safe to perform time-consuming operations.
It is **critical** to know the order of the sentences sent by your specific device. If you do not know what sentence is last in a batch, use the example program `NMEAorder.ino` to list the sentence order. When you know the last sentence, review `NMEAGPS_cfg.h` to confirm that the correct value on this line:
```
#define LAST_SENTENCE_IN_INTERVAL NMEAGPS::NMEA_GLL
```
Most example programs depend on this statement. `NMEAdiagnostic.ino` will emit a warning if that sentence is never recevied. If `LAST_SENTENCE_IN_INTERVAL` is not correct for your device, the example programs will probably lose GPS data, especially on `SoftwareSerial` data ports. The example programs may behave like they are using the wrong baud rate: empty fields, increasing Rx Errors, and increasing Rx Chars. Basically, the example programs are [Trying To Do Too Much](#trying-to-do-too-many-things) at the wrong time. With the correct `LAST_SENTENCE_IN_INTERVAL`, the example programs will not lose GPS data.
## Configuration errors
Because there are so many configurable items, it is possible that your configuration prevents acquiring the desired GPS information.
The compiler **cannot** catch message set dependencies: the enum
`nmea_msg_t` is always available. So even though a `fix` member is enabled,
you may have disabled all messages that would have set its value.
`NMEAtest.ino` can be used to check some configurations.
For example, if your application needs altitude, you **must** enable the GGA sentence. No other sentence provides the altitude member (see [this table](Choosing.md)). If `NMEA_PARSE_GGA` is not defined, `gps.decode()` will return COMPLETED after a GGA is received, but no parts of the GGA sentence will have been parsed, and altitude will never be valid. NeoGPS will _recognize_ the GGA sentence, but it will not be parsed.
The compiler will catch any attempt to use parts of a `fix` that have been
configured out: you will see something like `gps_fix does not have member
xxx`.
There are also compile-time checks to make sure the configuration is valid. For example, if you enable `NMEAGPS_PARSE_SATELLITES` so that you can access the array of satellite information, you *must* enable `GPS_FIX_SATELLITES`. An error message will tell you to do that. Until you disable `NMEAGPS_PARSE_SATELLITES` **or** enable `GPS_FIX_SATELLITES`, it will not compile.
__________________
## Trying to do too many things
Many libraries and their examples, and I mean almost all of 'em, are not structured in a way that lets you do more than one thing in a sketch. The result: the example program works great, but adding anything to it breaks it.
#### Printing too much
Many programmers run into trouble because they try to print too much debug info. The Arduino `Serial.print` function will "block" until those output characters can be stored in a buffer. While the sketch is blocked at `Serial.print`, the GPS device is probably still sending data. The _input_ buffer on an Arduino is only 64 bytes, about the size of one NMEA sentence. After 64 bytes have been received stored, all other data is dropped! Depending on the GPS baud rate and the Serial Monitor baud rate, it may be very easy to lose GPS characters.
It is crucial to call `gps.available( gps_port )` or `serial.read()` frequently, and _never_ to call a blocking function that takes more than (64*11/baud) seconds. If the GPS is running at 9600, you cannot block for more than 70ms. If your debug `Serial` is also running at 9600, you cannot write more than 64 bytes consecutively (i.e., in less than 70ms).
#### Blocking operations
Most Arduino libraries are written in a blocking fashion. That is, if you call a library's function, it will not return from that function until the operation has been completed. If that operation takes a long time, GPS characters will be dropped.
Many programmers want to write GPS data to an SD card. This is completely reasonable to do, but an `SD.write` can block long enough to cause the input buffer to overflow. SD libraries have their own buffers, and when they are filled, the library performs SPI operations to "flush" the buffer to the SD card. While that is happening, the GPS device is _still_ sending data, and it will eventually overflow the serial input buffer.
This is a very common problem! Here are some diagrams to help explain the timing for the Adafruit_GPS library. First, lets look at how the incoming GPS data relates to reading and parsing it:
<img src="images/GPS%20Timing%200.jpg"/>
Note how loop calls GPS.read, and when it has read all the chars that have been received up to that point, it returns. loop may get called lots of times while it's waiting for the chars to come in. Eventually, the whole sentence is received, newNMEAreceived returns true, and your sketch can `parse` the new data (not needed for **NeoGPS**).
The problem is that if you try to do anything that takes "too long", `GPS.read` won't get called. The incoming chars stack up in the input buffer until it's full. After that, the chars will be dropped:
<img src="images/GPS%20Timing%201.jpg"/>
The next sentence, a GPRMC, continues to come in while `Serial.print` and `SD.write` are doing their thing... and data gets lost.
Fortunately, there are two ways to work around this:
#### **1)** Use an interrupt-driven approach
The received data could be handled by an **I**nterrupt **S**ervice **R**outine. The example program [NMEA_isr.INO](/examples/NMEA_isr/NMEA_isr.ino) shows how to handle the received GPS characters *during* the RX interrupt. This program uses one of the replacement **NeoXXSerial** libraries to attach an interrupt handler to the GPS serial port.
When a character is received, the ISR is called, where it is immediately decoded. Normally, the character is stored in an input buffer, and you have to call `available()` and then `read()` to retrieve the character. Handling it in the ISR totally avoids having to continuously call `Serial1.read()`, and is much more efficient. Your program does not have to be structured around the GPS quiet time.
The ISR can also save (i.e., "buffer") complete fixes as they are completed. This allows the main sketch to perform an operation that takes multiple update intervals. This is especially important if your GPS is sending updates 10 times per second (10Hz), and your SD card takes ~150ms to complete a logging write.
Normally, this would cause a loss of data. You can specify the number of fixes to be stored by the ISR for later use: simply set NMEAGPS_FIX_MAX to 2. This will allow 2 complete fixes, accumulated from several sentences each, to be stored until `loop()` can call `gps.read()`. This is referred to as "fix-oriented operation" in the Data Model description.
While interrupt handling is considered a more advanced technique, it is similar to the existing Arduino [attachInterrupt](https://www.arduino.cc/en/Reference/AttachInterrupt) function for detecting when pin change.
Which **NeoXXLibrary** should you use?
* If `Serial` or `Serial1` is connected to the GPS device, you can use [NeoHWSerial](https://github.com/SlashDevin/NeoHWSerial).
* If the Input Capture pin can be connected to the GPS device, as required for AltSoftSerial, you can use [NeoICSerial](https://github.com/SlashDevin/NeoICSerial).
* If neither of those connections is possible, you can [NeoSWSerial](https://github.com/SlashDevin/NeoSWSerial) on almost any pair of digital pins. It only supports a few baud rates, though.
#### **2)** Restructure `loop()` to do time-consuming operations during the GPS [quiet time](#quiet-time-interval).
All non-ISR example programs are structured this way. The "quiet time" is perfect for doing other things:
<img src="images/GPS%20Timing%202.jpg"/>
All you need to do is hold on to the GPS information (date, time, location, etc.) until the quiet time comes around. You'll need to take the same approach for each additional task. For additional sensors, hold on to the temperature, acceleration, whatever, until the quiet time comes around. *Then* perform the blocking operation, like `SD.write`, and no GPS data will be lost.
This is why NeoGPS uses a `fix` structure: it can be
* _populated_ as the characters are received,
* _copied/merged_ when a sentence is complete, and then
* _used_ anytime (for fast operations) or during the quiet time (for slow operations).
You do not have to call a "parse" function after a complete sentence has been received -- the data was parsed as it was received. Essentially, the processing time for parsing is spread out across the receipt of all characters. When the last character of the sentence is received (i.e. `gps.available()` or `gps.decode(c) == DECODE_COMPLETED`), the relevant members of `gps.fix()` have already been populated.
These example programs are structured so that the (relatively) slow printing operations are performed during the GPS quiet time. Simply replace those trace/print statements with your specific code.
__________________
## When all else fails
If you still do not have enough time to complete your tasks during the GPS quiet time, you can
* Increase the baud rate on the debug port (takes less time to print)
* Increase the baud rate on the GPS port (increases quiet time)
* Configure the GPS device to send fewer sentences (decreases parsing time, increases quiet time)
* Use a binary protocol for your specific device (decreases parsing time, increases quiet time)
* Watch for a specific message to be COMPLETED, then begin your specific processing. This may cause some sentences to lose characters, but they may not be necessary. See comments regarding `LAST_SENTENCE_IN_INTERVAL` above.
* Use the interrupt-driven approach, described above. It is *guaranteed* to work!

View File

@@ -0,0 +1 @@
These .MD files are in github markdown format. They are intended to be read on the github website, as linked from the top repository page.

Some files were not shown because too many files have changed in this diff Show More