diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json
index b16913f5d592d9ad34870956331d1f027fc482d5..9d1fd9c7d58be5ffba0637229f395a479ac31d8b 100644
--- a/.vscode/c_cpp_properties.json
+++ b/.vscode/c_cpp_properties.json
@@ -273,6 +273,29 @@
                 "${workspaceFolder}/src/bsps/stm32f767zi_compute_unit"
             ]
         },
+        {
+            "name": "stm32f767zi_compute_unit_v2",
+            "cStandard": "c11",
+            "cppStandard": "c++14",
+            "compilerPath": "/opt/arm-miosix-eabi/bin/arm-miosix-eabi-g++",
+            "defines": [
+                "${defaultDefines}",
+                "_MIOSIX_BOARDNAME=stm32f767zi_compute_unit_v2",
+                "_BOARD_STM32F767ZI_COMPUTE_UNIT_V2",
+                "_ARCH_CORTEXM7_STM32F7",
+                "STM32F769xx",
+                "HSE_VALUE=25000000",
+                "SYSCLK_FREQ_216MHz=216000000",
+                "__ENABLE_XRAM",
+                "V_DDA_VOLTAGE=3.3f"
+            ],
+            "includePath": [
+                "${defaultIncludePaths}",
+                "${workspaceFolder}/libs/miosix-kernel/miosix/arch/cortexM7_stm32f7/common",
+                "${workspaceFolder}/src/bsps/stm32f767zi_compute_unit_v2/config",
+                "${workspaceFolder}/src/bsps/stm32f767zi_compute_unit_v2"
+            ]
+        },
         {
             "name": "stm32f767zi_death_stack_v4",
             "cStandard": "c11",
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2671df6f6a2cac5b9de395b7e3df6fdf23776b7f..820ee31d8375c2e1ccd0841b8e5b494102dc0513 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -48,6 +48,9 @@ sbs_target(imu-calibration stm32f429zi_parafoil)
 add_executable(mxgui-helloworld src/entrypoints/examples/mxgui-helloworld.cpp)
 sbs_target(mxgui-helloworld stm32f429zi_stm32f4discovery)
 
+add_executable(compute-unit-v2-testsuite src/entrypoints/compute-unit-v2-testsuite.cpp)
+sbs_target(compute-unit-v2-testsuite stm32f767zi_compute_unit_v2_no_xram)
+
 # add_executable(kernel-testsuite src/entrypoints/kernel-testsuite.cpp)
 # sbs_target(kernel-testsuite stm32f767zi_compute_unit)
 
diff --git a/cmake/boards.cmake b/cmake/boards.cmake
index aaf6074cbba4061cf1e15989a4c5b97de5cad32d..9db7a898b9bd5e204be0130f0772f1015d8462d6 100644
--- a/cmake/boards.cmake
+++ b/cmake/boards.cmake
@@ -31,6 +31,8 @@ set(BOARDCORE_BOARDS_OPTIONS_FILES
     ${BOARDCORE_PATH}/src/bsps/stm32f756zg_nucleo/config/board_options.cmake
     ${BOARDCORE_PATH}/src/bsps/stm32f767zi_automated_antennas/config/board_options.cmake
     ${BOARDCORE_PATH}/src/bsps/stm32f767zi_compute_unit/config/board_options.cmake
+    ${BOARDCORE_PATH}/src/bsps/stm32f767zi_compute_unit_v2/config/board_options.cmake
+    ${BOARDCORE_PATH}/src/bsps/stm32f767zi_compute_unit_v2/config/board_options_no_xram.cmake
     ${BOARDCORE_PATH}/src/bsps/stm32f767zi_gemini_gs/config/board_options.cmake
     ${BOARDCORE_PATH}/src/bsps/stm32f767zi_gemini_motor/config/board_options.cmake
     ${BOARDCORE_PATH}/src/bsps/stm32f767zi_death_stack_v4/config/board_options.cmake
diff --git a/src/bsps/stm32f767zi_compute_unit_v2/config/board_options.cmake b/src/bsps/stm32f767zi_compute_unit_v2/config/board_options.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..e4a000b5bff342d2ec4cbc01f5ad1e67b34fb124
--- /dev/null
+++ b/src/bsps/stm32f767zi_compute_unit_v2/config/board_options.cmake
@@ -0,0 +1,106 @@
+# Copyright (C) 2023 by Skyward
+#
+# This program is free software; you can redistribute it and/or 
+# it under the terms of the GNU General Public License as published 
+# the Free Software Foundation; either version 2 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.
+#
+# As a special exception, if other files instantiate templates or use
+# macros or inline functions from this file, or you compile this file
+# and link it with other works to produce a work based on this file,
+# this file does not by itself cause the resulting work to be covered
+# by the GNU General Public License. However the source code for this
+# file must still be made available in accordance with the GNU 
+# Public License. This exception does not invalidate any other 
+# why a work based on this file might be covered by the GNU General
+# Public License.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>
+
+set(BOARD_NAME stm32f767zi_compute_unit_v2)
+set(ARCH_NAME cortexM7_stm32f7)
+
+# Base directories with header files for this board
+set(ARCH_PATH ${KPATH}/arch/${ARCH_NAME}/common)
+set(BOARD_PATH ${BOARDCORE_PATH}/src/bsps/stm32f767zi_compute_unit_v2)
+set(BOARD_CONFIG_PATH ${BOARDCORE_PATH}/src/bsps/stm32f767zi_compute_unit_v2/config)
+
+# Specify where to find the board specific config/miosix_settings.h
+set(BOARD_MIOSIX_SETTINGS_PATH ${BOARD_PATH})
+
+# Specify where to find the board specific config/mxgui_settings.h
+set(BOARD_MXGUI_SETTINGS_PATH ${BOARD_PATH})
+
+# Optimization flags:
+# -O0 do no optimization, the default if no optimization level is specified
+# -O or -O1 optimize minimally
+# -O2 optimize more
+# -O3 optimize even more
+# -Ofast optimize very aggressively to the point of breaking the standard
+# -Og Optimize debugging experience, enables optimizations that do not
+# interfere with debugging
+# -Os Optimize for size with -O2 optimizations that do not increase code size
+set(OPT_OPTIMIZATION -O2)
+
+# Boot file and linker script
+set(BOOT_FILE ${BOARD_PATH}/core/stage_1_boot.cpp)
+# set(LINKER_SCRIPT ${BOARD_PATH}/stm32_2m+384k_ram.ld)
+set(LINKER_SCRIPT ${BOARD_PATH}/stm32_2m+32m_xram.ld)
+
+# Enables the initialization of the external 16MB SDRAM memory
+set(XRAM -D__ENABLE_XRAM)
+
+# Select clock frequency (HSE_VALUE is the xtal on board, fixed)
+set(CLOCK_FREQ -DHSE_VALUE=25000000 -DSYSCLK_FREQ_216MHz=216000000)
+
+# C++ Exception/rtti support disable flags.
+# To save code size if not using C++ exceptions (nor some STL code which
+# implicitly uses it) uncomment this option.
+# -D__NO_EXCEPTIONS is used by Miosix to know if exceptions are used.
+# set(OPT_EXCEPT -fno-exceptions -fno-rtti -D__NO_EXCEPTIONS)
+
+# Specify a custom flash command
+# This is the program that is invoked when the flash flag (-f or --flash) is
+# used with the Miosix Build System. Use $binary or $hex as placeolders, they
+# will be replaced by the build systems with the binary or hex file repectively.
+# If a command is not specified, the build system will use st-flash if found
+# set(PROGRAM_CMDLINE "here your custom flash command")
+
+# Basic flags
+set(FLAGS_BASE -mcpu=cortex-m7 -mthumb -mfloat-abi=hard -mfpu=fpv5-d16)
+
+# Flags for ASM and linker
+set(AFLAGS_BASE ${FLAGS_BASE})
+set(LFLAGS_BASE ${FLAGS_BASE} -Wl,--gc-sections,-Map,main.map -Wl,-T${LINKER_SCRIPT} ${OPT_EXCEPT} ${OPT_OPTIMIZATION} -nostdlib)
+
+# Flags for C/C++
+string(TOUPPER ${BOARD_NAME} BOARD_UPPER)
+set(CFLAGS_BASE
+    -D_BOARD_${BOARD_UPPER} -D_MIOSIX_BOARDNAME=\"${BOARD_NAME}\"
+    -D_DEFAULT_SOURCE=1 -ffunction-sections -Wall -Werror=return-type -g
+    -D_ARCH_CORTEXM7_STM32F7
+    ${CLOCK_FREQ} ${XRAM} ${SRAM_BOOT} ${FLAGS_BASE} ${OPT_OPTIMIZATION} -c
+)
+set(CXXFLAGS_BASE ${CFLAGS_BASE} ${OPT_EXCEPT})
+
+# Select architecture specific files
+set(ARCH_SRC
+    ${ARCH_PATH}/interfaces-impl/delays.cpp
+    ${ARCH_PATH}/interfaces-impl/gpio_impl.cpp
+    ${ARCH_PATH}/interfaces-impl/portability.cpp
+    ${BOARD_PATH}/interfaces-impl/bsp.cpp
+    ${KPATH}/arch/common/CMSIS/Device/ST/STM32F7xx/Source/Templates/system_stm32f7xx.c
+    ${KPATH}/arch/common/core/cache_cortexMx.cpp
+    ${KPATH}/arch/common/core/interrupts_cortexMx.cpp
+    ${KPATH}/arch/common/core/mpu_cortexMx.cpp
+    ${KPATH}/arch/common/core/stm32f2_f4_l4_f7_h7_os_timer.cpp
+    ${KPATH}/arch/common/drivers/sd_stm32f2_f4_f7.cpp
+    ${KPATH}/arch/common/drivers/serial_stm32.cpp
+    ${KPATH}/arch/common/drivers/stm32_hardware_rng.cpp
+)
diff --git a/src/bsps/stm32f767zi_compute_unit_v2/config/board_options_no_xram.cmake b/src/bsps/stm32f767zi_compute_unit_v2/config/board_options_no_xram.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..e16999d91d3d137f94caf4e64654575e4bdeb1c6
--- /dev/null
+++ b/src/bsps/stm32f767zi_compute_unit_v2/config/board_options_no_xram.cmake
@@ -0,0 +1,106 @@
+# Copyright (C) 2023 by Skyward
+#
+# This program is free software; you can redistribute it and/or 
+# it under the terms of the GNU General Public License as published 
+# the Free Software Foundation; either version 2 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.
+#
+# As a special exception, if other files instantiate templates or use
+# macros or inline functions from this file, or you compile this file
+# and link it with other works to produce a work based on this file,
+# this file does not by itself cause the resulting work to be covered
+# by the GNU General Public License. However the source code for this
+# file must still be made available in accordance with the GNU 
+# Public License. This exception does not invalidate any other 
+# why a work based on this file might be covered by the GNU General
+# Public License.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>
+
+set(BOARD_NAME stm32f767zi_compute_unit_v2_no_xram)
+set(ARCH_NAME cortexM7_stm32f7)
+
+# Base directories with header files for this board
+set(ARCH_PATH ${KPATH}/arch/${ARCH_NAME}/common)
+set(BOARD_PATH ${BOARDCORE_PATH}/src/bsps/stm32f767zi_compute_unit_v2)
+set(BOARD_CONFIG_PATH ${BOARDCORE_PATH}/src/bsps/stm32f767zi_compute_unit_v2/config)
+
+# Specify where to find the board specific config/miosix_settings.h
+set(BOARD_MIOSIX_SETTINGS_PATH ${BOARD_PATH})
+
+# Specify where to find the board specific config/mxgui_settings.h
+set(BOARD_MXGUI_SETTINGS_PATH ${BOARD_PATH})
+
+# Optimization flags:
+# -O0 do no optimization, the default if no optimization level is specified
+# -O or -O1 optimize minimally
+# -O2 optimize more
+# -O3 optimize even more
+# -Ofast optimize very aggressively to the point of breaking the standard
+# -Og Optimize debugging experience, enables optimizations that do not
+# interfere with debugging
+# -Os Optimize for size with -O2 optimizations that do not increase code size
+set(OPT_OPTIMIZATION -O2)
+
+# Boot file and linker script
+set(BOOT_FILE ${BOARD_PATH}/core/stage_1_boot.cpp)
+set(LINKER_SCRIPT ${BOARD_PATH}/stm32_2m+384k_ram.ld)
+# set(LINKER_SCRIPT ${BOARD_PATH}/stm32_2m+16m_xram.ld)
+
+# Enables the initialization of the external 16MB SDRAM memory
+set(XRAM -D__ENABLE_XRAM)
+
+# Select clock frequency (HSE_VALUE is the xtal on board, fixed)
+set(CLOCK_FREQ -DHSE_VALUE=25000000 -DSYSCLK_FREQ_216MHz=216000000)
+
+# C++ Exception/rtti support disable flags.
+# To save code size if not using C++ exceptions (nor some STL code which
+# implicitly uses it) uncomment this option.
+# -D__NO_EXCEPTIONS is used by Miosix to know if exceptions are used.
+# set(OPT_EXCEPT -fno-exceptions -fno-rtti -D__NO_EXCEPTIONS)
+
+# Specify a custom flash command
+# This is the program that is invoked when the flash flag (-f or --flash) is
+# used with the Miosix Build System. Use $binary or $hex as placeolders, they
+# will be replaced by the build systems with the binary or hex file repectively.
+# If a command is not specified, the build system will use st-flash if found
+# set(PROGRAM_CMDLINE "here your custom flash command")
+
+# Basic flags
+set(FLAGS_BASE -mcpu=cortex-m7 -mthumb -mfloat-abi=hard -mfpu=fpv5-d16)
+
+# Flags for ASM and linker
+set(AFLAGS_BASE ${FLAGS_BASE})
+set(LFLAGS_BASE ${FLAGS_BASE} -Wl,--gc-sections,-Map,main.map -Wl,-T${LINKER_SCRIPT} ${OPT_EXCEPT} ${OPT_OPTIMIZATION} -nostdlib)
+
+# Flags for C/C++
+string(TOUPPER ${BOARD_NAME} BOARD_UPPER)
+set(CFLAGS_BASE
+    -D_BOARD_${BOARD_UPPER} -D_MIOSIX_BOARDNAME=\"${BOARD_NAME}\"
+    -D_DEFAULT_SOURCE=1 -ffunction-sections -Wall -Werror=return-type -g
+    -D_ARCH_CORTEXM7_STM32F7
+    ${CLOCK_FREQ} ${XRAM} ${SRAM_BOOT} ${FLAGS_BASE} ${OPT_OPTIMIZATION} -c
+)
+set(CXXFLAGS_BASE ${CFLAGS_BASE} ${OPT_EXCEPT})
+
+# Select architecture specific files
+set(ARCH_SRC
+    ${ARCH_PATH}/interfaces-impl/delays.cpp
+    ${ARCH_PATH}/interfaces-impl/gpio_impl.cpp
+    ${ARCH_PATH}/interfaces-impl/portability.cpp
+    ${BOARD_PATH}/interfaces-impl/bsp.cpp
+    ${KPATH}/arch/common/CMSIS/Device/ST/STM32F7xx/Source/Templates/system_stm32f7xx.c
+    ${KPATH}/arch/common/core/cache_cortexMx.cpp
+    ${KPATH}/arch/common/core/interrupts_cortexMx.cpp
+    ${KPATH}/arch/common/core/mpu_cortexMx.cpp
+    ${KPATH}/arch/common/core/stm32f2_f4_l4_f7_h7_os_timer.cpp
+    ${KPATH}/arch/common/drivers/sd_stm32f2_f4_f7.cpp
+    ${KPATH}/arch/common/drivers/serial_stm32.cpp
+    ${KPATH}/arch/common/drivers/stm32_hardware_rng.cpp
+)
diff --git a/src/bsps/stm32f767zi_compute_unit_v2/config/board_settings.h b/src/bsps/stm32f767zi_compute_unit_v2/config/board_settings.h
new file mode 100644
index 0000000000000000000000000000000000000000..2332aa30ea0f6249a56c67a56e7aacd668d29c4b
--- /dev/null
+++ b/src/bsps/stm32f767zi_compute_unit_v2/config/board_settings.h
@@ -0,0 +1,62 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
+ *
+ * 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.
+ */
+
+#pragma once
+
+/**
+ * \internal
+ * Versioning for board_settings.h for out of git tree projects
+ */
+#define BOARD_SETTINGS_VERSION 300
+
+namespace miosix
+{
+
+/**
+ * \addtogroup Settings
+ * \{
+ */
+
+/// Size of stack for main().
+/// The C standard library is stack-heavy (iprintf requires 1KB)
+const unsigned int MAIN_STACK_SIZE = 16 * 1024;
+
+/// Serial port
+const unsigned int defaultSerial      = 1;
+const unsigned int defaultSerialSpeed = 115200;
+#define SERIAL_1_DMA
+// #define SERIAL_2_DMA
+// #define SERIAL_3_DMA
+
+// SD card driver
+static const unsigned char sdVoltage = 33;  // Board powered @ 3.3V
+// #define SD_ONE_BIT_DATABUS
+#define SD_SDMMC 1  // Select either SDMMC1 or SDMMC2
+
+/// Analog supply voltage for ADC, DAC, Reset blocks, RCs and PLL
+#define V_DDA_VOLTAGE 3.3f
+
+/**
+ * \}
+ */
+
+}  // namespace miosix
diff --git a/src/bsps/stm32f767zi_compute_unit_v2/config/miosix_settings.h b/src/bsps/stm32f767zi_compute_unit_v2/config/miosix_settings.h
new file mode 100644
index 0000000000000000000000000000000000000000..e1ca4aac6bef352c5b4d367b3ba84ff5f4445499
--- /dev/null
+++ b/src/bsps/stm32f767zi_compute_unit_v2/config/miosix_settings.h
@@ -0,0 +1,240 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
+ *
+ * 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.
+ */
+
+#pragma once
+
+// Before you can compile the kernel you have to configure it by editing this
+// file. After that, comment out this line to disable the reminder error.
+// The PARSING_FROM_IDE is because Netbeans gets confused by this, it is never
+// defined when compiling the code.
+#ifndef PARSING_FROM_IDE
+//#error This error is a reminder that you have not edited miosix_settings.h
+// yet.
+#endif  // PARSING_FROM_IDE
+
+/**
+ * \file miosix_settings.h
+ * NOTE: this file contains ONLY configuration options that are not dependent
+ * on architecture specific details. The other options are in the following
+ * files which are included here:
+ * miosix/arch/architecture name/common/arch_settings.h
+ * miosix/config/arch/architecture name/board name/board_settings.h
+ */
+#include "arch_settings.h"
+#include "board_settings.h"
+#include "util/version.h"
+
+/**
+ * \internal
+ * Versioning for miosix_settings.h for out of git tree projects
+ */
+#define MIOSIX_SETTINGS_VERSION 300
+
+namespace miosix
+{
+
+/**
+ * \addtogroup Settings
+ * \{
+ */
+
+//
+// Scheduler options
+//
+
+/// \def SCHED_TYPE_PRIORITY
+/// If uncommented selects the priority scheduler
+/// \def SCHED_TYPE_CONTROL_BASED
+/// If uncommented selects the control based scheduler
+/// \def SCHED_TYPE_EDF
+/// If uncommented selects the EDF scheduler
+// Uncomment only *one* of those
+
+#define SCHED_TYPE_PRIORITY
+//#define SCHED_TYPE_CONTROL_BASED
+//#define SCHED_TYPE_EDF
+
+/// \def WITH_CPU_TIME_COUNTER
+/// Allows to enable/disable CPUTimeCounter to save code size and remove its
+/// overhead from the scheduling process. By default it is not defined
+/// (CPUTimeCounter is disabled).
+//#define WITH_CPU_TIME_COUNTER
+
+//
+// Filesystem options
+//
+
+/// \def WITH_FILESYSTEM
+/// Allows to enable/disable filesystem support to save code size
+/// By default it is defined (filesystem support is enabled)
+#define WITH_FILESYSTEM
+
+/// \def WITH_DEVFS
+/// Allows to enable/disable DevFs support to save code size
+/// By default it is defined (DevFs is enabled)
+#define WITH_DEVFS
+
+/// \def SYNC_AFTER_WRITE
+/// Increases filesystem write robustness. After each write operation the
+/// filesystem is synced so that a power failure happens data is not lost
+/// (unless power failure happens exactly between the write and the sync)
+/// Unfortunately write latency and throughput becomes twice as worse
+/// By default it is defined (slow but safe)
+#define SYNC_AFTER_WRITE
+
+/// Maximum number of open files. Trying to open more will fail.
+/// Cannot be lower than 3, as the first three are stdin, stdout, stderr
+const unsigned char MAX_OPEN_FILES = 8;
+
+/// \def WITH_PROCESSES
+/// If uncommented enables support for processes as well as threads.
+/// This enables the dynamic loader to load elf programs, the extended system
+/// call service and, if the hardware supports it, the MPU to provide memory
+/// isolation of processes
+//#define WITH_PROCESSES
+
+#if defined(WITH_PROCESSES) && defined(__NO_EXCEPTIONS)
+#error Processes require C++ exception support
+#endif  // defined(WITH_PROCESSES) && defined(__NO_EXCEPTIONS)
+
+#if defined(WITH_PROCESSES) && !defined(WITH_FILESYSTEM)
+#error Processes require filesystem support
+#endif  // defined(WITH_PROCESSES) && !defined(WITH_FILESYSTEM)
+
+#if defined(WITH_PROCESSES) && !defined(WITH_DEVFS)
+#error Processes require devfs support
+#endif  // defined(WITH_PROCESSES) && !defined(WITH_DEVFS)
+
+//
+// C/C++ standard library I/O (stdin, stdout and stderr related)
+//
+
+/// \def WITH_BOOTLOG
+/// Uncomment to print bootlogs on stdout.
+/// By default it is defined (bootlogs are printed)
+#define WITH_BOOTLOG
+
+/// \def WITH_ERRLOG
+/// Uncomment for debug information on stdout.
+/// By default it is defined (error information is printed)
+#define WITH_ERRLOG
+
+//
+// Kernel related options (stack sizes, priorities)
+//
+
+/// \def WITH_DEEP_SLEEP
+/// Adds interfaces and required variables to support deep sleep state switch
+/// automatically when peripherals are not required
+//#define WITH_DEEP_SLEEP
+
+/**
+ * \def JTAG_DISABLE_SLEEP
+ * JTAG debuggers lose communication with the device if it enters sleep
+ * mode, so to use debugging it is necessary to disable sleep in the idle
+ * thread. By default it is not defined (idle thread calls sleep).
+ */
+//#define JTAG_DISABLE_SLEEP
+
+#if defined(WITH_DEEP_SLEEP) && defined(JTAG_DISABLE_SLEEP)
+#error Deep sleep cannot work together with jtag
+#endif  // defined(WITH_PROCESSES) && !defined(WITH_DEVFS)
+
+/// Minimum stack size (MUST be divisible by 4)
+const unsigned int STACK_MIN = 256;
+
+/// \internal Size of idle thread stack.
+/// Should be >=STACK_MIN (MUST be divisible by 4)
+const unsigned int STACK_IDLE = 256;
+
+/// Default stack size for pthread_create.
+/// The chosen value is enough to call C standard library functions
+/// such as printf/fopen which are stack-heavy
+const unsigned int STACK_DEFAULT_FOR_PTHREAD = 2048;
+
+/// Maximum size of the RAM image of a process. If a program requires more
+/// the kernel will not run it (MUST be divisible by 4)
+const unsigned int MAX_PROCESS_IMAGE_SIZE = 64 * 1024;
+
+/// Minimum size of the stack for a process. If a program specifies a lower
+/// size the kernel will not run it (MUST be divisible by 4)
+const unsigned int MIN_PROCESS_STACK_SIZE = STACK_MIN;
+
+/// Every userspace thread has two stacks, one for when it is running in
+/// userspace and one for when it is running in kernelspace (that is, while it
+/// is executing system calls). This is the size of the stack for when the
+/// thread is running in kernelspace (MUST be divisible by 4)
+const unsigned int SYSTEM_MODE_PROCESS_STACK_SIZE = 2 * 1024;
+
+/// Number of priorities (MUST be >1)
+/// PRIORITY_MAX-1 is the highest priority, 0 is the lowest. -1 is reserved as
+/// the priority of the idle thread.
+/// The meaning of a thread's priority depends on the chosen scheduler.
+#ifdef SCHED_TYPE_PRIORITY
+// Can be modified, but a high value makes context switches more expensive
+const short int PRIORITY_MAX = 4;
+#elif defined(SCHED_TYPE_CONTROL_BASED)
+// Don't touch, the limit is due to the fixed point implementation
+// It's not needed for if floating point is selected, but kept for consistency
+const short int PRIORITY_MAX = 64;
+#else  // SCHED_TYPE_EDF
+// Doesn't exist for this kind of scheduler
+#endif
+
+/// Priority of main()
+/// The meaning of a thread's priority depends on the chosen scheduler.
+const unsigned char MAIN_PRIORITY = 1;
+
+#ifdef SCHED_TYPE_PRIORITY
+/// Maximum thread time slice in nanoseconds, after which preemption occurs
+const unsigned int MAX_TIME_SLICE = 1000000;
+#endif  // SCHED_TYPE_PRIORITY
+
+//
+// Other low level kernel options. There is usually no need to modify these.
+//
+
+/// \internal Length of wartermark (in bytes) to check stack overflow.
+/// MUST be divisible by 4 and can also be zero.
+/// A high value increases context switch time.
+const unsigned int WATERMARK_LEN = 16;
+
+/// \internal Used to fill watermark
+const unsigned int WATERMARK_FILL = 0xaaaaaaaa;
+
+/// \internal Used to fill stack (for checking stack usage)
+const unsigned int STACK_FILL = 0xbbbbbbbb;
+
+// Compiler version checks
+#if !defined(_MIOSIX_GCC_PATCH_MAJOR) || _MIOSIX_GCC_PATCH_MAJOR < 3
+#error \
+    "You are using a too old or unsupported compiler. Get the latest one from https://miosix.org/wiki/index.php?title=Miosix_Toolchain"
+#endif
+#if _MIOSIX_GCC_PATCH_MAJOR > 3
+#warning "You are using a too new compiler, which may not be supported"
+#endif
+
+/**
+ * \}
+ */
+
+}  // namespace miosix
diff --git a/src/bsps/stm32f767zi_compute_unit_v2/core/stage_1_boot.cpp b/src/bsps/stm32f767zi_compute_unit_v2/core/stage_1_boot.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..83eac53b4b0b56309c0a9c26aacb7ff4289558d0
--- /dev/null
+++ b/src/bsps/stm32f767zi_compute_unit_v2/core/stage_1_boot.cpp
@@ -0,0 +1,505 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
+ *
+ * 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.
+ */
+
+#include <string.h>
+
+#include "core/cache_cortexMx.h"
+#include "core/interrupts.h"  //For the unexpected interrupt call
+#include "core/interrupts_cortexMx.h"
+#include "interfaces/arch_registers.h"
+#include "interfaces/bsp.h"
+#include "kernel/stage_2_boot.h"
+
+/*
+ * startup.cpp
+ * STM32 C++ startup.
+ * NOTE: for stm32f767 devices ONLY.
+ * Supports interrupt handlers in C++ without extern "C"
+ * Developed by Terraneo Federico, based on ST startup code.
+ * Additionally modified to boot Miosix.
+ */
+
+/**
+ * Called by Reset_Handler, performs initialization and calls main.
+ * Never returns.
+ */
+void program_startup() __attribute__((noreturn));
+void program_startup()
+{
+    // Cortex M7 core appears to get out of reset with interrupts already
+    // enabled
+    __disable_irq();
+
+    miosix::IRQconfigureCache((const unsigned int *)0xd0000000,
+                              8 * 1024 * 1024);
+
+    // These are defined in the linker script
+    extern unsigned char _etext asm("_etext");
+    extern unsigned char _data asm("_data");
+    extern unsigned char _edata asm("_edata");
+    extern unsigned char _bss_start asm("_bss_start");
+    extern unsigned char _bss_end asm("_bss_end");
+
+    // Initialize .data section, clear .bss section
+    unsigned char *etext     = &_etext;
+    unsigned char *data      = &_data;
+    unsigned char *edata     = &_edata;
+    unsigned char *bss_start = &_bss_start;
+    unsigned char *bss_end   = &_bss_end;
+    memcpy(data, etext, edata - data);
+    memset(bss_start, 0, bss_end - bss_start);
+
+    // Move on to stage 2
+    _init();
+
+    // If main returns, reboot
+    NVIC_SystemReset();
+    for (;;)
+        ;
+}
+
+/**
+ * Reset handler, called by hardware immediately after reset
+ */
+void Reset_Handler() __attribute__((__interrupt__, noreturn));
+void Reset_Handler()
+{
+    /**
+     * SystemInit() is called *before* initializing .data and zeroing .bss
+     * Despite all startup files provided by ST do the opposite, there are three
+     * good reasons to do so:
+     * 1. First, the CMSIS specifications say that SystemInit() must not access
+     *    global variables, so it is actually possible to call it before
+     * 2. Second, when running Miosix with the xram linker scripts .data and
+     *    .bss are placed in the external RAM, so we *must* call SystemInit(),
+     *    which enables xram, before touching .data and .bss
+     * 3. Third, this is a performance improvement since the loops that
+     *    initialize .data and zeros .bss now run with the CPU at full speed
+     *    instead of 8MHz
+     */
+    SystemInit();
+
+/**
+ * ST does not provide code to initialize the SDRAM at boot.
+ * Put after SystemInit() as SDRAM is timing-sensitive and needs the full
+ * clock speed.
+ */
+#ifdef __ENABLE_XRAM
+    miosix::configureSdram();
+#endif  //__ENABLE_XRAM
+
+    /*
+     * Load into the program stack pointer the heap end address and switch from
+     * the msp to sps.
+     * This is required for booting Miosix, a small portion of the top of the
+     * heap area will be used as stack until the first thread starts. After,
+     * this stack will be abandoned and the process stack will point to the
+     * current thread's stack.
+     */
+    asm volatile(
+        "ldr r0,  =_heap_end          \n\t"
+        "msr psp, r0                  \n\t"
+        "movw r0, #2                  \n\n"  // Set the control register to use
+        "msr control, r0              \n\t"  // the process stack
+        "isb                          \n\t" ::
+            : "r0");
+
+    program_startup();
+}
+
+/**
+ * All unused interrupts call this function.
+ */
+extern "C" void Default_Handler() { unexpectedInterrupt(); }
+
+// System handlers
+void /*__attribute__((weak))*/ Reset_Handler();      // These interrupts are not
+void /*__attribute__((weak))*/ NMI_Handler();        // weak because they are
+void /*__attribute__((weak))*/ HardFault_Handler();  // surely defined by Miosix
+void /*__attribute__((weak))*/ MemManage_Handler();
+void /*__attribute__((weak))*/ BusFault_Handler();
+void /*__attribute__((weak))*/ UsageFault_Handler();
+void /*__attribute__((weak))*/ SVC_Handler();
+void /*__attribute__((weak))*/ DebugMon_Handler();
+void /*__attribute__((weak))*/ PendSV_Handler();
+void __attribute__((weak)) SysTick_Handler();
+
+// Interrupt handlers
+void __attribute__((weak)) WWDG_IRQHandler();
+void __attribute__((weak)) PVD_IRQHandler();
+void __attribute__((weak)) TAMP_STAMP_IRQHandler();
+void __attribute__((weak)) RTC_WKUP_IRQHandler();
+void __attribute__((weak)) FLASH_IRQHandler();
+void __attribute__((weak)) RCC_IRQHandler();
+void __attribute__((weak)) EXTI0_IRQHandler();
+void __attribute__((weak)) EXTI1_IRQHandler();
+void __attribute__((weak)) EXTI2_IRQHandler();
+void __attribute__((weak)) EXTI3_IRQHandler();
+void __attribute__((weak)) EXTI4_IRQHandler();
+void __attribute__((weak)) DMA1_Stream0_IRQHandler();
+void __attribute__((weak)) DMA1_Stream1_IRQHandler();
+void __attribute__((weak)) DMA1_Stream2_IRQHandler();
+void __attribute__((weak)) DMA1_Stream3_IRQHandler();
+void __attribute__((weak)) DMA1_Stream4_IRQHandler();
+void __attribute__((weak)) DMA1_Stream5_IRQHandler();
+void __attribute__((weak)) DMA1_Stream6_IRQHandler();
+void __attribute__((weak)) ADC_IRQHandler();
+void __attribute__((weak)) CAN1_TX_IRQHandler();
+void __attribute__((weak)) CAN1_RX0_IRQHandler();
+void __attribute__((weak)) CAN1_RX1_IRQHandler();
+void __attribute__((weak)) CAN1_SCE_IRQHandler();
+void __attribute__((weak)) EXTI9_5_IRQHandler();
+void __attribute__((weak)) TIM1_BRK_TIM9_IRQHandler();
+void __attribute__((weak)) TIM1_UP_TIM10_IRQHandler();
+void __attribute__((weak)) TIM1_TRG_COM_TIM11_IRQHandler();
+void __attribute__((weak)) TIM1_CC_IRQHandler();
+void __attribute__((weak)) TIM2_IRQHandler();
+void __attribute__((weak)) TIM3_IRQHandler();
+void __attribute__((weak)) TIM4_IRQHandler();
+void __attribute__((weak)) I2C1_EV_IRQHandler();
+void __attribute__((weak)) I2C1_ER_IRQHandler();
+void __attribute__((weak)) I2C2_EV_IRQHandler();
+void __attribute__((weak)) I2C2_ER_IRQHandler();
+void __attribute__((weak)) SPI1_IRQHandler();
+void __attribute__((weak)) SPI2_IRQHandler();
+void __attribute__((weak)) USART1_IRQHandler();
+void __attribute__((weak)) USART2_IRQHandler();
+void __attribute__((weak)) USART3_IRQHandler();
+void __attribute__((weak)) EXTI15_10_IRQHandler();
+void __attribute__((weak)) RTC_Alarm_IRQHandler();
+void __attribute__((weak)) OTG_FS_WKUP_IRQHandler();
+void __attribute__((weak)) TIM8_BRK_TIM12_IRQHandler();
+void __attribute__((weak)) TIM8_UP_TIM13_IRQHandler();
+void __attribute__((weak)) TIM8_TRG_COM_TIM14_IRQHandler();
+void __attribute__((weak)) TIM8_CC_IRQHandler();
+void __attribute__((weak)) DMA1_Stream7_IRQHandler();
+void __attribute__((weak)) FMC_IRQHandler();
+void __attribute__((weak)) SDMMC1_IRQHandler();
+void __attribute__((weak)) TIM5_IRQHandler();
+void __attribute__((weak)) SPI3_IRQHandler();
+void __attribute__((weak)) UART4_IRQHandler();
+void __attribute__((weak)) UART5_IRQHandler();
+void __attribute__((weak)) TIM6_DAC_IRQHandler();
+void __attribute__((weak)) TIM7_IRQHandler();
+void __attribute__((weak)) DMA2_Stream0_IRQHandler();
+void __attribute__((weak)) DMA2_Stream1_IRQHandler();
+void __attribute__((weak)) DMA2_Stream2_IRQHandler();
+void __attribute__((weak)) DMA2_Stream3_IRQHandler();
+void __attribute__((weak)) DMA2_Stream4_IRQHandler();
+void __attribute__((weak)) ETH_IRQHandler();
+void __attribute__((weak)) ETH_WKUP_IRQHandler();
+void __attribute__((weak)) CAN2_TX_IRQHandler();
+void __attribute__((weak)) CAN2_RX0_IRQHandler();
+void __attribute__((weak)) CAN2_RX1_IRQHandler();
+void __attribute__((weak)) CAN2_SCE_IRQHandler();
+void __attribute__((weak)) OTG_FS_IRQHandler();
+void __attribute__((weak)) DMA2_Stream5_IRQHandler();
+void __attribute__((weak)) DMA2_Stream6_IRQHandler();
+void __attribute__((weak)) DMA2_Stream7_IRQHandler();
+void __attribute__((weak)) USART6_IRQHandler();
+void __attribute__((weak)) I2C3_EV_IRQHandler();
+void __attribute__((weak)) I2C3_ER_IRQHandler();
+void __attribute__((weak)) OTG_HS_EP1_OUT_IRQHandler();
+void __attribute__((weak)) OTG_HS_EP1_IN_IRQHandler();
+void __attribute__((weak)) OTG_HS_WKUP_IRQHandler();
+void __attribute__((weak)) OTG_HS_IRQHandler();
+void __attribute__((weak)) DCMI_IRQHandler();
+void __attribute__((weak)) CRYP_IRQHandler();
+void __attribute__((weak)) RNG_IRQHandler();
+void __attribute__((weak)) FPU_IRQHandler();
+void __attribute__((weak)) UART7_IRQHandler();
+void __attribute__((weak)) UART8_IRQHandler();
+void __attribute__((weak)) SPI4_IRQHandler();
+void __attribute__((weak)) SPI5_IRQHandler();
+void __attribute__((weak)) SPI6_IRQHandler();
+void __attribute__((weak)) SAI1_IRQHandler();
+void __attribute__((weak)) LTDC_IRQHandler();
+void __attribute__((weak)) LTDC_ER_IRQHandler();
+void __attribute__((weak)) DMA2D_IRQHandler();
+void __attribute__((weak)) SAI2_IRQHandler();
+void __attribute__((weak)) QUADSPI_IRQHandler();
+void __attribute__((weak)) LPTIM1_IRQHandler();
+void __attribute__((weak)) CEC_IRQHandler();
+void __attribute__((weak)) I2C4_EV_IRQHandler();
+void __attribute__((weak)) I2C4_ER_IRQHandler();
+void __attribute__((weak)) SPDIF_RX_IRQHandler();
+void __attribute__((weak)) DSIHOST_IRQHandler();
+void __attribute__((weak)) DFSDM1_FLT0_IRQHandler();
+void __attribute__((weak)) DFSDM1_FLT1_IRQHandler();
+void __attribute__((weak)) DFSDM1_FLT2_IRQHandler();
+void __attribute__((weak)) DFSDM1_FLT3_IRQHandler();
+void __attribute__((weak)) SDMMC2_IRQHandler();
+void __attribute__((weak)) CAN3_TX_IRQHandler();
+void __attribute__((weak)) CAN3_RX0_IRQHandler();
+void __attribute__((weak)) CAN3_RX1_IRQHandler();
+void __attribute__((weak)) CAN3_SCE_IRQHandler();
+void __attribute__((weak)) JPEG_IRQHandler();
+void __attribute__((weak)) MDIOS_IRQHandler();
+
+// Stack top, defined in the linker script
+extern char _main_stack_top asm("_main_stack_top");
+
+// Interrupt vectors, must be placed @ address 0x00000000
+// The extern declaration is required otherwise g++ optimizes it out
+extern void (*const __Vectors[])();
+void (*const __Vectors[])() __attribute__((section(".isr_vector"))) = {
+    reinterpret_cast<void (*)()>(&_main_stack_top), /* Stack pointer*/
+    Reset_Handler,                                  /* Reset Handler */
+    NMI_Handler,                                    /* NMI Handler */
+    HardFault_Handler,                              /* Hard Fault Handler */
+    MemManage_Handler,                              /* MPU Fault Handler */
+    BusFault_Handler,                               /* Bus Fault Handler */
+    UsageFault_Handler,                             /* Usage Fault Handler */
+    0,                                              /* Reserved */
+    0,                                              /* Reserved */
+    0,                                              /* Reserved */
+    0,                                              /* Reserved */
+    SVC_Handler,                                    /* SVCall Handler */
+    DebugMon_Handler,                               /* Debug Monitor Handler */
+    0,                                              /* Reserved */
+    PendSV_Handler,                                 /* PendSV Handler */
+    SysTick_Handler,                                /* SysTick Handler */
+
+    /* External Interrupts */
+    WWDG_IRQHandler,
+    PVD_IRQHandler,
+    TAMP_STAMP_IRQHandler,
+    RTC_WKUP_IRQHandler,
+    FLASH_IRQHandler,
+    RCC_IRQHandler,
+    EXTI0_IRQHandler,
+    EXTI1_IRQHandler,
+    EXTI2_IRQHandler,
+    EXTI3_IRQHandler,
+    EXTI4_IRQHandler,
+    DMA1_Stream0_IRQHandler,
+    DMA1_Stream1_IRQHandler,
+    DMA1_Stream2_IRQHandler,
+    DMA1_Stream3_IRQHandler,
+    DMA1_Stream4_IRQHandler,
+    DMA1_Stream5_IRQHandler,
+    DMA1_Stream6_IRQHandler,
+    ADC_IRQHandler,
+    CAN1_TX_IRQHandler,
+    CAN1_RX0_IRQHandler,
+    CAN1_RX1_IRQHandler,
+    CAN1_SCE_IRQHandler,
+    EXTI9_5_IRQHandler,
+    TIM1_BRK_TIM9_IRQHandler,
+    TIM1_UP_TIM10_IRQHandler,
+    TIM1_TRG_COM_TIM11_IRQHandler,
+    TIM1_CC_IRQHandler,
+    TIM2_IRQHandler,
+    TIM3_IRQHandler,
+    TIM4_IRQHandler,
+    I2C1_EV_IRQHandler,
+    I2C1_ER_IRQHandler,
+    I2C2_EV_IRQHandler,
+    I2C2_ER_IRQHandler,
+    SPI1_IRQHandler,
+    SPI2_IRQHandler,
+    USART1_IRQHandler,
+    USART2_IRQHandler,
+    USART3_IRQHandler,
+    EXTI15_10_IRQHandler,
+    RTC_Alarm_IRQHandler,
+    OTG_FS_WKUP_IRQHandler,
+    TIM8_BRK_TIM12_IRQHandler,
+    TIM8_UP_TIM13_IRQHandler,
+    TIM8_TRG_COM_TIM14_IRQHandler,
+    TIM8_CC_IRQHandler,
+    DMA1_Stream7_IRQHandler,
+    FMC_IRQHandler,
+    SDMMC1_IRQHandler,
+    TIM5_IRQHandler,
+    SPI3_IRQHandler,
+    UART4_IRQHandler,
+    UART5_IRQHandler,
+    TIM6_DAC_IRQHandler,
+    TIM7_IRQHandler,
+    DMA2_Stream0_IRQHandler,
+    DMA2_Stream1_IRQHandler,
+    DMA2_Stream2_IRQHandler,
+    DMA2_Stream3_IRQHandler,
+    DMA2_Stream4_IRQHandler,
+    ETH_IRQHandler,
+    ETH_WKUP_IRQHandler,
+    CAN2_TX_IRQHandler,
+    CAN2_RX0_IRQHandler,
+    CAN2_RX1_IRQHandler,
+    CAN2_SCE_IRQHandler,
+    OTG_FS_IRQHandler,
+    DMA2_Stream5_IRQHandler,
+    DMA2_Stream6_IRQHandler,
+    DMA2_Stream7_IRQHandler,
+    USART6_IRQHandler,
+    I2C3_EV_IRQHandler,
+    I2C3_ER_IRQHandler,
+    OTG_HS_EP1_OUT_IRQHandler,
+    OTG_HS_EP1_IN_IRQHandler,
+    OTG_HS_WKUP_IRQHandler,
+    OTG_HS_IRQHandler,
+    DCMI_IRQHandler,
+    CRYP_IRQHandler,
+    RNG_IRQHandler,
+    FPU_IRQHandler,
+    UART7_IRQHandler,
+    UART8_IRQHandler,
+    SPI4_IRQHandler,
+    SPI5_IRQHandler,
+    SPI6_IRQHandler,
+    SAI1_IRQHandler,
+    LTDC_IRQHandler,
+    LTDC_ER_IRQHandler,
+    DMA2D_IRQHandler,
+    SAI2_IRQHandler,
+    QUADSPI_IRQHandler,
+    LPTIM1_IRQHandler,
+    CEC_IRQHandler,
+    I2C4_EV_IRQHandler,
+    I2C4_ER_IRQHandler,
+    SPDIF_RX_IRQHandler,
+    DSIHOST_IRQHandler,
+    DFSDM1_FLT0_IRQHandler,
+    DFSDM1_FLT1_IRQHandler,
+    DFSDM1_FLT2_IRQHandler,
+    DFSDM1_FLT3_IRQHandler,
+    SDMMC2_IRQHandler,
+    CAN3_TX_IRQHandler,
+    CAN3_RX0_IRQHandler,
+    CAN3_RX1_IRQHandler,
+    CAN3_SCE_IRQHandler,
+    JPEG_IRQHandler,
+    MDIOS_IRQHandler,
+};
+
+#pragma weak SysTick_IRQHandler            = Default_Handler
+#pragma weak WWDG_IRQHandler               = Default_Handler
+#pragma weak PVD_IRQHandler                = Default_Handler
+#pragma weak TAMP_STAMP_IRQHandler         = Default_Handler
+#pragma weak RTC_WKUP_IRQHandler           = Default_Handler
+#pragma weak FLASH_IRQHandler              = Default_Handler
+#pragma weak RCC_IRQHandler                = Default_Handler
+#pragma weak EXTI0_IRQHandler              = Default_Handler
+#pragma weak EXTI1_IRQHandler              = Default_Handler
+#pragma weak EXTI2_IRQHandler              = Default_Handler
+#pragma weak EXTI3_IRQHandler              = Default_Handler
+#pragma weak EXTI4_IRQHandler              = Default_Handler
+#pragma weak DMA1_Stream0_IRQHandler       = Default_Handler
+#pragma weak DMA1_Stream1_IRQHandler       = Default_Handler
+#pragma weak DMA1_Stream2_IRQHandler       = Default_Handler
+#pragma weak DMA1_Stream3_IRQHandler       = Default_Handler
+#pragma weak DMA1_Stream4_IRQHandler       = Default_Handler
+#pragma weak DMA1_Stream5_IRQHandler       = Default_Handler
+#pragma weak DMA1_Stream6_IRQHandler       = Default_Handler
+#pragma weak ADC_IRQHandler                = Default_Handler
+#pragma weak CAN1_TX_IRQHandler            = Default_Handler
+#pragma weak CAN1_RX0_IRQHandler           = Default_Handler
+#pragma weak CAN1_RX1_IRQHandler           = Default_Handler
+#pragma weak CAN1_SCE_IRQHandler           = Default_Handler
+#pragma weak EXTI9_5_IRQHandler            = Default_Handler
+#pragma weak TIM1_BRK_TIM9_IRQHandler      = Default_Handler
+#pragma weak TIM1_UP_TIM10_IRQHandler      = Default_Handler
+#pragma weak TIM1_TRG_COM_TIM11_IRQHandler = Default_Handler
+#pragma weak TIM1_CC_IRQHandler            = Default_Handler
+#pragma weak TIM2_IRQHandler               = Default_Handler
+#pragma weak TIM3_IRQHandler               = Default_Handler
+#pragma weak TIM4_IRQHandler               = Default_Handler
+#pragma weak I2C1_EV_IRQHandler            = Default_Handler
+#pragma weak I2C1_ER_IRQHandler            = Default_Handler
+#pragma weak I2C2_EV_IRQHandler            = Default_Handler
+#pragma weak I2C2_ER_IRQHandler            = Default_Handler
+#pragma weak SPI1_IRQHandler               = Default_Handler
+#pragma weak SPI2_IRQHandler               = Default_Handler
+// #pragma weak USART1_IRQHandler             = Default_Handler
+// #pragma weak USART2_IRQHandler             = Default_Handler
+// #pragma weak USART3_IRQHandler             = Default_Handler
+#pragma weak EXTI15_10_IRQHandler          = Default_Handler
+#pragma weak RTC_Alarm_IRQHandler          = Default_Handler
+#pragma weak OTG_FS_WKUP_IRQHandler        = Default_Handler
+#pragma weak TIM8_BRK_TIM12_IRQHandler     = Default_Handler
+#pragma weak TIM8_UP_TIM13_IRQHandler      = Default_Handler
+#pragma weak TIM8_TRG_COM_TIM14_IRQHandler = Default_Handler
+#pragma weak TIM8_CC_IRQHandler            = Default_Handler
+#pragma weak DMA1_Stream7_IRQHandler       = Default_Handler
+#pragma weak FMC_IRQHandler                = Default_Handler
+#pragma weak SDMMC1_IRQHandler             = Default_Handler
+#pragma weak TIM5_IRQHandler               = Default_Handler
+#pragma weak SPI3_IRQHandler               = Default_Handler
+// #pragma weak UART4_IRQHandler              = Default_Handler
+// #pragma weak UART5_IRQHandler              = Default_Handler
+#pragma weak TIM6_DAC_IRQHandler     = Default_Handler
+#pragma weak TIM7_IRQHandler         = Default_Handler
+#pragma weak DMA2_Stream0_IRQHandler = Default_Handler
+#pragma weak DMA2_Stream1_IRQHandler = Default_Handler
+#pragma weak DMA2_Stream2_IRQHandler = Default_Handler
+#pragma weak DMA2_Stream3_IRQHandler = Default_Handler
+#pragma weak DMA2_Stream4_IRQHandler = Default_Handler
+#pragma weak ETH_IRQHandler          = Default_Handler
+#pragma weak ETH_WKUP_IRQHandler     = Default_Handler
+#pragma weak CAN2_TX_IRQHandler      = Default_Handler
+#pragma weak CAN2_RX0_IRQHandler     = Default_Handler
+#pragma weak CAN2_RX1_IRQHandler     = Default_Handler
+#pragma weak CAN2_SCE_IRQHandler     = Default_Handler
+#pragma weak OTG_FS_IRQHandler       = Default_Handler
+#pragma weak DMA2_Stream5_IRQHandler = Default_Handler
+#pragma weak DMA2_Stream6_IRQHandler = Default_Handler
+#pragma weak DMA2_Stream7_IRQHandler = Default_Handler
+// #pragma weak USART6_IRQHandler             = Default_Handler
+#pragma weak I2C3_EV_IRQHandler        = Default_Handler
+#pragma weak I2C3_ER_IRQHandler        = Default_Handler
+#pragma weak OTG_HS_EP1_OUT_IRQHandler = Default_Handler
+#pragma weak OTG_HS_EP1_IN_IRQHandler  = Default_Handler
+#pragma weak OTG_HS_WKUP_IRQHandler    = Default_Handler
+#pragma weak OTG_HS_IRQHandler         = Default_Handler
+#pragma weak DCMI_IRQHandler           = Default_Handler
+#pragma weak CRYP_IRQHandler           = Default_Handler
+#pragma weak RNG_IRQHandler            = Default_Handler
+#pragma weak FPU_IRQHandler            = Default_Handler
+// #pragma weak UART7_IRQHandler              = Default_Handler
+// #pragma weak UART8_IRQHandler              = Default_Handler
+#pragma weak SPI4_IRQHandler        = Default_Handler
+#pragma weak SPI5_IRQHandler        = Default_Handler
+#pragma weak SPI6_IRQHandler        = Default_Handler
+#pragma weak SAI1_IRQHandler        = Default_Handler
+#pragma weak LTDC_IRQHandler        = Default_Handler
+#pragma weak LTDC_ER_IRQHandler     = Default_Handler
+#pragma weak DMA2D_IRQHandler       = Default_Handler
+#pragma weak SAI2_IRQHandler        = Default_Handler
+#pragma weak QUADSPI_IRQHandler     = Default_Handler
+#pragma weak LPTIM1_IRQHandler      = Default_Handler
+#pragma weak CEC_IRQHandler         = Default_Handler
+#pragma weak I2C4_EV_IRQHandler     = Default_Handler
+#pragma weak I2C4_ER_IRQHandler     = Default_Handler
+#pragma weak SPDIF_RX_IRQHandler    = Default_Handler
+#pragma weak DSIHOST_IRQHandler     = Default_Handler
+#pragma weak DFSDM1_FLT0_IRQHandler = Default_Handler
+#pragma weak DFSDM1_FLT1_IRQHandler = Default_Handler
+#pragma weak DFSDM1_FLT2_IRQHandler = Default_Handler
+#pragma weak DFSDM1_FLT3_IRQHandler = Default_Handler
+#pragma weak SDMMC2_IRQHandler      = Default_Handler
+#pragma weak CAN3_TX_IRQHandler     = Default_Handler
+#pragma weak CAN3_RX0_IRQHandler    = Default_Handler
+#pragma weak CAN3_RX1_IRQHandler    = Default_Handler
+#pragma weak CAN3_SCE_IRQHandler    = Default_Handler
+#pragma weak JPEG_IRQHandler        = Default_Handler
+#pragma weak MDIOS_IRQHandler       = Default_Handler
diff --git a/src/bsps/stm32f767zi_compute_unit_v2/interfaces-impl/arch_registers_impl.h b/src/bsps/stm32f767zi_compute_unit_v2/interfaces-impl/arch_registers_impl.h
new file mode 100644
index 0000000000000000000000000000000000000000..3224edc9ea21e7d207549879bb48cac84b71defe
--- /dev/null
+++ b/src/bsps/stm32f767zi_compute_unit_v2/interfaces-impl/arch_registers_impl.h
@@ -0,0 +1,39 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
+ *
+ * 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.
+ */
+
+#ifndef ARCH_REGISTERS_IMPL_H
+#define ARCH_REGISTERS_IMPL_H
+
+// stm32f7xx.h defines a few macros like __ICACHE_PRESENT, __DCACHE_PRESENT and
+// includes core_cm7.h. Do not include core_cm7.h before.
+#define STM32F767xx
+#include "CMSIS/Device/ST/STM32F7xx/Include/stm32f7xx.h"
+
+#if (__ICACHE_PRESENT != 1) || (__DCACHE_PRESENT != 1)
+#error "Wrong include order"
+#endif
+
+#include "CMSIS/Device/ST/STM32F7xx/Include/system_stm32f7xx.h"
+
+#define RCC_SYNC() __DSB()  // TODO: can this dsb be removed?
+
+#endif  // ARCH_REGISTERS_IMPL_H
diff --git a/src/bsps/stm32f767zi_compute_unit_v2/interfaces-impl/bsp.cpp b/src/bsps/stm32f767zi_compute_unit_v2/interfaces-impl/bsp.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..366e5457e1fa63b17abf93c563edae9ce5755213
--- /dev/null
+++ b/src/bsps/stm32f767zi_compute_unit_v2/interfaces-impl/bsp.cpp
@@ -0,0 +1,304 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio, Davide Mor
+ *
+ * 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.
+ */
+
+/***********************************************************************
+ * bsp.cpp Part of the Miosix Embedded OS.
+ * Board support package, this file initializes hardware.
+ ************************************************************************/
+
+#include "interfaces/bsp.h"
+
+#include <inttypes.h>
+#include <sys/ioctl.h>
+
+#include <cstdlib>
+
+#include "board_settings.h"
+#include "config/miosix_settings.h"
+#include "drivers/sd_stm32f2_f4_f7.h"
+#include "drivers/serial.h"
+#include "drivers/serial_stm32.h"
+#include "drivers/stm32_sgm.h"
+#include "filesystem/console/console_device.h"
+#include "filesystem/file_access.h"
+#include "interfaces/arch_registers.h"
+#include "interfaces/delays.h"
+#include "interfaces/portability.h"
+#include "kernel/kernel.h"
+#include "kernel/logging.h"
+#include "kernel/sync.h"
+
+namespace miosix
+{
+
+//
+// Initialization
+//
+
+static void sdramCommandWait()
+{
+    for (int i = 0; i < 0xffff; i++)
+        if ((FMC_Bank5_6->SDSR & FMC_SDSR_BUSY) == 0)
+            return;
+}
+
+void configureSdram()
+{
+    // Enable gpios used by the ram
+    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN | RCC_AHB1ENR_GPIOCEN |
+                    RCC_AHB1ENR_GPIODEN | RCC_AHB1ENR_GPIOEEN |
+                    RCC_AHB1ENR_GPIOFEN | RCC_AHB1ENR_GPIOGEN;
+    RCC_SYNC();
+
+    // On the compute unit with F767ZI, the SDRAM pins are:
+    // - PG8:  FMC_SDCLK  (sdram clock)
+    // - PB5:  FMC_SDCKE1 (sdram bank 2 clock enable)
+    // - PB6:  FMC_SDNE1  (sdram bank 2 chip enable)
+    // - PF0:  FMC_A0
+    // - PF1:  FMC_A1
+    // - PF2:  FMC_A2
+    // - PF3:  FMC_A3
+    // - PF4:  FMC_A4
+    // - PF5:  FMC_A5
+    // - PF12: FMC_A6
+    // - PF13: FMC_A7
+    // - PF14: FMC_A8
+    // - PF15: FMC_A9
+    // - PG0:  FMC_A10
+    // - PG1:  FMC_A11
+    // - PG2:  FMC_A12    (used only by the 32MB ram, not by the 8MB one)
+    // - PD14: FMC_D0
+    // - PD15: FMC_D1
+    // - PD0:  FMC_D2
+    // - PD1:  FMC_D3
+    // - PE7:  FMC_D4
+    // - PE8:  FMC_D5
+    // - PE9:  FMC_D6
+    // - PE10: FMC_D7
+    // - PE11: FMC_D8
+    // - PE12: FMC_D9
+    // - PE13: FMC_D10
+    // - PE14: FMC_D11
+    // - PE15: FMC_D12
+    // - PD8:  FMC_D13
+    // - PD9:  FMC_D14
+    // - PD10: FMC_D15
+
+    // - PG4:  FMC_BA0
+    // - PG5:  FMC_BA1
+    // - PF11: FMC_SDNRAS
+    // - PG15: FMC_SDNCAS
+    // - PC0:  FMC_SDNWE
+    // - PE0:  FMC_NBL0
+    // - PE1:  FMC_NBL1
+
+    // All SDRAM GPIOs needs to be configured with alternate function 12 and
+    // maximum speed
+
+    // WARNING: The current configuration is for the 8MB ram
+
+    // Alternate functions
+    GPIOB->AFR[0] = 0x0cc00000;
+    GPIOC->AFR[0] = 0x0000000c;
+    GPIOD->AFR[0] = 0x000000cc;
+    GPIOD->AFR[1] = 0xcc000ccc;
+    GPIOE->AFR[0] = 0xc00000cc;
+    GPIOE->AFR[1] = 0xcccccccc;
+    GPIOF->AFR[0] = 0x00cccccc;
+    GPIOF->AFR[1] = 0xccccc000;
+    GPIOG->AFR[0] = 0x00cc0ccc;
+    GPIOG->AFR[1] = 0xc000000c;
+
+    // Mode
+    GPIOB->MODER = 0x00002800;
+    GPIOC->MODER = 0x00000002;
+    GPIOD->MODER = 0xa02a000a;
+    GPIOE->MODER = 0xaaaa800a;
+    GPIOF->MODER = 0xaa800aaa;
+    GPIOG->MODER = 0x80020a2a;
+
+    // Speed (high speed for all, very high speed for SDRAM pins)
+    GPIOB->OSPEEDR = 0x00003c00;
+    GPIOC->OSPEEDR = 0x00000003;
+    GPIOD->OSPEEDR = 0xf03f000f;
+    GPIOE->OSPEEDR = 0xffffc00f;
+    GPIOF->OSPEEDR = 0xffc00fff;
+    GPIOG->OSPEEDR = 0xc0030f3f;
+
+    // Since we'we un-configured PB3 and PB4 (by default they are SWO and NJRST)
+    // finish the job and remove the default pull-up
+    GPIOB->PUPDR = 0;
+
+    // Enable the SDRAM controller clock
+    RCC->AHB3ENR |= RCC_AHB3ENR_FMCEN;
+    RCC_SYNC();
+
+    // The SDRAM is a AS4C16M16SA-6TIN
+    // 16Mx16bit = 256Mb = 32MB
+    // HCLK = 216MHz -> SDRAM clock = HCLK/2 = 108MHz
+
+    // 1. Memory device features
+    FMC_Bank5_6->SDCR[0] = 0                     // 0 delay after CAS latency
+                           | FMC_SDCR1_RBURST    // Enable read bursts
+                           | FMC_SDCR1_SDCLK_1;  // SDCLK = HCLK / 2
+    FMC_Bank5_6->SDCR[1] = 0                     // Write accesses allowed
+                           | FMC_SDCR2_CAS_1     // 2 cycles CAS latency
+                           | FMC_SDCR2_NB        // 4 internal banks
+                           | FMC_SDCR2_MWID_0    // 16 bit data bus
+                           | FMC_SDCR2_NR_1      // 13 bit row address
+                           | FMC_SDCR2_NC_0;     // 9 bit column address
+
+// 2. Memory device timings
+#ifdef SYSCLK_FREQ_216MHz
+    // SDRAM timings. One clock cycle is 9.26ns
+    FMC_Bank5_6->SDTR[0] =
+        (2 - 1) << FMC_SDTR1_TRP_Pos     // 2 cycles TRP  (18.52ns > 18ns)
+        | (7 - 1) << FMC_SDTR1_TRC_Pos;  // 7 cycles TRC  (64.82ns > 60ns)
+    FMC_Bank5_6->SDTR[1] =
+        (2 - 1) << FMC_SDTR1_TRCD_Pos     // 2 cycles TRCD (18.52ns > 18ns)
+        | (2 - 1) << FMC_SDTR1_TWR_Pos    // 2 cycles TWR  (min 2cc > 12ns)
+        | (5 - 1) << FMC_SDTR1_TRAS_Pos   // 5 cycles TRAS (46.3ns  > 42ns)
+        | (7 - 1) << FMC_SDTR1_TXSR_Pos   // 7 cycles TXSR (74.08ns > 61.5ns)
+        | (2 - 1) << FMC_SDTR1_TMRD_Pos;  // 2 cycles TMRD (min 2cc > 12ns)
+#else
+#error No SDRAM timings for this clock
+#endif
+
+    // 3. Enable the bank 2 clock
+    FMC_Bank5_6->SDCMR =
+        0b001 << FMC_SDCMR_MODE_Pos  // Clock Configuration Enable
+        | FMC_SDCMR_CTB2;            // Bank 2
+    sdramCommandWait();
+
+    // 4. Wait during command execution
+    delayUs(100);
+
+    // 5. Issue a "Precharge All" command
+    FMC_Bank5_6->SDCMR = 0b010 << FMC_SDCMR_MODE_Pos  // Precharge all
+                         | FMC_SDCMR_CTB2;            // Bank 2
+    sdramCommandWait();
+
+    // 6. Issue Auto-Refresh commands
+    FMC_Bank5_6->SDCMR = 0b011 << FMC_SDCMR_MODE_Pos       // Auto-Refresh
+                         | FMC_SDCMR_CTB2                  // Bank 2
+                         | (8 - 1) << FMC_SDCMR_NRFS_Pos;  // 8 Auto-Refresh
+    sdramCommandWait();
+
+    // 7. Issue a Load Mode Register command
+    FMC_Bank5_6->SDCMR =
+        0b100 << FMC_SDCMR_MODE_Pos          // Load mode register
+        | FMC_SDCMR_CTB2                     // Bank 2
+        | 0 << FMC_SDCMR_MRD_Pos             // Burst length = 1
+        | (0b010 << 4) << FMC_SDCMR_MRD_Pos  // CAS = 2 clocks,
+        | (1 << 9) << FMC_SDCMR_MRD_Pos;     // Single bit write burst mode
+    sdramCommandWait();
+
+// 8. Program the refresh rate (4K / 32ms)
+// 64ms / 8192 = 7.8125us
+#ifdef SYSCLK_FREQ_216MHz
+    // 7.8125us * 133MHz = 1039 - 20 = 1019
+    FMC_Bank5_6->SDRTR = 1019 << FMC_SDRTR_COUNT_Pos;
+#else
+#error No SDRAM refresh timings for this clock
+#endif
+}
+
+void IRQbspInit()
+{
+    // Enable USART1 pins port
+    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
+
+    userLed1::mode(Mode::OUTPUT);
+    userLed2::mode(Mode::OUTPUT);
+    userLed3::mode(Mode::OUTPUT);
+    userLed4::mode(Mode::OUTPUT);
+
+    DefaultConsole::instance().IRQset(intrusive_ref_ptr<Device>(new STM32Serial(
+        defaultSerial, defaultSerialSpeed, STM32Serial::NOFLOWCTRL)));
+}
+
+void bspInit2()
+{
+#ifdef WITH_FILESYSTEM
+    basicFilesystemSetup(SDIODriver::instance());
+#endif  // WITH_FILESYSTEM
+
+#ifdef WITH_BACKUP_SRAM
+    // Print the reset reason
+    bootlog("Reset reson: ");
+    switch (SGM::instance().lastResetReason())
+    {
+        case ResetReason::RST_LOW_PWR:
+            bootlog("low power\n");
+            break;
+        case ResetReason::RST_WINDOW_WDG:
+            bootlog("window watchdog\n");
+            break;
+        case ResetReason::RST_INDEPENDENT_WDG:
+            bootlog("indeendent watchdog\n");
+            break;
+        case ResetReason::RST_SW:
+            bootlog("software reset\n");
+            break;
+        case ResetReason::RST_POWER_ON:
+            bootlog("power on\n");
+            break;
+        case ResetReason::RST_PIN:
+            bootlog("reset pin\n");
+            break;
+        case ResetReason::RST_UNKNOWN:
+            bootlog("unknown\n");
+            break;
+    }
+#endif  // WITH_BACKUP_SRAM
+}
+
+//
+// Shutdown and reboot
+//
+
+void shutdown()
+{
+    ioctl(STDOUT_FILENO, IOCTL_SYNC, 0);
+
+#ifdef WITH_FILESYSTEM
+    FilesystemManager::instance().umountAll();
+#endif  // WITH_FILESYSTEM
+
+    disableInterrupts();
+    for (;;)
+        ;
+}
+
+void reboot()
+{
+    ioctl(STDOUT_FILENO, IOCTL_SYNC, 0);
+
+#ifdef WITH_FILESYSTEM
+    FilesystemManager::instance().umountAll();
+#endif  // WITH_FILESYSTEM
+
+    disableInterrupts();
+    miosix_private::IRQsystemReboot();
+}
+
+}  // namespace miosix
diff --git a/src/bsps/stm32f767zi_compute_unit_v2/interfaces-impl/bsp_impl.h b/src/bsps/stm32f767zi_compute_unit_v2/interfaces-impl/bsp_impl.h
new file mode 100644
index 0000000000000000000000000000000000000000..feb02c1280930087540af07157270b0ac858f57c
--- /dev/null
+++ b/src/bsps/stm32f767zi_compute_unit_v2/interfaces-impl/bsp_impl.h
@@ -0,0 +1,108 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Davide Mor
+ *
+ * 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.
+ */
+
+/***************************************************************************
+ * bsp_impl.h Part of the Miosix Embedded OS.
+ * Board support package, this file initializes hardware.
+ ***************************************************************************/
+
+#ifndef BSP_IMPL_H
+#define BSP_IMPL_H
+
+#include "config/miosix_settings.h"
+#include "interfaces/gpio.h"
+
+namespace miosix
+{
+
+/**
+\addtogroup Hardware
+\{
+*/
+
+/**
+ * \internal
+ * Called by stage_1_boot.cpp to enable the SDRAM before initializing .data/.bss
+ * Requires the CPU clock to be already configured (running from the PLL)
+ */
+void configureSdram();
+
+/**
+ * \internal
+ * Board pin definition
+ */
+typedef Gpio<GPIOC_BASE, 15> userLed1;
+typedef Gpio<GPIOC_BASE, 2> userLed2;
+typedef Gpio<GPIOC_BASE, 13> userLed3;
+typedef Gpio<GPIOC_BASE, 14> userLed4;
+
+inline void led1On() { userLed1::high(); }
+
+inline void led1Off() { userLed1::low(); }
+
+inline void led2On() { userLed2::high(); }
+
+inline void led2Off() { userLed2::low(); }
+
+inline void led3On() { userLed3::high(); }
+
+inline void led3Off() { userLed3::low(); }
+
+inline void led4On() { userLed4::high(); }
+
+inline void led4Off() { userLed4::low(); }
+
+inline void ledOn()
+{
+    led1On();
+    led2On();
+    led3On();
+    led4On();
+}
+
+inline void ledOff()
+{
+    led1Off();
+    led2Off();
+    led3Off();
+    led4Off();
+}
+
+/**
+ * Polls the SD card sense GPIO.
+ *
+ * This board has no SD card whatsoever, but a card can be connected to the
+ * following GPIOs:
+ * TODO: never tested
+ *
+ * \return true. As there's no SD card sense switch, let's pretend that
+ * the card is present.
+ */
+inline bool sdCardSense() { return true; }
+
+/**
+\}
+*/
+
+}  // namespace miosix
+
+#endif  // BSP_IMPL_H
diff --git a/src/bsps/stm32f767zi_compute_unit_v2/stm32_2m+32m_xram.ld b/src/bsps/stm32f767zi_compute_unit_v2/stm32_2m+32m_xram.ld
new file mode 100644
index 0000000000000000000000000000000000000000..044ba1c62814ca2a4081a49fa5bca4d686ca16d5
--- /dev/null
+++ b/src/bsps/stm32f767zi_compute_unit_v2/stm32_2m+32m_xram.ld
@@ -0,0 +1,190 @@
+/*
+ * C++ enabled linker script for stm32f767zi (2M FLASH, 512K RAM, 32MB XRAM)
+ * Developed by TFT: Terraneo Federico Technologies
+ * Optimized for use with the Miosix kernel
+ */
+
+/*
+ * This linker script puts:
+ * - read only data and code (.text, .rodata, .eh_*) in FLASH
+ * - the 512Byte main (IRQ) stack, .data and .bss in the DTCM 128KB RAM
+ * - .data, .bss, stacks and heap in the external 32MB SDRAM.
+ */
+
+/*
+ * The main stack is used for interrupt handling by the kernel.
+ *
+ * *** Readme ***
+ * This linker script places the main stack (used by the kernel for interrupts)
+ * at the bottom of the ram, instead of the top. This is done for two reasons:
+ *
+ * - as an optimization for microcontrollers with little ram memory. In fact
+ *   the implementation of malloc from newlib requests memory to the OS in 4KB
+ *   block (except the first block that can be smaller). This is probably done
+ *   for compatibility with OSes with an MMU and paged memory. To see why this
+ *   is bad, consider a microcontroller with 8KB of ram: when malloc finishes
+ *   up the first 4KB it will call _sbrk_r asking for a 4KB block, but this will
+ *   fail because the top part of the ram is used by the main stack. As a
+ *   result, the top part of the memory will not be used by malloc, even if
+ *   available (and it is nearly *half* the ram on an 8KB mcu). By placing the
+ *   main stack at the bottom of the ram, the upper 4KB block will be entirely
+ *   free and available as heap space.
+ *
+ * - In case of main stack overflow the cpu will fault because access to memory
+ *   before the beginning of the ram faults. Instead with the default stack
+ *   placement the main stack will silently collide with the heap.
+ * Note: if increasing the main stack size also increase the ORIGIN value in
+ * the MEMORY definitions below accordingly.
+ */
+
+_main_stack_size = 512;                             /* main stack = 512Bytes */
+_main_stack_top  = 0x20000000 + _main_stack_size;
+ASSERT(_main_stack_size   % 8 == 0, "MAIN stack size error");
+
+/* Mapping the heap into XRAM */
+_heap_end = 0xd0000000 + 32M;                        /* end of available ram */
+
+/* Identify the Entry Point  */
+ENTRY(_Z13Reset_Handlerv)
+
+/*
+ * Specify the memory areas
+ *
+ * NOTE: starting at 0x20000000 there's 128KB of DTCM (Data Tightly Coupled
+ * Memory). Technically, we could use this as normal RAM as there's a way for
+ * the DMA to access it, but the datasheet is unclear about performance
+ * penalties for doing so. To avoid nonuniform DMA memory access latencies,
+ * we leave this 128KB DTCM unused except for the first 512Bytes which are for
+ * the interrupt stack. This leaves us with 384KB of RAM
+ */
+MEMORY
+{
+    xram(wx)  : ORIGIN = 0xd0000000, LENGTH =  32M
+    sram(wx)  : ORIGIN = 0x20020000, LENGTH = 384K
+    dtcm(wx)  : ORIGIN = 0x20000000, LENGTH = 128K    /* Used for main stack */
+    bram(rw)  : ORIGIN = 0x40024000, LENGTH =   4K    /* Bakup SRAM */
+    flash(rx) : ORIGIN = 0x08000000, LENGTH =   2M
+}
+
+/* now define the output sections  */
+SECTIONS
+{
+    . = 0;
+    
+    /* .text section: code goes to flash */
+    .text :
+    {
+        /* Startup code must go at address 0 */
+        KEEP(*(.isr_vector))
+        
+        *(.text)
+        *(.text.*)
+        *(.gnu.linkonce.t.*)
+        /* these sections for thumb interwork? */
+        *(.glue_7)
+        *(.glue_7t)
+        /* these sections for C++? */
+        *(.gcc_except_table)
+        *(.gcc_except_table.*)
+        *(.ARM.extab*)
+        *(.gnu.linkonce.armextab.*)
+
+        . = ALIGN(4);
+        /* .rodata: constant data */
+        *(.rodata)
+        *(.rodata.*)
+        *(.gnu.linkonce.r.*)
+
+        /* C++ Static constructors/destructors (eabi) */
+        . = ALIGN(4);
+        KEEP(*(.init))
+        
+        . = ALIGN(4);
+        __miosix_init_array_start = .;
+        KEEP (*(SORT(.miosix_init_array.*)))
+        KEEP (*(.miosix_init_array))
+        __miosix_init_array_end = .;
+
+        . = ALIGN(4);
+        __preinit_array_start = .;
+        KEEP (*(.preinit_array))
+        __preinit_array_end = .;
+
+        . = ALIGN(4);
+        __init_array_start = .;
+        KEEP (*(SORT(.init_array.*)))
+        KEEP (*(.init_array))
+        __init_array_end = .;
+
+        . = ALIGN(4);
+        KEEP(*(.fini))
+
+        . = ALIGN(4);
+        __fini_array_start = .;
+        KEEP (*(.fini_array))
+        KEEP (*(SORT(.fini_array.*)))
+        __fini_array_end = .;
+
+        /* C++ Static constructors/destructors (elf)  */
+        . = ALIGN(4);
+        _ctor_start = .;
+        KEEP (*crtbegin.o(.ctors))
+        KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors))
+        KEEP (*(SORT(.ctors.*)))
+        KEEP (*crtend.o(.ctors))
+       _ctor_end = .;
+
+        . = ALIGN(4);
+        KEEP (*crtbegin.o(.dtors))
+        KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors))
+        KEEP (*(SORT(.dtors.*)))
+        KEEP (*crtend.o(.dtors))
+    } > flash
+
+    /* .ARM.exidx is sorted, so has to go in its own output section.  */
+    __exidx_start = .;
+    .ARM.exidx :
+    {
+        *(.ARM.exidx* .gnu.linkonce.armexidx.*)
+    } > flash
+    __exidx_end = .;
+
+	/*
+     * .data section: global variables go to xram, but also store a copy to
+     * flash to initialize them
+     */
+    .data : ALIGN(8)
+    {
+        _data = .;
+        *(.data)
+        *(.data.*)
+        *(.gnu.linkonce.d.*)
+        . = ALIGN(8);
+        _edata = .;
+    } > xram AT > flash
+    _etext = LOADADDR(.data);
+
+    /* .bss section: uninitialized global variables go to xram */
+    _bss_start = .;
+    .bss :
+    {
+        *(.bss)
+        *(.bss.*)
+        *(.gnu.linkonce.b.*)
+        . = ALIGN(8);
+    } > xram
+    _bss_end = .;
+
+    _end = .;
+    PROVIDE(end = .);
+    
+    .preserve(NOLOAD) : ALIGN(4)
+    {
+        _preserve_start = .;
+        . = ALIGN(4);
+        *(.preserve);
+        *(.preserve*);
+        . = ALIGN(4);
+        _preserve_end = .;
+    } > bram
+}
diff --git a/src/bsps/stm32f767zi_compute_unit_v2/stm32_2m+384k_ram.ld b/src/bsps/stm32f767zi_compute_unit_v2/stm32_2m+384k_ram.ld
new file mode 100644
index 0000000000000000000000000000000000000000..497bd5198760b9637c774bf3900fd9dfbc01a2b2
--- /dev/null
+++ b/src/bsps/stm32f767zi_compute_unit_v2/stm32_2m+384k_ram.ld
@@ -0,0 +1,189 @@
+/*
+ * C++ enabled linker script for stm32f767zi (2M FLASH, 512K RAM)
+ * Developed by TFT: Terraneo Federico Technologies
+ * Optimized for use with the Miosix kernel
+ */
+
+/*
+ * This linker script puts:
+ * - read only data and code (.text, .rodata, .eh_*) in FLASH
+ * - the 512Byte main (IRQ) stack, .data and .bss in the DTCM 128KB RAM
+ * - .data, .bss, stacks and heap in the internal RAM.
+ */
+
+/*
+ * The main stack is used for interrupt handling by the kernel.
+ *
+ * *** Readme ***
+ * This linker script places the main stack (used by the kernel for interrupts)
+ * at the bottom of the ram, instead of the top. This is done for two reasons:
+ *
+ * - as an optimization for microcontrollers with little ram memory. In fact
+ *   the implementation of malloc from newlib requests memory to the OS in 4KB
+ *   block (except the first block that can be smaller). This is probably done
+ *   for compatibility with OSes with an MMU and paged memory. To see why this
+ *   is bad, consider a microcontroller with 8KB of ram: when malloc finishes
+ *   up the first 4KB it will call _sbrk_r asking for a 4KB block, but this will
+ *   fail because the top part of the ram is used by the main stack. As a
+ *   result, the top part of the memory will not be used by malloc, even if
+ *   available (and it is nearly *half* the ram on an 8KB mcu). By placing the
+ *   main stack at the bottom of the ram, the upper 4KB block will be entirely
+ *   free and available as heap space.
+ *
+ * - In case of main stack overflow the cpu will fault because access to memory
+ *   before the beginning of the ram faults. Instead with the default stack
+ *   placement the main stack will silently collide with the heap.
+ * Note: if increasing the main stack size also increase the ORIGIN value in
+ * the MEMORY definitions below accordingly.
+ */
+
+_main_stack_size = 512;                             /* main stack = 512Bytes */
+_main_stack_top  = 0x20000000 + _main_stack_size;
+ASSERT(_main_stack_size   % 8 == 0, "MAIN stack size error");
+
+/* Mapping the heap to the end of SRAM2 */
+_heap_end = 0x20000000 + 512K;                       /* end of available ram */
+
+/* Identify the Entry Point  */
+ENTRY(_Z13Reset_Handlerv)
+
+/*
+ * Specify the memory areas
+ *
+ * NOTE: starting at 0x20000000 there's 128KB of DTCM (Data Tightly Coupled
+ * Memory). Technically, we could use this as normal RAM as there's a way for
+ * the DMA to access it, but the datasheet is unclear about performance
+ * penalties for doing so. To avoid nonuniform DMA memory access latencies,
+ * we leave this 128KB DTCM unused except for the first 512Bytes which are for
+ * the interrupt stack. This leaves us with 384KB of RAM
+ */
+MEMORY
+{
+    sram(wx)  : ORIGIN = 0x20020000, LENGTH = 384K
+    dtcm(wx)  : ORIGIN = 0x20000000, LENGTH = 128K    /* Used for main stack */
+    bram(rw)  : ORIGIN = 0x40024000, LENGTH =   4K    /* Bakup SRAM */
+    flash(rx) : ORIGIN = 0x08000000, LENGTH =   2M
+}
+
+/* now define the output sections  */
+SECTIONS
+{
+    . = 0;
+    
+    /* .text section: code goes to flash */
+    .text :
+    {
+        /* Startup code must go at address 0 */
+        KEEP(*(.isr_vector))
+        
+        *(.text)
+        *(.text.*)
+        *(.gnu.linkonce.t.*)
+        /* these sections for thumb interwork? */
+        *(.glue_7)
+        *(.glue_7t)
+        /* these sections for C++? */
+        *(.gcc_except_table)
+        *(.gcc_except_table.*)
+        *(.ARM.extab*)
+        *(.gnu.linkonce.armextab.*)
+
+        . = ALIGN(4);
+        /* .rodata: constant data */
+        *(.rodata)
+        *(.rodata.*)
+        *(.gnu.linkonce.r.*)
+
+        /* C++ Static constructors/destructors (eabi) */
+        . = ALIGN(4);
+        KEEP(*(.init))
+        
+        . = ALIGN(4);
+        __miosix_init_array_start = .;
+        KEEP (*(SORT(.miosix_init_array.*)))
+        KEEP (*(.miosix_init_array))
+        __miosix_init_array_end = .;
+
+        . = ALIGN(4);
+        __preinit_array_start = .;
+        KEEP (*(.preinit_array))
+        __preinit_array_end = .;
+
+        . = ALIGN(4);
+        __init_array_start = .;
+        KEEP (*(SORT(.init_array.*)))
+        KEEP (*(.init_array))
+        __init_array_end = .;
+
+        . = ALIGN(4);
+        KEEP(*(.fini))
+
+        . = ALIGN(4);
+        __fini_array_start = .;
+        KEEP (*(.fini_array))
+        KEEP (*(SORT(.fini_array.*)))
+        __fini_array_end = .;
+
+        /* C++ Static constructors/destructors (elf)  */
+        . = ALIGN(4);
+        _ctor_start = .;
+        KEEP (*crtbegin.o(.ctors))
+        KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors))
+        KEEP (*(SORT(.ctors.*)))
+        KEEP (*crtend.o(.ctors))
+       _ctor_end = .;
+
+        . = ALIGN(4);
+        KEEP (*crtbegin.o(.dtors))
+        KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors))
+        KEEP (*(SORT(.dtors.*)))
+        KEEP (*crtend.o(.dtors))
+    } > flash
+
+    /* .ARM.exidx is sorted, so has to go in its own output section.  */
+    __exidx_start = .;
+    .ARM.exidx :
+    {
+        *(.ARM.exidx* .gnu.linkonce.armexidx.*)
+    } > flash
+    __exidx_end = .;
+
+	/*
+     * .data section: global variables go to sram, but also store a copy to
+     * flash to initialize them
+     */
+    .data : ALIGN(8)
+    {
+        _data = .;
+        *(.data)
+        *(.data.*)
+        *(.gnu.linkonce.d.*)
+        . = ALIGN(8);
+        _edata = .;
+    } > sram AT > flash
+    _etext = LOADADDR(.data);
+
+    /* .bss section: uninitialized global variables go to sram */
+    _bss_start = .;
+    .bss :
+    {
+        *(.bss)
+        *(.bss.*)
+        *(.gnu.linkonce.b.*)
+        . = ALIGN(8);
+    } > sram
+    _bss_end = .;
+
+    _end = .;
+    PROVIDE(end = .);
+    
+    .preserve(NOLOAD) : ALIGN(4)
+    {
+        _preserve_start = .;
+        . = ALIGN(4);
+        *(.preserve);
+        *(.preserve*);
+        . = ALIGN(4);
+        _preserve_end = .;
+    } > bram
+}
diff --git a/src/entrypoints/compute-unit-v2-testsuite.cpp b/src/entrypoints/compute-unit-v2-testsuite.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..424240b46e0dc55bd0be96fc1f747fa3b20d78e5
--- /dev/null
+++ b/src/entrypoints/compute-unit-v2-testsuite.cpp
@@ -0,0 +1,580 @@
+/* Copyright (c) 2024 Skyward Experimental Rocketry
+ * Author: Davide Mor, Emilio Corigliano
+ *
+ * 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.
+ */
+#include <interfaces-impl/bsp_impl.h>
+#include <miosix.h>
+
+#include <array>
+#include <atomic>
+#include <cstdint>
+#include <cstdio>
+#include <thread>
+
+using namespace miosix;
+
+struct PinDef
+{
+    GpioPin pin;
+    const char *gpio_name;
+    const char *conan_name;
+    // ADRIANOOOOOOOOOOOOOOOO
+    const char *breakout_v1_name;
+};
+
+std::array<PinDef, 53> PIN_DEFS{{
+    {Gpio<GPIOA_BASE, 0>::getPin(), "PA0", "PIN57", "PIN13"},
+    {Gpio<GPIOA_BASE, 1>::getPin(), "PA1", "PIN55", "PIN15"},
+    {Gpio<GPIOA_BASE, 2>::getPin(), "PA2", "PIN62", "PIN8"},
+    {Gpio<GPIOA_BASE, 3>::getPin(), "PA3", "PIN60", "PIN10"},
+    {Gpio<GPIOA_BASE, 4>::getPin(), "PA4", "PIN58", "PIN12"},
+    {Gpio<GPIOA_BASE, 5>::getPin(), "PA5", "PIN56", "PIN14"},
+    {Gpio<GPIOA_BASE, 6>::getPin(), "PA6", "PIN54", "PIN16"},
+    {Gpio<GPIOA_BASE, 7>::getPin(), "PA7", "PIN52", "PIN18"},
+    {Gpio<GPIOA_BASE, 8>::getPin(), "PA8", "PIN33", "PIN37"},
+    // {Gpio<GPIOA_BASE, 9>::getPin(), "PA9", "PIN36", "PIN"}, Miosix serial
+    // {Gpio<GPIOA_BASE, 10>::getPin(), "PA10", "PIN34", "PIN"}, Miosix serial
+    {Gpio<GPIOA_BASE, 11>::getPin(), "PA11", "PIN27", "PIN43"},
+    {Gpio<GPIOA_BASE, 12>::getPin(), "PA12", "PIN30", "PIN40"},
+    // {Gpio<GPIOA_BASE, 13>::getPin(), "PA13", NULL, NULL}, SWDIO
+    // {Gpio<GPIOA_BASE, 14>::getPin(), "PA14", NULL, NULL}, SWCLK
+    {Gpio<GPIOA_BASE, 15>::getPin(), "PA15", "PIN32", "PIN38"},
+    {Gpio<GPIOB_BASE, 0>::getPin(), "PB0", "PIN53", "PIN17"},
+    {Gpio<GPIOB_BASE, 1>::getPin(), "PB1", "PIN51", "PIN19"},
+    {Gpio<GPIOB_BASE, 2>::getPin(), "PB2", "PIN65", "PIN5"},
+    {Gpio<GPIOB_BASE, 3>::getPin(), "PB3", "PIN15", "PIN55"},
+    {Gpio<GPIOB_BASE, 4>::getPin(), "PB4", "PIN17", "PIN53"},
+    // {Gpio<GPIOB_BASE, 5>::getPin(), "PB5", NULL, NULL}, RAM
+    // {Gpio<GPIOB_BASE, 6>::getPin(), "PB6", NULL, NULL}, RAM
+    {Gpio<GPIOB_BASE, 7>::getPin(), "PB7", "PIN14", "PIN56"},
+    {Gpio<GPIOB_BASE, 8>::getPin(), "PB8", "PIN13", "PIN57"},
+    {Gpio<GPIOB_BASE, 9>::getPin(), "PB9", "PIN11", "PIN59"},
+    // {Gpio<GPIOB_BASE, 10>::getPin(), "PB10", NULL}, Flash
+    {Gpio<GPIOB_BASE, 11>::getPin(), "PB11", "PIN49", "PIN21"},
+    {Gpio<GPIOB_BASE, 12>::getPin(), "PB12", "PIN47", "PIN23"},
+    {Gpio<GPIOB_BASE, 13>::getPin(), "PB13", "PIN45", "PIN25"},
+    {Gpio<GPIOB_BASE, 14>::getPin(), "PB14", "PIN43", "PIN27"},
+    {Gpio<GPIOB_BASE, 15>::getPin(), "PB15", "PIN46", "PIN24"},
+    // {Gpio<GPIOC_BASE, 0>::getPin(), "PC0", NULL, NULL}, RAM
+    {Gpio<GPIOC_BASE, 1>::getPin(), "PC1", "PIN63", "PIN7"},
+    // {Gpio<GPIOC_BASE, 2>::getPin(), "PC2", NULL, NULL}, LED
+    {Gpio<GPIOC_BASE, 3>::getPin(), "PC3", "PIN61", "PIN9"},
+    {Gpio<GPIOC_BASE, 4>::getPin(), "PC4", "PIN50", "PIN20"},
+    {Gpio<GPIOC_BASE, 5>::getPin(), "PC5", "PIN48", "PIN22"},
+    {Gpio<GPIOC_BASE, 6>::getPin(), "PC6", "PIN37", "PIN33"},
+    {Gpio<GPIOC_BASE, 7>::getPin(), "PC7", "PIN35", "PIN35"},
+    // {Gpio<GPIOC_BASE, 8>::getPin(), "PC8", NULL, NULL}, SD Card
+    // {Gpio<GPIOC_BASE, 9>::getPin(), "PC9", NULL, NULL}, SD Card
+    // {Gpio<GPIOC_BASE, 10>::getPin(), "PC10", NULL, NULL}, SD Card
+    // {Gpio<GPIOC_BASE, 11>::getPin(), "PC11", NULL, NULL}, SD Card
+    // {Gpio<GPIOC_BASE, 12>::getPin(), "PC12", NULL, NULL}, SD Card
+    // {Gpio<GPIOC_BASE, 13>::getPin(), "PC13", NULL, NULL}, LED
+    // {Gpio<GPIOC_BASE, 14>::getPin(), "PC14", NULL, NULL}, LED
+    // {Gpio<GPIOC_BASE, 15>::getPin(), "PC15", NULL, NULL}, LED
+    // {Gpio<GPIOD_BASE, 0>::getPin(), "PD0", NULL, NULL}, RAM
+    // {Gpio<GPIOD_BASE, 1>::getPin(), "PD1", NULL, NULL}, RAM
+    // {Gpio<GPIOD_BASE, 2>::getPin(), "PD2", NULL, NULL}, SD Card
+    {Gpio<GPIOD_BASE, 3>::getPin(), "PD3", "PIN28", "PIN42"},
+    {Gpio<GPIOD_BASE, 4>::getPin(), "PD4", "PIN26", "PIN44"},
+    {Gpio<GPIOD_BASE, 5>::getPin(), "PD5", "PIN22", "PIN48"},
+    {Gpio<GPIOD_BASE, 6>::getPin(), "PD6", "PIN24", "PIN46"},
+    {Gpio<GPIOD_BASE, 7>::getPin(), "PD7", "PIN23", "PIN47"},
+    // {Gpio<GPIOD_BASE, 8>::getPin(), "PD8", NULL, NULL}, RAM
+    // {Gpio<GPIOD_BASE, 9>::getPin(), "PD9", NULL, NULL}, RAM
+    // {Gpio<GPIOD_BASE, 10>::getPin(), "PD10", NULL, NULL}, RAM
+    {Gpio<GPIOD_BASE, 11>::getPin(), "PD11", "PIN44", "PIN26"},
+    {Gpio<GPIOD_BASE, 12>::getPin(), "PD12", "PIN41", "PIN29"},
+    {Gpio<GPIOD_BASE, 13>::getPin(), "PD13", "PIN39", "PIN31"},
+    // {Gpio<GPIOD_BASE, 14>::getPin(), "PD14", NULL, NULL}, RAM
+    // {Gpio<GPIOD_BASE, 15>::getPin(), "PD15", NULL, NULL}, RAM
+    // {Gpio<GPIOE_BASE, 0>::getPin(), "PE0", NULL, NULL}, RAM
+    // {Gpio<GPIOE_BASE, 1>::getPin(), "PE1", NULL, NULL}, RAM
+    {Gpio<GPIOE_BASE, 2>::getPin(), "PE2", "PIN7", "PIN63"},
+    {Gpio<GPIOE_BASE, 3>::getPin(), "PE3", "PIN12", "PIN58"},
+    {Gpio<GPIOE_BASE, 4>::getPin(), "PE4", "PIN9", "PIN61"},
+    {Gpio<GPIOE_BASE, 5>::getPin(), "PE5", "PIN5", "PIN65"},
+    {Gpio<GPIOE_BASE, 6>::getPin(), "PE6", "PIN3", "PIN67"},
+    // {Gpio<GPIOE_BASE, 7>::getPin(), "PE7", NULL, NULL}, RAM
+    // {Gpio<GPIOE_BASE, 8>::getPin(), "PE8", NULL, NULL}, RAM
+    // {Gpio<GPIOE_BASE, 9>::getPin(), "PE9", NULL, NULL}, RAM
+    // {Gpio<GPIOE_BASE, 10>::getPin(), "PE10", NULL, NULL}, RAM
+    // {Gpio<GPIOE_BASE, 11>::getPin(), "PE11", NULL, NULL}, RAM
+    // {Gpio<GPIOE_BASE, 12>::getPin(), "PE12", NULL, NULL}, RAM
+    // {Gpio<GPIOE_BASE, 13>::getPin(), "PE13", NULL, NULL}, RAM
+    // {Gpio<GPIOE_BASE, 14>::getPin(), "PE14", NULL, NULL}, RAM
+    // {Gpio<GPIOE_BASE, 15>::getPin(), "PE15", NULL, NULL}, RAM
+    // {Gpio<GPIOF_BASE, 0>::getPin(), "PF0", NULL, NULL}, RAM
+    // {Gpio<GPIOF_BASE, 1>::getPin(), "PF1", NULL, NULL}, RAM
+    // {Gpio<GPIOF_BASE, 2>::getPin(), "PF2", NULL, NULL}, RAM
+    // {Gpio<GPIOF_BASE, 3>::getPin(), "PF3", NULL, NULL}, RAM
+    // {Gpio<GPIOF_BASE, 4>::getPin(), "PF4", NULL, NULL}, RAM
+    // {Gpio<GPIOF_BASE, 5>::getPin(), "PF5", NULL, NULL}, RAM
+    // {Gpio<GPIOF_BASE, 6>::getPin(), "PF6", NULL, NULL}, Flash
+    // {Gpio<GPIOF_BASE, 7>::getPin(), "PF7", NULL, NULL}, Flash
+    // {Gpio<GPIOF_BASE, 8>::getPin(), "PF8", NULL, NULL}, Flash
+    // {Gpio<GPIOF_BASE, 9>::getPin(), "PF9", NULL, NULL}, Flash
+    // {Gpio<GPIOF_BASE, 10>::getPin(), "PF10", NULL, NULL}, Flash
+    // {Gpio<GPIOF_BASE, 11>::getPin(), "PF11", NULL, NULL}, RAM
+    // {Gpio<GPIOF_BASE, 12>::getPin(), "PF12", NULL, NULL}, RAM
+    // {Gpio<GPIOF_BASE, 13>::getPin(), "PF13", NULL, NULL}, RAM
+    // {Gpio<GPIOF_BASE, 14>::getPin(), "PF14", NULL, NULL}, RAM
+    // {Gpio<GPIOF_BASE, 15>::getPin(), "PF15", NULL, NULL}, RAM
+    // {Gpio<GPIOG_BASE, 0>::getPin(), "PG0", NULL, NULL}, RAM
+    // {Gpio<GPIOG_BASE, 1>::getPin(), "PG1", NULL, NULL}, RAM
+    // {Gpio<GPIOG_BASE, 2>::getPin(), "PG2", NULL, NULL}, RAM
+    {Gpio<GPIOG_BASE, 3>::getPin(), "PG3", "PIN42", "PIN28"},
+    // {Gpio<GPIOG_BASE, 4>::getPin(), "PG4", NULL}, RAM
+    // {Gpio<GPIOG_BASE, 5>::getPin(), "PG5", NULL}, RAM
+    {Gpio<GPIOG_BASE, 6>::getPin(), "PG6", "PIN40", "PIN30"},
+    {Gpio<GPIOG_BASE, 7>::getPin(), "PG7", "PIN38", "PIN32"},
+    // {Gpio<GPIOG_BASE, 8>::getPin(), "PG8", NULL, NULL}, RAM
+    {Gpio<GPIOG_BASE, 9>::getPin(), "PG9", "PIN19", "PIN51"},
+    {Gpio<GPIOG_BASE, 10>::getPin(), "PG10", "PIN25", "PIN45"},
+    {Gpio<GPIOG_BASE, 11>::getPin(), "PG11", "PIN21", "PIN49"},
+    {Gpio<GPIOG_BASE, 12>::getPin(), "PG12", "PIN20", "PIN50"},
+    {Gpio<GPIOG_BASE, 13>::getPin(), "PG13", "PIN18", "PIN52"},
+    {Gpio<GPIOG_BASE, 14>::getPin(), "PG14", "PIN16", "PIN54"},
+    // {Gpio<GPIOG_BASE, 15>::getPin(), "PG15", NULL, NULL} RAM
+}};
+
+void banner(bool furry)
+{
+    if (furry)
+    {
+        puts(R"(
+⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⡀⠀⠀⠀⠀⠀⠀⠀⢀⣠⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
+⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⢶⣿⢃⣠⣴⣶⣶⣿⣛⢯⣹⣾⠁⠀⠀⠀⠀⠀⢀⣀⣤⣴⣶⠀⠀
+⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⣿⣿⣿⣻⣾⣿⣞⣯⣷⣽⣾⣿⣁⣀⣠⢤⡶⣶⠿⣻⣶⣟⡿⣿⠀⠀
+⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⣿⣟⣯⣿⣶⣯⣿⣿⣿⡁⠀⣴⠿⣟⣿⣟⣿⣻⢷⢯⣞⡏⠀⠀
+⠀⠀⠀⢠⡶⢒⣖⣲⡶⢶⡖⣶⣒⢶⡲⣞⣫⠭⣭⣭⣽⣭⣯⣽⣿⣿⣿⣿⣿⣿⣿⠿⢿⡿⠿⠿⠿⣌⠿⢿⣿⣿⣿⣿⡾⣽⢯⣟⣿⠃⠀⠀
+⠀⠀⠀⠀⢻⣯⣛⢧⣛⢧⣛⡶⣹⣎⢷⣝⡮⣟⢷⡾⣹⢯⡟⠉⠉⠛⢿⣿⣿⣯⣟⣿⣫⣽⡿⣃⣔⣬⣿⣷⣯⣿⣿⣿⣿⣽⣻⣾⡏⠀⠀⠀
+⠀⠀⠀⠀⠀⠹⣟⡾⣭⡟⣾⣹⢗⣾⣫⢾⡽⣽⢾⡽⢯⡟⠀⠀⠀⢀⢈⣿⣿⣿⣿⣿⠿⢻⣷⣼⣿⣿⣟⣿⣟⣿⣿⣿⣿⣿⣿⠏⠀⠀⠀⠀
+⠀⠀⠀⠀⠀⠀⠙⣿⣷⣿⣷⣯⣟⡾⣽⣳⢿⡽⣾⡽⡟⠀⠀⢀⣰⠞⠟⠙⢻⣷⣿⣸⣷⣾⣟⣺⣽⣟⣻⣿⣿⣿⣿⣿⣿⣿⠋⠀⠀⠀⠀⠀
+⠀⠀⠀⠀⠀⠀⠀⠈⠻⣿⣿⣿⣿⣿⣷⣯⣿⣽⣳⢿⠃⠀⠀⢀⠀⢠⣤⣦⣄⣇⠉⠛⠛⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠁⠀⠀⠀⠀⠀⠀
+⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⠻⣿⣿⣿⣿⣿⣉⠉⠉⠻⠀⠀⠠⢀⣾⢿⠋⢁⣿⣿⠀⠀⠀⠀⣿⣧⠙⢿⣹⣏⢠⣛⠟⠁⠀⠀⢀⣴⣷⠀⠀⠀
+⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠛⠿⣿⣿⣿⣓⣈⣰⣦⣤⣀⣼⠋⠀⠀⢭⣽⣿⡇⠀⣀⣀⣿⣿⡇⡸⣿⣿⡤⠉⠒⠤⣀⠀⠠⡄⠀⣀⠀⠀
+⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⢹⠏⠀⠛⣞⣧⣾⠍⠢⣀⠀⠈⠻⠟⣇⢉⣡⡶⣭⠯⡀⠐⠛⠙⢇⠀⠐⢲⠏⠀⠀⠈⠲⠞⠀⠀
+⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡴⠁⢀⠄⠂⢸⠛⠁⣠⣲⡆⢔⠾⠋⠀⠈⠢⣉⠀⢀⣀⣈⡆⠀⠀⢸⣀⠔⠋⠀⠀⠀⠀⠀⠀⠀⠀
+⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠚⠤⣌⡁⠀⠀⠈⢆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠹⣿⣿⣿⡿⠀⠀⣜⣩⠟⠃⠀⠀⠀⠀⠀⠀⠀⠀
+⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠼⣛⡉⠀⠀⠓⢄⡀⠀⠀⠀⠀⠀⠀⢀⡀⠀⠀⡹⠋⠀⣠⠞⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
+⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠉⠐⠒⠚⣿⣶⠦⣄⣀⡀⠘⠿⠽⠶⢾⣷⣀⠖⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
+⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣀⡀⣀⣀⣠⡿⠍⣛⠻⡿⠿⣟⣲⣶⣶⡶⠿⣿⣖⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
+⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠼⠛⢻⠿⠻⢿⣿⣷⣿⣶⣆⠬⡯⣑⣿⣿⣿⣿⣹⣍⣧⣾⠈⠉⠉⠒⠒⠠⣄⠀⠀⠀⠀⠀⠀⠀
+⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠋⠀⠀⠐⠒⠠⣔⠁⠀⠉⣿⣿⣿⢛⡖⠾⠿⢿⡿⠿⣾⠟⠁⠀⠀⠀⠀⠀⠸⠀⠱⡄⠀⠀⠀⠀⠀
+⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠰⡅⠀⠀⠀⠀⠀⠀⠀⠉⠒⠄⠘⢿⣿⠇⠈⠁⠒⠺⣤⣢⡼⠀⠀⠀⠀⠀⠀⠀⠸⠀⠀⠘⣆⠀⠀⠀⠀
+⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠹⡄⠐⠒⠠⠤⠀⣀⠀⠀⠀⠀⠈⣏⠀⠀⠀⠀⠀⠈⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⢆⠀⠀⠙⣆⠀⠀⠀
+⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡰⠁⠀⠘⣆⠀⠀⠀⠀⠀⠉⠑⠢⡀⢀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⠀⠀⠀⠈⢆⠀⠀
+⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡰⠁⠀⠀⠀⠈⢣⡀⠠⢄⣀⠀⠀⠀⢠⠞⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠄⠀⠀⠀⠀⢣⠀
+⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠰⠧⣀⣀⠀⢀⡤⠋⠀⠀⠀⠀⠈⢁⠒⡏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡆⠀⢀⣠⢾⡍⠀
+⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠞⣻⡿⠏⠀⠀⠀⠐⠢⠄⢀⡀⣰⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡷⠖⠋⣁⡼⣇⠀ 
+        )");
+    }
+    else
+    {
+        puts(R"(
+                                                 /
+                                            /  /  /
+                                        /    /      /
+                                     /    /   /  /
+                                      /  _=^|  /    /
+                                 /  /  <^   |   /
+                                     //     | /   /  /
+                                 /   /      /
+                                   >/      /    /   /
+                               /  //      /  /         /
+                                 </      /^>_   /  /
+                              /  /      /    ^>_     /   /
+                                /      /^<_     ^>_    /
+                               /      / /  ^<_     ^>_     /
+                               ^<_   /        ^<_     ^>_            ___----____
+                                  ^</            ^<_     ^>_   _-<^^^
+                                                    ^<_     ^<^
+                                                       ^<_  /   /
+                                                          ^/   /^>^>_  ********
+                                                          '<__/  /^> ^>_  ___***
+                                                              ^-/  /^>__^>_  ^^^
+                                                                ^-/  /  ! /
+                                                                  ^-/^_/_/
+               _____________._____________________
+              /,   O ..    ==   *          O ___  /
+             /,         ,@@    @@@@@@@@@@   /()/ /
+            === %%%%    """   @@@@@@@@@@   /"'/ /
+           ==='%%%%      +   @@@@@@@@@@   /"'/ /
+          /%     .   .%  .  @@@@@@@@@@   /"'/ /
+         /%     . %..%  . ,  ,  ,  ,    /__/ /
+        /____O_______________________O______/
+                                      L__V
+        )");
+    }
+
+    puts(R"(
+ Welcome to the
+   ________  __   _    _____      ______          __             _ __     
+  / ____/ / / /  | |  / /__ \    /_  __/__  _____/ /________  __(_) /____ 
+ / /   / / / /   | | / /__/ /     / / / _ \/ ___/ __/ ___/ / / / / __/ _ \
+/ /___/ /_/ /    | |/ // __/     / / /  __(__  ) /_(__  ) /_/ / / /_/  __/
+\____/\____/     |___//____/    /_/  \___/____/\__/____/\__,_/_/\__/\___/ 
+    )");
+}
+
+void led_task()
+{
+    ledOff();
+
+    const unsigned int DELAY = 200;
+    while (1)
+    {
+        led2Off();
+        led1On();
+        Thread::sleep(DELAY);
+        led1Off();
+        led2On();
+        Thread::sleep(DELAY);
+        led2Off();
+        led3On();
+        Thread::sleep(DELAY);
+        led3Off();
+        led4On();
+        Thread::sleep(DELAY);
+        led4Off();
+        led3On();
+        Thread::sleep(DELAY);
+        led3Off();
+        led2On();
+        Thread::sleep(DELAY);
+    }
+}
+
+void sd_test()
+{
+    // Write 1MB of bytes
+    const size_t COUNT           = 256 * 1024;
+    const uint32_t INITIAL_VALUE = 0xdeadbeef;
+
+    puts("*** Starting SD test...");
+    bool ok = true;
+
+    FILE *f = fopen("/sd/test.bin", "wb");
+    if (f == NULL)
+    {
+        puts("Failed to open test.bin");
+        ok = false;
+    }
+
+    if (ok)
+    {
+        uint32_t value = INITIAL_VALUE;
+        for (size_t i = 0; i < COUNT; i++)
+        {
+            size_t result = fwrite(&value, sizeof(value), 1, f);
+            if (result != 1)
+            {
+                printf("Failed to write word %d\n", i);
+
+                // We had a problem
+                ok = false;
+                break;
+            }
+
+            // Update value with xorshift
+            value = (value << 8) ^ (value >> 8);
+        }
+
+        fclose(f);
+    }
+
+    if (ok)
+    {
+        // Reopen file
+        f = fopen("/sd/test.bin", "rb");
+        if (f == NULL)
+        {
+            puts("Failed to reopen test.bin");
+            ok = false;
+        }
+    }
+
+    if (ok)
+    {
+        uint32_t value = INITIAL_VALUE;
+        for (size_t i = 0; i < COUNT; i++)
+        {
+            uint32_t actual = 0;
+            size_t result   = fread(&actual, sizeof(actual), 1, f);
+            if (result != 1)
+            {
+                printf("Failed to write word %d\n", i);
+                ok = false;
+                break;
+            }
+
+            if (actual != value)
+            {
+                printf(
+                    "Failed to validate word %d, expected: %lu, actual: %lu\n",
+                    i, value, actual);
+                ok = false;
+                break;
+            }
+
+            // Update value with xorshift
+            value = (value << 8) ^ (value >> 8);
+        }
+
+        fclose(f);
+    }
+
+    if (ok)
+    {
+        puts("*** SD test succesfull!");
+    }
+    else
+    {
+        puts("*** SD test failed!");
+    }
+}
+
+void xram_test()
+{
+    volatile uint16_t *const START = (volatile uint16_t *)0xd0000000;
+    volatile uint16_t *const END   = START + (16 * 1024 * 1024);
+
+    // First clear the whole RAM
+    for (volatile uint16_t *iter = START; iter != END; iter++)
+    {
+        *iter = 0;
+    }
+
+    __DMB();  // Flush cache
+
+    puts("*** Starting basic XRAM test...");
+    bool ok = true;
+
+    for (volatile uint16_t *iter = START; iter != END; iter++)
+    {
+        // Set a marker value at this address
+        *iter = 0xffff;
+        __DMB();  // Flush cache
+
+        if (*iter != 0xffff)
+        {
+            ok = false;
+            printf("Readback failed: %p\n", iter);
+        }
+
+        // Reset the value back to 0
+        *iter = 0;
+        __DMB();  // Flush cache
+    }
+
+    if (ok)
+    {
+        puts("*** XRAM basic test succesfull!");
+    }
+    else
+    {
+        puts("*** XRAM basic test failed!");
+    }
+
+    puts("*** Starting XRAM mirroring test...");
+    ok = true;
+
+    // Generate addresses to test every line
+    for (int i = 1; i < 25; i++)
+    {
+        volatile uint16_t *other =
+            (volatile uint16_t *)((size_t)START | (1 << i));
+
+        // Write something
+        *other = 0xdead;
+        __DMB();  // Flush cache
+
+        for (volatile uint16_t *iter = START; iter != END; iter++)
+        {
+            // Skip the written address
+            if (iter == other)
+            {
+                continue;
+            }
+
+            // Check for mirroring
+            if (*iter == 0xdead)
+            {
+                printf("Mirroring %p -> %p\n", other, iter);
+                ok = false;
+            }
+        }
+
+        // Reset the value
+        *other = 0;
+        __DMB();  // Flush cache
+    }
+
+    if (ok)
+    {
+        puts("*** XRAM mirror test succesfull!");
+    }
+    else
+    {
+        puts("*** XRAM mirror test failed!");
+    }
+}
+
+void pin_continuity_test()
+{
+    for (auto &a : PIN_DEFS)
+    {
+        a.pin.mode(Mode::INPUT_PULL_DOWN);
+    }
+
+    puts("*** Starting pin continuity test...");
+    bool ok = true;
+
+    // Ok now start testing
+    for (auto &a : PIN_DEFS)
+    {
+        a.pin.mode(Mode::OUTPUT);
+        a.pin.high();
+        for (auto &b : PIN_DEFS)
+        {
+            if (a.pin.getNumber() == b.pin.getNumber() &&
+                a.pin.getPort() == b.pin.getPort())
+            {
+                continue;
+            }
+
+            if (b.pin.value() == 1)
+            {
+                printf("Shorted pins %s %s\n", a.gpio_name, b.gpio_name);
+                ok = false;
+            }
+        }
+
+        a.pin.low();
+        a.pin.mode(Mode::INPUT_PULL_DOWN);
+    }
+
+    if (ok)
+    {
+        puts("*** Pin continuity test succesfull!");
+    }
+    else
+    {
+        puts("*** Pin continuity test failed!");
+    }
+}
+
+void pin_semi_test()
+{
+    for (auto &a : PIN_DEFS)
+    {
+        a.pin.mode(Mode::OUTPUT);
+        a.pin.low();
+    }
+
+    puts("*** Starting semi-automatic pin test...");
+
+    std::atomic<bool> running(true);
+    std::atomic<int> cur_idx(0);
+
+    // Parallel test pulsing pin
+    std::thread t(
+        [&]()
+        {
+            while (running)
+            {
+                int idx = cur_idx;
+
+                PIN_DEFS[idx].pin.high();
+                Thread::sleep(10);
+                PIN_DEFS[idx].pin.low();
+                Thread::sleep(10);
+            }
+        });
+
+    // Clean any pending newlines
+    while (getchar() != '\n')
+        ;
+    puts("Press enter to continue...");
+
+    for (size_t i = 0; i < PIN_DEFS.size(); i++)
+    {
+        auto &a = PIN_DEFS[i];
+
+        // Pause waiting for keys
+        while (getchar() != '\n')
+            ;
+
+        cur_idx = i;
+        printf(
+            "Pulsing %s, conan: %s, breakout v1: %s, press enter to "
+            "continue...\n",
+            a.gpio_name, a.conan_name, a.breakout_v1_name);
+    }
+
+    running = false;
+    t.join();
+
+    puts("*** Test finished!");
+}
+
+int main()
+{
+    banner(true);
+
+    // Spawn the led blinker task
+    std::thread t(led_task);
+
+    while (1)
+    {
+        printf(
+            "\nSelect test to run:\n"
+            "- 1 Automatic SD test\n"
+            "- 2 Automatic RAM test\n"
+            "- 3 Automatic pin continuity test\n"
+            "- 4 Semi-automatic pin test\n"
+            "\n"
+            "Command: ");
+
+        int cmd;
+        scanf("%d", &cmd);
+
+        switch (cmd)
+        {
+            case 1:
+                sd_test();
+                break;
+
+            case 2:
+                xram_test();
+                break;
+
+            case 3:
+                pin_continuity_test();
+                break;
+
+            case 4:
+                pin_semi_test();
+                break;
+        }
+    }
+
+    return 0;
+}