STM32 UART Library for MTK3329 GPS Module with binary protocol

STM32 UART Library for MTK3329 GPS Module

Please read Liability Disclaimer and License Agreement CAREFULLY

We can use this library as per example below in main.c

if(GPS_HasData()){
    Time = HAL_GetTick() - Time;
    printf("Interval = %d\r\n", Time);
    Time = HAL_GetTick();
    printf("H=%d M=%d S=%d D=%d M=%d Y=%d Lat=%d Lng=%d Alt=%d SAT=%d\r\n", GPS_Val.UTC_Hour, GPS_Val.UTC_Minute, GPS_Val.UTC_Second, GPS_Val.UTC_Day,GPS_Val.UTC_Month,GPS_Val.UTC_Year, GPS_Val.Latitude, GPS_Val.Longitude, GPS_Val.Altitude, GPS_Val.Satellites);
}

1. Set p U(S)ART and add the IDLE interrupt callback in usart.c. We Will call the function getGPS() when the receive line of usart gps goes idle.

I am using USART1 hardcoded in this example.

void HAL_UART_RxIdleCallback(UART_HandleTypeDef *uartHandle)
{
  if(uartHandle->Instance == USART1){
    uint8_t rxXferCount = 0;
        //wait to finish any transmission first
        while(uartHandle->gState == HAL_UART_STATE_BUSY_TX){}
        //Determine how many items of data have been received/
    rxXferCount = MTK_RX_SIZE - huart1.hdmarx->Instance->NDTR;
        //dataCount = rxXferCount - 2;    
        //Stop DMA    
        HAL_UART_DMAStop(uartHandle);
        //process the message
        if(rxXferCount > 0)
            getGPS();
        uartHandle->RxXferCount = 0;
        
        __HAL_UART_CLEAR_IDLEFLAG(&huart1);
        HAL_UART_Receive_DMA(&huart1, (uint8_t *)GPS_RxBuff, MTK_RX_SIZE);
    } else if(uartHandle->Instance==USART3){
        
        __HAL_UART_CLEAR_IDLEFLAG(&huart3);
        HAL_UART_Receive_DMA(&huart3, (uint8_t *)ESP_RxBuff, ESP_RX_SIZE);        
    }
 }

2. Create the GPS.h file

#ifndef __GPS_H
#define __GPS_H
#ifdef __cplusplus
extern "C" {
#endif

#include "stm32f7xx_hal.h"
#include "main.h"
#include "usart.h"

    #define X10                        10
    #define X100                    100
    #define X1000                    1000
    #define X10000                    10000

    //GPS specific
    #define PREWORD0                0x5A //Z
    #define PREWORD1                0x44 //D
    #define PREWORD2                0x41 //A
    
    #define ENDWORD0                0x0D //CR
    #define ENDWORD1                0x0A //LF
    #define NORTH                    0x4E //N
    #define EST                        0x45 //E
    #define FIX_CHECK                0x30 //E
    #define SEPARATOR                0x2C //,
    
    #define OK_POS                    18

    #define DATE_E                    30
    #define DATE_C                    31
    #define CMD_SIZE                15
    #define OUT_SIZE                51
    
    #define c_r                        0.006
    
    //Disable GPTXT sentence output and save the parameter into flash
    //$PQTXT,W,0,1*23
    //#define MTK_SET_BINARY        "$PMTK253,1,115200*00\r\n" //22
    #define MTK_AIC_ON                "$PMTK286,1*23\r\n" //15
    #define MTK_SBAS_ON                "$PMTK313,1*2E\r\n"
    #define MTK_WAAS_ON                "$PMTK301,2*2E\r\n"
    #define MTK_SET_5HZ                "$PMTK220,200*2C\r\n"
    //#define MTK_NAVTHRES_OFF    "$PMTK397,0*23\r\n"
    //turn on GGA + ZDA (for date)
    #define MTK_SET_OUTPUT "$PMTK314,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0*28\r\n"//51
    
    //UART specific
    #define MTK_RX_SIZE    256

    struct _mtk_msg {
        int32_t Latitude;
        int32_t Longitude;
        int32_t Altitude;
        uint8_t Satellites;
        uint8_t Fix_Type;
        uint8_t UTC_Day;
        uint8_t UTC_Month;
        uint16_t UTC_Year;
        uint8_t UTC_Hour;
        uint8_t UTC_Minute;
        uint8_t UTC_Second;
        uint16_t UTC_mSecond;
    };
    
    extern struct _mtk_msg GPS_Val;
    extern volatile uint8_t GPS_Ready;
    extern volatile uint8_t GPS_LEN;
    extern volatile uint8_t GPS_RxBuff[MTK_RX_SIZE];//buffer for received data from GPS

    void getGPS(void);
    void GPS_Start(void);
    uint8_t GPS_HasData(void);

#ifdef __cplusplus
}
#endif

