diff --git a/.gitignore b/.gitignore
index 1c25d3a0ae2a3c116a097855f681b805294ee1e7..84d239597667c4f4c6711cee1e1bad9a2f1088a8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,4 @@ __pycache__/
 *~
 compile_commands.json
 /tags
+/release-*/
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d947a8e4314479f503e0306eee4c38d3b8194d13..e580443a064b511fba178ad0f53a2330ced62723 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,18 +5,73 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
 ## [Unreleased]
 ### Added
+- `tools/pycard10.py`: Tool to interact with card10's serial connection and
+  upload files directly:
+  ```bash
+  ./tools/pycard10.py path/to/python-script.py
+  ```
+- `epic_disp_print_adv` & `Display.print(font=...)`: Print with different
+  fonts!  The following fonts are supported: `8px`, `12px`, `16px`, `20px`,
+  and `24px`.
+- **pycardium**: Support for RAW REPL mode.
+- **bhi160**: Function to disable all sensors (`bhi160.disable_all_sensors()`).
+- `ls_cmsis_dap`: A tool to enumerate CMSIS-DAP debuggers.
+
+### Changed
+- `main.py` was moved into an app to allow easier reconfiguration of the
+  default app.  The new `main.py` points to the "old" one so behavior is not
+  changed.
+- After a timeout, the menu will close and `main.py` will run again.
+
+### Removed
+- Some unused font files.
+
+### Fixed
+- Fixed a regression which made the ECG app no longer work.
+
+
+
+## [v1.8] - 2019-08-27 11:38 - [HabaneroChilli]
+[HabaneroChilli]: https://card10.badge.events.ccc.de/release/card10-v1.8-HabaneroChilli.zip
+
+### Added
+- API-call for direct light-sensor readout: `epic_light_sensor_read`.
+- Pause mode in ECG-App.
+- `bin` field in metatdata for an alternate entrypoint.
+- `shell.nix`: Nix-Shell which installs patched OpenOCD and dependencies.
+- Cool LED animation in default ECG app.
+
+### Changed
+- No longer require locking the display for setting the backlight.
+
+
+## [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]
@@ -137,7 +192,9 @@ 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.8...master
+[v1.8]: https://git.card10.badge.events.ccc.de/card10/firmware/compare/v1.7...v1.8
+[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/pycardium/light-sensor.rst b/Documentation/pycardium/light-sensor.rst
index 6ffcb05c866a6d8a6f2d20c1412173e69ace5bf8..57c6d1b949eeacd0baeee571dfce23fd131aded6 100644
--- a/Documentation/pycardium/light-sensor.rst
+++ b/Documentation/pycardium/light-sensor.rst
@@ -25,3 +25,13 @@ be fairly stable.
 .. py:function:: stop()
 
    Stop the ADC.
+
+.. py:function:: read()
+
+   Direct readout of the light-sensor.
+
+   Use this function for low latency readout.  The time between polls can have
+   an effect on the values measures.  If you do not need low latency, prefer
+   :py:func:`light_sensor.get_reading`.
+
+   .. versionadded:: 1.8
diff --git a/Documentation/pycardium/overview.rst b/Documentation/pycardium/overview.rst
index 0d3056c8bcf28062b8e51b47c6c34d4c636ac935..49370ada31cf7aa532e67928c5a21d3301cdbdb3 100644
--- a/Documentation/pycardium/overview.rst
+++ b/Documentation/pycardium/overview.rst
@@ -36,8 +36,46 @@ Baud-rate is 115200.  Some options are:
 * **screen**: ``sudo screen /dev/ttyACM0 115200``
 * **picocom**: ``sudo picocom -b 115200 /dev/ttyACM0``
 
-After connecting, reboot card10 and you should see the MicroPython REPL pop up.
+After connecting, reboot reset the card10 via the power button (left upper
+corner) and you should see the output of **menu.py** script (it's located in
+*preload/menu.py*). You can press CTRL-C to interrupt the script and jump into
+the MicroPython prompt.
+
+To switch on the blue fairy dust you must import the led python module::
+
+   import leds
+
+and power it on::
+
+   leds.set_rocket(0, 31)
+
+
+REPL modes
+^^^^^^^^^^
+
+MicroPython supports a different REPL modes over the serial console. The modes
+can be changed on every new line.
+
+Normal mode
+"""""""""""
+This is the mode you will first see. You can switch to it by pressing CTRL-B.
+If you are in a other mode you can return to this mode by pressing CTRL-B too.
+
+Paste mode
+""""""""""
+You can enter the paste mode by pressing CTRL-E and you can simple copy 'n'
+paste your source code into the console and it will be interpreted and executed
+line by line. Every new line will be reply by the prompt with **===**.
+
+RAW mode
+""""""""
+The RAW mode to be intendend for the usage with tools. By pressing CTRL-A you
+will enter the RAW REPL mode. The type in code will not printed. By pressing
+CTRL-D the whole entered code will be evaluated and executed. The board will
+reply with **OK** and print after that the output (print commands) of the code
+or give you tracebacks if an error occured.
+
+You can use **pycard10** (tools/pycard10.py) to execute python files from your
+PC directly on the card10.
 
-.. todo::
 
-   Getting Started Guide for people interested in writing Python code.
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/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 d394d6ce801d518e8c0146a17d00b87f550e43af..615440aff0caa2e42ce377c20ddcb55c0f812f08 100644
--- a/epicardium/epicardium.h
+++ b/epicardium/epicardium.h
@@ -54,6 +54,7 @@ typedef _Bool bool;
 #define API_DISP_PIXEL             0x28
 #define API_DISP_FRAMEBUFFER       0x29
 #define API_DISP_BACKLIGHT         0x2a
+#define API_DISP_PRINT_ADV         0x2b
 
 /* API_BATTERY_VOLTAGE              0x30 */
 #define API_BATTERY_CURRENT        0x31
@@ -102,6 +103,7 @@ typedef _Bool bool;
 #define API_LIGHT_SENSOR_RUN       0x80
 #define API_LIGHT_SENSOR_GET       0x81
 #define API_LIGHT_SENSOR_STOP      0x82
+#define API_LIGHT_SENSOR_READ	   0x83
 
 #define API_BUTTONS_READ           0x90
 
@@ -131,7 +133,11 @@ typedef _Bool bool;
 #define API_MAX86150_GET_DATA		0x0101
 #define API_MAX86150_SET_LED_AMPLITUDE	0x0102
 
-#define API_WS2812_WRITE           0x0110
+#define API_USB_SHUTDOWN           0x110
+#define API_USB_STORAGE            0x111
+#define API_USB_CDCACM             0x112
+
+#define API_WS2812_WRITE           0x0120
 
 /* clang-format on */
 
@@ -1236,6 +1242,40 @@ API(API_DISP_PRINT,
 	    uint16_t bg)
     );
 
+/*
+ * Font Selection
+ */
+enum disp_font_name {
+	DISP_FONT8  = 0, 
+	DISP_FONT12 = 1,
+	DISP_FONT16 = 2,
+	DISP_FONT20 = 3,
+	DISP_FONT24 = 4,
+};
+
+/**
+ * Prints a string into the display framebuffer with font type selectable
+ *
+ * :param fontName: number of font, use FontName enum
+ * :param posx: x position to print to. 0 <= x <= 160
+ * :param posy: y position to print to. 0 <= y <= 80
+ * :param pString: string to print
+ * :param fg: foreground color in rgb565
+ * :param bg: background color in rgb565
+ * :return: ``0`` on success or a negative value in case of an error:
+ *
+ *    - ``-EBUSY``: Display was already locked from another task.
+ */
+API(API_DISP_PRINT_ADV,
+	int epic_disp_print_adv(
+		uint8_t font,
+		uint16_t posx,
+		uint16_t posy,
+		const char *pString,
+		uint16_t fg,
+		uint16_t bg)
+);
+
 /**
  * Fills the whole screen with one color
  *
@@ -1349,12 +1389,12 @@ API(API_DISP_FRAMEBUFFER, int epic_disp_framebuffer(union disp_framebuffer *fb))
 
 
 /**
- * Set the backlight brightness value
+ * Set the backlight brightness.
  *
- * :param brightness: brightness from 0 - 100
- * :return: ``0`` on success or negative value in case of an error:
+ * Note that this function does not require acquiring the display.
  *
- *    - ``-EBUSY``: Display was already locked from another task.
+ * :param brightness: brightness from 0 - 100
+ * :return: ``0`` on success or negative value in case of an error
  */
 API(API_DISP_BACKLIGHT, int epic_disp_backlight(uint16_t brightness));
 
@@ -1397,6 +1437,20 @@ API(API_LIGHT_SENSOR_GET, int epic_light_sensor_get(uint16_t* value));
  */
 API(API_LIGHT_SENSOR_STOP, int epic_light_sensor_stop());
 
+/**
+ * Get the light level directly.
+ *
+ * Each call has an intrinsic delay of about 240us, I recommend another
+ * 100-300us delay  between calls. Whether or not the IR LED is fast enough is
+ * another issue.
+ *
+ * :return: Light level
+ *
+ * .. versionadded:: 1.8
+ */
+API(API_LIGHT_SENSOR_READ, uint16_t epic_light_sensor_read(void));
+
+
 /**
  * File
  * ====
@@ -1702,6 +1756,22 @@ 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));
+
 /**
  * WS2812
  * ======
@@ -1717,5 +1787,5 @@ void
  */
 API(API_WS2812_WRITE, void epic_ws2812_write(uint8_t pin, uint8_t *pixels, uint32_t n_bytes));
 
-
 #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 be3e35197329c723da10ca3562928b7a4f5e6a28..56639ea56ead01fc06138e0f2f712518193c475f 100644
--- a/epicardium/main.c
+++ b/epicardium/main.c
@@ -24,10 +24,10 @@ int main(void)
 	 * 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);
+	const int off           = (160 - (int)strlen(version_buf) * 14) / 2;
+	epic_disp_clear(0x9dc0);
+	epic_disp_print(10, 20, "Epicardium", 0x6c20, 0x9dc0);
+	epic_disp_print(off > 0 ? off : 0, 40, version_buf, 0x6c20, 0x9dc0);
 	epic_disp_update();
 	mxc_delay(2000000);
 
@@ -39,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();
@@ -134,6 +134,11 @@ int main(void)
 		abort();
 	}
 
+	/*
+	 * Initialize serial driver data structures.
+	 */
+	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/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/display.c b/epicardium/modules/display.c
index c8dc51492ba51f3867405784e30719935282147b..fe89fc324b41f7f9f944b0de4ce087558ac5a45e 100644
--- a/epicardium/modules/display.c
+++ b/epicardium/modules/display.c
@@ -27,12 +27,40 @@ int epic_disp_print(
 	const char *pString,
 	uint16_t fg,
 	uint16_t bg
+) {
+	return epic_disp_print_adv(DISP_FONT20, posx, posy, pString, fg, bg);
+}
+
+static const sFONT *font_map[] = {
+	[DISP_FONT8] = &Font8,   [DISP_FONT12] = &Font12,
+	[DISP_FONT16] = &Font16, [DISP_FONT20] = &Font20,
+	[DISP_FONT24] = &Font24,
+};
+
+int epic_disp_print_adv(
+	uint8_t font,
+	uint16_t posx,
+	uint16_t posy,
+	const char *pString,
+	uint16_t fg,
+	uint16_t bg
 ) {
 	int cl = check_lock();
+	if (font >= (sizeof(font_map) / sizeof(sFONT *))) {
+		return -EINVAL;
+	}
 	if (cl < 0) {
 		return cl;
 	} else {
-		gfx_puts(&Font20, &display_screen, posx, posy, pString, fg, bg);
+		gfx_puts(
+			font_map[font],
+			&display_screen,
+			posx,
+			posy,
+			pString,
+			fg,
+			bg
+		);
 		return 0;
 	}
 }
@@ -173,11 +201,7 @@ int epic_disp_framebuffer(union disp_framebuffer *fb)
 
 int epic_disp_backlight(uint16_t brightness)
 {
-	int cl = check_lock();
-	if (cl < 0) {
-		return cl;
-	}
-
+	/* TODO: lock? */
 	LCD_SetBacklight(brightness);
 	return 0;
 }
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 cb983dae9ad8a696596c28f5317f7587574f1e7b..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"
@@ -152,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");
 	}
 
 	/*
@@ -177,6 +177,16 @@ int hardware_early_init(void)
 	 */
 	hwlock_init();
 
+	/*
+	 * API Dispatcher Mutex
+	 */
+	dispatcher_mutex_init();
+
+	/*
+	 * MAX30001 mutex init
+	 */
+	max30001_mutex_init();
+
 	return 0;
 }
 
diff --git a/epicardium/modules/light_sensor.c b/epicardium/modules/light_sensor.c
index ff2cd81c15b0984ba91239076dc11c418e254fd2..de20b6e2b21c20e269f8fb19b8dde76b6a13ffdb 100644
--- a/epicardium/modules/light_sensor.c
+++ b/epicardium/modules/light_sensor.c
@@ -27,6 +27,19 @@ static int light_sensor_init()
 	return 0;
 }
 
