Cannot get Async UART0 to work with USB CDC

Hello,

I first got the USB CDC Zephyr sample with interrupt driven UART to work just fine. 

But because I want to to be able to TX outside of interrupts I tried to convert that sample to use Async UART while using USB CDC.  I have looked at all tickets that seem related to this but nothing works, maybe because I'm using a  newer SDK version, which is v2.4.2.

When I call uart_callback_set I get -ENOSYS.   I can see all callbacks for the API are not set which is why I get this.  (I print the pointers and they are 0)

from C:\ncs\v2.4.2\zephyr\include\zephyr\drivers\uart.h

static inline int uart_callback_set(const struct device *dev,
				    uart_callback_t callback,
				    void *user_data)
{
#ifdef CONFIG_UART_ASYNC_API
	const struct uart_driver_api *api =
			(const struct uart_driver_api *)dev->api;

	if (api->callback_set == NULL) {
		return -ENOSYS;
	}

	return api->callback_set(dev, callback, user_data);
#else
	ARG_UNUSED(dev);
	ARG_UNUSED(callback);
	ARG_UNUSED(user_data);
	return -ENOTSUP;
#endif
}

I have tried all kings of CONFIG options (from other tickets) and nothing seems to work.  I can see the ASYNC code is enabled in the file C:\ncs\v2.4.2\zephyr\drivers\serial\uart_nrfx_uarte.c.  I can not see the linkage with the calling of DEVICE_DT_GET_ONE(zephyr_cdc_acm_uart) and driver API callbacks being set correctly. 

Last configs I tried are:

CONFIG_LOG=y

CONFIG_MAIN_STACK_SIZE=1536


CONFIG_STDOUT_CONSOLE=y

CONFIG_USB_DEVICE_STACK=y
CONFIG_USB_DEVICE_PRODUCT="Zephyr CDC ACM sample"
CONFIG_USB_DEVICE_SN="0123456789ABCDEG"
CONFIG_USB_DEVICE_MANUFACTURER="Zephyr USBD CDC ACM with MS"
CONFIG_USB_DEVICE_PID=0x0002
CONFIG_USB_DEVICE_VID=0x2fe3
CONFIG_USB_DRIVER_LOG_LEVEL_ERR=y
CONFIG_USB_DEVICE_LOG_LEVEL_ERR=y
CONFIG_USB_DEVICE_INITIALIZE_AT_BOOT=n
CONFIG_USB_CDC_ACM=y
CONFIG_SERIAL=y

CONFIG_UART_ASYNC_API=y
CONFIG_UART_INTERRUPT_DRIVEN=y
CONFIG_UART_0_ASYNC=y
CONFIG_UART_0_INTERRUPT_DRIVEN=n
CONFIG_NRFX_UARTE0=y

CONFIG_UART_LINE_CTRL=y

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

My Code

#include <zephyr/kernel.h>
#include <zephyr/device.h>


#include <zephyr/usb/usb_device.h>
#include <zephyr/usb/usbd.h>

#include <zephyr/drivers/uart.h>
#include <zephyr/sys/ring_buffer.h>

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(main, LOG_LEVEL_INF);

#define RING_BUF_SIZE 1024
uint8_t ring_buffer[RING_BUF_SIZE];
struct ring_buf ringbuf;

// static void interrupt_handler(const struct device *dev, void *user_data)
// {
// 	ARG_UNUSED(user_data);

// 	LOG_INF("11");

// 	while (uart_irq_update(dev) && uart_irq_is_pending(dev)) {

// 		LOG_INF("222");

// 		if (uart_irq_rx_ready(dev)) {
// 			int recv_len, rb_len;
// 			uint8_t buffer[64];
// 			size_t len = MIN(ring_buf_space_get(&ringbuf), sizeof(buffer));

// 			recv_len = uart_fifo_read(dev, buffer, len);
// 			if (recv_len < 0) {
// 				LOG_ERR("Failed to read UART FIFO");
// 				recv_len = 0;
// 			};

