diff --git a/ports/stm32/usb.c b/ports/stm32/usb.c
index 2d1f6084fe044a856dda3d5cda9a52d289c31c6d..3308f074498333e077b09058adad2951c9d45f5f 100644
--- a/ports/stm32/usb.c
+++ b/ports/stm32/usb.c
@@ -787,6 +787,11 @@ STATIC mp_obj_t pyb_usb_hid_recv(size_t n_args, const mp_obj_t *args, mp_map_t *
     // receive the data
     int ret = usbd_hid_rx(&self->usb_dev->usbd_hid_itf, vstr.len, (uint8_t*)vstr.buf, vals[1].u_int);
 
+    if (ret < 0) {
+        // error, just return 0/empty bytes
+        ret = 0;
+    }
+
     // return the received data
     if (o_ret != MP_OBJ_NULL) {
         return mp_obj_new_int(ret); // number of bytes read into given buffer
diff --git a/ports/stm32/usbd_hid_interface.c b/ports/stm32/usbd_hid_interface.c
index 033d83ea648434ebfdf0723c7e28478d2cd1a402..386cf34d70bc36c59511d60fbf0b8a5d7c892d85 100644
--- a/ports/stm32/usbd_hid_interface.c
+++ b/ports/stm32/usbd_hid_interface.c
@@ -1,113 +1,70 @@
 /*
  * This file is part of the MicroPython project, http://micropython.org/
  *
- * Taken from ST Cube library and heavily modified.  See below for original
- * copyright header.
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Damien P. George
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
  */
 
-/**
-  ******************************************************************************
-  * @file    USB_Device/CDC_Standalone/Src/usbd_cdc_interface.c
-  * @author  MCD Application Team
-  * @version V1.0.1
-  * @date    26-February-2014
-  * @brief   Source file for USBD CDC interface
-  ******************************************************************************
-  * @attention
-  *
-  * <h2><center>&copy; COPYRIGHT(c) 2014 STMicroelectronics</center></h2>
-  *
-  * Licensed under MCD-ST Liberty SW License Agreement V2, (the "License");
-  * You may not use this file except in compliance with the License.
-  * You may obtain a copy of the License at:
-  *
-  *        http://www.st.com/software_license_agreement_liberty_v2
-  *
-  * Unless required by applicable law or agreed to in writing, software
-  * distributed under the License is distributed on an "AS IS" BASIS,
-  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  * See the License for the specific language governing permissions and
-  * limitations under the License.
-  *
-  ******************************************************************************
-  */
-
-/* Includes ------------------------------------------------------------------*/
-
-#include <stdint.h>
-
 #include "usbd_hid_interface.h"
 
-#include "py/obj.h"
-#include "irq.h"
+#include "py/mperrno.h"
+#include "py/mphal.h"
 #include "usb.h"
 
 #if MICROPY_HW_USB_HID
 
 uint8_t *usbd_hid_init(usbd_hid_state_t *hid_in) {
     usbd_hid_itf_t *hid = (usbd_hid_itf_t*)hid_in;
-
-    hid->current_read_buffer = 0;
-    hid->last_read_len = 0;
-    hid->current_write_buffer = 0;
-
-    // Return the buffer to place the first USB OUT packet
-    return hid->buffer[hid->current_write_buffer];
+    hid->report_in_len = USBD_HID_REPORT_INVALID;
+    return &hid->report_in_buf[0]; // location to place first incoming report
 }
 
-// Data received over USB OUT endpoint is processed here.
-// len: number of bytes received into the buffer we passed to USBD_HID_ReceivePacket
-// Returns USBD_OK if all operations are OK else USBD_FAIL
 int8_t usbd_hid_receive(usbd_hid_state_t *hid_in, size_t len) {
+    // Incoming report: save the length but don't schedule next report until user reads this one
     usbd_hid_itf_t *hid = (usbd_hid_itf_t*)hid_in;
-
-    hid->current_write_buffer = !hid->current_write_buffer;
-    hid->last_read_len = len;
-    // initiate next USB packet transfer, to append to existing data in buffer
-    USBD_HID_ReceivePacket(&hid->base, hid->buffer[hid->current_write_buffer]);
-    // Set NAK to indicate we need to process read buffer
-    USBD_HID_SetNAK(&hid->base);
+    hid->report_in_len = len;
     return USBD_OK;
 }
 
-// Returns number of ready rx buffers.
-int usbd_hid_rx_num(usbd_hid_itf_t *hid) {
-    return hid->current_read_buffer != hid->current_write_buffer;
-}
-
-// timout in milliseconds.
-// Returns number of bytes read from the device.
-int usbd_hid_rx(usbd_hid_itf_t *hid, size_t len, uint8_t *buf, uint32_t timeout) {
-    // Wait until we have buffer to read
-    uint32_t start = HAL_GetTick();
-    while (hid->current_read_buffer == hid->current_write_buffer) {
-        // Wraparound of tick is taken care of by 2's complement arithmetic.
-        if (HAL_GetTick() - start >= timeout) {
-            // timeout
-            return 0;
+int usbd_hid_rx(usbd_hid_itf_t *hid, size_t len, uint8_t *buf, uint32_t timeout_ms) {
+    // Wait for an incoming report
+    uint32_t t0 = mp_hal_ticks_ms();
+    while (hid->report_in_len == USBD_HID_REPORT_INVALID) {
+        if (mp_hal_ticks_ms() - t0 >= timeout_ms || query_irq() == IRQ_STATE_DISABLED) {
+            return -MP_ETIMEDOUT;
         }
-        if (query_irq() == IRQ_STATE_DISABLED) {
-            // IRQs disabled so buffer will never be filled; return immediately
-            return 0;
-        }
-        __WFI(); // enter sleep mode, waiting for interrupt
-    }
-
-    // There is not enough space in buffer
-    if (len < hid->last_read_len) {
-        return 0;
+        MICROPY_EVENT_POLL_HOOK
     }
 
-    // Copy bytes from device to user buffer
-    int read_len = hid->last_read_len;
-    memcpy(buf, hid->buffer[hid->current_read_buffer], read_len);
-    hid->current_read_buffer = !hid->current_read_buffer;
+    // Copy bytes from report to user buffer
+    int n = MIN(len, hid->report_in_len);
+    memcpy(buf, &hid->report_in_buf[0], n);
 
-    // Clear NAK to indicate we are ready to read more data
-    USBD_HID_ClearNAK(&hid->base);
+    // Schedule to receive next incoming report
+    hid->report_in_len = USBD_HID_REPORT_INVALID;
+    USBD_HID_ReceivePacket(&hid->base, &hid->report_in_buf[0]);
 
-    // Success, return number of bytes read
-    return read_len;
+    // Return number of bytes read into user buffer
+    return n;
 }
 
 #endif // MICROPY_HW_USB_HID
diff --git a/ports/stm32/usbd_hid_interface.h b/ports/stm32/usbd_hid_interface.h
index 777fa9340305239772f45cab8ce938ec61bccca6..5d2c9ad870f153b4deb51407f322a5b6a4288ff6 100644
--- a/ports/stm32/usbd_hid_interface.h
+++ b/ports/stm32/usbd_hid_interface.h
@@ -4,18 +4,22 @@
 #ifndef MICROPY_INCLUDED_STM32_USBD_HID_INTERFACE_H
 #define MICROPY_INCLUDED_STM32_USBD_HID_INTERFACE_H
 
+#include <stdint.h>
 #include "usbd_cdc_msc_hid.h"
 
+#define USBD_HID_REPORT_INVALID ((size_t)-1)
+
 typedef struct _usbd_hid_itf_t {
     usbd_hid_state_t base; // state for the base HID layer
 
-    uint8_t buffer[2][HID_DATA_FS_MAX_PACKET_SIZE]; // pair of buffers to read individual packets into
-    int8_t current_read_buffer; // which buffer to read from
-    uint32_t last_read_len; // length of last read
-    int8_t current_write_buffer; // which buffer to write to
+    volatile size_t report_in_len;
+    uint8_t report_in_buf[HID_DATA_FS_MAX_PACKET_SIZE];
 } usbd_hid_itf_t;
 
-int usbd_hid_rx_num(usbd_hid_itf_t *hid);
-int usbd_hid_rx(usbd_hid_itf_t *hid, size_t len, uint8_t *buf, uint32_t timeout);
+static inline int usbd_hid_rx_num(usbd_hid_itf_t *hid) {
+    return hid->report_in_len != USBD_HID_REPORT_INVALID;
+}
+
+int usbd_hid_rx(usbd_hid_itf_t *hid, size_t len, uint8_t *buf, uint32_t timeout_ms);
 
 #endif // MICROPY_INCLUDED_STM32_USBD_HID_INTERFACE_H
diff --git a/ports/stm32/usbdev/class/src/usbd_cdc_msc_hid.c b/ports/stm32/usbdev/class/src/usbd_cdc_msc_hid.c
index 32ce4ca8755eac874f68775384fa96b85a562413..e13a93ddbc9e16c274e762a837157d08e5594c69 100644
--- a/ports/stm32/usbdev/class/src/usbd_cdc_msc_hid.c
+++ b/ports/stm32/usbdev/class/src/usbd_cdc_msc_hid.c
@@ -1190,30 +1190,6 @@ uint8_t USBD_HID_SendReport(usbd_hid_state_t *hid, uint8_t *report, uint16_t len
     return USBD_FAIL;
 }
 
-uint8_t USBD_HID_SetNAK(usbd_hid_state_t *hid) {
-    // get USBx object from pdev (needed for USBx_OUTEP macro below)
-    PCD_HandleTypeDef *hpcd = hid->usbd->pdev->pData;
-    USB_OTG_GlobalTypeDef *USBx = hpcd->Instance;
-    #if defined(STM32H7)
-    uint32_t USBx_BASE = (uint32_t)USBx;
-    #endif
-    // set NAK on HID OUT endpoint
-    USBx_OUTEP(HID_OUT_EP_WITH_CDC)->DOEPCTL |= USB_OTG_DOEPCTL_SNAK;
-    return USBD_OK;
-}
-
-uint8_t USBD_HID_ClearNAK(usbd_hid_state_t *hid) {
-    // get USBx object from pdev (needed for USBx_OUTEP macro below)
-    PCD_HandleTypeDef *hpcd = hid->usbd->pdev->pData;
-    USB_OTG_GlobalTypeDef *USBx = hpcd->Instance;
-    #if defined(STM32H7)
-    uint32_t USBx_BASE = (uint32_t)USBx;
-    #endif
-    // clear NAK on HID OUT endpoint
-    USBx_OUTEP(HID_OUT_EP_WITH_CDC)->DOEPCTL |= USB_OTG_DOEPCTL_CNAK;
-    return USBD_OK;
-}
-
 #endif
 
 // CDC/MSC/HID interface class callback structure