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