CCS C Software and Maintenance Offers
FAQFAQ   FAQForum Help   FAQOfficial CCS Support   SearchSearch  RegisterRegister 

ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

CCS does not monitor this forum on a regular basis.

Please do not post bug reports on this forum. Send them to CCS Technical Support

NMEA GPS messages
Goto page 1, 2  Next
 
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion
View previous topic :: View next topic  
Author Message
aldina



Joined: 09 Oct 2009
Posts: 26

View user's profile Send private message Send e-mail

NMEA GPS messages
PostPosted: Wed Nov 04, 2020 3:10 am     Reply with quote

Hello,

I'm using a GPS module (Quectel L86) to read information from NMEA messages ($GPVTG....)

My Code:
Code:

#include <16F18323.h>
#FUSES NOLVP     
#FUSES NOWDT                     
#FUSES RSTOSC_HFINTRC
#FUSES NOCLKOUT
#FUSES BROWNOUT, LPBOR, PUT, BORV27     
#FUSES NOPROTECT 
#FUSES NOEXTOSC 

#use delay(internal=1000000) //,restart_wdt)
#use rs232(baud=9600, xmit=PIN_C4, rcv=PIN_C5)

//#BYTE OSCCON1 = 0x919  // OSCCON register (pg 37 datasheet)
//#BYTE WDTCON = 0x97 // WDTCON (pg 27 datasheet)
#include <stdio.h>
#include <stdlib.h>

void main(void)
{

   char aux;

   setup_wdt(WDT_OFF);

   setup_oscillator(OSC_HFINTRC_1MHZ);
   Set_tris_c (0x20); //  PIN_C5 (rcv input): 100000
   //set_tris_a (0xFF); //
     
   // Configuration of outputs
   output_low(PIN_C0);
   output_low(PIN_C1);
   output_low(PIN_C2);
   output_low(PIN_C3); // PIN_C3 LED output
   output_low(PIN_C4);
     
   while(true)
   {
      aux=0; // aux init

      while(true)
      {
         aux=getc();
         if(aux=='$')
         {
            OUTPUT_HIGH(PIN_C3);
            delay_ms(100);
            OUTPUT_LOW(PIN_C3);
         }

         else
        {
            OUTPUT_LOW(PIN_C3);
            delay_ms(100);
         }
    }
}

This is my simple code that I'm using. If I receive the $ char, LED will flash else not. The problem is that LED was always off. I've changed. If I receive the $ char, LED will turn off else it will flash but the result is the same - LED always off.
So it means that my code is stopped in getc(). Can you help me with what is wrong in my code?
I appreciate very much your help.
Regards,

Aldina
Ttelmah



Joined: 11 Mar 2010
Posts: 19515

View user's profile Send private message

PostPosted: Wed Nov 04, 2020 3:43 am     Reply with quote

First thing, the UART on this chip is a PPS peripheral. You _must_ use
pin select to set it up. Currently you are using a software UART.
Look at the sticky at the top of the forum for how to use pin select.

Second thing, with this done, the code you post would hang the UART.
The reason is that you sit for 100mSec lumps not reading the UART. If
a UART receives a couple of characters and they are not read, it switches
into an 'overrun error', and stops working.
Two solutions exist for this:
1) Use an interrupt driven RS232 RX. This then receives the characters
when they arrive. Long term the right solution, but for now not needed.
However the second fix is...
2) Use 'ERRORS' in your #use RS232 statement. On a hardware UART,
this should _always_ be used, unless you are adding your own error
handling. This adds code to the RS232 handling routine, to automatically
clear the overrun error if it occurs. So, get in the habit, of always using this.

As a comment then you drive C4 low. The idle for a TTL serial line is high.
Don't drive this pin (the UART will do it).
aldina



Joined: 09 Oct 2009
Posts: 26

View user's profile Send private message Send e-mail

PostPosted: Thu Nov 05, 2020 12:03 pm     Reply with quote

Hi Ttelmah,

Thank you very much for your answer and help.

First thing is done and works. Second thing, I'm choose using ERRORS in #USE RS232 statement. This is my new code:
Code:

