Beware that this post is related to an SDK in maintenance mode
More Info: Consider nRF Connect SDK for new designs

Add CDC class to the USB Device Example

I am developing a USB keyboard + mouse using a NRF52833. I have use the USB Device Example from nRF5 SDK v17.1.0. I chose this example because my project is in C++ and had problems compiling the USB HID Composite Example regarding to "designator order for field 'app_usbd_hid_inst_t::p_hid_methods' does not match declaration order in 'app_usbd_hid_inst_t'".

I have implemented the keyboard + mouse HID to my project since the original example only works as a mouse. Now I need to add the CDC class to have a serial com port, any suggestions or examples on how to do it?

Parents
  • I'm sorry, I think I didn't express myself well, I was referring to sending the USB HID report of the keyboard as I did with the legacy driver, anyway I was able to solve the problem.

    Now I am having problems with the CDC class, it does not receive the first Byte. I've seen this problem come up several times on the forum but I still can't fix it. This is my code, I am using the nRF5 SDK v17.1.0:

    void usb_cdc_ev_handler(app_usbd_class_inst_t const *p_inst, app_usbd_cdc_acm_user_event_t event)
    {
        app_usbd_cdc_acm_t const * p_cdc_acm = app_usbd_cdc_acm_class_get(p_inst);
    
        switch (event)
        {
            case APP_USBD_CDC_ACM_USER_EVT_PORT_OPEN:
                NRF_LOG_INFO("USB serial port open");
    
                uint32_t n = app_usbd_cdc_acm_bytes_stored(p_cdc_acm);
                NRF_LOG_INFO("Bytes waiting: %d", n);
                NRF_LOG_FLUSH();
    
                /*
                    Setup first transfer.
                    If it does not do this, no Bytes can be received later.
                */
                app_usbd_cdc_acm_read(&serial_cdc_class, serial_rx_buffer, 1);
                break;
    
            case APP_USBD_CDC_ACM_USER_EVT_PORT_CLOSE:
                NRF_LOG_INFO("USB serial port close");
                NRF_LOG_FLUSH();
                break;
    
            case APP_USBD_CDC_ACM_USER_EVT_TX_DONE:
                /*NRF_LOG_INFO("Tx done");
                NRF_LOG_FLUSH();*/
                break;
    
            case APP_USBD_CDC_ACM_USER_EVT_RX_DONE:
                {     
                    NRF_LOG_INFO(" ");                          
                    NRF_LOG_INFO("---- New Rx ----------------------");
                    
                    uint32_t n = app_usbd_cdc_acm_bytes_stored(p_cdc_acm);
                    NRF_LOG_INFO("Bytes waiting: %d", n);
                    NRF_LOG_INFO("----");
                    
                    ret_code_t ret;
                    num_bytes = 0;
                    do
                    {
                        n = app_usbd_cdc_acm_rx_size(p_cdc_acm);
                        NRF_LOG_INFO("app_usbd_cdc_acm_rx_size() = %d", n);
     
                        ret = app_usbd_cdc_acm_read(&serial_cdc_class, &serial_rx_buffer[num_bytes], 1);
                        /*
                            ret = 0   -> NRF_SUCCESS
                            ret = 146 -> NRF_ERROR_IO_PENDING
                        */
                        char _ret[30];
                        if (ret == NRF_SUCCESS)
                        {
                            strcpy(_ret, "NRF_SUCCESS");
                        }
                        else if (ret == NRF_ERROR_IO_PENDING)
                        {
                            strcpy(_ret, "NRF_ERROR_IO_PENDING");
                        }
                        else if (ret == NRF_ERROR_BUSY)
                        {
                            strcpy(_ret, "NRF_ERROR_BUSY");
                        }
                        else
                        {
                            strcpy(_ret, "OTHER");
                        }
                        NRF_LOG_INFO("ret = %d -> %s", ret, _ret);
                        NRF_LOG_INFO("serial_rx_buffer[%d] = (int)%d -> (char)%c", num_bytes, serial_rx_buffer[num_bytes], serial_rx_buffer[num_bytes]);
                        NRF_LOG_INFO("----");
                        NRF_LOG_FLUSH();
    
                        num_bytes++;
                    } while (ret == NRF_SUCCESS);  // Fetch data until internal buffer is empty.
                }
                break;
    
            default:
                break;
        }
    }

    And this is the log screen when I send "123" from another serial terminal:

    <info> app: USB serial port open
    
    <info> app: Bytes waiting: 0
    
    <info> app:  
    
    <info> app: ---- New Rx ----------------------
    
    <info> app: Bytes waiting: 2
    
    <info> app: ----
    
    <info> app: app_usbd_cdc_acm_rx_size() = 1
    
    <info> app: ret = 0 -> NRF_SUCCESS
    
    <info> app: serial_rx_buffer[0] = (int)50 -> (char)2
    
    <info> app: ----
    
    <info> app: app_usbd_cdc_acm_rx_size() = 1
    
    <info> app: ret = 0 -> NRF_SUCCESS
    
    <info> app: serial_rx_buffer[1] = (int)51 -> (char)3
    
    <info> app: ----
    
    <info> app: app_usbd_cdc_acm_rx_size() = 1
    
    <info> app: ret = 146 -> NRF_ERROR_IO_PENDING
    
    <info> app: serial_rx_buffer[2] = (int)0 -> (char)
    
    <info> app:  
    
    <info> app: ---- New Rx ----------------------
    
    <info> app: Bytes waiting: 2
    
    <info> app: ----
    
    <info> app: app_usbd_cdc_acm_rx_size() = 1
    
    <info> app: ret = 0 -> NRF_SUCCESS
    
    <info> app: serial_rx_buffer[0] = (int)50 -> (char)2
    
    <info> app: ----
    
    <info> app: app_usbd_cdc_acm_rx_size() = 1
    
    <info> app: ret = 0 -> NRF_SUCCESS
    
    <info> app: serial_rx_buffer[1] = (int)51 -> (char)3
    
    <info> app: ----
    
    <info> app: app_usbd_cdc_acm_rx_size() = 1
    
    <info> app: ret = 146 -> NRF_ERROR_IO_PENDING
    
    <info> app: serial_rx_buffer[2] = (int)49 -> (char)1
    
    <info> app: ----
    
    <info> app:  
    
    <info> app: ---- New Rx ----------------------
    
    <info> app: Bytes waiting: 2
    
    <info> app: ----
    
    <info> app: app_usbd_cdc_acm_rx_size() = 1
    
    <info> app: ret = 0 -> NRF_SUCCESS
    
    <info> app: serial_rx_buffer[0] = (int)50 -> (char)2
    
    <info> app: ----
    
    <info> app: app_usbd_cdc_acm_rx_size() = 1
    
    <info> app: ret = 0 -> NRF_SUCCESS
    
    <info> app: serial_rx_buffer[1] = (int)51 -> (char)3
    
    <info> app: ----
    
    <info> app: app_usbd_cdc_acm_rx_size() = 1
    
    <info> app: ret = 146 -> NRF_ERROR_IO_PENDING
    
    <info> app: serial_rx_buffer[2] = (int)49 -> (char)1
    
    <info> app: ----

    You can see that on the first try it only receives "23" and then the null character when ret == NRF_ERROR_IO_PENDING.
    On subsequent attempts it always receive "231".

    It's weird that the app_usbd_cdc_acm_bytes_stored() function always returns 2 Bytes when actually I'm sending 3 Bytes.

    I have also tried with app_usbd_cdc_acm_read_any() getting the same result.

