nRF52DK nRF52832 UART asynchronous receive

I am working through the Nordic Developer Academy and got stuck at lesson 4 (UART receive)

https://academy.nordicsemi.com/courses/nrf-connect-sdk-fundamentals/lessons/lesson-4-serial-communication-uart/topic/uart-driver/

In particular, I am confused about this part:

I have created a sample project and connected external CP2102 USB->Serial adapter to P0.06 and P0.08 pins.

The code that I use:

/*
 * Copyright (c) 2012-2014 Wind River Systems, Inc.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include "stdio.h"
#include "zephyr/drivers/uart.h"

const struct device *uart = DEVICE_DT_GET(DT_NODELABEL(uart0));

static uint8_t tx_buf[] =  {"nRF Connect SDK Fundamentals Course \n\r"};
static uint8_t rx_buf[10] = {0}; //A buffer to store incoming UART data 

const struct uart_config uart_cfg = {
		.baudrate = 115200,
		.parity = UART_CFG_PARITY_NONE,
		.stop_bits = UART_CFG_STOP_BITS_1,
		.data_bits = UART_CFG_DATA_BITS_8,
		.flow_ctrl = UART_CFG_FLOW_CTRL_NONE
	};

static void uart_cb(const struct device *dev, struct uart_event *evt, void *user_data)
{
	switch (evt->type) {
	
	case UART_TX_DONE:
		printf("transmission complete \n");
		break;

	case UART_TX_ABORTED:
		// do something
		break;
		
	case UART_RX_RDY:
		printf("rx rdy \n");
		if((evt->data.rx.len) != 0){
			printf("data received = %s \n", evt->data.rx.buf[evt->data.rx.offset]);
		}


		
		break;

	case UART_RX_BUF_REQUEST:
		printf("requesting buffer \n");
		// do something
		break;

	case UART_RX_BUF_RELEASED:
		printf("buffer released \n");
		// do something
		break;
		
	case UART_RX_DISABLED:
		printf("rx disabled \n");
		uart_rx_enable(dev, rx_buf, sizeof(rx_buf), 100);
		break;

	case UART_RX_STOPPED:
		// do something
		break;
		
	default:
		break;
	}
}


int main(void)
{	

	if (!device_is_ready(uart)) {
    	return;
	}

	int err;
	err = uart_callback_set(uart, uart_cb, NULL);
	if (err) {
		return err;
	}

	uart_rx_enable(uart, rx_buf, sizeof(rx_buf), 100);


	err = uart_tx(uart, tx_buf, sizeof(tx_buf), SYS_FOREVER_US);
	if (err) {
		return err;
	}



	while(1){
		k_msleep(1000);
	}
	return 0;
}

I am having issues understanding how to properly print out all the received data:

I tried to do:

	case UART_RX_RDY:
		printf("rx rdy \n");
		if((evt->data.rx.len) != 0){
			printf("data received = %s \n", evt->data.rx.buf[evt->data.rx.offset]);
		}

		break;

but when I send the data via UART (using Termite) , the following is printed:

I would appreciate if someone could clarify how to correctly print out all the received data.  Thanks in advance

Just for testing, I have also tried to print:

    case UART_RX_RDY:
        printf("rx rdy \n");
        printf("data: %s \n", rx_buf);
        break;
 
and the logs are as following: (captured using Termite):
 
As you can see, it receives the first message (ping) correctly, but after that, the buffer overflows (rx_buf is 10 bytes size) and then it cannot print out the data correctly)
  • I have looked at the sample project you have provided. I have removed all the TX related code since I am not interested in TX (I only want to learn about RX first). The latest  source code is available here:

    https://github.com/krupis/nrf52_learning

    I can confirm that it also does not work. Please look at the logs below:

    I have underlined response in green where the response is correct and in red where the response is not as expected.

    Could you please ask someone else from embedded software team from your side to look at this as this is getting a little frustrating?

    Thanks for keeping this going!

  • Hi,

    It looks like the repo you've provided is private as the link takes me to a 404 and the nrf52_learning is not listed in your repositories. Could you make it public/share a public version of it?

    Kind regards,
    Andreas

  • You were right. It was private, my bad!

    I have made it public now.

  • Thank you,

    I finally had the time to look at your code. Apologies for why it took so long and to the frustration you must've felt. Response times gets a bit longer when we're nearing release of a new major version of the SDK (v2.5.0).

    Your code works as expected when you're using asynch UART with static buffers (the central and peripheral_uart samples uses dynamically allocated buffers)

    Why, you ask? 
    This is the quick and dirty explanation: The uart-cb triggers either when the timeout you've set runs out (100ms) or when the buffer you've declared (16 bit) is filled. When you enter new messages and you the timeout expires before you're finished writing the message, you will be left with a message that is separated. In theory if the timeout is sufficiently small and you match your writing speed with the timeout, the callback can trigger 16 times and separate the message into 16 characters instead of 1.

    Hello (6) + ping (5) + message1 (expected to be 9)  > 16: You see the callback trigger when both the timeout expires and the buffer length limit is reached

    For further observation
    Increase the timeout so you have time to write the new messages, and or play around with the buffer length (which is currently set to 16)

    Please feel free to ask follow up questions if anything is unclear! 

    Kind regards,
    Andreas

  • Thanks for your help. It is getting a little bit more clear, but I do not think my issues are related to the timeout. I really appreciate you taking your time to look into this!

    However, I believe it is related to the way ring buffer works in Async UART.

    I have increased UART_BUF_SIZE to 50, see the logs below:

    What happens is that when I send messages via serial, the evt->data.rx.buf is slowly filling up. When it reaches the declared size (UART_BUF_SIZE), the condition:

    memcpy(new_message.bytes, evt->data.rx.buf + evt->data.rx.offset, evt->data.rx.len);
     
    will no longer be correct as half of the message can be at the end of the evt->data.rx.buf and the rest rolled over to the beginning of the buffer. Then pretty much all other messages after the ring buffer has been filled will be trashed (thats exactly what we see from the logs) . Do you see what I mean? 
    Zephyr states that Async UART is the most efficient way to use UART but I cannot see a single real world application where it could be used since it is not capable of receiving a full message properly (unless there is an issue in my code and below is not correct). 
    	case UART_RX_RDY:
    		memcpy(new_message.bytes, evt->data.rx.buf + evt->data.rx.offset, evt->data.rx.len);
    		new_message.length = evt->data.rx.len;
    		if (k_msgq_put(&uart_rx_msgq, &new_message, K_NO_WAIT) != 0)
    		{
    			printk("Error: Uart RX message queue full!\n");
    		}
    		break;
    Most UART applications relies on being able to receive and transmit a required size message length at once.
    I was under the impression that the reason why we use double buffer is to avoid this problem and ensure that when the data rolled over, we can still get the full message at once but perhaps that is not the case.
    Please tell me if that makes sense or I am completely misunderstanding how this works or how this supposed to work.
Related