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? 
Related