Reply
  • I'm sorry, I think I didn't express myself well, I was referring to sending the USB HID report of the keyboard as I did with the legacy driver, anyway I was able to solve the problem.

    Now I am having problems with the CDC class, it does not receive the first Byte. I've seen this problem come up several times on the forum but I still can't fix it. This is my code, I am using the nRF5 SDK v17.1.0:

    void usb_cdc_ev_handler(app_usbd_class_inst_t const *p_inst, app_usbd_cdc_acm_user_event_t event)
    {
        app_usbd_cdc_acm_t const * p_cdc_acm = app_usbd_cdc_acm_class_get(p_inst);
    
        switch (event)
        {
            case APP_USBD_CDC_ACM_USER_EVT_PORT_OPEN:
                NRF_LOG_INFO("USB serial port open");
    
                uint32_t n = app_usbd_cdc_acm_bytes_stored(p_cdc_acm);
                NRF_LOG_INFO("Bytes waiting: %d", n);
                NRF_LOG_FLUSH();
    
                /*
                    Setup first transfer.
                    If it does not do this, no Bytes can be received later.
                */
                app_usbd_cdc_acm_read(&serial_cdc_class, serial_rx_buffer, 1);
                break;
    
            case APP_USBD_CDC_ACM_USER_EVT_PORT_CLOSE:
                NRF_LOG_INFO("USB serial port close");
                NRF_LOG_FLUSH();
                break;
    
            case APP_USBD_CDC_ACM_USER_EVT_TX_DONE:
                /*NRF_LOG_INFO("Tx done");
                NRF_LOG_FLUSH();*/
                break;
    
            case APP_USBD_CDC_ACM_USER_EVT_RX_DONE:
                {     
                    NRF_LOG_INFO(" ");                          
                    NRF_LOG_INFO("---- New Rx ----------------------");
                    
                    uint32_t n = app_usbd_cdc_acm_bytes_stored(p_cdc_acm);
                    NRF_LOG_INFO("Bytes waiting: %d", n);
                    NRF_LOG_INFO("----");
                    
                    ret_code_t ret;
                    num_bytes = 0;
                    do
                    {
                        n = app_usbd_cdc_acm_rx_size(p_cdc_acm);
                        NRF_LOG_INFO("app_usbd_cdc_acm_rx_size() = %d", n);
     
                        ret = app_usbd_cdc_acm_read(&serial_cdc_class, &serial_rx_buffer[num_bytes], 1);
                        /*
                            ret = 0   -> NRF_SUCCESS
                            ret = 146 -> NRF_ERROR_IO_PENDING
                        */
                        char _ret[30];
                        if (ret == NRF_SUCCESS)
                        {
                            strcpy(_ret, "NRF_SUCCESS");
                        }
                        else if (ret == NRF_ERROR_IO_PENDING)
                        {
                            strcpy(_ret, "NRF_ERROR_IO_PENDING");
                        }
                        else if (ret == NRF_ERROR_BUSY)
                        {
                            strcpy(_ret, "NRF_ERROR_BUSY");
                        }
                        else
                        {
                            strcpy(_ret, "OTHER");
                        }
                        NRF_LOG_INFO("ret = %d -> %s", ret, _ret);
                        NRF_LOG_INFO("serial_rx_buffer[%d] = (int)%d -> (char)%c", num_bytes, serial_rx_buffer[num_bytes], serial_rx_buffer[num_bytes]);
                        NRF_LOG_INFO("----");
                        NRF_LOG_FLUSH();
    
                        num_bytes++;
                    } while (ret == NRF_SUCCESS);  // Fetch data until internal buffer is empty.
                }
                break;
    
            default:
                break;
        }
    }

    And this is the log screen when I send "123" from another serial terminal:

    <info> app: USB serial port open
    
    <info> app: Bytes waiting: 0
    
    <info> app:  
    
    <info> app: ---- New Rx ----------------------
    
    <info> app: Bytes waiting: 2
    
    <info> app: ----
    
    <info> app: app_usbd_cdc_acm_rx_size() = 1
    
    <info> app: ret = 0 -> NRF_SUCCESS
    
    <info> app: serial_rx_buffer[0] = (int)50 -> (char)2
    
    <info> app: ----
    
    <info> app: app_usbd_cdc_acm_rx_size() = 1
    
    <info> app: ret = 0 -> NRF_SUCCESS
    
    <info> app: serial_rx_buffer[1] = (int)51 -> (char)3
    
    <info> app: ----
    
    <info> app: app_usbd_cdc_acm_rx_size() = 1
    
    <info> app: ret = 146 -> NRF_ERROR_IO_PENDING
    
    <info> app: serial_rx_buffer[2] = (int)0 -> (char)
    
    <info> app:  
    
    <info> app: ---- New Rx ----------------------
    
    <info> app: Bytes waiting: 2
    
    <info> app: ----
    
    <info> app: app_usbd_cdc_acm_rx_size() = 1
    
    <info> app: ret = 0 -> NRF_SUCCESS
    
    <info> app: serial_rx_buffer[0] = (int)50 -> (char)2
    
    <info> app: ----
    
    <info> app: app_usbd_cdc_acm_rx_size() = 1
    
    <info> app: ret = 0 -> NRF_SUCCESS
    
    <info> app: serial_rx_buffer[1] = (int)51 -> (char)3
    
    <info> app: ----
    
    <info> app: app_usbd_cdc_acm_rx_size() = 1
    
    <info> app: ret = 146 -> NRF_ERROR_IO_PENDING
    
    <info> app: serial_rx_buffer[2] = (int)49 -> (char)1
    
    <info> app: ----
    
    <info> app:  
    
    <info> app: ---- New Rx ----------------------
    
    <info> app: Bytes waiting: 2
    
    <info> app: ----
    
    <info> app: app_usbd_cdc_acm_rx_size() = 1
    
    <info> app: ret = 0 -> NRF_SUCCESS
    
    <info> app: serial_rx_buffer[0] = (int)50 -> (char)2
    
    <info> app: ----
    
    <info> app: app_usbd_cdc_acm_rx_size() = 1
    
    <info> app: ret = 0 -> NRF_SUCCESS
    
    <info> app: serial_rx_buffer[1] = (int)51 -> (char)3
    
    <info> app: ----
    
    <info> app: app_usbd_cdc_acm_rx_size() = 1
    
    <info> app: ret = 146 -> NRF_ERROR_IO_PENDING
    
    <info> app: serial_rx_buffer[2] = (int)49 -> (char)1
    
    <info> app: ----

    You can see that on the first try it only receives "23" and then the null character when ret == NRF_ERROR_IO_PENDING.
    On subsequent attempts it always receive "231".

    It's weird that the app_usbd_cdc_acm_bytes_stored() function always returns 2 Bytes when actually I'm sending 3 Bytes.

    I have also tried with app_usbd_cdc_acm_read_any() getting the same result.