#include <16F18323.h>
#FUSES NOLVP     
#FUSES WDT                     
#FUSES RSTOSC_HFINTRC
#FUSES NOCLKOUT
#FUSES BROWNOUT, LPBOR, PUT, BORV27     
#FUSES NOPROTECT
#fuses NOEXTOSC   
#use delay(internal=1000000) //,restart_wdt)
#pin_select U1TX = PIN_C4
#pin_select U1RX = PIN_C5
#use rs232 (UART1, baud = 9600, ERRORS)
#BYTE OSCCON1 = 0x919 
#BYTE WDTCON = 0x97
#include <stdio.h>
#include <stdlib.h>

char GPS_speed()

   char ID_char;
   char start[50];
   int8 i;
   int8 j;
   char speed_K;
   char gps_speed[10];
   char speed;
   
   ID_char=0;
   speed_K=0;
   i=0;
   j=0;
   speed=0;
     
   //while(true)
   //{     
   if(kbhit())
   {
         ID_char = getc();
         if(ID_char == '$')  // NMEA text: $GPVTG... arriving
         {
            for(i=0 ; i<50 ; i++)
            {
               ID_char = getc();
               start[i] = ID_char;
               
               if((start[3] == 'T')) // GPVTG message - 'T? is in 3rd position
               {
                  if((start[i] == 'N')) // wait for 'N' char - km/h speed is next information
                  {

                     for(int j=0 ; j<3 ; j++)
                     {
                        speed_K = getc();
                        gps_speed[j] = speed_K;
                     
                        if((gps_speed[1] == '0')) // j=1 is 1st char of km/h Speed
                        speed = gps_speed[1]; // speed = 0 means speed <9km
 
                        else if((gps_speed[1] == '1'))
                           speed = gps_speed[1]; // speed = 1 means speed<20

                        else if((gps_speed[1] > '1'))
                           speed = gps_speed[1]; // speed > 1 means speed>20
           
                        else
                           speed = gps_speed[0];
                     }
                  }
               }
            }
         }
      }
      return speed;
}

void main()
{
   setup_oscillator(OSC_HFINTRC_1MHZ);
   //Set_tris_c (0x20); // 100000
   //set_tris_a (0xFF); //     

     
   // Configuration of outputs
   output_low(PIN_C0);
   output_low(PIN_C1);
   output_low(PIN_C2);
   output_low(PIN_C3);
   
   char aux;
   
   while(true)
   { 
      restart_wdt();
      aux = 0;
     
      aux = GPS_speed();
           
      if(aux==0) // C3 HIGH
      {
         OUTPUT_HIGH(PIN_C3);
         delay_ms(500);
      }
                     
      else if(aux == 1) // flash C3
      {
         OUTPUT_HIGH(PIN_C3);
         delay_ms(100);
         OUTPUT_LOW(PIN_C3);
         delay_ms(100);
      }

      else if((aux != 0) && (aux != 1)) // LOW C3
      {
         OUTPUT_LOW(PIN_C3);
         delay_ms(500);
      }
   }
}
 

My objective is capt $GPVTG NMEA message and read the speed (km/h). For example: $GPVTG,0.00,T,,M,0.00,N,0.00,K,... this is a message from L86 module so, after 'N' is the speed information that I want to capt.
But now my problem is that the value returned by my function GPS_speed is always '0', because PIN_C3 is always ON and it should change when I'm driving my car.

I think I have something wrong again. Maybe I should have an integer return coming from my function to the main program. What do you think? I appreciate your help, please.
Ttelmah



Joined: 11 Mar 2010
Posts: 19515

View user's profile Send private message

PostPosted: Thu Nov 05, 2020 12:41 pm     Reply with quote

The problem you now have is the difference between text, and numbers.
The text character '1' has a numeric value of 49. '0' a numeric value of
48. So you are not getting the value you expect. Look at the atol function.
Add a terminating NULL to the array holding the input characters, and this
can then convert the text into a numeric result.
bkamen



