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

Generic USB HID IN Report failing to receive on macOS

Hello!

Hardware: nrf52840
IDE: Segger Embedded Studio
SDK: nrf5 SDK 17.0.2

I've been having trouble getting IN reports to work on macOS. I based my code off of the generic mouse example in /example/peripheral/usbd_hid_generic/ to set an IN report all the time. (On the actual software, it sends every 1-2 ms, but that's besides the issue). The program simply puts a report with 0xAB as its first byte and a number from 0x00 to 0xFF in its second byte (that increases every 50 ms using a timer).

#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>

#include "nrf.h"
#include "app_util_platform.h"
#include "nrf_drv_usbd.h"
#include "nrf_drv_clock.h"
#include "nrf_gpio.h"
#include "nrf_drv_power.h"

#include "app_timer.h"
#include "app_usbd.h"
#include "app_usbd_core.h"
#include "app_usbd_hid_generic.h"
#include "app_usbd_hid_mouse.h"
#include "app_usbd_hid_kbd.h"
#include "app_error.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"

#if NRF_CLI_ENABLED
/**
 * @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);
#endif

/**
 * @brief Enable USB power detection
 */
#ifndef USBD_POWER_DETECTION
#define USBD_POWER_DETECTION true
#endif

/**
 * @brief HID generic class interface number.
 * */
#define HID_GENERIC_INTERFACE  0

/**
 * @brief HID generic class endpoint number.
 * */
#define HID_GENERIC_EPIN       NRF_DRV_USBD_EPIN1

/**
 * @brief Number of reports defined in report descriptor.
 */
#define REPORT_IN_QUEUE_SIZE    1

/**
 * @brief Size of maximum output report. HID generic class will reserve
 *        this buffer size + 1 memory space. 
 *
 * Maximum value of this define is 63 bytes. Library automatically adds
 * one byte for report ID. This means that output report size is limited
 * to 64 bytes.
 */
#define REPORT_OUT_MAXSIZE  0

/**
 * @brief Feature report maximum size. HID generic class will reserve
 *        this buffer size + 1 memory space. 
 */
#define REPORT_FEATURE_MAXSIZE  31

/**
 * @brief HID generic class endpoints count.
 * */
#define HID_GENERIC_EP_COUNT  1

/**
 * @brief List of HID generic class endpoints.
 * */
#define ENDPOINT_LIST()                                      \
(                                                            \
        HID_GENERIC_EPIN                                     \
)

// Teensy compatible
#define USBD_GENERIC_REPORT_DESCRIPTOR {                        \
        0x06, 0xAB, 0xFF,               /* 0xFFAB */            \
        0x0A, 0x00, 0x02,               /* 0x0200 */            \
        0xA1, 0x01,                     /* Collection 0x01 */   \
        0x75, 0x08,                     /*   report size = 8 bits */  \
        0x15, 0x00,                     /*   logical minimum = 0 */   \
        0x26, 0xFF, 0x00,               /*   logical maximum = 255 */ \
        0x95, 64,                       /*   report count */    \
        0x09, 0x01,                     /*   usage */           \
        0x81, 0x02,                     /*   Input (array) */   \
        0x95, 64,                       /*   report count */    \
        0x09, 0x02,                     /*   usage */           \
        0x91, 0x02,                     /*   Output (array) */  \
        0xC0                            /* end collection */    \
}

/**
 * @brief User event handler.
 * */
static void hid_user_ev_handler(app_usbd_class_inst_t const * p_inst,
                                app_usbd_hid_user_event_t event);

/**
 * @brief Reuse HID mouse report descriptor for HID generic class
 */
APP_USBD_HID_GENERIC_SUBCLASS_REPORT_DESC(desc, USBD_GENERIC_REPORT_DESCRIPTOR);

static const app_usbd_hid_subclass_desc_t * reps[] = {&desc};

/*lint -save -e26 -e64 -e123 -e505 -e651*/

/**
 * @brief Global HID generic instance
 */
APP_USBD_HID_GENERIC_GLOBAL_DEF(m_app_hid_generic,
                                HID_GENERIC_INTERFACE,
                                hid_user_ev_handler,
                                ENDPOINT_LIST(),
                                reps,
                                REPORT_IN_QUEUE_SIZE,
                                REPORT_OUT_MAXSIZE,
                                REPORT_FEATURE_MAXSIZE,
                                APP_USBD_HID_SUBCLASS_BOOT,
                                APP_USBD_HID_PROTO_GENERIC
                                );

/*lint -restore*/

/**
 * @brief Mark the ongoing transmission
 *
 * Marks that the report buffer is busy and cannot be used until transmission finishes
 * or invalidates (by USB reset or suspend event).
 */
static bool m_report_pending;

/**
 * @brief Timer to repeat mouse move
 */
APP_TIMER_DEF(m_mouse_move_timer);

uint8_t count = 0;

/**
 * @brief Internal function that process mouse state
 *
 * This function checks current mouse state and tries to send
 * new report if required.
 * If report sending was successful it clears accumulated positions
 * and mark last button state that was transfered.
 */
static void hid_generic_mouse_process_state(void)
{
    if (m_report_pending)
        return;

    ret_code_t ret;
    static uint8_t report[64] = {0};
    /* We have some status changed that we need to transfer */
    report[0] = 0xAB;
    report[1] = count;
    /* Start the transfer */
    ret = app_usbd_hid_generic_in_report_set(
        &m_app_hid_generic,
        report,
        sizeof(report));
    if (ret == NRF_SUCCESS)
    {
        printf("In report set SUCCESS\n");
        m_report_pending = true;
    }
    else
    {
        printf("In report set ERROR: %d\n", ret);
    }
}