Children
  • Hi,

     

    Don't log directly in your event handlers.

    You can push log messages, but only if deferred logging is enabled. This means that if sdk_config.h::NRF_LOG_DEFERRED = 1, you can log in the event handler, but you cannot call NRF_LOG_FLUSH then.

    This will cause issues.

     

    In your sdk_config.h, can you try to set "#define APP_USBD_CDC_ACM_ZLP_ON_EPSIZE_WRITE 1" and see if this helps as well?

     

    Kind regards,

    Håkon

  • That flag is already enabled:

    Also I have deleted all the logs inside the event handler and print the Rx buffer in the main but it still does not work:

    void usb_cdc_ev_handler(app_usbd_class_inst_t const *p_inst, app_usbd_cdc_acm_user_event_t event)
    {
        app_usbd_cdc_acm_t const * p_cdc_acm = app_usbd_cdc_acm_class_get(p_inst);
    
        switch (event)
        {
            case APP_USBD_CDC_ACM_USER_EVT_PORT_OPEN:
                /*
                    Setup first transfer.
                    If it does not do this, no Bytes can be received later.
                */
                app_usbd_cdc_acm_read(&serial_cdc_class, serial_rx_buffer, 1);
                break;
    
            case APP_USBD_CDC_ACM_USER_EVT_PORT_CLOSE:
                break;
    
            case APP_USBD_CDC_ACM_USER_EVT_TX_DONE:
                break;
    
            case APP_USBD_CDC_ACM_USER_EVT_RX_DONE:
                {
                    ret_code_t ret;
                    num_bytes = 0;
                    do
                    {
                        ret = app_usbd_cdc_acm_read(&serial_cdc_class, &serial_rx_buffer[num_bytes++], 1);
                    } while (ret == NRF_SUCCESS);  // Fetch data until internal buffer is empty.
    
                    serial_rx_buffer[num_bytes] = '\0';
    
                    flag_rx_done = true;
                }
                break;
    
            default:
                break;
        }
    }

    This is the log:

    <info> app: Rx 3 Bytes = 23
    
    <info> app: Rx 3 Bytes = 231
    
    <info> app: Rx 3 Bytes = 231
    
    <info> app: Rx 3 Bytes = 231

    The same result, the first Byte is missing and the third character of the first transmission is the null character '\0'.

    If I enable the log for the USBD_CDC_ACM module I get these messages:

    <debug> cdc_acm: Received 3 bytes. Stored in internal buffer.
    
    <info> cdc_acm: EPOUT_DATA: 04 done
    
    <info> app: -> Rx 3 Bytes = 23
    
    <debug> cdc_acm: Received 3 bytes. Stored in internal buffer.
    
    <info> cdc_acm: EPOUT_DATA: 04 done
    
    <info> app: -> Rx 3 Bytes = 231
    
    <debug> cdc_acm: Received 3 bytes. Stored in internal buffer.
    
    <info> cdc_acm: EPOUT_DATA: 04 done
    
    <info> app: -> Rx 3 Bytes = 231
    
    <debug> cdc_acm: Received 3 bytes. Stored in internal buffer.
    
    <info> cdc_acm: EPOUT_DATA: 04 done
    
    <info> app: -> Rx 3 Bytes = 231

    Update:

    I have added this log in the cdc_acm_consumer() function from the app_usbd_cdc_acm.c file to print the rx buffer:

            char *cstr = (char *)p_next->p_data.rx;
            NRF_LOG_DEBUG("Rx: %s", cstr);

    /**
     * @brief CDC ACM consumer.
     *
     * @note See @ref nrf_drv_usbd_consumer_t
     */
    static bool cdc_acm_consumer(nrf_drv_usbd_ep_transfer_t * p_next,
                                 void *                       p_context,
                                 size_t                       ep_size,
                                 size_t                       data_size)
    {
        app_usbd_cdc_acm_ctx_t * p_cdc_acm_ctx = (app_usbd_cdc_acm_ctx_t *) p_context;
        p_next->size = data_size;
    
        if (data_size <= p_cdc_acm_ctx->rx_transfer[0].read_left)
        {
            p_next->p_data.rx = p_cdc_acm_ctx->rx_transfer[0].p_buf;
    
            p_cdc_acm_ctx->rx_transfer[0].p_buf     += data_size;
            p_cdc_acm_ctx->bytes_read               += data_size;
            p_cdc_acm_ctx->rx_transfer[0].read_left -= data_size;
            NRF_LOG_DEBUG("Received %d bytes. Space left in user buffer: %d bytes.",
                          data_size,
                          p_cdc_acm_ctx->rx_transfer[0].read_left);
            return (p_cdc_acm_ctx->rx_transfer[0].read_left) != 0;
        }
        else
        {
            p_next->p_data.rx = p_cdc_acm_ctx->internal_rx_buf;
            p_cdc_acm_ctx->cur_read = data_size;
            NRF_LOG_DEBUG("Received %d bytes. Stored in internal buffer.", data_size);
            
            char *cstr = (char *)p_next->p_data.rx;
            NRF_LOG_DEBUG("Rx: %s", cstr);
            
            return false;
        }
    }

    And up to this point the Bytes are really being received well. This is the log:

    <debug> cdc_acm: Received 3 bytes. Stored in internal buffer.
    
    <debug> cdc_acm: Rx: 123
    
    <info> cdc_acm: EPOUT_DATA: 04 done
    
    <info> app: -> Rx 3 Bytes = 23
    
    <debug> cdc_acm: Received 3 bytes. Stored in internal buffer.
    
    <debug> cdc_acm: Rx: 123
    
    <info> cdc_acm: EPOUT_DATA: 04 done
    
    <info> app: -> Rx 3 Bytes = 231

  • Hi,

     

    Have you tried just reading the whole buffer instead of byte-by-byte?

    There seems to be something that goes wrong in the main.c logic, as seen by your results by looking at the internal buffers as well, so it might be beneficial to declare your variables used in different scopes as volatile (ie. flag_rx_done and num_bytes)

     

    Kind regards,

    Håkon

  • Those variables are already static volatile and yes I have tried reading all the buffer at once with the same results and then switch to reading Byte by Byte as this es the most common way seen in the forum examples.

  • Hi,

     

    Is it still printing like this?

    <info> cdc_acm: EPOUT_DATA: 04 done
    <info> app: -> Rx 3 Bytes = 23

     

    Are you sure it is not the logger itself that is blocked or otherwise showing the incorrect data? Since the amount of bytes is correct, it might be that the logger is having buffering issues when printing, while the actual data is present in a normalized format.

    Try catching the scenario by parsing the data (ie. check for content "231") instead of plain printing it.

     

    Kind regards,

    Håkon

Related