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 ?