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.
Any pointers are appreciated! Thank you!
Similar thread with some hints? devzone.nordicsemi.com/.../257508