diff --git a/Makefile b/Makefile
index beb889e4e8702ef63f284dd65b82a898454d3f63..b589275757e2eaecf30215a1f3b37660176636d6 100644
--- a/Makefile
+++ b/Makefile
@@ -121,9 +121,11 @@ LDSCRIPT= $(STARTUPLD)/STM32F411xE.ld
 
 # C sources that can be compiled in ARM or THUMB mode depending on the global
 # setting.
-CSRC = $(ALLCSRC)          \
-         $(SRCDIR)/crc32.c \
-         $(SRCDIR)/main.c
+CSRC = $(ALLCSRC)            \
+         $(SRCDIR)/crc32.c   \
+         $(SRCDIR)/main.c    \
+	 $(SRCDIR)/serial.c  \
+	 $(SRCDIR)/usb.c
 
 # C++ sources that can be compiled in ARM or THUMB mode depending on the global
 # setting.
diff --git a/cfg/halconf.h b/cfg/halconf.h
index ddb46c0da9da2a2878d77bffe34dd217a7de5a22..fb8be9548eb031b5f212a48e5fb67f09de089276 100644
--- a/cfg/halconf.h
+++ b/cfg/halconf.h
@@ -142,14 +142,14 @@
  * @brief   Enables the SERIAL subsystem.
  */
 #if !defined(HAL_USE_SERIAL) || defined(__DOXYGEN__)
-#define HAL_USE_SERIAL                      TRUE
+#define HAL_USE_SERIAL                      FALSE
 #endif
 
 /**
  * @brief   Enables the SERIAL over USB subsystem.
  */
 #if !defined(HAL_USE_SERIAL_USB) || defined(__DOXYGEN__)
-#define HAL_USE_SERIAL_USB                  FALSE
+#define HAL_USE_SERIAL_USB                  TRUE
 #endif
 
 /**
@@ -184,7 +184,7 @@
  * @brief   Enables the USB subsystem.
  */
 #if !defined(HAL_USE_USB) || defined(__DOXYGEN__)
-#define HAL_USE_USB                         FALSE
+#define HAL_USE_USB                         TRUE
 #endif
 
 /**
@@ -412,7 +412,7 @@
  *          buffers.
  */
 #if !defined(SERIAL_BUFFERS_SIZE) || defined(__DOXYGEN__)
-#define SERIAL_BUFFERS_SIZE                 16
+#define SERIAL_BUFFERS_SIZE                 1
 #endif
 
 /*===========================================================================*/
@@ -447,7 +447,7 @@
  *          buffers.
  */
 #if !defined(SERIAL_USB_BUFFERS_SIZE) || defined(__DOXYGEN__)
-#define SERIAL_USB_BUFFERS_SIZE             256
+#define SERIAL_USB_BUFFERS_SIZE             512
 #endif
 
 /**
diff --git a/cfg/mcuconf.h b/cfg/mcuconf.h
index ffb098a57e645e0b0a359abaf55a1bdbcabcb9d1..2c3e198cb51f84d9aed3d68d86a91f4a6d8cfa76 100644
--- a/cfg/mcuconf.h
+++ b/cfg/mcuconf.h
@@ -39,23 +39,23 @@
  * HAL driver system settings.
  */
 #define STM32_NO_INIT                       FALSE
-#define STM32_HSI_ENABLED                   TRUE
-#define STM32_LSI_ENABLED                   TRUE
+#define STM32_HSI_ENABLED                   FALSE
+#define STM32_LSI_ENABLED                   FALSE
 #define STM32_HSE_ENABLED                   TRUE
 #define STM32_LSE_ENABLED                   FALSE
-#define STM32_CLOCK48_REQUIRED              FALSE
+#define STM32_CLOCK48_REQUIRED              TRUE
 #define STM32_SW                            STM32_SW_PLL
 #define STM32_PLLSRC                        STM32_PLLSRC_HSE