#endif /* __GPS_H */

3. Create GPS.c file

#include <stdlib.h>
#include <stdio.h>
#include "GPS.h"

struct _mtk_msg GPS_Val;
volatile uint8_t GPS_Ready;
volatile uint8_t GPS_LEN;
static uint8_t gps_data[MTK_RX_SIZE];
volatile uint8_t GPS_RxBuff[MTK_RX_SIZE];

static int32_t Bytes2Long(uint8_t index)
{
    return ((uint32_t)GPS_RxBuff[index + 3] << 24) | ((uint32_t)GPS_RxBuff[index + 2] << 16) | ((uint32_t)GPS_RxBuff[index + 1] << 8) |  ((uint32_t)GPS_RxBuff[index]);
}

static int getValFromMsg(uint8_t startPos, uint8_t nChars){
    uint8_t strLen = nChars + 1;
    uint8_t endPos = startPos + nChars;
    char buff[strLen];
    for(uint8_t idx = startPos; idx < endPos; idx++) {
        buff[idx - startPos] = gps_data[idx];
    }
    buff[strLen] = '\0';
    return atoi(buff);
}

static double getDblFromMsg(uint8_t startPos, uint8_t nChars){
    uint8_t strLen = nChars + 1;
    uint8_t endPos = startPos + nChars;
    char buff[strLen];
    for(uint8_t idx = startPos; idx < endPos; idx++) {
        buff[idx - startPos] = gps_data[idx];
    }
    buff[strLen] = '\0';
    return atof(buff);
}

static uint8_t getCRC(uint8_t startPos){
    unsigned int ret;
    char buff[2] = {gps_data[startPos], gps_data[startPos + 1]};
  sscanf(buff,"%x", &ret);
    return (uint8_t)ret; 
}

//To Do MTK Binary protocol
//Parameters    Length(Byte)    Description
//Preamble        2                0x2404
//Length        2                Total number in the packet from Preamble to End Word.                            
//                                Maximum packet size: 256 bytes
//                                Use little endian
//                                Use one byte alignment
//CommandID        2                0~999: conform to PMTK ASCII protocol
//                                1000~65535: designated for MTK binary protocol
//Data            Variable        Data to be transferred
//Checksum        1                The checksum is the 8-bit exclusive OR of all bytes in the
//                                packet between (but not including) the “Preamble” and the “Checksum”                                            
//EndWord        2                0x0A0D

//GGA example
//$GPGGA,193229.000,6017.5584,N,00516.4752,E,1,5,3.54,39.9,M,43.8,M,,*6F#CR#LF
//$GPGGA,hhmmss.ss,llll.ll,a,yyyyy.yy,a,x,xx,x.x,x.x,M,x.x,M,x.x,xxxx*hh
//1    = UTC
//2    = Latitude
//3    = N or S
//4    = Longitude
//5    = E or W
//6    = GPS quality indicator (0=invalid; 1=GPS fix; 2=Diff. GPS fix)
//7    = Number of satellites in use [not those in view]
//8    = Horizontal dilution of position
//9    = Antenna altitude above/below mean sea level (geoid)
//10   = Meters  (Antenna height unit)
//11   = Geoidal separation (Diff. between WGS-84 earth ellipsoid and
//       mean sea level.  -=geoid is below WGS-84 ellipsoid)
//12   = Meters  (Units of geoidal separation)
//13   = Age in seconds since last update from diff. reference station
//14   = Diff. reference station ID#
//15   = Checksum

