diff --git a/CHANGELOG.md b/CHANGELOG.md
index b35050cb9ea9b3d0c317d855da707de4cd669c16..1951bda2c6af8670d8bd6ff2b1fa18407c6f311c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,35 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 ## [Unreleased]
 
 
+## [v1.7] - 2019-08-24 21:48 - [Garlic]
+[Garlic]: https://card10.badge.events.ccc.de/release/card10-v1.7-Garlic.zip
+
+### Added
+- **ESB**: Epic Serial Bus (Better than USB!), stability improvements of the
+  USB module.  Preparation for mass-storage access in the Firmware.
+- Enabled the Hardware Watchdog;  Card10 will reset itself if the firmware crashes
+- Log messages when BLE is pairing / connected.
+- The name of the offending app is printed to the serial console, if an app
+  crashes the metatdata parser.
+
+### Changed
+- Improved log messages in cases of lock-contention.
+- Menu will show an error message if a crash occurs.
+
+### Fixed
+- Fixed race-conditions in serial writes by using a queue.
+- "Card10 Nickname" crashing if only `nickname.txt` exists.
+- Lockup when debug prints are enabled.
+- Delayed BHI160 startup a bit so the PMIC task can check the battery first.
+- Relaxed the PMIC lock-timeouts so other task can take a little more time.
+- Fixed off-by-one error in `gfx_line()`.
+- Fixed the API interrupts sometimes getting stuck.
+- Fixed binary building on MacOS.
+- Fixed race-conditions in serial console prints by introducing a queue.
+- Fixed API & MAX30001 mutexes being initialized late sometimes.
+- Fixed wrong stripe width in bi flag.
+
+
 ## [v1.6] - 2019-08-23 20:30 - [Fennel]
 [Fennel]: https://card10.badge.events.ccc.de/release/card10-v1.6-Fennel.zip
 
@@ -124,7 +153,8 @@ fbf7c8c0 fix(menu.py) Refactored menu.py based on !138
 ## [v1.0] - 2019-08-21 00:50
 Initial release.
 
-[Unreleased]: https://git.card10.badge.events.ccc.de/card10/firmware/compare/v1.6...master
+[Unreleased]: https://git.card10.badge.events.ccc.de/card10/firmware/compare/v1.7...master
+[v1.7]: https://git.card10.badge.events.ccc.de/card10/firmware/compare/v1.6...v1.7
 [v1.6]: https://git.card10.badge.events.ccc.de/card10/firmware/compare/v1.5...v1.6
 [v1.5]: https://git.card10.badge.events.ccc.de/card10/firmware/compare/v1.4...v1.5
 [v1.4]: https://git.card10.badge.events.ccc.de/card10/firmware/compare/v1.3...v1.4
diff --git a/Documentation/bluetooth/file-transfer.rst b/Documentation/bluetooth/file-transfer.rst
index 439237b883ea69a2655f30511612949c64ab4b95..49191d2da8f25e610d0ef9f96696b73692a2d74f 100644
--- a/Documentation/bluetooth/file-transfer.rst
+++ b/Documentation/bluetooth/file-transfer.rst
@@ -77,9 +77,11 @@ CHUNK_ACK:
 ===== ===
   0   1-4
 ----- ---
-  C   CRC
+  C   CRC(*)
 ===== ===
 
+CRC32 of the whole CHUNK packet including first byte, offset and payload.
+
 FINISH:
 
 === ===
