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?

Parents
  • 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.

Reply Children
  • I haven't found anything in the documentation, that specifies that the asynchronous API for USB CDC is not implemented. As developing 3 different implementations just to find out, which API is working correctly is a lot of work, I would either expect all APIs to just work as documented, or at least a list of APIs that are supposed to work. Is the polling API working?

  • I found a place where it says that cdc_acm only supports CONFIG_UART_INTERRUPT_DRIVEN, and it gives a precompiler error as well. https://github.com/nrfconnect/sdk-zephyr/blob/main/subsys/usb/device/class/cdc_acm.c#L52.

    For the rest of your questions Torsten:
    While we in Nordic Semiconductor ASA frequently contribute to the Zephyr RTOS, we do not make all of it.
    What you complain about would be a lack or documentation philosophy in Zephyr RTOS I think, so I suggest that if you want to feedback this, making an issue for Zephyr RTOS is the best way to be heard by any zephyr developers.

  • Hi Sigurd,

    thank you for your fast response! :-)

    # Temporary workaround for https://devzone.nordicsemi.com/f/nordic-q-a/107445/colon-in-soc_cpu_idle-h-leads-to-failing-build-with-arm-none-eabi-gcc-version-12-2
    CONFIG_SOC_NRF53_ANOMALY_168_WORKAROUND=n
    
    # Enable support for C++
    CONFIG_CPP=y
    CONFIG_REQUIRES_FULL_LIBCPP=y
    CONFIG_NEWLIB_LIBC=y
    CONFIG_STD_CPP20=y
    
    CONFIG_STDOUT_CONSOLE=y
    CONFIG_LOG=y
    
    # USB
    CONFIG_USB_DEVICE_STACK=y
    CONFIG_USB_DEVICE_PRODUCT="Lovelace Pod"
    CONFIG_USB_DEVICE_PID=0x0001
    CONFIG_USB_DRIVER_LOG_LEVEL_ERR=y
    CONFIG_USB_DEVICE_LOG_LEVEL_ERR=y
    
    # UART
    CONFIG_SERIAL=y
    CONFIG_UART_ASYNC_API=y
    CONFIG_UART_INTERRUPT_DRIVEN=n
    CONFIG_UART_LINE_CTRL=y
    CONFIG_USB_DEVICE_INITIALIZE_AT_BOOT=n
    
    # MQTT
    CONFIG_MQTT_SN_LIB=y
    CONFIG_NETWORKING=y

    That's the configuration I'm currently working with. It compiles and links fine and then just returns `-ENOSYS`.

    Sure, Nodic is not the only contributor to Zephyr, but if the Zephyr documentation states, that there are 3 different APIs for UARTs, I would expect either support for the 3 APIs or I would expect that the documentation states, that there are some exceptions and how to find out which implementations do not support the documented features.

    In the end, when I have the task to develop firmware for the nordic hardware, I have not much choices, but to use Zephyr (and in deed, I have to use the Nordic branch of Zephyr). And in order to find out, how to do so, I have to rely on the documentation.

  • I've looked at the proprocessor output of the compilation of `cdc_acm.c` to figure out, why the compile time check does not work for my example. It turns out, that despite the prj.config given above, the generated autoconf.h contains this line:

    #define CONFIG_UART_INTERRUPT_DRIVEN 1
    

  • CONFIG_USB_CDC_ACM selects CONFIG_UART_INTERRUPT_DRIVEN.
    Do you by chance have this warning in your build log?

    warning: UART_INTERRUPT_DRIVEN (defined at boards/shields/sparkfun_sara_r4/Kconfig.defconfig:24,
    boards/shields/wnc_m14a2a/Kconfig.defconfig:17, soc/arm/quicklogic_eos_s3/Kconfig.defconfig:17,
    drivers/serial/Kconfig:72) was assigned the value 'n' but got the value 'y'. See
    http://docs.zephyrproject.org/latest/kconfig.html#CONFIG_UART_INTERRUPT_DRIVEN and/or look up
    UART_INTERRUPT_DRIVEN in the menuconfig/guiconfig interface. The Application Development Primer,
    Setting Configuration Values, and Kconfig - Tips and Best Practices sections of the manual might be
    helpful too.
    

    Torsten Robitzki said:
    Sure, Nodic is not the only contributor to Zephyr, but if the Zephyr documentation states, that there are 3 different APIs for UARTs, I would expect either support for the 3 APIs or I would expect that the documentation states, that there are some exceptions and how to find out which implementations do not support the documented features.

    For whats it's worth, I agree.

    Torsten Robitzki said:
    In the end, when I have the task to develop firmware for the nordic hardware, I have not much choices, but to use Zephyr (and in deed, I have to use the Nordic branch of Zephyr). And in order to find out, how to do so, I have to rely on the documentation.

    That is true. I can talk to our documentation team and see if we can get a disclaimer into the CDC ACM documentation of Zephyr.

Related