//$GPZDA,200436.000,03,05,2020,,*53#CR#LF
//UTC
//Day, 01 to 31
//Month, 01 to 12
//Year
//Local zone description, 00 to +/- 13 hours

void getGPS(void) {
    uint8_t cnt = 0;
    uint8_t checkSum = 0;
    uint8_t checkSumMsg = 0;
    uint8_t checkSumLen = GPS_LEN - 4;
    //If we have valid data \r\n at the end
    if((gps_data[GPS_LEN-1] == ENDWORD0) && (gps_data[GPS_LEN] == ENDWORD1)) {
        //first we process the $GPZDA to get time and date
        //$GPZDA,200436.000,03,05,2020,,*53rn
        for(cnt = 1; cnt < DATE_E; cnt++){
                checkSum ^= gps_data[cnt];
        }
        //t1 = checkSum;
        checkSumMsg = getCRC(DATE_C);
        //t2 = checkSumMsg;
        //If the date/time is OK then we go further
        if(checkSum == checkSumMsg){
            //get UTC Hour pos 7,8 from buffer
            GPS_Val.UTC_Hour = (uint8_t)getValFromMsg(7, 2);
            //get UTC Minutes pos 9,10 from buffer
            GPS_Val.UTC_Minute = (uint8_t)getValFromMsg(9, 2);    
            //get UTC Seconds pos 11,12 from buffer
            GPS_Val.UTC_Second = (uint8_t)getValFromMsg(11, 2);
            //get UTC miliSeconds pos 14,15 and 16 from buffer
            //GPS_Val.UTC_mSecond = (uint16_t)getValFromMsg(14, 2);
            //get UTC day pos 18,19 from buffer
            GPS_Val.UTC_Day = (uint8_t)getValFromMsg(18, 2);
            //get UTC month pos 21,22 from buffer
            GPS_Val.UTC_Month = (uint8_t)getValFromMsg(21, 2);    
            //get UTC year pos 24 to 27 from buffer
            GPS_Val.UTC_Year = (uint16_t)getValFromMsg(24, 4);
            //starting with index 35 we have the $GPGGA sentence
            //$GPGGA,200436.000,6017.5616,N,00516.4400,E,1,5,2.21,113.3,M,43.8,M,,*52rn
            // first we check if we have a fix on the position
            if(gps_data[78] > FIX_CHECK){
                //checksum
                checkSumMsg = getCRC(GPS_LEN - 3);
                //reset the checkSum
                checkSum = 0x00;
                //perform CheckSum on message
                for(cnt = 36; cnt < checkSumLen; cnt++){
                    checkSum ^= gps_data[cnt];
                }
                //t1 = checkSum;
                //t2 = checkSumMsg;
                //Check calculated value against the one from message
                if(checkSum == checkSumMsg){
                    //get Latitude in Seconds pos 18 to 26 from buffer
                    GPS_Val.Latitude = ((uint8_t)getValFromMsg(53, 2)) * 3600 + 
                                                         ((uint8_t)getValFromMsg(55, 2)) * 60 + 
                                                         ((uint16_t)getValFromMsg(58, 4)) * c_r;
                    //If North Latitude is +, If South Latitude is -
                    if(gps_data[63] != NORTH){
                        GPS_Val.Latitude *= -1;
                    }
                    GPS_Val.Longitude = ((uint16_t)getValFromMsg(65, 3)) * 3600 + 
                                                            ((uint8_t)getValFromMsg(68, 2)) * 60 + 
                                                            ((uint16_t)getValFromMsg(71, 4)) * c_r;
                    //If North Latitude is +, If South Latitude is -
                    if(gps_data[76] != EST){
                        GPS_Val.Longitude *= -1;
                    }
                    //this is the last fixed position in the string!!!! also what we can use as integer values
                    // we reuse checkSum variables to save some bytes
                    //checkSum will store "Horizontal dilution of position" start position
                    if(gps_data[81] == SEPARATOR) {
                        GPS_Val.Satellites = (uint8_t)getValFromMsg(80, 1);
                        checkSum = 82;
                    } else {
                        GPS_Val.Satellites = (uint8_t)getValFromMsg(80, 2);
                        checkSum = 83;
                    }
                    // we are left with Horizontal dilution of position, Antenna altitude, Meters
                    //Geoidal separation, Meters, ,,. We need to get only the antena altitude
                    checkSumMsg = 0;
                    for(cnt = checkSum; cnt < GPS_LEN; cnt++) {
                        //find the next comma in the string
                        //first one will be the end of Horizontal dilution of position
                        if(gps_data[cnt] == SEPARATOR){
                            if(!checkSumMsg){
                                checkSumMsg = cnt+1;//the start of altitude
                            } else {
                                checkSumLen = cnt+1;//the end of altitude
                                break;//exit the loop
                            }
                        }
                    }
                    //Altitude is multiplied with 10
                    GPS_Val.Altitude = (int)(getDblFromMsg(checkSumMsg, checkSumLen - checkSumMsg)*10.0);
                } else {
                    printf("GPS position CRC Error\r\n");
                    GPS_LEN = 0;
                }    
            } else {
                printf("GPS FIX Error\r\n");
                GPS_LEN = 0;
            }
        } else {
            printf("GPS Date/Time CRC Error\r\n");
            GPS_LEN = 0;
        }
    } else {
        printf("GPS Packet\r\n");
        GPS_LEN = 0;
    }
}