+uint16_t epic_light_sensor_read()
+{
+	if (hwlock_acquire(HWLOCK_ADC, pdMS_TO_TICKS(1000)) != 0) {
+		return 0;
+	}
+
+	ADC_StartConvert(ADC_CH_7, 0, 0);
+	ADC_GetData(&last_value);
+
+	hwlock_release(HWLOCK_ADC);
+	return last_value;
+}
+
 static void readAdcCallback()
 {
 	if (hwlock_acquire(HWLOCK_ADC, 0) != 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 cdf89e5b6108cac80a11368f2b4fbbb6e2c971c4..f2ffa57893dc9e7f3bb8404d3afc16f7e154942d 100644
--- a/epicardium/modules/meson.build
+++ b/epicardium/modules/meson.build
@@ -21,5 +21,6 @@ module_sources = files(
   'trng.c',
   'vibra.c',
   'watchdog.c',
-  'ws2812.c',
+  'usb.c',
+  'ws2812.c'
 )
diff --git a/epicardium/modules/modules.h b/epicardium/modules/modules.h
index 9eea0cb6130dbe3e94492e402620100714177af5..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);
@@ -91,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/serial.c b/epicardium/modules/serial.c
index b7be0594f3fcacbee9ac3ed7ab6ff0163c74f1a8..3292ab9330c973a83243b9aad0c3e7b63a6d0200 100644
--- a/epicardium/modules/serial.c
+++ b/epicardium/modules/serial.c
@@ -4,46 +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)
 {
-	uint32_t basepri = __get_BASEPRI();
-	if (xPortIsInsideInterrupt()) {
-		taskENTER_CRITICAL_FROM_ISR();
-	} else {
-		taskENTER_CRITICAL();
+	if (length == 0) {
+		return;
 	}
 
-	UART_Write(ConsoleUart, (uint8_t *)str, length);
+	/*
+	 * 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 {
-		taskEXIT_CRITICAL();
+		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);
 	}
-
-	cdcacm_write((uint8_t *)str, length);
-	ble_uart_write((uint8_t *)str, length);
 }
 
 /*
@@ -101,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);
 }
 
@@ -122,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);
@@ -145,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) {
@@ -152,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/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/Fonts/font12CN.c b/lib/gfx/Fonts/font12CN.c
deleted file mode 100644
index 325617a3430dbe4f2ad08f013ccf8c05adbd6e83..0000000000000000000000000000000000000000
--- a/lib/gfx/Fonts/font12CN.c
+++ /dev/null
@@ -1,120 +0,0 @@
-/**
-  ******************************************************************************
-  * @file    Font12.c
-  * @author  MCD Application Team
-  * @version V1.0.0
-  * @date    18-February-2014
-  * @brief   This file provides text Font12 for STM32xx-EVAL's LCD driver. 
-  ******************************************************************************
-  * @attention
-  *
-  * <h2><center>&copy; COPYRIGHT(c) 2014 STMicroelectronics</center></h2>
-  *
-  * Redistribution and use in source and binary forms, with or without modification,
-  * are permitted provided that the following conditions are met:
-  *   1. Redistributions of source code must retain the above copyright notice,
-  *      this list of conditions and the following disclaimer.
-  *   2. Redistributions in binary form must reproduce the above copyright notice,
-  *      this list of conditions and the following disclaimer in the documentation
-  *      and/or other materials provided with the distribution.
-  *   3. Neither the name of STMicroelectronics nor the names of its contributors
-  *      may be used to endorse or promote products derived from this software
-  *      without specific prior written permission.
-  *
-  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-  *
-  ******************************************************************************
-  */
-
-/* Includes ------------------------------------------------------------------*/
-#include "fonts.h"
-
-
-// 
-//  Font data for Courier New 12pt
-// 
-
-const CH_CN Font12CN_Table[] = 
-{
-/*--  ����:  ��  --*/
-/*--  ΢���ź�12;  �������¶�Ӧ�ĵ���Ϊ����x��=16x21   --*/
-{"��",
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1D,0xC0,0x1D,0x80,0x3B,0xFF,0x3B,0x07,
-0x3F,0x77,0x7E,0x76,0xF8,0x70,0xFB,0xFE,0xFB,0xFE,0x3F,0x77,0x3F,0x77,0x3E,0x73,
-0x38,0x70,0x38,0x70,0x3B,0xE0,0x00,0x00,0x00,0x00},
-
-/*--  ����:  ��  --*/
-/*--  ΢���ź�12;  �������¶�Ӧ�ĵ���Ϊ����x��=16x21   --*/
-{"��",
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x00,0x73,0xFF,0x70,0x0F,0xFE,0x1E,
-0x7E,0x3C,0x6E,0x38,0xEE,0x30,0xEF,0xFF,0xFC,0x30,0x7C,0x30,0x38,0x30,0x3E,0x30,
-0x7E,0x30,0xE0,0x30,0xC1,0xF0,0x00,0x00,0x00,0x00},
-
-/*--  ����:  ��  --*/
-/*--  ΢���ź�12;  �������¶�Ӧ�ĵ���Ϊ����x��=16x21   --*/
-{"��",
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x0E,0x30,0x0E,0x3F,0xEE,0x30,0xEE,
-0xFC,0xFF,0x76,0xCE,0x77,0xFE,0x7B,0xFE,0xFF,0xFE,0xF3,0xDE,0xF3,0xCE,0x37,0xEE,
-0x3E,0x6E,0x3C,0x0E,0x30,0x3E,0x00,0x00,0x00,0x00},
-
-/*--  ����:  ݮ  --*/
-/*--  ΢���ź�12;  �������¶�Ӧ�ĵ���Ϊ����x��=16x21   --*/
-{"ݮ",
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x70,0xFF,0xFF,0x3E,0x70,0x38,0x00,
-0x7F,0xFF,0xE0,0x00,0xFF,0xFC,0x3B,0x8C,0x39,0xCC,0xFF,0xFF,0x73,0x9C,0x71,0xDC,
-0x7F,0xFF,0x00,0x1C,0x01,0xF8,0x00,0x00,0x00,0x00},
-
-/*--  ����:  ��  --*/
-/*--  ΢���ź�12;  �������¶�Ӧ�ĵ���Ϊ����x��=16x21   --*/
-{"��",
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0x1F,0xFF,0xF0,0x3E,0x00,0x0E,0x1F,
-0xCF,0xFB,0xFF,0xF8,0x3F,0xFF,0x0F,0xFF,0x7F,0xD8,0x7F,0xDC,0x6F,0xCE,0xED,0xFF,
-0xFD,0xF7,0xF9,0xC0,0x00,0x00,0x00,0x00,0x00,0x00},
-
-/*--  ����:  a  --*/
-/*--  ΢���ź�12;  �������¶�Ӧ�ĵ���Ϊ����x��=16x21   --*/
-{"a",
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x3E,0x00,0x67,0x00,0x07,0x80,0x0F,0x80,0x7F,0x80,0xE3,0x80,0xE7,0x80,0xE7,0x80,
-0x7F,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
-
-/*--  ����:  b  --*/
-/*--  ΢���ź�12;  �������¶�Ӧ�ĵ���Ϊ����x��=16x21   --*/
-{"b",
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x70,0x00,0x70,0x00,0x70,0x00,0x70,0x00,
-0x7F,0x00,0x7B,0x80,0x71,0xC0,0x71,0xC0,0x71,0xC0,0x71,0xC0,0x71,0xC0,0x7B,0x80,
-0x7F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
-
-/*--  ����:  c  --*/
-/*--  ΢���ź�12;  �������¶�Ӧ�ĵ���Ϊ����x��=16x21   --*/
-{"c",
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x3F,0x00,0x73,0x00,0xF0,0x00,0xE0,0x00,0xE0,0x00,0xE0,0x00,0xF0,0x00,0x73,0x00,
-0x3F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
-
-/*--  ����:  A  --*/
-/*--  ΢���ź�12;  �������¶�Ӧ�ĵ���Ϊ����x��=16x21   --*/
-{"A",
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0E,0x00,0x1F,0x00,0x1F,0x00,
-0x1F,0x00,0x3B,0x80,0x3B,0x80,0x71,0x80,0x7F,0xC0,0x71,0xC0,0xE0,0xE0,0xE0,0xE0,
-0xE0,0xE0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
-};
-
-cFONT Font12CN = {
-  Font12CN_Table,
-  sizeof(Font12CN_Table)/sizeof(CH_CN),  /*size of table*/
-  11, /* ASCII Width */
-  16, /* Width */
-  21, /* Height */
-};
-
-/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
diff --git a/lib/gfx/Fonts/font24CN.c b/lib/gfx/Fonts/font24CN.c
deleted file mode 100644
index 70140e3012d94b7beea5adffbde12b224eab2f5e..0000000000000000000000000000000000000000
--- a/lib/gfx/Fonts/font24CN.c
+++ /dev/null
@@ -1,465 +0,0 @@
-/**
-  ******************************************************************************
-  * @file    Font12.c
-  * @author  MCD Application Team
-  * @version V1.0.0
-  * @date    18-February-2014
-  * @brief   This file provides text Font12 for STM32xx-EVAL's LCD driver. 
-  ******************************************************************************
-  * @attention
-  *
-  * <h2><center>&copy; COPYRIGHT(c) 2014 STMicroelectronics</center></h2>
-  *
-  * Redistribution and use in source and binary forms, with or without modification,
-  * are permitted provided that the following conditions are met:
-  *   1. Redistributions of source code must retain the above copyright notice,
-  *      this list of conditions and the following disclaimer.
-  *   2. Redistributions in binary form must reproduce the above copyright notice,
-  *      this list of conditions and the following disclaimer in the documentation
-  *      and/or other materials provided with the distribution.
-  *   3. Neither the name of STMicroelectronics nor the names of its contributors
-  *      may be used to endorse or promote products derived from this software
-  *      without specific prior written permission.
-  *
-  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-  *
-  ******************************************************************************
-  */
-
-/* Includes ------------------------------------------------------------------*/
-#include "fonts.h"
-
-
-// 
-//  Font data for Courier New 12pt
-// 
-
-const CH_CN Font24CN_Table[] = 
-{
-/*--  ����:  ��  --*/
-/*--  ΢���ź�24;  �������¶�Ӧ�ĵ���Ϊ����x��=32x41   --*/
-{"��",
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xC1,0xC0,0x00,
-0x01,0xE3,0xE0,0x00,0x03,0xE3,0xC0,0x00,0x03,0xC7,0x80,0x00,0x03,0xC7,0xFF,0xFF,
-0x07,0x8F,0xFF,0xFF,0x07,0x8F,0x00,0x0F,0x0F,0x1E,0x00,0x1E,0x0F,0x3C,0x1E,0x1E,
-0x1F,0x3C,0x1E,0x3E,0x1F,0x18,0x1E,0x3C,0x3F,0x00,0x1E,0x1C,0x7F,0x00,0x1E,0x00,
-0x7F,0x07,0x9E,0x70,0xFF,0x07,0x9E,0xF0,0xEF,0x0F,0x9E,0x78,0x6F,0x0F,0x1E,0x78,
-0x0F,0x0F,0x1E,0x3C,0x0F,0x1E,0x1E,0x3C,0x0F,0x1E,0x1E,0x1E,0x0F,0x3C,0x1E,0x1E,
-0x0F,0x3C,0x1E,0x1F,0x0F,0x7C,0x1E,0x0F,0x0F,0x78,0x1E,0x0E,0x0F,0x00,0x1E,0x00,
-0x0F,0x00,0x1E,0x00,0x0F,0x00,0x3C,0x00,0x0F,0x07,0xFC,0x00,0x0F,0x07,0xF8,0x00,
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00},
-
-/*--  ����:  ��  --*/
-/*--  ΢���ź�24;  �������¶�Ӧ�ĵ���Ϊ����x��=32x41   --*/
-{"��",
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0F,0x00,0x00,0x00,0x0F,0x00,0x00,0x00,
-0x0F,0x07,0xFF,0xFE,0x0F,0x07,0xFF,0xFE,0x0F,0x00,0x00,0x3E,0x1E,0x00,0x00,0xFC,
-0xFF,0xF8,0x01,0xF0,0xFF,0xF8,0x03,0xE0,0x1E,0x78,0x07,0xC0,0x1E,0x78,0x0F,0x80,
-0x3C,0x78,0x0F,0x00,0x3C,0x78,0x0F,0x00,0x3C,0x78,0x0F,0x00,0x3C,0x78,0x0F,0x00,
-0x3C,0x7F,0xFF,0xFF,0x78,0xFF,0xFF,0xFF,0x78,0xF0,0x0F,0x00,0x78,0xF0,0x0F,0x00,
-0x3D,0xE0,0x0F,0x00,0x1F,0xE0,0x0F,0x00,0x0F,0xE0,0x0F,0x00,0x07,0xC0,0x0F,0x00,
-0x07,0xE0,0x0F,0x00,0x07,0xF0,0x0F,0x00,0x0F,0xF8,0x0F,0x00,0x1E,0x7C,0x0F,0x00,
-0x3C,0x38,0x0F,0x00,0x78,0x00,0x0F,0x00,0xF0,0x03,0xFF,0x00,0x60,0x01,0xFE,0x00,
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00},
-
-/*--  ����:  ΢  --*/
-/*--  ΢���ź�24;  �������¶�Ӧ�ĵ���Ϊ����x��=32x41   --*/
-{"΢",
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x07,0x01,0xE0,0x07,0x87,0x01,0xE0,
-0x07,0x07,0x01,0xC0,0x0F,0xF7,0x79,0xC0,0x1E,0xF7,0x7B,0xC0,0x1E,0xF7,0x7B,0x80,
-0x3C,0xF7,0x7B,0xFF,0x78,0xF7,0x7B,0xFF,0xF8,0xF7,0x7F,0x9E,0xF7,0xFF,0xFF,0x9E,
-0x67,0xFF,0xFF,0x9E,0x07,0x00,0x7F,0x9C,0x0F,0x00,0x0F,0x9C,0x1E,0x00,0x1F,0x9C,
-0x1E,0x7F,0xFF,0xBC,0x3E,0x7F,0xF3,0xFC,0x3E,0x00,0x03,0xFC,0x7E,0x00,0x01,0xF8,
-0xFE,0x00,0x01,0xF8,0xFE,0x7F,0xE1,0xF8,0xDE,0x7F,0xE1,0xF8,0x1E,0x78,0xE0,0xF0,
-0x1E,0x78,0xEE,0xF0,0x1E,0x78,0xFF,0xF0,0x1E,0x78,0xFD,0xF8,0x1E,0x79,0xFB,0xFC,
-0x1E,0xF1,0xF7,0xBC,0x1E,0xF0,0xEF,0x9E,0x1F,0xE0,0x0F,0x0F,0x1E,0xC0,0x1E,0x0F,
-0x1E,0x00,0x0C,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00},
-
-/*--  ����:  ��  --*/
-/*--  ΢���ź�24;  �������¶�Ӧ�ĵ���Ϊ����x��=32x41   --*/
-{"��",
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00,0x03,0xC0,0x78,0x00,0x07,0x80,0x78,0x00,0x07,0x80,0x78,0x00,
-0x07,0x80,0xF0,0x00,0x0F,0x00,0xF0,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
-0x1E,0x03,0xC0,0x1F,0x1E,0x03,0xC0,0x1E,0x1F,0xE7,0x8F,0x3E,0x3D,0xE7,0x8F,0x3C,
-0x3D,0xEF,0x0F,0x7C,0x3D,0xE7,0x0F,0x78,0x79,0xE0,0x0F,0x00,0x79,0xE0,0x0E,0x00,
-0x7F,0xFE,0x0E,0x00,0x7F,0xFE,0x1F,0x00,0x01,0xE0,0x1F,0x00,0x01,0xE0,0x1F,0x00,
-0x01,0xE0,0x1F,0x80,0x01,0xE0,0x1F,0x80,0x01,0xE0,0x3F,0x80,0x01,0xFF,0x3F,0xC0,
-0x0F,0xFF,0x7B,0xC0,0xFF,0xF0,0x79,0xE0,0xF9,0xE0,0xF1,0xF0,0x01,0xE1,0xF0,0xF0,
-0x01,0xE3,0xE0,0xF8,0x01,0xE7,0xC0,0x7C,0x01,0xFF,0x80,0x3F,0x01,0xFF,0x00,0x1F,
-0x01,0xEC,0x00,0x0E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00},
-
-/*--  ����:  ��  --*/
-/*--  ΢���ź�24;  �������¶�Ӧ�ĵ���Ϊ����x��=32x41   --*/
-{"��",
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x77,0x00,0x00,0x00,0xFF,0x00,
-0x7F,0xFC,0xF7,0x80,0x7F,0xFD,0xE3,0xC0,0x01,0xC1,0xE3,0xC0,0x01,0xC3,0xC1,0x80,
-0x3D,0xC7,0xFF,0xFF,0x39,0xC7,0xFF,0xFF,0x39,0xCF,0x83,0x80,0x79,0xDF,0x83,0x80,
-0x79,0xFF,0x83,0x80,0x79,0xDF,0x83,0x80,0x71,0xC3,0x83,0x80,0x7F,0xFF,0xFF,0xFE,
-0x7F,0xFF,0xFF,0xFE,0x03,0xC3,0x83,0x80,0x07,0xC3,0x83,0x80,0x07,0xC3,0x83,0x80,
-0x0F,0xC3,0x83,0x80,0x0F,0xC3,0x83,0x80,0x1F,0xC3,0xFF,0xFE,0x1D,0xC3,0xFF,0xFE,
-0x3D,0xC3,0x83,0x80,0x79,0xC3,0x83,0x80,0xF1,0xC3,0x83,0x80,0xF1,0xC3,0x83,0x80,
-0x61,0xC3,0x83,0x80,0x01,0xC3,0xFF,0xFF,0x03,0xC3,0xFF,0xFF,0x1F,0xC3,0x80,0x00,
-0x1F,0x83,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00},
-
-/*--  ����:  ��  --*/
-/*--  ΢���ź�24;  �������¶�Ӧ�ĵ���Ϊ����x��=32x41   --*/
-{"��",
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x1F,0xFF,0xFF,0xFC,0x1F,0xFF,0xFF,0xFC,0x1E,0x03,0xC0,0x3C,0x1E,0xC3,0xC7,0x3C,
-0x1F,0xE3,0xC7,0xBC,0x1E,0xF3,0xCF,0x3C,0x1E,0xFB,0xDF,0x3C,0x1E,0x7B,0xDE,0x3C,
-0x1E,0x33,0xDC,0x3C,0x1E,0x03,0xC0,0x3C,0x1F,0xFF,0xFF,0xFC,0x1F,0xFF,0xFF,0xFC,
-0x1E,0x03,0xC0,0x3C,0x00,0x03,0xC0,0x00,0x00,0x03,0xC0,0x00,0x3F,0xFF,0xFF,0xFC,
-0x3F,0xFF,0xFF,0xFC,0x00,0x03,0xC0,0x00,0x00,0x03,0xC0,0x00,0x00,0x03,0xC0,0x00,
-0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x1C,0x38,0x70,0x70,
-0x3E,0x78,0xF8,0xF8,0x3C,0x7C,0x78,0x7C,0x7C,0x3C,0x3C,0x3E,0xF8,0x3E,0x3C,0x1F,
-0xF0,0x1C,0x18,0x0E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00},
-
-/*--  ����:  ��  --*/
-/*--  ΢���ź�24;  �������¶�Ӧ�ĵ���Ϊ����x��=32x41   --*/
-{"��",
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x78,0x3C,0x00,
-0x00,0x78,0x3C,0x00,0x00,0x78,0x3C,0x00,0x00,0x78,0x3C,0x00,0x00,0x78,0x3C,0x00,
-0x00,0x78,0x3C,0x0C,0x3C,0x78,0x3C,0x1E,0x3C,0x78,0x3C,0x3F,0x3C,0x78,0x3C,0xF8,
-0x3C,0x7F,0xFD,0xF0,0x3C,0x7F,0xFF,0xE0,0x3C,0x78,0x3F,0x80,0x3C,0x78,0x3E,0x00,
-0x3C,0x78,0x3C,0x00,0x3C,0x78,0x3C,0x00,0x3C,0x78,0x3C,0x00,0x3C,0x78,0x3C,0x00,
-0x3C,0x78,0x3C,0x00,0x3C,0x78,0x3C,0x00,0x3C,0x78,0x3C,0x0E,0x3C,0x78,0x3C,0x0F,
-0x3C,0x78,0x3C,0x0F,0x3C,0x79,0xFC,0x0F,0x3C,0x7F,0xFC,0x0F,0x3F,0xFF,0x3C,0x0F,
-0x3F,0xF0,0x3E,0x1E,0xFF,0x00,0x1F,0xFE,0xF0,0x00,0x0F,0xFC,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00},
-
-/*--  ����:  ��  --*/
-/*--  ΢���ź�24;  �������¶�Ӧ�ĵ���Ϊ����x��=32x41   --*/
-{"��",
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00,0x00,0x03,0x80,0x00,0x00,0x07,0x80,0x00,0x00,0x03,0xC0,0x00,
-0x00,0x03,0xE0,0x00,0x00,0x01,0xE0,0x00,0x7F,0xFF,0xFF,0xFE,0x7F,0xFF,0xFF,0xFE,
-0x78,0x00,0x00,0x1E,0x78,0x00,0x00,0x1E,0x78,0x00,0x00,0x1E,0x78,0x00,0x00,0x1E,
-0x7B,0xFF,0xFF,0xDE,0x03,0xFF,0xFF,0xC0,0x00,0x00,0x0F,0xC0,0x00,0x00,0x3F,0x00,
-0x00,0x00,0x7E,0x00,0x00,0x01,0xF8,0x00,0x00,0x01,0xE0,0x00,0x00,0x01,0xE0,0x00,
-0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x01,0xE0,0x00,0x00,0x01,0xE0,0x00,
-0x00,0x01,0xE0,0x00,0x00,0x01,0xE0,0x00,0x00,0x01,0xE0,0x00,0x00,0x01,0xE0,0x00,
-0x00,0x03,0xE0,0x00,0x00,0x03,0xC0,0x00,0x00,0xFF,0xC0,0x00,0x00,0xFF,0x80,0x00,
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00},
-
-/*--  ����:  ��  --*/
-/*--  ΢���ź�24;  �������¶�Ӧ�ĵ���Ϊ����x��=32x41   --*/
-{"��",
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0xC0,0x3C,0x00,
-0x03,0xC0,0x3C,0x00,0x03,0xC0,0x3C,0x00,0x07,0x80,0x3C,0x00,0x07,0x80,0x3C,0x00,
-0x07,0x80,0x3C,0x00,0x0F,0xFF,0xFF,0xFF,0x0F,0xFF,0xFF,0xFF,0x1F,0x01,0xFE,0x00,
-0x1F,0x01,0xFF,0x00,0x3F,0x01,0xFF,0x00,0x3F,0x03,0xFF,0x00,0x7F,0x03,0xFF,0x80,
-0x7F,0x07,0xBF,0x80,0xFF,0x07,0xBF,0xC0,0xEF,0x0F,0x3D,0xC0,0xCF,0x0F,0x3D,0xE0,
-0x0F,0x1E,0x3D,0xE0,0x0F,0x1E,0x3C,0xF0,0x0F,0x3C,0x3C,0x78,0x0F,0x7C,0x3C,0x7C,
-0x0F,0xF8,0x3C,0x3E,0x0F,0xF7,0xFF,0xDF,0x0F,0xE7,0xFF,0xCF,0x0F,0xC0,0x3C,0x06,
-0x0F,0x00,0x3C,0x00,0x0F,0x00,0x3C,0x00,0x0F,0x00,0x3C,0x00,0x0F,0x00,0x3C,0x00,
-0x0F,0x00,0x3C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00},
-
-/*--  ����:  ��  --*/
-/*--  ΢���ź�24;  �������¶�Ӧ�ĵ���Ϊ����x��=32x41   --*/
-{"��",
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x0F,0x80,0x00,0x00,0x0F,0x80,0x00,
-0x00,0x0F,0x80,0x00,0x00,0x0F,0x80,0x00,0x00,0x0F,0x80,0x00,0x00,0x0F,0x80,0x00,
-0x00,0x0F,0xE0,0x00,0x00,0x0F,0xF8,0x00,0x00,0x0F,0xFC,0x00,0x00,0x0F,0xBF,0x00,
-0x00,0x0F,0x9F,0x80,0x00,0x0F,0x87,0xE0,0x00,0x0F,0x83,0xF0,0x00,0x0F,0x80,0xF8,
-0x00,0x0F,0x80,0x7C,0x00,0x0F,0x80,0x38,0x00,0x0F,0x80,0x00,0x00,0x0F,0x80,0x00,
-0x00,0x0F,0x80,0x00,0x00,0x0F,0x80,0x00,0x00,0x0F,0x80,0x00,0x00,0x0F,0x80,0x00,
-0x00,0x0F,0x80,0x00,0x00,0x0F,0x80,0x00,0x00,0x0F,0x80,0x00,0x00,0x0F,0x80,0x00,
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00},
-
-/*--  ����:  ��  --*/
-/*--  ΢���ź�24;  �������¶�Ӧ�ĵ���Ϊ����x��=32x41   --*/
-{"��",
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x78,
-0x00,0x00,0x00,0x78,0x00,0x00,0x00,0x78,0x7F,0xFC,0x00,0x78,0x7F,0xFC,0x00,0x78,
-0x00,0x3C,0x00,0x78,0x00,0x3F,0xFF,0xFF,0x30,0x3F,0xFF,0xFF,0x78,0x3C,0x00,0x78,
-0x3C,0x38,0x00,0x78,0x3E,0x78,0x00,0x78,0x1E,0x78,0xC0,0x78,0x0F,0x79,0xE0,0x78,
-0x0F,0xF0,0xF0,0x78,0x07,0xF0,0xF8,0x78,0x03,0xF0,0x78,0x78,0x01,0xE0,0x3C,0x78,
-0x03,0xF0,0x3E,0x78,0x03,0xF0,0x18,0x78,0x07,0xF8,0x00,0x78,0x07,0xFC,0x00,0x78,
-0x0F,0x3E,0x00,0x78,0x1F,0x1E,0x00,0x78,0x3E,0x1F,0x00,0x78,0x7C,0x0E,0x00,0xF8,
-0xF8,0x00,0x00,0xF0,0xF0,0x00,0x3F,0xF0,0x60,0x00,0x3F,0xE0,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00},
-
-/*--  ����:  Ӧ  --*/
-/*--  ΢���ź�24;  �������¶�Ӧ�ĵ���Ϊ����x��=32x41   --*/
-{"Ӧ",
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00,0x00,0x01,0xC0,0x00,0x00,0x03,0xE0,0x00,0x00,0x01,0xE0,0x00,
-0x00,0x01,0xF0,0x00,0x00,0x00,0xF0,0x00,0x1F,0xFF,0xFF,0xFF,0x1F,0xFF,0xFF,0xFF,
-0x1E,0x00,0x00,0x00,0x1E,0x00,0x00,0x00,0x1E,0x01,0xE0,0x78,0x1E,0x01,0xE0,0x78,
-0x1E,0xE1,0xE0,0x78,0x1F,0xF1,0xF0,0xF8,0x1E,0xF0,0xF0,0xF0,0x1E,0xF0,0xF0,0xF0,
-0x1E,0xF8,0xF0,0xF0,0x1E,0x78,0xF1,0xF0,0x1E,0x78,0xF9,0xE0,0x1E,0x78,0x79,0xE0,
-0x1E,0x7C,0x7B,0xE0,0x1E,0x3C,0x7B,0xC0,0x1E,0x3C,0x7B,0xC0,0x1E,0x3C,0x7B,0xC0,
-0x3C,0x3E,0x07,0x80,0x3C,0x1C,0x07,0x80,0x3C,0x00,0x07,0x80,0x3C,0x00,0x0F,0x00,
-0x78,0x00,0x0F,0x00,0x7B,0xFF,0xFF,0xFF,0xF3,0xFF,0xFF,0xFF,0xF0,0x00,0x00,0x00,
-0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00},
-
-/*--  ����:  ��  --*/
-/*--  ΢���ź�24;  �������¶�Ӧ�ĵ���Ϊ����x��=32x41   --*/
-{"��",
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x80,0x3C,0x00,0x07,0xC0,0x3E,0x00,
-0x07,0x80,0x3C,0x00,0x07,0x80,0x7C,0x00,0x0F,0x00,0x78,0x00,0x7F,0xFE,0x7F,0xFE,
-0x7F,0xFE,0xFF,0xFE,0x78,0x1E,0xF0,0x1E,0x78,0x1F,0xE0,0x1E,0x78,0x1F,0xE0,0x1E,
-0x78,0x1F,0xC0,0x1E,0x78,0x1F,0xC0,0x1E,0x78,0x1F,0xF0,0x1E,0x78,0x1E,0xF8,0x1E,
-0x78,0x1E,0x7C,0x1E,0x7F,0xFE,0x3C,0x1E,0x7F,0xFE,0x1E,0x1E,0x78,0x1E,0x1F,0x1E,
-0x78,0x1E,0x0F,0x9E,0x78,0x1E,0x07,0x9E,0x78,0x1E,0x07,0x1E,0x78,0x1E,0x00,0x1E,
-0x78,0x1E,0x00,0x1E,0x78,0x1E,0x00,0x3E,0x78,0x1E,0x00,0x3C,0x78,0x1E,0x00,0x3C,
-0x7F,0xFE,0x00,0x3C,0x7F,0xFE,0x00,0x7C,0x78,0x1E,0x3F,0xF8,0x78,0x1E,0x3F,0xF0,
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00},
-
-/*--  ����:  ��  --*/
-/*--  ΢���ź�24;  �������¶�Ӧ�ĵ���Ϊ����x��=32x41   --*/
-{"��",
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0xC0,0x00,0x00,0x03,0xC0,0x00,
-0x00,0x03,0xC0,0x00,0x00,0x03,0xC0,0x00,0x00,0x03,0xFF,0xFF,0x00,0x03,0xFF,0xFF,
-0x00,0x03,0xC0,0x00,0x00,0x03,0xC0,0x00,0x00,0x03,0xC0,0x00,0x00,0x03,0xC0,0x00,
-0x0F,0xFF,0xFF,0xF8,0x0F,0xFF,0xFF,0xF8,0x0F,0x00,0x00,0x78,0x0F,0x00,0x00,0x78,
-0x0F,0x00,0x00,0x78,0x0F,0x00,0x00,0x78,0x0F,0x00,0x00,0x78,0x0F,0x00,0x00,0x78,
-0x0F,0xFF,0xFF,0xF8,0x0F,0xFF,0xFF,0xF8,0x0F,0x00,0x00,0x78,0x00,0x00,0x00,0x00,
-0x0C,0x38,0x38,0x30,0x1E,0x7C,0x78,0x78,0x3E,0x3C,0x78,0x78,0x3C,0x3C,0x3C,0x3C,
-0x7C,0x3E,0x3C,0x3E,0xF8,0x1E,0x3C,0x1E,0xF0,0x1E,0x1E,0x1F,0x70,0x1E,0x1C,0x0E,
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00},
-
-/*--  ����:  ��  --*/
-/*--  ΢���ź�24;  �������¶�Ӧ�ĵ���Ϊ����x��=32x41   --*/
-{"��",
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3C,0x00,0x00,0x00,0x78,0x00,
-0x7F,0xF0,0x78,0x00,0x7F,0xF0,0x78,0x00,0x79,0xFF,0xFF,0xFF,0x79,0xFF,0xFF,0xFF,
-0x79,0xE1,0xE0,0x00,0x79,0xE1,0xE0,0x00,0x7B,0xC1,0xEF,0x80,0x7B,0xC3,0xCF,0x80,
-0x7B,0xC3,0xCF,0x80,0x7F,0x87,0xCF,0x80,0x7F,0x87,0x8F,0x80,0x7F,0x87,0x8F,0x80,
-0x7B,0xCF,0x0F,0x80,0x7B,0xCF,0xFF,0xFE,0x79,0xEF,0xFF,0xFE,0x79,0xE0,0x0F,0x80,
-0x78,0xE0,0x0F,0x80,0x78,0xF0,0x0F,0x80,0x78,0xF0,0x0F,0x80,0x78,0xF0,0x0F,0x80,
-0x78,0xFF,0xFF,0xFF,0x79,0xFF,0xFF,0xFF,0x7F,0xE0,0x0F,0x80,0x7F,0xC0,0x0F,0x80,
-0x78,0x00,0x0F,0x80,0x78,0x00,0x0F,0x80,0x78,0x00,0x0F,0x80,0x78,0x00,0x0F,0x80,
-0x78,0x00,0x0F,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00},
-
-/*--  ����:  Ϊ  --*/
-/*--  ΢���ź�24;  �������¶�Ӧ�ĵ���Ϊ����x��=32x41   --*/
-{"Ϊ",
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x80,0x00,
-0x0E,0x07,0x80,0x00,0x1F,0x07,0x80,0x00,0x0F,0x87,0x80,0x00,0x07,0xC7,0x80,0x00,
-0x01,0xE7,0x80,0x00,0x00,0xC7,0x80,0x00,0x00,0x07,0x80,0x00,0x7F,0xFF,0xFF,0xFC,
-0x7F,0xFF,0xFF,0xFC,0x00,0x07,0x80,0x3C,0x00,0x0F,0x80,0x3C,0x00,0x0F,0x00,0x3C,
-0x00,0x0F,0x00,0x3C,0x00,0x0F,0x60,0x3C,0x00,0x1F,0xF0,0x3C,0x00,0x1E,0x78,0x3C,
-0x00,0x3E,0x3C,0x3C,0x00,0x3C,0x3E,0x3C,0x00,0x7C,0x1F,0x3C,0x00,0x78,0x0F,0x3C,
-0x00,0xF8,0x06,0x3C,0x01,0xF0,0x00,0x3C,0x03,0xE0,0x00,0x7C,0x07,0xC0,0x00,0x7C,
-0x0F,0x80,0x00,0x78,0x1F,0x00,0x00,0xF8,0x3E,0x00,0xFF,0xF0,0x7C,0x00,0xFF,0xE0,
-0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00},
-
-/*--  ����:  ��  --*/
-/*--  ΢���ź�24;  �������¶�Ӧ�ĵ���Ϊ����x��=32x41   --*/
-{"��",
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0F,0x00,0x00,0x38,
-0x0F,0x00,0x00,0x38,0x0F,0x00,0x00,0x38,0x0F,0x3F,0xF8,0x38,0x0F,0x3F,0xF8,0x38,
-0x0F,0x00,0x78,0x38,0xFF,0xE0,0x7F,0xFF,0xFF,0xE0,0x7F,0xFF,0x0F,0x00,0x70,0x38,
-0x0F,0x18,0xF0,0x38,0x1F,0x3C,0xF0,0x38,0x1F,0x1C,0xFE,0x38,0x1F,0xDE,0xFE,0x38,
-0x3F,0xEF,0xEF,0x38,0x3F,0xFF,0xEF,0x38,0x3F,0xF7,0xE7,0xB8,0x7F,0x67,0xC7,0xB8,
-0x7F,0x03,0xC3,0xB8,0xFF,0x07,0xE0,0x38,0xEF,0x07,0xE0,0x38,0xEF,0x0F,0xF0,0x38,
-0xCF,0x1F,0xF0,0x38,0x0F,0x1E,0x78,0x38,0x0F,0x3C,0x7C,0x38,0x0F,0x78,0x3C,0x38,
-0x0F,0xF8,0x38,0x38,0x0F,0x60,0x00,0x78,0x0F,0x00,0x0F,0xF8,0x0F,0x00,0x07,0xF0,
-0x0F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00},
-
-/*--  ����:  ݮ  --*/
-/*--  ΢���ź�24;  �������¶�Ӧ�ĵ���Ϊ����x��=32x41   --*/
-{"ݮ",
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3C,0x1E,0x00,0x00,0x3C,0x1E,0x00,
-0x00,0x3C,0x1E,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x3C,0x1E,0x00,
-0x07,0xBC,0x1E,0x00,0x07,0x80,0x00,0x00,0x0F,0xFF,0xFF,0xFC,0x0F,0xFF,0xFF,0xFC,
-0x1E,0x00,0x00,0x00,0x3C,0x00,0x00,0x00,0x3C,0x00,0x00,0x00,0x7F,0xFF,0xFF,0xF0,
-0xF7,0xFF,0xFF,0xF0,0x37,0x83,0x80,0xF0,0x07,0x87,0xC0,0xF0,0x07,0x83,0xF0,0xF0,
-0x07,0x00,0xE0,0xF0,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x0F,0x0F,0x00,0xE0,
-0x0F,0x0F,0x81,0xE0,0x0E,0x03,0xE1,0xE0,0x1E,0x01,0xC1,0xE0,0x1F,0xFF,0xFF,0xFE,
-0x1F,0xFF,0xFF,0xFE,0x00,0x00,0x01,0xE0,0x00,0x00,0x03,0xC0,0x00,0x00,0xFF,0xC0,
-0x00,0x00,0xFF,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00},
-
-/*--  ����:  ��  --*/
-/*--  ΢���ź�24;  �������¶�Ӧ�ĵ���Ϊ����x��=32x41   --*/
-{"��",
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x38,0x00,0x00,0x3E,
-0x7C,0x00,0x3F,0xFE,0x3F,0x3F,0xFF,0xF0,0x1F,0xBF,0xE0,0x00,0x07,0xBC,0x00,0x00,
-0x03,0x3C,0x00,0x00,0x00,0x3C,0x00,0x3C,0x00,0x3C,0x0F,0xFE,0x70,0x3D,0xFF,0xF8,
-0xF8,0x3D,0xFF,0x00,0x7C,0x3D,0xE7,0x80,0x3F,0x3D,0xE7,0x80,0x1F,0x3D,0xE7,0x8E,
-0x0E,0x3D,0xE7,0x9F,0x00,0x3D,0xE7,0xFE,0x00,0x39,0xE7,0xF8,0x00,0x39,0xE3,0xF0,
-0x1C,0x39,0xE3,0xC0,0x1E,0x79,0xE3,0xC0,0x1E,0x79,0xE1,0xE0,0x1E,0x79,0xE1,0xE0,
-0x3C,0x79,0xE0,0xF0,0x3C,0x79,0xE0,0xF8,0x3C,0xF1,0xE0,0x7C,0x3C,0xF1,0xE3,0x7C,
-0x7D,0xF1,0xEF,0x3F,0x79,0xE1,0xFE,0x1F,0x7B,0xE1,0xF8,0x0E,0x7B,0xC3,0xE0,0x00,
-0x79,0x81,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00},
-
-/*--  ����:  A  --*/
-/*--  ΢���ź�24;  �������¶�Ӧ�ĵ���Ϊ����x��=32x41   --*/
-{
-"A",
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x7C,0x00,0x00,0x00,0xFC,0x00,0x00,0x00,0xFE,0x00,0x00,0x00,0xFE,0x00,0x00,
-0x01,0xFF,0x00,0x00,0x01,0xFF,0x00,0x00,0x01,0xEF,0x00,0x00,0x03,0xEF,0x80,0x00,
-0x03,0xCF,0x80,0x00,0x07,0xC7,0x80,0x00,0x07,0xC7,0xC0,0x00,0x07,0x87,0xC0,0x00,
-0x0F,0x83,0xE0,0x00,0x0F,0x83,0xE0,0x00,0x0F,0x01,0xE0,0x00,0x1F,0xFF,0xF0,0x00,
-0x1F,0xFF,0xF0,0x00,0x3F,0xFF,0xF8,0x00,0x3E,0x00,0xF8,0x00,0x3C,0x00,0xF8,0x00,
-0x7C,0x00,0x7C,0x00,0x7C,0x00,0x7C,0x00,0x78,0x00,0x3C,0x00,0xF8,0x00,0x3E,0x00,
-0xF8,0x00,0x3E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00},
-
-/*--  ����:  a  --*/
-/*--  ΢���ź�24;  �������¶�Ӧ�ĵ���Ϊ����x��=32x41   --*/
-{"a",
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0xF8,0x00,0x00,
-0x1F,0xFE,0x00,0x00,0x3F,0xFE,0x00,0x00,0x3E,0x3F,0x00,0x00,0x38,0x1F,0x00,0x00,
-0x00,0x0F,0x00,0x00,0x00,0x0F,0x00,0x00,0x03,0xFF,0x00,0x00,0x1F,0xFF,0x00,0x00,
-0x3F,0x8F,0x00,0x00,0x7C,0x0F,0x00,0x00,0x7C,0x0F,0x00,0x00,0x78,0x1F,0x00,0x00,
-0x7C,0x1F,0x00,0x00,0x7E,0x7F,0x00,0x00,0x7F,0xFF,0x00,0x00,0x3F,0xFF,0x00,0x00,
-0x0F,0xCF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00},
-
-/*--  ����:  b  --*/
-/*--  ΢���ź�24;  �������¶�Ӧ�ĵ���Ϊ����x��=32x41   --*/
-{"b",
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3C,0x00,0x00,0x00,
-0x3C,0x00,0x00,0x00,0x3C,0x00,0x00,0x00,0x3C,0x00,0x00,0x00,0x3C,0x00,0x00,0x00,
-0x3C,0x00,0x00,0x00,0x3C,0x00,0x00,0x00,0x3C,0x00,0x00,0x00,0x3C,0xFE,0x00,0x00,
-0x3D,0xFF,0x80,0x00,0x3F,0xFF,0xC0,0x00,0x3F,0x8F,0xC0,0x00,0x3F,0x07,0xE0,0x00,
-0x3E,0x03,0xE0,0x00,0x3E,0x03,0xE0,0x00,0x3C,0x01,0xE0,0x00,0x3C,0x01,0xE0,0x00,
-0x3C,0x01,0xE0,0x00,0x3C,0x03,0xE0,0x00,0x3E,0x03,0xE0,0x00,0x3E,0x03,0xE0,0x00,
-0x3F,0x07,0xC0,0x00,0x3F,0x8F,0xC0,0x00,0x3F,0xFF,0x80,0x00,0x3F,0xFF,0x00,0x00,
-0x3C,0xFC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00},
-
-/*--  ����:  c  --*/
-/*--  ΢���ź�24;  �������¶�Ӧ�ĵ���Ϊ����x��=32x41   --*/
-{"c",
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xFC,0x00,0x00,
-0x07,0xFE,0x00,0x00,0x1F,0xFE,0x00,0x00,0x3F,0x86,0x00,0x00,0x3E,0x00,0x00,0x00,
-0x7C,0x00,0x00,0x00,0x7C,0x00,0x00,0x00,0x7C,0x00,0x00,0x00,0x78,0x00,0x00,0x00,
-0x78,0x00,0x00,0x00,0x7C,0x00,0x00,0x00,0x7C,0x00,0x00,0x00,0x7C,0x00,0x00,0x00,
-0x3E,0x00,0x00,0x00,0x3F,0x86,0x00,0x00,0x1F,0xFE,0x00,0x00,0x0F,0xFE,0x00,0x00,
-0x03,0xFC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00},
-
-/*--  ����:  ΢  --*/
-/*--  ΢���ź�24;  �������¶�Ӧ�ĵ���Ϊ����x��=32x41   --*/
-{"΢",
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x07,0x01,0xE0,0x07,0x87,0x01,0xE0,
-0x07,0x07,0x01,0xC0,0x0F,0xF7,0x79,0xC0,0x1E,0xF7,0x7B,0xC0,0x1E,0xF7,0x7B,0x80,
-0x3C,0xF7,0x7B,0xFF,0x78,0xF7,0x7B,0xFF,0xF8,0xF7,0x7F,0x9E,0xF7,0xFF,0xFF,0x9E,
-0x67,0xFF,0xFF,0x9E,0x07,0x00,0x7F,0x9C,0x0F,0x00,0x0F,0x9C,0x1E,0x00,0x1F,0x9C,
-0x1E,0x7F,0xFF,0xBC,0x3E,0x7F,0xF3,0xFC,0x3E,0x00,0x03,0xFC,0x7E,0x00,0x01,0xF8,
-0xFE,0x00,0x01,0xF8,0xFE,0x7F,0xE1,0xF8,0xDE,0x7F,0xE1,0xF8,0x1E,0x78,0xE0,0xF0,
-0x1E,0x78,0xEE,0xF0,0x1E,0x78,0xFF,0xF0,0x1E,0x78,0xFD,0xF8,0x1E,0x79,0xFB,0xFC,
-0x1E,0xF1,0xF7,0xBC,0x1E,0xF0,0xEF,0x9E,0x1F,0xE0,0x0F,0x0F,0x1E,0xC0,0x1E,0x0F,
-0x1E,0x00,0x0C,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00},
-
-/*--  ����:  ѩ  --*/
-/*--  ΢���ź�24;  �������¶�Ӧ�ĵ���Ϊ����x��=32x41   --*/
-{"ѩ",
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x1F,0xFF,0xFF,0xF8,0x1F,0xFF,0xFF,0xF8,0x00,0x03,0xC0,0x00,0x00,0x03,0xC0,0x00,
-0x7F,0xFF,0xFF,0xFE,0x7F,0xFF,0xFF,0xFE,0x78,0x03,0xC0,0x1E,0x78,0x03,0xC0,0x1E,
-0x7F,0xFF,0xFF,0xFE,0x7F,0xFF,0xFF,0xFE,0x00,0x03,0xC0,0x00,0x00,0x03,0xC0,0x00,
-0x07,0xFF,0xFF,0xE0,0x07,0xFF,0xFF,0xE0,0x00,0x03,0xC0,0x00,0x00,0x00,0x00,0x00,
-0x1F,0xFF,0xFF,0xF8,0x1F,0xFF,0xFF,0xF8,0x00,0x00,0x00,0x78,0x00,0x00,0x00,0x78,
-0x1F,0xFF,0xFF,0xF8,0x1F,0xFF,0xFF,0xF8,0x00,0x00,0x00,0x78,0x00,0x00,0x00,0x78,
-0x00,0x00,0x00,0x78,0x3F,0xFF,0xFF,0xF8,0x3F,0xFF,0xFF,0xF8,0x00,0x00,0x00,0x78,
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00},
-
-/*--  ����:  ��  --*/
-/*--  ΢���ź�24;  �������¶�Ӧ�ĵ���Ϊ����x��=32x41   --*/
-{"��",
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x80,0x00,0x00,0x07,0x80,0x00,
-0x00,0x07,0x80,0x00,0x00,0x07,0x80,0x00,0x7F,0xFF,0xFF,0xF8,0x7F,0xFF,0xFF,0xF8,
-0x78,0x07,0x80,0xF8,0x78,0x07,0x80,0xF8,0x78,0x07,0x80,0xF8,0x78,0x07,0x80,0xF8,
-0x78,0x07,0x80,0xF8,0x78,0x07,0x80,0xF8,0x7F,0xFF,0xFF,0xF8,0x7F,0xFF,0xFF,0xF8,
-0x78,0x07,0x80,0xF8,0x78,0x07,0x80,0xF8,0x78,0x07,0x80,0xF8,0x78,0x07,0x80,0xF8,
-0x78,0x07,0x80,0xF8,0x78,0x07,0x80,0xF8,0x7F,0xFF,0xFF,0xF8,0x7F,0xFF,0xFF,0xF8,
-0x78,0x07,0x80,0x0E,0x78,0x07,0x80,0x0F,0x00,0x07,0x80,0x0F,0x00,0x07,0x80,0x0F,
-0x00,0x07,0x80,0x1F,0x00,0x07,0x80,0x1E,0x00,0x03,0xFF,0xFE,0x00,0x01,0xFF,0xFC,
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00},
-
-/*--  ����:  ��  --*/
-/*--  ΢���ź�24;  �������¶�Ӧ�ĵ���Ϊ����x��=32x41   --*/
-{"��",
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x1F,0xFF,0xFF,0xF8,0x1F,0xFF,0xFF,0xF8,0x00,0x00,0x01,0xF8,0x00,0x00,0x07,0xE0,
-0x00,0x00,0x0F,0xC0,0x00,0x00,0x1F,0x80,0x00,0x00,0x3E,0x00,0x00,0x00,0xFC,0x00,
-0x00,0x01,0xF8,0x00,0x00,0x03,0xE0,0x00,0x00,0x03,0xE0,0x00,0x00,0x03,0xE0,0x00,
-0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x03,0xE0,0x00,0x00,0x03,0xE0,0x00,
-0x00,0x03,0xE0,0x00,0x00,0x03,0xE0,0x00,0x00,0x03,0xE0,0x00,0x00,0x03,0xE0,0x00,
-0x00,0x03,0xE0,0x00,0x00,0x03,0xE0,0x00,0x00,0x03,0xE0,0x00,0x00,0x03,0xE0,0x00,
-0x00,0x03,0xE0,0x00,0x00,0x03,0xC0,0x00,0x01,0xFF,0xC0,0x00,0x00,0xFF,0x80,0x00,
-0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
-0x00,0x00,0x00,0x00},
-
-
-};
-
-cFONT Font24CN = {
-  Font24CN_Table,
-  sizeof(Font24CN_Table)/sizeof(CH_CN),  /*size of table*/
-  24, /* ASCII Width */
-  32, /* Width */
-  41, /* Height */
-};
-
-/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
diff --git a/lib/gfx/Fonts/fonts.h b/lib/gfx/Fonts/fonts.h
index 33509b472f63a805df060e2b9e6f5c108757a1e0..04117b2acbbb32724b7066155a6b0dd8a558f221 100644
--- a/lib/gfx/Fonts/fonts.h
+++ b/lib/gfx/Fonts/fonts.h
@@ -61,32 +61,12 @@ typedef struct _tFont
 } sFONT;
 
 
-//GB2312
-typedef struct                                          // ������ģ���ݽṹ
-{
-  unsigned char index[2];                               // ������������
-  const char matrix[MAX_HEIGHT_FONT*MAX_WIDTH_FONT/8];  // ����������
-}CH_CN;
-
-
-typedef struct
-{    
-  const CH_CN *table;
-  uint16_t size;
-  uint16_t ASCII_Width;
-  uint16_t Width;
-  uint16_t Height;
-  
-}cFONT;
-
 extern sFONT Font24;
 extern sFONT Font20;
 extern sFONT Font16;
 extern sFONT Font12;
 extern sFONT Font8;
 
-extern cFONT Font12CN;
-extern cFONT Font24CN;
 #ifdef __cplusplus
 }
 #endif
diff --git a/lib/gfx/gfx.c b/lib/gfx/gfx.c
index dd92ecdc0cd736ba26ab15640b9afd7066e3cbed..5af5bbb360248650fc40cb1ec445d9091c90575d 100644
--- a/lib/gfx/gfx.c
+++ b/lib/gfx/gfx.c
@@ -33,7 +33,7 @@ struct gfx_region gfx_screen(struct framebuffer *fb)
 	return r;
 }
 
-static inline int letter_bit(sFONT *font, char c, int x, int y)
+static inline int letter_bit(const sFONT *font, char c, int x, int y)
 {
 	if (x < 0 || y < 0)
 		return 0;
@@ -53,7 +53,7 @@ static inline int letter_bit(sFONT *font, char c, int x, int y)
 }
 
 void gfx_putchar(
-	sFONT *font,
+	const sFONT *font,
 	struct gfx_region *r,
 	int x,
 	int y,
@@ -78,7 +78,7 @@ void gfx_putchar(
 }
 
 void gfx_puts(
-	sFONT *font,
+	const sFONT *font,
 	struct gfx_region *r,
 	int x,
 	int y,
@@ -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/lib/gfx/gfx.h b/lib/gfx/gfx.h
index 428b196c6a41465230538da60a6ff0ce3fccafb5..0f64a429b59170182077b7d01e2d1cd027743974 100644
--- a/lib/gfx/gfx.h
+++ b/lib/gfx/gfx.h
@@ -14,9 +14,9 @@ struct gfx_region {
 
 void gfx_setpixel(struct gfx_region *r, int x, int y, Color c);
 struct gfx_region gfx_screen(struct framebuffer *fb);
-void gfx_putchar(sFONT *font, struct gfx_region *reg, int x, int y, char ch,
+void gfx_putchar(const sFONT *font, struct gfx_region *reg, int x, int y, char ch,
 						        Color fg, Color bg);
-void gfx_puts(sFONT *font, struct gfx_region *reg, int x, int y,
+void gfx_puts(const sFONT *font, struct gfx_region *reg, int x, int y,
 			   const char *str, Color fg, Color bg);
 Color gfx_color_rgb_f(struct gfx_region *reg, float r, float g, float b);
 Color gfx_color_rgb(struct gfx_region *reg, uint8_t r, uint8_t g, uint8_t b);
diff --git a/lib/gfx/meson.build b/lib/gfx/meson.build
index 37b90e436950b8e2b164dcb0bede57373281d4ce..4df9f3c1c49f901652834e1d2a670932da52a3de 100644
--- a/lib/gfx/meson.build
+++ b/lib/gfx/meson.build
@@ -10,11 +10,9 @@ sources = files(
   './LCD/LCD_Driver.c',
   './Fonts/font8.c',
   './Fonts/font12.c',
-  './Fonts/font12CN.c',
   './Fonts/font16.c',
   './Fonts/font20.c',
   './Fonts/font24.c',
-  './Fonts/font24CN.c',
   'framebuffer.c',
   'gfx.c',
   'textbuffer.c'
diff --git a/preload/apps/analog_clock/__init__.py b/preload/apps/analog_clock/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..c679a82ec20191acec37fa99519f1cb81e52fc71
--- /dev/null
+++ b/preload/apps/analog_clock/__init__.py
@@ -0,0 +1,331 @@
+# Adapted from https://github.com/muccc/flipdots/blob/master/scripts/clock.py
+import display
+from utime import sleep
+import utime
+import math
+import leds
+import buttons
+import ujson
+import os
+
+CONFIG_NAME = "clock.json"
+
+
+class Time:
+    def __init__(self, start=0):
+        self.time = start
+        self.wait_time = 0.95
+
+    def tick(self):
+        sleep(self.wait_time)
+        self.time += 1
+
+    @property
+    def second(self):
+        return self.time % 60
+
+    @property
+    def minute(self):
+        return (self.time / 60) % 60
+
+    @property
+    def hour(self):
+        return (self.time / 3600) % 24
+
+
+class Clock:
+    def __init__(
+        self,
+        sizex=80,
+        sizey=80,
+        radius=38,
+        offsetx=30,
+        hour_hand=True,
+        minute_hand=True,
+        second_hand=True,
+        console_out=False,
+        run_once=False,
+        update_interval=0,
+    ):
+        self.sizex = sizex
+        self.sizey = sizey
+        self.radius = radius
+        self.center = (int(self.sizex / 2), int(self.sizey / 2))
+        self.hour_hand = hour_hand
+        self.minute_hand = minute_hand
+        self.second_hand = second_hand
+        self.console_out = console_out
+        self.update_interval = (
+            update_interval if update_interval != 0 else (1 if self.second_hand else 30)
+        )
+        self.run_once = run_once
+        self.offsetx = offsetx
+        self.time = Time()
+        self.theme = 0
+        self.default_themes = [
+            {
+                "background": [0, 0, 0],
+                "center": [255, 255, 255],
+                "m1": [255, 255, 255],
+                "m5": [255, 255, 255],
+                "hour_hand": [255, 255, 255],
+                "minute_hand": [255, 255, 255],
+                "second_hand": [255, 255, 255],
+            },
+            {
+                "background": [130, 30, 70],
+                "center": [255, 255, 255],
+                "m1": [255, 255, 255],
+                "m5": [255, 255, 255],
+                "hour_hand": [255, 255, 255],
+                "minute_hand": [255, 255, 255],
+                "second_hand": [255, 255, 255],
+            },
+            {
+                "background": [0, 80, 0],
+                "center": [255, 255, 255],
+                "m1": [255, 255, 255],
+                "m5": [255, 255, 255],
+                "hour_hand": [255, 255, 255],
+                "minute_hand": [255, 255, 255],
+                "second_hand": [255, 255, 255],
+            },
+            {
+                "background": [0, 80, 80],
+                "center": [255, 255, 255],
+                "m1": [255, 255, 255],
+                "m5": [255, 255, 255],
+                "hour_hand": [255, 255, 255],
+                "minute_hand": [255, 255, 255],
+                "second_hand": [255, 255, 255],
+            },
+            {
+                "background": [255, 255, 255],
+                "center": [0, 0, 0],
+                "m1": [0, 0, 0],
+                "m5": [0, 0, 0],
+                "hour_hand": [0, 0, 0],
+                "minute_hand": [0, 0, 0],
+                "second_hand": [0, 0, 0],
+            },
+        ]
+        self.themes = self.default_themes
+
+        # check for config file
+        if CONFIG_NAME in os.listdir("."):
+            self.readConfig()
+        else:
+            self.writeConfig()
+
+        # load colors
+        self.setTheme(self.theme)
+
+    def readConfig(self):
+        with open(CONFIG_NAME, "r") as f:
+            try:
+                c = ujson.loads(f.read())
+                if (
+                    "themes" in c
+                    and len(c["themes"]) > 0
+                    and isinstance(c["themes"], list)
+                ):
+                    self.themes = c["themes"]
+                if "theme" and isinstance(c["theme"], int):
+                    self.theme = c["theme"]
+            except ValueError:
+                print("parsing %s failed" % CONFIG_NAME)
+
+    def writeConfig(self):
+        with open(CONFIG_NAME, "w") as f:
+            f.write(ujson.dumps({"theme": self.theme, "themes": self.themes}))
+
+    def setTheme(self, theme):
+        self.theme = theme % len(self.themes)
+        self.background_col = (
+            self.themes[self.theme]["background"]
+            if "background" in self.themes[self.theme]
+            else self.default_themes[0]["background"]
+        )
+        self.center_col = (
+            self.themes[self.theme]["center"]
+            if "center" in self.themes[self.theme]
+            else self.default_themes[0]["center"]
+        )
+        self.m1_col = (
+            self.themes[self.theme]["m1"]
+            if "m1" in self.themes[self.theme]
+            else self.default_themes[0]["m1"]
+        )
+        self.m5_col = (
+            self.themes[self.theme]["m5"]
+            if "m5" in self.themes[self.theme]
+            else self.default_themes[0]["m5"]
+        )
+        self.hour_hand_col = (
+            self.themes[self.theme]["hour_hand"]
+            if "hour_hand" in self.themes[self.theme]
+            else self.default_themes[0]["hour_hand"]
+        )
+        self.minute_hand_col = (
+            self.themes[self.theme]["minute_hand"]
+            if "minute_hand" in self.themes[self.theme]
+            else self.default_themes[0]["minute_hand"]
+        )
+        self.second_hand_col = (
+            self.themes[self.theme]["second_hand"]
+            if "second_hand" in self.themes[self.theme]
+            else self.default_themes[0]["second_hand"]
+        )
+
+    def loop(self):
+        colored = False
+        try:
+            with display.open() as disp:
+                button_pressed = False
+                while True:
+                    self.updateClock(disp)
+                    if self.run_once:
+                        break
+
+                    # check for button presses
+                    v = buttons.read(buttons.BOTTOM_LEFT | buttons.BOTTOM_RIGHT)
+                    if v == 0:
+                        button_pressed = False
+
+                    if not button_pressed and v & buttons.BOTTOM_LEFT != 0:
+                        button_pressed = True
+                        self.setTheme(self.theme - 1)
+                        self.writeConfig()
+                    elif not button_pressed and v & buttons.BOTTOM_RIGHT != 0:
+                        button_pressed = True
+                        self.setTheme(self.theme + 1)
+                        self.writeConfig()
+
+        except KeyboardInterrupt:
+            for i in range(11):
+                leds.set(i, (0, 0, 0))
+            return
+
+    def drawImage(self, image):
+        with display.open() as d:
+            d.clear()
+            for x in range(len(image)):
+                for y in range(len(image[x])):
+                    d.pixel(
+                        x + self.offsetx,
+                        y,
+                        col=(255, 255, 255) if image[x][y] else (0, 0, 0),
+                    )
+            d.update()
+
+    def updateClock(self, disp):
+        disp.clear(self.background_col)
+        localtime = utime.localtime()
+
+        disp.pixel(self.center[0] + self.offsetx, self.center[1], col=self.center_col)
+        hour_coords = self.circlePoint(
+            math.radians(
+                (((localtime[3] % 12) / 12.0) if localtime[3] else 0) * 360
+                + 270
+                + (localtime[4] / 2)
+            )
+        )
+        minute_coords = self.circlePoint(math.radians(localtime[4] * 6 + 270))
+        second_coords = self.circlePoint(math.radians(localtime[5] * 6 + 270))
+
+        for i in range(60):
+            degree = i * 6 + 90
+            radian = -math.radians(degree)
+            coords = self.circlePoint(radian)
+
+            if not i % 5:
+                self.addLine(disp, coords, self.center, 3, 1, col=self.m5_col)
+            else:
+                self.addLine(disp, coords, self.center, 1, col=self.m1_col)
+
+        if self.hour_hand:
+            self.addLine(
+                disp,
+                self.center,
+                hour_coords,
+                int(self.radius / 3),
+                1,
+                col=self.hour_hand_col,
+            )
+        if self.minute_hand:
+            self.addLine(
+                disp,
+                self.center,
+                minute_coords,
+                int(self.radius / 2),
+                col=self.minute_hand_col,
+            )
+        if self.second_hand:
+            self.addLine(
+                disp,
+                self.center,
+                second_coords,
+                self.radius - int(self.radius / 8.0),
+                col=self.second_hand_col,
+            )
+
+        if self.console_out:
+            for y in range(self.radius * 2):
+                line = ""
+                for x in range(self.radius * 2):
+                    line = line + (
+                        "."
+                        if image[(self.center[1] - self.radius) + y][
+                            (self.center[0] - self.radius) + x
+                        ]
+                        else " "
+                    )
+                print(line)
+
+        disp.update()
+
+    def circlePoint(self, t):
+        return (
+            int(round(self.radius * math.cos(t))) + self.center[0],
+            int(round(self.radius * math.sin(t))) + self.center[1],
+        )
+
+    def addLine(self, disp, source, aim, length, thickness=1, col=(255, 255, 255)):
+        vector = self.subVector(aim, source)
+        vector = self.normVector(vector)
+        destination = self.addVector(source, self.multiplyVector(vector, length))
+
+        disp.line(
+            round(source[0]) + self.offsetx,
+            round(source[1]),
+            round(destination[0]) + self.offsetx,
+            round(destination[1]),
+            col=col,
+            size=thickness,
+        )
+
+    def normVector(self, v):
+        length = math.sqrt(sum([i ** 2 for i in v]))
+        new_v = []
+        for i in range(len(v)):
+            new_v.append(v[i] / length)
+        return tuple(new_v)
+
+    def subVector(self, v1, v2):
+        res = []
+        for i in range(len(v1)):
+            res.append(v1[i] - v2[i])
+        return tuple(res)
+
+    def addVector(self, v1, v2):
+        res = []
+        for i in range(len(v1)):
+            res.append(v1[i] + v2[i])
+        return tuple(res)
+
+    def multiplyVector(self, v, multiplier):
+        return tuple([i * multiplier for i in v])
+
+
+clock = Clock()
+clock.loop()
diff --git a/preload/apps/analog_clock/metadata.json b/preload/apps/analog_clock/metadata.json
new file mode 100644
index 0000000000000000000000000000000000000000..3b3303d5ce09cf9c89f63fe2b2174b1958b0a118
--- /dev/null
+++ b/preload/apps/analog_clock/metadata.json
@@ -0,0 +1 @@
+{"name":"Analog Clock","description":"Analog Clock","category":"graphics","author":"markus","revision":-1}
diff --git a/preload/apps/ecg/__init__.py b/preload/apps/ecg/__init__.py
index ccc38843d0201298e5933318c2f5124c220e732d..d8ff79e736d3a267a19d7c5e99697231ed9d6543 100644
--- a/preload/apps/ecg/__init__.py
+++ b/preload/apps/ecg/__init__.py
@@ -1,242 +1,342 @@
-import os
-import display
-import utime
-import buttons
-import max30001
-import math
-import struct
-
-WIDTH = 160
-HEIGHT = 80
-OFFSET = 50
-ECG_RATE = 128
-HISTORY_MAX = ECG_RATE * 2
-DRAW_AFTER_SAMPLES = 5
-SCALE_FACTOR = 30
-MODE_USB = "USB"
-MODE_FINGER = "Finger"
-FILEBUFFERBLOCK = 4096
-COLOR_BACKGROUND = [0, 0, 0]
-COLOR_LINE = [255, 255, 255]
-COLOR_TEXT = [255, 255, 255]
-COLOR_MODE_FINGER = [0, 255, 0]
-COLOR_MODE_USB = [0, 0, 255]
-COLOR_WRITE_FG = [255, 255, 255]
-COLOR_WRITE_BG = [255, 0, 0]
-
-current_mode = MODE_FINGER
-history = []
-filebuffer = bytearray()
-write = 0
-bias = True
-update_screen = 0
-pause_screen = 0
-sensor = 0
-disp = display.open()
-
-
-def callback_ecg(datasets):
-    global update_screen, history, filebuffer, write
-    if len(datasets) > 0:
-        for value in datasets:
-            history.append(value)
-        update_screen += len(datasets)
-    if len(history) > HISTORY_MAX:
-        r = len(history) - HISTORY_MAX
-        for i in range(r):
-            history.pop(0)
-
-    # buffer for writes
-    if write > 0:
-        if len(datasets) > 0:
-            for value in datasets:
-                filebuffer.extend(struct.pack("h", value))
-                if len(filebuffer) >= FILEBUFFERBLOCK:
-                    write_filebuffer()
-
-    # don't update on every callback
-    if update_screen >= DRAW_AFTER_SAMPLES:
-        # print("history: %i, filebuffer: %i" % (len(history), len(filebuffer)))
-        draw_histogram()
-        update_screen = 0
-
-
-def write_filebuffer():
-    global write, filebuffer
-    # write to file
-    chars = ""
-    lt = utime.localtime(write)
-    filename = "/ecg-%04d-%02d-%02d_%02d%02d%02d.log" % (
-        lt[0],
-        lt[1],
-        lt[2],
-        lt[3],
-        lt[4],
-        lt[5],
-    )
-
-    # write stuff to disk
-    # print("Write %i bytes to %s" % (len(filebuffer), filename))
-    try:
-        f = open(filename, "ab")
-        f.write(filebuffer)
-        f.close()
-    except OSError as e:
-        print("Please check the file or filesystem", e)
-        write = 0
-        pause_screen = -1
-        disp.clear(COLOR_BACKGROUND)
-        disp.print("IO Error", posy=0, fg=COLOR_TEXT)
-        disp.print("Please check", posy=20, fg=COLOR_TEXT)
-        disp.print("your", posy=40, fg=COLOR_TEXT)
-        disp.print("filesystem", posy=60, fg=COLOR_TEXT)
-        disp.update()
-        close_sensor()
-    except:
-        print("Unexpected error, stop writeing logfile")
-        write = 0
-
-    filebuffer = bytearray()
-    return
-
-
-def open_sensor():
-    global sensor
-    sensor = max30001.MAX30001(
-        usb=(False if current_mode == MODE_FINGER else True),
-        bias=bias,
-        sample_rate=ECG_RATE,
-        callback=callback_ecg,
-    )
-
-
-def close_sensor():
-    global sensor
-    sensor.close()
-
-
-def toggle_mode():
-    global current_mode
-    close_sensor()
-    current_mode = MODE_USB if current_mode == MODE_FINGER else MODE_FINGER
-    open_sensor()
-
-
-def toggle_bias():
-    global bias
-    close_sensor()
-    bias = False if bias == True else True
-    open_sensor()
-    return
-
-
-def toggle_write():
-    global write, disp, pause_screen
-    pause_screen = utime.time_ms() + 1000
-    disp.clear(COLOR_BACKGROUND)
-    if write > 0:
-        write_filebuffer()
-        write = 0
-        disp.print("Stop", posx=50, posy=20, fg=COLOR_TEXT)
-        disp.print("logging", posx=30, posy=40, fg=COLOR_TEXT)
-    else:
-        filebuffer = bytearray()
-        write = utime.time()
-        disp.print("Start", posx=45, posy=20, fg=COLOR_TEXT)
-        disp.print("logging", posx=30, posy=40, fg=COLOR_TEXT)
-
-    disp.update()
-    return
-
-
-def draw_histogram():
-    global disp, history, current_mode, bias, write, pause_screen
-
-    # skip rendering due to message beeing shown
-    if pause_screen == -1:
-        return
-    elif pause_screen > 0:
-        t = utime.time_ms()
-        if t > pause_screen:
-            pause_screen = 0
-        else:
-            return
-
-    disp.clear(COLOR_BACKGROUND)
-
-    # get max value and calc scale
-    value_max = 0
-    for value in history:
-        if abs(value) > value_max:
-            value_max = abs(value)
-    scale = SCALE_FACTOR / (value_max if value_max > 0 else 1)
-
-    # draw histogram
-    old = False
-    x = 0
-    samples = len(history)
-    for i, value in enumerate(history):
-        if old == False:
-            old = (x, int(value * scale) + OFFSET)
-            x += 1
-            continue
-        elif i > samples - WIDTH:
-            disp.line(old[0], old[1], x, int(value * scale) + OFFSET, col=COLOR_LINE)
-            old = (x, int(value * scale) + OFFSET)
-            x += 1
-
-    # draw text: mode/bias/write
-    disp.print(
-        current_mode + ("+Bias" if bias else ""),
-        posx=0,
-        posy=0,
-        fg=(COLOR_MODE_FINGER if current_mode == MODE_FINGER else COLOR_MODE_USB),
-    )
-
-    # announce writing ecg log
-    if write > 0:
-        t = utime.time()
-        if write > 0 and t % 2 == 0:
-            disp.print("LOG", posx=0, posy=60, fg=COLOR_WRITE_FG, bg=COLOR_WRITE_BG)
-
-    disp.update()
-
-
-def main():
-    # show button layout
-    disp.clear(COLOR_BACKGROUND)
-    disp.print("  BUTTONS  ", posx=0, posy=0, fg=COLOR_TEXT)
-    disp.print("Finger/USB>", posx=0, posy=20, fg=COLOR_MODE_FINGER)
-    disp.print("     Bias >", posx=0, posy=40, fg=COLOR_MODE_USB)
-    disp.print("< Write Log", posx=0, posy=60, fg=COLOR_WRITE_BG)
-    disp.update()
-    utime.sleep(3)
-
-    # start ecg
-    open_sensor()
-    while True:
-        button_pressed = False
-        while True:
-            v = buttons.read(
-                buttons.BOTTOM_LEFT | buttons.BOTTOM_RIGHT | buttons.TOP_RIGHT
-            )
-            if v == 0:
-                button_pressed = False
-
-            if not button_pressed and v & buttons.BOTTOM_LEFT != 0:
-                button_pressed = True
-                toggle_write()
-
-            elif not button_pressed and v & buttons.BOTTOM_RIGHT != 0:
-                button_pressed = True
-                toggle_bias()
-
-            elif not button_pressed and v & buttons.TOP_RIGHT != 0:
-                button_pressed = True
-                toggle_mode()
-
-        pass
-
-
-if __name__ == "__main__":
-    main()
+import os
+import display
+import leds
+import utime
+import buttons
+import max30001
+import math
+import struct
+
+WIDTH = 160
+HEIGHT = 80
+OFFSET_Y = 49
+ECG_RATE = 128
+HISTORY_MAX = ECG_RATE * 4
+DRAW_AFTER_SAMPLES = 5
+SCALE_FACTOR = 30
+MODE_USB = "USB"
+MODE_FINGER = "Finger"
+FILEBUFFERBLOCK = 4096
+COLOR_BACKGROUND = [0, 0, 0]
+COLOR_LINE = [255, 255, 255]
+COLOR_TEXT = [255, 255, 255]
+COLOR_MODE_FINGER = [0, 255, 0]
+COLOR_MODE_USB = [0, 0, 255]
+COLOR_WRITE_FG = [255, 255, 255]
+COLOR_WRITE_BG = [255, 0, 0]
+
+current_mode = MODE_FINGER
+history = []
+filebuffer = bytearray()
+write = 0
+bias = True
+update_screen = 0
+pause_screen = 0
+pause_histogram = False
+histogram_offset = 0
+sensor = 0
+disp = display.open()
+
+leds.dim_top(1)
+COLORS = [((23 + (15 * i)) % 360, 1.0, 1.0) for i in range(11)]
+colors = COLORS
+
+
+def callback_ecg(datasets):
+    global update_screen, history, filebuffer, write
+    update_screen += len(datasets)
+
+    # update histogram datalist
+    if pause_histogram == False:
+        if len(datasets) > 0:
+            for value in datasets:
+                history.append(value)
+        if len(history) > HISTORY_MAX:
+            r = len(history) - HISTORY_MAX
+            for i in range(r):
+                history.pop(0)
+
+    # buffer for writes
+    if write > 0:
+        if len(datasets) > 0:
+            for value in datasets:
+                filebuffer.extend(struct.pack("h", value))
+                if len(filebuffer) >= FILEBUFFERBLOCK:
+                    write_filebuffer()
+
+    # don't update on every callback
+    if update_screen >= DRAW_AFTER_SAMPLES:
+        draw_histogram()
+
+
+def write_filebuffer():
+    global write, filebuffer
+    # write to file
+    chars = ""
+    lt = utime.localtime(write)
+    filename = "/ecg-%04d-%02d-%02d_%02d%02d%02d.log" % (
+        lt[0],
+        lt[1],
+        lt[2],
+        lt[3],
+        lt[4],
+        lt[5],
+    )
+
+    # write stuff to disk
+    try:
+        f = open(filename, "ab")
+        f.write(filebuffer)
+        f.close()
+    except OSError as e:
+        print("Please check the file or filesystem", e)
+        write = 0
+        pause_screen = -1
+        disp.clear(COLOR_BACKGROUND)
+        disp.print("IO Error", posy=0, fg=COLOR_TEXT)
+        disp.print("Please check", posy=20, fg=COLOR_TEXT)
+        disp.print("your", posy=40, fg=COLOR_TEXT)
+        disp.print("filesystem", posy=60, fg=COLOR_TEXT)
+        disp.update()
+        close_sensor()
+    except:
+        print("Unexpected error, stop writeing logfile")
+        write = 0
+
+    filebuffer = bytearray()
+    return
+
+
+def open_sensor():
+    global sensor
+    sensor = max30001.MAX30001(
+        usb=(False if current_mode == MODE_FINGER else True),
+        bias=bias,
+        sample_rate=ECG_RATE,
+        callback=callback_ecg,
+    )
+
+
+def close_sensor():
+    global sensor
+    sensor.close()
+
+
+def toggle_mode():
+    global current_mode
+    close_sensor()
+    current_mode = MODE_USB if current_mode == MODE_FINGER else MODE_FINGER
+    open_sensor()
+
+
+def toggle_bias():
+    global bias
+    close_sensor()
+    bias = False if bias == True else True
+    open_sensor()
+    return
+
+
+def toggle_write():
+    global write, disp, pause_screen
+    pause_screen = utime.time_ms() + 1000
+    disp.clear(COLOR_BACKGROUND)
+    if write > 0:
+        write_filebuffer()
+        write = 0
+        disp.print("Stop", posx=50, posy=20, fg=COLOR_TEXT)
+        disp.print("logging", posx=30, posy=40, fg=COLOR_TEXT)
+    else:
+        filebuffer = bytearray()
+        write = utime.time()
+        disp.print("Start", posx=45, posy=20, fg=COLOR_TEXT)
+        disp.print("logging", posx=30, posy=40, fg=COLOR_TEXT)
+
+    disp.update()
+    return
+
+
+def toggle_pause():
+    global pause_histogram, histogram_offset, history
+    if pause_histogram == True:
+        pause_histogram = False
+        history = []
+    else:
+        pause_histogram = True
+    histogram_offset = 0
+
+
+def draw_leds(val):
+    global colors
+    # val should be in [0, 11]
+    for i in range(11):
+        leds.prep_hsv(10 - i, COLORS[10 - i] if i < val else (0, 0, 0))
+    leds.update()
+
+
+def draw_histogram():
+    global disp, history, current_mode, bias, write, pause_screen, update_screen
+
+    # skip rendering due to message beeing shown
+    if pause_screen == -1:
+        return
+    elif pause_screen > 0:
+        t = utime.time_ms()
+        if t > pause_screen:
+            pause_screen = 0
+        else:
+            return
+
+    disp.clear(COLOR_BACKGROUND)
+
+    # offset in pause_histogram mode
+    samples = len(history)
+    s_start = samples - (histogram_offset + (ECG_RATE * 2))
+    s_end = samples - (histogram_offset + 1)
+    s_draw = s_end - (WIDTH - 1)
+
+    # get max value and calc scale
+    value_max = 0
+    for i, value in enumerate(history):
+        if i >= s_start and i <= s_end and abs(value) > value_max:
+            value_max = abs(value)
+    scale = SCALE_FACTOR / (value_max if value_max > 0 else 1)
+
+    # draw histogram
+    old = False
+    x = 0
+    for i, value in enumerate(history):
+        if old == False:
+            old = value
+            x += 1
+            continue
+        elif i > s_start and i > s_draw and i < s_end:
+
+            oldy = int(old * scale)
+            if oldy < -SCALE_FACTOR:
+                oldy = -SCALE_FACTOR
+            elif oldy > SCALE_FACTOR:
+                oldy = SCALE_FACTOR
+
+            disp.line(
+                x - 1, oldy + OFFSET_Y, x, int(value * scale) + OFFSET_Y, col=COLOR_LINE
+            )
+            old = value
+            x += 1
+
+    draw_leds((60 - int((max(history[-3:]) * scale + OFFSET_Y) - 20)) * 11 / 60)
+    # draw text: mode/bias/write
+    if pause_histogram == True:
+        disp.print(
+            "Pause"
+            + (
+                " -%0.1fs" % (histogram_offset / ECG_RATE)
+                if histogram_offset > 0
+                else ""
+            ),
+            posx=0,
+            posy=0,
+            fg=COLOR_TEXT,
+        )
+    else:
+        disp.print(
+            current_mode + ("+Bias" if bias else ""),
+            posx=0,
+            posy=0,
+            fg=(COLOR_MODE_FINGER if current_mode == MODE_FINGER else COLOR_MODE_USB),
+        )
+
+    # announce writing ecg log
+    if write > 0:
+        t = utime.time()
+        if write > 0 and t % 2 == 0:
+            disp.print("LOG", posx=0, posy=60, fg=COLOR_WRITE_FG, bg=COLOR_WRITE_BG)
+
+    disp.update()
+    update_screen = 0
+
+
+def main():
+    global pause_histogram, histogram_offset
+
+    # show button layout
+    disp.clear(COLOR_BACKGROUND)
+    disp.print("  BUTTONS  ", posx=0, posy=0, fg=COLOR_TEXT)
+    disp.print("Finger/USB>", posx=0, posy=20, fg=COLOR_MODE_FINGER)
+    disp.print("     Bias >", posx=0, posy=40, fg=COLOR_MODE_USB)
+    disp.print("< Write Log", posx=0, posy=60, fg=COLOR_WRITE_BG)
+    disp.update()
+    utime.sleep(3)
+
+    # start ecg
+    open_sensor()
+    while True:
+        button_pressed = {"BOTTOM_LEFT": 0, "BOTTOM_RIGHT": 0, "TOP_RIGHT": 0}
+        while True:
+            v = buttons.read(
+                buttons.BOTTOM_LEFT | buttons.BOTTOM_RIGHT | buttons.TOP_RIGHT
+            )
+
+            # BUTTOM LEFT
+
+            if button_pressed["BOTTOM_LEFT"] == 0 and v & buttons.BOTTOM_LEFT != 0:
+                button_pressed["BOTTOM_LEFT"] = utime.time_ms()
+                if pause_histogram == False:
+                    toggle_write()
+                else:
+                    l = len(history)
+                    histogram_offset += ECG_RATE / 2
+                    if l - histogram_offset < WIDTH:
+                        histogram_offset = l - WIDTH
+
+            if button_pressed["BOTTOM_LEFT"] > 0 and v & buttons.BOTTOM_LEFT == 0:
+                duration = utime.time_ms() - button_pressed["BOTTOM_LEFT"]
+                button_pressed["BOTTOM_LEFT"] = 0
+
+            # BUTTOM RIGHT
+
+            if button_pressed["BOTTOM_RIGHT"] == 0 and v & buttons.BOTTOM_RIGHT != 0:
+                button_pressed["BOTTOM_RIGHT"] = utime.time_ms()
+                if pause_histogram == False:
+                    toggle_bias()
+                else:
+                    histogram_offset -= ECG_RATE / 2
+                    histogram_offset -= histogram_offset % (ECG_RATE / 2)
+                    if histogram_offset < 0:
+                        histogram_offset = 0
+
+            if button_pressed["BOTTOM_RIGHT"] > 0 and v & buttons.BOTTOM_RIGHT == 0:
+                duration = utime.time_ms() - button_pressed["BOTTOM_RIGHT"]
+                button_pressed["BOTTOM_RIGHT"] = 0
+
+            # TOP RIGHT
+
+            # down, and still pressed
+            if button_pressed["TOP_RIGHT"] > 0 and v & buttons.TOP_RIGHT != 0:
+                duration = utime.time_ms() - button_pressed["TOP_RIGHT"]
+                if duration > 1000:
+                    button_pressed["TOP_RIGHT"] = -1
+                    toggle_pause()
+
+            # register down event
+            elif button_pressed["TOP_RIGHT"] == 0 and v & buttons.TOP_RIGHT != 0:
+                button_pressed["TOP_RIGHT"] = utime.time_ms()
+
+            # register up event but event already called
+            if button_pressed["TOP_RIGHT"] == -1 and v & buttons.TOP_RIGHT == 0:
+                button_pressed["TOP_RIGHT"] = 0
+
+            # register normal up event
+            elif button_pressed["TOP_RIGHT"] > 0 and v & buttons.TOP_RIGHT == 0:
+                duration = utime.time_ms() - button_pressed["TOP_RIGHT"]
+                button_pressed["TOP_RIGHT"] = 0
+                if pause_histogram == True:
+                    toggle_pause()
+                else:
+                    toggle_mode()
+
+        pass
+
+
+if __name__ == "__main__":
+    main()
diff --git a/preload/main.py b/preload/main.py
index c679a82ec20191acec37fa99519f1cb81e52fc71..7c8417e9ae1375779ef5a7c2fd4fdcca35cd36b7 100644
--- a/preload/main.py
+++ b/preload/main.py
@@ -1,331 +1,18 @@
-# Adapted from https://github.com/muccc/flipdots/blob/master/scripts/clock.py
-import display
-from utime import sleep
-import utime
-import math
-import leds
-import buttons
-import ujson
 import os
 
-CONFIG_NAME = "clock.json"
 
+def main():
+    # Try loading analog clock
+    default_app = "apps/analog_clock/__init__.py"
+    try:
+        with open(default_app, "r"):
+            pass
 
-class Time:
-    def __init__(self, start=0):
-        self.time = start
-        self.wait_time = 0.95
+        print("main.py: Loading " + default_app)
+        os.exec(default_app)
+    finally:
+        os.exit(1)
 
-    def tick(self):
-        sleep(self.wait_time)
-        self.time += 1
 
-    @property
-    def second(self):
-        return self.time % 60
-
-    @property
-    def minute(self):
-        return (self.time / 60) % 60
-
-    @property
-    def hour(self):
-        return (self.time / 3600) % 24
-
-
-class Clock:
-    def __init__(
-        self,
-        sizex=80,
-        sizey=80,
-        radius=38,
-        offsetx=30,
-        hour_hand=True,
-        minute_hand=True,
-        second_hand=True,
-        console_out=False,
-        run_once=False,
-        update_interval=0,
-    ):
-        self.sizex = sizex
-        self.sizey = sizey
-        self.radius = radius
-        self.center = (int(self.sizex / 2), int(self.sizey / 2))
-        self.hour_hand = hour_hand
-        self.minute_hand = minute_hand
-        self.second_hand = second_hand
-        self.console_out = console_out
-        self.update_interval = (
-            update_interval if update_interval != 0 else (1 if self.second_hand else 30)
-        )
-        self.run_once = run_once
-        self.offsetx = offsetx
-        self.time = Time()
-        self.theme = 0
-        self.default_themes = [
-            {
-                "background": [0, 0, 0],
-                "center": [255, 255, 255],
-                "m1": [255, 255, 255],
-                "m5": [255, 255, 255],
-                "hour_hand": [255, 255, 255],
-                "minute_hand": [255, 255, 255],
-                "second_hand": [255, 255, 255],
-            },
-            {
-                "background": [130, 30, 70],
-                "center": [255, 255, 255],
-                "m1": [255, 255, 255],
-                "m5": [255, 255, 255],
-                "hour_hand": [255, 255, 255],
-                "minute_hand": [255, 255, 255],
-                "second_hand": [255, 255, 255],
-            },
-            {
-                "background": [0, 80, 0],
-                "center": [255, 255, 255],
-                "m1": [255, 255, 255],
-                "m5": [255, 255, 255],
-                "hour_hand": [255, 255, 255],
-                "minute_hand": [255, 255, 255],
-                "second_hand": [255, 255, 255],
-            },
-            {
-                "background": [0, 80, 80],
-                "center": [255, 255, 255],
-                "m1": [255, 255, 255],
-                "m5": [255, 255, 255],
-                "hour_hand": [255, 255, 255],
-                "minute_hand": [255, 255, 255],
-                "second_hand": [255, 255, 255],
-            },
-            {
-                "background": [255, 255, 255],
-                "center": [0, 0, 0],
-                "m1": [0, 0, 0],
-                "m5": [0, 0, 0],
-                "hour_hand": [0, 0, 0],
-                "minute_hand": [0, 0, 0],
-                "second_hand": [0, 0, 0],
-            },
-        ]
-        self.themes = self.default_themes
-
-        # check for config file
-        if CONFIG_NAME in os.listdir("."):
-            self.readConfig()
-        else:
-            self.writeConfig()
-
-        # load colors
-        self.setTheme(self.theme)
-
-    def readConfig(self):
-        with open(CONFIG_NAME, "r") as f:
-            try:
-                c = ujson.loads(f.read())
-                if (
-                    "themes" in c
-                    and len(c["themes"]) > 0
-                    and isinstance(c["themes"], list)
-                ):
-                    self.themes = c["themes"]
-                if "theme" and isinstance(c["theme"], int):
-                    self.theme = c["theme"]
-            except ValueError:
-                print("parsing %s failed" % CONFIG_NAME)
-
-    def writeConfig(self):
-        with open(CONFIG_NAME, "w") as f:
-            f.write(ujson.dumps({"theme": self.theme, "themes": self.themes}))
-
-    def setTheme(self, theme):
-        self.theme = theme % len(self.themes)
-        self.background_col = (
-            self.themes[self.theme]["background"]
-            if "background" in self.themes[self.theme]
-            else self.default_themes[0]["background"]
-        )
-        self.center_col = (
-            self.themes[self.theme]["center"]
-            if "center" in self.themes[self.theme]
-            else self.default_themes[0]["center"]
-        )
-        self.m1_col = (
-            self.themes[self.theme]["m1"]
-            if "m1" in self.themes[self.theme]
-            else self.default_themes[0]["m1"]
-        )
-        self.m5_col = (
-            self.themes[self.theme]["m5"]
-            if "m5" in self.themes[self.theme]
-            else self.default_themes[0]["m5"]
-        )
-        self.hour_hand_col = (
-            self.themes[self.theme]["hour_hand"]
-            if "hour_hand" in self.themes[self.theme]
-            else self.default_themes[0]["hour_hand"]
-        )
-        self.minute_hand_col = (
-            self.themes[self.theme]["minute_hand"]
-            if "minute_hand" in self.themes[self.theme]
-            else self.default_themes[0]["minute_hand"]
-        )
-        self.second_hand_col = (
-            self.themes[self.theme]["second_hand"]
-            if "second_hand" in self.themes[self.theme]
-            else self.default_themes[0]["second_hand"]
-        )
-
-    def loop(self):
-        colored = False
-        try:
-            with display.open() as disp:
-                button_pressed = False
-                while True:
-                    self.updateClock(disp)
-                    if self.run_once:
-                        break
-
-                    # check for button presses
-                    v = buttons.read(buttons.BOTTOM_LEFT | buttons.BOTTOM_RIGHT)
-                    if v == 0:
-                        button_pressed = False
-
-                    if not button_pressed and v & buttons.BOTTOM_LEFT != 0:
-                        button_pressed = True
-                        self.setTheme(self.theme - 1)
-                        self.writeConfig()
-                    elif not button_pressed and v & buttons.BOTTOM_RIGHT != 0:
-                        button_pressed = True
-                        self.setTheme(self.theme + 1)
-                        self.writeConfig()
-
-        except KeyboardInterrupt:
-            for i in range(11):
-                leds.set(i, (0, 0, 0))
-            return
-
-    def drawImage(self, image):
-        with display.open() as d:
-            d.clear()
-            for x in range(len(image)):
-                for y in range(len(image[x])):
-                    d.pixel(
-                        x + self.offsetx,
-                        y,
-                        col=(255, 255, 255) if image[x][y] else (0, 0, 0),
-                    )
-            d.update()
-
-    def updateClock(self, disp):
-        disp.clear(self.background_col)
-        localtime = utime.localtime()
-
-        disp.pixel(self.center[0] + self.offsetx, self.center[1], col=self.center_col)
-        hour_coords = self.circlePoint(
-            math.radians(
-                (((localtime[3] % 12) / 12.0) if localtime[3] else 0) * 360
-                + 270
-                + (localtime[4] / 2)
-            )
-        )
-        minute_coords = self.circlePoint(math.radians(localtime[4] * 6 + 270))
-        second_coords = self.circlePoint(math.radians(localtime[5] * 6 + 270))
-
-        for i in range(60):
-            degree = i * 6 + 90
-            radian = -math.radians(degree)
-            coords = self.circlePoint(radian)
-
-            if not i % 5:
-                self.addLine(disp, coords, self.center, 3, 1, col=self.m5_col)
-            else:
-                self.addLine(disp, coords, self.center, 1, col=self.m1_col)
-
-        if self.hour_hand:
-            self.addLine(
-                disp,
-                self.center,
-                hour_coords,
-                int(self.radius / 3),
-                1,
-                col=self.hour_hand_col,
-            )
-        if self.minute_hand:
-            self.addLine(
-                disp,
-                self.center,
-                minute_coords,
-                int(self.radius / 2),
-                col=self.minute_hand_col,
-            )
-        if self.second_hand:
-            self.addLine(
-                disp,
-                self.center,
-                second_coords,
-                self.radius - int(self.radius / 8.0),
-                col=self.second_hand_col,
-            )
-
-        if self.console_out:
-            for y in range(self.radius * 2):
-                line = ""
-                for x in range(self.radius * 2):
-                    line = line + (
-                        "."
-                        if image[(self.center[1] - self.radius) + y][
-                            (self.center[0] - self.radius) + x
-                        ]
-                        else " "
-                    )
-                print(line)
-
-        disp.update()
-
-    def circlePoint(self, t):
-        return (
-            int(round(self.radius * math.cos(t))) + self.center[0],
-            int(round(self.radius * math.sin(t))) + self.center[1],
-        )
-
-    def addLine(self, disp, source, aim, length, thickness=1, col=(255, 255, 255)):
-        vector = self.subVector(aim, source)
-        vector = self.normVector(vector)
-        destination = self.addVector(source, self.multiplyVector(vector, length))
-
-        disp.line(
-            round(source[0]) + self.offsetx,
-            round(source[1]),
-            round(destination[0]) + self.offsetx,
-            round(destination[1]),
-            col=col,
-            size=thickness,
-        )
-
-    def normVector(self, v):
-        length = math.sqrt(sum([i ** 2 for i in v]))
-        new_v = []
-        for i in range(len(v)):
-            new_v.append(v[i] / length)
-        return tuple(new_v)
-
-    def subVector(self, v1, v2):
-        res = []
-        for i in range(len(v1)):
-            res.append(v1[i] - v2[i])
-        return tuple(res)
-
-    def addVector(self, v1, v2):
-        res = []
-        for i in range(len(v1)):
-            res.append(v1[i] + v2[i])
-        return tuple(res)
-
-    def multiplyVector(self, v, multiplier):
-        return tuple([i * multiplier for i in v])
-
-
-clock = Clock()
-clock.loop()
+if __name__ == "__main__":
+    main()
diff --git a/preload/menu.py b/preload/menu.py
index 5063803fc81145b4c9ccb8d9fb5515c6aa31a5d2..b8479c1dbdf9600d2af700249a35a33a770e9ad1 100644
--- a/preload/menu.py
+++ b/preload/menu.py
@@ -14,8 +14,12 @@ import ujson
 import sys
 
 BUTTON_TIMER_POPPED = -1
-COLOR1, COLOR2 = (color.CHAOSBLUE_DARK, color.CHAOSBLUE)
+COLOR_BG = color.CHAOSBLUE_DARK
+COLOR_BG_SEL = color.CHAOSBLUE
+COLOR_ARROW = color.COMMYELLOW
+COLOR_TEXT = color.COMMYELLOW
 MAXCHARS = 11
+HOMEAPP = "main.py"
 
 
 def create_folders():
@@ -31,13 +35,13 @@ 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": "",
             "name": app_folder,
-            "descriptionr": "",
+            "description": "",
             "category": "",
             "revision": 0,
         }
@@ -49,10 +53,10 @@ def list_apps():
 
     # add main application
     for mainFile in os.listdir("/"):
-        if mainFile == "main.py":
+        if mainFile == HOMEAPP:
             apps.append(
                 [
-                    "/main.py",
+                    "/%s" % HOMEAPP,
                     {
                         "author": "card10badge Team",
                         "name": "Home",
@@ -71,7 +75,12 @@ def list_apps():
     # with or without metadata.json
     for appFolder in dirlist:
         if not (appFolder.endswith(".py") or appFolder.endswith(".elf")):
-            apps.append(["/apps/%s/__init__.py" % appFolder, read_metadata(appFolder)])
+            metadata = read_metadata(appFolder)
+            if not metadata.get("bin", None):
+                fileName = "/apps/%s/__init__.py" % appFolder
+            else:
+                fileName = "/apps/%s/%s" % (appFolder, metadata["bin"])
+            apps.append([fileName, metadata])
 
     # list simple python scripts
     for pyFile in dirlist:
@@ -167,7 +176,7 @@ def draw_menu(disp, applist, pos, appcount, lineoffset):
                 (i - start) * 20,
                 159,
                 (i - start) * 20 + 20,
-                col=COLOR1 if i == pos else COLOR2,
+                col=COLOR_BG_SEL if i == pos else COLOR_BG,
             )
 
             line = app[1]["name"]
@@ -187,10 +196,11 @@ def draw_menu(disp, applist, pos, appcount, lineoffset):
             disp.print(
                 " " + line[off : (off + (MAXCHARS - 1))],
                 posy=(i - start) * 20,
-                bg=COLOR1 if i == pos else COLOR2,
+                fg=COLOR_TEXT,
+                bg=COLOR_BG_SEL if i == pos else COLOR_BG,
             )
             if i == pos:
-                disp.print(">", posy=(i - start) * 20, fg=color.COMMYELLOW, bg=COLOR1)
+                disp.print(">", posy=(i - start) * 20, fg=COLOR_ARROW, bg=COLOR_BG_SEL)
 
             if linelength > (MAXCHARS - 1) and off < linelength - (MAXCHARS - 1):
                 triangle(disp, 153, (i - start) * 20 + 6, False, 6)
@@ -214,23 +224,12 @@ def main():
     timerscrollspeed = 1
     timerstartscroll = 5
     timercountpopped = 0
+    timerinactivity = 100
     for ev in button_events(10):
         if numapps == 0:
-            disp.clear(color.COMMYELLOW)
-            disp.print(
-                " No apps ",
-                posx=17,
-                posy=20,
-                fg=color.COMMYELLOW_DARK,
-                bg=color.COMMYELLOW,
-            )
-            disp.print(
-                "available",
-                posx=17,
-                posy=40,
-                fg=color.COMMYELLOW_DARK,
-                bg=color.COMMYELLOW,
-            )
+            disp.clear(COLOR_BG)
+            disp.print(" No apps ", posx=17, posy=20, fg=COLOR_TEXT, bg=COLOR_BG)
+            disp.print("available", posx=17, posy=40, fg=COLOR_TEXT, bg=COLOR_BG)
             disp.update()
             continue
 
@@ -254,6 +253,16 @@ def main():
             ):
                 lineoffset += 1
 
+            if applist[0][0] == "/%s" % HOMEAPP and timercountpopped >= timerinactivity:
+                print("Inactivity timer popped")
+                disp.clear().update()
+                disp.close()
+                try:
+                    os.exec("/%s" % HOMEAPP)
+                except OSError as e:
+                    print("Loading failed: ", e)
+                    os.exit(1)
+
         elif ev == buttons.TOP_RIGHT:
             # Select & start
             disp.clear().update()
@@ -268,4 +277,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/main.c b/pycardium/main.c
index 56a7575a2cafb27774ae3785b2390f40262f433e..b87b4a1e26cedd4da3ccfd744c0f66894be6b2d0 100644
--- a/pycardium/main.c
+++ b/pycardium/main.c
@@ -60,7 +60,18 @@ int main(void)
 		}
 
 		epic_uart_write_str(header, sizeof(header));
-		pyexec_friendly_repl();
+
+		for (;;) {
+			if (pyexec_mode_kind == PYEXEC_MODE_RAW_REPL) {
+				if (pyexec_raw_repl() != 0) {
+					break;
+				}
+			} else {
+				if (pyexec_friendly_repl() != 0) {
+					break;
+				}
+			}
+		}
 
 		mp_deinit();
 	}
diff --git a/pycardium/modules/bhi160-sys.c b/pycardium/modules/bhi160-sys.c
index db0a5c9a65eb42f9b8ee392ffa5650909877bfb0..7320885cd9268bacd0fdfdc68e92b177fe2d1c6d 100644
--- a/pycardium/modules/bhi160-sys.c
+++ b/pycardium/modules/bhi160-sys.c
@@ -66,6 +66,17 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(
 	mp_bhi160_disable_sensor_obj, mp_bhi160_disable_sensor
 );
 
+STATIC mp_obj_t mp_bhi160_disable_all_sensors()
+{
+	epic_bhi160_disable_all_sensors();
+
+	return mp_const_none;
+}
+
+STATIC MP_DEFINE_CONST_FUN_OBJ_0(
+	mp_bhi160_disable_all_sensors_obj, mp_bhi160_disable_all_sensors
+);
+
 STATIC const mp_rom_map_elem_t bhi160_module_globals_table[] = {
 	{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_sys_bhi160) },
 	{ MP_ROM_QSTR(MP_QSTR_enable_sensor),
@@ -74,6 +85,8 @@ STATIC const mp_rom_map_elem_t bhi160_module_globals_table[] = {
 	  MP_ROM_PTR(&mp_bhi160_read_sensor_obj) },
 	{ MP_ROM_QSTR(MP_QSTR_disable_sensor),
 	  MP_ROM_PTR(&mp_bhi160_disable_sensor_obj) },
+	{ MP_ROM_QSTR(MP_QSTR_disable_all_sensors),
+	  MP_ROM_PTR(&mp_bhi160_disable_all_sensors_obj) },
 };
 STATIC MP_DEFINE_CONST_DICT(bhi160_module_globals, bhi160_module_globals_table);
 
diff --git a/pycardium/modules/light_sensor.c b/pycardium/modules/light_sensor.c
index ce8343aa72c0a0a5e02ee28cba9b764dcad8ef7d..a27791de1a98fef59e6964631eca81ff932e222f 100644
--- a/pycardium/modules/light_sensor.c
+++ b/pycardium/modules/light_sensor.c
@@ -42,11 +42,18 @@ static mp_obj_t mp_light_sensor_stop()
 }
 static MP_DEFINE_CONST_FUN_OBJ_0(light_sensor_stop_obj, mp_light_sensor_stop);
 
+static mp_obj_t mp_light_sensor_read()
+{
+	return mp_obj_new_int_from_uint(epic_light_sensor_read());
+}
+static MP_DEFINE_CONST_FUN_OBJ_0(light_sensor_read_obj, mp_light_sensor_read);
+
 static const mp_rom_map_elem_t light_sensor_module_globals_table[] = {
 	{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_light_sensor) },
 	{ MP_ROM_QSTR(MP_QSTR_start), MP_ROM_PTR(&light_sensor_start_obj) },
 	{ MP_ROM_QSTR(MP_QSTR_stop), MP_ROM_PTR(&light_sensor_stop_obj) },
-	{ MP_ROM_QSTR(MP_QSTR_get_reading), MP_ROM_PTR(&light_sensor_get_obj) }
+	{ MP_ROM_QSTR(MP_QSTR_get_reading), MP_ROM_PTR(&light_sensor_get_obj) },
+	{ MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&light_sensor_read_obj) },
 };
 static MP_DEFINE_CONST_DICT(
 	light_sensor_module_globals, light_sensor_module_globals_table
diff --git a/pycardium/modules/py/bhi160.py b/pycardium/modules/py/bhi160.py
index f95874e544c6456ebc4712653ce05b053c5f97dc..5d28e1e61dfbb913c3ee4dfdc00064fc4bb1342a 100644
--- a/pycardium/modules/py/bhi160.py
+++ b/pycardium/modules/py/bhi160.py
@@ -5,6 +5,15 @@ import ucollections
 DataVector = ucollections.namedtuple("DataVector", ["x", "y", "z", "status"])
 
 
+def disable_all_sensors():
+    """
+    Disable all sensor also if they are already deactivated.
+
+    :returns: None
+    """
+    sys_bhi160.disable_all_sensors()
+
+
 class BHI160:
     def enable_sensor(self):
         interrupt.disable_callback(self.interrupt_id)
diff --git a/pycardium/modules/py/display.py b/pycardium/modules/py/display.py
index e957977d6fc3603889f3155e515d351dc8f1753b..7b51cef374f8006b550ca955230a5ef9f3652d6c 100644
--- a/pycardium/modules/py/display.py
+++ b/pycardium/modules/py/display.py
@@ -1,6 +1,13 @@
 import sys_display
 import color
 
+# font enumeration
+FONT8 = 0
+FONT12 = 1
+FONT16 = 2
+FONT20 = 3
+FONT24 = 4
+
 
 class Display:
     """
@@ -61,7 +68,7 @@ class Display:
         sys_display.clear(col)
         return self
 
-    def print(self, text, *, fg=None, bg=None, posx=0, posy=0):
+    def print(self, text, *, fg=None, bg=None, posx=0, posy=0, font=FONT20):
         """
         Prints a string on the display. Font size is locked to 20px
 
@@ -70,11 +77,29 @@ class Display:
         :param bg: Background color (expects RGB triple)
         :param posx: X-Position of the first character, 0 <= posx <= 160
         :param posy: Y-Position of the first character, 0 <= posy <= 80
+        :param font: 0 <= font <= 4 (currently) selects a font
+
+        Avaiable Fonts:
+
+         - :py:data:`display.FONT8`
+         - :py:data:`display.FONT12`
+         - :py:data:`display.FONT16`
+         - :py:data:`display.FONT20`
+         - :py:data:`display.FONT24`
+
+         **Example:**
+
+        .. code-block:: python
+
+            with display.open() as d:
+                d.clear()
+                d.print('Hello Earth!', font=display.FONT24)
+                d.update()
         """
         fg = fg or color.WHITE
         bg = bg or color.BLACK
 
-        sys_display.print(text, posx, posy, fg, bg)
+        sys_display.print_adv(text, posx, posy, fg, bg, font)
         return self
 
     def pixel(self, x, y, *, col=None):
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()
 
diff --git a/pycardium/modules/qstrdefs.h b/pycardium/modules/qstrdefs.h
index a75d3ee2b01ada3648ad273178ca14149f0644f5..a3a873cbdc01b9f7fd295ed72aa4159f338ccada 100644
--- a/pycardium/modules/qstrdefs.h
+++ b/pycardium/modules/qstrdefs.h
@@ -67,6 +67,7 @@ Q(RTC_ALARM)
 Q(sys_bhi160)
 Q(enable_sensor)
 Q(disable_sensor)
+Q(disable_all_sensors)
 Q(read_sensor)
 Q(x)
 Q(y)
@@ -85,6 +86,7 @@ Q(read_thermistor_voltage)
 Q(sys_display)
 Q(display)
 Q(print)
+Q(print_adv)
 Q(pixel)
 Q(backlight)
 Q(line)
@@ -97,6 +99,7 @@ Q(light_sensor)
 Q(start)
 Q(get_reading)
 Q(stop)
+Q(read)
 
 /* bme680 */
 Q(bme680)
diff --git a/pycardium/modules/sys_display.c b/pycardium/modules/sys_display.c
index f5523d54831695679965ddcb76e7df8c6fa2ba29..4f92ab92dcc2cfc8cede58018c8778fe9e345918 100644
--- a/pycardium/modules/sys_display.c
+++ b/pycardium/modules/sys_display.c
@@ -53,6 +53,30 @@ static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(
 	display_print_obj, 5, 5, mp_display_print
 );
 
+/* print something on the display */
+static mp_obj_t mp_display_print_adv(size_t n_args, const mp_obj_t *args)
+{
+	if (!mp_obj_is_str_or_bytes(args[0])) {
+		mp_raise_TypeError("input text must be a string");
+	}
+	GET_STR_DATA_LEN(args[0], print, print_len);
+	uint32_t posx    = mp_obj_get_int(args[1]);
+	uint32_t posy    = mp_obj_get_int(args[2]);
+	uint32_t fg      = get_color(args[3]);
+	uint32_t bg      = get_color(args[4]);
+	uint8_t fontName = mp_obj_get_int(args[5]);
+	int res          = epic_disp_print_adv(
+                fontName, posx, posy, (const char *)print, fg, bg
+	);
+	if (res < 0) {
+		mp_raise_OSError(-res);
+	}
+	return mp_const_none;
+}
+static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(
+	display_print_adv_obj, 6, 6, mp_display_print_adv
+);
+
 /* draw pixel on the display */
 static mp_obj_t mp_display_pixel(size_t n_args, const mp_obj_t *args)
 {
@@ -234,6 +258,7 @@ static const mp_rom_map_elem_t display_module_globals_table[] = {
 	{ MP_ROM_QSTR(MP_QSTR_open), MP_ROM_PTR(&display_open_obj) },
 	{ MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&display_close_obj) },
 	{ MP_ROM_QSTR(MP_QSTR_print), MP_ROM_PTR(&display_print_obj) },
+	{ MP_ROM_QSTR(MP_QSTR_print_adv), MP_ROM_PTR(&display_print_adv_obj) },
 	{ MP_ROM_QSTR(MP_QSTR_pixel), MP_ROM_PTR(&display_pixel_obj) },
 	{ MP_ROM_QSTR(MP_QSTR_backlight), MP_ROM_PTR(&display_backlight_obj) },
 	{ MP_ROM_QSTR(MP_QSTR_line), MP_ROM_PTR(&display_line_obj) },
diff --git a/shell.nix b/shell.nix
new file mode 100644
index 0000000000000000000000000000000000000000..cdb5f3d5a787cdb4b16904da1fb1d442bc7bb3b8
--- /dev/null
+++ b/shell.nix
@@ -0,0 +1,60 @@
+# This nix expression describes the build and debug environment for the
+# firmware. To use it, first install nix and then invoke the "nix-shell"
+# command to activate the environment. It will install all dependencies for and
+# build and install a patched openocd, before dropping you into a shell where
+# everything required for compiling/debugging the firmware is available.
+
+{ pkgs ? import <nixpkgs> {} }:
+
+let
+  crc16 = { stdenv, buildPythonPackage, fetchPypi }:
+  buildPythonPackage rec {
+    pname = "crc16";
+    version = "0.1.1";
+    src = fetchPypi {
+      inherit pname version;
+      sha256 = "15nkx0pa4lskwin84flpk8fsw3jqg6wic6v3s83syjqg76h6my61";
+      };
+  };
+
+  ocd = pkgs.openocd.overrideAttrs(old: {
+    name = "openocd-cardio";
+
+    src = pkgs.fetchgit {
+      url = "https://git.card10.badge.events.ccc.de/card10/openocd.git";
+      rev = "90d828185cea44b29cffb40f2c8aea19282b9130";
+      sha256 = "092wg19kjapv9s70b23ckd4j5i8ykk3d7mcl4h8cgl2acwcw8myr";
+      fetchSubmodules = true;
+    };
+
+    nativeBuildInputs = old.nativeBuildInputs ++ [
+      pkgs.which
+      pkgs.libtool
+      pkgs.autoconf
+      pkgs.automake
+    ];
+
+    SKIP_SUBMODULE="1";
+
+    preConfigure = ''
+      ./bootstrap
+    '';
+  });
+
+in pkgs.mkShell {
+
+  buildInputs = [
+    pkgs.meson
+    pkgs.ninja
+    pkgs.git
+    pkgs.gcc-arm-embedded
+    pkgs.jq
+    (pkgs.python3.withPackages (ps: [
+      ps.pillow
+      (ps.callPackage crc16 {})
+    ]))
+    ocd
+    pkgs.gcc-arm-embedded
+  ];
+
+}
diff --git a/tools/ls_cmsis_dap/Makefile b/tools/ls_cmsis_dap/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..3a1fefe7fa6953d733c704fad77a47a0d34d8449
--- /dev/null
+++ b/tools/ls_cmsis_dap/Makefile
@@ -0,0 +1,16 @@
+.PHONY: all clean
+
+all: ls_cmsis_dap-hidraw ls_cmsis_dap-libusb
+
+clean:
+	-rm *.o ls_cmsis_dap-hidraw ls_cmsis_dap-libusb
+
+ls_cmsis_dap.o: ls_cmsis_dap.c
+
+ls_cmsis_dap-hidraw: LDFLAGS=-lhidapi-hidraw
+ls_cmsis_dap-hidraw: ls_cmsis_dap.o
+	$(CC) $(LDFLAGS) -o $@ $<
+
+ls_cmsis_dap-libusb: LDFLAGS=-lhidapi-libusb
+ls_cmsis_dap-libusb: ls_cmsis_dap.o
+	$(CC) $(LDFLAGS) -o $@ $<
diff --git a/tools/ls_cmsis_dap/ls_cmsis_dap.c b/tools/ls_cmsis_dap/ls_cmsis_dap.c
new file mode 100644
index 0000000000000000000000000000000000000000..bd39674f12a5b3174a220fc67f4d96c78a6efc23
--- /dev/null
+++ b/tools/ls_cmsis_dap/ls_cmsis_dap.c
@@ -0,0 +1,31 @@
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <hidapi/hidapi.h>
+
+int main(int argc, char *argv[])
+{
+	int rc = 0;
+	if ((rc = hid_init())) {
+		fprintf(stderr, "hid_init: %d\n", rc);
+		goto done;
+	}
+
+	struct hid_device_info *hid_devs = hid_enumerate(0x0d28, 0x0204);
+	if (!hid_devs) {
+		fprintf(stderr, "hid_enumerate: NULL\n");
+		rc = 1;
+		goto done;
+	}
+
+	for (struct hid_device_info *dev = hid_devs; dev; dev = dev->next) {
+		fprintf(stdout, "%s\n", dev->path);
+	}
+
+done:
+	if (hid_devs) {
+		hid_free_enumeration(hid_devs);
+	}
+	hid_exit();
+	return -1;
+}
diff --git a/tools/make-release.sh b/tools/make-release.sh
new file mode 100755
index 0000000000000000000000000000000000000000..09946b6543c6371ac5688410cb7221bcb5f082c7
--- /dev/null
+++ b/tools/make-release.sh
@@ -0,0 +1,85 @@
+#!/usr/bin/env bash
+set -e
+
+cd "$(dirname "$0")/.."
+
+# Script to aid in creating a new card10 release
+args=$(
+    getopt -l "version:,name:,help" -o "v:n:h" -- "$@"
+)
+eval set -- $args
+
+while [ $# -ge 1 ]; do
+    case "$1" in
+        -n|--name)
+            name="$2"
+            shift
+            ;;
+        -v|--version)
+            version="$2"
+            shift
+            ;;
+        -h|--help)
+            echo "usage: $0 --name <release-name> --version x.x" >&2
+            exit 0
+            ;;
+    esac
+    shift
+done
+
+[[ "$name" == "" ]] && { echo "name must be set" >&2; exit 1; }
+[[ "$version" == "" ]] && { echo "version must be set" >&2; exit 1; }
+
+# add a leading v to the version
+if [[ "$version" =~ ^[^v].*$ ]]; then
+    version="v$version"
+fi
+
+message() {
+    echo "$(tput bold)>>> $(tput sgr0)$(tput setaf 6)$*$(tput sgr0)"
+}
+
+release_name="card10-${version}-${name}"
+
+message "Building release \"$release_name\" ..."
+
+git_version="$(git describe --always --dirty)"
+if [[ "$git_version" != "$version" ]]; then
+    echo "Git says version is \"$git_version\" instead of \"$version\"!" >&2
+    exit 1
+fi
+
+release_dir="release-$version"
+mkdir "$release_dir"
+
+message "Building (non-jailbreak) release version ..."
+./bootstrap.sh
+ninja -C build/
+
+message "Creating (non-jailbreak) release archive ..."
+mkdir "$release_dir/$release_name"
+cp -r -t "$release_dir/$release_name" preload/*
+cp build/pycardium/pycardium_epicardium.bin "$release_dir/$release_name/card10.bin"
+( cd "$release_dir"; zip -r "$release_name"{.zip,}; )
+
+# Copy ELFs
+mkdir "$release_dir/elfs"
+cp -t "$release_dir/elfs" build/epicardium/epicardium.elf build/pycardium/pycardium.elf build/bootloader/bootloader.elf
+
+message "Building (jailbreak) release version ..."
+./bootstrap.sh -Djailbreak_card10=true
+ninja -C build/
+
+message "Creating (jailbreak) release archive ..."
+mkdir "$release_dir/$release_name-jailbreak"
+cp -r -t "$release_dir/$release_name-jailbreak" preload/*
+cp build/pycardium/pycardium_epicardium.bin "$release_dir/$release_name-jailbreak/card10.bin"
+( cd "$release_dir"; zip -r "$release_name-jailbreak"{.zip,}; )
+
+# Copy ELFs
+mkdir "$release_dir/elfs-jailbreak"
+cp -t "$release_dir/elfs-jailbreak" build/epicardium/epicardium.elf build/pycardium/pycardium.elf
+
+message "Done!"
+echo "Archive (non-jailbreak): $release_dir/$release_name.zip"
+echo "Archive (jailbreak):     $release_dir/$release_name-jailbreak.zip"
diff --git a/tools/pyboard.py b/tools/pyboard.py
new file mode 100755
index 0000000000000000000000000000000000000000..148e07d9cf48706c6e7cb6f175a42a32722e49b9
--- /dev/null
+++ b/tools/pyboard.py
@@ -0,0 +1,537 @@
+#!/usr/bin/env python
+#
+# This file is part of the MicroPython project, http://micropython.org/
+#
+# The MIT License (MIT)
+#
+# Copyright (c) 2014-2016 Damien P. George
+# Copyright (c) 2017 Paul Sokolovsky
+#
+# 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.
+
+"""
+pyboard interface
+
+This module provides the Pyboard class, used to communicate with and
+control a MicroPython device over a communication channel. Both real
+boards and emulated devices (e.g. running in QEMU) are supported.
+Various communication channels are supported, including a serial
+connection, telnet-style network connection, external process
+connection.
+
+Example usage:
+
+    import pyboard
+    pyb = pyboard.Pyboard('/dev/ttyACM0')
+
+Or:
+
+    pyb = pyboard.Pyboard('192.168.1.1')
+
+Then:
+
+    pyb.enter_raw_repl()
+    pyb.exec('import pyb')
+    pyb.exec('pyb.LED(1).on()')
+    pyb.exit_raw_repl()
+
+Note: if using Python2 then pyb.exec must be written as pyb.exec_.
+To run a script from the local machine on the board and print out the results:
+
+    import pyboard
+    pyboard.execfile('test.py', device='/dev/ttyACM0')
+
+This script can also be run directly.  To execute a local script, use:
+
+    ./pyboard.py test.py
+
+Or:
+
+    python pyboard.py test.py
+
+"""
+
+import sys
+import time
+import os
+
+try:
+    stdout = sys.stdout.buffer
+except AttributeError:
+    # Python2 doesn't have buffer attr
+    stdout = sys.stdout
+
+
+def stdout_write_bytes(b):
+    b = b.replace(b"\x04", b"")
+    stdout.write(b)
+    stdout.flush()
+
+
+class PyboardError(Exception):
+    pass
+
+
+class TelnetToSerial:
+    def __init__(self, ip, user, password, read_timeout=None):
+        self.tn = None
+        import telnetlib
+
+        self.tn = telnetlib.Telnet(ip, timeout=15)
+        self.read_timeout = read_timeout
+        if b"Login as:" in self.tn.read_until(b"Login as:", timeout=read_timeout):
+            self.tn.write(bytes(user, "ascii") + b"\r\n")
+
+            if b"Password:" in self.tn.read_until(b"Password:", timeout=read_timeout):
+                # needed because of internal implementation details of the telnet server
+                time.sleep(0.2)
+                self.tn.write(bytes(password, "ascii") + b"\r\n")
+
+                if b"for more information." in self.tn.read_until(
+                    b'Type "help()" for more information.', timeout=read_timeout
+                ):
+                    # login successful
+                    from collections import deque
+
+                    self.fifo = deque()
+                    return
+
+        raise PyboardError("Failed to establish a telnet connection with the board")
+
+    def __del__(self):
+        self.close()
+
+    def close(self):
+        if self.tn:
+            self.tn.close()
+
+    def read(self, size=1):
+        while len(self.fifo) < size:
+            timeout_count = 0
+            data = self.tn.read_eager()
+            if len(data):
+                self.fifo.extend(data)
+                timeout_count = 0
+            else:
+                time.sleep(0.25)
+                if (
+                    self.read_timeout is not None
+                    and timeout_count > 4 * self.read_timeout
+                ):
+                    break
+                timeout_count += 1
+
+        data = b""
+        while len(data) < size and len(self.fifo) > 0:
+            data += bytes([self.fifo.popleft()])
+        return data
+
+    def write(self, data):
+        self.tn.write(data)
+        return len(data)
+
+    def inWaiting(self):
+        n_waiting = len(self.fifo)
+        if not n_waiting:
+            data = self.tn.read_eager()
+            self.fifo.extend(data)
+            return len(data)
+        else:
+            return n_waiting
+
+
+class ProcessToSerial:
+    "Execute a process and emulate serial connection using its stdin/stdout."
+
+    def __init__(self, cmd):
+        import subprocess
+
+        self.subp = subprocess.Popen(
+            cmd,
+            bufsize=0,
+            shell=True,
+            preexec_fn=os.setsid,
+            stdin=subprocess.PIPE,
+            stdout=subprocess.PIPE,
+        )
+
+        # Initially was implemented with selectors, but that adds Python3
+        # dependency. However, there can be race conditions communicating
+        # with a particular child process (like QEMU), and selectors may
+        # still work better in that case, so left inplace for now.
+        #
+        # import selectors
+        # self.sel = selectors.DefaultSelector()
+        # self.sel.register(self.subp.stdout, selectors.EVENT_READ)
+
+        import select
+
+        self.poll = select.poll()
+        self.poll.register(self.subp.stdout.fileno())
+
+    def close(self):
+        import signal
+
+        os.killpg(os.getpgid(self.subp.pid), signal.SIGTERM)
+
+    def read(self, size=1):
+        data = b""
+        while len(data) < size:
+            data += self.subp.stdout.read(size - len(data))
+        return data
+
+    def write(self, data):
+        self.subp.stdin.write(data)
+        return len(data)
+
+    def inWaiting(self):
+        # res = self.sel.select(0)
+        res = self.poll.poll(0)
+        if res:
+            return 1
+        return 0
+
+
+class ProcessPtyToTerminal:
+    """Execute a process which creates a PTY and prints slave PTY as
+    first line of its output, and emulate serial connection using
+    this PTY."""
+
+    def __init__(self, cmd):
+        import subprocess
+        import re
+        import serial
+
+        self.subp = subprocess.Popen(
+            cmd.split(),
+            bufsize=0,
+            shell=False,
+            preexec_fn=os.setsid,
+            stdin=subprocess.PIPE,
+            stdout=subprocess.PIPE,
+            stderr=subprocess.PIPE,
+        )
+        pty_line = self.subp.stderr.readline().decode("utf-8")
+        m = re.search(r"/dev/pts/[0-9]+", pty_line)
+        if not m:
+            print("Error: unable to find PTY device in startup line:", pty_line)
+            self.close()
+            sys.exit(1)
+        pty = m.group()
+        # rtscts, dsrdtr params are to workaround pyserial bug:
+        # http://stackoverflow.com/questions/34831131/pyserial-does-not-play-well-with-virtual-port
+        self.ser = serial.Serial(pty, interCharTimeout=1, rtscts=True, dsrdtr=True)
+
+    def close(self):
+        import signal
+
+        os.killpg(os.getpgid(self.subp.pid), signal.SIGTERM)
+
+    def read(self, size=1):
+        return self.ser.read(size)
+
+    def write(self, data):
+        return self.ser.write(data)
+
+    def inWaiting(self):
+        return self.ser.inWaiting()
+
+
+class Pyboard:
+    def __init__(
+        self, device, baudrate=115200, user="micro", password="python", wait=0
+    ):
+        if device.startswith("exec:"):
+            self.serial = ProcessToSerial(device[len("exec:") :])
+        elif device.startswith("execpty:"):
+            self.serial = ProcessPtyToTerminal(device[len("qemupty:") :])
+        elif (
+            device
+            and device[0].isdigit()
+            and device[-1].isdigit()
+            and device.count(".") == 3
+        ):
+            # device looks like an IP address
+            self.serial = TelnetToSerial(device, user, password, read_timeout=10)
+        else:
+            import serial
+
+            delayed = False
+            for attempt in range(wait + 1):
+                try:
+                    self.serial = serial.Serial(
+                        device, baudrate=baudrate, interCharTimeout=1
+                    )
+                    break
+                except (OSError, IOError):  # Py2 and Py3 have different errors
+                    if wait == 0:
+                        continue
+                    if attempt == 0:
+                        sys.stdout.write("Waiting {} seconds for pyboard ".format(wait))
+                        delayed = True
+                time.sleep(1)
+                sys.stdout.write(".")
+                sys.stdout.flush()
+            else:
+                if delayed:
+                    print("")
+                raise PyboardError("failed to access " + device)
+            if delayed:
+                print("")
+
+    def close(self):
+        self.serial.close()
+
+    def read_until(self, min_num_bytes, ending, timeout=10, data_consumer=None):
+        # if data_consumer is used then data is not accumulated and the ending must be 1 byte long
+        assert data_consumer is None or len(ending) == 1
+
+        data = self.serial.read(min_num_bytes)
+        if data_consumer:
+            data_consumer(data)
+        timeout_count = 0
+        while True:
+            if data.endswith(ending):
+                break
+            elif self.serial.inWaiting() > 0:
+                new_data = self.serial.read(1)
+                if data_consumer:
+                    data_consumer(new_data)
+                    data = new_data
+                else:
+                    data = data + new_data
+                timeout_count = 0
+            else:
+                timeout_count += 1
+                if timeout is not None and timeout_count >= 100 * timeout:
+                    break
+                time.sleep(0.01)
+        return data
+
+    def enter_raw_repl(self):
+        self.serial.write(b"\r\x03\x03")  # ctrl-C twice: interrupt any running program
+
+        # flush input (without relying on serial.flushInput())
+        n = self.serial.inWaiting()
+        while n > 0:
+            self.serial.read(n)
+            n = self.serial.inWaiting()
+
+        self.serial.write(b"\r\x01")  # ctrl-A: enter raw REPL
+        data = self.read_until(1, b"raw REPL; CTRL-B to exit\r\n>")
+        if not data.endswith(b"raw REPL; CTRL-B to exit\r\n>"):
+            print(data)
+            raise PyboardError("could not enter raw repl")
+
+        self.serial.write(b"\x04")  # ctrl-D: soft reset
+        data = self.read_until(1, b"soft reboot\r\n")
+        if not data.endswith(b"soft reboot\r\n"):
+            print(data)
+            raise PyboardError("could not enter raw repl")
+        # By splitting this into 2 reads, it allows boot.py to print stuff,
+        # which will show up after the soft reboot and before the raw REPL.
+        data = self.read_until(1, b"raw REPL; CTRL-B to exit\r\n")
+        if not data.endswith(b"raw REPL; CTRL-B to exit\r\n"):
+            print(data)
+            raise PyboardError("could not enter raw repl")
+
+    def exit_raw_repl(self):
+        self.serial.write(b"\r\x02")  # ctrl-B: enter friendly REPL
+
+    def follow(self, timeout, data_consumer=None):
+        # wait for normal output
+        data = self.read_until(1, b"\x04", timeout=timeout, data_consumer=data_consumer)
+        if not data.endswith(b"\x04"):
+            raise PyboardError("timeout waiting for first EOF reception")
+        data = data[:-1]
+
+        # wait for error output
+        data_err = self.read_until(1, b"\x04", timeout=timeout)
+        if not data_err.endswith(b"\x04"):
+            raise PyboardError("timeout waiting for second EOF reception")
+        data_err = data_err[:-1]
+
+        # return normal and error output
+        return data, data_err
+
+    def exec_raw_no_follow(self, command):
+        if isinstance(command, bytes):
+            command_bytes = command
+        else:
+            command_bytes = bytes(command, encoding="utf8")
+
+        # check we have a prompt
+        data = self.read_until(1, b">")
+        if not data.endswith(b">"):
+            raise PyboardError("could not enter raw repl")
+
+        # write command
+        for i in range(0, len(command_bytes), 256):
+            self.serial.write(command_bytes[i : min(i + 256, len(command_bytes))])
+            time.sleep(0.01)
+        self.serial.write(b"\x04")
+
+        # check if we could exec command
+        data = self.serial.read(2)
+        if data != b"OK":
+            raise PyboardError("could not exec command (response: %r)" % data)
+
+    def exec_raw(self, command, timeout=10, data_consumer=None):
+        self.exec_raw_no_follow(command)
+        return self.follow(timeout, data_consumer)
+
+    def eval(self, expression):
+        ret = self.exec_("print({})".format(expression))
+        ret = ret.strip()
+        return ret
+
+    def exec_(self, command):
+        ret, ret_err = self.exec_raw(command)
+        if ret_err:
+            raise PyboardError("exception", ret, ret_err)
+        return ret
+
+    def execfile(self, filename):
+        with open(filename, "rb") as f:
+            pyfile = f.read()
+        return self.exec_(pyfile)
+
+    def get_time(self):
+        t = str(self.eval("pyb.RTC().datetime()"), encoding="utf8")[1:-1].split(", ")
+        return int(t[4]) * 3600 + int(t[5]) * 60 + int(t[6])
+
+
+# in Python2 exec is a keyword so one must use "exec_"
+# but for Python3 we want to provide the nicer version "exec"
+setattr(Pyboard, "exec", Pyboard.exec_)
+
+
+def execfile(
+    filename, device="/dev/ttyACM0", baudrate=115200, user="micro", password="python"
+):
+    pyb = Pyboard(device, baudrate, user, password)
+    pyb.enter_raw_repl()
+    output = pyb.execfile(filename)
+    stdout_write_bytes(output)
+    pyb.exit_raw_repl()
+    pyb.close()
+
+
+def main():
+    import argparse
+
+    cmd_parser = argparse.ArgumentParser(description="Run scripts on the pyboard.")
+    cmd_parser.add_argument(
+        "--device",
+        default="/dev/ttyACM0",
+        help="the serial device or the IP address of the pyboard",
+    )
+    cmd_parser.add_argument(
+        "-b", "--baudrate", default=115200, help="the baud rate of the serial device"
+    )
+    cmd_parser.add_argument(
+        "-u", "--user", default="micro", help="the telnet login username"
+    )
+    cmd_parser.add_argument(
+        "-p", "--password", default="python", help="the telnet login password"
+    )
+    cmd_parser.add_argument("-c", "--command", help="program passed in as string")
+    cmd_parser.add_argument(
+        "-w",
+        "--wait",
+        default=0,
+        type=int,
+        help="seconds to wait for USB connected board to become available",
+    )
+    cmd_parser.add_argument(
+        "--follow",
+        action="store_true",
+        help="follow the output after running the scripts [default if no scripts given]",
+    )
+    cmd_parser.add_argument("files", nargs="*", help="input files")
+    args = cmd_parser.parse_args()
+
+    # open the connection to the pyboard
+    try:
+        pyb = Pyboard(args.device, args.baudrate, args.user, args.password, args.wait)
+    except PyboardError as er:
+        print(er)
+        sys.exit(1)
+
+    # run any command or file(s)
+    if args.command is not None or len(args.files):
+        # we must enter raw-REPL mode to execute commands
+        # this will do a soft-reset of the board
+        try:
+            pyb.enter_raw_repl()
+        except PyboardError as er:
+            print(er)
+            pyb.close()
+            sys.exit(1)
+
+        def execbuffer(buf):
+            try:
+                ret, ret_err = pyb.exec_raw(
+                    buf, timeout=None, data_consumer=stdout_write_bytes
+                )
+            except PyboardError as er:
+                print(er)
+                pyb.close()
+                sys.exit(1)
+            except KeyboardInterrupt:
+                sys.exit(1)
+            if ret_err:
+                pyb.exit_raw_repl()
+                pyb.close()
+                stdout_write_bytes(ret_err)
+                sys.exit(1)
+
+        # run the command, if given
+        if args.command is not None:
+            execbuffer(args.command.encode("utf-8"))
+
+        # run any files
+        for filename in args.files:
+            with open(filename, "rb") as f:
+                pyfile = f.read()
+                execbuffer(pyfile)
+
+        # exiting raw-REPL just drops to friendly-REPL mode
+        pyb.exit_raw_repl()
+
+    # if asked explicitly, or no files given, then follow the output
+    if args.follow or (args.command is None and len(args.files) == 0):
+        try:
+            ret, ret_err = pyb.follow(timeout=None, data_consumer=stdout_write_bytes)
+        except PyboardError as er:
+            print(er)
+            sys.exit(1)
+        except KeyboardInterrupt:
+            sys.exit(1)
+        if ret_err:
+            pyb.close()
+            stdout_write_bytes(ret_err)
+            sys.exit(1)
+
+    # close the connection to the pyboard
+    pyb.close()
+
+
+if __name__ == "__main__":
+    main()
diff --git a/tools/pycard10.py b/tools/pycard10.py
new file mode 100755
index 0000000000000000000000000000000000000000..d5b51e7ed0572f62088201e075b6d4f5dc3d5d05
--- /dev/null
+++ b/tools/pycard10.py
@@ -0,0 +1,303 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# This file is part of the card10 project card10.badge.events.ccc.de
+#
+# The MIT License (MIT)
+#
+# Copyright (c) 2019 Alexander Böhm
+#
+# 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.
+
+"""
+card10 interface
+
+This module provides is an extenstion of the Pyboard class, used to
+communicate with the card10 badge over the serial connection via USB.
+
+For description of the card10 board look at the project website <https://card10.badge.events.ccc.de>.
+
+Python2 isn't supported.
+
+Example usage:
+
+    import pycard10
+    card10 = pycard10.PyCard10('/dev/ttyACM0')
+
+Then:
+
+    card10.enter_raw_repl()
+    card10.exec('import leds')
+    card10.exec('leds.set_rocket(0, 31)')
+    card10.exit_raw_repl()
+
+    import pycard10
+    pycard10.execfile('test.py', device='/dev/ttyACM0')
+
+This script can also be run directly.  To execute a local script, use:
+
+    tools/pycard10.py test.py
+
+Or:
+    python3 tools/pycard10.py test.py
+
+"""
+
+__author__ = "Alexander Böhm"
+__copyright__ = "Copyright 2019, Alexander Böhm"
+__license__ = "MIT"
+__email__ = "alexander.boehm@malbolge.net"
+
+import sys
+import time
+import os
+from pyboard import (
+    stdout_write_bytes,
+    PyboardError,
+    Pyboard,
+    ProcessPtyToTerminal,
+    ProcessToSerial,
+    TelnetToSerial,
+)
+
+
+class PyCard10(Pyboard):
+    """
+    Python card10 connector.
+    """
+
+    def __init__(self, device, wait=0):
+        """
+        Open a connection to the card10 over the serial device *device*.
+        """
+        Pyboard.__init__(
+            self, device=device, baudrate=115200, user=None, password=None, wait=wait
+        )
+
+    def exec_raw_no_follow(self, command):
+        """
+        Execute a the command 'command' on the card10.
+
+        Parameters:
+        command (bytes): Command or multiple commands
+
+        Returns:
+        None
+        """
+
+        if isinstance(command, bytes):
+            command_bytes = command
+        else:
+            command_bytes = bytes(command, encoding="utf8")
+
+        data = self.read_until(1, b">")
+        if not data.endswith(b">"):
+            raise PyboardError("card10 not in raw repl mode: (response: %r)" % (data))
+
+        # write command
+        for i in range(0, len(command_bytes), 256):
+            self.serial.write(command_bytes[i : min(i + 256, len(command_bytes))])
+            time.sleep(0.01)
+
+        self.serial.write(b"\x04")
+
+        # check if we could exec command
+        data = self.serial.read(2)
+        if data != b"OK":
+            raise PyboardError("could not exec command (response: %r)" % data)
+
+    def enter_raw_repl(self):
+        """
+        Enter the RAW repl mode. After the prompt character ('>') left in the buffer of the serial line.
+
+        Returns:
+        None
+        """
+        self.serial.write(b"\x03\x03")  # ctrl-C twice: interrupt any running program
+
+        # flush input (without relying on serial.flushInput())
+        n = self.serial.inWaiting()
+        while n > 0:
+            self.serial.read(n)
+            n = self.serial.inWaiting()
+
+        self.serial.write(b"\x01")  # ctrl-A: enter raw REPL
+        data = self.read_until(1, b"raw REPL; CTRL-B to exit\r\n>")
+        if not data.endswith(b"raw REPL; CTRL-B to exit\r\n>"):
+            raise PyboardError("could not enter raw repl")
+
+        self.serial.write(b"\r\x04")  # execute nothing
+        data = self.serial.read(2)
+        if data != b"OK":
+            raise PyboardError("could not enter raw repl")
+
+    def exec(self, command):
+        """
+        Execute a command on the card10 and return the output of the card10.
+
+        Parameters:
+        command (bytes): Command or multiple commands
+
+        Returns:
+        data(bytes), data_err(bytes): data is standard out, data_err is standard error
+        """
+        return self.exec_(command)
+
+    def soft_reset(self):
+        """
+        Doing a soft reset on the board and going to menu.
+
+        Returns:
+        None
+        """
+
+        self.serial.write(b"\x03\x03")  # ctrl-C twice: interrupt any running program
+
+        self.serial.write(b"\x01")  # ctrl-B: ensue it's the normal mode
+
+        self.serial.write(b"\x04")  # ctrl-D: do the reset
+
+        n = self.serial.inWaiting()
+        while n > 0:
+            self.serial.read(n)
+            n = self.serial.inWaiting()
+
+
+def execfile(filename, device="/dev/ttyACM0"):
+    """
+    Execute python source from *filename* via the RAW repl mode on the card10 connected via serial line *device*.
+
+    Parameters:
+    filename(str): Path text file with commands
+    device(str): Path to the card10 device.
+
+    Returns:
+    None
+    """
+    c = PyCard10(device)
+    c.enter_raw_repl()
+    output = c.execfile(filename)
+    stdout_write_bytes(output)
+    c.exit_raw_repl()
+    c.close()
+
+
+def main():
+    """
+    The main method.
+
+    Returns:
+    None
+    """
+
+    import argparse
+
+    cmd_parser = argparse.ArgumentParser(description="Run scripts on the card10.")
+    cmd_parser.add_argument(
+        "--device", default="/dev/ttyACM0", help="the serial device of the card10"
+    )
+    cmd_parser.add_argument("-c", "--command", help="program passed in as string")
+    cmd_parser.add_argument(
+        "-w",
+        "--wait",
+        default=0,
+        type=int,
+        help="seconds to wait for USB connected board to become available",
+    )
+    cmd_parser.add_argument(
+        "--follow",
+        action="store_true",
+        help="follow the output after running the scripts [default if no scripts given]",
+    )
+    cmd_parser.add_argument(
+        "--reset", action="store_true", help="Soft reseting the card10"
+    )
+    cmd_parser.add_argument("files", nargs="*", help="input files")
+    args = cmd_parser.parse_args()
+
+    # open the connection to the card10
+    try:
+        card10 = PyCard10(args.device, args.wait)
+    except PyboardError as er:
+        print(er)
+        sys.exit(1)
+
+    if args.reset:
+        card10.soft_reset()
+
+    elif args.command is not None or len(args.files):
+        # we must enter raw-REPL mode to execute commands
+        # this will do a soft-reset of the board
+        try:
+            card10.enter_raw_repl()
+        except PyboardError as er:
+            print(er)
+            card10.close()
+            sys.exit(1)
+
+        def execbuffer(buf):
+            try:
+                ret, ret_err = card10.exec_raw(
+                    buf, timeout=None, data_consumer=stdout_write_bytes
+                )
+            except PyboardError as er:
+                print(er)
+                card10.close()
+                sys.exit(1)
+            except KeyboardInterrupt:
+                sys.exit(1)
+            if ret_err:
+                card10.exit_raw_repl()
+                card10.close()
+                stdout_write_bytes(ret_err)
+                sys.exit(1)
+
+        # run the command, if given
+        if args.command is not None:
+            execbuffer(args.command.encode("utf-8"))
+
+        # run any files
+        for filename in args.files:
+            with open(filename, "rb") as f:
+                pyfile = f.read()
+                execbuffer(pyfile)
+
+        # exiting raw-REPL just drops to friendly-REPL mode
+        card10.exit_raw_repl()
+
+    # if asked explicitly, or no files given, then follow the output
+    elif args.follow or (args.command is None and len(args.files) == 0):
+        try:
+            ret, ret_err = card10.follow(timeout=None, data_consumer=stdout_write_bytes)
+        except PyboardError as er:
+            print(er)
+            sys.exit(1)
+        except KeyboardInterrupt:
+            sys.exit(1)
+        if ret_err:
+            card10.close()
+            stdout_write_bytes(ret_err)
+            sys.exit(1)
+
+    # close the connection to the card10
+    card10.close()
+
+
+if __name__ == "__main__":
+    main()