Add UM982 GPS driver (um982_gps.h/.cpp) for NMEA sentence parsing and integration
This commit is contained in:
@@ -24,6 +24,7 @@
|
|||||||
#include "adar1000.h"
|
#include "adar1000.h"
|
||||||
#include "ADAR1000_Manager.h"
|
#include "ADAR1000_Manager.h"
|
||||||
#include "ADAR1000_AGC.h"
|
#include "ADAR1000_AGC.h"
|
||||||
|
#include "um982_gps.h"
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include "ad9523.h"
|
#include "ad9523.h"
|
||||||
}
|
}
|
||||||
@@ -121,8 +122,8 @@ UART_HandleTypeDef huart5;
|
|||||||
UART_HandleTypeDef huart3;
|
UART_HandleTypeDef huart3;
|
||||||
|
|
||||||
/* USER CODE BEGIN PV */
|
/* USER CODE BEGIN PV */
|
||||||
// The TinyGPSPlus object
|
// Global UM982 instance
|
||||||
TinyGPSPlus gps;
|
UM982_GPS* um982_gps = nullptr;
|
||||||
|
|
||||||
// Global data structures
|
// Global data structures
|
||||||
GPS_Data_t current_gps_data = {0};
|
GPS_Data_t current_gps_data = {0};
|
||||||
@@ -322,6 +323,52 @@ void delay_ns(uint32_t nanoseconds)
|
|||||||
while ((DWT->CYCCNT - start_cycles) < cycles);
|
while ((DWT->CYCCNT - start_cycles) < cycles);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////
|
||||||
|
////////////////////UM982GPS//////////////////
|
||||||
|
//////////////////////////////////////////////
|
||||||
|
void initUM982GPS() {
|
||||||
|
// Initialize UM982 on UART5 (same as your current GPS)
|
||||||
|
um982_gps = new UM982_GPS(&huart5);
|
||||||
|
|
||||||
|
if (um982_gps->init(true, true, 1.0f, 10)) {
|
||||||
|
uint8_t msg[] = "UM982 GPS initialized successfully\r\n";
|
||||||
|
HAL_UART_Transmit(&huart3, msg, sizeof(msg)-1, 1000);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
uint8_t msg[] = "UM982 GPS initialization failed\r\n";
|
||||||
|
HAL_UART_Transmit(&huart3, msg, sizeof(msg)-1, 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateGPSData() {
|
||||||
|
if (um982_gps) {
|
||||||
|
um982_gps->process();
|
||||||
|
|
||||||
|
if (um982_gps->isDataUpdated()) {
|
||||||
|
RADAR_Latitude = um982_gps->getLatitude();
|
||||||
|
RADAR_Longitude = um982_gps->getLongitude();
|
||||||
|
|
||||||
|
// Update heading for stepper motor alignment
|
||||||
|
float heading = um982_gps->getHeading();
|
||||||
|
|
||||||
|
// Use heading for North alignment
|
||||||
|
if (um982_gps->hasRTKFix()) {
|
||||||
|
// Use RTK heading for precise alignment
|
||||||
|
// Adjust stepper motor based on true heading
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update GPS data structure for GUI
|
||||||
|
current_gps_data.latitude = RADAR_Latitude;
|
||||||
|
current_gps_data.longitude = RADAR_Longitude;
|
||||||
|
current_gps_data.altitude = um982_gps->getAltitude();
|
||||||
|
current_gps_data.pitch = Pitch_Sensor; // From IMU
|
||||||
|
current_gps_data.timestamp = HAL_GetTick();
|
||||||
|
|
||||||
|
// Send to GUI
|
||||||
|
GPS_SendBinaryToGUI(¤t_gps_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
//////////////////////////////////////////////
|
//////////////////////////////////////////////
|
||||||
//////////////////////RADAR///////////////////
|
//////////////////////RADAR///////////////////
|
||||||
//////////////////////////////////////////////
|
//////////////////////////////////////////////
|
||||||
@@ -747,7 +794,7 @@ SystemError_t checkSystemHealth(void) {
|
|||||||
DIAG_ERR("SYS", "checkSystemHealth returning error code %d", current_error);
|
DIAG_ERR("SYS", "checkSystemHealth returning error code %d", current_error);
|
||||||
}
|
}
|
||||||
return current_error;
|
return current_error;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error recovery function
|
// Error recovery function
|
||||||
void attemptErrorRecovery(SystemError_t error) {
|
void attemptErrorRecovery(SystemError_t error) {
|
||||||
@@ -1455,8 +1502,10 @@ int main(void)
|
|||||||
HAL_GPIO_WritePin(EN_P_3V3_FPGA_GPIO_Port,EN_P_3V3_FPGA_Pin,GPIO_PIN_SET);
|
HAL_GPIO_WritePin(EN_P_3V3_FPGA_GPIO_Port,EN_P_3V3_FPGA_Pin,GPIO_PIN_SET);
|
||||||
HAL_Delay(100);
|
HAL_Delay(100);
|
||||||
DIAG("PWR", "FPGA power sequencing complete -- 1.0V -> 1.8V -> 3.3V");
|
DIAG("PWR", "FPGA power sequencing complete -- 1.0V -> 1.8V -> 3.3V");
|
||||||
|
|
||||||
|
// Initialize module UM982GPS
|
||||||
|
initUM982GPS();
|
||||||
|
|
||||||
// Initialize module IMU
|
// Initialize module IMU
|
||||||
DIAG_SECTION("IMU INIT (GY-85)");
|
DIAG_SECTION("IMU INIT (GY-85)");
|
||||||
DIAG("IMU", "Initializing GY-85 IMU...");
|
DIAG("IMU", "Initializing GY-85 IMU...");
|
||||||
@@ -1465,12 +1514,12 @@ int main(void)
|
|||||||
Error_Handler();
|
Error_Handler();
|
||||||
}
|
}
|
||||||
DIAG("IMU", "GY-85 initialized OK, running 10 calibration samples");
|
DIAG("IMU", "GY-85 initialized OK, running 10 calibration samples");
|
||||||
for(int i=0; i<10;i++){
|
for(int i=0; i<10;i++){
|
||||||
if (!GY85_Update(&imu)) {
|
if (!GY85_Update(&imu)) {
|
||||||
Error_Handler();
|
Error_Handler();
|
||||||
}
|
}
|
||||||
|
|
||||||
ax = imu.ax;
|
ax = imu.ax;
|
||||||
ay = imu.ay;
|
ay = imu.ay;
|
||||||
az = imu.az;
|
az = imu.az;
|
||||||
gx = -imu.gx;
|
gx = -imu.gx;
|
||||||
@@ -1563,7 +1612,7 @@ int main(void)
|
|||||||
|
|
||||||
float magRawX = mx*cos(Pitch_Sensor*PI/180.0f) - mz*sin(Pitch_Sensor*PI/180.0f);
|
float magRawX = mx*cos(Pitch_Sensor*PI/180.0f) - mz*sin(Pitch_Sensor*PI/180.0f);
|
||||||
float magRawY = mx*sin(Roll_Sensor*PI/180.0f)*sin(Pitch_Sensor*PI/180.0f) + my*cos(Roll_Sensor*PI/180.0f)- mz*sin(Roll_Sensor*PI/180.0f)*cos(Pitch_Sensor*PI/180.0f);
|
float magRawY = mx*sin(Roll_Sensor*PI/180.0f)*sin(Pitch_Sensor*PI/180.0f) + my*cos(Roll_Sensor*PI/180.0f)- mz*sin(Roll_Sensor*PI/180.0f)*cos(Pitch_Sensor*PI/180.0f);
|
||||||
Yaw_Sensor = (180*atan2(magRawY,magRawX)/PI) - Mag_Declination;
|
Yaw_Sensor = heading - Mag_Declination;
|
||||||
|
|
||||||
if(Yaw_Sensor<0)Yaw_Sensor+=360;
|
if(Yaw_Sensor<0)Yaw_Sensor+=360;
|
||||||
RxEst_0 = RxEst_1;
|
RxEst_0 = RxEst_1;
|
||||||
@@ -1742,9 +1791,7 @@ int main(void)
|
|||||||
//////////////////////////////////////////GPS/////////////////////////////////////////
|
//////////////////////////////////////////GPS/////////////////////////////////////////
|
||||||
//////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////
|
||||||
for(int i=0; i<10;i++){
|
for(int i=0; i<10;i++){
|
||||||
smartDelay(1000);
|
updateGPSData();
|
||||||
RADAR_Longitude = gps.location.lng();
|
|
||||||
RADAR_Latitude = gps.location.lat();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//move Stepper to position 1 = 0°
|
//move Stepper to position 1 = 0°
|
||||||
@@ -1761,14 +1808,14 @@ int main(void)
|
|||||||
/**********wait for GUI start flag and Send Lat/Long/alt********/
|
/**********wait for GUI start flag and Send Lat/Long/alt********/
|
||||||
/***************************************************************/
|
/***************************************************************/
|
||||||
|
|
||||||
GPS_Data_t gps_data;
|
GPS_Data_t gps_data;
|
||||||
// Binary packet structure:
|
// Binary packet structure:
|
||||||
// [Header 4 bytes][Latitude 8 bytes][Longitude 8 bytes][Altitude 4 bytes][Pitch 4 bytes][CRC 2 bytes]
|
// [Header 4 bytes][Latitude 8 bytes][Longitude 8 bytes][Altitude 4 bytes][Pitch 4 bytes][CRC 2 bytes]
|
||||||
gps_data = {RADAR_Latitude, RADAR_Longitude, RADAR_Altitude, Pitch_Sensor, HAL_GetTick()};
|
gps_data = {RADAR_Latitude, RADAR_Longitude, RADAR_Altitude, Pitch_Sensor, HAL_GetTick()};
|
||||||
if (!GPS_SendBinaryToGUI(&gps_data)) {
|
if (!GPS_SendBinaryToGUI(&gps_data)) {
|
||||||
const uint8_t gps_send_error[] = "GPS binary send failed\r\n";
|
const uint8_t gps_send_error[] = "GPS binary send failed\r\n";
|
||||||
HAL_UART_Transmit(&huart3, (uint8_t*)gps_send_error, sizeof(gps_send_error) - 1, 1000);
|
HAL_UART_Transmit(&huart3, (uint8_t*)gps_send_error, sizeof(gps_send_error) - 1, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if start flag was received and settings are ready
|
// Check if start flag was received and settings are ready
|
||||||
do{
|
do{
|
||||||
@@ -2567,7 +2614,7 @@ static void MX_UART5_Init(void)
|
|||||||
|
|
||||||
/* USER CODE END UART5_Init 1 */
|
/* USER CODE END UART5_Init 1 */
|
||||||
huart5.Instance = UART5;
|
huart5.Instance = UART5;
|
||||||
huart5.Init.BaudRate = 9600;
|
huart5.Init.BaudRate = 115200;
|
||||||
huart5.Init.WordLength = UART_WORDLENGTH_8B;
|
huart5.Init.WordLength = UART_WORDLENGTH_8B;
|
||||||
huart5.Init.StopBits = UART_STOPBITS_1;
|
huart5.Init.StopBits = UART_STOPBITS_1;
|
||||||
huart5.Init.Parity = UART_PARITY_NONE;
|
huart5.Init.Parity = UART_PARITY_NONE;
|
||||||
|
|||||||
@@ -0,0 +1,684 @@
|
|||||||
|
// um982_gps.cpp
|
||||||
|
#include "um982_gps.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
// UM982 Default NMEA Commands
|
||||||
|
#define UM982_CMD_RESET "RESET"
|
||||||
|
#define UM982_CMD_SAVE_CONFIG "SAVECONFIG"
|
||||||
|
#define UM982_CMD_RATE_1HZ "GPTHS COM1 1"
|
||||||
|
#define UM982_CMD_RATE_5HZ "GPTHS COM1 0.2"
|
||||||
|
#define UM982_CMD_RATE_10HZ "GPTHS COM1 0.1"
|
||||||
|
#define UM982_CMD_RATE_20HZ "GPTHS COM1 0.05"
|
||||||
|
#define UM982_CMD_ENABLE_RTK "CONFIG RTK RELIABILITY 3 1"
|
||||||
|
#define UM982_CMD_DISABLE_RTK "CONFIG RTK DISABLE"
|
||||||
|
#define UM982_CMD_HEADING_FIXLENGTH "CONFIG HEADING FIXLENGTH"
|
||||||
|
#define UM982_CMD_HEADING_LENGTH "CONFIG HEADING LENGTH %.1f"
|
||||||
|
|
||||||
|
// NMEA Sentence Starters
|
||||||
|
#define NMEA_GGA_START "GGA"
|
||||||
|
#define NMEA_RMC_START "RMC"
|
||||||
|
#define NMEA_GSA_START "GSA"
|
||||||
|
#define NMEA_VTG_START "VTG"
|
||||||
|
#define NMEA_GPTHS_START "GNTHS" // UM982 Heading/Track | "GNTHS" is correct, The Output Message is: $GNTHS,341.3065,A*1F
|
||||||
|
|
||||||
|
// Constructor
|
||||||
|
UM982_GPS::UM982_GPS(UART_HandleTypeDef *huart)
|
||||||
|
: uart_(huart), buffer_index_(0), data_updated_(false), last_update_ms_(0)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (uart_ == nullptr)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(&data_, 0, sizeof(UM982_Data_t));
|
||||||
|
memset(&last_data_, 0, sizeof(UM982_Data_t));
|
||||||
|
memset(rx_buffer_, 0, sizeof(rx_buffer_));
|
||||||
|
}
|
||||||
|
|
||||||
|
UM982_GPS::~UM982_GPS()
|
||||||
|
{
|
||||||
|
// Cleanup if needed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialization
|
||||||
|
bool UM982_GPS::init(bool enable_rtk, bool enable_heading, float baseline_m, byte update_rate)
|
||||||
|
{
|
||||||
|
// Clear buffer
|
||||||
|
resetBuffer();
|
||||||
|
|
||||||
|
// Wait for UM982 to stabilize
|
||||||
|
HAL_Delay(1000);
|
||||||
|
|
||||||
|
if (enable_rtk)
|
||||||
|
{
|
||||||
|
sendCommand(UM982_CMD_ENABLE_RTK);
|
||||||
|
HAL_Delay(50);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sendCommand(UM982_CMD_DISABLE_RTK);
|
||||||
|
HAL_Delay(50);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enable_heading)
|
||||||
|
{
|
||||||
|
sendCommand(UM982_CMD_HEADING_FIXLENGTH);
|
||||||
|
HAL_Delay(50);
|
||||||
|
|
||||||
|
char len_cmd[64];
|
||||||
|
snprintf(len_cmd, sizeof(len_cmd), UM982_CMD_HEADING_LENGTH, baseline_m);
|
||||||
|
sendCommand(len_cmd);
|
||||||
|
HAL_Delay(50);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (update_rate)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
sendCommand(UM982_CMD_RATE_1HZ);
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
sendCommand(UM982_CMD_RATE_5HZ);
|
||||||
|
break;
|
||||||
|
case 10:
|
||||||
|
sendCommand(UM982_CMD_RATE_10HZ);
|
||||||
|
break;
|
||||||
|
case 20:
|
||||||
|
sendCommand(UM982_CMD_RATE_20HZ);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
sendCommand(UM982_CMD_RATE_10HZ);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
HAL_Delay(100);
|
||||||
|
|
||||||
|
sendCommand(UM982_CMD_SAVE_CONFIG);
|
||||||
|
HAL_Delay(300);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set update rate
|
||||||
|
bool UM982_GPS::setUpdateRate(uint8_t hz)
|
||||||
|
{
|
||||||
|
const char *cmd;
|
||||||
|
|
||||||
|
switch (hz)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
cmd = UM982_CMD_RATE_1HZ;
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
cmd = UM982_CMD_RATE_5HZ;
|
||||||
|
break;
|
||||||
|
case 10:
|
||||||
|
cmd = UM982_CMD_RATE_10HZ;
|
||||||
|
break;
|
||||||
|
case 20:
|
||||||
|
cmd = UM982_CMD_RATE_20HZ;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
cmd = UM982_CMD_RATE_10HZ;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendCommand(cmd);
|
||||||
|
HAL_Delay(100);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set RTK mode
|
||||||
|
bool UM982_GPS::setRTKMode(bool enable)
|
||||||
|
{
|
||||||
|
if (enable)
|
||||||
|
{
|
||||||
|
sendCommand(UM982_CMD_ENABLE_RTK);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sendCommand(UM982_CMD_DISABLE_RTK);
|
||||||
|
}
|
||||||
|
|
||||||
|
HAL_Delay(100);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save configuration to flash
|
||||||
|
bool UM982_GPS::saveConfiguration()
|
||||||
|
{
|
||||||
|
sendCommand(UM982_CMD_SAVE_CONFIG);
|
||||||
|
HAL_Delay(500);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send command to UM982
|
||||||
|
void UM982_GPS::sendCommand(const char *command)
|
||||||
|
{
|
||||||
|
if (command == nullptr || uart_ == nullptr)
|
||||||
|
return;
|
||||||
|
if (strlen(command) > 240)
|
||||||
|
return;
|
||||||
|
|
||||||
|
HAL_UART_Transmit(uart_, (uint8_t *)command, strlen(command), 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process incoming data (call in main loop)
|
||||||
|
void UM982_GPS::process()
|
||||||
|
{
|
||||||
|
uint8_t data;
|
||||||
|
|
||||||
|
// Read all available bytes
|
||||||
|
while (HAL_UART_Receive(uart_, &data, 1, 0) == HAL_OK)
|
||||||
|
{
|
||||||
|
processByte(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UM982_GPS::processByte(uint8_t data)
|
||||||
|
{
|
||||||
|
// Start of new sentence
|
||||||
|
if (data == '$')
|
||||||
|
{
|
||||||
|
resetBuffer();
|
||||||
|
rx_buffer_[buffer_index_++] = data;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// End of sentence (accept both \r\n and \n only)
|
||||||
|
if ((data == '\n' || data == '\r') && buffer_index_ > 0)
|
||||||
|
{
|
||||||
|
if (data == '\n')
|
||||||
|
{
|
||||||
|
rx_buffer_[buffer_index_] = '\0';
|
||||||
|
|
||||||
|
const char *sentence = rx_buffer_ + 1; // skip '$'
|
||||||
|
NMEA_SentenceType type = identifySentence(sentence);
|
||||||
|
|
||||||
|
// Only parse if checksum is valid
|
||||||
|
if (verifyNMEAChecksum(rx_buffer_))
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case NMEA_GGA:
|
||||||
|
parseGGA(sentence);
|
||||||
|
break;
|
||||||
|
case NMEA_RMC:
|
||||||
|
parseRMC(sentence);
|
||||||
|
break;
|
||||||
|
case NMEA_GSA:
|
||||||
|
parseGSA(sentence);
|
||||||
|
break;
|
||||||
|
case NMEA_VTG:
|
||||||
|
parseVTG(sentence);
|
||||||
|
break;
|
||||||
|
case NMEA_GPTHS:
|
||||||
|
parseGPTHS(sentence);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resetBuffer();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store byte if buffer not full
|
||||||
|
if (buffer_index_ < sizeof(rx_buffer_) - 1)
|
||||||
|
{
|
||||||
|
rx_buffer_[buffer_index_++] = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UM982_GPS::verifyNMEAChecksum(const char *sentence)
|
||||||
|
{
|
||||||
|
const char *asterisk = strchr(sentence, '*');
|
||||||
|
if (!asterisk || asterisk[3] != '\0')
|
||||||
|
return false; // must have *XX
|
||||||
|
|
||||||
|
uint8_t calculated = 0;
|
||||||
|
for (const char *p = sentence + 1; p < asterisk; ++p)
|
||||||
|
{
|
||||||
|
calculated ^= *p;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t received = (uint8_t)strtol(asterisk + 1, NULL, 16);
|
||||||
|
return calculated == received;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Identify NMEA sentence type
|
||||||
|
NMEA_SentenceType UM982_GPS::identifySentence(const char *sentence)
|
||||||
|
{
|
||||||
|
if (strncmp(sentence, NMEA_GGA_START, 3) == 0)
|
||||||
|
return NMEA_GGA;
|
||||||
|
if (strncmp(sentence, NMEA_RMC_START, 3) == 0)
|
||||||
|
return NMEA_RMC;
|
||||||
|
if (strncmp(sentence, NMEA_GSA_START, 3) == 0)
|
||||||
|
return NMEA_GSA;
|
||||||
|
if (strncmp(sentence, NMEA_VTG_START, 3) == 0)
|
||||||
|
return NMEA_VTG;
|
||||||
|
if (strncmp(sentence, NMEA_GPTHS_START, 5) == 0)
|
||||||
|
return NMEA_GPTHS;
|
||||||
|
return NMEA_UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse GGA sentence (time, position, fix type)
|
||||||
|
bool UM982_GPS::parseGGA(const char *sentence)
|
||||||
|
{
|
||||||
|
char buffer[32];
|
||||||
|
int field = 0;
|
||||||
|
const char *ptr = sentence;
|
||||||
|
|
||||||
|
while (*ptr && *ptr != '*')
|
||||||
|
{
|
||||||
|
if (*ptr == ',' || *ptr == '\r')
|
||||||
|
{
|
||||||
|
field++;
|
||||||
|
ptr++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract field based on position
|
||||||
|
int i = 0;
|
||||||
|
while (*ptr && *ptr != ',' && *ptr != '\r' && i < 31)
|
||||||
|
{
|
||||||
|
buffer[i++] = *ptr++;
|
||||||
|
}
|
||||||
|
buffer[i] = '\0';
|
||||||
|
|
||||||
|
switch (field)
|
||||||
|
{
|
||||||
|
case 0: // Skip "GGA"
|
||||||
|
break;
|
||||||
|
case 1: // UTC Time (HHMMSS.SS)
|
||||||
|
if (strlen(buffer) >= 6)
|
||||||
|
{
|
||||||
|
data_.utc_hour = (buffer[0] - '0') * 10 + (buffer[1] - '0');
|
||||||
|
data_.utc_minute = (buffer[2] - '0') * 10 + (buffer[3] - '0');
|
||||||
|
data_.utc_second = (buffer[4] - '0') * 10 + (buffer[5] - '0');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2: // Latitude
|
||||||
|
{
|
||||||
|
char lat_str[16];
|
||||||
|
strcpy(lat_str, buffer);
|
||||||
|
// Next field is hemisphere
|
||||||
|
while (*ptr && (*ptr == ',' || *ptr == '\r'))
|
||||||
|
ptr++;
|
||||||
|
char hem = *ptr;
|
||||||
|
data_.latitude = parseNMEACoordinate(lat_str, hem);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 3: // Skip hemisphere (already processed)
|
||||||
|
break;
|
||||||
|
case 4: // Longitude
|
||||||
|
{
|
||||||
|
char lon_str[16];
|
||||||
|
strcpy(lon_str, buffer);
|
||||||
|
while (*ptr && (*ptr == ',' || *ptr == '\r'))
|
||||||
|
ptr++;
|
||||||
|
char hem = *ptr;
|
||||||
|
data_.longitude = parseNMEACoordinate(lon_str, hem);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 5: // Skip hemisphere
|
||||||
|
break;
|
||||||
|
case 6: // Fix quality
|
||||||
|
data_.fix_type = (RTK_FixType)parseNMEAInt(buffer);
|
||||||
|
break;
|
||||||
|
case 7: // Number of satellites
|
||||||
|
data_.sat_count = parseNMEAInt(buffer);
|
||||||
|
break;
|
||||||
|
case 8: // HDOP
|
||||||
|
data_.hdop = parseNMEAFloat(buffer);
|
||||||
|
break;
|
||||||
|
case 9: // Altitude
|
||||||
|
data_.altitude = parseNMEAFloat(buffer);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip to next field
|
||||||
|
while (*ptr && *ptr != ',')
|
||||||
|
ptr++;
|
||||||
|
}
|
||||||
|
|
||||||
|
data_.position_valid = (data_.fix_type >= RTK_SINGLE);
|
||||||
|
data_.time_valid = true;
|
||||||
|
updateData();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse RMC sentence (recommended minimum - includes heading)
|
||||||
|
bool UM982_GPS::parseRMC(const char *sentence)
|
||||||
|
{
|
||||||
|
char buffer[32];
|
||||||
|
int field = 0;
|
||||||
|
const char *ptr = sentence;
|
||||||
|
|
||||||
|
while (*ptr && *ptr != '*')
|
||||||
|
{
|
||||||
|
if (*ptr == ',' || *ptr == '\r')
|
||||||
|
{
|
||||||
|
field++;
|
||||||
|
ptr++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
while (*ptr && *ptr != ',' && *ptr != '\r' && i < 31)
|
||||||
|
{
|
||||||
|
buffer[i++] = *ptr++;
|
||||||
|
}
|
||||||
|
buffer[i] = '\0';
|
||||||
|
|
||||||
|
switch (field)
|
||||||
|
{
|
||||||
|
case 0: // Skip "RMC"
|
||||||
|
break;
|
||||||
|
case 1: // UTC Time
|
||||||
|
if (strlen(buffer) >= 6)
|
||||||
|
{
|
||||||
|
data_.utc_hour = (buffer[0] - '0') * 10 + (buffer[1] - '0');
|
||||||
|
data_.utc_minute = (buffer[2] - '0') * 10 + (buffer[3] - '0');
|
||||||
|
data_.utc_second = (buffer[4] - '0') * 10 + (buffer[5] - '0');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2: // Status (A=valid, V=invalid)
|
||||||
|
data_.data_valid = (buffer[0] == 'A');
|
||||||
|
break;
|
||||||
|
case 3: // Latitude
|
||||||
|
{
|
||||||
|
char lat_str[16];
|
||||||
|
strcpy(lat_str, buffer);
|
||||||
|
while (*ptr && (*ptr == ',' || *ptr == '\r'))
|
||||||
|
ptr++;
|
||||||
|
char hem = *ptr;
|
||||||
|
data_.latitude = parseNMEACoordinate(lat_str, hem);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 4: // Skip hemisphere
|
||||||
|
break;
|
||||||
|
case 5: // Longitude
|
||||||
|
{
|
||||||
|
char lon_str[16];
|
||||||
|
strcpy(lon_str, buffer);
|
||||||
|
while (*ptr && (*ptr == ',' || *ptr == '\r'))
|
||||||
|
ptr++;
|
||||||
|
char hem = *ptr;
|
||||||
|
data_.longitude = parseNMEACoordinate(lon_str, hem);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 6: // Skip hemisphere
|
||||||
|
break;
|
||||||
|
case 7: // Speed over ground (knots)
|
||||||
|
data_.ground_speed_knots = parseNMEAFloat(buffer);
|
||||||
|
data_.ground_speed_kmh = data_.ground_speed_knots * 1.852f;
|
||||||
|
data_.ground_speed_ms = data_.ground_speed_knots * 0.514444f;
|
||||||
|
break;
|
||||||
|
case 8: // Track angle (heading)
|
||||||
|
data_.heading = parseNMEAFloat(buffer);
|
||||||
|
data_.heading_valid = true;
|
||||||
|
break;
|
||||||
|
case 9: // Date (DDMMYY)
|
||||||
|
if (strlen(buffer) >= 6)
|
||||||
|
{
|
||||||
|
// Date is available but we may not need it
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (*ptr && *ptr != ',')
|
||||||
|
ptr++;
|
||||||
|
}
|
||||||
|
|
||||||
|
data_.position_valid = data_.data_valid;
|
||||||
|
updateData();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse GSA sentence (DOP and active satellites)
|
||||||
|
bool UM982_GPS::parseGSA(const char *sentence)
|
||||||
|
{
|
||||||
|
char buffer[32];
|
||||||
|
int field = 0;
|
||||||
|
const char *ptr = sentence;
|
||||||
|
|
||||||
|
while (*ptr && *ptr != '*')
|
||||||
|
{
|
||||||
|
if (*ptr == ',' || *ptr == '\r')
|
||||||
|
{
|
||||||
|
field++;
|
||||||
|
ptr++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
while (*ptr && *ptr != ',' && *ptr != '\r' && i < 31)
|
||||||
|
{
|
||||||
|
buffer[i++] = *ptr++;
|
||||||
|
}
|
||||||
|
buffer[i] = '\0';
|
||||||
|
|
||||||
|
switch (field)
|
||||||
|
{
|
||||||
|
case 0: // Skip "GSA"
|
||||||
|
break;
|
||||||
|
case 1: // Mode (M=manual, A=auto)
|
||||||
|
break;
|
||||||
|
case 2: // Fix type (1=no fix, 2=2D, 3=3D)
|
||||||
|
break;
|
||||||
|
case 15: // PDOP
|
||||||
|
data_.pdop = parseNMEAFloat(buffer);
|
||||||
|
break;
|
||||||
|
case 16: // HDOP
|
||||||
|
data_.hdop = parseNMEAFloat(buffer);
|
||||||
|
break;
|
||||||
|
case 17: // VDOP
|
||||||
|
data_.vdop = parseNMEAFloat(buffer);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (*ptr && *ptr != ',')
|
||||||
|
ptr++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse VTG sentence (course and speed)
|
||||||
|
bool UM982_GPS::parseVTG(const char *sentence)
|
||||||
|
{
|
||||||
|
char buffer[32];
|
||||||
|
int field = 0;
|
||||||
|
const char *ptr = sentence;
|
||||||
|
|
||||||
|
while (*ptr && *ptr != '*')
|
||||||
|
{
|
||||||
|
if (*ptr == ',' || *ptr == '\r')
|
||||||
|
{
|
||||||
|
field++;
|
||||||
|
ptr++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
while (*ptr && *ptr != ',' && *ptr != '\r' && i < 31)
|
||||||
|
{
|
||||||
|
buffer[i++] = *ptr++;
|
||||||
|
}
|
||||||
|
buffer[i] = '\0';
|
||||||
|
|
||||||
|
switch (field)
|
||||||
|
{
|
||||||
|
case 0: // Skip "VTG"
|
||||||
|
break;
|
||||||
|
case 1: // True track (heading)
|
||||||
|
data_.heading = parseNMEAFloat(buffer);
|
||||||
|
data_.heading_valid = true;
|
||||||
|
break;
|
||||||
|
case 2: // Skip "T"
|
||||||
|
break;
|
||||||
|
case 3: // Magnetic track (skip)
|
||||||
|
break;
|
||||||
|
case 4: // Skip "M"
|
||||||
|
break;
|
||||||
|
case 5: // Speed (knots)
|
||||||
|
data_.ground_speed_knots = parseNMEAFloat(buffer);
|
||||||
|
data_.ground_speed_kmh = data_.ground_speed_knots * 1.852f;
|
||||||
|
data_.ground_speed_ms = data_.ground_speed_knots * 0.514444f;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (*ptr && *ptr != ',')
|
||||||
|
ptr++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse GPTHS sentence (UM982 proprietary heading)
|
||||||
|
bool UM982_GPS::parseGPTHS(const char *sentence)
|
||||||
|
{
|
||||||
|
char buffer[32];
|
||||||
|
int field = 0;
|
||||||
|
const char *ptr = sentence;
|
||||||
|
|
||||||
|
while (*ptr && *ptr != '*')
|
||||||
|
{
|
||||||
|
if (*ptr == ',' || *ptr == '\r')
|
||||||
|
{
|
||||||
|
field++;
|
||||||
|
ptr++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
while (*ptr && *ptr != ',' && *ptr != '\r' && i < 31)
|
||||||
|
{
|
||||||
|
buffer[i++] = *ptr++;
|
||||||
|
}
|
||||||
|
buffer[i] = '\0';
|
||||||
|
|
||||||
|
switch (field)
|
||||||
|
{
|
||||||
|
case 0: // Skip "GNTHS"
|
||||||
|
break;
|
||||||
|
case 1: // Heading (True North)
|
||||||
|
data_.heading = parseNMEAFloat(buffer);
|
||||||
|
data_.heading_valid = true;
|
||||||
|
break;
|
||||||
|
case 2: // Status (A/V)
|
||||||
|
data_.data_valid = (buffer[0] == 'A');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (*ptr && *ptr != ',')
|
||||||
|
ptr++;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateData();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse NMEA coordinate (DDMM.MMMMM format)
|
||||||
|
double UM982_GPS::parseNMEACoordinate(const char *coord, char hemisphere)
|
||||||
|
{
|
||||||
|
if (coord == nullptr || strlen(coord) < 4)
|
||||||
|
return 0.0;
|
||||||
|
|
||||||
|
// Find decimal point position
|
||||||
|
int dot_pos = 0;
|
||||||
|
while (coord[dot_pos] != '.' && dot_pos < (int)strlen(coord))
|
||||||
|
{
|
||||||
|
dot_pos++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Degrees are the first 2 digits before decimal
|
||||||
|
int degrees = 0;
|
||||||
|
if (dot_pos >= 2)
|
||||||
|
{
|
||||||
|
degrees = (coord[0] - '0') * 10 + (coord[1] - '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Minutes are the rest
|
||||||
|
double minutes = atof(coord + 2);
|
||||||
|
|
||||||
|
double decimal = degrees + minutes / 60.0;
|
||||||
|
|
||||||
|
// Apply hemisphere (S or W = negative)
|
||||||
|
if (hemisphere == 'S' || hemisphere == 'W')
|
||||||
|
{
|
||||||
|
decimal = -decimal;
|
||||||
|
}
|
||||||
|
|
||||||
|
return decimal;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse NMEA float value
|
||||||
|
float UM982_GPS::parseNMEAFloat(const char *str)
|
||||||
|
{
|
||||||
|
if (str == NULL || strlen(str) == 0)
|
||||||
|
return 0.0f;
|
||||||
|
return atof(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse NMEA integer value
|
||||||
|
uint32_t UM982_GPS::parseNMEAInt(const char *str)
|
||||||
|
{
|
||||||
|
if (str == NULL || strlen(str) == 0)
|
||||||
|
return 0;
|
||||||
|
return atoi(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update data and mark as updated
|
||||||
|
void UM982_GPS::updateData()
|
||||||
|
{
|
||||||
|
data_.timestamp_ms = HAL_GetTick();
|
||||||
|
|
||||||
|
// Check if data has changed significantly
|
||||||
|
if (fabs(data_.latitude - last_data_.latitude) > 1e-6 ||
|
||||||
|
fabs(data_.longitude - last_data_.longitude) > 1e-6 ||
|
||||||
|
fabs(data_.heading - last_data_.heading) > 0.1f)
|
||||||
|
{
|
||||||
|
|
||||||
|
data_updated_ = true;
|
||||||
|
last_update_ms_ = data_.timestamp_ms;
|
||||||
|
memcpy(&last_data_, &data_, sizeof(UM982_Data_t));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if data has been updated
|
||||||
|
bool UM982_GPS::isDataUpdated()
|
||||||
|
{
|
||||||
|
bool updated = data_updated_;
|
||||||
|
data_updated_ = false;
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get complete data structure
|
||||||
|
UM982_Data_t UM982_GPS::getData()
|
||||||
|
{
|
||||||
|
return data_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset receive buffer
|
||||||
|
void UM982_GPS::resetBuffer()
|
||||||
|
{
|
||||||
|
memset(rx_buffer_, 0, sizeof(rx_buffer_));
|
||||||
|
buffer_index_ = 0;
|
||||||
|
}
|
||||||
+146
@@ -0,0 +1,146 @@
|
|||||||
|
// um982_gps.h
|
||||||
|
#ifndef UM982_GPS_H
|
||||||
|
#define UM982_GPS_H
|
||||||
|
|
||||||
|
#include "main.h"
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// UM982 NMEA Sentence Types
|
||||||
|
typedef enum {
|
||||||
|
NMEA_UNKNOWN = 0,
|
||||||
|
NMEA_GGA, // Global Positioning System Fix Data
|
||||||
|
NMEA_RMC, // Recommended Minimum Specific GNSS Data
|
||||||
|
NMEA_GSA, // GNSS DOP and Active Satellites
|
||||||
|
NMEA_GSV, // GNSS Satellites in View
|
||||||
|
NMEA_VTG, // Course Over Ground and Ground Speed
|
||||||
|
NMEA_GLL, // Geographic Position - Latitude/Longitude
|
||||||
|
NMEA_ZDA, // Time and Date
|
||||||
|
NMEA_GPTHS, // Heading/Track from UM982
|
||||||
|
NMEA_PASH // Proprietary UM982 messages
|
||||||
|
} NMEA_SentenceType;
|
||||||
|
|
||||||
|
// UM982 RTK Status
|
||||||
|
typedef enum {
|
||||||
|
RTK_FIX_INVALID = 0,
|
||||||
|
RTK_SINGLE = 1, // Single point positioning
|
||||||
|
RTK_DGPS = 2, // Differential GPS (SBAS)
|
||||||
|
RTK_FLOAT = 4, // Float RTK
|
||||||
|
RTK_FIXED = 5, // Fixed RTK (centimeter precision)
|
||||||
|
RTK_DEAD_RECKON = 6 // Dead reckoning
|
||||||
|
} RTK_FixType;
|
||||||
|
|
||||||
|
// UM982 Navigation Status
|
||||||
|
typedef struct {
|
||||||
|
// Position
|
||||||
|
double latitude; // Degrees (negative = South)
|
||||||
|
double longitude; // Degrees (negative = West)
|
||||||
|
float altitude; // Meters (MSL)
|
||||||
|
float geoid_separation; // Geoid separation (WGS84)
|
||||||
|
|
||||||
|
// Time
|
||||||
|
uint32_t utc_hour;
|
||||||
|
uint32_t utc_minute;
|
||||||
|
uint32_t utc_second;
|
||||||
|
uint32_t utc_millisecond;
|
||||||
|
uint32_t timestamp_ms; // System timestamp when data was received
|
||||||
|
|
||||||
|
// Course and Speed
|
||||||
|
float heading; // Degrees (0-360, True North)
|
||||||
|
float ground_speed_knots; // Speed over ground in knots
|
||||||
|
float ground_speed_kmh; // Speed over ground in km/h
|
||||||
|
float ground_speed_ms; // Speed over ground in m/s
|
||||||
|
|
||||||
|
// Accuracy and Quality
|
||||||
|
RTK_FixType fix_type; // RTK fix type
|
||||||
|
uint8_t sat_count; // Number of satellites used
|
||||||
|
float hdop; // Horizontal Dilution of Precision
|
||||||
|
float vdop; // Vertical Dilution of Precision
|
||||||
|
float pdop; // Position Dilution of Precision
|
||||||
|
|
||||||
|
// RTK specific
|
||||||
|
float age_of_differential; // Age of differential corrections (seconds)
|
||||||
|
uint32_t differential_ref_station_id;
|
||||||
|
|
||||||
|
// Validity flags
|
||||||
|
bool data_valid;
|
||||||
|
bool heading_valid;
|
||||||
|
bool position_valid;
|
||||||
|
bool time_valid;
|
||||||
|
|
||||||
|
} UM982_Data_t;
|
||||||
|
|
||||||
|
// UM982 Handler Class
|
||||||
|
class UM982_GPS {
|
||||||
|
public:
|
||||||
|
UM982_GPS(UART_HandleTypeDef* huart);
|
||||||
|
~UM982_GPS();
|
||||||
|
|
||||||
|
// Initialization and Configuration
|
||||||
|
bool init(bool enable_rtk, bool enable_heading, float baseline_m, byte update_rate);
|
||||||
|
bool setUpdateRate(uint8_t hz);
|
||||||
|
bool setRTKMode(bool enable);
|
||||||
|
bool saveConfiguration();
|
||||||
|
|
||||||
|
// Data Processing
|
||||||
|
void process(); // Call in main loop to parse incoming data
|
||||||
|
bool processByte(uint8_t data);
|
||||||
|
bool isDataUpdated();
|
||||||
|
|
||||||
|
// Data Access
|
||||||
|
UM982_Data_t getData();
|
||||||
|
double getLatitude() const { return data_.latitude; }
|
||||||
|
double getLongitude() const { return data_.longitude; }
|
||||||
|
float getHeading() const { return data_.heading; }
|
||||||
|
float getAltitude() const { return data_.altitude; }
|
||||||
|
RTK_FixType getFixType() const { return data_.fix_type; }
|
||||||
|
uint8_t getSatelliteCount() const { return data_.sat_count; }
|
||||||
|
|
||||||
|
// Status
|
||||||
|
bool hasFix() const { return (data_.fix_type >= RTK_SINGLE); }
|
||||||
|
bool hasRTKFix() const { return (data_.fix_type == RTK_FIXED); }
|
||||||
|
bool isDataValid() const { return data_.data_valid; }
|
||||||
|
|
||||||
|
// Utility
|
||||||
|
static double parseNMEACoordinate(const char* coord, char hemisphere);
|
||||||
|
static float parseNMEAFloat(const char* str);
|
||||||
|
static uint32_t parseNMEAInt(const char* str);
|
||||||
|
|
||||||
|
private:
|
||||||
|
UART_HandleTypeDef* uart_;
|
||||||
|
UM982_Data_t data_;
|
||||||
|
UM982_Data_t last_data_;
|
||||||
|
UM982_Config_t config_;
|
||||||
|
|
||||||
|
// NMEA Parsing
|
||||||
|
char rx_buffer_[256];
|
||||||
|
uint16_t buffer_index_;
|
||||||
|
bool data_updated_;
|
||||||
|
uint32_t last_update_ms_;
|
||||||
|
|
||||||
|
// Sentence parsing
|
||||||
|
NMEA_SentenceType identifySentence(const char* sentence);
|
||||||
|
bool parseGGA(const char* sentence);
|
||||||
|
bool parseRMC(const char* sentence);
|
||||||
|
bool parseGSA(const char* sentence);
|
||||||
|
bool parseVTG(const char* sentence);
|
||||||
|
bool parseGPTHS(const char* sentence); // UM982 proprietary heading sentence
|
||||||
|
|
||||||
|
// Command generation
|
||||||
|
void sendCommand(const char* command);
|
||||||
|
|
||||||
|
// Helper methods
|
||||||
|
void updateData();
|
||||||
|
void resetBuffer();
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // UM982_GPS_H
|
||||||
Reference in New Issue
Block a user