Library / SDK
kosma/minmea avatar
kosma/minmea

minmea: a tiny NMEA 0183 GPS parser in pure C

a lightweight GPS NMEA 0183 parser library in pure C

968 stars303 forksCWTFPL

At a glance

What is it?
minmea is a minimalistic GPS sentence parser in ISO C99, written for microcontrollers and other embedded targets: one source file, one header, no dynamic memory allocation and no floating point in the core, with fractional numbers stored as integer pairs so a coordinate keeps its precision without a soft-float library. It covers nine sentence types and leaves room to add more by editing one file.
Who is it for?
Adopt minmea when you are writing firmware that has to read a GPS receiver and you cannot justify a dynamic allocation, a floating point routine or a dependency graph in the build. It is the wrong tool if you need a long list of sentence types, because only a handful are supported and each new one is a manual edit, and the wrong tool if your target lacks timegm, since the time fields then need the mktime substitution that only works in UTC.
Can I use it commercially?
Yes. WTFPL is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 49 days ago.
What is it written in?
Mainly C, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 6, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • kosma/minmea968 ★ · +1
Star history
Repository2026-10-212026-11-06
kosma/minmea967968

1 Stars in the last 16 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

A parser sized for a microcontroller

minmea is described as a minimalistic GPS parser library written in pure C, intended for resource constrained platforms and especially microcontrollers and other embedded systems. The feature list is the specification of that intent.

It is written in ISO C99. It performs no dynamic memory allocation. It uses no floating point in the core library, while still supporting both fixed point and floating point values at the interface. It is one source file and one header, and the README's phrasing is that it cannot get any simpler. It is tested under Linux, macOS, Windows and embedded ARM GCC. It is easily extendable to support new sentences. And it comes complete with a test suite and static analysis.

Those constraints are the whole design. A parser that allocates nothing can be called from an interrupt or from a task with a fixed heap. A parser with no floating point in its core can be linked into a target whose FPU is absent, or into one where floating point is a library call you cannot afford. The single file and header means integrating it is a copy rather than a dependency resolution, and the README says exactly that: add minmea.c and minmea.h to your project, include the header, and you are good to go.

The supported sentence list is what an integrator checks first. There is GBS for satellite fault detection, GGA for fix data, GLL for geographic position with latitude and longitude, GSA for the dilution of precision and the active satellites, GST for pseudorange noise statistics, GSV for the satellites in view, RMC for the recommended minimum with position, velocity and time, VTG for track made good and ground speed, and ZDA for time and date with UTC, the day, the month, the year and the local time zone. The README says adding support for more is trivial and points at the source, and links to the gpsd documentation on NMEA as the reference to read.

Nine sentence types out of a specification that defines dozens is the main limitation, and the README lists it as such: only a handful of frames is supported right now.

Fractional numbers, and why the core avoids floating point

The number format is the most interesting decision in the library and the one that will affect your code most.

Internally, minmea stores fractional numbers as pairs of two integers, written as a value and a scale. The example in the README is that a value of -123.456 is parsed as {-123456, 1000}. Nothing is rounded at parse time, so the exact digits the receiver sent survive the parse.

The format is described as quite unwieldy, which is true, and the library provides two convenience functions to get out of it. minmea_rescale turns {-123456, 1000} into -1235 when given a scale of 10, which is fixed point conversion with an explicit rounding step, and minmea_float turns the same pair into -123.456, which is the floating point version.

The precision claim is spelled out carefully. The compound type struct minmea_float uses int_least32_t internally, so the coordinate precision is guaranteed to be at least five decimal digits in the DDDMM.MMMMM form, which is plus or minus 2cm in the least significant bit at the equator. The README then makes the more useful observation for anyone calibrating expectations, that GPS modules commonly provide only four decimal digits, DDDMM.MMMM, which works out to plus or minus 20cm at the equator, since 0.0001 of a minute is 0.0001 of 60 degrees and a degree is about 111km.

So the library will not lose precision, but the hardware will, and the difference between 2cm and 20cm is a property of the receiver rather than of the parser. That is worth knowing before you build something that claims centimetre accuracy on the strength of a float in your code.

For a target without a hardware float, the second half of the design matters. The core never uses floating point, so the only place that costs anything is the convenience function, and on a target with no FPU you simply do not call it.

Why the coordinate conversion is left to you

