cancel
Showing results for 
Search instead for 
Did you mean: 

Issue with USART RX buffer and char filter

Bogdan
Senior
Posted on September 12, 2014 at 07:21

Hello all, i started a little project with my discovery board, using stm32f407vg and a ublox neo 6m gps.

The comunication between the two is done via USART1. At every second the gps sends somewhere at 500bytes over serial, the message in ascii looks like this:

$GPRMC,173500,A,4562351,N,0250244,E,0.066,,100914,,,D*79\r\n$GPGGA,18000,4562881,N,0250231,E,1,10,0.90,2,M,8,M,,*65\r\n$GPVTG,,T,,M,0.048,N,0.089,K,D*2B\r\n$GP..... and so on

GPRMC,GPGGA,GPVTG are NMEA standard 183 message ids for gps data, and i want to extract only the GGA data

My rx routine looks like this

void USART1_IRQHandler(void)

{

static uint8_t cnt = 0; // counts number of characters

int i;

if(USART_GetITStatus(USART1, USART_IT_RXNE)) // check if the USART1 receive interrupt flag was set

{

char t = USART1->DR; // character from data reg is stored into t

if (t != '\r') // if not end of string

{

received_string[cnt] = t; // charstored in receive buffer

cnt++; //

}

else // if end detected

{

received_string[cnt++] = '\0'; // put terminator character in the end of buffer

cnt = 0; // reset character counter

USART_Puts(USART1, received_string); // send character to terminal

for (i = 0; i <= MAX_STRLEN+1; i++) // flush buffer

received_string[i] = '\0';

}

}

}

My main concern is to filter only the dat between $GPGGA and its \r\n, i have tryied instead of t !='\r' to put

if (t = 'GGA') .

How can i filter only that part of the data? My experience with serial is very poor, in fact this is my first project with serial dat

The init code for usart1 is this

#define GPIO_RS232_TX GPIO_Pin_6

#define GPIO_RS232_RX GPIO_Pin_7

#define baudrate 9600

NVIC_InitTypeDef NVIC_InitStruct;

void Usart_interrupt_enable(){ USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); // enable the USART1 receive interrupt

NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn; // USART1 interrupts

NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;// this sets the priority group of the USART1 interrupts

NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0; // this sets the subpriority inside the group

NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; // the USART1 interrupts are globally enabled

NVIC_Init(&NVIC_InitStruct); // the properties are passed to the NVIC_Init function which takes care of the low level stuff

}

void serial_init(){

USART_InitTypeDef USART_InitStruct;

GPIO_InitTypeDef GPIO_InitStruct; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); // enable the peripheral

RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); // Enable clock for USART1

GPIO_InitStruct.GPIO_Pin = GPIO_RS232_TX | GPIO_RS232_RX ;

GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;

GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStruct.GPIO_OType = GPIO_OType_OD;

GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;

GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_USART1); // Serial TX

GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_USART1); // Serial Rx

GPIO_Init(GPIOB, &GPIO_InitStruct);

USART_InitStruct.USART_BaudRate = baudrate; // the baudrate is set to the value we passed into this init function

USART_InitStruct.USART_WordLength = USART_WordLength_8b;// we want the data frame size to be 8 bits (standard)

USART_InitStruct.USART_StopBits = USART_StopBits_1; // we want 1 stop bit (standard)

USART_InitStruct.USART_Parity = USART_Parity_No; // we don't want a parity bit (standard)

USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // we don't want flow control (standard)

USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; // we want to enable the transmitter and the receiver

USART_Init(USART1, &USART_InitStruct); // again all the properties are passed to the USART_Init function which takes care of all the bit setting

Usart_interrupt_enable();

USART_Cmd(USART1, ENABLE);

}

#nmea #gps #state-machine #fsm

7 REPLIES 7
frankmeyer9
Associate II
Posted on September 12, 2014 at 09:13

I did it this way, in the USART interrupt handler routine:

First, I catch all full sentences, the differentiation between GGA, RMC, etc. is done elsewhere.

