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
  • Hi!

    Apologies for the very late answer. I've been trying to fix the issue, though no success yet. Have you been able to debug your code in SES to see if it gives any indications to what might be happening?

    I did have some interesting observations, as the application did start to work after being connected to the mac for some time. I'm not sure what triggered the behavior, but it seems like the device is waiting for some kind of event before it starts working. Will report back when I know more.

    Best regards,
    Carl Richard

Children
Related