diff --git a/.gitignore b/.gitignore
index 9b89790f971bdc8817c9e536f0eccafcf4009da4..a9f1775b4bc49623280a7697bbb213db7e2b4b91 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,3 +32,5 @@ core
 __pycache__
 /scripts/generators/generated
 /scripts/generators/scxmls
+
+scripts/logdecoder/logdecoder
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 7ba04dac130512cce20d22323bcbfdb06ad39a51..2cbb65d50d357cbf8bf1386ac3b39b0021f77012 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -25,6 +25,7 @@ variables:
 stages:
   - build-release
   - build-debug
+  - build-logdecoder
   - test
   - lint
   - documentation
@@ -36,7 +37,7 @@ build-release:
   tags:
     - miosix
   script:
-    - ./sbs --jobs 2
+    - ./sbs
 
 # Stage build-debug
 
@@ -45,34 +46,54 @@ build-debug:
   tags:
     - miosix
   script:
-    - ./sbs --jobs 2 --debug
+    - ./sbs --debug
+
+# Stage build-logdecoder
+
+build-logdecoder:
+  stage: build-logdecoder
+  tags:
+    - miosix
+  script:
+    - cd scripts/logdecoder
+    - make
 
 # Stage test
 
 test:
   stage: test
+  tags:
+    - miosix
   script:
-    - ./sbs --jobs 2 --test catch-tests-entry
+    - ./sbs --test catch-tests-boardcore
 
 # Stage lint
 
 cppcheck:
   stage: lint
+  tags:
+    - miosix
   script:
     - ./scripts/linter.py --cppcheck src
 
 format:
   stage: lint
+  tags:
+    - miosix
   script:
     - ./scripts/linter.py --format src
 
 copyright:
   stage: lint
+  tags:
+    - miosix
   script:
     - ./scripts/linter.py --copyright src
 
 find:
   stage: lint
+  tags:
+    - miosix
   script:
     - ./scripts/linter.py --find src
 
diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json
index b600d2af98677534acbe0414a25b1022a5d7f720..897e004670cc70a17c97858293aba85ad96709ab 100644
--- a/.vscode/c_cpp_properties.json
+++ b/.vscode/c_cpp_properties.json
@@ -1,5 +1,59 @@
 {
     "configurations": [
+        {
+            "name": "stm32f407vg_stm32f4discovery",
+            "cStandard": "c11",
+            "cppStandard": "c++11",
+            "compilerPath": "/opt/arm-miosix-eabi/bin/arm-miosix-eabi-g++",
+            "defines": [
+                "DEBUG",
+                "_ARCH_CORTEXM4_STM32F4",
+                "_BOARD_STM32F407VG_STM32F4DISCOVERY",
+                "_MIOSIX_BOARDNAME=stm32f407vg_stm32f4discovery",
+                "HSE_VALUE=8000000",
+                "SYSCLK_FREQ_168MHz=168000000",
+                "_MIOSIX",
+                "__cplusplus=201103L"
+            ],
+            "includePath": [
+                "${workspaceFolder}/libs/miosix-kernel/miosix/config/arch/cortexM4_stm32f4/stm32f407vg_stm32f4discovery",
+                "${workspaceFolder}/libs/miosix-kernel/miosix/arch/cortexM4_stm32f4/stm32f407vg_stm32f4discovery",
+                "${workspaceFolder}/libs/miosix-kernel/miosix/arch/cortexM4_stm32f4/common",
+                "${workspaceFolder}/libs/miosix-kernel/miosix/arch/common",
+                "${workspaceFolder}/libs/miosix-kernel/miosix",
+                "${workspaceFolder}/libs/mavlink_skyward_lib",
+                "${workspaceFolder}/libs/fmt/include",
+                "${workspaceFolder}/libs/eigen",
+                "${workspaceFolder}/libs/tscpp",
+                "${workspaceFolder}/libs",
+                "${workspaceFolder}/src/shared",
+                "${workspaceFolder}/src/tests"
+            ],
+            "browse": {
+                "path": [
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/config/arch/cortexM4_stm32f4/stm32f407vg_stm32f4discovery",
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/arch/cortexM4_stm32f4/stm32f407vg_stm32f4discovery",
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/arch/cortexM4_stm32f4/common",
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/stdlib_integration",
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/arch/common",
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/interfaces",
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/filesystem",
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/kernel",
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/util",
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/e20",
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/*",
+                    "${workspaceFolder}/libs/mavlink_skyward_lib",
+                    "${workspaceFolder}/libs/eigen",
+                    "${workspaceFolder}/libs/tscpp",
+                    "${workspaceFolder}/libs/mxgui",
+                    "${workspaceFolder}/libs/fmt",
+                    "${workspaceFolder}/src/shared",
+                    "${workspaceFolder}/src/tests"
+                ],
+                "limitSymbolsToIncludedHeaders": true
+            },
+            "compileCommands": "${workspaceFolder}/build/compile_commands.json"
+        },
         {
             "name": "stm32f429zi_stm32f4discovery",
             "cStandard": "c11",
@@ -24,6 +78,7 @@
                 "${workspaceFolder}/libs/mavlink_skyward_lib",
                 "${workspaceFolder}/libs/fmt/include",
                 "${workspaceFolder}/libs/eigen",
+                "${workspaceFolder}/libs/tscpp",
                 "${workspaceFolder}/libs",
                 "${workspaceFolder}/src/shared",
                 "${workspaceFolder}/src/tests"
@@ -50,8 +105,113 @@
                     "${workspaceFolder}/src/tests"
                 ],
                 "limitSymbolsToIncludedHeaders": true
-            },
-            "compileCommands": "${workspaceFolder}/build/compile_commands.json"
+            }
+        },
+        {
+            "name": "stm32f429zi_skyward_death_stack_x",
+            "cStandard": "c11",
+            "cppStandard": "c++11",
+            "compilerPath": "/opt/arm-miosix-eabi/bin/arm-miosix-eabi-g++",
+            "defines": [
+                "DEBUG",
+                "_ARCH_CORTEXM4_STM32F4",
+                "_BOARD_STM32F429ZI_SKYWARD_DEATH_STACK_X",
+                "_MIOSIX_BOARDNAME=stm32f429zi_skyward_death_stack_x",
+                "HSE_VALUE=8000000",
+                "SYSCLK_FREQ_168MHz=168000000",
+                "_MIOSIX",
+                "__cplusplus=201103L"
+            ],
+            "includePath": [
+                "${workspaceFolder}/libs/miosix-kernel/miosix/config/arch/cortexM4_stm32f4/stm32f429zi_skyward_death_stack_x",
+                "${workspaceFolder}/libs/miosix-kernel/miosix/arch/cortexM4_stm32f4/stm32f429zi_skyward_death_stack_x",
+                "${workspaceFolder}/libs/miosix-kernel/miosix/arch/cortexM4_stm32f4/common",
+                "${workspaceFolder}/libs/miosix-kernel/miosix/arch/common",
+                "${workspaceFolder}/libs/miosix-kernel/miosix",
+                "${workspaceFolder}/libs/mavlink_skyward_lib",
+                "${workspaceFolder}/libs/fmt/include",
+                "${workspaceFolder}/libs/eigen",
+                "${workspaceFolder}/libs/tscpp",
+                "${workspaceFolder}/libs",
+                "${workspaceFolder}/src/shared",
+                "${workspaceFolder}/src/tests"
+            ],
+            "browse": {
+                "path": [
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/config/arch/cortexM4_stm32f4/stm32f429zi_skyward_death_stack_x",
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/arch/cortexM4_stm32f4/stm32f429zi_skyward_death_stack_x",
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/arch/cortexM4_stm32f4/common",
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/stdlib_integration",
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/arch/common",
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/interfaces",
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/filesystem",
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/kernel",
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/util",
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/e20",
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/*",
+                    "${workspaceFolder}/libs/mavlink_skyward_lib",
+                    "${workspaceFolder}/libs/eigen",
+                    "${workspaceFolder}/libs/tscpp",
+                    "${workspaceFolder}/libs/mxgui",
+                    "${workspaceFolder}/libs/fmt",
+                    "${workspaceFolder}/src/shared",
+                    "${workspaceFolder}/src/tests"
+                ],
+                "limitSymbolsToIncludedHeaders": true
+            }
+        },
+        {
+            "name": "stm32f429zi_parafoil",
+            "cStandard": "c11",
+            "cppStandard": "c++11",
+            "compilerPath": "/opt/arm-miosix-eabi/bin/arm-miosix-eabi-g++",
+            "defines": [
+                "DEBUG",
+                "_ARCH_CORTEXM4_STM32F4",
+                "_BOARD_STM32F429ZI_PARAFOIL",
+                "_MIOSIX_BOARDNAME=stm32f429zi_parafoil",
+                "HSE_VALUE=8000000",
+                "SYSCLK_FREQ_168MHz=168000000",
+                "_MIOSIX",
+                "__cplusplus=201103L"
+            ],
+            "includePath": [
+                "${workspaceFolder}/libs/miosix-kernel/miosix/config/arch/cortexM4_stm32f4/stm32f429zi_parafoil",
+                "${workspaceFolder}/libs/miosix-kernel/miosix/arch/cortexM4_stm32f4/stm32f429zi_parafoil",
+                "${workspaceFolder}/libs/miosix-kernel/miosix/arch/cortexM4_stm32f4/common",
+                "${workspaceFolder}/libs/miosix-kernel/miosix/arch/common",
+                "${workspaceFolder}/libs/miosix-kernel/miosix",
+                "${workspaceFolder}/libs/mavlink_skyward_lib",
+                "${workspaceFolder}/libs/fmt/include",
+                "${workspaceFolder}/libs/eigen",
+                "${workspaceFolder}/libs/tscpp",
+                "${workspaceFolder}/libs",
+                "${workspaceFolder}/src/shared",
+                "${workspaceFolder}/src/tests"
+            ],
+            "browse": {
+                "path": [
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/config/arch/cortexM4_stm32f4/stm32f429zi_parafoil",
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/arch/cortexM4_stm32f4/stm32f429zi_parafoil",
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/arch/cortexM4_stm32f4/common",
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/stdlib_integration",
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/arch/common",
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/interfaces",
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/filesystem",
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/kernel",
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/util",
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/e20",
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/*",
+                    "${workspaceFolder}/libs/mavlink_skyward_lib",
+                    "${workspaceFolder}/libs/eigen",
+                    "${workspaceFolder}/libs/tscpp",
+                    "${workspaceFolder}/libs/mxgui",
+                    "${workspaceFolder}/libs/fmt",
+                    "${workspaceFolder}/src/shared",
+                    "${workspaceFolder}/src/tests"
+                ],
+                "limitSymbolsToIncludedHeaders": true
+            }
         }
     ],
     "version": 4
diff --git a/.vscode/launch.json b/.vscode/launch.json
index f6dd88a2243d5f8ed189a89979fe54ac4128d51f..ef12355a4e4b74e6afa893f09f2e5d91cf40dabb 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -3,7 +3,7 @@
     "configurations": [
         {
             "cwd": "${workspaceRoot}",
-            "executable": "${workspaceFolder}/bin/${fileBasenameNoExtension}/${fileBasenameNoExtension}.elf",
+            "executable": "${workspaceFolder}/build/${fileBasenameNoExtension}",
             "name": "ST-LINK V2 (current entrypoint)",
             "request": "attach",
             "type": "cortex-debug",
@@ -17,7 +17,7 @@
         },
         {
             "cwd": "${workspaceRoot}",
-            "executable": "${workspaceFolder}/bin/${fileBasenameNoExtension}/${fileBasenameNoExtension}.elf",
+            "executable": "${workspaceFolder}/build/${fileBasenameNoExtension}",
             "name": "ST-LINK V1 (current entrypoint)",
             "request": "attach",
             "type": "cortex-debug",
diff --git a/.vscode/settings.json b/.vscode/settings.json
index bb68ef811f81ac7a0bfddb42a01bda684a1df228..43e514f2603ebb9bb2b90fdab6c1c80067e2f17d 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -98,11 +98,131 @@
         "valarray": "cpp",
         "ranges": "cpp",
         "compare": "cpp",
-        "concepts": "cpp"
+        "concepts": "cpp",
+        "core": "cpp",
+        "iterativelinearsolvers": "cpp",
+        "pastixsupport": "cpp",
+        "superlusupport": "cpp",
+        "adolcforward": "cpp",
+        "alignedvector3": "cpp",
+        "bvh": "cpp",
+        "fft": "cpp",
+        "iterativesolvers": "cpp",
+        "mprealsupport": "cpp",
+        "matrixfunctions": "cpp",
+        "nonlinearoptimization": "cpp",
+        "openglsupport": "cpp",
+        "polynomials": "cpp",
+        "autodiff": "cpp",
+        "sparseextra": "cpp",
+        "specialfunctions": "cpp"
     },
     "cSpell.words": [
+        "aelf",
+        "Alessandro",
+        "AMSL",
+        "atthr",
+        "AVDD",
+        "Baro",
+        "boardcore",
+        "Boardcorev",
+        "Corigliano",
+        "CORTEXM",
+        "cpitch",
+        "cppcheck",
+        "croll",
+        "cwise",
+        "cyaw",
+        "deleteme",
+        "DMEIE",
+        "Doxyfile",
+        "doxygen",
+        "DRDY",
+        "Duca",
+        "Ecompass",
+        "Eigen",
+        "elfs",
+        "Erbetta",
+        "Fatt",
+        "Fatttr",
+        "fedetft's",
+        "fiprintf",
+        "Gatttr",
+        "getdetahstate",
         "Gpio",
+        "GPIOA",
         "GPIOB",
-        "miosix"
-    ]
+        "GPIOC",
+        "GPIOD",
+        "GPIOE",
+        "GPIOF",
+        "GPIOG",
+        "GPIOS",
+        "gpstr",
+        "Hatt",
+        "HSCMAND",
+        "HSCMRNN",
+        "IDLEIE",
+        "irqn",
+        "irqv",
+        "Kalman",
+        "Katt",
+        "kbps",
+        "ldrex",
+        "LIFCR",
+        "logdecoder",
+        "Luca",
+        "MEKF",
+        "MINC",
+        "miosix",
+        "mkdir",
+        "MPXHZ",
+        "Musso",
+        "NATT",
+        "NBAR",
+        "NDTR",
+        "NGPS",
+        "NMAG",
+        "NMEKF",
+        "nord",
+        "NVIC",
+        "peripehral",
+        "PINC",
+        "Pitot",
+        "Plin",
+        "Qgbw",
+        "Qget",
+        "Qhandle",
+        "Qput",
+        "Qwait",
+        "Qwakeup",
+        "Riccardo",
+        "RXNE",
+        "RXNEIE",
+        "sats",
+        "Satt",
+        "SDRAM",
+        "spitch",
+        "sqdip",
+        "sqtrp",
+        "sroll",
+        "SSCDANN",
+        "SSCDRRN",
+        "stof",
+        "syaw",
+        "TCIE",
+        "TEIE",
+        "testsuite",
+        "TSCPP",
+        "Ublox",
+        "Unsync",
+        "upcounter",
+        "USART",
+        "velnord",
+        "Xbee",
+        "xnord",
+        "yned"
+    ],
+    "cSpell.language": "en",
+    "cSpell.enabled": true
 }
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b5369a8580e1b65fa13d3c70e17929555bf10d87..53273bb8481f7b3ac5c2385fd4505fd29a0f90fe 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -39,6 +39,9 @@ sbs_target(bmx160-calibration-entry stm32f429zi_skyward_death_stack_x)
 add_executable(config-dsgamma src/entrypoints/config-dsgamma.cpp)
 sbs_target(config-dsgamma stm32f429zi_stm32f4discovery)
 
+add_executable(imu-calibration src/entrypoints/imu-calibration.cpp)
+sbs_target(imu-calibration stm32f429zi_parafoil)
+
 add_executable(mxgui-helloworld src/entrypoints/examples/mxgui-helloworld.cpp)
 sbs_target(mxgui-helloworld stm32f429zi_stm32f4discovery)
 
@@ -55,9 +58,6 @@ sbs_target(sdcard-benchmark stm32f429zi_stm32f4discovery)
 #                                    Tests                                    #
 #-----------------------------------------------------------------------------#
 
-add_executable(test-kalman-eigen-benchmark src/tests/kalman/test-kalman-eigen-benchmark.cpp)
-sbs_target(test-kalman-eigen-benchmark stm32f429zi_stm32f4discovery)
-
 add_executable(test-logger src/tests/logger/test-logger.cpp)
 sbs_target(test-logger stm32f429zi_stm32f4discovery)
 
@@ -80,7 +80,7 @@ add_executable(test-sensormanager src/tests/test-sensormanager.cpp)
 sbs_target(test-sensormanager stm32f429zi_skyward_death_stack_x)
 
 add_executable(test-serial src/tests/test-serial.cpp)
-sbs_target(test-serial stm32f429zi_stm32f4discovery)
+sbs_target(test-serial stm32f407vg_stm32f4discovery)
 
 add_executable(test-taskscheduler src/tests/scheduler/test-taskscheduler.cpp)
 sbs_target(test-taskscheduler stm32f407vg_stm32f4discovery)
@@ -92,11 +92,11 @@ sbs_target(test-trace-logger stm32f429zi_stm32f4discovery)
 #                                Tests - Catch                                #
 #-----------------------------------------------------------------------------#
 
-add_executable(catch-tests-entry
+add_executable(catch-tests-boardcore
     src/tests/catch/catch-tests-entry.cpp
     src/tests/catch/examples/example-test-factorial.cpp
     src/tests/catch/test-aero.cpp
-    src/tests/catch/test-buttonhandler.cpp
+    # src/tests/catch/test-buttonhandler.cpp
     src/tests/catch/test-circularbuffer.cpp
     src/tests/catch/test-eventbroker.cpp
     src/tests/catch/test-timestamptimer.cpp
@@ -105,9 +105,56 @@ add_executable(catch-tests-entry
     src/tests/catch/test-sensormanager-catch.cpp
     src/tests/catch/xbee/test-xbee-parser.cpp
 )
-target_compile_definitions(catch-tests-entry PRIVATE USE_MOCK_PERIPHERALS)
-sbs_target(catch-tests-entry stm32f429zi_stm32f4discovery)
-sbs_catch_test(catch-tests-entry)
+target_compile_definitions(catch-tests-boardcore PRIVATE USE_MOCK_PERIPHERALS)
+sbs_target(catch-tests-boardcore stm32f429zi_stm32f4discovery)
+sbs_catch_test(catch-tests-boardcore)
+
+#-----------------------------------------------------------------------------#
+#                             Tests - Actuators                               #
+#-----------------------------------------------------------------------------#
+
+add_executable(test-hbridge src/tests/actuators/test-hbridge.cpp)
+sbs_target(test-hbridge stm32f429zi_stm32f4discovery)
+
+add_executable(test-servo src/tests/actuators/test-servo.cpp)
+sbs_target(test-servo stm32f429zi_stm32f4discovery)
+
+add_executable(test-buzzer src/tests/actuators/test-buzzer.cpp)
+sbs_target(test-buzzer stm32f429zi_hre_test_stand)
+
+add_executable(test-stepper src/tests/actuators/test-stepper.cpp)
+sbs_target(test-stepper stm32f429zi_stm32f4discovery)
+
+#-----------------------------------------------------------------------------#
+#                             Tests - Algorithms                              #
+#-----------------------------------------------------------------------------#
+
+add_executable(test-kalman-eigen-benchmark src/tests/algorithms/Kalman/test-kalman-eigen-benchmark.cpp)
+sbs_target(test-kalman-eigen-benchmark stm32f429zi_stm32f4discovery)
+
+add_executable(test-tmp src/tests/algorithms/NAS/test-tmp.cpp)
+sbs_target(test-tmp stm32f429zi_skyward_death_stack_x)
+
+add_executable(test-attitude-parafoil src/tests/algorithms/NAS/test-attitude-parafoil.cpp)
+sbs_target(test-attitude-parafoil stm32f429zi_parafoil)
+
+add_executable(test-attitude-stack src/tests/algorithms/NAS/test-attitude-stack.cpp)
+sbs_target(test-attitude-stack stm32f429zi_skyward_death_stack_x)
+
+add_executable(test-nas-parafoil src/tests/algorithms/NAS/test-nas-parafoil.cpp)
+sbs_target(test-nas-parafoil stm32f429zi_parafoil)
+
+add_executable(test-nas-stack src/tests/algorithms/NAS/test-nas-stack.cpp)
+sbs_target(test-nas-stack stm32f429zi_skyward_death_stack_x)
+
+add_executable(test-nas-with-triad src/tests/algorithms/NAS/test-nas-with-triad.cpp)
+sbs_target(test-nas-with-triad stm32f429zi_skyward_death_stack_x)
+
+add_executable(test-triad src/tests/algorithms/NAS/test-triad.cpp)
+sbs_target(test-triad stm32f429zi_skyward_death_stack_x)
+
+add_executable(test-triad-parafoil src/tests/algorithms/NAS/test-triad-parafoil.cpp)
+sbs_target(test-triad-parafoil stm32f429zi_parafoil)
 
 #-----------------------------------------------------------------------------#
 #                               Tests - Drivers                               #
@@ -128,9 +175,6 @@ sbs_target(test-dsgamma stm32f429zi_stm32f4discovery)
 add_executable(test-general-purpose-timer src/tests/drivers/timer/test-general-purpose-timer.cpp)
 sbs_target(test-general-purpose-timer stm32f429zi_stm32f4discovery)
 
-add_executable(test-hbridge src/tests/drivers/test-hbridge.cpp)
-sbs_target(test-hbridge stm32f429zi_stm32f4discovery)
-
 add_executable(test-internal-adc src/tests/drivers/test-internal-adc.cpp)
 sbs_target(test-internal-adc stm32f429zi_skyward_death_stack_x)
 
@@ -146,9 +190,6 @@ sbs_target(test-MBLoadCell stm32f407vg_stm32f4discovery)
 add_executable(test-pwm src/tests/drivers/timer/test-pwm.cpp)
 sbs_target(test-pwm stm32f429zi_stm32f4discovery)
 
-add_executable(test-servo src/tests/drivers/test-servo.cpp)
-sbs_target(test-servo stm32f429zi_stm32f4discovery)
-
 add_executable(test-spi src/tests/drivers/spi/test-spi.cpp)
 sbs_target(test-spi stm32f429zi_stm32f4discovery)
 
@@ -185,8 +226,8 @@ sbs_target(test-xbee-rcv stm32f429zi_stm32f4discovery)
 add_executable(test-xbee-snd src/tests/drivers/xbee/test-xbee-snd.cpp)
 sbs_target(test-xbee-snd stm32f429zi_stm32f4discovery)
 
-add_executable(test-stepper src/tests/drivers/stepper/test-stepper.cpp)
-sbs_target(test-stepper stm32f429zi_stm32f4discovery)
+add_executable(test-usart src/tests/drivers/usart/test-usart.cpp)
+sbs_target(test-usart stm32f407vg_stm32f4discovery)
 
 #-----------------------------------------------------------------------------#
 #                               Tests - Events                                #
@@ -281,6 +322,9 @@ sbs_target(test-bmx160 stm32f429zi_skyward_death_stack_x)
 add_executable(test-bmx160-with-correction src/tests/sensors/test-bmx160-with-correction.cpp)
 sbs_target(test-bmx160-with-correction stm32f429zi_skyward_death_stack_x)
 
+add_executable(test-hx711 src/tests/sensors/test-hx711.cpp)
+sbs_target(test-hx711 stm32f429zi_stm32f4discovery)
+
 add_executable(test-l3gd20 src/tests/sensors/test-l3gd20.cpp)
 sbs_target(test-l3gd20 stm32f429zi_stm32f4discovery)
 
@@ -296,14 +340,17 @@ sbs_target(test-lis3mdl stm32f429zi_skyward_death_stack_x)
 add_executable(test-max6675 src/tests/sensors/test-max6675.cpp)
 sbs_target(test-max6675 stm32f429zi_stm32f4discovery)
 
+add_executable(test-max31855 src/tests/sensors/test-max31855.cpp)
+sbs_target(test-max31855 stm32f429zi_stm32f4discovery)
+
 add_executable(test-mpu9250 src/tests/sensors/test-mpu9250.cpp)
-sbs_target(test-mpu9250 stm32f407vg_stm32f4discovery)
+sbs_target(test-mpu9250 stm32f429zi_parafoil)
 
 add_executable(test-ms5803 src/tests/sensors/test-ms5803.cpp)
 sbs_target(test-ms5803 stm32f429zi_skyward_death_stack_x)
 
 add_executable(test-ubloxgps-serial src/tests/sensors/test-ubloxgps.cpp)
-sbs_target(test-ubloxgps-serial stm32f407vg_stm32f4discovery)
+sbs_target(test-ubloxgps-serial stm32f429zi_skyward_death_stack_x)
 
 add_executable(test-ubloxgps-spi src/tests/sensors/test-ubloxgps.cpp)
 target_compile_definitions(test-ubloxgps-spi PRIVATE USE_SPI)
@@ -318,3 +365,6 @@ sbs_target(test-vn100 stm32f407vg_stm32f4discovery)
 
 add_executable(test-csvreader src/tests/utils/test-csvreader.cpp)
 sbs_target(test-csvreader stm32f429zi_stm32f4discovery)
+
+add_executable(test-buttonhandler src/tests/utils/test-buttonhandler.cpp)
+sbs_target(test-buttonhandler stm32f429zi_stm32f4discovery)
diff --git a/cmake/boardcore-host.cmake b/cmake/boardcore-host.cmake
index 36cf3d102918be929ec4500f01a50c8701105e60..85d11ef2498c72e2528591aabf6a092ebb82b0af 100644
--- a/cmake/boardcore-host.cmake
+++ b/cmake/boardcore-host.cmake
@@ -25,8 +25,11 @@ add_library(boardcore-host STATIC EXCLUDE_FROM_ALL
     ${SBS_BASE}/src/shared/diagnostic/CpuMeter.cpp
     ${SBS_BASE}/src/shared/diagnostic/PrintLogger.cpp
 
-    # Radio
-    ${SBS_BASE}/src/shared/radio/Xbee/APIFrameParser.cpp
+    # Actuators
+    ${SBS_BASE}/src/shared/actuators/Servo/Servo.cpp
+
+    # Drivers
+    ${SBS_BASE}/src/shared/drivers/timer/TimestampTimer.cpp
 
     # Events
     ${SBS_BASE}/src/shared/events/EventBroker.cpp
@@ -34,10 +37,8 @@ add_library(boardcore-host STATIC EXCLUDE_FROM_ALL
     # Logger
     ${SBS_BASE}/src/shared/logger/Logger.cpp
 
-    # Math
-    ${SBS_BASE}/src/shared/math/Matrix.cpp
-    ${SBS_BASE}/src/shared/math/SkyQuaternion.cpp
-    ${SBS_BASE}/src/shared/math/Stats.cpp
+    # Radio
+    ${SBS_BASE}/src/shared/radio/Xbee/APIFrameParser.cpp
 
     # Scheduler
     ${SBS_BASE}/src/shared/scheduler/TaskScheduler.cpp
@@ -46,19 +47,16 @@ add_library(boardcore-host STATIC EXCLUDE_FROM_ALL
     ${SBS_BASE}/src/shared/sensors/SensorManager.cpp
     ${SBS_BASE}/src/shared/sensors/SensorSampler.cpp
 
-    # Timer
-    ${SBS_BASE}/src/shared/drivers/timer/TimestampTimer.cpp
-
-    # AeroUtils
-    ${SBS_BASE}/src/shared/utils/aero/AeroUtils.cpp
-
-    # TestUtils
-    ${SBS_BASE}/src/shared/utils/testutils/TestHelper.cpp
+    # Utils
+    ${SBS_BASE}/src/shared/utils/AeroUtils/AeroUtils.cpp
+    ${SBS_BASE}/src/shared/utils/SkyQuaternion/SkyQuaternion.cpp
+    ${SBS_BASE}/src/shared/utils/Stats/Stats.cpp
+    ${SBS_BASE}/src/shared/utils/TestUtils/TestHelper.cpp
 )
-add_library(SkywardBoardcore::Boardcore-host ALIAS boardcore-host)
+add_library(SkywardBoardcore::Boardcore::host ALIAS boardcore-host)
 target_include_directories(boardcore-host PUBLIC ${SBS_BASE}/src/shared)
 target_link_libraries(boardcore-host PUBLIC
-    Miosix::Miosix-host
+    Miosix::Miosix::host
     TSCPP::TSCPP
     Eigen3::Eigen
     fmt::fmt-header-only
diff --git a/cmake/boardcore.cmake b/cmake/boardcore.cmake
index a37df344373e492d13a75e30d802312f17430004..2fdea04d4a7b2628845b1bcfc62952433eee4e2b 100644
--- a/cmake/boardcore.cmake
+++ b/cmake/boardcore.cmake
@@ -26,6 +26,14 @@ include(boardcore-host)
 foreach(OPT_BOARD ${BOARDS})
     set(BOARDCORE_LIBRARY boardcore-${OPT_BOARD})
     add_library(${BOARDCORE_LIBRARY} STATIC EXCLUDE_FROM_ALL
+        # Actuators
+        ${SBS_BASE}/src/shared/actuators/HBridge/HBridge.cpp
+        ${SBS_BASE}/src/shared/actuators/Servo/Servo.cpp
+
+
+        # Algorithms
+        ${SBS_BASE}/src/shared/algorithms/NAS/NAS.cpp
+
         # Debug
         ${SBS_BASE}/src/shared/utils/Debug.cpp
         ${SBS_BASE}/src/shared/diagnostic/CpuMeter.cpp
@@ -35,14 +43,13 @@ foreach(OPT_BOARD ${BOARDS})
         ${SBS_BASE}/src/shared/drivers/adc/InternalADC.cpp
         ${SBS_BASE}/src/shared/drivers/canbus/Canbus.cpp
         ${SBS_BASE}/src/shared/drivers/canbus/CanInterrupt.cpp
-        ${SBS_BASE}/src/shared/drivers/hbridge/HBridge.cpp
         ${SBS_BASE}/src/shared/drivers/i2c/stm32f2_f4_i2c.cpp
         ${SBS_BASE}/src/shared/drivers/interrupt/external_interrupts.cpp
         ${SBS_BASE}/src/shared/drivers/timer/PWM.cpp
         ${SBS_BASE}/src/shared/drivers/timer/TimestampTimer.cpp
         ${SBS_BASE}/src/shared/drivers/runcam/Runcam.cpp
-        ${SBS_BASE}/src/shared/drivers/servo/Servo.cpp
         ${SBS_BASE}/src/shared/drivers/spi/SPITransaction.cpp
+        ${SBS_BASE}/src/shared/drivers/usart/USART.cpp
 
         # Events
         ${SBS_BASE}/src/shared/events/EventBroker.cpp
@@ -50,11 +57,6 @@ foreach(OPT_BOARD ${BOARDS})
         # Logger
         ${SBS_BASE}/src/shared/logger/Logger.cpp
 
-        # Math
-        ${SBS_BASE}/src/shared/math/Matrix.cpp
-        ${SBS_BASE}/src/shared/math/SkyQuaternion.cpp
-        ${SBS_BASE}/src/shared/math/Stats.cpp
-
         # Radio
         ${SBS_BASE}/src/shared/radio/gamma868/Gamma868.cpp
         ${SBS_BASE}/src/shared/radio/Xbee/APIFrameParser.cpp
@@ -72,8 +74,10 @@ foreach(OPT_BOARD ${BOARDS})
         ${SBS_BASE}/src/shared/sensors/BMP280/BMP280.cpp
         ${SBS_BASE}/src/shared/sensors/BMX160/BMX160.cpp
         ${SBS_BASE}/src/shared/sensors/BMX160/BMX160WithCorrection.cpp
+        ${SBS_BASE}/src/shared/sensors/HX711/HX711.cpp
         ${SBS_BASE}/src/shared/sensors/calibration/SensorDataExtra.cpp
         ${SBS_BASE}/src/shared/sensors/MAX6675/MAX6675.cpp
+        ${SBS_BASE}/src/shared/sensors/MAX31855/MAX31855.cpp
         ${SBS_BASE}/src/shared/sensors/MBLoadCell/MBLoadCell.cpp
         ${SBS_BASE}/src/shared/sensors/MPU9250/MPU9250.cpp
         ${SBS_BASE}/src/shared/sensors/MS5803/MS5803.cpp
@@ -82,17 +86,21 @@ foreach(OPT_BOARD ${BOARDS})
         ${SBS_BASE}/src/shared/sensors/UbloxGPS/UbloxGPS.cpp
         ${SBS_BASE}/src/shared/sensors/VN100/VN100.cpp
 
-        # AeroUtils
-        ${SBS_BASE}/src/shared/utils/aero/AeroUtils.cpp
+        # Calibration
+        ${SBS_BASE}/src/shared/sensors/calibration/SoftAndHardIronCalibration/SoftAndHardIronCalibration.cpp
 
-        # TestUtils
-        ${SBS_BASE}/src/shared/utils/testutils/TestHelper.cpp
+        # Utils
+        ${SBS_BASE}/src/shared/utils/AeroUtils/AeroUtils.cpp
+        ${SBS_BASE}/src/shared/utils/ButtonHandler/ButtonHandler.cpp
+        ${SBS_BASE}/src/shared/utils/SkyQuaternion/SkyQuaternion.cpp
+        ${SBS_BASE}/src/shared/utils/Stats/Stats.cpp
+        ${SBS_BASE}/src/shared/utils/TestUtils/TestHelper.cpp
     )
-    add_library(SkywardBoardcore::Boardcore-${OPT_BOARD} ALIAS ${BOARDCORE_LIBRARY})
+    add_library(SkywardBoardcore::Boardcore::${OPT_BOARD} ALIAS ${BOARDCORE_LIBRARY})
     target_include_directories(${BOARDCORE_LIBRARY} PUBLIC ${SBS_BASE}/src/shared)
     target_link_libraries(${BOARDCORE_LIBRARY} PUBLIC
-        Miosix::Miosix-${OPT_BOARD}
-        Mxgui::Mxgui-${OPT_BOARD}
+        Miosix::Miosix::${OPT_BOARD}
+        Mxgui::Mxgui::${OPT_BOARD}
         TSCPP::TSCPP
         Eigen3::Eigen
         fmt::fmt-header-only
diff --git a/cmake/dependencies.cmake b/cmake/dependencies.cmake
index 1043f75367791030a36a6f5f2285482eed9b2aec..5e77e8cd88ceb12f063df6c60b3f9abc5d7c9e4f 100644
--- a/cmake/dependencies.cmake
+++ b/cmake/dependencies.cmake
@@ -28,6 +28,7 @@ include(${KPATH}/config/boards.cmake)
 
 add_subdirectory(${SBS_BASE}/libs/miosix-host EXCLUDE_FROM_ALL)
 
+set(KPATH ${KPATH} CACHE PATH "Path to kernel directory")
 add_subdirectory(${SBS_BASE}/libs/mxgui EXCLUDE_FROM_ALL)
 
 add_subdirectory(${SBS_BASE}/libs/tscpp EXCLUDE_FROM_ALL)
diff --git a/cmake/sbs.cmake b/cmake/sbs.cmake
index 17f5f3ef5c0d2372ece9d4d751a70e248268806e..e3d2065733043e139997f535b224820f82e00b8a 100644
--- a/cmake/sbs.cmake
+++ b/cmake/sbs.cmake
@@ -47,7 +47,7 @@ function(sbs_target TARGET OPT_BOARD)
     endif()
     target_include_directories(${TARGET} PRIVATE src/shared)
     if(CMAKE_CROSSCOMPILING)
-        target_link_libraries(${TARGET} PRIVATE SkywardBoardcore::Boardcore-${OPT_BOARD})
+        target_link_libraries(${TARGET} PRIVATE SkywardBoardcore::Boardcore::${OPT_BOARD})
         add_custom_command(
             TARGET ${TARGET} POST_BUILD
             COMMAND ${CMAKE_OBJCOPY} -O ihex ${TARGET} ${TARGET}.hex
@@ -56,7 +56,7 @@ function(sbs_target TARGET OPT_BOARD)
             VERBATIM
         )
     else()
-        target_link_libraries(${TARGET} PRIVATE SkywardBoardcore::Boardcore-host)
+        target_link_libraries(${TARGET} PRIVATE SkywardBoardcore::Boardcore::host)
     endif()
 endfunction()
 
diff --git a/doc/Doxyfile b/doc/Doxyfile
index 22fa2cf1651e5958dd0cc1bea9af113e7bfaf484..607537c5692282602a46f660851838436d564a88 100644
--- a/doc/Doxyfile
+++ b/doc/Doxyfile
@@ -873,7 +873,7 @@ RECURSIVE              = YES
 # Note that relative paths are relative to the directory from which doxygen is
 # run.
 
-EXCLUDE                = ./src/shared/utils/testutils
+EXCLUDE                = ./src/shared/utils/TestUtils
 
 # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
 # directories that are symbolic links (a Unix file system feature) are excluded
diff --git a/libs/miosix-host b/libs/miosix-host
index f93e4e7f8025322c365e649cad96b65faf9d6ab5..ed8a4d8c24e59ce7513b1cebc9b6427fde55c7ce 160000
--- a/libs/miosix-host
+++ b/libs/miosix-host
@@ -1 +1 @@
-Subproject commit f93e4e7f8025322c365e649cad96b65faf9d6ab5
+Subproject commit ed8a4d8c24e59ce7513b1cebc9b6427fde55c7ce
diff --git a/libs/miosix-kernel b/libs/miosix-kernel
index a5d535dd506f30399edb3e315de41e2f97e87c4b..441aca88b44e5438e04abbef1639cd7f9df7bb37 160000
--- a/libs/miosix-kernel
+++ b/libs/miosix-kernel
@@ -1 +1 @@
-Subproject commit a5d535dd506f30399edb3e315de41e2f97e87c4b
+Subproject commit 441aca88b44e5438e04abbef1639cd7f9df7bb37
diff --git a/libs/mxgui b/libs/mxgui
index 3c3954db30418264863e8dda93ef3f8a91e00695..0052374bc85c2bf90753faba61ecc08905658c83 160000
--- a/libs/mxgui
+++ b/libs/mxgui
@@ -1 +1 @@
-Subproject commit 3c3954db30418264863e8dda93ef3f8a91e00695
+Subproject commit 0052374bc85c2bf90753faba61ecc08905658c83
diff --git a/libs/tscpp b/libs/tscpp
index c8674a48e794c246f79c56b163ebab72d3654846..935bf1944e1ddeaf51fd22cdce04a2a97762890f 160000
--- a/libs/tscpp
+++ b/libs/tscpp
@@ -1 +1 @@
-Subproject commit c8674a48e794c246f79c56b163ebab72d3654846
+Subproject commit 935bf1944e1ddeaf51fd22cdce04a2a97762890f
diff --git a/src/shared/math/Matrix.cpp b/old_examples/shared/math/Matrix.cpp
similarity index 100%
rename from src/shared/math/Matrix.cpp
rename to old_examples/shared/math/Matrix.cpp
diff --git a/src/shared/math/Matrix.h b/old_examples/shared/math/Matrix.h
similarity index 100%
rename from src/shared/math/Matrix.h
rename to old_examples/shared/math/Matrix.h
diff --git a/src/shared/math/Vec3.h b/old_examples/shared/math/Vec3.h
similarity index 100%
rename from src/shared/math/Vec3.h
rename to old_examples/shared/math/Vec3.h
diff --git a/old_examples/tests/catch/example-test-fsm.cpp b/old_examples/tests/catch/example-test-fsm.cpp
index 5c79a22446c0506fea3c8c91c6de62132d612a25..9c69e2b3000ca19754a076f3c52b379bb12e66b1 100644
--- a/old_examples/tests/catch/example-test-fsm.cpp
+++ b/old_examples/tests/catch/example-test-fsm.cpp
@@ -34,7 +34,7 @@
 #define protected public
 
 #include <miosix.h>
-#include <utils/testutils/TestHelper.h>
+#include <utils/TestUtils/TestHelper.h>
 
 #include <catch2/catch.hpp>
 
@@ -131,7 +131,7 @@ public:
     // Start the thread in the constructor
     FSMTestFixture()
     {
-        sEventBroker.start();
+        EventBroker::getInstance().start();
         fsm.start();
     }
     // Stop the thread in the destructor
@@ -168,7 +168,7 @@ TEST_CASE_METHOD(FSMTestFixture, "Testing async transitions")
 
         SECTION("S3 --> S4 if EV_B. Delayed event should be removed")
         {
-            EventCounter counter{*sEventBroker};
+            EventCounter counter{*EventBroker::getInstance()};
 
             // Post EV_B before EV_D fires. should move to S4 and remove EV_D
             // from the delayed events
diff --git a/old_examples/tests/catch/example-test-fsm.h b/old_examples/tests/catch/example-test-fsm.h
index 3aa648ec901654b0a6dbfaf836981f1d66e72e81..03f46222db911c7304992b33a1ee525771567b5e 100644
--- a/old_examples/tests/catch/example-test-fsm.h
+++ b/old_examples/tests/catch/example-test-fsm.h
@@ -39,8 +39,8 @@
  */
 enum ExampleEvents : uint8_t
 {
-    EV_A = EV_FIRST_SIGNAL,  // The first event must always have value
-                             // EV_FIRST_SIGNAL
+    EV_A = EV_FIRST_CUSTOM,  // The first event must always have value
+                             // EV_FIRST_CUSTOM
     EV_B,  // Values for the following event can be manually specified or
            // assigned automatically
     EV_C,
@@ -110,10 +110,10 @@ public:
     FSMExample() : FSM(&FSMExample::state_S1), v(0)
     {
         // Subscribe for events posted on TOPIC_T1
-        sEventBroker.subscribe(this, TOPIC_T1);
+        EventBroker::getInstance().subscribe(this, TOPIC_T1);
     }
 
-    ~FSMExample() { sEventBroker.unsubscribe(this); }
+    ~FSMExample() { EventBroker::getInstance().unsubscribe(this); }
 
     /*
      * State function definitions.
@@ -207,8 +207,8 @@ public:
                 v = 1;
 
                 // Post EV_D to itself in 1 seconds
-                delayed_ev_id =
-                    sEventBroker.postDelayed(Event{EV_D}, TOPIC_T1, 1000);
+                delayed_ev_id = EventBroker::getInstance().postDelayed(
+                    Event{EV_D}, TOPIC_T1, 1000);
 
                 break;
             }
@@ -219,7 +219,7 @@ public:
                 // don't remove a delayed event. This is an error! Uncomment to
                 // fix, see the wiki for further information !!!
 
-                // sEventBroker.removeDelayed(delayed_ev_id);
+                // EventBroker::getInstance().removeDelayed(delayed_ev_id);
                 break;
             }
             case EV_B:
diff --git a/old_examples/tests/catch/misc/xbee-bitrate.cpp b/old_examples/tests/catch/misc/xbee-bitrate.cpp
index 6bf877b9ee87a832215351ffbd0d65b2b7bbf44d..47f40a10fce5ea70e4060026333041251e2c4db4 100644
--- a/old_examples/tests/catch/misc/xbee-bitrate.cpp
+++ b/old_examples/tests/catch/misc/xbee-bitrate.cpp
@@ -22,9 +22,9 @@
 
 #include <drivers/spi/SPIDriver.h>
 #include <interfaces-impl/hwmapping.h>
-#include <math/Stats.h>
 #include <miosix.h>
 #include <radio/Xbee/Xbee.h>
+#include <utils/Stats/Stats.h>
 
 #include <cstdio>
 #include <iostream>
diff --git a/old_examples/tests/catch/misc/xbee-send-rcv.cpp b/old_examples/tests/catch/misc/xbee-send-rcv.cpp
index cba700960f8e653a164f6dc9394851a5c00b5044..87c8c8335961362424518bfb8cdf842bca50d559 100644
--- a/old_examples/tests/catch/misc/xbee-send-rcv.cpp
+++ b/old_examples/tests/catch/misc/xbee-send-rcv.cpp
@@ -22,9 +22,9 @@
 
 #include <drivers/spi/SPIDriver.h>
 #include <interfaces-impl/hwmapping.h>
-#include <math/Stats.h>
 #include <miosix.h>
 #include <radio/Xbee/Xbee.h>
+#include <utils/Stats/Stats.h>
 
 #include <cstdio>
 #include <iostream>
diff --git a/old_examples/tests/catch/misc/xbee-time-to-send.cpp b/old_examples/tests/catch/misc/xbee-time-to-send.cpp
index c915459d6b41fb1446239b49d76dfbf70485cd0a..eb08047f3087a7fe5b9aa00aaaecaaf006836e79 100644
--- a/old_examples/tests/catch/misc/xbee-time-to-send.cpp
+++ b/old_examples/tests/catch/misc/xbee-time-to-send.cpp
@@ -22,9 +22,9 @@
 
 #include <drivers/BusTemplate.h>
 #include <interfaces-impl/hwmapping.h>
-#include <math/Stats.h>
 #include <miosix.h>
 #include <radio/Xbee/Xbee.h>
+#include <utils/Stats/Stats.h>
 
 #include <cstdio>
 #include <iostream>
diff --git a/old_examples/tests/catch/test-xbee.cpp b/old_examples/tests/catch/test-xbee.cpp
index 9973a4db02411e48f2acc8bc9f962d1c22493544..3ec81dfba6464ce558d983edec75abb2cbde0d2c 100644
--- a/old_examples/tests/catch/test-xbee.cpp
+++ b/old_examples/tests/catch/test-xbee.cpp
@@ -30,7 +30,7 @@
 #define private public
 
 #include <radio/Xbee/Xbee.h>
-#include <utils/testutils/BusTemplateMock.h>
+#include <utils/TestUtils/BusTemplateMock.h>
 
 using std::vector;
 
diff --git a/old_examples/tests/drivers/test-imu-adis.cpp b/old_examples/tests/drivers/test-imu-adis.cpp
index e6f5e31ac86b92e29bd1a45348605d402464447d..777c33d8f60da0d7f638fdaaf3ec229f54af6a4e 100644
--- a/old_examples/tests/drivers/test-imu-adis.cpp
+++ b/old_examples/tests/drivers/test-imu-adis.cpp
@@ -25,9 +25,9 @@
 #include <drivers/BusTemplate.h>
 #include <drivers/spi/SensorSpi.h>
 #include <interfaces-impl/hwmapping.h>
-#include <math/Stats.h>
 #include <sensors/ADIS16405/ADIS16405.h>
 #include <sensors/SensorSampler.h>
+#include <utils/Stats/Stats.h>
 
 using namespace miosix;
 using namespace miosix::interfaces;
diff --git a/old_examples/tests/drivers/test-lsm.cpp b/old_examples/tests/drivers/test-lsm.cpp
index 5aec0b6344c8db38d24387512b62cc3d7d5a7fb2..6f0dbc430be8d67b25d945007e6c9519df8647b5 100644
--- a/old_examples/tests/drivers/test-lsm.cpp
+++ b/old_examples/tests/drivers/test-lsm.cpp
@@ -25,9 +25,9 @@
 #include <drivers/BusTemplate.h>
 #include <drivers/spi/SensorSpi.h>
 #include <interfaces-impl/hwmapping.h>
-#include <math/Stats.h>
 #include <sensors/LSM6DS3H/LSM6DS3H.h>
 #include <sensors/SensorSampler.h>
+#include <utils/Stats/Stats.h>
 
 using namespace miosix;
 using namespace miosix::interfaces;
diff --git a/scripts/generators/eventgen.py b/scripts/generators/eventgen.py
index dedf167d77171b073d56623f605f9139855b4010..f5014d9517d50e1ee6b3db965868af308db73ffc 100755
--- a/scripts/generators/eventgen.py
+++ b/scripts/generators/eventgen.py
@@ -93,7 +93,7 @@ def generate_events_h(events, date):
 
     # Prepare the event list
     enum_event_list = list(events)
-    enum_event_list[0] += ' = EV_FIRST_SIGNAL'
+    enum_event_list[0] += ' = EV_FIRST_CUSTOM'
     enum_event_list = '\n'.join(
         [' '*4 + event + ',' for event in enum_event_list])
     vector_event_list = '\n'.join([' '*4 + event + ',' for event in events])
diff --git a/scripts/generators/fsmgen.py b/scripts/generators/fsmgen.py
index b3c6bab216cb027497e6c5c3fd4129f91b583ba7..5288d3618a19e29653f97d4a7ce26e8bc5bc649e 100755
--- a/scripts/generators/fsmgen.py
+++ b/scripts/generators/fsmgen.py
@@ -59,7 +59,7 @@ STATE_FUNCTION_CASE_TEMPLATE = ' '*8 + 'case {event}:\n' + \
     ' '*12 + 'break;\n' + \
     ' '*8 + '}}'
 UTILITY_FUNCTION_DECLARATION_TEMPLATE = 'void {function_name}();'
-TOPICS_SUBSCIPTION_TEMPLATE = ' '*4 + 'sEventBroker.subscribe(this, {topic});'
+TOPICS_SUBSCIPTION_TEMPLATE = ' '*4 + 'EventBroker::getInstance().subscribe(this, {topic});'
 UTILITY_FUNCTION_DEFINITION_TEMPLATE = 'void {state_machine_name}Controller::{function_name}()\n{{\n    // ...\n}}'
 STATE_FUNCTION_CASE_TRANSITION_TO_TARGET_TEMPLATE = 'transition(&{state_machine_name}Controller::state_{state_name});'
 TEST_CASE_METHOD_TEMPLATE = 'TEST_CASE_METHOD({state_machine_name}ControllerFixture, "Testing transitions from {state}")\n' + \
diff --git a/scripts/generators/templates/FSMController.cpp.template b/scripts/generators/templates/FSMController.cpp.template
index 583fb435c4bdb89414a829df229a87349a374155..2ea5f355f54898bee5e1eb88647bcb87b97ac4e9 100644
--- a/scripts/generators/templates/FSMController.cpp.template
+++ b/scripts/generators/templates/FSMController.cpp.template
@@ -41,7 +41,7 @@ using namespace {state_machine_name}Config;
 
 {state_machine_name}Controller::~{state_machine_name}Controller()
 {{
-    sEventBroker.unsubscribe(this);
+    EventBroker::getInstance().unsubscribe(this);
 }}
 
 {states_functions_definitions}
diff --git a/scripts/generators/templates/test.cpp.template b/scripts/generators/templates/test.cpp.template
index 73a93088404afceda5b285fa48429ce3e88172d0..2eb55502e2ed85421c8bed9310b86efd1e715f86 100644
--- a/scripts/generators/templates/test.cpp.template
+++ b/scripts/generators/templates/test.cpp.template
@@ -30,11 +30,11 @@
 
 #include <miosix.h>
 
-#include <utils/testutils/catch.hpp>
+#include <utils/TestUtils/catch.hpp>
 
 #include "{state_machine_name}Controller/{state_machine_name}Controller.h"
 #include <events/Events.h>
-#include <utils/testutils/TestHelper.h>
+#include <utils/TestUtils/TestHelper.h>
 
 using miosix::Thread;
 using namespace DeathStackBoard;
@@ -46,7 +46,7 @@ public:
     {state_machine_name}ControllerFixture()
     {{
         controller = new {state_machine_name}Controller();
-        sEventBroker.start();
+        EventBroker::getInstance().start();
         controller->start();
     }}
 
@@ -54,8 +54,8 @@ public:
     ~{state_machine_name}ControllerFixture()
     {{
         controller->stop();
-        sEventBroker.unsubscribe(controller);
-        sEventBroker.clearDelayedEvents();
+        EventBroker::getInstance().unsubscribe(controller);
+        EventBroker::getInstance().clearDelayedEvents();
         delete controller;
     }}
 
diff --git a/scripts/logdecoder/Makefile b/scripts/logdecoder/Makefile
index 0a30ee85eaf9786bc68f52dcd335486e4766392e..2523965cd9ce3b7e20457d7d5c1f047d90386c7c 100644
--- a/scripts/logdecoder/Makefile
+++ b/scripts/logdecoder/Makefile
@@ -1,6 +1,12 @@
+BOARDCORE := ../../
 
 all:
-	g++ -std=c++11 -O2 -o logdecoder logdecoder.cpp ../libs/tscpp/stream.cpp  -I ../libs
-
+	g++ -std=c++17 -O2 -o logdecoder logdecoder.cpp \
+					-DCOMPILE_FOR_X86 \
+					$(BOARDCORE)libs/tscpp/tscpp/stream.cpp \
+	 				-I$(BOARDCORE)libs/mavlink_skyward_lib \
+	 				-I$(BOARDCORE)libs/eigen \
+	 				-I$(BOARDCORE)libs/tscpp \
+	 				-I$(BOARDCORE)src/shared
 clean:
 	rm logdecoder
diff --git a/scripts/logdecoder/logdecoder.cpp b/scripts/logdecoder/logdecoder.cpp
index 96b81e338727137ff3d25d077d1c56891226fd15..670873a182784e3a882b1649fb48e69502e3b164 100644
--- a/scripts/logdecoder/logdecoder.cpp
+++ b/scripts/logdecoder/logdecoder.cpp
@@ -1,59 +1,139 @@
-/***************************************************************************
- *   Copyright (C) 2018 by Terraneo Federico                               *
- *                                                                         *
- *   This program is free software; you can redistribute it and/or modify  *
- *   it under the terms of the GNU General Public License as published by  *
- *   the Free Software Foundation; either version 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 General  *
- *   Public License. This exception does not invalidate any other reasons  *
- *   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/>   *
- ***************************************************************************/
-
-/*
- * This is a stub program for the program that will decode the logged data.
- * Fill in the TODO to make it work.
+/* Copyright (c) 2018-2022 Skyward Experimental Rocketry
+ * Authors: Terrane Federico
+ *
+ * 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 <iostream>
+#include <logger/Deserializer.h>
+#include <logger/LogTypes.h>
+#include <tscpp/stream.h>
+
 #include <fstream>
+#include <iostream>
 #include <stdexcept>
-#include <tscpp/stream.h>
+#include <string>
 
-//TODO: add here include files of serialized classes
-#include "../src/shared/logger/LogStats.h"
-#include "../src/entrypoints/test_logger.h"
+/**
+ * @brief Binary log files decoder.
+ *
+ * This program is to compile for you computer and decodes binary log files
+ * through the tscpp library.
+ *
+ * In LogTypes.h there should be included all the classes you want to
+ * deserialize.
+ */
 
-using namespace std;
 using namespace tscpp;
+using namespace Boardcore;
+
+void showUsage(string commandName);
+
+/**
+ * @brief Deserialize a file.
+ *
+ * @param fileName File name complete with extension.
+ * @return Whether the deserialization was successful.
+ */
+bool deserialize(string fileName);
+
+/**
+ * @brief Deserialize all log file in the directory. Assumes the log files named
+ * as logXX.dat.
+ *
+ * Scans for all the 100 possible log files and decode the ones found.
+ *
+ * @return False if an error was encountered.
+ */
+bool deserializeAll();
+
+int main(int argc, char* argv[])
+{
+    if (argc < 2)
+    {
+        showUsage(string(argv[0]));
+        return 1;  // Error
+    }
+
+    bool success = false;
+    string arg1  = string(argv[1]);
+
+    // Help message
+    if (arg1 == "-h" || arg1 == "--help")
+    {
+        showUsage(string(argv[0]));
+        return 0;
+    }
 
-int main(int argc, char *argv[])
-try {
-    TypePoolStream tp;
-    //TODO: Register the serialized classes
-    tp.registerType<LogStats>([](LogStats& t){ t.print(cout); cout<<'\n'; });
-    tp.registerType<Dummy>([](Dummy& t){ t.print(cout); cout<<'\n'; });
-
-    if(argc!=2) return 1;
-    ifstream in(argv[1]);
-    in.exceptions(ios::eofbit);
-    UnknownInputArchive ia(in,tp);
-    for(;;) ia.unserialize();
-} catch(exception&) {
+    // File deserialization
+    if (arg1 == "-a" || arg1 == "--all")
+        success = deserializeAll();
+    else
+        success = deserialize(arg1);
+
+    // End
+    if (success)
+        std::cout << "Deserialization completed successfully\n";
+    else
+        std::cout << "Deserialization ended with errors\n";
     return 0;
 }
+
+void showUsage(string commandName)
+{
+    std::cerr << "Usage: " << commandName << " {-a | <log_file_name> | -h}"
+              << "Options:\n"
+              << "\t-h,--help\t\tShow this help message\n"
+              << "\t-a,--all Deserialize all logs in the current directory "
+                 "named as logXX.dat\n"
+              << std::endl;
+}
+
+bool deserialize(string fileName)
+{
+    std::cout << "Deserializing " << fileName << "...\n";
+
+    Deserializer d(fileName);
+    LogTypes::registerTypes(d);
+
+    return d.deserialize();
+}
+
+bool deserializeAll()
+{
+    std::cout << "Deserializing all logs in the current directory...\n";
+
+    for (int i = 0; i < 100; i++)
+    {
+        char fileName[11];
+        sprintf(fileName, "log%02d.dat", i);
+        struct stat st;
+
+        // Check if the current logfile exists
+        if (stat(fileName, &st) != 0)
+            continue;
+
+        // File found
+        if (!deserialize(string(fileName)))
+            return false;
+
+        std::cout << fileName << " deserialized successfully";
+    }
+
+    return true;
+}
diff --git a/scripts/logdecoder/sample_no_cereal.dat.dat b/scripts/logdecoder/sample_no_cereal.dat.dat
deleted file mode 100644
index 23dff819fee59674f0cfff237b94fffa191d4a06..0000000000000000000000000000000000000000
Binary files a/scripts/logdecoder/sample_no_cereal.dat.dat and /dev/null differ
diff --git a/scripts/logdecoder/xbee/.gitignore b/scripts/logdecoder/xbee/.gitignore
deleted file mode 100644
index 3e6fd163051d66dceb9c808d07e314ae4d022ee5..0000000000000000000000000000000000000000
--- a/scripts/logdecoder/xbee/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-logdecoder
diff --git a/scripts/logdecoder/xbee/LogTypes.h b/scripts/logdecoder/xbee/LogTypes.h
deleted file mode 100644
index cb82189b489a302f28b3f62dfd61fec88c1326b7..0000000000000000000000000000000000000000
--- a/scripts/logdecoder/xbee/LogTypes.h
+++ /dev/null
@@ -1,73 +0,0 @@
-/* Copyright (c) 2015-2018 Skyward Experimental Rocketry
- * Author: Luca Erbetta
- *
- * 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
-
-#include <logger/Deserializer.h>
-#include <logger/LogStats.h>
-#include <radio/Xbee/APIFramesLog.h>
-#include <radio/Xbee/Mark.h>
-#include <radio/Xbee/XbeeStatus.h>
-#include <radio/Xbee/XbeeTestData.h>
-
-#include <fstream>
-#include <iostream>
-
-// Serialized classes
-using std::ofstream;
-
-namespace Boardcore
-{
-
-template <typename T>
-void print(T& t, ostream& os)
-{
-    t.print(os);
-}
-
-template <typename T>
-void registerType(Deserializer& ds)
-{
-    ds.registerType<T>(print<T>, T::header());
-}
-
-void registerTypes(Deserializer& ds)
-{
-    registerType<LogStats>(ds);
-
-    registerType<Xbee::APIFrameLog>(ds);
-    registerType<Xbee::ATCommandFrameLog>(ds);
-    registerType<Xbee::ATCommandResponseFrameLog>(ds);
-    registerType<Xbee::ModemStatusFrameLog>(ds);
-    registerType<Xbee::TXRequestFrameLog>(ds);
-    registerType<Xbee::TXStatusFrameLog>(ds);
-    registerType<Xbee::RXPacketFrameLog>(ds);
-    registerType<Xbee::XbeeStatus>(ds);
-
-    registerType<TxData>(ds);
-    registerType<RxData>(ds);
-    registerType<Mark>(ds);
-    registerType<XbeeConfig>(ds);
-    registerType<EnergyScanData>(ds);
-}
-
-}  // namespace Boardcore
diff --git a/scripts/logdecoder/xbee/Makefile b/scripts/logdecoder/xbee/Makefile
deleted file mode 100644
index 47258b629723d159885432ac1e485023f5d40002..0000000000000000000000000000000000000000
--- a/scripts/logdecoder/xbee/Makefile
+++ /dev/null
@@ -1,13 +0,0 @@
-BASE := ../../../../../
-
-all:
-	g++ -std=c++17 -O2 -o logdecoder logdecoder.cpp \
-					-DCOMPILE_FOR_X86 \
-					$(BASE)libs/tscpp/stream.cpp \
-	 				-I$(BASE)src/shared \
-	 				-I$(BASE)src/tests \
-					-I$(BASE)libs \
-					-I$(BASE)libs/miosix-kernel/miosix
-
-clean:
-	rm logdecoder
diff --git a/scripts/logdecoder/xbee/logdecoder.cpp b/scripts/logdecoder/xbee/logdecoder.cpp
deleted file mode 100644
index 40577eef2088983028f356bb13b341f97641ed3d..0000000000000000000000000000000000000000
--- a/scripts/logdecoder/xbee/logdecoder.cpp
+++ /dev/null
@@ -1,168 +0,0 @@
-/* Copyright (c) 2018 Skyward Experimental Rocketry
- * Author: Federico Terraneo
- *
- * 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.
- */
-
-/*
- * This is a stub program for the program that will decode the logged data.
- * You must first define a function
- *  "void registerTypes(Deserializer& ds);"
- *
- * that registers all the required log data types in the deserializer and then
- * INCLUDE this cpp file in your source. finally compile and run
- */
-
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <tscpp/stream.h>
-
-#include <filesystem>
-#include <fstream>
-#include <iostream>
-#include <stdexcept>
-#include <string>
-#include <vector>
-
-#include "LogTypes.h"
-
-using namespace std;
-using namespace tscpp;
-
-namespace fs = filesystem;
-
-void showHelp(string cmdName)
-{
-    std::cerr << "Usage: " << cmdName
-              << " {-a [logs_diretory] | <log_file_path> | -h}"
-              << "Options:\n"
-              << "\t-h,--help\t\tShow help message\n"
-              << "\t-a,--all [dir=\".\"] Deserialize all logs in the provided "
-                 "directory\n"
-              << std::endl;
-}
-
-vector<fs::path> listLogFiles(fs::path dir)
-{
-    vector<fs::path> out;
-    for (const auto& entry : fs::directory_iterator(dir))
-    {
-        if (entry.exists() && entry.is_regular_file())
-        {
-            if (entry.path().extension() == ".dat")
-            {
-                out.push_back(entry.path());
-            }
-        }
-    }
-    return out;
-}
-
-bool deserialize(fs::path log_path)
-{
-    cout << "Deserializing " << log_path << ".dat...\n";
-
-    // remove extension
-    log_path.replace_extension("");
-    Deserializer d(log_path);
-    registerTypes(d);
-
-    return d.deserialize();
-}
-
-bool deserializeAll(fs::path dir = ".")
-{
-    vector<fs::path> logs = listLogFiles(dir);
-    for (auto log : logs)
-    {
-        if (!deserialize(log.string()))
-        {
-            return false;
-        }
-    }
-
-    return true;
-}
-
-int main(int argc, char* argv[])
-{
-    if (argc < 2)
-    {
-        showHelp(string(argv[0]));
-        return 1;
-    }
-
-    bool success = false;
-    string arg1  = string(argv[1]);
-    if (arg1 == "-h" || arg1 == "--help")
-    {
-        showHelp(string(argv[0]));
-        return 0;
-    }
-
-    if (arg1 == "-a" || arg1 == "--all")
-    {
-        fs::path dir = ".";
-        if (argc == 3)
-        {
-            string arg2 = string(argv[2]);
-            fs::directory_entry entry(arg2);
-            if (entry.exists() && entry.is_directory())
-            {
-                dir = arg2;
-            }
-            else
-            {
-                cout << "Second argument after '-a' or '--all' must be a "
-                        "directory\n";
-                showHelp(string(argv[0]));
-                return 1;
-            }
-        }
-        cout << "Deserializing all logs...\n";
-        success = deserializeAll(dir);
-    }
-    else if (arg1[0] == '-')
-    {
-        cerr << "Unknown option\n";
-        return 1;
-    }
-    else
-    {
-        fs::directory_entry entry(arg1);
-        if (entry.exists() && entry.is_regular_file())
-        {
-            success = deserialize(arg1);
-        }
-        else
-        {
-            showHelp(string(argv[0]));
-            return 1;
-        }
-    }
-
-    if (success)
-    {
-        cout << "Deserialization completed successfully.\n";
-    }
-    else
-    {
-        cout << "Deserialization ended with errors.\n";
-    }
-}
diff --git a/src/entrypoints/bmx160-calibration-entry.cpp b/src/entrypoints/bmx160-calibration-entry.cpp
index 0124a2310951b7803879e9fce2c79466ba04880f..d2aa4746b97f41ee1b47c5306c6ea884f1f859ef 100644
--- a/src/entrypoints/bmx160-calibration-entry.cpp
+++ b/src/entrypoints/bmx160-calibration-entry.cpp
@@ -24,7 +24,8 @@
 #include <drivers/timer/TimestampTimer.h>
 #include <sensors/BMX160/BMX160.h>
 #include <sensors/BMX160/BMX160WithCorrection.h>
-#include <sensors/calibration/SoftIronCalibration.h>
+#include <sensors/calibration/AxisOrientation.h>
+#include <sensors/calibration/SoftAndHardIronCalibration/SoftAndHardIronCalibration.h>
 #include <utils/Debug.h>
 
 #include <fstream>
@@ -41,9 +42,9 @@ constexpr int ACC_CALIBRATION_SLEEP_TIME     = 25;  // [us]
 constexpr int ACC_CALIBRATION_N_ORIENTATIONS = 6;
 
 constexpr int MAG_CALIBRATION_SAMPLES  = 500;
-constexpr int MAG_CALIBRATION_DURAITON = 60;  // [s]
+constexpr int MAG_CALIBRATION_DURATION = 60;  // [s]
 constexpr int MAG_CALIBRATION_SLEEP_TIME =
-    MAG_CALIBRATION_DURAITON * 1000 / MAG_CALIBRATION_SAMPLES;  // [us]
+    MAG_CALIBRATION_DURATION * 1000 / MAG_CALIBRATION_SAMPLES;  // [us]
 
 BMX160* bmx160;
 
@@ -330,7 +331,7 @@ BMX160CorrectionParameters calibrateMagnetometer(
     BMX160CorrectionParameters correctionParameters)
 {
     Eigen::Matrix<float, 3, 2> m;
-    auto* calibrationModel = new SoftIronCalibration<MAG_CALIBRATION_SAMPLES>;
+    auto* calibrationModel = new SoftAndHardIronCalibration;
     Eigen::Vector3f avgMag{0, 0, 0}, vec;
 
     SPIBus bus(SPI1);
@@ -389,7 +390,7 @@ BMX160CorrectionParameters calibrateMagnetometer(
         "the "
         "most different directions.\n");
     printf("The calibration will run for %d seconds\n",
-           MAG_CALIBRATION_DURAITON);
+           MAG_CALIBRATION_DURATION);
 
     if (!askToContinue())
     {
@@ -412,26 +413,6 @@ BMX160CorrectionParameters calibrateMagnetometer(
     printf("Computing the result....");
     auto corrector = calibrationModel->computeResult();
 
-    // Save the calibration data in the sd card
-    // Save the correction parameters in the file
-    {
-        std::ofstream magnetometerCalibrationDataFile(
-            MAG_CALIBRATION_DATA_FILE);
-
-        auto samples        = calibrationModel->getSamples();
-        int numberOfSamples = samples.rows();
-
-        for (int i = 0; i < numberOfSamples; i++)
-        {
-            auto row = samples.row(i);
-            for (int j = 0; j < row.cols(); j++)
-            {
-                magnetometerCalibrationDataFile << samples.row(i)(j) << ",";
-            }
-            magnetometerCalibrationDataFile << "\n";
-        }
-    }
-
     corrector >> m;
     corrector >> correctionParameters.magnetoParams;
 
diff --git a/src/entrypoints/imu-calibration.cpp b/src/entrypoints/imu-calibration.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..fbb00378cb7c5c7e0d21d41616847012ce166ad4
--- /dev/null
+++ b/src/entrypoints/imu-calibration.cpp
@@ -0,0 +1,131 @@
+/* Copyright (c) 2022 Skyward Experimental Rocketry
+ * Authors: 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 <sensors/MPU9250/MPU9250.h>
+#include <sensors/SensorManager.h>
+#include <sensors/calibration/AxisOrientation.h>
+#include <sensors/calibration/SoftAndHardIronCalibration/SoftAndHardIronCalibration.h>
+#include <utils/Debug.h>
+
+#include <iostream>
+
+using namespace Boardcore;
+using namespace Eigen;
+using namespace miosix;
+
+int menu();
+bool askToContinue();
+
+void calibrateMagnetometer();
+
+constexpr int MAG_CALIBRATION_DURATION = 30;  // [s]
+constexpr int IMU_SAMPLE_PERIOD        = 10;  // [ms] 100Hz
+
+int main()
+{
+    // Greet the user
+    printf("\nWelcome to the calibration procedure!\n");
+
+    // Make the user choose what to do
+    switch (menu())
+    {
+        case 2:
+            calibrateMagnetometer();
+            break;
+
+        default:
+            break;
+    }
+
+    printf("end\n");
+    reboot();
+}
+
+int menu()
+{
+    int choice;
+
+    printf("\nWhat do you want to do?\n");
+    printf("1. Calibrate accelerometer\n");
+    printf("2. Calibrate magnetometer\n");
+    printf("3. Set minimum gyroscope samples for calibration\n");
+    printf("\n>> ");
+    scanf("%d", &choice);
+
+    return choice;
+}
+
+bool askToContinue()
+{
+    char c;
+
+    std::cout << "Write 'c' to continue, otherwise stop:\n";
+    scanf("%c", &c);
+
+    return c != 'c';
+}
+
+void calibrateMagnetometer()
+{
+    SPIBus spiBus(SPI1);
+    MPU9250 mpu(spiBus, sensors::mpu9250::cs::getPin());
+    mpu.init();
+
+    printf("Now the magnetometer calibration will begin\n");
+    printf("Please, rotate the gyroscope A LOT!\n");
+    printf("The calibration will run for %d seconds\n",
+           MAG_CALIBRATION_DURATION);
+
+    // if (!askToContinue())
+    //     return;
+    printf("Starting...\n");
+
+    // Prepare the calibration model
+    SoftAndHardIronCalibration calibration;
+
+    // Prepare and start the sensor manager
+    TaskScheduler scheduler;
+    scheduler.addTask([]() { printf("...\n"); }, 500);
+    scheduler.start();
+
+    // Wait and then stop the sampling
+    auto startTick = getTick();
+    auto lastTick  = startTick;
+    while (getTick() - startTick < MAG_CALIBRATION_DURATION * 1e3)
+    {
+        mpu.sample();
+        calibration.feed(mpu.getLastSample());
+        Thread::sleepUntil(lastTick + IMU_SAMPLE_PERIOD);
+        lastTick = getTick();
+    }
+
+    scheduler.stop();
+
+    printf("Computing the result...\n");
+
+    auto correction = calibration.computeResult();
+
+    printf("b: the bias vector\n");
+    std::cout << correction.getOffset().transpose() << std::endl;
+    printf("g: the gain to be multiplied to the input vector\n");
+    std::cout << correction.getGain().transpose() << std::endl;
+}
diff --git a/src/entrypoints/kernel-testsuite.cpp b/src/entrypoints/kernel-testsuite.cpp
index 8a84d3533bec6477c13def0060f364d9e31649d1..89c3260272c61d4c0e2e4247573fc97362176ce3 100644
--- a/src/entrypoints/kernel-testsuite.cpp
+++ b/src/entrypoints/kernel-testsuite.cpp
@@ -187,8 +187,7 @@ int main()
                 break;
         }
         // For testing mpu
-        unsigned int *m;
-        (void)m;  // Disable unused warning
+        unsigned int *m __attribute__((unused));
         switch (c)
         {
             case 't':
@@ -427,9 +426,7 @@ void process_test_file_concurrency()
             fail("Wrong data from file 2");
     }
 
-    // cppcheck-suppress nullPointerRedundantCheck
     fclose(f1);
-    // cppcheck-suppress nullPointerRedundantCheck
     fclose(f2);
 
     pass();
@@ -587,7 +584,7 @@ void syscall_test_files()
             fail("Open with O_RDWR should have failed, the file doesn't exist");
             break;
         case 2:
-            fail("Cannot craete new file");
+            fail("Cannot create new file");
             break;
         case 3:
             fail("file descriptor not valid");
@@ -599,7 +596,7 @@ void syscall_test_files()
             fail("read has read the wrong amount of data");
             break;
         case 6:
-            fail("readed data doesn't match the written one");
+            fail("read data doesn't match the written one");
             break;
         case 7:
             fail("close hasn't returned 0");
@@ -608,10 +605,10 @@ void syscall_test_files()
             fail("open with O_RDWR failed, but the file exists");
             break;
         case 9:
-            fail("read has return the wrogn amount of data");
+            fail("read has return the wrong amount of data");
             break;
         case 10:
-            fail("readed data doesn't match the written one");
+            fail("read data doesn't match the written one");
             break;
         case 11:
             fail("close hasn't returned 0");
@@ -638,9 +635,8 @@ and test argv pointer passing
 
 static volatile bool t1_v1;
 
-static void t1_p1(void *argv)
+static void t1_p1(void *argv __attribute__((unused)))
 {
-    (void)argv;
     for (;;)
     {
         if (Thread::testTerminate())
@@ -742,10 +738,8 @@ Thread::getCurrentThread
 static volatile bool t2_v1;
 static Thread *t2_p_v1;
 
-static void t2_p1(void *argv)
+static void t2_p1(void *argv __attribute__((unused)))
 {
-    (void)argv;
-
     // This is to fix a race condition: the immediately after the thread
     // creation a yield occurs, t2_p_v1 is not yet assigned so the check fails
     Thread::sleep(5);
@@ -762,9 +756,8 @@ static void t2_p1(void *argv)
     }
 }
 
-static void t2_p2(void *argv)
+static void t2_p2(void *argv __attribute__((unused)))
 {
-    (void)argv;
     while (Thread::testTerminate() == false)
         t2_v1 = true;
 }
@@ -836,9 +829,8 @@ getTick()
 also tests creation of multiple instances of the same thread
 */
 
-static void t3_p1(void *argv)
+static void t3_p1(void *argv __attribute__((unused)))
 {
-    (void)argv;
     const int SLEEP_TIME = 100;
     for (;;)
     {
@@ -855,9 +847,8 @@ static void t3_p1(void *argv)
 static volatile bool t3_v2;       // Set to true by t3_p2
 static volatile bool t3_deleted;  // Set when an instance of t3_p2 is deleted
 
-static void t3_p2(void *argv)
+static void t3_p2(void *argv __attribute__((unused)))
 {
-    (void)argv;
     const int SLEEP_TIME = 15;
     for (;;)
     {
@@ -959,9 +950,8 @@ Thread::IRQgetPriority
 
 static volatile bool t4_v1;
 
-static void t4_p1(void *argv)
+static void t4_p1(void *argv __attribute__((unused)))
 {
-    (void)argv;
     for (;;)
     {
         if (Thread::testTerminate())
@@ -1112,9 +1102,8 @@ static volatile bool t5_v1;
 static volatile bool
     t5_v2;  // False=testing Thread::wait() else Thread::IRQwait()
 
-static void t5_p1(void *argv)
+static void t5_p1(void *argv __attribute__((unused)))
 {
-    (void)argv;
     for (;;)
     {
         if (Thread::testTerminate())
@@ -1231,9 +1220,8 @@ static Sequence seq;
 static Mutex t6_m1;
 static FastMutex t6_m1a;
 
-static void t6_p1(void *argv)
+static void t6_p1(void *argv __attribute__((unused)))
 {
-    (void)argv;
     t6_m1.lock();
     seq.add('1');
     Thread::sleep(100);
@@ -1244,9 +1232,8 @@ static void t6_p1(void *argv)
     t6_m1.unlock();
 }
 
-static void t6_p2(void *argv)
+static void t6_p2(void *argv __attribute__((unused)))
 {
-    (void)argv;
     t6_m1.lock();
     seq.add('2');
     Thread::sleep(100);
@@ -1256,9 +1243,8 @@ static void t6_p2(void *argv)
     t6_m1.unlock();
 }
 
-static void t6_p3(void *argv)
+static void t6_p3(void *argv __attribute__((unused)))
 {
-    (void)argv;
     t6_m1.lock();
     seq.add('3');
     Thread::sleep(100);
@@ -1268,9 +1254,8 @@ static void t6_p3(void *argv)
     t6_m1.unlock();
 }
 
-static void t6_p4(void *argv)
+static void t6_p4(void *argv __attribute__((unused)))
 {
-    (void)argv;
     t6_m1.lock();
     for (;;)
     {
@@ -1287,36 +1272,32 @@ static void t6_p4(void *argv)
     }
 }
 
-static void t6_p1a(void *argv)
+static void t6_p1a(void *argv __attribute__((unused)))
 {
-    (void)argv;
     t6_m1a.lock();
     seq.add('1');
     Thread::sleep(100);
     t6_m1a.unlock();
 }
 
-static void t6_p2a(void *argv)
+static void t6_p2a(void *argv __attribute__((unused)))
 {
-    (void)argv;
     t6_m1a.lock();
     seq.add('2');
     Thread::sleep(100);
     t6_m1a.unlock();
 }
 
-static void t6_p3a(void *argv)
+static void t6_p3a(void *argv __attribute__((unused)))
 {
-    (void)argv;
     t6_m1a.lock();
     seq.add('3');
     Thread::sleep(100);
     t6_m1a.unlock();
 }
 
-static void t6_p4a(void *argv)
+static void t6_p4a(void *argv __attribute__((unused)))
 {
-    (void)argv;
     t6_m1a.lock();
     for (;;)
     {
@@ -1337,9 +1318,8 @@ static volatile bool t6_v1;
 static Mutex t6_m2;
 static FastMutex t6_m2a;
 
-static void t6_p5(void *argv)
+static void t6_p5(void *argv __attribute__((unused)))
 {
-    (void)argv;
     for (;;)
     {
         if (Thread::testTerminate())
@@ -1354,9 +1334,8 @@ static void t6_p5(void *argv)
     }
 }
 
-static void t6_p5a(void *argv)
+static void t6_p5a(void *argv __attribute__((unused)))
 {
-    (void)argv;
     for (;;)
     {
         if (Thread::testTerminate())
@@ -1395,9 +1374,8 @@ static void t6_p6a(void *argv)
 static Mutex t6_m5(Mutex::RECURSIVE);
 static FastMutex t6_m5a(FastMutex::RECURSIVE);
 
-static void *t6_p7(void *argv)
+static void *t6_p7(void *argv __attribute__((unused)))
 {
-    (void)argv;
     if (t6_m5.tryLock() == false)
         return reinterpret_cast<void *>(1);  // 1 = locked
     t6_m5.unlock();
@@ -1412,9 +1390,8 @@ bool checkIft6_m5IsLocked()
     return reinterpret_cast<int>(result) == 0 ? false : true;
 }
 
-static void *t6_p7a(void *argv)
+static void *t6_p7a(void *argv __attribute__((unused)))
 {
-    (void)argv;
     if (t6_m5a.tryLock() == false)
         return reinterpret_cast<void *>(1);  // 1 = locked
     t6_m5a.unlock();
@@ -1896,9 +1873,8 @@ FIXME: The overloaded versions of IRQput and IRQget are not tested
 static Queue<char, 4> t8_q1;
 static Queue<char, 4> t8_q2;
 
-static void t8_p1(void *argv)
+static void t8_p1(void *argv __attribute__((unused)))
 {
-    (void)argv;
     for (;;)
     {
         if (Thread::testTerminate())
@@ -2157,7 +2133,7 @@ static void test_9()
 // Test 10
 //
 /*
-tests exception propagation throug entry point of a thread
+tests exception propagation through entry point of a thread
 */
 
 #ifndef __NO_EXCEPTIONS
@@ -2176,9 +2152,8 @@ void t10_f2()
     }
 }
 
-void t10_p1(void *argv)
+void t10_p1(void *argv __attribute__((unused)))
 {
-    (void)argv;
     t10_f2();
     fail("Exception not thrown");
 }
@@ -2204,9 +2179,8 @@ tests class MemoryProfiling
 
 static volatile unsigned int t11_v1;  // Free heap after spawning thread
 
-void t11_p1(void *argv)
+void t11_p1(void *argv __attribute__((unused)))
 {
-    (void)argv;
     if (MemoryProfiling::getStackSize() != STACK_SMALL)
         fail("getStackSize (2)");
     // Check that getCurrentFreeHeap returns the same value from different
@@ -2256,18 +2230,13 @@ Additional test for priority inheritance
 Mutex t12_m1;
 Mutex t12_m2;
 
-void t12_p1(void *argv)
+void t12_p1(void *argv __attribute__((unused)))
 {
-    (void)argv;
     Lock<Mutex> l1(t12_m1);
     Lock<Mutex> l2(t12_m2);
 }
 
-void t12_p2(void *argv)
-{
-    (void)argv;
-    Lock<Mutex> l(t12_m1);
-}
+void t12_p2(void *argv __attribute__((unused))) { Lock<Mutex> l(t12_m1); }
 
 void test_12()
 {
@@ -2485,9 +2454,8 @@ static int t15_v3;
 static ConditionVariable t15_c1;
 static Mutex t15_m1;
 
-void t15_p1(void *argv)
+void t15_p1(void *argv __attribute__((unused)))
 {
-    (void)argv;
     for (int i = 0; i < 10; i++)
     {
         Lock<Mutex> l(t15_m1);
@@ -2497,9 +2465,8 @@ void t15_p1(void *argv)
     }
 }
 
-void t15_p2(void *argv)
+void t15_p2(void *argv __attribute__((unused)))
 {
-    (void)argv;
     for (int i = 0; i < 10; i++)
     {
         Lock<Mutex> l(t15_m1);
@@ -2622,9 +2589,8 @@ void *t16_p2(void *argv)
 
 pthread_cond_t t16_c1 = PTHREAD_COND_INITIALIZER;
 
-void *t16_p3(void *argv)
+void *t16_p3(void *argv __attribute__((unused)))
 {
-    (void)argv;
     Thread::sleep(30);
     if (pthread_mutex_trylock(&t16_m1) != 0)
         fail("cond_wait did not release mutex");
@@ -2638,9 +2604,8 @@ void *t16_p3(void *argv)
 
 pthread_cond_t t16_c2;
 
-void *t16_p4(void *argv)
+void *t16_p4(void *argv __attribute__((unused)))
 {
-    (void)argv;
     Thread::sleep(30);
     if (pthread_mutex_trylock(&t16_m1) != 0)
         fail("cond_wait did not release mutex (2)");
@@ -3093,9 +3058,8 @@ static void be()
     bq.bufferEmptied();
 }
 
-static void t19_p1(void *argv)
+static void t19_p1(void *argv __attribute__((unused)))
 {
-    (void)argv;
     Thread::sleep(50);
     {
         FastInterruptDisableLock dLock;
@@ -3628,9 +3592,8 @@ struct t22_s1
     int b;
 };
 
-static void t22_t2(void *argv)
+static void t22_t2(void *argv __attribute__((unused)))
 {
-    (void)argv;
     while (Thread::testTerminate() == false)
     {
         t22_v5 = true;
@@ -4208,7 +4171,7 @@ static void test_24()
     checkAtomicOps<Derived1, Derived1>();
     checkAtomicOps<Derived2, Derived2>();
 
-    // atomic_load(), atomic_store(), with polimorphism
+    // atomic_load(), atomic_store(), with polymorphism
     checkAtomicOps<Base0, Derived0>();
     checkAtomicOps<Middle1, Derived1>();
     checkAtomicOps<Middle1, Derived2>();
@@ -4428,9 +4391,8 @@ Also tests concurrent write by opening and writing 3 files from 3 threads
 */
 static volatile bool fs_1_error;
 
-static void fs_t1_p1(void *argv)
+static void fs_t1_p1(void *argv __attribute__((unused)))
 {
-    (void)argv;
     FILE *f;
     if ((f = fopen("/sd/testdir/file_1.txt", "w")) == NULL)
     {
@@ -4460,9 +4422,8 @@ static void fs_t1_p1(void *argv)
     }
 }
 
-static void fs_t1_p2(void *argv)
+static void fs_t1_p2(void *argv __attribute__((unused)))
 {
-    (void)argv;
     FILE *f;
     if ((f = fopen("/sd/testdir/file_2.txt", "w")) == NULL)
     {
@@ -4492,9 +4453,8 @@ static void fs_t1_p2(void *argv)
     }
 }
 
-static void fs_t1_p3(void *argv)
+static void fs_t1_p3(void *argv __attribute__((unused)))
 {
-    (void)argv;
     FILE *f;
     if ((f = fopen("/sd/testdir/file_3.txt", "w")) == NULL)
     {
@@ -4570,6 +4530,7 @@ static void fs_test_1()
     i = 0;
     for (;;)
     {
+        // cppcheck-suppress nullPointerRedundantCheck
         j = fread(buf, 1, 1024, f);
         if (j == 0)
             break;
@@ -4589,6 +4550,7 @@ static void fs_test_1()
     i = 0;
     for (;;)
     {
+        // cppcheck-suppress nullPointerRedundantCheck
         j = fread(buf, 1, 1024, f);
         if (j == 0)
             break;
@@ -4608,6 +4570,7 @@ static void fs_test_1()
     i = 0;
     for (;;)
     {
+        // cppcheck-suppress nullPointerRedundantCheck
         j = fread(buf, 1, 1024, f);
         if (j == 0)
             break;
@@ -4633,6 +4596,7 @@ static void fs_test_1()
         fail("can't open a file_4.txt");
     for (i = 2; i <= 128; i++)
     {
+        // cppcheck-suppress nullPointerRedundantCheck
         fprintf(f, "Hello world line %03d\n", i);
     }
     if (fclose(f) != 0)  // cppcheck-suppress nullPointerRedundantCheck
@@ -4645,11 +4609,9 @@ static void fs_test_1()
     fgets(line, sizeof(line), f);
     if (strcmp(line, "Hello world line 001\n"))
         fail("file_4.txt line 1 error");
-    // cppcheck-suppress nullPointerRedundantCheck
     fgets(line, sizeof(line), f);
     if (strcmp(line, "Hello world line 002\n"))
         fail("file_4.txt line 2 error");
-    // cppcheck-suppress nullPointerRedundantCheck
     if (fclose(f) != 0)
         fail("Can't close r file_4.txt");
     // Test fseek and ftell. When reaching this point file_4.txt contains:
@@ -4660,45 +4622,33 @@ static void fs_test_1()
     if ((f = fopen("/sd/testdir/file_4.txt", "r")) == NULL)
         fail("can't open r2 file_4.txt");
     if (ftell(f) != 0)  // cppcheck-suppress nullPointerRedundantCheck
-        fail("File opend but cursor not @ address 0");
-    // cppcheck-suppress nullPointerRedundantCheck
+        fail("File opened but cursor not @ address 0");
     fseek(f, -4, SEEK_END);  // Seek to 128\n
-    // cppcheck-suppress nullPointerRedundantCheck
     if ((fgetc(f) != '1') | (fgetc(f) != '2') | (fgetc(f) != '8'))
         fail("fgetc SEEK_END");
-    // cppcheck-suppress nullPointerRedundantCheck
     if (ftell(f) != (21 * 128 - 1))
     {
         iprintf("ftell=%ld\n", ftell(f));
         fail("ftell() 1");
     }
-    // cppcheck-suppress nullPointerRedundantCheck
     fseek(f, 21 + 17, SEEK_SET);  // Seek to 002\n
-    // cppcheck-suppress nullPointerRedundantCheck
     if ((fgetc(f) != '0') | (fgetc(f) != '0') | (fgetc(f) != '2') |
-        // cppcheck-suppress nullPointerRedundantCheck
         (fgetc(f) != '\n'))
         fail("fgetc SEEK_SET");
-    // cppcheck-suppress nullPointerRedundantCheck
     if (ftell(f) != (21 * 2))
     {
         iprintf("ftell=%ld\n", ftell(f));
         fail("ftell() 2");
     }
-    // cppcheck-suppress nullPointerRedundantCheck
     fseek(f, 21 * 50 + 17, SEEK_CUR);  // Seek to 053\n
-    // cppcheck-suppress nullPointerRedundantCheck
     if ((fgetc(f) != '0') | (fgetc(f) != '5') | (fgetc(f) != '3') |
-        // cppcheck-suppress nullPointerRedundantCheck
         (fgetc(f) != '\n'))
         fail("fgetc SEEK_CUR");
-    // cppcheck-suppress nullPointerRedundantCheck
     if (ftell(f) != (21 * 53))
     {
         iprintf("ftell=%ld\n", ftell(f));
         fail("ftell() 2");
     }
-    // cppcheck-suppress nullPointerRedundantCheck
     if (fclose(f) != 0)
         fail("Can't close r2 file_4.txt");
     // Testing remove()
@@ -4792,7 +4742,6 @@ static void checkInDir(const std::string &d, bool createFile)
     fgets(s, sizeof(s), f);  // cppcheck-suppress nullPointerRedundantCheck
     if (strcmp(s, teststr))
         fail("file content after rename");
-    // cppcheck-suppress nullPointerRedundantCheck
     fclose(f);
     if (unlink((d + filename2).c_str()))
         fail("unlink 3");
@@ -4842,10 +4791,10 @@ static void fs_test_3()
         for (unsigned int j = 0; j < size; j++)
             buf[j] = rand() & 0xff;
         checksum ^= crc16(buf, size);
+        // cppcheck-suppress nullPointerRedundantCheck
         if (fwrite(buf, 1, size, f) != size)
             fail("write");
     }
-    // cppcheck-suppress nullPointerRedundantCheck
     if (fclose(f) != 0)
         fail("close 1");
 
@@ -4856,11 +4805,11 @@ static void fs_test_3()
     for (unsigned int i = 0; i < numBlocks; i++)
     {
         memset(buf, 0, size);
+        // cppcheck-suppress nullPointerRedundantCheck
         if (fread(buf, 1, size, f) != size)
             fail("read");
         outChecksum ^= crc16(buf, size);
     }
-    // cppcheck-suppress nullPointerRedundantCheck
     if (fclose(f) != 0)
         fail("close 2");
     delete[] buf;
@@ -5063,6 +5012,7 @@ const int nThreads = 8;
 bool flags[nThreads];
 
 static int throwable(std::vector<int> &v) __attribute__((noinline));
+// cppcheck-suppress containerOutOfBounds
 static int throwable(std::vector<int> &v) { return v.at(10); }
 
 static void test(void *argv)
@@ -5162,9 +5112,8 @@ tests:
 context switch speed
 */
 
-static void b2_p1(void *argv)
+static void b2_p1(void *argv __attribute__((unused)))
 {
-    (void)argv;
     for (;;)
     {
         if (Thread::testTerminate())
@@ -5219,7 +5168,7 @@ static void benchmark_2()
 //
 /*
 tests:
-Fisesystem write speed and latency
+File system write speed and latency
 makes a 1MB file and measures time required to read/write it.
 */
 
@@ -5310,14 +5259,13 @@ quit:
 //
 /*
 tests:
-Mutex lonk/unlock time
+Mutex lock/unlock time
 */
 
 volatile bool b4_end = false;
 
-void b4_t1(void *argv)
+void b4_t1(void *argv __attribute__((unused)))
 {
-    (void)argv;
     Thread::sleep(1000);
     b4_end = true;
 }
diff --git a/src/entrypoints/sdcard-benchmark.cpp b/src/entrypoints/sdcard-benchmark.cpp
index f41ede96ff37ce124d3c97b984e867df26c839fb..89aa596440629e7fca70113320d987ad15e63467 100644
--- a/src/entrypoints/sdcard-benchmark.cpp
+++ b/src/entrypoints/sdcard-benchmark.cpp
@@ -26,8 +26,8 @@
 #include <ctime>
 #include <iostream>
 // #include <thread>
-#include <math/Stats.h>
 #include <miosix.h>
+#include <utils/Stats/Stats.h>
 
 #include <vector>
 
diff --git a/src/shared/Singleton.h b/src/shared/Singleton.h
index 5639c0b15810496bd6a8e669ff19e0ab409f1ee3..5c3975f35899c20e78d8ffd2252d9fc626754ea3 100644
--- a/src/shared/Singleton.h
+++ b/src/shared/Singleton.h
@@ -57,7 +57,7 @@ protected:
     Singleton() {}
 
 public:
-    Singleton(const Singleton&) = delete;
+    Singleton(const Singleton&)            = delete;
     Singleton& operator=(const Singleton&) = delete;
 };
 
diff --git a/src/shared/actuators/Buzzer.h b/src/shared/actuators/Buzzer.h
new file mode 100644
index 0000000000000000000000000000000000000000..50c6b33409f87870da9f18dbeb04d4050f4216dc
--- /dev/null
+++ b/src/shared/actuators/Buzzer.h
@@ -0,0 +1,174 @@
+/* Copyright (c) 2022 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
+
+#include <drivers/timer/GeneralPurposeTimer.h>
+#include <miosix.h>
+
+namespace Boardcore
+{
+
+/**
+ * @brief This driver does not provide a square wave signal but insted is a
+ * simple utility that provides long PWM signals to make the buzzer beep
+ * on and off.
+ */
+class Buzzer
+{
+public:
+    /**
+     * Note: The timer must be a General Purpose Timer.
+     *
+     * @param timer Timer used to provide the alternating on and off signal.
+     * @param channel Timer channel to output the signal.
+     */
+    Buzzer(TIM_TypeDef *timer, TimerUtils::Channel channel);
+
+    /**
+     * @brief Turns on the buzzer.
+     *
+     * Note: Keeps the timer off.
+     */
+    void on();
+
+    /**
+     * @brief Turns off the buzzer.
+     *
+     * Note: disables and resets the timer.
+     */
+    void off();
+
+    /**
+     * @brief Turns on the buzzer for the specified amount of time.
+     *
+     * Note: This function wait for the given duration with Thread::sleep.
+     *
+     * @param ms Beep duration in milliseconds.
+     */
+    void oneTimeToggle(uint16_t ms);
+
+    /**
+     * @brief Turns on and off the buzzer indefinitely with the given timings.
+     *
+     * @param onTime Buzzer on time [ms].
+     * @param offTime Buzzer off time [ms].
+     */
+    void continuoslyToggle(uint16_t onTime, uint16_t offTime);
+
+private:
+    GP16bitTimer timer;
+    TimerUtils::Channel channel;
+};
+
+inline Buzzer::Buzzer(TIM_TypeDef *timer, TimerUtils::Channel channel)
+    : timer(timer), channel(channel)
+{
+    // this->timer.setPrescaler(
+    //     TimerUtils::computePrescalerValue(timer, frequency * 2));
+    // this->timer.setAutoReloadRegister(1);
+    // this->timer.setOutputCompareMode(channel,
+    //                                  TimerUtils::OutputCompareMode::TOGGLE);
+    // this->timer.setCaptureCompareRegister(channel, 1);
+    // this->timer.generateUpdate();
+    // this->timer.enableCaptureCompareOutput(channel);
+    // this->timer.enableCaptureCompareComplementaryOutput(channel);
+}
+
+inline void Buzzer::on()
+{
+    // First turn off and reset the timer
+    off();
+
+    // Set the polarity of the channel to low to make the output high
+    timer.setOutputCompareMode(channel,
+                               TimerUtils::OutputCompareMode::FORCE_ACTIVE);
+    timer.setCaptureComparePolarity(
+        channel, TimerUtils::OutputComparePolarity::ACTIVE_LOW);
+    timer.enableCaptureCompareOutput(channel);
+}
+
+inline void Buzzer::off()
+{
+    timer.disable();
+    timer.reset();
+}
+
+inline void Buzzer::oneTimeToggle(uint16_t ms)
+{
+    if (ms == 0)
+        return;
+
+    // First turn off and reset the timer
+    off();
+
+    // Set the prescaler with a 10KHz frequency
+    timer.setPrescaler(
+        TimerUtils::computePrescalerValue(timer.getTimer(), 10000));
+
+    // Make the timer reload after the specified amount of millisseconds
+    timer.setAutoReloadRegister(ms * 10);
+    timer.setCaptureCompareRegister(channel, 1);
+
+    timer.setOutputCompareMode(channel,
+                               TimerUtils::OutputCompareMode::PWM_MODE_2);
+    timer.setCaptureComparePolarity(
+        channel, TimerUtils::OutputComparePolarity::ACTIVE_LOW);
+    timer.enableCaptureCompareOutput(channel);
+
+    // Update the configuration
+    timer.generateUpdate();
+
+    // Use One Pulse Mode to stop the timer after the first update event
+    timer.enableOnePulseMode();
+    timer.enable();
+}
+
+inline void Buzzer::continuoslyToggle(uint16_t onTime, uint16_t offTime)
+{
+    if (onTime == 0 || offTime == 0)
+        return;
+
+    // First turn off and reset the timer
+    off();
+
+    // Set the prescaler with a 10KHz frequency
+    timer.setPrescaler(
+        TimerUtils::computePrescalerValue(timer.getTimer(), 10000));
+
+    // Make the timer reload after the specified amount of millisseconds
+    timer.setAutoReloadRegister((onTime + offTime) * 10);
+    timer.setCaptureCompareRegister(channel, onTime * 10);
+
+    timer.setOutputCompareMode(channel,
+                               TimerUtils::OutputCompareMode::PWM_MODE_1);
+    timer.setCaptureComparePolarity(
+        channel, TimerUtils::OutputComparePolarity::ACTIVE_LOW);
+    timer.enableCaptureCompareOutput(channel);
+
+    // Update the configuration
+    timer.generateUpdate();
+
+    timer.enable();
+}
+
+}  // namespace Boardcore
diff --git a/src/shared/drivers/hbridge/HBridge.cpp b/src/shared/actuators/HBridge/HBridge.cpp
similarity index 100%
rename from src/shared/drivers/hbridge/HBridge.cpp
rename to src/shared/actuators/HBridge/HBridge.cpp
diff --git a/src/shared/drivers/hbridge/HBridge.h b/src/shared/actuators/HBridge/HBridge.h
similarity index 100%
rename from src/shared/drivers/hbridge/HBridge.h
rename to src/shared/actuators/HBridge/HBridge.h
diff --git a/src/shared/drivers/servo/Servo.cpp b/src/shared/actuators/Servo/Servo.cpp
similarity index 74%
rename from src/shared/drivers/servo/Servo.cpp
rename to src/shared/actuators/Servo/Servo.cpp
index ce0f9330209bbd1137bcc5000443a8fcda21618c..b4d9b8e647261844027f406fcf268e04bae68611 100644
--- a/src/shared/drivers/servo/Servo.cpp
+++ b/src/shared/actuators/Servo/Servo.cpp
@@ -27,26 +27,57 @@
 namespace Boardcore
 {
 
+#ifndef COMPILE_FOR_HOST
+
 Servo::Servo(TIM_TypeDef* const timer, TimerUtils::Channel pwmChannel,
              unsigned int minPulse, unsigned int maxPulse,
-             unsigned int frequency)
+             unsigned int frequency, unsigned int resetPulse)
     : pwm(timer, frequency), pwmChannel(pwmChannel), minPulse(minPulse),
-      maxPulse(maxPulse), frequency(frequency)
+      maxPulse(maxPulse), resetPulse(resetPulse), frequency(frequency)
+{
+    setPosition(resetPulse);
+}
+
+Servo::Servo(TIM_TypeDef* const timer, TimerUtils::Channel pwmChannel,
+             unsigned int minPulse, unsigned int maxPulse,
+             unsigned int frequency)
+    : Servo(timer, pwmChannel, minPulse, maxPulse, frequency, minPulse)
 {
-    setPosition(0);
 }
 
 void Servo::enable() { pwm.enableChannel(pwmChannel); }
 
 void Servo::disable() { pwm.disableChannel(pwmChannel); }
 
+void Servo::reset() { setPosition(resetPulse); }
+
+#else
+
+Servo::Servo(unsigned int minPulse, unsigned int maxPulse,
+             unsigned int frequency)
+    : minPulse(minPulse), maxPulse(maxPulse), frequency(frequency),
+      resetPulse(minPulse)
+{
+    setPosition(0);
+}
+
+void Servo::enable() {}
+
+void Servo::disable() {}
+
+#endif
+
 void Servo::setPosition(float position)
 {
     float pulse = minPulse + (maxPulse - minPulse) * position;
 
     float dutyCycle = pulse * frequency / 1000000.0f;
 
+#ifndef COMPILE_FOR_HOST
     pwm.setDutyCycle(pwmChannel, dutyCycle);
+#else
+    this->dutyCycle = dutyCycle;
+#endif
 }
 
 void Servo::setPosition90Deg(float degrees) { setPosition(degrees / 90); }
@@ -59,7 +90,11 @@ void Servo::setPosition360Deg(float degrees) { setPosition(degrees / 360); }
 
 float Servo::getPosition()
 {
+#ifndef COMPILE_FOR_HOST
     float dutyCycle = pwm.getDutyCycle(pwmChannel);
+#else
+    float dutyCycle = this->dutyCycle;
+#endif
 
     float pulse = dutyCycle * 1000000.0f / frequency;
 
diff --git a/src/shared/drivers/servo/Servo.h b/src/shared/actuators/Servo/Servo.h
similarity index 81%
rename from src/shared/drivers/servo/Servo.h
rename to src/shared/actuators/Servo/Servo.h
index f070f196b1a7a2d2af03534ac90f18f591b6e661..43c1ca54a708a0171dab661297ce97a1944dc6a1 100644
--- a/src/shared/drivers/servo/Servo.h
+++ b/src/shared/actuators/Servo/Servo.h
@@ -20,7 +20,9 @@
  * THE SOFTWARE.
  */
 
+#ifndef COMPILE_FOR_HOST
 #include <drivers/timer/PWM.h>
+#endif
 
 #pragma once
 
@@ -54,6 +56,7 @@ namespace Boardcore
 class Servo
 {
 public:
+#ifndef COMPILE_FOR_HOST
     /**
      * @brief Prepare the timer and sets the PWM output to the minimum.
      *
@@ -73,6 +76,25 @@ public:
     explicit Servo(TIM_TypeDef* const timer, TimerUtils::Channel pwmChannel,
                    unsigned int minPulse = 1000, unsigned int maxPulse = 2000,
                    unsigned int frequency = 50);
+    /**
+     * @brief Prepare the timer and sets the PWM output to the minimum.
+     *
+     * @see Servo::Servo
+     *
+     * @param timer Timer peripheral used for the PWM signal.
+     * @param pwmChannel Timer's channel used for the PWM signal.
+     * @param frequency Frequency of the PWM driving the H-bridge.
+     * @param minPulse Minimum signal pulse in microseconds.
+     * @param maxPulse Maximum signal pulse in microseconds.
+     * @param resetPulse Reset signal pulse in microseconds.
+     */
+    explicit Servo(TIM_TypeDef* const timer, TimerUtils::Channel pwmChannel,
+                   unsigned int minPulse, unsigned int maxPulse,
+                   unsigned int frequency, unsigned int resetPulse);
+#else
+    explicit Servo(unsigned int minPulse = 1000, unsigned int maxPulse = 2000,
+                   unsigned int frequency = 50);
+#endif
 
     /**
      * @brief Starts producing the PWM signal.
@@ -84,6 +106,11 @@ public:
      */
     void disable();
 
+    /**
+     * @brief Moves the servo to the reset position.
+     */
+    void reset();
+
     /**
      * @brief Set the position of the servomotor.
      *
@@ -120,11 +147,16 @@ private:
     Servo& operator=(const Servo&) = delete;
     Servo(const Servo& s)          = delete;
 
+#ifndef COMPILE_FOR_HOST
     PWM pwm;
     TimerUtils::Channel pwmChannel;
+#else
+    float dutyCycle;
+#endif
 
     float minPulse;
     float maxPulse;
+    float resetPulse;
     float frequency;
 };
 
diff --git a/src/shared/drivers/stepper/Stepper.h b/src/shared/actuators/Stepper.h
similarity index 95%
rename from src/shared/drivers/stepper/Stepper.h
rename to src/shared/actuators/Stepper.h
index fa22b4641be1ce4e4a98c9ce85eac281ea360bce..b08639af1075f1d3534df3fd5543f3fe0e5e6b25 100644
--- a/src/shared/drivers/stepper/Stepper.h
+++ b/src/shared/actuators/Stepper.h
@@ -20,9 +20,11 @@
  * THE SOFTWARE.
  */
 
+#pragma once
+
 #include <interfaces-impl/gpio_impl.h>
 #include <interfaces/delays.h>
-#include <utils/testutils/MockGpioPin.h>
+#include <utils/TestUtils/MockGpioPin.h>
 
 namespace Boardcore
 {
@@ -74,6 +76,12 @@ public:
      */
     void setMicrostepping(Microstep microstep);
 
+    /**
+     * @brief Zeros the driver's internal position.
+     *
+     */
+    void zeroPosition();
+
     /**
      * @brief Move the stepper motor by the specified amount of steps.
      */
@@ -184,6 +192,8 @@ inline void Stepper::setMicrostepping(Microstep microstep)
     }
 }
 
+void Stepper::zeroPosition() { currentPosition = 0; }
+
 inline void Stepper::move(int32_t steps)
 {
     if (speed == 0)
@@ -229,7 +239,7 @@ inline uint32_t Stepper::getCurrentPosition() { return currentPosition; }
 
 inline float Stepper::getCurrentDegPosition()
 {
-    return currentPosition * stepAngle;
+    return currentPosition * stepAngle / getMicrosteppingValue();
 }
 
 inline int Stepper::getMicrosteppingValue()
@@ -251,4 +261,4 @@ inline int Stepper::getMicrosteppingValue()
     return 1;
 }
 
-}  // namespace Boardcore
\ No newline at end of file
+}  // namespace Boardcore
diff --git a/src/shared/algorithms/Algorithm.h b/src/shared/algorithms/Algorithm.h
new file mode 100644
index 0000000000000000000000000000000000000000..56d31830cdc7218ecd795ab34e003e57261a13d8
--- /dev/null
+++ b/src/shared/algorithms/Algorithm.h
@@ -0,0 +1,70 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Luca Conterio
+ *
+ * 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
+
+namespace Boardcore
+{
+
+class Algorithm
+{
+public:
+    /**
+     * @brief Initializes the Algorithm object, must be called as soon as the
+     * object is created.
+     * */
+    virtual bool init() = 0;
+
+    /**
+     * @brief Starts the execution of the algorithm and set the running flag to
+     * true.
+     * */
+    void begin() { running = true; }
+
+    /**
+     * @brief Terminates the algorithm's execution and sets the running flag to
+     * false.
+     * */
+    void end() { running = false; }
+
+    /**
+     * @brief Checks wether the algorithm is in a running state or not, and
+     * eventually calls the @see{step} routine.
+     * */
+    void update()
+    {
+        if (running)
+        {
+            step();
+        }
+    }
+
+protected:
+    /**
+     * @brief The actual algorithm step.
+     */
+    virtual void step() = 0;
+
+    bool running = false;
+};
+
+}  // namespace Boardcore
diff --git a/src/shared/kalman/KalmanEigen.h b/src/shared/algorithms/Kalman/Kalman.h
similarity index 97%
rename from src/shared/kalman/KalmanEigen.h
rename to src/shared/algorithms/Kalman/Kalman.h
index 214e1184e9f75f2d7835fde22f99826ce533d2c3..204311d7532230955459247fcc4ece54e2777785 100644
--- a/src/shared/kalman/KalmanEigen.h
+++ b/src/shared/algorithms/Kalman/Kalman.h
@@ -36,7 +36,7 @@ namespace Boardcore
  *           dynamically.
  */
 template <typename t, uint8_t n, uint8_t p>
-class KalmanEigen
+class Kalman
 {
     using MatrixNN = Eigen::Matrix<t, n, n>;
     using MatrixPN = Eigen::Matrix<t, p, n>;
@@ -47,7 +47,7 @@ class KalmanEigen
 
 public:
     /**
-     * @brief Configuration struct for the KalmanEigen class.
+     * @brief Configuration struct for the Kalman class.
      */
     struct KalmanConfig
     {
@@ -63,7 +63,7 @@ public:
      * @param config configuration object containing all the initialized
      *               matrices
      */
-    KalmanEigen(const KalmanConfig& config)
+    Kalman(const KalmanConfig& config)
         : F(config.F), H(config.H), Q(config.Q), R(config.R), P(config.P),
           S(MatrixPP::Zero(p, p)), K(MatrixNP::Zero(n, p)), x(config.x)
     {
diff --git a/src/shared/algorithms/NAS/NAS.cpp b/src/shared/algorithms/NAS/NAS.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..1296b639b48483d3a0309d5136e77c5a97c5fc7e
--- /dev/null
+++ b/src/shared/algorithms/NAS/NAS.cpp
@@ -0,0 +1,338 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Authors: Alessandro Del Duca, Luca Conterio, Marco Cella
+ *
+ * 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 "NAS.h"
+
+#include <drivers/timer/TimestampTimer.h>
+#include <utils/SkyQuaternion/SkyQuaternion.h>
+
+#include <iostream>
+
+using namespace Boardcore;
+using namespace Eigen;
+
+namespace Boardcore
+{
+
+NAS::NAS(NASConfig config) : config(config)
+{
+    // Covariance setup
+    {
+        // clang-format off
+        Eigen::Matrix3f P_pos{
+            {config.P_POS, 0,            0},
+            {0,            config.P_POS, 0},
+            {0,            0,            config.P_POS_VERTICAL}
+        };
+        Eigen::Matrix3f P_vel{
+            {config.P_VEL, 0,            0},
+            {0,            config.P_VEL, 0},
+            {0,            0,            config.P_VEL_VERTICAL}
+        };
+        Eigen::Matrix3f P_att  = Matrix3f::Identity() * config.P_ATT;
+        Eigen::Matrix3f P_bias = Matrix3f::Identity() * config.P_BIAS;
+        P << P_pos,               MatrixXf::Zero(3, 9),
+            MatrixXf::Zero(3, 3), P_vel, MatrixXf::Zero(3, 6),
+            MatrixXf::Zero(3, 6),        P_att, MatrixXf::Zero(3, 3),
+            MatrixXf::Zero(3, 9),               P_bias; // cppcheck-suppress constStatement
+        // clang-format on
+    }
+
+    // GPS matrixes
+    {
+        H_gps                = Matrix<float, 4, 6>::Zero();
+        H_gps.coeffRef(0, 0) = 1;
+        H_gps.coeffRef(1, 1) = 1;
+        H_gps.coeffRef(2, 3) = 1;
+        H_gps.coeffRef(3, 4) = 1;
+        H_gps_tr             = H_gps.transpose();
+        R_gps << config.SIGMA_GPS * Matrix<float, 4, 4>::Identity();
+    }
+
+    // Magnetometer utility matrix
+    R_mag << config.SIGMA_MAG * Matrix3f::Identity();
+
+    // Other utility matrixes
+    {
+        // clang-format off
+        Q_quat << (config.SIGMA_W * config.SIGMA_W * config.T + (1.0f / 3.0f) * config.SIGMA_BETA * config.SIGMA_BETA * config.T * config.T * config.T) * Matrix3f::Identity(),
+            (0.5f * config.SIGMA_BETA * config.SIGMA_BETA * config.T * config.T) * Matrix3f::Identity(),
+            (0.5f * config.SIGMA_BETA * config.SIGMA_BETA * config.T * config.T) * Matrix3f::Identity(), // cppcheck-suppress constStatement
+            (config.SIGMA_BETA * config.SIGMA_BETA * config.T) * Matrix3f::Identity();
+        // clang-format on
+
+        Q_lin << config.SIGMA_POS * Matrix3f::Identity(), MatrixXf::Zero(3, 3),
+            // cppcheck-suppress constStatement
+            MatrixXf::Zero(3, 3), config.SIGMA_VEL * Matrix3f::Identity();
+
+        F_lin                   = Matrix<float, 6, 6>::Identity();
+        F_lin.block<3, 3>(0, 3) = config.T * Matrix3f::Identity();
+        F_lin_tr                = F_lin.transpose();
+
+        F_quat << -Matrix3f::Identity(), -Matrix3f::Identity() * config.T,
+            Matrix3f::Zero(3, 3), Matrix3f::Identity();
+        F_quat_tr = F_quat.transpose();
+
+        G_quat << -Matrix3f::Identity(), Matrix3f::Zero(3, 3),
+            Matrix3f::Zero(3, 3), Matrix3f::Identity();
+        G_quat_tr = G_quat.transpose();
+    }
+}
+
+void NAS::predictAcc(const Vector3f& acceleration)
+{
+    Matrix3f A   = body2ned(x.block<4, 1>(IDX_QUAT, 0));
+    Vector3f pos = x.block<3, 1>(IDX_POS, 0);
+    Vector3f vel = x.block<3, 1>(IDX_VEL, 0);
+
+    // Update position by integrating the velocity
+    pos = pos + vel * config.T;
+
+    // Measured acceleration in NED frame without gravity
+    Vector3f a = A * acceleration - gravityNed;
+
+    // Update velocity by integrating the acceleration
+    vel = vel + a * config.T;
+
+    // Save the updated state
+    x.block<3, 1>(IDX_POS, 0) = pos;
+    x.block<3, 1>(IDX_VEL, 0) = vel;
+
+    // Variance propagation
+    Eigen::Matrix<float, 6, 6> Pl = P.block<6, 6>(0, 0);
+    P.block<6, 6>(0, 0)           = F_lin * Pl * F_lin_tr + Q_lin;
+}
+
+void NAS::predictGyro(const Vector3f& angularVelocity)
+{
+    Vector3f bias = x.block<3, 1>(IDX_BIAS, 0);
+    Vector4f q    = x.block<4, 1>(IDX_QUAT, 0);
+
+    Vector3f omega = angularVelocity - bias;
+    // clang-format off
+    Matrix4f omega_mat{
+        { 0.0f,      omega(2), -omega(1), omega(0)},
+        {-omega(2),  0.0f,      omega(0), omega(1)},
+        { omega(1), -omega(0),  0.0f,     omega(2)},
+        {-omega(0), -omega(1), -omega(2), 0.0f}
+    };
+    // clang-format on
+
+    // Update orientation by integrating the angular velocity
+    q = (Matrix4f::Identity() + 0.5F * omega_mat * config.T) * q;
+    q.normalize();
+
+    // Save the updated state
+    x.block<4, 1>(IDX_QUAT, 0) = q;
+
+    // Variance propagation
+    // TODO: Optimize last G_quat * Q_quat * G_att_tr
+    Matrix<float, 6, 6> Pq = P.block<6, 6>(IDX_QUAT, IDX_QUAT);
+    Pq = F_quat * Pq * F_quat_tr + G_quat * Q_quat * G_quat_tr;
+    P.block<6, 6>(IDX_QUAT, IDX_QUAT) = Pq;
+}
+
+void NAS::correctBaro(const float pressure, const float mslPress,
+                      const float mslTemp)
+{
+    Matrix<float, 1, 6> H = Matrix<float, 1, 6>::Zero();
+
+    // Temperature at current altitude. Since in NED the altitude is negative,
+    // mslTemperature returns temperature at current altitude and not at msl
+    float temp = Aeroutils::mslTemperature(mslTemp, x(2));
+
+    // Compute gradient of the altitude-pressure function
+    H[2] = Constants::a * Constants::n * mslPress *
+           powf(1 - Constants::a * x(2) / temp, -Constants::n - 1) / temp;
+
+    Eigen::Matrix<float, 6, 6> Pl = P.block<6, 6>(0, 0);
+    Matrix<float, 1, 1> S =
+        H * Pl * H.transpose() + Matrix<float, 1, 1>(config.SIGMA_BAR);
+    Matrix<float, 6, 1> K = Pl * H.transpose() * S.inverse();
+    P.block<6, 6>(0, 0)   = (Matrix<float, 6, 6>::Identity() - K * H) * Pl;
+
+    float y_hat = Aeroutils::mslPressure(mslPress, mslTemp, x(2));
+
+    // Update the state
+    x.head<6>() = x.head<6>() + K * (pressure - y_hat);
+}
+
+void NAS::correctGPS(const Vector4f& gps)
+{
+    Eigen::Matrix<float, 6, 6> Pl = P.block<6, 6>(0, 0);
+
+    Matrix<float, 4, 4> S = H_gps * Pl * H_gps_tr + R_gps;
+    Matrix<float, 6, 4> K = Pl * H_gps_tr * S.inverse();
+
+    P.block<6, 6>(0, 0) =
+        (Eigen::Matrix<float, 6, 6>::Identity() - K * H_gps) * Pl;
+
+    // Current state [n e vn ve]
+    Matrix<float, 4, 1> H{x(0), x(1), x(3), x(4)};
+
+    // Update the state
+    x.head<6>() = x.head<6>() + K * (gps - H);
+}
+
+void NAS::correctMag(const Vector3f& mag)
+{
+    Vector4f q = x.block<4, 1>(IDX_QUAT, 0);
+    Matrix3f A = body2ned(q).transpose();
+
+    // Rotate the NED magnetic field in the relative reference frame
+    Vector3f mEst = A * config.NED_MAG;
+
+    // clang-format off
+    Matrix3f M{
+        { 0,      -mEst(2), mEst(1)},
+        { mEst(2), 0,      -mEst(0)},
+        {-mEst(1), mEst(0), 0}
+    };
+    // clang-format on
+
+    Matrix<float, 3, 6> H;
+    H << M, Matrix3f::Zero(3, 3);
+    Matrix<float, 6, 6> Pq = P.block<6, 6>(IDX_QUAT, IDX_QUAT);
+    Matrix<float, 3, 3> S  = H * Pq * H.transpose() + R_mag;
+
+    Matrix<float, 6, 3> K  = Pq * H.transpose() * S.inverse();
+    Matrix<float, 6, 1> dx = K * (mag - mEst);
+    Vector4f r{0.5f * dx(0), 0.5f * dx(1), 0.5f * dx(2),
+               sqrtf(1.0f - 0.25f * dx.head<3>().transpose() * dx.head<3>())};
+
+    x.block<4, 1>(IDX_QUAT, 0) = SkyQuaternion::quatProd(r, q);
+    x.block<3, 1>(IDX_BIAS, 0) += dx.tail<3>();
+    Matrix<float, 6, 6> tmp = Matrix<float, 6, 6>::Identity() - K * H;
+    Pq = tmp * Pq * tmp.transpose() + K * R_mag * K.transpose();
+    P.block<6, 6>(IDX_QUAT, IDX_QUAT) = Pq;
+}
+
+void NAS::correctAcc(const Eigen::Vector3f& acc)
+{
+    Vector4f q = x.block<4, 1>(IDX_QUAT, 0);
+    Matrix3f A = body2ned(q).transpose();
+
+    // Rotate the NED magnetic field in the relative reference frame
+    Vector3f aEst = A * gravityNed;
+
+    // Gradient matrix
+    // clang-format off
+    Matrix3f M{
+        { 0,      -aEst(2), aEst(1)},
+        { aEst(2), 0,      -aEst(0)},
+        {-aEst(1), aEst(0), 0}
+    };
+    // clang-format on
+
+    Matrix<float, 3, 6> H;
+    H << M, Matrix3f::Zero(3, 3);
+    Matrix<float, 6, 6> Pq = P.block<6, 6>(IDX_QUAT, IDX_QUAT);
+    Matrix<float, 3, 3> S =
+        H * Pq * H.transpose() + R_mag;  // TODO: Change R_mag with R_acc
+
+    Matrix<float, 6, 3> K = Pq * H.transpose() * S.inverse();
+
+    aEst.normalize();
+    Vector3f e             = acc - aEst;
+    Matrix<float, 6, 1> dx = K * e;
+    Vector4f r{0.5f * dx(0), 0.5f * dx(1), 0.5f * dx(2),
+               sqrtf(1.0f - 0.25f * dx.head<3>().squaredNorm())};
+
+    x.block<4, 1>(IDX_QUAT, 0) = SkyQuaternion::quatProd(r, q);
+    x.block<3, 1>(IDX_BIAS, 0) += dx.tail<3>();
+    Matrix<float, 6, 6> tmp = Matrix<float, 6, 6>::Identity() - K * H;
+    Pq = tmp * Pq * tmp.transpose() + K * R_mag * K.transpose();
+    P.block<6, 6>(IDX_QUAT, IDX_QUAT) = Pq;
+}
+
+void NAS::correctPitot(const float deltaP, const float staticP)
+{
+    if (deltaP >= 0)
+    {
+        float c      = 343;  // Speed of sound
+        Vector3f vel = x.block<3, 1>(IDX_VEL, 0);
+        float vPitot;
+
+        if (vel.norm() / c >= 0.9419)
+        {
+            float rho = 1.225;  // Air density
+            vPitot    = -sqrtf(
+                   fabs(-2.0f * c * c +
+                        sqrtf((4.0f * powf(c, 4) + 8.0f * c * c * deltaP / rho))));
+        }
+        else
+        {
+            float gamma = 1.4;  // Heat capacity ratio of dry air
+            float p0    = staticP + deltaP;
+            vPitot =
+                -sqrtf(c * c * 2 / (gamma - 1) *
+                       fabs(powf((p0 / staticP), (gamma - 1) / gamma) - 1));
+        }
+
+        Matrix<float, 1, 6> H = Matrix<float, 1, 6>::Zero();
+        H.coeffRef(0, 5)      = 1;
+
+        Eigen::Matrix<float, 6, 6> Pl = P.block<6, 6>(0, 0);
+        Matrix<float, 1, 1> S =
+            H * Pl * H.transpose() + Matrix<float, 1, 1>(config.SIGMA_PITOT);
+
+        Matrix<float, 6, 1> K = Pl * H.transpose() * S.inverse();
+        x.head<6>()           = x.head<6>() + K * (vPitot - x[5]);
+        P.block<6, 6>(0, 0)   = (Matrix<float, 6, 6>::Identity() - K * H) * Pl;
+    }
+}
+
+NASState NAS::getState() const
+{
+    return NASState(TimestampTimer::getInstance().getTimestamp(), x);
+}
+
+Eigen::Matrix<float, 13, 1> NAS::getX() const { return x; }
+
+void NAS::setState(const NASState& state) { this->x = state.getX(); }
+
+void NAS::setX(const Eigen::Matrix<float, 13, 1>& x) { this->x = x; }
+
+Matrix3f NAS::body2ned(const Vector4f& q)
+{
+    // clang-format off
+    return Matrix3f{
+        {
+            q[0] * q[0] - q[1] * q[1] - q[2] * q[2] + q[3] * q[3],
+            2.0f * (q[0] * q[1] - q[2] * q[3]),
+            2.0f * (q[0] * q[2] + q[1] * q[3]),
+        },
+        {
+            2.0f * (q[0] * q[1] + q[2] * q[3]),
+            - q[0] * q[0] + q[1] * q[1] - q[2] * q[2] + q[3] * q[3],
+            2.0f * (q[1] * q[2] - q[0] * q[3]),
+        },
+        {
+            2.0f * (q[0] * q[2] - q[1] * q[3]),
+            2.0f * (q[1] * q[2] + q[0] * q[3]),
+            - q[0] * q[0] - q[1] * q[1] + q[2] * q[2] + q[3] * q[3]
+        }
+    };
+    // clang-format on
+}
+
+}  // namespace Boardcore
diff --git a/src/shared/algorithms/NAS/NAS.h b/src/shared/algorithms/NAS/NAS.h
new file mode 100644
index 0000000000000000000000000000000000000000..d232f4beb6f6951c1d8ecded27b3aea6c2c9a77f
--- /dev/null
+++ b/src/shared/algorithms/NAS/NAS.h
@@ -0,0 +1,168 @@
+/* Copyright (c) 2020-2022 Skyward Experimental Rocketry
+ * Authors: Alessandro Del Duca, Luca Conterio, Marco Cella
+ *
+ * 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
+
+#include <utils/AeroUtils/AeroUtils.h>
+#include <utils/Constants.h>
+
+#include <Eigen/Dense>
+
+#include "NASConfig.h"
+#include "NASState.h"
+
+namespace Boardcore
+{
+
+class NAS
+{
+public:
+    ///< Index of position elements in the state.
+    static constexpr uint16_t IDX_POS = 0;
+
+    ///< Index of velocity elements in the state.
+    static constexpr uint16_t IDX_VEL = 3;
+
+    ///< Index of quaternions elements in the state.
+    static constexpr uint16_t IDX_QUAT = 6;
+
+    ///< Index of bias elements in the state.
+    static constexpr uint16_t IDX_BIAS = 10;
+
+    explicit NAS(NASConfig config);
+
+    /**
+     * @brief Prediction with accelerometer data.
+     *
+     * @param u Vector with acceleration data [x y z][m/s^2]
+     */
+    void predictAcc(const Eigen::Vector3f& acceleration);
+
+    /**
+     * @brief Prediction with gyroscope data.
+     *
+     * @param u Vector with angular velocity data [x y z]
+     */
+    void predictGyro(const Eigen::Vector3f& angularVelocity);
+
+    /**
+     * @brief Correction with barometer data.
+     *
+     * @param pressure Pressure read from the barometer [Pa]
+     * @param mslPress Pressure at sea level [Pa]
+     * @param mslTemp Temperature at sea level [Kelvin]
+     */
+    void correctBaro(const float pressure, const float mslPress,
+                     const float mslTemp);
+
+    /**
+     * @brief Correction with gps data.
+     *
+     * @param gps Vector of the gps readings [n e vn ve][m m m/s m/s]
+     * @param sats_num Number of satellites available
+     */
+    void correctGPS(const Eigen::Vector4f& gps);
+
+    /**
+     * @brief Correction with magnetometer data.
+     *
+     * @param mag Normalized vector of the magnetometer readings [x y z]
+     */
+    void correctMag(const Eigen::Vector3f& mag);
+
+    /**
+     * @brief Correction with accelerometer data.
+     *
+     * @param u Normaliezed vector with acceleration data [x y z]
+     */
+    void correctAcc(const Eigen::Vector3f& acceleration);
+
+    /**
+     * @brief Correction with pitot pressure.
+     *
+     * Do not call this function after apogee!
+     *
+     * @param deltaP Delta pressure measured by the differential sensor [Pa]
+     * @param staticP Static pressure [Pa]
+     */
+    void correctPitot(const float deltaP, const float staticP);
+
+    /**
+     * @return EKF state.
+     */
+    NASState getState() const;
+
+    /**
+     * @return State vector [n e d vn ve vd qx qy qz qw bx by bz].
+     */
+    Eigen::Matrix<float, 13, 1> getX() const;
+
+    /**
+     * @param state EKF state.
+     */
+    void setState(const NASState& state);
+
+    /**
+     * @param state State vector [n e d vn ve vd qx qy qz qw bx by bz].
+     */
+    void setX(const Eigen::Matrix<float, 13, 1>& x);
+
+private:
+    /**
+     * @brief Return a rotation matrix from body from to NED frame;
+     *
+     * TODO: Move to SkyQuaternion utilities
+     */
+    Eigen::Matrix3f body2ned(const Eigen::Vector4f& q);
+
+    ///< Extended Kalman filter configuration parameters
+    NASConfig config;
+
+    ///< State vector [n e d vn ve vd qx qy qz qw bx by bz]
+    Eigen::Matrix<float, 13, 1> x;
+
+    ///< Covariance matrix
+    Eigen::Matrix<float, 12, 12> P;
+
+    ///< NED gravity vector [m/s^2]
+    Eigen::Vector3f gravityNed{0.0f, 0.0f, -Constants::g};
+
+    // Utility matrixes used for the gps
+    Eigen::Matrix<float, 4, 6> H_gps;
+    Eigen::Matrix<float, 6, 4> H_gps_tr;
+    Eigen::Matrix<float, 4, 4> R_gps;
+
+    // Utility matrixes used for the magnetometer
+    Eigen::Matrix3f R_mag;
+
+    // Other utility matrixes
+    Eigen::Matrix<float, 6, 6> Q_quat;
+    Eigen::Matrix<float, 6, 6> Q_lin;
+    Eigen::Matrix<float, 6, 6> F_lin;
+    Eigen::Matrix<float, 6, 6> F_lin_tr;
+    Eigen::Matrix<float, 6, 6> F_quat;
+    Eigen::Matrix<float, 6, 6> F_quat_tr;
+    Eigen::Matrix<float, 6, 6> G_quat;
+    Eigen::Matrix<float, 6, 6> G_quat_tr;
+};
+
+}  // namespace Boardcore
diff --git a/src/shared/algorithms/NAS/NASConfig.h b/src/shared/algorithms/NAS/NASConfig.h
new file mode 100644
index 0000000000000000000000000000000000000000..b73b2f8f8b3bdf6a5eb1d48194d81f9a8ad0ad75
--- /dev/null
+++ b/src/shared/algorithms/NAS/NASConfig.h
@@ -0,0 +1,56 @@
+/* Copyright (c) 2022 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
+
+#include <Eigen/Dense>
+
+namespace Boardcore
+{
+
+struct NASConfig
+{
+    float T;            ///< [s]       Sample period
+    float SIGMA_BETA;   ///< [rad/s^2] Estimated gyroscope bias variance
+    float SIGMA_W;      ///< [rad^2]   Estimated gyroscope variance
+    float SIGMA_MAG;    ///< [uT^2]    Estimated magnetometer variance
+    float SIGMA_GPS;    ///< [m^2]     Estimated GPS variance
+    float SIGMA_BAR;    ///< [m^2]     Estimated altitude variance
+    float SIGMA_POS;    ///< [m^2]     Estimated variance of the position noise
+    float SIGMA_VEL;    ///< [(m/s)^2] Estimated variance of the velocity noise
+    float SIGMA_PITOT;  ///< [Pa^2]    Estimated variance of the pitot velocity
+
+    float P_POS;  ///< Position prediction covariance
+    float P_POS_VERTICAL;
+
+    float P_VEL;  ///< Velocity prediction covariance
+    float P_VEL_VERTICAL;
+
+    float P_ATT;   ///< Attitude prediction covariance
+    float P_BIAS;  ///< Bias prediction covariance
+
+    float SATS_NUM = 6.0f;  ///< Number of satellites used at setup time
+
+    Eigen::Vector3f NED_MAG;  ///< Normalized magnetic field vector in NED frame
+};
+
+}  // namespace Boardcore
diff --git a/src/shared/algorithms/NAS/NASState.h b/src/shared/algorithms/NAS/NASState.h
new file mode 100644
index 0000000000000000000000000000000000000000..6c5a39db9c12ba68a643b5755a1bd26085ebd205
--- /dev/null
+++ b/src/shared/algorithms/NAS/NASState.h
@@ -0,0 +1,85 @@
+/* Copyright (c) 2022 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
+
+#include <Eigen/Core>
+
+namespace Boardcore
+{
+
+struct NASState
+{
+    uint64_t timestamp;
+
+    // 13 extended kalman states
+
+    // Position [m]
+    float n = 0;
+    float e = 0;
+    float d = 0;
+
+    // Velocity [m/s]
+    float vn = 0;
+    float ve = 0;
+    float vd = 0;
+
+    // Attitude as quaternion
+    float qx = 0;
+    float qy = 0;
+    float qz = 0;
+    float qw = 1;
+
+    // Gyroscope bias
+    float bx = 0;
+    float by = 0;
+    float bz = 0;
+
+    NASState() {}
+
+    NASState(uint64_t timestamp, Eigen::Matrix<float, 13, 1> x)
+        : timestamp(timestamp), n(x(0)), e(x(1)), d(x(2)), vn(x(3)), ve(x(4)),
+          vd(x(5)), qx(x(6)), qy(x(7)), qz(x(8)), qw(x(9)), bx(x(10)),
+          by(x(11)), bz(x(12))
+    {
+    }
+
+    Eigen::Matrix<float, 13, 1> getX() const
+    {
+        return Eigen::Matrix<float, 13, 1>(n, e, d, vn, ve, vd, qx, qy, qz, qw,
+                                           bx, by, bz);
+    }
+
+    static std::string header()
+    {
+        return "timestamp,n,e,d,vn,ve,vd,qx,qy,qz,qw,bx,by,bz\n";
+    }
+
+    void print(std::ostream& os) const
+    {
+        os << timestamp << "," << n << "," << e << "," << d << "," << vn << ","
+           << ve << "," << vd << "," << qx << "," << qy << "," << qz << ","
+           << qw << "," << bx << "," << by << "," << bz << "\n";
+    }
+};
+
+}  // namespace Boardcore
diff --git a/src/shared/algorithms/NAS/StateInitializer.h b/src/shared/algorithms/NAS/StateInitializer.h
new file mode 100644
index 0000000000000000000000000000000000000000..959597beb70db8e4b2133eacf12df22d90f10add
--- /dev/null
+++ b/src/shared/algorithms/NAS/StateInitializer.h
@@ -0,0 +1,169 @@
+/* Copyright (c) 2022 Skyward Experimental Rocketry
+ * Authors: Marco Cella, 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
+
+#include <diagnostic/PrintLogger.h>
+#include <utils/AeroUtils/AeroUtils.h>
+#include <utils/SkyQuaternion/SkyQuaternion.h>
+
+#include <Eigen/Dense>
+
+#include "NAS.h"
+
+namespace Boardcore
+{
+
+/**
+ * @brief Utility used to initialize the extended kalman filter's state.
+ *
+ * The intended use is the following:
+ * - Instantiate the object, the state is initialize to zero;
+ * - Call positionInit to set the initial position;
+ * - Call either eCompass or triad to set the initial orientation;
+ * - Get the initial state for the kalman with getInitX
+ */
+class StateInitializer
+{
+public:
+    /**
+     * @brief Initialize the state of the Extended Kalman Filter.
+     *
+     * The state is initialized to zero. Velocity should be null and gyro biases
+     * can be left to zero since the sensor performs self-calibration and the
+     * measurements are already compensated.
+     */
+    StateInitializer();
+
+    /**
+     * @brief Ecompass algorithm to estimate the attitude before the liftoff.
+     *
+     * ecompass reference:
+     *   https://de.mathworks.com/help/fusion/ref/ecompass.html
+     *
+     * @param acc 3x1 accelerometer readings [x y z][m/s^2].
+     * @param mag 3x1 magnetometer readings [x y z][uT].
+     *
+     */
+    void eCompass(const Eigen::Vector3f acc, const Eigen::Vector3f mag);
+
+    /**
+     * @brief Triad algorithm to estimate the attitude before the liftoff.
+     *
+     * triad reference:
+     *   https://en.wikipedia.org/wiki/Triad_method
+     *   https://www.aero.iitb.ac.in/satelliteWiki/index.php/Triad_Algorithm
+     *
+     * @param acc Normalized accelerometer readings [x y z].
+     * @param mag Normalized magnetometer readings [x y z].
+     * @param nedMag Normalized magnetic field vector in NED frame [x y z].
+     */
+    void triad(Eigen::Vector3f& acc, Eigen::Vector3f& mag,
+               Eigen::Vector3f& nedMag);
+
+    /**
+     * @brief Initialization of the position at a specific altitude
+     *
+     * @param pressure Current pressure [Pas]
+     * @param pressureRef Pressure at reference altitude (must be > 0) [Pa]
+     * @param temperatureRef Temperature at reference altitude [K]
+     */
+    void positionInit(const float pressure, const float pressureRef,
+                      const float temperatureRef);
+
+    Eigen::Matrix<float, 13, 1> getInitX();
+
+private:
+    Eigen::Matrix<float, 13, 1> x_init;
+
+    PrintLogger log = Logging::getLogger("initstates");
+};
+
+StateInitializer::StateInitializer() { x_init << Eigen::MatrixXf::Zero(13, 1); }
+
+void StateInitializer::eCompass(const Eigen::Vector3f acc,
+                                const Eigen::Vector3f mag)
+{
+    // ndr: since this method runs only when the rocket is stationary, there's
+    // no need to add the gravity vector because the accelerometers already
+    // measure it. This is not true if we consider the flying rocket.
+
+    Eigen::Vector3f am(acc.cross(mag));
+
+    Eigen::Matrix3f R;
+    R << am.cross(acc), am, acc;
+    R.col(0).normalize();
+    R.col(1).normalize();
+    R.col(2).normalize();
+
+    Eigen::Vector4f x_quat = SkyQuaternion::rotationMatrix2quat(R);
+
+    x_init(NAS::IDX_QUAT)     = x_quat(0);
+    x_init(NAS::IDX_QUAT + 1) = x_quat(1);
+    x_init(NAS::IDX_QUAT + 2) = x_quat(2);
+    x_init(NAS::IDX_QUAT + 3) = x_quat(3);
+}
+
+void StateInitializer::triad(Eigen::Vector3f& acc, Eigen::Vector3f& mag,
+                             Eigen::Vector3f& nedMag)
+{
+    // The gravity vector is expected to be read inversely because
+    // accelerometers read the binding reaction
+    Eigen::Vector3f nedAcc(0.0f, 0.0f, -1.0f);
+
+    // Compute the reference triad
+    Eigen::Vector3f R1 = nedAcc;
+    Eigen::Vector3f R2 = nedAcc.cross(nedMag).normalized();
+    Eigen::Vector3f R3 = R1.cross(R2);
+
+    // Compute the measured triad
+    Eigen::Vector3f r1 = acc;
+    Eigen::Vector3f r2 = acc.cross(mag).normalized();
+    Eigen::Vector3f r3 = r1.cross(r2);
+
+    // Compose the reference and measured matrixes
+    Eigen::Matrix3f M;
+    M << R1, R2, R3;
+    Eigen::Matrix3f m;
+    m << r1, r2, r3;
+
+    // Compute the rotation matrix and the corresponding quaternion
+    Eigen::Matrix3f A = M * m.transpose();
+    Eigen::Vector4f q = SkyQuaternion::rotationMatrix2quat(A);
+
+    // Save the orientation in the state
+    x_init.block<4, 1>(NAS::IDX_QUAT, 0) = q;
+}
+
+void StateInitializer::positionInit(const float pressure,
+                                    const float pressureRef,
+                                    const float temperatureRef)
+{
+    x_init(0) = 0.0;
+    x_init(1) = 0.0;
+    // msl altitude (in NED, so negative)
+    x_init(2) = -Aeroutils::relAltitude(pressure, pressureRef, temperatureRef);
+}
+
+Eigen::Matrix<float, 13, 1> StateInitializer::getInitX() { return x_init; }
+
+}  // namespace Boardcore
diff --git a/src/shared/algorithms/PID.h b/src/shared/algorithms/PID.h
index 727f1e5477eddb3f51eed688e824775d2d2ea706..5f460f4381ee7e9e9e9aa16886dda6850ca620b6 100644
--- a/src/shared/algorithms/PID.h
+++ b/src/shared/algorithms/PID.h
@@ -47,16 +47,17 @@ public:
     double update(double error)
     {
 
-        double p = Kp * error;
+        lastP = Kp * error;
 
         if (!saturation)
         {
             i = i + Ki * Ts * error;
         }
 
-        double u = p + i;
+        double u = lastP + i;
 
-        return antiWindUp(u);
+        lastOutput = antiWindUp(u);
+        return lastOutput;
     }
 
     /**
@@ -84,13 +85,21 @@ public:
 
     double getI() { return i; }
 
+    double getLastP() { return lastP; }
+
+    double getLastOutput() { return lastOutput; }
+
+    bool isSaturated() { return saturation; }
+
     double Kp;          // Proportional factor.
     double Ki;          // Integral factor.
     double Ts;          // Sampling period.
     double uMin, uMax;  // Anti-windup limits.
 
 private:
-    double i        = 0;  // Integral contribution.
+    double i     = 0;   // Integral contribution.
+    double lastP = 0;   // Last computer proportional contribution.
+    double lastOutput;  // Last computed controller output.
     bool saturation = false;
 };
 
diff --git a/src/shared/diagnostic/LogSink.h b/src/shared/diagnostic/LogSink.h
index 1efbaf52e445a8e8616f0c213688a85e048c3db2..c06917f36c5edf7869f92b7240815ff1a89322a9 100644
--- a/src/shared/diagnostic/LogSink.h
+++ b/src/shared/diagnostic/LogSink.h
@@ -33,7 +33,7 @@ class LogSink
 {
 public:
     LogSink() {}
-    LogSink(const LogSink&) = delete;
+    LogSink(const LogSink&)            = delete;
     LogSink& operator=(const LogSink&) = delete;
 
     virtual ~LogSink() {}
diff --git a/src/shared/diagnostic/PrintLogger.h b/src/shared/diagnostic/PrintLogger.h
index 3ad39429b7dd4ff38d476f2ef0ad3eb77bf64f3a..d8b253b77e4aa9264b0227fd32926af43ef1ee36 100644
--- a/src/shared/diagnostic/PrintLogger.h
+++ b/src/shared/diagnostic/PrintLogger.h
@@ -27,7 +27,6 @@
 #include <fmt/format.h>
 #include <logger/Logger.h>
 #include <utils/Constants.h>
-#include <utils/Unused.h>
 #include <utils/collections/CircularBuffer.h>
 
 #include <memory>
@@ -101,7 +100,7 @@ class Logging : public Singleton<Logging>
     friend class PrintLogger;
 
 public:
-    static PrintLogger getLogger(string name)
+    static PrintLogger getLogger(const string& name)
     {
         return PrintLogger(getInstance(), name);
     }
diff --git a/src/shared/drivers/runcam/Runcam.h b/src/shared/drivers/runcam/Runcam.h
index fe089afee56c02845d0f084be8ec3ceb7bbaea00..eabb564aee0b43d768e2b0c15fa0f490f4d2310f 100644
--- a/src/shared/drivers/runcam/Runcam.h
+++ b/src/shared/drivers/runcam/Runcam.h
@@ -130,7 +130,7 @@ public:
 
 private:
     /**
-     * @brief Confiugre Serial Communication
+     * @brief Configure Serial Communication
      */
     bool configureSerialCommunication();
 
diff --git a/src/shared/drivers/spi/SPI.h b/src/shared/drivers/spi/SPI.h
index de60c5ed54ae18cfb7156e3a4f31826e6f434ee7..8d2077fca048d27085f7f3f64447ac7ca5222663 100644
--- a/src/shared/drivers/spi/SPI.h
+++ b/src/shared/drivers/spi/SPI.h
@@ -29,7 +29,7 @@
 #ifndef USE_MOCK_PERIPHERALS
 using SPIType = SPI_TypeDef;
 #else
-#include <utils/testutils/FakeSpiTypedef.h>
+#include <utils/TestUtils/FakeSpiTypedef.h>
 using SPIType = FakeSpiTypedef;
 #endif
 
@@ -205,7 +205,7 @@ public:
      * @param data Buffer containing data to write.
      * @param size Size of the buffer in bytes.
      */
-    void write(uint8_t *data, size_t nBytes);
+    void write(const uint8_t *data, size_t nBytes);
 
     /**
      * @brief Writes multiple half words to the bus.
@@ -213,7 +213,7 @@ public:
      * @param data Buffer containing data to write.
      * @param size Size of the buffer in bytes.
      */
-    void write(uint16_t *data, size_t nBytes);
+    void write(const uint16_t *data, size_t nBytes);
 
     /**
      * @brief Full duplex transmission of one byte on the bus.
@@ -393,7 +393,7 @@ inline void SPI::write(uint16_t data)
     (void)spi->DR;
 }
 
-inline void SPI::write(uint8_t *data, size_t nBytes)
+inline void SPI::write(const uint8_t *data, size_t nBytes)
 {
     // Write the first data item in the Tx buffer
     spi->DR = data[0];
@@ -419,7 +419,7 @@ inline void SPI::write(uint8_t *data, size_t nBytes)
     (void)spi->DR;
 }
 
-inline void SPI::write(uint16_t *data, size_t nBytes)
+inline void SPI::write(const uint16_t *data, size_t nBytes)
 {
     // Set 16 bit frame format
     set16BitFrameFormat();
diff --git a/src/shared/drivers/spi/SPIBus.h b/src/shared/drivers/spi/SPIBus.h
index 4706f158dcf519ee68ba74ae1830de7441b78e03..0d5b0c0b7faeb8c166fd55b8e454a1409ba22aac 100644
--- a/src/shared/drivers/spi/SPIBus.h
+++ b/src/shared/drivers/spi/SPIBus.h
@@ -46,10 +46,10 @@ public:
     SPIBus(SPIType* spi);
 
     ///< Delete copy/move contructors/operators.
-    SPIBus(const SPIBus&) = delete;
+    SPIBus(const SPIBus&)            = delete;
     SPIBus& operator=(const SPIBus&) = delete;
     SPIBus(SPIBus&&)                 = delete;
-    SPIBus& operator=(SPIBus&&) = delete;
+    SPIBus& operator=(SPIBus&&)      = delete;
 
     /**
      * @brief Configures and enables the bus with the provided configuration.
@@ -168,37 +168,43 @@ public:
 private:
     SPI spi;
     SPIBusConfig config{};
+    bool firstConfigApplied = false;
 };
 
 inline SPIBus::SPIBus(SPIType* spi) : spi(spi) {}
 
 inline void SPIBus::configure(SPIBusConfig newConfig)
 {
-    // Save the new configuration
-    config = newConfig;
+    // Do not reconfigure if already in the correct configuration.
+    if (!firstConfigApplied || newConfig != config)
+    {
+        // Save the new configuration
+        config             = newConfig;
+        firstConfigApplied = true;
 
-    // Wait until the peripheral is done before changing configuration
-    spi.waitPeripheral();
+        // Wait until the peripheral is done before changing configuration
+        spi.waitPeripheral();
 
-    // Disable the peripheral
-    spi.disable();
+        // Disable the peripheral
+        spi.disable();
 
-    // Configure clock polarity and phase
-    spi.setMode(config.mode);
+        // Configure clock polarity and phase
+        spi.setMode(config.mode);
 
-    // Configure clock frequency
-    spi.setClockDiver(config.clockDivider);
+        // Configure clock frequency
+        spi.setClockDiver(config.clockDivider);
 
-    // Configure bit order
-    spi.setBitOrder(config.bitOrder);
+        // Configure bit order
+        spi.setBitOrder(config.bitOrder);
 
-    // Configure chip select and master mode
-    spi.enableSoftwareSlaveManagement();
-    spi.enableInternalSlaveSelection();
-    spi.setMasterConfiguration();
+        // Configure chip select and master mode
+        spi.enableSoftwareSlaveManagement();
+        spi.enableInternalSlaveSelection();
+        spi.setMasterConfiguration();
 
-    // Enable the peripheral
-    spi.enable();
+        // Enable the peripheral
+        spi.enable();
+    }
 }
 
 inline void SPIBus::select(GpioType& cs)
diff --git a/src/shared/drivers/spi/SPIBusInterface.h b/src/shared/drivers/spi/SPIBusInterface.h
index e0eb8bd38f5366451aff51f82aec89102fbb328e..f10a7e33228d5b43bf701e5e8178cead2abf8411 100644
--- a/src/shared/drivers/spi/SPIBusInterface.h
+++ b/src/shared/drivers/spi/SPIBusInterface.h
@@ -29,7 +29,7 @@
 #ifndef USE_MOCK_PERIPHERALS
 using GpioType = miosix::GpioPin;
 #else
-#include <utils/testutils/MockGpioPin.h>
+#include <utils/TestUtils/MockGpioPin.h>
 using GpioType = MockGpioPin;
 #endif
 
@@ -93,10 +93,10 @@ public:
     SPIBusInterface() {}
 
     ///< Delete copy/move contructors/operators.
-    SPIBusInterface(const SPIBusInterface&) = delete;
+    SPIBusInterface(const SPIBusInterface&)            = delete;
     SPIBusInterface& operator=(const SPIBusInterface&) = delete;
     SPIBusInterface(SPIBusInterface&&)                 = delete;
-    SPIBusInterface& operator=(SPIBusInterface&&) = delete;
+    SPIBusInterface& operator=(SPIBusInterface&&)      = delete;
 
     /**
      * @brief Configures the bus with the provided configuration parameters.
diff --git a/src/shared/drivers/spi/SPISlaveBus.h b/src/shared/drivers/spi/SPISlaveBus.h
index a530c7bd0cfc44e81dad78b60abd7d86764eb3c4..fcd274c25937ff768f112e0312176d0d3344024f 100644
--- a/src/shared/drivers/spi/SPISlaveBus.h
+++ b/src/shared/drivers/spi/SPISlaveBus.h
@@ -38,10 +38,10 @@ public:
     SPISlaveBus(SPIType* spi, SPISignalGenerator signalGenerator);
 
     ///< Delete copy/move contructors/operators.
-    SPISlaveBus(const SPISlaveBus&) = delete;
+    SPISlaveBus(const SPISlaveBus&)            = delete;
     SPISlaveBus& operator=(const SPISlaveBus&) = delete;
     SPISlaveBus(SPISlaveBus&&)                 = delete;
-    SPISlaveBus& operator=(SPISlaveBus&&) = delete;
+    SPISlaveBus& operator=(SPISlaveBus&&)      = delete;
 
     /**
      * @brief Configures and enables the bus with the provided configuration.
diff --git a/src/shared/drivers/spi/SPITransaction.h b/src/shared/drivers/spi/SPITransaction.h
index 3cc057519782f9a0abdc4ce96c9e0e440ad2ce33..6c98cafbe1b4ba524f8ade19e399acb4c2b61b47 100644
--- a/src/shared/drivers/spi/SPITransaction.h
+++ b/src/shared/drivers/spi/SPITransaction.h
@@ -84,10 +84,10 @@ public:
                    WriteBit writeBit = WriteBit::NORMAL);
 
     ///< Delete copy/move contructors/operators.
-    SPITransaction(const SPITransaction &) = delete;
+    SPITransaction(const SPITransaction &)            = delete;
     SPITransaction &operator=(const SPITransaction &) = delete;
     SPITransaction(SPITransaction &&)                 = delete;
-    SPITransaction &operator=(SPITransaction &&) = delete;
+    SPITransaction &operator=(SPITransaction &&)      = delete;
 
     /**
      * @brief Returns the underlying bus for low level access.
diff --git a/src/shared/drivers/timer/BasicTimer.h b/src/shared/drivers/timer/BasicTimer.h
index 1260739b7c1f3bc8f8357c102d623630ec8b43cf..00428f59ac6fc23490b067bef83a21c51e26b58d 100644
--- a/src/shared/drivers/timer/BasicTimer.h
+++ b/src/shared/drivers/timer/BasicTimer.h
@@ -85,7 +85,7 @@ public:
     explicit BasicTimer(TIM_TypeDef *timer);
 
     /**
-     * @brief Resets the registers and disables the peripheral clock.
+     * @brief Disables the peripheral clock.
      */
     ~BasicTimer();
 
@@ -207,11 +207,7 @@ inline BasicTimer::BasicTimer(TIM_TypeDef *timer) : timer(timer)
     ClockUtils::enablePeripheralClock(timer);
 }
 
-inline BasicTimer::~BasicTimer()
-{
-    reset();
-    ClockUtils::disablePeripheralClock(timer);
-}
+inline BasicTimer::~BasicTimer() { ClockUtils::disablePeripheralClock(timer); }
 
 inline TIM_TypeDef *BasicTimer::getTimer() { return timer; }
 
diff --git a/src/shared/drivers/timer/GeneralPurposeTimer.h b/src/shared/drivers/timer/GeneralPurposeTimer.h
index ca968af17cdf858d7224842373987a2be33d9690..de22562375a598275a56c8231ed9e8e5752cb5c7 100644
--- a/src/shared/drivers/timer/GeneralPurposeTimer.h
+++ b/src/shared/drivers/timer/GeneralPurposeTimer.h
@@ -110,7 +110,7 @@ public:
     explicit GeneralPurposeTimer(TIM_TypeDef *timer);
 
     /**
-     * @brief Resets the registers and disables the peripheral clock.
+     * @brief Disables the peripheral clock.
      */
     ~GeneralPurposeTimer();
 
@@ -230,7 +230,6 @@ inline GeneralPurposeTimer<T>::GeneralPurposeTimer(TIM_TypeDef *timer)
 template <typename T>
 inline GeneralPurposeTimer<T>::~GeneralPurposeTimer()
 {
-    reset();
     ClockUtils::disablePeripheralClock(timer);
 }
 
diff --git a/src/shared/drivers/timer/PWM.cpp b/src/shared/drivers/timer/PWM.cpp
index 471466913c65ab4e768d3b2d5cbea301d6e371fc..b6f8a6aca9e5ec5b1fa782d36cbf18a010c802d8 100644
--- a/src/shared/drivers/timer/PWM.cpp
+++ b/src/shared/drivers/timer/PWM.cpp
@@ -30,8 +30,6 @@ PWM::PWM(TIM_TypeDef* const timer, unsigned int pwmFrequency,
     : timer(timer), pwmFrequency(pwmFrequency),
       dutyCycleResolution(dutyCycleResolution)
 {
-    // TODO: Enable the peripheral clock
-
     // Erase the previous timer configuration
     this->timer.reset();
 
@@ -39,11 +37,7 @@ PWM::PWM(TIM_TypeDef* const timer, unsigned int pwmFrequency,
     setTimerConfiguration();
 }
 
-PWM::~PWM()
-{
-    // TODO: Disable the peripheral clock
-    timer.reset();
-}
+PWM::~PWM() { timer.reset(); }
 
 void PWM::setFrequency(unsigned int pwmFrequency)
 {
@@ -90,8 +84,8 @@ void PWM::setDutyCycle(TimerUtils::Channel channel, float dutyCycle)
 
 float PWM::getDutyCycle(TimerUtils::Channel channel)
 {
-    return static_cast<float>(timer.readCaptureCompareRegister(channel) /
-                              timer.readAutoReloadRegister());
+    return static_cast<float>(timer.readCaptureCompareRegister(channel)) /
+           static_cast<float>(timer.readAutoReloadRegister());
 }
 
 void PWM::setTimerConfiguration()
diff --git a/src/shared/drivers/timer/PWM.h b/src/shared/drivers/timer/PWM.h
index 988613fa78738e2bbbd93d15c244c937cc3b2645..b25e508ef893510f406e89cfd47161ca5ef9911c 100644
--- a/src/shared/drivers/timer/PWM.h
+++ b/src/shared/drivers/timer/PWM.h
@@ -35,13 +35,13 @@ namespace Boardcore
  * when deleted) but no channels are enabled. This means that you will only need
  * to use the channels without worrying about the underlying timer.
  *
- * The PWM driver accepts a the pointer to the peripheral registers of a timer
- * and uses it as a 16bit general purpose timer. No checks are in place to this
+ * The PWM driver accepts a pointer to the peripheral registers of a timer and
+ * uses it as a 16bit general purpose timer. No checks are in place to this
  * pointer, thus make sure to pass a proper value!
  *
  * Moreover, even 32bit general purpose timers are used as if they where 16bit.
  * At the moment there is no need for further accuracy but if it ever will be,
- * exceeding this class is simble, just add a template parameter and pass it to
+ * exceeding this class is simple, just add a template parameter and pass it to
  * the GeneralPurposeTimer parameter.
  *
  * Check out the following spread sheet to visually see how the timers registers
diff --git a/src/shared/drivers/timer/TimerUtils.h b/src/shared/drivers/timer/TimerUtils.h
index b575dcb16a35b3a11e0f42e2f60aa2cf039ba1a6..64d7e6801881e12f836fa62de423f83e0ae8477d 100644
--- a/src/shared/drivers/timer/TimerUtils.h
+++ b/src/shared/drivers/timer/TimerUtils.h
@@ -254,7 +254,7 @@ enum class Channel : int
  *
  * @return Timer input clock, APB1 or ABP2.
  */
-ClockUtils::APB getTimerInputClock(TIM_TypeDef *timer);
+ClockUtils::APB getTimerInputClock(const TIM_TypeDef *timer);
 
 /**
  * @brief Returns the timer clock frequency before the prescaler.
@@ -264,7 +264,7 @@ ClockUtils::APB getTimerInputClock(TIM_TypeDef *timer);
  * @param timer Timer to use.
  * @return Prescaler input frequency.
  */
-uint32_t getPrescalerInputFrequency(TIM_TypeDef *timer);
+uint32_t getPrescalerInputFrequency(const TIM_TypeDef *timer);
 
 /**
  * @brief Return the timer clock frequency.
@@ -360,7 +360,7 @@ uint16_t computePrescalerValue(TIM_TypeDef *timer, int targetFrequency);
 
 }  // namespace TimerUtils
 
-inline ClockUtils::APB TimerUtils::getTimerInputClock(TIM_TypeDef *timer)
+inline ClockUtils::APB TimerUtils::getTimerInputClock(const TIM_TypeDef *timer)
 {
     // Timers can be connected to APB1 or APB2 clocks.
     // APB1: TIM2-7,12-15
@@ -377,7 +377,7 @@ inline ClockUtils::APB TimerUtils::getTimerInputClock(TIM_TypeDef *timer)
     }
 }
 
-inline uint32_t TimerUtils::getPrescalerInputFrequency(TIM_TypeDef *timer)
+inline uint32_t TimerUtils::getPrescalerInputFrequency(const TIM_TypeDef *timer)
 {
     return ClockUtils::getAPBFrequency(getTimerInputClock(timer));
 }
diff --git a/src/shared/drivers/timer/TimestampTimer.cpp b/src/shared/drivers/timer/TimestampTimer.cpp
index 7c0739fadaf48ba13172009ab36bfa41cc520321..8faa0167753afc9907b8d245aeeda165f5fb4971 100644
--- a/src/shared/drivers/timer/TimestampTimer.cpp
+++ b/src/shared/drivers/timer/TimestampTimer.cpp
@@ -27,27 +27,21 @@
 namespace Boardcore
 {
 
-void TimestampTimer::resetTimestamp()
+TimestampTimer::TimestampTimer()
 {
-#ifndef COMPILE_FOR_HOST
-    timer.setCounter(0);
-#endif
+    initTimestampTimer();
+    enableTimestampTimer();
 }
 
 #ifndef COMPILE_FOR_HOST
+
 TIM_TypeDef *TimestampTimer::getTimer() { return timer.getTimer(); }
-#endif
 
-TimestampTimer::TimestampTimer()
-{
-    initTimestampTimer();
-    enableTimestampTimer();
-}
+void TimestampTimer::resetTimestamp() { timer.setCounter(0); }
 
 // TODO: Keep support for STM32F103
 void TimestampTimer::initTimestampTimer()
 {
-#ifndef COMPILE_FOR_HOST
     {
         miosix::FastInterruptDisableLock dLock;
         // Enable TIM2 peripheral clock
@@ -60,7 +54,6 @@ void TimestampTimer::initTimestampTimer()
 
     // Generate an update event to apply the new prescaler value
     timer.generateUpdate();
-#endif
 
     PrintLogger logger = Logging::getLogger("timestamptimer");
     LOG_INFO(logger, "Initialized timestamp timer");
@@ -68,12 +61,28 @@ void TimestampTimer::initTimestampTimer()
 
 void TimestampTimer::enableTimestampTimer()
 {
-#ifndef COMPILE_FOR_HOST
     timer.enable();
-#endif
 
     PrintLogger logger = Logging::getLogger("timestamptimer");
     LOG_INFO(logger, "Enabled timestamp timer");
 }
 
+#else
+
+void TimestampTimer::resetTimestamp() {}
+
+void TimestampTimer::initTimestampTimer()
+{
+    PrintLogger logger = Logging::getLogger("timestamptimer");
+    LOG_INFO(logger, "Initialized timestamp timer [COMPILE_FOR_HOST]");
+}
+
+void TimestampTimer::enableTimestampTimer()
+{
+    PrintLogger logger = Logging::getLogger("timestamptimer");
+    LOG_INFO(logger, "Enabled timestamp timer [COMPILE_FOR_HOST]");
+}
+
+#endif
+
 }  // namespace Boardcore
diff --git a/src/shared/drivers/usart/USART.cpp b/src/shared/drivers/usart/USART.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..bc5612a0855997b158c2a1cc5f35c2b01248ce94
--- /dev/null
+++ b/src/shared/drivers/usart/USART.cpp
@@ -0,0 +1,780 @@
+/* Copyright (c) 2022 Skyward Experimental Rocketry
+ * Author: 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 "drivers/usart/USART.h"
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <utils/Debug.h>
+
+#include <string>
+
+#include "arch/common/drivers/serial.h"
+#include "filesystem/file_access.h"
+#include "miosix.h"
+
+Boardcore::USART *Boardcore::USART::ports[N_USART_PORTS];
+
+/**
+ * \internal Interrupt routine for usart1 actual implementation.
+ */
+void __attribute__((used)) usart1irqImplBoardcore()
+{
+    Boardcore::USART *port_boardcore = Boardcore::USART::ports[0];
+    if (port_boardcore)
+        port_boardcore->IRQhandleInterrupt();
+    else
+    {
+        miosix::STM32Serial *port = miosix::STM32Serial::ports[0];
+        if (port)
+            port->IRQhandleInterrupt();
+    }
+}
+
+/**
+ * \internal Interrupt routine for usart1.
+ */
+void __attribute__((naked, used)) USART1_IRQHandler()
+{
+    saveContext();
+    asm volatile("bl _Z22usart1irqImplBoardcorev");
+    restoreContext();
+}
+
+/**
+ * \internal Interrupt routine for usart2 actual implementation.
+ */
+void __attribute__((used)) usart2irqImplBoardcore()
+{
+    Boardcore::USART *port_boardcore = Boardcore::USART::ports[1];
+    if (port_boardcore)
+        port_boardcore->IRQhandleInterrupt();
+    else
+    {
+        miosix::STM32Serial *port = miosix::STM32Serial::ports[1];
+        if (port)
+            port->IRQhandleInterrupt();
+    }
+}
+
+/**
+ * \internal Interrupt routine for usart2.
+ */
+void __attribute__((naked, used)) USART2_IRQHandler()
+{
+    saveContext();
+    asm volatile("bl _Z22usart2irqImplBoardcorev");
+    restoreContext();
+}
+
+/**
+ * \internal Interrupt routine for usart3 actual implementation.
+ */
+void __attribute__((used)) usart3irqImplBoardcore()
+{
+    Boardcore::USART *port_boardcore = Boardcore::USART::ports[2];
+    if (port_boardcore)
+        port_boardcore->IRQhandleInterrupt();
+    else
+    {
+        miosix::STM32Serial *port = miosix::STM32Serial::ports[2];
+        if (port)
+            port->IRQhandleInterrupt();
+    }
+}
+
+/**
+ * \internal Interrupt routine for usart3.
+ */
+void __attribute__((naked, used)) USART3_IRQHandler()
+{
+    saveContext();
+    asm volatile("bl _Z22usart3irqImplBoardcorev");
+    restoreContext();
+}
+
+/**
+ * \internal Interrupt routine for uart4 actual implementation.
+ */
+void __attribute__((used)) uart4irqImplBoardcore()
+{
+    Boardcore::USART *port_boardcore = Boardcore::USART::ports[3];
+    if (port_boardcore)
+        port_boardcore->IRQhandleInterrupt();
+    else
+    {
+        miosix::STM32Serial *port = miosix::STM32Serial::ports[3];
+        if (port)
+            port->IRQhandleInterrupt();
+    }
+}
+
+/**
+ * \internal Interrupt routine for uart4.
+ */
+void __attribute__((naked, used)) UART4_IRQHandler()
+{
+    saveContext();
+    asm volatile("bl _Z21uart4irqImplBoardcorev");
+    restoreContext();
+}
+
+/**
+ * \internal Interrupt routine for uart5 actual implementation.
+ */
+void __attribute__((used)) uart5irqImplBoardcore()
+{
+    Boardcore::USART *port_boardcore = Boardcore::USART::ports[4];
+    if (port_boardcore)
+        port_boardcore->IRQhandleInterrupt();
+    else
+    {
+        miosix::STM32Serial *port = miosix::STM32Serial::ports[4];
+        if (port)
+            port->IRQhandleInterrupt();
+    }
+}
+
+/**
+ * \internal Interrupt routine for uart5.
+ */
+void __attribute__((naked, used)) UART5_IRQHandler()
+{
+    saveContext();
+    asm volatile("bl _Z21uart5irqImplBoardcorev");
+    restoreContext();
+}
+
+/**
+ * \internal Interrupt routine for usart6 actual implementation.
+ */
+void __attribute__((used)) usart6irqImplBoardcore()
+{
+    Boardcore::USART *port_boardcore = Boardcore::USART::ports[5];
+    if (port_boardcore)
+        port_boardcore->IRQhandleInterrupt();
+    else
+    {
+        miosix::STM32Serial *port = miosix::STM32Serial::ports[5];
+        if (port)
+            port->IRQhandleInterrupt();
+    }
+}
+
+/**
+ * \internal Interrupt routine for usart6.
+ */
+void __attribute__((naked, used)) USART6_IRQHandler()
+{
+    saveContext();
+    asm volatile("bl _Z22usart6irqImplBoardcorev");
+    restoreContext();
+}
+
+#ifdef STM32F429xx
+
+/**
+ * \internal Interrupt routine for uart7 actual implementation.
+ */
+void __attribute__((used)) uart7irqImplBoardcore()
+{
+    Boardcore::USART *port_boardcore = Boardcore::USART::ports[6];
+    if (port_boardcore)
+        port_boardcore->IRQhandleInterrupt();
+    else
+    {
+        miosix::STM32Serial *port = miosix::STM32Serial::ports[6];
+        if (port)
+            port->IRQhandleInterrupt();
+    }
+}
+
+/**
+ * \internal Interrupt routine for uart7.
+ */
+void __attribute__((naked, used)) UART7_IRQHandler()
+{
+    saveContext();
+    asm volatile("bl _Z21uart7irqImplBoardcorev");
+    restoreContext();
+}
+
+/**
+ * \internal Interrupt routine for uart8 actual implementation.
+ */
+void __attribute__((used)) uart8irqImplBoardcore()
+{
+    Boardcore::USART *port_boardcore = Boardcore::USART::ports[7];
+    if (port_boardcore)
+        port_boardcore->IRQhandleInterrupt();
+    else
+    {
+        miosix::STM32Serial *port = miosix::STM32Serial::ports[7];
+        if (port)
+            port->IRQhandleInterrupt();
+    }
+}
+
+/**
+ * \internal Interrupt routine for uart8.
+ */
+void __attribute__((naked, used)) UART8_IRQHandler()
+{
+    saveContext();
+    asm volatile("bl _Z21uart8irqImplBoardcorev");
+    restoreContext();
+}
+
+#endif  // STM32F429xx
+
+namespace Boardcore
+{
+
+USARTInterface::~USARTInterface() {}
+
+bool USARTInterface::initPins(miosix::GpioPin tx, int nAFtx, miosix::GpioPin rx,
+                              int nAFrx)
+{
+    if (pinInitialized)
+    {
+        return false;
+    }
+
+    miosix::FastInterruptDisableLock dLock;
+
+    this->tx = tx;
+    this->rx = rx;
+
+    tx.mode(miosix::Mode::ALTERNATE);
+    tx.alternateFunction(nAFtx);
+
+    rx.mode(miosix::Mode::ALTERNATE);
+    rx.alternateFunction(nAFrx);
+
+    pinInitialized = true;
+    return true;
+}
+
+void USART::IRQhandleInterrupt()
+{
+    char c;
+
+    // If read data register is empty then read data
+    if (usart->SR & USART_SR_RXNE)
+    {
+        // Always read data, since this clears interrupt flags
+        c = usart->DR;
+        // If no error put data in buffer
+        if (!(usart->SR & USART_SR_FE))
+            if (rxQueue.tryPut(c) == false)  // FIFO overflow
+                ;
+
+        idle = false;
+    }
+
+    if (usart->SR & USART_SR_IDLE)
+        idle = true;
+
+    if (usart->SR & USART_SR_IDLE || rxQueue.size() >= rxQueue.capacity() / 2)
+    {
+        c = usart->DR;  // Clears interrupt flags
+
+        // Enough data in buffer or idle line, awake thread
+        if (rxWaiting)
+        {
+            rxWaiting->IRQwakeup();
+            rxWaiting = 0;
+        }
+    }
+}
+
+USART::USART(USARTType *usart, Baudrate baudrate, unsigned int queueLen)
+    : rxQueue(queueLen)
+{
+    // Setting the id of the serial port
+    switch (reinterpret_cast<uint32_t>(usart))
+    {
+        case USART1_BASE:
+            this->id = 1;
+            initPins(u1tx1::getPin(), 7, u1rx1::getPin(), 7);
+            irqn = USART1_IRQn;
+            break;
+        case USART2_BASE:
+            this->id = 2;
+            initPins(u2tx1::getPin(), 7, u2rx1::getPin(), 7);
+            irqn = USART2_IRQn;
+            break;
+        case USART3_BASE:
+            this->id = 3;
+            initPins(u3tx1::getPin(), 7, u3rx1::getPin(), 7);
+            irqn = USART3_IRQn;
+            break;
+        case UART4_BASE:
+            this->id = 4;
+            initPins(u4tx1::getPin(), 8, u4rx1::getPin(), 8);
+            irqn = UART4_IRQn;
+            break;
+        case UART5_BASE:
+            this->id = 5;
+            initPins(u5tx::getPin(), 8, u5rx::getPin(), 8);
+            irqn = UART5_IRQn;
+            break;
+        case USART6_BASE:
+            this->id = 6;
+            initPins(u6tx1::getPin(), 8, u6rx1::getPin(), 8);
+            irqn = USART6_IRQn;
+            break;
+#ifdef STM32F429xx
+        case UART7_BASE:
+            this->id = 7;
+            initPins(u7tx1::getPin(), 8, u7rx1::getPin(), 8);
+            irqn = UART7_IRQn;
+            break;
+        case UART8_BASE:
+            this->id = 8;
+            initPins(u8tx::getPin(), 8, u8rx::getPin(), 8);
+            irqn = UART8_IRQn;
+            break;
+#endif  // STM32F429xx
+    }
+
+    commonConstructor(usart, baudrate);
+}
+
+USART::USART(USARTType *usart, Baudrate baudrate, miosix::GpioPin tx,
+             miosix::GpioPin rx, unsigned int queueLen)
+    : rxQueue(queueLen)
+{
+    // Setting the id of the serial port
+    switch (reinterpret_cast<uint32_t>(usart))
+    {
+        case USART1_BASE:
+            this->id = 1;
+            initPins(tx, 7, rx, 7);
+            irqn = USART1_IRQn;
+            break;
+        case USART2_BASE:
+            this->id = 2;
+            initPins(tx, 7, rx, 7);
+            irqn = USART2_IRQn;
+            break;
+        case USART3_BASE:
+            this->id = 3;
+            initPins(tx, 7, rx, 7);
+            irqn = USART3_IRQn;
+            break;
+        case UART4_BASE:
+            this->id = 4;
+            initPins(tx, 8, rx, 8);
+            irqn = UART4_IRQn;
+            break;
+        case UART5_BASE:
+            this->id = 5;
+            initPins(tx, 8, rx, 8);
+            irqn = UART5_IRQn;
+            break;
+        case USART6_BASE:
+            this->id = 6;
+            initPins(tx, 8, rx, 8);
+            irqn = USART6_IRQn;
+            break;
+#ifdef STM32F429xx
+        case UART7_BASE:
+            this->id = 7;
+            initPins(tx, 8, rx, 8);
+            irqn = UART7_IRQn;
+            break;
+        case UART8_BASE:
+            this->id = 8;
+            initPins(tx, 8, rx, 8);
+            irqn = UART8_IRQn;
+            break;
+#endif  // STM32F429xx
+    }
+
+    commonConstructor(usart, baudrate);
+}
+
+void USART::commonConstructor(USARTType *usart, Baudrate baudrate)
+{
+    this->usart = usart;
+
+    // Enabling the peripehral on the right APB
+    ClockUtils::enablePeripheralClock(usart);
+    RCC_SYNC();
+
+    // Enabling the usart peripheral
+    {
+        miosix::FastInterruptDisableLock dLock;
+        usart->CR1 |= USART_CR1_UE;
+    }
+
+    // Setting the baudrate chosen
+    setBaudrate(baudrate);
+
+    // Default settings
+    setStopBits(1);
+    setWordLength(USART::WordLength::BIT8);
+    setParity(USART::ParityBit::NO_PARITY);
+    setOversampling(false);
+}
+
+USART::~USART()
+{
+    {
+        miosix::FastInterruptDisableLock dLock;
+
+        // Take out the usart object we are going to destruct
+        USART::ports[this->id - 1] = nullptr;
+
+        // Disabling the usart
+        usart->CR1 &= ~(USART_CR1_UE | USART_CR1_TE | USART_CR1_RE);
+
+        // Disabling the interrupt of the serial port
+        NVIC_DisableIRQ(irqn);
+    }
+}
+
+bool USART::init()
+{
+    if (id < 1 || id > MAX_SERIAL_PORTS || !pinInitialized)
+    {
+        TRACE("Not supported USART id or pins not initialized\n");
+        return false;
+    }
+
+    {
+        miosix::FastInterruptDisableLock dLock;
+
+        // Enable usart, receiver, receiver interrupt and idle interrupt
+        usart->CR1 |= USART_CR1_RXNEIE    // Interrupt on data received
+                      | USART_CR1_IDLEIE  // interrupt on idle line
+                      | USART_CR1_TE      // Transmission enabled
+                      | USART_CR1_RE;     // Reception enabled
+
+        // Sample only one bit
+        usart->CR3 |= USART_CR3_ONEBIT;
+
+        // Enabling the interrupt for the relative serial port
+        NVIC_SetPriority(irqn, 15);
+        NVIC_EnableIRQ(irqn);
+
+        // Add to the array of usarts so that the interrupts can see it
+        USART::ports[id - 1] = this;
+    }
+
+    // Clearing the queue for random data read at the beginning
+    miosix::Thread::sleep(1);
+    this->clearQueue();
+
+    return true;
+}
+
+void USART::setWordLength(WordLength wordLength)
+{
+
+    miosix::FastInterruptDisableLock dLock;
+    (wordLength == WordLength::BIT8 ? usart->CR1 &= ~USART_CR1_M
+                                    : usart->CR1 |= USART_CR1_M);
+    this->wordLength = wordLength;
+}
+
+void USART::setParity(ParityBit parity)
+{
+    miosix::FastInterruptDisableLock dLock;
+    (parity == ParityBit::NO_PARITY ? usart->CR1 &= ~USART_CR1_PCE
+                                    : usart->CR1 |= USART_CR1_PCE);
+    this->parity = parity;
+}
+
+void USART::setStopBits(int stopBits)
+{
+    miosix::FastInterruptDisableLock dLock;
+    this->stopBits = stopBits;
+    usart->CR2 &= ~USART_CR2_STOP;
+    if (stopBits == 2)
+    {
+        usart->CR2 |= USART_CR2_STOP_1;
+    }
+}
+
+void USART::setOversampling(bool oversampling)
+{
+    miosix::FastInterruptDisableLock dLock;
+    this->over8 = oversampling;
+    (oversampling ? usart->CR1 |= USART_CR1_OVER8
+                  : usart->CR1 &= ~USART_CR1_OVER8);
+}
+
+void USART::setBaudrate(Baudrate baudrate)
+{
+    /*
+     * Baudrate setting:
+     * fixed point: mantissa first 12 bits, other 4 bits are the fixed point
+     * value of the fraction.
+     * - if over8==0 => DIV_Fraction[3:0] (all fraction used)
+     * USART_DIV = f/(16*baud)
+     * - if over8==1 => 0+DIV_Fraction[2:0] (first bit of fraction is 0)
+     * USART_DIV = f/(8*baud)
+     */
+    miosix::InterruptDisableLock dLock;
+
+    // USART1 and USART6 is always connected to the APB2, while all the others
+    // UART/USART peripherals are always connected to APB1
+    uint32_t f = ClockUtils::getAPBFrequency(
+        (id == 1 || id == 6 ? ClockUtils::APB::APB2     // High speed APB2
+                            : ClockUtils::APB::APB1));  // Low speed APB1,
+
+    // <<4 in order to shift to left of 4 positions, to create a fixed point
+    // number of 4 decimal digits /8 == >>3 in order to divide per 8 (from the
+    // formula in the datasheet)
+    uint32_t USART_DIV = ((f << 1) / (((int)baudrate * (over8 ? 1 : 2))));
+
+    // rounding to the nearest
+    uint32_t brr = (USART_DIV / 2) + (USART_DIV & 1);
+
+    if (over8)
+    {
+        brr += brr & 1;
+    }
+
+    usart->BRR = brr;
+
+    this->baudrate = baudrate;
+}
+
+int USART::read(void *buffer, size_t nBytes)
+{
+    miosix::Lock<miosix::FastMutex> l(rxMutex);
+
+    char *buf     = reinterpret_cast<char *>(buffer);
+    size_t result = 0;
+    miosix::FastInterruptDisableLock dLock;
+    for (;;)
+    {
+        // Try to get data from the queue
+        for (; result < nBytes; result++)
+        {
+            if (rxQueue.tryGet(buf[result]) == false)
+                break;
+            // This is here just not to keep IRQ disabled for the whole loop
+            miosix::FastInterruptEnableLock eLock(dLock);
+        }
+
+        // Not checking if the nBytes read are more than 0
+        if (result == nBytes || (idle && result > 0))
+            break;
+
+        // Wait for data in the queue
+        do
+        {
+            rxWaiting = miosix::Thread::IRQgetCurrentThread();
+            miosix::Thread::IRQwait();
+            {
+                miosix::FastInterruptEnableLock eLock(dLock);
+                miosix::Thread::yield();
+            }
+        } while (rxWaiting);
+    }
+
+    return result;
+}
+
+int USART::write(void *buffer, size_t nBytes)
+{
+    miosix::Lock<miosix::FastMutex> l(txMutex);
+
+    // TODO: Use the send complete interrupt in order not to have a busy while
+    // loop waiting
+    const char *buf = reinterpret_cast<const char *>(buffer);
+    size_t i        = 0;
+    for (i = 0; i < nBytes; i++)
+    {
+        while ((usart->SR & USART_SR_TXE) == 0)
+            ;
+
+        usart->DR = *buf;
+        buf++;
+    }
+
+    return i;
+}
+
+int USART::writeString(const char *buffer)
+{
+    int i = 0;
+    miosix::Lock<miosix::FastMutex> l(txMutex);
+
+    // Send everything, also the ending '\0' character
+    usart->DR = *buffer;
+    i++;
+    while (*buffer != '\0')
+    {
+        buffer++;
+        while (!(usart->SR & USART_SR_TXE))
+            ;
+
+        usart->DR = *buffer;
+        i++;
+    };
+
+    return i;
+}
+
+void USART::clearQueue() { rxQueue.reset(); }
+
+STM32SerialWrapper::STM32SerialWrapper(USARTType *usart, Baudrate baudrate)
+{
+    this->usart    = usart;
+    this->baudrate = baudrate;
+    switch (reinterpret_cast<uint32_t>(usart))
+    {
+        case USART1_BASE:
+            this->id = 1;
+            initPins(u1tx1::getPin(), 7, u1rx1::getPin(), 7);
+            this->serialPortName = std::string("usart1");
+            break;
+        case USART2_BASE:
+            this->id = 2;
+            initPins(u2tx1::getPin(), 7, u2rx1::getPin(), 7);
+            this->serialPortName = std::string("usart2");
+            break;
+        case USART3_BASE:
+            this->id = 3;
+            initPins(u3tx1::getPin(), 7, u3rx1::getPin(), 7);
+            this->serialPortName = std::string("usart3");
+            break;
+    }
+    initialized = false;
+    fd          = -1;
+}
+
+STM32SerialWrapper::STM32SerialWrapper(USARTType *usart, Baudrate baudrate,
+                                       miosix::GpioPin tx, miosix::GpioPin rx)
+{
+    this->usart    = usart;
+    this->baudrate = baudrate;
+    switch (reinterpret_cast<uint32_t>(usart))
+    {
+        case USART1_BASE:
+            this->id             = 1;
+            this->serialPortName = std::string("usart1");
+            break;
+        case USART2_BASE:
+            this->id             = 2;
+            this->serialPortName = std::string("usart2");
+            break;
+        case USART3_BASE:
+            this->id             = 3;
+            this->serialPortName = std::string("usart3");
+            break;
+    }
+    initPins(tx, 7, rx, 7);
+    initialized = false;
+    fd          = -1;
+}
+
+STM32SerialWrapper::~STM32SerialWrapper()
+{
+    miosix::intrusive_ref_ptr<miosix::DevFs> devFs =
+        miosix::FilesystemManager::instance().getDevFs();
+    close(fd);
+    devFs->remove(serialPortName.c_str());
+}
+
+bool STM32SerialWrapper::init()
+{
+    if (id > 3)
+    {
+        TRACE(
+            "[STM32SerialWrapper] USART id greater than 3 is not supported\n");
+        return false;
+    }
+
+    if (initialized)
+    {
+        TRACE(
+            "[STM32SerialWrapper] Error : serial communication already "
+            "initialized!\n");
+        return false;
+    }
+    else if (!serialCommSetup())
+    {
+        TRACE(
+            "[STM32SerialWrapper] Error : can't initialize serial "
+            "communication!\n");
+        return false;
+    }
+
+    initialized = true;
+    return true;
+}
+
+bool STM32SerialWrapper::serialCommSetup()
+{
+    // Creates and adds the serial port to the devices
+    if (!pinInitialized)
+        serial = new miosix::STM32Serial(id, static_cast<int>(baudrate));
+    else
+    {
+        serial =
+            new miosix::STM32Serial(id, static_cast<int>(baudrate), tx, rx);
+    }
+
+    // Adds a device to the file system
+    if (!miosix::FilesystemManager::instance().getDevFs()->addDevice(
+            serialPortName.c_str(),
+            miosix::intrusive_ref_ptr<miosix::Device>(serial)))
+        return false;
+
+    // Path string "/dev/<name_of_port>" for the port we want to open
+    std::string serialPortPath = "/dev/" + serialPortName;
+
+    // Open serial port
+    fd = open(serialPortPath.c_str(), O_RDWR);
+
+    if (fd <= -1)
+    {
+        TRACE("Cannot open %s\n", serialPortPath.c_str());
+        return false;
+    }
+
+    return true;
+}
+
+int STM32SerialWrapper::writeString(const char *data)
+{
+    // strlen + 1 in order to send the '/0' terminated string
+    return ::write(fd, data, strlen(data) + 1);
+}
+
+int STM32SerialWrapper::write(void *buf, size_t nChars)
+{
+    return ::write(fd, buf, nChars);
+}
+
+int STM32SerialWrapper::read(void *buf, size_t nBytes)
+{
+    return ::read(fd, buf, nBytes);
+}
+
+}  // namespace Boardcore
diff --git a/src/shared/drivers/usart/USART.h b/src/shared/drivers/usart/USART.h
new file mode 100644
index 0000000000000000000000000000000000000000..05cee3a1662984393fc8790c31dab329eb1f201c
--- /dev/null
+++ b/src/shared/drivers/usart/USART.h
@@ -0,0 +1,426 @@
+/* Copyright (c) 2022 Skyward Experimental Rocketry
+ * Author: 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.
+ */
+
+#pragma once
+
+#include <interfaces/arch_registers.h>
+#include <miosix.h>
+#include <utils/ClockUtils.h>
+
+#include <string>
+
+#include "arch/common/drivers/serial.h"
+
+#ifndef USE_MOCK_PERIPHERALS
+using USARTType = USART_TypeDef;
+#else
+// TODO: Create test utils
+#endif
+
+#ifdef STM32F429xx
+#define N_USART_PORTS 8
+#else
+#define N_USART_PORTS 6
+#endif
+
+// A nice feature of the stm32 is that the USART are connected to the same
+// GPIOS in all families, stm32f1, f2, f4 and l1. Additionally, USART1 and
+// USART6 are always connected to the APB2, while the other USART/UARTs are
+// connected to the APB1.
+
+// USART1: AF7
+typedef miosix::Gpio<GPIOB_BASE, 6> u1tx1;
+typedef miosix::Gpio<GPIOB_BASE, 7> u1rx1;
+typedef miosix::Gpio<GPIOA_BASE, 9> u1tx2;
+typedef miosix::Gpio<GPIOA_BASE, 10> u1rx2;
+// typedef miosix::Gpio<GPIOA_BASE, 11> u1cts;
+// typedef miosix::Gpio<GPIOA_BASE, 12> u1rts;
+
+// USART2: AF7
+typedef miosix::Gpio<GPIOA_BASE, 2> u2tx1;
+typedef miosix::Gpio<GPIOA_BASE, 3> u2rx1;
+typedef miosix::Gpio<GPIOD_BASE, 5> u2tx2;
+typedef miosix::Gpio<GPIOD_BASE, 6> u2rx2;
+// typedef miosix::Gpio<GPIOA_BASE, 0> u2cts;
+// typedef miosix::Gpio<GPIOA_BASE, 1> u2rts;
+
+// USART3: AF7
+typedef miosix::Gpio<GPIOB_BASE, 10> u3tx1;
+typedef miosix::Gpio<GPIOB_BASE, 11> u3rx1;
+typedef miosix::Gpio<GPIOD_BASE, 8> u3tx2;
+typedef miosix::Gpio<GPIOD_BASE, 9> u3rx2;
+// typedef miosix::Gpio<GPIOB_BASE, 13> u3cts;
+// typedef miosix::Gpio<GPIOB_BASE, 14> u3rts;
+
+// UART4: AF8
+typedef miosix::Gpio<GPIOA_BASE, 0> u4tx1;
+typedef miosix::Gpio<GPIOA_BASE, 1> u4rx1;
+typedef miosix::Gpio<GPIOC_BASE, 10> u4tx2;
+typedef miosix::Gpio<GPIOC_BASE, 11> u4rx2;
+
+// UART5: AF8
+typedef miosix::Gpio<GPIOC_BASE, 12> u5tx;
+typedef miosix::Gpio<GPIOD_BASE, 2> u5rx;
+
+// USART6: AF8
+typedef miosix::Gpio<GPIOC_BASE, 6> u6tx1;
+typedef miosix::Gpio<GPIOC_BASE, 7> u6rx1;
+#ifdef STM32F429xx
+typedef miosix::Gpio<GPIOG_BASE, 14> u6tx2;
+typedef miosix::Gpio<GPIOG_BASE, 9> u6rx2;
+
+// USART7: AF8
+typedef miosix::Gpio<GPIOE_BASE, 8> u7tx1;
+typedef miosix::Gpio<GPIOE_BASE, 7> u7rx1;
+typedef miosix::Gpio<GPIOF_BASE, 7> u7tx2;
+typedef miosix::Gpio<GPIOF_BASE, 6> u7rx2;
+
+// USART8: AF8
+typedef miosix::Gpio<GPIOE_BASE, 1> u8tx;
+typedef miosix::Gpio<GPIOE_BASE, 0> u8rx;
+#endif  // STM32F429xx
+
+namespace Boardcore
+{
+/**
+ * @brief Abstract class that implements the interface for the USART/UART serial
+ * communication.
+ */
+class USARTInterface
+{
+public:
+    enum class Baudrate : int
+    {
+        // B1200   = 1200, // NOT WORKING WITH 1200 baud
+        B2400   = 2400,
+        B9600   = 9600,
+        B19200  = 19200,
+        B38400  = 38400,
+        B57600  = 57600,
+        B115200 = 115200,
+        B230400 = 230400,
+        B460800 = 460800,
+        B921600 = 921600
+    };
+
+    virtual ~USARTInterface() = 0;
+
+    /**
+     * @brief Initializes the peripheral enabling his interrupts, the interrupts
+     * in the NVIC.
+     *
+     * All the setup phase (with the setting of the pins and their alternate
+     * functions) must be done before the initialization of the peripheral.
+     */
+    virtual bool init() = 0;
+
+    /**
+     * @brief Blocking read operation to read nBytes or till the data transfer
+     * is complete.
+     */
+    virtual int read(void *buffer, size_t nBytes) = 0;
+
+    /**
+     * @brief Blocking write operation.
+     */
+    virtual int write(void *buf, size_t nChars) = 0;
+
+    /**
+     * @brief Write a string to the serial, comprising the '\0' character.
+     */
+    virtual int writeString(const char *buffer) = 0;
+
+    /**
+     * @brief Returns the id of the serial.
+     */
+    int getId() { return id; };
+
+protected:
+    /**
+     * @brief Initializes the pins with the appropriate alternate functions.
+     *
+     * @param tx Tranmission pin.
+     * @param nAFtx Tranmission pin alternate function.
+     * @param rx Reception pin.
+     * @param nAFrx Reception pin alternate function.
+     */
+    bool initPins(miosix::GpioPin tx, int nAFtx, miosix::GpioPin rx, int nAFrx);
+
+    ///< True if initPins() already called successfully, false otherwise
+    bool pinInitialized = false;
+
+    miosix::GpioPin tx{GPIOA_BASE, 0};
+    miosix::GpioPin rx{GPIOA_BASE, 0};
+
+    USARTType *usart;
+    int id           = 1;  ///< Can be 1, 2, 3, 4, 5, 6, 7, 8
+    bool initialized = false;
+    Baudrate baudrate;  ///< Baudrate of the serial communication
+};
+
+/**
+ * @brief Driver for STM32F4 low level USART/UART peripheral.
+ *
+ * It allows to configure some low level parameters such as word length, parity,
+ * stop bits and oversampling.
+ */
+class USART : public USARTInterface
+{
+public:
+    enum class WordLength : bool
+    {
+        BIT8 = 0,
+        BIT9 = 1
+    };
+
+    enum class ParityBit : bool
+    {
+        NO_PARITY = 0,
+        PARITY    = 1
+    };
+
+    ///< Pointer to serial port classes to let interrupts access the classes
+    static USART *ports[];
+
+    /**
+     * @brief Interrupt handler that deals with receive and idle interrupts.
+     *
+     * Used to implement the reads, fills up a buffer when the interrupt is
+     * fired.
+     */
+    void IRQhandleInterrupt();
+
+    /**
+     * @brief Automatically enables the peripheral and timer peripheral clock.
+     *
+     * Sets the default values for all the parameters (1 stop bit, 8 bit data,
+     * no control flow and no oversampling).
+     * Initializes the serial port using the default pins, which are:
+     * - USART1: tx=PA9  rx=PA10
+     * - USART2: tx=PA2  rx=PA3
+     * - USART3: tx=PB10 rx=PB11
+     * - UART4:  tx=PA0  rx=PA1
+     * - UART5:  tx=PC12 rx=PD2
+     * - USART6: tx=PC6  rx=PC7
+     * - UART7:  tx=PE8  rx=PE7
+     * - UART8:  tx=PE1  rx=PE0
+     *
+     * @param usart structure that represents the usart peripheral [accepted
+     * are: USART1, USART2, USART3, UART4, UART5, USART6, UART7, UART8].
+     * @param baudrate member of the enum Baudrate that represents the baudrate
+     * with which the communication will take place.
+     */
+    USART(USARTType *usart, Baudrate baudrate,
+          unsigned int queueLen = usart_queue_default_capacity);
+
+    /**
+     * @brief Automatically enables the peripheral and timer peripheral clock.
+     *
+     * Sets the default values for all the parameters (1 stop bit, 8 bit data,
+     * no control flow and no oversampling).
+     * Initializes the serial port using custom pins.
+     *
+     * @param usart Structure that represents the usart peripheral [accepted
+     * are: USART1, USART2, USART3, UART4, UART5, USART6, UART7, UART8].
+     * @param baudrate Member of the enum Baudrate that represents the baudrate
+     * with which the communication will take place.
+     * @param tx Tranmission pin.
+     * @param rx Reception pin.
+     */
+    USART(USARTType *usart, Baudrate baudrate, miosix::GpioPin tx,
+          miosix::GpioPin rx,
+          unsigned int queueLen = usart_queue_default_capacity);
+
+    /**
+     * @brief Disables the flags for the generation of the interrupts, the IRQ
+     * from the NVIC, the peripheral and removes his pointer from the ports
+     * list.
+     */
+    ~USART();
+
+    /**
+     * @brief Initializes the peripheral enabling his interrupts, the interrupts
+     * in the NVIC and setting the pins with the appropriate alternate
+     * functions.
+     *
+     * All the setup phase must be done before the initialization of the
+     * peripheral. The pins must be initialized before calling this function.
+     */
+    bool init();
+
+    /**
+     * @brief Blocking read operation to read nBytes or till the data transfer
+     * is complete.
+     */
+    int read(void *buffer, size_t nBytes);
+
+    /**
+     * @brief Blocking write operation.
+     */
+    int write(void *buf, size_t nChars);
+
+    /**
+     * @brief Write a string to the serial, comprising the '\0' character.
+     */
+    int writeString(const char *buffer);
+
+    /**
+     * @brief Set the length of the word to 8 or to 9.
+     *
+     * @param wl WordLength element that represents the length of the word.
+     */
+    void setWordLength(WordLength wl);
+
+    /**
+     * @brief Set the presence of the parity in the data sent.
+     *
+     * @param pb ParityBit element that represents the presence of the parity
+     * bit.
+     */
+    void setParity(ParityBit pb);
+
+    /**
+     * @brief Set the number of stop bits.
+     *
+     * @param stopBits number of stop bits [1,2].
+     */
+    void setStopBits(int stopBits);
+
+    /**
+     * @brief Set the baudrate in the BRR register.
+     *
+     * @param pb Baudrate element that represents the baudrate.
+     */
+    void setBaudrate(Baudrate br);
+
+    /**
+     * @brief Sets the Over8 bit.
+     *
+     * If it is set, the speed is increased; If it is reset the tolerance is
+     * increased.
+     */
+    void setOversampling(bool oversampling);
+
+    /**
+     * @brief Clears the rxQueue.
+     */
+    void clearQueue();
+
+private:
+    void commonConstructor(USARTType *usart, Baudrate baudrate);
+
+    IRQn_Type irqn;
+    miosix::FastMutex rxMutex;  ///< mutex for receiving on serial
+    miosix::FastMutex txMutex;  ///< mutex for transmitting on serial
+
+    ///< Pointer to the waiting on receive thread
+    miosix::Thread *rxWaiting = 0;
+
+    miosix::DynUnsyncQueue<char> rxQueue;  ///< Receiving queue
+    bool idle             = true;          ///< Receiver idle
+    ParityBit parity      = ParityBit::NO_PARITY;
+    WordLength wordLength = WordLength::BIT8;
+    int stopBits          = 1;      ///< Number of stop bits [1,2]
+    bool over8            = false;  ///< Oversalmpling 8 bit
+
+    ///< Default queue length
+    const static unsigned int usart_queue_default_capacity = 256;
+};
+
+/**
+ * @brief Wrapper for the STM32Serial driver in miosix.
+ */
+class STM32SerialWrapper : public USARTInterface
+{
+public:
+    /**
+     * @brief Initializes the serialPortName and initializes the default pins,
+     * which are:
+     * - USART1: tx=PA9  rx=PA10
+     * - USART2: tx=PA2  rx=PA3
+     * - USART3: tx=PB10 rx=PB11
+     * @param usart structure that represents the usart peripheral [accepted
+     * are: USART1, USART2, USART3].
+     * @param baudrate member of the enum Baudrate that represents the baudrate
+     * with which the communication will take place.
+     */
+    STM32SerialWrapper(USARTType *usart, Baudrate baudrate);
+
+    /**
+     * @brief Initializes the serialPortName and initializes the serial port
+     * using custom pins.
+     * @param usart structure that represents the usart peripheral [accepted
+     * are: USART1, USART2, USART3].
+     * @param baudrate member of the enum Baudrate that represents the baudrate
+     * with which the communication will take place.
+     * @param tx Tranmission pin
+     * @param rx Reception pin
+     */
+    STM32SerialWrapper(USARTType *usart, Baudrate baudrate, miosix::GpioPin tx,
+                       miosix::GpioPin rx);
+
+    /**
+     * @brief Removes the device from the list of the devices and closes the
+     * file of the device.
+     */
+    ~STM32SerialWrapper();
+
+    /**
+     * @brief Initializes the peripheral.
+     *
+     * @see{STM32SerialWrapper::serialCommSetup}
+     */
+    bool init();
+
+    /**
+     * @brief Blocking read operation to read nBytes or till the data transfer
+     * is complete.
+     */
+    int read(void *buffer, size_t nBytes);
+
+    /**
+     * @brief Blocking write operation.
+     */
+    int write(void *buf, size_t nChars);
+
+    /**
+     * @brief Write a string to the serial, comprising the '\0' character.
+     */
+    int writeString(const char *buffer);
+
+private:
+    /**
+     * @brief Creates a device that represents the serial port, adds it to the
+     * file system and opens the file that represents the device.
+     */
+    bool serialCommSetup();
+
+    miosix::STM32Serial *serial;  ///< Pointer to the serial object
+
+    //< Port name of the port that has to be created for the communication
+    std::string serialPortName;
+
+    ///< File descriptor of the serial port file opened for transmission
+    int fd;
+};
+
+}  // namespace Boardcore
diff --git a/src/shared/events/Event.h b/src/shared/events/Event.h
index d35c6ab219a2d5c2c7e0060e75eece7ea2eeb075..c38ec3ef83a50bbbe86428bbb8f7d9e89a363427 100644
--- a/src/shared/events/Event.h
+++ b/src/shared/events/Event.h
@@ -27,38 +27,27 @@
 namespace Boardcore
 {
 
-enum EventSignal : uint8_t
+typedef uint8_t Event;
+
+enum BasicEvent : Event
 {
     EV_ENTRY        = 0,
     EV_EXIT         = 1,
     EV_EMPTY        = 2,
     EV_INIT         = 3,
-    EV_FIRST_SIGNAL = 4
+    EV_FIRST_CUSTOM = 4
 };
 
 /**
- * 	Example definiton of custom signals:
-
-        enum CustomSignal : uint8_t
-        {
-                SIG_ONE = SG_FIRST_SIGNAL,
-                SIG_TWO,
-                SIG_THREE,
-                SIG_FOUR
-        };
-
+ * Example definition of custom events:
+ *
+ * enum CustomEvent : Event
+ * {
+ *     EV_ONE = EV_FIRST_CUSTOM,
+ *     EV_TWO,
+ *     EV_THREE.
+ *     EV_FOUR
+ * }
  */
 
-struct Event
-{
-    uint8_t code;
-};
-
-/* Example of extended Event structure
-
-        struct ExtendedEvent : public Event{
-                uint32_t customMember;
-        };
-*/
-
 }  // namespace Boardcore
diff --git a/src/shared/events/EventBroker.cpp b/src/shared/events/EventBroker.cpp
index 019aefc8e0464bd9ab3b88e4e3cb27cf329e1d0c..b748fc1e91393133da2709cab2a761fe341ed442 100644
--- a/src/shared/events/EventBroker.cpp
+++ b/src/shared/events/EventBroker.cpp
@@ -29,11 +29,10 @@ namespace Boardcore
 {
 
 EventBroker::EventBroker() {}
-
 void EventBroker::post(const Event& ev, uint8_t topic)
 {
 #ifdef TRACE_EVENTS
-    LOG_DEBUG(logger, "Event: {}, Topic: {}", ev.code, topic);
+    LOG_DEBUG(logger, "Event: {}, Topic: {}", ev, topic);
 #endif
 
     Lock<FastMutex> lock(mtxSubscribers);
@@ -66,6 +65,34 @@ void EventBroker::removeDelayed(uint16_t id)
     }
 }
 
+void EventBroker::subscribe(EventHandlerBase* subscriber, uint8_t topic)
+{
+    Lock<FastMutex> lock(mtxSubscribers);
+    subscribers[topic].push_back(subscriber);
+}
+
+void EventBroker::unsubscribe(EventHandlerBase* subscriber, uint8_t topic)
+{
+    Lock<FastMutex> lock(mtxSubscribers);
+
+    deleteSubscriber(subscribers.at(topic), subscriber);
+}
+
+void EventBroker::unsubscribe(EventHandlerBase* subscriber)
+{
+    Lock<FastMutex> lock(mtxSubscribers);
+    for (auto it = subscribers.begin(); it != subscribers.end(); it++)
+    {
+        deleteSubscriber(it->second, subscriber);
+    }
+}
+
+void EventBroker::clearDelayedEvents()
+{
+    Lock<FastMutex> lock(mtxDelayedEvents);
+    delayedEvents.clear();
+}
+
 // Posts delayed events with expired deadline
 void EventBroker::run()
 {
@@ -110,28 +137,6 @@ void EventBroker::run()
     }
 }
 
-void EventBroker::subscribe(EventHandlerBase* subscriber, uint8_t topic)
-{
-    Lock<FastMutex> lock(mtxSubscribers);
-    subscribers[topic].push_back(subscriber);
-}
-
-void EventBroker::unsubscribe(EventHandlerBase* subscriber, uint8_t topic)
-{
-    Lock<FastMutex> lock(mtxSubscribers);
-
-    deleteSubscriber(subscribers.at(topic), subscriber);
-}
-
-void EventBroker::unsubscribe(EventHandlerBase* subscriber)
-{
-    Lock<FastMutex> lock(mtxSubscribers);
-    for (auto it = subscribers.begin(); it != subscribers.end(); it++)
-    {
-        deleteSubscriber(it->second, subscriber);
-    }
-}
-
 void EventBroker::deleteSubscriber(vector<EventHandlerBase*>& subs,
                                    EventHandlerBase* subscriber)
 {
@@ -150,10 +155,4 @@ void EventBroker::deleteSubscriber(vector<EventHandlerBase*>& subs,
     }
 }
 
-void EventBroker::clearDelayedEvents()
-{
-    Lock<FastMutex> lock(mtxDelayedEvents);
-    delayedEvents.clear();
-}
-
 }  // namespace Boardcore
diff --git a/src/shared/events/EventBroker.h b/src/shared/events/EventBroker.h
index 27222a5a486524adefff64905e649689a422cda4..581ff8011164a47766542460c3959b9fcad1ec4f 100644
--- a/src/shared/events/EventBroker.h
+++ b/src/shared/events/EventBroker.h
@@ -62,8 +62,6 @@ class EventBroker : public Singleton<EventBroker>, public ActiveObject
 public:
     /**
      * Posts an event to the specified topic.
-     * @param ev
-     * @param topic
      */
     void post(const Event& ev, uint8_t topic);
 
@@ -150,14 +148,12 @@ public:
 
     /**
      * @brief Construct a new Event Broker object.
+     *
      * Public access required for testing purposes. Use the singleton interface
      * to access this class in production code.
-     *
      */
     EventBroker();
 
-    virtual ~EventBroker(){};
-
 private:
     /**
      * Private structure for holding a delayed event data in the list.
@@ -190,5 +186,3 @@ private:
 };
 
 }  // namespace Boardcore
-
-#define sEventBroker Boardcore::Singleton<Boardcore::EventBroker>::getInstance()
diff --git a/src/shared/events/FSM.h b/src/shared/events/FSM.h
index 0f32e4c973f5334264ee67cae30937dbff064fe4..f4ed570643450d16616eafcd84c575b02b6de859 100644
--- a/src/shared/events/FSM.h
+++ b/src/shared/events/FSM.h
@@ -63,8 +63,8 @@ FSM<T>::FSM(void (T::*initialState)(const Event&), unsigned int stacksize,
             miosix::Priority priority)
     : EventHandler(stacksize, priority)
 {
-    state             = initialState;
-    specialEvent.code = EV_ENTRY;
+    state        = initialState;
+    specialEvent = EV_ENTRY;
     postEvent(specialEvent);
 }
 
@@ -74,10 +74,10 @@ FSM<T>::~FSM(){};
 template <class T>
 void FSM<T>::transition(void (T::*nextState)(const Event&))
 {
-    specialEvent.code = EV_EXIT;
+    specialEvent = EV_EXIT;
     (static_cast<T*>(this)->*state)(specialEvent);
-    state             = nextState;
-    specialEvent.code = EV_ENTRY;
+    state        = nextState;
+    specialEvent = EV_ENTRY;
     (static_cast<T*>(this)->*state)(specialEvent);
 }
 
diff --git a/src/shared/events/HSM.h b/src/shared/events/HSM.h
index 01020e669b8edfb256cdf0a07b4aae32ee794999..435aef317a4cfd59ab49a6b0958a49e883d4fa90 100644
--- a/src/shared/events/HSM.h
+++ b/src/shared/events/HSM.h
@@ -381,8 +381,8 @@ private:
         StateHandler target = this->state;
         State retState;
 
-        State s = (static_cast<T*>(this)->*temp)({EV_EMPTY});
-        UNUSED(s);  // Avoid warning when not compiling for DEBUG
+        State s __attribute__((unused)) =
+            (static_cast<T*>(this)->*temp)({EV_EMPTY});
         D(assert(s == TRAN));
 
         do
diff --git a/src/shared/events/utils/EventCounter.h b/src/shared/events/utils/EventCounter.h
index db1f43bedcec3001abbdb066729c0198d39d5f3f..9f0914d26e17c0fca31960c706320deb5c5eebed 100644
--- a/src/shared/events/utils/EventCounter.h
+++ b/src/shared/events/utils/EventCounter.h
@@ -60,27 +60,22 @@ public:
     {
         Lock<FastMutex> l(mutex);
 
-        ++mapCounter[ev.code];
+        ++mapCounter[ev];
         ++totalCount;
 
-        lastEvent = ev.code;
+        lastEvent = ev;
     }
 
     /**
-     * @brief Returns the number of times a specific event has been received
+     * @brief Returns the number of times a specific event has been received.
      */
-    unsigned int getCount(const Event& ev) { return getCount(ev.code); }
-
-    /**
-     * @brief Returns the number of times a specific event has been received
-     */
-    unsigned int getCount(uint8_t evSig)
+    unsigned int getCount(const Event& ev)
     {
         Lock<FastMutex> l(mutex);
 
-        if (mapCounter.count(evSig) == 1)
+        if (mapCounter.count(ev) == 1)
         {
-            return mapCounter.at(evSig);
+            return mapCounter.at(ev);
         }
 
         return 0;
@@ -92,17 +87,13 @@ public:
     unsigned int getTotalCount() { return totalCount; }
 
     /**
-     * @brief Returns the signature of the last event received (ev.code)
+     * @brief Returns the signature of the last event received (ev)
      */
     uint8_t getLastEvent() { return lastEvent; }
 
 protected:
     // Do nothing
-    void handleEvent(const Event& ev) override
-    {
-        // Avoid unused argument warning
-        (void)ev;
-    };
+    void handleEvent(const Event& ev __attribute__((unused))) override{};
 
 private:
     EventBroker& broker;
diff --git a/src/shared/events/utils/EventInjector.h b/src/shared/events/utils/EventInjector.h
index d57c820f137c6385fb6ce75bbb7ede6a1a89692d..c9df410e213085153a1e6146e5b123d2fd89dcbd 100644
--- a/src/shared/events/utils/EventInjector.h
+++ b/src/shared/events/utils/EventInjector.h
@@ -57,7 +57,7 @@ protected:
             getline(cin, temp);
             stringstream(temp) >> ev >> topic;
 
-            sEventBroker.post({(uint8_t)ev}, topic);
+            EventBroker::getInstance().post({(uint8_t)ev}, topic);
         }
     }
 };
diff --git a/src/shared/events/utils/EventSniffer.h b/src/shared/events/utils/EventSniffer.h
index 19dff058ca32d00d0d6edf2c0b1755f62e317b69..67785774247ec3f5e6c17f5b2194365766d4e685 100644
--- a/src/shared/events/utils/EventSniffer.h
+++ b/src/shared/events/utils/EventSniffer.h
@@ -94,10 +94,7 @@ private:
             parent.broker.subscribe(this, topic);
         }
 
-        void postEvent(const Event& ev)
-        {
-            parent.onEventReceived(ev.code, topic);
-        }
+        void postEvent(const Event& ev) { parent.onEventReceived(ev, topic); }
 
         ~Sniffer() { parent.broker.unsubscribe(this); }
 
diff --git a/src/shared/logger/Deserializer.h b/src/shared/logger/Deserializer.h
index 63bdfbe53bec423e99ff24544062da452b0adba7..e0382dffcaec69b821faa0b7f28b81f0095c0a4a 100644
--- a/src/shared/logger/Deserializer.h
+++ b/src/shared/logger/Deserializer.h
@@ -27,6 +27,7 @@
 
 #include <cstdio>
 #include <fstream>
+#include <iostream>
 #include <limits>
 #include <ostream>
 #include <string>
@@ -51,151 +52,155 @@ typedef std::numeric_limits<float> flt;
 class Deserializer
 {
 public:
-    Deserializer(std::string logfile, std::string prefix = "")
-        : prefix(prefix), logFile(logfile),
-          logFileWithExt(prefix + logFile + ".dat")
-    {
-    }
+    Deserializer(std::string fileName);
 
-    ~Deserializer()
-    {
-        if (!closed)
-        {
-            for (auto it = fileStreams.begin(); it != fileStreams.end(); it++)
-            {
-                (*it)->close();
-                delete *it;
-            }
-        }
-    }
+    ~Deserializer();
 
     /**
-     * Register a type to be deserialized, and the associated print function
+     * Register a type to be deserialized, and the associated print function.
      *
-     * @param t the object to be deserialized
-     * @param fncPrint function that prints the deserialized data on the
+     * @param t The object to be deserialized.
+     * @param fncPrint Function that prints the deserialized data on the
      * provided output stream.
+     * @param header Optional CSV header text.
      */
     template <typename T>
     bool registerType(std::function<void(T& t, std::ostream& os)> fncPrint,
-                      std::string header = "")
-    {
-        if (closed)
-        {
-            printf("Error: Deserializer is closed.\n");
-            return false;
-        }
+                      std::string header = "");
 
-        char cFilename[128];
-        sprintf(cFilename, "%s%s_%s.csv", prefix.c_str(), logFile.c_str(),
-                typeid(T).name());
+    /**
+     * @brief Deserializes the provided file.
+     *
+     * @return Whether the deserialization was successful.
+     */
+    bool deserialize();
 
-        std::string filename(cFilename);
+    void close();
 
-        std::ofstream* stream = new std::ofstream();
-        stream->open(filename);
+private:
+    bool closed = false;
 
-        if (!stream->is_open())
-        {
-            printf("Error opening file %s.\n", filename.c_str());
-            perror("Error is:");
-            delete stream;
-            return false;
-        }
+    std::vector<std::ofstream*> fileStreams;
+    tscpp::TypePoolStream tps;
+
+    std::string fileName;
+};
+
+Deserializer::Deserializer(std::string fileName) : fileName(fileName) {}
 
-        fileStreams.push_back(stream);
-        stream->precision(flt::max_digits10);  // Set stream precision to
-                                               // maximum float precision
-        // Print the header
-        if (header.length() > 0)
+Deserializer::~Deserializer()
+{
+    if (!closed)
+        for (auto it = fileStreams.begin(); it != fileStreams.end(); it++)
         {
-            *stream << header;
+            (*it)->close();
+            delete *it;
         }
+}
 
-        using namespace std::placeholders;  // for _1
+template <typename T>
+bool Deserializer::registerType(
+    std::function<void(T& t, std::ostream& os)> fncPrint, std::string header)
+{
+    if (closed)
+    {
+        printf("Error: Deserializer is closed.\n");
+        return false;
+    }
 
-        std::function<void(T & t)> callback =
-            std::bind(fncPrint, _1, std::ref(*stream));
+    char cFilename[128];
+    sprintf(cFilename, "%s_%s.csv", fileName.c_str(), typeid(T).name());
 
-        tps.registerType<T>(callback);
+    std::string filename(cFilename);
 
-        return true;
+    std::ofstream* stream = new std::ofstream();
+    stream->open(filename);
+
+    if (!stream->is_open())
+    {
+        printf("Error opening file %s.\n", filename.c_str());
+        perror("Error is:");
+        delete stream;
+        return false;
     }
 
-    /**
-     * @brief Deserializes the provided file.
-     *
-     * @return Wheter the deserialization was successful.
-     */
-    bool deserialize()
+    fileStreams.push_back(stream);
+    stream->precision(flt::max_digits10);  // Set stream precision to
+                                           // maximum float precision
+    // Print the header
+    if (header.length() > 0)
     {
-        if (closed)
-        {
-            return false;
-        }
+        *stream << header;
+    }
+
+    using namespace std::placeholders;  // for _1
+
+    std::function<void(T & t)> callback =
+        std::bind(fncPrint, _1, std::ref(*stream));
 
-        bool success = true;
-        std::string unknownTypeName;
+    tps.registerType<T>(callback);
+
+    return true;
+}
+
+bool Deserializer::deserialize()
+{
+    if (closed)
+        return false;
+
+    bool success = true;
+    std::string unknownTypeName;
+
+    std::ifstream file(fileName);
+
+    // Check if the file exists
+    if (!file)
+    {
+        std::cout << fileName << " does not exists." << std::endl;
+        return false;
+    }
 
-        struct stat st;
-        if (stat(logFileWithExt.c_str(), &st) != 0)
+    tscpp::UnknownInputArchive ia(file, tps);
+    int i = 0;
+    while (success)
+    {
+        try
         {
-            printf("File %s does not exists.\n", logFileWithExt.c_str());
-            return false;
+            ia.unserialize();
         }
-
-        std::ifstream file(logFileWithExt);
-        // file.open;
-        tscpp::UnknownInputArchive ia(file, tps);
-        int i = 0;
-        while (success)
+        catch (tscpp::TscppException& ex)
         {
-            try
+            // Reached end of file
+            if (strcmp(ex.what(), "eof") == 0)
             {
-                ia.unserialize();
+                break;
             }
-            catch (tscpp::TscppException& ex)
+            else if (strcmp(ex.what(), "unknown type") == 0)
             {
-                // Reached end of file
-                if (strcmp(ex.what(), "eof") == 0)
-                {
-                    break;
-                }
-                else if (strcmp(ex.what(), "unknown type") == 0)
-                {
-                    unknownTypeName = ex.name();
-                    success         = false;
-                    printf("Unknown type found: %s\n", unknownTypeName.c_str());
-                    break;
-                }
+                unknownTypeName = ex.name();
+                success         = false;
+                std::cout << "Unknown type found: " << unknownTypeName
+                          << std::endl;
+                break;
             }
         }
-        file.close();
-        return success;
     }
 
-    void close()
+    file.close();
+    return success;
+}
+
+void Deserializer::close()
+{
+    if (!closed)
     {
-        if (!closed)
+        closed = true;
+        for (auto it = fileStreams.begin(); it != fileStreams.end(); it++)
         {
-            closed = true;
-            for (auto it = fileStreams.begin(); it != fileStreams.end(); it++)
-            {
-                (*it)->close();
-                delete *it;
-            }
+            (*it)->close();
+            delete *it;
         }
     }
-
-private:
-    bool closed = false;
-
-    std::vector<std::ofstream*> fileStreams;
-    tscpp::TypePoolStream tps;
-
-    std::string prefix;
-    std::string logFile;
-    std::string logFileWithExt;
-};
+}
 
 }  // namespace Boardcore
diff --git a/src/shared/logger/LogTypes.h b/src/shared/logger/LogTypes.h
new file mode 100644
index 0000000000000000000000000000000000000000..bb2a1c06dfee3da621fe2676f56cdce841af3904
--- /dev/null
+++ b/src/shared/logger/LogTypes.h
@@ -0,0 +1,125 @@
+/* Copyright (c) 2015-2022 Skyward Experimental Rocketry
+ * Authors: Luca Erbetta, 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
+
+#include <algorithms/NAS/NASState.h>
+#include <diagnostic/PrintLoggerData.h>
+#include <drivers/adc/InternalADCData.h>
+#include <events/EventData.h>
+#include <logger/Deserializer.h>
+#include <logger/LoggerStats.h>
+#include <radio/MavlinkDriver/MavlinkStatus.h>
+#include <radio/Xbee/XbeeStatus.h>
+#include <scheduler/TaskSchedulerData.h>
+#include <sensors/ADS1118/ADS1118Data.h>
+#include <sensors/ADS131M04/ADS131M04Data.h>
+#include <sensors/BME280/BME280Data.h>
+#include <sensors/BMP280/BMP280Data.h>
+#include <sensors/BMX160/BMX160Data.h>
+#include <sensors/BMX160/BMX160WithCorrectionData.h>
+#include <sensors/HX711/HX711Data.h>
+#include <sensors/L3GD20/L3GD20Data.h>
+#include <sensors/LIS3DSH/LIS3DSHData.h>
+#include <sensors/LIS3MDL/LIS3MDLData.h>
+#include <sensors/MBLoadCell/MBLoadCellData.h>
+#include <sensors/MPU9250/MPU9250Data.h>
+#include <sensors/MS5803/MS5803Data.h>
+#include <sensors/SensorData.h>
+#include <sensors/UbloxGPS/UbloxGPSData.h>
+#include <sensors/VN100/VN100Data.h>
+#include <sensors/analog/BatteryVoltageSensorData.h>
+#include <sensors/analog/CurrentSensorData.h>
+#include <sensors/analog/pressure/MPXHZ6130A/MPXHZ6130AData.h>
+#include <sensors/analog/pressure/honeywell/HSCMAND015PAData.h>
+#include <sensors/analog/pressure/honeywell/HSCMRNN030PAData.h>
+#include <sensors/analog/pressure/honeywell/HSCMRNN160KAData.h>
+#include <sensors/analog/pressure/honeywell/SSCDANN030PAAData.h>
+#include <sensors/analog/pressure/honeywell/SSCDRRN015PDAData.h>
+
+#include <fstream>
+#include <iostream>
+
+/**
+ * @brief This file includes all the types the logdecoder script will decode.
+ *
+ * All logged classes inside Boardcore should be reported here.
+ */
+
+namespace Boardcore
+{
+
+namespace LogTypes
+{
+
+template <typename T>
+void print(T& t, std::ostream& os)
+{
+    t.print(os);
+}
+
+template <typename T>
+void registerType(Deserializer& ds)
+{
+    ds.registerType<T>(print<T>, T::header());
+}
+
+void registerTypes(Deserializer& ds)
+{
+    registerType<NASState>(ds);
+    registerType<LoggingString>(ds);
+    registerType<InternalADCData>(ds);
+    registerType<EventData>(ds);
+    registerType<LoggerStats>(ds);
+    registerType<MavlinkStatus>(ds);
+    registerType<Xbee::XbeeStatus>(ds);
+    registerType<TaskStatsResult>(ds);
+    registerType<ADS1118Data>(ds);
+    registerType<ADS131M04Data>(ds);
+    registerType<BME280Data>(ds);
+    registerType<BMP280Data>(ds);
+    registerType<BMX160Data>(ds);
+    registerType<BMX160WithCorrectionData>(ds);
+    registerType<HX711Data>(ds);
+    registerType<L3GD20Data>(ds);
+    registerType<LIS3DSHData>(ds);
+    registerType<LIS3MDLData>(ds);
+    registerType<MBLoadCellData>(ds);
+    registerType<MPU9250Data>(ds);
+    registerType<MS5803Data>(ds);
+    registerType<TemperatureData>(ds);
+    registerType<UbloxGPSData>(ds);
+    registerType<VN100Data>(ds);
+    registerType<BatteryVoltageSensorData>(ds);
+    registerType<CurrentSensorData>(ds);
+    registerType<LoadCellData>(ds);
+    registerType<MPXHZ6130AData>(ds);
+    registerType<HSCMAND015PAData>(ds);
+    registerType<HSCMRNN030PAData>(ds);
+    registerType<HSCMRNN160KAData>(ds);
+    registerType<SSCDANN030PAAData>(ds);
+    registerType<SSCDRRN015PDAData>(ds);
+}
+
+}  // namespace LogTypes
+
+}  // namespace Boardcore
diff --git a/src/shared/logger/Logger.cpp b/src/shared/logger/Logger.cpp
index 2709fbcc2f7d3a6e9bf860f180f7036200b4a433..31876f6014c64a9e77bae874c4b26fcd7442725b 100644
--- a/src/shared/logger/Logger.cpp
+++ b/src/shared/logger/Logger.cpp
@@ -42,10 +42,10 @@ using namespace miosix;
 namespace Boardcore
 {
 
-int Logger::start()
+bool Logger::start()
 {
     if (started)
-        return fileNumber;
+        return true;
 
     // Find the proper log filename base on the current files on the disk
     string filename;
@@ -68,13 +68,13 @@ int Logger::start()
     {
         fileNumber = -1;
         TRACE("Error opening %s file\n", filename.c_str());
-        throw runtime_error("Error opening log file");
+        return false;
     }
     setbuf(file, NULL);  // Disable buffering for the file stream
 
-    // The boring part, start threads one by one and if they fail, undo
+    // The boring part, start threads one by one and if they fail, undo.
     // Perhaps excessive defensive programming as thread creation failure is
-    // highly unlikely (only if ram is full)
+    // highly unlikely (only if ram is full).
 
     packTh = Thread::create(packThreadLauncher, skywardStack(16 * 1024), 1,
                             this, Thread::JOINABLE);
@@ -82,7 +82,7 @@ int Logger::start()
     {
         fclose(file);
         TRACE("Error creating pack thread\n");
-        throw runtime_error("Error creating pack thread");
+        return false;
     }
 
     writeTh = Thread::create(writeThreadLauncher, skywardStack(16 * 1024), 1,
@@ -101,13 +101,13 @@ int Logger::start()
         fullBufferList.pop();  // Remove nullptr
         fclose(file);
         TRACE("Error creating write thread\n");
-        throw runtime_error("Error creating write thread");
+        return false;
     }
 
     started         = true;
     stats.logNumber = fileNumber;
 
-    return fileNumber;
+    return true;
 }
 
 void Logger::stop()
@@ -301,7 +301,6 @@ LoggerResult Logger::logImpl(const char* name, const void* data,
 {
     if (started == false)
     {
-        TRACE("Attempting to log %s but the Logger is not started!\n", name);
         stats.droppedSamples++;
 
         // Signal that we are trying to write to a closed log
diff --git a/src/shared/logger/Logger.h b/src/shared/logger/Logger.h
index 9679b837dea71c0d03de3f1bdb79719105c09278..f1ad95f7b077151a555348400c4ff10d82aa98ce 100644
--- a/src/shared/logger/Logger.h
+++ b/src/shared/logger/Logger.h
@@ -59,15 +59,17 @@ public:
     /**
      * @brief Call this function to start the logger.
      *
-     * When this function returns, the logger is started, and subsequent calls
-     * to log will actually log the data.
+     * The function tryies to start the logger. It first opens the log file and
+     * then create the pack and write threads. If it fails on one of this
+     * operation, the logger is not started.
+     *
+     * Use getCurrentLogNumber to retrieve the log file number.
      *
      * Blocking call. May take a long time.
      *
-     * \throws runtime_error if the log could not be opened.
-     * \return log number.
+     * \return true if the logger was started correctly.
      */
-    int start();
+    bool start();
 
     /**
      * @brief Call this function to stop the logger.
diff --git a/src/shared/math/SkyQuaternion.h b/src/shared/math/SkyQuaternion.h
deleted file mode 100644
index 24b424bc85b489f6c9df96ad3fc5f8de5faaa7e1..0000000000000000000000000000000000000000
--- a/src/shared/math/SkyQuaternion.h
+++ /dev/null
@@ -1,88 +0,0 @@
-/* Copyright (c) 2020 Skyward Experimental Rocketry
- * Author: Marco Cella
- *
- * 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
-
-#include <Eigen/Dense>
-
-namespace Boardcore
-{
-
-/**
- * @brief Class for managing quaternions.
- *        Convention used: qx, qy, qz, qw (scalar element as last element)
- */
-class SkyQuaternion
-{
-
-public:
-    SkyQuaternion();
-
-    /**
-     * @brief Transform a vector of euler angles ZYX to quaternion.
-     *
-     * @param degeul the vector of euler angles to be transformed [deg]
-     *
-     * @return transformed quaternion
-     */
-    Eigen::Vector4f eul2quat(Eigen::Vector3f degeul);
-
-    /**
-     * @brief Transform a quaternion to a vector of euler angles.
-     *
-     * @param quat the quaternion to be transformed
-     *
-     * @return transformed vector of euler angles [deg]
-     */
-    Eigen::Vector3f quat2eul(Eigen::Vector4f quat);
-
-    /**
-     * @brief Transform a rotation matrix to a quaternion.
-     *
-     * @param R the rotation matrix to be transformed (3x3)
-     *
-     * @return transformed quaternion
-     */
-    Eigen::Vector4f rotm2quat(Eigen::Matrix3f R);
-
-    /**
-     * @brief Normalize a quaternion.
-     *
-     * @param quat the quaternion to be normalized
-     *
-     * @return boolean indicating if the operation succeded or not
-     */
-    bool quatnormalize(Eigen::Vector4f& quat);
-
-    /**
-     * @brief Compute the product of two quaternions.
-     *
-     * @param q1 the first factor
-     * @param q2 the second factor
-     *
-     * @return the resulting quaternions product
-     */
-    Eigen::Vector4f quatProd(const Eigen::Vector4f q1,
-                             const Eigen::Vector4f q2);
-};
-
-}  // namespace Boardcore
diff --git a/src/shared/radio/Xbee/Xbee.h b/src/shared/radio/Xbee/Xbee.h
index ea748a68ff96aee0b6a58a78a4c9b447a3a3c41d..abb1be5015ba548258ab5207dd6abccff0bbd00a 100644
--- a/src/shared/radio/Xbee/Xbee.h
+++ b/src/shared/radio/Xbee/Xbee.h
@@ -38,7 +38,7 @@ using miosix::FastMutex;
 #ifndef USE_MOCK_PERIPHERALS
 using GpioType = miosix::GpioPin;
 #else
-#include <utils/testutils/MockGpioPin.h>
+#include <utils/TestUtils/MockGpioPin.h>
 using GpioType = MockGpioPin;
 #endif
 
diff --git a/src/shared/radio/Xbee/XbeeStatus.h b/src/shared/radio/Xbee/XbeeStatus.h
index 05f25880a39fa721c3453a6cce5597fdb2679925..ae0381dd915392ee8aa35e2a95f94a6a6d2baed2 100644
--- a/src/shared/radio/Xbee/XbeeStatus.h
+++ b/src/shared/radio/Xbee/XbeeStatus.h
@@ -22,7 +22,7 @@
 
 #pragma once
 
-#include <math/Stats.h>
+#include <utils/Stats/Stats.h>
 
 #include <cstdint>
 #include <cstdio>
diff --git a/src/shared/scheduler/TaskScheduler.cpp b/src/shared/scheduler/TaskScheduler.cpp
index 26dc700a04091a73c7d9d9c33a38db9f3a4612d6..0518b34d9e8b23c4a65efc67804e1b1edbd87125 100644
--- a/src/shared/scheduler/TaskScheduler.cpp
+++ b/src/shared/scheduler/TaskScheduler.cpp
@@ -40,14 +40,6 @@ TaskScheduler::TaskScheduler()
 bool TaskScheduler::addTask(function_t function, uint32_t period, uint8_t id,
                             Policy policy, int64_t startTick)
 {
-    // Perion must be grater than zero!
-    if (period <= 0 && policy != Policy::ONE_SHOT)
-    {
-        LOG_ERR(logger,
-                "Trying to add a task non one shot with an invalid period");
-        return false;
-    }
-
     Lock<FastMutex> lock(mutex);
 
     // Register the task into the map
@@ -69,6 +61,18 @@ bool TaskScheduler::addTask(function_t function, uint32_t period, uint8_t id,
     return result.second;
 }
 
+bool TaskScheduler::addTask(function_t function, uint32_t period, Policy policy,
+                            int64_t startTick)
+{
+    uint8_t id = 1;
+
+    auto it = tasks.cbegin(), end = tasks.cend();
+    for (; it != end && id == it->first; ++it, ++id)
+        ;
+
+    return addTask(function, period, id, policy, startTick);
+}
+
 bool TaskScheduler::removeTask(uint8_t id)
 {
     Lock<FastMutex> lock(mutex);
@@ -99,6 +103,8 @@ bool TaskScheduler::removeTask(uint8_t id)
 
 bool TaskScheduler::start()
 {
+    // This check is necessary to prevent task normalization if the scheduler is
+    // already stopped
     if (running)
         return false;
 
@@ -128,7 +134,7 @@ vector<TaskStatsResult> TaskScheduler::getTaskStats()
     return result;
 }
 
-void TaskScheduler ::normalizeTasks()
+void TaskScheduler::normalizeTasks()
 {
     int64_t currentTick = getTick();
 
diff --git a/src/shared/scheduler/TaskScheduler.h b/src/shared/scheduler/TaskScheduler.h
index 9a3ba6fe09c0940c59c20dcf643a2e5d282534c8..6eb0664158761b6cbc2b1eae124244429d890d94 100644
--- a/src/shared/scheduler/TaskScheduler.h
+++ b/src/shared/scheduler/TaskScheduler.h
@@ -25,7 +25,7 @@
 #include <ActiveObject.h>
 #include <Singleton.h>
 #include <diagnostic/PrintLogger.h>
-#include <math/Stats.h>
+#include <utils/Stats/Stats.h>
 
 #include <cstdint>
 #include <list>
@@ -110,6 +110,25 @@ public:
                  Policy policy     = Policy::SKIP,
                  int64_t startTick = miosix::getTick());
 
+    /**
+     * @brief Add a task function to the scheduler with an auto generated id.
+     *
+     * Note that each task has it's own unique ID, even one shot tasks!
+     * Therefore, if a task already exists with the same id, the function will
+     * fail and return false.
+     *
+     * For one shot tasks, the period is useless and not used.
+     *
+     * @param function Function to be called periodically.
+     * @param period Inter call period.
+     * @param policy Task policy, default is SKIP.
+     * @param startTick First activation time, useful for synchronizing tasks.
+     * @return true if the task was added successfully.
+     */
+    bool addTask(function_t function, uint32_t period,
+                 Policy policy     = Policy::SKIP,
+                 int64_t startTick = miosix::getTick());
+
     /**
      * @brief Removes the task identified by the given id if it exists.
      *
diff --git a/src/shared/scheduler/TaskSchedulerData.h b/src/shared/scheduler/TaskSchedulerData.h
index 35b04bbddc1627f8e6477ce6ebc1e44bdd13df4a..0cbe3865763d3d144b304ce2af5e26ea62dcb070 100644
--- a/src/shared/scheduler/TaskSchedulerData.h
+++ b/src/shared/scheduler/TaskSchedulerData.h
@@ -22,7 +22,7 @@
 
 #pragma once
 
-#include <math/Stats.h>
+#include <utils/Stats/Stats.h>
 
 #include <cstdint>
 #include <ostream>
diff --git a/src/shared/sensors/ADS1118/ADS1118.cpp b/src/shared/sensors/ADS1118/ADS1118.cpp
index 93724f64a2177cfdb5dd801c500deaee0d427dc8..b49ceb3c61a1da619856134ae200177a034ffb4d 100644
--- a/src/shared/sensors/ADS1118/ADS1118.cpp
+++ b/src/shared/sensors/ADS1118/ADS1118.cpp
@@ -236,7 +236,7 @@ void ADS1118::readChannel(int8_t nextChannel, int8_t prevChannel)
     }
 
     // Convert and save the value if last written configuration is valid
-    if (prevChannel >= 0)
+    if (prevChannel > INVALID_CHANNEL && prevChannel < NUM_OF_CHANNELS)
     {
         int16_t rawValue = swapBytes16(transferData);
 
diff --git a/src/shared/sensors/BME280/BME280.cpp b/src/shared/sensors/BME280/BME280.cpp
index fe621101c837e69e7bee8ad661de96ad1116a03e..c8fd6e5400f5209f54a43a2897d990a0505d585a 100644
--- a/src/shared/sensors/BME280/BME280.cpp
+++ b/src/shared/sensors/BME280/BME280.cpp
@@ -47,8 +47,8 @@ const BME280::BME280Config BME280::BME280_CONFIG_TEMP_SINGLE = {
     SKIPPED,        0, 0,          FORCED_MODE, SKIPPED,
     OVERSAMPLING_1, 0, FILTER_OFF, STB_TIME_0_5};
 
-BME280::BME280(SPISlave spiSlave_, BME280Config config_)
-    : spiSlave(spiSlave_), config(config_)
+BME280::BME280(SPISlave spiSlave, BME280Config config)
+    : spiSlave(spiSlave), config(config)
 {
 }
 
diff --git a/src/shared/sensors/BME280/BME280.h b/src/shared/sensors/BME280/BME280.h
index 7ab2c6cdd39e4d1973091a4c4c40d24e546369db..7da38ced045595836d85fcd9b59925f25f9d579d 100644
--- a/src/shared/sensors/BME280/BME280.h
+++ b/src/shared/sensors/BME280/BME280.h
@@ -155,8 +155,8 @@ public:
     static const BME280Config
         BME280_CONFIG_TEMP_SINGLE;  ///< Temperature enabled in forced mode
 
-    explicit BME280(SPISlave spiSlave_,
-                    BME280Config config_ = BME280_CONFIG_ALL_ENABLED);
+    explicit BME280(SPISlave spiSlave,
+                    BME280Config config = BME280_CONFIG_ALL_ENABLED);
 
     /**
      * @brief Initialize the device with the specified configuration
diff --git a/src/shared/sensors/BMP280/BMP280.cpp b/src/shared/sensors/BMP280/BMP280.cpp
index 11839de28598dcfc60d049bc5985e4cba3c8f283..35ad4ee5fddbd32667e6b361bd80f76d364094dc 100644
--- a/src/shared/sensors/BMP280/BMP280.cpp
+++ b/src/shared/sensors/BMP280/BMP280.cpp
@@ -45,8 +45,8 @@ const BMP280::BMP280Config BMP280::BMP280_CONFIG_ALL_ENABLED = {0,
 const BMP280::BMP280Config BMP280::BMP280_CONFIG_TEMP_SINGLE = {
     0, 0, FORCED_MODE, SKIPPED, OVERSAMPLING_1, 0, FILTER_OFF, STB_TIME_0_5};
 
-BMP280::BMP280(SPISlave spiSlave_, BMP280Config config_)
-    : spiSlave(spiSlave_), config(config_)
+BMP280::BMP280(SPISlave spiSlave, BMP280Config config)
+    : spiSlave(spiSlave), config(config)
 {
 }
 
diff --git a/src/shared/sensors/BMP280/BMP280.h b/src/shared/sensors/BMP280/BMP280.h
index 385a40695fc386cc647cd051431198c150f9b835..8b3263dfaa376c6108bec7bf03ef154bace53423 100644
--- a/src/shared/sensors/BMP280/BMP280.h
+++ b/src/shared/sensors/BMP280/BMP280.h
@@ -147,8 +147,8 @@ public:
                                                           ///< enabled in
                                                           ///< forced mode
 
-    explicit BMP280(SPISlave spiSlave_,
-                    BMP280Config config_ = BMP280_CONFIG_ALL_ENABLED);
+    explicit BMP280(SPISlave spiSlave,
+                    BMP280Config config = BMP280_CONFIG_ALL_ENABLED);
 
     /**
      * @brief Initialize the device with the specified configuration
diff --git a/src/shared/sensors/BMX160/BMX160.cpp b/src/shared/sensors/BMX160/BMX160.cpp
index 594c6a1854e8f2ece95d032c431f96f46a5f8083..287f9b5231dc7a08d87240f595a291b75a13089e 100644
--- a/src/shared/sensors/BMX160/BMX160.cpp
+++ b/src/shared/sensors/BMX160/BMX160.cpp
@@ -23,6 +23,7 @@
 #include "BMX160.h"
 
 #include <assert.h>
+#include <utils/Constants.h>
 
 namespace Boardcore
 {
@@ -598,25 +599,24 @@ MagnetometerData BMX160::buildMagData(BMX160Defs::MagRaw data,
 AccelerometerData BMX160::buildAccData(BMX160Defs::AccRaw data,
                                        uint64_t timestamp)
 {
-    return AccelerometerData{timestamp, data.x * accSensibility * EARTH_GRAVITY,
-                             data.y * accSensibility * EARTH_GRAVITY,
-                             data.z * accSensibility * EARTH_GRAVITY};
+    using namespace Constants;
+
+    return AccelerometerData{timestamp, data.x * accSensibility * g,
+                             data.y * accSensibility * g,
+                             data.z * accSensibility * g};
 }
 
 GyroscopeData BMX160::buildGyrData(BMX160Defs::GyrRaw data, uint64_t timestamp)
 {
+    using namespace Constants;
+
     if (config.gyroscopeUnit == BMX160Config::GyroscopeMeasureUnit::DEG)
-    {
         return GyroscopeData{timestamp, data.x * gyrSensibility,
                              data.y * gyrSensibility, data.z * gyrSensibility};
-    }
     else
-    {
-        return GyroscopeData{timestamp,
-                             data.x * gyrSensibility * DEGREES_TO_RADIANS,
-                             data.y * gyrSensibility * DEGREES_TO_RADIANS,
-                             data.z * gyrSensibility * DEGREES_TO_RADIANS};
-    }
+        return GyroscopeData{timestamp, data.x * gyrSensibility * g,
+                             data.y * gyrSensibility * g,
+                             data.z * gyrSensibility * g};
 }
 
 const char* BMX160::debugErr(SPITransaction& spi)
diff --git a/src/shared/sensors/BMX160/BMX160Data.h b/src/shared/sensors/BMX160/BMX160Data.h
index 273ed10aa2f13339362a90b95247784e091931e0..5347ad81cdd5cbbded71cbdea25b7033c350f333 100644
--- a/src/shared/sensors/BMX160/BMX160Data.h
+++ b/src/shared/sensors/BMX160/BMX160Data.h
@@ -45,13 +45,9 @@ struct BMX160Data : public AccelerometerData,
     static std::string header()
     {
         return "accelerationTimestamp,accelerationX,accelerationY,"
-               "accelerationZ,gyro_"
-               "timestamp,"
-               "angularVelocityX,"
-               "angularVelocityY,"
-               "angularVelocityZ,magneticFieldTimestamp,magneticFieldX,"
-               "magneticFieldY,"
-               "magneticFieldZ\n";
+               "accelerationZ,angularVelocityTimestamp,angularVelocityX,"
+               "angularVelocityY,angularVelocityZ,magneticFieldTimestamp,"
+               "magneticFieldX,magneticFieldY,magneticFieldZ\n";
     }
 
     void print(std::ostream& os) const
diff --git a/src/shared/sensors/BMX160/BMX160WithCorrection.h b/src/shared/sensors/BMX160/BMX160WithCorrection.h
index 627685ec1fec4a5c62dc6907cfe3a2178d40bd0f..fa139ef6bca95e2b6296a97be26185c050fedfd4 100644
--- a/src/shared/sensors/BMX160/BMX160WithCorrection.h
+++ b/src/shared/sensors/BMX160/BMX160WithCorrection.h
@@ -24,6 +24,7 @@
 
 #include <diagnostic/PrintLogger.h>
 #include <sensors/BMX160/BMX160.h>
+#include <sensors/calibration/AxisOrientation.h>
 #include <sensors/calibration/BiasCalibration.h>
 #include <sensors/calibration/SixParameterCalibration.h>
 
diff --git a/src/shared/sensors/HX711/HX711.cpp b/src/shared/sensors/HX711/HX711.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..8e3fa92694daa960e71db8a1463147e11bdd3e9d
--- /dev/null
+++ b/src/shared/sensors/HX711/HX711.cpp
@@ -0,0 +1,96 @@
+/* Copyright (c) 2022 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 "HX711.h"
+
+#include <drivers/timer/TimestampTimer.h>
+#include <interfaces-impl/gpio_impl.h>
+#include <miosix.h>
+
+namespace Boardcore
+{
+
+HX711::HX711(SPIBusInterface &bus, miosix::GpioPin sckPin, SPIBusConfig config,
+             unsigned char sckAlternateFunction)
+    : bus(bus), sckPin(sckPin), config(config),
+      sckAlternateFunction(sckAlternateFunction)
+{
+}
+
+SPIBusConfig HX711::getDefaultSPIConfig()
+{
+    SPIBusConfig spiConfig{};
+    spiConfig.clockDivider = SPI::ClockDivider::DIV_128;
+    spiConfig.mode         = SPI::Mode::MODE_1;
+    return spiConfig;
+}
+
+bool HX711::init() { return true; }
+
+bool HX711::selfTest() { return true; }
+
+HX711Data HX711::sampleImpl()
+{
+    int32_t sample;
+
+    bus.configure(config);
+    sample = bus.read16() << 8;
+    sample |= bus.read();
+    sckPin.mode(miosix::Mode::OUTPUT);
+    sckPin.high();
+    miosix::delayUs(1);
+    sckPin.low();
+    sckPin.mode(miosix::Mode::ALTERNATE);
+    sckPin.alternateFunction(sckAlternateFunction);
+
+    if (sample & static_cast<int32_t>(0x800000))
+        sample |= static_cast<uint32_t>(0xFF) << 24;
+
+    if (sample == static_cast<int32_t>(0xFFFFFFFF))
+        return lastSample;
+
+    return {TimestampTimer::getInstance().getTimestamp(),
+            static_cast<float>(sample - offset) * scale};
+}
+
+void HX711::computeScale(float value, float sample)
+{
+    // Convert the sample in raw measurement with current scale factor
+    sample = sample / scale;
+
+    // Update the scale such that the sample corresponds to the given value
+    this->scale = value / sample;
+}
+
+void HX711::computeScale(float value) { computeScale(value, lastSample.load); }
+
+void HX711::setScale(float scale) { this->scale = scale; }
+
+float HX711::getScale() { return scale; }
+
+void HX711::setOffset(float offset) { this->offset = offset / scale; }
+
+void HX711::updateOffset(float offset) { this->offset += offset / scale; }
+
+float HX711::getOffset() { return offset; }
+
+}  // namespace Boardcore
diff --git a/src/shared/sensors/HX711/HX711.h b/src/shared/sensors/HX711/HX711.h
new file mode 100644
index 0000000000000000000000000000000000000000..4674ea16e84c255470bb80ec54aebb997d624665
--- /dev/null
+++ b/src/shared/sensors/HX711/HX711.h
@@ -0,0 +1,127 @@
+/* Copyright (c) 2022 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
+
+#include <drivers/spi/SPIDriver.h>
+#include <sensors/Sensor.h>
+
+#include "HX711Data.h"
+
+namespace Boardcore
+{
+
+/**
+ * @brief Load cell transducer.
+ *
+ * HX711 is a precision 24-bit analog-to-digital converter (ADC) designed for
+ * weigh scales and industrial control applications to interface directly with a
+ * bridge sensor.
+ *
+ * The input multiplexer selects either Channel A or B differential input to the
+ * low-noise programmable gain amplifier (PGA). Channel A can be programmed with
+ * a gain of 128 or 64, corresponding to a full-scale differential input voltage
+ * of ±20mV or ±40mV respectively, when a 5V supply is connected to AVDD analog
+ * power supply pin. Channel B has a fixed gain of 32. On-chip power supply
+ * regulator eliminates the need for an external supply regulator to provide
+ * analog power for the ADC and the sensor. Clock input is flexible. It can be
+ * from an external clock source, a crystal, or the on-chip oscillator that does
+ * not require any external component. On-chip power-on-reset circuitry
+ * simplifies digital interface initialization. There is no programming needed
+ * for the internal registers. All controls to the HX711 are through the pins.
+ *
+ * Reference: https://github.com/bogde/HX711
+ */
+class HX711 : public Sensor<HX711Data>
+{
+public:
+    HX711(SPIBusInterface& bus, miosix::GpioPin sckPin,
+          SPIBusConfig config                = getDefaultSPIConfig(),
+          unsigned char sckAlternateFunction = 5);
+
+    static SPIBusConfig getDefaultSPIConfig();
+
+    bool init() override;
+
+    bool selfTest() override;
+
+    /**
+     * @brief Calculates the scale value such that the load cell's output
+     * matches the given value.
+     *
+     * The value is used to compute the scale coefficient in this way:
+     * scale = value / (sample - offset)
+     *
+     * @param value Value that the load cell should read now.
+     * @param sample Sensor sample used to compute the scale.
+     */
+    void computeScale(float value, float sample);
+
+    /**
+     * @brief Same as computeScale but uses the last sample.
+     */
+    void computeScale(float value);
+
+    /**
+     * @brief Simply changes the scale.
+     *
+     * @param scale New scale value.
+     */
+    void setScale(float scale);
+
+    /**
+     * @brief Returns the current scale.
+     */
+    float getScale();
+
+    /**
+     * @brief Sets the offset to the given value.
+     *
+     * @param offset Offset that will be removed from the measurement.
+     */
+    void setOffset(float offset);
+
+    /**
+     * @brief Updates the offset by adding it to the current offset.
+     *
+     * @param offset Offset that will be removed from the measurement.
+     */
+    void updateOffset(float offset);
+
+    /**
+     * @brief Return the current offset.
+     */
+    float getOffset();
+
+private:
+    HX711Data sampleImpl() override;
+
+    SPIBusInterface& bus;
+    miosix::GpioPin sckPin;
+    const SPIBusConfig config;
+    unsigned char sckAlternateFunction;
+
+    float scale    = 1;
+    int32_t offset = 0;
+};
+
+}  // namespace Boardcore
diff --git a/src/shared/utils/Unused.h b/src/shared/sensors/HX711/HX711Data.h
similarity index 70%
rename from src/shared/utils/Unused.h
rename to src/shared/sensors/HX711/HX711Data.h
index 5540e82257824b05063fe131935d5e3e355fe759..49e25db1942c56331a90e9bbe6cd29f21741607d 100644
--- a/src/shared/utils/Unused.h
+++ b/src/shared/sensors/HX711/HX711Data.h
@@ -1,5 +1,6 @@
+
 /* Copyright (c) 2019 Skyward Experimental Rocketry
- * Author: Alvise de' Faveri Tron
+ * Author: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -22,4 +23,26 @@
 
 #pragma once
 
-#define UNUSED(x) (void)(x)
+#include <sensors/SensorData.h>
+
+namespace Boardcore
+{
+
+struct HX711Data : public LoadCellData
+{
+    HX711Data() : LoadCellData{0, 0} {}
+
+    HX711Data(uint64_t loadTimestamp, float load)
+        : LoadCellData{loadTimestamp, load}
+    {
+    }
+
+    static std::string header() { return "loadTimestamp,load\n"; }
+
+    void print(std::ostream& os) const
+    {
+        os << loadTimestamp << "," << load << "\n";
+    }
+};
+
+}  // namespace Boardcore
diff --git a/src/shared/sensors/L3GD20/L3GD20.h b/src/shared/sensors/L3GD20/L3GD20.h
index bdd7c78fda3d8d82fffaddd95d0f3a915613be65..802b0ed2fefad0331afcd5da5598178984e4eadc 100644
--- a/src/shared/sensors/L3GD20/L3GD20.h
+++ b/src/shared/sensors/L3GD20/L3GD20.h
@@ -25,10 +25,10 @@
 
 #include <drivers/spi/SPIDriver.h>
 #include <drivers/timer/TimestampTimer.h>
-#include <math/Vec3.h>
 #include <miosix.h>
 #include <sensors/Sensor.h>
 
+#include <Eigen/Core>
 #include <array>
 
 #include "L3GD20Data.h"
@@ -220,9 +220,8 @@ public:
             int16_t y = buf[2] | buf[3] << 8;
             int16_t z = buf[4] | buf[5] << 8;
 
-            Vec3 rads   = toRadiansPerSecond(x, y, z);
-            lastFifo[0] = {lastSampleTimestamp, rads.getX(), rads.getY(),
-                           rads.getZ()};
+            Eigen::Vector3f rads = toRadiansPerSecond(x, y, z);
+            lastFifo[0] = {lastSampleTimestamp, rads(0), rads(1), rads(2)};
         }
 
         else  // FIFO is enabled
@@ -243,7 +242,7 @@ public:
             {
                 bool rmv;
                 // Check for duplicates: there seems to be a bug where the
-                // sensor occasionaly shifts out the same sample two times in a
+                // sensor occasionally shifts out the same sample two times in a
                 // row: discard one
                 if (i < fifoLevel - 1)
                 {
@@ -276,7 +275,7 @@ public:
                 // Ignoring possible duplicates, the timestamp of the ith sample
                 // in the fifo is:
                 // ts(i) = ts(fifoWatermark) + (i - fifoWatermark)*dt;
-                Vec3 rads =
+                Eigen::Vector3f rads =
                     toRadiansPerSecond(buf[i * 6] | buf[i * 6 + 1] << 8,
                                        buf[i * 6 + 2] | buf[i * 6 + 3] << 8,
                                        buf[i * 6 + 4] | buf[i * 6 + 5] << 8);
@@ -284,7 +283,7 @@ public:
                 lastFifo[i - duplicates] = L3GD20Data{
                     lastInterruptTimestamp +
                         ((int)i - (int)fifoWatermark - (int)duplicates) * dt,
-                    rads.getX(), rads.getY(), rads.getZ()};
+                    rads(0), rads(1), rads(2)};
             }
 
             lastFifoLevel = fifoLevel - duplicates;
@@ -294,9 +293,10 @@ public:
     }
 
 private:
-    Vec3 toRadiansPerSecond(int16_t x, int16_t y, int16_t z)
+    Eigen::Vector3f toRadiansPerSecond(int16_t x, int16_t y, int16_t z)
     {
-        return Vec3(x * sensitivity, y * sensitivity, z * sensitivity);
+        return Eigen::Vector3f(x * sensitivity, y * sensitivity,
+                               z * sensitivity);
     }
 
     static constexpr float SENSITIVITY_250  = 0.00875f;
diff --git a/src/shared/sensors/MAX31855/MAX31855.cpp b/src/shared/sensors/MAX31855/MAX31855.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3a723a6d8a7f46878fafd41162925a734fccf6ab
--- /dev/null
+++ b/src/shared/sensors/MAX31855/MAX31855.cpp
@@ -0,0 +1,113 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Angelo Zangari
+ *
+ * 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 "MAX31855.h"
+
+#include <drivers/timer/TimestampTimer.h>
+
+namespace Boardcore
+{
+
+MAX31855::MAX31855(SPIBusInterface& bus, miosix::GpioPin cs,
+                   SPIBusConfig config)
+    : slave(bus, cs, config)
+{
+}
+
+SPIBusConfig MAX31855::getDefaultSPIConfig()
+{
+    SPIBusConfig spiConfig{};
+    spiConfig.clockDivider = SPI::ClockDivider::DIV_32;
+    spiConfig.mode         = SPI::Mode::MODE_1;
+    return spiConfig;
+}
+
+bool MAX31855::init() { return true; }
+
+bool MAX31855::selfTest() { return true; }
+
+bool MAX31855::checkConnection()
+{
+    uint16_t sample[2];
+
+    {
+        SPITransaction spi{slave};
+        spi.read(sample, sizeof(sample));
+    }
+
+    // Bits D0, D1 and D2 go high if thermocouple is open or shorted either to
+    // gnd or Vcc
+    if ((sample[1] & 0x7) != 0)
+    {
+        lastError = SensorErrors::SELF_TEST_FAIL;
+        LOG_ERR(logger, "Self test failed, the thermocouple is not connected");
+        return false;
+    }
+
+    return true;
+}
+
+TemperatureData MAX31855::sampleImpl()
+{
+    uint16_t sample;
+
+    {
+        SPITransaction spi{slave};
+        sample = spi.read16();
+    }
+
+    TemperatureData result{};
+    result.temperatureTimestamp = TimestampTimer::getInstance().getTimestamp();
+
+    // Extract data bits
+    sample = sample >> 2;
+
+    // Convert the integer and decimal part separetly
+    result.temperature = static_cast<float>(sample >> 2);
+    result.temperature += static_cast<float>(sample & 0x3) * 0.25;
+
+    return result;
+}
+
+TemperatureData MAX31855::readInternalTemperature()
+{
+    uint16_t sample[2];
+
+    {
+        SPITransaction spi{slave};
+        spi.read(sample, sizeof(sample));
+    }
+
+    TemperatureData result{};
+    result.temperatureTimestamp = TimestampTimer::getInstance().getTimestamp();
+
+    // Extract data bits
+    sample[1] = sample[1] >> 4;
+
+    // Convert the integer and decimal part separetly
+    result.temperature = static_cast<float>(sample[1] >> 4);
+    result.temperature += static_cast<float>(sample[1] & 0xF) * 0.0625;
+
+    return result;
+}
+
+}  // namespace Boardcore
diff --git a/src/shared/sensors/MAX31855/MAX31855.h b/src/shared/sensors/MAX31855/MAX31855.h
new file mode 100644
index 0000000000000000000000000000000000000000..89aa3484f0dc2e589c35f96329bfac0536d22896
--- /dev/null
+++ b/src/shared/sensors/MAX31855/MAX31855.h
@@ -0,0 +1,81 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Angelo Zangari
+ *
+ * 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
+
+#include <diagnostic/PrintLogger.h>
+#include <drivers/spi/SPIDriver.h>
+#include <sensors/Sensor.h>
+
+namespace Boardcore
+{
+/**
+ * @brief MAX31855 termocouple sensor driver.
+ */
+class MAX31855 : public Sensor<TemperatureData>
+{
+public:
+    /**
+     * @brief Constructor.
+     *
+     * @param bus The Spi bus.
+     * @param cs The CS pin to lower when we need to sample.
+     * @param config The SPI configuration.
+     */
+    MAX31855(SPIBusInterface &bus, miosix::GpioPin cs,
+             SPIBusConfig config = getDefaultSPIConfig());
+
+    /**
+     * Constructs the default config for SPI Bus.
+     *
+     * @returns The default SPIBusConfig.
+     */
+    static SPIBusConfig getDefaultSPIConfig();
+
+    bool init();
+
+    /**
+     * @brief Checks wheter the termocouple is open.
+     *
+     * @return True if the termocouple is connected.
+     */
+    bool selfTest();
+
+    /**
+     * @brief Checks whether the thermocouple is connected or not.
+     */
+    bool checkConnection();
+
+    /**
+     * @brief Read the device internal temperature (cold junction).
+     */
+    TemperatureData readInternalTemperature();
+
+private:
+    TemperatureData sampleImpl() override;
+
+    SPISlave slave;
+
+    PrintLogger logger = Logging::getLogger("max31855");
+};
+
+}  // namespace Boardcore
diff --git a/src/shared/sensors/MAX6675/MAX6675.cpp b/src/shared/sensors/MAX6675/MAX6675.cpp
index 08ca8e3016c3b32b8f33f7a57938419db8c90141..b7e5e5b67869773ca5b9f65a381f472af36146d8 100644
--- a/src/shared/sensors/MAX6675/MAX6675.cpp
+++ b/src/shared/sensors/MAX6675/MAX6675.cpp
@@ -55,7 +55,7 @@ bool MAX6675::checkConnection()
 
     // The third bit (D2) indicates wheter the termocouple is connected or not
     // It is high if open
-    if ((sample % 0x2) != 0)
+    if ((sample & 0x4) != 0)
     {
         lastError = SensorErrors::SELF_TEST_FAIL;
         LOG_ERR(logger, "Self test failed, the termocouple is not connected");
@@ -74,19 +74,16 @@ TemperatureData MAX6675::sampleImpl()
         sample = spi.read16();
     }
 
+    TemperatureData result{};
+    result.temperatureTimestamp = TimestampTimer::getInstance().getTimestamp();
+
     // Extract bits 14-3
     sample &= 0x7FF8;
     sample >>= 3;
 
-    TemperatureData result{};
-    result.temperatureTimestamp = TimestampTimer::getInstance().getTimestamp();
-
-    // Convert the sample value
-    result.temperature =
-        static_cast<unsigned int>(sample >> 2);  // Integer part
-    sample &= 0x0003;                            // Take the floating point part
-    result.temperature +=
-        static_cast<float>(sample * 0.25);  // Floating point part
+    // Convert the integer and decimal part separetly
+    result.temperature = static_cast<float>(sample >> 2);
+    result.temperature += static_cast<float>(sample & 0x3) * 0.25;
 
     return result;
 }
diff --git a/src/shared/sensors/MAX6675/MAX6675.h b/src/shared/sensors/MAX6675/MAX6675.h
index 1276549318e5caa0a479faddf02b46b2b3661d45..0ee3e01bdf07bf807ee76a6287c80d7cb6d53733 100644
--- a/src/shared/sensors/MAX6675/MAX6675.h
+++ b/src/shared/sensors/MAX6675/MAX6675.h
@@ -31,34 +31,22 @@ namespace Boardcore
 {
 
 /**
- * @brief MAX6675 termocouple sensor driver.
+ * @brief MAX6675 thermocouple sensor driver.
  */
 class MAX6675 : public Sensor<TemperatureData>
 {
 public:
-    /**
-     * @brief Constructor.
-     *
-     * @param bus The Spi bus.
-     * @param cs The CS pin to lower when we need to sample.
-     * @param config The SPI configuration.
-     */
     MAX6675(SPIBusInterface &bus, miosix::GpioPin cs,
             SPIBusConfig config = getDefaultSPIConfig());
 
-    /**
-     * Constructs the default config for SPI Bus.
-     *
-     * @returns The default SPIBusConfig.
-     */
     static SPIBusConfig getDefaultSPIConfig();
 
     bool init();
 
     /**
-     * @brief Checks wheter the termocouple is open.
+     * @brief Checks whether the thermocouple is connected or not.
      *
-     * @return True if the termocouple is connected.
+     * @return True if the thermocouple is connected.
      */
     bool selfTest();
 
@@ -70,7 +58,7 @@ public:
 private:
     TemperatureData sampleImpl() override;
 
-    SPISlave slave;
+    const SPISlave slave;
 
     PrintLogger logger = Logging::getLogger("max6675");
 };
diff --git a/src/shared/sensors/MBLoadCell/MBLoadCell.cpp b/src/shared/sensors/MBLoadCell/MBLoadCell.cpp
index 1874fff78cb19a8b22ff69cefb07e4c1462c9bb2..7f943cd5c2078d7d26c0d0b4ecf2db0433c67416 100644
--- a/src/shared/sensors/MBLoadCell/MBLoadCell.cpp
+++ b/src/shared/sensors/MBLoadCell/MBLoadCell.cpp
@@ -152,28 +152,28 @@ void MBLoadCell::printData()
 
     if (maxPrint)
     {
-        TRACE("NEW MAX %.2f (ts: %.3f [s])\n", maxWeight.weight,
-              maxWeight.weightTimestamp / 1000000.0);
+        TRACE("NEW MAX %.2f (ts: %.3f [s])\n", maxWeight.load,
+              maxWeight.loadTimestamp / 1000000.0);
         maxPrint = false;
     }
 
     if (minPrint)
     {
-        TRACE("NEW MIN %.2f (ts: %.3f [s])\n", minWeight.weight,
-              minWeight.weightTimestamp / 1000000.0);
+        TRACE("NEW MIN %.2f (ts: %.3f [s])\n", minWeight.load,
+              minWeight.loadTimestamp / 1000000.0);
         minPrint = false;
     }
 }
 
-Data MBLoadCell::getMaxWeight() { return maxWeight; }
+MBLoadCellData MBLoadCell::getMaxWeight() { return maxWeight; }
 
-Data MBLoadCell::getMinWeight() { return minWeight; }
+MBLoadCellData MBLoadCell::getMinWeight() { return minWeight; }
 
 bool MBLoadCell::selfTest() { return true; }
 
-Data MBLoadCell::sampleImpl()
+MBLoadCellData MBLoadCell::sampleImpl()
 {
-    Data value;
+    MBLoadCellData value;
     switch (settings.mode)
     {
         case LoadCellModes::CONT_MOD_T:
@@ -191,7 +191,7 @@ Data MBLoadCell::sampleImpl()
     }
 
     // Memorizing also the maximum gross weight registered
-    if (!maxWeight.valid || maxWeight.weight < value.weight)
+    if (!maxWeight.valid || maxWeight.load < value.load)
     {
         maxWeight = value;
         maxSetted = true;
@@ -203,7 +203,7 @@ Data MBLoadCell::sampleImpl()
     }
 
     // Memorizing also the minimum gross weight registered
-    if (!minWeight.valid || minWeight.weight > value.weight)
+    if (!minWeight.valid || minWeight.load > value.load)
     {
         minWeight = value;
         minSetted = true;
@@ -217,23 +217,23 @@ Data MBLoadCell::sampleImpl()
     return value;
 }
 
-Data MBLoadCell::sampleContModT()
+MBLoadCellData MBLoadCell::sampleContModT()
 {
     DataModT data;
     receive(&data);
 
-    return Data(atof(data.weight) / 10.0);
+    return MBLoadCellData(atof(data.weight) / 10.0);
 }
 
-Data MBLoadCell::sampleContModTd()
+MBLoadCellData MBLoadCell::sampleContModTd()
 {
     DataModTd data;
     receive(&data);
 
-    return Data(atof(data.weightT) / 10.0);
+    return MBLoadCellData(atof(data.weightT) / 10.0);
 }
 
-Data MBLoadCell::sampleAsciiModTd()
+MBLoadCellData MBLoadCell::sampleAsciiModTd()
 {
     DataAsciiRequest request;
 
@@ -262,7 +262,7 @@ Data MBLoadCell::sampleAsciiModTd()
     else
     {
         // Taking the value returned
-        return Data(stof(response.substr(3, 6)) / 10.0);
+        return MBLoadCellData(stof(response.substr(3, 6)) / 10.0);
     }
 }
 
diff --git a/src/shared/sensors/MBLoadCell/MBLoadCell.h b/src/shared/sensors/MBLoadCell/MBLoadCell.h
index aefe3c6808653ca2ca93abd0742feae5d8f23273..d8e57f5f65bdded510a9ad6830bed13d1c053106 100644
--- a/src/shared/sensors/MBLoadCell/MBLoadCell.h
+++ b/src/shared/sensors/MBLoadCell/MBLoadCell.h
@@ -47,7 +47,7 @@ namespace Boardcore
  * - ASCII-modTd: bidirectional mode that consists in sending a request and
  * receiving a response with the data requested or an error message
  */
-class MBLoadCell : public Sensor<Data>
+class MBLoadCell : public Sensor<MBLoadCellData>
 {
 public:
     /**
@@ -86,12 +86,12 @@ public:
     /**
      * @brief Returns a copy of the max weight detected.
      */
-    Data getMaxWeight();
+    MBLoadCellData getMaxWeight();
 
     /**
      * @brief Returns a copy of the min weight detected.
      */
-    Data getMinWeight();
+    MBLoadCellData getMinWeight();
 
     bool selfTest() override;
 
@@ -102,22 +102,22 @@ protected:
      *
      * @return The weight measured from the load cell.
      */
-    Data sampleImpl() override;
+    MBLoadCellData sampleImpl() override;
 
     /**
      * @brief Sampling in the "continuous Mod T" mode.
      */
-    Data sampleContModT(void);
+    MBLoadCellData sampleContModT(void);
 
     /**
      * @brief Sampling in the "continuous Mod Td" mode.
      */
-    Data sampleContModTd(void);
+    MBLoadCellData sampleContModTd(void);
 
     /**
      * @brief Sampling in the "ASCII Mod Td" mode.
      */
-    Data sampleAsciiModTd(void);
+    MBLoadCellData sampleAsciiModTd(void);
 
     /**
      * @brief Forges a request for the ascii mode.
@@ -158,8 +158,8 @@ protected:
 
 private:
     MBLoadCellSettings settings;  ///< Contains all the configuration
-    Data maxWeight;               ///< Maximum weight detected by the load cell
-    Data minWeight;               ///< Minimum weight detected by the load cell
+    MBLoadCellData maxWeight;     ///< Maximum weight detected by the load cell
+    MBLoadCellData minWeight;     ///< Minimum weight detected by the load cell
     bool maxSetted;
     bool maxPrint;
     bool minSetted;
diff --git a/src/shared/sensors/MBLoadCell/MBLoadCellData.h b/src/shared/sensors/MBLoadCell/MBLoadCellData.h
index 978c6772ef16f11632482f686f45c1467d596202..3c9c12495040743ac5dc2bfd0a7647f62115ee94 100644
--- a/src/shared/sensors/MBLoadCell/MBLoadCellData.h
+++ b/src/shared/sensors/MBLoadCell/MBLoadCellData.h
@@ -22,9 +22,9 @@
 
 #pragma once
 
-#include <drivers/timer/TimestampTimer.h>
 #include <utils/Debug.h>
 
+#include <cstdio>
 #include <map>
 
 #include "sensors/SensorData.h"
@@ -87,25 +87,25 @@ enum ReturnsStates
  * @brief Structure that stores a data value, with his timestamp and his
  * validity.
  */
-struct Data : public DigitalLoadCellData
+struct MBLoadCellData : public LoadCellData
 {
     bool valid = false;
 
-    Data() : DigitalLoadCellData{0, 0.0}, valid(false) {}
+    MBLoadCellData() : LoadCellData{0, 0.0}, valid(false) {}
 
-    explicit Data(float data)
-        : DigitalLoadCellData{TimestampTimer::getInstance().getTimestamp(),
-                              data},
-          valid(true)
+    explicit MBLoadCellData(float data) : MBLoadCellData{0, data} {}
+
+    explicit MBLoadCellData(uint64_t loadTimestamp, float data)
+        : LoadCellData{loadTimestamp, data}, valid(true)
     {
     }
 
-    static std::string header() { return "weightTimestamp,weight\n"; }
+    static std::string header() { return "loadTimestamp,weight\n"; }
 
     void print(std::ostream& os) const
     {
         if (valid)
-            os << weightTimestamp / 1000000.0 << "," << weight << "\n";
+            os << loadTimestamp / 1000000.0 << "," << load << "\n";
     }
 };
 
@@ -116,10 +116,10 @@ struct MBLoadCellSettings
 {
     LoadCellModes mode;
     bool grossMode;
-    Data peakWeight;
-    Data setpoint1;
-    Data setpoint2;
-    Data setpoint3;
+    MBLoadCellData peakWeight;
+    MBLoadCellData setpoint1;
+    MBLoadCellData setpoint2;
+    MBLoadCellData setpoint3;
 
     /**
      * @brief Updates the correct value with the data passed. Also, memorizes
@@ -130,16 +130,16 @@ struct MBLoadCellSettings
         switch (val)
         {
             case PEAK_WEIGHT:
-                peakWeight = Data(data);
+                peakWeight = MBLoadCellData(data);
                 break;
             case GET_SETPOINT_1:
-                setpoint1 = Data(data);
+                setpoint1 = MBLoadCellData(data);
                 break;
             case GET_SETPOINT_2:
-                setpoint2 = Data(data);
+                setpoint2 = MBLoadCellData(data);
                 break;
             case GET_SETPOINT_3:
-                setpoint3 = Data(data);
+                setpoint3 = MBLoadCellData(data);
                 break;
             default:
                 break;
@@ -152,22 +152,22 @@ struct MBLoadCellSettings
     void print() const
     {
         /*if (netWeight.valid)
-            TRACE("Net Weight     : %f [Kg]\n", netWeight.data);
+            TRACE("Net Weight     : %f [Kg]\n", netWeight.load);
 
         if (grossWeight.valid)
-            TRACE("Gross Weight   : %f [Kg]\n", grossWeight.data);
+            TRACE("Gross Weight   : %f [Kg]\n", grossWeight.load);
         */
         if (peakWeight.valid)
-            TRACE("Peak Weight    : %f [Kg]\n", peakWeight.weight);
+            TRACE("Peak Weight    : %f [Kg]\n", peakWeight.load);
 
         if (setpoint1.valid)
-            TRACE("Setpoint 1     : %f [Kg]\n", setpoint1.weight);
+            TRACE("Setpoint 1     : %f [Kg]\n", setpoint1.load);
 
         if (setpoint2.valid)
-            TRACE("Setpoint 2     : %f [Kg]\n", setpoint2.weight);
+            TRACE("Setpoint 2     : %f [Kg]\n", setpoint2.load);
 
         if (setpoint3.valid)
-            TRACE("Setpoint 3     : %f [Kg]\n", setpoint3.weight);
+            TRACE("Setpoint 3     : %f [Kg]\n", setpoint3.load);
     }
 };
 
@@ -224,7 +224,7 @@ struct DataAsciiRequest
             checksum ^= str[i];
         }
 
-        itoa(checksum, ck, 16);
+        sprintf(ck, "%x", checksum);
     }
 
     /**
diff --git a/src/shared/sensors/MPU9250/MPU9250.cpp b/src/shared/sensors/MPU9250/MPU9250.cpp
index 4f0d1eaa1ed0e0849ef1fbd41080a0d32a12e5dd..0348e7afe5ae76c272fc2cbea109439f795dbcea 100644
--- a/src/shared/sensors/MPU9250/MPU9250.cpp
+++ b/src/shared/sensors/MPU9250/MPU9250.cpp
@@ -29,11 +29,12 @@
 namespace Boardcore
 {
 
-MPU9250::MPU9250(SPISlave spiSlave_, unsigned short samplingRate_,
-                 MPU9250GyroFSR gyroFsr_, MPU9250AccelFSR accelFsr_,
-                 SPI::ClockDivider highSpeedSpiClockDivider_)
-    : spiSlave(spiSlave_), samplingRate(samplingRate_), gyroFsr(gyroFsr_),
-      accelFsr(accelFsr_), highSpeedSpiClockDivider(highSpeedSpiClockDivider_)
+MPU9250::MPU9250(SPIBusInterface& bus, miosix::GpioPin cs, SPIBusConfig config,
+                 unsigned short samplingRate, MPU9250GyroFSR gyroFsr,
+                 MPU9250AccelFSR accelFsr,
+                 SPI::ClockDivider highSpeedSpiClockDivider)
+    : spiSlave(bus, cs, config), samplingRate(samplingRate), gyroFsr(gyroFsr),
+      accelFsr(accelFsr), highSpeedSpiClockDivider(highSpeedSpiClockDivider)
 {
 }
 
@@ -434,7 +435,7 @@ void MPU9250::writeSPIWithDelay(SPITransaction& transaction, uint8_t reg,
 float MPU9250::normalizeAcceleration(int16_t rawValue)
 {
     return static_cast<float>(rawValue) / 32768.0f *
-           ACCELERATION_FS_MAP[accelFsr >> 3] * EARTH_GRAVITY;
+           ACCELERATION_FS_MAP[accelFsr >> 3] * Constants::g;
 }
 
 // Page 33 of register map document
@@ -446,7 +447,7 @@ float MPU9250::normalizeTemperature(int16_t rawValue)
 float MPU9250::normalizeGyroscope(int16_t rawValue)
 {
     return static_cast<float>(rawValue) / 32768.0f *
-           GYROSCOPE_FS_MAP[gyroFsr >> 3] * DEGREES_TO_RADIANS;
+           GYROSCOPE_FS_MAP[gyroFsr >> 3] * Constants::DEGREES_TO_RADIANS;
 }
 
 float MPU9250::normalizeMagnetometer(int16_t rawValue, float adjustmentCoeff)
diff --git a/src/shared/sensors/MPU9250/MPU9250.h b/src/shared/sensors/MPU9250/MPU9250.h
index e6cf27e7f66ab86346e95c59c6605f841098270c..039eda52f5177318bdea77a24679c5e31079e1cb 100644
--- a/src/shared/sensors/MPU9250/MPU9250.h
+++ b/src/shared/sensors/MPU9250/MPU9250.h
@@ -189,14 +189,16 @@ public:
     /**
      * @brief Instantiates the driver
      *
-     * @param highSpeedSpiClockDivider_ Clocl diver for 20MHz SPI communication
+     * @param highSpeedSpiClockDivider Clocl diver for 20MHz SPI communication
      * with the device
      */
     explicit MPU9250(
-        SPISlave spiSlave_, unsigned short samplingRate_ = 100,
-        MPU9250GyroFSR gyroFsr_                     = GYRO_FSR_250DPS,
-        MPU9250AccelFSR accelFsr_                   = ACCEL_FSR_2G,
-        SPI::ClockDivider highSpeedSpiClockDivider_ = SPI::ClockDivider::DIV_4);
+        SPIBusInterface& bus, miosix::GpioPin cs,
+        SPIBusConfig config                        = getDefaultSPIConfig(),
+        unsigned short samplingRate                = 100,
+        MPU9250GyroFSR gyroFsr                     = GYRO_FSR_250DPS,
+        MPU9250AccelFSR accelFsr                   = ACCEL_FSR_2G,
+        SPI::ClockDivider highSpeedSpiClockDivider = SPI::ClockDivider::DIV_4);
 
     /**
      * @brief Constructs the default config for SPI Bus.
diff --git a/src/shared/sensors/MS5803/MS5803.cpp b/src/shared/sensors/MS5803/MS5803.cpp
index 1a2735f642e15deb572aa2e0c8bcd7be29d67636..52eec1bb6c3f0c15e833b75eaea66205c26b56e2 100644
--- a/src/shared/sensors/MS5803/MS5803.cpp
+++ b/src/shared/sensors/MS5803/MS5803.cpp
@@ -28,15 +28,9 @@
 namespace Boardcore
 {
 
-MS5803::MS5803(SPISlave spiSlave_, uint16_t temperatureDivider_)
-    : spiSlave(spiSlave_), temperatureDivider(temperatureDivider_)
-{
-}
-
-MS5803::MS5803(SPIBusInterface& spiBus_, miosix::GpioPin cs_,
-               SPIBusConfig spiConfig_, uint16_t temperatureDivider_)
-    : spiSlave(spiBus_, cs_, spiConfig_),
-      temperatureDivider(temperatureDivider_)
+MS5803::MS5803(SPIBusInterface& spiBus, miosix::GpioPin cs,
+               SPIBusConfig spiConfig, uint16_t temperatureDivider)
+    : spiSlave(spiBus, cs, spiConfig), temperatureDivider(temperatureDivider)
 {
 }
 
diff --git a/src/shared/sensors/MS5803/MS5803.h b/src/shared/sensors/MS5803/MS5803.h
index 427cb78ca57671c6afda72e3c4e4c0941f106132..6065ebcbab862231cf4fcd37e12d857cb4119198 100644
--- a/src/shared/sensors/MS5803/MS5803.h
+++ b/src/shared/sensors/MS5803/MS5803.h
@@ -73,9 +73,8 @@ public:
 
     static constexpr uint8_t TIMEOUT = 5;
 
-    explicit MS5803(SPISlave spiSlave_, uint16_t temperatureDivider_ = 1);
-    MS5803(SPIBusInterface& spiBus_, miosix::GpioPin cs_,
-           SPIBusConfig spiConfig_, uint16_t temperatureDivider_ = 1);
+    MS5803(SPIBusInterface& spiBus, miosix::GpioPin cs,
+           SPIBusConfig spiConfig = {}, uint16_t temperatureDivider = 1);
 
     bool init() override;
 
diff --git a/src/shared/sensors/MS5803/MS5803Data.h b/src/shared/sensors/MS5803/MS5803Data.h
index 596dcec13c84878696b79119a5be30b1be0f5a3a..10eb959bd39c60b15e4c1abac74986f517d7ce3d 100644
--- a/src/shared/sensors/MS5803/MS5803Data.h
+++ b/src/shared/sensors/MS5803/MS5803Data.h
@@ -55,7 +55,7 @@ struct MS5803Data : public PressureData, TemperatureData
 
     static std::string header()
     {
-        return "pressureTimestamp,press,temperatureTimestamp,temp\n";
+        return "pressureTimestamp,pressure,temperatureTimestamp,temperature\n";
     }
 
     void print(std::ostream& os) const
diff --git a/src/shared/sensors/SensorData.h b/src/shared/sensors/SensorData.h
index a0e76f7aa0a207fa38c96edf5aee385280a1b9cf..d40024e73622d384aacdd9bddf89b3a76307e217 100644
--- a/src/shared/sensors/SensorData.h
+++ b/src/shared/sensors/SensorData.h
@@ -1,5 +1,5 @@
-/* Copyright (c) 2020 Skyward Experimental Rocketry
- * Author: Luca Conterio
+/* Copyright (c) 2020-2022 Skyward Experimental Rocketry
+ * Authors: Luca Conterio, 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
@@ -54,10 +54,17 @@ struct TimestampData
     uint64_t timestamp;
 };
 
-struct DigitalLoadCellData
+struct LoadCellData
 {
-    uint64_t weightTimestamp;
-    float weight;
+    uint64_t loadTimestamp;
+    float load;
+
+    static std::string header() { return "loadTimestamp,load\n"; }
+
+    void print(std::ostream& os) const
+    {
+        os << loadTimestamp << "," << load << "\n";
+    }
 };
 
 struct TemperatureData
diff --git a/src/shared/sensors/SensorManager.cpp b/src/shared/sensors/SensorManager.cpp
index eb15921551a1744e778d8b93c51575dac5413682..53ac4adc512f65aea68ca0e313ee6b51b3984870 100644
--- a/src/shared/sensors/SensorManager.cpp
+++ b/src/shared/sensors/SensorManager.cpp
@@ -50,7 +50,12 @@ SensorManager::~SensorManager()
         delete scheduler;
 }
 
-bool SensorManager::start() { return initResult; }
+bool SensorManager::start()
+{
+    if (customScheduler)
+        scheduler->start();
+    return initResult;
+}
 
 void SensorManager::stop() { scheduler->stop(); }
 
@@ -173,11 +178,9 @@ bool SensorManager::init(const SensorMap_t& sensorsMap)
             samplersMap[sensor] = newSampler;
 
             if (currentSamplerId == MAX_TASK_ID)
-            {
                 LOG_WARN(logger,
                          "Max task ID (255) reached in task scheduler, IDs "
                          "will start again from 0");
-            }
 
             currentSamplerId++;
         }
diff --git a/src/shared/sensors/SensorManager.h b/src/shared/sensors/SensorManager.h
index 4fb828a37ce87b05c59321dc78902a54a5a58fd3..1874bc082084e05bf9d16b54dda9586724f36bb1 100644
--- a/src/shared/sensors/SensorManager.h
+++ b/src/shared/sensors/SensorManager.h
@@ -59,8 +59,14 @@ public:
      */
     ~SensorManager();
 
+    /**
+     * @brief Starts the task scheduler.
+     */
     bool start();
 
+    /**
+     * @brief Starts the task scheduler.
+     */
     void stop();
 
     /**
diff --git a/src/shared/sensors/VN100/VN100.cpp b/src/shared/sensors/VN100/VN100.cpp
index cb97c2b0818efdf597c3e0b270b774e67c212c96..eb9d3e9043d62522ec566c40e501d139403ee1d4 100644
--- a/src/shared/sensors/VN100/VN100.cpp
+++ b/src/shared/sensors/VN100/VN100.cpp
@@ -27,7 +27,8 @@
 namespace Boardcore
 {
 
-VN100::VN100(unsigned int portNumber, BaudRates baudRate, CRCOptions crc)
+VN100::VN100(USARTType *portNumber, USARTInterface::Baudrate baudRate,
+             CRCOptions crc)
     : portNumber(portNumber), baudRate(baudRate), crc(crc)
 {
 }
@@ -128,8 +129,7 @@ bool VN100::sampleRaw()
     }
 
     // Send the IMU sampling command
-    if (!(serialInterface->send(preSampleImuString->c_str(),
-                                preSampleImuString->length())))
+    if (!(serialInterface->writeString(preSampleImuString->c_str())))
     {
         LOG_WARN(logger, "Unable to sample due to serial communication error");
         return false;
@@ -177,13 +177,6 @@ bool VN100::closeAndReset()
         return false;
     }
 
-    // Close the serial
-    if (!(serialInterface->closeSerial()))
-    {
-        LOG_WARN(logger, "Impossible to close vn100 serial communication");
-        return false;
-    }
-
     isInit = false;
 
     // Free the recvString memory
@@ -224,8 +217,7 @@ VN100Data VN100::sampleImpl()
     }
 
     // Returns Quaternion, Magnetometer, Accelerometer and Gyro
-    if (!(serialInterface->send(preSampleImuString->c_str(),
-                                preSampleImuString->length())))
+    if (!(serialInterface->writeString(preSampleImuString->c_str())))
     {
         // If something goes wrong i return the last sampled data
         return lastSample;
@@ -257,8 +249,7 @@ VN100Data VN100::sampleImpl()
     // Returns Magnetometer, Accelerometer, Gyroscope, Temperature and Pressure
     // (UNCOMPENSATED) DO NOT USE THESE MAGNETOMETER, ACCELEROMETER AND
     // GYROSCOPE VALUES
-    if (!(serialInterface->send(preSampleTempPressString->c_str(),
-                                preSampleTempPressString->length())))
+    if (!(serialInterface->writeString(preSampleTempPressString->c_str())))
     {
         // If something goes wrong i return the last sampled data
         return lastSample;
@@ -312,16 +303,10 @@ bool VN100::disableAsyncMessages(bool waitResponse)
 bool VN100::configDefaultSerialPort()
 {
     // Initial default settings
-    serialInterface = new VN100Serial(
-        portNumber, static_cast<unsigned int>(BaudRates::Baud_115200));
+    serialInterface = new USART(portNumber, USARTInterface::Baudrate::B115200);
 
     // Check correct serial init
-    if (!serialInterface->init())
-    {
-        return false;
-    }
-
-    return true;
+    return serialInterface->init();
 }
 
 /**
@@ -333,7 +318,7 @@ bool VN100::configUserSerialPort()
     std::string command;
 
     // I format the command to change baud rate
-    command = fmt::format("{}{}", "VNWRG,5,", baudRate);
+    command = fmt::format("{}{}", "VNWRG,5,", static_cast<int>(baudRate));
 
     // I can send the command
     if (!sendStringCommand(command))
@@ -341,23 +326,14 @@ bool VN100::configUserSerialPort()
         return false;
     }
 
-    // I can close the serial
-    serialInterface->closeSerial();
-
     // Destroy the serial object
     delete serialInterface;
 
     // I can open the serial with user's baud rate
-    serialInterface =
-        new VN100Serial(portNumber, static_cast<unsigned int>(baudRate));
+    serialInterface = new USART(portNumber, baudRate);
 
     // Check correct serial init
-    if (!serialInterface->init())
-    {
-        return false;
-    }
-
-    return true;
+    return serialInterface->init();
 }
 
 bool VN100::setCrc(bool waitResponse)
@@ -443,11 +419,13 @@ bool VN100::selfTestImpl()
 
     if (!sendStringCommand("VNRRG,01"))
     {
+        LOG_WARN(logger, "Unable to send string command");
         return false;
     }
 
     if (!recvStringCommand(recvString, recvStringMaxDimension))
     {
+        LOG_WARN(logger, "Unable to receive string command");
         return false;
     }
 
@@ -661,14 +639,14 @@ bool VN100::sendStringCommand(std::string command)
     }
 
     // I send the final command
-    if (!(serialInterface->send(command.c_str(), command.length() + 1)))
+    if (!serialInterface->writeString(command.c_str()))
     {
         return false;
     }
 
     // Wait some time
     // TODO dimension the time
-    miosix::Thread::sleep(1);
+    miosix::Thread::sleep(500);
 
     return true;
 }
@@ -677,7 +655,7 @@ bool VN100::recvStringCommand(char *command, int maxLength)
 {
     int i = 0;
     // Read the buffer
-    if (!(serialInterface->recv(command, maxLength)))
+    if (!(serialInterface->read(command, maxLength)))
     {
         return false;
     }
diff --git a/src/shared/sensors/VN100/VN100.h b/src/shared/sensors/VN100/VN100.h
index 9d40dc304d1de2101beecf71664ecbb7a806fb30..aa61dbdd9b458a96b411047e66452912337e6adc 100644
--- a/src/shared/sensors/VN100/VN100.h
+++ b/src/shared/sensors/VN100/VN100.h
@@ -26,7 +26,7 @@
  * @brief Driver for the VN100S IMU.
  *
  * The VN100S sensor is a calibrated IMU which includes accelerometer,
- * magnetometer, gyroscope, barometer and temperature sensor. ùThe device
+ * magnetometer, gyroscope, barometer and temperature sensor. The device
  * provides also a calibration matrix and an anti-drift matrix for the gyroscope
  * values. The goal of this driver though is to interface the sensor in its
  * basic use. Things like asynchronous data and anti-drift techniques haven't
@@ -58,7 +58,7 @@
 #include <utils/Debug.h>
 
 #include "VN100Data.h"
-#include "VN100Serial.h"
+#include "drivers/usart/USART.h"
 
 namespace Boardcore
 {
@@ -69,19 +69,6 @@ namespace Boardcore
 class VN100 : public Sensor<VN100Data>
 {
 public:
-    enum class BaudRates : unsigned int
-    {
-        Baud_9600   = 9600,
-        Baud_19200  = 19200,
-        Baud_38400  = 38400,
-        Baud_57600  = 57600,
-        Baud_115200 = 115200,
-        Baud_128000 = 128000,
-        Baud_230400 = 230400,
-        Baud_460800 = 460800,
-        Baud_921600 = 921600
-    };
-
     enum class CRCOptions : uint8_t
     {
         CRC_NO        = 0x00,
@@ -93,12 +80,13 @@ public:
      * @brief Constructor.
      *
      * @param USART port number.
-     * @param BaudRate different from the sensor's default.
+     * @param BaudRate different from the sensor's default [9600, 19200, 38400,
+     * 57600, 115200, 128000, 230400, 460800, 921600].
      * @param Redundancy check option.
      */
-    VN100(unsigned int portNumber = defaultPortNumber,
-          BaudRates baudRate      = BaudRates::Baud_115200,
-          CRCOptions crc          = CRCOptions::CRC_ENABLE_8);
+    VN100(USARTType *portNumber    = USART2,
+          USART::Baudrate baudRate = USART::Baudrate::B921600,
+          CRCOptions crc           = CRCOptions::CRC_ENABLE_8);
 
     bool init() override;
 
@@ -236,8 +224,8 @@ private:
      */
     uint16_t calculateChecksum16(uint8_t *message, int length);
 
-    unsigned int portNumber;
-    BaudRates baudRate;
+    USARTType *portNumber;
+    USART::Baudrate baudRate;
     CRCOptions crc;
     bool isInit = false;
 
@@ -267,11 +255,10 @@ private:
      * @brief Serial interface that is needed to communicate
      * with the sensor via ASCII codes.
      */
-    VN100Serial *serialInterface = nullptr;
+    USARTInterface *serialInterface = nullptr;
 
     PrintLogger logger = Logging::getLogger("vn100");
 
-    static const unsigned int defaultPortNumber      = 2;
     static const unsigned int recvStringMaxDimension = 200;
 };
 }  // namespace Boardcore
diff --git a/src/shared/sensors/VN100/VN100Data.h b/src/shared/sensors/VN100/VN100Data.h
index 5671733e8a479513c71b05df7dcc317f15c20bc0..210a3b82d77b5788afeb00a808a345c7dc1f6fad 100644
--- a/src/shared/sensors/VN100/VN100Data.h
+++ b/src/shared/sensors/VN100/VN100Data.h
@@ -53,6 +53,7 @@ struct VN100Data : public QuaternionData,
     /**
      * @brief Void parameters constructor
      */
+    // cppcheck-suppress uninitDerivedMemberVar
     VN100Data()
         : QuaternionData{0, 0.0, 0.0, 0.0, 0.0}, MagnetometerData{0, 0.0, 0.0,
                                                                   0.0},
@@ -67,35 +68,24 @@ struct VN100Data : public QuaternionData,
      * @param single data structures for all the data
      */
     // cppcheck-suppress passedByValue
+    // cppcheck-suppress uninitDerivedMemberVar
     VN100Data(QuaternionData quat, MagnetometerData magData,
               AccelerometerData accData, GyroscopeData gyro,
               TemperatureData temp, PressureData pres)
-        : QuaternionData{quat.quatTimestamp, quat.quatX, quat.quatY, quat.quatZ,
-                         quat.quatW},
-          MagnetometerData{magData.magneticFieldTimestamp,
-                           magData.magneticFieldX, magData.magneticFieldY,
-                           magData.magneticFieldZ},
-          AccelerometerData{accData.accelerationTimestamp,
-                            accData.accelerationX, accData.accelerationY,
-                            accData.accelerationZ},
-          GyroscopeData{gyro.angularVelocityTimestamp, gyro.angularVelocityX,
-                        gyro.angularVelocityY, gyro.angularVelocityZ},
-          TemperatureData{temp.temperatureTimestamp, temp.temperature},
-          PressureData{pres.pressureTimestamp, pres.pressure}
+        : QuaternionData(quat), MagnetometerData(magData),
+          AccelerometerData(accData), GyroscopeData(gyro),
+          TemperatureData(temp), PressureData(pres)
     {
     }
 
     static std::string header()
     {
-        return "quatTimestamp,quatX,quatY,quatZ,quatW,"
-               "magneticFieldTimestamp,magneticFieldX,"
-               "magneticFieldY,magneticFieldZ,"
+        return "quatTimestamp,quatX,quatY,quatZ,quatW,magneticFieldTimestamp,"
+               "magneticFieldX,magneticFieldY,magneticFieldZ,"
                "accelerationTimestamp,accelerationX,accelerationY,"
-               "accelerationZ,gyro_"
-               "timestamp,"
-               "angularVelocityX,"
-               "angularVelocityY,angularVelocityZ"
-               "temperatureTimestamp,temp,pressureTimestamp,press\n";
+               "accelerationZ,gyro_timestamp,angularVelocityX,angularVelocityY,"
+               "angularVelocityZ,temperatureTimestamp,temperature,"
+               "pressureTimestamp,pressure\n";
     }
 
     void print(std::ostream& os) const
diff --git a/src/shared/sensors/analog/loadcell/LoadCell.h b/src/shared/sensors/analog/AnalogLoadCell.h
similarity index 85%
rename from src/shared/sensors/analog/loadcell/LoadCell.h
rename to src/shared/sensors/analog/AnalogLoadCell.h
index 76d678ce2304542247fb0fa57b33aed73972ff4d..f554bee330b2595a382b9c5cddc7bb7a44699282 100644
--- a/src/shared/sensors/analog/loadcell/LoadCell.h
+++ b/src/shared/sensors/analog/AnalogLoadCell.h
@@ -26,15 +26,15 @@
 
 #include <functional>
 
-#include "LoadCellData.h"
+#include "AnalogLoadCellData.h"
 
 namespace Boardcore
 {
 
-class LoadCellSensor : public Sensor<LoadCellData>
+class AnalogLoadCell : public Sensor<AnalogLoadCellData>
 {
 public:
-    LoadCellSensor(std::function<std::pair<uint64_t, float>()> getVoltage,
+    AnalogLoadCell(std::function<std::pair<uint64_t, float>()> getVoltage,
                    const float mVtoV, const unsigned int fullScale,
                    const float supplyVoltage = 5)
         : getVoltage(getVoltage),
@@ -46,11 +46,11 @@ public:
 
     bool selfTest() override { return true; };
 
-    LoadCellData sampleImpl() override
+    AnalogLoadCellData sampleImpl() override
     {
-        LoadCellData data;
+        AnalogLoadCellData data;
 
-        std::tie(data.timestamp, data.voltage) = getVoltage();
+        std::tie(data.loadTimestamp, data.voltage) = getVoltage();
 
         data.load = data.voltage / conversionCoeff;
 
diff --git a/src/shared/sensors/analog/loadcell/LoadCellData.h b/src/shared/sensors/analog/AnalogLoadCellData.h
similarity index 85%
rename from src/shared/sensors/analog/loadcell/LoadCellData.h
rename to src/shared/sensors/analog/AnalogLoadCellData.h
index 665ae93111656de0f1c756d2a4003d27093fd1fc..8cb8a7274ad3e2cd0101b43a2b7c70742addaa04 100644
--- a/src/shared/sensors/analog/loadcell/LoadCellData.h
+++ b/src/shared/sensors/analog/AnalogLoadCellData.h
@@ -22,22 +22,20 @@
 
 #pragma once
 
-#include <ostream>
+#include <sensors/SensorData.h>
 
 namespace Boardcore
 {
 
-struct LoadCellData
+struct AnalogLoadCellData : LoadCellData
 {
-    uint64_t timestamp;
     float voltage;
-    float load;
 
-    static std::string header() { return "timestamp,voltage,load\n"; }
+    static std::string header() { return "loadTimestamp,load,voltage\n"; }
 
     void print(std::ostream& os) const
     {
-        os << timestamp << "," << voltage << "," << load << "\n";
+        os << loadTimestamp << "," << load << "," << voltage << "\n";
     }
 };
 
diff --git a/src/shared/sensors/analog/battery/BatteryVoltageSensor.h b/src/shared/sensors/analog/BatteryVoltageSensor.h
similarity index 100%
rename from src/shared/sensors/analog/battery/BatteryVoltageSensor.h
rename to src/shared/sensors/analog/BatteryVoltageSensor.h
diff --git a/src/shared/sensors/analog/battery/BatteryVoltageSensorData.h b/src/shared/sensors/analog/BatteryVoltageSensorData.h
similarity index 100%
rename from src/shared/sensors/analog/battery/BatteryVoltageSensorData.h
rename to src/shared/sensors/analog/BatteryVoltageSensorData.h
diff --git a/src/shared/sensors/analog/current/CurrentSensor.h b/src/shared/sensors/analog/CurrentSensor.h
similarity index 100%
rename from src/shared/sensors/analog/current/CurrentSensor.h
rename to src/shared/sensors/analog/CurrentSensor.h
diff --git a/src/shared/sensors/analog/current/CurrentSensorData.h b/src/shared/sensors/analog/CurrentSensorData.h
similarity index 100%
rename from src/shared/sensors/analog/current/CurrentSensorData.h
rename to src/shared/sensors/analog/CurrentSensorData.h
diff --git a/src/shared/sensors/analog/pressure/honeywell/SSCDRRN015PDA.h b/src/shared/sensors/analog/pressure/honeywell/SSCDRRN015PDA.h
index 24e9dd136665c371bf48b6a8ac3b2054ee4424de..1e2785023c487613b58f094d19113e9d5fe51c6f 100644
--- a/src/shared/sensors/analog/pressure/honeywell/SSCDRRN015PDA.h
+++ b/src/shared/sensors/analog/pressure/honeywell/SSCDRRN015PDA.h
@@ -22,7 +22,7 @@
 
 #pragma once
 
-#include <math/Stats.h>
+#include <utils/Stats/Stats.h>
 
 #include "HoneywellPressureSensor.h"
 #include "SSCDRRN015PDAData.h"
diff --git a/src/shared/sensors/calibration/AxisOrientation.h b/src/shared/sensors/calibration/AxisOrientation.h
new file mode 100644
index 0000000000000000000000000000000000000000..7b6924e7f9ebb9878995dcec88e2cc65345b8fd4
--- /dev/null
+++ b/src/shared/sensors/calibration/AxisOrientation.h
@@ -0,0 +1,233 @@
+/* Copyright (c) 2021-2022 Skyward Experimental Rocketry
+ * Authors: Riccardo Musso, 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
+
+#include <sensors/Sensor.h>
+
+#include <Eigen/Core>
+#include <Eigen/Geometry>
+
+namespace Boardcore
+{
+
+/**
+ * This enum act like versors towards the chosen axis.
+ *
+ * X, Y and Z always set according to the right hand rule, so that:
+ * X is the index
+ * Y is the second finger
+ * Z is the thumb
+ *
+ */
+enum class Direction : uint8_t
+{
+    POSITIVE_X = 0,
+    NEGATIVE_X,
+    POSITIVE_Y,
+    NEGATIVE_Y,
+    POSITIVE_Z,
+    NEGATIVE_Z,
+};
+
+constexpr const char* humanFriendlyDirection[]{
+    "North", "South", "East", "West", "Down", "Up",
+};
+
+inline Eigen::Vector3f orientationToVector(Direction val)
+{
+    switch (val)
+    {
+        case Direction::POSITIVE_X:
+            return {1, 0, 0};
+        case Direction::NEGATIVE_X:
+            return {-1, 0, 0};
+        case Direction::POSITIVE_Y:
+            return {0, 1, 0};
+        case Direction::NEGATIVE_Y:
+            return {0, -1, 0};
+        case Direction::POSITIVE_Z:
+            return {0, 0, 1};
+        case Direction::NEGATIVE_Z:
+            return {0, 0, -1};
+        default:
+            /* never happens, added just to shut up the warnings */
+            return {0, 0, 0};
+    }
+}
+
+/**
+ * This struct represents in the most general way any kind of transformation of
+ * the reference frame (axis X, Y and Z).
+ * This data type is intended to simplify the code, so you shouldn't instantiate
+ * this struct directly, but rather use the structures AxisAngleOrientation or
+ * AxisOrthoOrientation that will be automatically corrected to this one, thanks
+ * to the implicit cast in favor of AxisOrientation.
+ *
+ * For example:
+ *
+ * AxisAngleOrientation angles ( PI/2, PI, 0);
+ * AxisOrthoOrientation ortho  ( Direction::NEGATIVE_X,
+ * Direction::POSITIVE_Z );
+ *
+ * // The implicit cast is supported and recommended
+ * AxisOrientation converted1 = angles;
+ * AxisOrientation converted2 = ortho;
+ *
+ * // Now we can use the generated matrix:
+ * Eigen::Vector3f zeta = convertedX.getMatrix() * Eigen::Vector3f { 0, 0, 1 }
+ *
+ */
+struct AxisOrientation
+{
+    Eigen::Matrix3f mat;
+
+    AxisOrientation() : mat(Eigen::Matrix3f::Identity()) {}
+
+    AxisOrientation(Eigen::Matrix3f _mat) : mat(_mat) {}
+
+    void setMatrix(Eigen::Matrix3f _mat) { mat = _mat; }
+
+    Eigen::Matrix3f getMatrix() const { return mat; }
+};
+
+/**
+ * This struct uses the three angles yaw, pitch and roll to define the
+ * transformation of the reference frame, so according to N.E.D standard we get:
+ *
+ *         X (north)
+ *        /
+ *       /
+ *      .----> Y (east)
+ *      |
+ *      |
+ *      v
+ *     Z (down)
+ *
+ * Where:
+ * Yaw is rotation of Z axis
+ * Pitch is rotation of Y axis
+ * Roll is rotation of X axis
+ */
+struct AxisAngleOrientation
+{
+    float yaw, pitch, roll;
+
+    AxisAngleOrientation() : yaw(0), pitch(0), roll(0) {}
+
+    AxisAngleOrientation(float _yaw, float _pitch, float _roll)
+        : yaw(_yaw), pitch(_pitch), roll(_roll)
+    {
+    }
+
+    operator AxisOrientation() const { return AxisOrientation(getMatrix()); }
+
+    Eigen::Matrix3f getMatrix() const
+    {
+        return (Eigen::AngleAxisf(yaw, Eigen::Vector3f{0, 0, 1}) *
+                Eigen::AngleAxisf(pitch, Eigen::Vector3f{0, 1, 0}) *
+                Eigen::AngleAxisf(roll, Eigen::Vector3f{1, 0, 0}))
+            .toRotationMatrix();
+    }
+};
+
+/**
+ * This struct represents the orientation of the reference frame relative
+ * to X, Y, Z in the start orientation.
+ * If we know the orientation of the X and Y axis, using the right hand rule
+ * we can infer the Z axis.
+ *
+ * For example, if the base reference is:
+ *
+ *        z
+ *        ^
+ *        |
+ *        |
+ *        /----> y
+ *       /
+ *      x
+ *
+ * Then if we set x = NEGATIVE_Y, y = POSITIVE_Z, we get:
+ *
+ *          y   z
+ *          ^   ^
+ *          |  /
+ *          | /
+ *   x <----/
+ *
+ */
+struct AxisOrthoOrientation
+{
+    Direction xAxis, yAxis;
+
+    AxisOrthoOrientation()
+        : xAxis(Direction::POSITIVE_X), yAxis(Direction::POSITIVE_Y)
+    {
+    }
+
+    AxisOrthoOrientation(Direction _xAxis, Direction _yAxis)
+        : xAxis(_xAxis), yAxis(_yAxis)
+    {
+    }
+
+    operator AxisOrientation() const { return AxisOrientation(getMatrix()); }
+
+    Eigen::Matrix3f getMatrix() const
+    {
+        Eigen::Vector3f vx, vy, vz;
+
+        vx = orientationToVector(xAxis);
+        vy = orientationToVector(yAxis);
+        vz = vx.cross(vy);
+
+        Eigen::Matrix3f mat;
+        mat.col(0) << vx;
+        mat.col(1) << vy;
+        mat.col(2) << vz;
+        return mat;
+    }
+};
+
+/**
+ * This struct represents axis orientation relative to a reference system.
+ * Operatively it simply combines two transformations. It is particularly useful
+ * to obtain an AxisOrientation from a reference system generally not N.E.D.
+ */
+struct AxisRelativeOrientation
+{
+    AxisOrientation systemOrientation, orientation;
+
+    AxisRelativeOrientation(const AxisOrientation& _systemOrientation,
+                            const AxisOrientation& _orientation)
+        : systemOrientation(_systemOrientation), orientation(_orientation)
+    {
+    }
+
+    operator AxisOrientation() const { return {getMatrix()}; }
+
+    Eigen::Matrix3f getMatrix() const
+    {
+        return systemOrientation.getMatrix() * orientation.getMatrix();
+    }
+};
+
+}  // namespace Boardcore
diff --git a/src/shared/sensors/calibration/BiasCalibration.h b/src/shared/sensors/calibration/BiasCalibration.h
index 5fc2ed071900c11446e6e5d25c27f34ba050e2b2..be84f860e0118b37dff9bc6e4b6c600ff74aa473 100644
--- a/src/shared/sensors/calibration/BiasCalibration.h
+++ b/src/shared/sensors/calibration/BiasCalibration.h
@@ -1,5 +1,5 @@
-/* Copyright (c) 2020 Skyward Experimental Rocketry
- * Author: Riccardo Musso
+/* Copyright (c) 2020-2022 Skyward Experimental Rocketry
+ * Authors: Riccardo Musso, 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
@@ -23,93 +23,163 @@
 #pragma once
 
 #include <sensors/SensorData.h>
+#include <sensors/calibration/AxisOrientation.h>
+#include <sensors/calibration/Calibration.h>
+#include <sensors/calibration/SensorDataExtra.h>
 
 #include <Eigen/Core>
 
-#include "Calibration.h"
-#include "SensorDataExtra.h"
-
 namespace Boardcore
 {
 
 /**
- * This is the dumbest type of calibration possible: it stores a 3d vector
- * (called "bias") that will be added to every measurement.
- * During the calibration phase it will use a given reference vector (for
- * example the gravitational acceleration for the accelerometer), and every time
- * you'll feed the model with a new value, you have to give it the orientation
- * of the sensor, so it can guess the bias.
+ * @brief Applies an offset to the data
+ *
+ * It stores a vector that will be added to every measurement.
+ * Note: The type T must implement >> and << operators with a Vector3f.
+ *
+ * @tparam T Type used for data items.
  */
 template <typename T>
 class BiasCorrector : public ValuesCorrector<T>
 {
-
 public:
-    BiasCorrector() : bias(0, 0, 0) {}
-    BiasCorrector(const Eigen::Vector3f& _bias) : bias(_bias) {}
-
-    void operator>>(Eigen::Vector3f& rhs) { rhs = bias; }
+    BiasCorrector();
 
-    void operator<<(const Eigen::Vector3f& rhs) { bias = rhs; }
+    BiasCorrector(const Eigen::Vector3f& bias);
 
-    void setIdentity() override { bias = {0, 0, 0}; }
+    void operator>>(Eigen::Vector3f& rhs);
 
-    T correct(const T& data) const override
-    {
-        Eigen::Vector3f tmp;
-        T out;
+    void operator<<(const Eigen::Vector3f& rhs);
 
-        data >> tmp;
-        tmp += bias;
-        out << tmp;
+    void setIdentity() override;
 
-        return out;
-    }
+    T correct(const T& data) const override;
 
 private:
-    Eigen::Vector3f bias;
+    Eigen::Vector3f bias = Eigen::Vector3f::Zero();
 };
 
+/**
+ * @brief This is the dumbest type of calibration possible: an offset.
+ *
+ * During the calibration phase it will use a given reference vector (for
+ * example the gravitational acceleration), and every time you'll feed the model
+ * with a new value, you have to give it the orientation of the sensor, so it
+ * can guess the bias.
+ *
+ * @tparam T
+ */
 template <typename T>
 class BiasCalibration
-    : public AbstractCalibrationModel<T, BiasCorrector<T>, AxisOrientation>
+    : public AbstractCalibration<T, BiasCorrector<T>, AxisOrientation>
 {
 public:
-    BiasCalibration() : sum(0, 0, 0), ref(0, 0, 0), numSamples(0) {}
-
-    void setReferenceVector(Eigen::Vector3f vec) { ref = vec; }
-    Eigen::Vector3f getReferenceVector() { return ref; }
-
-    /**
-     * BiasCalibration accepts an indefinite number of samples,
-     * so feed(...) never returns false.
-     */
-    bool feed(const T& measured, const AxisOrientation& transform) override
-    {
-        Eigen::Vector3f vec;
-        measured >> vec;
-
-        sum += (transform.getMatrix().transpose() * ref) - vec;
-        numSamples++;
-
-        return true;
-    }
-
-    bool feed(const T& measured)
-    {
-        return feed(measured, AxisOrthoOrientation());
-    }
-
-    BiasCorrector<T> computeResult()
-    {
-        if (numSamples == 0)
-            return {Eigen::Vector3f{0, 0, 0}};
-        return {sum / numSamples};
-    }
+    BiasCalibration();
+
+    void setReferenceVector(Eigen::Vector3f vec);
+
+    Eigen::Vector3f getReferenceVector();
+
+    bool feed(const T& measured, const AxisOrientation& transform) override;
+
+    bool feed(const T& measured);
+
+    BiasCorrector<T> computeResult();
 
 private:
     Eigen::Vector3f sum, ref;
     unsigned numSamples;
 };
 
+template <typename T>
+BiasCorrector<T>::BiasCorrector()
+{
+}
+
+template <typename T>
+BiasCorrector<T>::BiasCorrector(const Eigen::Vector3f& bias) : bias(bias)
+{
+}
+
+template <typename T>
+void BiasCorrector<T>::operator>>(Eigen::Vector3f& rhs)
+{
+    rhs = bias;
+}
+
+template <typename T>
+void BiasCorrector<T>::operator<<(const Eigen::Vector3f& rhs)
+{
+    bias = rhs;
+}
+
+template <typename T>
+void BiasCorrector<T>::setIdentity()
+{
+    bias = {0, 0, 0};
+}
+
+template <typename T>
+T BiasCorrector<T>::correct(const T& data) const
+{
+    T output;
+    Eigen::Vector3f tmp;
+
+    data >> tmp;
+    tmp += bias;
+    output << tmp;
+
+    return output;
+}
+
+template <typename T>
+BiasCalibration<T>::BiasCalibration()
+    : sum(0, 0, 0), ref(0, 0, 0), numSamples(0)
+{
+}
+
+template <typename T>
+void BiasCalibration<T>::setReferenceVector(Eigen::Vector3f vec)
+{
+    ref = vec;
+}
+
+template <typename T>
+Eigen::Vector3f BiasCalibration<T>::getReferenceVector()
+{
+    return ref;
+}
+
+/**
+ * BiasCalibration accepts an indefinite number of samples,
+ * so feed(...) never returns false.
+ */
+template <typename T>
+bool BiasCalibration<T>::feed(const T& measured,
+                              const AxisOrientation& transform)
+{
+    Eigen::Vector3f vec;
+    measured >> vec;
+
+    sum += (transform.getMatrix().transpose() * ref) - vec;
+    numSamples++;
+
+    return true;
+}
+
+template <typename T>
+bool BiasCalibration<T>::feed(const T& measured)
+{
+    return feed(measured, AxisOrthoOrientation());
+}
+
+template <typename T>
+BiasCorrector<T> BiasCalibration<T>::computeResult()
+{
+    if (numSamples == 0)
+        return {Eigen::Vector3f{0, 0, 0}};
+    return {sum / numSamples};
+}
+
 }  // namespace Boardcore
diff --git a/src/shared/sensors/calibration/Calibration.h b/src/shared/sensors/calibration/Calibration.h
index 8fa838df73d4de676d18c9256e4f9d86bb21caec..64673246b4ef425db93d3cddd2219d48533ee8da 100644
--- a/src/shared/sensors/calibration/Calibration.h
+++ b/src/shared/sensors/calibration/Calibration.h
@@ -1,5 +1,5 @@
-/* Copyright (c) 2021 Skyward Experimental Rocketry
- * Author: Riccardo Musso
+/* Copyright (c) 2021-2022 Skyward Experimental Rocketry
+ * Authors: Riccardo Musso, 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
@@ -22,315 +22,67 @@
 
 #pragma once
 
-#include <sensors/Sensor.h>
-
 #include <Eigen/Core>
-#include <Eigen/Geometry>
 
 namespace Boardcore
 {
 
 /**
- * This class is used to adjust the values given by sensors during the flight.
- * An object can be obtained only via deserialization or if produced by an
- * instance of the "CalibrationModel" class.
- *
- * T is the type of sensor data the model is applied to (e.g.
- * GyroscopeData, MagnetometerData). This is needed because certain models could
- * work slightly differently depending on it
- *
- *  Note: derived classes of ValuesCorrector<XX> MUST implement the following
- *  operators (to make possible to store and load the coefficients):
+ * @brief This class is used to adjust the values given by a sensor.
  *
- *  operator << (const XX& t);
- *  operator >> (XX& t);
- *
- *  where XX is a datatype that can fully contain all the coefficients used
- *  by the function correct(input).
- *
- *  Also, an empty constructor must create a neutral instance (identity
- * transformation).
+ * @tparam T Type of sensor data the model is applied to (e.g.
+ * GyroscopeData, MagnetometerData).
  */
 template <typename T>
 class ValuesCorrector
 {
 public:
     /**
-     * This method will sets the internal coefficients so that the corrected
+     * @brief Sets the internal coefficients so that the corrected
      * values are exactly the same of the inputted ones.
      */
     virtual void setIdentity() = 0;
 
+    /**
+     * @brief Applies the correction to the given input.
+     */
     virtual T correct(const T& input) const = 0;
 };
 
 /**
- * AbstractCalibrationModel represents a "factory" of ValuesCorrector instances,
- * and it's necessary to create one. You will always use one of its derived
- * classes, of course.
+ * @brief Utility to compute calibration parameters for a specific sensor.
  *
- * Values given to the feed() function are needed for the training of the model.
+ * The calibration appens in two phases:
+ * - Sample collection: feed is called to store values;
+ * - Coefficient computation: computeResult is used to calculate the correction
+ * parameters.
  *
- * T is the sensor data type
- * C is a class that extends ValuesCorrector<T>
+ * @tparam T Sensor's data type used in the feed function.
+ * @tparam C ValuesCorrector returned by computeResult.
  */
 template <typename T, typename C, typename... AdditionalFeedParams>
-class AbstractCalibrationModel
+class AbstractCalibration
 {
 public:
     /**
-     * Gives to the model a single measurement to store and use to produce the
-     * adapted ValueCorrector instance
+     * @brief Stores the measurement for further processing.
      *
      * @returns false if the model can't accept the given data (usually because
-     * the internal buffers are full)
+     * the internal buffers are full).
      */
     virtual bool feed(const T& measurement,
                       const AdditionalFeedParams&... params) = 0;
 
     /**
-     * Creates the best ValuesCorrector instance for the given measurements.
-     * Note: you must feed some data to the model before getting the result!
+     * @brief Uses the recorded measurements to computer the correction
+     * parameters needed to correct sensor's data.
+     *
+     * Note: You need to feed the algorithm enough samples. Otherwise the method
+     * returns an ineffective corrector.
+     *
+     * @return ValuesCorrector containing the correction parameters.
      */
     virtual C computeResult() = 0;
-
-    virtual ~AbstractCalibrationModel(){};
-};
-
-/**
- * This class acts like a Sensor driver but incorporates both a Sensor<T>
- * instance and a ValuesCorrector. It can be useful to add a calibration step to
- * alredy existing code that uses the Sensor API.
- */
-template <typename SensorData>
-class SensorWrapper : public Sensor<SensorData>
-{
-    using S = Sensor<SensorData>;
-    using C = ValuesCorrector<SensorData>;
-
-public:
-    SensorWrapper(S* _sensor, C* _calib) : sensor(_sensor), calib(_calib) {}
-
-    S* getSensor() { return sensor; }
-    void setSensor(S* s) { sensor = s; }
-
-    C* getValuesCorrector() { return calib; }
-    void setValuesCorrector(C* c) { calib = c; }
-
-    void init() override { sensor->init(); }
-
-    bool test() override { return sensor->test(); }
-
-    SensorData sampleImpl() override
-    {
-        return calib->correct(sensor->sampleImpl());
-    }
-
-    SensorErrors getWrappedSensorError() { return sensor->getLastError(); }
-
-private:
-    S* sensor;
-    C* calib;
-};
-
-/**
- * This enum act like versors towards the chosen axis.
- *
- * X, Y and Z always set according to the right hand rule, so that:
- * X is the index
- * Y is the second finger
- * Z is the thumb
- *
- */
-enum class Direction : uint8_t
-{
-    POSITIVE_X = 0,
-    NEGATIVE_X,
-    POSITIVE_Y,
-    NEGATIVE_Y,
-    POSITIVE_Z,
-    NEGATIVE_Z,
-};
-
-constexpr const char* humanFriendlyDirection[]{
-    "North", "South", "East", "West", "Down", "Up",
-};
-
-inline Eigen::Vector3f orientationToVector(Direction val)
-{
-    switch (val)
-    {
-        case Direction::POSITIVE_X:
-            return {1, 0, 0};
-        case Direction::NEGATIVE_X:
-            return {-1, 0, 0};
-        case Direction::POSITIVE_Y:
-            return {0, 1, 0};
-        case Direction::NEGATIVE_Y:
-            return {0, -1, 0};
-        case Direction::POSITIVE_Z:
-            return {0, 0, 1};
-        case Direction::NEGATIVE_Z:
-            return {0, 0, -1};
-        default:
-            /* never happens, added just to shut up the warnings */
-            return {0, 0, 0};
-    }
-}
-
-/**
- * This struct represents in the most general way any kind of transformation of
- * the reference frame (axis X, Y and Z).
- * This data type is intended to simplify the code, so you shouldn't instantiate
- * this struct directly, but rather use the structures AxisAngleOrientation or
- * AxisOrthoOrientation that will be automatically corrected to this one, thanks
- * to the implicit cast in favor of AxisOrientation.
- *
- * For example:
- *
- * AxisAngleOrientation angles ( PI/2, PI, 0);
- * AxisOrthoOrientation ortho  ( Direction::NEGATIVE_X,
- * Direction::POSITIVE_Z );
- *
- * // The implicit cast is supported and recommended
- * AxisOrientation converted1 = angles;
- * AxisOrientation converted2 = ortho;
- *
- * // Now we can use the generated matrix:
- * Eigen::Vector3f zeta = convertedX.getMatrix() * Eigen::Vector3f { 0, 0, 1 }
- *
- */
-struct AxisOrientation
-{
-    Eigen::Matrix3f mat;
-
-    AxisOrientation() : mat(Eigen::Matrix3f::Identity()) {}
-
-    AxisOrientation(Eigen::Matrix3f _mat) : mat(_mat) {}
-
-    void setMatrix(Eigen::Matrix3f _mat) { mat = _mat; }
-
-    Eigen::Matrix3f getMatrix() const { return mat; }
-};
-
-/**
- * This struct uses the three angles yaw, pitch and roll to define the
- * transformation of the reference frame, so according to N.E.D standard we get:
- *
- *         X (north)
- *        /
- *       /
- *      .----> Y (east)
- *      |
- *      |
- *      v
- *     Z (down)
- *
- * Where:
- * Yaw is rotation of Z axis
- * Pitch is rotation of Y axis
- * Roll is rotation of X axis
- */
-struct AxisAngleOrientation
-{
-    float yaw, pitch, roll;
-
-    AxisAngleOrientation() : yaw(0), pitch(0), roll(0) {}
-
-    AxisAngleOrientation(float _yaw, float _pitch, float _roll)
-        : yaw(_yaw), pitch(_pitch), roll(_roll)
-    {
-    }
-
-    operator AxisOrientation() const { return AxisOrientation(getMatrix()); }
-
-    Eigen::Matrix3f getMatrix() const
-    {
-        return (Eigen::AngleAxisf(yaw, Eigen::Vector3f{0, 0, 1}) *
-                Eigen::AngleAxisf(pitch, Eigen::Vector3f{0, 1, 0}) *
-                Eigen::AngleAxisf(roll, Eigen::Vector3f{1, 0, 0}))
-            .toRotationMatrix();
-    }
-};
-
-/**
- * This struct represents the orientation of the reference frame relative
- * to X, Y, Z in the start orientation.
- * If we know the orientation of the X and Y axis, using the right hand rule
- * we can infer the Z axis.
- *
- * For example, if the base reference is:
- *
- *        z
- *        ^
- *        |
- *        |
- *        /----> y
- *       /
- *      x
- *
- * Then if we set x = NEGATIVE_Y, y = POSITIVE_Z, we get:
- *
- *          y   z
- *          ^   ^
- *          |  /
- *          | /
- *   x <----/
- *
- */
-struct AxisOrthoOrientation
-{
-    Direction xAxis, yAxis;
-
-    AxisOrthoOrientation()
-        : xAxis(Direction::POSITIVE_X), yAxis(Direction::POSITIVE_Y)
-    {
-    }
-
-    AxisOrthoOrientation(Direction _xAxis, Direction _yAxis)
-        : xAxis(_xAxis), yAxis(_yAxis)
-    {
-    }
-
-    operator AxisOrientation() const { return AxisOrientation(getMatrix()); }
-
-    Eigen::Matrix3f getMatrix() const
-    {
-        Eigen::Vector3f vx, vy, vz;
-
-        vx = orientationToVector(xAxis);
-        vy = orientationToVector(yAxis);
-        vz = vx.cross(vy);
-
-        Eigen::Matrix3f mat;
-        mat.col(0) << vx;
-        mat.col(1) << vy;
-        mat.col(2) << vz;
-        return mat;
-    }
-};
-
-/**
- * This struct represents axis orientation relative to a reference system.
- * Operatively it simply combines two transformations. It is particularly useful
- * to obtain an AxisOrientation from a reference system generally not N.E.D.
- */
-struct AxisRelativeOrientation
-{
-    AxisOrientation systemOrientation, orientation;
-
-    AxisRelativeOrientation(const AxisOrientation& _systemOrientation,
-                            const AxisOrientation& _orientation)
-        : systemOrientation(_systemOrientation), orientation(_orientation)
-    {
-    }
-
-    operator AxisOrientation() const { return {getMatrix()}; }
-
-    Eigen::Matrix3f getMatrix() const
-    {
-        return systemOrientation.getMatrix() * orientation.getMatrix();
-    }
 };
 
 }  // namespace Boardcore
diff --git a/src/shared/sensors/calibration/HardIronCalibration.h b/src/shared/sensors/calibration/HardIronCalibration.h
index 720f9a7edaae543870665af1e92f54ce9a4484ca..2cd616ac58379671d3958859f3eea3d12585ff64 100644
--- a/src/shared/sensors/calibration/HardIronCalibration.h
+++ b/src/shared/sensors/calibration/HardIronCalibration.h
@@ -68,7 +68,7 @@ private:
 
 template <unsigned MaxSamples>
 class HardIronCalibration
-    : public AbstractCalibrationModel<MagnetometerData, HardIronCorrector>
+    : public AbstractCalibration<MagnetometerData, HardIronCorrector>
 {
 public:
     HardIronCalibration() : samples(), numSamples(0) {}
diff --git a/src/shared/sensors/calibration/SensorDataExtra.cpp b/src/shared/sensors/calibration/SensorDataExtra.cpp
index f8ffd7ea87afd7c786ca4b59b98bf88453ad62c9..ed69028b1d522410bb381d021002bcd39bc0739c 100644
--- a/src/shared/sensors/calibration/SensorDataExtra.cpp
+++ b/src/shared/sensors/calibration/SensorDataExtra.cpp
@@ -34,6 +34,13 @@ void operator<<(AccelerometerData& lhs, const Vector3f& rhs)
     lhs.accelerationZ = rhs[2];
 }
 
+void operator<<(Eigen::Vector3f& lhs, const AccelerometerData& rhs)
+{
+    lhs[0] = rhs.accelerationX;
+    lhs[1] = rhs.accelerationY;
+    lhs[2] = rhs.accelerationZ;
+}
+
 void operator<<(GyroscopeData& lhs, const Vector3f& rhs)
 {
     lhs.angularVelocityX = rhs[0];
@@ -41,6 +48,13 @@ void operator<<(GyroscopeData& lhs, const Vector3f& rhs)
     lhs.angularVelocityZ = rhs[2];
 }
 
+void operator<<(Eigen::Vector3f& lhs, const GyroscopeData& rhs)
+{
+    lhs[0] = rhs.angularVelocityX;
+    lhs[1] = rhs.angularVelocityY;
+    lhs[2] = rhs.angularVelocityZ;
+}
+
 void operator<<(MagnetometerData& lhs, const Vector3f& rhs)
 {
     lhs.magneticFieldX = rhs[0];
@@ -48,25 +62,35 @@ void operator<<(MagnetometerData& lhs, const Vector3f& rhs)
     lhs.magneticFieldZ = rhs[2];
 }
 
-void operator>>(const AccelerometerData& lhs, Vector3f& rhs)
+void operator<<(Eigen::Vector3f& lhs, const MagnetometerData& rhs)
+{
+    lhs[0] = rhs.magneticFieldX;
+    lhs[1] = rhs.magneticFieldY;
+    lhs[2] = rhs.magneticFieldZ;
+}
+
+void operator>>(const AccelerometerData& lhs, Eigen::Vector3f& rhs)
 {
-    rhs[0] = lhs.accelerationX;
-    rhs[1] = lhs.accelerationY;
-    rhs[2] = lhs.accelerationZ;
+    rhs << lhs;
 }
 
-void operator>>(const GyroscopeData& lhs, Vector3f& rhs)
+void operator>>(const Eigen::Vector3f& lhs, AccelerometerData& rhs)
+{
+    rhs << lhs;
+}
+
+void operator>>(const GyroscopeData& lhs, Eigen::Vector3f& rhs) { rhs << lhs; }
+
+void operator>>(const Eigen::Vector3f& lhs, GyroscopeData& rhs) { rhs << lhs; }
+
+void operator>>(const MagnetometerData& lhs, Eigen::Vector3f& rhs)
 {
-    rhs[0] = lhs.angularVelocityX;
-    rhs[1] = lhs.angularVelocityY;
-    rhs[2] = lhs.angularVelocityZ;
+    rhs << lhs;
 }
 
-void operator>>(const MagnetometerData& lhs, Vector3f& rhs)
+void operator>>(const Eigen::Vector3f& lhs, MagnetometerData& rhs)
 {
-    rhs[0] = lhs.magneticFieldX;
-    rhs[1] = lhs.magneticFieldY;
-    rhs[2] = lhs.magneticFieldZ;
+    rhs << lhs;
 }
 
 }  // namespace Boardcore
diff --git a/src/shared/sensors/calibration/SensorDataExtra.h b/src/shared/sensors/calibration/SensorDataExtra.h
index 0885a3a5ead12f3eb67c718451031f4388a882f8..cc52c472f14c904ff56913c3bb7d8e54ef8f6bae 100644
--- a/src/shared/sensors/calibration/SensorDataExtra.h
+++ b/src/shared/sensors/calibration/SensorDataExtra.h
@@ -46,16 +46,26 @@ namespace Boardcore
 
 void operator<<(AccelerometerData& lhs, const Eigen::Vector3f& rhs);
 
+void operator<<(Eigen::Vector3f& lhs, const AccelerometerData& rhs);
+
 void operator<<(GyroscopeData& lhs, const Eigen::Vector3f& rhs);
 
+void operator<<(Eigen::Vector3f& lhs, const GyroscopeData& rhs);
+
 void operator<<(MagnetometerData& lhs, const Eigen::Vector3f& rhs);
 
+void operator<<(Eigen::Vector3f& lhs, const MagnetometerData& rhs);
+
 void operator>>(const AccelerometerData& lhs, Eigen::Vector3f& rhs);
 
+void operator>>(const Eigen::Vector3f& lhs, AccelerometerData& rhs);
+
 void operator>>(const GyroscopeData& lhs, Eigen::Vector3f& rhs);
 
-void operator>>(const MagnetometerData& lhs, Eigen::Vector3f& rhs);
+void operator>>(const Eigen::Vector3f& lhs, GyroscopeData& rhs);
 
 void operator>>(const MagnetometerData& lhs, Eigen::Vector3f& rhs);
 
+void operator>>(const Eigen::Vector3f& lhs, MagnetometerData& rhs);
+
 }  // namespace Boardcore
diff --git a/src/shared/sensors/calibration/SixParameterCalibration.h b/src/shared/sensors/calibration/SixParameterCalibration.h
index b080020861fb33ac622ad94047d82b4d71ba859d..8296a1ac974718cc02c0b174d1cd2c4f56a1482f 100644
--- a/src/shared/sensors/calibration/SixParameterCalibration.h
+++ b/src/shared/sensors/calibration/SixParameterCalibration.h
@@ -89,8 +89,7 @@ private:
 
 template <typename T, unsigned MaxSamples>
 class SixParameterCalibration
-    : public AbstractCalibrationModel<T, SixParameterCorrector<T>,
-                                      AxisOrientation>
+    : public AbstractCalibration<T, SixParameterCorrector<T>, AxisOrientation>
 {
 public:
     SixParameterCalibration() : samples(), ref(1, 0, 0), numSamples(0) {}
diff --git a/src/shared/sensors/calibration/SoftAndHardIronCalibration/SoftAndHardIronCalibration.cpp b/src/shared/sensors/calibration/SoftAndHardIronCalibration/SoftAndHardIronCalibration.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c4d49d703425c8ec7d84fb1052e135b0b655ae97
--- /dev/null
+++ b/src/shared/sensors/calibration/SoftAndHardIronCalibration/SoftAndHardIronCalibration.cpp
@@ -0,0 +1,159 @@
+/* Copyright (c) 2021-2022 Skyward Experimental Rocketry
+ * Authors: Riccardo Musso, 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 "SoftAndHardIronCalibration.h"
+
+#include <sensors/calibration/SensorDataExtra.h>
+
+#include <iostream>
+
+using namespace Eigen;
+
+namespace Boardcore
+{
+
+SoftAndHardIronCorrector::SoftAndHardIronCorrector()
+    : SoftAndHardIronCorrector({0, 0, 0}, {1, 1, 1})
+{
+}
+
+SoftAndHardIronCorrector::SoftAndHardIronCorrector(const Vector3f& offset,
+                                                   const Vector3f& gain)
+    : offset(offset), gain(gain)
+{
+}
+
+void SoftAndHardIronCorrector::setIdentity()
+{
+    offset = {0, 0, 0};
+    gain   = {1, 1, 1};
+}
+
+MagnetometerData SoftAndHardIronCorrector::correct(
+    const MagnetometerData& sample) const
+{
+    MagnetometerData output;
+    Vector3f tmp;
+
+    tmp << sample;
+    tmp = (tmp + offset).cwiseProduct(gain);
+    output << tmp;
+
+    return output;
+}
+
+Eigen::Vector3f SoftAndHardIronCorrector::getOffset() const { return offset; }
+
+void SoftAndHardIronCorrector::setOffset(const Eigen::Vector3f& offset)
+{
+    this->offset = offset;
+}
+
+Eigen::Vector3f SoftAndHardIronCorrector::getGain() const { return gain; }
+
+void SoftAndHardIronCorrector::setGain(const Eigen::Vector3f& gain)
+{
+    this->gain = gain;
+}
+
+SoftAndHardIronCalibration::SoftAndHardIronCalibration() {}
+
+bool SoftAndHardIronCalibration::feed(const MagnetometerData& data)
+{
+    // Let S a matrix of Nx7 composed as [x^2, y^2, z^2, x, y, z, 1]
+    // D need to be S^T * S
+    // To avoid storing all measurements we just need to incrementally add to D
+
+    Vector3f vector;
+    vector << data;
+    Vector<float, 7> S;
+    // cppcheck-suppress constStatement
+    S << vector.cwiseProduct(vector), vector, 1;
+
+    for (int i = 0; i < 7; i++)
+        for (int j = 0; j < 7; j++)
+            D(i, j) += S(i) * S(j);
+
+    return true;
+}
+
+SoftAndHardIronCorrector SoftAndHardIronCalibration::computeResult()
+{
+    // Compute eigen value and vectors of D
+    SelfAdjointEigenSolver<Matrix<float, 7, 7>> solver(D);
+    auto eigenValues = solver.eigenvalues();
+
+    // Find the smallest eigen value and vector
+    float minValue = eigenValues[0];
+    int minIdx     = 0;
+
+    for (int i = 0; i < eigenValues.rows(); i++)
+        if (minValue > eigenValues[i])
+        {
+            minValue = eigenValues[i];
+            minIdx   = i;
+        }
+    Eigen::Matrix<float, 7, 1> vec = solver.eigenvectors().col(minIdx);
+
+    // Invert the vector if necessary
+    float det = vec[0] * vec[1] * vec[2];
+    if (det)
+    {
+        vec *= -1;
+        det *= -1;
+    }
+
+    // Compute offset and gain
+    Vector3f offset{vec[3] / vec[0] / 2, vec[4] / vec[1] / 2,
+                    vec[5] / vec[2] / 2};
+    Vector3f gain = (vec.block(0, 0, 3, 1) / cbrt(det)).cwiseSqrt();
+
+    return {offset, gain};
+}
+
+void operator<<(Eigen::Matrix<float, 3, 2>& lhs,
+                const SoftAndHardIronCorrector& rhs)
+{
+    lhs.col(0) = rhs.getOffset();
+    lhs.col(0) = rhs.getGain();
+}
+
+void operator<<(SoftAndHardIronCorrector& lhs,
+                const Eigen::Matrix<float, 3, 2>& rhs)
+{
+    lhs.setOffset(rhs.col(0));
+    lhs.setGain(rhs.col(1));
+}
+
+void operator>>(const Eigen::Matrix<float, 3, 2>& lhs,
+                SoftAndHardIronCorrector& rhs)
+{
+    rhs << lhs;
+}
+
+void operator>>(const SoftAndHardIronCorrector& lhs,
+                Eigen::Matrix<float, 3, 2>& rhs)
+{
+    rhs << lhs;
+}
+
+}  // namespace Boardcore
diff --git a/src/shared/sensors/calibration/SoftAndHardIronCalibration/SoftAndHardIronCalibration.h b/src/shared/sensors/calibration/SoftAndHardIronCalibration/SoftAndHardIronCalibration.h
new file mode 100644
index 0000000000000000000000000000000000000000..61b36c35fdcb91117a604a5f4cecc278ee5794f4
--- /dev/null
+++ b/src/shared/sensors/calibration/SoftAndHardIronCalibration/SoftAndHardIronCalibration.h
@@ -0,0 +1,108 @@
+/* Copyright (c) 2021-2022 Skyward Experimental Rocketry
+ * Authors: Riccardo Musso, 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
+
+#include <sensors/SensorData.h>
+#include <sensors/calibration/Calibration.h>
+
+#include <Eigen/Core>
+#include <Eigen/Eigenvalues>
+#include <vector>
+
+namespace Boardcore
+{
+
+/*
+ * The Soft-iron calibration removes the measurement error given by both the
+ * Hard and Soft Iron distortion of the magnetic field.
+ */
+class SoftAndHardIronCorrector : public ValuesCorrector<MagnetometerData>
+{
+public:
+    SoftAndHardIronCorrector();
+
+    SoftAndHardIronCorrector(const Eigen::Vector3f& offset,
+                             const Eigen::Vector3f& gain);
+
+    void setIdentity() override;
+
+    MagnetometerData correct(const MagnetometerData& sample) const override;
+
+    Eigen::Vector3f getOffset() const;
+
+    void setOffset(const Eigen::Vector3f& offset);
+
+    Eigen::Vector3f getGain() const;
+
+    void setGain(const Eigen::Vector3f& gain);
+
+private:
+    Eigen::Vector3f offset;
+    Eigen::Vector3f gain;
+};
+
+/**
+ * @brief Soft and hard iron calibration utility.
+ *
+ * Fits a non-rotated ellipsoid to the calibration data and then derives the
+ * correction parameters.
+ *
+ * Reference:
+ * https://www.st.com/resource/en/design_tip/dt0059-ellipsoid-or-sphere-fitting-for-sensor-calibration-stmicroelectronics.pdf
+ *
+ * @tparam MaxSamples
+ */
+class SoftAndHardIronCalibration
+    : public AbstractCalibration<MagnetometerData, SoftAndHardIronCorrector>
+{
+public:
+    SoftAndHardIronCalibration();
+
+    bool feed(const MagnetometerData& data) override;
+
+    /**
+     * @brief Uses the recorded measurements to compute the correction
+     * parameters needed to correct sensor's data.
+     *
+     * Note: Feed at leas 9 measurements!
+     *
+     * @return SoftAndHardIronCorrector containing the correction parameters.
+     */
+    SoftAndHardIronCorrector computeResult() override;
+
+private:
+    Eigen::Matrix<float, 7, 7> D = Eigen::Matrix<float, 7, 7>::Zero();
+};
+
+void operator<<(Eigen::Matrix<float, 3, 2>& lhs,
+                const SoftAndHardIronCorrector& rhs);
+
+void operator<<(SoftAndHardIronCorrector& lhs,
+                const Eigen::Matrix<float, 3, 2>& rhs);
+
+void operator>>(const Eigen::Matrix<float, 3, 2>& lhs,
+                SoftAndHardIronCorrector& rhs);
+
+void operator>>(const SoftAndHardIronCorrector& lhs,
+                Eigen::Matrix<float, 3, 2>& rhs);
+
+}  // namespace Boardcore
diff --git a/src/shared/sensors/calibration/SoftIronCalibration.h b/src/shared/sensors/calibration/SoftIronCalibration.h
deleted file mode 100644
index 3b6dba4a9e4dc761798c71343191c7f68b994843..0000000000000000000000000000000000000000
--- a/src/shared/sensors/calibration/SoftIronCalibration.h
+++ /dev/null
@@ -1,194 +0,0 @@
-/* Copyright (c) 2021 Skyward Experimental Rocketry
- * Author: Riccardo Musso
- *
- * 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
-
-#include <sensors/SensorData.h>
-
-#include <Eigen/Core>
-#include <Eigen/Eigenvalues>
-
-#include "Calibration.h"
-
-namespace Boardcore
-{
-
-/*
- * The Soft-iron calibration removes the measurement error given by both the
- * Hard and Soft Iron distortion of the magnetic field.
- */
-class SoftIronCorrector : public ValuesCorrector<MagnetometerData>
-{
-public:
-    SoftIronCorrector() : SoftIronCorrector({1, 1, 1}, {0, 0, 0}) {}
-
-    SoftIronCorrector(const Eigen::Vector3f& _p, const Eigen::Vector3f& _q)
-        : p(_p), q(_q)
-    {
-    }
-
-    void setIdentity() override
-    {
-        p = {1, 1, 1};
-        q = {0, 0, 0};
-    }
-
-    void operator>>(Eigen::Matrix<float, 3, 2>& rhs)
-    {
-        rhs.col(0) = p.transpose();
-        rhs.col(1) = q.transpose();
-    }
-
-    void operator<<(const Eigen::Matrix<float, 3, 2>& rhs)
-    {
-        p = rhs.col(0).transpose();
-        q = rhs.col(1).transpose();
-    }
-
-    MagnetometerData correct(const MagnetometerData& input) const override
-    {
-        MagnetometerData output;
-        Eigen::Vector3f vec;
-
-        input >> vec;
-        vec = vec.cwiseProduct(p) + q;
-        output << vec;
-
-        return output;
-    }
-
-private:
-    Eigen::Vector3f p, q;
-};
-
-template <unsigned MaxSamples>
-class SoftIronCalibration
-    : public AbstractCalibrationModel<MagnetometerData, SoftIronCorrector>
-{
-public:
-    SoftIronCalibration() : samples(), numSamples(0) {}
-
-    bool feed(const MagnetometerData& data) override
-    {
-        if (numSamples >= MaxSamples)
-            return false;
-
-        Eigen::Vector3f vec;
-        data >> vec;
-
-        for (int i = 0; i < 3; ++i)
-        {
-            samples(numSamples, i)     = vec[i] * vec[i];
-            samples(numSamples, i + 3) = vec[i];
-        }
-        samples(numSamples, 6) = 1;
-
-        numSamples++;
-        return true;
-    }
-
-    SoftIronCorrector computeResult() override
-    {
-        using Mx   = Eigen::Matrix<float, 7, 7>;
-        using Vec7 = Eigen::Matrix<float, 7, 1>;
-
-        float minValue, det;
-        int minIdx;
-
-        Eigen::MatrixXf tmp1, tmp2;
-        Eigen::Vector3f p, q;
-        Vec7 vec;
-        Mx mat;
-
-        tmp1 = samples.block(0, 0, numSamples, 7);
-        tmp2 = tmp1.transpose();
-
-        // mat = tmp2 * tmp1;
-
-        /*
-         * tmp1: N x 7
-         * tmp2: 7 x N
-         *
-         * Big matrices multiplication: if done with Eigen, the program crashes
-         * (bus fault), so we'll do that with good old for's
-         */
-        for (unsigned i = 0; i < 7; ++i)
-        {
-            for (unsigned j = 0; j < 7; ++j)
-            {
-                mat(i, j) = 0;
-
-                for (unsigned k = 0; k < numSamples; ++k)
-                {
-                    mat(i, j) += tmp2(i, k) * tmp1(k, j);
-                }
-            }
-        }
-
-        Eigen::SelfAdjointEigenSolver<Mx> solver(mat);
-        auto eigenvalues = solver.eigenvalues();
-
-        minValue = eigenvalues[0];
-        minIdx   = 0;
-
-        for (int i = 1; i < eigenvalues.rows(); ++i)
-        {
-            if (minValue > eigenvalues[i])
-            {
-                minValue = eigenvalues[i];
-                minIdx   = i;
-            }
-        }
-
-        vec = solver.eigenvectors().col(minIdx);
-        det = vec[0] * vec[1] * vec[2];
-
-        if (det < 0)
-        {
-            vec *= -1;
-            det *= -1;
-        }
-
-        p = vec.block(0, 0, 3, 1) / cbrt(det);
-        q = {vec[3] / vec[0] / 2, vec[4] / vec[1] / 2, vec[5] / vec[2] / 2};
-
-        p[0] = sqrt(p[0]);
-        p[1] = sqrt(p[1]);
-        p[2] = sqrt(p[2]);
-
-        q = q.cwiseProduct(p);
-
-        return {p, q};
-    }
-
-    Eigen::Matrix<float, MaxSamples, 7> getSamples() { return samples; }
-
-private:
-    /*
-     * The matrix contains x, y, z, x^2, y^2, z^2 and a column of 1s
-     * row. Its shape is (N x 7)
-     */
-    Eigen::Matrix<float, MaxSamples, 7> samples;
-    unsigned numSamples;
-};
-
-}  // namespace Boardcore
diff --git a/src/shared/sensors/calibration/TwelveParameterCalibration.h b/src/shared/sensors/calibration/TwelveParameterCalibration.h
index 061fbbb25d61f7e76a4f1091a32f17aa00f10b9a..832eac3d4466512f365aeee270d0946915f7d184 100644
--- a/src/shared/sensors/calibration/TwelveParameterCalibration.h
+++ b/src/shared/sensors/calibration/TwelveParameterCalibration.h
@@ -97,8 +97,8 @@ private:
 
 template <typename T, unsigned MaxSamples>
 class TwelveParameterCalibration
-    : public AbstractCalibrationModel<T, TwelveParameterCorrector<T>,
-                                      AxisOrientation>
+    : public AbstractCalibration<T, TwelveParameterCorrector<T>,
+                                 AxisOrientation>
 {
 public:
     TwelveParameterCalibration() : samples(), numSamples(0), ref({1, 0, 0}) {}
diff --git a/src/shared/utils/aero/AeroUtils.cpp b/src/shared/utils/AeroUtils/AeroUtils.cpp
similarity index 76%
rename from src/shared/utils/aero/AeroUtils.cpp
rename to src/shared/utils/AeroUtils/AeroUtils.cpp
index 434236c180f0f7fe4bdfd2a55330d5ecab9887c9..c984cdfc247550036c4d88eef544fc4cf4d68663 100644
--- a/src/shared/utils/aero/AeroUtils.cpp
+++ b/src/shared/utils/AeroUtils/AeroUtils.cpp
@@ -22,15 +22,19 @@
 
 #include "AeroUtils.h"
 
+#include <utils/Constants.h>
+
+using namespace Eigen;
+
 namespace Boardcore
 {
 
-namespace aeroutils
+namespace Aeroutils
 {
 
 float relAltitude(float pressure, float pressureRef, float temperatureRef)
 {
-    using namespace constants;
+    using namespace Constants;
 
     return temperatureRef / a * (1 - powf(pressure / pressureRef, nInv));
 }
@@ -38,7 +42,7 @@ float relAltitude(float pressure, float pressureRef, float temperatureRef)
 float relDensity(float pressure, float pressureRef, float altitudeRef,
                  float temperatureRef)
 {
-    using namespace constants;
+    using namespace Constants;
 
     return pressure / (R * a * altitudeRef +
                        R * temperatureRef * powf(pressure / pressureRef, nInv));
@@ -46,7 +50,7 @@ float relDensity(float pressure, float pressureRef, float altitudeRef,
 
 float mslPressure(float pressureRef, float temperatureRef, float altitudeRef)
 {
-    using namespace constants;
+    using namespace Constants;
     float T0 = mslTemperature(temperatureRef, altitudeRef);
 
     return pressureRef / powf(1 - a * altitudeRef / T0, n);
@@ -54,16 +58,28 @@ float mslPressure(float pressureRef, float temperatureRef, float altitudeRef)
 
 float mslTemperature(float temperatureRef, float altitudeRef)
 {
-    return temperatureRef + (altitudeRef * constants::a);
+    return temperatureRef + (altitudeRef * Constants::a);
 }
 
 float verticalSpeed(float p, float dpDt, float pRef, float tRef)
 {
-    using namespace constants;
+    using namespace Constants;
 
     return -(tRef * dpDt * powf(p / pRef, nInv)) / (a * n * p);
 }
 
-}  // namespace aeroutils
+Vector2f geodetic2NED(const Vector2f& gpsData, const Vector2f& offset)
+{
+    float mPerDegLat = 111132.95225;
+    float mPerDegLon =
+        fabsf(111412.87733 * cosf(gpsData[0] * Constants::DEGREES_TO_RADIANS));
+
+    return {
+        mPerDegLat * (gpsData[0] - offset[0]),
+        mPerDegLon * (gpsData[1] - offset[1]),
+    };
+}
+
+}  // namespace Aeroutils
 
 }  // namespace Boardcore
diff --git a/src/shared/utils/aero/AeroUtils.h b/src/shared/utils/AeroUtils/AeroUtils.h
similarity index 73%
rename from src/shared/utils/aero/AeroUtils.h
rename to src/shared/utils/AeroUtils/AeroUtils.h
index 24f0d3edde0b4cf1a22a78410941decde2398e7a..80abb8dac650fc6054e359b34e65442bf1402d41 100644
--- a/src/shared/utils/aero/AeroUtils.h
+++ b/src/shared/utils/AeroUtils/AeroUtils.h
@@ -22,34 +22,18 @@
 
 #pragma once
 
+#include <Eigen/Core>
 #include <cmath>
 
 namespace Boardcore
 {
 
-namespace aeroutils
+namespace Aeroutils
 {
 
-namespace constants
-{
-
-// Troposphere temperature gradient [deg/m]
-constexpr float a = 0.0065f;
-
-// Acceleration of gravity [m^s^2]
-constexpr float g = 9.80665f;
-
-// Air gas constant [J/(Kg*K])]
-constexpr float R = 287.05f;
-
-constexpr float n    = g / (R * a);
-constexpr float nInv = (R * a) / g;
-
-}  // namespace constants
-
 /**
  * Returns the current altitude with respect to a reference altitude for the
- * given pressure, using International Standard Atmosphere  model.
+ * given pressure, using International Standard Atmosphere model.
  *
  * @warning This function is valid for altitudes below 11000 meters above sea
  * level
@@ -57,10 +41,10 @@ constexpr float nInv = (R * a) / g;
  * altitude. It does not provide altitude above mean sea level unless the
  * reference is, in fact, the sea level.
  *
- * @param pressure Current pressure [Pascal]
- * @param pressureRef Pressure at reference altitude (must be > 0) [Pascal]
- * @param temperatureRef Temperature at reference altitude [Kelvin]
- * @return Current altitude with respect to the reference altitude [meters]
+ * @param pressure Current pressure [Pas]
+ * @param pressureRef Pressure at reference altitude (must be > 0) [Pa]
+ * @param temperatureRef Temperature at reference altitude [K]
+ * @return Current altitude with respect to the reference altitude [m]
  */
 float relAltitude(float pressure, float pressureRef, float temperatureRef);
 
@@ -68,10 +52,10 @@ float relAltitude(float pressure, float pressureRef, float temperatureRef);
  * Returns the current air density with respect to a reference density and
  * temperature, using the Internation Standard Atmosphere model.
  *
- * @param pressure Current atmospheric pressure [Pascal]
- * @param pressureRef Pressure at reference altitude (must be > 0) [Pascal]
+ * @param pressure Current atmospheric pressure [Pa]
+ * @param pressureRef Pressure at reference altitude (must be > 0) [Pa]
  * @param altitudeRef Reference altitude [m]
- * @param temperatureRef Temperature at reference altitude [Kelvin]
+ * @param temperatureRef Temperature at reference altitude [K]
  * @return Current air density  [Kg/m^3]
  */
 float relDensity(float pressure, float pressureRef, float altitudeRef,
@@ -85,11 +69,10 @@ float relDensity(float pressure, float pressureRef, float altitudeRef,
  * @warning This function is valid for altitudes below 11000 meters above sea
  * level
  *
- * @param pressureRef Pressure at reference altitude [Pascal]
- * @param temperatureRef Temperature at reference altitude. Must be > 0
- * [Kelvin]
- * @param altitudeRef Reference altitude [meters]
- * @return Pressure at mean sea level [pascal]
+ * @param pressureRef Pressure at reference altitude [Pa]
+ * @param temperatureRef Temperature at reference altitude. Must be > 0 [K]
+ * @param altitudeRef Reference altitude [m]
+ * @return Pressure at mean sea level [Pa]
  */
 float mslPressure(float pressureRef, float temperatureRef, float altitudeRef);
 
@@ -100,9 +83,9 @@ float mslPressure(float pressureRef, float temperatureRef, float altitudeRef);
  * @warning This function is valid for altitudes below 11000 meters above sea
  * level
  *
- * @param temperatureRef Temperature at reference altitude [Kelvin]
- * @param altitudeRef Reference altitude [meters]
- * @return Temperature at mean sea level [Kelvin]
+ * @param temperatureRef Temperature at reference altitude [K]
+ * @param altitudeRef Reference altitude [m]
+ * @return Temperature at mean sea level [K]
  */
 float mslTemperature(float temperatureRef, float altitudeRef);
 
@@ -114,13 +97,24 @@ float mslTemperature(float temperatureRef, float altitudeRef);
  * level
  *
  * @param p Current pressure (must be > 0) [Pa]
- * @param dpDt [Rate of change of pressure [Pa/s]]
+ * @param dpDt Rate of change of pressure [Pa/s]
  * @param pRef Reference pressure (must be > 0) [Pa]
  * @param tRef Reference temperature [K]
  * @return Vertical speed, positive upwards [m/s]
  */
 float verticalSpeed(float p, float dpDt, float pRef, float tRef);
 
-}  // namespace aeroutils
+/**
+ * @brief Converts decimal degrees of latitude and longitude into displacement
+ * in meters between two positions the with an ellipsoidal earth model.
+ *
+ * @param position1 Latitude and longitude of current position [lat lon][deg]
+ * @param position2 Initial position used as an offset [lat lon][deg]
+ * @return Distance between the two coordinates [n e][m]
+ */
+Eigen::Vector2f geodetic2NED(const Eigen::Vector2f& position1,
+                             const Eigen::Vector2f& position2);
+
+}  // namespace Aeroutils
 
 }  // namespace Boardcore
diff --git a/src/shared/utils/ButtonHandler.h b/src/shared/utils/ButtonHandler.h
deleted file mode 100644
index c803f4201d12e87b05a4e859364e397abfe4a3ed..0000000000000000000000000000000000000000
--- a/src/shared/utils/ButtonHandler.h
+++ /dev/null
@@ -1,129 +0,0 @@
-/* Copyright (c) 2015-2018 Skyward Experimental Rocketry
- * Author: Luca Erbetta
- *
- * 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
-
-#include <diagnostic/PrintLogger.h>
-
-#include <functional>
-
-#include "ActiveObject.h"
-
-using miosix::Thread;
-
-namespace Boardcore
-{
-
-static const int TICK_LENGTH           = 100;  // milliseconds
-static const int LONG_PRESS_TICKS      = 10;
-static const int VERY_LONG_PRESS_TICKS = 50;
-
-enum class ButtonPress
-{
-    DOWN,      // Called as soon as the button is pressed
-    UP,        // Called when the button is released
-    SHORT,     // Short press
-    LONG,      // Long press
-    VERY_LONG  // Very long press
-};
-
-/**
- * @brief Class that detects if a button is pressed, long pressed or long-long
- * pressed and calls a callback in each case
- *
- * @tparam Button GPIO of the button
- */
-template <typename Button>
-class ButtonHandler : public ActiveObject
-{
-public:
-    using ButtonCallback = std::function<void(uint8_t, ButtonPress)>;
-
-    static bool isPressed() { return Button::value(); }
-
-    ButtonHandler(uint8_t btnId, ButtonCallback callback)
-        : btnId(btnId), callback(callback)
-    {
-        Button::mode(miosix::Mode::INPUT);
-    }
-
-    ~ButtonHandler() {}
-
-protected:
-    void run() override
-    {
-        while (!shouldStop() && callback)
-        {
-            if (Button::value())
-            {
-                if (!pressed && callback)
-                {
-                    callback(btnId, ButtonPress::DOWN);
-                }
-
-                pressedTicks++;
-                pressed = true;
-            }
-            else if (pressed)  // if the button was unpressed since
-                               // the last operation
-            {
-                if (pressedTicks >= VERY_LONG_PRESS_TICKS)
-                {
-                    LOG_DEBUG(logger, "Button pressed (very long) (%d ticks)",
-                              pressedTicks);
-
-                    callback(btnId, ButtonPress::VERY_LONG);
-                }
-                else if (pressedTicks >= LONG_PRESS_TICKS)
-                {
-                    LOG_DEBUG(logger, "Button pressed (long) (%d ticks)",
-                              pressedTicks);
-                    callback(btnId, ButtonPress::LONG);
-                }
-                else
-                {
-                    LOG_DEBUG(logger, "Button pressed (short) (%d ticks)",
-                              pressedTicks);
-                    callback(btnId, ButtonPress::SHORT);
-                }
-
-                callback(btnId, ButtonPress::UP);
-
-                pressed      = false;
-                pressedTicks = 0;
-            }
-
-            Thread::sleep(TICK_LENGTH);
-        }
-    }
-
-private:
-    uint8_t btnId    = 0;
-    bool pressed     = false;
-    int pressedTicks = 0;
-
-    ButtonCallback callback;
-
-    PrintLogger logger = Logging::getLogger("buttonhandler");
-};
-
-}  // namespace Boardcore
diff --git a/src/shared/utils/ButtonHandler/ButtonHandler.cpp b/src/shared/utils/ButtonHandler/ButtonHandler.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..56a0e5df40a5f5e5b2d12d4c3c80e2f0bfb00774
--- /dev/null
+++ b/src/shared/utils/ButtonHandler/ButtonHandler.cpp
@@ -0,0 +1,100 @@
+/* Copyright (c) 2022 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 "ButtonHandler.h"
+
+#include <functional>
+
+namespace Boardcore
+{
+
+bool ButtonHandler::registerButtonCallback(miosix::GpioPin pin,
+                                           ButtonCallback callback)
+{
+    // Try to insert the callback
+    auto result = callbacks.insert({pin, {callback, false, 0}});
+
+    // Check if the insertion took place
+    if (result.second)
+    {
+        return scheduler.addTask(
+            std::bind(&ButtonHandler::periodicButtonValueCheck, this, pin),
+            SAMPLE_PERIOD, TaskScheduler::Policy::SKIP);
+    }
+
+    return false;
+}
+
+bool ButtonHandler::start() { return scheduler.start(); }
+
+void ButtonHandler::stop() { scheduler.stop(); }
+
+ButtonHandler::ButtonHandler()
+{
+    // Start the scheduler immediately
+    scheduler.start();
+}
+
+void ButtonHandler::periodicButtonValueCheck(miosix::GpioPin pin)
+{
+    // Make sure the pin informations are still present
+    if (callbacks.find(pin) == callbacks.end())
+        return;
+
+    // Retrieve the pin information
+    const ButtonCallback &callback = std::get<0>(callbacks[pin]);
+    bool &wasPressed               = std::get<1>(callbacks[pin]);
+    int &pressedTicks              = std::get<2>(callbacks[pin]);
+
+    // Read the current button status
+    // Note: The button is assumed to be pressed if the pin value is low
+    // (pulldown)
+    bool isNowPressed = !pin.value();
+
+    if (isNowPressed)
+    {
+        // If the pin was not pressed before it has just been pressed
+        if (!wasPressed && callback)
+            callback(ButtonEvent::PRESSED);
+
+        // Increment the tick
+        pressedTicks++;
+    }
+    // If the button was pressed before and just released
+    else if (wasPressed)
+    {
+        if (pressedTicks >= VERY_LONG_PRESS_TICKS)
+            callback(ButtonEvent::VERY_LONG_PRESS);
+        else if (pressedTicks >= LONG_PRESS_TICKS)
+            callback(ButtonEvent::LONG_PRESS);
+        else
+            callback(ButtonEvent::SHORT_PRESS);
+
+        // Reset the ticks
+        pressedTicks = 0;
+    }
+
+    // Save the current button status
+    wasPressed = isNowPressed;
+}
+
+}  // namespace Boardcore
diff --git a/src/shared/utils/ButtonHandler/ButtonHandler.h b/src/shared/utils/ButtonHandler/ButtonHandler.h
new file mode 100644
index 0000000000000000000000000000000000000000..2ff97a9beb9e4acb7e6e98c45ee88550fa3deddf
--- /dev/null
+++ b/src/shared/utils/ButtonHandler/ButtonHandler.h
@@ -0,0 +1,140 @@
+/* Copyright (c) 2015-2022 Skyward Experimental Rocketry
+ * Authors: Luca Erbetta, 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
+
+#include <Singleton.h>
+#include <miosix.h>
+#include <scheduler/TaskScheduler.h>
+
+#include <map>
+
+namespace Boardcore
+{
+
+enum class ButtonEvent
+{
+    PRESSED,         // Called as soon as the button is pressed
+    SHORT_PRESS,     // Called as soon as the button is released
+    LONG_PRESS,      // Called as soon as the button is released
+    VERY_LONG_PRESS  // Called as soon as the button is released
+};
+
+/**
+ * @brief Comparison operator between GpioPins used for std::map.
+ */
+struct GpioPinCompare
+{
+    bool operator()(const miosix::GpioPin& lhs,
+                    const miosix::GpioPin& rhs) const
+    {
+        if (lhs.getPort() == rhs.getPort())
+            return lhs.getNumber() < rhs.getNumber();
+        return lhs.getPort() < rhs.getPort();
+    }
+};
+
+/**
+ * @brief Utility to detects if buttons are pressed, long pressed or long-long
+ * pressed and calls a callback in each case
+ *
+ * Note: The ButtonHandler assumes the all the buttons to be pulldown meaning
+ * that when the button is pressed, the pin is assumed low.
+ *
+ * TODO: Allow to set pullup or pulldown configuration for each registered pin.
+ */
+class ButtonHandler : public Singleton<ButtonHandler>
+{
+    friend Singleton<ButtonHandler>;
+
+    static constexpr uint32_t SAMPLE_PERIOD    = 100;  // 10Hz
+    static constexpr int LONG_PRESS_TICKS      = 10;   // 1s
+    static constexpr int VERY_LONG_PRESS_TICKS = 50;   // 5s
+
+public:
+    using ButtonCallback = std::function<void(ButtonEvent)>;
+
+    /**
+     * @brief Registers a callback on the specified pin.
+     *
+     * @param pin Pin to listen to.
+     * @param callback Function to call on button events.
+     * @return False if another callback was already registered for the pin.
+     */
+    bool registerButtonCallback(miosix::GpioPin pin, ButtonCallback callback);
+
+    /**
+     * @brief Unregisters the callback associated with the specified pin, if
+     * any.
+     *
+     * @param pin Pin whose callback function is to be removed.
+     * @return True if a callback was present and removed for the given pin.
+     */
+    bool unregisterButtonCallback(miosix::GpioPin pin);
+
+    /**
+     * @brief Starts the ButtonHandler's task scheduler.
+     *
+     * Note that the scheduler is started as soon as the ButtonHandler is first
+     * used.
+     *
+     * @return Whether the task scheduler was started or not.
+     */
+    bool start();
+
+    /**
+     * @brief Stops the ButtonHandler's task scheduler.
+     */
+    void stop();
+
+private:
+    ButtonHandler();
+
+    /**
+     * @brief This function is added to the scheduler for every pin registered
+     * in the ButtonHandler.
+     *
+     * @param pin Pin whose value need to be checked.
+     */
+    void periodicButtonValueCheck(miosix::GpioPin pin);
+
+    TaskScheduler scheduler;
+
+    /**
+     * @brief Map of all the callbacks registered in the ButtonHandler.
+     *
+     * The key is the GpioPin for which the callback is registered. To used
+     * GpioPin as a map key, the GpioPinCompare operator was defined as
+     * explained here:
+     * https://stackoverflow.com/questions/1102392/how-can-i-use-stdmaps-with-user-defined-types-as-key
+     *
+     * The type stored is a tuple containing:
+     * - The button callback function;
+     * - Whether or not the button was pressed in the last check iteration;
+     * - The relative tick of the last pin value change.
+     */
+    std::map<miosix::GpioPin, std::tuple<ButtonCallback, bool, int>,
+             GpioPinCompare>
+        callbacks;
+};
+
+}  // namespace Boardcore
diff --git a/src/shared/utils/CSVReader/CSVReader.h b/src/shared/utils/CSVReader/CSVReader.h
index 32388292c5890d88e3fa43182e049ac89ddaa477..a8f59a5efae736e8de7fb31193163d7fb4964f16 100644
--- a/src/shared/utils/CSVReader/CSVReader.h
+++ b/src/shared/utils/CSVReader/CSVReader.h
@@ -123,6 +123,9 @@ private:
 /**
  * @brief Iterable parser of CSV files.
  *
+ * If the CSV file has an header row, you must specify true as the second
+ * parameter in the constructor.
+ *
  * Given the file name, reads the contents as elements of type Data. Can be used
  * with CSVIterator to iterate through all the CSV rows.
  * You can retrieve all data inside the file as a vector with collect().
@@ -134,7 +137,14 @@ template <typename Data>
 class CSVParser
 {
 public:
-    CSVParser(const char* fileName) : fileStream(fileName) {}
+    CSVParser(const char* fileName, bool hasHeader = false)
+        : fileStream(fileName)
+    {
+        // If the file has the header, ignore everithing in the first line
+        if (hasHeader)
+            fileStream.ignore(std::numeric_limits<std::streamsize>::max(),
+                              '\n');
+    }
 
     ~CSVParser() { fileStream.close(); }
 
diff --git a/src/shared/utils/ClockUtils.h b/src/shared/utils/ClockUtils.h
index 6e7c449b53e0cf8a2e0098fce331c331f900cb4c..3d671d9f48311aae9bae40d24b5f022e6d9d8e8e 100644
--- a/src/shared/utils/ClockUtils.h
+++ b/src/shared/utils/ClockUtils.h
@@ -341,6 +341,8 @@ inline bool ClockUtils::enablePeripheralClock(void* peripheral)
             return false;
     }
 
+    RCC_SYNC();
+
     return true;
 }
 
@@ -575,6 +577,8 @@ inline bool ClockUtils::disablePeripheralClock(void* peripheral)
             return false;
     }
 
+    RCC_SYNC();
+
     return true;
 }
 
diff --git a/src/shared/utils/Constants.h b/src/shared/utils/Constants.h
index a2f8ca35922c623794e83229466daa013344b856..ddc1e810901159f6b92d95041a8a3606dd8ef939 100644
--- a/src/shared/utils/Constants.h
+++ b/src/shared/utils/Constants.h
@@ -25,13 +25,23 @@
 namespace Boardcore
 {
 
-static constexpr const float PI                 = 3.14159265f;
-static constexpr const float EARTH_GRAVITY      = 9.80665f;
-static constexpr const float EARTH_RADIUS       = 6371.0f * 1000.0f;  // [m]
+namespace Constants
+{
+
+static constexpr const float PI                 = 3.14159265f;  // [rad]
 static constexpr const float DEGREES_TO_RADIANS = PI / 180.0f;
 static constexpr const float RADIANS_TO_DEGREES = 180.0f / PI;
-static constexpr const float KNOTS_TO_MPS       = 0.514444;
-static constexpr const float MSL_PRESSURE       = 101325.0f;  // [Pa]
-static constexpr const float MSL_TEMPERATURE    = 288.15f;    // [Kelvin]
+
+static constexpr const float g = 9.80665f;  // [m^s^2]
+
+static constexpr float a = 0.0065f;  // Troposphere temperature gradient [deg/m]
+static constexpr float R = 287.05f;  // Air gas constant [J/Kg/K]
+static constexpr float n = g / (R * a);
+static constexpr float nInv = (R * a) / g;
+
+static constexpr const float MSL_PRESSURE    = 101325.0f;  // [Pa]
+static constexpr const float MSL_TEMPERATURE = 288.15f;    // [Kelvin]
+
+}  // namespace Constants
 
 }  // namespace Boardcore
diff --git a/src/shared/utils/MovingAverage.h b/src/shared/utils/MovingAverage.h
index 94e8fda20d5577c21c9de585a6f3081547c2a0e3..6a6efd1bbcf6790efa89a17919146cb6bc770b37 100644
--- a/src/shared/utils/MovingAverage.h
+++ b/src/shared/utils/MovingAverage.h
@@ -45,12 +45,21 @@ public:
         value += newValue * movingAverageCoeff;
     }
 
+    void reset() { value = 0; }
+
+    void setN(int movingAverageN)
+    {
+        this->movingAverageN   = movingAverageN;
+        movingAverageCoeff     = 1 / (float)movingAverageN;
+        movingAverageCompCoeff = 1 - movingAverageCoeff;
+    }
+
 private:
     T value = 0;
 
-    const int movingAverageN           = 20;
-    const float movingAverageCoeff     = 1 / (float)movingAverageN;
-    const float movingAverageCompCoeff = 1 - movingAverageCoeff;
+    int movingAverageN           = 20;
+    float movingAverageCoeff     = 1 / (float)movingAverageN;
+    float movingAverageCompCoeff = 1 - movingAverageCoeff;
 };
 
 }  // namespace Boardcore
diff --git a/src/shared/math/SkyQuaternion.cpp b/src/shared/utils/SkyQuaternion/SkyQuaternion.cpp
similarity index 51%
rename from src/shared/math/SkyQuaternion.cpp
rename to src/shared/utils/SkyQuaternion/SkyQuaternion.cpp
index d8f678341d333d84a30f2bdf127ecf8b57a2b79c..1af74a6b622c2c287b4e602167ac5d0d0e1335af 100644
--- a/src/shared/math/SkyQuaternion.cpp
+++ b/src/shared/utils/SkyQuaternion/SkyQuaternion.cpp
@@ -1,5 +1,5 @@
-/* Copyright (c) 2020 Skyward Experimental Rocketry
- * Author: Marco Cella
+/* Copyright (c) 2022 Skyward Experimental Rocketry
+ * Authors: Marco Cella, 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
@@ -32,13 +32,15 @@ using namespace Eigen;
 namespace Boardcore
 {
 
-SkyQuaternion::SkyQuaternion() {}
+namespace SkyQuaternion
+{
 
-Vector4f SkyQuaternion::eul2quat(Vector3f degeul)  // ZYX rotation
+Vector4f eul2quat(Vector3f euler)
 {
-    float yaw   = degeul(0) * DEGREES_TO_RADIANS;
-    float pitch = degeul(1) * DEGREES_TO_RADIANS;
-    float roll  = degeul(2) * DEGREES_TO_RADIANS;
+    // Euler angles are ZYX
+    float yaw   = euler(0) * Constants::DEGREES_TO_RADIANS;
+    float pitch = euler(1) * Constants::DEGREES_TO_RADIANS;
+    float roll  = euler(2) * Constants::DEGREES_TO_RADIANS;
 
     float cyaw   = cosf(yaw * 0.5F);
     float syaw   = sinf(yaw * 0.5F);
@@ -52,52 +54,45 @@ Vector4f SkyQuaternion::eul2quat(Vector3f degeul)  // ZYX rotation
     float qz = syaw * cpitch * croll - cyaw * spitch * sroll;
     float qw = cyaw * cpitch * croll + syaw * spitch * sroll;
 
-    Vector4f quat(qx, qy, qz, qw);
-
-    return quat;
+    return Vector4f(qx, qy, qz, qw);
 }
 
-Vector3f SkyQuaternion::quat2eul(Vector4f quater)
+Vector3f quat2eul(Vector4f quat)
 {
-    float qx = quater(0);
-    float qy = quater(1);
-    float qz = quater(2);
-    float qw = quater(3);
+    // Euler angles are ZYX
+    float qx = quat(0);
+    float qy = quat(1);
+    float qz = quat(2);
+    float qw = quat(3);
 
     float yaw = atan2f(2.0F * (qx * qy + qw * qz),
                        powf(qw, 2) + powf(qx, 2) - powf(qy, 2) - powf(qz, 2));
 
     float a = -2.0F * (qx * qz - qw * qy);
     if (a > 1.0F)
-    {
         a = 1.0F;
-    }
     else if (a < -1.0F)
-    {
         a = -1.0F;
-    }
 
     float pitch = asinf(a);
 
     float roll = atan2f(2.0F * (qy * qz + qw * qx),
                         powf(qw, 2) - powf(qx, 2) - powf(qy, 2) + powf(qz, 2));
 
-    Vector3f eul(roll, pitch, yaw);
-
-    return eul * 180.0F / PI;
+    return Vector3f(roll, pitch, yaw) * Constants::RADIANS_TO_DEGREES;
 }
 
-Vector4f SkyQuaternion::rotm2quat(Matrix3f R)
+Vector4f rotationMatrix2quat(Matrix3f rtm)
 {
-    float r11 = R(0, 0);
-    float r12 = R(0, 1);
-    float r13 = R(0, 2);
-    float r21 = R(1, 0);
-    float r22 = R(1, 1);
-    float r23 = R(1, 2);
-    float r31 = R(2, 0);
-    float r32 = R(2, 1);
-    float r33 = R(2, 2);
+    float r11 = rtm(0, 0);
+    float r12 = rtm(0, 1);
+    float r13 = rtm(0, 2);
+    float r21 = rtm(1, 0);
+    float r22 = rtm(1, 1);
+    float r23 = rtm(1, 2);
+    float r31 = rtm(2, 0);
+    float r32 = rtm(2, 1);
+    float r33 = rtm(2, 2);
 
     float qx;
     float qy;
@@ -105,63 +100,63 @@ Vector4f SkyQuaternion::rotm2quat(Matrix3f R)
     float qw;
 
     float tr = r11 + r22 + r33;
-    float r  = sqrt(1 + tr);
 
-    if (r32 - r23 > 0)
-    {
-        qx = 0.5 * sqrt(1 + r11 - r22 - r33);
-    }
-    else if (r32 - r23 < 0)
+    if (tr > 0)
     {
-        qx = -0.5 * sqrt(1 + r11 - r22 - r33);
-    }
-    else
-    {
-        qx = 0;
-    }
+        float sqtrp1 = sqrt(1 + tr);
 
-    if (r13 - r31 > 0)
-    {
-        qy = 0.5 * sqrt(1 - r11 + r22 - r33);
-    }
-    else if (r13 - r31 < 0)
-    {
-        qy = -0.5 * sqrt(1 - r11 + r22 - r33);
+        qx = (r23 - r32) / (2.0 * sqtrp1);
+        qy = (r31 - r13) / (2.0 * sqtrp1);
+        qz = (r12 - r21) / (2.0 * sqtrp1);
+        qw = 0.5 * sqtrp1;
     }
     else
     {
-        qy = 0;
-    }
+        if ((r22 > r11) && (r22 > r33))
+        {
+            float sqdip1 = sqrt(r22 - r11 - r33 + 1.0);
 
-    if (r21 - r12 > 0)
-    {
-        qz = 0.5 * sqrt(1 - r11 - r22 + r33);
-    }
-    else if (r21 - r12 < 0)
-    {
-        qz = -0.5 * sqrt(1 - r11 - r22 + r33);
-    }
-    else
-    {
-        qz = 0;
-    }
+            qy = 0.5 * sqdip1;
 
-    qw = 0.5 * r;
+            if (sqdip1 != 0)
+                sqdip1 = 0.5 / sqdip1;
 
-    if (qw < 0)
-    {
-        qx = -qx;
-        qy = -qy;
-        qz = -qz;
-        qw = -qw;
-    }
+            qx = (r12 + r21) * sqdip1;
+            qz = (r23 + r32) * sqdip1;
+            qw = (r31 - r13) * sqdip1;
+        }
+        else if (r33 > r11)
+        {
+            float sqdip1 = sqrt(r33 - r11 - r22 + 1.0);
+
+            qz = 0.5 * sqdip1;
+
+            if (sqdip1 != 0)
+                sqdip1 = 0.5 / sqdip1;
+
+            qx = (r31 + r13) * sqdip1;
+            qy = (r23 + r32) * sqdip1;
+            qw = (r12 - r21) * sqdip1;
+        }
+        else
+        {
+            float sqdip1 = sqrt(r11 - r22 - r33 + 1.0);
 
-    Vector4f quat(qx, qy, qz, qw);
+            qx = 0.5 * sqdip1;
 
-    return quat / quat.norm();
+            if (sqdip1 != 0)
+                sqdip1 = 0.5 / sqdip1;
+
+            qy = (r12 + r21) * sqdip1;
+            qz = (r31 + r13) * sqdip1;
+            qw = (r23 - r32) * sqdip1;
+        }
+    }
+
+    return Vector4f(qx, qy, qz, qw);
 }
 
-bool SkyQuaternion::quatnormalize(Vector4f& quat)
+bool quatNormalize(Vector4f& quat)
 {
     float den = sqrt(powf(quat(0), 2) + powf(quat(1), 2) + powf(quat(2), 2) +
                      powf(quat(3), 2));
@@ -173,28 +168,21 @@ bool SkyQuaternion::quatnormalize(Vector4f& quat)
     return true;
 }
 
-Vector4f SkyQuaternion::quatProd(const Vector4f q1, const Vector4f q2)
+Vector4f quatProd(const Vector4f q1, const Vector4f q2)
 {
-    float q1x = q1(0);
-    float q1y = q1(1);
-    float q1z = q1(2);
-    Vector3f qv1(q1x, q1y, q1z);
-    float q1w = q1(3);
-
-    float q2x = q2(0);
-    float q2y = q2(1);
-    float q2z = q2(2);
-    Vector3f qv2(q2x, q2y, q2z);
-    float q2w = q2(3);
+    Vector3f qv1 = q1.head<3>();
+    float qs1    = q1(3);
+    Vector3f qv2 = q2.head<3>();
+    float qs2    = q2(3);
 
     Vector4f quater;
     // cppcheck-suppress constStatement
-    quater << q1w * qv2 + q2w * qv1 - qv1.cross(qv2), q1w * q2w - qv1.dot(qv2);
-    float quaternionNorm = sqrt(quater(0) * quater(0) + quater(1) * quater(1) +
-                                quater(2) * quater(2) + quater(3) * quater(3));
-    quater               = quater / quaternionNorm;
+    quater << qs1 * qv2 + qs2 * qv1 - qv1.cross(qv2), qs1 * qs2 - qv1.dot(qv2);
+    quater.normalize();
 
     return quater;
 }
 
+}  // namespace SkyQuaternion
+
 }  // namespace Boardcore
diff --git a/src/shared/utils/SkyQuaternion/SkyQuaternion.h b/src/shared/utils/SkyQuaternion/SkyQuaternion.h
new file mode 100644
index 0000000000000000000000000000000000000000..fecab90dcc1aa9e9541bc3ac8de59470a354ac18
--- /dev/null
+++ b/src/shared/utils/SkyQuaternion/SkyQuaternion.h
@@ -0,0 +1,81 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Marco Cella
+ *
+ * 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
+
+#include <Eigen/Dense>
+
+namespace Boardcore
+{
+
+/**
+ * @brief Functions for managing quaternions.
+ *
+ * Convention used: x, y, z, w (scalar element as last element)
+ */
+namespace SkyQuaternion
+{
+
+/**
+ * @brief Transform a vector of euler angles (ZYX) to quaternion.
+ *
+ * @param euler The vector of euler angles to be transformed [deg]
+ * @return Transformed quaternion [x, y, z, w]
+ */
+Eigen::Vector4f eul2quat(Eigen::Vector3f euler);
+
+/**
+ * @brief Transform a quaternion to a vector of euler angles (ZYX).
+ *
+ * @param quat The quaternion to be transformed [x, y, z, w]
+ * @return Transformed vector of euler angles [deg]
+ */
+Eigen::Vector3f quat2eul(Eigen::Vector4f quat);
+
+/**
+ * @brief Transform a rotation matrix to a quaternion.
+ *
+ * @param rtm The rotation matrix to be transformed (3x3)
+ * @return Transformed quaternion [x, y, z, w]
+ */
+Eigen::Vector4f rotationMatrix2quat(Eigen::Matrix3f rtm);
+
+/**
+ * @brief Normalize a quaternion.
+ *
+ * @param quat The quaternion to be normalized [x, y, z, w]
+ * @return True if the operation succeeded or not
+ */
+bool quatNormalize(Eigen::Vector4f& quat);
+
+/**
+ * @brief Compute the product of two quaternions.
+ *
+ * @param q1 First factor [x, y, z, w]
+ * @param q2 Second factor [x, y, z, w]
+ * @return The resulting quaternions product [x, y, z, w]
+ */
+Eigen::Vector4f quatProd(const Eigen::Vector4f q1, const Eigen::Vector4f q2);
+
+};  // namespace SkyQuaternion
+
+}  // namespace Boardcore
diff --git a/src/shared/math/Stats.cpp b/src/shared/utils/Stats/Stats.cpp
similarity index 100%
rename from src/shared/math/Stats.cpp
rename to src/shared/utils/Stats/Stats.cpp
diff --git a/src/shared/math/Stats.h b/src/shared/utils/Stats/Stats.h
similarity index 100%
rename from src/shared/math/Stats.h
rename to src/shared/utils/Stats/Stats.h
diff --git a/src/shared/utils/testutils/FakeSpiTypedef.h b/src/shared/utils/TestUtils/FakeSpiTypedef.h
similarity index 98%
rename from src/shared/utils/testutils/FakeSpiTypedef.h
rename to src/shared/utils/TestUtils/FakeSpiTypedef.h
index 46b770c44e3b82eb406d54e5718aac248b2b9b61..dae48554ab5c4ac85e0d6e642f7e723a77b48be0 100644
--- a/src/shared/utils/testutils/FakeSpiTypedef.h
+++ b/src/shared/utils/TestUtils/FakeSpiTypedef.h
@@ -22,7 +22,7 @@
 
 #pragma once
 #include <miosix.h>
-#include <utils/testutils/MockGpioPin.h>
+#include <utils/TestUtils/MockGpioPin.h>
 
 #include <cstdint>
 #include <vector>
diff --git a/src/shared/utils/testutils/MockGpioPin.h b/src/shared/utils/TestUtils/MockGpioPin.h
similarity index 100%
rename from src/shared/utils/testutils/MockGpioPin.h
rename to src/shared/utils/TestUtils/MockGpioPin.h
diff --git a/src/shared/utils/testutils/MockSPIBus.h b/src/shared/utils/TestUtils/MockSPIBus.h
similarity index 98%
rename from src/shared/utils/testutils/MockSPIBus.h
rename to src/shared/utils/TestUtils/MockSPIBus.h
index 9bdc6129e9e2c13f547fe2bada8c1259738d8b71..2fdf6a948ac106fb0fd09338f850c8ef186d2ca4 100644
--- a/src/shared/utils/testutils/MockSPIBus.h
+++ b/src/shared/utils/TestUtils/MockSPIBus.h
@@ -57,7 +57,7 @@ namespace Boardcore
     "SpiBusInterface must be built using MockGpioPin (-DUSE_MOCK_PERIPHERALS)"
 #endif
 
-#include <utils/testutils/MockGpioPin.h>
+#include <utils/TestUtils/MockGpioPin.h>
 
 using miosix::FastMutex;
 using miosix::Lock;
@@ -69,10 +69,10 @@ public:
     ~MockSPIBus() {}
 
     // Delete copy/move contructors/operators
-    MockSPIBus(const MockSPIBus&) = delete;
+    MockSPIBus(const MockSPIBus&)            = delete;
     MockSPIBus& operator=(const MockSPIBus&) = delete;
 
-    MockSPIBus(MockSPIBus&&) = delete;
+    MockSPIBus(MockSPIBus&&)            = delete;
     MockSPIBus& operator=(MockSPIBus&&) = delete;
 
     /**
diff --git a/src/shared/utils/testutils/TestHelper.cpp b/src/shared/utils/TestUtils/TestHelper.cpp
similarity index 100%
rename from src/shared/utils/testutils/TestHelper.cpp
rename to src/shared/utils/TestUtils/TestHelper.cpp
diff --git a/src/shared/utils/testutils/TestHelper.h b/src/shared/utils/TestUtils/TestHelper.h
similarity index 96%
rename from src/shared/utils/testutils/TestHelper.h
rename to src/shared/utils/TestUtils/TestHelper.h
index 660838fc04fcf103dd581f828e80b99443e6fe23..2e7592f4273969efca7d3ea6a4d38c70b7d02695 100644
--- a/src/shared/utils/testutils/TestHelper.h
+++ b/src/shared/utils/TestUtils/TestHelper.h
@@ -98,7 +98,7 @@ bool testFSMTransition(FSM_type& fsm, const Event& ev,
 template <class FSM_type>
 bool testFSMAsyncTransition(FSM_type& fsm, const Event& ev, uint8_t topic,
                             void (FSM_type::*expectedState)(const Event&),
-                            EventBroker& broker = sEventBroker)
+                            EventBroker& broker = EventBroker::getInstance())
 {
     broker.post(ev, topic);
     // Wait for the event to be handled
@@ -154,7 +154,7 @@ bool testHSMTransition(HSM_type& hsm, const Event& ev,
 template <class HSM_type>
 bool testHSMAsyncTransition(HSM_type& hsm, const Event& ev, uint8_t topic,
                             State (HSM_type::*expectedState)(const Event&),
-                            EventBroker& broker = sEventBroker)
+                            EventBroker& broker = EventBroker::getInstance())
 {
     broker.post(ev, topic);
     // Wait for the event to be handled
@@ -183,7 +183,7 @@ bool testHSMAsyncTransition(HSM_type& hsm, const Event& ev, uint8_t topic,
  */
 bool expectEvent(uint8_t eventId, uint8_t topic, long long when,
                  long long uncertainty = EVENT_TIMING_UNCERTAINTY,
-                 EventBroker& broker   = sEventBroker);
+                 EventBroker& broker   = EventBroker::getInstance());
 
 /**
  * @brief Waits until the specified event is received or a timeout expires
@@ -197,6 +197,6 @@ bool expectEvent(uint8_t eventId, uint8_t topic, long long when,
  *         false    if the timeout has expired
  */
 bool waitForEvent(uint8_t event, uint8_t topic, long long timeout = 0,
-                  EventBroker& broker = sEventBroker);
+                  EventBroker& broker = EventBroker::getInstance());
 
 }  // namespace Boardcore
diff --git a/src/shared/utils/testutils/TestSensor.h b/src/shared/utils/TestUtils/TestSensor.h
similarity index 83%
rename from src/shared/utils/testutils/TestSensor.h
rename to src/shared/utils/TestUtils/TestSensor.h
index 09cc692d12cd7d08446a9567110de3bb68cc2e18..07b19e98b8c415c4f98e4388d1e42b02dec8414b 100644
--- a/src/shared/utils/testutils/TestSensor.h
+++ b/src/shared/utils/TestUtils/TestSensor.h
@@ -29,9 +29,6 @@
 
 #include <cmath>
 
-using miosix::getTick;
-using miosix::TICK_FREQ;
-
 namespace Boardcore
 {
 
@@ -40,11 +37,14 @@ struct TestData : public TimestampData
     float value;
 
     TestData(float v)
-        : TimestampData{static_cast<uint64_t>(getTick())}, value(v)
+        : TimestampData{static_cast<uint64_t>(miosix::getTick())}, value(v)
     {
     }
 
-    TestData() : TimestampData{static_cast<uint64_t>(getTick())}, value(0.0) {}
+    TestData()
+        : TimestampData{static_cast<uint64_t>(miosix::getTick())}, value(0.0)
+    {
+    }
 };
 
 class TestSensor : public Sensor<TestData>
@@ -60,8 +60,9 @@ public:
     TestData sampleImpl() override
     {
         TRACE("[TestSensor] sampleImpl() \n");
-        return TestData(10 * sin(PI * static_cast<float>(getTick()) /
-                                 static_cast<float>(TICK_FREQ)));
+        return TestData(
+            10 * sin(Constants::PI * static_cast<float>(miosix::getTick()) /
+                     static_cast<float>(miosix::TICK_FREQ)));
     }
 };
 
diff --git a/src/shared/utils/testutils/ThroughputCalculator.h b/src/shared/utils/TestUtils/ThroughputCalculator.h
similarity index 100%
rename from src/shared/utils/testutils/ThroughputCalculator.h
rename to src/shared/utils/TestUtils/ThroughputCalculator.h
diff --git a/src/shared/utils/collections/SyncQueue.h b/src/shared/utils/collections/SyncQueue.h
index c1b4ccc9a38b4bc2e6ae0d34fd375f5dd0b1eaf2..5f340bba650b6e8f5238e4cc8b8be02ef7c070ef 100644
--- a/src/shared/utils/collections/SyncQueue.h
+++ b/src/shared/utils/collections/SyncQueue.h
@@ -39,7 +39,7 @@ public:
     int len();
 
 private:
-    SynchronizedQueue(const SynchronizedQueue&) = delete;
+    SynchronizedQueue(const SynchronizedQueue&)            = delete;
     SynchronizedQueue& operator=(const SynchronizedQueue&) = delete;
     std::list<T> queue;
     miosix::Mutex mMutex;
diff --git a/src/shared/utils/collections/contiguous_queue.h b/src/shared/utils/collections/contiguous_queue.h
index 039482f1516f9b29505973222a6275f6a6b46044..bc63a664d11bb5240eda743fe7750f340ea1cb18 100644
--- a/src/shared/utils/collections/contiguous_queue.h
+++ b/src/shared/utils/collections/contiguous_queue.h
@@ -128,7 +128,7 @@ public:
     }
 
 private:
-    ContiguousQueue(const ContiguousQueue&) = delete;
+    ContiguousQueue(const ContiguousQueue&)            = delete;
     ContiguousQueue& operator=(const ContiguousQueue&) = delete;
 
     T elements[N]     = {0};
diff --git a/src/shared/utils/gui/NavController.h b/src/shared/utils/gui/NavController.h
index 528692b3342b3f807465d64099345eb356fea937..617af98d72b4cf4914a08b2a9098e2188ae830cd 100644
--- a/src/shared/utils/gui/NavController.h
+++ b/src/shared/utils/gui/NavController.h
@@ -23,7 +23,7 @@
 #pragma once
 
 #include <diagnostic/PrintLogger.h>
-#include <utils/ButtonHandler.h>
+#include <utils/ButtonHandler/ButtonHandler.h>
 #include <utils/Debug.h>
 
 #include <vector>
@@ -66,43 +66,37 @@ public:
         }
     }
 
-    void onButtonPress(ButtonPress press)
+    void onButtonEvent(ButtonEvent press)
     {
         switch (press)
         {
-            case ButtonPress::SHORT:
-                if (vecSelectable.size() > 0)
-                {
-                    selectNext();
-                }
-                break;
-            case ButtonPress::DOWN:
+            case ButtonEvent::PRESSED:
                 if (selectedIndex < vecSelectable.size())
-                {
                     vecSelectable.at(selectedIndex)
                         ->performInteraction(Interaction::BTN_DOWN);
-                }
+                if (vecSelectable.size() > 0)
+                    selectNext();
                 break;
-            case ButtonPress::UP:
+            case ButtonEvent::SHORT_PRESS:
                 if (selectedIndex < vecSelectable.size())
-                {
                     vecSelectable.at(selectedIndex)
                         ->performInteraction(Interaction::BTN_UP);
-                }
+                if (vecSelectable.size() > 0)
+                    selectNext();
                 break;
-            case ButtonPress::LONG:
+            case ButtonEvent::LONG_PRESS:
                 if (selectedIndex < vecSelectable.size())
-                {
                     vecSelectable.at(selectedIndex)
                         ->performInteraction(Interaction::CLICK);
-                }
+                if (vecSelectable.size() > 0)
+                    selectNext();
                 break;
-            case ButtonPress::VERY_LONG:
+            case ButtonEvent::VERY_LONG_PRESS:
                 if (selectedIndex < vecSelectable.size())
-                {
                     vecSelectable.at(selectedIndex)
                         ->performInteraction(Interaction::LONG_CLICK);
-                }
+                if (vecSelectable.size() > 0)
+                    selectNext();
                 break;
             default:
                 break;
diff --git a/src/shared/utils/gui/ScreenManager.h b/src/shared/utils/gui/ScreenManager.h
index 4553fed13ea7126b924a1d4c9d9df008c3957092..ce731f437f638bdf904b44e0f5d1fb89ff11a69b 100644
--- a/src/shared/utils/gui/ScreenManager.h
+++ b/src/shared/utils/gui/ScreenManager.h
@@ -65,7 +65,7 @@ public:
         }
     }
 
-    void onButtonPress(ButtonPress press) { controller.onButtonPress(press); }
+    void onButtonEvent(ButtonEvent press) { controller.onButtonEvent(press); }
 
     mxgui::DrawingContext& getDrawingContext() { return dc; }
 
@@ -86,7 +86,7 @@ protected:
 
             drawViewTree(screens[activeScreen], dc);
 
-            Thread::sleepUntil(start + refreshInterval);
+            miosix::Thread::sleepUntil(start + refreshInterval);
         }
     }
 
diff --git a/src/tests/drivers/test-servo.cpp b/src/tests/actuators/test-buzzer.cpp
similarity index 55%
rename from src/tests/drivers/test-servo.cpp
rename to src/tests/actuators/test-buzzer.cpp
index 10eaf9c81ba9e43dbdcfdc7c9db94e9b12ec7801..fc6f6fc6bafecae80a801f91b1115223891c31f7 100644
--- a/src/tests/drivers/test-servo.cpp
+++ b/src/tests/actuators/test-buzzer.cpp
@@ -1,5 +1,5 @@
-/* Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Luca Erbetta, Alberto Nidasio
+/* Copyright (c) 2022 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
@@ -20,47 +20,33 @@
  * THE SOFTWARE.
  */
 
-#include <drivers/servo/Servo.h>
+#include <actuators/Buzzer.h>
 #include <miosix.h>
-#include <utils/ClockUtils.h>
 
-using namespace Boardcore;
 using namespace miosix;
-
-using ps1 = Gpio<GPIOB_BASE, 4>;
-using ps2 = Gpio<GPIOA_BASE, 7>;
-using ps3 = Gpio<GPIOB_BASE, 8>;
+using namespace Boardcore;
 
 int main()
 {
-    ps1::mode(Mode::ALTERNATE);
-    ps2::mode(Mode::ALTERNATE);
-    ps3::mode(Mode::ALTERNATE);
-
-    ps1::alternateFunction(2);
-    ps2::alternateFunction(2);
-    ps3::alternateFunction(3);
-
-    Servo s1(TIM3, TimerUtils::Channel::CHANNEL_1);
-    Servo s2(TIM3, TimerUtils::Channel::CHANNEL_2);
-    Servo s3(TIM10, TimerUtils::Channel::CHANNEL_1);
+    Buzzer buzzer(TIM4, TimerUtils::Channel::CHANNEL_2);
 
-    s1.enable();
-    s2.enable();
-    s3.enable();
+    printf("Now beeping for 2 seconds\n");
+    buzzer.oneTimeToggle(2 * 1000);
+    Thread::sleep(3 * 1000);
 
-    float pos[] = {0, 0.5, 1.0};
+    printf("Now continuosly toggle the buzzer every 500ms for 5 seconds");
+    buzzer.continuoslyToggle(500, 500);
+    Thread::sleep(6 * 1000);
 
-    s1.setPosition(pos[0]);
-    s2.setPosition(pos[1]);
-    s3.setPosition(pos[2]);
+    printf("Now continuosly toggle the buzzer every 200ms for 5 seconds");
+    buzzer.continuoslyToggle(200, 200);
+    Thread::sleep(6 * 1000);
 
-    for (int i = 0;; i++)
-    {
-        s1.setPosition(pos[i % 3]);
-        s2.setPosition(pos[(i + 1) % 3]);
-        s3.setPosition(pos[(i + 2) % 3]);
+    printf("Now manually turning on the buzzer for 1 second\n");
+    buzzer.on();
+    Thread::sleep(1000);
+    buzzer.off();
 
-        Thread::sleep(2000);
-    }
+    while (true)
+        Thread::sleep(1000);
 }
diff --git a/src/tests/drivers/test-hbridge.cpp b/src/tests/actuators/test-hbridge.cpp
similarity index 98%
rename from src/tests/drivers/test-hbridge.cpp
rename to src/tests/actuators/test-hbridge.cpp
index 0f2eb7c18ce9de005af15b1a216743ce02529f58..a2aa0438aa91015c899273a37c38ff87458def55 100644
--- a/src/tests/drivers/test-hbridge.cpp
+++ b/src/tests/actuators/test-hbridge.cpp
@@ -20,7 +20,7 @@
  * THE SOFTWARE.
  */
 
-#include <drivers/hbridge/HBridge.h>
+#include <actuators/HBridge/HBridge.h>
 #include <miosix.h>
 #include <utils/Debug.h>
 
diff --git a/src/tests/actuators/test-servo.cpp b/src/tests/actuators/test-servo.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3606640a5468e02a6e8b2dae060b440640a70bbe
--- /dev/null
+++ b/src/tests/actuators/test-servo.cpp
@@ -0,0 +1,101 @@
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Authors: Luca Erbetta, 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 <actuators/Servo/Servo.h>
+#include <miosix.h>
+#include <scheduler/TaskScheduler.h>
+#include <utils/ClockUtils.h>
+
+#include <iostream>
+
+/**
+ * The test uses 4 gpio pins:
+ * - PB4: TIM3-CH1
+ * - PA7: TIM3-CH2
+ * - PC8: TIM3-CH3
+ * - PB7: TIM4-CH2
+ */
+
+using namespace Boardcore;
+using namespace miosix;
+
+GpioPin pin1(GPIOB_BASE, 4);
+GpioPin pin2(GPIOA_BASE, 7);
+GpioPin pin3(GPIOC_BASE, 8);
+GpioPin pin4(GPIOB_BASE, 7);
+
+Servo s1(TIM3, TimerUtils::Channel::CHANNEL_1);
+Servo s2(TIM3, TimerUtils::Channel::CHANNEL_2);
+Servo s3(TIM3, TimerUtils::Channel::CHANNEL_3);
+Servo s4(TIM4, TimerUtils::Channel::CHANNEL_2);
+
+// Position to cycle through for the servo 1, 2 and 3
+float positions[] = {0, 0.5, 1.0};
+int lastPosition  = 0;
+
+void moveServo()
+{
+    s1.setPosition(positions[lastPosition % 3]);
+    s2.setPosition(positions[(lastPosition + 1) % 3]);
+    s3.setPosition(positions[(lastPosition + 2) % 3]);
+
+    lastPosition++;
+}
+
+int main()
+{
+    pin1.mode(Mode::ALTERNATE);
+    pin1.alternateFunction(2);
+    pin2.mode(Mode::ALTERNATE);
+    pin2.alternateFunction(2);
+    pin3.mode(Mode::ALTERNATE);
+    pin3.alternateFunction(2);
+    pin4.mode(Mode::ALTERNATE);
+    pin4.alternateFunction(2);
+
+    // Enable the timers
+    s1.enable();
+    s2.enable();
+    s3.enable();
+    s4.enable();
+
+    // Start a periodic task to move the first three servos
+    TaskScheduler scheduler;
+    scheduler.addTask(&moveServo, 2 * 1000, 1);
+    scheduler.start();
+
+    // Control the fourth servo manually
+    float percentage;
+
+    printf("Please enter a percentage for the servo ");
+    printf("or anything else to exit: ");
+
+    while (std::cin >> percentage)
+    {
+        s4.setPosition(percentage / 100);
+
+        printf("Position set to: %.2f%%\n", percentage);
+
+        printf("Please enter a percentage for the servo ");
+        printf("or anything else to exit: ");
+    }
+}
diff --git a/src/tests/drivers/stepper/test-stepper.cpp b/src/tests/actuators/test-stepper.cpp
similarity index 99%
rename from src/tests/drivers/stepper/test-stepper.cpp
rename to src/tests/actuators/test-stepper.cpp
index 3b27f5f0f565a98e6c41882148f92e31f2ef2ae2..707f227892df7545e5aec8b6a9ef174322ed8e46 100644
--- a/src/tests/drivers/stepper/test-stepper.cpp
+++ b/src/tests/actuators/test-stepper.cpp
@@ -20,7 +20,7 @@
  * THE SOFTWARE.
  */
 
-#include <drivers/stepper/Stepper.h>
+#include <actuators/Stepper.h>
 
 using namespace miosix;
 using namespace Boardcore;
@@ -125,4 +125,4 @@ int main()
         doRoutine(stepper);
         printf("The stepper is now disabled\n");
     }
-}
\ No newline at end of file
+}
diff --git a/src/tests/kalman/test-kalman-data.h b/src/tests/algorithms/Kalman/test-kalman-data.h
similarity index 100%
rename from src/tests/kalman/test-kalman-data.h
rename to src/tests/algorithms/Kalman/test-kalman-data.h
diff --git a/src/tests/kalman/test-kalman-eigen-benchmark.cpp b/src/tests/algorithms/Kalman/test-kalman-eigen-benchmark.cpp
similarity index 95%
rename from src/tests/kalman/test-kalman-eigen-benchmark.cpp
rename to src/tests/algorithms/Kalman/test-kalman-eigen-benchmark.cpp
index df4de132c0bc67bafcb539c9eb7107f13b8f1331..7b47c1b28b1e716ed826ce55c6ae9787b6e9e106 100644
--- a/src/tests/kalman/test-kalman-eigen-benchmark.cpp
+++ b/src/tests/algorithms/Kalman/test-kalman-eigen-benchmark.cpp
@@ -25,12 +25,12 @@
 
 #define EIGEN_RUNTIME_NO_MALLOC
 
+#include <algorithms/Kalman/Kalman.h>
 #include <drivers/timer/GeneralPurposeTimer.h>
 #include <drivers/timer/TimerUtils.h>
-#include <kalman/KalmanEigen.h>
 #include <kernel/kernel.h>
-#include <math/SkyQuaternion.h>
 #include <util/util.h>
+#include <utils/SkyQuaternion/SkyQuaternion.h>
 
 #include <iostream>
 
@@ -71,7 +71,7 @@ int main()
 
     Matrix<float, p, 1> y(p);  // vector with p elements (only one in this case)
 
-    KalmanEigen<float, n, p>::KalmanConfig config;
+    Kalman<float, n, p>::KalmanConfig config;
     config.F = F;
     config.H = H;
     config.Q = Q;
@@ -79,7 +79,7 @@ int main()
     config.P = P;
     config.x = x0;
 
-    KalmanEigen<float, n, p> filter(config);
+    Kalman<float, n, p> filter(config);
 
     float lastTime = 0.0;  // Variable to save the time of the last sample
 
diff --git a/src/tests/algorithms/NAS/test-attitude-parafoil.cpp b/src/tests/algorithms/NAS/test-attitude-parafoil.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4f8af2b692b965d8873f721c62d57308931475a8
--- /dev/null
+++ b/src/tests/algorithms/NAS/test-attitude-parafoil.cpp
@@ -0,0 +1,141 @@
+/* Copyright (c) 2022 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 <algorithms/NAS/NAS.h>
+#include <algorithms/NAS/StateInitializer.h>
+#include <miosix.h>
+#include <sensors/MPU9250/MPU9250.h>
+#include <sensors/SensorManager.h>
+#include <utils/SkyQuaternion/SkyQuaternion.h>
+
+#include <iostream>
+
+using namespace miosix;
+using namespace Boardcore;
+using namespace Eigen;
+
+NASConfig getEKConfig();
+void setInitialOrientation();
+void imuInit();
+void imuStep();
+
+Vector3f nedMag = Vector3f(0.4747, 0.0276, 0.8797);
+
+NAS* nas;
+
+SPIBus spi1(SPI1);
+MPU9250* imu = nullptr;
+
+int main()
+{
+    imuInit();
+
+    nas = new NAS(getEKConfig());
+    setInitialOrientation();
+
+    TaskScheduler scheduler;
+    scheduler.addTask(imuStep, 20);
+    scheduler.start();
+
+    while (true)
+        Thread::sleep(1000);
+}
+
+NASConfig getEKConfig()
+{
+    NASConfig config;
+
+    config.T              = 0.02f;
+    config.SIGMA_BETA     = 0.0001f;
+    config.SIGMA_W        = 0.3f;
+    config.SIGMA_MAG      = 0.1f;
+    config.SIGMA_GPS      = 10.0f;
+    config.SIGMA_BAR      = 4.3f;
+    config.SIGMA_POS      = 10.0;
+    config.SIGMA_VEL      = 10.0;
+    config.P_POS          = 1.0f;
+    config.P_POS_VERTICAL = 10.0f;
+    config.P_VEL          = 1.0f;
+    config.P_VEL_VERTICAL = 10.0f;
+    config.P_ATT          = 0.01f;
+    config.P_BIAS         = 0.01f;
+    config.SATS_NUM       = 6.0f;
+    config.NED_MAG        = nedMag;
+
+    return config;
+}
+
+void setInitialOrientation()
+{
+    Eigen::Matrix<float, 13, 1> x;
+
+    // Set quaternions
+    Eigen::Vector4f q = SkyQuaternion::eul2quat({0, 0, 0});
+    x(6)              = q(0);
+    x(7)              = q(1);
+    x(8)              = q(2);
+    x(9)              = q(3);
+
+    nas->setX(x);
+}
+
+void imuInit()
+{
+    imu = new MPU9250(spi1, sensors::mpu9250::cs::getPin());
+    imu->init();
+}
+
+void imuStep()
+{
+    imu->sample();
+    auto data = imu->getLastSample();
+    Vector3f acceleration(data.accelerationX, data.accelerationY,
+                          data.accelerationZ);
+    Vector3f angularVelocity(data.angularVelocityX, data.angularVelocityY,
+                             data.angularVelocityZ);
+    Vector3f magneticField(data.magneticFieldX, data.magneticFieldY,
+                           data.magneticFieldZ);
+
+    // Calibration
+    {
+        Vector3f bias(0.0218462708018154, 0.0281755574886535,
+                      0.0264119470499244);
+        angularVelocity -= bias;
+        Vector3f offset(15.9850903462129, -15.6775071377074, -33.8438469147423);
+        magneticField -= offset;
+        magneticField = {magneticField[1], magneticField[0], -magneticField[2]};
+    }
+
+    acceleration.normalize();
+    magneticField.normalize();
+
+    // Predict step
+    nas->predictGyro(angularVelocity);
+
+    // Correct step
+    nas->correctMag(magneticField);
+    nas->correctAcc(acceleration);
+
+    auto nasState = nas->getState();
+    printf("w%fwa%fab%fbc%fc\n", nasState.qw, nasState.qx, nasState.qy,
+           nasState.qz);
+}
diff --git a/src/tests/algorithms/NAS/test-attitude-stack.cpp b/src/tests/algorithms/NAS/test-attitude-stack.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..beacab318c26c6005805ca9f7bc24aa7e1147e6b
--- /dev/null
+++ b/src/tests/algorithms/NAS/test-attitude-stack.cpp
@@ -0,0 +1,167 @@
+/* Copyright (c) 2022 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 <algorithms/NAS/NAS.h>
+#include <algorithms/NAS/StateInitializer.h>
+#include <miosix.h>
+#include <sensors/BMX160/BMX160.h>
+#include <sensors/SensorManager.h>
+#include <utils/SkyQuaternion/SkyQuaternion.h>
+
+#include <iostream>
+
+using namespace miosix;
+using namespace Boardcore;
+using namespace Eigen;
+
+NASConfig getEKConfig();
+void setInitialOrientation();
+void imuInit();
+void imuStep();
+
+Vector3f nedMag = Vector3f(0.4747, 0.0276, 0.8797);
+
+NAS* nas;
+
+SPIBus spi1(SPI1);
+BMX160* imu = nullptr;
+
+int main()
+{
+    imuInit();
+
+    nas = new NAS(getEKConfig());
+    setInitialOrientation();
+
+    TaskScheduler scheduler;
+    scheduler.addTask(imuStep, 20);
+    scheduler.start();
+
+    while (true)
+        Thread::sleep(1000);
+}
+
+NASConfig getEKConfig()
+{
+    NASConfig config;
+
+    config.T              = 0.02f;
+    config.SIGMA_BETA     = 0.0001f;
+    config.SIGMA_W        = 0.3f;
+    config.SIGMA_MAG      = 0.1f;
+    config.SIGMA_GPS      = 10.0f;
+    config.SIGMA_BAR      = 4.3f;
+    config.SIGMA_POS      = 10.0;
+    config.SIGMA_VEL      = 10.0;
+    config.P_POS          = 1.0f;
+    config.P_POS_VERTICAL = 10.0f;
+    config.P_VEL          = 1.0f;
+    config.P_VEL_VERTICAL = 10.0f;
+    config.P_ATT          = 0.01f;
+    config.P_BIAS         = 0.01f;
+    config.SATS_NUM       = 6.0f;
+    config.NED_MAG        = nedMag;
+
+    return config;
+}
+
+void setInitialOrientation()
+{
+    Eigen::Matrix<float, 13, 1> x;
+
+    // Set quaternions
+    Eigen::Vector4f q = SkyQuaternion::eul2quat({0, 0, 0});
+    x(6)              = q(0);
+    x(7)              = q(1);
+    x(8)              = q(2);
+    x(9)              = q(3);
+
+    nas->setX(x);
+}
+
+void imuInit()
+{
+    SPIBusConfig spiConfig;
+    spiConfig.clockDivider = SPI::ClockDivider::DIV_8;
+
+    BMX160Config bmx_config;
+    bmx_config.fifoMode      = BMX160Config::FifoMode::HEADER;
+    bmx_config.fifoWatermark = 80;
+    bmx_config.fifoInterrupt = BMX160Config::FifoInterruptPin::PIN_INT1;
+
+    bmx_config.temperatureDivider = 1;
+
+    bmx_config.accelerometerRange = BMX160Config::AccelerometerRange::G_16;
+
+    bmx_config.gyroscopeRange = BMX160Config::GyroscopeRange::DEG_1000;
+
+    bmx_config.accelerometerDataRate = BMX160Config::OutputDataRate::HZ_100;
+    bmx_config.gyroscopeDataRate     = BMX160Config::OutputDataRate::HZ_100;
+    bmx_config.magnetometerRate      = BMX160Config::OutputDataRate::HZ_100;
+
+    bmx_config.gyroscopeUnit = BMX160Config::GyroscopeMeasureUnit::RAD;
+
+    imu = new BMX160(spi1, miosix::sensors::bmx160::cs::getPin(), bmx_config,
+                     spiConfig);
+    imu->init();
+}
+
+void imuStep()
+{
+    imu->sample();
+    auto data = imu->getLastSample();
+    Vector3f acceleration(data.accelerationX, data.accelerationY,
+                          data.accelerationZ);
+    Vector3f angularVelocity(data.angularVelocityX, data.angularVelocityY,
+                             data.angularVelocityZ);
+    Vector3f magneticField(data.magneticFieldX, data.magneticFieldY,
+                           data.magneticFieldZ);
+
+    // Calibration
+    {
+        Vector3f offset{-1.63512255486542, 3.46523431469979, -3.08516033954451};
+        angularVelocity = angularVelocity - offset;
+        angularVelocity = angularVelocity / 180 * Constants::PI / 10;
+        Vector3f b{21.5356818859811, -22.7697302909894, -2.68219304319269};
+        Matrix3f A{{0.688760050772712, 0, 0},
+                   {0, 0.637715211784480, 0},
+                   {0, 0, 2.27669720320908}};
+        magneticField = (magneticField - b).transpose() * A;
+    }
+
+    acceleration.normalize();
+    magneticField.normalize();
+
+    // Predict step
+    nas->predictGyro(angularVelocity);
+
+    // Correct step
+    nas->correctMag(magneticField);
+    nas->correctAcc(acceleration);
+
+    // std::cout << acceleration.transpose() << angularVelocity.transpose()
+    //           << magneticField.transpose() << std::endl;
+
+    auto nasState = nas->getState();
+    printf("w%fwa%fab%fbc%fc\n", nasState.qw, nasState.qx, nasState.qy,
+           nasState.qz);
+}
diff --git a/src/tests/algorithms/NAS/test-nas-parafoil.cpp b/src/tests/algorithms/NAS/test-nas-parafoil.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..57fd167c839ec3bdef2d24c69417ddb7f118aab1
--- /dev/null
+++ b/src/tests/algorithms/NAS/test-nas-parafoil.cpp
@@ -0,0 +1,183 @@
+/* Copyright (c) 2022 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 <algorithms/NAS/NAS.h>
+#include <algorithms/NAS/StateInitializer.h>
+#include <logger/Logger.h>
+#include <miosix.h>
+#include <sensors/MPU9250/MPU9250.h>
+#include <sensors/SensorManager.h>
+#include <sensors/UbloxGPS/UbloxGPS.h>
+#include <utils/AeroUtils/AeroUtils.h>
+#include <utils/SkyQuaternion/SkyQuaternion.h>
+
+#include <iostream>
+
+using namespace miosix;
+using namespace Boardcore;
+using namespace Eigen;
+
+NASConfig getEKConfig();
+void setInitialOrientation();
+void init();
+void step();
+void print();
+
+Vector3f nedMag   = Vector3f(0.4747, 0.0276, 0.8797);
+Vector2f startPos = Vector2f(45.501141, 9.156281);
+
+NAS* nas;
+
+SPIBus spi1(SPI1);
+MPU9250* imu  = nullptr;
+UbloxGPS* gps = nullptr;
+
+int main()
+{
+    init();
+
+    nas = new NAS(getEKConfig());
+    setInitialOrientation();
+
+    TaskScheduler scheduler;
+    scheduler.addTask(step, 20, TaskScheduler::Policy::RECOVER);
+    scheduler.addTask(print, 250);
+    scheduler.start();
+
+    while (true)
+        Thread::sleep(1000);
+}
+
+NASConfig getEKConfig()
+{
+    NASConfig config;
+
+    config.T              = 0.02f;
+    config.SIGMA_BETA     = 0.0001f;
+    config.SIGMA_W        = 0.3f;
+    config.SIGMA_MAG      = 0.1f;
+    config.SIGMA_GPS      = 10.0f;
+    config.SIGMA_BAR      = 4.3f;
+    config.SIGMA_POS      = 10.0;
+    config.SIGMA_VEL      = 10.0;
+    config.P_POS          = 1.0f;
+    config.P_POS_VERTICAL = 10.0f;
+    config.P_VEL          = 1.0f;
+    config.P_VEL_VERTICAL = 10.0f;
+    config.P_ATT          = 0.01f;
+    config.P_BIAS         = 0.01f;
+    config.SATS_NUM       = 6.0f;
+    config.NED_MAG        = nedMag;
+
+    return config;
+}
+
+void setInitialOrientation()
+{
+    Matrix<float, 13, 1> x = Matrix<float, 13, 1>::Zero();
+
+    // Set quaternions
+    Vector4f q = SkyQuaternion::eul2quat({0, 0, 0});
+    x(6)       = q(0);
+    x(7)       = q(1);
+    x(8)       = q(2);
+    x(9)       = q(3);
+
+    nas->setX(x);
+}
+
+void init()
+{
+    imu = new MPU9250(spi1, sensors::mpu9250::cs::getPin());
+    imu->init();
+
+    gps = new UbloxGPS(38400, 10, 2, "gps", 38400);
+    gps->init();
+    gps->start();
+
+    Logger::getInstance().start();
+}
+
+void step()
+{
+    imu->sample();
+    gps->sample();
+    auto imuData = imu->getLastSample();
+    auto gpsData = gps->getLastSample();
+
+    Vector3f acceleration(imuData.accelerationX, imuData.accelerationY,
+                          imuData.accelerationZ);
+    Vector3f angularVelocity(imuData.angularVelocityX, imuData.angularVelocityY,
+                             imuData.angularVelocityZ);
+    Vector3f magneticField(imuData.magneticFieldX, imuData.magneticFieldY,
+                           imuData.magneticFieldZ);
+
+    Vector2f gpsPos(gpsData.latitude, gpsData.longitude);
+    gpsPos = Aeroutils::geodetic2NED(gpsPos, startPos);
+    Vector2f gpsVel(gpsData.velocityNorth, gpsData.velocityNorth);
+    Vector4f gpsCorrection;
+    // cppcheck-suppress constStatement
+    gpsCorrection << gpsPos, gpsVel;
+
+    // Calibration
+    {
+        Vector3f biasAcc(-0.1255, 0.2053, -0.2073);
+        acceleration -= biasAcc;
+        Vector3f bias(-0.0291, 0.0149, 0.0202);
+        angularVelocity -= bias;
+        Vector3f offset(15.9850903462129, -15.6775071377074, -33.8438469147423);
+        magneticField -= offset;
+        magneticField = {magneticField[1], magneticField[0], -magneticField[2]};
+    }
+
+    // Predict step
+    nas->predictGyro(angularVelocity);
+    if (gpsPos[0] < 1e3 && gpsPos[0] > -1e3 && gpsPos[1] < 1e3 &&
+        gpsPos[1] > -1e3)
+        nas->predictAcc(acceleration);
+
+    // Correct step
+    magneticField.normalize();
+    nas->correctMag(magneticField);
+    acceleration.normalize();
+    nas->correctAcc(acceleration);
+    if (gpsData.fix)
+        nas->correctGPS(gpsCorrection);
+    nas->correctBaro(100000, 110000, 20 + 273.5);
+
+    auto nasState = nas->getState();
+    Logger::getInstance().log(imuData);
+    Logger::getInstance().log(gpsData);
+    Logger::getInstance().log(nasState);
+}
+
+void print()
+{
+    auto gpsData  = gps->getLastSample();
+    auto nasState = nas->getState();
+
+    Vector2f gpsPos(gpsData.latitude, gpsData.longitude);
+    gpsPos = Aeroutils::geodetic2NED(gpsPos, startPos);
+
+    printf("%d, %f, %f, %f, %f, %f, %f\n", gpsData.fix, gpsPos[0], gpsPos[1],
+           nasState.n, nasState.e, nasState.vn, nasState.ve);
+}
diff --git a/src/tests/algorithms/NAS/test-nas-stack.cpp b/src/tests/algorithms/NAS/test-nas-stack.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e72a1c9830b63682cffff7968cbc27106b85a61f
--- /dev/null
+++ b/src/tests/algorithms/NAS/test-nas-stack.cpp
@@ -0,0 +1,216 @@
+/* Copyright (c) 2022 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 <algorithms/NAS/NAS.h>
+#include <algorithms/NAS/StateInitializer.h>
+#include <logger/Logger.h>
+#include <miosix.h>
+#include <sensors/BMX160/BMX160.h>
+#include <sensors/MS5803/MS5803.h>
+#include <sensors/SensorManager.h>
+#include <sensors/UbloxGPS/UbloxGPS.h>
+#include <utils/AeroUtils/AeroUtils.h>
+#include <utils/Constants.h>
+#include <utils/SkyQuaternion/SkyQuaternion.h>
+
+#include <iostream>
+
+using namespace miosix;
+using namespace Boardcore;
+using namespace Eigen;
+
+NASConfig getEKConfig();
+void setInitialOrientation();
+void init();
+void step();
+void print();
+
+Vector3f nedMag   = Vector3f(0.4747, 0.0276, 0.8797);
+Vector2f startPos = Vector2f(45.501141, 9.156281);
+
+NAS* nas;
+
+SPIBus spi1(SPI1);
+BMX160* imu   = nullptr;
+UbloxGPS* gps = nullptr;
+MS5803* baro  = nullptr;
+
+int main()
+{
+    init();
+
+    nas = new NAS(getEKConfig());
+    setInitialOrientation();
+
+    TaskScheduler scheduler;
+    scheduler.addTask(step, 20, TaskScheduler::Policy::RECOVER);
+    // scheduler.addTask(print, 250);
+    scheduler.start();
+
+    while (true)
+        Thread::sleep(1000);
+}
+
+NASConfig getEKConfig()
+{
+    NASConfig config;
+
+    config.T              = 0.02f;
+    config.SIGMA_BETA     = 0.0001f;
+    config.SIGMA_W        = 0.3f;
+    config.SIGMA_MAG      = 0.1f;
+    config.SIGMA_GPS      = 10.0f;
+    config.SIGMA_BAR      = 4.3f;
+    config.SIGMA_POS      = 10.0;
+    config.SIGMA_VEL      = 10.0;
+    config.P_POS          = 1.0f;
+    config.P_POS_VERTICAL = 10.0f;
+    config.P_VEL          = 1.0f;
+    config.P_VEL_VERTICAL = 10.0f;
+    config.P_ATT          = 0.01f;
+    config.P_BIAS         = 0.01f;
+    config.SATS_NUM       = 6.0f;
+    config.NED_MAG        = nedMag;
+
+    return config;
+}
+
+void setInitialOrientation()
+{
+    Matrix<float, 13, 1> x = Matrix<float, 13, 1>::Zero();
+
+    // Set quaternions
+    Vector4f q = SkyQuaternion::eul2quat({0, 0, 0});
+    x(6)       = q(0);
+    x(7)       = q(1);
+    x(8)       = q(2);
+    x(9)       = q(3);
+
+    nas->setX(x);
+}
+
+void init()
+{
+    SPIBusConfig spiConfig;
+    spiConfig.clockDivider = SPI::ClockDivider::DIV_8;
+
+    BMX160Config bmx_config;
+    bmx_config.fifoMode      = BMX160Config::FifoMode::HEADER;
+    bmx_config.fifoWatermark = 80;
+    bmx_config.fifoInterrupt = BMX160Config::FifoInterruptPin::PIN_INT1;
+
+    bmx_config.temperatureDivider = 1;
+
+    bmx_config.accelerometerRange = BMX160Config::AccelerometerRange::G_16;
+
+    bmx_config.gyroscopeRange = BMX160Config::GyroscopeRange::DEG_1000;
+
+    bmx_config.accelerometerDataRate = BMX160Config::OutputDataRate::HZ_100;
+    bmx_config.gyroscopeDataRate     = BMX160Config::OutputDataRate::HZ_100;
+    bmx_config.magnetometerRate      = BMX160Config::OutputDataRate::HZ_100;
+
+    bmx_config.gyroscopeUnit = BMX160Config::GyroscopeMeasureUnit::RAD;
+
+    imu =
+        new BMX160(spi1, sensors::bmx160::cs::getPin(), bmx_config, spiConfig);
+    imu->init();
+
+    gps = new UbloxGPS(921600, 10, 2, "gps", 38400);
+    gps->init();
+    gps->start();
+
+    baro = new MS5803(spi1, sensors::ms5803::cs::getPin());
+    baro->init();
+
+    Logger::getInstance().start();
+}
+
+void step()
+{
+    imu->sample();
+    gps->sample();
+    baro->sample();
+    auto imuData  = imu->getLastSample();
+    auto gpsData  = gps->getLastSample();
+    auto baroData = baro->getLastSample();
+
+    Vector3f acceleration(imuData.accelerationX, imuData.accelerationY,
+                          imuData.accelerationZ);
+    Vector3f angularVelocity(imuData.angularVelocityX, imuData.angularVelocityY,
+                             imuData.angularVelocityZ);
+    Vector3f magneticField(imuData.magneticFieldX, imuData.magneticFieldY,
+                           imuData.magneticFieldZ);
+
+    Vector2f gpsPos(gpsData.latitude, gpsData.longitude);
+    gpsPos = Aeroutils::geodetic2NED(gpsPos, startPos);
+    Vector2f gpsVel(gpsData.velocityNorth, gpsData.velocityEast);
+    Vector4f gpsCorrection;
+    // cppcheck-suppress constStatement
+    gpsCorrection << gpsPos, gpsVel;
+
+    // Calibration
+    {
+        Vector3f offset{-1.63512255486542, 3.46523431469979, -3.08516033954451};
+        angularVelocity = angularVelocity - offset;
+        angularVelocity = angularVelocity / 180 * Constants::PI / 10;
+        Vector3f b{21.5356818859811, -22.7697302909894, -2.68219304319269};
+        Matrix3f A{{0.688760050772712, 0, 0},
+                   {0, 0.637715211784480, 0},
+                   {0, 0, 2.27669720320908}};
+        magneticField = (magneticField - b).transpose() * A;
+    }
+
+    // Predict step
+    nas->predictGyro(angularVelocity);
+    nas->predictAcc(acceleration);
+
+    // Correct step
+    magneticField.normalize();
+    nas->correctMag(magneticField);
+    acceleration.normalize();
+    nas->correctAcc(acceleration);
+    if (gpsData.fix)
+        nas->correctGPS(gpsCorrection);
+    nas->correctBaro(100000, Constants::MSL_PRESSURE,
+                     Constants::MSL_TEMPERATURE);
+
+    auto nasState = nas->getState();
+
+    Logger::getInstance().log(imuData);
+    Logger::getInstance().log(gpsData);
+    Logger::getInstance().log(baroData);
+    Logger::getInstance().log(nasState);
+}
+
+void print()
+{
+    auto gpsData  = gps->getLastSample();
+    auto baroData = baro->getLastSample();
+    auto nasState = nas->getState();
+
+    Vector2f gpsPos(gpsData.latitude, gpsData.longitude);
+    gpsPos = Aeroutils::geodetic2NED(gpsPos, startPos);
+
+    printf("%d, %f, %f, %f, %f, %f, %f, %f, %f\n", gpsData.fix, gpsPos[0],
+           gpsPos[1], nasState.n, nasState.e, nasState.d, nasState.vn,
+           nasState.ve, baroData.pressure);
+}
diff --git a/src/tests/algorithms/NAS/test-nas-with-triad.cpp b/src/tests/algorithms/NAS/test-nas-with-triad.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..59baeb1d1e37a36035e2fe6208ec22617ea8ad2d
--- /dev/null
+++ b/src/tests/algorithms/NAS/test-nas-with-triad.cpp
@@ -0,0 +1,213 @@
+/* Copyright (c) 2022 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 <algorithms/NAS/NAS.h>
+#include <algorithms/NAS/StateInitializer.h>
+#include <miosix.h>
+#include <sensors/BMX160/BMX160.h>
+#include <sensors/SensorManager.h>
+#include <utils/SkyQuaternion/SkyQuaternion.h>
+
+#include <iostream>
+
+using namespace miosix;
+using namespace Boardcore;
+using namespace Eigen;
+
+NASConfig getEKConfig();
+void bmxInit();
+void bmxCallback();
+
+constexpr uint64_t CALIBRATION_TIMEOUT = 5 * 1e6;
+
+Vector3f nedMag = Vector3f(0.47472049, 0.02757190, 0.87970463);
+
+StateInitializer* stateInitializer;
+NAS* nas;
+
+SPIBus spi1(SPI1);
+BMX160* bmx160 = nullptr;
+
+Boardcore::SensorManager::SensorMap_t sensorsMap;
+Boardcore::SensorManager* sensorManager = nullptr;
+
+int main()
+{
+    bmxInit();
+
+    sensorManager    = new SensorManager(sensorsMap);
+    stateInitializer = new StateInitializer();
+    nas              = new NAS(getEKConfig());
+
+    // Logger::getInstance().start();
+    TimestampTimer::getInstance().resetTimestamp();
+    sensorManager->start();
+
+    while (true)
+        Thread::sleep(1000);
+}
+
+NASConfig getEKConfig()
+{
+    NASConfig config;
+
+    config.T              = 0.02f;
+    config.SIGMA_BETA     = 0.0001f;
+    config.SIGMA_W        = 0.3f;
+    config.SIGMA_MAG      = 0.1f;
+    config.SIGMA_GPS      = 10.0f;
+    config.SIGMA_BAR      = 4.3f;
+    config.SIGMA_POS      = 10.0;
+    config.SIGMA_VEL      = 10.0;
+    config.P_POS          = 1.0f;
+    config.P_POS_VERTICAL = 10.0f;
+    config.P_VEL          = 1.0f;
+    config.P_VEL_VERTICAL = 10.0f;
+    config.P_ATT          = 0.01f;
+    config.P_BIAS         = 0.01f;
+    config.SATS_NUM       = 6.0f;
+
+    // Normalized magnetic field in Milan
+    config.NED_MAG = nedMag;
+
+    return config;
+}
+
+void bmxInit()
+{
+    SPIBusConfig spiConfig;
+    spiConfig.clockDivider = SPI::ClockDivider::DIV_8;
+
+    BMX160Config bmx_config;
+    bmx_config.fifoMode              = BMX160Config::FifoMode::HEADER;
+    bmx_config.fifoWatermark         = 80;
+    bmx_config.fifoInterrupt         = BMX160Config::FifoInterruptPin::PIN_INT1;
+    bmx_config.temperatureDivider    = 1;
+    bmx_config.accelerometerRange    = BMX160Config::AccelerometerRange::G_16;
+    bmx_config.gyroscopeRange        = BMX160Config::GyroscopeRange::DEG_1000;
+    bmx_config.accelerometerDataRate = BMX160Config::OutputDataRate::HZ_100;
+    bmx_config.gyroscopeDataRate     = BMX160Config::OutputDataRate::HZ_100;
+    bmx_config.magnetometerRate      = BMX160Config::OutputDataRate::HZ_100;
+    bmx_config.gyroscopeUnit         = BMX160Config::GyroscopeMeasureUnit::RAD;
+
+    bmx160 = new BMX160(spi1, miosix::sensors::bmx160::cs::getPin(), bmx_config,
+                        spiConfig);
+
+    SensorInfo info("BMX160", 20, &bmxCallback);
+
+    sensorsMap.emplace(std::make_pair(bmx160, info));
+}
+
+void bmxCallback()
+{
+    static int meanCount    = 0;
+    static bool calibrating = true;
+    static Vector3f accMean = Vector3f::Zero();
+    static Vector3f magMean = Vector3f::Zero();
+
+    auto data = bmx160->getLastSample();
+    Vector3f acceleration(data.accelerationX, data.accelerationY,
+                          data.accelerationZ);
+    Vector3f angularVelocity(data.angularVelocityX, data.angularVelocityY,
+                             data.angularVelocityZ);
+    Vector3f magneticField(data.magneticFieldX, data.magneticFieldY,
+                           data.magneticFieldZ);
+
+    // Apply correction
+    Vector3f acc_b(0.3763 + 0.094, -0.1445 - 0.0229, -0.1010 + 0.0150);
+    acceleration -= acc_b;
+    Vector3f gyro_b{-1.63512255486542, 3.46523431469979, -3.08516033954451};
+    angularVelocity = angularVelocity - gyro_b;
+    angularVelocity = angularVelocity / 180 * Constants::PI / 10;
+    Vector3f mag_b{21.0730033648425, -24.3997259703105, -2.32621524742862};
+    Matrix3f A{{0.659926504672263, 0, 0},
+               {0, 0.662442130094073, 0},
+               {0, 0, 2.28747567094359}};
+    magneticField = (magneticField - mag_b).transpose() * A;
+
+    if (calibrating)
+    {
+        if (TimestampTimer::getInstance().getTimestamp() < CALIBRATION_TIMEOUT)
+        {
+            accMean = (accMean * meanCount + acceleration) / (meanCount + 1);
+            magMean = (magMean * meanCount + acceleration) / (meanCount + 1);
+            meanCount++;
+        }
+        else
+        {
+            // Now the calibration has ended, compute and log the nas state
+            calibrating = false;
+
+            // Compute the initial nas state
+            stateInitializer->triad(accMean, magMean, nedMag);
+            nas->setX(stateInitializer->getInitX());
+
+            // Save the state and the IMU data
+            // Logger::getInstance().log(nas->getState());
+            data.accelerationX  = accMean[0];
+            data.accelerationY  = accMean[1];
+            data.accelerationZ  = accMean[2];
+            data.magneticFieldX = magMean[0];
+            data.magneticFieldY = magMean[1];
+            data.magneticFieldZ = magMean[2];
+            // Logger::getInstance().log(data);
+
+            // Restart the logger to change log filename
+            // Logger::getInstance().stop();
+            // Logger::getInstance().start();
+        }
+    }
+    else
+    {
+        // Predict step
+        {
+            // nas->predictAcc(acceleration);
+            nas->predictGyro(angularVelocity);
+
+            data.angularVelocityX = angularVelocity[0];
+            data.angularVelocityY = angularVelocity[1];
+            data.angularVelocityZ = angularVelocity[2];
+        }
+
+        // Correct step
+        {
+            magneticField.normalize();
+            nas->correctMag(magneticField);
+
+            data.magneticFieldX = magneticField[0];
+            data.magneticFieldY = magneticField[1];
+            data.magneticFieldZ = magneticField[2];
+        }
+
+        auto nasState = nas->getState();
+
+        nasState.timestamp = TimestampTimer::getInstance().getTimestamp();
+        data.accelerationTimestamp    = nasState.timestamp;
+        data.magneticFieldTimestamp   = nasState.timestamp;
+        data.angularVelocityTimestamp = nasState.timestamp;
+
+        // Logger::getInstance().log(nasState);
+        // Logger::getInstance().log(data);
+        printf("w%fwa%fab%fbc%fc\n", nasState.qw, nasState.qx, nasState.qy,
+               nasState.qz);
+    }
+}
diff --git a/src/tests/algorithms/NAS/test-tmp.cpp b/src/tests/algorithms/NAS/test-tmp.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..39fcdbd511705c39361f2a926ef92c15ea662ff9
--- /dev/null
+++ b/src/tests/algorithms/NAS/test-tmp.cpp
@@ -0,0 +1,89 @@
+/* Copyright (c) 2021 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 <drivers/spi/SPIDriver.h>
+#include <drivers/timer/TimestampTimer.h>
+#include <miosix.h>
+#include <sensors/BMX160/BMX160.h>
+#include <sensors/MPU9250/MPU9250.h>
+#include <utils/Debug.h>
+
+using namespace miosix;
+
+using namespace Boardcore;
+
+GpioPin mpuCs = GpioPin(GPIOG_BASE, 3);
+SPIBus spi1(SPI1);
+
+MPU9250* mpu = nullptr;
+BMX160* bmx  = nullptr;
+
+void initBoard()
+{
+    // Chip select pin as output starting high
+    mpuCs.mode(miosix::Mode::OUTPUT);
+    mpuCs.high();
+
+    mpu = new MPU9250(spi1, mpuCs);
+
+    SPIBusConfig spiConfig;
+    spiConfig.clockDivider = SPI::ClockDivider::DIV_8;
+
+    BMX160Config bmxConfig;
+    bmxConfig.fifoMode              = BMX160Config::FifoMode::HEADER;
+    bmxConfig.fifoWatermark         = 80;
+    bmxConfig.fifoInterrupt         = BMX160Config::FifoInterruptPin::PIN_INT1;
+    bmxConfig.temperatureDivider    = 1;
+    bmxConfig.accelerometerRange    = BMX160Config::AccelerometerRange::G_16;
+    bmxConfig.gyroscopeRange        = BMX160Config::GyroscopeRange::DEG_1000;
+    bmxConfig.accelerometerDataRate = BMX160Config::OutputDataRate::HZ_100;
+    bmxConfig.gyroscopeDataRate     = BMX160Config::OutputDataRate::HZ_100;
+    bmxConfig.magnetometerRate      = BMX160Config::OutputDataRate::HZ_100;
+    bmxConfig.gyroscopeUnit         = BMX160Config::GyroscopeMeasureUnit::RAD;
+    bmx = new BMX160(spi1, miosix::sensors::bmx160::cs::getPin(), bmxConfig,
+                     spiConfig);
+
+    mpu->init();
+    bmx->init();
+}
+
+int main()
+{
+    initBoard();
+
+    auto lastTick = getTick();
+    while (true)
+    {
+        mpu->sample();
+        bmx->sample();
+        auto mpuData = mpu->getLastSample();
+        auto bmxData = bmx->getLastSample();
+
+        printf("%f,%f,%f,", mpuData.magneticFieldX, mpuData.magneticFieldY,
+               mpuData.magneticFieldZ);
+        printf("%f,%f,%f\n", bmxData.magneticFieldX, bmxData.magneticFieldY,
+               bmxData.magneticFieldZ);
+
+        Thread::sleepUntil(lastTick + 20);
+        lastTick = getTick();
+    }
+}
diff --git a/src/tests/algorithms/NAS/test-triad-parafoil.cpp b/src/tests/algorithms/NAS/test-triad-parafoil.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ed93635ffcb04004de8ce7810051b48a21bb08bb
--- /dev/null
+++ b/src/tests/algorithms/NAS/test-triad-parafoil.cpp
@@ -0,0 +1,79 @@
+/* Copyright (c) 2022 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 <algorithms/NAS/StateInitializer.h>
+#include <miosix.h>
+#include <sensors/MPU9250/MPU9250.h>
+#include <sensors/SensorManager.h>
+#include <sensors/calibration/SensorDataExtra.h>
+#include <sensors/calibration/SoftAndHardIronCalibration/SoftAndHardIronCalibration.h>
+
+#include <cmath>
+#include <iostream>
+
+using namespace miosix;
+using namespace Boardcore;
+using namespace Eigen;
+
+void mpuInit();
+void bmxCallback();
+
+Vector3f nedMag = Vector3f(0.4747, 0.0276, 0.8797);
+
+SPIBus spi1(SPI1);
+MPU9250* mpu = nullptr;
+
+int main()
+{
+    mpu = new MPU9250(spi1, sensors::mpu9250::cs::getPin());
+    mpu->init();
+
+    auto lastTick = getTick();
+    while (true)
+    {
+        mpu->sample();
+        auto data = mpu->getLastSample();
+
+        Vector3f acceleration(data.accelerationX, data.accelerationY,
+                              data.accelerationZ);
+
+        Vector3f offset(15.9850903462129, -15.6775071377074, -33.8438469147423);
+        Vector3f magneticField(data.magneticFieldX, data.magneticFieldY,
+                               data.magneticFieldZ);
+        magneticField -= offset;
+        magneticField = {magneticField[1], magneticField[0], -magneticField[2]};
+
+        acceleration.normalize();
+        magneticField.normalize();
+
+        StateInitializer state;
+        state.triad(acceleration, magneticField, nedMag);
+
+        auto kalmanState = state.getInitX();
+        if (!std::isnan(kalmanState(9)))
+            printf("w%fwa%fab%fbc%fc\n", kalmanState(9), kalmanState(6),
+                   kalmanState(7), kalmanState(8));
+
+        Thread::sleepUntil(lastTick + 20);
+        lastTick = getTick();
+    }
+}
diff --git a/src/tests/algorithms/NAS/test-triad.cpp b/src/tests/algorithms/NAS/test-triad.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f5104a1e0f2cb838a1b14f7b58ec072fbabcdcab
--- /dev/null
+++ b/src/tests/algorithms/NAS/test-triad.cpp
@@ -0,0 +1,110 @@
+/* Copyright (c) 2022 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 <algorithms/NAS/StateInitializer.h>
+#include <miosix.h>
+#include <sensors/BMX160/BMX160.h>
+#include <sensors/SensorManager.h>
+#include <sensors/calibration/SensorDataExtra.h>
+#include <sensors/calibration/SoftAndHardIronCalibration/SoftAndHardIronCalibration.h>
+
+#include <cmath>
+#include <iostream>
+
+using namespace miosix;
+using namespace Boardcore;
+using namespace Eigen;
+
+void imuInit();
+
+Vector3f nedMag = Vector3f(0.4747, 0.0276, 0.8797);
+
+SPIBus spi1(SPI1);
+BMX160* bmx = nullptr;
+
+int main()
+{
+    imuInit();
+    bmx->init();
+
+    auto lastTick = getTick();
+    while (true)
+    {
+        bmx->sample();
+        auto data = bmx->getLastSample();
+
+        Vector3f acceleration(data.accelerationX, data.accelerationY,
+                              data.accelerationZ);
+        Vector3f angularVelocity(data.angularVelocityX, data.angularVelocityY,
+                                 data.angularVelocityZ);
+        Vector3f offset{-1.63512255486542, 3.46523431469979, -3.08516033954451};
+        angularVelocity = angularVelocity - offset;
+        angularVelocity = angularVelocity / 180 * Constants::PI / 10;
+        Vector3f magneticField(data.magneticFieldX, data.magneticFieldY,
+                               data.magneticFieldZ);
+        Vector3f b{21.5356818859811, -22.7697302909894, -2.68219304319269};
+        Matrix3f A{{0.688760050772712, 0, 0},
+                   {0, 0.637715211784480, 0},
+                   {0, 0, 2.27669720320908}};
+        magneticField = (magneticField - b).transpose() * A;
+
+        acceleration.normalize();
+        magneticField.normalize();
+
+        StateInitializer state;
+        state.triad(acceleration, magneticField, nedMag);
+
+        auto kalmanState = state.getInitX();
+        if (!std::isnan(kalmanState(9)))
+            printf("w%fwa%fab%fbc%fc\n", kalmanState(9), kalmanState(6),
+                   kalmanState(7), kalmanState(8));
+
+        Thread::sleepUntil(lastTick + 20);
+        lastTick = getTick();
+    }
+}
+
+void imuInit()
+{
+    SPIBusConfig spiConfig;
+    spiConfig.clockDivider = SPI::ClockDivider::DIV_8;
+
+    BMX160Config bmx_config;
+    bmx_config.fifoMode      = BMX160Config::FifoMode::HEADER;
+    bmx_config.fifoWatermark = 80;
+    bmx_config.fifoInterrupt = BMX160Config::FifoInterruptPin::PIN_INT1;
+
+    bmx_config.temperatureDivider = 1;
+
+    bmx_config.accelerometerRange = BMX160Config::AccelerometerRange::G_16;
+
+    bmx_config.gyroscopeRange = BMX160Config::GyroscopeRange::DEG_1000;
+
+    bmx_config.accelerometerDataRate = BMX160Config::OutputDataRate::HZ_100;
+    bmx_config.gyroscopeDataRate     = BMX160Config::OutputDataRate::HZ_100;
+    bmx_config.magnetometerRate      = BMX160Config::OutputDataRate::HZ_100;
+
+    bmx_config.gyroscopeUnit = BMX160Config::GyroscopeMeasureUnit::RAD;
+
+    bmx = new BMX160(spi1, miosix::sensors::bmx160::cs::getPin(), bmx_config,
+                     spiConfig);
+}
diff --git a/src/tests/catch/test-aero.cpp b/src/tests/catch/test-aero.cpp
index d9542136f7a385267efb7039f95738b92212b784..6e8ba3a2d5603a3910323cbff12097e014dbff84 100644
--- a/src/tests/catch/test-aero.cpp
+++ b/src/tests/catch/test-aero.cpp
@@ -24,7 +24,7 @@
 #include "catch-tests-entry.cpp"
 #endif
 
-#include <utils/aero/AeroUtils.h>
+#include <utils/AeroUtils/AeroUtils.h>
 
 #include <catch2/catch.hpp>
 
@@ -32,7 +32,7 @@ using namespace Boardcore;
 
 TEST_CASE("[AeroUtils] mslTemperature")
 {
-    using namespace aeroutils;
+    using namespace Aeroutils;
     Approx isa_T0 =
         Approx(288.151).margin(0.001);  // 15 deg celsius, 0.01% error allowed
 
@@ -46,7 +46,7 @@ TEST_CASE("[AeroUtils] mslTemperature")
 
 TEST_CASE("[AeroUtils] mslPressure")
 {
-    using namespace aeroutils;
+    using namespace Aeroutils;
     Approx isa_P0 =
         Approx(101325).epsilon(0.0001);  // 15 deg celsius, 0.01% error allowed
 
@@ -61,7 +61,7 @@ TEST_CASE("[AeroUtils] mslPressure")
 float mslAltitude(float pressure, float pressureRef, float temperatureRef,
                   float zRef)
 {
-    using namespace aeroutils;
+    using namespace Aeroutils;
     float t0 = mslTemperature(temperatureRef, zRef);
 
     return relAltitude(pressure, mslPressure(pressureRef, temperatureRef, zRef),
@@ -84,7 +84,7 @@ TEST_CASE("[AeroUtils] relAltitude")
 
 TEST_CASE("[AeroUtils] relDensity")
 {
-    using namespace aeroutils;
+    using namespace Aeroutils;
 
     REQUIRE(relDensity(101325, 101325, 0, 288.15) ==
             Approx(1.225).epsilon(0.0001));
@@ -100,7 +100,7 @@ TEST_CASE("[AeroUtils] relDensity")
 
 TEST_CASE("[AeroUtils] verticalSpeed")
 {
-    using namespace aeroutils;
+    using namespace Aeroutils;
 
     const int count = 5;
     float p[]       = {100129.4, 99555.8, 89153.1, 23611.1, 101284.6};
diff --git a/src/tests/catch/test-eventbroker.cpp b/src/tests/catch/test-eventbroker.cpp
index 3139a50ed78563fb01404f22ab88ae780139a2ad..18a9eb65c348d484a29c2c7823be366e4cc042d0 100644
--- a/src/tests/catch/test-eventbroker.cpp
+++ b/src/tests/catch/test-eventbroker.cpp
@@ -29,7 +29,7 @@
 
 #include <events/EventBroker.h>
 #include <miosix.h>
-#include <utils/testutils/TestHelper.h>
+#include <utils/TestUtils/TestHelper.h>
 
 #include <catch2/catch.hpp>
 #include <cstdio>
@@ -75,26 +75,26 @@ TEST_CASE("EventBroker - Posts to different topics")
 
     SECTION("Post event on TOPIC_1, only sub1 should receive it")
     {
-        ev.code = EV_A;
+        ev = EV_A;
         broker.post(ev, TOPIC_1);
         REQUIRE(sub1.getTotalCount() == 1);
-        REQUIRE(sub1.getLastEvent() == ev.code);
+        REQUIRE(sub1.getLastEvent() == ev);
         REQUIRE(sub2.getTotalCount() == 0);
     }
 
     SECTION("Post event on TOPIC_2, both sub1 and sub2 should receive it")
     {
-        ev.code = EV_B;
+        ev = EV_B;
         broker.post(ev, TOPIC_2);
         REQUIRE(sub1.getTotalCount() == 1);
         REQUIRE(sub2.getTotalCount() == 1);
-        REQUIRE(sub1.getLastEvent() == ev.code);
-        REQUIRE(sub2.getLastEvent() == ev.code);
+        REQUIRE(sub1.getLastEvent() == ev);
+        REQUIRE(sub2.getLastEvent() == ev);
     }
 
     SECTION("Post event on TOPIC_3, no one should receive it")
     {
-        ev.code = EV_C;
+        ev = EV_C;
         broker.post(ev, TOPIC_3);
 
         REQUIRE(sub1.getTotalCount() == 0);
diff --git a/src/tests/catch/test-kalman-eigen.cpp b/src/tests/catch/test-kalman-eigen.cpp
index 77ecb04bd8d0e5d4f7c2ac2d6cff0fe3e0cf45fc..71974bebedf31ff6790e7cc7104c9c76b5fd2538 100644
--- a/src/tests/catch/test-kalman-eigen.cpp
+++ b/src/tests/catch/test-kalman-eigen.cpp
@@ -26,12 +26,12 @@
 
 #define EIGEN_RUNTIME_NO_MALLOC
 
-#include <kalman/KalmanEigen.h>
+#include <algorithms/Kalman/Kalman.h>
 
 #include <catch2/catch.hpp>
 #include <iostream>
 
-#include "../kalman/test-kalman-data.h"
+#include "../algorithms/Kalman/test-kalman-data.h"
 
 using namespace Boardcore;
 using namespace Eigen;
@@ -61,10 +61,10 @@ static const Matrix<float, OUTPUTS_DIM, OUTPUTS_DIM> R{10};
 // State vector
 static const Matrix<float, STATES_DIM, 1> x0(INPUT[0], 0.0, 0.0);
 
-static const KalmanEigen<float, STATES_DIM, OUTPUTS_DIM>::KalmanConfig
+static const Kalman<float, STATES_DIM, OUTPUTS_DIM>::KalmanConfig
 getKalmanConfig()
 {
-    KalmanEigen<float, STATES_DIM, OUTPUTS_DIM>::KalmanConfig config;
+    Kalman<float, STATES_DIM, OUTPUTS_DIM>::KalmanConfig config;
     config.F = F;
     config.H = H;
     config.Q = Q;
@@ -77,7 +77,7 @@ getKalmanConfig()
 
 TEST_CASE("Update test")
 {
-    KalmanEigen<float, STATES_DIM, OUTPUTS_DIM> filter(getKalmanConfig());
+    Kalman<float, STATES_DIM, OUTPUTS_DIM> filter(getKalmanConfig());
 
     Matrix<float, OUTPUTS_DIM, 1> y{};
     float lastTime = TIME[0];
diff --git a/src/tests/catch/test-packetqueue.cpp b/src/tests/catch/test-packetqueue.cpp
index 6d09548dc20e7b2cf7323c25d9b654919033c2c8..aa2beb55466235a36a0f3336034b1c3bf8bf5262 100644
--- a/src/tests/catch/test-packetqueue.cpp
+++ b/src/tests/catch/test-packetqueue.cpp
@@ -152,7 +152,7 @@ TEST_CASE("Packet tests")
         REQUIRE(p.getMsgCount() == 0);
         REQUIRE(p.dump(buf) == 0);
 
-        INFO("Adding something to full packet")
+        INFO("Adding something to full packet");
         REQUIRE(p.tryAppend(messageBase, PKT_LEN));
         REQUIRE_FALSE(p.tryAppend(messageBase, 1));
 
@@ -288,17 +288,17 @@ TEST_CASE("PacketQueue tests")
 
     SECTION("Edge cases")
     {
-        INFO("Adding too big msg")
+        INFO("Adding too big msg");
         REQUIRE(pq.put(messageBase, PKT_LEN + 1) == -1);
         REQUIRE_FALSE(pq.isFull());
         REQUIRE(pq.isEmpty());
         REQUIRE(pq.countNotEmpty() == 0);
         REQUIRE(pq.countReady() == 0);
 
-        INFO("Adding empty message")
+        INFO("Adding empty message");
         REQUIRE(pq.put(messageBase, 0) == -1);
 
-        INFO("Adding something to full queue")
+        INFO("Adding something to full queue");
         REQUIRE(pq.put(messageBase, PKT_LEN) == 0);
         REQUIRE(pq.put(messageBase + 5, PKT_LEN) == 0);
         REQUIRE(pq.put(messageBase + 10, PKT_LEN) == 0);
@@ -319,7 +319,7 @@ TEST_CASE("PacketQueue tests")
         REQUIRE(pq.countNotEmpty() == 3);
         REQUIRE(pq.countReady() == 3);
 
-        INFO("Get/Pop on empty queue")
+        INFO("Get/Pop on empty queue");
         REQUIRE_NOTHROW(pq.pop());
         REQUIRE_NOTHROW(pq.pop());
         REQUIRE_NOTHROW(pq.pop());
diff --git a/src/tests/catch/test-sensormanager-catch.cpp b/src/tests/catch/test-sensormanager-catch.cpp
index 2a23d9685b18bf7ab0d08ade5367c9648a3e9896..1bead9c7c321e4110ac29a6a4c249340a98d9d8d 100644
--- a/src/tests/catch/test-sensormanager-catch.cpp
+++ b/src/tests/catch/test-sensormanager-catch.cpp
@@ -24,7 +24,7 @@
 #include "catch-tests-entry.cpp"
 #endif
 
-#include <utils/testutils/TestSensor.h>
+#include <utils/TestUtils/TestSensor.h>
 
 #include <catch2/catch.hpp>
 #include <iostream>
@@ -39,7 +39,7 @@ using namespace Boardcore;
 
 static const uint8_t FIRST_TASK_ID = 7;  // used to test IDs assignment to tasks
 
-class FailingSensor : public Sensor<TestData>
+class FailingSensorCatch : public Sensor<TestData>
 {
     bool init() { return true; }
 
@@ -122,7 +122,7 @@ private:
         /*Enabled=*/true};
 
     // always failing self-test
-    FailingSensor s5;
+    FailingSensorCatch s5;
     SensorInfo s5_info{
         /*ID=*/"s5",
         /*Period=*/2000,
diff --git a/src/tests/catch/xbee/MockXbeeSPIBus.h b/src/tests/catch/xbee/MockXbeeSPIBus.h
index d54b3167cdecfa1faf8514a19c8a854f5c6aaf1d..44c48d14175a5b647a9a60ffe97241f496fdfb91 100644
--- a/src/tests/catch/xbee/MockXbeeSPIBus.h
+++ b/src/tests/catch/xbee/MockXbeeSPIBus.h
@@ -24,8 +24,8 @@
 
 #include <radio/Xbee/APIFrameParser.h>
 #include <radio/Xbee/APIFrames.h>
-#include <utils/testutils/MockGpioPin.h>
-#include <utils/testutils/MockSPIBus.h>
+#include <utils/TestUtils/MockGpioPin.h>
+#include <utils/TestUtils/MockSPIBus.h>
 
 #include <deque>
 #include <functional>
diff --git a/src/tests/catch/xbee/test-xbee-parser.cpp b/src/tests/catch/xbee/test-xbee-parser.cpp
index e783c98051dadf7ab9c191f5f9d9e5c16887d1db..924d96851e8970f3729ef57d13f65de28ae78be1 100644
--- a/src/tests/catch/xbee/test-xbee-parser.cpp
+++ b/src/tests/catch/xbee/test-xbee-parser.cpp
@@ -330,8 +330,8 @@ TEST_CASE("Frame serialization")
         txOrig.setTransmitOptions(0x40);
         REQUIRE(txOrig.getTrasmitOptions() == 0x40);
 
-        uint8_t* rfData = txOrig.getRFDataPointer();
-        UNUSED(rfData);  // TODO: Check rfData
+        uint8_t* rfData __attribute__((unused)) = txOrig.getRFDataPointer();
+        // TODO: Check rfData
 
         REQUIRE(txOrig.getRFDataLength() == 0);
         REQUIRE(txOrig.getFrameDataLength() == 13);
diff --git a/src/tests/drivers/canbus/test-canbus-2way.cpp b/src/tests/drivers/canbus/test-canbus-2way.cpp
index 2178668e0ffa0a9d36734ff77cf5261abda7a7fd..ab566f0e4555090de6499ef6d35544dd73464bf1 100644
--- a/src/tests/drivers/canbus/test-canbus-2way.cpp
+++ b/src/tests/drivers/canbus/test-canbus-2way.cpp
@@ -26,6 +26,7 @@
 // which it will respond
 
 #include <utils/Debug.h>
+#include <utils/Stats/Stats.h>
 
 #include <string>
 
@@ -34,7 +35,6 @@
 #include "diagnostic/PrintLogger.h"
 #include "drivers/canbus/BusLoadEstimation.h"
 #include "drivers/canbus/Canbus.h"
-#include "math/Stats.h"
 #include "utils/collections/CircularBuffer.h"
 
 constexpr uint32_t BAUD_RATE         = 1000 * 1000;
diff --git a/src/tests/drivers/test-MBLoadCell.cpp b/src/tests/drivers/test-MBLoadCell.cpp
index 5993c26044a69a6a537e82b5effba75ffee69f74..f5d97bb0da552add766b54542ba97f45b014e32d 100644
--- a/src/tests/drivers/test-MBLoadCell.cpp
+++ b/src/tests/drivers/test-MBLoadCell.cpp
@@ -21,15 +21,16 @@
  */
 
 #include <diagnostic/PrintLogger.h>
-
-#include "sensors/MBLoadCell/MBLoadCell.h"
-#include "string.h"
-#include "utils/ButtonHandler.h"
+#include <sensors/MBLoadCell/MBLoadCell.h>
+#include <string.h>
+#include <utils/ButtonHandler/ButtonHandler.h>
 
 //#define PRINT_ALL_SAMPLES // To be defined if we want to print all the samples
 
 using namespace Boardcore;
 using namespace miosix;
+using namespace std;
+using namespace placeholders;
 
 using button            = miosix::Gpio<GPIOA_BASE, 0>;  ///< User button
 const uint8_t btnUserId = 1;
@@ -39,17 +40,17 @@ const uint8_t btnUserId = 1;
  * button is pressed for a long time, resets the minimum and maximum values of
  * the recorded weights
  */
-void buttonCallback(uint8_t btnId, ButtonPress btnPress, MBLoadCell *loadcell)
+void buttonCallback(ButtonEvent btnPress, MBLoadCell *loadcell)
 {
-    if (btnId == btnUserId && btnPress == ButtonPress::DOWN)
+    if (btnPress == ButtonEvent::PRESSED)
         TRACE(
             "## MAX: %.2f [Kg] (ts: %.3f)\t##\tMIN: %.2f [Kg] (ts: %.3f) ##\n",
-            loadcell->getMaxWeight().weight,
-            loadcell->getMaxWeight().weightTimestamp / 1000000.0,
-            loadcell->getMinWeight().weight,
-            loadcell->getMinWeight().weightTimestamp / 1000000.0);
+            loadcell->getMaxWeight().load,
+            loadcell->getMaxWeight().loadTimestamp / 1000000.0,
+            loadcell->getMinWeight().load,
+            loadcell->getMinWeight().load / 1000000.0);
 
-    if (btnId == btnUserId && btnPress == ButtonPress::LONG)
+    if (btnPress == ButtonEvent::LONG_PRESS)
         loadcell->resetMaxMinWeights();
 }
 
@@ -67,14 +68,9 @@ int main()
      */
     MBLoadCell loadCell(LoadCellModes::CONT_MOD_T, 2, 115200);
 
-    // Binding the load cell instance to the callback to be called
-    std::function<void(uint8_t, ButtonPress)> callback =
-        std::bind(buttonCallback, std::placeholders::_1, std::placeholders::_2,
-                  &loadCell);
-
     // Instanciating the button
-    ButtonHandler<button> btnHandler(btnUserId, callback);
-    btnHandler.start();
+    ButtonHandler::getInstance().registerButtonCallback(
+        button::getPin(), bind(buttonCallback, _1, &loadCell));
 
     // Initializing the load cell
     if (!loadCell.init())
diff --git a/src/tests/drivers/test-dsgamma.cpp b/src/tests/drivers/test-dsgamma.cpp
index 03c9555075b41cec6929157105bea79f95b9037a..1445ae8a674aaa467b3713faf1f8eef368ab9694 100644
--- a/src/tests/drivers/test-dsgamma.cpp
+++ b/src/tests/drivers/test-dsgamma.cpp
@@ -170,8 +170,10 @@ int main()
         }
 
         printf("Bytes received: %d\nDropped: %d,Time:%d ms\n", index, lostBytes,
+               // cppcheck-suppress uninitvar
                (int)(endT - startT));
         printf("Speed: %.3f KB/s\n",
+               // cppcheck-suppress uninitvar
                index / ((endT - startT) / 1024.0f) / 1024.0f);
         /*  for (int i = 0; i < index; i++)
           {
diff --git a/src/tests/drivers/usart/test-usart.cpp b/src/tests/drivers/usart/test-usart.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ce99d3d5a5a764b696598f8b0bf2c1ef49eccd7f
--- /dev/null
+++ b/src/tests/drivers/usart/test-usart.cpp
@@ -0,0 +1,182 @@
+/* Copyright (c) 2022 Skyward Experimental Rocketry
+ * Author: 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 <fmt/format.h>
+
+#include <cassert>
+
+#include "drivers/usart/USART.h"
+#include "miosix.h"
+#include "string"
+#include "string.h"
+#include "thread"
+#include "utils/SerialInterface.h"
+
+using namespace miosix;
+using namespace Boardcore;
+
+/**
+ * SETUP:
+ * - connect the default serial (USART3) to the pc
+ * - connect usartx_rx to the usarty_tx
+ * - connect usarty_rx to the usartx_tx
+ *
+ * WARNING: If using the STM32SerialWrapper to read, the test passes only if we:
+ *  1. do a write/writeString with USART or STM32SerialWrapper;
+ *  2. sleep 10ms or more (or a printf...)
+ *  3. we finally read from STM32SerialWrapper::read method.
+ * if we omit the waiting time we end up failing the test. If more data is sent,
+ * then more baudrates tests will fail (from 2400 to greater). The USART driver
+ * doesn't have this problem.
+ */
+
+typedef struct
+{
+    char dataChar;
+    int dataInt;
+    float dataFloat;
+    double dataDouble;
+
+    std::string print()
+    {
+        return fmt::format("{},{:d},{:f},{:f}", dataChar, dataInt, dataFloat,
+                           dataDouble);
+    }
+} StructToSend;
+StructToSend struct_tx = {'C', 42, 420.69, 48.84};
+char buf_tx[64]        = "Testing communication, but very very very loong :D";
+USARTInterface::Baudrate baudrates[] = {
+    USARTInterface::Baudrate::B2400,   USARTInterface::Baudrate::B9600,
+    USARTInterface::Baudrate::B19200,  USARTInterface::Baudrate::B38400,
+    USARTInterface::Baudrate::B57600,  USARTInterface::Baudrate::B115200,
+    USARTInterface::Baudrate::B230400, USARTInterface::Baudrate::B460800,
+    USARTInterface::Baudrate::B921600};
+
+/**
+ * Communication: src -> dst
+ * tests the writeString, write and read methods of the USART drivers
+ */
+bool testCommunicationSequential(USARTInterface *src, USARTInterface *dst)
+{
+    char buf_rx[64];
+    StructToSend struct_rx;
+    bool passed = true;
+
+    /************************** SENDING STRING **************************/
+    printf("Sending string\n");
+
+    printf("\t%d--> sent: \t'%s'\n", src->getId(), buf_tx);
+    src->writeString(buf_tx);
+    // Thread::sleep(10); // enable to pass the test with STM32SerialWrapper
+    dst->read(buf_rx, 64);
+
+    printf("\t%d<-- received: \t'%s'\n", dst->getId(), buf_rx);
+
+    if (strcmp(buf_tx, buf_rx) == 0)
+    {
+        printf("*** %d -> %d WORKING!\n", src->getId(), dst->getId());
+    }
+    else
+    {
+        printf("### %d -> %d ERROR!\n", src->getId(), dst->getId());
+        passed = false;
+    }
+
+    /*********************** SENDING BINARY DATA ************************/
+
+    printf("Sending binary data\n");
+    printf("\t%d--> sent: \t'%s'\n", src->getId(), struct_tx.print().c_str());
+    src->write(&struct_tx, sizeof(StructToSend));
+    // Thread::sleep(10); // enable to pass the test with STM32SerialWrapper
+    dst->read(&struct_rx, sizeof(StructToSend));
+    printf("\t%d<-- received: \t'%s'\n", dst->getId(),
+           struct_rx.print().c_str());
+
+    if (memcmp(&struct_tx, &struct_rx, sizeof(StructToSend)) == 0)
+    {
+        printf("*** %d -> %d WORKING!\n", src->getId(), dst->getId());
+    }
+    else
+    {
+        printf("### %d -> %d ERROR!\n", src->getId(), dst->getId());
+        passed = false;
+    }
+
+    return passed;
+}
+
+/* Available default pins:
+ * - USART1: tx=PA9  rx=PA10
+ * - USART2: tx=PA2  rx=PA3
+ * - USART3: tx=PB10 rx=PB11
+ * - UART4:  tx=PA0 rx=PA1
+ * - UART5:  tx=PC12 rx=PD2
+ * - USART6: tx=PC6 rx=PC7
+ * - UART7: tx=PE8 rx=PE7
+ * - UART8: tx=PE1 rx=PE0
+ */
+int main()
+{
+    bool testPassed = true;
+    printf("*** SERIAL 3 WORKING!\n");
+    for (unsigned int iBaud = 0;
+         iBaud < sizeof(baudrates) / sizeof(baudrates[0]); iBaud++)
+    {
+        USARTInterface::Baudrate baudrate = baudrates[iBaud];
+        printf("\n\n########################### %d\n", (int)baudrate);
+
+        // declaring the usart peripherals
+        STM32SerialWrapper usartx(USART1, baudrate, u1rx2::getPin(),
+                                  u1tx1::getPin());
+        usartx.init();
+
+        USART usarty(UART4, baudrate);
+        // usarty.initPins(u5tx::getPin(), 8, u5rx::getPin(), 8);
+        // usarty.setOversampling(false);
+        // usarty.setStopBits(1);
+        // usarty.setWordLength(USART::WordLength::BIT8);
+        // usarty.setParity(USART::ParityBit::NO_PARITY);
+        usarty.init();
+
+        // testing transmission (both char and binary) "serial 1 <- serial 2"
+        testPassed &= testCommunicationSequential(&usartx, &usarty);
+
+        // testing transmission (both char and binary) "serial 1 -> serial 2"
+        testPassed &= testCommunicationSequential(&usarty, &usartx);
+    }
+
+    if (testPassed)
+    {
+        printf(
+            "********************************\n"
+            "***        TEST PASSED       ***\n"
+            "********************************\n");
+    }
+    else
+    {
+        printf(
+            "################################\n"
+            "###        TEST FAILED       ###\n"
+            "################################\n");
+    }
+    return 0;
+}
diff --git a/src/tests/drivers/xbee/XbeeTestData.h b/src/tests/drivers/xbee/XbeeTestData.h
index 26229c35e1286fa63a27154407547ca6a6ec8d8e..cde8e70c9d678bd10c26a2907de0d53b0dc59686 100644
--- a/src/tests/drivers/xbee/XbeeTestData.h
+++ b/src/tests/drivers/xbee/XbeeTestData.h
@@ -22,7 +22,7 @@
 
 #pragma once
 
-#include <math/Stats.h>
+#include <utils/Stats/Stats.h>
 
 #include <array>
 #include <ostream>
@@ -122,7 +122,7 @@ struct EnergyScanData
 
     EnergyScanData() = default;
 
-    EnergyScanData(long long ts, array<int, 30> scan)
+    EnergyScanData(long long ts, const array<int, 30> scan)
     {
         for (int i = 0; i < 30; i++)
         {
diff --git a/src/tests/drivers/xbee/XbeeTransceiver.h b/src/tests/drivers/xbee/XbeeTransceiver.h
index 049b9afe73c538d7db0245505ff6446ed2e30470..3e41850e557367880e47fde90bcd57fc11788527 100644
--- a/src/tests/drivers/xbee/XbeeTransceiver.h
+++ b/src/tests/drivers/xbee/XbeeTransceiver.h
@@ -28,7 +28,7 @@
 #include <radio/Xbee/APIFramesLog.h>
 #include <radio/Xbee/Xbee.h>
 #include <utils/Debug.h>
-#include <utils/testutils/ThroughputCalculator.h>
+#include <utils/TestUtils/ThroughputCalculator.h>
 
 #include <functional>
 
diff --git a/src/tests/drivers/xbee/gui/EnergyScanScreen.h b/src/tests/drivers/xbee/gui/EnergyScanScreen.h
index 081d375486f11fba2cf4a95af1e08f81d248e069..49259f2d8967279274d3adca993b0bdaec713a8b 100644
--- a/src/tests/drivers/xbee/gui/EnergyScanScreen.h
+++ b/src/tests/drivers/xbee/gui/EnergyScanScreen.h
@@ -22,8 +22,8 @@
 
 #pragma once
 
-#include <math/Stats.h>
 #include <mxgui/display.h>
+#include <utils/Stats/Stats.h>
 #include <utils/gui/GridLayout.h>
 #include <utils/gui/TextView.h>
 #include <utils/gui/VerticalLayout.h>
diff --git a/src/tests/drivers/xbee/gui/StatusScreen.h b/src/tests/drivers/xbee/gui/StatusScreen.h
index 19cc9e6e9b8e4224c177b60da5f6299762f82d2b..b397715e0d772a8261ddfbb42f127b320ac9b848 100644
--- a/src/tests/drivers/xbee/gui/StatusScreen.h
+++ b/src/tests/drivers/xbee/gui/StatusScreen.h
@@ -24,11 +24,11 @@
 
 #include <logger/Logger.h>
 #include <mxgui/display.h>
+#include <utils/TestUtils/ThroughputCalculator.h>
 #include <utils/gui/GridLayout.h>
 #include <utils/gui/OptionView.h>
 #include <utils/gui/TextView.h>
 #include <utils/gui/VerticalLayout.h>
-#include <utils/testutils/ThroughputCalculator.h>
 
 #include <cstdint>
 #include <cstring>
diff --git a/src/tests/drivers/xbee/test-xbee-gui.cpp b/src/tests/drivers/xbee/test-xbee-gui.cpp
index dacc4bca74194bae6ef50bec5a0dd4857f049cbc..407f90a1dd4c9682e45c5aa504388544b83ea0b0 100644
--- a/src/tests/drivers/xbee/test-xbee-gui.cpp
+++ b/src/tests/drivers/xbee/test-xbee-gui.cpp
@@ -27,7 +27,7 @@
 #include <radio/Xbee/APIFramesLog.h>
 #include <radio/Xbee/ATCommands.h>
 #include <radio/Xbee/Xbee.h>
-#include <utils/ButtonHandler.h>
+#include <utils/ButtonHandler/ButtonHandler.h>
 
 #include <array>
 #include <cstdio>
@@ -75,7 +75,6 @@ Xbee::Xbee* xbee = nullptr;
 ConstSendInterval sndInt{0};
 XbeeTransceiver* trans = nullptr;
 XbeeGUI* gui;
-ButtonHandler<GpioUserBtn>* btnHandler;
 
 unsigned int markCounter = 1;
 
@@ -145,10 +144,9 @@ int main()
     // GUI
     gui = new XbeeGUI();
 
-    btnHandler = new ButtonHandler<GpioUserBtn>(
-        0, bind(&ScreenManager::onButtonPress, &gui->screenManager, _2));
-
-    btnHandler->start();
+    ButtonHandler::getInstance().registerButtonCallback(
+        GpioUserBtn::getPin(),
+        bind(&ScreenManager::onButtonEvent, &gui->screenManager, _1));
 
     gui->screenConfig.btnStart.addOnInteractionListener(onStartButtonClick);
     gui->screenConfig.btnEnergy.addOnInteractionListener(onEnergyButtonClick);
@@ -159,25 +157,22 @@ int main()
     gui->screenEnergy.btnStop.addOnInteractionListener(onStopButtonClick);
     gui->screenEnergy.btnMark.addOnInteractionListener(onMarkButtonClick);
     gui->screenEnergy.btnReset.addOnInteractionListener(
-        [&](View* d, Interaction action)
+        [&](View* d __attribute__((unused)), Interaction action)
         {
-            UNUSED(d);
             if (action == Interaction::CLICK)
                 gui->screenEnergy.resetStats();
         });
 
     gui->screenEnd.tvF.addOnInteractionListener(
-        [&](View* d, Interaction action)
+        [&](View* d __attribute__((unused)), Interaction action)
         {
-            UNUSED(d);
             if (action == Interaction::CLICK)
                 gui->screenManager.showScreen(XbeeGUI::SCREEN_RESPECT);
         });
 
     gui->screenEnd.tvReset.addOnInteractionListener(
-        [&](View* d, Interaction action)
+        [&](View* d __attribute__((unused)), Interaction action)
         {
-            UNUSED(d);
             if (action == Interaction::CLICK)
                 miosix::reboot();
         });
@@ -218,9 +213,8 @@ int main()
     }
 }
 
-void onStartButtonClick(View* btn, Interaction action)
+void onStartButtonClick(View* btn __attribute__((unused)), Interaction action)
 {
-    UNUSED(btn);
     if (action == Interaction::CLICK)
     {
 
@@ -267,9 +261,8 @@ void onStopButtonClick(View* btn, Interaction action)
     }
 }
 
-void onMarkButtonClick(View* btn, Interaction action)
+void onMarkButtonClick(View* btn __attribute__((unused)), Interaction action)
 {
-    UNUSED(btn);
     if (action == Interaction::CLICK)
     {
         Mark m{getTick(), markCounter++};
@@ -283,9 +276,8 @@ void onMarkButtonClick(View* btn, Interaction action)
     }
 }
 
-void onEnergyButtonClick(View* btn, Interaction action)
+void onEnergyButtonClick(View* btn __attribute__((unused)), Interaction action)
 {
-    UNUSED(btn);
     if (action == Interaction::CLICK)
     {
         energyScanner.start();
diff --git a/src/tests/events/fsm/test-fsm.cpp b/src/tests/events/fsm/test-fsm.cpp
index 7c119cc16ddddbd6a58914e4f8f422a59a60ac09..b6f2dc4f7e3a8dfd43f198962d74abe988ee9c60 100644
--- a/src/tests/events/fsm/test-fsm.cpp
+++ b/src/tests/events/fsm/test-fsm.cpp
@@ -33,17 +33,18 @@ int main()
 {
     FSMExample fsm;
 
-    sEventBroker.start();  // Start broker thread
-    fsm.start();           // Start FSM thread
+    EventBroker::getInstance().start();  // Start broker thread
+    fsm.start();                         // Start FSM thread
 
     // State machine starts in state S1. Post EV_A to move to S2
-    sEventBroker.post(Event{EV_A}, TOPIC_T1);
+    EventBroker::getInstance().post(Event{EV_A}, TOPIC_T1);
 
     // FSM now in State S2
-    sEventBroker.post(Event{EV_E},
-                      TOPIC_T1);  // This makes the FSM print hello world
+    EventBroker::getInstance().post(
+        Event{EV_E},
+        TOPIC_T1);  // This makes the FSM print hello world
 
-    sEventBroker.post(Event{EV_C}, TOPIC_T1);  // Transition to S3
+    EventBroker::getInstance().post(Event{EV_C}, TOPIC_T1);  // Transition to S3
 
     printf("Waiting for the FSM to transition to S1\n");
     Thread::sleep(1100);
@@ -53,7 +54,7 @@ int main()
 
     // Since previously we've been in state S3, now v == 1 and EV_A will make
     // the FSM transition to S4 instead of S1
-    sEventBroker.post(Event{EV_A}, TOPIC_T1);
+    EventBroker::getInstance().post(Event{EV_A}, TOPIC_T1);
 
     // Now the state machine is in state S4
 
@@ -68,5 +69,5 @@ int main()
     // Stop the threds, even though we will never reach this point, but just for
     // correctness ;)
     fsm.stop();
-    sEventBroker.stop();
+    EventBroker::getInstance().stop();
 }
diff --git a/src/tests/events/fsm/test-fsm.h b/src/tests/events/fsm/test-fsm.h
index 7b16d6703e9cc8c6910dd012b2377d5461d7063c..f603cdfec09f72e09864f4dbeaf43e08264c9b6d 100644
--- a/src/tests/events/fsm/test-fsm.h
+++ b/src/tests/events/fsm/test-fsm.h
@@ -40,8 +40,8 @@ namespace Boardcore
  */
 enum ExampleEvents : uint8_t
 {
-    EV_A = EV_FIRST_SIGNAL,  // The first event must always have
-                             // value EV_FIRST_SIGNAL
+    EV_A = EV_FIRST_CUSTOM,  // The first event must always have
+                             // value EV_FIRST_CUSTOM
     EV_B,  // Values for the following event can be manually specified or
            // assigned automatically
     EV_C,
@@ -64,7 +64,7 @@ enum ExampleTopics : uint8_t
  */
 void traceState(uint8_t state, const Event& ev)
 {
-    switch (ev.code)
+    switch (ev)
     {
         case EV_ENTRY:
         {
@@ -78,7 +78,7 @@ void traceState(uint8_t state, const Event& ev)
         }
         default:
         {
-            printf("(S%d) Received event %d\n", state, ev.code);
+            printf("(S%d) Received event %d\n", state, ev);
             break;
         }
     }
@@ -110,10 +110,10 @@ public:
     FSMExample() : FSM(&FSMExample::state_S1), v(0)
     {
         // Subscribe for events posted on TOPIC_T1
-        sEventBroker.subscribe(this, TOPIC_T1);
+        EventBroker::getInstance().subscribe(this, TOPIC_T1);
     }
 
-    ~FSMExample() { sEventBroker.unsubscribe(this); }
+    ~FSMExample() { EventBroker::getInstance().unsubscribe(this); }
 
 private:
     /*
@@ -129,7 +129,7 @@ private:
         // state machine on the terminal.
         traceState(STATE_S1, ev);
 
-        switch (ev.code)
+        switch (ev)
         {
             // It's always good to add braces to every single case statement, to
             // avoid problems
@@ -169,7 +169,7 @@ private:
     {
         traceState(STATE_S2, ev);
 
-        switch (ev.code)
+        switch (ev)
         {
             case EV_ENTRY:
             {
@@ -200,7 +200,7 @@ private:
     {
         traceState(STATE_S3, ev);
 
-        switch (ev.code)
+        switch (ev)
         {
             case EV_ENTRY:
             {
@@ -208,15 +208,15 @@ private:
                 v = 1;
 
                 // Post EV_D to itself in 1 seconds
-                delayed_ev_id =
-                    sEventBroker.postDelayed<1000>(Event{EV_D}, TOPIC_T1);
+                delayed_ev_id = EventBroker::getInstance().postDelayed<1000>(
+                    Event{EV_D}, TOPIC_T1);
 
                 break;
             }
             case EV_EXIT:
             {
                 // Remove the delayed event in case it has not fired yet
-                sEventBroker.removeDelayed(delayed_ev_id);
+                EventBroker::getInstance().removeDelayed(delayed_ev_id);
                 break;
             }
             case EV_B:
@@ -243,7 +243,7 @@ private:
     {
         traceState(STATE_S4, ev);
 
-        switch (ev.code)
+        switch (ev)
         {
             case EV_ENTRY:
             {
diff --git a/src/tests/sensors/analog/test-battery-voltage.cpp b/src/tests/sensors/analog/test-battery-voltage.cpp
index 1e2065433ee6f6da108ba4e28896aa31294ab755..3bc6d96fbbfb94fdfcad3c9dc762e4a0d27bb020 100644
--- a/src/tests/sensors/analog/test-battery-voltage.cpp
+++ b/src/tests/sensors/analog/test-battery-voltage.cpp
@@ -23,7 +23,7 @@
 #include <drivers/adc/InternalADC.h>
 #include <drivers/timer/TimestampTimer.h>
 #include <miosix.h>
-#include <sensors/analog/battery/BatteryVoltageSensor.h>
+#include <sensors/analog/BatteryVoltageSensor.h>
 #include <utils/Debug.h>
 
 using namespace Boardcore;
diff --git a/src/tests/sensors/analog/test-current-sensor.cpp b/src/tests/sensors/analog/test-current-sensor.cpp
index 5983e82b6a53b915bf9a63036c847c66c1e1b2a2..b223d9041c2fb626326adb5c3e5bd29cf2063163 100644
--- a/src/tests/sensors/analog/test-current-sensor.cpp
+++ b/src/tests/sensors/analog/test-current-sensor.cpp
@@ -24,7 +24,7 @@
 #include <drivers/timer/TimestampTimer.h>
 #include <interfaces-impl/gpio_impl.h>
 #include <miosix.h>
-#include <sensors/analog/current/CurrentSensor.h>
+#include <sensors/analog/CurrentSensor.h>
 #include <utils/Debug.h>
 
 using namespace miosix;
diff --git a/src/tests/sensors/calibration/test-calibration-benchmark.cpp b/src/tests/sensors/calibration/test-calibration-benchmark.cpp
index 066536cf73f042b53047cc615ae3ab5c67070f9d..1ed6b0f81de257186fd9c1c858b8648fce157998 100644
--- a/src/tests/sensors/calibration/test-calibration-benchmark.cpp
+++ b/src/tests/sensors/calibration/test-calibration-benchmark.cpp
@@ -21,9 +21,9 @@
  */
 
 /*  ACCELEROMETER calibration: please set the chosen one to 1  */
-#define BIAS_CALIBRATION_LOAD_TEST 1
-#define SIX_PARAMETER_CALIBRATION_LOAD_TEST 0
-#define TWELVE_PARAMETER_CALIBRATION_LOAD_TEST 0
+#define BIAS_CALIBRATION_LOAD_TEST
+// #define SIX_PARAMETER_CALIBRATION_LOAD_TEST
+// #define TWELVE_PARAMETER_CALIBRATION_LOAD_TEST
 
 /* Expressed in Hertz: valid values: 1 <= frequency <= 1000 */
 #define SAMPLE_FREQUENCY_LOAD_TEST 1000
@@ -35,7 +35,6 @@
 #include <sensors/calibration/BiasCalibration.h>
 #include <sensors/calibration/HardIronCalibration.h>
 #include <sensors/calibration/SixParameterCalibration.h>
-#include <sensors/calibration/SoftIronCalibration.h>
 #include <sensors/calibration/TwelveParameterCalibration.h>
 #include <utils/Debug.h>
 
@@ -47,17 +46,17 @@ volatile AccelerometerData testData;
 
 int main()
 {
-#if BIAS_CALIBRATION_LOAD_TEST
+#ifdef BIAS_CALIBRATION_LOAD_TEST
     BiasCorrector<AccelerometerData> corrector;
     TRACE("Using BIAS calibration model.\n");
 #endif
 
-#if SIX_PARAMETER_CALIBRATION_LOAD_TEST
+#ifdef SIX_PARAMETER_CALIBRATION_LOAD_TEST
     SixParameterCorrector<AccelerometerData> corrector;
     TRACE("Using SIX-PARAMETER calibration model.\n");
 #endif
 
-#if TWELVE_PARAMETER_CALIBRATION_LOAD_TEST
+#ifdef TWELVE_PARAMETER_CALIBRATION_LOAD_TEST
     TwelveParameterCorrector<AccelerometerData> corrector;
     TRACE("Using TWELVE-PARAMETER calibration model.\n");
 #endif
diff --git a/src/tests/sensors/calibration/test-calibration-stats.cpp b/src/tests/sensors/calibration/test-calibration-stats.cpp
index a52fabb0f3b7ff29e35d60cbf52b6de16b8d648f..40aa29ad31fb57621dc67c00d6c83193c4b6d30b 100644
--- a/src/tests/sensors/calibration/test-calibration-stats.cpp
+++ b/src/tests/sensors/calibration/test-calibration-stats.cpp
@@ -23,13 +23,14 @@
 #define SENSOR_LIS3DSH_STATS_TEST 1
 
 #include <drivers/spi/SPIDriver.h>
-#include <math/Stats.h>
 #include <miosix.h>
+#include <utils/Stats/Stats.h>
 
 #if SENSOR_LIS3DSH_STATS_TEST
 #include <sensors/LIS3DSH/LIS3DSH.h>
 #endif
 
+#include <sensors/calibration/AxisOrientation.h>
 #include <sensors/calibration/Calibration.h>
 
 using namespace Boardcore;
diff --git a/src/tests/sensors/calibration/test-calibration.cpp b/src/tests/sensors/calibration/test-calibration.cpp
index 4172dec1c3bface25671de10f8f8a3f9479c511f..9e2429572ddd71e14b969bb8c10f77d473e9a619 100644
--- a/src/tests/sensors/calibration/test-calibration.cpp
+++ b/src/tests/sensors/calibration/test-calibration.cpp
@@ -39,7 +39,6 @@
 #include <sensors/calibration/BiasCalibration.h>
 #include <sensors/calibration/HardIronCalibration.h>
 #include <sensors/calibration/SixParameterCalibration.h>
-#include <sensors/calibration/SoftIronCalibration.h>
 #include <sensors/calibration/TwelveParameterCalibration.h>
 #include <utils/Debug.h>
 
diff --git a/src/tests/sensors/test-bmx160-with-correction.cpp b/src/tests/sensors/test-bmx160-with-correction.cpp
index 27e005d273636ecb3605dd73b644ca7020ede208..32c0363c182d739ca6e7e98b1715f5660c8bfaf6 100644
--- a/src/tests/sensors/test-bmx160-with-correction.cpp
+++ b/src/tests/sensors/test-bmx160-with-correction.cpp
@@ -160,10 +160,8 @@ int main()
     return 0;
 }
 
-void bmx160Sample(void *args)
+void bmx160Sample(void *args __attribute__((unused)))
 {
-    UNUSED(args);
-
     while (!stopSamplingThread)
     {
         // Sample the bmx160
diff --git a/src/tests/sensors/test-hx711.cpp b/src/tests/sensors/test-hx711.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f4acb0d76fc4e5c9dee08a4b8a6477bb478a192c
--- /dev/null
+++ b/src/tests/sensors/test-hx711.cpp
@@ -0,0 +1,73 @@
+/* Copyright (c) 2022 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 <drivers/timer/TimestampTimer.h>
+#include <miosix.h>
+#include <sensors/HX711/HX711.h>
+
+using namespace miosix;
+using namespace Boardcore;
+
+GpioPin sckPin  = GpioPin(GPIOB_BASE, 3);
+GpioPin misoPin = GpioPin(GPIOB_BASE, 4);
+
+void initBoard()
+{
+    // Setup gpio pins
+    sckPin.mode(Mode::ALTERNATE);
+    sckPin.alternateFunction(5);
+    misoPin.mode(Mode::ALTERNATE);
+    misoPin.alternateFunction(5);
+}
+
+int main()
+{
+    // Enable SPI clock and set gpios
+    initBoard();
+
+    SPIBus spiBus(SPI1);
+    HX711 sensor{spiBus, sckPin};
+    sensor.setScale(1.0f);
+
+    // Average 100 samples and then apply the offset
+    printf("Measuring offset...\n");
+    float average = 0;
+    for (int i = 0; i < 100; i++)
+    {
+        sensor.sample();
+        average += sensor.getLastSample().load;
+        Thread::sleep(12);
+    }
+    average /= 100;
+    sensor.setOffset(average);
+    sensor.setScale(214000);
+
+    while (true)
+    {
+        sensor.sample();
+
+        printf("[%.1f] %f\n", sensor.getLastSample().loadTimestamp / 1e6,
+               sensor.getLastSample().load);
+
+        Thread::sleep(10);
+    }
+}
diff --git a/src/tests/sensors/test-l3gd20-fifo.cpp b/src/tests/sensors/test-l3gd20-fifo.cpp
index 31208a2f48ff4754786a88ab7b99eea51c8b0a57..2b6d46286f8df3ff7f3b620eb4e0a04860642da4 100644
--- a/src/tests/sensors/test-l3gd20-fifo.cpp
+++ b/src/tests/sensors/test-l3gd20-fifo.cpp
@@ -80,7 +80,7 @@ static constexpr unsigned int FIFO_WATERMARK = 12;
 // output data rate) is the following:
 static constexpr float SAMPLE_FREQUENCY = 782.3f;
 
-struct GyroSample
+struct GyroSampleFifo
 {
     int fifoNum;
     L3GD20Data gyro;
@@ -93,7 +93,7 @@ struct GyroSample
 // How many samples to collect
 static constexpr int NUM_SAMPLES = SAMPLE_FREQUENCY * 20;
 
-GyroSample data[NUM_SAMPLES];
+GyroSampleFifo data[NUM_SAMPLES];
 int dataCounter = 0;
 
 // Last interrupt received timer tick
diff --git a/src/tests/sensors/test-lis3mdl.cpp b/src/tests/sensors/test-lis3mdl.cpp
index b1117912111407c0f38b00f948884057bec72647..af8abf3d3535ddc8fe187479abd88a8dbb857070 100644
--- a/src/tests/sensors/test-lis3mdl.cpp
+++ b/src/tests/sensors/test-lis3mdl.cpp
@@ -76,8 +76,7 @@ int main()
     while (true)
     {
         sensor.sample();
-        LIS3MDLData data = sensor.getLastSample();
-        UNUSED(data);
+        LIS3MDLData data __attribute__((unused)) = sensor.getLastSample();
         TRACE("%f C | x: %f | y: %f | z %f\n", data.temperature,
               data.magneticFieldX, data.magneticFieldY, data.magneticFieldZ);
         miosix::Thread::sleep(2000);
diff --git a/src/tests/sensors/test-max31855.cpp b/src/tests/sensors/test-max31855.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..00171ebab0da1cb58c04885821dc96e0b7526754
--- /dev/null
+++ b/src/tests/sensors/test-max31855.cpp
@@ -0,0 +1,75 @@
+/* Copyright (c) 2022 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 <drivers/timer/TimestampTimer.h>
+#include <miosix.h>
+#include <sensors/MAX31855/MAX31855.h>
+
+using namespace miosix;
+using namespace Boardcore;
+
+GpioPin sckPin  = GpioPin(GPIOF_BASE, 7);
+GpioPin misoPin = GpioPin(GPIOF_BASE, 8);
+GpioPin csPin   = GpioPin(GPIOD_BASE, 13);
+
+GpioPin csMems = GpioPin(GPIOC_BASE, 1);
+
+void initBoard()
+{
+    // Setup gpio pins
+    csPin.mode(Mode::OUTPUT);
+    csPin.high();
+    csMems.mode(Mode::OUTPUT);
+    csMems.high();
+    sckPin.mode(Mode::ALTERNATE);
+    sckPin.alternateFunction(5);
+    misoPin.mode(Mode::ALTERNATE);
+    misoPin.alternateFunction(5);
+}
+
+int main()
+{
+    // Enable SPI clock and set gpios
+    initBoard();
+
+    SPIBus spiBus(SPI5);
+    MAX31855 sensor{spiBus, csPin};
+
+    printf("Starting process verification!\n");
+
+    if (!sensor.selfTest())
+    {
+        printf("Sensor self test failed!\n");
+    }
+
+    while (true)
+    {
+        sensor.sample();
+        TemperatureData sample = sensor.getLastSample();
+
+        printf("[%.2f] %.2f\n", sample.temperatureTimestamp / 1e6,
+               sample.temperature);
+
+        Thread::sleep(500);
+    }
+    return 0;
+}
diff --git a/src/tests/sensors/test-max6675.cpp b/src/tests/sensors/test-max6675.cpp
index 48f73fd292473cd23190f39f6b3b971e3fbff523..be541c114bb5f38e1bd8f53634cf0dd71d501a63 100644
--- a/src/tests/sensors/test-max6675.cpp
+++ b/src/tests/sensors/test-max6675.cpp
@@ -29,7 +29,6 @@ using namespace Boardcore;
 
 GpioPin sckPin  = GpioPin(GPIOE_BASE, 2);
 GpioPin misoPin = GpioPin(GPIOE_BASE, 5);
-GpioPin mosiPin = GpioPin(GPIOE_BASE, 6);
 GpioPin csPin   = GpioPin(GPIOE_BASE, 4);
 
 void initBoard()
@@ -41,8 +40,6 @@ void initBoard()
     sckPin.alternateFunction(5);
     misoPin.mode(Mode::ALTERNATE);
     misoPin.alternateFunction(5);
-    mosiPin.mode(Mode::ALTERNATE);
-    mosiPin.alternateFunction(5);
 }
 
 int main()
@@ -69,5 +66,4 @@ int main()
 
         Thread::sleep(500);
     }
-    return 0;
 }
diff --git a/src/tests/sensors/test-mpu9250.cpp b/src/tests/sensors/test-mpu9250.cpp
index ba2ad4ea4f8d1687c18554f12cacf41fa892ff35..03e798fa2ac28a1d7c23b334f8c715b67d2db0ae 100644
--- a/src/tests/sensors/test-mpu9250.cpp
+++ b/src/tests/sensors/test-mpu9250.cpp
@@ -53,18 +53,11 @@ void initBoard()
 int main()
 {
     // Enable SPI clock and set gpios
-    initBoard();
-
-    // SPI configuration setup
-
-    SPIBusConfig spiConfig;
-    spiConfig.clockDivider = SPI::ClockDivider::DIV_64;
-    spiConfig.mode         = SPI::Mode::MODE_3;
-    SPIBus spiBus(SPI2);
-    SPISlave spiSlave(spiBus, csPin, spiConfig);
+    // initBoard();
 
     // Device initialization
-    MPU9250 mpu9250(spiSlave);
+    SPIBus spiBus(SPI1);
+    MPU9250 mpu9250(spiBus, sensors::mpu9250::cs::getPin());
 
     // Initialize the device
     bool result = mpu9250.init();
@@ -85,7 +78,7 @@ int main()
         printf("%lld,%f,%f,%f\n", data.magneticFieldTimestamp,
                data.magneticFieldX, data.magneticFieldY, data.magneticFieldZ);
 
-        // Serial communicaion at 115200 baud takes aprx. 10ms
+        // Serial communication at 115200 baud takes approximately 10ms
         // miosix::delayMs(10);
     }
 
diff --git a/src/tests/sensors/test-ms5803.cpp b/src/tests/sensors/test-ms5803.cpp
index f3a8bcc1539d5b4eb9a7255f9056a85d043f6fec..4c00318899a0fd596e002c0baa3e350203ecd876 100644
--- a/src/tests/sensors/test-ms5803.cpp
+++ b/src/tests/sensors/test-ms5803.cpp
@@ -35,12 +35,9 @@ using namespace miosix;
 
 int main()
 {
-    SPIBusConfig spiConfig;
+    // Sample temperature every 10 pressure samples
     SPIBus spiBus(SPI1);
-    SPISlave spiSlave(spiBus, miosix::sensors::ms5803::cs::getPin(), spiConfig);
-
-    // Sample temperature every 5 pressure samples
-    MS5803 sensor(spiSlave, 10);
+    MS5803 sensor(spiBus, miosix::sensors::ms5803::cs::getPin(), {}, 10);
 
     Thread::sleep(100);
 
diff --git a/src/tests/sensors/test-ubloxgps.cpp b/src/tests/sensors/test-ubloxgps.cpp
index 0c611daf4e7ffc2ccabcc56da70d989496e1d45f..24b6cc092a73e531765fbbe4897a29133a827014 100644
--- a/src/tests/sensors/test-ubloxgps.cpp
+++ b/src/tests/sensors/test-ubloxgps.cpp
@@ -69,15 +69,17 @@ int main()
 
     while (true)
     {
+        printf("a\n");
         // Give time to the thread
         Thread::sleep(1000 / RATE);
 
         // Sample
         gps.sample();
+        printf("b\n");
         dataGPS = gps.getLastSample();
 
         // Print out the latest sample
-        TRACE(
+        printf(
             "[gps] timestamp: % 4.3f, fix: %01d lat: % f lon: % f "
             "height: %4.1f nsat: %2d speed: %3.2f velN: % 3.2f velE: % 3.2f "
             "track %3.1f\n",
diff --git a/src/tests/sensors/test-vn100.cpp b/src/tests/sensors/test-vn100.cpp
index 6d3c0bdbf7762840613c311729d25a08e6d70ada..df1d1870ec448660c8fe809f3cf3fb41783c7be3 100644
--- a/src/tests/sensors/test-vn100.cpp
+++ b/src/tests/sensors/test-vn100.cpp
@@ -29,19 +29,11 @@ using namespace Boardcore;
 
 int main()
 {
-    GpioPin tx(GPIOB_BASE, 6);
-    GpioPin rx(GPIOB_BASE, 7);
     VN100Data sample;
     string sampleRaw;
-    VN100 sensor{1, VN100::BaudRates::Baud_921600,
+    VN100 sensor{USART1, USARTInterface::Baudrate::B921600,
                  VN100::CRCOptions::CRC_ENABLE_16};
 
-    tx.mode(Mode::ALTERNATE);
-    rx.mode(Mode::ALTERNATE);
-
-    tx.alternateFunction(7);
-    rx.alternateFunction(7);
-
     // Let the sensor start up
     Thread::sleep(1000);
 
@@ -64,18 +56,19 @@ int main()
     // Sample and print 100 samples
     for (int i = 0; i < 100; i++)
     {
-        /*sensor.sample();
+        sensor.sample();
         sample = sensor.getLastSample();
-        //printf("%" PRIu64 ", %.3f, %.3f, %.3f\n",
-        sample.accelerationTimestamp, sample.accelerationX,
-        sample.accelerationY, sample.accelerationZ);
-        //printf("%.3f, %.3f, %.3f\n", sample.angularVelocityX,
-        sample.angularVelocityY, sample.angularVelocityZ);*/
+        printf("acc: %" PRIu64 ", %.3f, %.3f, %.3f\n",
+               sample.accelerationTimestamp, sample.accelerationX,
+               sample.accelerationY, sample.accelerationZ);
+        printf("ang: %.3f, %.3f, %.3f\n", sample.angularVelocityX,
+               sample.angularVelocityY, sample.angularVelocityZ);
 
         sensor.sampleRaw();
         sampleRaw = sensor.getLastRawSample();
         printf("%s\n", sampleRaw.c_str());
         // Thread::sleep(100);
+        printf("\n");
     }
 
     sensor.closeAndReset();
diff --git a/src/tests/test-eventinjector.cpp b/src/tests/test-eventinjector.cpp
index 424637b10027b701572790948541c489e6071e10..f36f11ba2a9bbfb3d0524d4369e69df63849fddc 100644
--- a/src/tests/test-eventinjector.cpp
+++ b/src/tests/test-eventinjector.cpp
@@ -37,7 +37,7 @@ int main()
     EventInjector injector;
     injector.start();
 
-    EventCounter counter(sEventBroker);
+    EventCounter counter(EventBroker::getInstance());
     counter.subscribe({topic});
 
     for (;;)
diff --git a/src/tests/test-hsm.cpp b/src/tests/test-hsm.cpp
index 1eba767a4811a93b971c234b4fa6d8b13e398d67..c33ba46ba6bffbb54c97b8682b79665d4b3275ef 100644
--- a/src/tests/test-hsm.cpp
+++ b/src/tests/test-hsm.cpp
@@ -37,16 +37,14 @@ using namespace miosix;
 
 #define TOPIC_TEST 1
 
-#define CHECK_INIT()        \
-    bool testValue = false; \
-    (void)testValue
+#define CHECK_INIT() bool testValue __attribute__((unused)) = false;
 
 #define CHECK_STATE(HSM, SIGNAL, STATE)                            \
     do                                                             \
     {                                                              \
         cout << "------------------------------" << endl;          \
         cout << "Triggering signal " << #SIGNAL << endl;           \
-        sEventBroker.post({SIGNAL}, TOPIC_TEST);                   \
+        EventBroker::getInstance().post({SIGNAL}, TOPIC_TEST);     \
         Thread::sleep(400);                                        \
         testValue = HSM.testState(STATE);                          \
         cout << "Check State " << #STATE << " "                    \
@@ -56,7 +54,7 @@ using namespace miosix;
 
 enum TestEvents : uint8_t
 {
-    EV_A = EV_FIRST_SIGNAL,
+    EV_A = EV_FIRST_CUSTOM,
     EV_B,
     EV_C,
     EV_D,
@@ -86,14 +84,13 @@ public:
     bool foo;
 };
 
-#define DEBUG_PRINT \
-    cout << __func__ << ": event received:" << (int)e.code << endl
+#define DEBUG_PRINT cout << __func__ << ": event received:" << (int)e << endl
 
 using namespace std;
 
 HSMUTTest::HSMUTTest() : HSM(&HSMUTTest::state_initialization)
 {
-    sEventBroker.subscribe(this, TOPIC_TEST);
+    EventBroker::getInstance().subscribe(this, TOPIC_TEST);
 }
 
 State HSMUTTest::state_initialization(const Event& e)
@@ -107,7 +104,7 @@ State HSMUTTest::state_S(const Event& e)
 {
     State retState = HANDLED;
     DEBUG_PRINT;
-    switch (e.code)
+    switch (e)
     {
         case EV_ENTRY:
             break;
@@ -140,7 +137,7 @@ State HSMUTTest::state_S1(const Event& e)
 {
     State retState = HANDLED;
     DEBUG_PRINT;
-    switch (e.code)
+    switch (e)
     {
         case EV_ENTRY:
             break;
@@ -186,7 +183,7 @@ State HSMUTTest::state_S11(const Event& e)
 {
     State retState = HANDLED;
     DEBUG_PRINT;
-    switch (e.code)
+    switch (e)
     {
         case EV_ENTRY:
             break;
@@ -222,7 +219,7 @@ State HSMUTTest::state_S2(const Event& e)
 {
     State retState = HANDLED;
     DEBUG_PRINT;
-    switch (e.code)
+    switch (e)
     {
         case EV_ENTRY:
             break;
@@ -258,7 +255,7 @@ State HSMUTTest::state_S21(const Event& e)
 {
     State retState = HANDLED;
     DEBUG_PRINT;
-    switch (e.code)
+    switch (e)
     {
         case EV_ENTRY:
             break;
@@ -283,7 +280,7 @@ State HSMUTTest::state_S211(const Event& e)
 {
     State retState = HANDLED;
     DEBUG_PRINT;
-    switch (e.code)
+    switch (e)
     {
         case EV_ENTRY:
             break;
@@ -311,49 +308,36 @@ State HSMUTTest::state_S211(const Event& e)
 int main()
 {
 
-    sEventBroker.start();
+    EventBroker::getInstance().start();
 
     HSMUTTest& hsm = HSMUTTest::getInstance();
     hsm.start();
 
     CHECK_INIT();
-    // cppcheck-suppress unreadVariable
     // cppcheck-suppress assertWithSideEffect
     CHECK_STATE(hsm, EV_G, &HSMUTTest::state_S11);
-    // cppcheck-suppress unreadVariable
     // cppcheck-suppress assertWithSideEffect
     CHECK_STATE(hsm, EV_I, &HSMUTTest::state_S11);
-    // cppcheck-suppress unreadVariable
     // cppcheck-suppress assertWithSideEffect
     CHECK_STATE(hsm, EV_A, &HSMUTTest::state_S11);
-    // cppcheck-suppress unreadVariable
     // cppcheck-suppress assertWithSideEffect
     CHECK_STATE(hsm, EV_D, &HSMUTTest::state_S11);
-    // cppcheck-suppress unreadVariable
     // cppcheck-suppress assertWithSideEffect
     CHECK_STATE(hsm, EV_D, &HSMUTTest::state_S11);
-    // cppcheck-suppress unreadVariable
     // cppcheck-suppress assertWithSideEffect
     CHECK_STATE(hsm, EV_B, &HSMUTTest::state_S11);
-    // cppcheck-suppress unreadVariable
     // cppcheck-suppress assertWithSideEffect
     CHECK_STATE(hsm, EV_C, &HSMUTTest::state_S211);
-    // cppcheck-suppress unreadVariable
     // cppcheck-suppress assertWithSideEffect
     CHECK_STATE(hsm, EV_E, &HSMUTTest::state_S11);
-    // cppcheck-suppress unreadVariable
     // cppcheck-suppress assertWithSideEffect
     CHECK_STATE(hsm, EV_E, &HSMUTTest::state_S11);
-    // cppcheck-suppress unreadVariable
     // cppcheck-suppress assertWithSideEffect
     CHECK_STATE(hsm, EV_G, &HSMUTTest::state_S211);
-    // cppcheck-suppress unreadVariable
     // cppcheck-suppress assertWithSideEffect
     CHECK_STATE(hsm, EV_B, &HSMUTTest::state_S211);
-    // cppcheck-suppress unreadVariable
     // cppcheck-suppress assertWithSideEffect
     CHECK_STATE(hsm, EV_I, &HSMUTTest::state_S211);
-    // cppcheck-suppress unreadVariable
     // cppcheck-suppress assertWithSideEffect
     CHECK_STATE(hsm, EV_I, &HSMUTTest::state_S211);
 
diff --git a/src/tests/test-max485.cpp b/src/tests/test-max485.cpp
index c63a1695a82d48f48a8a3aebe420835a4af2cdf2..18bb9077befedf2d449ad8707bf85a740e3b6dce 100644
--- a/src/tests/test-max485.cpp
+++ b/src/tests/test-max485.cpp
@@ -37,10 +37,10 @@
  *
  *  WIRINGS:
  *  Max485 n1   |   stm32f407vg_discovery ser 1
- *      DI      |       PA9
+ *      DI      |       PA9 / PB6
  *      DE      |       PC9
  *      RE      |       PC8
- *      RO      |       PA10
+ *      RO      |       PA10 / PB7
  *      VCC     |       3.3/5 V
  *      GND     |       GND
  *
@@ -56,8 +56,6 @@
  *      A       |       A
  *      B       |       B
  *
- * WARNINGS:
- * - the baudrate has to be extremely low. "guaranteed" at 2400
  */
 
 #include "string.h"
@@ -77,7 +75,8 @@ using ctrlPin2_s2 = miosix::Gpio<GPIOC_BASE, 2>;
 
 char msg[64] = "Testing communication :D";
 char rcv[64];
-int baudrates[6] = {2400, 3600, 4800, 9600, 19200, 115200};
+int baudrates[] = {2400,   3600,   4800,   9600,  19200,
+                   115200, 230400, 460800, 921600};
 
 // function for the thread that has to read from serial
 void readSer(SerialInterface *s)
@@ -132,7 +131,8 @@ int main()
     }
 
     printf("*** SERIAL 3 WORKING!\n");
-    for (int iBaud = 0; iBaud < 6; iBaud++)
+    for (unsigned int iBaud = 0;
+         iBaud < sizeof(baudrates) / sizeof(baudrates[0]); iBaud++)
     {
         Thread::sleep(1000);
 
diff --git a/src/tests/test-sensormanager.cpp b/src/tests/test-sensormanager.cpp
index ee235db75ad919be5512fcbf0fe114c5f76b439e..1a8b06648b7c60c46f31c461c5485285622577d1 100644
--- a/src/tests/test-sensormanager.cpp
+++ b/src/tests/test-sensormanager.cpp
@@ -25,7 +25,7 @@
 #include <sensors/Sensor.h>
 #include <sensors/SensorManager.h>
 #include <utils/Debug.h>
-#include <utils/testutils/TestSensor.h>
+#include <utils/TestUtils/TestSensor.h>
 
 #include <functional>
 #include <iostream>
@@ -259,7 +259,7 @@ int main()
 
     // TEST SENSORS WITH FIFO
 
-    const uint32_t fifoSize = 20;  // cppcheck-suppress unreadVariable
+    const uint32_t fifoSize = 20;
 
     MySensorFIFO s;
 
@@ -269,9 +269,8 @@ int main()
     {
         s.sample();
 
-        MySensorDataFIFO data = fifoProxy.getLastSample();
-
-        UNUSED(data);
+        MySensorDataFIFO data __attribute__((unused)) =
+            fifoProxy.getLastSample();
 
         TRACE("AccelProxy : %llu %f %f %f \n", data.accelerationTimestamp,
               data.accelerationX, data.accelerationY, data.accelerationZ);
diff --git a/src/tests/utils/test-buttonhandler.cpp b/src/tests/utils/test-buttonhandler.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a47acd7bf682edbd120dbb0aa92a84338b277561
--- /dev/null
+++ b/src/tests/utils/test-buttonhandler.cpp
@@ -0,0 +1,72 @@
+/* Copyright (c) 2022 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 <miosix.h>
+#include <utils/ButtonHandler/ButtonHandler.h>
+
+#include <functional>
+
+using namespace miosix;
+using namespace Boardcore;
+using namespace std;
+using namespace placeholders;
+
+GpioPin button1 = GpioPin(GPIOG_BASE, 10);
+GpioPin button2 = GpioPin(GPIOG_BASE, 9);
+
+void buttonCallback(ButtonEvent event, int buttonId);
+
+int main()
+{
+    button1.mode(Mode::INPUT);
+    button2.mode(Mode::INPUT);
+
+    ButtonHandler::getInstance().registerButtonCallback(
+        button1, bind(buttonCallback, _1, 1));
+    ButtonHandler::getInstance().registerButtonCallback(
+        button2, bind(buttonCallback, _1, 2));
+
+    while (true)
+        Thread::sleep(1000);
+}
+
+void buttonCallback(ButtonEvent event, int buttonId)
+{
+    switch (event)
+    {
+        case ButtonEvent::PRESSED:
+            printf("Button %d pressed\n", buttonId);
+            break;
+        case ButtonEvent::SHORT_PRESS:
+            printf("Button %d released, it was a short press\n", buttonId);
+            break;
+        case ButtonEvent::LONG_PRESS:
+            printf("Button %d released, it was a long press\n", buttonId);
+            break;
+        case ButtonEvent::VERY_LONG_PRESS:
+            printf("Button %d released, it was a very long press\n", buttonId);
+            break;
+
+        default:
+            break;
+    }
+}
\ No newline at end of file