|
|
View previous topic :: View next topic |
Author |
Message |
JAM2014
Joined: 24 Apr 2014 Posts: 138
|
Converting two characters to an integer..... |
Posted: Wed Jun 06, 2018 10:32 am |
|
|
Hi All,
I'm in the process of adding checksum validation to a GPS enabled project based on an NMEA parsing routine that's available in the Code Library. The NMEA parsing routines did not parse & make available the NMEA checksum, so that was job #1. I have some new code that extracts the two character checksum from the NMEA string, and then converts it to an integer value. It's working as intended, except in the case where a character is the checksum is an 'A' - 'F', then it fails. For example, if the checksum is '40' it works, but if it's '4C' is doesn't. So far, I don't see a nice clean way to convert the two (hex) checksum characters to an integer in all cases.
Here are the NMEA parsing routines with my added code to parse and convert the checksum characters:
Code: |
///////////////////////////////////////////////////////////////////////////////
#include <string.h>
#include <stdlib.h>
///////////////////////////////////////////////////////////////////////////////
typedef struct _DateTimeInfo
{
int8 Day;
int8 Month;
int8 Year;
int8 Hour;
int8 Minute;
int8 Second;
} DateTimeInfo;
////////////////////////////////////////
typedef struct _GPRMCInfo
{
char Valid;
DateTimeInfo DT;
float Latitude;
char N_S;
float Longitude;
char E_W;
float Speed;
int8 Checksum;
} GPRMCInfo;
///////////////////////////////////////////////////////////////////////////////
//copy string (pos n to pos m) from s2 to s1
char* StrnmCpy(char *s1, char *s2, size_t n, size_t m)
{
int8 i;
char *s;
for (s=s1, i=n, s2+=n; i<=m; i++)
*s++ = *s2++;
*s = '\0';
return s1;
}
///////////////////////////////////////////////////////////////////////////////
// find c in s starting from pos st
int8 StrFnd(char *s, char c, size_t st)
{
int8 l;
for (l=st, s+=st ; *s != '\0' ; l++, s++)
if (*s == c)
return l;
return -1;
}
///////////////////////////////////////////////////////////////////////////////
void GPRMC_decode(char *GPRMCStr, GPRMCInfo *RMCInfo)
{
int8 p1, p2;
char TempStr[16];
p1 = StrFnd(GPRMCStr, ',', 0); //find first comma
if (p1 == 6)
{
//check for valid packet:
if ( (StrFnd(GPRMCStr, 'A', p1+1) == 18) && (GPRMCStr[0]=='$')) //valid?
{
RMCInfo->Valid = 'A';
//Get time
p1 = StrFnd(GPRMCStr, ',', 0); //find first comma
p2 = StrFnd(GPRMCStr, ',', p1+1); //find next comma
RMCInfo->DT.Hour = atoi(StrnmCpy(TempStr, GPRMCStr, p1+1, p1+2)); //hour
RMCInfo->DT.Minute = atoi(StrnmCpy(TempStr, GPRMCStr, p1+3, p1+4)); //min
RMCInfo->DT.Second = atoi(StrnmCpy(TempStr, GPRMCStr, p1+5, p1+6)); //sec
//Get latitude & direction
p1 = StrFnd(GPRMCStr, ',', p2+1); //find next comma
p2 = StrFnd(GPRMCStr, ',', p1+1); //find next comma
RMCInfo->Latitude = atof(StrnmCpy(TempStr, GPRMCStr, p1+1, p2-1));
RMCInfo->N_S = GPRMCStr[p2+1];
//Get longitude & direction
p1 = StrFnd(GPRMCStr, ',', p2+1); //find next comma
p2 = StrFnd(GPRMCStr, ',', p1+1); //find next comma
RMCInfo->Longitude = atof(StrnmCpy(TempStr, GPRMCStr, p1+1, p2-1));
RMCInfo->E_W = GPRMCStr[p2+1];
//Get speed
p1 = StrFnd(GPRMCStr, ',', p2+1); //find next comma
p2 = StrFnd(GPRMCStr, ',', p1+1); //find next comma
RMCInfo->Speed = atof(StrnmCpy(TempStr, GPRMCStr, p1+1, p2-1));
//Get date
p1 = StrFnd(GPRMCStr, ',', p2+1); //find next comma
p2 = StrFnd(GPRMCStr, ',', p1+1); //find next comma
RMCInfo->DT.Day = atoi(StrnmCpy(TempStr, GPRMCStr, p1+1, p1+2)); //dd
RMCInfo->DT.Month = atoi(StrnmCpy(TempStr, GPRMCStr, p1+3, p1+4));//mm
RMCInfo->DT.year = atoi(StrnmCpy(TempStr, GPRMCStr, p1+5, p1+6)); //yy
//Get checksum
p1 = StrFnd(GPRMCStr, '*', 0); //find first asterisk
RMCInfo->Checksum = atoi(StrnmCpy(TempStr, GPRMCStr, p1+1, p1+2)); //nn <--- Added Code
}
else //not valid
{
RMCInfo->Valid = 'V';
}
}
}
///////////////////////////////////////////////////////////////////////////////
|
Can anyone suggest a simple method to convert the two NMEA checksum characters to an integer in all cases?
Jack |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19545
|
|
Posted: Wed Jun 06, 2018 10:53 am |
|
|
OK. Your problem is atoi, does not know you are giving it a hex value.
atoi, needs the text 0x in front of the value to treat is as hex.
Option #1.
Copy the two characters to a 5 character char buffer array, and put 0x in front of these (and a null terminator).
Code: |
TempStr[0]='0';
TempStr[1]='x';
StrnmCpy(TempStr+2, GPRMCStr, p1+1, p1+2));
RMCInfo->Checksum = atoi(TempStr);
|
Or
Option #2
Simply convert the two characters yourself. You can convert a single hex digit to a value with something like:
Code: |
int hex2int(char ch)
{
if (ch >= '0' && ch <= '9')
return ch - '0';
if (ch >= 'A' && ch <= 'F')
return ch - 'A' + 10;
if (ch >= 'a' && ch <= 'f')
return ch - 'a' + 10;
return -1;
}
|
I see PCM_programmer has posted a third great option while I was typing. |
|
|
JAM2014
Joined: 24 Apr 2014 Posts: 138
|
|
Posted: Thu Jun 07, 2018 1:40 pm |
|
|
Hi All,
Thanks for the help so far! I'm still trying to put this issue to bed, but without total success!
I used this implementation to convert the two ascii NMEA CRC characters to a binary value:
Code: |
//Get checksum
p1 = StrFnd(GPRMCStr, '*', 0); //find first asterisk
TempStr[0]='0';
TempStr[1]='x';
RMCInfo->Checksum = atoi(StrnmCpy(TempStr+2, GPRMCStr, p1+1, p1+2));
fprintf(Diag, "TempStr: %s\n\r", TempStr);
|
Here are the results:
Quote: |
$GPRMC,192829.000,A,4030.1334,N,07304.9255,W,0.22,96.91,070618,,,A*43
TempStr: 0x43
Calc. CRC: 67 Rcvd. CRC: 67 <--- Correct!!
$GPRMC,192830.000,A,4030.1333,N,07304.9256,W,0.10,96.91,070618,,,A*4E
TempStr: 0x4E
Calc. CRC: 78 Rcvd. CRC: 04 <--- Incorrect!!
|
So, the problem remains the same. As long as the CRC is fully numeric, the atoi function it works correctly, but if a character is 'A' thru 'F' is included, it does not!
Jack |
|
|
JAM2014
Joined: 24 Apr 2014 Posts: 138
|
|
Posted: Thu Jun 07, 2018 2:28 pm |
|
|
Hi All,
Something else must be going on here, because a test program seems to validate the functionality of the atoi function...
Code: |
#include <18F46K22.h>
#fuses INTRC_IO, NOWDT, BROWNOUT, PUT, NOPBADEN
#use delay(clock=4M)
#use rs232(baud=9600, xmit=PIN_b4, stream = Diag)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void main () {
int8 iIndex = 0;
int val;
char str[20];
strcpy(str, "0x41");
val = atoi(str);
fprintf(Diag, "String value = %s, Int value = %d\n\r", str, val);
strcpy(str, "0x4A");
val = atoi(str);
fprintf(Diag, "String value = %s, Int value = %d\n\r", str, val);
while(1){}
}
|
And the results are:
Quote: |
String value = 0x41, Int value = 65
String value = 0x4A, Int value = 74
|
Jack |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Fri Jun 08, 2018 3:41 am |
|
|
Quote: |
p1 = StrFnd(GPRMCStr, '*', 0); //find first asterisk
TempStr[0]='0';
TempStr[1]='x';
RMCInfo->Checksum = atoi(StrnmCpy(TempStr+2, GPRMCStr, p1+1, p1+2));
fprintf(Diag, "TempStr: %s\n\r", TempStr);
|
The problem is that you're trying to do too much. You're trying to pack
everything together in one line, and it's a mistake.
1. First you run your StrnmCpy() to copy "4E" after the "0x" in TempStr[].
2. Then you run atoi() on the returned pointer from StrnmCpy().
3 But, the returned pointer is TempStr+2. When you run atoi() you
are skipping over the "0x". Hence, running atoi() on "4E" is going to
return 4, or "04".
Split it up into two lines and then it will work properly. Note atoi() is run
on the full TempStr, starting from the beginning of the string:
Code: | p1 = StrFnd(GPRMCStr, '*', 0); //find first asterisk
TempStr[0]='0';
TempStr[1]='x';
StrnmCpy(TempStr+2, GPRMCStr, p1+1, p1+2);
checksum = atoi(TempStr);
|
|
|
|
JAM2014
Joined: 24 Apr 2014 Posts: 138
|
|
Posted: Fri Jun 08, 2018 9:33 am |
|
|
Hi All,
Ah, yes, I see that now! I split the lines as shown, and it's working like a champ!
I'll post the updates to the NMEA parsing routines to the code library to include the return of the checksum!
Thanks,
Jack |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19545
|
|
Posted: Fri Jun 08, 2018 2:38 pm |
|
|
As a comment though it'd be a lot smaller and much faster to use the direct hex read and not atoi. Atoi, has to parse the string, work out that you want to convert hex, and then call the routines to do this. Bulky and slow.
If you have two characters in 'tempstr', containing hex characters, with the little routine I already posted:
Checksum=hex2int(*TempStr)*16+hex2int(*(TempStr+1));
You will find the result will be smaller code, and this will be a lot faster as well.... |
|
|
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|