Merge pull request #74 from NawfalMotii79/revert-68-feature/add-um982-gps-driver
Revert "Add UM982 GPS driver (um982_gps.h/.cpp) for NMEA sentence parsing
This commit is contained in:
@@ -24,7 +24,6 @@
|
|||||||
#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"
|
||||||
}
|
}
|
||||||
@@ -122,8 +121,8 @@ UART_HandleTypeDef huart5;
|
|||||||
UART_HandleTypeDef huart3;
|
UART_HandleTypeDef huart3;
|
||||||
|
|
||||||
/* USER CODE BEGIN PV */
|
/* USER CODE BEGIN PV */
|
||||||
// Global UM982 instance
|
// The TinyGPSPlus object
|
||||||
UM982_GPS* um982_gps = nullptr;
|
TinyGPSPlus gps;
|
||||||
|
|
||||||
// Global data structures
|
// Global data structures
|
||||||
GPS_Data_t current_gps_data = {0};
|
GPS_Data_t current_gps_data = {0};
|
||||||
@@ -323,52 +322,6 @@ 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///////////////////
|
||||||
//////////////////////////////////////////////
|
//////////////////////////////////////////////
|
||||||
@@ -1517,8 +1470,6 @@ int main(void)
|
|||||||
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)");
|
||||||
@@ -1626,7 +1577,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 = heading - Mag_Declination;
|
Yaw_Sensor = (180*atan2(magRawY,magRawX)/PI) - Mag_Declination;
|
||||||
|
|
||||||
if(Yaw_Sensor<0)Yaw_Sensor+=360;
|
if(Yaw_Sensor<0)Yaw_Sensor+=360;
|
||||||
RxEst_0 = RxEst_1;
|
RxEst_0 = RxEst_1;
|
||||||
@@ -1805,7 +1756,9 @@ int main(void)
|
|||||||
//////////////////////////////////////////GPS/////////////////////////////////////////
|
//////////////////////////////////////////GPS/////////////////////////////////////////
|
||||||
//////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////
|
||||||
for(int i=0; i<10;i++){
|
for(int i=0; i<10;i++){
|
||||||
updateGPSData();
|
smartDelay(1000);
|
||||||
|
RADAR_Longitude = gps.location.lng();
|
||||||
|
RADAR_Latitude = gps.location.lat();
|
||||||
}
|
}
|
||||||
|
|
||||||
//move Stepper to position 1 = 0°
|
//move Stepper to position 1 = 0°
|
||||||
@@ -2628,7 +2581,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 = 115200;
|
huart5.Init.BaudRate = 9600;
|
||||||
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;
|
||||||
|
|||||||
@@ -1,684 +0,0 @@
|
|||||||
// 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;
|
|
||||||
}
|
|
||||||
@@ -1,146 +0,0 @@
|
|||||||
// 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