diff --git a/Cargo.lock b/Cargo.lock
index 9673353166ecc56d903781cb65c0310fec1753f0..a157e19a6feb3e0d7d7cb303123438a24406522e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -138,6 +138,7 @@ dependencies = [
  "defmt-rtt",
  "embassy-executor",
  "embassy-stm32",
+ "embassy-sync",
  "embassy-time",
  "heapless",
  "i2c2-target",
diff --git a/controller/Cargo.toml b/controller/Cargo.toml
index 5bcdbcd5e52696b7bcf68269c9d49ae71903e6c1..f44fef9197fc223cb52ce7f0ece91676456c1b53 100644
--- a/controller/Cargo.toml
+++ b/controller/Cargo.toml
@@ -12,6 +12,7 @@ defmt = "0.3.8"
 defmt-rtt = "0.4.1"
 embassy-executor = { git = "https://github.com/embassy-rs/embassy", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] }
 embassy-stm32 = { git = "https://github.com/embassy-rs/embassy", features = ["defmt", "stm32f103c8", "unstable-pac", "time-driver-tim4", "memory-x"] }
+embassy-sync = { git = "https://github.com/embassy-rs/embassy", features = ["defmt"] }
 embassy-time = { git = "https://github.com/embassy-rs/embassy", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
 heapless = "0.8.0"
 i2c2-target = { path = "../i2c2-target" }
diff --git a/controller/src/blinker.rs b/controller/src/blinker.rs
new file mode 100644
index 0000000000000000000000000000000000000000..fe81c8d8337fd95747992f7d554b74302e761d6f
--- /dev/null
+++ b/controller/src/blinker.rs
@@ -0,0 +1,49 @@
+use embassy_stm32::{
+    gpio::{Level, Output, Speed},
+    peripherals::PB15,
+};
+use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, signal::Signal};
+use embassy_time::Timer;
+
+static BLINK_PATTERN: Signal<CriticalSectionRawMutex, &'static str> = Signal::new();
+
+pub fn set_blink_pattern(pattern: &'static str) {
+    BLINK_PATTERN.signal(pattern);
+}
+
+#[embassy_executor::task]
+pub async fn blink(pin: PB15, initial_pattern: &'static str) {
+    let mut led = Output::new(pin, Level::High, Speed::Low);
+    let mut pattern = initial_pattern;
+    loop {
+        if let Some(new_pattern) = BLINK_PATTERN.try_take() {
+            pattern = new_pattern;
+        }
+        for b in pattern.chars() {
+            match b {
+                ' ' => {
+                    Timer::after_millis(200).await;
+                }
+                '.' => {
+                    led.toggle();
+                    Timer::after_millis(150).await;
+                    led.toggle();
+                    Timer::after_millis(200).await;
+                }
+                '-' => {
+                    led.toggle();
+                    Timer::after_millis(300).await;
+                    led.toggle();
+                    Timer::after_millis(200).await;
+                }
+                _ => {
+                    defmt::error!("unknown pattern letter {}", b);
+                    led.toggle();
+                    Timer::after_secs(5).await;
+                    led.toggle();
+                }
+            }
+        }
+        Timer::after_secs(1).await;
+    }
+}
diff --git a/controller/src/main.rs b/controller/src/main.rs
index b3de259234d74c625735585a32d2bddddebffd26..5252c4db7089717d5cd74a3cb5c39fcf148e95b8 100644
--- a/controller/src/main.rs
+++ b/controller/src/main.rs
@@ -5,17 +5,16 @@ use crate::{encoders::Encoders, tb6612fng::Tb6612fng};
 use defmt_rtt as _;
 use embassy_executor::Spawner;
 use embassy_stm32::{
-    gpio::{Level, Output, Speed},
     interrupt::Priority,
     pac,
-    peripherals::{PB15, RCC},
+    peripherals::RCC,
     rcc::{APBPrescaler, Hse, HseMode, Pll, PllMul, PllPreDiv, PllSource, Sysclk},
     time::{khz, mhz},
     Config,
 };
-use embassy_time::Timer;
 use panic_probe as _;
 
+pub mod blinker;
 mod encoders;
 mod logic;
 mod tb6612fng;
@@ -88,7 +87,7 @@ async fn main(spawner: Spawner) {
     );
 
     spawner
-        .spawn(blink(p.PB15, blink_pattern(reset_cause)))
+        .spawn(blinker::blink(p.PB15, blink_pattern(reset_cause)))
         .unwrap();
 
     let motors = Tb6612fng::new(
@@ -111,36 +110,3 @@ async fn main(spawner: Spawner) {
         .spawn(logic::start_i2c_target(i2c2, motors, encoders, p.WWDG))
         .unwrap();
 }
-
-#[embassy_executor::task]
-async fn blink(pin: PB15, pattern: &'static str) {
-    let mut led = Output::new(pin, Level::High, Speed::Low);
-    loop {
-        for b in pattern.chars() {
-            match b {
-                ' ' => {
-                    Timer::after_millis(200).await;
-                }
-                '.' => {
-                    led.toggle();
-                    Timer::after_millis(150).await;
-                    led.toggle();
-                    Timer::after_millis(200).await;
-                }
-                '-' => {
-                    led.toggle();
-                    Timer::after_millis(300).await;
-                    led.toggle();
-                    Timer::after_millis(200).await;
-                }
-                _ => {
-                    defmt::error!("unknown pattern letter {}", b);
-                    led.toggle();
-                    Timer::after_secs(5).await;
-                    led.toggle();
-                }
-            }
-        }
-        Timer::after_secs(1).await;
-    }
-}