In this exercise, we will use the GNSS interface in the Modem library to enable the GNSS receiver on the nRF91 Series SiP and retrieve the GPS coordinates.
1. In the GitHub repository for this course, go to the base code for this exercise, found in lesson6/cellfund_less6_exer1
of whichever version directory you are using (v2.2.0-v2.3.0
or v2.4.0-v2.x.x
).
2. Enable relevant configurations in prj.conf
.
2.1 Enable the modem’s network mode for GPS through CONFIG_LTE_NETWORK_MODE_LTE_M_NBIOT
.
CONFIG_LTE_NETWORK_MODE_LTE_M_NBIOT_GPS=y
Kconfig2.2 Enable the following two configurations two enable printing floating-point numbers to console.
CONFIG_NEWLIB_LIBC_FLOAT_PRINTF=y
CONFIG_FPU=y
KconfigThe GNSS interface is included when enabling the modem library (CONFIG_NRF_MODEM_LIB
).
3. In the Kconfig
file define the configurations for the fix interval and fix retry period.
3.1 Define the configuration GNSS_PERIODIC_INTERVAL
that specifies at which interval the GNSS receiver will initiate searching for a fix.
config GNSS_PERIODIC_INTERVAL
int "Fix interval for periodic GPS fixes"
range 10 65535
default 120
help
Fix interval (in seconds) for periodic fixes.
Kconfig3.2 Define the configuration GNSS_PERIODIC_TIMEOUT
, which sets the maximum time the GNSS receiver is allowed to run while trying to produce a valid PVT estimate.
config GNSS_PERIODIC_TIMEOUT
int "Fix timeout for periodic GPS fixes"
range 0 65535
default 480
help
Fix timeout (in seconds) for periodic fixes.
If set to zero, GNSS is allowed to run indefinitely until a valid PVT estimate is produced.
KconfigWe are configuring the GNSS in periodic mode, by setting the fix interval to 2 minutes and the fix retry period to 8 minutes.
4. In main.c
, include the header file for the GNSS interface.
#include <nrf_modem_gnss.h>
C5. Define the PVT data frame variables, of type struct nrf_modem_gnss_pvt_data_frame
. This will be used when reading PVT data from GNSS.
static struct nrf_modem_gnss_pvt_data_frame pvt_data;
C6. Define the function print_fix_data()
, to log fix data in a readable format.
This function will take a parameter of type struct nrf_modem_gnss_pvt_data_frame
and print out the members double latitude
, double longitude
, float altitude
and struct nrf_modem_gnss_datetime datetime
static void print_fix_data(struct nrf_modem_gnss_pvt_data_frame *pvt_data)
{
LOG_INF("Latitude: %.06f", pvt_data->latitude);
LOG_INF("Longitude: %.06f", pvt_data->longitude);
LOG_INF("Altitude: %.01f m", pvt_data->altitude);
LOG_INF("Time (UTC): %02u:%02u:%02u.%03u",
pvt_data->datetime.hour,
pvt_data->datetime.minute,
pvt_data->datetime.seconds,
pvt_data->datetime.ms);
}
CWhen printing floats, the format specifier (%*.*f
) determines how many digits to print. The number before the decimal point sets the number of integers and the number after sets the number of decimals.
7. In the event handler gnss_event_handler()
, add the events EVT_PVT
, EVT_PERIODIC_WAKEUP
and EVT_SLEEP_AFTER_FIX
.
7.1 On a PVT event, read PVT data from GNSS, check if it’s a valid fix and print fix data to console.
Pass the data frame variable pvt_data
, and the data type NRF_MODEM_GNSS_DATA_PVT
to nrf_modem_gnss_read()
to read the PVT data. Then check if it’s a valid fix by seeing if the NRF_MODEM_GNSS_PVT_FLAG_FIX_VALID
flag is set. If so, call print_fix_data()
to print the information to console.
case NRF_MODEM_GNSS_EVT_PVT:
LOG_INF("Searching...");
/* STEP 15 - Print satellite information */
err = nrf_modem_gnss_read(&pvt_data, sizeof(pvt_data), NRF_MODEM_GNSS_DATA_PVT);
if (err) {
LOG_ERR("nrf_modem_gnss_read failed, err %d", err);
return;
}
if (pvt_data.flags & NRF_MODEM_GNSS_PVT_FLAG_FIX_VALID) {
dk_set_led_on(DK_LED1);
print_fix_data(&pvt_data);
/* STEP 12.3 - Print the time to first fix */
return;
}
break;
CIgnore the comments, that’s to help you later.
7.2. Add EVT_PERIODIC_WAKEUP
and EVT_SLEEP_AFTER_FIX
to the event handler, and log the events to console.
This will tell us when the GNSS is sleeping and when it wakes up to get the next fix.
case NRF_MODEM_GNSS_EVT_PERIODIC_WAKEUP:
LOG_INF("GNSS has woken up");
break;
case NRF_MODEM_GNSS_EVT_SLEEP_AFTER_FIX:
LOG_INF("GNSS enters sleep after fix");
break;
C8. In main()
, activate the GNSS stack only.
Using lte_lc_func_mode_set()
, set the modem to mode LTE_LC_FUNC_MODE_ACTIVATE_GNSS
. This will activate only the GNSS stack.
if (lte_lc_func_mode_set(LTE_LC_FUNC_MODE_ACTIVATE_GNSS) != 0) {
LOG_ERR("Failed to activate GNSS functional mode");
return;
}
CIn an actual application, you wouldn’t completely deactivate LTE but rather configure PSM or eDRX so that the GNSS and LTE can be used rather often without having to completely activate and deactivate the modes.
We will take a look at how to do this in Exercise 2.
9. Then register the GNSS event handler, using nrf_modem_gnss_event_handler_set()
.
if (nrf_modem_gnss_event_handler_set(gnss_event_handler) != 0) {
LOG_ERR("Failed to set GNSS event handler");
return;
}
C10. Using nrf_modem_gnss_fix_interval_set()
and nrf_modem_gnss_fix_retry_set()
, set the GNSS fix interval and GNSS fix retry period, which we defined in the Kconfig
file.
if (nrf_modem_gnss_fix_interval_set(CONFIG_GNSS_PERIODIC_INTERVAL) != 0) {
LOG_ERR("Failed to set GNSS fix interval");
return;
}
if (nrf_modem_gnss_fix_retry_set(CONFIG_GNSS_PERIODIC_TIMEOUT) != 0) {
LOG_ERR("Failed to set GNSS fix retry");
return;
}
C11. When everything is configured, start the GNSS receiver using nrf_modem_gnss_start()
.
LOG_INF("Starting GNSS");
if (nrf_modem_gnss_start() != 0) {
LOG_ERR("Failed to start GNSS");
return;
}
C12. Upon the first fix, print the time it took from GNSS start to first fix (Time-To-First-Fix).
12.1 Declare the variable gnss_start_time
to store the GNSS start time and a flag first_fix
.
static int64_t gnss_start_time;
static bool first_fix = false;
C12.2 After starting GNSS, log the system uptime using k_uptime_get()
.
gnss_start_time = k_uptime_get();
C12.3 In gnss_event_handler()
, check whether valid fixes are the first using the first_fix
flag, and if so, print the time from GNSS start to the current system uptime and set the first_fix
flag false
.
if (!first_fix) {
first_fix = true;
dk_set_led_on(DK_LED1);
LOG_INF("Time to first fix: %2.1lld s", (k_uptime_get() - gnss_start_time)/1000);
}
C13. Build the exercise and flash it to your board.
14. To test the application, make sure you are outdoors under an open sky to achieve the best conditions.
14.1 Run the application until the LED indicating the first fix lights up. Your console should look something like this.
*** Booting nRF Connect SDK 2.6.1-3758bcbfa5cd ***
[00:00:00.234,609] <inf> Lesson6_Exercise1: Initializing modem library
[00:00:00.583,709] <inf> Lesson6_Exercise1: Connecting to LTE network
[00:00:01.562,622] <inf> Lesson6_Exercise1: RRC mode: Connected
[00:00:03.604,370] <inf> Lesson6_Exercise1: Network registration status: Connected - roaming
[00:00:03.604,553] <inf> Lesson6_Exercise1: Connected to LTE network
[00:00:03.604,553] <inf> Lesson6_Exercise1: Deactivating LTE
[00:00:05.000,640] <inf> Lesson6_Exercise1: RRC mode: Idle
[00:00:05.076,110] <inf> Lesson6_Exercise1: Activating GNSS
[00:00:05.078,826] <inf> Lesson6_Exercise1: Starting GNSS
[00:00:05.104,583] <inf> Lesson6_Exercise1: Searching...
[00:00:06.108,734] <inf> Lesson6_Exercise1: Searching...
[00:00:07.110,412] <inf> Lesson6_Exercise1: Searching...
[00:00:08.109,893] <inf> Lesson6_Exercise1: Searching...
[00:00:09.109,954] <inf> Lesson6_Exercise1: Searching...
[00:00:10.110,687] <inf> Lesson6_Exercise1: Searching...
[00:00:11.110,656] <inf> Lesson6_Exercise1: Searching...
[00:00:12.110,748] <inf> Lesson6_Exercise1: Searching...
[00:00:13.110,687] <inf> Lesson6_Exercise1: Searching...
[00:00:14.111,053] <inf> Lesson6_Exercise1: Searching...
[00:00:15.110,870] <inf> Lesson6_Exercise1: Searching...
[00:00:16.111,755] <inf> Lesson6_Exercise1: Searching...
[00:00:17.110,900] <inf> Lesson6_Exercise1: Searching...
[00:00:18.111,938] <inf> Lesson6_Exercise1: Searching...
[00:00:19.111,633] <inf> Lesson6_Exercise1: Searching...
[00:00:20.111,419] <inf> Lesson6_Exercise1: Searching...
[00:00:21.112,030] <inf> Lesson6_Exercise1: Searching...
[00:00:22.111,999] <inf> Lesson6_Exercise1: Searching...
[00:00:23.112,091] <inf> Lesson6_Exercise1: Searching...
[00:00:24.112,060] <inf> Lesson6_Exercise1: Searching...
[00:00:25.112,182] <inf> Lesson6_Exercise1: Searching...
[00:00:28.112,243] <inf> Lesson6_Exercise1: Searching...
[00:00:29.112,396] <inf> Lesson6_Exercise1: Searching...
[00:00:30.112,304] <inf> Lesson6_Exercise1: Searching...
[00:00:31.112,365] <inf> Lesson6_Exercise1: Searching...
[00:00:32.112,396] <inf> Lesson6_Exercise1: Searching...
[00:00:33.112,548] <inf> Lesson6_Exercise1: Searching...
[00:00:34.112,518] <inf> Lesson6_Exercise1: Searching...
[00:00:35.112,579] <inf> Lesson6_Exercise1: Searching...
[00:00:36.112,579] <inf> Lesson6_Exercise1: Searching...
[00:00:37.112,701] <inf> Lesson6_Exercise1: Searching...
[00:00:38.112,670] <inf> Lesson6_Exercise1: Searching...
[00:00:39.178,436] <inf> Lesson6_Exercise1: Searching...
[00:00:39.178,497] <inf> Lesson6_Exercise1: Latitude: 63.421139
[00:00:39.178,527] <inf> Lesson6_Exercise1: Longitude: 10.437277
[00:00:39.178,527] <inf> Lesson6_Exercise1: Altitude: 157.6 m
[00:00:39.178,558] <inf> Lesson6_Exercise1: Time (UTC): 15:18:00.567
[00:00:39.178,558] <inf> Lesson6_Exercise1: Time to first fix: 34 s
[00:00:39.180,928] <inf> Lesson6_Exercise1: GNSS enter sleep after fix
TerminalWe can see that the time to first fix took 34 seconds, which is an expected amount of time.
14.2. Now run the appliation for around 10 minutes. The output should look something like this.
[00:00:36.771,087] <inf> Lesson6_Exercise1: Searching...
[00:00:37.771,179] <inf> Lesson6_Exercise1: Searching...
[00:00:38.820,800] <inf> Lesson6_Exercise1: Searching...
[00:00:38.820,861] <inf> Lesson6_Exercise1: Latitude: 63.421116
[00:00:38.820,892] <inf> Lesson6_Exercise1: Longitude: 10.437321
[00:00:38.820,892] <inf> Lesson6_Exercise1: Altitude: 154.2 m
[00:00:38.820,892] <inf> Lesson6_Exercise1: Time (UTC): 10:28:49.022
[00:00:38.820,922] <inf> Lesson6_Exercise1: Time to first fix: 34 s
[00:00:38.822,265] <inf> Lesson6_Exercise1: GNSS enter sleep after fix
[00:02:04.753,479] <inf> Lesson6_Exercise1: GNSS has woken up
[00:02:04.782,135] <inf> Lesson6_Exercise1: Searching...
[00:02:05.813,415] <inf> Lesson6_Exercise1: Searching...
[00:02:05.813,476] <inf> Lesson6_Exercise1: Latitude: 63.421127
[00:02:05.813,507] <inf> Lesson6_Exercise1: Longitude: 10.437298
[00:02:05.813,507] <inf> Lesson6_Exercise1: Altitude: 158.2 m
[00:02:05.813,537] <inf> Lesson6_Exercise1: Time (UTC): 10:30:16.030
[00:02:05.814,666] <inf> Lesson6_Exercise1: GNSS enter sleep after fix
[00:04:04.758,117] <inf> Lesson6_Exercise1: GNSS has woken up
[00:04:04.786,712] <inf> Lesson6_Exercise1: Searching...
[00:04:05.815,643] <inf> Lesson6_Exercise1: Searching...
[00:04:05.815,704] <inf> Lesson6_Exercise1: Latitude: 63.421158
[00:04:05.815,734] <inf> Lesson6_Exercise1: Longitude: 10.437305
[00:04:05.815,734] <inf> Lesson6_Exercise1: Altitude: 161.0 m
[00:04:05.815,734] <inf> Lesson6_Exercise1: Time (UTC): 10:32:16.030
[00:04:05.817,047] <inf> Lesson6_Exercise1: GNSS enter sleep after fix
TerminalFrom the time stamps, it is clear that after getting the first fix, the GNSS sleeps then wakes up when the GNSS fix interval has passed (120 seconds or 2 minutes) and starts searching for a new fix. Then it sleeps and repeats.
Let’s modify the application to print a bit more debugging information when the GNSS is running.
15. Print the satellites that the GNSS is currently tracking and their signal strength.
The PVT event payload contains the array sv[12]
of type struct nrf_modem_gnss_sv
, which describes up to 12 of the space vehicles used for the measurements. It has the following signature
We are interested in the carrier-to-noise density ratio for all the valid satellites the GNSS is currently tracking, which is found in the member cn0
. This tells us something about the signal strength from that satellite and it has to be above 30 dB/Hz (cn0 = 300
) for the GNSS receiver to consider the satellite “healthy” and use it in calculations.
Insert the following code in the PVT event in gnss_event_handler()
.
int num_satellites = 0;
for (int i = 0; i < 12 ; i++) {
if (pvt_data.sv[i].signal != 0) {
LOG_INF("sv: %d, cn0: %d", pvt_data.sv[i].sv, pvt_data.sv[i].cn0);
num_satellites++;
}
}
LOG_INF("Number of satellites: %d", num_satellites);
C16. Build the exercise and flash it to your board.
You should have a log output that looks something like this.
[00:00:05.153,625] <inf> Lesson6_Exercise1: Searching...
[00:00:05.153,656] <inf> Lesson6_Exercise1: Number of satellites: 0
[00:00:06.157,745] <inf> Lesson6_Exercise1: Searching...
[00:00:06.157,775] <inf> Lesson6_Exercise1: Number of satellites: 0
[00:00:07.158,843] <inf> Lesson6_Exercise1: Searching...
[00:00:07.158,874] <inf> Lesson6_Exercise1: sv: 18, cn0: 453
[00:00:07.158,874] <inf> Lesson6_Exercise1: sv: 30, cn0: 486
[00:00:07.158,905] <inf> Lesson6_Exercise1: sv: 27, cn0: 432
[00:00:07.158,905] <inf> Lesson6_Exercise1: sv: 8, cn0: 428
[00:00:07.158,905] <inf> Lesson6_Exercise1: sv: 5, cn0: 446
[00:00:07.158,935] <inf> Lesson6_Exercise1: sv: 13, cn0: 453
[00:00:07.158,935] <inf> Lesson6_Exercise1: sv: 7, cn0: 492
[00:00:07.158,966] <inf> Lesson6_Exercise1: sv: 14, cn0: 461
[00:00:07.158,966] <inf> Lesson6_Exercise1: sv: 15, cn0: 465
[00:00:07.158,996] <inf> Lesson6_Exercise1: sv: 28, cn0: 421
[00:00:07.158,996] <inf> Lesson6_Exercise1: Number of satellites: 10
[00:00:08.158,874] <inf> Lesson6_Exercise1: Searching...
[00:00:08.158,905] <inf> Lesson6_Exercise1: sv: 18, cn0: 445
[00:00:08.158,905] <inf> Lesson6_Exercise1: sv: 30, cn0: 484
[00:00:08.158,935] <inf> Lesson6_Exercise1: sv: 27, cn0: 432
[00:00:08.158,935] <inf> Lesson6_Exercise1: sv: 8, cn0: 428
[00:00:08.158,966] <inf> Lesson6_Exercise1: sv: 5, cn0: 446
[00:00:08.158,966] <inf> Lesson6_Exercise1: sv: 13, cn0: 453
[00:00:08.158,966] <inf> Lesson6_Exercise1: sv: 7, cn0: 492
[00:00:08.158,996] <inf> Lesson6_Exercise1: sv: 14, cn0: 461
[00:00:08.158,996] <inf> Lesson6_Exercise1: sv: 15, cn0: 465
[00:00:08.159,027] <inf> Lesson6_Exercise1: sv: 28, cn0: 421
[00:00:08.159,057] <inf> Lesson6_Exercise1: sv: 20, cn0: 432
[00:00:08.159,057] <inf> Lesson6_Exercise1: Number of satellites: 11
TerminalPrinting the satellite information and signal strength is useful for debugging purposes if your application is not getting a fix.
Because the GNSS is solving for four unknown variables, it needs at least 4 satellites to get a valid fix.
The solution for this exercise can be found in lesson6/cellfund_less6_exer1_solution
of whichever version directory you are using (v2.2.0-v2.3.0
or v2.4.0-v2.x.x
).