diff --git a/9_Firmware/9_1_Microcontroller/9_1_3_C_Cpp_Code/main.cpp b/9_Firmware/9_1_Microcontroller/9_1_3_C_Cpp_Code/main.cpp index 09468d0..e5dabca 100644 --- a/9_Firmware/9_1_Microcontroller/9_1_3_C_Cpp_Code/main.cpp +++ b/9_Firmware/9_1_Microcontroller/9_1_3_C_Cpp_Code/main.cpp @@ -24,6 +24,7 @@ #include "adar1000.h" #include "ADAR1000_Manager.h" #include "ADAR1000_AGC.h" +#include "um982_gps.h" extern "C" { #include "ad9523.h" } @@ -121,8 +122,8 @@ UART_HandleTypeDef huart5; UART_HandleTypeDef huart3; /* USER CODE BEGIN PV */ -// The TinyGPSPlus object -TinyGPSPlus gps; +// Global UM982 instance +UM982_GPS* um982_gps = nullptr; // Global data structures GPS_Data_t current_gps_data = {0}; @@ -322,6 +323,52 @@ void delay_ns(uint32_t nanoseconds) 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/////////////////// ////////////////////////////////////////////// @@ -747,7 +794,7 @@ SystemError_t checkSystemHealth(void) { DIAG_ERR("SYS", "checkSystemHealth returning error code %d", current_error); } return current_error; -} +} // Error recovery function 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_Delay(100); DIAG("PWR", "FPGA power sequencing complete -- 1.0V -> 1.8V -> 3.3V"); - - + + // Initialize module UM982GPS + initUM982GPS(); + // Initialize module IMU DIAG_SECTION("IMU INIT (GY-85)"); DIAG("IMU", "Initializing GY-85 IMU..."); @@ -1465,12 +1514,12 @@ int main(void) Error_Handler(); } DIAG("IMU", "GY-85 initialized OK, running 10 calibration samples"); - for(int i=0; i<10;i++){ - if (!GY85_Update(&imu)) { - Error_Handler(); - } - - ax = imu.ax; + for(int i=0; i<10;i++){ + if (!GY85_Update(&imu)) { + Error_Handler(); + } + + ax = imu.ax; ay = imu.ay; az = imu.az; 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 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; RxEst_0 = RxEst_1; @@ -1742,9 +1791,7 @@ int main(void) //////////////////////////////////////////GPS///////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////// for(int i=0; i<10;i++){ - smartDelay(1000); - RADAR_Longitude = gps.location.lng(); - RADAR_Latitude = gps.location.lat(); + updateGPSData(); } //move Stepper to position 1 = 0° @@ -1761,14 +1808,14 @@ int main(void) /**********wait for GUI start flag and Send Lat/Long/alt********/ /***************************************************************/ - GPS_Data_t gps_data; - // Binary packet structure: - // [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()}; - if (!GPS_SendBinaryToGUI(&gps_data)) { - 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); - } + GPS_Data_t gps_data; + // Binary packet structure: + // [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()}; + if (!GPS_SendBinaryToGUI(&gps_data)) { + 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); + } // Check if start flag was received and settings are ready do{ @@ -2567,7 +2614,7 @@ static void MX_UART5_Init(void) /* USER CODE END UART5_Init 1 */ huart5.Instance = UART5; - huart5.Init.BaudRate = 9600; + huart5.Init.BaudRate = 115200; huart5.Init.WordLength = UART_WORDLENGTH_8B; huart5.Init.StopBits = UART_STOPBITS_1; huart5.Init.Parity = UART_PARITY_NONE; diff --git a/9_Firmware/9_1_Microcontroller/9_1_3_C_Cpp_Code/um982_gps.cpp b/9_Firmware/9_1_Microcontroller/9_1_3_C_Cpp_Code/um982_gps.cpp new file mode 100755 index 0000000..df249e2 --- /dev/null +++ b/9_Firmware/9_1_Microcontroller/9_1_3_C_Cpp_Code/um982_gps.cpp @@ -0,0 +1,684 @@ +// um982_gps.cpp +#include "um982_gps.h" +#include + +// 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; +} \ No newline at end of file diff --git a/9_Firmware/9_1_Microcontroller/9_1_3_C_Cpp_Code/um982_gps.h b/9_Firmware/9_1_Microcontroller/9_1_3_C_Cpp_Code/um982_gps.h new file mode 100755 index 0000000..bfb8d4f --- /dev/null +++ b/9_Firmware/9_1_Microcontroller/9_1_3_C_Cpp_Code/um982_gps.h @@ -0,0 +1,146 @@ +// um982_gps.h +#ifndef UM982_GPS_H +#define UM982_GPS_H + +#include "main.h" +#include +#include +#include + +#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 \ No newline at end of file