diff --git a/controller/Cargo.toml b/controller/Cargo.toml
index ff8dfc8d0d6d0999fd873576dbb61f4d0b44c00f..2b9430fddd0d5fa4f38ceee32981c701cf2c845f 100644
--- a/controller/Cargo.toml
+++ b/controller/Cargo.toml
@@ -16,3 +16,6 @@ embassy-time = { git = "https://github.com/embassy-rs/embassy", features = ["def
 heapless = "0.8.0"
 i2c2-target = { path = "../i2c2-target" }
 panic-probe = { version = "0.3.1", features = ["print-defmt"] }
+
+[lints.clippy]
+pedantic = "deny"
diff --git a/controller/src/encoders.rs b/controller/src/encoders.rs
index f586befa2fecf09faa0c328e1b34748b943b25ca..c07b58a8e933891378d209306ab50378c6bb199d 100644
--- a/controller/src/encoders.rs
+++ b/controller/src/encoders.rs
@@ -66,6 +66,7 @@ impl Encoders<'_> {
     }
 }
 
+#[allow(clippy::cast_possible_wrap)]
 fn ticks_since(old: &mut u16, new: u16) -> i16 {
     let diff = (new - *old) as i16;
     *old = new;
diff --git a/controller/src/logic.rs b/controller/src/logic.rs
index 4c9da50455ef44bbfc3c012589dc72009092a52f..21b37196b4c8e286e9b148b499b1d0f579515b48 100644
--- a/controller/src/logic.rs
+++ b/controller/src/logic.rs
@@ -31,6 +31,7 @@ impl State {
         }
     }
 
+    #[allow(clippy::cast_possible_wrap)]
     fn set_motor_speed(&mut self, ms: [u8; 2]) {
         let (max_pwm, mmp) = (
             self.motors.max_pwm() as i32,
@@ -86,6 +87,12 @@ static mut STATE: Option<State> = None;
 /// to be able to be checked outside any critical section.
 static WATCHDOG_EXPIRATION: AtomicU32 = AtomicU32::new(0);
 
+/// Number of milliseconds since boot
+#[allow(clippy::cast_possible_truncation)]
+fn current_millis() -> u32 {
+    Instant::now().as_millis() as u32
+}
+
 #[embassy_executor::task]
 pub async fn start_i2c_target(
     i2c2: I2C2,
@@ -99,11 +106,10 @@ pub async fn start_i2c_target(
     }
     loop {
         Timer::after_millis(100).await;
-        if Instant::now().as_millis() as u32 >= WATCHDOG_EXPIRATION.load(Ordering::Relaxed) {
+        if current_millis() >= WATCHDOG_EXPIRATION.load(Ordering::Relaxed) {
             State::with_critical_section(|state| {
                 if state.is_moving()
-                    && Instant::now().as_millis() as u32
-                        >= WATCHDOG_EXPIRATION.load(Ordering::Relaxed)
+                    && current_millis() >= WATCHDOG_EXPIRATION.load(Ordering::Relaxed)
                 {
                     state.standby();
                 }
@@ -117,7 +123,7 @@ fn i2c_callback(command: &[u8], response: &mut Deque<u8, MESSAGE_SIZE>) {
     let state = unsafe { STATE.as_mut().unwrap() };
     if process_command(state, command, response) {
         WATCHDOG_EXPIRATION.store(
-            Instant::now().as_millis() as u32 + 100 * state.watchdog_ticks as u32,
+            current_millis() + 100 * u32::from(state.watchdog_ticks),
             Ordering::Relaxed,
         );
     } else {
@@ -180,7 +186,7 @@ fn process_command(
             response.push_back(right[1]).unwrap();
         }
         [STATUS] => {
-            response.push_back(state.is_moving() as u8).unwrap();
+            response.push_back(u8::from(state.is_moving())).unwrap();
         }
         _ => {
             defmt::warn!("unknown command or args {:#04x}", command);
diff --git a/controller/src/tb6612fng.rs b/controller/src/tb6612fng.rs
index 91eca2f0fb2ecec4770e90f187889b5300d32825..13844848411317c1d603f923e8ca403257e7e1a1 100644
--- a/controller/src/tb6612fng.rs
+++ b/controller/src/tb6612fng.rs
@@ -5,6 +5,7 @@ use embassy_stm32::{
     peripherals::{PA10, PA11, PA4, PA5, PB5, PB6, PB8, TIM1},
     time::Hertz,
     timer::{
+        low_level::CountingMode,
         simple_pwm::{PwmPin, SimplePwm},
         Channel,
     },
@@ -40,7 +41,7 @@ impl Neg for Movement {
 }
 
 impl Tb6612fng<'_> {
-    #[allow(clippy::too_many_arguments)]
+    #[allow(clippy::similar_names, clippy::too_many_arguments)]
     pub fn new(
         pa4: PA4,
         pa5: PA5,
@@ -66,7 +67,7 @@ impl Tb6612fng<'_> {
             Some(ch3),
             Some(ch4),
             freq,
-            Default::default(),
+            CountingMode::default(),
         );
         pwm.enable(Channel::Ch3);
         pwm.enable(Channel::Ch4);
@@ -112,6 +113,7 @@ impl Tb6612fng<'_> {
         self.move_right(right);
     }
 
+    #[allow(clippy::cast_sign_loss)]
     fn make_command(&self, movement: Movement) -> (Level, Level, u32) {
         let max_duty = self.max_pwm();
         match movement {
diff --git a/i2c2-target/Cargo.toml b/i2c2-target/Cargo.toml
index 5d6abad28307b12753b95dfeb03416198cd9e75a..9ff8f9855f20f1939031131814beceb3a1ad11a4 100644
--- a/i2c2-target/Cargo.toml
+++ b/i2c2-target/Cargo.toml
@@ -8,3 +8,8 @@ edition = "2021"
 defmt = "0.3.5"
 embassy-stm32 = { git = "https://github.com/embassy-rs/embassy", features = ["defmt", "stm32f103c8", "unstable-pac", "time-driver-tim4"] }
 heapless = "0.8.0"
+
+[lints.clippy]
+pedantic = "deny"
+missing_panics_doc = "allow"
+must_use_candidate = "allow"
diff --git a/i2c2-target/src/lib.rs b/i2c2-target/src/lib.rs
index 1d33e99a09c0269db3dec45fbec00463c2191bf7..0425734ce83bc5536df45213c2d8850594e52548 100644
--- a/i2c2-target/src/lib.rs
+++ b/i2c2-target/src/lib.rs
@@ -176,7 +176,7 @@ unsafe fn I2C2_EV() {
     let state = unsafe { STATE.as_mut().unwrap() };
     if sr1.rxne() {
         defmt::trace!("I2C2_EV RXNE");
-        let b = pac::I2C2.dr().read().0 as u8;
+        let b = pac::I2C2.dr().read().dr();
         defmt::trace!(
             "received byte {:#04x} - sr1 is now {:#06x}",
             &b,