-#define STM32_PLLM_VALUE                    8
-#define STM32_PLLN_VALUE                    200
-#define STM32_PLLP_VALUE                    2
-#define STM32_PLLQ_VALUE                    7
+#define STM32_PLLM_VALUE                    4
+#define STM32_PLLN_VALUE                    192
+#define STM32_PLLP_VALUE                    4
+#define STM32_PLLQ_VALUE                    8
 #define STM32_HPRE                          STM32_HPRE_DIV1
 #define STM32_PPRE1                         STM32_PPRE1_DIV4
 #define STM32_PPRE2                         STM32_PPRE2_DIV2
 #define STM32_RTCSEL                        STM32_RTCSEL_LSI
 #define STM32_RTCPRE_VALUE                  8
-#define STM32_MCO1SEL                       STM32_MCO1SEL_HSI
+#define STM32_MCO1SEL                       STM32_MCO1SEL_PLL
 #define STM32_MCO1PRE                       STM32_MCO1PRE_DIV1
 #define STM32_MCO2SEL                       STM32_MCO2SEL_SYSCLK
 #define STM32_MCO2PRE                       STM32_MCO2PRE_DIV5
@@ -261,7 +261,7 @@
  * SERIAL driver system settings.
  */
 #define STM32_SERIAL_USE_USART1             FALSE
-#define STM32_SERIAL_USE_USART2             TRUE
+#define STM32_SERIAL_USE_USART2             FALSE
 #define STM32_SERIAL_USE_USART3             FALSE
 #define STM32_SERIAL_USE_UART4              FALSE
 #define STM32_SERIAL_USE_UART5              FALSE
@@ -325,7 +325,7 @@
 /*
  * USB driver system settings.
  */
-#define STM32_USB_USE_OTG1                  FALSE
+#define STM32_USB_USE_OTG1                  TRUE
 #define STM32_USB_USE_OTG2                  FALSE
 #define STM32_USB_OTG1_IRQ_PRIORITY         14
 #define STM32_USB_OTG2_IRQ_PRIORITY         14