Joined: 07 Jan 2004
Posts: 1615
Location: Central Illinois, USA

View user's profile Send private message

PostPosted: Thu Nov 05, 2020 11:44 pm     Reply with quote

As Ttelmah has said - and having written enough NMEA parsing systems, I can't also stress enough:

Really consider writing your GPS Serial receiver as an ISR.

Do it on a simple project like this so you have the hang of it for more complex programs where it will matter.

I did a fun secondary display for a Garmin GPS's VHF-Out mode and there's a lot of data and it actually comes out of the GPS at 9600bps... and every cycle matters.

The RS232 RX had to be an ISR with good buffering.

So -- do it now for the practice if you plan on doing more of this down the road.
_________________
Dazed and confused? I don't think so. Just "plain lost" will do. :D
temtronic



Joined: 01 Jul 2010
Posts: 9228
Location: Greensville,Ontario

View user's profile Send private message

PostPosted: Fri Nov 06, 2020 5:15 am     Reply with quote

As others have said, you really need to get GPS data inside an ISR. Actually you should use an ISR for all 'serial data' if the PIC has a Hardware UART.
Also, you should run the PIC at the highest clock speed that it can. The faster it runs, the more it can do for you. I downloaded the datasheet and that PIC can run at 32MHz. That is 32x FASTER than you have it going ! This will allow the PIC to do a LOT more, faster which is especially important for 'parsing' routines, floating point math, etc.

Jay
aldina



Joined: 09 Oct 2009
Posts: 26

View user's profile Send private message Send e-mail

PostPosted: Mon Nov 16, 2020 11:44 am     Reply with quote

Hello everybody,


I continue trying to discover the solution to my problem.
This is my recent code:
Code:

#include <16F18323.h>
#FUSES NOLVP     
#FUSES WDT                     
#FUSES RSTOSC_HFINTRC
#FUSES NOCLKOUT
#FUSES BROWNOUT, LPBOR, PUT, BORV27   
#FUSES NOPROTECT 
#fuses NOEXTOSC   
#use delay(internal=1000000) //,restart_wdt)
#pin_select U1TX = PIN_C4
#pin_select U1RX = PIN_C5
#use rs232 (UART1, baud = 9600, ERRORS, RECEIVE_BUFFER=128)
#BYTE OSCCON1 = 0x919 
#BYTE WDTCON = 0x97
#include <stdio.h>
#include <stdlib.h>

char rcv_data;

#INT_RDA
void  RDA_isr(void)         
{
   rcv_data=getc();
}

void main()
{
   enable_interrupts(INT_RDA);
   setup_oscillator(OSC_HFINTRC_1MHZ);
       
   // Configuration of outputs
   output_low(PIN_C0);
   output_low(PIN_C1);
   output_low(PIN_C2);
   output_low(PIN_C3);
   
   char ID_char;
   char start[50];
   int8 i;
   int8 j;
   char speed_K;
   char gps_speed[10];
   int8 speed;
   
   while(true)
   { 
      restart_wdt();

      ID_char=0;
      speed_K=0;
      i=0;
      j=0;
      speed=0;
           
      if(rcv_data == '$')  // NMEA text: $GPVTG...
      {
         for(i=0 ; i<50 ; i++)
         {
            ID_char = getc();
            start[i] = ID_char;
            //OUTPUT_HIGH(PIN_C3); // flash Port A5 - it means $ capture
            if((start[3] == 'T')) // GPVTG text
            {
               if((start[i] == 'N')) // N - miles
               {
                  for(int j=1 ; j<10 ; j++)
                  {
                     do
                     {
                        speed_K = getc();
                        gps_speed[j] = speed_K;
                     }
                     while(gps_speed[j]!= ',' || gps_speed[j]!="."); // end read
                  }
                  speed = atoi(gps_speed);    // convert km/h speed
               }
            }
         }
      }
     
      if(speed <= 15) // km/h > 15 ---> C3 ON
      {
         OUTPUT_HIGH(PIN_C3);
         delay_ms(300);
      }

      else if(speed > 15) // km/h < 15 ---> C3 OFF
      {
         OUTPUT_LOW(PIN_C3);
         delay_ms(500);
      }
   }
}