Reserve a buffer large enough to hold the longest possible sentence.

Upon receiving a '$' character, reset the buffer pointer to zero (sentence start).

When receiving a '\n' character, a sentence is done. Copy it immediately to another buffer, for evaluation outside of the interrupt handler. I set a flag here to signal the main loop that a new sentence is ready. Trying to dissect the sentence in the receive buffer is not a good idea, since the GPS receiver is soon starting to overwrite it.

And for robustness, check the receive buffer index for overflow on each character.

When evaluating the sentence in the main loop, check for the proper start (''$GPGGA'') and termination ('\r','\n'). Testing the checksum is recommended, too.

By the way, most GPS receivers have commands to enable/disable individual sentences, set their update rate, and change serial settings. Disabling all undesired sentences reduces the processing load.

Andrew Neil
Evangelist III
Posted on September 12, 2014 at 09:54

Here's an article on using a State Machine (aka ''Finite State Machine''; ''FSM'') to parse NMEA:

http://www.visualgps.net/WhitePapers/NMEAParser/NMEAParser.html

Bogdan
Senior
Posted on September 28, 2014 at 23:18

Hello all, i am back with some god news and some bad news as well.

I managed to somehow filter the NMEA GGA sentence, but sometimes it runs out of sync... i dont know how to explain this very well. I will begin with my code

