nRF52840 UART TX in Interrupt mode

Hello. I have been further exploring nRF52 UART and got a bit stuck when trying to transmit some data using interrupt mode. I have been reading the Zephyr documentation about the UART Interrupt mode: 
developer.nordicsemi.com/.../uart.html

and exploring the devzone forum for some relevant information. The only relevant post I have managed to find is the following:

devzone.nordicsemi.com/.../issue-using-interrupt-driven-uart-callback-in-nrfconnect-to-transmit-data

I believe it put me on the right track but I still have some questions. I am working on nRF52840DK nrf52840 board.

Full source code:

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

#include <zephyr/kernel.h>
#include "stdio.h"
#include "zephyr/drivers/uart.h"
#define LOG_LEVEL 4
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(nrf52_learning);

static const struct device *dev_uart1;

static void app_uart1_init();
static void uart1_irq_receive_handler(const struct device *dev, void *context);
static void uart1_irq_transmit_handler(const struct device *dev, void *context);

static K_SEM_DEFINE(tx_sem, 0, 1); // Semaphore to signal UART data transmitted

static uint8_t *tx_data;
static size_t tx_data_length;

#define RX_BUF_SIZE 64

// Initialize UART1
static void app_uart1_init()
{
	dev_uart1 = DEVICE_DT_GET(DT_NODELABEL(uart1));
	if (!device_is_ready(dev_uart1))
	{
		printk("UART1 is not ready \n");
		return;
	}
	uart_irq_callback_set(dev_uart1, uart1_irq_receive_handler);
	uart_irq_rx_enable(dev_uart1);
}

static void uart1_irq_receive_handler(const struct device *dev, void *context)
{
	uint8_t char_received;
	static uint8_t command_line[RX_BUF_SIZE];
	static int char_counter = 0;

	if (uart_irq_rx_ready(dev))
	{
		int len = uart_fifo_read(dev, &char_received, 1);
		if (len)
		{
			command_line[char_counter] = char_received;
			char_counter++;
			if (char_received == '\n' || char_received == '\r')
			{
				command_line[char_counter] = '\0'; // put null at the end of the string to indicate end of message or end of string
				char_counter = 0;				   // reset the char counter
				printk("UART1 Received = %s \n", command_line);
				memset(&command_line, 0, sizeof(command_line));
			}
		}
	}
}

static void uart1_irq_transmit_handler(const struct device *dev, void *user_data)
{
	ARG_UNUSED(user_data);
	static int tx_data_idx;
	if (!uart_irq_update(dev))
	{
		LOG_ERR("Couldn't update IRQ\n");
		return;
	}
	// METHOD2
	if (uart_irq_tx_ready(dev) && tx_data_idx < tx_data_length)
	{
		LOG_INF("Transmitting data over UART1\n");
		LOG_INF("tx_data_length = %u \n", tx_data_length);
		LOG_INF("tx_data_idx = %u \n", tx_data_idx);
		int tx_send = MIN(CONFIG_UART_1_NRF_TX_BUFFER_SIZE, tx_data_length - tx_data_idx);
		int tx_sent = uart_fifo_fill(dev, (uint8_t *)&tx_data[tx_data_idx], tx_send);
		if (tx_sent <= 0)
		{
			LOG_ERR("Error %d sending data over UART1 bus\n", tx_sent);
			return;
		}

		tx_data_idx += tx_sent;
		if (tx_data_idx == tx_data_length)
		{
			uart_irq_tx_disable(dev);
			tx_data_idx = 0;
			k_sem_give(&tx_sem);
		}
	}
}

int uart_irq_tx(const struct device *dev, const uint8_t *buf, size_t len)
{
	tx_data = buf;
	tx_data_length = len;
	uart_irq_callback_set(dev, uart1_irq_transmit_handler);
	uart_irq_tx_enable(dev);
	k_sem_take(&tx_sem, K_FOREVER);
	return len;
}

int main(void)
{
	app_uart1_init();
	while (1)
	{
		k_msleep(1000);
		uart_irq_tx(dev_uart1, "Hello from UART1\n", 17);
	}
}

prj.conf:

# nothing here
CONFIG_GPIO=y
CONFIG_SERIAL=y

CONFIG_UART_INTERRUPT_DRIVEN=y
CONFIG_NRFX_UARTE1=y
CONFIG_UART_1_INTERRUPT_DRIVEN=y

CONFIG_LOG=y
CONFIG_LOG_MODE_IMMEDIATE=y


And in my nrf52840dk_nrf52840.overlay I have uart1 status changed to "okay": 

arduino_serial: &uart1 {
	status = "okay";
};

My questions:

Question1:

In my uart1_irq_transmit_handler I have the following logic:

static void uart1_irq_transmit_handler(const struct device *dev, void *user_data)
{
	ARG_UNUSED(user_data);
	static int tx_data_idx;
	if (!uart_irq_update(dev))
	{
		LOG_ERR("Couldn't update IRQ\n");
		return;
	}
	// METHOD2
	if (uart_irq_tx_ready(dev) && tx_data_idx < tx_data_length)
	{
		LOG_INF("Transmitting data over UART1\n");
		LOG_INF("tx_data_length = %u \n", tx_data_length);
		LOG_INF("tx_data_idx = %u \n", tx_data_idx);
		int tx_send = MIN(CONFIG_UART_1_NRF_TX_BUFFER_SIZE, tx_data_length - tx_data_idx);
		int tx_sent = uart_fifo_fill(dev, (uint8_t *)&tx_data[tx_data_idx], tx_send);
		if (tx_sent <= 0)
		{
			LOG_ERR("Error %d sending data over UART1 bus\n", tx_sent);
			return;
		}

		tx_data_idx += tx_sent;
		if (tx_data_idx == tx_data_length)
		{
			uart_irq_tx_disable(dev);
			tx_data_idx = 0;
			k_sem_give(&tx_sem);
		}
	}
}

