Using USB instead of UART0 for debugging/communciation on the nRF52840DK

Hello :) The default setting for the console on the nRF52840 is over UART0. I can easily print logging messages to the console and I have also implemented a function to parse incoming serial messages. See the code below:

/*
 * 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 "my_gpio.h"
#include "zephyr/drivers/uart.h"
#include <zephyr/sys/ring_buffer.h>

#define LOG_LEVEL 4
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(nrf52_learning);

#define UART_BUF_SIZE 24

#define UART_RX_TIMEOUT_MS 1000
K_SEM_DEFINE(rx_disabled, 0, 1);

// UART RX primary buffers
uint8_t uart_double_buffer[2][UART_BUF_SIZE];

uint8_t *uart_buf_next = uart_double_buffer[1];

uint8_t complete_message[UART_BUF_SIZE];
uint8_t complete_message_counter = 0;
bool currently_active_buffer = 1; // 0 - uart_double_buffer[0] is active, 1 - uart_double_buffer[1] is active

static const struct device *dev_uart;

static void uart_cb(const struct device *dev, struct uart_event *evt, void *user_data)
{

	switch (evt->type)
	{

	case UART_TX_DONE:
		break;

	case UART_TX_ABORTED:
		// do something
		break;

	case UART_RX_RDY:

		LOG_INF("Received %i bytes \n", evt->data.rx.len);
		LOG_INF("Offset = %i  \n", evt->data.rx.offset);

		if (currently_active_buffer == 0)
		{
			// read all characters one by one till new line is found
			for (int i = 0 + evt->data.rx.offset; i < UART_BUF_SIZE; i++)
			{
				complete_message[complete_message_counter] = uart_double_buffer[0][i];
				complete_message_counter++;
				if (uart_double_buffer[0][i] == '\n')
				{
					complete_message_counter = 0;
					LOG_INF("complete_message = %s \n", complete_message);
					memset(&complete_message, 0, sizeof(complete_message)); // clear out the buffer to prepare for next read.
					break;
				}
			}
		}

		if (currently_active_buffer == 1)
		{
			// read all characters one by one till new line is found
			for (int i = 0 + evt->data.rx.offset; i < UART_BUF_SIZE; i++)
			{
				complete_message[complete_message_counter] = uart_double_buffer[1][i];
				complete_message_counter++;
				if (uart_double_buffer[1][i] == '\n')
				{
					complete_message_counter = 0;
					LOG_INF("complete_message = %s \n", complete_message);
					memset(&complete_message, 0, sizeof(complete_message)); // clear out the buffer to prepare for next read.
					break;
				}
			}
		}

		break;

	case UART_RX_BUF_REQUEST:
		uart_rx_buf_rsp(dev_uart, uart_buf_next, UART_BUF_SIZE);
		currently_active_buffer = !currently_active_buffer;
		break;

	case UART_RX_BUF_RELEASED:
		uart_buf_next = evt->data.rx_buf.buf;
		break;

	case UART_RX_DISABLED:
		k_sem_give(&rx_disabled);
		break;

	case UART_RX_STOPPED:
		// do something
		break;

	default:
		break;
	}
}

void app_uart_init()
{
	dev_uart = DEVICE_DT_GET(DT_NODELABEL(uart0));

	if (!device_is_ready(dev_uart))
	{
		return 0;
	}

	int err;
	err = uart_callback_set(dev_uart, uart_cb, NULL);
	if (err)
	{
		return err;
	}
	uart_rx_enable(dev_uart, uart_double_buffer[0], UART_BUF_SIZE, UART_RX_TIMEOUT_MS);
}

int main(void)
{
	app_uart_init();
}

I want to be able to send any messages via the serial terminal to the device and I want the device to be able to parse incomming messages and take any required action. For example. I may want so end a serial command "Turn on LED1" and the device can parse the message and turn on the LED1...

For my application, I want to reconfigure UART0 to communicate with external device instead of logging and console and I want to use USB for this instead.

So the plan is:

UART0 - Communication with external device 1

UART1 - Communication with external device 2

USB - Logging and parsing commands

I have looked at the Console over USB example (zephyr/samples/subsys/usb/console) to check how to setup the console over the USB and I came up with the project below:

https://github.com/krupis/nrf52_learning/tree/USB_debug

The full code:

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

#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/sys/poweroff.h>
#include "stdio.h"
#include <zephyr/usb/usb_device.h>
#include <zephyr/usb/usbd.h>
#include <zephyr/drivers/uart.h>

BUILD_ASSERT(DT_NODE_HAS_COMPAT(DT_CHOSEN(zephyr_console), zephyr_cdc_acm_uart),
			 "Console device is not ACM CDC UART device");

#define LOG_LEVEL 4
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(nrf52_learning);

int main(void)
{
	if (usb_enable(NULL))
	{
		return 0;
	}

	while (1)
	{
		LOG_INF("Hello");
		k_sleep(K_MSEC(1000));
	}
}

nrf52840dk_nrf52840.overlay:

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




/ {
	chosen {
		zephyr,console = &cdc_acm_uart0;
	};
};

&zephyr_udc0 {
	cdc_acm_uart0: cdc_acm_uart0 {
		compatible = "zephyr,cdc-acm-uart";
		label = "CDC_ACM_0";
	};
};



prj.conf:


CONFIG_LOG=y
CONFIG_LOG_MODE_IMMEDIATE=y


CONFIG_USB_DEVICE_STACK=y
CONFIG_USB_DEVICE_PRODUCT="Zephyr test"
CONFIG_USB_DEVICE_PID=0x0004
CONFIG_USB_DEVICE_MANUFACTURER="Nordic Semiconductor"
CONFIG_USB_DEVICE_VID=0x1915
CONFIG_USB_DEVICE_INITIALIZE_AT_BOOT=n






CONFIG_SERIAL=y
CONFIG_CONSOLE=y
CONFIG_UART_CONSOLE=y






The result:

I can see that the logging via the USB is working as expected. If I connect the USB to the nRF USB port instead of the nRF debug USB, I can see that the logs are coming through:

My questions:

Question 1:

I have read through the USB documentation (https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.2-dev1/zephyr/reference/usb/uds.html) but it is still not fully clear to me how can I implement serial command parsing via the USB. I want to be able to send the commands via the serial terminal and I expect some callback to be triggered that receives the data (Simillarly to how the UART_RX_RDY event is triggered when the data is received using UART ASYNC method) and I can then parse the data accordingly. Could someone point me in the right direction on how to get this implemented as I could not find any relevant example projects to get started with.

Question 2:

I would like to understand how can I change the COM port name that my device appears as. Currently it appears as a USB Serial Device (COM41).  Can I change this to any name I want? 

Question 3:

What is the purpose of 

CONFIG_USB_DEVICE_PRODUCT="Zephyr test"
and 
CONFIG_USB_DEVICE_MANUFACTURER="Nordic Semiconductor"

As they do not seem to show up anywhere? 
Parents
  • Hello,

    Question 1:

    You can look at the USB shell sample located at zephyr\samples\subsys\usb\shell.

    I would like to understand how can I change the COM port name that my device appears as. Currently it appears as a USB Serial Device (COM41).  Can I change this to any name I want? 

    Try to set CONFIG_USB_DEVICE_PRODUCT="My COM PORT" in the Zephyr CDC ACM sample.

  • I have been further experimenting with the USB instead of UART0 and I have found that changing the uart0_irq_handler to the following seems to work:

    static void uart0_irq_handler(const struct device *dev, void *context)
    {
    	uint8_t char_received;
    	static uint8_t command_line[RX_BUF_SIZE];
    	static int char_counter = 0;
    	while (uart_irq_update(dev) && uart_irq_is_pending(dev))
    	{
    		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("command_line = %s \n", command_line);
    					memset(&command_line, 0, sizeof(command_line));
    				}
    			}
    		}
    	}
    }



    The full source code:
    /*
     * Copyright (c) 2012-2014 Wind River Systems, Inc.
     *
     * SPDX-License-Identifier: Apache-2.0
     */
    
    #include <zephyr/kernel.h>
    #include <zephyr/drivers/gpio.h>
    #include <zephyr/sys/poweroff.h>
    #include "stdio.h"
    #include <zephyr/usb/usb_device.h>
    #include <zephyr/usb/usbd.h>
    #include <zephyr/drivers/uart.h>
    
    #include <zephyr/sys/ring_buffer.h>
    #define RING_BUF_SIZE 1024
    uint8_t ring_buffer[RING_BUF_SIZE];
    
    struct ring_buf ringbuf;
    
    BUILD_ASSERT(DT_NODE_HAS_COMPAT(DT_CHOSEN(zephyr_console), zephyr_cdc_acm_uart),
    			 "Console device is not ACM CDC UART device");
    
    #define LOG_LEVEL 4
    #include <zephyr/logging/log.h>
    LOG_MODULE_REGISTER(nrf52_learning);
    
    static const struct device *usb_device;
    
    static void uart0_irq_handler(const struct device *dev, void *context);
    
    #define RX_BUF_SIZE 64
    
    static void uart0_irq_handler(const struct device *dev, void *context)
    {
    	uint8_t char_received;
    	static uint8_t command_line[RX_BUF_SIZE];
    	static int char_counter = 0;
    	while (uart_irq_update(dev) && uart_irq_is_pending(dev))
    	{
    		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("command_line = %s \n", command_line);
    					memset(&command_line, 0, sizeof(command_line));
    				}
    			}
    		}
    	}
    }
    
    int main(void)
    {
    	printk("Starting USB CDC ACM example\n");
    	if (usb_enable(NULL))
    	{
    		return 0;
    	}
    
    	usb_device = DEVICE_DT_GET_ONE(zephyr_cdc_acm_uart);
    	if (!device_is_ready(usb_device))
    	{
    		return 0;
    	}
    	uart_irq_callback_set(usb_device, uart0_irq_handler);
    	uart_irq_rx_enable(usb_device);
    	while (1)
    	{
    		// printf("Hello world \n");
    		//  printk("Printing using printk\n");
    
    		k_sleep(K_MSEC(1000));
    	}
    }
    

    .overlay:

    arduino_serial: &uart1 {
    	status = "okay";
    };
    
    
    
    
    / {
    	chosen {
    		zephyr,console = &cdc_acm_uart0;
    	};
    };
    
    &zephyr_udc0 {
    	cdc_acm_uart0: cdc_acm_uart0 {
    		compatible = "zephyr,cdc-acm-uart";
    		label = "CDC_ACM_0";
    	};
    };
    
    
    
    

    prj.conf :

    
    CONFIG_LOG=y
    CONFIG_LOG_MODE_IMMEDIATE=y
    
    
    CONFIG_USB_DEVICE_STACK=y
    CONFIG_USB_DEVICE_PRODUCT="Zephyr test"
    CONFIG_USB_DEVICE_PID=0x0004
    CONFIG_USB_DEVICE_MANUFACTURER="Nordic Semiconductor"
    CONFIG_USB_DEVICE_VID=0x1915
    CONFIG_USB_DEVICE_INITIALIZE_AT_BOOT=n
    
    
    
    
    
    
    
    CONFIG_SERIAL=y
    CONFIG_CONSOLE=y
    CONFIG_UART_CONSOLE=y
    
    
    CONFIG_UART_LINE_CTRL=y
    CONFIG_UART_INTERRUPT_DRIVEN=y
    
    
    
    
    
    
    

    With the code above, I can see that the USB com port appears and I can send commands via the USB port :

    This is exactly what I needed :) 

  •   Hey. I have been further thinking about using USB and it is not fully clear to me. I have been reading about USB cdc acm trying to figure out how exactly it works over UART API. I would really appreciate if you could answer the following questions:

    1. The main reason why I am trying to implement communication/logging via USB cdc acm is to ensure that I have spare uart0 and uart1 for other things. By default, logging is done via uart0 and that is not good for me. I want to use uart0 for something else.

    Is my understanding correct that I can use USB cdc acm and communicate with the device via USB while still having uart0 and uart1 dedicated for other things

    2. I have read that ASYNC api is not supported for CDC ACM ( Cannot get Async UART0 to work with USB CDC ) . That means that I have to use interrupt driven API. Is there a possibility to use interrupt driven API for USB CDC ACM and use UART ASYNC API for uart0 and uart1? 

  • zazas321 said:
    Is my understanding correct that I can use USB cdc acm and communicate with the device via USB while still having uart0 and uart1 dedicated for other things

    Yes, that should work.

    zazas321 said:
    2. I have read that ASYNC api is not supported for CDC ACM ( Cannot get Async UART0 to work with USB CDC ) . That means that I have to use interrupt driven API. Is there a possibility to use interrupt driven API for USB CDC ACM and use UART ASYNC API for uart0 and uart1? 

    I see no reason why that should not work.

  • I see no reason why that should not work.

    In order to use usb cdc acm I need to enable the

    CONFIG_UART_INTERRUPT_DRIVEN=y

    but if I want to use ASYNC UART I need to enable:

    CONFIG_UART_ASYNC_API=y



    I have just read in some other forum post ( UART Interrput in Zephyr nRF9160)

    And someone said that CONFIG_UART_INTERRUPT_DRIVEN will get priority over ASYNC and thus disable the ASYN API so I am not really sure why you say that there is no reason why that should not work. Looking forward to heard back from you

  • Hi! 

    Håkon is out of office, so I'm replying instead.

    You can have e.g. interrupt-driven for UART0 and then Async for UART1 in the same application.

    e.g.

    CONFIG_UART_INTERRUPT_DRIVEN=y
    CONFIG_UART_ASYNC_API=y

    CONFIG_UART_0_INTERRUPT_DRIVEN=y

    CONFIG_UART_1_INTERRUPT_DRIVEN=n
    CONFIG_UART_1_ASYNC=y

    BR,

    Sigurd

  • Thank you very much! I didint realise that you can do this.. I have managed to get communication/debugging working via USB while also having uart1/uart0 implemented in ASYNC mode.

    I believe this project will be really valuable for anyone who wants to use uart0/uart1 for something else and have just USB for debugging.

    Project source can be accessed here:
    github.com/.../async_uart0_uart1_usb

Reply Children
No Data
Related