GPS tracker on PGBoard, STM32, NB-IoT communication

RosarIOT
Posts: 1
Joined: Thu 08. Aug 2019 17:59:25

GPS tracker on PGBoard, STM32, NB-IoT communication

Post by RosarIOT » Wed 18. Sep 2019 17:27:24

I recently discovered STM32 CubeMX, Nucleo-64 boards and got hooked into firmware development. Here is an tutorial/example of GPS tracker, using PGBoard, which is based on Nucleo-64 L452RE board, with STM32l452RE microprocessor. It is a low-consumption board, ideal for GPS tracker which should stay months without charging.

The original firmware for PGBoard has a lot of embedded functionalities and drivers to fully manage the board, you can download it here: https://www.m2mc.cz/files/nbiotevk_v2-0-5.zip


If you are using MacOS and you don't know how to get started, you can get useful info on this previous tutorial (viewtopic.php?f=65&t=17&p=17&hilit=maco ... cubemx#p17)

Also I wrote a previous tutorial to show how to make a led blink, you can have a look as I use some of the LED functionalities: viewtopic.php?f=65&t=18

Let's first define the structure in PGBoard main files, and calls to main loop and initializations phases.

Main files changes


Src/main.c (diff)

Code: Select all

  ...
  BSP_ACCELERO_Init(); /* Init accelerometer */
  BSP_HSENSOR_Init();
  BSP_TSENSOR_Init();
+  APP_Init();
  /* USER CODE END 2 */
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */
    /* USER CODE BEGIN 3 */
		NB_Handler();
		CmdLineHandler();
		BUTTON_Handler();
		NMEA_Handler();
		HALLSENSE_Handler();
+		APP_Handler();
  }
  /* USER CODE END 3 */
}
...


Src/stm32l4xx_it.c (diff)

Code: Select all

  ...
  BUTTON_TimerHandler();
  LED_Handler();
  NB_TimerHandler();
+  APP_TimerHandler();
  /* USER CODE END SysTick_IRQn 1 */
}

Drivers/BSP/board.h (diff)

Code: Select all

...
#include "cmdline.h"
#include "usart.h"
#include "nb.h"
+#include "app.h"
#include "button.h"
#include "mram.h"
#include "spi.h"
...

The app!


The simplest low-power handler we can imagine must do the following:
  • Waiting for timer
  • Waking up GPS
  • Send GPS data
  • Turn off GPS

We need then a function to toggle GPS on/off and a function to send data through NB-IoT integrated communication chip. Hopefully most of the code has been written for us:

GPS:
NMEA_ToggleON();
/* GNSSInfo.Mode can have following values
* GNSS_MODE_OFF power off
* GNSS_MODE_TRACKING: getting signal
* GNSS_MODE_FIXED: position has been fixed, in that case position is found in GNSSInfo.Latitude, GNSSInfo.Longitude.
*/
NB-IoT communication:

The packet library will encode the data and send over 5G network (or LTE/4G if 5G is not available). It is possible to send data types of 1 byte, 2 bytes, 4 bytes or string.
PACKET_Start();
PACKET_AddElement(ELEMENT_ID_LATITUDE, ELEMENT_TYPE_4BYTE, (uint8_t *)&GNSSInfo.Latitude, 4);
PACKET_AddElement(ELEMENT_ID_LONGITUDE, ELEMENT_TYPE_4BYTE, (uint8_t *)&GNSSInfo.Longitude, 4);
PACKET_Finish();
So our code looks like the following:


Drivers/BSP/app.h (create new file)

Code: Select all

#ifndef _DRV_APP_H_
#define _DRV_APP_H_
#include "board.h"
typedef enum
{
	APP_PING = 0,
	APP_CRUISE = 1,
	APP_PING_NEXT = 3,
} APP_PhaseType; 
 
typedef struct
{
	APP_PhaseType PhaseID;
} APP_Info_TypeDef;
 
extern APP_Info_TypeDef APP_Info;
void APP_Init(void);
void APP_TimerHandler(void);
void APP_Handler(void);
#endif

Drivers/BSP/app.c (create new file)

Code: Select all

#include "app.h"

#include <stdio.h>
#include <stm32l4xx.h>
#include <stm32l4xx_hal_uart.h>
#include <string.h>
#include <sys/_stdint.h>

