Is there a better way of converting an uint8_t array into a single float value?
I am extremely new at using this. I am currently using a ESP32-S3-WROOM-1 to take UART data from a NEO-6M GPS. IDE: Visual Studio Code
I think my code (which I attached below the c and h file) should take the UART data and place it into an array. For time I can just set it two by two, but for longer values like Latitude and Longitude, I will have to have the code add and multiply each other until it reaches the decimal point, then add the rest of the values later.
Basically, going byte by byte.
char* Post_Time_String = strstr(String_Input, ",");
F_Extract_Until_Comma(Post_Time_String, Lat_Val);
for(int itr=0; itr<sizeof(Lat_Val),itr++)
{
if(Lat_Val[itr] != ".")
{
gps_info->GPS_POSITION->latitude = (10 * gps_info->GPS_POSITION->latitude) + Lat_Val[itr];
}
}
I have been trying to find a better way of doing this, because I get the feeling that this is very inefficient and slow. If I can get the above like a string, then I can use strof() I think?
For reference the header and main files I am currently working on. WIP and definitely has issues that I can't find yet. NMEA_PARSE_CODE.c
#include <stdio.h>
#include <NMEA_PARSE_CODE.h>
void F_FIND_COMMA(char *String_Input, char* String_Output)
{
char *String_Output = strstr(String_Input, ",");
return String_Output;
}
void F_Extract_Until_Comma(char *String_Input, uint8_t* Extracted_Output[])
{
uint8_t String_Length = strlen(String_Input);
uint8_t* F_Buffer[20] = {NULL};
char* comma_parse_string = F_FIND_COMMA(String_Input); //Get string after comma
//Find comma and extract data until next comma
uint8_t comma_parse_string_length = strlen(comma_parse_string); //Get length of string after comma
for (uint8_t itr = 0; itr < comma_parse_string_length, itr++)
{
if(comma_parse_string[itr] == ",")
{
break;
}
F_Buffer[itr] = comma_parse_string[itr];
Extracted_Output[itr] = comma_parse_string[itr];
}
}
void F_U8_to_F(uint8_t Input_val, uint8_t pos, float* Output)
{
float OutputHold;
OutputHold = 10*Input_val[pos];
OutputHold = OutputHold + Input_val[pos+1];
Output = OutputHold;
}
void F_NMEA_TIME_CONVERT(uint8_t Input_Val, NMEA_GPS gps_info)
{
F_U8_to_F(timeval, 0, gps_info->GPS_TIME->nmea_hours);
F_U8_to_F(timeval, 2, gps_info->GPS_TIME->nmea_minutes);
F_U8_to_F(timeval, 4, gps_info->GPS_TIME->nmea_seconds);
}
void F_GGA_Parse(uint8_t* String_Input, NMEA_GPS gps_info)
{
//TIME, Lat, N/S, Long, E/W, Fix, Sat#, HDOP, Alt, unit, Geoid, unit, .........
strof();
uint8_t time_value[12], Lat_Val[12]; //obtain from String_Input and to convert into float value
F_Extract_Until_Comma(String_Input, time_value);
F_NMEA_TIME_CONVERT(time_value, gps_info)
//time end
char* Post_Time_String = strstr(String_Input, ",");
F_Extract_Until_Comma(Post_Time_String, Lat_Val);
for(int itr=0; itr<sizeof(Lat_Val),itr++)
{
if(Lat_Val[itr] != ".")
{
gps_info->GPS_POSITION->latitude = (10 * gps_info->GPS_POSITION->latitude) + Lat_Val[itr];
}
}
F_U8_to_F(Lat_Val,0,gps_info);
}
char F_Extract_GPS_Data(char * raw_string_input, NMEA_GPS* gps_info)
{
char* GPS_ID_FOUND = strstr(raw_string_input, "$GP");
uint8_t Comma_Rounds = 0;
if(NULL == GPS_ID_FOUND)
{
return NULL;
}
else
{
if(strstr(GPS_ID_FOUND, "GGA"))
{
gps_info->GPS_TYPE = GGA;
gps_info->GGA_Flag = YES;
Comma_Rounds = 14;
}
else if(strstr(GPS_ID_FOUND,"RMC"))
{
gps_info->GPS_TYPE = RMC;
gps_info->RMC_Flag = YES;
Comma_Rounds = 11;
}
else if(strstr(GPS_ID_FOUND,"VTG"))
{
gps_info->GPS_TYPE = VTG;
gps_info->VTG_Flag = YES;
Comma_Rounds = 9;
}
if(gps_info->GPS_TYPE == 1)
{
F_GGA_Parse(GPS_ID_FOUND, gps_info);
}
// if(GPS_ID_FOUND[0])
}
}
NMEA_PARSE_CODE.h
#include <stdio.h>
typedef enum
{
NO=0,
YES=1
} NMEA_ID_GGA;
typedef enum
{
NO=0,
YES=1
} NMEA_ID_RMC;
typedef enum
{
NO=0,
YES=1
} NMEA_ID_VTG;
typedef enum
{
GGA=1,
RMC,
VTG
} NMEA_ID;
typedef struct
{
float nmea_hours;
float nmea_minutes;
float nmea_seconds;
float nmea_thousands;
} NMEA_UTC_TIME;
typedef struct
{
uint8_t nmea_year;
uint8_t nmea_month;
uint8_t nmea_day;
} NMEA_DATE;
typedef struct
{
float longitude;
float latitude;
} NMEA_POSITION;
typedef struct
{
char NORTHSOUTH;
char EASTWEST;
float course; //In degrees
float knot_speed; //In knots
float meter_speed; //In meters per second
} NMEA_VELOCITY;
typedef struct
{
NMEA_ID GPS_TYPE;
NMEA_ID_GGA GGA_Flag;
NMEA_ID_RMC RMC_Flag;
NMEA_ID_VTG VTG_Flag;
NMEA_UTC_TIME GPS_TIME;
NMEA_DATE GPS_DATE;
NMEA_POSITION GPS_POSITION;
NMEA_VELOCITY GPS_VELOCITY;
} NMEA_GPS;
typedef struct
{
NMEA_ID ID;
NMEA_UTC_TIME TIME;
} NMEA_GGA;
Going byte by byte, multiply the existing float value by 10 before adding the new byte. Repeat until end of length is reached. The code compiles somehow in Visual Studio Code, but that isn't saying much. VSC isn't showing me any errors either.
In answer to the question you asked, no, there is not a way of converting a string like
42.23234to afloatthat does not involve a loop, somewhere. Moreover, simply "multiply the existing float value by 10 before adding the new byte" is completely insufficient for floating-point values, because it doesn't handle the decimal point or the fractional digits after it.The simple way to convert a string to a floating-point value (which, incidentally, does not involve any loops in your code) is to call a prewritten library function such as
atof,strtod, orsscanf. And using a prewritten library function is highly, highly recommended here, because properly converting floating-point strings is quite a hard problem in general, and it's much better to let someone else do that work.Here's a simple example:
This prints
The way this works is that
strtodconverts the first part of the string you give it to adouble, then returns you (via a "reference" pointer parameter) a pointer to the rest of the string, that it didn't convert, because it ran into a non-numeric character, in this case the first comma.[Beware, though, that in NMEA, 42.23234 does not mean 42.23234 degrees. It's actually 42 degrees, 23.234 minutes, which works out to 42.3872333 degrees. So it turns out you're not going to be able to use a straightforward call to
strtodto get your latitudes and longitudes, after all.]Although not the question you asked, the rest of your code has many, many problems beyond that of converting floating-point strings. I recommend starting with a smaller, simpler problem. Or if you need to parse NMEA strings today, see if you can find some prewritten code that fits your needs — there's gobs and gobs of it out there.
If you do want to write your own NMEA parser, I recommend treating the problem as two completely separate parts:
But trying to move along the string one character at a time, simultaneously converting field contents and looking for commas, tends to lead to an unholy mess.
One decent way to break a string up into comma-separated fields is to use the library function
strtok.Miscellaneous other notes:
floatdoes not have enough precision for global latitudes and longitudes. Usedouble. (Actually, a fine general rule is that typefloatdoesn't really have enough precision for anything, so you should always usedouble.)void F_U8_to_F(uint8_t Input_val, uint8_t pos, float* Output)that's supposed to return a value via the reference parameterOutput, you need to assign that using*Output = OutputHold;. (Your compiler should have warned you about this.)F_U8_to_Fthat's supposed to return a value via a reference parameter, you need to pass a pointer to the value you want to fill in:F_U8_to_F(timeval, 2, &gps_info->GPS_TIME->nmea_minutes);int(notfloat) fornmea_hoursandnmea_minutes. (And if you're going to carrynmea_thousandsseparately, you don't need afloatfor it or fornmea_seconds, either.) But depending on the needs of the rest of the program, you might find it more useful to convert to a more computation-friendly time format, not separate hours, minutes, and seconds fields.