/**
 * @brief Class specific event handler.
 *
 * @param p_inst    Class instance.
 * @param event     Class specific event.
 * */
static void hid_user_ev_handler(app_usbd_class_inst_t const * p_inst,
                                app_usbd_hid_user_event_t event)
{
    switch (event)
    {
        case APP_USBD_HID_USER_EVT_OUT_REPORT_READY:
        {
            /* No output report defined for this example.*/
            ASSERT(0);
            break;
        }
        case APP_USBD_HID_USER_EVT_IN_REPORT_DONE:
        {
            printf("In report done\n");
            m_report_pending = false;
            hid_generic_mouse_process_state();
            break;
        }
        case APP_USBD_HID_USER_EVT_SET_BOOT_PROTO:
        {
            UNUSED_RETURN_VALUE(hid_generic_clear_buffer(p_inst));
            NRF_LOG_INFO("SET_BOOT_PROTO");
            break;
        }
        case APP_USBD_HID_USER_EVT_SET_REPORT_PROTO:
        {
            UNUSED_RETURN_VALUE(hid_generic_clear_buffer(p_inst));
            NRF_LOG_INFO("SET_REPORT_PROTO");
            break;
        }
        default:
            break;
    }
}

/**
 * @brief USBD library specific event handler.
 *
 * @param event     USBD library event.
 * */
static void usbd_user_ev_handler(app_usbd_event_type_t event)
{
    switch (event)
    {
        case APP_USBD_EVT_DRV_SOF:
            break;
        case APP_USBD_EVT_DRV_RESET:
            m_report_pending = false;
            break;
        case APP_USBD_EVT_DRV_SUSPEND:
            m_report_pending = false;
            app_usbd_suspend_req(); // Allow the library to put the peripheral into sleep mode
            break;
        case APP_USBD_EVT_DRV_RESUME:
            m_report_pending = false;
            break;
        case APP_USBD_EVT_STARTED:
            m_report_pending = false;
            break;
        case APP_USBD_EVT_STOPPED:
            app_usbd_disable();
            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 mouse_move_timer_handler(void * p_context)
{
    printf("count: %d\n", count);
    count++;
}

static ret_code_t idle_handle(app_usbd_class_inst_t const * p_inst, uint8_t report_id)
{
    switch (report_id)
    {
        case 0:
        {
            uint8_t report[] = {0xBE, 0xEF};
            return app_usbd_hid_generic_idle_report_set(
              &m_app_hid_generic,
              report,
              sizeof(report));
        }
        default:
            return NRF_ERROR_NOT_SUPPORTED;
    }
    
}

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);

    ret = app_timer_create(&m_mouse_move_timer, APP_TIMER_MODE_REPEATED, mouse_move_timer_handler);
    APP_ERROR_CHECK(ret);
    
    ret = app_timer_start(m_mouse_move_timer, APP_TIMER_TICKS(200), NULL);
    APP_ERROR_CHECK(ret);

    printf("Hello USB!\n");

    ret = app_usbd_init(&usbd_config);
    APP_ERROR_CHECK(ret);

    NRF_LOG_INFO("USBD HID generic example started.");

    app_usbd_class_inst_t const * class_inst_generic;
    class_inst_generic = app_usbd_hid_generic_class_inst_get(&m_app_hid_generic);

    ret = hid_generic_idle_handler_set(class_inst_generic, idle_handle);
    APP_ERROR_CHECK(ret);

    ret = app_usbd_class_append(class_inst_generic);
    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 */
        }
        hid_generic_mouse_process_state();

        UNUSED_RETURN_VALUE(NRF_LOG_PROCESS());
        /* Sleep CPU only if there was no interrupt since last loop processing */
        __WFE();
    }
}

I tested this using hidapitester (https://github.com/todbot/hidapitester) on both Windows 10 and macOS Catalina on the same MacBook Pro. 

Using the command: 

hidapitester --vidpid 1915:520C -t 50 -l 64 --open --read-input-forever

On Windows 10, the output is as expected, AB,00, ..., AB,01, ..., AB,02, ... etc.

On macOS however, absolutely nothing. 0 bytes read constantly, like it was never sent. APP_USBD_HID_USER_EVT_IN_REPORT_DONE is never called.

I've got this slightly working by hammering app_usbd_hid_generic_in_report_set() every ms (instead of waiting for a successful REPORT_DONE), but it ends up crashing and reinitializing USB every few reports.

And to rule out any macOS issues, similar programs running on both a Teensy 4.0 and a Cypress PSOC6 work flawlessly. Slight smile

Any pointers are appreciated! Thank you!

Similar thread with some hints? devzone.nordicsemi.com/.../257508

Parents Reply Children
  • I managed to fix it.

    The subclass in APP_USBD_HID_GENERIC_GLOBAL_DEF must not be APP_USBD_HID_SUBCLASS_BOOT. I used APP_USBD_HID_SUBCLASS_NONE instead. This was actually an unrelated fix to get it to work with my Android system.

    Full define:

    APP_USBD_HID_GENERIC_GLOBAL_DEF(m_app_hid_generic,
                                    HID_GENERIC_INTERFACE,
                                    hid_user_ev_handler,
                                    ENDPOINT_LIST(),
                                    reps,
                                    REPORT_IN_QUEUE_SIZE,
                                    REPORT_OUT_MAXSIZE,
                                    REPORT_FEATURE_MAXSIZE,
                                    APP_USBD_HID_SUBCLASS_NONE,
                                    APP_USBD_HID_PROTO_GENERIC
                                    );
Related