diff --git a/Documentation/conf.py b/Documentation/conf.py
index ae5361ac408293b054274b0051bbad963da2a738..bb211aa0cf679dd8486cc21ed5bcbc56418a3d7f 100644
--- a/Documentation/conf.py
+++ b/Documentation/conf.py
@@ -88,9 +88,11 @@ html_context = {
 
 # -- Options for Auto-Doc ---------------------------------------------------- {{{
 autodoc_mock_imports = [
+    "buttons",
+    "interrupt",
     "sys_display",
     "sys_leds",
-    "buttons",
+    "sys_max30001",
     "ucollections",
     "urandom",
     "utime",
diff --git a/Documentation/how-to-build.rst b/Documentation/how-to-build.rst
index 1db20eda46d7496588a15b56576b9f2a071821b3..0b616ab2624888f18b7460221d229e6a3513a57d 100644
--- a/Documentation/how-to-build.rst
+++ b/Documentation/how-to-build.rst
@@ -28,20 +28,23 @@ Dependencies
     .. code-block:: shell-session
 
         dnf install arm-none-eabi-gcc arm-none-eabi-binutils arm-none-eabi-newlib
-        
+
   - macOS (Note: The card10 firmware team used Linux so far. macOS recommendations here are experimental.) 
-    
-    You can use `Homebrew`_ to install the required tools.
-    The version of the Arm crosscompiler tool chain is quite important; with the wrong version, e.g. strip and/or ld might throw strange errors.
-    
+
+    You can use `Homebrew`_ to install the required tools.  The version of the
+    ARM crosscompiler tool chain is quite important; with the wrong version,
+    e.g. strip and/or ld might throw strange errors.
+
     .. code-block:: shell-session
-            
+
         brew tap px4/px4
         brew install px4/px4/gcc-arm-none-eabi-63
         brew install coreutils
 
+    .. _Homebrew: https://brew.sh/
+
   - Alternative: Download `ARM's GNU toolchain`_.  **TODO**
-.. _Homebrew: https://brew.sh/
+
 
 * **python3**:  For meson and various scripts needed for building.
 * **meson** (>0.43.0) & **ninja**:  Unfortunately most distros only have very old versions
@@ -140,3 +143,13 @@ In order to do a rebuild you can issue a clean command to ninja via
   $ ninja -C build/ -t clean
 
 Otherwise, rerunning ``./bootstrap.sh`` will also clean the build-directory.
+
+.. note::
+
+   If you try to flash pycardium_epicardium.bin (renamed to card10.bin) 
+   and the bootloader does not finish updating, the file might be too large.
+   ~700kB is the normal size, but problems were reported where the file size 
+   was >1MB. This was caused by the ``tr`` tool in the build process 
+   (it's supposed to create a large file with 0xff in it) - this requires the 
+   LC_ALL environment variable to be not set, or set to "C" 
+   (but not UTF8 or similar).
\ No newline at end of file
diff --git a/Documentation/pycardium/max30001.rst b/Documentation/pycardium/max30001.rst
index 1fb0ddb954a53dfd04282e16aa549a2de5a9a726..5eccd6a448ddda30d663dedbaab266f3bfd89d74 100644
--- a/Documentation/pycardium/max30001.rst
+++ b/Documentation/pycardium/max30001.rst
@@ -1,5 +1,5 @@
 ``max30001`` - MAX30001
-=====================
+=======================
 
 .. automodule:: max30001
    :members:
diff --git a/bootloader/build_multi_image.sh b/bootloader/build_multi_image.sh
index e6ae042a08dcd94205f55977086869b20f50299b..b584dfcc5ff92411275f7ced9f9a74c81ce45c0a 100755
--- a/bootloader/build_multi_image.sh
+++ b/bootloader/build_multi_image.sh
@@ -6,7 +6,7 @@ BIN1="$2"
 BIN2="$3"
 BINOUT="$4"
 
-dd if=/dev/zero ibs=1k count=448 2>/dev/null | LANG=C LC_CTYPE=C tr "\000" "\377" > "$BINOUT"
+dd if=/dev/zero ibs=1k count=448 2>/dev/null | LANG=C LC_CTYPE=C LC_ALL=C LC_COLLATE=C tr "\000" "\377" > "$BINOUT"
 dd if="$BIN1" of="$BINOUT" conv=notrunc 2>/dev/null
 dd if="$BIN2" >> "$BINOUT" 2>/dev/null
 
diff --git a/epicardium/FreeRTOSConfig.h b/epicardium/FreeRTOSConfig.h
index b92b12b9ec223da13082f23fcad849a1337ba316..f1d3aa46915e9c4373d281a1a4ab37707a918bf9 100644
--- a/epicardium/FreeRTOSConfig.h
+++ b/epicardium/FreeRTOSConfig.h
@@ -52,7 +52,7 @@
 #define INCLUDE_vTaskSuspend        1
 #define INCLUDE_vTaskDelay          1
 #define INCLUDE_uxTaskGetStackHighWaterMark 1
-
+#define INCLUDE_xTimerPendFunctionCall 1
 /* Allow static allocation of data structures */
 #define configSUPPORT_STATIC_ALLOCATION 1
 
diff --git a/epicardium/api/common.h b/epicardium/api/common.h
index ff0c9f3abf4da6822ca89c890c3a6bb55fab7ce6..5ebb29431fa09580fe44875ee0f76f908a21180a 100644
--- a/epicardium/api/common.h
+++ b/epicardium/api/common.h
@@ -38,7 +38,7 @@ struct api_call_mem {
 	api_id_t id;
 
 	/* ID of the current interrupt */
-	api_int_id_t int_id;
+	volatile api_int_id_t int_id;
 
 	/*
 	 * Buffer for arguments/return value.  This buffer will be
diff --git a/epicardium/ble/app/app_main.c b/epicardium/ble/app/app_main.c
index f795c4e7222adfeb7cbe5b19e15f3c1659b7827c..b279c758bb5857eb3d649fe7cc8c36f7e16ef35f 100644
--- a/epicardium/ble/app/app_main.c
+++ b/epicardium/ble/app/app_main.c
@@ -37,6 +37,8 @@
 #include "app_main.h"
 #include "app_ui.h"
 
+#include "modules/log.h"
+
 /**************************************************************************************************
   Global Variables
 **************************************************************************************************/
@@ -283,6 +285,8 @@ void AppHandleNumericComparison(dmSecCnfIndEvt_t *pCnfInd)
 {
   uint32_t confirm = DmSecGetCompareValue(pCnfInd->confirm);
 
+  LOG_INFO("ble", "Confirm Value: %ld", confirm);
+
   /* display confirmation value */
   AppUiDisplayConfirmValue(confirm);
 
diff --git a/epicardium/ble/ble_main.c b/epicardium/ble/ble_main.c
index af041f9cfd7c27fcd69774d3d7a9eaf882c990c6..07783d8619cc05c688db056ec0d17d06b72413dc 100644
--- a/epicardium/ble/ble_main.c
+++ b/epicardium/ble/ble_main.c
@@ -38,6 +38,8 @@
 #include "hrps/hrps_api.h"
 #include "rscp/rscp_api.h"
 
+#include "modules/log.h"
+
 /**************************************************************************************************
   Macros
 **************************************************************************************************/
@@ -373,6 +375,7 @@ static void bleSetup(bleMsg_t *pMsg)
 static void bleProcMsg(bleMsg_t *pMsg)
 {
   uint8_t uiEvent = APP_UI_NONE;
+  hciLeConnCmplEvt_t *connOpen;
 
   switch(pMsg->hdr.event)
   {
@@ -395,36 +398,88 @@ static void bleProcMsg(bleMsg_t *pMsg)
       break;
 
     case DM_ADV_START_IND:
+      LOG_INFO("ble", "Advertisement started");
       uiEvent = APP_UI_ADV_START;
       break;
 
     case DM_ADV_STOP_IND:
+      LOG_INFO("ble", "Advertisement stopped");
       uiEvent = APP_UI_ADV_STOP;
       break;
 
     case DM_CONN_OPEN_IND:
+      connOpen = &pMsg->dm.connOpen;
+      LOG_INFO("ble", "connection from %02X:%02X:%02X:%02X:%02X:%02X opened",
+               connOpen->peerAddr[0], connOpen->peerAddr[1],
+               connOpen->peerAddr[2], connOpen->peerAddr[3],
+               connOpen->peerAddr[4], connOpen->peerAddr[5]);
       BasProcMsg(&pMsg->hdr);
       uiEvent = APP_UI_CONN_OPEN;
       break;
 
     case DM_CONN_CLOSE_IND:
+      switch (pMsg->dm.connClose.reason)
+      {
+        case HCI_ERR_CONN_TIMEOUT:
+          LOG_INFO("ble", "Connection closed (0x%02X), Connection timeout",
+                   pMsg->dm.connClose.reason);
+          break;
+        case HCI_ERR_LOCAL_TERMINATED:
+          LOG_INFO("ble", "Connection closed (0x%02X), Connection terminated by local host",
+                   pMsg->dm.connClose.reason);
+          break;
+        case HCI_ERR_REMOTE_TERMINATED:
+          LOG_INFO("ble", "Connection closed (0x%02X), Remote user terminated connection",
+                   pMsg->dm.connClose.reason);
+          break;
+        case HCI_ERR_CONN_FAIL:
+          LOG_INFO("ble", "Connection closed (0x%02X), Connection failed to be established",
+                   pMsg->dm.connClose.reason);
+          break;
+        case HCI_ERR_MIC_FAILURE:
+          LOG_INFO("ble", "Connection closed (0x%02X), Connection terminated due to MIC failure",
+                   pMsg->dm.connClose.reason);
+          break;
+        default:
+          LOG_INFO("ble", "Connection closed (0x%02X)",
+                   pMsg->dm.connClose.reason);
+          break;
+      }
       bleClose(pMsg);
       uiEvent = APP_UI_CONN_CLOSE;
       break;
 
     case DM_SEC_PAIR_CMPL_IND:
+      LOG_INFO("ble", "Secure pairing successful, auth: 0x%02X",
+               pMsg->dm.pairCmpl.auth);
       uiEvent = APP_UI_SEC_PAIR_CMPL;
       break;
 
     case DM_SEC_PAIR_FAIL_IND:
+      switch (pMsg->hdr.status) {
+        case SMP_ERR_TIMEOUT:
+          LOG_INFO("ble", "Secure pairing failed (0x%02X), Transaction timeout",
+                   pMsg->hdr.status);
+          break;
+        case SMP_ERR_ATTEMPTS:
+          LOG_INFO("ble", "Secure pairing failed (0x%02X), Repeated attempts",
+                   pMsg->hdr.status);
+          break;
+        default:
+          LOG_INFO("ble", "Secure pairing failed (0x%02X)",
+                   pMsg->hdr.status);
+          break;
+      }
       uiEvent = APP_UI_SEC_PAIR_FAIL;
       break;
 
     case DM_SEC_ENCRYPT_IND:
+      LOG_INFO("ble", "Encrypted handshake successful");
       uiEvent = APP_UI_SEC_ENCRYPT;
       break;
 
     case DM_SEC_ENCRYPT_FAIL_IND:
+      LOG_INFO("ble", "Encrypted handshake failed");
       uiEvent = APP_UI_SEC_ENCRYPT_FAIL;
       break;
 
@@ -441,6 +496,7 @@ static void bleProcMsg(bleMsg_t *pMsg)
       break;
 
     case DM_HW_ERROR_IND:
+      LOG_ERR("ble", "HW Error");
       uiEvent = APP_UI_HW_ERROR;
       break;
 
diff --git a/epicardium/cdcacm.c b/epicardium/cdcacm.c
deleted file mode 100644
index 1cbbb154452a9ac96b9300bdded5071353121260..0000000000000000000000000000000000000000
--- a/epicardium/cdcacm.c
+++ /dev/null
@@ -1,382 +0,0 @@
-/*******************************************************************************
- * Copyright (C) 2016 Maxim Integrated Products, Inc., All Rights Reserved.
- *
- * 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 MAXIM INTEGRATED 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.
- *
- * Except as contained in this notice, the name of Maxim Integrated
- * Products, Inc. shall not be used except as stated in the Maxim Integrated
- * Products, Inc. Branding Policy.
- *
- * The mere transfer of this software does not imply any licenses
- * of trade secrets, proprietary technology, copyrights, patents,
- * trademarks, maskwork rights, or any other form of intellectual
- * property whatsoever. Maxim Integrated Products, Inc. retains all
- * ownership rights.
- *
- * $Id: main.c 32120 2017-11-28 23:51:11Z lorne.smith $
- *
- *******************************************************************************
- */
-
-/**
- * @file    main.c
- * @brief   USB CDC-ACM example
- * @details This project creates a virtual COM port, which loops back data sent to it.
- *          Load the project, connect a cable from the PC to the USB connector
- *          on the Evaluation Kit, and observe that the PC now recognizes a new COM port.
- *          A driver for the COM port, if needed, is located in the Driver/ subdirectory.
- *
- */
-
-#include <stdio.h>
-#include <stddef.h>
-#include "mxc_config.h"
-#include "mxc_sys.h"
-#include "mxc_delay.h"
-#include "board.h"
-#include "led.h"
-#include "usb.h"
-#include "usb_event.h"
-#include "enumerate.h"
-#include "cdc_acm.h"
-#include "descriptors.h"
-
-#include "modules/modules.h"
-#include "modules/log.h"
-#include <errno.h>
-
-/***** Definitions *****/
-#define EVENT_ENUM_COMP MAXUSB_NUM_EVENTS
-
-#define BUFFER_SIZE 64
-
-#define STRINGIFY(x) #x
-#define TOSTRING(x) STRINGIFY(x)
-
-/***** Global Data *****/
-//SWYM: rename to CDC_xy or put into struct CDC_state
-volatile int configured; //SWYM: actually unused...
-volatile int suspended;
-volatile unsigned int event_flags;
-int remote_wake_en;
-
-/***** Function Prototypes *****/
-static int setconfig_callback(usb_setup_pkt *sud, void *cbdata);
-static int setfeature_callback(usb_setup_pkt *sud, void *cbdata);
-static int clrfeature_callback(usb_setup_pkt *sud, void *cbdata);
-static int event_callback(maxusb_event_t evt, void *data);
-static void usb_app_sleep(void);
-static void usb_app_wakeup(void);
-static int usb_read_callback(void);
-//static void echo_usb(void);
-
-/***** File Scope Variables *****/
-
-/* This EP assignment must match the Configuration Descriptor */
-static const acm_cfg_t acm_cfg = {
-	1,                    /* EP OUT */
-	MXC_USBHS_MAX_PACKET, /* OUT max packet size */
-	2,                    /* EP IN */
-	MXC_USBHS_MAX_PACKET, /* IN max packet size */
-	3,                    /* EP Notify */
-	MXC_USBHS_MAX_PACKET, /* Notify max packet size */
-};
-
-static volatile int usb_read_complete;
-
-int usb_startup_cb()
-{
-	const sys_cfg_usbhs_t sys_usbhs_cfg = NULL;
-	return SYS_USBHS_Init(&sys_usbhs_cfg);
-}
-
-int usb_shutdown_cb()
-{
-	return SYS_USBHS_Shutdown();
-}
-
-/* User-supplied function to delay usec micro-seconds */
-void delay_us(unsigned int usec)
-{
-	/* mxc_delay() takes unsigned long, so can't use it directly */
-	mxc_delay(usec);
-}
-
-/******************************************************************************/
-int cdcacm_init(void)
-{
-	maxusb_cfg_options_t usb_opts;
-
-	/* Initialize state */
-	configured     = 0;
-	suspended      = 0;
-	event_flags    = 0;
-	remote_wake_en = 0;
-
-	/* Start out in full speed */
-	usb_opts.enable_hs = 0;
-	usb_opts.delay_us =
-		delay_us; /* Function which will be used for delays */
-	usb_opts.init_callback     = usb_startup_cb;
-	usb_opts.shutdown_callback = usb_shutdown_cb;
-
-	/* Initialize the usb module */
-	if (usb_init(&usb_opts) != 0) {
-		LOG_ERR("cdcacm", "usb_init() failed");
-		return -EIO;
-	}
-
-	/* Initialize the enumeration module */
-	if (enum_init() != 0) {
-		LOG_ERR("cdcacm", "enum_init() failed");
-		return -EIO;
-	}
-
-	/* Register enumeration data */
-	enum_register_descriptor(
-		ENUM_DESC_DEVICE, (uint8_t *)&device_descriptor, 0
-	);
-	enum_register_descriptor(
-		ENUM_DESC_CONFIG, (uint8_t *)&config_descriptor, 0
-	);
-	enum_register_descriptor(ENUM_DESC_STRING, lang_id_desc, 0);
-	enum_register_descriptor(ENUM_DESC_STRING, mfg_id_desc, 1);
-	enum_register_descriptor(ENUM_DESC_STRING, prod_id_desc, 2);
-
-	/* Handle configuration */
-	enum_register_callback(ENUM_SETCONFIG, setconfig_callback, NULL);
-
-	/* Handle feature set/clear */
-	enum_register_callback(ENUM_SETFEATURE, setfeature_callback, NULL);
-	enum_register_callback(ENUM_CLRFEATURE, clrfeature_callback, NULL);
-
-	/* Initialize the class driver */
-	if (acm_init(&config_descriptor.comm_interface_descriptor) != 0) {
-		LOG_ERR("cdcacm", "acm_init() failed");
-		return -EIO;
-	}
-
-	/* Register callbacks */
-	usb_event_enable(MAXUSB_EVENT_NOVBUS, event_callback, NULL);
-	usb_event_enable(MAXUSB_EVENT_VBUS, event_callback, NULL);
-	acm_register_callback(ACM_CB_READ_READY, usb_read_callback);
-	usb_read_complete = 0;
-
-	/* Start with USB in low power mode */
-	usb_app_sleep();
-	/* TODO: Fix priority */
-	NVIC_SetPriority(USB_IRQn, 6);
-	NVIC_EnableIRQ(USB_IRQn);
-
-	return 0;
-}
-
-int cdcacm_num_read_avail(void)
-{
-	return acm_canread();
-}
-
-uint8_t cdcacm_read(void)
-{
-	while (acm_canread() <= 0) {
-	}
-
-	uint8_t buf;
-	acm_read(&buf, 1);
-	return buf;
-}
-
-void cdcacm_write(uint8_t *data, int len)
-{
-	static int lockup_disable = 0;
-	if (acm_present() && !lockup_disable) {
-		int ret = acm_write(data, len);
-		if (ret < 0) {
-			lockup_disable = 1;
-			LOG_ERR("cdcacm", "fifo lockup detected");
-		} else if (ret != len) {
-			LOG_WARN(
-				"cdcacm", "write length mismatch, got %d", ret
-			);
-		}
-	}
-}
-
-/******************************************************************************/
-#if 0
-static void echo_usb(void)
-{
-  int chars;
-  uint8_t buffer[BUFFER_SIZE];
-  
-  if ((chars = acm_canread()) > 0) {
-
-    if (chars > BUFFER_SIZE) {
-      chars = BUFFER_SIZE;
-    }
-
-    // Read the data from USB
-    if (acm_read(buffer, chars) != chars) {
-      printf("acm_read() failed\n");
-      return;
-    }
-
-    // Echo it back
-    if (acm_present()) {
-      if (acm_write(buffer, chars) != chars) {
-        printf("acm_write() failed\n");
-      }
-    }
-  }
-}
-#endif
-/******************************************************************************/
-static int setconfig_callback(usb_setup_pkt *sud, void *cbdata)
-{
-	/* Confirm the configuration value */
-	if (sud->wValue ==
-	    config_descriptor.config_descriptor.bConfigurationValue) {
-		configured = 1;
-		MXC_SETBIT(&event_flags, EVENT_ENUM_COMP);
-		return acm_configure(&acm_cfg); /* Configure the device class */
-	} else if (sud->wValue == 0) {
-		configured = 0;
-		return acm_deconfigure();
-	}
-
-	return -1;
-}
-
-/******************************************************************************/
-static int setfeature_callback(usb_setup_pkt *sud, void *cbdata)
-{
-	if (sud->wValue == FEAT_REMOTE_WAKE) {
-		remote_wake_en = 1;
-	} else {
-		// Unknown callback
-		return -1;
-	}
-
-	return 0;
-}
-
-/******************************************************************************/
-static int clrfeature_callback(usb_setup_pkt *sud, void *cbdata)
-{
-	if (sud->wValue == FEAT_REMOTE_WAKE) {
-		remote_wake_en = 0;
-	} else {
-		// Unknown callback
-		return -1;
-	}
-
-	return 0;
-}
-
-/******************************************************************************/
-static void usb_app_sleep(void)
-{
-	/* TODO: Place low-power code here */
-	suspended = 1;
-}
-
-/******************************************************************************/
-static void usb_app_wakeup(void)
-{
-	/* TODO: Place low-power code here */
-	suspended = 0;
-}
-
-/******************************************************************************/
-static int event_callback(maxusb_event_t evt, void *data)
-{
-	/* Set event flag */
-	MXC_SETBIT(&event_flags, evt);
-
-	switch (evt) {
-	case MAXUSB_EVENT_NOVBUS:
-		usb_event_disable(MAXUSB_EVENT_BRST);
-		usb_event_disable(MAXUSB_EVENT_SUSP);
-		usb_event_disable(MAXUSB_EVENT_DPACT);
-		usb_disconnect();
-		configured = 0;
-		enum_clearconfig();
-		acm_deconfigure();
-		usb_app_sleep();
-		break;
-	case MAXUSB_EVENT_VBUS:
-		usb_event_clear(MAXUSB_EVENT_BRST);
-		usb_event_enable(MAXUSB_EVENT_BRST, event_callback, NULL);
-		usb_event_clear(MAXUSB_EVENT_SUSP);
-		usb_event_enable(MAXUSB_EVENT_SUSP, event_callback, NULL);
-		usb_connect();
-		usb_app_sleep();
-		break;
-	case MAXUSB_EVENT_BRST:
-		usb_app_wakeup();
-		enum_clearconfig();
-		acm_deconfigure();
-		configured = 0;
-		suspended  = 0;
-		break;
-	case MAXUSB_EVENT_SUSP:
-		usb_app_sleep();
-		break;
-	case MAXUSB_EVENT_DPACT:
-		usb_app_wakeup();
-		break;
-	default:
-		break;
-	}
-
-	return 0;
-}
-
-/******************************************************************************/
-static int usb_read_callback(void)
-{
-	usb_read_complete = 1;
-	return 0;
-}
-
-/******************************************************************************/
-#include "FreeRTOS.h"
-#include "task.h"
-
-void USB_IRQHandler(void)
-{
-	usb_event_handler();
-
-	if (serial_task_id != NULL) {
-		BaseType_t xHigherPriorityTaskWoken = pdFALSE;
-		vTaskNotifyGiveFromISR(
-			serial_task_id, &xHigherPriorityTaskWoken
-		);
-		portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
-	}
-}
-
-/******************************************************************************/
-/* TODO: We probably need to fix something related to this */
-#if 0
-void SysTick_Handler(void)
-{
-    mxc_delay_handler();
-}
-#endif /* 0 */
diff --git a/epicardium/cdcacm.h b/epicardium/cdcacm.h
deleted file mode 100644
index 1c25d75027cebffd593f32364ebab7eec65fa7e6..0000000000000000000000000000000000000000
--- a/epicardium/cdcacm.h
+++ /dev/null
@@ -1,10 +0,0 @@
-#ifndef CDCACM_H
-#define CDCACM_H
-#include <stdint.h>
-
-int cdcacm_init(void);
-int cdcacm_num_read_avail(void);
-uint8_t cdcacm_read(void);
-void cdcacm_write(uint8_t *data, int len);
-
-#endif
diff --git a/epicardium/descriptors.h b/epicardium/descriptors.h
deleted file mode 100644
index c081c4010e65c7c86980ad94a096a81febb69f61..0000000000000000000000000000000000000000
--- a/epicardium/descriptors.h
+++ /dev/null
@@ -1,227 +0,0 @@
-/*******************************************************************************
- * Copyright (C) 2016 Maxim Integrated Products, Inc., All Rights Reserved.
- *
- * 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 MAXIM INTEGRATED 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.
- *
- * Except as contained in this notice, the name of Maxim Integrated
- * Products, Inc. shall not be used except as stated in the Maxim Integrated
- * Products, Inc. Branding Policy.
- *
- * The mere transfer of this software does not imply any licenses
- * of trade secrets, proprietary technology, copyrights, patents,
- * trademarks, maskwork rights, or any other form of intellectual
- * property whatsoever. Maxim Integrated Products, Inc. retains all
- * ownership rights.
- *
- * Description: Communications Device Class ACM (Serial Port) over USB
- * $Id: descriptors.h 36682 2018-08-06 21:14:03Z michael.bayern $
- *
- *******************************************************************************
- */
-
-#ifndef _DESCRIPTORS_H_
-#define _DESCRIPTORS_H_
-
-#include <stdint.h>
-#include "usb.h"
-#include "hid_kbd.h"
-
-usb_device_descriptor_t __attribute__((aligned(4))) device_descriptor = {
-    0x12,         /* bLength = 18                     */
-    0x01,         /* bDescriptorType = Device         */
-    0x0110,       /* bcdUSB USB spec rev (BCD)        */
-    0x02,         /* bDeviceClass = comm class (2)    */
-    0x00,         /* bDeviceSubClass                  */
-    0x00,         /* bDeviceProtocol                  */
-    0x40,         /* bMaxPacketSize0 is 64 bytes      */
-    0x0B6A,       /* idVendor (Maxim Integrated)      */
-    0x003C,       /* idProduct                        */
-    0x0100,       /* bcdDevice                        */
-    0x01,         /* iManufacturer Descriptor ID      */
-    0x02,         /* iProduct Descriptor ID           */
-    0x00,         /* iSerialNumber = (0) No string    */
-    0x01          /* bNumConfigurations               */
-};
-
-__attribute__((aligned(4)))
-struct __attribute__((packed)) {
-    usb_configuration_descriptor_t  config_descriptor;
-    usb_interface_descriptor_t      comm_interface_descriptor;
-    uint8_t                         header_functional_descriptor[5];
-    uint8_t                         call_management_descriptor[5];
-    uint8_t                         acm_functional_descriptor[4];
-    uint8_t                         union_functional_descriptor[5];
-    usb_endpoint_descriptor_t       endpoint_descriptor_3;
-    usb_interface_descriptor_t      data_interface_descriptor;
-    usb_endpoint_descriptor_t       endpoint_descriptor_1;
-    usb_endpoint_descriptor_t       endpoint_descriptor_2;
-} config_descriptor =
-{
-    {
-        0x09,       /*  bLength = 9                     */
-        0x02,       /*  bDescriptorType = Config (2)    */
-        0x0043,     /*  wTotalLength(L/H)               */
-        0x02,       /*  bNumInterfaces                  */
-        0x01,       /*  bConfigValue                    */
-        0x00,       /*  iConfiguration                  */
-        0xE0,       /*  bmAttributes (self-powered, remote wakeup) */
-        0x01,       /*  MaxPower is 2ma (units are 2ma/bit) */
-    },
-    { /*  First Interface Descriptor For Comm Class Interface */
-        0x09,       /*  bLength = 9                     */
-        0x04,       /*  bDescriptorType = Interface (4) */
-        0x00,       /*  bInterfaceNumber                */
-        0x00,       /*  bAlternateSetting               */
-        0x01,       /*  bNumEndpoints (one for OUT)     */
-        0x02,       /*  bInterfaceClass = Communications Interface Class (2) */
-        0x02,       /*  bInterfaceSubClass = Abstract Control Model (2) */
-        0x01,       /*  bInterfaceProtocol = Common "AT" commands (1), no class specific protocol (0) */
-        0x00,       /*  iInterface                      */
-    },
-    { /*  Header Functional Descriptor */
-        0x05,         /*  bFunctionalLength = 5           */
-        0x24,         /*  bDescriptorType                 */
-        0x00,         /*  bDescriptorSubtype              */
-        0x10, 0x01,   /*  bcdCDC                          */
-    },
-    { /*  Call Management Descriptor */
-        0x05,         /*  bFunctionalLength = 5           */
-        0x24,         /*  bDescriptorType                 */
-        0x01,         /*  bDescriptorSubtype              */
-        0x03,         /*  bmCapabilities = Device handles call management itself (0x01), management over data class (0x02) */
-        0x01,         /*  bmDataInterface                 */
-    },
-    { /*  Abstract Control Management Functional Descriptor */
-        0x04,         /*  bFunctionalLength = 4           */
-        0x24,         /*  bDescriptorType                 */
-        0x02,         /*  bDescriptorSubtype              */
-        0x02,         /*  bmCapabilities                  */
-    },
-    { /*  Union Functional Descriptor */
-        0x05,         /*  bFunctionalLength = 5           */
-        0x24,         /*  bDescriptorType                 */
-        0x06,         /*  bDescriptorSubtype              */
-        0x00,         /*  bmMasterInterface               */
-        0x01,         /*  bmSlaveInterface0               */
-    },
-    { /*  IN Endpoint 3 (Descriptor #1) */
-        0x07,         /*  bLength                          */
-        0x05,         /*  bDescriptorType (Endpoint)       */
-        0x83,         /*  bEndpointAddress (EP3-IN)        */
-        0x03,         /*  bmAttributes (interrupt)         */
-        0x0040,       /*  wMaxPacketSize                   */
-        0xff,         /*  bInterval (milliseconds)         */
-    },
-    { /*  Second Interface Descriptor For Data Interface */
-        0x09,         /*  bLength                          */
-        0x04,         /*  bDescriptorType (Interface)      */
-        0x01,         /*  bInterfaceNumber                 */
-        0x00,         /*  bAlternateSetting                */
-        0x02,         /*  bNumEndpoints                    */
-        0x0a,         /*  bInterfaceClass = Data Interface (10) */
-        0x00,         /*  bInterfaceSubClass = none (0)    */
-        0x00,         /*  bInterfaceProtocol = No class specific protocol (0) */
-        0x00,         /*  biInterface = No Text String (0) */
-    },
-    { /*  OUT Endpoint 1 (Descriptor #2) */
-        0x07,         /*  bLength                          */
-        0x05,         /*  bDescriptorType (Endpoint)       */
-        0x01,         /*  bEndpointAddress (EP1-OUT)       */
-        0x02,         /*  bmAttributes (bulk)              */
-        0x0040,       /*  wMaxPacketSize                   */
-        0x00,         /*  bInterval (N/A)                  */
-    },
-    { /*  IN Endpoint 2 (Descriptor #3) */
-        0x07,         /*  bLength                          */
-        0x05,         /*  bDescriptorType (Endpoint)       */
-        0x82,         /*  bEndpointAddress (EP2-IN)        */
-        0x02,         /*  bmAttributes (bulk)              */
-        0x0040,       /*  wMaxPacketSize                   */
-        0x00          /*  bInterval (N/A)                  */
-    }
-};
-
-__attribute__((aligned(4)))
-uint8_t lang_id_desc[] = {
-    0x04,         /* bLength */
-    0x03,         /* bDescriptorType */
-    0x09, 0x04    /* bString = wLANGID (see usb_20.pdf 9.6.7 String) */
-};
-
-__attribute__((aligned(4)))
-uint8_t mfg_id_desc[] = {
-    0x22,         /* bLength */
-    0x03,         /* bDescriptorType */
-    'M', 0,
-    'a', 0,
-    'x', 0,
-    'i', 0,
-    'm', 0,
-    ' ', 0,
-    'I', 0,
-    'n', 0,
-    't', 0,
-    'e', 0,
-    'g', 0,
-    'r', 0,
-    'a', 0,
-    't', 0,
-    'e', 0,
-    'd', 0,
-};
-
-__attribute__((aligned(4)))
-uint8_t prod_id_desc[] = {
-    0x22,         /* bLength */
-    0x03,         /* bDescriptorType */
-    'M', 0,
-    'A', 0,
-    'X', 0,
-    '3', 0,
-    '2', 0,
-    '6', 0,
-    '6', 0,
-    '5', 0,
-    ' ', 0,
-    'C', 0,
-    'D', 0,
-    'C', 0,
-    '-', 0,
-    'A', 0,
-    'C', 0,
-    'M', 0,
-};
-
-/* Not currently used (see device descriptor), but could be enabled if desired */
-__attribute__((aligned(4)))
-uint8_t serial_id_desc[] = {
-    0x14,         /* bLength */
-    0x03,         /* bDescriptorType */
-    '0', 0,
-    '0', 0,
-    '0', 0,
-    '0', 0,
-    '0', 0,
-    '0', 0,
-    '0', 0,
-    '0', 0,
-    '1', 0
-};
-
-#endif /* _DESCRIPTORS_H_ */
diff --git a/epicardium/epicardium.h b/epicardium/epicardium.h
index 4eea7d3fee2f2af194c669d4b24b768be41928ca..74b0c814653b0ad0193d2ffd11dc50f0d7fae8d7 100644
--- a/epicardium/epicardium.h
+++ b/epicardium/epicardium.h
@@ -131,6 +131,10 @@ typedef _Bool bool;
 #define API_MAX86150_GET_DATA		0x0101
 #define API_MAX86150_SET_LED_AMPLITUDE	0x0102
 
+#define API_USB_SHUTDOWN           0x110
+#define API_USB_STORAGE            0x111
+#define API_USB_CDCACM             0x112
+
 /* clang-format on */
 
 typedef uint32_t api_int_id_t;
@@ -1638,8 +1642,8 @@ API_ISR(EPIC_INT_RTC_ALARM, epic_isr_rtc_alarm);
 API(API_TRNG_READ, int epic_trng_read(uint8_t *dest, size_t size));
 
 /**
- * MAX30001 API
- * ----------
+ * MAX30001
+ * ========
  */
 
 /**
@@ -1700,5 +1704,20 @@ API(API_MAX30001_DISABLE, int epic_max30001_disable_sensor(
 void
 ));
 
+/**
+ * De-initialize the currently configured USB device (if any)
+ *
+ */
+API(API_USB_SHUTDOWN, int epic_usb_shutdown(void));
+/**
+ * Configure the USB peripheral to export the internal FLASH
+ * as a Mass Storage device
+ */
+API(API_USB_STORAGE, int epic_usb_storage(void));
+/**
+ * Configure the USB peripheral to provide card10's stdin/stdout
+ * on a USB CDC-ACM device
+ */
+API(API_USB_CDCACM, int epic_usb_cdcacm(void));
 
 #endif /* _EPICARDIUM_H */
diff --git a/epicardium/fs/filesystem_fat.c b/epicardium/fs/filesystem_fat.c
index 7d95510360324ac5e105a53aa90a3a96ee0936c1..ccaec62157174cdfab14c957d157941b063acc64 100644
--- a/epicardium/fs/filesystem_fat.c
+++ b/epicardium/fs/filesystem_fat.c
@@ -16,6 +16,7 @@
 
 #include <FreeRTOS.h>
 #include <semphr.h>
+#include <timers.h>
 
 #include "fs/internal.h"
 #include "modules/filesystem.h"
@@ -57,7 +58,7 @@ struct FatObject {
 struct EpicFileSystem {
 	struct FatObject pool[EPIC_FAT_MAX_OPENED];
 	uint32_t generationCount;
-	bool initialized;
+	bool attached;
 	FATFS FatFs;
 	int lockCoreMask;
 };
@@ -102,6 +103,11 @@ static StaticSemaphore_t s_globalLockBuffer;
 
 static SemaphoreHandle_t s_globalLock = NULL;
 
+static void cb_attachTimer(void *a, uint32_t b)
+{
+	fatfs_attach();
+}
+
 void fatfs_init()
 {
 	static volatile bool s_initCalled = false;
@@ -117,6 +123,7 @@ void fatfs_init()
 #else
 	s_globalLock = xSemaphoreCreateMutex();
 #endif
+
 	s_globalFileSystem.generationCount = 1;
 	fatfs_attach();
 }
@@ -137,11 +144,11 @@ int fatfs_attach()
 	int rc = 0;
 	if (globalLockAccquire()) {
 		EpicFileSystem *fs = &s_globalFileSystem;
-		if (!fs->initialized) {
+		if (!fs->attached) {
 			ff_res = f_mount(&fs->FatFs, "/", 0);
 			if (ff_res == FR_OK) {
-				fs->initialized = true;
-				SSLOG_DEBUG("FatFs mounted\n");
+				fs->attached = true;
+				SSLOG_INFO("attached\n");
 			} else {
 				SSLOG_ERR(
 					"f_mount error %s\n",
@@ -158,25 +165,38 @@ int fatfs_attach()
 	return rc;
 }
 
+void fatfs_schedule_attach(void)
+{
+	//if we're running in thread context, cont't call the *FromISR version
+	if (xPortIsInsideInterrupt()) {
+		xTimerPendFunctionCallFromISR(cb_attachTimer, NULL, 0, NULL);
+	} else {
+		xTimerPendFunctionCall(
+			cb_attachTimer, NULL, 0, 1); //wait 1 tick
+	}
+}
+
 void fatfs_detach()
 {
 	FRESULT ff_res;
 	EpicFileSystem *fs;
 	if (efs_lock_global(&fs) == 0) {
-		efs_close_all(fs, EPICARDIUM_COREMASK_BOTH);
-
-		//unmount by passing NULL as fs object, will destroy our sync object via ff_del_syncobj
-		ff_res = f_mount(NULL, "/", 0);
-		if (ff_res != FR_OK) {
-			SSLOG_ERR(
-				"f_mount (unmount) error %s\n",
-				f_get_rc_string(ff_res)
-			);
-		}
+		if (fs->attached) {
+			efs_close_all(fs, EPICARDIUM_COREMASK_BOTH);
+
+			//unmount by passing NULL as fs object, will destroy our sync object via ff_del_syncobj
+			ff_res = f_mount(NULL, "/", 0);
+			if (ff_res != FR_OK) {
+				SSLOG_ERR(
+					"f_mount (unmount) error %s\n",
+					f_get_rc_string(ff_res)
+				);
+			}
 
-		fs->initialized = false;
-		disk_deinitialize();
-		SSLOG_INFO("detached\n");
+			fs->attached = false;
+			disk_deinitialize();
+			SSLOG_INFO("detached\n");
+		}
 		efs_unlock_global(fs);
 	}
 }
@@ -223,7 +243,7 @@ int efs_lock_global(EpicFileSystem **fs)
 	if (!globalLockAccquire()) {
 		return -EBUSY;
 	}
-	if (!s_globalFileSystem.initialized) {
+	if (!s_globalFileSystem.attached) {
 		globalLockRelease();
 		return -ENODEV;
 	}
diff --git a/epicardium/main.c b/epicardium/main.c
index c7f58a05c889fc0c47eafb97d1f2995a8afce881..dadad9bf44ed4de087e21416b8536d5daf3a4c1f 100644
--- a/epicardium/main.c
+++ b/epicardium/main.c
@@ -12,15 +12,22 @@
 
 int main(void)
 {
+	watchdog_init();
+
 	LOG_INFO("startup", "Epicardium startup ...");
 	LOG_INFO("startup", "Version " CARD10_VERSION);
 
 	LOG_DEBUG("startup", "Initializing hardware ...");
 	hardware_early_init();
 
-	char *version_buf = CARD10_VERSION;
-	epic_disp_print(0, 5, "epicardium:", 0xffff, 0x0000);
-	epic_disp_print(0, 24, version_buf, 0xffff, 0x0000);
+	/*
+	 * Version Splash
+	 */
+	const char *version_buf = CARD10_VERSION;
+	const int offset        = (160 - (int)strlen(version_buf) * 14) / 2;
+	epic_disp_clear(0x3b7);
+	epic_disp_print(10, 20, "Epicardium", 0x290, 0x3b7);
+	epic_disp_print(offset > 0 ? offset : 0, 40, version_buf, 0x290, 0x3b7);
 	epic_disp_update();
 	mxc_delay(2000000);
 
@@ -32,7 +39,7 @@ int main(void)
 		    (const char *)"Serial",
 		    configMINIMAL_STACK_SIZE * 2,
 		    NULL,
-		    tskIDLE_PRIORITY + 1,
+		    tskIDLE_PRIORITY + 3,
 		    NULL) != pdPASS) {
 		LOG_CRIT("startup", "Failed to create %s task!", "Serial");
 		abort();
@@ -127,13 +134,10 @@ int main(void)
 		abort();
 	}
 
-	/* Watchdog petting */
-#if 0
 	/*
-	 * Disabled for this release.
+	 * Initialize serial driver data structures.
 	 */
-	watchdog_clearer_init();
-#endif
+	serial_init();
 
 	LOG_DEBUG("startup", "Starting FreeRTOS ...");
 	vTaskStartScheduler();
diff --git a/epicardium/meson.build b/epicardium/meson.build
index 1aa5556010941d2267f8b10aafcaf9ec7683c8a2..cf67023b9f7143336875d973108d8ee0a080ffd1 100644
--- a/epicardium/meson.build
+++ b/epicardium/meson.build
@@ -80,7 +80,9 @@ endif
 
 elf = executable(
   name + '.elf',
-  'cdcacm.c',
+  'usb/epc_usb.c',
+  'usb/cdcacm.c',
+  'usb/mass_storage.c',
   'main.c',
   'support.c',
   'fs/filesystem_fat.c',
diff --git a/epicardium/modules/bhi.c b/epicardium/modules/bhi.c
index 7d3689502e07c3de554744b1f10684d9d0af256b..66f8bb7ea42f4f679f9247c85b323d04ddf621f9 100644
--- a/epicardium/modules/bhi.c
+++ b/epicardium/modules/bhi.c
@@ -412,6 +412,11 @@ void vBhi160Task(void *pvParameters)
 	bhi160_task_id = xTaskGetCurrentTaskHandle();
 	bhi160_mutex   = xSemaphoreCreateMutexStatic(&bhi160_mutex_data);
 
+	/*
+	 * Wait a little before initializing BHI160.
+	 */
+	vTaskDelay(pdMS_TO_TICKS(3));
+
 	int lockret = hwlock_acquire(HWLOCK_I2C, pdMS_TO_TICKS(100));
 	if (lockret < 0) {
 		LOG_CRIT("bhi160", "Failed to acquire I2C lock!");
diff --git a/epicardium/modules/dispatcher.c b/epicardium/modules/dispatcher.c
index 1f504eca52e20bfb8c118281c772942e72ac7a18..03f8534cc0a1c02dccc61f8c03e486e2ab472ff8 100644
--- a/epicardium/modules/dispatcher.c
+++ b/epicardium/modules/dispatcher.c
@@ -13,14 +13,17 @@ TaskHandle_t dispatcher_task_id;
 static StaticSemaphore_t api_mutex_data;
 SemaphoreHandle_t api_mutex = NULL;
 
+void dispatcher_mutex_init(void)
+{
+	api_mutex = xSemaphoreCreateMutexStatic(&api_mutex_data);
+}
+
 /*
  * API dispatcher task.  This task will sleep until an API call is issued and
  * then wake up to dispatch it.
  */
 void vApiDispatcher(void *pvParameters)
 {
-	api_mutex = xSemaphoreCreateMutexStatic(&api_mutex_data);
-
 	LOG_DEBUG("dispatcher", "Ready.");
 	while (1) {
 		if (api_dispatcher_poll()) {
diff --git a/epicardium/modules/filesystem.h b/epicardium/modules/filesystem.h
index 720147ecd04a8f07755052d05adb90871b360bd7..db54a2895621d0e316dee83c362faac737b1a9e2 100644
--- a/epicardium/modules/filesystem.h
+++ b/epicardium/modules/filesystem.h
@@ -16,9 +16,19 @@ void fatfs_init(void);
 
 /**
  * initialize and mount the FLASH storage
+ * 
+ * NOTE: not safe to be called from an ISR
  */
 int fatfs_attach(void);
 
+
+/**
+ * asynchronously attach the FLASH storage
+ * 
+ * safe to be called from an ISR
+ */
+void fatfs_schedule_attach(void);
+
 /** close all opened FDs, sync and deinitialize FLASH layer */
 void fatfs_detach(void);
 
diff --git a/epicardium/modules/hardware.c b/epicardium/modules/hardware.c
index 6148513eb03014b19ff01873701ae3aedefb582f..17c707217283c41f61bfad79b9b6fb69d7b20d11 100644
--- a/epicardium/modules/hardware.c
+++ b/epicardium/modules/hardware.c
@@ -2,7 +2,7 @@
 
 #include "api/dispatcher.h"
 #include "api/interrupt-sender.h"
-#include "cdcacm.h"
+#include "usb/epc_usb.h"
 #include "modules/filesystem.h"
 #include "modules/log.h"
 #include "modules/modules.h"
@@ -32,24 +32,7 @@ int hardware_early_init(void)
 	/*
 	 * Watchdog timer
 	 */
-#if 0
-	/*
-	 * Disabled for this release.
-	 */
-	sys_cfg_wdt_t wdt_cfg = NULL;
-	WDT_Init(MXC_WDT0, wdt_cfg);
-
-	if (WDT_GetResetFlag(MXC_WDT0)) {
-		WDT_ClearResetFlag(MXC_WDT0);
-		LOG_INFO("watchdog", "Reset due to watchdog timeout");
-	}
-
-	WDT_Enable(MXC_WDT0, 1);
-	WDT_SetResetPeriod(
-		MXC_WDT0,
-		WDT_PERIOD_2_27); /* Clocked by PCLK at 50MHz, reset at 2^27 ticks = 2.7 seconds */
-	WDT_EnableReset(MXC_WDT0, 1);
-#endif
+	watchdog_init();
 
 	/*
 	 * I2C bus for onboard peripherals (ie. PMIC, BMA400, BHI160, BME680,
@@ -169,8 +152,8 @@ int hardware_early_init(void)
 	/*
 	 * USB-Serial
 	 */
-	if (cdcacm_init() < 0) {
-		LOG_ERR("init", "USB-Serial unavailable");
+	if (epic_usb_cdcacm() < 0) {
+		LOG_ERR("startup", "USB-Serial unavailable");
 	}
 
 	/*
@@ -194,6 +177,16 @@ int hardware_early_init(void)
 	 */
 	hwlock_init();
 
+	/*
+	 * API Dispatcher Mutex
+	 */
+	dispatcher_mutex_init();
+
+	/*
+	 * MAX30001 mutex init
+	 */
+	max30001_mutex_init();
+
 	return 0;
 }
 
@@ -207,6 +200,9 @@ int hardware_early_init(void)
  */
 int hardware_init(void)
 {
+	/* Watchdog clearer software timer */
+	watchdog_clearer_init();
+
 	/* Light Sensor */
 	LOG_DEBUG("init", "Starting light sensor ...");
 	epic_light_sensor_run();
diff --git a/epicardium/modules/hw-lock.c b/epicardium/modules/hw-lock.c
index f02a5882b1bdb2aabddeb2ccea77d59fe14939c4..ce88d921748302edd849eea0b923e6c61eab8dec 100644
--- a/epicardium/modules/hw-lock.c
+++ b/epicardium/modules/hw-lock.c
@@ -25,13 +25,30 @@ int hwlock_acquire(enum hwlock_periph p, TickType_t wait)
 	if (p >= _HWLOCK_MAX) {
 		return -EINVAL;
 	}
+	TaskHandle_t task = xTaskGetCurrentTaskHandle();
 
 	if (xSemaphoreTake(hwlock_mutex[p], wait) != pdTRUE) {
-		LOG_WARN("hwlock", "Lock %u is busy.", p);
+		/* Don't print warnings for 0 wait acquires */
+		if (wait == 0) {
+			return -EBUSY;
+		}
+
+		LOG_WARN(
+			"hwlock",
+			"Lock %u is busy! Held by \"%s\" and attempted to accquire by \"%s\"",
+			p,
+			pcTaskGetName(hwlock_tasks[p]),
+			pcTaskGetName(task)
+		);
+		LOG_DEBUG(
+			"hwlock",
+			"...attempted to lock from pc %p",
+			__builtin_return_address(0)
+		);
 		return -EBUSY;
 	}
 
-	hwlock_tasks[p] = xTaskGetCurrentTaskHandle();
+	hwlock_tasks[p] = task;
 
 	return 0;
 }
diff --git a/epicardium/modules/max30001.c b/epicardium/modules/max30001.c
index 5cc3ef3654e19b6ea298ab7ba8e019676dfc4332..811af4bf8aa274443abb1415a7586c2766660991 100644
--- a/epicardium/modules/max30001.c
+++ b/epicardium/modules/max30001.c
@@ -370,10 +370,14 @@ static void max300001_interrupt_callback(void *_)
 }
 /* }}} */
 
+void max30001_mutex_init(void)
+{
+	max30001_mutex = xSemaphoreCreateMutexStatic(&max30001_mutex_data);
+}
+
 void vMAX30001Task(void *pvParameters)
 {
 	max30001_task_id = xTaskGetCurrentTaskHandle();
-	max30001_mutex   = xSemaphoreCreateMutexStatic(&max30001_mutex_data);
 
 	int lockret = hwlock_acquire(HWLOCK_SPI_ECG, pdMS_TO_TICKS(100));
 	if (lockret < 0) {
diff --git a/epicardium/modules/meson.build b/epicardium/modules/meson.build
index 1aa7494ea0eff9003b48f0aa41a63257b1ab3833..693ca9a8f94a432ff64d770f91d9e9005c88d42a 100644
--- a/epicardium/modules/meson.build
+++ b/epicardium/modules/meson.build
@@ -21,4 +21,5 @@ module_sources = files(
   'trng.c',
   'vibra.c',
   'watchdog.c',
+  'usb.c'
 )
diff --git a/epicardium/modules/modules.h b/epicardium/modules/modules.h
index 144b18fb765a78580d66a8e8b59a9593d8127a77..2d51f786373cea0bb108212b225df7badf0bdc25 100644
--- a/epicardium/modules/modules.h
+++ b/epicardium/modules/modules.h
@@ -9,6 +9,7 @@
 
 /* ---------- Dispatcher --------------------------------------------------- */
 void vApiDispatcher(void *pvParameters);
+void dispatcher_mutex_init(void);
 extern SemaphoreHandle_t api_mutex;
 extern TaskHandle_t dispatcher_task_id;
 
@@ -23,10 +24,18 @@ void return_to_menu(void);
 
 /* ---------- Serial ------------------------------------------------------- */
 #define SERIAL_READ_BUFFER_SIZE 128
+#define SERIAL_WRITE_STREAM_BUFFER_SIZE 512
+void serial_init();
 void vSerialTask(void *pvParameters);
 void serial_enqueue_char(char chr);
 extern TaskHandle_t serial_task_id;
 
+// For the eSetBit xTaskNotify task semaphore trigger
+enum serial_notify{
+      SERIAL_WRITE_NOTIFY = 0x01,
+      SERIAL_READ_NOTIFY  = 0x02,
+};
+
 /* ---------- LED Animation / Personal States ------------------------------ */
 #define PERSONAL_STATE_LED 14
 void vLedTask(void *pvParameters);
@@ -36,6 +45,7 @@ int personal_state_enabled();
 void vPmicTask(void *pvParameters);
 
 /* ---------- Watchdog ----------------------------------------------------- */
+void watchdog_init();
 void watchdog_clearer_init();
 
 /* Critical battery voltage */
@@ -90,7 +100,9 @@ void disp_forcelock();
 #define BHI160_MUTEX_WAIT_MS          50
 void vBhi160Task(void *pvParameters);
 
+/* ---------- MAX30001 ----------------------------------------------------- */
 #define MAX30001_MUTEX_WAIT_MS          50
 void vMAX30001Task(void *pvParameters);
+void max30001_mutex_init(void);
 
 #endif /* MODULES_H */
diff --git a/epicardium/modules/pmic.c b/epicardium/modules/pmic.c
index 093e97ecdad2bd250636150c59ac18fcb16ad279..4df12ea91d49aaf666e73e609160a0bd5e3024e4 100644
--- a/epicardium/modules/pmic.c
+++ b/epicardium/modules/pmic.c
@@ -16,6 +16,9 @@
 #include "timers.h"
 
 #include <stdio.h>
+#include <string.h>
+
+#define LOCK_WAIT pdMS_TO_TICKS(1000)
 
 /* Task ID for the pmic handler */
 static TaskHandle_t pmic_task_id = NULL;
@@ -50,12 +53,12 @@ int pmic_read_amux(enum pmic_amux_signal sig, float *result)
 		return -EINVAL;
 	}
 
-	int adc_ret = hwlock_acquire(HWLOCK_ADC, pdMS_TO_TICKS(100));
+	int adc_ret = hwlock_acquire(HWLOCK_ADC, LOCK_WAIT);
 	if (adc_ret < 0) {
 		ret = adc_ret;
 		goto done;
 	}
-	i2c_ret = hwlock_acquire(HWLOCK_I2C, pdMS_TO_TICKS(100));
+	i2c_ret = hwlock_acquire(HWLOCK_I2C, LOCK_WAIT);
 	if (i2c_ret < 0) {
 		ret = i2c_ret;
 		goto done;
@@ -70,10 +73,9 @@ int pmic_read_amux(enum pmic_amux_signal sig, float *result)
 	 * release the I2C mutex.
 	 */
 	hwlock_release(HWLOCK_I2C);
-	i2c_ret = 0;
 
 	vTaskDelay(pdMS_TO_TICKS(5));
-	i2c_ret = hwlock_acquire(HWLOCK_I2C, pdMS_TO_TICKS(100));
+	i2c_ret = hwlock_acquire(HWLOCK_I2C, LOCK_WAIT);
 	if (i2c_ret < 0) {
 		ret = i2c_ret;
 		goto done;
@@ -137,7 +139,7 @@ done:
 static void
 pmic_poll_interrupts(TickType_t *button_start_tick, TickType_t duration)
 {
-	while (hwlock_acquire(HWLOCK_I2C, pdMS_TO_TICKS(500)) < 0) {
+	while (hwlock_acquire(HWLOCK_I2C, LOCK_WAIT) < 0) {
 		LOG_WARN("pmic", "Failed to acquire I2C. Retrying ...");
 		xTaskNotify(pmic_task_id, PMIC_NOTIFY_IRQ, eSetBits);
 		return;
@@ -207,7 +209,10 @@ static void pmic_check_battery()
 
 	res = pmic_read_amux(PMIC_AMUX_BATT_U, &u_batt);
 	if (res < 0) {
-		LOG_ERR("pmic", "Failed reading battery voltage: %d", res);
+		LOG_ERR("pmic",
+			"Failed reading battery voltage: %s (%d)",
+			strerror(-res),
+			res);
 		return;
 	}
 
diff --git a/epicardium/modules/serial.c b/epicardium/modules/serial.c
index a7392056f31705e9c6f334f60952ece4dee22d10..3292ab9330c973a83243b9aad0c3e7b63a6d0200 100644
--- a/epicardium/modules/serial.c
+++ b/epicardium/modules/serial.c
@@ -4,32 +4,121 @@
 #include "modules/modules.h"
 
 #include "max32665.h"
-#include "cdcacm.h"
+#include "usb/cdcacm.h"
 #include "uart.h"
 
 #include "FreeRTOS.h"
 #include "task.h"
 #include "queue.h"
+#include "stream_buffer.h"
 
 #include <stdint.h>
 #include <stdio.h>
 
+/* The serial console in use (UART0) */
+extern mxc_uart_regs_t *ConsoleUart;
+
 /* Task ID for the serial handler */
 TaskHandle_t serial_task_id = NULL;
 
-/* The serial console in use (UART0) */
-extern mxc_uart_regs_t *ConsoleUart;
 /* Read queue, filled by both UART and CDCACM */
 static QueueHandle_t read_queue;
+/* Stream Buffer for handling all writes to serial */
+static StreamBufferHandle_t write_stream_buffer = NULL;
+
+void serial_init()
+{
+	/* Setup read queue */
+	static uint8_t buffer[sizeof(char) * SERIAL_READ_BUFFER_SIZE];
+	static StaticQueue_t read_queue_data;
+	read_queue = xQueueCreateStatic(
+		SERIAL_READ_BUFFER_SIZE, sizeof(char), buffer, &read_queue_data
+	);
+
+	/* Setup write queue */
+	static uint8_t ucWrite_stream_buffer[SERIAL_WRITE_STREAM_BUFFER_SIZE];
+	static StaticStreamBuffer_t xStream_buffer_struct;
+	write_stream_buffer = xStreamBufferCreateStatic(
+		sizeof(ucWrite_stream_buffer),
+		1,
+		ucWrite_stream_buffer,
+		&xStream_buffer_struct
+	);
+}
 
 /*
  * API-call to write a string.  Output goes to both CDCACM and UART
  */
 void epic_uart_write_str(const char *str, intptr_t length)
 {
-	UART_Write(ConsoleUart, (uint8_t *)str, length);
-	cdcacm_write((uint8_t *)str, length);
-	ble_uart_write((uint8_t *)str, length);
+	if (length == 0) {
+		return;
+	}
+
+	/*
+	 * Check if the stream buffer is even initialized yet
+	 */
+	if (write_stream_buffer == NULL) {
+		UART_Write(ConsoleUart, (uint8_t *)str, length);
+		cdcacm_write((uint8_t *)str, length);
+		return;
+	}
+
+	if (xPortIsInsideInterrupt()) {
+		BaseType_t resched1 = pdFALSE;
+		BaseType_t resched2 = pdFALSE;
+
+		/*
+		 * Enter a critial section so no other task can write to the
+		 * stream buffer.
+		 */
+		uint32_t basepri = __get_BASEPRI();
+		taskENTER_CRITICAL_FROM_ISR();
+
+		xStreamBufferSendFromISR(
+			write_stream_buffer, str, length, &resched1
+		);
+
+		taskEXIT_CRITICAL_FROM_ISR(basepri);
+
+		if (serial_task_id != NULL) {
+			xTaskNotifyFromISR(
+				serial_task_id,
+				SERIAL_WRITE_NOTIFY,
+				eSetBits,
+				&resched2
+			);
+		}
+
+		/* Yield if this write woke up a higher priority task */
+		portYIELD_FROM_ISR(resched1 || resched2);
+	} else {
+		size_t bytes_sent = 0;
+		size_t index      = 0;
+		do {
+			taskENTER_CRITICAL();
+			/*
+			 * Wait time needs to be zero, because we are in a
+			 * critical section.
+			 */
+			bytes_sent = xStreamBufferSend(
+				write_stream_buffer,
+				&str[index],
+				length - index,
+				0
+			);
+			index += bytes_sent;
+			taskEXIT_CRITICAL();
+			if (serial_task_id != NULL) {
+				xTaskNotify(
+					serial_task_id,
+					SERIAL_WRITE_NOTIFY,
+					eSetBits
+				);
+				portYIELD();
+			}
+		} while (index < length);
+	}
 }
 
 /*
@@ -87,7 +176,12 @@ void UART0_IRQHandler(void)
 static void uart_callback(uart_req_t *req, int error)
 {
 	BaseType_t xHigherPriorityTaskWoken = pdFALSE;
-	vTaskNotifyGiveFromISR(serial_task_id, &xHigherPriorityTaskWoken);
+	xTaskNotifyFromISR(
+		serial_task_id,
+		SERIAL_READ_NOTIFY,
+		eSetBits,
+		&xHigherPriorityTaskWoken
+	);
 	portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
 }
 
@@ -108,16 +202,8 @@ void serial_enqueue_char(char chr)
 
 void vSerialTask(void *pvParameters)
 {
-	static uint8_t buffer[sizeof(char) * SERIAL_READ_BUFFER_SIZE];
-	static StaticQueue_t read_queue_data;
-
 	serial_task_id = xTaskGetCurrentTaskHandle();
 
-	/* Setup read queue */
-	read_queue = xQueueCreateStatic(
-		SERIAL_READ_BUFFER_SIZE, sizeof(char), buffer, &read_queue_data
-	);
-
 	/* Setup UART interrupt */
 	NVIC_ClearPendingIRQ(UART0_IRQn);
 	NVIC_DisableIRQ(UART0_IRQn);
@@ -131,6 +217,9 @@ void vSerialTask(void *pvParameters)
 		.callback = uart_callback,
 	};
 
+	uint8_t rx_data[20];
+	size_t received_bytes;
+
 	while (1) {
 		int ret = UART_ReadAsync(ConsoleUart, &read_req);
 		if (ret != E_NO_ERROR && ret != E_BUSY) {
@@ -138,18 +227,55 @@ void vSerialTask(void *pvParameters)
 			vTaskDelay(portMAX_DELAY);
 		}
 
-		ulTaskNotifyTake(pdTRUE, portTICK_PERIOD_MS * 1000);
+		ret = ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
 
-		if (read_req.num > 0) {
-			serial_enqueue_char(*read_req.data);
-		}
+		if (ret & SERIAL_WRITE_NOTIFY) {
+			do {
+				received_bytes = xStreamBufferReceive(
+					write_stream_buffer,
+					(void *)rx_data,
+					sizeof(rx_data),
+					0
+				);
+
+				if (received_bytes == 0) {
+					break;
+				}
+
+				/*
+				 * The SDK-driver for UART is not reentrant
+				 * which means we need to perform UART writes
+				 * in a critical section.
+				 */
+				taskENTER_CRITICAL();
+				UART_Write(
+					ConsoleUart,
+					(uint8_t *)&rx_data,
+					received_bytes
+				);
+				taskEXIT_CRITICAL();
 
-		while (UART_NumReadAvail(ConsoleUart) > 0) {
-			serial_enqueue_char(UART_ReadByte(ConsoleUart));
+				cdcacm_write(
+					(uint8_t *)&rx_data, received_bytes
+				);
+				ble_uart_write(
+					(uint8_t *)&rx_data, received_bytes
+				);
+			} while (received_bytes > 0);
 		}
 
-		while (cdcacm_num_read_avail() > 0) {
-			serial_enqueue_char(cdcacm_read());
+		if (ret & SERIAL_READ_NOTIFY) {
+			if (read_req.num > 0) {
+				serial_enqueue_char(*read_req.data);
+			}
+
+			while (UART_NumReadAvail(ConsoleUart) > 0) {
+				serial_enqueue_char(UART_ReadByte(ConsoleUart));
+			}
+
+			while (cdcacm_num_read_avail() > 0) {
+				serial_enqueue_char(cdcacm_read());
+			}
 		}
 	}
 }
diff --git a/epicardium/modules/usb.c b/epicardium/modules/usb.c
new file mode 100644
index 0000000000000000000000000000000000000000..e14032e4e3dccb91342630317786d8c35d363b3a
--- /dev/null
+++ b/epicardium/modules/usb.c
@@ -0,0 +1,322 @@
+/**
+ * 
+ * Implementation of public epic_usb_ interface
+ * 
+ * Configuration and FLASH-specific implementation of USB mass storage device
+ * Configuration of USB CDCACM device
+ * 
+ * USB descriptors for both are defined here.
+ * 
+ */
+
+#include "epicardium.h"
+
+#include "modules/filesystem.h"
+
+#include "usb/cdcacm.h"
+#include "usb/mass_storage.h"
+#include "usb/descriptors.h"
+#include "usb/epc_usb.h"
+
+#include "modules/log.h"
+
+#include "mx25lba.h"
+#include "msc.h"
+
+/* memory access callbacks for the mass storage (FLASH) device */
+static int mscmem_init();
+static uint32_t mscmem_size(void);
+static int mscmem_read(uint32_t lba, uint8_t *buffer);
+static int mscmem_write(uint32_t lba, uint8_t *buffer);
+static int mscmem_start();
+static int mscmem_stop();
+static int mscmem_ready();
+
+/* USB descriptors, definition at the end of this file */
+static usb_device_descriptor_t device_descriptor_cdcacm
+	__attribute__((aligned(4)));
+static struct config_descriptor_cdcacm config_descriptor_cdcacm
+	__attribute__((aligned(4)));
+static usb_device_descriptor_t device_descriptor_msc
+	__attribute__((aligned(4)));
+static struct config_descriptor_msc config_descriptor_msc
+	__attribute__((aligned(4)));
+
+/* definitions of config structs */
+static const msc_mem_t s_mscCallbacks = {
+	mscmem_init, mscmem_start, mscmem_stop,  mscmem_ready,
+	mscmem_size, mscmem_read,  mscmem_write,
+};
+
+static struct esb_device_descriptors s_dd_cdcacm = {
+	.device = &device_descriptor_cdcacm, .cdcacm = &config_descriptor_cdcacm
+};
+
+static struct esb_device_descriptors s_dd_msc = {
+	.device = &device_descriptor_msc, .msc = &config_descriptor_msc
+};
+
+static struct esb_config s_cfg_cdcacm = {
+	.name        = "cdcacm",
+	.init        = esb_cdc_init,
+	.configure   = esb_cdc_configure,
+	.deconfigure = esb_cdc_deconfigure,
+	.deinit      = NULL,
+
+	.descriptors = &s_dd_cdcacm,
+	.deviceData  = NULL,
+};
+
+static struct esb_config s_cfg_msc = {
+	.name        = "msc FLASH",
+	.init        = esb_msc_init,
+	.configure   = esb_msc_configure,
+	.deconfigure = esb_msc_deconfigure,
+	.deinit      = NULL,
+
+	.descriptors = &s_dd_msc,
+	.deviceData  = (void *)&s_mscCallbacks,
+};
+
+/* function implementations */
+
+static int dirty = 0;
+static int mscmem_init()
+{
+	LOG_DEBUG("msc", "mem.init");
+	fatfs_detach();
+	return mx25_init();
+}
+
+static uint32_t mscmem_size(void)
+{
+	return mx25_size();
+}
+
+static int mscmem_read(uint32_t lba, uint8_t *buffer)
+{
+	return mx25_read(lba, buffer);
+}
+
+static int mscmem_write(uint32_t lba, uint8_t *buffer)
+{
+	if (dirty == 0) {
+		//bootloader_dirty();
+	}
+	dirty = 2;
+	return mx25_write(lba, buffer);
+}
+
+static int mscmem_start()
+{
+	return mx25_start();
+}
+
+static int mscmem_stop()
+{
+	LOG_DEBUG("msc", "mem.stop");
+	int ret = mx25_stop();
+	fatfs_schedule_attach();
+	return ret;
+}
+
+static int mscmem_ready()
+{
+	if (dirty) {
+		dirty--;
+		if (dirty == 0) {
+			LOG_DEBUG("msc", "sync");
+			mx25_sync();
+			//bootloader_clean();
+		}
+	}
+	return mx25_ready();
+}
+
+static bool s_fsDetached = false;
+int epic_usb_shutdown(void)
+{
+	esb_deinit();
+	if (s_fsDetached) {
+		fatfs_attach();
+	}
+	return 0;
+}
+
+int epic_usb_storage(void)
+{
+	esb_deinit();
+	s_fsDetached = true;
+	return esb_init(&s_cfg_msc);
+}
+
+int epic_usb_cdcacm(void)
+{
+	esb_deinit();
+	if (s_fsDetached) {
+		fatfs_attach();
+	}
+	return esb_init(&s_cfg_cdcacm);
+}
+
+/* clang-format off */
+static usb_device_descriptor_t device_descriptor_cdcacm = {
+	.bLength            = 0x12,
+	.bDescriptorType    = DT_DEVICE,
+	.bcdUSB             = 0x0110,
+	.bDeviceClass       = CLS_COMM,
+	.bDeviceSubClass    = 0x00,
+	.bDeviceProtocol    = 0x00,
+	.bMaxPacketSize     = 0x40,
+	.idVendor           = VNDR_MAXIM,
+	.idProduct          = 0x003C,
+	.bcdDevice          = 0x0100,
+	.iManufacturer      = 0x01,
+	.iProduct           = 0x02,
+	.iSerialNumber      = 0x00,
+	.bNumConfigurations = 0x01
+};
+
+static struct config_descriptor_cdcacm config_descriptor_cdcacm = {
+    .config = {
+        .bLength            = 0x09,
+        .bDescriptorType    = DT_CONFIG,
+        .wTotalLength       = 0x0043,     /*  sizeof(struct config_descriptor_cdcacm) */
+        .bNumInterfaces     = 0x02,
+        .bConfigurationValue= 0x01,
+        .iConfiguration     = 0x00,
+        .bmAttributes       = 0xE0,       /*  (self-powered, remote wakeup) */
+        .bMaxPower          = 0x01,       /*  MaxPower is 2ma (units are 2ma/bit) */
+    },
+    .comm_interface = { /*  First Interface Descriptor For Comm Class Interface */
+        .bLength            = 0x09,
+        .bDescriptorType    = DT_INTERFACE,
+        .bInterfaceNumber   = 0x00,
+        .bAlternateSetting  = 0x00,
+        .bNumEndpoints      = 0x01,
+        .bInterfaceClass    = CLS_COMM,
+        .bInterfaceSubClass = SCLS_ACM,
+        .bInterfaceProtocol = PROT_AT_CMDS,
+        .iInterface         = 0x00,
+    },
+    .header_functional = {
+        0x05,         /*  bFunctionalLength = 5           */
+        DT_FUNCTIONAL,/*  bDescriptorType                 */
+        0x00,         /*  bDescriptorSubtype              */
+        0x10, 0x01,   /*  bcdCDC                          */
+    },
+    .call_management = {
+        0x05,         /*  bFunctionalLength = 5           */
+        DT_FUNCTIONAL,/*  bDescriptorType                 */
+        0x01,         /*  bDescriptorSubtype              */
+        0x03,         /*  bmCapabilities = Device handles call management itself (0x01), management over data class (0x02) */
+        0x01,         /*  bmDataInterface                 */
+    },
+    .acm_functional = {
+        0x04,         /*  bFunctionalLength = 4           */
+        DT_FUNCTIONAL,/*  bDescriptorType                 */
+        0x02,         /*  bDescriptorSubtype              */
+        0x02,         /*  bmCapabilities                  */
+    },
+    .union_functional = {
+        0x05,         /*  bFunctionalLength = 5           */
+        DT_FUNCTIONAL,/*  bDescriptorType                 */
+        0x06,         /*  bDescriptorSubtype              */
+        0x00,         /*  bmMasterInterface               */
+        0x01,         /*  bmSlaveInterface0               */
+    },
+    .endpoint_notify = {
+        .bLength            = 0x07,
+        .bDescriptorType    = DT_ENDPOINT,
+        .bEndpointAddress   = 0x80 | ESB_ENDPOINT_CDCACM_NOTIFY,
+        .bmAttributes       = ATTR_INTERRUPT,
+        .wMaxPacketSize     = 0x0040,
+        .bInterval          = 0xff,
+    },
+    .data_interface = {
+        .bLength            = 0x09,
+        .bDescriptorType    = DT_INTERFACE,
+        .bInterfaceNumber   = 0x01,
+        .bAlternateSetting  = 0x00,
+        .bNumEndpoints      = 0x02,
+        .bInterfaceClass    = CLS_DATA,
+        .bInterfaceSubClass = 0x00,
+        .bInterfaceProtocol = 0x00,
+        .iInterface         = 0x00, //not 1?
+    },
+    .endpoint_out = {
+        .bLength            = 0x07,
+        .bDescriptorType    = DT_ENDPOINT,
+        .bEndpointAddress   = ESB_ENDPOINT_CDCACM_OUT,
+        .bmAttributes       = ATTR_BULK,
+        .wMaxPacketSize     = 0x0040,
+        .bInterval          = 0x00,
+    },
+    .endpoint_in = {
+        .bLength            = 0x07,
+        .bDescriptorType    = DT_ENDPOINT,
+        .bEndpointAddress   = 0x80 | ESB_ENDPOINT_CDCACM_IN,
+        .bmAttributes       = ATTR_BULK,
+        .wMaxPacketSize     = 0x0040,
+        .bInterval          = 0x00
+    }
+};
+
+static usb_device_descriptor_t device_descriptor_msc = {
+	.bLength            = 0x12,
+	.bDescriptorType    = DT_DEVICE,
+	.bcdUSB             = 0x0200,
+	.bDeviceClass       = CLS_UNSPECIFIED,
+	.bDeviceSubClass    = 0x00,
+	.bDeviceProtocol    = 0x00,
+	.bMaxPacketSize     = 0x40,
+	.idVendor           = VNDR_MAXIM,
+	.idProduct          = 0x4402,
+	.bcdDevice          = 0x0100,
+	.iManufacturer      = 0x01,
+	.iProduct           = 0x02,
+	.iSerialNumber      = 0x03,
+	.bNumConfigurations = 0x01
+};
+
+static struct config_descriptor_msc config_descriptor_msc =
+{
+    .config = {
+        .bLength            = 0x09,
+        .bDescriptorType    = DT_CONFIG,
+        .wTotalLength       = 0x0020,
+        .bNumInterfaces     = 0x01,
+        .bConfigurationValue= 0x01,
+        .iConfiguration     = 0x00,
+        .bmAttributes       = 0xC0,       /* (self-powered, no remote wakeup) */
+        .bMaxPower          = 0x32,
+    },
+    .msc_interface = {
+        .bLength                = 0x09,
+        .bDescriptorType        = DT_INTERFACE,
+        .bInterfaceNumber       = 0x00,
+        .bAlternateSetting      = 0x00,
+        .bNumEndpoints          = 0x02,
+        .bInterfaceClass        = CLS_MASS_STOR,
+        .bInterfaceSubClass     = SCLS_SCSI_CMDS,
+        .bInterfaceProtocol     = PROT_BULK_TRANS,
+        .iInterface             = 0x00,
+    },
+    .endpoint_out = {
+        .bLength            = 0x07,
+        .bDescriptorType    = DT_ENDPOINT,
+        .bEndpointAddress   = ESB_ENDPOINT_MSC_OUT,
+        .bmAttributes       = ATTR_BULK,
+        .wMaxPacketSize     = 0x0040,
+        .bInterval          = 0x00,
+    },
+    .endpoint_in = {
+        .bLength            = 0x07,
+        .bDescriptorType    = DT_ENDPOINT,
+        .bEndpointAddress   = 0x80 | ESB_ENDPOINT_MSC_IN,
+        .bmAttributes       = ATTR_BULK,
+        .wMaxPacketSize     = 0x0040,
+        .bInterval          = 0x00
+    }
+};
+/* clang-format on */
\ No newline at end of file
diff --git a/epicardium/modules/watchdog.c b/epicardium/modules/watchdog.c
index a538035d685673ff1e3976037211c1e8e766bb3c..fa8792cce1f8d6dc9caccf7531d9d6034439b7b4 100644
--- a/epicardium/modules/watchdog.c
+++ b/epicardium/modules/watchdog.c
@@ -14,8 +14,34 @@ static void watchdog_clearer_callback()
 	WDT_ResetTimer(MXC_WDT0);
 }
 
+void watchdog_init()
+{
+	/*
+	 * Don't enable the the watchdog when a debugger is connected.
+	 */
+	if ((CoreDebug->DHCSR & CoreDebug_DHCSR_C_DEBUGEN_Msk) != 0) {
+		return;
+	}
+
+	sys_cfg_wdt_t wdt_cfg = NULL;
+	WDT_Init(MXC_WDT0, wdt_cfg);
+
+	if (WDT_GetResetFlag(MXC_WDT0)) {
+		WDT_ClearResetFlag(MXC_WDT0);
+		LOG_INFO("watchdog", "Last reset was due to watchdog timeout");
+	}
+
+	WDT_Enable(MXC_WDT0, 1);
+	WDT_SetResetPeriod(
+		MXC_WDT0,
+		WDT_PERIOD_2_28); /* Clocked by PCLK at 50MHz, reset at 2^28 ticks = 5.4 seconds */
+	WDT_EnableReset(MXC_WDT0, 1);
+}
+
 void watchdog_clearer_init()
 {
+	WDT_ResetTimer(MXC_WDT0);
+
 	clearer_timer = xTimerCreateStatic(
 		"watchdog_clearer_timer",
 		CLEAR_PERIOD,
diff --git a/epicardium/usb/cdcacm.c b/epicardium/usb/cdcacm.c
new file mode 100644
index 0000000000000000000000000000000000000000..ea046721a99b672c114831b5c6da752a6579a5b6
--- /dev/null
+++ b/epicardium/usb/cdcacm.c
@@ -0,0 +1,130 @@
+/**
+ * 
+ * epicardium-specific implemnetation of cdcacm device
+ * services CDCACM API
+ */
+
+#include "usb/epc_usb.h"
+#include "usb/cdcacm.h"
+#include "usb/descriptors.h"
+
+#include <stdio.h>
+#include <stddef.h>
+#include <errno.h>
+
+#include "usb.h"
+#include "usb_event.h"
+#include "cdc_acm.h"
+
+#include "modules/log.h"
+#include "modules/modules.h"
+
+#include "FreeRTOS.h"
+#include "task.h"
+
+static inline struct config_descriptor_cdcacm *
+descriptors(struct esb_config *self)
+{
+	return self->descriptors->cdcacm;
+}
+
+/******************************************************************************/
+static volatile int usb_read_complete; // SWYM: only ever written to
+static int cb_acm_read_ready(void)
+{
+	usb_read_complete = 1;
+	return 0;
+}
+
+int esb_cdc_init(struct esb_config *self)
+{
+	LOG_DEBUG("cdcacm", "init");
+	struct config_descriptor_cdcacm *dsc = descriptors(self);
+	usb_read_complete                    = 0;
+	acm_register_callback(
+		ACM_CB_READ_READY,
+		cb_acm_read_ready); //SWYM: actually not needed
+	return acm_init(&dsc->comm_interface);
+}
+
+int esb_cdc_configure(struct esb_config *self)
+{
+	struct config_descriptor_cdcacm *dsc = descriptors(self);
+
+	//acm_configure does not keep the amc_cfg_t pointer around
+	//so stack-local lifetime is fine
+	acm_cfg_t acm_cfg = {
+		.in_ep         = dsc->endpoint_in.bEndpointAddress & 0x0f,
+		.in_maxpacket  = MXC_USBHS_MAX_PACKET,
+		.out_ep        = dsc->endpoint_out.bEndpointAddress,
+		.out_maxpacket = MXC_USBHS_MAX_PACKET,
+		.notify_ep     = dsc->endpoint_notify.bEndpointAddress & 0x0f,
+		.notify_maxpacket = MXC_USBHS_MAX_PACKET,
+	};
+
+	LOG_DEBUG(
+		"cdcacm",
+		"configure, endpoints %d,%d,%d",
+		acm_cfg.in_ep,
+		acm_cfg.out_ep,
+		acm_cfg.notify_ep
+	);
+
+	return acm_configure(&acm_cfg);
+}
+
+int esb_cdc_deconfigure(struct esb_config *self)
+{
+	LOG_DEBUG("cdcacm", "deconfigure");
+	return acm_deconfigure();
+}
+
+int cdcacm_num_read_avail(void)
+{
+	return acm_canread();
+}
+
+uint8_t cdcacm_read(void)
+{
+	while (acm_canread() <= 0) {
+	}
+
+	uint8_t buf;
+	acm_read(&buf, 1);
+	return buf;
+}
+
+void cdcacm_write(uint8_t *data, int len)
+{
+	static int lockup_disable = 0;
+	if (acm_present() && !lockup_disable) {
+		int ret = acm_write(data, len);
+		if (ret < 0) {
+			LOG_ERR("cdcacm", "fifo lockup detected");
+			lockup_disable = 1;
+		} else if (ret != len) {
+			LOG_WARN(
+				"cdcacm", "write length mismatch, got %d", ret
+			);
+		}
+	}
+}
+
+/******************************************************************************/
+
+extern TaskHandle_t serial_task_id;
+void USB_IRQHandler(void)
+{
+	usb_event_handler();
+
+	if (serial_task_id != NULL) {
+		BaseType_t xHigherPriorityTaskWoken = pdFALSE;
+		xTaskNotifyFromISR(
+			serial_task_id,
+			SERIAL_READ_NOTIFY,
+			eSetBits,
+			&xHigherPriorityTaskWoken
+		);
+		portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
+	}
+}
diff --git a/epicardium/usb/cdcacm.h b/epicardium/usb/cdcacm.h
new file mode 100644
index 0000000000000000000000000000000000000000..6683e05558aa06ffef793e3f5a8ab4fe03df1a35
--- /dev/null
+++ b/epicardium/usb/cdcacm.h
@@ -0,0 +1,15 @@
+#ifndef CDCACM_H
+#define CDCACM_H
+
+#include "usb/epc_usb.h"
+
+// callbacks for esb_config
+int esb_cdc_init(struct esb_config* self);
+int esb_cdc_configure(struct esb_config* self);
+int esb_cdc_deconfigure(struct esb_config* self);
+
+int cdcacm_num_read_avail(void);
+uint8_t cdcacm_read(void);
+void cdcacm_write(uint8_t *data, int len);
+
+#endif
diff --git a/epicardium/usb/descriptors.h b/epicardium/usb/descriptors.h
new file mode 100644
index 0000000000000000000000000000000000000000..2fb2cc452ffec6cfe35106c5e311df1fc09a67ac
--- /dev/null
+++ b/epicardium/usb/descriptors.h
@@ -0,0 +1,83 @@
+#ifndef _DESCRIPTORS_H_
+#define _DESCRIPTORS_H_
+
+#include <stdint.h>
+#include "usb.h"
+
+#define ESB_ENDPOINT_MSC_IN         2
+#define ESB_ENDPOINT_MSC_OUT        1
+#define ESB_ENDPOINT_CDCACM_NOTIFY  3
+#define ESB_ENDPOINT_CDCACM_IN      2
+#define ESB_ENDPOINT_CDCACM_OUT     1
+
+/* device types */
+#define DT_DEVICE       0x01
+#define DT_CONFIG       0x02
+#define DT_INTERFACE    0x04
+#define DT_ENDPOINT     0x05
+#define DT_FUNCTIONAL   0x24
+
+/* interface classes */
+#define CLS_UNSPECIFIED 0x00
+#define CLS_COMM        0x02
+#define CLS_MASS_STOR   0x08
+#define CLS_DATA        0x0a
+
+/* sub-classes */
+#define SCLS_NONE       0x00
+#define SCLS_ACM        0x02
+#define SCLS_SCSI_CMDS  0x06
+
+/* interface protocols */
+#define PROT_AT_CMDS    0x01
+#define PROT_BULK_TRANS 0x50
+
+/* endpoint attributes */
+#define ATTR_BULK       0x02
+#define ATTR_INTERRUPT  0x03
+
+
+#define VNDR_MAXIM  0x0B6A
+
+#if defined(__GNUC__)
+#define PACKED_STRUCT struct __attribute__((packed))
+#else
+#define PACKED_STRUCT __packed struct
+#endif
+
+
+
+
+PACKED_STRUCT
+config_descriptor_cdcacm {
+    usb_configuration_descriptor_t  config;
+    usb_interface_descriptor_t      comm_interface;
+    uint8_t                         header_functional[5];
+    uint8_t                         call_management[5];
+    uint8_t                         acm_functional[4];
+    uint8_t                         union_functional[5];
+    usb_endpoint_descriptor_t       endpoint_notify;
+    usb_interface_descriptor_t      data_interface;
+    usb_endpoint_descriptor_t       endpoint_out;
+    usb_endpoint_descriptor_t       endpoint_in;
+};
+
+PACKED_STRUCT
+config_descriptor_msc {
+    usb_configuration_descriptor_t  config;
+    usb_interface_descriptor_t      msc_interface;
+    usb_endpoint_descriptor_t       endpoint_out;
+    usb_endpoint_descriptor_t       endpoint_in;
+};
+
+struct esb_device_descriptors {
+    usb_device_descriptor_t* device;
+    union {
+        usb_configuration_descriptor_t* config;
+        struct config_descriptor_cdcacm* cdcacm;
+        struct config_descriptor_msc* msc;
+    };
+};
+
+
+#endif /* _DESCRIPTORS_H_ */
diff --git a/epicardium/usb/epc_usb.c b/epicardium/usb/epc_usb.c
new file mode 100644
index 0000000000000000000000000000000000000000..b99c34a075689a1f470fd66119359f844547bd6a
--- /dev/null
+++ b/epicardium/usb/epc_usb.c
@@ -0,0 +1,459 @@
+/**
+ * 
+ * 
+ * Core of the USB implementation:
+ * 
+ * - device independent
+ * - handles setup of MAXUSB stack
+ * - handles events and state of the MAXUSB stack
+ * 
+ * Also contains definitions of device-independent String descriptors
+ * 
+ * 
+ * 
+ * 
+ * 
+ * swym's USB ramblings:
+ * 
+ * setting up USB basically consists of two parts:
+ * 
+ * - set up descriptors
+ * - initialize the corresponding MAXUSB stacks - acm, msc...
+ * 
+ * at the moment, the descriptors are statically configured in
+ * descriptors.h several descriptors point to enumeration descriptors
+ * via iFooBar indices. Those enumeration descriptors are registered
+ * via enum_register_descriptor(type, desc, idx), where the idx passed
+ * corresponds to the iFooBar index mentioned above.
+ * 
+ * There are several callbacks, some of which do not perform anything meaningful
+ * at the moment. For example usb_write_callback sets a global flag that is never
+ * read from. These will be removed later on.
+ * 
+ * The  initialization routines of acm & msc refer to the descriptors from which,
+ * among other things, they get their enpoint IDs. These are hard-coded right now
+ * but it would make sense to allocate these IDs at runtime. There is, AFAICT, no
+ * reason for these endpoint IDs to be 3, 1, 2 in the CDCACM case for example.
+ * 
+ * Allocating those at runtime would also make the setup less rigid: we can have all
+ * combinations of {cdcacm, storage} interfaces without the awkward descriptor hackery
+ * in epc_usb_init.
+ * 
+ * To generalize even further, the descriptors could be malloced. The total length of
+ * the descriptor structure can easily be derived from the number of interfaces to be
+ * initialized. The memory allocated will then be split up (with alignment of the single
+ * descriptor structs taken care of) in a simple bump-allocator strategy on demand.
+ * 
+ * 
+ */
+#include "usb/epc_usb.h"
+
+#include <assert.h>
+#include <stdio.h>
+#include <stddef.h>
+
+#include <FreeRTOS.h>
+#include <task.h>
+#include <timers.h>
+
+#include "mxc_config.h"
+#include "mxc_sys.h"
+#include "mxc_delay.h"
+
+// MAXUSB includes
+#include "usb.h"
+#include "usb_event.h"
+#include "enumerate.h"
+#include "msc.h"
+#include "cdc_acm.h"
+
+#include "usb/descriptors.h"
+#include "modules/log.h"
+#include "modules/filesystem.h"
+
+//#define USE_REMOTE_WAKE_ENABLE
+
+/***** Function Prototypes *****/
+static int cb_usb_setconfig(usb_setup_pkt *sud, void *cbdata);
+#ifdef USE_REMOTE_WAKE_ENABLE
+static int cb_usb_setfeature(usb_setup_pkt *sud, void *cbdata);
+static int cb_usb_clrfeature(usb_setup_pkt *sud, void *cbdata);
+#endif
+static int cb_usb_event(maxusb_event_t evt, void *data);
+static int cb_usb_shutdown();
+static int cb_usb_init();
+static void usb_app_sleep(void);
+static void usb_app_wakeup(void);
+/* needed for usb_opts. mxc_delay() takes unsigned long, so can't use it directly */
+static void delay_us(unsigned int usec);
+static void device_deinit();
+static bool device_deconfigure();
+static int device_configure();
+
+volatile int suspended;
+#ifdef USE_REMOTE_WAKE_ENABLE
+int remote_wake_en; //SWYM: unused, only written to!
+#endif
+
+static StaticTimer_t x;
+static TimerHandle_t timerWakeup = NULL;
+static void cb_timerReset(TimerHandle_t t);
+
+static struct esb_config s_device = { 0 };
+
+__attribute__((aligned(4))) uint8_t lang_id_desc[] = {
+	0x04, /* bLength */
+	0x03, /* bDescriptorType */
+	0x09,
+	0x04 /* bString = wLANGID (see usb_20.pdf 9.6.7 String) */
+};
+
+__attribute__((aligned(4))) uint8_t mfg_id_desc[] = {
+	0x22, /* bLength */
+	0x03, /* bDescriptorType */
+	'M',  0, 'a', 0, 'x', 0, 'i', 0, 'm', 0, ' ', 0, 'I', 0, 'n', 0,
+	't',  0, 'e', 0, 'g', 0, 'r', 0, 'a', 0, 't', 0, 'e', 0, 'd', 0,
+};
+
+__attribute__((aligned(4))) uint8_t prod_id_desc[] = {
+	0x24, /* bLength */
+	0x03, /* bDescriptorType */
+	'C',  0, 'A', 0, 'R', 0, 'D', 0, '1', 0, '0', 0, ' ', 0, 'U', 0, 'S', 0,
+	'B',  0, ' ', 0, 'G', 0, 'A', 0, 'D', 0, 'G', 0, 'E', 0, 'T', 0,
+};
+
+/* Not currently used (see device descriptor), but could be enabled if desired */
+__attribute__((aligned(4)))
+uint8_t serial_id_desc[] = { 0x14, /* bLength */
+			     0x03, /* bDescriptorType */
+			     'c',  0,   'a', 0,   'a', 0,   'a', 0,   'a',
+			     0,    'd', 0,   '1', 0,   '0', 0,   '0', 0 };
+
+int cb_usb_init()
+{
+	const sys_cfg_usbhs_t sys_usbhs_cfg = NULL;
+	LOG_DEBUG("usb", "init");
+	return SYS_USBHS_Init(&sys_usbhs_cfg);
+}
+
+volatile bool shutDownCompleted;
+void do_usb_shutdown()
+{
+	LOG_DEBUG("usb", "shutting down...");
+	shutDownCompleted = false;
+	usb_shutdown();
+	for (int i = 0; i < 10000 && !shutDownCompleted; ++i) {
+	}
+}
+int cb_usb_shutdown()
+{
+	SYS_USBHS_Shutdown();
+	SYS_Reset_Periph(SYS_RESET_USB);
+	device_deinit();
+	LOG_DEBUG("usb", "shutdown complete");
+	shutDownCompleted = true;
+	return 0;
+}
+
+/* User-supplied function to delay usec micro-seconds */
+static void delay_us(unsigned int usec)
+{
+	/* mxc_delay() takes unsigned long, so can't use it directly */
+	mxc_delay(usec);
+}
+
+static void device_deinit()
+{
+	esb_cfg_handler deinit = s_device.deinit;
+	s_device.deinit        = NULL;
+	s_device.configure     = NULL;
+	s_device.deconfigure   = NULL;
+	if (deinit) {
+		LOG_DEBUG("usb", "deinit");
+		deinit(&s_device);
+	}
+}
+
+//de-configure last device, if any
+static volatile bool s_configured = false;
+static bool device_deconfigure()
+{
+	if (s_configured) {
+		s_configured = false;
+		if (s_device.deconfigure) {
+			LOG_DEBUG("usb", "deconfigure");
+			enum_clearconfig();
+			s_device.deconfigure(&s_device);
+			return true;
+		}
+	}
+	return false;
+}
+
+static int device_configure()
+{
+	if (!s_configured && s_device.configure) {
+		s_configured = true;
+		LOG_DEBUG("usb", "configure");
+		return s_device.configure(&s_device);
+	}
+	return 0;
+}
+
+static volatile bool s_connected = false;
+static void disconnect()
+{
+	if (s_connected) {
+		LOG_DEBUG("usb", "disconnect");
+		s_connected = false;
+		usb_disconnect();
+		//		SYS_Reset_Periph(SYS_RESET_USB);
+		mxc_delay(10000);
+	}
+}
+static void connect()
+{
+	s_connected = true;
+	usb_connect();
+}
+
+static volatile bool s_initialized = false;
+void esb_deinit(void)
+{
+	if (s_initialized) {
+		s_initialized = false;
+		disconnect();
+		device_deconfigure();
+		do_usb_shutdown();
+		device_deinit();
+	}
+}
+int esb_init(struct esb_config *cfg)
+{
+	esb_deinit();
+	if (cfg == NULL) {
+		return 0;
+	}
+	if (timerWakeup == NULL) {
+		timerWakeup = xTimerCreateStatic(
+			"timerWakeup",     /* name */
+			pdMS_TO_TICKS(50), /* period/time */
+			pdFALSE,           /* auto reload */
+			NULL,              /* timer ID */
+			cb_timerReset,
+			&x);
+	}
+
+	maxusb_cfg_options_t usb_opts;
+
+	/* Initialize state */
+	suspended = 0;
+#ifdef USE_REMOTE_WAKE_ENABLE
+	remote_wake_en = 0;
+#endif
+
+	/* Start out in full speed */
+	usb_opts.enable_hs         = 0;
+	usb_opts.delay_us          = delay_us;
+	usb_opts.init_callback     = cb_usb_init;
+	usb_opts.shutdown_callback = cb_usb_shutdown;
+
+	/* Initialize the usb module */
+	if (usb_init(&usb_opts) != 0) {
+		LOG_ERR("usb", "usb_init() failed");
+		return -EIO;
+	}
+
+	/* Initialize the enumeration module */
+	if (enum_init() != 0) {
+		do_usb_shutdown();
+		LOG_ERR("usb", "enum_init() failed");
+		return -EIO;
+	}
+
+	LOG_INFO("usb", "initializing device %s", cfg->name);
+	if (cfg->init(cfg)) {
+		enum_clearconfig();
+		do_usb_shutdown();
+		LOG_ERR("usb", "device init failed");
+		return -EIO;
+	}
+
+	s_initialized = true;
+	s_device      = *cfg;
+
+	enum_register_descriptor(
+		ENUM_DESC_DEVICE, (uint8_t *)cfg->descriptors->device, 0
+	);
+	enum_register_descriptor(
+		ENUM_DESC_CONFIG, (uint8_t *)cfg->descriptors->config, 0
+	);
+	enum_register_descriptor(ENUM_DESC_STRING, lang_id_desc, 0);
+	enum_register_descriptor(ENUM_DESC_STRING, mfg_id_desc, 1);
+	enum_register_descriptor(ENUM_DESC_STRING, prod_id_desc, 2);
+	enum_register_descriptor(ENUM_DESC_STRING, serial_id_desc, 3);
+	//enum_register_descriptor(ENUM_DESC_STRING, cdcacm_func_desc, 4);
+	//enum_register_descriptor(ENUM_DESC_STRING, msc_func_desc, 5);
+
+	/* Handle configuration */
+	enum_register_callback(ENUM_SETCONFIG, cb_usb_setconfig, NULL);
+#ifdef USE_REMOTE_WAKE_ENABLE
+	/* Handle feature set/clear */
+	enum_register_callback(ENUM_SETFEATURE, cb_usb_setfeature, NULL);
+	enum_register_callback(ENUM_CLRFEATURE, cb_usb_clrfeature, NULL);
+#endif
+
+	/* Register callbacks */
+	usb_event_enable(MAXUSB_EVENT_NOVBUS, cb_usb_event, NULL);
+	usb_event_enable(MAXUSB_EVENT_VBUS, cb_usb_event, NULL);
+
+	/* Start with USB in low power mode */
+	usb_app_sleep();
+	/* TODO: Fix priority */
+	NVIC_SetPriority(USB_IRQn, 6);
+	NVIC_EnableIRQ(USB_IRQn);
+
+	return 0;
+}
+
+static int cb_usb_setconfig(usb_setup_pkt *sud, void *cbdata)
+{
+	LOG_DEBUG("usb", "setconfig %d", sud->wValue);
+	if (sud->wValue == s_device.descriptors->config->bConfigurationValue) {
+		return device_configure();
+	} else if (sud->wValue == 0) {
+		device_deconfigure();
+		return 0;
+	}
+	return -1;
+}
+
+#ifdef USE_REMOTE_WAKE_ENABLE
+static int cb_usb_setfeature(usb_setup_pkt *sud, void *cbdata)
+{
+	if (sud->wValue == FEAT_REMOTE_WAKE) {
+		remote_wake_en = 1;
+		return 0;
+	}
+	return -1;
+}
+
+static int cb_usb_clrfeature(usb_setup_pkt *sud, void *cbdata)
+{
+	if (sud->wValue == FEAT_REMOTE_WAKE) {
+		remote_wake_en = 0;
+		return 0;
+	}
+	return -1;
+}
+#endif
+
+/******************************************************************************/
+static void usb_app_sleep(void)
+{
+	/* TODO: Place low-power code here */
+	suspended = 1;
+}
+
+/******************************************************************************/
+static void usb_app_wakeup(void)
+{
+	/* TODO: Place low-power code here */
+	suspended = 0;
+}
+
+static void cb_timerReset(TimerHandle_t t)
+{
+	(void)t;
+	LOG_DEBUG("usb", "cb_timerReset %08x", usb_get_status());
+
+	LOG_DEBUG("usb", "SYS_USBHS_Shutdown");
+	SYS_USBHS_Shutdown();
+	LOG_DEBUG("usb", "SYS_Reset_Periph");
+	SYS_Reset_Periph(SYS_RESET_USB);
+
+	//copy-paste from esb_init(), need to refactor
+	maxusb_cfg_options_t usb_opts;
+	usb_opts.enable_hs         = 0;
+	usb_opts.delay_us          = delay_us;
+	usb_opts.init_callback     = cb_usb_init;
+	usb_opts.shutdown_callback = cb_usb_shutdown;
+
+	/* Initialize the usb module */
+	if (usb_init(&usb_opts) != 0) {
+		LOG_ERR("usb", "usb_init() failed");
+		return;
+	}
+	usb_event_enable(MAXUSB_EVENT_NOVBUS, cb_usb_event, NULL);
+	usb_event_enable(MAXUSB_EVENT_VBUS, cb_usb_event, NULL);
+}
+
+static void scheduleReset()
+{
+	LOG_DEBUG("usb", "scheduleReset");
+	xTimerChangePeriodFromISR(timerWakeup, pdMS_TO_TICKS(500), NULL);
+}
+
+#if LOG_ENABLE_DEBUG
+static const char *maxusb_event_string(maxusb_event_t evt)
+{
+	static const char *names[MAXUSB_NUM_EVENTS] = {
+		[MAXUSB_EVENT_DPACT]  = "DPACT",
+		[MAXUSB_EVENT_RWUDN]  = "RWUDN",
+		[MAXUSB_EVENT_BACT]   = "BACT",
+		[MAXUSB_EVENT_BRST]   = "BRST",
+		[MAXUSB_EVENT_SUSP]   = "SUSP",
+		[MAXUSB_EVENT_NOVBUS] = "NOVBUS",
+		[MAXUSB_EVENT_VBUS]   = "VBUS",
+		[MAXUSB_EVENT_BRSTDN] = "BRSTDN",
+		[MAXUSB_EVENT_SUDAV]  = "SUDAV",
+	};
+	return evt < MAXUSB_NUM_EVENTS ? names[evt] : "<INVALID>";
+}
+#endif
+
+/******************************************************************************/
+static int cb_usb_event(maxusb_event_t evt, void *data)
+{
+	static int s_VBUS_SUSP_sequence = 0;
+	LOG_DEBUG("usb", "event %s (%d)\n", maxusb_event_string(evt), evt);
+
+	switch (evt) {
+	case MAXUSB_EVENT_NOVBUS:
+		usb_event_disable(MAXUSB_EVENT_BRST);
+		usb_event_disable(MAXUSB_EVENT_SUSP);
+		usb_event_disable(MAXUSB_EVENT_DPACT);
+		usb_disconnect();
+		device_deconfigure();
+		usb_app_sleep();
+		break;
+	case MAXUSB_EVENT_VBUS:
+		s_VBUS_SUSP_sequence = 1;
+		usb_event_clear(MAXUSB_EVENT_BRST);
+		usb_event_enable(MAXUSB_EVENT_BRST, cb_usb_event, NULL);
+		usb_event_clear(MAXUSB_EVENT_SUSP);
+		usb_event_enable(MAXUSB_EVENT_SUSP, cb_usb_event, NULL);
+		connect();
+		usb_app_sleep();
+		break;
+	case MAXUSB_EVENT_BRST:
+		usb_app_wakeup();
+		device_deconfigure();
+		suspended = 0;
+		break;
+	case MAXUSB_EVENT_SUSP:
+		if (!s_VBUS_SUSP_sequence) {
+			scheduleReset();
+		}
+		s_VBUS_SUSP_sequence = 0;
+		//usb_app_sleep();
+		break;
+	case MAXUSB_EVENT_DPACT:
+		usb_app_wakeup();
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
diff --git a/epicardium/usb/epc_usb.h b/epicardium/usb/epc_usb.h
new file mode 100644
index 0000000000000000000000000000000000000000..c6917bd328b270f240ab38a7c8e11f10aa4fea21
--- /dev/null
+++ b/epicardium/usb/epc_usb.h
@@ -0,0 +1,32 @@
+#ifndef EPICARDIUM_USB_EPC_USB_H_INCLUDED
+#define EPICARDIUM_USB_EPC_USB_H_INCLUDED
+
+/**
+ * 
+ * EPC - it's not just a Universal Serial Bus,
+ * it's an Epic Serial Bus!
+ */
+
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+struct esb_config;
+typedef int (*esb_cfg_handler)(struct esb_config* self);
+
+struct esb_config {
+    const char* name;
+    esb_cfg_handler init;
+    esb_cfg_handler configure;
+    esb_cfg_handler deconfigure;
+    esb_cfg_handler deinit;
+
+    struct esb_device_descriptors* descriptors;
+    void* deviceData;
+};
+
+int esb_init(struct esb_config* cfg);
+void esb_deinit(void);
+
+#endif//EPICARDIUM_USB_EPC_USB_H_INCLUDED
diff --git a/epicardium/usb/mass_storage.c b/epicardium/usb/mass_storage.c
new file mode 100644
index 0000000000000000000000000000000000000000..b3c6c0f6cd04efb5cf02e8eb898b3eef7b02efe3
--- /dev/null
+++ b/epicardium/usb/mass_storage.c
@@ -0,0 +1,55 @@
+/**
+ * backend-independent implementation of the mass storage device
+ * services MSC API and esb_cfg API
+ */
+
+#include "usb/mass_storage.h"
+#include "usb/epc_usb.h"
+#include "usb/descriptors.h"
+
+#include "modules/log.h"
+
+#include "msc.h"
+#include "usb.h"
+#include "max32665.h"
+
+#define VENDOR_STRING "CCC"
+#define PRODUCT_STRING "card10"
+#define VERSION_STRING "1.0"
+
+static inline struct config_descriptor_msc *descriptors(struct esb_config *self)
+{
+	return self->descriptors->msc;
+}
+
+int esb_msc_configure(struct esb_config *self)
+{
+	LOG_DEBUG("msc", "configure");
+	struct config_descriptor_msc *dsc = descriptors(self);
+	const msc_cfg_t msc_cfg           = {
+                dsc->endpoint_out.bEndpointAddress,
+                MXC_USBHS_MAX_PACKET, /* OUT max packet size */
+                dsc->endpoint_in.bEndpointAddress & 0x0f,
+                MXC_USBHS_MAX_PACKET, /* IN max packet size */
+	};
+	return msc_configure(&msc_cfg);
+}
+
+int esb_msc_deconfigure(struct esb_config *self)
+{
+	LOG_DEBUG("msc", "deconfigure");
+	(void)self;
+	return msc_deconfigure();
+}
+
+int esb_msc_init(struct esb_config *self)
+{
+	LOG_DEBUG("msc", "init");
+	static const msc_idstrings_t ids = { VENDOR_STRING,
+					     PRODUCT_STRING,
+					     VERSION_STRING };
+
+	msc_mem_t *mem                    = self->deviceData;
+	struct config_descriptor_msc *dsc = descriptors(self);
+	return msc_init(&dsc->msc_interface, &ids, mem);
+}
diff --git a/epicardium/usb/mass_storage.h b/epicardium/usb/mass_storage.h
new file mode 100644
index 0000000000000000000000000000000000000000..77c36bcecd433be56305a94a180945b6dba38dff
--- /dev/null
+++ b/epicardium/usb/mass_storage.h
@@ -0,0 +1,10 @@
+#ifndef EPICARDIUM_USB_MSC_H_INCLUDED
+#define EPICARDIUM_USB_MSC_H_INCLUDED
+
+#include "usb/epc_usb.h"
+
+int esb_msc_configure(struct esb_config* self);
+int esb_msc_deconfigure(struct esb_config* self);
+int esb_msc_init(struct esb_config* self);
+
+#endif//EPICARDIUM_USB_MSC_H_INCLUDED
diff --git a/lib/gfx/gfx.c b/lib/gfx/gfx.c
index dd92ecdc0cd736ba26ab15640b9afd7066e3cbed..9d27d2a5530e168e0ba4cbc795c2fcf27e135885 100644
--- a/lib/gfx/gfx.c
+++ b/lib/gfx/gfx.c
@@ -198,7 +198,7 @@ static void plot_line_low(
 
 	int d = 2 * dy - dx;
 	int y = y0;
-	for (int x = x0; x < x1; x++) {
+	for (int x = x0; x <= x1; x++) {
 		if (t > 1) {
 			gfx_circle_fill(reg, x, y, t, c);
 		} else {
@@ -231,7 +231,7 @@ static void plot_line_high(
 
 	int d = 2 * dx - dy;
 	int x = x0;
-	for (int y = y0; y < y1; y++) {
+	for (int y = y0; y <= y1; y++) {
 		if (t > 1) {
 			gfx_circle_fill(reg, x, y, t, c);
 		} else {
diff --git a/preload/apps/card10_nickname/__init__.py b/preload/apps/card10_nickname/__init__.py
index 0e89c38916b21c2b45d92cf60fe5bcdf31fce8dd..16fc8ad00869017bca56a5a329e3eeb13b074225 100644
--- a/preload/apps/card10_nickname/__init__.py
+++ b/preload/apps/card10_nickname/__init__.py
@@ -343,4 +343,6 @@ else:
                 ([255, 255, 255], [255, 255, 255]),
                 ([0, 0, 0], [0, 0, 0]),
                 ([0, 0, 0], [0, 0, 0]),
+                0,
+                (0, [255, 0, 255], [255, 0, 255], [255, 0, 255]),
             )
diff --git a/preload/menu.py b/preload/menu.py
index 890b888403cfc618aaacb18ccc21490febfaf5b8..e0a84a84169f6a0da3f1e64d6a4aa9eb3a4f8ee4 100644
--- a/preload/menu.py
+++ b/preload/menu.py
@@ -31,7 +31,8 @@ def read_metadata(app_folder):
         with open(info_file) as f:
             information = f.read()
         return ujson.loads(information)
-    except BaseException as e:
+    except Exception as e:
+        print("Failed to read metadata for %s" % (app_folder))
         sys.print_exception(e)
         return {
             "author": "",
@@ -267,4 +268,14 @@ def main():
 
 
 if __name__ == "__main__":
-    main()
+    try:
+        main()
+    except Exception as e:
+        sys.print_exception(e)
+        with display.open() as d:
+            d.clear(color.COMMYELLOW)
+            d.print("Menu", posx=52, posy=20, fg=color.COMMYELLOW_DARK, bg=color.COMMYELLOW)
+            d.print("crashed", posx=31, posy=40, fg=color.COMMYELLOW_DARK, bg=color.COMMYELLOW)
+            d.update()
+            utime.sleep(2)
+        os.exit(1)
diff --git a/pycardium/modules/py/leds.py b/pycardium/modules/py/leds.py
index 0cbf2c3fe3351088e6c89d16fbf01730c5718d49..ad5563cfb2a3c8fca7bf4a4909ac460ba0e86808 100644
--- a/pycardium/modules/py/leds.py
+++ b/pycardium/modules/py/leds.py
@@ -41,7 +41,13 @@ def set_flashlight(on):
     This LED can serve as a flashlight if worn on the left wrist or as a rad
     tattoo illuminator if worn on the right wrist.
 
-    :param bool on:  Side LED on if true.
+    .. warning::
+
+        Because of a small error in the Harmonic Board layout, we could not
+        populate the flashlight LEDs in the production run.  You can handsolder
+        it, though you have to reverse the direction.
+
+    :param bool on:  Side LED on if ``True``.
     """
     sys_leds.set_flashlight(on)
 
diff --git a/pycardium/modules/py/pride.py b/pycardium/modules/py/pride.py
index 3bfb29b601a41c77a921463908a0b1d5b509e105..867e16c05d27f0881765c070303a823e20dae95a 100644
--- a/pycardium/modules/py/pride.py
+++ b/pycardium/modules/py/pride.py
@@ -14,7 +14,7 @@ flags["trans"] = [
     [247, 168, 184],
     [85, 205, 252],
 ]
-flags["bi"] = [[214, 2, 112], [155, 79, 150], [0, 56, 168]]
+flags["bi"] = [[214, 2, 112], [214, 2, 112], [155, 79, 150], [0, 56, 168], [0, 56, 168]]
 flags["ace"] = [[1, 1, 1], [164, 164, 164], [255, 255, 255], [150, 0, 150]]
 flags["greyace"] = [
     [150, 0, 150],
diff --git a/pycardium/modules/py/simple_menu.py b/pycardium/modules/py/simple_menu.py
index 6450e6a1cafd0cf686a4ac1930a1b15ab65ce544..42b117cc7998ac94397bee646f1c3fa82538af11 100644
--- a/pycardium/modules/py/simple_menu.py
+++ b/pycardium/modules/py/simple_menu.py
@@ -167,7 +167,7 @@ class Menu:
             )
 
         self.disp.line(4, 22, 11, 29, col=self.color_sel, size=2)
-        self.disp.line(3, 37, 11, 29, col=self.color_sel, size=2)
+        self.disp.line(4, 36, 11, 29, col=self.color_sel, size=2)
 
         self.disp.update()