This projects uses the open source minmea NMEA parser with the low cost LPC845-BRK development board as a simple location finder.
The MCU will wait for an initial lock from the off-board GPS (product link*), connected via UART1. Once a lock is established (detected via the FIX pin), the current position will constantly be updated. You can then periodically check the distance between the current position and the defined destination position.
Debug output can be seen on UART0, which is available over USB CDC on the LPC845-BRK, making it easy to get status updates when making any modifications to the source code.
* Any GPS that provides standard NMEA sentences over UART should work in this project.
The following pins are used to connect to the external GPS (3 pins) and PWM piezo buzzer (1 pin):
- SCT_OUT2 (PWM Output) for the Piezo Buzzer: P0.29
- USART1_RXD (GPS): P0.26
- USART1_TXD (GPS): P0.27
- GPS Fix: P0.28
When you flash the firmware to the LPC845 (using the free cross-platform MCUXpresso IDE and the debugger on the LPC845-BRK), you can connect to USART1 using any terminal emulator package at 9600bps.
Coming out of reset, you will see the following message:
LPC845 GPS Wayfinder
Waiting for a fix on the GPS module.
The GREEN LED indicates that we are waiting for a fix.
The BLUE LED indicates that we are parsing GPS data.
At this point, the GREEN LED on the LED will flash at 1Hz while it is waiting for a lock on 3 or more satellites, which is the required for a position lock.
Once a lock is obtained, the BLUE LED will start blinking as an indication that the MCU is currently processing NMEA data coming from the GPS unit, and the debug output will be updated with the following (example) output:
Current degree coordinates and speed: xx.xxxxxx, xx.xxxxxx (0.002333)
Distance to target: 14.30 km.
Source CodeWhile most of the source code is relatively easy to understand, some of the key concepts are described below.
Haversine Distance EstimatorWe can determine the distance between two points using an implementation of the Haversine formula, shown below:
float
calc_distance(struct gps_coord_fp_deg *a, struct gps_coord_fp_deg *b)
{
float hav_r_meters = 6371e3; /* Mean radius of the earth in meters. */
//float hav_r_miles = 3961; /* Mean radius of the earth in miles. */
/* Convert degrees to radians and calculate the deltas. */
float lat1 = deg_to_rad(a->latitude);
float lat2 = deg_to_rad(b->latitude);
float lon1 = deg_to_rad(a->longitude);
float lon2 = deg_to_rad(b->longitude);
float delta_lat = lat2 - lat1;
float delta_long = lon2 - lon1;
/* Haversine */
float hav_a = pow(sin(delta_lat/2.0f),2.0f) + cos(lat1) * cos(lat2) * pow(sin(delta_long/2.0f),2.0f);
float hav_c = 2.0f * atan2(sqrt(hav_a), sqrt(1.0f-hav_a));
/* Calculate the great circle distance in meters. */
return hav_c * hav_r_meters;
}
The Haversine formula will approximate the distance across the surface of a sphere between two positions, with an output in meters. You can optionally update the code to output miles or any other unit by adjusting the coefficient at the top of the function.
Haversine is somewhat imperfect since the Earth isn't a perfect sphere and it calculate the distance in a straight-line, but it's a good overall approximation of distance with minimal computation.
GPS Position DataAnother key function in the application is the NMEA parser (based on minmea) that parses RMC sentences (other sentences can easily be added as needed!):
int
parse_nmea_sentence_release(char *line)
{
switch (minmea_sentence_id(line, false))
{
case MINMEA_SENTENCE_RMC: {
struct minmea_sentence_rmc frame;
if (minmea_parse_rmc(&frame, line)) {
g_gps_coord_fp_deg_last.latitude = minmea_tocoord(&frame.latitude);
g_gps_coord_fp_deg_last.longitude = minmea_tocoord(&frame.longitude);
g_gps_coord_fp_deg_last.speed = minmea_tocoord(&frame.speed);
g_gps_coord_fp_deg_last.is_valid = true;
PRINTF("Current degree coordinates and speed: %f, %f (%f)\r\n",
g_gps_coord_fp_deg_last.latitude,
g_gps_coord_fp_deg_last.longitude,
g_gps_coord_fp_deg_last.speed);
float dist_m = calc_distance(&g_gps_coord_fp_deg_last, &g_gps_coord_fp_deg_trgt);
if (dist_m > 1000.0F) {
/* Show distance in kilometers. */
PRINTF("Distance to target: %.2f km.\r\n", dist_m / 1000.0F);
} else {
/* Show distance in meters */
PRINTF("Distance to target: %.1f meters.\r\n", dist_m);
}
} else {
PRINTF("$xxRMC sentence is not parsed\r\n");
}
} break;
case MINMEA_INVALID: {
/* $xxxxx sentence is not valid */
error_blink();
} break;
default: {
/* $xxxxx sentence is valid but wasn't handled above. */
} break;
}
return 0;
}
In this basic example, we only parse RMC sentences, which stands for the Minimum Recommended Data, and has the following NMEA sentence format (source):
$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A
Where:
- RMC - Recommended Minimum sentence C
- 123519 - Fix taken at 12:35:19 UTC
- A - Status A=active or V=Void.
- 4807.038, N - Latitude 48 deg 07.038' N
- 01131.000, E - Longitude 11 deg 31.000' E
- 022.4 - Speed over the ground in knots
- 084.4 - Track angle in degrees True
- 230394 - Date - 23rd of March 1994
- 003.1, W - Magnetic Variation
- *6A - The checksum data, always begins with *
The key part of this application is the following few lines inside the GPS parsing function above, which will constantly compare the current position to the destination whenever a new location fix is parsed by the parsing engine:
if (dist_m > 1000.0F) {
/* Show distance in kilometers. */
PRINTF("Distance to target: %.2f km.\r\n", dist_m / 1000.0F);
} else {
/* Show distance in meters */
PRINTF("Distance to target: %.1f meters.\r\n", dist_m);
}
Depending on what you want to do, you should extend the code above, such as emitting an increasingly loud or frequency sound on the piezo buzzer as you approach the destination.
Piezo BuzzerDepending on your application requirements, you can optionally enable or disable the piezo buzzer with the following macros:
PIEZO_ON();
/* ... do something ... */
PIEZO_OFF();
The exact frequency emitted from the piezo buzzer (using the SCT peripheral) can be adjusted with the following macro definitions in main.c:
#define SCTIMER_CLK_FREQ (CLOCK_GetFreq(kCLOCK_Fro))
#define SCTIMER_OUT (kSCTIMER_Out_2)
#define SCTIMER_PIEZO_FREQ (4000U)
#define SCTIMER_PIEZO_DUTY (25U)
#define PIEZO_ON() SCTIMER_StartTimer(SCT0, kSCTIMER_Counter_L);
#define PIEZO_OFF() SCTIMER_StopTimer(SCT0, kSCTIMER_Counter_L);
Further DevelopmentsWhile the current proof of concept has the key elements of a simple, portable wayfinder, I'm currently working on an enclosure to make the entire device portable and battery powered from a 3.7V LIPO cell. The hardware files will be available here as I work through some prototypes.
Comments
Please log in or sign up to comment.