NMEA expresses a coordinate in the DDMM.MMMM format, and the README is characteristically blunt about it, calling the format clunky and noting that it is not good in the internet era.

Internally minmea keeps the value as the fractional number it parsed, and the conversion to the decimal degrees format that modern software expects is available through minmea_tocoord, with the example minmea_tocoord({-375165, 100}) => -37.860832.

What is interesting is that the library does not do that conversion for you, and it gives three reasons. The conversion is not reversible, so doing it silently throws away the original representation and you cannot get it back. It requires floating point support, which would defeat the core design on targets that do not have it. And the user might want to perform the conversion later, or retain the original values.

Those are the reasons a library that respects its constraints gives, and they are the right ones. A parser that converted silently would be simpler to use and worse to depend on, because a consumer that later needs the raw value, or that runs on hardware without a float, would have no way back.

The example in the README shows the whole pattern, reading sentences from standard input, dispatching on the sentence id and printing the raw fractional values alongside the rescaled ones:

c
char line[MINMEA_MAX_SENTENCE_LENGTH];
while (fgets(line, sizeof(line), stdin) != NULL) {
    switch (minmea_sentence_id(line, false)) {
        case MINMEA_SENTENCE_RMC: {
            struct minmea_sentence_rmc frame;
            if (minmea_parse_rmc(&frame, line)) {
                printf("$RMC: raw coordinates and speed: (%d/%d,%d/%d) %d/%d\n",
                        frame.latitude.value, frame.latitude.scale,
                        frame.longitude.value, frame.longitude.scale,
                        frame.speed.value, frame.speed.scale);

and the same idea for a different sentence type, where the interesting field is the fix quality:

c
        case MINMEA_SENTENCE_GGA: {
            struct minmea_sentence_gga frame;
            if (minmea_parse_gga(&frame, line)) {
                printf("$GGA: fix quality: %d\n", frame.fix_quality);
            }
        } break;

The pattern to copy is the dispatch on minmea_sentence_id with the boolean argument, and then a per sentence parse function that returns a success flag, with the frame struct filled in on success.

Portability beyond Unix, and the sentence length limit

minmea runs out of the box under most Unix compatible systems. Support for non-Unix systems, including native Windows builds under MSVC, is provided through compatibility headers, and the README describes the two steps it takes.

You define MINMEA_INCLUDE_COMPAT in the build environment, and you add the appropriate compatibility header from under the compat directory as minmea_compat.h. That is the whole mechanism, and it is a good one for a library of this size: rather than carrying conditionals through the source, the platform differences live in a header you choose at build time, and the library itself stays portable C99.

The other build-time knob is MINMEA_MAX_SENTENCE_LENGTH. The README notes that if your GPS receiver outputs very long sentences, you should consider increasing it in the build environment. That is a buffer, and the parser reads a sentence at a time into it, so a sentence longer than the limit is truncated, and a truncated sentence is a parse failure that looks like a corrupt transmission rather than a buffer problem.

This is the kind of setting that is easy to leave at its default and then blame the receiver for. The size of a buffer is not a property of the standard, it is a property of the data, and NMEA sentences vary in length between receivers and between sentence types. If your device reports a fix with a lot of satellites in view, or you enable verbose sentences, the default may not be enough, and the symptom is a parse that works for some sentences and silently fails for others.

Both of these settings are compile-time, which is consistent with the rest of the design. Nothing about the library is configured at runtime.

Building, testing and the static analysis stack

The build is CMake, and the test suite uses the Check framework together with the Clang Static Analyzer, with both of them expected on the path. The sequence is:

bash
mkdir build
cd build
cmake ../
make
make test

The static analysis being part of the required tooling rather than an optional extra is a good sign for a library that runs on hardware you cannot easily debug. A parser that reads bytes off a serial line is exactly the kind of code where an off by one in an index or an unchecked length produces a memory error that shows up as a reset in production, and a static analyser in the required toolchain is a reasonable defence.

The tests can be left out, which matters for embedded builds where the test dependencies are a burden:

bash
mkdir build
cd build
cmake -DMINMEA_ENABLE_TESTING=OFF ..
make

The repository layout supports that story. There is minmea.c and minmea.h at the root, which is the library, an example.c with the usage example from the README, tests.c for the test suite, a compat directory for the platform headers, and a CMakeLists.txt. There is also a .clusterfuzzlite directory, which is the configuration for continuous fuzzing, and that is a meaningful thing to find in a parser that takes bytes from a device: fuzzing a line parser is cheap and finds the buffer problems faster than review does.

The CI badge in the README header refers to a C and C++ workflow, and the tested platform list in the features section names Linux, macOS, Windows and embedded ARM GCC, so the cross platform claim is backed by a matrix rather than by a promise.

The limitations, the licence and the one release

The README has an explicit limitations section, and it lists three things.

First, only a handful of frames is supported right now, which is the practical constraint on the whole library. Second, there is no support for omitting parts of the library from building, with the workaround given as the -ffunction-sections and -Wl,--gc-sections linker flags, or the equivalent, to remove the unused parsers from the final image. That is a real cost for a microcontroller project, where flash is the scarcest resource and a parser for a sentence you will never receive is dead weight, and the workaround is a linker flag rather than a build option in the library.

Third, some systems lack timegm. On those systems the recommended course of action is to build with -Dtimegm=mktime, which will work correctly as long as the system runs in the default UTC timezone. That last caveat is the important half, because mktime interprets its argument in local time, so the substitution is only correct where local time is UTC, and a device that changes its timezone will silently produce wrong times.

The licence situation needs a note. The repository's metadata records the licence as WTFPL, while the tree contains a COPYING file, a LICENSE.LGPL-3.0, a LICENSE.MIT and a LICENSE.grants, and the README points the reader at COPYING for the terms. The presence of both LGPL and MIT texts alongside a grants file suggests a dual licensing arrangement with a separate grant, and the metadata field appears to describe only one of them. Anyone shipping this in a product should read COPYING and the licence files rather than trusting the badge.

The project is a single release so far, v1.0.0 on 2026-05-11, with the last push on 2026-09-20. The contribution rules are worth reading for the third item, which asks you to write unit tests for any new functionality, and there is a clanker policy that asks you to disclose automated contributions in the pull request and to name the co-authors in the commits.

That policy is a reasonable compromise for a project of this size. A parser that is one file is easy to review, and asking for tests with every change keeps it that way.

Editorial conclusion

Adopt minmea when you are writing firmware that has to read a GPS receiver and you cannot justify a dynamic allocation, a floating point routine or a dependency graph in the build. It is the wrong tool if you need a long list of sentence types, because only a handful are supported and each new one is a manual edit, and the wrong tool if your target lacks timegm, since the time fields then need the mktime substitution that only works in UTC. Before integrating, check the sentence list against what your receiver actually emits, and if your sentences are long, raise MINMEA_MAX_SENTENCE_LENGTH before you start debugging truncated parses.

Frequently asked questions

What is minmea and what is it for?

It is a minimalistic GPS NMEA 0183 parser written in pure C for resource constrained platforms, especially microcontrollers. It is ISO C99, performs no dynamic memory allocation and uses no floating point in the core, and it is one source file and one header.

Which NMEA sentences does minmea support?

Nine types: GBS for satellite fault detection, GGA for fix data, GLL for geographic position, GSA for dilution of precision and active satellites, GST for pseudorange noise statistics, GSV for satellites in view, RMC for position, velocity and time, VTG for track and ground speed, and ZDA for time and date.

How does minmea store fractional numbers?

As a pair of two integers, a value and a scale, so -123.456 is stored as {-123456, 1000}. The minmea_rescale function converts to fixed point with an explicit scale and minmea_float converts to floating point, and the core itself never uses floating point.

How do I build minmea on a non-Unix system such as native Windows?

Define MINMEA_INCLUDE_COMPAT in the build environment and add the appropriate header from the compat directory as minmea_compat.h. If the receiver emits long sentences, increase MINMEA_MAX_SENTENCE_LENGTH in the build environment as well.

What are the main limitations of minmea?

Only a handful of sentence types is supported, there is no way to omit unused parsers from the build except with the -ffunction-sections and -Wl,--gc-sections linker flags, and systems without timegm need to be built with -Dtimegm=mktime, which is only correct while the system runs in the UTC timezone.

Official sources

  1. Issues
  2. kosma/minmea on GitHub
  3. License: WTFPL
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/kosma-minmea.svg)](https://hysenlabs.com/projects/kosma-minmea)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/kosma-minmea.svg)](https://hysenlabs.com/projects/kosma-minmea)

Was this useful?