// 			rb_len = ring_buf_put(&ringbuf, buffer, recv_len);
// 			if (rb_len < recv_len) {
// 				LOG_ERR("Drop %u bytes", recv_len - rb_len);
// 			}

// 			LOG_DBG("tty fifo -> ringbuf %d bytes", rb_len);
// 			if (rb_len) {
// 				uart_irq_tx_enable(dev);
// 			}
// 		}

// 		if (uart_irq_tx_ready(dev)) {
// 			uint8_t buffer[64];
// 			int rb_len, send_len;

// 			rb_len = ring_buf_get(&ringbuf, buffer, sizeof(buffer));
// 			if (!rb_len) {
// 				LOG_DBG("Ring buffer empty, disable TX IRQ");
// 				uart_irq_tx_disable(dev);
// 				continue;
// 			}

// 			send_len = uart_fifo_fill(dev, buffer, rb_len);
// 			if (send_len < rb_len) {
// 				LOG_ERR("Drop %d bytes", rb_len - send_len);
// 			}

// 			LOG_DBG("ringbuf -> tty fifo %d bytes", send_len);
// 		}
// 	}
// }

static uint8_t rx_buf[64] = {0}; //A buffer to store incoming UART data 

static void async_interrupt_cb(const struct device *dev, struct uart_event *evt, void *user_data)
{
	switch (evt->type) {
	
	case UART_TX_DONE:
		// do something
		break;

	case UART_TX_ABORTED:
		// do something
		break;
		
	case UART_RX_RDY:
		LOG_INF("UART_RX_RDY");
		break;

	case UART_RX_BUF_REQUEST:
		LOG_INF("UART_RX_BUF_REQUEST");
		break;

	case UART_RX_BUF_RELEASED:
		LOG_INF("UART_RX_BUF_RELEASED");
		break;
		
	case UART_RX_DISABLED:
		// do something
		break;

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

int main(void) {

	const struct device *dev;
	uint32_t baudrate, dtr = 0U;
	int ret;

	//
	// Setup USB
	//

	dev = DEVICE_DT_GET_ONE(zephyr_cdc_acm_uart);
	if (!device_is_ready(dev)) {
		LOG_ERR("CDC ACM device not ready");
		return 0;
	}

	ret = usb_enable(NULL);

	LOG_INF("Configured.......");

	if (ret != 0) {
		LOG_ERR("Failed to enable USB");
		return 0;
	}

	ring_buf_init(&ringbuf, sizeof(ring_buffer), ring_buffer);

	LOG_INF("Wait for DTR");

	while (true) {
		uart_line_ctrl_get(dev, UART_LINE_CTRL_DTR, &dtr);
		if (dtr) {
			break;
		} else {
			/* Give CPU resources to low priority threads. */
			k_sleep(K_MSEC(100));
		}
	}

	LOG_INF("DTR set");

		/* They are optional, we use them to test the interrupt endpoint */
	ret = uart_line_ctrl_set(dev, UART_LINE_CTRL_DCD, 1);
	if (ret) {
		LOG_WRN("Failed to set DCD, ret code %d", ret);
	}

	ret = uart_line_ctrl_set(dev, UART_LINE_CTRL_DSR, 1);
	if (ret) {
		LOG_WRN("Failed to set DSR, ret code %d", ret);
	}

	/* Wait 100ms for the host to do all settings */
	k_msleep(100);

	ret = uart_line_ctrl_get(dev, UART_LINE_CTRL_BAUD_RATE, &baudrate);
	if (ret) {
		LOG_WRN("Failed to get baudrate, ret code %d", ret);
	} else {
		LOG_INF("Baudrate detected: %d", baudrate);
	}


	//uart_irq_callback_set(dev, interrupt_handler);
	ret = uart_callback_set(dev, async_interrupt_cb, NULL);
	if (ret != 0) { // :TODO:
		LOG_ERR("Failed to register UART UART cb: %i", ret);
		return 0;
	}

	/* Enable rx interrupts */
	//uart_irq_rx_enable(dev);

	ret = uart_rx_enable(dev, rx_buf, sizeof(rx_buf), 100);
	if (ret != 0) { // :TODO:
		LOG_ERR("Failed to enable UART RX");
		return 0;
	}

	LOG_INF("Starting Observer Demo\n");

	LOG_ERR("Exiting %s thread", __func__);
	return 0;
}

When I connect with pyserial I always get the error NOSYS, 88.  You will note I can make some sort of connection to get the baudrate.  (below)

*** Booting Zephyr OS build v3.3.99-ncs1-1 ***
[00:00:00.416,503] <inf> main: Configured.......
[00:00:00.416,534] <inf> main: Wait for DTR
[00:00:00.420,562] <inf> usb_cdc_acm: Device suspended
[00:00:00.518,188] <inf> usb_cdc_acm: Device resumed
[00:00:00.869,995] <inf> usb_cdc_acm: Device configured
[00:00:04.319,244] <inf> main: DTR set
[00:00:04.433,929] <inf> main: Baudrate detected: 115200
[00:00:04.433,959] <err> main: Failed to register UART UART cb: -88

After spending hours on this I don't know what else to try.  Any help with this would be very much appreciated.

Async UART0 (with EasyDMA) is supposed to work with USB CDC, they are compatible?

  • Hi,

    use Async UART while using USB CDC.

    Do you mean that you use USB CDC at the same time as UART for something else, or that you use the Async API for USB CDC?

    Also, this case seems kinda similar to  nRF5340: issue with USB CDC ACM and UART driver running in parallel . What do you think about that?

  • The later, use Async API for USB CDC.  I need to use the uart in async mode going over USB CDC.

    I did see that ticket but didn’t get any hints.

    It seems like there is a disconnect on setup between the uart and usb cdc stacks.  I can see all the individual uart api callbacks are just not set, I.e.

    if (api->callback_set == NULL) {

         return -ENOSYS;
    }

    In uart_callback_set.  So it is easy to see what is not working, but just cannot figure out why.

    Thanks

  • To follow up with more, I did see from that ticket:

    "The CDC ACM driver does not support the ASYNC API, and will return ENOTSUP if you try to enable the callback"

    From the code:

    from C:\ncs\v2.4.2\zephyr\include\zephyr\drivers\uart.h
    
    static inline int uart_callback_set(const struct device *dev,
    				    uart_callback_t callback,
    				    void *user_data)
    {
    #ifdef CONFIG_UART_ASYNC_API
    	const struct uart_driver_api *api =
    			(const struct uart_driver_api *)dev->api;
    
    	if (api->callback_set == NULL) {
    		return -ENOSYS;
    	}
    
    	return api->callback_set(dev, callback, user_data);
    #else
    	ARG_UNUSED(dev);
    	ARG_UNUSED(callback);
    	ARG_UNUSED(user_data);
    	return -ENOTSUP;
    #endif
    }

    There is specific logic for CONFIG_UART_ASYNC_API use in uart_callback_set.  And this is why I get the ENOSYS error and not the ENOTSUP error as in that ticket (he was likely not specifying CONFIG_UART_ASYNC_API).

    But I guess fundamentally does the CDC ACM driver support the ASYNC API? This could theoretically cause something farther up the stack to cause api->callback_set == NULL too be TRUE.

    Thanks

  • I am looking into this, but have not been able to find any reason for it yet.
    I will continue to look into this next week

  • Dean said:
    But I guess fundamentally does the CDC ACM driver support the ASYNC API?

    I was not able to find any official docs saying this, which is what I hoped.

    But one of our experts on the field says CDC ACM does not support the Async API in zephyr, so from what I can find that is just how it is.

Related