#include "board.h"
#include "buffer.h"
#include "cmdline.h"
#include "mram.h"
#include "packet.h"
#include "nmea.h"

APP_Info_TypeDef APP_Info;


void APP_Init(void)
{
	printf("APP_Init\n");
	APP_Info.PhaseID = APP_PING;
	// toggle GPS
	NMEA_ToggleON();
}

//Define timeout,  first ping after 5 min, to give time to GPS to get signal
uint32_t APP_Timeout = 300000;

// Accelerometer timeout
uint32_t APP_AccTimeout = 10000;

//Define ping time for tracker, 10min
#define APP_PING_TIME 600000

void APP_TimerHandler(void)
{
	if (APP_Timeout) APP_Timeout--;
	if (APP_AccTimeout) APP_AccTimeout--;
}

void APP_wakeGPS(){
		// toggle ON GPS 30 sec before sending event
		if (APP_Timeout == 30000) {
				if (GNSSInfo.Mode == GNSS_MODE_OFF) {
					NMEA_ToggleON();
					printf("[APP_waitGPS] GPS toggle ON\r\n");
				}
		}
}

void APP_sendMessage() {
		float tmp_f = 0;
		// generate packet to send
		PACKET_Start();

		/* Send position to packet */
		printf("[APP_sendMessage] GNSS: %.6f %.6f\r\n", GNSSInfo.Latitude, GNSSInfo.Longitude);
		PACKET_AddElement(ELEMENT_ID_LATITUDE, ELEMENT_TYPE_4BYTE, (uint8_t *)&GNSSInfo.Latitude, 4);
		PACKET_AddElement(ELEMENT_ID_LONGITUDE, ELEMENT_TYPE_4BYTE, (uint8_t *)&GNSSInfo.Longitude, 4);

		// finish and send packet
		PACKET_Finish();
}

void APP_Handler(void)
{
	APP_wakeGPS();
	if (APP_Timeout) return;

	APP_Timeout = APP_PING_TIME;
	
	// quick led flash when sending data
	LED_SetState(LED_GREEN, 0x01);

	// send message with GPS/temp info
	APP_sendMessage();

	// toggle OFF
	if (GNSSInfo.Mode != GNSS_MODE_OFF) {
		printf("[APP] GPS toggle OFF signal\r\n");
		NMEA_ToggleON();
	}
}

Running and Debugging

If you connect your PGBoard through the ST-Link debugger you can get see all code's printf() calls:

Code: Select all

minicom -D /dev/tty.usbmodem14113

---(FW:nbiotevk [Sep 17 2019 14:56:47] ver.: 2.0.5)---
RAI_Init
[NMEA_ToggleON] state=2
 ===== NB connection ===== 
NB MODE=00
NB MODE=01
... 
NB> device is running.
...
 ===== GPS Start tracking ===== 
Sats(0/0): 0.000000,0.000000
Sats(0/0): 0.000000,0.000000
Sats(0/0): 0.000000,0.000000
...
Sats(0/0): 50.083523,14.446124
...
 ===== SendMessage with GPS coordinate then switch OFF ===== 
[APP_sendMessage] GNSS: 50.083523 14.446124
[APP_Handler] toggle OFF if mod!=0, lat=0.000000 mod=0
...
 ===== Manage NB-IoT connection ===== 
 NB_ATCommandSend
   at+qiopen=1,0,"UDP",192.168.0.20,4242,4455,0,0
NB_ATCommandSend:     at+qisendex=0100475349b52638363739393730333032383137343409010202290305041c159d410305038ef41d42020305f0ff0203063000020307f0ff03050a5f55484203050bd0e5664101020c00b714


That's it!! We have a basic GPS tracker that will send our position every 10 minutes. The GPS will be powered on only 30 sec every 10 minutes, and help battery usage.

However, we can think about a more complex example, where we have 2 modes (PING/CRUISE).

PING Mode: will send data every 4 hours
CRUISE Mode: data is sent every 10 min
When the device is still for more than 10 min, it will go to PING Mode.
When the device is in PING Mode, a movement will switch mode to CRUISE and send GPS data.

In the code I also added a APP_PING_NEXT Mode, which means the Mode will be set to PING if no movements are detected for 5 minutes.

To know if the device is in movement we need to store the values for Accelerometer sensor with function BSP_ACCELERO_AccGetXYZ() and check the variation in last 5 seconds; if the difference is bigger than 1,000 on axis X,Y or Z, then we can think there is a movement. Those numbers are based on some tests I made, feel free to use the calibration/speed you want to make sure you trigger the tracking when it suits you.