diff --git a/src/serial.c b/src/serial.c
new file mode 100644
index 0000000000000000000000000000000000000000..a688a7c4e0cdef13e00defb3dd960075f98310af
--- /dev/null
+++ b/src/serial.c
@@ -0,0 +1,118 @@
+// This file is part of the execution-time evaluation for the qronos observer abstractions.
+// Copyright (C) 2022-2023  Tim Rheinfels  <tim.rheinfels@fau.de>
+// See https://gitlab.cs.fau.de/qronos-state-abstractions/execution-time
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+///
+///  @file  serial.c
+///
+///  @brief  Provides the implementation of the serial over USB utilities
+///
+///  @author  Tim Rheinfels  (tim.rheinfels@fau.de)
+///
+
+#include "serial.h"
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <ch.h>
+#include <hal.h>
+#include <chprintf.h>
+
+#include <crc32.h>
+#include <usb.h>
+
+///
+///  @brief  Buffer used for formatting output
+///
+static char serial_buffer[1024u];
+
+void serial_init(void)
+{
+
+}
+
+void serial_printf(const char *fmt, ...)
+{
+
+    va_list ap;
+    va_start(ap, fmt);
+    size_t n = chvsnprintf(serial_buffer, sizeof(serial_buffer), fmt, ap);
+    va_end(ap);
+
+    // Check for overruns
+    osalDbgAssert(n < sizeof(serial_buffer), "line buffer overrun");
+
+    // Add to checksum and print
+    for(size_t i = 0u; i < n; ++i)
+    {
+        crc32_add(serial_buffer[i]);
+    }
+
+    // Write to serial driver
+    size_t written = streamWrite((BaseSequentialStream *) &usb_serial_driver, (const uint8_t *) serial_buffer, n);
+    osalDbgAssert(n == written, "lost characters");
+
+}
+
+void serial_print_vector(const char *name, float *M, size_t n)
+{
+    serial_printf("\"%s\": [", name);
+    for(size_t i = 0u; i < n; ++i)
+    {
+        serial_printf("%u,\n", serial_encode(M[i]));
+    }
+    serial_printf("],\n");
+}
+
+void serial_print_matrix(const char *name, float *M, size_t n, size_t m)
+{
+    serial_printf("\"%s\": [", name);
+    for(size_t i = 0u; i < n; ++i)
+    {
+        serial_printf("[");
+        for(size_t j = 0u; j < m; ++j)
+        {
+            serial_printf("%u,\n", serial_encode(M[i*m+j]));
+        }
+        serial_printf("],");
+    }
+    serial_printf("],\n");
+}
+
+void serial_reset_checksum(void)
+{
+
+    crc32_reset();
+
+}
+
+void serial_print_checksum(void)
+{
+
+    uint32_t checksum = crc32_get();
+
+    chprintf((BaseSequentialStream *) &usb_serial_driver, "CRC32: 0x%08x\n", checksum);
+
+}
+
+uint32_t serial_encode(float f)
+{
+    uint32_t i;
+    memcpy(&i, &f, sizeof(i));
+    return i;
+}
diff --git a/src/serial.h b/src/serial.h
new file mode 100644
index 0000000000000000000000000000000000000000..55cd2e09adbd7f25bb61a2af244e485e8fab75fd
--- /dev/null
+++ b/src/serial.h
@@ -0,0 +1,83 @@
+// This file is part of the execution-time evaluation for the qronos observer abstractions.
+// Copyright (C) 2022-2023  Tim Rheinfels  <tim.rheinfels@fau.de>
+// See https://gitlab.cs.fau.de/qronos-state-abstractions/execution-time
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+///
+///  @file  serial.h
+///
+///  @brief  Provides the interface for the serial utilities
+///
+///  @author  Tim Rheinfels  (tim.rheinfels@fau.de)
+///
+
+#ifndef SERIAL_H
+#define SERIAL_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+///
+///  @brief  Initializes the serial over USB utilities
+///
+void serial_init(void);
+
+///
+///  @brief  Formats the string @p fmt using the variadic parameters and outputs the result via serial over USB
+///
+///  @param[in]  fmt  Format string
+///  @param[in]  ...  Parameters used for the format string @p fmt
+///
+void serial_printf(const char *fmt, ...);
+
+///
+///  @brief  Formats the vector @p M of size @p n named @p name and outputs the result via serial over USB
+///
+///  @param[in]  name  Name for the vector
+///  @param[in]  M     Vector data
+///  @param[in]  n     Number of rows
+///
+void serial_print_vector(const char *name, float *M, size_t n);
+
+///
+///  @brief  Formats the matrix @p M of size @p n by @p m named @p name and outputs the result via serial over USB
+///
+///  @param[in]  name  Name for the vector
+///  @param[in]  M     Vector data
+///  @param[in]  n     Number of rows
+///  @param[in]  m     Number of columns
+///
+void serial_print_matrix(const char *name, float *M, size_t n, size_t m);
+
+///
+///  @brief  Resets the serial checksum
+///
+void serial_reset_checksum(void);
+
+///
+///  @brief  Prints the accumulated serial checksum via serial over USB
+///
+void serial_print_checksum(void);
+
+///
+///  @brief  Encodes the single-precision float @p f as interger
+///
+///  @param[in]  f  Single-precision float to encode
+///
+///  @returns  32-bit unsigned representing the float @p f
+///
+uint32_t serial_encode(float f);
+
+#endif // SERIAL_H
diff --git a/src/usb.c b/src/usb.c
new file mode 100644
index 0000000000000000000000000000000000000000..27232e5152f18d0ecc49316d0dbdf78934d13ee3
--- /dev/null
+++ b/src/usb.c
@@ -0,0 +1,423 @@
+// This file is part of the execution-time evaluation for the qronos observer abstractions.
+// Copyright (C) 2022-2023  Tim Rheinfels  <tim.rheinfels@fau.de>
+// See https://gitlab.cs.fau.de/qronos-state-abstractions/execution-time
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+///
+///  @file  usb.c
+///
+///  @brief  Provides the implementation of the USB driver
+///
+///  @author  Tim Rheinfels  <tim.rheinfels@fau.de>
+///
+
+#include "usb.h"
+
+#include <ch.h>
+#include <hal.h>
+
+SerialUSBDriver usb_serial_driver;
+
+///
+///  @brief  Flag indicating that a client terminal is connected
+///
+static volatile bool active = false;
+
+// Set USB active flag when SET_CONTROL_LINE_STATE request occurs
+///
+///  @brief  USB request handler setting the @ref active flag when a SET_CONTROL_LINE_STATE request occurs or passes the request to @ref sduRequestsHook otherwise
+///
+///  @param[in]  usb  USB driver instance
+///
+///  @returns  Either
+///      - true if the USB request was handled or
+///      - false otherwise
+///
+static bool usb_request_handler(USBDriver *usb)
+{
+    osalSysLockFromISR();
+    uint8_t rtype = usb->setup[0u];
+    if(((rtype & USB_RTYPE_DIR_MASK) == USB_RTYPE_DIR_HOST2DEV)
+       && (((rtype & USB_RTYPE_TYPE_MASK) == USB_RTYPE_TYPE_CLASS))
+       && (((rtype & USB_RTYPE_RECIPIENT_MASK) == USB_RTYPE_RECIPIENT_INTERFACE)))
+    {
+        if((usb->setup[1u] == 0x22u))
+        {
+            active = true;
+            osalSysUnlockFromISR();
+            return true;
+        }
+    }
+
+    osalSysUnlockFromISR();
+    return sduRequestsHook(usb);
+}
+
+// Adapted from chibios-contrib/demo/STM32/RT-STM32F411-DISCOVERY-blinker
+
+// Forward declarations
+
+///
+///  @brief  USB config structure for ChibiOS/HAL driver
+///
+extern const USBConfig usbcfg;
+
+///
+///  @brief  Serial over USB config structure for ChibiOS/HAL driver
+///
+extern const SerialUSBConfig serusbcfg;
+
+#define USBD1_DATA_REQUEST_EP           1  ///<  Endpoint for data requests (IN direction)
+#define USBD1_DATA_AVAILABLE_EP         1  ///<  Endpoint for data available (OUT direction)
+#define USBD1_INTERRUPT_REQUEST_EP      2  ///<  Endpoint for interrupt requests (IN direction)
+
+///
+///  @brief  USB device descriptor
+///
+static const uint8_t vcom_device_descriptor_data[18] = {
+  USB_DESC_DEVICE       (0x0110,        /* bcdUSB (1.1).                    */
+                         0x02,          /* bDeviceClass (CDC).              */
+                         0x00,          /* bDeviceSubClass.                 */
+                         0x00,          /* bDeviceProtocol.                 */
+                         0x40,          /* bMaxPacketSize.                  */
+                         0xCAFE,        /* idVendor.                        */
+                         0xF00D,        /* idProduct.                       */
+                         0x0200,        /* bcdDevice.                       */
+                         1,             /* iManufacturer.                   */
+                         2,             /* iProduct.                        */
+                         3,             /* iSerialNumber.                   */
+                         1)             /* bNumConfigurations.              */
+};
+
+///
+///  @brief  Wrapper for USB device descriptor
+///
+static const USBDescriptor vcom_device_descriptor = {
+  sizeof vcom_device_descriptor_data,
+  vcom_device_descriptor_data
+};
+
+///
+///  @brief  USB configuration descriptor
+///
+static const uint8_t vcom_configuration_descriptor_data[67] = {
+  /* Configuration Descriptor.*/
+  USB_DESC_CONFIGURATION(67,            /* wTotalLength.                    */
+                         0x02,          /* bNumInterfaces.                  */
+                         0x01,          /* bConfigurationValue.             */
+                         0,             /* iConfiguration.                  */
+                         0xC0,          /* bmAttributes (self powered).     */
+                         50),           /* bMaxPower (100mA).               */
+  /* Interface Descriptor.*/
+  USB_DESC_INTERFACE    (0x00,          /* bInterfaceNumber.                */
+                         0x00,          /* bAlternateSetting.               */
+                         0x01,          /* bNumEndpoints.                   */
+                         0x02,          /* bInterfaceClass (Communications
+                                           Interface Class, CDC section
+                                           4.2).                            */
+                         0x02,          /* bInterfaceSubClass (Abstract
+                                         Control Model, CDC section 4.3).   */
+                         0x01,          /* bInterfaceProtocol (AT commands,
+                                           CDC section 4.4).                */
+                         0),            /* iInterface.                      */
+  /* Header Functional Descriptor (CDC section 5.2.3).*/
+  USB_DESC_BYTE         (5),            /* bLength.                         */
+  USB_DESC_BYTE         (0x24),         /* bDescriptorType (CS_INTERFACE).  */
+  USB_DESC_BYTE         (0x00),         /* bDescriptorSubtype (Header
+                                           Functional Descriptor.           */
+  USB_DESC_BCD          (0x0110),       /* bcdCDC.                          */
+  /* Call Management Functional Descriptor. */
+  USB_DESC_BYTE         (5),            /* bFunctionLength.                 */
+  USB_DESC_BYTE         (0x24),         /* bDescriptorType (CS_INTERFACE).  */
+  USB_DESC_BYTE         (0x01),         /* bDescriptorSubtype (Call Management
+                                           Functional Descriptor).          */
+  USB_DESC_BYTE         (0x00),         /* bmCapabilities (D0+D1).          */
+  USB_DESC_BYTE         (0x01),         /* bDataInterface.                  */
+  /* ACM Functional Descriptor.*/
+  USB_DESC_BYTE         (4),            /* bFunctionLength.                 */
+  USB_DESC_BYTE         (0x24),         /* bDescriptorType (CS_INTERFACE).  */
+  USB_DESC_BYTE         (0x02),         /* bDescriptorSubtype (Abstract
+                                           Control Management Descriptor).  */
+  USB_DESC_BYTE         (0x02),         /* bmCapabilities.                  */
+  /* Union Functional Descriptor.*/
+  USB_DESC_BYTE         (5),            /* bFunctionLength.                 */
+  USB_DESC_BYTE         (0x24),         /* bDescriptorType (CS_INTERFACE).  */
+  USB_DESC_BYTE         (0x06),         /* bDescriptorSubtype (Union
+                                           Functional Descriptor).          */
+  USB_DESC_BYTE         (0x00),         /* bMasterInterface (Communication
+                                           Class Interface).                */
+  USB_DESC_BYTE         (0x01),         /* bSlaveInterface0 (Data Class
+                                           Interface).                      */
+  /* Endpoint 2 Descriptor.*/
+  USB_DESC_ENDPOINT     (USBD1_INTERRUPT_REQUEST_EP|0x80,
+                         0x03,          /* bmAttributes (Interrupt).        */
+                         0x0008,        /* wMaxPacketSize.                  */
+                         0xFF),         /* bInterval.                       */
+  /* Interface Descriptor.*/
+  USB_DESC_INTERFACE    (0x01,          /* bInterfaceNumber.                */
+                         0x00,          /* bAlternateSetting.               */
+                         0x02,          /* bNumEndpoints.                   */
+                         0x0A,          /* bInterfaceClass (Data Class
+                                           Interface, CDC section 4.5).     */
+                         0x00,          /* bInterfaceSubClass (CDC section
+                                           4.6).                            */
+                         0x00,          /* bInterfaceProtocol (CDC section
+                                           4.7).                            */
+                         0x00),         /* iInterface.                      */
+  /* Endpoint 3 Descriptor.*/
+  USB_DESC_ENDPOINT     (USBD1_DATA_AVAILABLE_EP,       /* bEndpointAddress.*/
+                         0x02,          /* bmAttributes (Bulk).             */
+                         0x0040,        /* wMaxPacketSize.                  */
+                         0x00),         /* bInterval.                       */
+  /* Endpoint 1 Descriptor.*/
+  USB_DESC_ENDPOINT     (USBD1_DATA_REQUEST_EP|0x80,    /* bEndpointAddress.*/
+                         0x02,          /* bmAttributes (Bulk).             */
+                         0x0040,        /* wMaxPacketSize.                  */
+                         0x00)          /* bInterval.                       */
+};
+
+///
+///  @brief  Wrapper for USB configuration descriptor
+///
+static const USBDescriptor vcom_configuration_descriptor = {
+  sizeof vcom_configuration_descriptor_data,
+  vcom_configuration_descriptor_data
+};
+
+///
+///  @brief  Language descriptor representing U.S. English
+///
+static const uint8_t vcom_string0[] = {
+  USB_DESC_BYTE(4),                     /* bLength.                         */
+  USB_DESC_BYTE(USB_DESCRIPTOR_STRING), /* bDescriptorType.                 */
+  USB_DESC_WORD(0x0409)                 /* wLANGID (U.S. English).          */
+};
+
+///
+///  @brief  String descriptor for vendor clear name
+///
+static const uint8_t vcom_string1[] = {
+  USB_DESC_BYTE(38),                    /* bLength.                         */
+  USB_DESC_BYTE(USB_DESCRIPTOR_STRING), /* bDescriptorType.                 */
+  'i', 0, 'n', 0, 'f', 0, '4', 0, '@', 0, 'F', 0, 'A', 0, 'U', 0,
+};
+
+///
+///  @brief  String descriptor for device clear name
+///
+static const uint8_t vcom_string2[] = {
+  USB_DESC_BYTE(88),                    /* bLength.                         */
+  USB_DESC_BYTE(USB_DESCRIPTOR_STRING), /* bDescriptorType.                 */
+  'S', 0, 't', 0, 'a', 0, 't', 0, 'e', 0, ' ', 0,
+  'A', 0, 'b', 0, 's', 0, 't', 0, 'r', 0, 'a', 0, 'c', 0, 't', 0, 'i', 0, 'o', 0, 'n', 0, 's', 0, ' ', 0,
+  'E', 0, 'x', 0, 'e', 0, 'c', 0, 'u', 0, 't', 0, 'i', 0, 'o', 0, 'n', 0, ' ', 0,
+  'T', 0, 'i', 0, 'm', 0, 'e', 0, ' ', 0,
+  'B', 0, 'e', 0, 'n', 0, 'c', 0, 'h', 0, 'm', 0, 'a', 0, 'r', 0, 'k', 0,
+};
+
+///
+///  @brief  Wrapper for USB string descriptors
+///
+static const USBDescriptor vcom_strings[] = {
+    {sizeof(vcom_string0), vcom_string0},
+    {sizeof(vcom_string1), vcom_string1},
+    {sizeof(vcom_string2), vcom_string2}
+};
+
+///
+///  @brief  Handles descriptor requests
+///
+///  @param[in]  usbp    USB driver instance
+///  @param[in]  dtype   Descriptor type
+///  @param[in]  dindex  Descriptor index
+///  @param[in]  lang    Language identifier
+///
+///  @returns  Either
+///      - pointer to the requested descriptor of parameters are valid or
+///      - NULL otherwise
+///
+static const USBDescriptor *get_descriptor(USBDriver *usbp,
+                                           uint8_t dtype,
+                                           uint8_t dindex,
+                                           uint16_t lang) {
+
+  (void)usbp;
+  (void)lang;
+  switch (dtype) {
+  case USB_DESCRIPTOR_DEVICE:
+    return &vcom_device_descriptor;
+  case USB_DESCRIPTOR_CONFIGURATION:
+    return &vcom_configuration_descriptor;
+  case USB_DESCRIPTOR_STRING:
+    if (dindex < 3)
+      return &vcom_strings[dindex];
+  }
+  return NULL;
+}
+
+///
+///  @brief  State for the IN direction of endpoint 1
+///
+static USBInEndpointState ep1instate;
+
+///
+///  @brief  State for the OUT direction of endpoint 1
+///
+static USBOutEndpointState ep1outstate;
+
+///
+///  @brief  Configuration for endpoint 1
+///
+static const USBEndpointConfig ep1config = {
+  USB_EP_MODE_TYPE_BULK,
+  NULL,
+  sduDataTransmitted,
+  sduDataReceived,
+  0x0040,
+  0x0040,
+  &ep1instate,
+  &ep1outstate,
+  2,
+  NULL
+};
+
+///
+///  @brief  State for the IN direction of endpoint 2
+///
+static USBInEndpointState ep2instate;
+
+///
+///  @brief  Configuration for endpoint 2
+///
+static const USBEndpointConfig ep2config = {
+  USB_EP_MODE_TYPE_INTR,
+  NULL,
+  sduInterruptTransmitted,
+  NULL,
+  0x0010,
+  0x0000,
+  &ep2instate,
+  NULL,
+  1,
+  NULL
+};
+
+///
+///  @brief  USB event handler for the active flag and SDU driver state
+///
+///  @param[in]  usbp   USB driver instance
+///  @param[in]  event  Event to handle
+///
+static void usb_event(USBDriver *usbp, usbevent_t event) {
+
+  switch (event) {
+  case USB_EVENT_ADDRESS:
+    return;
+  case USB_EVENT_CONFIGURED:
+    chSysLockFromISR();
+
+    /* Enables the endpoints specified into the configuration.
+       Note, this callback is invoked from an ISR so I-Class functions
+       must be used.*/
+    active = false;
+    usbInitEndpointI(usbp, USBD1_DATA_REQUEST_EP, &ep1config);
+    usbInitEndpointI(usbp, USBD1_INTERRUPT_REQUEST_EP, &ep2config);
+
+    /* Resetting the state of the CDC subsystem.*/
+    sduConfigureHookI(&usb_serial_driver);
+
+    chSysUnlockFromISR();
+    return;
+  case USB_EVENT_RESET:
+  /* Falls into.*/
+  case USB_EVENT_UNCONFIGURED:
+  /* Falls into.*/
+  case USB_EVENT_SUSPEND:
+    chSysLockFromISR();
+
+    /* Disconnection event on suspend.*/
+    active = false;
+    sduSuspendHookI(&usb_serial_driver);
+
+    chSysUnlockFromISR();
+    return;
+  case USB_EVENT_WAKEUP:
+    chSysLockFromISR();
+
+    /* Connection event on wakeup.*/
+    sduWakeupHookI(&usb_serial_driver);
+
+    chSysUnlockFromISR();
+    return;
+  case USB_EVENT_STALLED:
+    return;
+  }
+  return;
+}
+
+///
+///  @brief  Handler for start-of-frame condition calling the SDU sof handler
+///
+///  @param[in]  usbp  USB driver instance
+///
+static void sof_handler(USBDriver *usbp) {
+
+  (void)usbp;
+
+  osalSysLockFromISR();
+  sduSOFHookI(&usb_serial_driver);
+  osalSysUnlockFromISR();
+}
+
+///
+///  @brief  USB driver configuration
+///
+const USBConfig usbcfg = {
+  usb_event,
+  get_descriptor,
+  usb_request_handler,
+  sof_handler
+};
+
+///
+///  @brief  Serial over USB driver configuration
+///
+const SerialUSBConfig serusbcfg = {
+  &USBD1,
+  USBD1_DATA_REQUEST_EP,
+  USBD1_DATA_AVAILABLE_EP,
+  USBD1_INTERRUPT_REQUEST_EP
+};
+
+void usb_init(void)
+{
+
+    palSetPadMode(GPIOA, 11, PAL_MODE_ALTERNATE(10));
+    palSetPadMode(GPIOA, 12, PAL_MODE_ALTERNATE(10));
+
+    sduObjectInit(&usb_serial_driver);
+    sduStart(&usb_serial_driver, &serusbcfg);
+
+    usbDisconnectBus(&USBD1);
+    chThdSleepMilliseconds(1000);
+    usbStart(&USBD1, &usbcfg);
+    usbConnectBus(&USBD1);
+
+}
+
+bool usb_active(void)
+{
+    return active;
+}
diff --git a/src/usb.h b/src/usb.h
new file mode 100644
index 0000000000000000000000000000000000000000..85a47eddd1cbfb1ca9d1b363a523c6292afdc970
--- /dev/null
+++ b/src/usb.h
@@ -0,0 +1,51 @@
+// This file is part of the execution-time evaluation for the qronos observer abstractions.
+// Copyright (C) 2022-2023  Tim Rheinfels  <tim.rheinfels@fau.de>
+// See https://gitlab.cs.fau.de/qronos-state-abstractions/execution-time
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+///
+///  @file  usb.h
+///
+///  @brief  Provides the interface for the USB driver
+///
+///  @author  Tim Rheinfels  <tim.rheinfels@fau.de>
+///
+
+#ifndef USB_H
+#define USB_H
+
+#include <ch.h>
+#include <hal.h>
+
+///
+///  @brief  USB serial driver instance
+///
+extern SerialUSBDriver usb_serial_driver;
+
+///
+///  @brief  Initializes the USB stack
+///
+void usb_init(void);
+
+///
+///  @brief  Checks if there is a terminal attached to the USB over serial driver
+///
+///  @returns  Either
+///      - true if a client terminal is attached or
+///      - false otherwise
+///
+bool usb_active(void);
+
+#endif // USB_H