But C3 PIN output is still always ON even when the speed car is more than 15 km/h.

Any suggestion?

+++++++++++++++++
Code block added by moderator.
- Forum Moderator
+++++++++++++++++
temtronic



Joined: 01 Jul 2010
Posts: 9228
Location: Greensville,Ontario

View user's profile Send private message

PostPosted: Mon Nov 16, 2020 12:01 pm     Reply with quote

Get RID of the 'restart_WDT' AND disable the WDT ( fuse = NOWDT )
there is no need for you enable the WDT until your code is 100% reliable and 'ready for market'. Then and only then, enable the WDT and test your code under all conditions for at least a week of 24/7 testing.

While WDT may not be the actual problem regarding C3, it can be resetting the PIC and causing additional headaches.
At the very least by disabling it, you'll eliminate a possible source of problem.

also

If possible, use a faster clock rate. 1MHz may not be fast enough, especially to receive and parse the GPS serial data. Typically @4MHz, it take 1us per line of machine code..... faster is generally always better.

and
press the 'code' button before posting your program. That way it'll 'magically' turn green and line up better ! Easier for eyes to see what may be wrong....
PrinceNai



Joined: 31 Oct 2016
Posts: 479
Location: Montenegro

View user's profile Send private message

PostPosted: Mon Nov 16, 2020 1:23 pm     Reply with quote

Hi,

My approach is different. All the parsing of UART characters received is done inside the RDA ISR using a switch statement. When $ is received, raise a flag to inform main to turn on one LED. When $GPVTG is received, record next characters into a buffer and raise a flag inside ISR to inform main to do something with the data. If you try to decode many different messages this switch statement gets quite big, but it is relatively easy to follow. In your case with just one message it is very easy.

Code:

  tmp=getc();                      // get received char and thus also clear interrupt flag                                       

// ............................................................................                                                                                 
// Process reception through state machine
// Possible data from NMEA module that will be handled is $GPVTG plus data
//
// Actions of this state machine are:
//                 
// Dollar_Sign = 1;            inform MAIN that we have an $
// Got_Data = 1;             inform MAIN we have some data to work on
// main is responsible to clear those flags
                                                                 
  switch (Nmea_State) {
 
//get initial character, '$'                 
      case 0:{                                                                                                   
        if(tmp == '$'){                 //we have "$", could be "$GPVTG"
            gsm_state = 1;                //expecting G
         Dollar_Sign = 1;            // inform main we have an $
        }                                     
         else {                         
            gsm_state = 0;                     
         }
         break;         
            }
//********WE HAVE RECEIVED '$' AS A FIRST CHARACTER***************************
// we have "$", expecting G   
      case 1:{
         if    (tmp == 'G')         //have "G"
               gsm_state = 11;      //expecting 'P'             
         else
            gsm_state = 0;          //reset state machine           
         break;
            }

// we have "$G", expecting 'P'
      case 11:  {
         if(tmp == 'P' )                     // expecting 'V'
            gsm_state = 12;     
         else
            gsm_state = 0;                   //error, reset state machine
         break;         
               }
// we have "$GP", expecting 'V'               
      case 12:  {
         if(tmp == 'V' )                     // expecting 'T'
            gsm_state = 13;
         else
            gsm_state = 0;                   //error, reset state machine
         break;   
     }         
// we have "$GPV", expecting 'T'                  }
      case 13:  {
         if(tmp == 'T' )                     // we have $SGPV so far
            gsm_state = 14;
         else
            gsm_state = 0;                   //error, reset state machine
         break;
     }     
// we have "$GPVT", expecting 'G'                 }
      case 14:  {
         if(tmp == 'G' )   {                  // we have $SGPVT so far
            gsm_state = 15;               // and now  $SGPVTG
      }   
         else
            gsm_state = 0;                   //error, reset state machine
         break;           
               }
// we have "$GPVTG", now wait for the N character to start recording your data
      case 15: {
         if(tmp == 'N'){
            gsm_state = 16;
            Character_Counter = 0;            
         }
         else{
            Character_Counter++;   // allow max. x characters before you encounter 'N', otherwise
                              // you could get stuck here in case of garbled data from the module
            if (Character_Counter == 15){      // put your number here!!!!
               gsm_state = 0;            // abort, something went wrong
               Character_Counter = 0;
               next_in = 0;               
            }            
         }
         break;         
      }

      case 16:  {
        if(tmp != 'X'){               // I do not know the end character of the message!!!!! Record until received.
         buffer[next_in]= tmp;            // move received char to the appropriate place in buffer.
         ++next_in;                   // increment next_in pointer
         
         if(next_in == BUFFER_SIZE) {       // prevent rollover         
         next_in=0;                           
         }//;
        }
      else{
         Got_Data = 1;               // inform main we have the data
         gsm_state = 0;               // reset state machine
         next_in = 0;               // go to beginning of the data buffer         
      }         
         break;           
     }           
                                                 
   }  // switch brace


