Hardware Interface and Communication Protocol
The ATGM336H module, produced by Zhongke Microelectronics, is a high-performance GNSS receiver supporting multiple satellite systems including BDS, GPS, GLONASS, and Galileo. It communicates via UART interface with a default baud rate of 9600. The module operates within a voltage range of 3.3V to 5V, making it compatible with standard STM32 logic levels. For basic positioning applications, only four pins are required: VCC, GND, TXD (connected to MCU RX), and RXD (connected to MCU TX). The PPS (Pulse Per Second) pin can be left unconnected for simple tracking tasks.
Effective positioning requires an outdoor environment with an unobstructed view of the sky. The ceramic antenna patch must face upwards. Tall buildings or dense urban environments may cause signal multipath effects or shielding, resulting in positioning failure. The module outputs data continuously upon power-up. The data stream follows the NMEA-0183 standard, containing various sentence types such as GNRMC, GNGGA, and GNGSA. The GNRMC (Recommended Minimum Specific GPS/Transit Data) sentence provides the essential time, latitude, longitude, and status information.
NMEA Data Parsing Strategies
A typical GNRMC frame appears as follows: $GNRMC,121520.000,A,3438.1766,N,11224.5016,E,0.08,292.36,140816,,,A*77. The fields are comma-delimited. The third field indicates the validity of the data ('A' for Active/Valid, 'V' for Void/Invalid). The fourth and fifth fields represent latitude and its hemisphere (N/S), while the sixth and seventh fields represent longitude and its hemisphere (E/W).
The raw coordinate format from the module is Degrees-Minutes (ddmm.mmmm). For mapping applications, this must be converted to Decimal Degrees (dd.dddddd). The conversion formula involves separating the degrees and minutes, then dividing the minutes by 60:
Decimal_Degrees = Degrees + (Minutes / 60)For example, a raw latitude of 3438.1766 is parsed as 34 degrees and 38.1766 minutes. The converted value is 34 + (38.1766 / 60) ≈ 34.63627.
STM32 Driver Implementation
The driver implementation involves configuring the UART peripheral, handling reception interrupts to buffer incoming data, and parsing the buffer to extract coordinates.
UART Peripheral Configuration
The following code initializes USART1 on PA9 (TX) and PA10 (RX) with a baud rate of 9600, enabling the receive interrupt.
#include "stm32f10x.h"
#include "string.h"
#include "stdio.h"
#include "stdlib.h"
#define RX_BUF_LEN 256
typedef struct {
char rx_buffer[RX_BUF_LEN];
uint16_t rx_index;
char parsed_lat[16];
char parsed_ns[2];
char parsed_lon[16];
char parsed_ew[2];
uint8_t data_ready;
} GPS_State;
volatile GPS_State gps;
void UART1_Init(uint32_t baud_rate) {
GPIO_InitTypeDef GPIO_InitStruct;
USART_InitTypeDef USART_InitStruct;
NVIC_InitTypeDef NVIC_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);
// Configure PA9 as TX (Alternate Function Push-Pull)
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// Configure PA10 as RX (Floating Input)
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &gpioInitStruct);
// USART Configuration
USART_InitStruct.USART_BaudRate = baud_rate;
USART_InitStruct.USART_WordLength = USART_WordLength_8b;
USART_InitStruct.USART_StopBits = USART_StopBits_1;
USART_InitStruct.USART_Parity = USART_Parity_No;
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1, &USART_InitStruct);
// Enable USART1 Receive Interrupt
NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
USART_Cmd(USART1, ENABLE);
}Interrupt Service Routine
The ISR captures bytes until a newline character is detected, indicating the end of an NMEA sentence. It checks for the "$GNRMC" header to filter relevant data.
void USART1_IRQHandler(void) {
if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
char received_char = USART_ReceiveData(USART1);
// Reset buffer on start character
if (received_char == '$') {
gps.rx_index = 0;
memset((void*)gps.rx_buffer, 0, RX_BUF_LEN);
}
if (gps.rx_index < RX_BUF_LEN - 1) {
gps.rx_buffer[gps.rx_index++] = received_char;
}
// Check for end of line
if (received_char == '\n') {
// Verify if this is a GNRMC sentence
if (strstr(gps.rx_buffer, "GNRMC") != NULL) {
gps.data_ready = 1; // Flag for main loop processing
}
gps.rx_index = 0; // Reset for next frame
}
}
}Coordinate Parsing and Conversion Logic
The parsing function splits the NMEA string by commas. Standard C library functions like strtok or pointer arithmetic can be used to locate field separators. The following implementation extracts latitude and longitude strings and converts them to floating-point decimal degrees.
void Parse_GPS_Data(void) {
if (!gps.data_ready) return;
char *token;
int field_count = 0;
char *buffer_ptr = gps.rx_buffer;
// Iterate through comma-separated fields
while ((token = strtok_r(buffer_ptr, ",", &buffer_ptr))) {
field_count++;
switch (field_count) {
case 3: // UTC Time
break;
case 4: // Latitude
strncpy(gps.parsed_lat, token, 15);
break;
case 5: // N/S Indicator
strncpy(gps.parsed_ns, token, 1);
break;
case 6: // Longitude
strncpy(gps.parsed_lon, token, 15);
break;
case 7: // E/W Indicator
strncpy(gps.parsed_ew, token, 1);
gps.data_ready = 0; // Clear flag
return; // Parsing complete
}
}
}
void Convert_Coordinates(float *lat_out, float *lon_out) {
float raw_lat = atof(gps.parsed_lat);
float raw_lon = atof(gps.parsed_lon);
// Convert Latitude: ddmm.mmmm -> dd.dddddd
int lat_deg = (int)(raw_lat / 100);
float lat_min = raw_lat - (lat_deg * 100);
*lat_out = lat_deg + (lat_min / 60.0f);
// Adjust sign based on hemisphere
if (gps.parsed_ns[0] == 'S') *lat_out *= -1;
// Convert Longitude: dddmm.mmmm -> ddd.dddddd
int lon_deg = (int)(raw_lon / 100);
float lon_min = raw_lon - (lon_deg * 100);
*lon_out = lon_deg + (lon_min / 60.0f);
if (gps.parsed_ew[0] == 'W') *lon_out *= -1;
}