This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

multiple nRF52840's USB CDC ACM instances

Hi, a question may need your kind help. Just wonder if nRF52840's USB CDC ACM could support multiple instances? For example, would it support CLI as well as an another serial instance like for AT command? If could , how could I get these? Many thanks.

  • Hi 

    Yes, the USBD driver uses a modular structure that allows you to instantiate multiple different classes, the only limitation being the number of IN/OUT endpoints supported by the USBD interface. 

    I made a quick version of the usbd_cdc_acm example in nRF5 SDK v15.3.0 to demonstrate this, setting up two unique CDC instances, and you will find the main file below:

    /**
     * Copyright (c) 2017 - 2019, Nordic Semiconductor ASA
     *
     * All rights reserved.
     *
     * Redistribution and use in source and binary forms, with or without modification,
     * are permitted provided that the following conditions are met:
     *
     * 1. Redistributions of source code must retain the above copyright notice, this
     *    list of conditions and the following disclaimer.
     *
     * 2. Redistributions in binary form, except as embedded into a Nordic
     *    Semiconductor ASA integrated circuit in a product or a software update for
     *    such product, must reproduce the above copyright notice, this list of
     *    conditions and the following disclaimer in the documentation and/or other
     *    materials provided with the distribution.
     *
     * 3. Neither the name of Nordic Semiconductor ASA nor the names of its
     *    contributors may be used to endorse or promote products derived from this
     *    software without specific prior written permission.
     *
     * 4. This software, with or without modification, must only be used with a
     *    Nordic Semiconductor ASA integrated circuit.
     *
     * 5. Any software provided in binary form under this license must not be reverse
     *    engineered, decompiled, modified and/or disassembled.
     *
     * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
     * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
     * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE
     * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
     * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
     * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     *
     */
    #include <stdint.h>
    #include <stdbool.h>
    #include <stddef.h>
    #include <stdio.h>
    
    #include "nrf.h"
    #include "nrf_drv_usbd.h"
    #include "nrf_drv_clock.h"
    #include "nrf_gpio.h"
    #include "nrf_delay.h"
    #include "nrf_drv_power.h"
    
    #include "app_error.h"
    #include "app_util.h"
    #include "app_usbd_core.h"
    #include "app_usbd.h"
    #include "app_usbd_string_desc.h"
    #include "app_usbd_cdc_acm.h"
    #include "app_usbd_serial_num.h"
    
    #include "boards.h"
    #include "bsp.h"
    #include "bsp_cli.h"
    #include "nrf_cli.h"
    #include "nrf_cli_uart.h"
    
    #include "nrf_log.h"
    #include "nrf_log_ctrl.h"
    #include "nrf_log_default_backends.h"
    
    /**
     * @brief CLI interface over UART
     */
    NRF_CLI_UART_DEF(m_cli_uart_transport, 0, 64, 16);
    NRF_CLI_DEF(m_cli_uart,
                "uart_cli:~$ ",
                &m_cli_uart_transport.transport,
                '\r',
                4);
    
    /**@file
     * @defgroup usbd_cdc_acm_example main.c
     * @{
     * @ingroup usbd_cdc_acm_example
     * @brief USBD CDC ACM example
     *
     */
    
    #define LED_USB_RESUME      (BSP_BOARD_LED_0)
    #define LED_CDC_ACM_OPEN    (BSP_BOARD_LED_1)
    #define LED_CDC_ACM_RX      (BSP_BOARD_LED_2)
    #define LED_CDC_ACM_TX      (BSP_BOARD_LED_3)
    
    #define BTN_CDC_DATA_SEND       0
    #define BTN_CDC_NOTIFY_SEND     1
    
    #define BTN_CDC_DATA_KEY_RELEASE        (bsp_event_t)(BSP_EVENT_KEY_LAST + 1)
    
    /**
     * @brief Enable power USB detection
     *
     * Configure if example supports USB port connection
     */
    #ifndef USBD_POWER_DETECTION
    #define USBD_POWER_DETECTION true
    #endif
    
    
    static void cdc_acm_user_ev_handler(app_usbd_class_inst_t const * p_inst,
                                        app_usbd_cdc_acm_user_event_t event);
    
    static void cdc_acm2_user_ev_handler(app_usbd_class_inst_t const * p_inst,
                                        app_usbd_cdc_acm_user_event_t event);
    
    #define CDC_ACM_COMM_INTERFACE  0
    #define CDC_ACM_COMM_EPIN       NRF_DRV_USBD_EPIN2
    
    #define CDC_ACM_DATA_INTERFACE  1
    #define CDC_ACM_DATA_EPIN       NRF_DRV_USBD_EPIN1
    #define CDC_ACM_DATA_EPOUT      NRF_DRV_USBD_EPOUT1
    
    #define CDC_ACM_COMM2_INTERFACE  2
    #define CDC_ACM_COMM2_EPIN       NRF_DRV_USBD_EPIN4
    
    #define CDC_ACM_DATA2_INTERFACE  3
    #define CDC_ACM_DATA2_EPIN       NRF_DRV_USBD_EPIN3
    #define CDC_ACM_DATA2_EPOUT      NRF_DRV_USBD_EPOUT3
    
    /**
     * @brief CDC_ACM class instance
     * */
    APP_USBD_CDC_ACM_GLOBAL_DEF(m_app_cdc_acm,
                                cdc_acm_user_ev_handler,
                                CDC_ACM_COMM_INTERFACE,
                                CDC_ACM_DATA_INTERFACE,
                                CDC_ACM_COMM_EPIN,
                                CDC_ACM_DATA_EPIN,
                                CDC_ACM_DATA_EPOUT,
                                APP_USBD_CDC_COMM_PROTOCOL_AT_V250
    );
    
    /**
     * @brief CDC_ACM class instance
     * */
    APP_USBD_CDC_ACM_GLOBAL_DEF(m_app_cdc_acm2,
                                cdc_acm2_user_ev_handler,
                                CDC_ACM_COMM2_INTERFACE,
                                CDC_ACM_DATA2_INTERFACE,
                                CDC_ACM_COMM2_EPIN,
                                CDC_ACM_DATA2_EPIN,
                                CDC_ACM_DATA2_EPOUT,
                                APP_USBD_CDC_COMM_PROTOCOL_AT_V250
    );
    
    
    #define READ_SIZE 1
    
    static char m_rx_buffer[READ_SIZE];
    static char m_tx_buffer[NRF_DRV_USBD_EPSIZE];
    static bool m_send_flag = 0;
    
    /**
     * @brief User event handler @ref app_usbd_cdc_acm_user_ev_handler_t (headphones)
     * */
    static void cdc_acm_user_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:
            {
                bsp_board_led_on(LED_CDC_ACM_OPEN);
    
                /*Setup first transfer*/
                ret_code_t ret = app_usbd_cdc_acm_read(&m_app_cdc_acm,
                                                       m_rx_buffer,
                                                       READ_SIZE);
                UNUSED_VARIABLE(ret);
                break;
            }
            case APP_USBD_CDC_ACM_USER_EVT_PORT_CLOSE:
                bsp_board_led_off(LED_CDC_ACM_OPEN);
                break;
            case APP_USBD_CDC_ACM_USER_EVT_TX_DONE:
                bsp_board_led_invert(LED_CDC_ACM_TX);
                break;
            case APP_USBD_CDC_ACM_USER_EVT_RX_DONE:
            {
                ret_code_t ret;
                NRF_LOG_INFO("Bytes waiting: %d", app_usbd_cdc_acm_bytes_stored(p_cdc_acm));
                do
                {
                    /*Get amount of data transfered*/
                    size_t size = app_usbd_cdc_acm_rx_size(p_cdc_acm);
                    NRF_LOG_INFO("RX: size: %lu char: %c", size, m_rx_buffer[0]);
    
                    /* Fetch data until internal buffer is empty */
                    ret = app_usbd_cdc_acm_read(&m_app_cdc_acm,
                                                m_rx_buffer,
                                                READ_SIZE);
                } while (ret == NRF_SUCCESS);
    
                bsp_board_led_invert(LED_CDC_ACM_RX);
                break;
            }
            default:
                break;
        }
    }
    
    /**
     * @brief User event handler @ref app_usbd_cdc_acm_user_ev_handler_t (headphones)
     * */
    static void cdc_acm2_user_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:
            {
                bsp_board_led_on(LED_CDC_ACM_OPEN);
    
                /*Setup first transfer*/
                ret_code_t ret = app_usbd_cdc_acm_read(&m_app_cdc_acm2,
                                                       m_rx_buffer,
                                                       READ_SIZE);
                UNUSED_VARIABLE(ret);
                break;
            }
            case APP_USBD_CDC_ACM_USER_EVT_PORT_CLOSE:
                bsp_board_led_off(LED_CDC_ACM_OPEN);
                break;
            case APP_USBD_CDC_ACM_USER_EVT_TX_DONE:
                bsp_board_led_invert(LED_CDC_ACM_TX);
                break;
            case APP_USBD_CDC_ACM_USER_EVT_RX_DONE:
            {
                ret_code_t ret;
                NRF_LOG_INFO("Bytes waiting: %d", app_usbd_cdc_acm_bytes_stored(p_cdc_acm));
                do
                {
                    /*Get amount of data transfered*/
                    size_t size = app_usbd_cdc_acm_rx_size(p_cdc_acm);
                    NRF_LOG_INFO("RX: size: %lu char: %c", size, m_rx_buffer[0]);
    
                    /* Fetch data until internal buffer is empty */
                    ret = app_usbd_cdc_acm_read(&m_app_cdc_acm2,
                                                m_rx_buffer,
                                                READ_SIZE);
                } while (ret == NRF_SUCCESS);
    
                bsp_board_led_invert(LED_CDC_ACM_RX);
                break;
            }
            default:
                break;
        }
    }
    
    static void usbd_user_ev_handler(app_usbd_event_type_t event)
    {
        switch (event)
        {
            case APP_USBD_EVT_DRV_SUSPEND:
                bsp_board_led_off(LED_USB_RESUME);
                break;
            case APP_USBD_EVT_DRV_RESUME:
                bsp_board_led_on(LED_USB_RESUME);
                break;
            case APP_USBD_EVT_STARTED:
                break;
            case APP_USBD_EVT_STOPPED:
                app_usbd_disable();
                bsp_board_leds_off();
                break;
            case APP_USBD_EVT_POWER_DETECTED:
                NRF_LOG_INFO("USB power detected");
    
                if (!nrf_drv_usbd_is_enabled())
                {
                    app_usbd_enable();
                }
                break;
            case APP_USBD_EVT_POWER_REMOVED:
                NRF_LOG_INFO("USB power removed");
                app_usbd_stop();
                break;
            case APP_USBD_EVT_POWER_READY:
                NRF_LOG_INFO("USB ready");
                app_usbd_start();
                break;
            default:
                break;
        }
    }
    
    static void bsp_event_callback(bsp_event_t ev)
    {
        ret_code_t ret;
        switch ((unsigned int)ev)
        {
            case CONCAT_2(BSP_EVENT_KEY_, BTN_CDC_DATA_SEND):
            {
                m_send_flag = 1;
                break;
            }
            
            case BTN_CDC_DATA_KEY_RELEASE :
            {
                m_send_flag = 0;
                break;
            }
    
            case CONCAT_2(BSP_EVENT_KEY_, BTN_CDC_NOTIFY_SEND):
            {
                ret = app_usbd_cdc_acm_serial_state_notify(&m_app_cdc_acm,
                                                           APP_USBD_CDC_ACM_SERIAL_STATE_BREAK,
                                                           false);
                UNUSED_VARIABLE(ret);
                break;
            }
    
            default:
                return; // no implementation needed
        }
    }
    
    static void init_bsp(void)
    {
        ret_code_t ret;
        ret = bsp_init(BSP_INIT_BUTTONS, bsp_event_callback);
        APP_ERROR_CHECK(ret);
        
        UNUSED_RETURN_VALUE(bsp_event_to_button_action_assign(BTN_CDC_DATA_SEND,
                                                              BSP_BUTTON_ACTION_RELEASE,
                                                              BTN_CDC_DATA_KEY_RELEASE));
        
        /* Configure LEDs */
        bsp_board_init(BSP_INIT_LEDS);
    }
    
    static void init_cli(void)
    {
        ret_code_t ret;
        ret = bsp_cli_init(bsp_event_callback);
        APP_ERROR_CHECK(ret);
        nrf_drv_uart_config_t uart_config = NRF_DRV_UART_DEFAULT_CONFIG;
        uart_config.pseltxd = TX_PIN_NUMBER;
        uart_config.pselrxd = RX_PIN_NUMBER;
        uart_config.hwfc    = NRF_UART_HWFC_DISABLED;
        ret = nrf_cli_init(&m_cli_uart, &uart_config, true, true, NRF_LOG_SEVERITY_INFO);
        APP_ERROR_CHECK(ret);
        ret = nrf_cli_start(&m_cli_uart);
        APP_ERROR_CHECK(ret);
    }
    
    int main(void)
    {
        ret_code_t ret;
        static const app_usbd_config_t usbd_config = {
            .ev_state_proc = usbd_user_ev_handler
        };
    
        ret = NRF_LOG_INIT(NULL);
        APP_ERROR_CHECK(ret);
    
        ret = nrf_drv_clock_init();
        APP_ERROR_CHECK(ret);
        
        nrf_drv_clock_lfclk_request(NULL);
    
        while(!nrf_drv_clock_lfclk_is_running())
        {
            /* Just waiting */
        }
    
        ret = app_timer_init();
        APP_ERROR_CHECK(ret);
    
        init_bsp();
        init_cli();
    
        app_usbd_serial_num_generate();
    
        ret = app_usbd_init(&usbd_config);
        APP_ERROR_CHECK(ret);
        NRF_LOG_INFO("USBD CDC ACM example started.");
    
        app_usbd_class_inst_t const * class_cdc_acm = app_usbd_cdc_acm_class_inst_get(&m_app_cdc_acm);
        ret = app_usbd_class_append(class_cdc_acm);
        APP_ERROR_CHECK(ret);
    
        app_usbd_class_inst_t const * class_cdc_acm2 = app_usbd_cdc_acm_class_inst_get(&m_app_cdc_acm2);
        ret = app_usbd_class_append(class_cdc_acm2);
        APP_ERROR_CHECK(ret);
    
        if (USBD_POWER_DETECTION)
        {
            ret = app_usbd_power_events_enable();
            APP_ERROR_CHECK(ret);
        }
        else
        {
            NRF_LOG_INFO("No USB power detection enabled\r\nStarting USB now");
    
            app_usbd_enable();
            app_usbd_start();
        }
    
        while (true)
        {
            while (app_usbd_event_queue_process())
            {
                /* Nothing to do */
            }
            
            if(m_send_flag)
            {
                static int  frame_counter;
    
                size_t size = sprintf(m_tx_buffer, "Hello USB CDC FA demo: %u\r\n", frame_counter);
    
                ret = app_usbd_cdc_acm_write(&m_app_cdc_acm, m_tx_buffer, size);
                if (ret == NRF_SUCCESS)
                {
                    ++frame_counter;
                }
            }
            
            nrf_cli_process(&m_cli_uart);
    
            UNUSED_RETURN_VALUE(NRF_LOG_PROCESS());
            /* Sleep CPU only if there was no interrupt since last loop processing */
            __WFE();
        }
    }
    
    /** @} */
    

    Best regards
    Torbjørn

  • Hi ovrebekk,

    Thanks. Got it. Actually today I also figured it out just like what you are instructing. Anyway appreciate. 

    Btw, could we let the windows/linux enumerate the virtual com ports with some customised name, but not as below? Because,  in my situation, there would be one serial port(device) acting as AT command interface and the other working with CLI&Log. Some bit silly thought, for example, could I have the displaying name as "USB Serial Device_AT (COM10)", "USB Serial Device_CLIandLog(COM9). 

  • Hi 

    Good question. I have been digging into the code for a while without finding a way to do this, and the developer didn't know either. 

    It might be possible to link to a user string in the interface descriptor iInterface field, but I need some more time to try this out. I will try to get back to you on this by Monday next week. 

    Best regards
    Torbjørn

  • Hi, 

    Thanks for your quick response. Another question, it seems that USBD would not work properly with Softdevice. As you would be able to see in  nrf_drv_power.c. Any thought? Thanks.

    #ifdef SOFTDEVICE_PRESENT
    if (nrf_sdh_is_enabled())
    {
    return NRF_ERROR_INVALID_STATE;
    }
    #endif

  • Hi, 

    I have got a workaround for the issue that USBD would conflict with Softdevice. I swap to the example under the folder /example/peripheral/usbd_ble_uart and integrated a CLI CDC ACM instance over it. Job done although I still take more time to drill up how this example gets USBD along with Softdevice.  

    Still in pursuing to modify the USB serial display name.

Related