Just put this inside your RDA ISR, declare tmp, buffer, next_in, flags, Character_Counter and Nmea_State.


Last edited by PrinceNai on Tue Nov 17, 2020 10:31 am; edited 4 times in total
PrinceNai



Joined: 31 Oct 2016
Posts: 479
Location: Montenegro

View user's profile Send private message

PostPosted: Mon Nov 16, 2020 1:27 pm     Reply with quote

Please excuse indents and comment placements, I did it in Notepad++ and it differs a bit from CCS editor regarding tabs.
PrinceNai



Joined: 31 Oct 2016
Posts: 479
Location: Montenegro

View user's profile Send private message

PostPosted: Tue Nov 17, 2020 10:37 am     Reply with quote

The code was copied from an existing project and rewritten for this specific case. There were some mistakes in it regarding braces in if statements and no error checking. Please see the edited version. Still no guarantee that it is 100% clean of mistakes, but the previous version 100% wouldn't work.

Regards
aldina



Joined: 09 Oct 2009
Posts: 26

View user's profile Send private message Send e-mail

PostPosted: Tue Nov 17, 2020 10:43 am     Reply with quote

Hi PrinceNai,

Thank you so much for your reply and help.
I'm trying to implement your RDA_ISR() code. I understand the essence of your proposed code but I want to ask you the following:

I'm receiving NMEA data like:
Code:
$GPVTG,0.00,T,,M,0.00,N,0.00,K,....

where:
0.00,N is knot speed and
0.00,K is km/h speed (data from gps I need to know).

