|
|
View previous topic :: View next topic |
Author |
Message |
aldina
Joined: 09 Oct 2009 Posts: 26
|
NMEA GPS messages |
Posted: Wed Nov 04, 2020 3:10 am |
|
|
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: 19549
|
|
Posted: Wed Nov 04, 2020 3:43 am |
|
|
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
|
|
Posted: Thu Nov 05, 2020 12:03 pm |
|
|
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: 19549
|
|
Posted: Thu Nov 05, 2020 12:41 pm |
|
|
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
|
|
Posted: Thu Nov 05, 2020 11:44 pm |
|
|
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: 9245 Location: Greensville,Ontario
|
|
Posted: Fri Nov 06, 2020 5:15 am |
|
|
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
|
|
Posted: Mon Nov 16, 2020 11:44 am |
|
|
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: 9245 Location: Greensville,Ontario
|
|
Posted: Mon Nov 16, 2020 12:01 pm |
|
|
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: 480 Location: Montenegro
|
|
Posted: Mon Nov 16, 2020 1:23 pm |
|
|
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: 480 Location: Montenegro
|
|
Posted: Mon Nov 16, 2020 1:27 pm |
|
|
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: 480 Location: Montenegro
|
|
Posted: Tue Nov 17, 2020 10:37 am |
|
|
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
|
|
Posted: Tue Nov 17, 2020 10:43 am |
|
|
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: 480 Location: Montenegro
|
|
Posted: Tue Nov 17, 2020 11:28 am |
|
|
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
|
|
Posted: Tue Nov 17, 2020 2:42 pm |
|
|
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! _________________ 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
|
|
Posted: Tue Nov 17, 2020 3:47 pm |
|
|
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 |
|
|
|
|
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
|