Close

GyPSy

A project log for Careless WSPR

A desultorily executed Weak Signal Propagation Reporter beacon.

ziggurat29ziggurat29 08/09/2019 at 17:520 Comments

Summary

GPS modules have arrived.  They're a bit sketchy.

Deets

The Neo-6M modules have arrived.  These came with a small bar-shaped patch antenna about 1/3 the size of the square ones I am more accustomed to seeing.  I wonder if this will affect sensitivity...

As a quicky, I connected it via a handy FTDI adapter.  This module by default runs at 9600 bps.  Data was immediately sent from the module.  However, the first lock took about 1/2 hr to be made!  However, I am inside, and the limited length of the cables keeps unit close to computer.  The tiny antenna possibly does not help, either.  I'll order some USB extension cables (which you practically need, anyway, for that ST-Link) and external GPS antenna, though that will take some time for it to arrive.  I'm sure it would fare better outside, however that doesn't really work for my development activities.

Receiving Data

This project's needs are very specific, and in fact the only message I need to parse is the standard 'Recommended Minimum C'; e.g.:

$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A
$GPRMC,225446,A,4916.45,N,12311.12,W,000.5,054.7,191194,020.3,E*68

Because of this simplicity I chose not to bother with using an existing library.  Instead, I used a simple state machine to capture the text lines, and a trivial parser to tokenize the results on the command and extract the fields.  The code is actually a simplified version of what already exists for the command processor, which does a similar thing over the CDC serial port.

//sentence buffer
char g_achNMEA0183Sentence[82];    //abs max len is 82

static char _gpsGetChar ( const IOStreamIF* pio )
{
    char ret;
    pio->_receiveCompletely ( pio, &ret, 1, TO_INFINITY );
    return ret;
}

//this gets characters from the input stream until line termination occurs.
static void _getSentence ( const IOStreamIF* pio )
{
    int nIdxSentence;

    int bCont = 1;

    //pull characters into sentence buffer until full or line terminated
    nIdxSentence = 0;
    while ( bCont && nIdxSentence < COUNTOF(g_achNMEA0183Sentence) )
    {
        char chNow = _gpsGetChar ( pio );
        switch ( chNow )
        {
        case '\r':    //CR is a line terminator
        case '\n':    //LF is a line terminator
            memset ( &g_achNMEA0183Sentence[nIdxSentence], '\0',
                                 COUNTOF(g_achNMEA0183Sentence) - 
                                 nIdxSentence );    //clear rest of buffer
            ++nIdxSentence;
            bCont = 0;
        break;

        default:
            //everything else simply accumulates the character
            g_achNMEA0183Sentence[nIdxSentence] = chNow;
            ++nIdxSentence;
        break;
        }
    }
}

so, that fills the statically allocated 'sentence buffer' with incoming characters until either the CR or LF is received, which terminates it.

A new task module was created, 'task_gps.h, .c', and it works much like the one we created for the monitor -- a loop calling the line reception and handling function 'GPS_process()'.  Wiring it is was similarly trivial -- just adding yet-another task creation in __startWorkerTasks():

	//kick off the GPS thread, which handles incoming NMEA data
	{
	osThreadStaticDef(taskGPS, thrdfxnGPSTask, osPriorityNormal, 0, COUNTOF(g_tbGPS), g_tbGPS, &g_tcbGPS);
	g_thGPS = osThreadCreate(osThread(taskGPS), NULL);
	}

For the moment I will just be setting some global variables that can be inspected, but later I will add functionality to set the RTC clock to the satellite time, and to update the maidenhead to the current location. 

Parsing

The trivial parser then tokenizes the incoming data by converting the comma to a nul.  This effectively makes the sentences into a sequence of nul-terminated strings, themselves terminated by an empty string.

Most of the fields are straightforward, but the latitude and longitude are oddballs in that they are degrees and minutes, with the two numbers mooshed together.  So we need to separate the numbers and then convert the minutes into decimal degrees.  (We want decimal degrees for the forthcoming maidenhead conversion code).  I cheated on this and used sscanf() to make the parsing easy.

//the lat/lon is 2 or 3 char degrees, and float minutes
int deg;
float fmin;
sscanf ( pszLat, "%2u%f", °, &fmin );
g_fLat = deg + fmin / 60;    //convert to decimal degrees
if ( 'S' == *pszLatHemi )    //+ is N, - is S
{
    g_fLat *= -1;
}

sscanf ( pszLon, "%3u%f", °, &fmin );
g_fLon = deg + fmin / 60;    //convert to decimal degrees
if ( 'W' == *pszLonHemi )    //+ is E, - is W
{
    g_fLon *= -1;
}

I say 'cheat' because scanf and printf are heavyweight functions.  In fact, because we're using the '%f' feature, we have to add some more linker flags or they won't even work as desired

-u _printf_float -u _scanf_float

The initial cut of code resulted in a quick trip to the Hard Fault vector.  Stepping through the code I could see that the first sscanf did return, but the next line of code crashed.  One's first intuition in those sorts of cases is 'stack overflow'.  I had given the GPS task 1K of stack (which is twice as much as I normally like), but I guess it's not enough?  Doubling it to 2K resolved the crash.  I did some inspection via the Monitor:

So, the GPS task is taking 2048 - 872 = 1175 bytes.  This is kind of a lot.  It's also interesting to see that in the Monitor task, before issuing the 'gps' command the minimum free is 828 (out of 1K), but after issuing 'gps' it is 136.  That is because of the printf ( "%f" ... ) that is performed in the 'gps' command.  So that printf is approximately incurring 828 - 136 = 692 bytes of stack usage!  (Not all of that is the printf -- some is probably in the command handler, but most is from the printf.)

The build is now 57540 bytes, which is getting very close to the 64 KiB limit of the flash.  I'll probably have to revisit this, but we'll see what we can further fit into the 7996 bytes remaining...

Locking

One thing that vexes me and confounds the development is the propensity of this device to take a long time to lock.  I can't really debug the code that operates under GPS control effectively if I can't get a lock to stimulate those code paths.  I have an antenna on the way which hopefully will help with that, but I did notice a curious part on the board.  In the picture of the board above it is in the upper left.  It looks a bit like a very tiny battery.  It is supposed to hold some parameters in RAM on the chip which help achieve locks more quickly.  It's not a critical component, but it helps.

Doing some research on the web, it does seem that some folks have a battery on their NEO-6M boards.  It seems likely that the batter is a MS621FE part:

https://www.sii.co.jp/en/me/battery/support/charging-circuit1/
http://www.sih.com.hk/sih_eng/products/bat_02_manganese.html

This is a rechargeable Lithium cell.  It states that from a deep discharge state that the part support 200 cycles.  Egads, 200?!  Better not let it ever get discharged.

However, upon further study of the pieces of part number visible (most is hidden by the solder terminal), I am now convinced that my board does not have this part, but instead has a super-capacitor 'XH414H'.  This is a 70 milli-Farad cap.  Well, at least that has indefinite charge cycles.  I have no idea how long the supercap will support the memory, though, so it might not help much in the practical world.

Next

Translation of lat/lon to maidenhead grid locator

Discussions