diff --git a/Cargo.toml b/Cargo.toml
index a59ec3c55228fd654298c0aee3a036fcce4bf1d4..cb5119677829fe63e56fa76e46aef40fd2adc8a1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "tudelft-quadrupel"
-version = "1.0.1"
+version = "2.0.0"
 edition = "2021"
 authors = [
     "Anne Stijns <anstijns@gmail.com>",
diff --git a/src/barometer.rs b/src/barometer.rs
index a79cd3e63ee67e86352f483113612dd6eaf52661..a0752246e29a68ab4571516ee101f1015db4d3fe 100644
--- a/src/barometer.rs
+++ b/src/barometer.rs
@@ -4,7 +4,6 @@ use crate::time::Instant;
 use crate::twi::TWI;
 use core::time::Duration;
 use embedded_hal::prelude::_embedded_hal_blocking_i2c_WriteRead;
-use nrf51_hal::Twi;
 
 const MS5611_ADDR: u8 = 0b0111_0111;
 const REG_READ: u8 = 0x0;
@@ -76,31 +75,35 @@ struct Ms5611 {
 static BAROMETER: Mutex<OnceCell<Ms5611>> = Mutex::new(OnceCell::uninitialized());
 
 pub(crate) fn initialize() {
-    let twi: &mut Twi<_> = &mut TWI.lock();
-
-    let mut prom = [0; 8];
-    let mut data = [0u8; 2];
-    for c in 0..8 {
-        twi.write_read(MS5611_ADDR, &[REG_PROM + 2 * c], &mut data)
-            .unwrap();
-        prom[c as usize] = u16::from_be_bytes(data);
-    }
+    TWI.modify(|twi| {
+        let mut prom = [0; 8];
+        let mut data = [0u8; 2];
+        for c in 0..8 {
+            twi.write_read(MS5611_ADDR, &[REG_PROM + 2 * c], &mut data)
+                .unwrap();
+            prom[c as usize] = u16::from_be_bytes(data);
+        }
 
-    BAROMETER.lock().initialize(Ms5611 {
-        pressure_sensitivity: prom[1],
-        pressure_offset: prom[2],
-        temp_coef_pressure_sensitivity: prom[3],
-        temp_coef_pressure_offset: prom[4],
-        temp_ref: prom[5],
-        over_sampling_ratio: OverSamplingRatio::Opt4096,
-        loop_state: Ms5611LoopState::Reset,
-        most_recent_pressure: 0,
+        BAROMETER.modify(|baro| {
+            baro.initialize(Ms5611 {
+                pressure_sensitivity: prom[1],
+                pressure_offset: prom[2],
+                temp_coef_pressure_sensitivity: prom[3],
+                temp_coef_pressure_offset: prom[4],
+                temp_ref: prom[5],
+                over_sampling_ratio: OverSamplingRatio::Opt4096,
+                loop_state: Ms5611LoopState::Reset,
+                most_recent_pressure: 0,
+            })
+        })
     });
 }
 
 fn update() {
-    let baro: &mut Ms5611 = &mut BAROMETER.lock();
-    let twi: &mut Twi<_> = &mut TWI.lock();
+    // Safety: The TWI and BAROMETER mutexes are not accessed in an interrupt
+    let twi = unsafe { TWI.no_critical_section_lock_mut() };
+    let baro = unsafe { BAROMETER.no_critical_section_lock_mut() };
+
     let now = Instant::now();
 
     match baro.loop_state {
@@ -175,5 +178,8 @@ fn update() {
 /// This function will never block, instead it will return an old value if no new value is available.
 pub fn read_pressure() -> u32 {
     update();
-    BAROMETER.lock().most_recent_pressure
+
+    // Safety: The BAROMETER mutexes is not accessed in an interrupt
+    let baro = unsafe { BAROMETER.no_critical_section_lock_mut() };
+    baro.most_recent_pressure
 }
diff --git a/src/battery.rs b/src/battery.rs
index d0b0332ea0744aa6ef4dd06701bd2c7570424122..b0a0ac24e4877574c38b0df5f58bbcf00a8660cc 100644
--- a/src/battery.rs
+++ b/src/battery.rs
@@ -31,9 +31,11 @@ pub(crate) fn initialize(adc: nrf51_pac::ADC, nvic: &mut NVIC) {
         nvic.set_priority(Interrupt::ADC, 3);
     }
 
-    ADC_STATE.lock().initialize(Adc {
-        adc,
-        last_result: 0,
+    ADC_STATE.modify(|a| {
+        a.initialize(Adc {
+            adc,
+            last_result: 0,
+        })
     });
 
     // Safety: The initialize function is not called inside of an interrupt-free section.
@@ -44,21 +46,22 @@ pub(crate) fn initialize(adc: nrf51_pac::ADC, nvic: &mut NVIC) {
 
 #[interrupt]
 unsafe fn ADC() {
-    let adc = &mut **ADC_STATE.lock();
-    adc.adc.events_end.reset();
-    // Battery voltage = (result*1.2*3/255*2) = RESULT*0.007058824
-    adc.last_result = adc.adc.result.read().result().bits() * 7;
+    ADC_STATE.modify(|adc| {
+        adc.adc.events_end.reset();
+        // Battery voltage = (result*1.2*3/255*2) = RESULT*0.007058824
+        adc.last_result = adc.adc.result.read().result().bits() * 7;
+    })
 }
 
 /// Returns the battery voltage in 10^-2 volt.
 /// This function will never block, instead it will return an old value if no new value is available.
 pub fn read_battery() -> u16 {
-    let adc = &mut **ADC_STATE.lock();
+    ADC_STATE.modify(|adc| {
+        if !adc.adc.busy.read().busy().bit() {
+            //For some reason, there is no field inside this register, so we set it to 1 manually.
+            adc.adc.tasks_start.write(|w| unsafe { w.bits(1) });
+        }
 
-    if !adc.adc.busy.read().busy().bit() {
-        //For some reason, there is no field inside this register, so we set it to 1 manually.
-        adc.adc.tasks_start.write(|w| unsafe { w.bits(1) });
-    }
-
-    adc.last_result
+        adc.last_result
+    })
 }
diff --git a/src/flash.rs b/src/flash.rs
index 9bc4ce28a58cbf3e4e3e08fa3aa8cf1b79351e75..cd91da15fe7d621dc46f76ba424ce2ea870ba07d 100644
--- a/src/flash.rs
+++ b/src/flash.rs
@@ -76,13 +76,15 @@ pub(crate) fn initialize(
     let pin_hold = pin_hold.into_push_pull_output(Level::High);
     let pin_cs = pin_cs.into_push_pull_output(Level::High);
 
-    let mut spi_flash = FLASH.lock();
-    spi_flash.initialize(SpiFlash {
-        spi,
-        _pin_wp: pin_wp,
-        _pin_hold: pin_hold,
-        pin_cs,
+    FLASH.modify(|spi_flash| {
+        spi_flash.initialize(SpiFlash {
+            spi,
+            _pin_wp: pin_wp,
+            _pin_hold: pin_hold,
+            pin_cs,
+        });
     });
+
     flash_enable_wsr()?;
     flash_set_wrsr()?;
     flash_chip_erase()?;
@@ -94,7 +96,8 @@ pub(crate) fn initialize(
 fn spi_master_tx(tx_data: &[u8]) -> Result<(), FlashError> {
     assert_ne!(tx_data.len(), 0);
 
-    let mut guard = FLASH.lock();
+    // Safety: The FLASH mutex is not accessed in an interrupt
+    let guard = unsafe { FLASH.no_critical_section_lock_mut() };
 
     // Enable slave
     guard.pin_cs.set_low()?;
@@ -108,14 +111,15 @@ fn spi_master_tx(tx_data: &[u8]) -> Result<(), FlashError> {
 
     // Disable slave
     guard.pin_cs.set_high()?;
-
     Ok(())
 }
 
 /// Transmit data over SPI. Optimized to read bytes from the flash memory.
 fn spi_master_tx_rx_fast_read(tx_data: [u8; 4], rx_data: &mut [u8]) -> Result<(), FlashError> {
     assert_ne!(rx_data.len(), 0);
-    let mut guard = FLASH.lock();
+
+    // Safety: The FLASH mutex is not accessed in an interrupt
+    let guard = unsafe { FLASH.no_critical_section_lock_mut() };
 
     // Enable slave
     guard.pin_cs.set_low()?;
@@ -144,7 +148,9 @@ fn spi_master_tx_rx_fast_write(tx_data: [u8; 4], bytes: &[u8]) -> Result<(), Fla
     let address: u32 =
         u32::from(tx_data[3]) + (u32::from(tx_data[2]) << 8) + (u32::from(tx_data[1]) << 16);
 
-    let mut guard = FLASH.lock();
+    // Safety: The FLASH mutex is not accessed in an interrupt
+    let guard = unsafe { FLASH.no_critical_section_lock_mut() };
+
     // Enable slave
     guard.pin_cs.set_low()?;
 
diff --git a/src/initialize.rs b/src/initialize.rs
index 2df63a36976d13a0fda317932c38bd22febc06bc..429d28d4193b20643772effadcc6dfddc9bbce61 100644
--- a/src/initialize.rs
+++ b/src/initialize.rs
@@ -32,9 +32,10 @@ pub fn initialize(heap_memory: &'static mut [MaybeUninit<u8>], debug: bool) {
     assembly_delay(2_500_000);
 
     // keep this guard around until the end of the function (so interrupts stay off)
-    let mut guard = INITIALIZED.lock();
-    assert!(!(*guard), "ALREADY INITIALIZED");
-    *guard = true;
+    INITIALIZED.modify(|guard| {
+        assert!(!(*guard), "ALREADY INITIALIZED");
+        *guard = true;
+    });
 
     // unwrap: will never panic because this function can only be called once (see the guard above)
     let mut nrf51_peripherals = Peripherals::take().unwrap();
diff --git a/src/led.rs b/src/led.rs
index 5c2d2fb7b98487842bd35fbaad6e1668bcb94fcb..94110244362fe8b58e69464d6ccfb2bac304ef31 100644
--- a/src/led.rs
+++ b/src/led.rs
@@ -36,7 +36,7 @@ impl Led {
         // inline comments from the `nrf_51` crate on `set_high`.
         //
         // NOTE: single writes on ARM are always atomic so this makes sense.
-        let leds = unsafe { LEDS.no_critical_section_lock() };
+        let leds = unsafe { LEDS.no_critical_section_lock_mut() };
 
         // ignore the error here. Its type is `Void` and is impossible
         // to construct (and thus impossible to actually happen)
@@ -55,7 +55,7 @@ impl Led {
         // inline comments from the `nrf_51` crate on `set_low`.
         //
         // NOTE: single writes on ARM are always atomic so this makes sense.
-        let leds = unsafe { LEDS.no_critical_section_lock() };
+        let leds = unsafe { LEDS.no_critical_section_lock_mut() };
 
         // ignore the error here. Its type is `Void` and is impossible
         // to construct (and thus impossible to actually happen)
@@ -74,7 +74,7 @@ impl Led {
         // inline comments from the `nrf_51` crate on `is_set_high`.
         //
         // NOTE: single writes on ARM are always atomic so this makes sense.
-        let leds = unsafe { LEDS.no_critical_section_lock() };
+        let leds = unsafe { LEDS.no_critical_section_lock_mut() };
 
         let res = match self {
             Red => leds.led_red.is_set_high(),
@@ -135,10 +135,12 @@ pub(crate) fn initialize(
     led_green: P0_28<Disconnected>,
     led_blue: P0_30<Disconnected>,
 ) {
-    LEDS.lock().initialize(Leds {
-        led_red: led_red.into_push_pull_output(Level::High),
-        led_yellow: led_yellow.into_push_pull_output(Level::High),
-        led_green: led_green.into_push_pull_output(Level::High),
-        led_blue: led_blue.into_push_pull_output(Level::High),
+    LEDS.modify(|leds| {
+        leds.initialize(Leds {
+            led_red: led_red.into_push_pull_output(Level::High),
+            led_yellow: led_yellow.into_push_pull_output(Level::High),
+            led_green: led_green.into_push_pull_output(Level::High),
+            led_blue: led_blue.into_push_pull_output(Level::High),
+        })
     });
 }
diff --git a/src/lib.rs b/src/lib.rs
index 28da01a29e7938c2803f94a02ce139d5a242056e..d02fbaca8fdfb3f67205b791112d365ab0524c07 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -54,8 +54,8 @@ pub mod flash;
 /// Initialize all the drivers
 pub mod initialize;
 
-/// Utilities to control the leds on the board. 
-/// 
+/// Utilities to control the leds on the board.
+///
 /// Note that in the template,
 /// some leds have already been assigned meaning:
 ///
diff --git a/src/motor.rs b/src/motor.rs
index 4540949a1a2d105edccaa3daa869c53c1fe73f42..fc1c7dc97772b761aa8ec1ad52b29cf316635f91 100644
--- a/src/motor.rs
+++ b/src/motor.rs
@@ -28,14 +28,12 @@ static MOTORS: Mutex<OnceCell<Motors>> = Mutex::new(OnceCell::uninitialized());
 /// - For old drones (carbon fiber), 800 is a reasonable maximum.
 /// - For new drones (aluminium frame), 1000 is a reasonable maximum.
 pub fn set_motor_max(max: u16) {
-    let mut guard = MOTORS.lock();
-    guard.motor_max = max;
+    MOTORS.modify(|motors| motors.motor_max = max);
 }
 
 /// This gets the maximum motor value that the motor driver will cap the motor values at. This is 400 by default.
 pub fn get_motor_max() -> u16 {
-    let guard = MOTORS.lock();
-    guard.motor_max
+    MOTORS.modify(|motors| motors.motor_max)
 }
 
 /// Get the current motor values. This is an array of four values:
@@ -44,7 +42,7 @@ pub fn get_motor_max() -> u16 {
 /// - 2: The back motor
 /// - 3: The left motor
 pub fn get_motors() -> [u16; 4] {
-    MOTORS.lock().motor_values
+    MOTORS.modify(|motors| motors.motor_values)
 }
 
 /// Set the motor values. This will cap the motor values to the value set by `set_motor_max`. The motor values are an array of four values:
@@ -53,8 +51,9 @@ pub fn get_motors() -> [u16; 4] {
 /// - 2: The back motor
 /// - 3: The left motor
 pub fn set_motors(val: [u16; 4]) {
-    let mut guard = MOTORS.lock();
-    guard.motor_values = val.map(|v| v.min(guard.motor_max));
+    MOTORS.modify(|guard| {
+        guard.motor_values = val.map(|v| v.min(guard.motor_max));
+    });
 }
 
 #[allow(clippy::too_many_lines)]
@@ -69,185 +68,186 @@ pub(crate) fn initialize(
     // Configure pin20
     let pin20 = pin20.into_push_pull_output(Level::Low);
 
-    let mut motors = MOTORS.lock();
-    motors.initialize(Motors {
-        motor_values: [0; 4],
-        motor_max: 400,
-        timer1,
-        timer2,
-        pin20,
-    });
+    MOTORS.modify(|motors| {
+        motors.initialize(Motors {
+            motor_values: [0; 4],
+            motor_max: 400,
+            timer1,
+            timer2,
+            pin20,
+        });
 
-    // Configure GPIOTE. GPIOTE is stands for GPIO tasks and events.
-    // Safety: Writing the motor pins to the register is the intended behaviour. The pins have been verified to be correct.
-    gpiote.config[0].write(|w| unsafe {
-        w.mode()
-            .task()
-            .psel()
-            .bits(MOTOR_0_PIN)
-            .polarity()
-            .toggle()
-            .outinit()
-            .set_bit()
-    });
-    gpiote.config[1].write(|w| unsafe {
-        w.mode()
-            .task()
-            .psel()
-            .bits(MOTOR_1_PIN)
-            .polarity()
-            .toggle()
-            .outinit()
-            .set_bit()
-    });
-    gpiote.config[2].write(|w| unsafe {
-        w.mode()
-            .task()
-            .psel()
-            .bits(MOTOR_2_PIN)
-            .polarity()
-            .toggle()
-            .outinit()
-            .set_bit()
-    });
-    gpiote.config[3].write(|w| unsafe {
-        w.mode()
-            .task()
-            .psel()
-            .bits(MOTOR_3_PIN)
-            .polarity()
-            .toggle()
-            .outinit()
-            .set_bit()
-    });
+        // Configure GPIOTE. GPIOTE is stands for GPIO tasks and events.
+        // Safety: Writing the motor pins to the register is the intended behaviour. The pins have been verified to be correct.
+        gpiote.config[0].write(|w| unsafe {
+            w.mode()
+                .task()
+                .psel()
+                .bits(MOTOR_0_PIN)
+                .polarity()
+                .toggle()
+                .outinit()
+                .set_bit()
+        });
+        gpiote.config[1].write(|w| unsafe {
+            w.mode()
+                .task()
+                .psel()
+                .bits(MOTOR_1_PIN)
+                .polarity()
+                .toggle()
+                .outinit()
+                .set_bit()
+        });
+        gpiote.config[2].write(|w| unsafe {
+            w.mode()
+                .task()
+                .psel()
+                .bits(MOTOR_2_PIN)
+                .polarity()
+                .toggle()
+                .outinit()
+                .set_bit()
+        });
+        gpiote.config[3].write(|w| unsafe {
+            w.mode()
+                .task()
+                .psel()
+                .bits(MOTOR_3_PIN)
+                .polarity()
+                .toggle()
+                .outinit()
+                .set_bit()
+        });
 
-    // Configure timer 2
-    motors
-        .timer2
-        .prescaler
-        .write(|w| unsafe { w.prescaler().bits(1) }); //0.125us. Safety: Allowed range of values is 0-9
-    motors.timer2.intenset.write(|w| w.compare3().set_bit());
-    motors.timer2.cc[0].write(|w| unsafe { w.bits(1000) }); // Safety: Any time is allowed
-    motors.timer2.cc[1].write(|w| unsafe { w.bits(1000) }); // Safety: Any time is allowed
-    motors.timer2.cc[3].write(|w| unsafe { w.bits(2500) }); // Safety: Any time is allowed
-    motors.timer2.shorts.write(|w| w.compare3_clear().set_bit());
-    motors.timer2.tasks_clear.write(|w| unsafe { w.bits(1) }); // Safety: Writing 1 to a task-clear register is allowed.
+        // Configure timer 2
+        motors
+            .timer2
+            .prescaler
+            .write(|w| unsafe { w.prescaler().bits(1) }); //0.125us. Safety: Allowed range of values is 0-9
+        motors.timer2.intenset.write(|w| w.compare3().set_bit());
+        motors.timer2.cc[0].write(|w| unsafe { w.bits(1000) }); // Safety: Any time is allowed
+        motors.timer2.cc[1].write(|w| unsafe { w.bits(1000) }); // Safety: Any time is allowed
+        motors.timer2.cc[3].write(|w| unsafe { w.bits(2500) }); // Safety: Any time is allowed
+        motors.timer2.shorts.write(|w| w.compare3_clear().set_bit());
+        motors.timer2.tasks_clear.write(|w| unsafe { w.bits(1) }); // Safety: Writing 1 to a task-clear register is allowed.
 
-    // Configure timer 1
-    // Safety: Allowed range of values is 0-9
-    motors
-        .timer1
-        .prescaler
-        .write(|w| unsafe { w.prescaler().bits(1) }); //0.125us
-    motors.timer1.intenset.write(|w| w.compare3().set_bit());
-    motors.timer1.cc[0].write(|w| unsafe { w.bits(1000) }); // Safety: Any time is allowed
-    motors.timer1.cc[1].write(|w| unsafe { w.bits(1000) }); // Safety: Any time is allowed
-    motors.timer1.cc[3].write(|w| unsafe { w.bits(2500) }); // Safety: Any time is allowed
-    motors.timer1.shorts.write(|w| w.compare3_clear().set_bit());
-    motors.timer1.tasks_clear.write(|w| unsafe { w.bits(1) }); // Safety: Writing 1 to a task-clear register is allowed.
+        // Configure timer 1
+        // Safety: Allowed range of values is 0-9
+        motors
+            .timer1
+            .prescaler
+            .write(|w| unsafe { w.prescaler().bits(1) }); //0.125us
+        motors.timer1.intenset.write(|w| w.compare3().set_bit());
+        motors.timer1.cc[0].write(|w| unsafe { w.bits(1000) }); // Safety: Any time is allowed
+        motors.timer1.cc[1].write(|w| unsafe { w.bits(1000) }); // Safety: Any time is allowed
+        motors.timer1.cc[3].write(|w| unsafe { w.bits(2500) }); // Safety: Any time is allowed
+        motors.timer1.shorts.write(|w| w.compare3_clear().set_bit());
+        motors.timer1.tasks_clear.write(|w| unsafe { w.bits(1) }); // Safety: Writing 1 to a task-clear register is allowed.
 
-    // Start the timer tasks
-    // Safety: Writing 1 to a task-start registers is allowed.
-    motors.timer2.tasks_start.write(|w| unsafe { w.bits(1) });
-    motors.timer1.tasks_start.write(|w| unsafe { w.bits(1) });
+        // Start the timer tasks
+        // Safety: Writing 1 to a task-start registers is allowed.
+        motors.timer2.tasks_start.write(|w| unsafe { w.bits(1) });
+        motors.timer1.tasks_start.write(|w| unsafe { w.bits(1) });
 
-    // Link motor 0 - gpiote 0
-    ppi.ch[0]
-        .eep
-        .write(|w| unsafe { w.bits(motors.timer1.events_compare[0].as_ptr() as u32) });
-    ppi.ch[0]
-        .tep
-        .write(|w| unsafe { w.bits(gpiote.tasks_out[0].as_ptr() as u32) });
-    ppi.ch[1]
-        .eep
-        .write(|w| unsafe { w.bits(motors.timer1.events_compare[3].as_ptr() as u32) });
-    ppi.ch[1]
-        .tep
-        .write(|w| unsafe { w.bits(gpiote.tasks_out[0].as_ptr() as u32) });
+        // Link motor 0 - gpiote 0
+        ppi.ch[0]
+            .eep
+            .write(|w| unsafe { w.bits(motors.timer1.events_compare[0].as_ptr() as u32) });
+        ppi.ch[0]
+            .tep
+            .write(|w| unsafe { w.bits(gpiote.tasks_out[0].as_ptr() as u32) });
+        ppi.ch[1]
+            .eep
+            .write(|w| unsafe { w.bits(motors.timer1.events_compare[3].as_ptr() as u32) });
+        ppi.ch[1]
+            .tep
+            .write(|w| unsafe { w.bits(gpiote.tasks_out[0].as_ptr() as u32) });
 
-    // Link motor 1 - gpiote 1
-    ppi.ch[2]
-        .eep
-        .write(|w| unsafe { w.bits(motors.timer1.events_compare[1].as_ptr() as u32) });
-    ppi.ch[2]
-        .tep
-        .write(|w| unsafe { w.bits(gpiote.tasks_out[1].as_ptr() as u32) });
-    ppi.ch[3]
-        .eep
-        .write(|w| unsafe { w.bits(motors.timer1.events_compare[3].as_ptr() as u32) });
-    ppi.ch[3]
-        .tep
-        .write(|w| unsafe { w.bits(gpiote.tasks_out[1].as_ptr() as u32) });
+        // Link motor 1 - gpiote 1
+        ppi.ch[2]
+            .eep
+            .write(|w| unsafe { w.bits(motors.timer1.events_compare[1].as_ptr() as u32) });
+        ppi.ch[2]
+            .tep
+            .write(|w| unsafe { w.bits(gpiote.tasks_out[1].as_ptr() as u32) });
+        ppi.ch[3]
+            .eep
+            .write(|w| unsafe { w.bits(motors.timer1.events_compare[3].as_ptr() as u32) });
+        ppi.ch[3]
+            .tep
+            .write(|w| unsafe { w.bits(gpiote.tasks_out[1].as_ptr() as u32) });
 
-    // Link motor 2 - gpiote 2
-    ppi.ch[4]
-        .eep
-        .write(|w| unsafe { w.bits(motors.timer2.events_compare[0].as_ptr() as u32) });
-    ppi.ch[4]
-        .tep
-        .write(|w| unsafe { w.bits(gpiote.tasks_out[2].as_ptr() as u32) });
-    ppi.ch[5]
-        .eep
-        .write(|w| unsafe { w.bits(motors.timer2.events_compare[3].as_ptr() as u32) });
-    ppi.ch[5]
-        .tep
-        .write(|w| unsafe { w.bits(gpiote.tasks_out[2].as_ptr() as u32) });
+        // Link motor 2 - gpiote 2
+        ppi.ch[4]
+            .eep
+            .write(|w| unsafe { w.bits(motors.timer2.events_compare[0].as_ptr() as u32) });
+        ppi.ch[4]
+            .tep
+            .write(|w| unsafe { w.bits(gpiote.tasks_out[2].as_ptr() as u32) });
+        ppi.ch[5]
+            .eep
+            .write(|w| unsafe { w.bits(motors.timer2.events_compare[3].as_ptr() as u32) });
+        ppi.ch[5]
+            .tep
+            .write(|w| unsafe { w.bits(gpiote.tasks_out[2].as_ptr() as u32) });
 
-    // Link motor 3 - gpiote 3
-    ppi.ch[6]
-        .eep
-        .write(|w| unsafe { w.bits(motors.timer2.events_compare[1].as_ptr() as u32) });
-    ppi.ch[6]
-        .tep
-        .write(|w| unsafe { w.bits(gpiote.tasks_out[3].as_ptr() as u32) });
-    ppi.ch[7]
-        .eep
-        .write(|w| unsafe { w.bits(motors.timer2.events_compare[3].as_ptr() as u32) });
-    ppi.ch[7]
-        .tep
-        .write(|w| unsafe { w.bits(gpiote.tasks_out[3].as_ptr() as u32) });
+        // Link motor 3 - gpiote 3
+        ppi.ch[6]
+            .eep
+            .write(|w| unsafe { w.bits(motors.timer2.events_compare[1].as_ptr() as u32) });
+        ppi.ch[6]
+            .tep
+            .write(|w| unsafe { w.bits(gpiote.tasks_out[3].as_ptr() as u32) });
+        ppi.ch[7]
+            .eep
+            .write(|w| unsafe { w.bits(motors.timer2.events_compare[3].as_ptr() as u32) });
+        ppi.ch[7]
+            .tep
+            .write(|w| unsafe { w.bits(gpiote.tasks_out[3].as_ptr() as u32) });
 
-    // Set which channels of PPI are enabled
-    ppi.chenset.write(|w| {
-        w.ch0()
-            .set_bit()
-            .ch1()
-            .set_bit()
-            .ch2()
-            .set_bit()
-            .ch3()
-            .set_bit()
-            .ch4()
-            .set_bit()
-            .ch5()
-            .set_bit()
-            .ch6()
-            .set_bit()
-            .ch7()
-            .set_bit()
-    });
+        // Set which channels of PPI are enabled
+        ppi.chenset.write(|w| {
+            w.ch0()
+                .set_bit()
+                .ch1()
+                .set_bit()
+                .ch2()
+                .set_bit()
+                .ch3()
+                .set_bit()
+                .ch4()
+                .set_bit()
+                .ch5()
+                .set_bit()
+                .ch6()
+                .set_bit()
+                .ch7()
+                .set_bit()
+        });
 
-    // Configure timer interrupts
-    // Safety: We are not using priority-based critical sections.
-    unsafe {
-        nvic.set_priority(Interrupt::TIMER2, 1);
-        NVIC::unpend(Interrupt::TIMER2);
-        nvic.set_priority(Interrupt::TIMER1, 1);
-        NVIC::unpend(Interrupt::TIMER1);
-    }
+        // Configure timer interrupts
+        // Safety: We are not using priority-based critical sections.
+        unsafe {
+            nvic.set_priority(Interrupt::TIMER2, 1);
+            NVIC::unpend(Interrupt::TIMER2);
+            nvic.set_priority(Interrupt::TIMER1, 1);
+            NVIC::unpend(Interrupt::TIMER1);
+        }
 
-    // Enable interrupts
-    // Safety: We are not using mask-based critical sections.
-    unsafe {
-        NVIC::unmask(Interrupt::TIMER2);
-        NVIC::unmask(Interrupt::TIMER1);
-    }
+        // Enable interrupts
+        // Safety: We are not using mask-based critical sections.
+        unsafe {
+            NVIC::unmask(Interrupt::TIMER2);
+            NVIC::unmask(Interrupt::TIMER1);
+        }
+    });
 }
 
 #[interrupt]
 unsafe fn TIMER2() {
     // Safety: interrupts are already turned off here, since we are inside an interrupt
-    let motors = unsafe { MOTORS.no_critical_section_lock() };
+    let motors = unsafe { MOTORS.no_critical_section_lock_mut() };
     if motors.timer2.events_compare[3].read().bits() != 0 {
         motors.timer2.events_compare[3].reset();
         //2500 * 0.125
@@ -264,7 +264,7 @@ unsafe fn TIMER2() {
 #[interrupt]
 unsafe fn TIMER1() {
     // Safety: interrupts are already turned off here, since we are inside an interrupt
-    let motors = unsafe { MOTORS.no_critical_section_lock() };
+    let motors = unsafe { MOTORS.no_critical_section_lock_mut() };
     if motors.timer1.events_compare[3].read().bits() != 0 {
         motors.timer1.events_compare[3].reset();
         motors.timer1.tasks_capture[2].write(|w| w.bits(1));
diff --git a/src/mpu.rs b/src/mpu.rs
index 6858cb19f4a864a90c139d1fe5300d1b89ec90f2..c935ecb695a25f337d4a9164130d962199a96be5 100644
--- a/src/mpu.rs
+++ b/src/mpu.rs
@@ -34,26 +34,28 @@ struct Mpu {
 static MPU: Mutex<OnceCell<Mpu>> = Mutex::new(OnceCell::uninitialized());
 
 pub(crate) fn initialize() {
-    let twi: &mut Twi<_> = &mut TWI.lock();
-
-    let mut mpu = Mpu6050::new(twi).unwrap();
-
-    mpu.initialize_dmp(twi).unwrap();
-
-    mpu.set_sample_rate_divider(twi, SAMPLE_RATE_DIVIDER_MPU)
-        .unwrap();
-    mpu.set_digital_lowpass_filter(twi, DigitalLowPassFilter::Filter5)
-        .unwrap();
-    MPU.lock().initialize(Mpu {
-        mpu,
-        dmp_enabled: true,
+    TWI.modify(|twi| {
+        let mut mpu: Mpu6050<Twi<TWI0>> = Mpu6050::new(&mut **twi).unwrap();
+
+        mpu.initialize_dmp(twi).unwrap();
+
+        mpu.set_sample_rate_divider(twi, SAMPLE_RATE_DIVIDER_MPU)
+            .unwrap();
+        mpu.set_digital_lowpass_filter(twi, DigitalLowPassFilter::Filter5)
+            .unwrap();
+        MPU.modify(|m| {
+            m.initialize(Mpu {
+                mpu,
+                dmp_enabled: true,
+            })
+        });
     });
 }
 
 /// Is the DMP (digital motion processor) of the MPU enabled?
 /// It is enabled by default.
 pub fn is_dmp_enabled() -> bool {
-    MPU.lock().dmp_enabled
+    MPU.modify(|mpu| mpu.dmp_enabled)
 }
 
 /// Disable the DMP (digital motion processor) of the MPU
@@ -61,8 +63,9 @@ pub fn is_dmp_enabled() -> bool {
 /// # Panics
 /// when the global constant `SAMPLE_RATE_DIVIDER_RAW` is wrong (i.e. won't panic under normal conditions)
 pub fn disable_dmp() {
-    let twi: &mut Twi<_> = &mut TWI.lock();
-    let mpu: &mut Mpu = &mut MPU.lock();
+    // Safety: The TWI and MPU mutexes are not accessed in an interrupt
+    let twi = unsafe { TWI.no_critical_section_lock_mut() };
+    let mpu = unsafe { MPU.no_critical_section_lock_mut() };
 
     mpu.mpu
         .set_sample_rate_divider(twi, SAMPLE_RATE_DIVIDER_RAW)
@@ -77,8 +80,9 @@ pub fn disable_dmp() {
 /// # Errors
 /// when the global constant `SAMPLE_RATE_DIVIDER_MPU` is wrong (i.e. will not panic under normal conditions)
 pub fn enable_dmp() -> Result<(), Error<Twi<TWI0>>> {
-    let twi: &mut Twi<_> = &mut TWI.lock();
-    let mpu: &mut Mpu = &mut MPU.lock();
+    // Safety: The TWI and MPU mutexes are not accessed in an interrupt
+    let twi = unsafe { TWI.no_critical_section_lock_mut() };
+    let mpu = unsafe { MPU.no_critical_section_lock_mut() };
 
     mpu.mpu
         .set_sample_rate_divider(twi, SAMPLE_RATE_DIVIDER_MPU)?;
@@ -99,8 +103,9 @@ pub fn enable_dmp() -> Result<(), Error<Twi<TWI0>>> {
 /// # Errors
 /// when a TWI(I2C) operation failed
 pub fn read_dmp_bytes() -> nb::Result<Quaternion, Error<Twi<TWI0>>> {
-    let twi: &mut Twi<_> = &mut TWI.lock();
-    let mpu: &mut Mpu = &mut MPU.lock();
+    // Safety: The TWI and MPU mutexes are not accessed in an interrupt
+    let twi = unsafe { TWI.no_critical_section_lock_mut() };
+    let mpu = unsafe { MPU.no_critical_section_lock_mut() };
 
     assert!(mpu.dmp_enabled);
 
@@ -127,8 +132,9 @@ pub fn read_dmp_bytes() -> nb::Result<Quaternion, Error<Twi<TWI0>>> {
 /// # Errors
 /// when a TWI operation failed
 pub fn read_raw() -> Result<(Accel, Gyro), Error<Twi<TWI0>>> {
-    let twi: &mut Twi<_> = &mut TWI.lock();
-    let mpu: &mut Mpu = &mut MPU.lock();
+    // Safety: The TWI and MPU mutexes are not accessed in an interrupt
+    let twi = unsafe { TWI.no_critical_section_lock_mut() };
+    let mpu = unsafe { MPU.no_critical_section_lock_mut() };
 
     let accel = mpu.mpu.accel(twi)?;
     let gyro = mpu.mpu.gyro(twi)?;
diff --git a/src/mutex.rs b/src/mutex.rs
index 69f213b44b59d5ab5e2ebd1cf7faab5c2fe2ff02..216732ac64113dbcb4041a515c23fec7e699696f 100644
--- a/src/mutex.rs
+++ b/src/mutex.rs
@@ -1,9 +1,4 @@
 use core::cell::UnsafeCell;
-use core::ops::{Deref, DerefMut};
-
-use cortex_m::interrupt::{disable, enable};
-use cortex_m::register::primask;
-use cortex_m::register::primask::Primask;
 
 /// A mutual exclusion primitive useful for protecting shared data. It works by disabling interrupts while the lock is being held.
 ///
@@ -36,71 +31,40 @@ impl<T> Mutex<T> {
         }
     }
 
-    /// This function gets a reference to the inner `T` by locking the lock.
-    ///
-    /// This disables interrupts while the `LockGuard` returned by this function is alive.
-    pub fn lock(&self) -> LockGuard<T> {
-        let primask = primask::read();
-
-        // disable interrupts
-        disable();
-
-        LockGuard {
-            // SAFETY: we disabled interrupts, creating a critical section.
-            // that means we can safely mutably access the internals of our
-            // UnsafeCell. Note that this is safe until interrupts are turned
-            // back on, which happens when users drop the LockGuard. Since the
-            // guard is the only way to access &mut T, this means that interrupts
-            // are turned on at the moment you can never have access to &mut T anymore
-            inner: unsafe { &mut *self.inner.get() },
-            primask,
-        }
+    /// Locks the mutex in a callback
+    #[inline(always)]
+    pub fn modify<U>(&self, f: impl FnOnce(&mut T) -> U) -> U {
+        cortex_m::interrupt::free(|_| {
+            // Safety: Interrupts are disabled, so the current code is the only one that can be running.
+            // TODO make sure you can't lock the mutex in here
+            f(unsafe { &mut *self.inner.get() })
+        })
     }
 
     /// This function gets a reference to the inner `T` _without_ locking the lock.
     /// This is inherently unsafe.
     ///
     /// # Safety
-    /// This function is only safe if you can guarantee that nothing is
-    /// mutating this or holding a lock while this reference exists. That means, you should
-    /// generally drop this reference ASAP.
+    /// This function is only safe if you can guarantee that no mutable references to the contents of this lock exist.
     ///
     /// This generally can be used in the following cases:
-    /// * You only access this from within interrupts, as interrupts themselves can not be interrupted
-    /// * You only access this outside of interrupts, as interrupts don't break the mutex guarantees
-    #[allow(clippy::mut_from_ref)]
-    pub unsafe fn no_critical_section_lock(&self) -> &mut T {
-        &mut *self.inner.get()
+    /// * You only access this mutex from within a single interrupt
+    /// * You only access this mutex outside of interrupts, as interrupts don't break the mutex guarantees
+    pub unsafe fn no_critical_section_lock(&self) -> &T {
+        &*self.inner.get()
     }
-}
-
-/// A `LockGuard` is an object that, as long as it is alive, means the corresponding mutex is locked.
-/// The mutex is unlocked when this object is automatically dropped or explicitly dropped using `drop`.
-pub struct LockGuard<'a, T> {
-    inner: &'a mut T,
-    primask: Primask,
-}
 
-impl<T> Drop for LockGuard<'_, T> {
-    fn drop(&mut self) {
-        // If the interrupts were active before our `disable` call, then re-enable
-        // them. Otherwise, keep them disabled
-        if self.primask.is_active() {
-            unsafe { enable() }
-        }
-    }
-}
-
-impl<T> Deref for LockGuard<'_, T> {
-    type Target = T;
-
-    fn deref(&self) -> &Self::Target {
-        self.inner
-    }
-}
-
-impl<T> DerefMut for LockGuard<'_, T> {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        self.inner
+    /// This function gets a mutable reference to the inner `T` _without_ locking the lock.
+    /// This is inherently unsafe.
+    ///
+    /// # Safety
+    /// This function is only safe if you can guarantee that no other references to the contents of this lock exist.
+    ///
+    /// This generally can be used in the following cases:
+    /// * You only access this mutex from within a single interrupt
+    /// * You only access this mutex outside of interrupts, as interrupts don't break the mutex guarantees
+    #[allow(clippy::mut_from_ref)]
+    pub unsafe fn no_critical_section_lock_mut(&self) -> &mut T {
+        &mut *self.inner.get()
     }
 }
diff --git a/src/time.rs b/src/time.rs
index 00af2371b42b3ea78cf4fd9de4047479df850589..846fa2a43063b99a6ac9ffd992ef85c68fb1a3a8 100644
--- a/src/time.rs
+++ b/src/time.rs
@@ -106,7 +106,7 @@ const PERIOD: u64 = 30517;
 const COUNTER_MAX: u32 = 1 << 24;
 
 /// is set to true when the timer interrupt has gone off.
-/// Used to wait on the timer interrupt in [`wait_for_interrupt`]
+/// Used to wait on the timer interrupt in [`wait_for_next_tick`]
 static TIMER_FLAG: AtomicBool = AtomicBool::new(false);
 
 /// Global time in magic timer units ([`PERIOD`]) since timers started
@@ -120,25 +120,25 @@ static PREV_COUNTER: AtomicU32 = AtomicU32::new(0);
 static COUNTER_PERIOD: AtomicU32 = AtomicU32::new(0);
 
 pub(crate) fn initialize(clock_instance: RTC0, nvic: &mut NVIC) {
-    let mut rtc = RTC.lock();
-
-    rtc.initialize(Rtc::new(clock_instance, PRESCALER).unwrap());
-    rtc.enable_event(RtcInterrupt::Compare0);
-    rtc.enable_interrupt(RtcInterrupt::Compare0, Some(nvic));
-    rtc.enable_counter();
+    RTC.modify(|rtc| {
+        rtc.initialize(Rtc::new(clock_instance, PRESCALER).unwrap());
+        rtc.enable_event(RtcInterrupt::Compare0);
+        rtc.enable_interrupt(RtcInterrupt::Compare0, Some(nvic));
+        rtc.enable_counter();
+    });
 }
 
 // get the current global time in nanoseconds. The precision is not necessarily in single nanoseconds
 fn get_time_ns() -> u64 {
-    // the number of periods of the clock that passed in total
-    let global_time = GLOBAL_TIME.lock();
-    // the current state of the clock
-    let counter = RTC.lock().get_counter();
-    // the previous state of the clock, when the global_time was last updated
-    let prev_counter = PREV_COUNTER.load(Ordering::SeqCst);
-
-    // take the global time, and add to that how much time has passed since the last interrupt
-    (*global_time + u64::from(counter_diff(prev_counter, counter))) * PERIOD
+    GLOBAL_TIME.modify(|global_time| {
+        let counter = RTC.modify(|counter| counter.get_counter());
+
+        // the previous state of the clock, when the global_time was last updated
+        let prev_counter = PREV_COUNTER.load(Ordering::SeqCst);
+
+        // take the global time, and add to that how much time has passed since the last interrupt
+        (*global_time + u64::from(counter_diff(prev_counter, counter))) * PERIOD
+    })
 }
 
 /// neatly calculates a difference in clockcycles between two rtc counter values
@@ -156,9 +156,9 @@ fn counter_diff(prev: u32, curr: u32) -> u32 {
 #[interrupt]
 unsafe fn RTC0() {
     // SAFETY: we're in an interrupt so this code cannot be run concurrently anyway
-    let rtc = RTC.no_critical_section_lock();
+    let rtc = RTC.no_critical_section_lock_mut();
     // SAFETY: we're in an interrupt so this code cannot be run concurrently anyway
-    let global_time = GLOBAL_TIME.no_critical_section_lock();
+    let global_time = GLOBAL_TIME.no_critical_section_lock_mut();
 
     if rtc.is_event_triggered(RtcInterrupt::Compare0) {
         let counter = rtc.get_counter();
@@ -192,18 +192,21 @@ pub fn wait_for_next_tick() {
 ///
 #[allow(clippy::missing_panics_doc)]
 pub fn set_tick_frequency(hz: u64) {
-    let mut rtc = RTC.lock();
+    RTC.modify(|rtc| {
+        let counter_setting = (1_000_000_000 / hz) / PERIOD;
+        // this can't actually happen, since it'd need hz < 1
+        debug_assert!(counter_setting < (1 << 24), "counter period should be less than 1<<24 (roughly 6 minutes with the default PRESCALER settings)");
 
-    let counter_setting = (1_000_000_000 / hz) / PERIOD;
-    // this can't actually happen, since it'd need hz < 1
-    debug_assert!(counter_setting < (1 << 24), "counter period should be less than 1<<24 (roughly 6 minutes with the default PRESCALER settings)");
+        COUNTER_PERIOD.store(counter_setting as u32, Ordering::SeqCst);
 
-    COUNTER_PERIOD.store(counter_setting as u32, Ordering::SeqCst);
+        rtc.set_compare(RtcCompareReg::Compare0, counter_setting as u32)
+            .unwrap();
+        rtc.clear_counter();
+        PREV_COUNTER.store(0, Ordering::SeqCst);
+    });
 
-    rtc.set_compare(RtcCompareReg::Compare0, counter_setting as u32)
-        .unwrap();
-    rtc.clear_counter();
-    PREV_COUNTER.store(0, Ordering::SeqCst);
+    // Give the counter time to clear (Section 19.1.8 of the NRF51 reference manual V3)
+    delay_us_assembly(100);
 }
 
 /// Delay the program for a time using assembly instructions.
diff --git a/src/twi.rs b/src/twi.rs
index 2ce109a53da69075b0d28ef8754640256d01364f..f8ec8c3023f4eb471c406563740ad536af7681de 100644
--- a/src/twi.rs
+++ b/src/twi.rs
@@ -13,12 +13,14 @@ pub(crate) fn initialize(twi: TWI0, scl_pin: P0_04<Disconnected>, sda_pin: P0_02
     let scl_pin = scl_pin.into_floating_input();
     let sda_pin = sda_pin.into_floating_input();
 
-    TWI.lock().initialize(Twi::new(
-        twi,
-        Pins {
-            scl: Pin::from(scl_pin),
-            sda: Pin::from(sda_pin),
-        },
-        FREQUENCY_A::K400,
-    ));
+    TWI.modify(|t| {
+        t.initialize(Twi::new(
+            twi,
+            Pins {
+                scl: Pin::from(scl_pin),
+                sda: Pin::from(sda_pin),
+            },
+            FREQUENCY_A::K400,
+        ))
+    });
 }
diff --git a/src/uart.rs b/src/uart.rs
index a47fbc54f45f5efca25d81f2cd240575697c8252..c74c0699d7add24d6dcd354a8da24e4ef7993b32 100644
--- a/src/uart.rs
+++ b/src/uart.rs
@@ -1,4 +1,4 @@
-use crate::mutex::{LockGuard, Mutex};
+use crate::mutex::Mutex;
 use crate::once_cell::OnceCell;
 use cortex_m::peripheral::NVIC;
 use nrf51_pac::interrupt;
@@ -44,17 +44,19 @@ pub(crate) fn initialize(uart: nrf51_pac::UART0, nvic: &mut NVIC) {
     // actualy enable the uart interrupt
     unsafe { NVIC::unmask(nrf51_pac::Interrupt::UART0) };
 
-    UART.lock().initialize(UartDriver {
-        rx_buffer: ConstGenericRingBuffer::default(),
-        tx_buffer: ConstGenericRingBuffer::default(),
-        tx_data_available: true,
-        uart,
-    });
+    UART.modify(|uartd| {
+        uartd.initialize(UartDriver {
+            rx_buffer: ConstGenericRingBuffer::default(),
+            tx_buffer: ConstGenericRingBuffer::default(),
+            tx_data_available: true,
+            uart,
+        })
+    })
 }
 
 /// Checks if the UART is initialized
 pub fn is_initialized() -> bool {
-    UART.lock().is_initialized()
+    UART.modify(|uart| uart.is_initialized())
 }
 
 /// Safe usage: this should only be called if you never run any real code after this again.
@@ -63,45 +65,46 @@ pub fn is_initialized() -> bool {
 /// again
 #[doc(hidden)]
 pub unsafe fn uninitialize() {
-    UART.lock().uninitialize();
+    UART.modify(|uart| uart.uninitialize())
 }
 
 /// Reads as many bytes as possible from the UART
 pub fn receive_bytes(bytes: &mut [u8]) -> usize {
-    let mut uart = UART.lock();
-
-    let mut i = 0;
-    while let Some(byte) = get_byte(&mut uart) {
-        bytes[i] = byte;
-        i += 1;
-        if i == bytes.len() {
-            break;
+    UART.modify(|uart| {
+        let mut i = 0;
+        while let Some(byte) = get_byte(uart) {
+            bytes[i] = byte;
+            i += 1;
+            if i == bytes.len() {
+                break;
+            }
         }
-    }
-    i
+        i
+    })
 }
 
 /// Reads a single byte from the UART
-fn get_byte(uart: &mut LockGuard<OnceCell<UartDriver>>) -> Option<u8> {
+fn get_byte(uart: &mut OnceCell<UartDriver>) -> Option<u8> {
     uart.rx_buffer.dequeue()
 }
 
 /// Writes the entire buffer over UART
 pub fn send_bytes(bytes: &[u8]) -> bool {
-    let mut uart = UART.lock();
-    if uart.tx_buffer.len() + bytes.len() >= uart.tx_buffer.capacity() {
-        return false;
-    }
+    UART.modify(|uart| {
+        if uart.tx_buffer.len() + bytes.len() >= uart.tx_buffer.capacity() {
+            return false;
+        }
 
-    for byte in bytes {
-        put_byte(&mut uart, *byte);
-    }
+        for byte in bytes {
+            put_byte(uart, *byte);
+        }
 
-    true
+        true
+    })
 }
 
 /// Pushes a single byte over uart
-fn put_byte(uart: &mut LockGuard<OnceCell<UartDriver>>, byte: u8) {
+fn put_byte(uart: &mut OnceCell<UartDriver>, byte: u8) {
     if uart.tx_data_available {
         uart.tx_data_available = false;
         uart.uart.txd.write(|w| unsafe { w.txd().bits(byte) });
@@ -115,7 +118,7 @@ fn put_byte(uart: &mut LockGuard<OnceCell<UartDriver>>, byte: u8) {
 /// It's called when the enabled interrupts for uart0 are triggered
 unsafe fn UART0() {
     // Safety: interrupts are already turned off here, since we are inside an interrupt
-    let uart = unsafe { UART.no_critical_section_lock() };
+    let uart = unsafe { UART.no_critical_section_lock_mut() };
 
     if uart.uart.events_rxdrdy.read().bits() != 0 {
         uart.uart.events_rxdrdy.reset();