Code: Select all

int16_t last_acc[3] = {0, 0, 0};
int16_t diff_acc[3] = {0, 0, 0};
uint8_t APP_isAccelerating() {
	int16_t acc[3] = {0, 0, 0};
	uint8_t APP_isAcceleratingRet = 0;
	if(APP_AccTimeout==5000) {
		BSP_ACCELERO_AccGetXYZ(acc);
	}
	if(APP_AccTimeout==0) {
		BSP_ACCELERO_AccGetXYZ(acc);
		diff_acc[0] = last_acc[0] - acc[0];
		diff_acc[1] = last_acc[1] - acc[1];
		diff_acc[2] = last_acc[2] - acc[2];
		if(
			(abs(diff_acc[0])>1000)
			||
			(abs(diff_acc[1])>1000)
			||
			(abs(diff_acc[2])>1000)
			)
		{
			APP_isAcceleratingRet = 1;
			LED_SetState(LED_GREEN, 0x33);
		}
		printf("[APP_isAccelerating] ACC: x=%d y=%d z=%d\r\n", acc[0], acc[1], acc[2]);
		printf("[APP_isAccelerating]: phase=%d, timeout=%ld, accel=%d, Dx=%d Dy=%d Dz=%d\r\n",APP_Info.PhaseID, APP_Timeout, APP_isAcceleratingRet, diff_acc[0], diff_acc[1] , diff_acc[2]);
		last_acc[0] = acc[0];
		last_acc[1] = acc[1];
		last_acc[2] = acc[2];
		APP_AccTimeout = 10000;
	}
	return APP_isAcceleratingRet;

}
// in CRUISE MODE, send GPS data every 10 min otherwise every 4 hours
#define APP_CRUISE_TIME 600000
#define APP_PING_TIME 14400000


void APP_Handler(void)
{
	// check acceleration to change mode
	uint8_t APP_accelerating;
	APP_accelerating = APP_isAccelerating();
	if(APP_accelerating && APP_Info.PhaseID==APP_PING){
		APP_Info.PhaseID = APP_CRUISE;
		printf("[APP_Handler] phase=%d, accel: going to CRUISE MODE in 30 sec\r\n",APP_Info.PhaseID);
		APP_Timeout = 30000;
	}
	if(APP_accelerating && APP_Info.PhaseID==APP_PING_NEXT){
		APP_Info.PhaseID = APP_CRUISE;
		printf("[APP_Handler] phase=%d, accel: going to CRUISE MODE in %d sec\r\n",APP_Info.PhaseID, (int)APP_Timeout/1000);
	}

	APP_wakeGPS();

	if (APP_Timeout) return;

	switch(APP_Info.PhaseID)
	{
		case APP_PING_NEXT:
			APP_Timeout = APP_PING_TIME;
			APP_Info.PhaseID = APP_PING;
			break;
		case APP_PING:
			APP_Timeout = APP_PING_TIME;
			break;
		case APP_CRUISE:
			if(!APP_accelerating){
				APP_Info.PhaseID = APP_PING_NEXT;
				printf("[APP_Handler] no accel: going to APP_PING_NEXT MODE in 5min sec\r\n");
			}
			APP_Timeout = APP_CRUISE_TIME;
			break;
	}

	printf("[APP_Handler] phase=%d, timeout=%ld \r\n", APP_Info.PhaseID, APP_Timeout);

	// quick led flash when sending data
	LED_SetState(LED_GREEN, 0x01);

	// send message with GPS/temp info
	APP_sendMessage();

	// toggle OFF
	printf("[APP_Handler] toggle OFF if mod!=0, lat=%f mod=%d\r\n", GNSSInfo.Latitude, GNSSInfo.Mode);
	if (GNSSInfo.Mode != GNSS_MODE_OFF) {
		printf("[APP] GPS toggle OFF signal\r\n");
		NMEA_ToggleON();
	}
}


I hope this was useful for you, I will next time post an article on how to communicate through NB-IoT with PGBoard (send/receive messages).

I put all tracking in a simple table, check it out at http://meter.pgboard.eu/tracker.php
gps_tracker.jpg
gps_tracker.jpg (123.55 KiB) Viewed 1188 times