#define MAX 512 
int ready=0;
char rxbuff[MAX];
char *st1;
char *st2;
char gps[100]='''';
uint16_t l1,l2,ll;
void USART1_IRQHandler(void) {
static uint16_t cnt = 0; 
if(USART_GetITStatus(USART1, USART_IT_RXNE)) { // check if the USART1 receive interrupt flag was set
char t = USART1->DR;
if (cnt < 
MAX
) { 
rxbuff[cnt] = t; // Read data and store it to buffer
cnt++; // Increment string index
} else { // if max lenghts is reached
cnt
= 
0
; // reset counter
ready
= 
1
; // Ready for parsing GPS data
GPIOD->BSRRH = GPIO_Pin_13; // just some LED blinking state
}}} 
int main(void){
SystemInit();
int i=0;
while(1){
if(ready==1){ // if flag is set from interrupt, proces buffer data
ready=0; // reset the flag for another buffer fill
GPIOD->BSRRL = GPIO_Pin_13; // just some LED blink
st1=strstr(rxbuff,''GGA'' ); //returns GGAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx til the end of buffer
st2=strchr(st1 , '$'); // finds first ocourence of $ in st1 ( ex: GGAxxxxxxxxxxxx$ 
l1=strlen(st1); // get st1 lenght 
l2=strlen(st2); // get st2 lenght
ll=l1-l2; // calculatest actual lenght of intrest
for(i=1; i<ll; i++) {
gps[0]=st1[0]; 
gps[i] = st1[i];
if(i==ll){
gps[ll]='\0';
}}
USART_Puts(USART1, gps); // send data to the new charr buffer
} }}

sometimes if i move the GPS to somewhere i have no signal instead of reading a full GGA,21100,4562676,N,0250226,E,1,11,1.00,7,M,8,M,,*59CrLf i get GGA,211400,,,,,0,00,99,,,,,,*6A E,1,05,4.43,3,M,8,M,,*51 instead of only GGA,211600,,,,,0,00,99,,,,,,*60 What i want to say is that i dont have always starting from gps[0] GGA and the last CrLf I get wrong chars on diferent rows How can i make a stable reading undependent of the strings checksum ? The strange thing is, that if i reset hardware the MCU, the data is fine aligned, Why?
Posted on September 29, 2014 at 04:16

The original post was too long to process during our migration. Please click on the provided URL to read the original post. https://st--c.eu10.content.force.com/sfc/dist/version/download/?oid=00Db0000000YtG6&ids=0680X000006I6YU&d=%2Fa%2F0X0000000bqd%2FHts2587KLMQHYYngixMriXOrelzeVIQfR8_qgR6bZMY&asPdf=false
Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
Bogdan
Senior
Posted on September 29, 2014 at 18:11

Thanks you Clive for that example. So the main idea is to do a memory copy from the dynamic serial rx  buffer to an outside buffer and then proces it.. ok

I will come back latter with the results

Posted on September 29, 2014 at 18:28

Well the purpose is two fold, a) the content of the buffer isn't transient and is NUL terminated, and b) it's processed when it's complete. It can resync quickly based on the nature of the message. It also separates the more complicated parsing, from the quick acquisition of characters in the IRQ Handler.

Another solution could be to ping-pong between two input buffers, or more, so you fill one whilst processing the other.

Where/if appropriate sscanf() type functionality could be handled with atoi(), or atod() type processing.
Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..

Posted on September 29, 2014 at 19:28

I pulled the other stuff from one of my data sheet/app notes I wrote because people were always fouling up the parsing, especially the NMEA latitude/longitude representation which is designed to be easily passed to a display, not processed directly as a numeric value.

Here's what I have for $GPGGA that I cut from another example/processing app of mine.

  else  if (((strcmp(field[0],"$GPGGA") == 0) ||
             (strcmp(field[0],"$GNGGA") == 0)) && (f > 14))
  {
    double lat, lon, alt, msl;
    int lat_deg, lon_deg;
    double lat_min, lon_min;
    double fix_time;
    int fix_hour, fix_minute;
    double fix_second;
    char lat_hemi, lon_hemi;
    int valid;
    double hdop;
    int sv;
 
    // Field  1 UTC Time HHMMSS.SSS
    // Field  2 Latitude
    // Field  3 Lat Hemi
    // Field  4 Longitude
    // Field  5 Lon Hemi
    // Field  6 Position Fix Indicator - 0=Fix Not available, 1=GPS SPS, 2=GPS SPS DIFF, 3=GPS PPS,
    //              4=RTK, 5=Float RTK, 6=Estimate/Dead Reckoning, 7=Manual, 8=Simulator
    // Field  7 Satellites Used
    // Field  8 HDOP
    // Field  9 MSL Altitude
    // Field 10 Units M
    // Field 11 Geoid Separation
    // Field 12 Units M
    // Field 13 Age of Differential (NULL when not used)
    // Field 14 Differential Reference Station ID
 
    sscanf(field[2],"%lf",&lat);
 
    lat_hemi = field[3][0];
 
    sscanf(field[4],"%lf",&lon);
 
    lon_hemi = field[5][0];
 
    sscanf(field[9],"%lf",&msl);
 
    sscanf(field[11],"%lf",&alt);
 
    sscanf(field[1],"%lf",&fix_time);
 
    if (sscanf(field[6],"%d",&valid) != 1)// 0=Fix Not available, 1=GPS SPS, 2=GPS SPS DIFF
      valid = 0;
 
    sscanf(field[7],"%d",&sv);    // Satellites used (or in view, depends on receiver)
 
    sscanf(field[8],"%lf",&hdop); // Horizontal Dilution of precision (HDOP)
 
    if (valid != 0)
    {
      lat_deg = (int)lat / 100;
 
      lat_min = lat - (lat_deg * 100);
 
      lat = (double)lat_deg + (lat_min / 60.0);
 
      if (lat_hemi == 'S')
        lat = -lat;
 
 
      lon_deg = (int)lon / 100;
 
      lon_min = lon - (lon_deg * 100);
 
      lon = (double)lon_deg + (lon_min / 60.0);
 
      if (lon_hemi == 'W')
        lon = -lon;
 
      alt += msl; // Convert to height above datum
 
      fix_minute = (int)fix_time / 100;
 
      fix_second = fix_time - (fix_minute * 100);
 
      fix_hour = fix_minute / 100;
 
      fix_minute = fix_minute % 100;
 
 
      printf("GPGGA\t%+14.10lf %+14.10lf %8.2lf @ %02d:%02d:%06.3lf\n",
          lat, lon, alt,
          fix_hour, fix_minute, fix_second);
    }
  }

Edit: Fixed dogs breakfast forum conversion managed to do with this

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..