This code has been copied from the other relevant post I have found on the devzone forum. I am not fully understanding what is the purpose of tx_data_idx and tx_send? It seems to be quite complex and unnecessary. Additionally, I do not understand why check for transmission complete using the following condition:

		tx_data_idx += tx_sent;
		if (tx_data_idx == tx_data_length){
		...
		...
		...

Under which conditions tx_data_idx would not be equal to tx_data_length?

I have tried to simplify this function and came up with something like that:

static void uart1_irq_transmit_handler(const struct device *dev, void *user_data)
{
	ARG_UNUSED(user_data);
	static int tx_data_idx;
	if (!uart_irq_update(dev))
	{
		LOG_ERR("Couldn't update IRQ\n");
		return;
	}

	// METHOD1

	if (uart_irq_tx_ready(dev))
	{
		int tx_sent = uart_fifo_fill(dev, (uint8_t *)tx_data, tx_data_length);

		if (tx_sent <= 0)
		{
			LOG_ERR("Error %d sending data over UART1 bus\n", tx_sent);
			return;
		}

		while (uart_irq_tx_complete(dev) != 1)
		{
			LOG_INF("Wait for UART1 data transmition complete\n");
		}
		LOG_INF("UART1 data transmitted\n");
		uart_irq_tx_disable(dev);
		k_sem_give(&tx_sem);
	}
}

As you can see from the function above, I am not using tx_data_idx or tx_send, and I am detecting whether the transition is complete using the following condition:

		while (uart_irq_tx_complete(dev) != 1)
		{
			LOG_INF("Wait for UART1 data transmition complete\n");
		}
		LOG_INF("UART1 data transmitted\n");
		uart_irq_tx_disable(dev);
		k_sem_give(&tx_sem);

Although I am not sure if that makes sense. If there is a problem with the transmission, it will just get stuck in infinite while loop so that is probably not the most appropriate method to detect end of transmission.

 

Anyways, I have flashed the board (I have tried both methods mentioned above) and both seem to work fine from the first glance. I have connected Saleae logic analyzer to monitor RX/TX pins and I can see that there is data being sent every 1 second which is what I expected to see:

Question2:

Apart from the uart1_irq_transmit_handler, I also have the receive handler uart1_irq_receive_handler which I was hoping to use for data reception. So one handler is used for data transmission and the other one for the data reception.

However, that does not seem to work. In order to try data reception, I have simply shorted RX/TX pins, so whatever is being sent will be received. After shorting those 2 pins, I can see on the Logic analyzer that the data has been sent/received once:

But after that, it feels like UART1 transmit no longer works. It does not even transmit any data even when I disconnect RX/TX pins from each other. In order to fix this, I need to reboot the device and then the transmission works again until I short it. What could be wrong ?

 

Parents
  • Hi Lukas,

    Vidar does not remember specifics of the case that you linked in your initial question. And I do not know the context of those threads. I cannot answer specifically why those code snippets have that logic when it comes from outside Nordic solutions. I was hoping that Vidar who was looking at the other thread have some context but he does not.

    Where did you get this code snippet from? Can't you ask these questions in that same post as this is relevant to those threads?

  • Hi Lukas,

    I tried your code snippet with some changes.  I also do not know what is the use of tx_data_idx.The code does not look very optimal. This does not seem to be production quality. Unfortunately we do not seem to have any dedicated sample that is using the peripheral in interrupt mode apart from what is being used with other features like ble_hci_uart.Most of them are using UART0 as the interrupt driven port and they seems to be working. I have not tested changing them to use uart1 port instead of uart0.

    There is some issue in the code and shorting the TX/RX pin like you said break the flow like you mentioned. 

    I need to find more time to test this as the code you mentioned is not official from us (hence I cannot create any internal ticket to allocate time immediately)

  • Thanks for looking at this.

    I would suggest you try this project. This is the project that I work on and thats where I posted the code snippets from
    https://github.com/krupis/nrf52_learning/tree/uart0_uart1_interrupt

    You can easily flash this to nRF52540DK to check UART0 and UART1 yourself and it will become clear to you what is the problem that I am trying to solve.

    What I want to achieve is very simple:

    I want to get the UART1 and UART0 working properly (receive and transmit) in interrupt mode. 

    UART0 I use for logging and sending custom commands the device

    UART1 I plan to connect to an external device and I need to be able to receive/transmit various length messages but as I shown above currently I cannot get the transmit/receive to work properly on UART1 :)

    For some reason, I am convinced that the issue lies within incorrect implementation of uart1_irq_transmit_handler becuase I could not find any proper sample projects where it has been shown how to correctly transmit data over the UART in interrupt mode.

Reply
  • Thanks for looking at this.

    I would suggest you try this project. This is the project that I work on and thats where I posted the code snippets from
    https://github.com/krupis/nrf52_learning/tree/uart0_uart1_interrupt

    You can easily flash this to nRF52540DK to check UART0 and UART1 yourself and it will become clear to you what is the problem that I am trying to solve.

    What I want to achieve is very simple:

    I want to get the UART1 and UART0 working properly (receive and transmit) in interrupt mode. 

    UART0 I use for logging and sending custom commands the device

    UART1 I plan to connect to an external device and I need to be able to receive/transmit various length messages but as I shown above currently I cannot get the transmit/receive to work properly on UART1 :)

    For some reason, I am convinced that the issue lies within incorrect implementation of uart1_irq_transmit_handler becuase I could not find any proper sample projects where it has been shown how to correctly transmit data over the UART in interrupt mode.

Children
No Data
Related