diff --git a/Cargo.lock b/Cargo.lock
index 66b56eefa35248226c40ba7285bf7f617338c157..0c2cb1427ae963cf508a3d1208a0330b09d214bd 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -53,6 +53,13 @@ version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 
+[[package]]
+name = "common"
+version = "0.1.0"
+dependencies = [
+ "embassy-stm32",
+]
+
 [[package]]
 name = "cortex-m"
 version = "0.7.7"
@@ -131,6 +138,7 @@ dependencies = [
 name = "dc-motor-driver-hat"
 version = "0.2.0"
 dependencies = [
+ "common",
  "cortex-m",
  "cortex-m-rt",
  "critical-section",
diff --git a/Cargo.toml b/Cargo.toml
index 04213659e1eb70b34082bd84b2e1751e3d390150..f52579b3e238e43b441cb8cfc69ea3534f9dba3d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,5 +1,5 @@
 [workspace]
-members = ["controller", "i2c2-target"]
+members = [ "common","controller", "i2c2-target"]
 exclude = ["pid"]
 resolver = "2"
 
diff --git a/common/Cargo.toml b/common/Cargo.toml
new file mode 100644
index 0000000000000000000000000000000000000000..1a0b871ac69dbcd988e7a064331f8c6c29703b03
--- /dev/null
+++ b/common/Cargo.toml
@@ -0,0 +1,7 @@
+[package]
+name = "common"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+embassy-stm32 = { git = "https://github.com/embassy-rs/embassy", features = ["unstable-pac"] }
diff --git a/common/src/lib.rs b/common/src/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..5a145a9d0353be065678eb372df3066ab8761549
--- /dev/null
+++ b/common/src/lib.rs
@@ -0,0 +1,48 @@
+#![no_std]
+
+use embassy_stm32::pac;
+
+pub fn mcu_kind() -> (&'static str, &'static str) {
+    let dbgmcu = pac::DBGMCU.idcode().read();
+    match (dbgmcu.dev_id(), dbgmcu.rev_id()) {
+        (0x412, 0x1000) => ("low-density", "A"),
+        (0x412, _) => ("low-density", "?"),
+        (0x410, 0x0000) => ("medium-density", "A"),
+        (0x410, 0x2000) => ("medium-density", "B"),
+        (0x410, 0x2001) => ("medium-density", "Z"),
+        (0x410, 0x2003) => ("medium-density", "1/2/3/X/Y"),
+        (0x414, 0x1000) => ("high-density", "1/A"),
+        (0x414, 0x1001) => ("high-density", "Z"),
+        (0x414, 0x1003) => ("high-density", "1/2/3/X/Y"),
+        (0x414, _) => ("high-density", "?"),
+        (0x430, 0x1000) => ("XL-density", "1/A"),
+        (0x430, _) => ("XL-density", "?"),
+        (0x418, 0x1000) => ("connectivity", "A"),
+        (0x418, 0x1001) => ("connectivity", "Z"),
+        (0x418, _) => ("connectivity", "?"),
+        (_, _) => ("?", "?"),
+    }
+}
+
+pub fn flash_size() -> usize {
+    usize::from(unsafe { *(0x1FFF_F7E0 as *const u16) })
+}
+
+pub fn identity() -> Option<(u32, u32, &'static str)> {
+    let pid = 0xe00f_ffe0 as *const [u32; 3];
+    let pid = unsafe { *pid };
+    (pid[2] & 8 != 0).then(|| {
+        let identity_code = ((pid[1] & 0xf0) >> 4) | ((pid[2] & 0x7) << 4);
+        let continuation_code = unsafe { *(0xe00f_ffd0 as *const u32) };
+        let device = match (identity_code, continuation_code) {
+            (0x20, 0x00) => "STM32",
+            (0x3b, 0x04) => "APM32",
+            _ => "?",
+        };
+        (identity_code, continuation_code, device)
+    })
+}
+
+pub fn device_id() -> &'static [u8; 8] {
+    unsafe { &*(0x1fff_f7e8 as *const [u8; 8]) }
+}
diff --git a/controller/Cargo.toml b/controller/Cargo.toml
index 4a671b324eb368f130e2ec88a017828e86e5f658..6bbd988520a57278a1706784feae9493c56516b9 100644
--- a/controller/Cargo.toml
+++ b/controller/Cargo.toml
@@ -5,6 +5,7 @@ authors = ["Samuel Tardieu <sam@rfc1149.net>"]
 edition = "2021"
 
 [dependencies]
+common = { version = "0.1.0", path = "../common" }
 cortex-m = { version = "0.7.7", features = ["critical-section-single-core", "inline-asm"] }
 cortex-m-rt = "0.7.3"
 critical-section = "1.1.2"
diff --git a/controller/src/chip.rs b/controller/src/chip.rs
index a92564de1a8ea36d32c03ee4021d829f05e55f37..b2523c16bde785f4bf5e934bece668a5bc0bf73c 100644
--- a/controller/src/chip.rs
+++ b/controller/src/chip.rs
@@ -1,26 +1,6 @@
-use embassy_stm32::pac;
-
 pub fn identify() {
-    let dbgmcu = pac::DBGMCU.idcode().read();
-    let (density, rev) = match (dbgmcu.dev_id(), dbgmcu.rev_id()) {
-        (0x412, 0x1000) => ("low-density", "A"),
-        (0x412, _) => ("low-density", "?"),
-        (0x410, 0x0000) => ("medium-density", "A"),
-        (0x410, 0x2000) => ("medium-density", "B"),
-        (0x410, 0x2001) => ("medium-density", "Z"),
-        (0x410, 0x2003) => ("medium-density", "1/2/3/X/Y"),
-        (0x414, 0x1000) => ("high-density", "1/A"),
-        (0x414, 0x1001) => ("high-density", "Z"),
-        (0x414, 0x1003) => ("high-density", "1/2/3/X/Y"),
-        (0x414, _) => ("high-density", "?"),
-        (0x430, 0x1000) => ("XL-density", "1/A"),
-        (0x430, _) => ("XL-density", "?"),
-        (0x418, 0x1000) => ("connectivity", "A"),
-        (0x418, 0x1001) => ("connectivity", "Z"),
-        (0x418, _) => ("connectivity", "?"),
-        (_, _) => ("?", "?"),
-    };
-    let flash_size = unsafe { *(0x1FFF_F7E0 as *const u16) };
+    let (density, rev) = common::mcu_kind();
+    let flash_size = common::flash_size();
     defmt::info!(
         "Device type: {}, revision: {} – flash size: {}kB",
         density,
@@ -28,15 +8,18 @@ pub fn identify() {
         flash_size
     );
 
-    let pid = 0xe00f_ffe0 as *const [u32; 3];
-    let pid = unsafe { *pid };
-    let used = pid[2] & 8 != 0;
-    let identity_code = ((pid[1] & 0xf0) >> 4) | ((pid[2] & 0x7) << 4);
-    let continuation_code = unsafe { *(0xe00f_ffd0 as *const u32) };
-    defmt::info!(
-        "used = {}, identity_code = {:#04x}, continuation_code = {:#04x}",
-        used,
-        identity_code,
-        continuation_code
-    );
+    if let Some((identity_code, continuation_code, device)) = common::identity() {
+        defmt::info!(
+            "device = {} (identity code = {:#04x}, continuation code = {:#04x})",
+            device,
+            identity_code,
+            continuation_code
+        );
+    } else {
+        defmt::info!("no identity and continuation codes");
+    }
+}
+
+pub fn device_id() -> &'static [u8; 8] {
+    unsafe { &*(0x1fff_f7e8 as *const [u8; 8]) }
 }
diff --git a/controller/src/logic/mod.rs b/controller/src/logic/mod.rs
index 82ed9c5dcf3cc236fd98234d2f0fadb110fd4d47..2848bb8ef7d19b36af0ab00867c5a75a632219b8 100644
--- a/controller/src/logic/mod.rs
+++ b/controller/src/logic/mod.rs
@@ -1,4 +1,4 @@
-use crate::{encoders::Encoders, tb6612fng::Tb6612fng};
+use crate::{chip, encoders::Encoders, tb6612fng::Tb6612fng};
 use embassy_executor::Spawner;
 use embassy_stm32::{peripherals::WWDG, time::hz};
 use futures::FutureExt;
@@ -156,8 +156,7 @@ fn process_command(
             cortex_m::peripheral::SCB::sys_reset();
         }
         [CMD_DEVICE_ID] => {
-            let dev_id = unsafe { &*(0x1fff_f7e8 as *const [u8; 8]) };
-            for &b in dev_id {
+            for &b in chip::device_id() {
                 response.push(b).unwrap();
             }
         }