void GPS_Start(void){
    if(HAL_UART_Receive_DMA(&huart1, (uint8_t *)GPS_RxBuff, MTK_RX_SIZE) != HAL_OK){
        _Error_Handler(__FILE__, __LINE__);
    }
    HAL_UART_Transmit(&huart1, (uint8_t*)MTK_SBAS_ON, CMD_SIZE, PORT_TIMEOUT);
    __HAL_UART_FLUSH_DRREGISTER(&huart1);    
    HAL_UART_Transmit(&huart1, (uint8_t*)MTK_WAAS_ON, CMD_SIZE, PORT_TIMEOUT);
    __HAL_UART_FLUSH_DRREGISTER(&huart1);
    HAL_UART_Transmit(&huart1, (uint8_t*)MTK_AIC_ON, CMD_SIZE, PORT_TIMEOUT);
    __HAL_UART_FLUSH_DRREGISTER(&huart1);
    HAL_UART_Transmit(&huart1, (uint8_t*)MTK_AIC_ON, CMD_SIZE, PORT_TIMEOUT);
    __HAL_UART_FLUSH_DRREGISTER(&huart1);
    HAL_UART_Transmit(&huart1, (uint8_t*)MTK_SET_5HZ, 17, PORT_TIMEOUT);
    __HAL_UART_FLUSH_DRREGISTER(&huart1);        
    HAL_UART_Transmit(&huart1, (uint8_t*)MTK_SET_OUTPUT, OUT_SIZE, PORT_TIMEOUT);
    __HAL_UART_FLUSH_DRREGISTER(&huart1);
    HAL_Delay(PORT_TIMEOUT);
    __HAL_UART_FLUSH_DRREGISTER(&huart1);    
    //Wait to get the GPGGA enabled
    while((GPS_RxBuff[3] != PREWORD0) && (GPS_RxBuff[4] != PREWORD1) && (GPS_RxBuff[5] != PREWORD2)){
        __HAL_UART_FLUSH_DRREGISTER(&huart1);
        //HAL_UART_Transmit(&huart1, (uint8_t*)MTK_SET_OUTPUT, OUT_SIZE, UART_WAIT);
        HAL_Delay(100);
    }
}

uint8_t GPS_HasData(void){
    if(GPS_LEN){
        //if we have data first check if we are receiving new data
        //while(hdma_uart4_rx.State == HAL_BUSY){}
        //process available data
        getGPS();    
        GPS_LEN = 0;
        return 1;    
    } else {
        return 0;
    }
}

Comments powered by CComment