According your code I can use:
Code:
if(tmp != 'K'){               // K is km/h speed I want to know and I'm recording data until hear

So, when tmp is == 'K' I need to obtain the information before (0.00) and read it, but I don't know how can I only read data from 'N' and 'K', in fact I only want data between (N,) and (,K).
Can you understand what I mean and have you any idea?
PrinceNai



Joined: 31 Oct 2016
Posts: 479
Location: Montenegro

View user's profile Send private message

PostPosted: Tue Nov 17, 2020 11:28 am     Reply with quote

Yes, I do. So, your start character is 'N', which is already in the code. This is when you start recording characters in the buffer, after 'N' is received. Your end character is 'K', or ',' , so replace 'X' in state 16 with 'K' or with ','. That way you will start filling the buffer after 'N' is received and stop when you get 'K' or ','. Then you only have to extract characters from buffer (you should have something like ",15.8," in your buffer and send those characters from correct location in the buffer to LCD or wherever you want.

Do you have a debugger? If yes, just add delay_cycles(1) into every state and have a breakpoint there. That way you'll see how you progress through state machine and check if there is a state the program doesn't reach. I can't test it, but I assure you that this approach works. I'm using it to get the messages from a GSM module (800 lines of code) and this part of the code never failed. As soon as I got all my braces right :-). Don't forget to initialize all the variables to 0 when you declare them, otherwise you'll have a problem.

Regards,
Samo
bkamen



Joined: 07 Jan 2004
Posts: 1615
Location: Central Illinois, USA

View user's profile Send private message

PostPosted: Tue Nov 17, 2020 2:42 pm     Reply with quote

temtronic wrote:
As others have said, you really need to get GPS data inside an ISR. Actually you should use an ISR for all 'serial data' if the PIC has a Hardware UART.
Also, you should run the PIC at the highest clock speed that it can. The faster it runs, the more it can do for you. I downloaded the datasheet and that PIC can run at 32MHz. That is 32x FASTER than you have it going ! This will allow the PIC to do a LOT more, faster which is especially important for 'parsing' routines, floating point math, etc.


I just looked at the datasheet too.

To build on what Jay said and be more specific, the INTERNAL oscillator is capable of 32MHz.

So without any external parts -- all you have to do is change your #USE DELAY to (internal=32M) and be running 32x faster.

Do that. Do it like right away.

Zooooooooooooooooooooom! Razz
_________________
Dazed and confused? I don't think so. Just "plain lost" will do. :D
bkamen



Joined: 07 Jan 2004
Posts: 1615
Location: Central Illinois, USA

View user's profile Send private message

PostPosted: Tue Nov 17, 2020 3:47 pm     Reply with quote

I wrote something similar to PrinceNai...

I was only interested in a single string -- so I could strip off stuff when I wanted.

I also used ring buffer of arrays (rather than a single dimension. Uses more RAM... but I had it so I wrote it that way)

The main routine just monitors the buffer index to see if there's new strings to process and if so, it processes them and then sets another index so the ISR doesn't overrun.

If the main loop can't keep up, the ISR throws strings away.

With this PIC running at 40MHz, I've never had that problem. :D

Code:

#INT_RDA
void isr_gps ( void ) {
   unsigned char c;
   output_low(LED1);

   c = rcreg;

   switch (gps_state) {
      case 0: if (! gps_hold_isr) {
               if (c == '$') {
                  gps_timeout = GPS_TIMEOUT_VALUE;   // reset timeout value. If we get stuck because of a hold, our friend the timeout counter will clear.
                  gps_char_index = 0;
                  gps_state++;
               }
            }
            break;
      // look for the ONLY string we want.
      case 1: if (c == 'P') { goto incr_gps_state; } else { goto reset_gps_state; } break;
      case 2: if (c == 'M') { goto incr_gps_state; } else { goto reset_gps_state; } break;
      case 3: if (c == 'R') { goto incr_gps_state; } else { goto reset_gps_state; } break;
      case 4: if (c == 'R') { goto incr_gps_state; } else { goto reset_gps_state; } break;
      case 5: if (c == 'C') { goto incr_gps_state; } else { goto reset_gps_state; } break;
      case 6: if (c == '0') { goto incr_gps_state; } else { goto reset_gps_state; } break;

      case 7:   // Store away our character provided it's not a CR or LF
            if  ( (c == 0x0A) || (c == 0x0D) ) {
               gps_string[gps_string_in][gps_char_index] = 0;

               gps_string_in++;

               // Increment and roll-over if needed.
               if ( gps_string_in >= GPS_MAX_STRINGS ) {
                  gps_string_in = 0;
               }

               // Think about it. If we've incremented to where our processor is currently working, hold.
               if (gps_string_in == gps_string_out) {
                  gps_hold_isr = 1;
               }

               // Reset to beginning of new string
               gps_state = 0;

            } else {
               // Otherwise, Save current char
               gps_string[gps_string_in][gps_char_index] = c;
            }

            // bump to the next char in the current buffer
            if ( gps_char_index < (GPS_STRING_SIZE - 1) ) {
               gps_char_index++;
            } else {
               gps_state = 0;
            }
            break;

      default:
reset_gps_state:   gps_state = 0;
               break;
   }
   output_high(LED1);
   return;

incr_gps_state:      gps_state++;

   output_high(LED1);
}

_________________
Dazed and confused? I don't think so. Just "plain lost" will do. :D
Display posts from previous:   
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion All times are GMT - 6 Hours
Goto page 1, 2  Next
Page 1 of 2

 
Jump to:  
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