Algorithm of work with GNSS via UART

Good Day.

I am using a custom board based on an nRF52840 microcontroller and a RYS8830 GNSS receiver connected to it via UART0. 

SDK v2.5.99 (Toolchain v2.5.0).

I want to get the coordinates of the board location (via asynchronous UART0) and output them to the terminal (via RTT and SWDIO programming connector ).

Could you please tell me how to correctly process incoming data from GNSS (NMEA message packet that arrives once per second) via UART? 

1. What is the correct action I should take in the UART callback function (uart_cb) when an NMEA data packet arrives (UART_RX_RDY)? 

2. In which part of the program should actions be performed to process incoming data? In the callback function (UART_RX_RDY), in the main function (main) or in an infinite loop (while(1))? 

I apologize for such basic questions, it's just that I'm new to programming.

I hope for your understanding and help.

Thanks.

Parents
  • Hi,

    You should not process the data in an interrupt (like a UART callback), so I would suggest that you use a worqeueue for that. See Workqueue Threads. You can refer to the Workqueue creation and work item submission Exercise for an eaxmple of using a workqueue.

  • Thank you, Einar, for your reply.
    Can you please tell me how to properly clear the receive buffer (rx_buf) of the UART?

    My plan is as follows, I receive data (NMEA messages) from the GNSS receiver and write it to rx_buf (buffer size 83 bytes) until the newline character '\n' arrives.

    /* Define the callback function for UART0 */
    static void uart_cb(const struct device *dev, struct uart_event *evt, void *user_data)
    {
    	switch (evt->type) {
    
    	case UART_RX_RDY:
    		if(evt->data.rx.buf[evt->data.rx.offset] == '\n'){
    		    memcpy(rx_buf_cpy, rx_buf, sizeof(rx_buf));
    			k_work_submit_to_queue(&gnss_work_q, &gnss_work);
    			memset(rx_buf, 0, sizeof(rx_buf));
    		}
    		break;
    
    	case UART_RX_DISABLED:
    		uart_rx_enable(dev, rx_buf, sizeof rx_buf, RECEIVE_TIMEOUT);
    		break;
    
    	default:
    		break;
    	}
    }

    When a newline character is detected, I copy the contents of rx_buf to rx_buf_cpy using the memcpy function and send the data to the queue.

    static void gnss_work_cb (struct k_work *work){
    
       // Parsing rx_buf_cpy array
    }

    I then clear the receive buffer rx_buf in the UART callback function using the memset function. 

    But apparently my approach doesn't work, because after the first clearing of rx_buf no further writes are made to the buffer and the buffer remains empty.

    Please tell me how to do it correctly in my case. I will be grateful for any help.

  • Hi,

    Logs are dropped, so you are logging faster than the logs can be processed output. If the logging is in bursts, you can solve this issue by increasing the log buffers like this (adjust the values as needed):

    CONFIG_LOG_BUFFER_SIZE=15360
    CONFIG_SEGGER_RTT_BUFFER_SIZE_UP=15360

    You can also consider adding CONFIG_LOG_MODE_IMMEDIATE=y to process log, but this will impact the timing/performance more though, as logs are processed in place wherever you do any logging. (If you do this th einternal log buffer is not used, so there would be no point in increasing CONFIG_LOG_BUFFER_SIZE.

  • Hi. At the moment I have the following problem. 

    I want to receive one symbol at a time from GNSS (rx_buf size = 1) and pass it immediately to the queue for further processing. 

    static void uart_cb(const struct device *dev, struct uart_event *evt, void *user_data)
    {
    	switch (evt->type) {
    
    	case UART_RX_RDY:
    		LOG_DBG("rx_buf[0] = %c \n", rx_buf[0]);
    			k_work_submit_to_queue(&gnss_work_q, &gnss_work);
    		break;
    
    	case UART_RX_DISABLED:
    		uart_rx_enable(dev, rx_buf, sizeof(rx_buf), RECEIVE_TIMEOUT);
    		break;
    
    	default:
    		break;
    	}
    }

    In the thread, I write the received characters to the "sentence" array until the end of line character is received.
    Then the parser decodes the sentence array.

    int i = 0;
    
    static void gnss_work_cb (struct k_work *work){
    
    if (rx_buf[0] != '\n')
    {
    	sentence[i] = rx_buf[0];
    	LOG_DBG("sentence[%d] = %c \n", i, sentence[i]);
    	i++;
    }
      else
     {
    	LOG_DBG("%s \r\n", sentence);
    	struct minmea_sentence_rmc frame;
    	if(minmea_parse_rmc(&frame, sentence)){
                        LOG_DBG(INDENT_SPACES "$xxRMC floating point degree coordinates and speed: (%f,%f) %f\n",
                                minmea_tocoord(&frame.latitude),
                                minmea_tocoord(&frame.longitude),
                                minmea_tofloat(&frame.speed));
                    }
                    else {
                        LOG_DBG(INDENT_SPACES "$xxRMC sentence is not parsed\n");
                    }
                    
    	memset(sentence, 0, sizeof(sentence));
    	LOG_DBG("i = %d \r\n", i);
    	i = 0;
    }

    When I connect GNSS and test the program I get the following results as on the screenshot

    It seems that there is not enough CPU time for the workflow.

    How can this be fixed?   

    If I disable GNSS and manually enter the NMEA string (simulating sending a message from GNSS), everything works correctly. 

    Can you please tell me where I'm making a mistake?
    Thank you.

  • Any idea how to implement this, assuming that the interrupt from the UART consumes almost all of the CPU time because the data from GNSS is coming almost continuously? 

  • Hi,

    In the original post you wrote that one character is recevied every second. If that is the case, then you should have ample time to receive and process one character at a time as you describe. Why did you conclude that CPU is a limiting factor here? Do you do a lot of other work every time a character is received?

    If it is so that this casue a lot of CPU activity, then I would start to figure out what parts of the handling contribute to the CPU usage, as that is not obvious to me.

  • Hi, Einar.

    When I start GNSS I get a data packet like the one below once per second. And as time passes, the amount of data (number of characters) increases. 

    $GNRMC,,V,,,,,,,,,,N*4D
    $GNVTG,,,,,,,,,N*2E
    $GNGGA,,,,,,0,00,99.99,,,,,,*56
    $GNGSA,A,1,,,,,,,,,,,,,99.99,99.99,99.99*2E
    $GNGSA,A,1,,,,,,,,,,,,,99.99,99.99,99.99*2E
    $GPGSV,1,1,00*79
    $GLGSV,1,1,00*65
    $GNGLL,,,,,,V,N*7A

    The result is that the interrupt from the UART is triggered as many times as there are characters in the packet, which is several hundred times per second. The time between UART interrupts is not enough time for gnss_work_cb to execute. This can be seen on the screenshot (underlined with a red line), the thread does not have time to print the copied character in the sentence array.

    I don't have any other tasks in my program at the moment. I only need to parsing the string $GNRMC, but so far I can't do it.

    What can you advise me based on your experience?

    Thank you.

Reply
  • Hi, Einar.

    When I start GNSS I get a data packet like the one below once per second. And as time passes, the amount of data (number of characters) increases. 

    $GNRMC,,V,,,,,,,,,,N*4D
    $GNVTG,,,,,,,,,N*2E
    $GNGGA,,,,,,0,00,99.99,,,,,,*56
    $GNGSA,A,1,,,,,,,,,,,,,99.99,99.99,99.99*2E
    $GNGSA,A,1,,,,,,,,,,,,,99.99,99.99,99.99*2E
    $GPGSV,1,1,00*79
    $GLGSV,1,1,00*65
    $GNGLL,,,,,,V,N*7A

    The result is that the interrupt from the UART is triggered as many times as there are characters in the packet, which is several hundred times per second. The time between UART interrupts is not enough time for gnss_work_cb to execute. This can be seen on the screenshot (underlined with a red line), the thread does not have time to print the copied character in the sentence array.

    I don't have any other tasks in my program at the moment. I only need to parsing the string $GNRMC, but so far I can't do it.

    What can you advise me based on your experience?

    Thank you.

Children
Related