Repository: HaoboGu/rmk (language: rust)

Issue:
async memory usage
run_rmk cause stack protection fault on esp32c6 with 100kb stack.

This article talks about wrapper async function's memory usage [rebinding values in an async function can double the memory usage
[#96084](https://github.com/rust-lang/rust/issues/96084)](https://github.com/rust-lang/rust/issues/96084)

```
0.959410 INFO Advertising..
    Guru Meditation Error: Core  0 panic'ed (Stack protection fault).
    
    Detected in task "main" at 0x4080c1be
    0x4080c1be - xQueueSemaphoreTake
        at /home/embedded/rmk/examples/use_rust/esp32c6_ble/.embuild/espressif/esp-idf/v5.2.2/components/freertos/FreeRTOS-Kernel/queue.c:1699
    Stack pointer: 0x40818f20
    Stack bounds: 0x40818f4c - 0x408317e0
    
    
    Core  0 register dump:
    MEPC    : 0x4080da26  RA      : 0x4080c1d4  SP      : 0x40818f20  GP      : 0x40812040
    0x4080da26 - xTaskGetSchedulerState
        at /home/embedded/rmk/examples/use_rust/esp32c6_ble/.embuild/espressif/esp-idf/v5.2.2/components/freertos/FreeRTOS-Kernel/tasks.c:4972
    0x4080c1d4 - xQueueSemaphoreTake
        at /home/embedded/rmk/examples/use_rust/esp32c6_ble/.embuild/espressif/esp-idf/v5.2.2/components/freertos/FreeRTOS-Kernel/queue.c:1718
    0x40812040 - coex_schm_ble_mesh_standby_bt_a2dp_paused_wifi_connecting
        at ??:??
    TP      : 0x408053b0  T0      : 0x40028140  T1      : 0xffffffe0  T2      : 0xffffffff
    0x408053b0 - r_ble_lll_sched_insertion_begin
        at ??:??
    S0/FP   : 0x408179bc  S1      : 0xffffffff  A0      : 0x408179bc  A1      : 0xffffffff
    A2      : 0x00000004  A3      : 0x00000000  A4      : 0x00000001  A5      : 0x00000001
    A6      : 0x00000000  A7      : 0x00000000  S2      : 0x00000000  S3      : 0xffffffff
    S4      : 0x40831848  S5      : 0x40820768  S6      : 0x40820768  S7      : 0x40823e58
    S8      : 0x40819800  S9      : 0x40820768  S10     : 0x420aceaf  S11     : 0x0000000c
    T3      : 0x00000001  T4      : 0x00000000  T5      : 0x0000003e  T6      : 0x40803e80
    0x40803e80 - npl_freertos_time_delay
        at /home/embedded/rmk/examples/use_rust/esp32c6_ble/.embuild/espressif/esp-idf/v5.2.2/components/bt/porting/npl/freertos/src/npl_os_freertos.c:997
    MSTATUS : 0x00001881  MTVEC   : 0x40800001  MCAUSE  : 0x0000001b  MTVAL   : 0x408167b7
    0x40800001 - _vector_table
        at ??:??
    0x408167b7 - ble_store_config_our_secs
        at ??:??
    MHARTID : 0x00000000
```


Discussion / hints:
The issue says "This seems to only happen if the value was borrowed earlier". I suspect the cause is not the async `run_rmk`. 
I did some improvements for `run_rmk`: https://github.com/HaoboGu/rmk/pull/76
Still happens,

I committed the larger kinesis keymap, see if you can reproduce it.

My fix, alias esp function can run within 64kb stack.
this is quite strange🤔, I will take a look later
I checked stack size using `-Zemit-stack-sizes` on nrf52840 example, using `run_rmk` and `initialize_nrf_ble_keyboard_with_config_and_run` emits same stack size. I suspect it's related only to esp32. (I was not able to get stack size for esp :(


Fix patch:
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 72b639b1..f134b2f7 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -17,11 +17,11 @@
         // Enable ONE of the following for accurate analyzation of RA
         // "examples/use_rust/esp32c3_ble/Cargo.toml",
         // "examples/use_rust/esp32c6_ble/Cargo.toml",
-        // "examples/use_rust/stm32h7/Cargo.toml",
+        "examples/use_rust/stm32h7/Cargo.toml",
         // "examples/use_rust/stm32f4/Cargo.toml",
         // "examples/use_config/stm32f4/Cargo.toml",
         // "examples/use_rust/stm32f1/Cargo.toml",
-        // "examples/use_rust/nrf52840_ble/Cargo.toml",
+        "examples/use_rust/nrf52840_ble/Cargo.toml",
         // "examples/use_rust/nrf52840_ble_split/Cargo.toml",
         // "examples/use_rust/nrf52832_ble/Cargo.toml",
         // "examples/use_rust/rp2040/Cargo.toml"
diff --git a/docs/src/device.md b/docs/src/device.md
index 0101b75f..7c0a7d96 100644
--- a/docs/src/device.md
+++ b/docs/src/device.md
@@ -6,16 +6,19 @@ There are two types of device in RMK:
 
 - output device: an external device which is triggered by RMK, to perform some functionalities, such as LED, RGB, screen, motor, etc
 
+## Input device
+
+Here is a simple(but not exhaustive) list of input devices:
+
+- Keyboard itself
+- Rotary encoder
+- Touchpad
+- Trackball
+- Joystick
+
+Except keyboard and rotary encoder, the others protocol/implementation depend on the actual device. A driver like interface is what we need.
+
+### Rotary encoder
+
+The encoder list is represented separately in vial, different from normal matrix. But layers still have effect on encoder. The behavior of rotary encoder could be changed by vial.
 
-## Current tasks
-
-- `keyboard_task`
-- communication task
-  - `communication_task`
-  - `ble_communication_task`
-- communication background task
-  - `gatt_server::run()`
-  - `usb_device.run()`
-- storage_task
-- vial_task
-- led_hid_task
\ No newline at end of file
diff --git a/docs/src/how_to_contribute.md b/docs/src/how_to_contribute.md
index 92463f91..8793e16c 100644
--- a/docs/src/how_to_contribute.md
+++ b/docs/src/how_to_contribute.md
@@ -31,31 +31,19 @@ So, if you want to contribute new features of RMK, just look into `rmk` core cra
 
 `rmk` crate is the main crate, it provides several entry API to start the keyboard firmware. All the entry APIs are similar, it:
 
-- Initialize the storage and keymap first
-- Create services: main keyboard service, usb service, ble service, vial service, light service, etc.
-- Create `keyboard_report_channel` which is used to send message from matrix scanner to communication task(USB or BLE)
+- Initialize the storage, keymap and matrix first
+- Create services: main keyboard service, matrix service, usb service, ble service, vial service, light service, etc.
 - Run all tasks in an infinite loop, if there's a task failed, wait some time and rerun
 
-Generally, there are 4-5 running tasks in the meanwhile, according to the user's config. Communication between tasks is done by channel. There are two common channel: `FLASH_CHANNEL` and `keyboard_report_channel`. 
+Generally, there are 4-5 running tasks in the meanwhile, according to the user's config. Communication between tasks is done by channels.There are several built-in channels: 
 
-The former is global, which is actually a multi-sender, single-receiver channel. There are many tasks send the `FlashOperationMessage`, such as BLE task(which saves bond info), vial task(which saves key), etc.
+- `FLASH_CHANNEL`: a multi-sender, single-receiver channel. There are many tasks send the `FlashOperationMessage`, such as BLE task(which saves bond info), vial task(which saves key), etc.
+- `key_event_channel`: a multi-sender, single-receiver channel. The sender can be a matrix task which scans the key matrix or a split peripheral monitor which receives key event from split peripheral. The receiver, i.e. keyboard task, receives the key event and processes the key
+- `keyboard_report_channel`: a single-sender, single-receiver channel, keyboard task sends keyboard report to channel after the key event is processed, and USB/BLE task receives the keyboard report and sends the key to the host.
 
-`keyboard_report_channel` is a SPSC channel: keyboard task sends keyboard report to channel when scanning the matrix, and USB/BLE task receives the keyboard report and sends the key to the host.
-
-### Matrix scanning
+### Matrix scanning & key processing
 
 An important part of a keyboard firmware is how it performs [matrix scanning](https://en.wikipedia.org/wiki/Keyboard_matrix_circuit) and how it processes the scanning result to generate keys.
 
-In RMK, this work is done in `keyboard_task`. The `keyboard_task` is very simple: it scans the matrix, saves scanning result in `Keyboard` struct and sends keyboard reports to USB/BLE tasks after each scanning:
-
-```rust
-// Main loop of keybaord_task 
-loop {
-    let _ = keyboard.scan_matrix(sender).await;
-    keyboard.send_keyboard_report(sender).await;
-    keyboard.send_media_report(sender).await;
-    keyboard.send_mouse_report(sender).await;
-    keyboard.send_system_control_report(sender).await;
-}
-```
+In RMK, this work is done in `Matrix` and `Keyboard` respectively. The `Matrix` scans the key matrix and send `KeyEvent` if there's a key change in matrix. Then the `Keyboard` receives the `KeyEvent` and processes it into actual keyboard report. Finally, the keyboard report is sent to USB/BLE tasks and forwarded to the host via USB/BLE.
 
diff --git a/docs/src/split_keyboard_design.md b/docs/src/split_keyboard_design.md
index 01095177..b804ed1d 100644
--- a/docs/src/split_keyboard_design.md
+++ b/docs/src/split_keyboard_design.md
@@ -8,9 +8,7 @@
 
 In RMK's current implementation, the peripheral continously scans it's matrix, if there's a key change, A `SplitMessage` is sent to the central.
 
-In central, there's a peripheral monitor for each peripheral, which receives the `SplitMessage` from the peripheral and caches all key states in that peripheral. 
-
-And there's a separate keyboard thread in central, which does the keyboard stuffs. The only difference is, in matrix scanning stage, it synchronizes key states from peripheral monitor and scans central's matrix.
+In central, there's a peripheral monitor for each peripheral, which receives the `SplitMessage` from the peripheral and forwards the key event to keyboard task. 
 
 ## Protocol
 
@@ -24,9 +22,8 @@ A single message can be defined like:
 
 ```rust
 pub enum SplitMessage {
-    /// Activated key info (row, col, pressed), from peripheral to central.
-    /// Only key changes are sent in the split message, aka if pressed = true, the actual event is this key state changes from released -> pressed and vice versa.
-    Key(u8, u8, bool)
+    /// Key event from peripheral to central
+    Key(KeyEvent)
     /// Led state, on/off
     LedState(u8),
 }
diff --git a/examples/use_config/esp32c3_ble/.cargo/config.toml b/examples/use_config/esp32c3_ble/.cargo/config.toml
index cd74f9ec..29c169db 100644
--- a/examples/use_config/esp32c3_ble/.cargo/config.toml
+++ b/examples/use_config/esp32c3_ble/.cargo/config.toml
@@ -12,7 +12,7 @@ build-std = ["std", "panic_abort"]
 [env]
 MCU="esp32c3"
 # Note: this variable is not used by the pio builder (`cargo build --features pio`)
-ESP_IDF_VERSION = "v5.2.3"
+ESP_IDF_VERSION = "v5.2"
 DEFMT_LOG="debug"
 ESP_IDF_PATH_ISSUES="warn"
 CRATE_CC_NO_DEFAULTS="1"
\ No newline at end of file
diff --git a/examples/use_config/esp32c3_ble/Cargo.lock b/examples/use_config/esp32c3_ble/Cargo.lock
index 1acfcae2..7db22dfd 100644
--- a/examples/use_config/esp32c3_ble/Cargo.lock
+++ b/examples/use_config/esp32c3_ble/Cargo.lock
@@ -1499,7 +1499,7 @@ dependencies = [
 
 [[package]]
 name = "rmk"
-version = "0.3.0"
+version = "0.3.2"
 dependencies = [
  "bitfield-struct",
  "byteorder",
diff --git a/examples/use_config/esp32c3_ble/sdkconfig.defaults b/examples/use_config/esp32c3_ble/sdkconfig.defaults
index 0755c529..3aff089f 100644
--- a/examples/use_config/esp32c3_ble/sdkconfig.defaults
+++ b/examples/use_config/esp32c3_ble/sdkconfig.defaults
@@ -1,5 +1,5 @@
 # Rust often needs a bit of an extra main task stack size compared to C (the default is 3K)
-CONFIG_ESP_MAIN_TASK_STACK_SIZE=12000
+CONFIG_ESP_MAIN_TASK_STACK_SIZE=64000
 
 # Use this to set FreeRTOS kernel tick frequency to 1000 Hz (100 Hz by default).
 # This allows to use 1 ms granuality for thread sleeps (10 ms by default).
diff --git a/examples/use_config/esp32c6_ble/.cargo/config.toml b/examples/use_config/esp32c6_ble/.cargo/config.toml
index a04271e9..816670f8 100644
--- a/examples/use_config/esp32c6_ble/.cargo/config.toml
+++ b/examples/use_config/esp32c6_ble/.cargo/config.toml
@@ -12,7 +12,7 @@ build-std = ["std", "panic_abort"]
 [env]
 MCU="esp32c6"
 # Note: this variable is not used by the pio builder (`cargo build --features pio`)
-ESP_IDF_VERSION = "v5.2.3"
+ESP_IDF_VERSION = "v5.2"
 DEFMT_LOG="debug"
 ESP_IDF_PATH_ISSUES="warn"
 CRATE_CC_NO_DEFAULTS="1"
\ No newline at end of file
diff --git a/examples/use_config/esp32c6_ble/Cargo.lock b/examples/use_config/esp32c6_ble/Cargo.lock
index 2f5e7ef0..679a413b 100644
--- a/examples/use_config/esp32c6_ble/Cargo.lock
+++ b/examples/use_config/esp32c6_ble/Cargo.lock
@@ -1498,7 +1498,7 @@ dependencies = [
 
 [[package]]
 name = "rmk"
-version = "0.3.0"
+version = "0.3.2"
 dependencies = [
  "bitfield-struct",
  "byteorder",
diff --git a/examples/use_config/esp32c6_ble/sdkconfig.defaults b/examples/use_config/esp32c6_ble/sdkconfig.defaults
index 0755c529..3aff089f 100644
--- a/examples/use_config/esp32c6_ble/sdkconfig.defaults
+++ b/examples/use_config/esp32c6_ble/sdkconfig.defaults
@@ -1,5 +1,5 @@
 # Rust often needs a bit of an extra main task stack size compared to C (the default is 3K)
-CONFIG_ESP_MAIN_TASK_STACK_SIZE=12000
+CONFIG_ESP_MAIN_TASK_STACK_SIZE=64000
 
 # Use this to set FreeRTOS kernel tick frequency to 1000 Hz (100 Hz by default).
 # This allows to use 1 ms granuality for thread sleeps (10 ms by default).
diff --git a/examples/use_config/esp32s3_ble/.cargo/config.toml b/examples/use_config/esp32s3_ble/.cargo/config.toml
index 10401b46..e5622473 100644
--- a/examples/use_config/esp32s3_ble/.cargo/config.toml
+++ b/examples/use_config/esp32s3_ble/.cargo/config.toml
@@ -12,7 +12,7 @@ build-std = ["std", "panic_abort"]
 [env]
 MCU="esp32s3"
 # Note: this variable is not used by the pio builder (`cargo build --features pio`)
-ESP_IDF_VERSION = "v5.2.3"
+ESP_IDF_VERSION = "v5.2"
 DEFMT_LOG="debug"
 ESP_IDF_PATH_ISSUES="warn"
 CRATE_CC_NO_DEFAULTS="1"
\ No newline at end of file
diff --git a/examples/use_config/esp32s3_ble/Cargo.lock b/examples/use_config/esp32s3_ble/Cargo.lock
index 0f50fac8..e17b0b51 100644
--- a/examples/use_config/esp32s3_ble/Cargo.lock
+++ b/examples/use_config/esp32s3_ble/Cargo.lock
@@ -1499,7 +1499,7 @@ dependencies = [
 
 [[package]]
 name = "rmk"
-version = "0.3.0"
+version = "0.3.2"
 dependencies = [
  "bitfield-struct",
  "byteorder",
diff --git a/examples/use_config/esp32s3_ble/sdkconfig.defaults b/examples/use_config/esp32s3_ble/sdkconfig.defaults
index 0755c529..3aff089f 100644
--- a/examples/use_config/esp32s3_ble/sdkconfig.defaults
+++ b/examples/use_config/esp32s3_ble/sdkconfig.defaults
@@ -1,5 +1,5 @@
 # Rust often needs a bit of an extra main task stack size compared to C (the default is 3K)
-CONFIG_ESP_MAIN_TASK_STACK_SIZE=12000
+CONFIG_ESP_MAIN_TASK_STACK_SIZE=64000
 
 # Use this to set FreeRTOS kernel tick frequency to 1000 Hz (100 Hz by default).
 # This allows to use 1 ms granuality for thread sleeps (10 ms by default).
diff --git a/examples/use_config/nrf52832_ble/Cargo.lock b/examples/use_config/nrf52832_ble/Cargo.lock
index 78e417e7..9e56eb58 100644
--- a/examples/use_config/nrf52832_ble/Cargo.lock
+++ b/examples/use_config/nrf52832_ble/Cargo.lock
@@ -1224,7 +1224,7 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
 
 [[package]]
 name = "rmk"
-version = "0.3.0"
+version = "0.3.2"
 dependencies = [
  "bitfield-struct",
  "byteorder",
diff --git a/examples/use_config/nrf52840_ble/Cargo.lock b/examples/use_config/nrf52840_ble/Cargo.lock
index feaf8159..a905a9bc 100644
--- a/examples/use_config/nrf52840_ble/Cargo.lock
+++ b/examples/use_config/nrf52840_ble/Cargo.lock
@@ -1225,7 +1225,7 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
 
 [[package]]
 name = "rmk"
-version = "0.3.0"
+version = "0.3.2"
 dependencies = [
  "bitfield-struct",
  "byteorder",
diff --git a/examples/use_config/nrf52840_ble_split/Cargo.lock b/examples/use_config/nrf52840_ble_split/Cargo.lock
index 4a939fd9..9f85f9eb 100644
--- a/examples/use_config/nrf52840_ble_split/Cargo.lock
+++ b/examples/use_config/nrf52840_ble_split/Cargo.lock
@@ -1225,7 +1225,7 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
 
 [[package]]
 name = "rmk"
-version = "0.3.0"
+version = "0.3.2"
 dependencies = [
  "bitfield-struct",
  "byteorder",
diff --git a/examples/use_config/nrf52840_usb/Cargo.lock b/examples/use_config/nrf52840_usb/Cargo.lock
index 9a1ebc09..28940e8d 100644
--- a/examples/use_config/nrf52840_usb/Cargo.lock
+++ b/examples/use_config/nrf52840_usb/Cargo.lock
@@ -1079,7 +1079,7 @@ checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
 
 [[package]]
 name = "rmk"
-version = "0.3.0"
+version = "0.3.2"
 dependencies = [
  "bitfield-struct",
  "byteorder",
diff --git a/examples/use_config/rp2040/Cargo.lock b/examples/use_config/rp2040/Cargo.lock
index 64781ccb..e9ea1e22 100644
--- a/examples/use_config/rp2040/Cargo.lock
+++ b/examples/use_config/rp2040/Cargo.lock
@@ -1335,7 +1335,7 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
 
 [[package]]
 name = "rmk"
-version = "0.3.0"
+version = "0.3.2"
 dependencies = [
  "bitfield-struct",
  "byteorder",
diff --git a/examples/use_config/rp2040_split/Cargo.lock b/examples/use_config/rp2040_split/Cargo.lock
index 6d2752af..be8a6102 100644
--- a/examples/use_config/rp2040_split/Cargo.lock
+++ b/examples/use_config/rp2040_split/Cargo.lock
@@ -1318,7 +1318,7 @@ checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
 
 [[package]]
 name = "rmk"
-version = "0.3.0"
+version = "0.3.2"
 dependencies = [
  "bitfield-struct",
  "byteorder",
diff --git a/examples/use_config/stm32f1/Cargo.lock b/examples/use_config/stm32f1/Cargo.lock
index 0ce3c487..17f51f9e 100644
--- a/examples/use_config/stm32f1/Cargo.lock
+++ b/examples/use_config/stm32f1/Cargo.lock
@@ -961,7 +961,7 @@ checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
 
 [[package]]
 name = "rmk"
-version = "0.3.0"
+version = "0.3.2"
 dependencies = [
  "bitfield-struct",
  "byteorder",
diff --git a/examples/use_config/stm32f4/Cargo.lock b/examples/use_config/stm32f4/Cargo.lock
index f20ed726..867a488a 100644
--- a/examples/use_config/stm32f4/Cargo.lock
+++ b/examples/use_config/stm32f4/Cargo.lock
@@ -975,7 +975,7 @@ checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
 
 [[package]]
 name = "rmk"
-version = "0.3.0"
+version = "0.3.2"
 dependencies = [
  "bitfield-struct",
  "byteorder",
diff --git a/examples/use_config/stm32h7/Cargo.lock b/examples/use_config/stm32h7/Cargo.lock
index d99c1ef5..512d36f1 100644
--- a/examples/use_config/stm32h7/Cargo.lock
+++ b/examples/use_config/stm32h7/Cargo.lock
@@ -976,7 +976,7 @@ checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
 
 [[package]]
 name = "rmk"
-version = "0.3.0"
+version = "0.3.2"
 dependencies = [
  "bitfield-struct",
  "byteorder",
diff --git a/examples/use_rust/esp32c3_ble/.cargo/config.toml b/examples/use_rust/esp32c3_ble/.cargo/config.toml
index cd74f9ec..29c169db 100644
--- a/examples/use_rust/esp32c3_ble/.cargo/config.toml
+++ b/examples/use_rust/esp32c3_ble/.cargo/config.toml
@@ -12,7 +12,7 @@ build-std = ["std", "panic_abort"]
 [env]
 MCU="esp32c3"
 # Note: this variable is not used by the pio builder (`cargo build --features pio`)
-ESP_IDF_VERSION = "v5.2.3"
+ESP_IDF_VERSION = "v5.2"
 DEFMT_LOG="debug"
 ESP_IDF_PATH_ISSUES="warn"
 CRATE_CC_NO_DEFAULTS="1"
\ No newline at end of file
diff --git a/examples/use_rust/esp32c3_ble/sdkconfig.defaults b/examples/use_rust/esp32c3_ble/sdkconfig.defaults
index 0755c529..3aff089f 100644
--- a/examples/use_rust/esp32c3_ble/sdkconfig.defaults
+++ b/examples/use_rust/esp32c3_ble/sdkconfig.defaults
@@ -1,5 +1,5 @@
 # Rust often needs a bit of an extra main task stack size compared to C (the default is 3K)
-CONFIG_ESP_MAIN_TASK_STACK_SIZE=12000
+CONFIG_ESP_MAIN_TASK_STACK_SIZE=64000
 
 # Use this to set FreeRTOS kernel tick frequency to 1000 Hz (100 Hz by default).
 # This allows to use 1 ms granuality for thread sleeps (10 ms by default).
diff --git a/examples/use_rust/esp32c6_ble/.cargo/config.toml b/examples/use_rust/esp32c6_ble/.cargo/config.toml
index a04271e9..816670f8 100644
--- a/examples/use_rust/esp32c6_ble/.cargo/config.toml
+++ b/examples/use_rust/esp32c6_ble/.cargo/config.toml
@@ -12,7 +12,7 @@ build-std = ["std", "panic_abort"]
 [env]
 MCU="esp32c6"
 # Note: this variable is not used by the pio builder (`cargo build --features pio`)
-ESP_IDF_VERSION = "v5.2.3"
+ESP_IDF_VERSION = "v5.2"
 DEFMT_LOG="debug"
 ESP_IDF_PATH_ISSUES="warn"
 CRATE_CC_NO_DEFAULTS="1"
\ No newline at end of file
diff --git a/examples/use_rust/esp32c6_ble/Cargo.lock b/examples/use_rust/esp32c6_ble/Cargo.lock
index 46b0a7b7..59c52da6 100644
--- a/examples/use_rust/esp32c6_ble/Cargo.lock
+++ b/examples/use_rust/esp32c6_ble/Cargo.lock
@@ -1498,7 +1498,7 @@ dependencies = [
 
 [[package]]
 name = "rmk"
-version = "0.3.1"
+version = "0.3.2"
 dependencies = [
  "bitfield-struct",
  "byteorder",
diff --git a/examples/use_rust/esp32s3_ble/.cargo/config.toml b/examples/use_rust/esp32s3_ble/.cargo/config.toml
index 10401b46..e5622473 100644
--- a/examples/use_rust/esp32s3_ble/.cargo/config.toml
+++ b/examples/use_rust/esp32s3_ble/.cargo/config.toml
@@ -12,7 +12,7 @@ build-std = ["std", "panic_abort"]
 [env]
 MCU="esp32s3"
 # Note: this variable is not used by the pio builder (`cargo build --features pio`)
-ESP_IDF_VERSION = "v5.2.3"
+ESP_IDF_VERSION = "v5.2"
 DEFMT_LOG="debug"
 ESP_IDF_PATH_ISSUES="warn"
 CRATE_CC_NO_DEFAULTS="1"
\ No newline at end of file
diff --git a/examples/use_rust/esp32s3_ble/Cargo.lock b/examples/use_rust/esp32s3_ble/Cargo.lock
index 0f50fac8..e17b0b51 100644
--- a/examples/use_rust/esp32s3_ble/Cargo.lock
+++ b/examples/use_rust/esp32s3_ble/Cargo.lock
@@ -1499,7 +1499,7 @@ dependencies = [
 
 [[package]]
 name = "rmk"
-version = "0.3.0"
+version = "0.3.2"
 dependencies = [
  "bitfield-struct",
  "byteorder",
diff --git a/examples/use_rust/esp32s3_ble/sdkconfig.defaults b/examples/use_rust/esp32s3_ble/sdkconfig.defaults
index 0755c529..3aff089f 100644
--- a/examples/use_rust/esp32s3_ble/sdkconfig.defaults
+++ b/examples/use_rust/esp32s3_ble/sdkconfig.defaults
@@ -1,5 +1,5 @@
 # Rust often needs a bit of an extra main task stack size compared to C (the default is 3K)
-CONFIG_ESP_MAIN_TASK_STACK_SIZE=12000
+CONFIG_ESP_MAIN_TASK_STACK_SIZE=64000
 
 # Use this to set FreeRTOS kernel tick frequency to 1000 Hz (100 Hz by default).
 # This allows to use 1 ms granuality for thread sleeps (10 ms by default).
diff --git a/examples/use_rust/hpm5300/Cargo.lock b/examples/use_rust/hpm5300/Cargo.lock
index ba6bc998..7a755374 100644
--- a/examples/use_rust/hpm5300/Cargo.lock
+++ b/examples/use_rust/hpm5300/Cargo.lock
@@ -926,7 +926,7 @@ dependencies = [
 
 [[package]]
 name = "rmk"
-version = "0.3.0"
+version = "0.3.2"
 dependencies = [
  "bitfield-struct",
  "byteorder",
diff --git a/examples/use_rust/nrf52832_ble/Cargo.lock b/examples/use_rust/nrf52832_ble/Cargo.lock
index 78e417e7..9e56eb58 100644
--- a/examples/use_rust/nrf52832_ble/Cargo.lock
+++ b/examples/use_rust/nrf52832_ble/Cargo.lock
@@ -1224,7 +1224,7 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
 
 [[package]]
 name = "rmk"
-version = "0.3.0"
+version = "0.3.2"
 dependencies = [
  "bitfield-struct",
  "byteorder",
diff --git a/examples/use_rust/nrf52840/Cargo.lock b/examples/use_rust/nrf52840/Cargo.lock
index 9a1ebc09..28940e8d 100644
--- a/examples/use_rust/nrf52840/Cargo.lock
+++ b/examples/use_rust/nrf52840/Cargo.lock
@@ -1079,7 +1079,7 @@ checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
 
 [[package]]
 name = "rmk"
-version = "0.3.0"
+version = "0.3.2"
 dependencies = [
  "bitfield-struct",
  "byteorder",
diff --git a/examples/use_rust/nrf52840_ble_split/Cargo.lock b/examples/use_rust/nrf52840_ble_split/Cargo.lock
index 4a939fd9..9f85f9eb 100644
--- a/examples/use_rust/nrf52840_ble_split/Cargo.lock
+++ b/examples/use_rust/nrf52840_ble_split/Cargo.lock
@@ -1225,7 +1225,7 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
 
 [[package]]
 name = "rmk"
-version = "0.3.0"
+version = "0.3.2"
 dependencies = [
  "bitfield-struct",
  "byteorder",
diff --git a/examples/use_rust/rp2040/Cargo.lock b/examples/use_rust/rp2040/Cargo.lock
index 64781ccb..e9ea1e22 100644
--- a/examples/use_rust/rp2040/Cargo.lock
+++ b/examples/use_rust/rp2040/Cargo.lock
@@ -1335,7 +1335,7 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
 
 [[package]]
 name = "rmk"
-version = "0.3.0"
+version = "0.3.2"
 dependencies = [
  "bitfield-struct",
  "byteorder",
diff --git a/examples/use_rust/rp2040_split/Cargo.lock b/examples/use_rust/rp2040_split/Cargo.lock
index 6d2752af..be8a6102 100644
--- a/examples/use_rust/rp2040_split/Cargo.lock
+++ b/examples/use_rust/rp2040_split/Cargo.lock
@@ -1318,7 +1318,7 @@ checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
 
 [[package]]
 name = "rmk"
-version = "0.3.0"
+version = "0.3.2"
 dependencies = [
  "bitfield-struct",
  "byteorder",
diff --git a/examples/use_rust/stm32f1/Cargo.lock b/examples/use_rust/stm32f1/Cargo.lock
index 44f54a88..a477e668 100644
--- a/examples/use_rust/stm32f1/Cargo.lock
+++ b/examples/use_rust/stm32f1/Cargo.lock
@@ -958,7 +958,7 @@ checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
 
 [[package]]
 name = "rmk"
-version = "0.3.0"
+version = "0.3.2"
 dependencies = [
  "bitfield-struct",
  "byteorder",
diff --git a/examples/use_rust/stm32f4/Cargo.lock b/examples/use_rust/stm32f4/Cargo.lock
index f20ed726..867a488a 100644
--- a/examples/use_rust/stm32f4/Cargo.lock
+++ b/examples/use_rust/stm32f4/Cargo.lock
@@ -975,7 +975,7 @@ checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
 
 [[package]]
 name = "rmk"
-version = "0.3.0"
+version = "0.3.2"
 dependencies = [
  "bitfield-struct",
  "byteorder",
diff --git a/examples/use_rust/stm32h7/Cargo.lock b/examples/use_rust/stm32h7/Cargo.lock
index 3021aceb..09d54ba0 100644
--- a/examples/use_rust/stm32h7/Cargo.lock
+++ b/examples/use_rust/stm32h7/Cargo.lock
@@ -975,7 +975,7 @@ checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
 
 [[package]]
 name = "rmk"
-version = "0.3.0"
+version = "0.3.2"
 dependencies = [
  "bitfield-struct",
  "byteorder",
diff --git a/examples/use_rust/stm32h7_async/Cargo.lock b/examples/use_rust/stm32h7_async/Cargo.lock
index 1efb9184..273b1cab 100644
--- a/examples/use_rust/stm32h7_async/Cargo.lock
+++ b/examples/use_rust/stm32h7_async/Cargo.lock
@@ -955,7 +955,7 @@ checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
 
 [[package]]
 name = "rmk"
-version = "0.3.0"
+version = "0.3.2"
 dependencies = [
  "bitfield-struct",
  "byteorder",
diff --git a/rmk/src/ble/esp.rs b/rmk/src/ble/esp.rs
index 3f086b2a..acffeb77 100644
--- a/rmk/src/ble/esp.rs
+++ b/rmk/src/ble/esp.rs
@@ -5,7 +5,8 @@ use self::server::{BleServer, VialReaderWriter};
 use crate::debounce::default_bouncer::DefaultDebouncer;
 #[cfg(feature = "rapid_debouncer")]
 use crate::debounce::fast_debouncer::RapidDebouncer;
-use crate::matrix::Matrix;
+use crate::keyboard::keyboard_report_channel;
+use crate::matrix::{Matrix, MatrixTrait};
 use crate::storage::nor_flash::esp_partition::{Partition, PartitionType};
 use crate::storage::Storage;
 use crate::via::process::VialService;
@@ -13,7 +14,7 @@ use crate::via::vial_task;
 use crate::KEYBOARD_STATE;
 use crate::{
     action::KeyAction, ble::ble_communication_task, config::RmkConfig, keyboard::Keyboard,
-    keyboard_task, keymap::KeyMap, KeyboardReportMessage,
+    keymap::KeyMap,
 };
 use core::cell::RefCell;
 use defmt::{debug, info, warn};
@@ -70,22 +71,24 @@ pub(crate) async fn initialize_esp_ble_keyboard_with_config_and_run<
         KeyMap::<ROW, COL, NUM_LAYER>::new_from_storage(default_keymap, Some(&mut storage)).await,
     );
 
-    static keyboard_channel: Channel<CriticalSectionRawMutex, KeyboardReportMessage, 8> =
-        Channel::new();
-    let keyboard_report_sender = keyboard_channel.sender();
-    let keyboard_report_receiver = keyboard_channel.receiver();
+    let keyboard_report_sender = keyboard_report_channel.sender();
+    let keyboard_report_receiver = keyboard_report_channel.receiver();
 
     // Keyboard matrix
     #[cfg(all(feature = "col2row", feature = "rapid_debouncer"))]
-    let matrix = Matrix::<_, _, RapidDebouncer<ROW, COL>, ROW, COL>::new(input_pins, output_pins);
+    let mut matrix =
+        Matrix::<_, _, RapidDebouncer<ROW, COL>, ROW, COL>::new(input_pins, output_pins);
     #[cfg(all(feature = "col2row", not(feature = "rapid_debouncer")))]
-    let matrix = Matrix::<_, _, DefaultDebouncer<ROW, COL>, ROW, COL>::new(input_pins, output_pins);
+    let mut matrix =
+        Matrix::<_, _, DefaultDebouncer<ROW, COL>, ROW, COL>::new(input_pins, output_pins);
     #[cfg(all(not(feature = "col2row"), feature = "rapid_debouncer"))]
-    let matrix = Matrix::<_, _, RapidDebouncer<COL, ROW>, COL, ROW>::new(input_pins, output_pins);
+    let mut matrix =
+        Matrix::<_, _, RapidDebouncer<COL, ROW>, COL, ROW>::new(input_pins, output_pins);
     #[cfg(all(not(feature = "col2row"), not(feature = "rapid_debouncer")))]
-    let matrix = Matrix::<_, _, DefaultDebouncer<COL, ROW>, COL, ROW>::new(input_pins, output_pins);
+    let mut matrix =
+        Matrix::<_, _, DefaultDebouncer<COL, ROW>, COL, ROW>::new(input_pins, output_pins);
 
-    let mut keyboard = Keyboard::new(matrix, &keymap);
+    let mut keyboard = Keyboard::new(&keymap, &keyboard_report_sender);
     // esp32c3 doesn't have USB device, so there is no usb here
     // TODO: add usb service for other chips of esp32 which have USB device
 
@@ -113,7 +116,7 @@ pub(crate) async fn initialize_esp_ble_keyboard_with_config_and_run<
 
         let disconnect = BleServer::wait_for_disconnection(ble_server.server);
 
-        let keyboard_fut = keyboard_task(&mut keyboard, &keyboard_report_sender);
+        let keyboard_fut = keyboard.run();
         let ble_fut = ble_communication_task(
             &keyboard_report_receiver,
             &mut keyboard_writer,
@@ -132,17 +135,18 @@ pub(crate) async fn initialize_esp_ble_keyboard_with_config_and_run<
             hid_writer: ble_server.input_vial,
         };
         let via_fut = vial_task(&mut via_rw, &mut vial_service);
-
+        let matrix_fut = matrix.scan();
         let storage_fut = storage.run::<ROW, COL, NUM_LAYER>();
         pin_mut!(storage_fut);
         pin_mut!(via_fut);
         pin_mut!(keyboard_fut);
         pin_mut!(disconnect);
         pin_mut!(ble_fut);
+        pin_mut!(matrix_fut);
 
         select4(
             select(storage_fut, keyboard_fut),
-            disconnect,
+            select(disconnect, matrix_fut),
             ble_fut,
             via_fut,
         )
diff --git a/rmk/src/ble/mod.rs b/rmk/src/ble/mod.rs
index f2e0c422..bf4f9ebd 100644
--- a/rmk/src/ble/mod.rs
+++ b/rmk/src/ble/mod.rs
@@ -38,7 +38,10 @@ pub(crate) async fn ble_communication_task<
     loop {
         match keyboard_report_receiver.receive().await {
             KeyboardReportMessage::KeyboardReport(report) => {
-                debug!("Send keyboard report: {}", report.keycodes);
+                debug!(
+                    "Send keyboard report via BLE: {=[u8]:#X}, modifier: {:b}",
+                    report.keycodes, report.modifier
+                );
                 match ble_keyboard_writer.write_serialize(&report).await {
                     Ok(()) => {}
                     Err(e) => error!("Send keyboard report error: {}", e),
diff --git a/rmk/src/ble/nrf.rs b/rmk/src/ble/nrf.rs
index 569505be..f323e6b1 100644
--- a/rmk/src/ble/nrf.rs
+++ b/rmk/src/ble/nrf.rs
@@ -12,6 +12,7 @@ use self::server::BleServer;
 use crate::debounce::default_bouncer::DefaultDebouncer;
 #[cfg(feature = "rapid_debouncer")]
 use crate::debounce::fast_debouncer::RapidDebouncer;
+use crate::keyboard::keyboard_report_channel;
 use crate::matrix::{Matrix, MatrixTrait};
 use crate::KEYBOARD_STATE;
 use crate::{
@@ -23,7 +24,7 @@ use crate::{
             server::BleHidWriter,
         },
     },
-    keyboard::{keyboard_task, Keyboard, KeyboardReportMessage},
+    keyboard::{Keyboard, KeyboardReportMessage},
     light::led_service_task,
     storage::{get_bond_info_key, Storage, StorageData},
     vial_task, KeyAction, KeyMap, LightService, RmkConfig, VialService,
@@ -32,10 +33,7 @@ use core::{cell::RefCell, mem};
 use defmt::*;
 use embassy_executor::Spawner;
 use embassy_futures::select::{select, select4, Either4};
-use embassy_sync::{
-    blocking_mutex::raw::CriticalSectionRawMutex,
-    channel::{Channel, Receiver, Sender},
-};
+use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, channel::Receiver};
 use embassy_time::Timer;
 use embedded_hal::digital::{InputPin, OutputPin};
 #[cfg(feature = "async_matrix")]
@@ -53,16 +51,14 @@ use vial_service::VialReaderWriter;
 #[cfg(not(feature = "_no_usb"))]
 use {
     crate::{
-        keyboard::communication_task,
-        light::led_hid_task,
-        usb::{wait_for_usb_configured, wait_for_usb_suspend, USB_DEVICE_ENABLED},
+        run_usb_keyboard,
+        usb::{wait_for_usb_suspend, USB_DEVICE_ENABLED},
         KeyboardUsbDevice,
     },
     core::sync::atomic::Ordering,
     embassy_futures::select::Either,
     embassy_nrf::usb::vbus_detect::SoftwareVbusDetect,
     embassy_usb::driver::Driver,
-    futures::pin_mut,
     once_cell::sync::OnceCell,
 };
 
@@ -244,26 +240,28 @@ pub(crate) async fn initialize_nrf_ble_keyboard_with_config_and_run<
 
     // Keyboard matrix
     #[cfg(all(feature = "col2row", feature = "rapid_debouncer"))]
-    let matrix = Matrix::<_, _, RapidDebouncer<ROW, COL>, ROW, COL>::new(input_pins, output_pins);
+    let mut matrix =
+        Matrix::<_, _, RapidDebouncer<ROW, COL>, ROW, COL>::new(input_pins, output_pins);
     #[cfg(all(feature = "col2row", not(feature = "rapid_debouncer")))]
-    let matrix = Matrix::<_, _, DefaultDebouncer<ROW, COL>, ROW, COL>::new(input_pins, output_pins);
+    let mut matrix =
+        Matrix::<_, _, DefaultDebouncer<ROW, COL>, ROW, COL>::new(input_pins, output_pins);
     #[cfg(all(not(feature = "col2row"), feature = "rapid_debouncer"))]
-    let matrix = Matrix::<_, _, RapidDebouncer<COL, ROW>, COL, ROW>::new(input_pins, output_pins);
+    let mut matrix =
+        Matrix::<_, _, RapidDebouncer<COL, ROW>, COL, ROW>::new(input_pins, output_pins);
     #[cfg(all(not(feature = "col2row"), not(feature = "rapid_debouncer")))]
-    let matrix = Matrix::<_, _, DefaultDebouncer<COL, ROW>, COL, ROW>::new(input_pins, output_pins);
+    let mut matrix =
+        Matrix::<_, _, DefaultDebouncer<COL, ROW>, COL, ROW>::new(input_pins, output_pins);
+
+    let keyboard_report_sender = keyboard_report_channel.sender();
+    let keyboard_report_receiver = keyboard_report_channel.receiver();
 
     // Keyboard services
-    let mut keyboard = Keyboard::new(matrix, &keymap);
+    let mut keyboard = Keyboard::new(&keymap, &keyboard_report_sender);
     #[cfg(not(feature = "_no_usb"))]
     let mut usb_device = KeyboardUsbDevice::new(usb_driver, keyboard_config.usb_config);
     let mut vial_service = VialService::new(&keymap, keyboard_config.vial_config);
     let mut light_service = LightService::from_config(keyboard_config.light_config);
 
-    static keyboard_channel: Channel<CriticalSectionRawMutex, KeyboardReportMessage, 8> =
-        Channel::new();
-    let keyboard_report_sender = keyboard_channel.sender();
-    let keyboard_report_receiver = keyboard_channel.receiver();
-
     // Main loop
     loop {
         KEYBOARD_STATE.store(false, core::sync::atomic::Ordering::Release);
@@ -285,69 +283,41 @@ pub(crate) async fn initialize_nrf_ble_keyboard_with_config_and_run<
             // Check and run via USB first
             if USB_DEVICE_ENABLED.load(Ordering::SeqCst) {
                 // Run usb keyboard
-                let usb_fut = async {
-                    let usb_fut = usb_device.device.run();
-                    let keyboard_fut = keyboard_task(&mut keyboard, &keyboard_report_sender);
-                    let communication_fut = communication_task(
-                        &keyboard_report_receiver,
-                        &mut usb_device.keyboard_hid_writer,
-                        &mut usb_device.other_hid_writer,
-                    );
-                    let led_fut =
-                        led_hid_task(&mut usb_device.keyboard_hid_reader, &mut light_service);
-                    let via_fut = vial_task(&mut usb_device.via_hid, &mut vial_service);
-                    let storage_fut = storage.run::<ROW, COL, NUM_LAYER>();
-                    pin_mut!(usb_fut);
-                    pin_mut!(storage_fut);
-                    pin_mut!(keyboard_fut);
-                    pin_mut!(led_fut);
-                    pin_mut!(via_fut);
-                    pin_mut!(communication_fut);
-
-                    match select4(
-                        select(usb_fut, keyboard_fut),
-                        select(storage_fut, via_fut),
-                        led_fut,
-                        communication_fut,
-                    )
-                    .await
-                    {
-                        Either4::First(_) => error!("Usb task or keyboard task exited"),
-                        Either4::Second(_) => error!("Storage or vial task has died"),
-                        Either4::Third(_) => error!("Led task has died"),
-                        Either4::Fourth(_) => error!("Communication task has died"),
-                    }
-                };
+                let usb_fut = run_usb_keyboard(
+                    &mut usb_device,
+                    &mut keyboard,
+                    &mut matrix,
+                    &mut storage,
+                    &mut light_service,
+                    &mut vial_service,
+                    &keyboard_report_receiver,
+                );
                 info!("Running USB keyboard!");
                 select(usb_fut, wait_for_usb_suspend()).await;
             }
 
             // Usb device have to be started to check if usb is configured
-            let usb_fut = usb_device.device.run();
-            let usb_configured = wait_for_usb_configured();
             info!("USB suspended, BLE Advertising");
 
             // Wait for BLE or USB connection
-            match select(adv_fut, select(usb_fut, usb_configured)).await {
+            match select(adv_fut, usb_device.wait_for_usb_configured()).await {
                 Either::First(re) => match re {
                     Ok(conn) => {
                         info!("Connected to BLE");
                         bonder.load_sys_attrs(&conn);
-                        let usb_configured = wait_for_usb_configured();
-                        let usb_fut = usb_device.device.run();
                         match select(
                             run_ble_keyboard(
                                 &conn,
                                 &ble_server,
                                 &mut keyboard,
+                                &mut matrix,
                                 &mut storage,
                                 &mut light_service,
                                 &mut vial_service,
                                 &mut keyboard_config.ble_battery_config,
                                 &keyboard_report_receiver,
-                                &keyboard_report_sender,
                             ),
-                            select(usb_fut, usb_configured),
+                            usb_device.wait_for_usb_configured(),
                         )
                         .await
                         {
@@ -376,12 +346,12 @@ pub(crate) async fn initialize_nrf_ble_keyboard_with_config_and_run<
                     &conn,
                     &ble_server,
                     &mut keyboard,
+                    &mut matrix,
                     &mut storage,
                     &mut light_service,
                     &mut vial_service,
                     &mut keyboard_config.ble_battery_config,
                     &keyboard_report_receiver,
-                    &keyboard_report_sender,
                 )
                 .await
             }
@@ -397,22 +367,22 @@ pub(crate) async fn initialize_nrf_ble_keyboard_with_config_and_run<
 pub(crate) async fn run_ble_keyboard<
     'a,
     'b,
+    M: MatrixTrait,
     F: AsyncNorFlash,
     Out: OutputPin,
-    M: MatrixTrait,
     const ROW: usize,
     const COL: usize,
     const NUM_LAYER: usize,
 >(
     conn: &Connection,
     ble_server: &BleServer,
-    keyboard: &mut Keyboard<'a, M, ROW, COL, NUM_LAYER>,
+    keyboard: &mut Keyboard<'a, ROW, COL, NUM_LAYER>,
+    matrix: &mut M,
     storage: &mut Storage<F>,
     light_service: &mut LightService<Out>,
     vial_service: &mut VialService<'a, ROW, COL, NUM_LAYER>,
     battery_config: &mut BleBatteryConfig<'b>,
     keyboard_report_receiver: &Receiver<'a, CriticalSectionRawMutex, KeyboardReportMessage, 8>,
-    keyboard_report_sender: &Sender<'a, CriticalSectionRawMutex, KeyboardReportMessage, 8>,
 ) {
     info!("Starting GATT server 20 ms later");
     Timer::after_millis(20).await;
@@ -428,9 +398,10 @@ pub(crate) async fn run_ble_keyboard<
     // Tasks
     let battery_fut = bas.run(battery_config, &conn);
     let led_fut = led_service_task(light_service);
+    let matrix_fut = matrix.scan();
     // Run the GATT server on the connection. This returns when the connection gets disconnected.
     let ble_fut = gatt_server::run(&conn, ble_server, |_| {});
-    let keyboard_fut = keyboard_task(keyboard, keyboard_report_sender);
+    let keyboard_fut = keyboard.run();
     let ble_communication_task = ble_communication_task(
         keyboard_report_receiver,
         &mut ble_keyboard_writer,
@@ -442,7 +413,7 @@ pub(crate) async fn run_ble_keyboard<
 
     // Exit if anyone of three futures exits
     match select4(
-        ble_fut,
+        select(matrix_fut, ble_fut),
         select(ble_communication_task, keyboard_fut),
         select(battery_fut, led_fut),
         select(vial_task, storage_fut),
diff --git a/rmk/src/keyboard.rs b/rmk/src/keyboard.rs
index 2d3a5f82..338c2cd5 100644
--- a/rmk/src/keyboard.rs
+++ b/rmk/src/keyboard.rs
@@ -4,20 +4,37 @@ use crate::{
     keyboard_macro::{MacroOperation, NUM_MACRO},
     keycode::{KeyCode, ModifierCombination},
     keymap::KeyMap,
-    matrix::{KeyState, MatrixTrait},
     usb::descriptor::{CompositeReport, CompositeReportType, ViaReport},
     KEYBOARD_STATE,
 };
 use core::cell::RefCell;
-use defmt::{debug, error, warn};
-use embassy_futures::yield_now;
+use defmt::{debug, error, warn, Format};
+use embassy_futures::{select::select, yield_now};
 use embassy_sync::{
     blocking_mutex::raw::CriticalSectionRawMutex,
-    channel::{Receiver, Sender},
+    channel::{Channel, Receiver, Sender},
 };
 use embassy_time::{Instant, Timer};
+use heapless::{FnvIndexMap, Vec};
+use postcard::experimental::max_size::MaxSize;
+use serde::{Deserialize, Serialize};
 use usbd_hid::descriptor::KeyboardReport;
 
+#[derive(Serialize, Deserialize, Clone, Copy, Debug, Format, MaxSize)]
+pub(crate) struct KeyEvent {
+    pub(crate) row: u8,
+    pub(crate) col: u8,
+    pub(crate) pressed: bool,
+}
+
+pub(crate) static key_event_channel: Channel<CriticalSectionRawMutex, KeyEvent, 16> =
+    Channel::new();
+pub(crate) static keyboard_report_channel: Channel<
+    CriticalSectionRawMutex,
+    KeyboardReportMessage,
+    8,
+> = Channel::new();
+
 /// Matrix scanning task sends this [KeyboardReportMessage] to communication task.
 pub(crate) enum KeyboardReportMessage {
     /// Normal keyboard hid report
@@ -26,29 +43,6 @@ pub(crate) enum KeyboardReportMessage {
     CompositeReport(CompositeReport, CompositeReportType),
 }
 
-/// This is the main keyboard task, this task do the matrix scanning and key processing
-/// The report is sent to communication task, and finally sent to the host
-pub(crate) async fn keyboard_task<
-    'a,
-    M: MatrixTrait,
-    const ROW: usize,
-    const COL: usize,
-    const NUM_LAYER: usize,
->(
-    keyboard: &mut Keyboard<'a, M, ROW, COL, NUM_LAYER>,
-    sender: &Sender<'a, CriticalSectionRawMutex, KeyboardReportMessage, 8>,
-) {
-    KEYBOARD_STATE.store(true, core::sync::atomic::Ordering::Release);
-    loop {
-        keyboard.scan_matrix(sender).await;
-        keyboard.send_keyboard_report(sender).await;
-        keyboard.send_media_report(sender).await;
-        keyboard.send_mouse_report(sender).await;
-        keyboard.send_system_control_report(sender).await;
-        Timer::after_micros(100).await;
-    }
-}
-
 /// This task processes all keyboard reports and send them to the host
 pub(crate) async fn communication_task<'a, W: HidWriterWrapper, W2: HidWriterWrapper>(
     receiver: &Receiver<'a, CriticalSectionRawMutex, KeyboardReportMessage, 8>,
@@ -91,19 +85,19 @@ pub(crate) async fn write_other_report_to_host<W: HidWriterWrapper>(
         Err(_) => error!("Serialize other report error"),
     }
 }
-pub(crate) struct Keyboard<
-    'a,
-    M: MatrixTrait,
-    const ROW: usize,
-    const COL: usize,
-    const NUM_LAYER: usize,
-> {
-    /// Keyboard matrix, use COL2ROW by default
-    pub(crate) matrix: M,
-
+pub(crate) struct Keyboard<'a, const ROW: usize, const COL: usize, const NUM_LAYER: usize> {
     /// Keymap
     pub(crate) keymap: &'a RefCell<KeyMap<ROW, COL, NUM_LAYER>>,
 
+    /// Report Sender
+    pub(crate) sender: &'a Sender<'a, CriticalSectionRawMutex, KeyboardReportMessage, 8>,
+
+    /// Unprocessed events
+    unprocessed_events: Vec<KeyEvent, 16>,
+
+    /// Timer which records the timestamp of key changes
+    pub(crate) timer: [[Option<Instant>; ROW]; COL],
+
     /// Keyboard internal hid report buf
     report: KeyboardReport,
 
@@ -113,34 +107,29 @@ pub(crate) struct Keyboard<
     /// Via report
     via_report: ViaReport,
 
-    /// Should send a new keyboard report?
-    need_send_key_report: bool,
-
-    /// Should send a consumer control report?
-    need_send_consumer_control_report: bool,
-
-    /// Should send a system control report?
-    need_send_system_control_report: bool,
-
-    /// Should send a mouse report?
-    need_send_mouse_report: bool,
-
     /// Mouse key is different from other keyboard keys, it should be sent continuously while the key is pressed.
-    /// The last tick of mouse is recorded to control the reporting rate.
-    last_mouse_tick: u64,
+    /// `last_mouse_tick` tracks at most 8 mouse keys, with its recent state.
+    /// It can be used to control the mouse report rate and release mouse key properly.
+    /// The key is mouse keycode, the value is the last action and its timestamp.
+    last_mouse_tick: FnvIndexMap<KeyCode, (bool, Instant), 4>,
 
     /// The current distance of mouse key moving
     mouse_key_move_delta: i8,
     mouse_wheel_move_delta: i8,
 }
 
-impl<'a, M: MatrixTrait, const ROW: usize, const COL: usize, const NUM_LAYER: usize>
-    Keyboard<'a, M, ROW, COL, NUM_LAYER>
+impl<'a, const ROW: usize, const COL: usize, const NUM_LAYER: usize>
+    Keyboard<'a, ROW, COL, NUM_LAYER>
 {
-    pub(crate) fn new(matrix: M, keymap: &'a RefCell<KeyMap<ROW, COL, NUM_LAYER>>) -> Self {
+    pub(crate) fn new(
+        keymap: &'a RefCell<KeyMap<ROW, COL, NUM_LAYER>>,
+        sender: &'a Sender<'a, CriticalSectionRawMutex, KeyboardReportMessage, 8>,
+    ) -> Self {
         Keyboard {
-            matrix,
             keymap,
+            sender,
+            timer: [[None; ROW]; COL],
+            unprocessed_events: Vec::new(),
             report: KeyboardReport {
                 modifier: 0,
                 reserved: 0,
@@ -152,254 +141,139 @@ impl<'a, M: MatrixTrait, const ROW: usize, const COL: usize, const NUM_LAYER: us
                 input_data: [0; 32],
                 output_data: [0; 32],
             },
-            need_send_key_report: false,
-            need_send_consumer_control_report: false,
-            need_send_system_control_report: false,
-            need_send_mouse_report: false,
-            last_mouse_tick: 0,
+            last_mouse_tick: FnvIndexMap::new(),
             mouse_key_move_delta: 8,
             mouse_wheel_move_delta: 1,
         }
     }
 
-    pub(crate) async fn send_keyboard_report(
-        &mut self,
-        sender: &Sender<'a, CriticalSectionRawMutex, KeyboardReportMessage, 8>,
-    ) {
-        if self.need_send_key_report {
-            debug!(
-                "Sending keyboard report: {=[u8]:#X}, modifier: {:b}",
-                self.report.keycodes, self.report.modifier
-            );
-            sender
-                .send(KeyboardReportMessage::KeyboardReport(self.report))
-                .await;
-            self.need_send_key_report = false;
-            // Yield once after sending the report to channel
-            yield_now().await;
-        }
+    pub(crate) async fn send_keyboard_report(&mut self) {
+        self.sender
+            .send(KeyboardReportMessage::KeyboardReport(self.report))
+            .await;
+        // Yield once after sending the report to channel
+        yield_now().await;
     }
 
     /// Send system control report if needed
-    pub(crate) async fn send_system_control_report(
-        &mut self,
-        sender: &Sender<'a, CriticalSectionRawMutex, KeyboardReportMessage, 8>,
-    ) {
-        if self.need_send_system_control_report {
-            sender
-                .send(KeyboardReportMessage::CompositeReport(
-                    self.other_report,
-                    CompositeReportType::System,
-                ))
-                .await;
-            self.other_report.system_usage_id = 0;
-            self.need_send_system_control_report = false;
-            yield_now().await;
-        }
+    pub(crate) async fn send_system_control_report(&mut self) {
+        self.sender
+            .send(KeyboardReportMessage::CompositeReport(
+                self.other_report,
+                CompositeReportType::System,
+            ))
+            .await;
+        self.other_report.system_usage_id = 0;
+        yield_now().await;
     }
 
     /// Send media report if needed
-    pub(crate) async fn send_media_report(
-        &mut self,
-        sender: &Sender<'a, CriticalSectionRawMutex, KeyboardReportMessage, 8>,
-    ) {
-        if self.need_send_consumer_control_report {
-            sender
-                .send(KeyboardReportMessage::CompositeReport(
-                    self.other_report,
-                    CompositeReportType::Media,
-                ))
-                .await;
-            self.other_report.media_usage_id = 0;
-            self.need_send_consumer_control_report = false;
-            yield_now().await;
-        }
+    pub(crate) async fn send_media_report(&mut self) {
+        self.sender
+            .send(KeyboardReportMessage::CompositeReport(
+                self.other_report,
+                CompositeReportType::Media,
+            ))
+            .await;
+        self.other_report.media_usage_id = 0;
+        yield_now().await;
     }
 
     /// Send mouse report if needed
-    pub(crate) async fn send_mouse_report(
-        &mut self,
-        sender: &Sender<'a, CriticalSectionRawMutex, KeyboardReportMessage, 8>,
-    ) {
-        if self.need_send_mouse_report {
-            // Prevent mouse report flooding, set maximum mouse report rate to 100 HZ
-            let cur_tick = Instant::now().as_millis();
-            // The default internal of sending mouse report is 20 ms, same as qmk accelerated mode
-            // TODO: make it configurable
-            if cur_tick - self.last_mouse_tick > 20 {
-                sender
-                    .send(KeyboardReportMessage::CompositeReport(
-                        self.other_report,
-                        CompositeReportType::Mouse,
-                    ))
-                    .await;
-                self.last_mouse_tick = cur_tick;
-            }
-            // Do nothing
-            if self.other_report.x == 0
-                && self.other_report.y == 0
-                && self.other_report.wheel == 0
-                && self.other_report.pan == 0
-            {
-                // Release, stop report mouse report
-                self.need_send_mouse_report = false;
-            }
-            yield_now().await;
-        }
+    pub(crate) async fn send_mouse_report(&mut self) {
+        // Prevent mouse report flooding, set maximum mouse report rate to 50 HZ
+        self.sender
+            .send(KeyboardReportMessage::CompositeReport(
+                self.other_report,
+                CompositeReportType::Mouse,
+            ))
+            .await;
+        yield_now().await;
     }
 
-    /// Main keyboard task, it scans matrix, processes active keys.
-    /// If there is any change of key states, set self.changed=true.
-    ///
-    /// `sender` is required because when there's tap action, the report should be sent immediately and then continue scanning
-    pub(crate) async fn scan_matrix(
-        &mut self,
-        sender: &Sender<'a, CriticalSectionRawMutex, KeyboardReportMessage, 8>,
-    ) {
-        #[cfg(feature = "async_matrix")]
-        self.matrix.wait_for_key().await;
-
-        // Matrix scan
-        self.matrix.scan().await;
-
-        // Check matrix states, process key if there is a key state change
-        // Keys are processed in the following order:
-        // process_key_change -> process_key_action_* -> process_action_*
-        for row_idx in 0..self.matrix.get_row_num() {
-            for col_idx in 0..self.matrix.get_col_num() {
-                let ks = self.matrix.get_key_state(row_idx, col_idx);
-                if ks.changed {
-                    self.process_key_change(row_idx, col_idx, sender).await;
-                } else if ks.pressed {
-                    // When there's no key change, only tap/hold action needs to be processed
-                    // Continuously check the hold state
-                    let action = self
-                        .keymap
-                        .borrow_mut()
-                        .get_action_with_layer_cache(row_idx, col_idx, ks);
-                    let tap_hold = match action {
-                        KeyAction::TapHold(tap_action, hold_action) => {
-                            Some((tap_action, hold_action))
-                        }
-                        KeyAction::LayerTapHold(tap_action, layer_num) => {
-                            Some((tap_action, Action::LayerOn(layer_num)))
-                        }
-                        KeyAction::ModifierTapHold(tap_action, modifier) => {
-                            Some((tap_action, Action::Modifier(modifier)))
-                        }
-                        _ => None,
-                    };
-                    if let Some((tap_action, hold_action)) = tap_hold {
-                        self.process_key_action_tap_hold(
-                            tap_action,
-                            hold_action,
-                            row_idx,
-                            col_idx,
-                            ks,
-                            sender,
-                        )
-                        .await;
-                    };
+    /// Main keyboard task, it receives input devices result, processes keys.
+    /// The report is sent to communication task via keyboard_report_channel, and finally sent to the host
+    pub(crate) async fn run(&mut self) {
+        KEYBOARD_STATE.store(true, core::sync::atomic::Ordering::Release);
+        loop {
+            let key_event = key_event_channel.receive().await;
+
+            // Process the key change
+            self.process_key_change(key_event).await;
+
+            // After processing the key change, check if there are unprocessed events
+            // This will happen if there's recursion in key processing
+            loop {
+                if self.unprocessed_events.is_empty() {
+                    break;
+                }
+                // Process unprocessed events
+                if let Some(e) = self.unprocessed_events.pop() {
+                    self.process_key_change(e).await;
                 }
             }
         }
     }
 
     /// Process key changes at (row, col)
-    async fn process_key_change(
-        &mut self,
-        row: usize,
-        col: usize,
-        sender: &Sender<'a, CriticalSectionRawMutex, KeyboardReportMessage, 8>,
-    ) {
-        let key_state = self.matrix.get_key_state(row, col);
-
+    async fn process_key_change(&mut self, key_event: KeyEvent) {
         // Matrix should process key pressed event first, record the timestamp of key changes
-        if key_state.pressed {
-            self.matrix.update_key_state(row, col, |ks| {
-                ks.start_timer();
-            });
+        if key_event.pressed {
+            self.timer[key_event.col as usize][key_event.row as usize] = Some(Instant::now());
         }
 
         // Process key
         let action = self
             .keymap
             .borrow_mut()
-            .get_action_with_layer_cache(row, col, key_state);
+            .get_action_with_layer_cache(key_event);
         match action {
             KeyAction::No | KeyAction::Transparent => (),
-            KeyAction::Single(a) => self.process_key_action_normal(a, key_state, sender).await,
+            KeyAction::Single(a) => self.process_key_action_normal(a, key_event).await,
             KeyAction::WithModifier(a, m) => {
-                self.process_key_action_with_modifier(a, m, key_state, sender)
-                    .await
+                self.process_key_action_with_modifier(a, m, key_event).await
             }
-            KeyAction::Tap(a) => self.process_key_action_tap(a, key_state, sender).await,
+            KeyAction::Tap(a) => self.process_key_action_tap(a, key_event).await,
             KeyAction::TapHold(tap_action, hold_action) => {
-                self.process_key_action_tap_hold(
-                    tap_action,
-                    hold_action,
-                    row,
-                    col,
-                    key_state,
-                    sender,
-                )
-                .await;
+                self.process_key_action_tap_hold(tap_action, hold_action, key_event)
+                    .await;
             }
             KeyAction::OneShot(oneshot_action) => {
                 self.process_key_action_oneshot(oneshot_action).await
             }
             KeyAction::LayerTapHold(tap_action, layer_num) => {
                 let layer_action = Action::LayerOn(layer_num);
-                self.process_key_action_tap_hold(
-                    tap_action,
-                    layer_action,
-                    row,
-                    col,
-                    key_state,
-                    sender,
-                )
-                .await;
+                self.process_key_action_tap_hold(tap_action, layer_action, key_event)
+                    .await;
             }
             KeyAction::ModifierTapHold(tap_action, modifier) => {
                 let modifier_action = Action::Modifier(modifier);
-                self.process_key_action_tap_hold(
-                    tap_action,
-                    modifier_action,
-                    row,
-                    col,
-                    key_state,
-                    sender,
-                )
-                .await;
+                self.process_key_action_tap_hold(tap_action, modifier_action, key_event)
+                    .await;
             }
         }
     }
 
-    async fn process_key_action_normal(
-        &mut self,
-        action: Action,
-        key_state: KeyState,
-        sender: &Sender<'a, CriticalSectionRawMutex, KeyboardReportMessage, 8>,
-    ) {
+    async fn process_key_action_normal(&mut self, action: Action, key_event: KeyEvent) {
         match action {
-            Action::Key(key) => self.process_action_keycode(key, key_state, sender).await,
-            Action::LayerOn(layer_num) => self.process_action_layer_switch(layer_num, key_state),
+            Action::Key(key) => self.process_action_keycode(key, key_event).await,
+            Action::LayerOn(layer_num) => self.process_action_layer_switch(layer_num, key_event),
             Action::LayerOff(layer_num) => {
                 // Turn off a layer temporarily when the key is pressed
                 // Reactivate the layer after the key is released
-                if key_state.is_pressing() {
+                if key_event.pressed {
                     self.keymap.borrow_mut().deactivate_layer(layer_num);
                 }
             }
             Action::LayerToggle(layer_num) => {
                 // Toggle a layer when the key is release
-                if key_state.is_releasing() {
+                if !key_event.pressed {
                     self.keymap.borrow_mut().toggle_layer(layer_num);
                 }
             }
             Action::LayerToggleOnly(layer_num) => {
                 // Activate a layer and deactivate all other layers(except default layer)
-                if key_state.is_pressing() {
+                if key_event.pressed {
                     // Disable all layers except the default layer
                     let default_layer = self.keymap.borrow().get_default_layer();
                     for i in 0..NUM_LAYER as u8 {
@@ -418,7 +292,7 @@ impl<'a, M: MatrixTrait, const ROW: usize, const COL: usize, const NUM_LAYER: us
             Action::Modifier(modifier) => {
                 let (keycodes, n) = modifier.to_modifier_keycodes();
                 for kc in keycodes.iter().take(n) {
-                    self.process_action_keycode(*kc, key_state, sender).await;
+                    self.process_action_keycode(*kc, key_event).await;
                 }
             }
         }
@@ -428,117 +302,105 @@ impl<'a, M: MatrixTrait, const ROW: usize, const COL: usize, const NUM_LAYER: us
         &mut self,
         action: Action,
         modifier: ModifierCombination,
-        key_state: KeyState,
-        sender: &Sender<'a, CriticalSectionRawMutex, KeyboardReportMessage, 8>,
+        key_event: KeyEvent,
     ) {
-        if key_state.is_pressing() {
+        if key_event.pressed {
             // Process modifier
             let (keycodes, n) = modifier.to_modifier_keycodes();
             for kc in keycodes.iter().take(n) {
-                self.process_action_keycode(*kc, key_state, sender).await;
+                self.process_action_keycode(*kc, key_event).await;
             }
             // Send the modifier first, then send the key
-            self.send_keyboard_report(sender).await;
-            self.process_key_action_normal(action, key_state, sender)
-                .await;
+            self.send_keyboard_report().await;
+            self.process_key_action_normal(action, key_event).await;
         } else {
             // Releasing, release the key first, then release the modifier
-            self.process_key_action_normal(action, key_state, sender)
-                .await;
-            self.send_keyboard_report(sender).await;
+            self.process_key_action_normal(action, key_event).await;
+            self.send_keyboard_report().await;
             let (keycodes, n) = modifier.to_modifier_keycodes();
             for kc in keycodes.iter().take(n) {
-                self.process_action_keycode(*kc, key_state, sender).await;
+                self.process_action_keycode(*kc, key_event).await;
             }
         }
     }
 
     /// Tap action, send a key when the key is pressed, then release the key.
-    async fn process_key_action_tap(
-        &mut self,
-        action: Action,
-        mut key_state: KeyState,
-        sender: &Sender<'a, CriticalSectionRawMutex, KeyboardReportMessage, 8>,
-    ) {
-        if key_state.is_pressing() {
-            self.process_key_action_normal(action, key_state, sender)
-                .await;
+    async fn process_key_action_tap(&mut self, action: Action, mut key_event: KeyEvent) {
+        if key_event.pressed {
+            self.process_key_action_normal(action, key_event).await;
 
             // Wait 10ms, then send release
             Timer::after_millis(10).await;
-            // Manually trigger send report
-            self.send_keyboard_report(sender).await;
-            self.send_media_report(sender).await;
-            self.send_system_control_report(sender).await;
-            self.send_mouse_report(sender).await;
-
-            key_state.pressed = false;
-            self.process_key_action_normal(action, key_state, sender)
-                .await;
+
+            key_event.pressed = false;
+            self.process_key_action_normal(action, key_event).await;
         }
     }
 
     /// Process tap/hold action.
-    /// There are several cases:
     ///
-    /// 1. `key_state.changed` is true, and `key_state.pressed` is false,
-    ///     which means the key is released. Then the duration time should be checked
+    /// This function will wait until timeout or a new key event comes:
+    /// - timeout: trigger hold action
+    /// - new key event: means there's another key within the threshold
+    ///     - if it's the same key, trigger tap action
+    ///     - if it's another key, trigger hold action + that key
     ///
-    /// 2. `key_state.changed` is false, and `key_state.pressed` is true,
-    ///     which means that the key is held. The duration time should to be checked.
+    /// This behavior is same as "Hold On Other Key Press" in qmk or "hold-preferred" in zmk
     ///     
     /// TODO: make tap/hold threshold customizable
     async fn process_key_action_tap_hold(
         &mut self,
         tap_action: Action,
         hold_action: Action,
-        row: usize,
-        col: usize,
-        mut key_state: KeyState,
-        sender: &Sender<'a, CriticalSectionRawMutex, KeyboardReportMessage, 8>,
+        key_event: KeyEvent,
     ) {
-        if key_state.is_releasing() {
-            // Case 1, the key is released
-            match key_state.hold_start {
-                Some(s) => {
-                    // Released: tap
-                    // The hold_start isn't cleared, means that the key is released within the tap/hold threshold
-                    key_state.pressed = true;
-                    debug!(
-                        "Release tap hold, got TAP: {}, {}, time elapsed: {}ms",
-                        tap_action,
-                        key_state,
-                        s.elapsed().as_millis()
-                    );
-                    self.process_key_action_tap(tap_action, key_state, sender)
-                        .await;
-
-                    // Reset timer after release
-                    self.matrix.update_key_state(row, col, |ks| {
-                        ks.clear_timer();
-                    });
+        let row = key_event.row as usize;
+        let col = key_event.col as usize;
+        if key_event.pressed {
+            self.timer[col][row] = Some(Instant::now());
+            let hold_timeout = embassy_time::Timer::after_millis(200);
+            match select(hold_timeout, key_event_channel.receive()).await {
+                embassy_futures::select::Either::First(_) => {
+                    // Timeout, trigger hold
+                    debug!("Hold timeout, got HOLD: {}, {}", hold_action, key_event);
+                    self.process_key_action_normal(hold_action, key_event).await;
                 }
-                None => {
-                    // Released: hold action
-                    // The hold_start is cleared, means that the key is released after the tap/hold threshold
-                    debug!("Release tap hold, got HOLD: {}, {}", hold_action, key_state);
-                    self.process_key_action_normal(hold_action, key_state, sender)
-                        .await;
+                embassy_futures::select::Either::Second(e) => {
+                    if e.row == key_event.row && e.col == key_event.col {
+                        // If it's same key event and releasing within 200ms, trigger tap
+                        if !e.pressed {
+                            let elapsed = self.timer[col][row].unwrap().elapsed().as_millis();
+                            debug!("TAP action: {}, time elapsed: {}ms", tap_action, elapsed);
+                            self.process_key_action_tap(tap_action, key_event).await;
+
+                            // Clear timer
+                            self.timer[col][row] = None;
+                        }
+                    } else {
+                        // A different key comes within the threshold, trigger hold + that key
+
+                        // Process hold action first
+                        self.process_key_action_normal(hold_action, key_event).await;
+
+                        // The actual processing is postponed because we cannot do recursion on async function without alloc
+                        // After current key processing is done, we can process events in queue until the queue is empty
+                        if self.unprocessed_events.push(key_event).is_err() {
+                            warn!("unprocessed event queue is full, dropping event");
+                        }
+                    }
                 }
             }
-        } else if key_state.pressed && !key_state.changed {
-            // Case 2, the key is held
-            if let Some(s) = key_state.hold_start {
-                let d = s.elapsed().as_millis();
-                if d > 200 {
-                    // The key is held for more than 200ms, send hold action, then clear timer
-                    self.process_key_action_normal(hold_action, key_state, sender)
-                        .await;
-
-                    // Clear timer if the key is held
-                    self.matrix.update_key_state(row, col, |ks| {
-                        ks.clear_timer();
-                    });
+        } else {
+            if let Some(start) = self.timer[col][row] {
+                let elapsed = start.elapsed().as_millis();
+                if elapsed > 200 {
+                    // Release hold action, then clear timer
+                    debug!(
+                        "HOLD releasing: {}, {}, time elapsed: {}ms",
+                        hold_action, key_event.pressed, elapsed
+                    );
+                    self.process_key_action_normal(hold_action, key_event).await;
+                    self.timer[col][row] = None;
                 }
             }
         }
@@ -549,35 +411,30 @@ impl<'a, M: MatrixTrait, const ROW: usize, const COL: usize, const NUM_LAYER: us
     }
 
     // Process a single keycode, typically a basic key or a modifier key.
-    async fn process_action_keycode(
-        &mut self,
-        key: KeyCode,
-        key_state: KeyState,
-        sender: &Sender<'a, CriticalSectionRawMutex, KeyboardReportMessage, 8>,
-    ) {
+    async fn process_action_keycode(&mut self, key: KeyCode, key_event: KeyEvent) {
         if key.is_consumer() {
-            self.process_action_consumer_control(key, key_state);
+            self.process_action_consumer_control(key, key_event).await;
         } else if key.is_system() {
-            self.process_action_system_control(key, key_state);
+            self.process_action_system_control(key, key_event).await;
         } else if key.is_mouse_key() {
-            self.process_action_mouse(key, key_state);
+            self.process_action_mouse(key, key_event).await;
         } else if key.is_basic() {
-            self.need_send_key_report = true;
-            if key_state.pressed {
+            if key_event.pressed {
                 self.register_key(key);
             } else {
                 self.unregister_key(key);
             }
+            self.send_keyboard_report().await;
         } else if key.is_macro() {
             // Process macro
-            self.process_action_macro(key, key_state, sender).await;
+            self.process_action_macro(key, key_event).await;
         }
     }
 
     /// Process layer switch action.
-    fn process_action_layer_switch(&mut self, layer_num: u8, key_state: KeyState) {
+    fn process_action_layer_switch(&mut self, layer_num: u8, key_event: KeyEvent) {
         // Change layer state only when the key's state is changed
-        if key_state.pressed {
+        if key_event.pressed {
             self.keymap.borrow_mut().activate_layer(layer_num);
         } else {
             self.keymap.borrow_mut().deactivate_layer(layer_num);
@@ -585,37 +442,47 @@ impl<'a, M: MatrixTrait, const ROW: usize, const COL: usize, const NUM_LAYER: us
     }
 
     /// Process consumer control action. Consumer control keys are keys in hid consumer page, such as media keys.
-    fn process_action_consumer_control(&mut self, key: KeyCode, key_state: KeyState) {
+    async fn process_action_consumer_control(&mut self, key: KeyCode, key_event: KeyEvent) {
         if key.is_consumer() {
-            self.other_report.media_usage_id = if key_state.pressed {
+            self.other_report.media_usage_id = if key_event.pressed {
                 key.as_consumer_control_usage_id() as u16
             } else {
                 0
             };
-            self.need_send_consumer_control_report = true;
+
+            self.send_media_report().await;
         }
     }
 
     /// Process system control action. System control keys are keys in system page, such as power key.
-    fn process_action_system_control(&mut self, key: KeyCode, key_state: KeyState) {
+    async fn process_action_system_control(&mut self, key: KeyCode, key_event: KeyEvent) {
         if key.is_system() {
-            if key_state.pressed {
+            if key_event.pressed {
                 if let Some(system_key) = key.as_system_control_usage_id() {
                     self.other_report.system_usage_id = system_key as u8;
-                    self.need_send_system_control_report = true;
+                    self.send_system_control_report().await;
                 }
             } else {
                 self.other_report.system_usage_id = 0;
-                self.need_send_system_control_report = true;
+                self.send_system_control_report().await;
             }
         }
     }
 
     /// Process mouse key action.
-    fn process_action_mouse(&mut self, key: KeyCode, key_state: KeyState) {
+    async fn process_action_mouse(&mut self, key: KeyCode, key_event: KeyEvent) {
         if key.is_mouse_key() {
+            // Check whether the key is held, or it's released within the time interval
+            if let Some((pressed, last_tick)) = self.last_mouse_tick.get(&key) {
+                if !pressed && last_tick.elapsed().as_millis() <= 30 {
+                    // The key is just released, ignore the key event, ues a slightly longer time interval
+                    self.last_mouse_tick.remove(&key);
+                    return;
+                }
+            }
             // Reference(qmk): https://github.com/qmk/qmk_firmware/blob/382c3bd0bd49fc0d53358f45477c48f5ae47f2ff/quantum/mousekey.c#L410
-            if key_state.pressed {
+            // https://github.com/qmk/qmk_firmware/blob/fb598e7e617692be0bf562afaf3c852c8db1c349/quantum/action.c#L332
+            if key_event.pressed {
                 match key {
                     // TODO: Add accerated mode when pressing the mouse key
                     // https://github.com/qmk/qmk_firmware/blob/master/docs/feature_mouse_keys.md#accelerated-mode
@@ -681,18 +548,30 @@ impl<'a, M: MatrixTrait, const ROW: usize, const COL: usize, const NUM_LAYER: us
                     _ => {}
                 }
             }
-            self.need_send_mouse_report = true;
+            self.send_mouse_report().await;
+
+            if let Err(_) = self
+                .last_mouse_tick
+                .insert(key, (key_event.pressed, Instant::now()))
+            {
+                error!("The buffer for last moust tick is full");
+            }
+
+            // Send the key event back to channel again, to keep processing the mouse key until release
+            if key_event.pressed {
+                // FIXME: The ideal approach is to spawn another task and send the event after 20ms.
+                // But it requires embassy-executor, which is not available for esp-idf-svc.
+                // So now we just block for 20ms for mouse keys.
+                // In the future, we're going to use esp-hal once it have good support for BLE
+                embassy_time::Timer::after_millis(20).await;
+                key_event_channel.try_send(key_event).ok();
+            }
         }
     }
 
-    async fn process_action_macro(
-        &mut self,
-        key: KeyCode,
-        key_state: KeyState,
-        sender: &Sender<'a, CriticalSectionRawMutex, KeyboardReportMessage, 8>,
-    ) {
+    async fn process_action_macro(&mut self, key: KeyCode, key_event: KeyEvent) {
         // Execute the macro only when releasing the key
-        if !key_state.is_releasing() {
+        if !!key_event.pressed {
             return;
         }
 
@@ -715,36 +594,29 @@ impl<'a, M: MatrixTrait, const ROW: usize, const COL: usize, const NUM_LAYER: us
                     // Execute the operation
                     match operation {
                         MacroOperation::Press(k) => {
-                            self.need_send_key_report = true;
                             self.register_key(k);
                         }
                         MacroOperation::Release(k) => {
-                            self.need_send_key_report = true;
                             self.unregister_key(k);
                         }
                         MacroOperation::Tap(k) => {
-                            self.need_send_key_report = true;
                             self.register_key(k);
-                            self.send_keyboard_report(sender).await;
+                            self.send_keyboard_report().await;
                             embassy_time::Timer::after_millis(2).await;
-                            self.need_send_key_report = true;
-                            self.unregister_key(k)
+                            self.unregister_key(k);
                         }
                         MacroOperation::Text(k, is_cap) => {
-                            self.need_send_key_report = true;
                             if is_cap {
                                 // If it's a capital letter, send shift first
                                 self.register_modifier(KeyCode::LShift.as_modifier_bit());
-                                self.send_keyboard_report(sender).await;
-                                self.need_send_key_report = true;
+                                self.send_keyboard_report().await;
                             }
                             self.register_keycode(k);
-                            self.send_keyboard_report(sender).await;
-                            self.need_send_key_report = true;
+                            self.send_keyboard_report().await;
+
                             self.unregister_keycode(k);
                             if is_cap {
-                                self.send_keyboard_report(sender).await;
-                                self.need_send_key_report = true;
+                                self.send_keyboard_report().await;
                                 self.unregister_modifier(KeyCode::LShift.as_modifier_bit());
                             }
                         }
@@ -752,13 +624,13 @@ impl<'a, M: MatrixTrait, const ROW: usize, const COL: usize, const NUM_LAYER: us
                             embassy_time::Timer::after_millis(t as u64).await;
                         }
                         MacroOperation::End => {
-                            self.send_keyboard_report(sender).await;
+                            self.send_keyboard_report().await;
                             break;
                         }
                     };
 
                     // Send the item in the macro sequence
-                    self.send_keyboard_report(sender).await;
+                    self.send_keyboard_report().await;
 
                     offset = new_offset;
                     if offset > self.keymap.borrow().macro_cache.len() {
diff --git a/rmk/src/keycode.rs b/rmk/src/keycode.rs
index 3006a32b..3c994eb1 100644
--- a/rmk/src/keycode.rs
+++ b/rmk/src/keycode.rs
@@ -188,7 +188,7 @@ pub enum SystemControlKey {
 
 /// KeyCode is the internal representation of all keycodes, keyboard operations, etc.
 /// Use flat representation of keycodes.
-#[derive(Debug, Format, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, FromPrimitive)]
+#[derive(Debug, Format, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, FromPrimitive, Hash)]
 #[repr(u16)]
 pub enum KeyCode {
     /// Reserved, no-key.
diff --git a/rmk/src/keymap.rs b/rmk/src/keymap.rs
index d59077b8..3de283b0 100644
--- a/rmk/src/keymap.rs
+++ b/rmk/src/keymap.rs
@@ -1,8 +1,8 @@
 use crate::{
     action::KeyAction,
+    keyboard::KeyEvent,
     keyboard_macro::{MacroOperation, MACRO_SPACE_SIZE},
     keycode::KeyCode,
-    matrix::KeyState,
     reboot_keyboard,
     storage::Storage,
 };
@@ -197,28 +197,15 @@ impl<const ROW: usize, const COL: usize, const NUM_LAYER: usize> KeyMap<ROW, COL
     }
 
     /// Fetch the action in keymap, with layer cache
-    pub(crate) fn get_action_with_layer_cache(
-        &mut self,
-        row: usize,
-        col: usize,
-        key_state: KeyState,
-    ) -> KeyAction {
-        if key_state.is_releasing() {
+    pub(crate) fn get_action_with_layer_cache(&mut self, key_event: KeyEvent) -> KeyAction {
+        let row = key_event.row as usize;
+        let col = key_event.col as usize;
+        if !key_event.pressed {
             // Releasing a pressed key, use cached layer and restore the cache
             let layer = self.pop_layer_from_cache(row, col);
             return self.layers[layer as usize][row][col];
         }
 
-        if !key_state.changed && key_state.pressed {
-            // If current key_state doesn't change, and the key is pressed, use the cached layer.
-            //
-            // This situation is specifically for tap/hold, when the key is held, the layer could be continuously checked.
-            // The layer cache should not be popped in this case.
-            // This function should return the cached layer to make layer tap/hold action performs correctly.
-            let layer = self.layer_cache[row][col];
-            return self.layers[layer as usize][row][col];
-        }
-
         // Iterate from higher layer to lower layer, the lowest checked layer is the default layer
         for (layer_idx, layer) in self.layers.iter().enumerate().rev() {
             if self.layer_state[layer_idx] || layer_idx as u8 == self.default_layer {
@@ -243,6 +230,16 @@ impl<const ROW: usize, const COL: usize, const NUM_LAYER: usize> KeyMap<ROW, COL
         KeyAction::No
     }
 
+    fn get_activated_layer(&self) -> u8 {
+        for (layer_idx, _) in self.layers.iter().enumerate().rev() {
+            if self.layer_state[layer_idx] || layer_idx as u8 == self.default_layer {
+                return layer_idx as u8;
+            }
+        }
+
+        self.default_layer
+    }
+
     fn get_layer_from_cache(&self, row: usize, col: usize) -> u8 {
         self.layer_cache[row][col]
     }
diff --git a/rmk/src/lib.rs b/rmk/src/lib.rs
index 57cadf1e..6cab9351 100644
--- a/rmk/src/lib.rs
+++ b/rmk/src/lib.rs
@@ -20,7 +20,6 @@ use crate::debounce::default_bouncer::DefaultDebouncer;
 #[cfg(feature = "rapid_debouncer")]
 use crate::debounce::fast_debouncer::RapidDebouncer;
 use crate::{
-    keyboard::keyboard_task,
     light::{led_hid_task, LightService},
     via::vial_task,
 };
@@ -30,19 +29,18 @@ use defmt::{error, warn};
 #[cfg(not(feature = "_esp_ble"))]
 use embassy_executor::Spawner;
 use embassy_futures::select::{select, select4, Either4};
-use embassy_sync::{
-    blocking_mutex::raw::CriticalSectionRawMutex,
-    channel::{Channel, Receiver, Sender},
-};
+use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, channel::Receiver};
 use embassy_time::Timer;
 use embassy_usb::driver::Driver;
 pub use embedded_hal;
 use embedded_hal::digital::{InputPin, OutputPin};
 #[cfg(feature = "async_matrix")]
 use embedded_hal_async::digital::Wait;
+#[cfg(not(feature = "_no_external_storage"))]
+use embedded_storage::nor_flash::NorFlash;
 pub use flash::EmptyFlashWrapper;
 use futures::pin_mut;
-use keyboard::{communication_task, Keyboard, KeyboardReportMessage};
+use keyboard::{communication_task, keyboard_report_channel, Keyboard, KeyboardReportMessage};
 use keymap::KeyMap;
 use matrix::{Matrix, MatrixTrait};
 pub use rmk_config as config;
@@ -50,11 +48,8 @@ use rmk_config::RmkConfig;
 pub use rmk_macro as macros;
 use usb::KeyboardUsbDevice;
 use via::process::VialService;
-#[cfg(not(feature = "_no_external_storage"))]
-use {
-    embedded_storage::nor_flash::NorFlash,
-    embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash, storage::Storage,
-};
+#[cfg(any(feature = "_nrf_ble", not(feature = "_no_external_storage")))]
+use {embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash, storage::Storage};
 
 pub mod action;
 #[cfg(feature = "_ble")]
@@ -207,7 +202,7 @@ pub(crate) async fn initialize_usb_keyboard_and_run<
     #[cfg(not(feature = "async_matrix"))] In: InputPin,
     Out: OutputPin,
     D: Driver<'static>,
-    #[cfg(not(feature = "_no_external_storage"))] F: AsyncNorFlash,
+    #[cfg(any(feature = "_nrf_ble", not(feature = "_no_external_storage")))] F: AsyncNorFlash,
     const ROW: usize,
     const COL: usize,
     const NUM_LAYER: usize,
@@ -217,13 +212,13 @@ pub(crate) async fn initialize_usb_keyboard_and_run<
     #[cfg(feature = "col2row")] output_pins: [Out; COL],
     #[cfg(not(feature = "col2row"))] output_pins: [Out; ROW],
     usb_driver: D,
-    #[cfg(not(feature = "_no_external_storage"))] flash: F,
+    #[cfg(any(feature = "_nrf_ble", not(feature = "_no_external_storage")))] flash: F,
     default_keymap: [[[KeyAction; COL]; ROW]; NUM_LAYER],
     keyboard_config: RmkConfig<'static, Out>,
 ) -> ! {
     // Initialize storage and keymap
     // For USB keyboard, the "external" storage means the storage initialized by the user.
-    #[cfg(not(feature = "_no_external_storage"))]
+    #[cfg(any(feature = "_nrf_ble", not(feature = "_no_external_storage")))]
     let (mut storage, keymap) = {
         let mut s = Storage::new(flash, &default_keymap, keyboard_config.storage_config).await;
         let keymap = RefCell::new(
@@ -231,44 +226,46 @@ pub(crate) async fn initialize_usb_keyboard_and_run<
         );
         (s, keymap)
     };
-    #[cfg(feature = "_no_external_storage")]
+    #[cfg(all(not(feature = "_nrf_ble"), feature = "_no_external_storage"))]
     let keymap = RefCell::new(KeyMap::<ROW, COL, NUM_LAYER>::new(default_keymap).await);
 
     // Keyboard matrix, use COL2ROW by default
     #[cfg(all(feature = "col2row", feature = "rapid_debouncer"))]
-    let matrix = Matrix::<_, _, RapidDebouncer<ROW, COL>, ROW, COL>::new(input_pins, output_pins);
+    let mut matrix =
+        Matrix::<_, _, RapidDebouncer<ROW, COL>, ROW, COL>::new(input_pins, output_pins);
     #[cfg(all(feature = "col2row", not(feature = "rapid_debouncer")))]
-    let matrix = Matrix::<_, _, DefaultDebouncer<ROW, COL>, ROW, COL>::new(input_pins, output_pins);
+    let mut matrix =
+        Matrix::<_, _, DefaultDebouncer<ROW, COL>, ROW, COL>::new(input_pins, output_pins);
     #[cfg(all(not(feature = "col2row"), feature = "rapid_debouncer"))]
-    let matrix = Matrix::<_, _, RapidDebouncer<COL, ROW>, COL, ROW>::new(input_pins, output_pins);
+    let mut matrix =
+        Matrix::<_, _, RapidDebouncer<COL, ROW>, COL, ROW>::new(input_pins, output_pins);
     #[cfg(all(not(feature = "col2row"), not(feature = "rapid_debouncer")))]
-    let matrix = Matrix::<_, _, DefaultDebouncer<COL, ROW>, COL, ROW>::new(input_pins, output_pins);
+    let mut matrix =
+        Matrix::<_, _, DefaultDebouncer<COL, ROW>, COL, ROW>::new(input_pins, output_pins);
+
+    let keyboard_report_sender = keyboard_report_channel.sender();
+    let keyboard_report_receiver = keyboard_report_channel.receiver();
 
     // Create keyboard services and devices
     let (mut keyboard, mut usb_device, mut vial_service, mut light_service) = (
-        Keyboard::new(matrix, &keymap),
+        Keyboard::new(&keymap, &keyboard_report_sender),
         KeyboardUsbDevice::new(usb_driver, keyboard_config.usb_config),
         VialService::new(&keymap, keyboard_config.vial_config),
         LightService::from_config(keyboard_config.light_config),
     );
 
-    static keyboard_channel: Channel<CriticalSectionRawMutex, KeyboardReportMessage, 8> =
-        Channel::new();
-    let keyboard_report_sender = keyboard_channel.sender();
-    let keyboard_report_receiver = keyboard_channel.receiver();
-
     loop {
         KEYBOARD_STATE.store(false, core::sync::atomic::Ordering::Release);
         // Run all tasks, if one of them fails, wait 1 second and then restart
         run_usb_keyboard(
             &mut usb_device,
             &mut keyboard,
-            #[cfg(not(feature = "_no_external_storage"))]
+            &mut matrix,
+            #[cfg(any(feature = "_nrf_ble", not(feature = "_no_external_storage")))]
             &mut storage,
             &mut light_service,
             &mut vial_service,
             &keyboard_report_receiver,
-            &keyboard_report_sender,
         )
         .await;
 
@@ -282,23 +279,26 @@ pub(crate) async fn run_usb_keyboard<
     'a,
     'b,
     D: Driver<'a>,
-    #[cfg(not(feature = "_no_external_storage"))] F: AsyncNorFlash,
     M: MatrixTrait,
+    #[cfg(any(feature = "_nrf_ble", not(feature = "_no_external_storage")))] F: AsyncNorFlash,
     Out: OutputPin,
     const ROW: usize,
     const COL: usize,
     const NUM_LAYER: usize,
 >(
     usb_device: &mut KeyboardUsbDevice<'a, D>,
-    keyboard: &mut Keyboard<'b, M, ROW, COL, NUM_LAYER>,
-    #[cfg(not(feature = "_no_external_storage"))] storage: &mut Storage<F>,
+    keyboard: &mut Keyboard<'b, ROW, COL, NUM_LAYER>,
+    matrix: &mut M,
+    #[cfg(any(feature = "_nrf_ble", not(feature = "_no_external_storage")))] storage: &mut Storage<
+        F,
+    >,
     light_service: &mut LightService<Out>,
     vial_service: &mut VialService<'b, ROW, COL, NUM_LAYER>,
     keyboard_report_receiver: &Receiver<'b, CriticalSectionRawMutex, KeyboardReportMessage, 8>,
-    keyboard_report_sender: &Sender<'b, CriticalSectionRawMutex, KeyboardReportMessage, 8>,
 ) {
     let usb_fut = usb_device.device.run();
-    let keyboard_fut = keyboard_task(keyboard, keyboard_report_sender);
+    let keyboard_fut = keyboard.run();
+    let matrix_fut = matrix.scan();
     let communication_fut = communication_task(
         keyboard_report_receiver,
         &mut usb_device.keyboard_hid_writer,
@@ -308,23 +308,25 @@ pub(crate) async fn run_usb_keyboard<
     let via_fut = vial_task(&mut usb_device.via_hid, vial_service);
     pin_mut!(usb_fut);
     pin_mut!(keyboard_fut);
+    pin_mut!(matrix_fut);
     pin_mut!(led_fut);
     pin_mut!(via_fut);
     pin_mut!(communication_fut);
 
-    #[cfg(not(feature = "_no_external_storage"))]
+    #[cfg(any(feature = "_nrf_ble", not(feature = "_no_external_storage")))]
     let storage_fut = storage.run::<ROW, COL, NUM_LAYER>();
-    #[cfg(not(feature = "_no_external_storage"))]
+    #[cfg(any(feature = "_nrf_ble", not(feature = "_no_external_storage")))]
     pin_mut!(storage_fut);
 
     match select4(
         select(usb_fut, keyboard_fut),
-        #[cfg(not(feature = "_no_external_storage"))]
+        #[cfg(any(feature = "_nrf_ble", not(feature = "_no_external_storage")))]
         select(storage_fut, via_fut),
+        #[cfg(all(not(feature = "_nrf_ble"), feature = "_no_external_storage"))]
         #[cfg(feature = "_no_external_storage")]
         via_fut,
         led_fut,
-        communication_fut,
+        select(matrix_fut, communication_fut),
     )
     .await
     {
diff --git a/rmk/src/matrix.rs b/rmk/src/matrix.rs
index b2b1f197..8e69f9c6 100644
--- a/rmk/src/matrix.rs
+++ b/rmk/src/matrix.rs
@@ -1,5 +1,8 @@
-use crate::debounce::{DebounceState, DebouncerTrait};
-use defmt::Format;
+use crate::{
+    debounce::{DebounceState, DebouncerTrait},
+    keyboard::{key_event_channel, KeyEvent},
+};
+use defmt::{error, Format};
 use embassy_time::{Instant, Timer};
 use embedded_hal::digital::{InputPin, OutputPin};
 #[cfg(feature = "async_matrix")]
@@ -41,9 +44,7 @@ pub(crate) struct KeyState {
     // True if the key is pressed
     pub(crate) pressed: bool,
     // True if the key's state is just changed
-    pub(crate) changed: bool,
-    // If the key is held, `hold_start` records the time of it was pressed.
-    pub(crate) hold_start: Option<Instant>,
+    // pub(crate) changed: bool,
 }
 
 impl Default for KeyState {
@@ -54,21 +55,7 @@ impl Default for KeyState {
 
 impl KeyState {
     fn new() -> Self {
-        KeyState {
-            pressed: false,
-            changed: false,
-            hold_start: None,
-        }
-    }
-
-    // Record the start time of pressing
-    pub(crate) fn start_timer(&mut self) {
-        self.hold_start = Some(Instant::now());
-    }
-
-    // Clear held timer
-    pub(crate) fn clear_timer(&mut self) {
-        self.hold_start = None;
+        KeyState { pressed: false }
     }
 
     pub(crate) fn toggle_pressed(&mut self) {
@@ -76,11 +63,11 @@ impl KeyState {
     }
 
     pub(crate) fn is_releasing(&self) -> bool {
-        !self.pressed && self.changed
+        !self.pressed
     }
 
     pub(crate) fn is_pressing(&self) -> bool {
-        self.pressed && self.changed
+        self.pressed
     }
 }
 
@@ -177,36 +164,58 @@ impl<
 
     /// Do matrix scanning, the result is stored in matrix's key_state field.
     async fn scan(&mut self) {
-        for (out_idx, out_pin) in self.output_pins.iter_mut().enumerate() {
-            // Pull up output pin, wait 1us ensuring the change comes into effect
-            out_pin.set_high().ok();
-            Timer::after_micros(1).await;
-            for (in_idx, in_pin) in self.input_pins.iter_mut().enumerate() {
-                // Check input pins and debounce
-                let debounce_state = self.debouncer.detect_change_with_debounce(
-                    in_idx,
-                    out_idx,
-                    in_pin.is_high().ok().unwrap_or_default(),
-                    &self.key_states[out_idx][in_idx],
-                );
-
-                match debounce_state {
-                    DebounceState::Debounced => {
-                        self.key_states[out_idx][in_idx].toggle_pressed();
-                        self.key_states[out_idx][in_idx].changed = true;
+        defmt::info!("Matrix scanning");
+        loop {
+            #[cfg(feature = "async_matrix")]
+            self.wait_for_key().await;
+
+            // Scan matrix and send report
+            for (out_idx, out_pin) in self.output_pins.iter_mut().enumerate() {
+                // Pull up output pin, wait 1us ensuring the change comes into effect
+                out_pin.set_high().ok();
+                Timer::after_micros(1).await;
+                for (in_idx, in_pin) in self.input_pins.iter_mut().enumerate() {
+                    // Check input pins and debounce
+                    let debounce_state = self.debouncer.detect_change_with_debounce(
+                        in_idx,
+                        out_idx,
+                        in_pin.is_high().ok().unwrap_or_default(),
+                        &self.key_states[out_idx][in_idx],
+                    );
+
+                    match debounce_state {
+                        DebounceState::Debounced => {
+                            self.key_states[out_idx][in_idx].toggle_pressed();
+                            #[cfg(feature = "col2row")]
+                            let (row, col, key_state) =
+                                (in_idx, out_idx, self.key_states[out_idx][in_idx]);
+                            #[cfg(not(feature = "col2row"))]
+                            let (row, col, key_state) =
+                                (out_idx, in_idx, self.key_states[out_idx][in_idx]);
+
+                            // `try_send` is used here because we don't want to block scanning if the channel is full
+                            let send_re = key_event_channel.try_send(KeyEvent {
+                                row: row as u8,
+                                col: col as u8,
+                                pressed: key_state.pressed,
+                            });
+                            if send_re.is_err() {
+                                error!("Failed to send key event: key event channel full");
+                            }
+                        }
+                        _ => (),
                     }
-                    _ => self.key_states[out_idx][in_idx].changed = false,
-                }
 
-                // If there's key changed or pressed, always refresh the self.scan_start
-                #[cfg(feature = "async_matrix")]
-                if self.key_states[out_idx][in_idx].changed
-                    || self.key_states[out_idx][in_idx].pressed
-                {
-                    self.scan_start = Some(Instant::now());
+                    // If there's key still pressed, always refresh the self.scan_start
+                    #[cfg(feature = "async_matrix")]
+                    if self.key_states[out_idx][in_idx].pressed {
+                        self.scan_start = Some(Instant::now());
+                    }
                 }
+                out_pin.set_low().ok();
             }
-            out_pin.set_low().ok();
+
+            embassy_time::Timer::after_micros(100).await;
         }
     }
 
diff --git a/rmk/src/split/central.rs b/rmk/src/split/central.rs
index ebbdad91..714eaaf5 100644
--- a/rmk/src/split/central.rs
+++ b/rmk/src/split/central.rs
@@ -1,9 +1,7 @@
 use core::cell::RefCell;
 
-use defmt::warn;
+use defmt::{error, warn};
 use embassy_executor::Spawner;
-use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
-use embassy_sync::channel::Channel;
 use embassy_time::{Instant, Timer};
 use embassy_usb::driver::Driver;
 use embedded_hal::digital::{InputPin, OutputPin};
@@ -17,26 +15,20 @@ use crate::debounce::default_bouncer::DefaultDebouncer;
 #[cfg(feature = "rapid_debouncer")]
 use crate::debounce::fast_debouncer::RapidDebouncer;
 use crate::debounce::{DebounceState, DebouncerTrait};
-use crate::keyboard::{Keyboard, KeyboardReportMessage};
+use crate::keyboard::{key_event_channel, keyboard_report_channel, KeyEvent, Keyboard};
 use crate::keymap::KeyMap;
 use crate::light::LightService;
 use crate::matrix::{KeyState, MatrixTrait};
 use crate::run_usb_keyboard;
 #[cfg(feature = "_nrf_ble")]
 use crate::split::nrf::central::initialize_ble_split_central_and_run;
-use crate::split::KeySyncSignal;
-use crate::split::SYNC_SIGNALS;
 use crate::usb::KeyboardUsbDevice;
 use crate::via::process::VialService;
 
 #[cfg(not(feature = "_nrf_ble"))]
-use {
-    crate::storage::Storage,
-    embedded_io_async::{Read, Write},
-    embedded_storage_async::nor_flash::NorFlash,
-};
-
-use super::{KeySyncMessage, CENTRAL_SYNC_CHANNELS};
+use embedded_io_async::{Read, Write};
+#[cfg(any(feature = "_nrf_ble", not(feature = "_no_external_storage")))]
+use {crate::storage::Storage, embedded_storage_async::nor_flash::NorFlash};
 
 /// Run RMK split central keyboard service. This function should never return.
 ///
@@ -161,7 +153,7 @@ pub(crate) async fn initialize_usb_split_central_and_run<
     #[cfg(not(feature = "async_matrix"))] In: InputPin,
     Out: OutputPin,
     D: Driver<'static>,
-    #[cfg(not(feature = "_no_external_storage"))] F: NorFlash,
+    #[cfg(any(feature = "_nrf_ble", not(feature = "_no_external_storage")))] F: NorFlash,
     const TOTAL_ROW: usize,
     const TOTAL_COL: usize,
     const CENTRAL_ROW: usize,
@@ -175,13 +167,13 @@ pub(crate) async fn initialize_usb_split_central_and_run<
     #[cfg(feature = "col2row")] output_pins: [Out; CENTRAL_COL],
     #[cfg(not(feature = "col2row"))] output_pins: [Out; CENTRAL_ROW],
     #[cfg(not(feature = "_no_usb"))] usb_driver: D,
-    #[cfg(not(feature = "_no_external_storage"))] flash: F,
+    #[cfg(any(feature = "_nrf_ble", not(feature = "_no_external_storage")))] flash: F,
     default_keymap: [[[KeyAction; TOTAL_COL]; TOTAL_ROW]; NUM_LAYER],
     keyboard_config: RmkConfig<'static, Out>,
 ) -> ! {
     // Initialize storage and keymap
     // For USB keyboard, the "external" storage means the storage initialized by the user.
-    #[cfg(not(feature = "_no_external_storage"))]
+    #[cfg(any(feature = "_nrf_ble", not(feature = "_no_external_storage")))]
     let (mut storage, keymap) = {
         let mut s = Storage::new(flash, &default_keymap, keyboard_config.storage_config).await;
         let keymap = RefCell::new(
@@ -193,83 +185,74 @@ pub(crate) async fn initialize_usb_split_central_and_run<
         );
         (s, keymap)
     };
-    #[cfg(feature = "_no_external_storage")]
+
+    #[cfg(all(not(feature = "_nrf_ble"), feature = "_no_external_storage"))]
     let keymap = RefCell::new(KeyMap::<TOTAL_ROW, TOTAL_COL, NUM_LAYER>::new(default_keymap).await);
 
     // Keyboard matrix, use COL2ROW by default
     #[cfg(all(feature = "col2row", feature = "rapid_debouncer"))]
-    let matrix = CentralMatrix::<
+    let mut matrix = CentralMatrix::<
         In,
         Out,
         RapidDebouncer<CENTRAL_ROW, CENTRAL_COL>,
-        TOTAL_ROW,
-        TOTAL_COL,
         CENTRAL_ROW_OFFSET,
         CENTRAL_COL_OFFSET,
         CENTRAL_ROW,
         CENTRAL_COL,
     >::new(input_pins, output_pins, RapidDebouncer::new());
     #[cfg(all(feature = "col2row", not(feature = "rapid_debouncer")))]
-    let matrix = CentralMatrix::<
+    let mut matrix = CentralMatrix::<
         In,
         Out,
         DefaultDebouncer<CENTRAL_ROW, CENTRAL_COL>,
-        TOTAL_ROW,
-        TOTAL_COL,
         CENTRAL_ROW_OFFSET,
         CENTRAL_COL_OFFSET,
         CENTRAL_ROW,
         CENTRAL_COL,
     >::new(input_pins, output_pins, DefaultDebouncer::new());
     #[cfg(all(not(feature = "col2row"), feature = "rapid_debouncer"))]
-    let matrix = CentralMatrix::<
+    let mut matrix = CentralMatrix::<
         In,
         Out,
         RapidDebouncer<CENTRAL_COL, CENTRAL_ROW>,
-        TOTAL_ROW,
-        TOTAL_COL,
         CENTRAL_ROW_OFFSET,
         CENTRAL_COL_OFFSET,
         CENTRAL_COL,
         CENTRAL_ROW,
     >::new(input_pins, output_pins, RapidDebouncer::new());
     #[cfg(all(not(feature = "col2row"), not(feature = "rapid_debouncer")))]
-    let matrix = CentralMatrix::<
+    let mut matrix = CentralMatrix::<
         In,
         Out,
         DefaultDebouncer<CENTRAL_COL, CENTRAL_ROW>,
-        TOTAL_ROW,
-        TOTAL_COL,
         CENTRAL_ROW_OFFSET,
         CENTRAL_COL_OFFSET,
         CENTRAL_COL,
         CENTRAL_ROW,
     >::new(input_pins, output_pins, DefaultDebouncer::new());
 
+    let keyboard_report_sender = keyboard_report_channel.sender();
+    let keyboard_report_receiver = keyboard_report_channel.receiver();
+
     // Create keyboard services and devices
     let (mut keyboard, mut usb_device, mut vial_service, mut light_service) = (
-        Keyboard::new(matrix, &keymap),
+        Keyboard::new(&keymap, &keyboard_report_sender),
         KeyboardUsbDevice::new(usb_driver, keyboard_config.usb_config),
         VialService::new(&keymap, keyboard_config.vial_config),
         LightService::from_config(keyboard_config.light_config),
     );
 
-    static keyboard_channel: Channel<CriticalSectionRawMutex, KeyboardReportMessage, 8> =
-        Channel::new();
-    let keyboard_report_sender = keyboard_channel.sender();
-    let keyboard_report_receiver = keyboard_channel.receiver();
-
     loop {
         // Run all tasks, if one of them fails, wait 1 second and then restart
         run_usb_keyboard(
             &mut usb_device,
             &mut keyboard,
-            #[cfg(not(feature = "_no_external_storage"))]
+            &mut matrix,
+            #[cfg(any(feature = "_nrf_ble", not(feature = "_no_external_storage")))]
             &mut storage,
             &mut light_service,
             &mut vial_service,
             &keyboard_report_receiver,
-            &keyboard_report_sender,
         )
         .await;
 
@@ -284,8 +267,6 @@ pub(crate) struct CentralMatrix<
     #[cfg(not(feature = "async_matrix"))] In: InputPin,
     Out: OutputPin,
     D: DebouncerTrait,
-    const TOTAL_ROW: usize,
-    const TOTAL_COL: usize,
     const ROW_OFFSET: usize,
     const COL_OFFSET: usize,
     const INPUT_PIN_NUM: usize,
@@ -298,7 +279,7 @@ pub(crate) struct CentralMatrix<
     /// Debouncer
     debouncer: D,
     /// Key state matrix
-    key_states: [[KeyState; TOTAL_COL]; TOTAL_ROW],
+    key_states: [[KeyState; INPUT_PIN_NUM]; OUTPUT_PIN_NUM],
     /// Start scanning
     scan_start: Option<Instant>,
 }
@@ -308,21 +289,82 @@ impl<
         #[cfg(not(feature = "async_matrix"))] In: InputPin,
         Out: OutputPin,
         D: DebouncerTrait,
-        const ROW: usize,
-        const COL: usize,
         const ROW_OFFSET: usize,
         const COL_OFFSET: usize,
         const INPUT_PIN_NUM: usize,
         const OUTPUT_PIN_NUM: usize,
     > MatrixTrait
-    for CentralMatrix<In, Out, D, ROW, COL, ROW_OFFSET, COL_OFFSET, INPUT_PIN_NUM, OUTPUT_PIN_NUM>
+    for CentralMatrix<In, Out, D, ROW_OFFSET, COL_OFFSET, INPUT_PIN_NUM, OUTPUT_PIN_NUM>
 {
-    const ROW: usize = ROW;
-    const COL: usize = COL;
+    #[cfg(feature = "col2row")]
+    const ROW: usize = INPUT_PIN_NUM;
+    #[cfg(feature = "col2row")]
+    const COL: usize = OUTPUT_PIN_NUM;
+    #[cfg(not(feature = "col2row"))]
+    const ROW: usize = OUTPUT_PIN_NUM;
+    #[cfg(not(feature = "col2row"))]
+    const COL: usize = INPUT_PIN_NUM;
 
     async fn scan(&mut self) {
-        self.internal_scan().await;
-        self.scan_peripheral().await;
+        defmt::info!("Central matrix scanning");
+        loop {
+            #[cfg(feature = "async_matrix")]
+            self.wait_for_key().await;
+
+            // Scan matrix and send report
+            for (out_idx, out_pin) in self.output_pins.iter_mut().enumerate() {
+                // Pull up output pin, wait 1us ensuring the change comes into effect
+                out_pin.set_high().ok();
+                Timer::after_micros(1).await;
+                for (in_idx, in_pin) in self.input_pins.iter_mut().enumerate() {
+                    // Check input pins and debounce
+                    let debounce_state = self.debouncer.detect_change_with_debounce(
+                        in_idx,
+                        out_idx,
+                        in_pin.is_high().ok().unwrap_or_default(),
+                        &self.key_states[out_idx][in_idx],
+                    );
+
+                    match debounce_state {
+                        DebounceState::Debounced => {
+                            self.key_states[out_idx][in_idx].toggle_pressed();
+                            #[cfg(feature = "col2row")]
+                            let (row, col, key_state) = (
+                                (in_idx + ROW_OFFSET) as u8,
+                                (out_idx + COL_OFFSET) as u8,
+                                self.key_states[out_idx][in_idx],
+                            );
+                            #[cfg(not(feature = "col2row"))]
+                            let (row, col, key_state) = (
+                                out_idx + ROW_OFFSET as u8,
+                                in_idx + COL_OFFSET as u8,
+                                self.key_states[out_idx][in_idx],
+                            );
+
+                            // `try_send` is used here because we don't want to block scanning if the channel is full
+                            let send_re = key_event_channel.try_send(KeyEvent {
+                                row,
+                                col,
+                                pressed: key_state.pressed,
+                            });
+                            if send_re.is_err() {
+                                error!("Failed to send key event: key event channel full");
+                            }
+                        }
+                        _ => (),
+                    }
+
+                    // If there's key still pressed, always refresh the self.scan_start
+                    #[cfg(feature = "async_matrix")]
+                    if self.key_states[out_idx][in_idx].pressed {
+                        self.scan_start = Some(Instant::now());
+                    }
+                }
+                out_pin.set_low().ok();
+            }
+
+            embassy_time::Timer::after_micros(100).await;
+        }
     }
 
     fn get_key_state(&mut self, row: usize, col: usize) -> KeyState {
@@ -335,8 +377,8 @@ impl<
 
     #[cfg(feature = "async_matrix")]
     async fn wait_for_key(&mut self) {
-        use super::SCAN_SIGNAL;
-        use embassy_futures::select::{select, select_slice};
+        use defmt::info;
+        use embassy_futures::select::select_slice;
         use heapless::Vec;
 
         if let Some(start_time) = self.scan_start {
@@ -351,23 +393,14 @@ impl<
         for out in self.output_pins.iter_mut() {
             out.set_high().ok();
         }
-
         Timer::after_micros(1).await;
-
-        // Enable SCAN_SIGNAL, wait for peripheral's report
-        SCAN_SIGNAL.reset();
-
-        // Current board's matrix
+        info!("Waiting for high");
         let mut futs: Vec<_, INPUT_PIN_NUM> = self
             .input_pins
             .iter_mut()
             .map(|input_pin| input_pin.wait_for_high())
             .collect();
-
-        // Wait for split event
-        let split_event = SCAN_SIGNAL.wait();
-
-        let _ = select(split_event, select_slice(futs.as_mut_slice())).await;
+        let _ = select_slice(futs.as_mut_slice()).await;
 
         // Set all output pins back to low
         for out in self.output_pins.iter_mut() {
@@ -375,9 +408,6 @@ impl<
         }
 
         self.scan_start = Some(Instant::now());
-
-        // Enable SCAN_SIGNAL, wait for peripheral's report
-        SCAN_SIGNAL.reset();
     }
 }
 
@@ -386,13 +416,11 @@ impl<
         #[cfg(not(feature = "async_matrix"))] In: InputPin,
         Out: OutputPin,
         D: DebouncerTrait,
-        const ROW: usize,
-        const COL: usize,
         const ROW_OFFSET: usize,
         const COL_OFFSET: usize,
         const INPUT_PIN_NUM: usize,
         const OUTPUT_PIN_NUM: usize,
-    > CentralMatrix<In, Out, D, ROW, COL, ROW_OFFSET, COL_OFFSET, INPUT_PIN_NUM, OUTPUT_PIN_NUM>
+    > CentralMatrix<In, Out, D, ROW_OFFSET, COL_OFFSET, INPUT_PIN_NUM, OUTPUT_PIN_NUM>
 {
     /// Initialization of central
     pub(crate) fn new(
@@ -404,89 +432,8 @@ impl<
             input_pins,
             output_pins,
             debouncer,
-            key_states: [[KeyState::default(); COL]; ROW],
+            key_states: [[KeyState::default(); INPUT_PIN_NUM]; OUTPUT_PIN_NUM],
             scan_start: None,
         }
     }
-
-    pub(crate) async fn scan_peripheral(&mut self) {
-        for (id, peripheral_channel) in CENTRAL_SYNC_CHANNELS.iter().enumerate() {
-            // TODO: Skip unused peripherals
-            if id > 0 {
-                break;
-            }
-            // Signal that peripheral scanning is started
-            SYNC_SIGNALS[id].signal(KeySyncSignal::Start);
-            // Receive peripheral key states
-            if let KeySyncMessage::StartSend(n) = peripheral_channel.receive().await {
-                // Update peripheral's key states
-                for _ in 0..n {
-                    if let KeySyncMessage::Key(row, col, key_state) =
-                        peripheral_channel.receive().await
-                    {
-                        if key_state != self.key_states[row as usize][col as usize].pressed {
-                            self.key_states[row as usize][col as usize].pressed = key_state;
-                            self.key_states[row as usize][col as usize].changed = true;
-                        } else {
-                            self.key_states[row as usize][col as usize].changed = false;
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    pub(crate) async fn internal_scan(&mut self) {
-        // Get the row and col index of current board in the whole key matrix
-        for (out_idx, out_pin) in self.output_pins.iter_mut().enumerate() {
-            // Pull up output pin, wait 1us ensuring the change comes into effect
-            out_pin.set_high().ok();
-            Timer::after_micros(1).await;
-            for (in_idx, in_pin) in self.input_pins.iter_mut().enumerate() {
-                #[cfg(feature = "col2row")]
-                let (row_idx, col_idx) = (in_idx + ROW_OFFSET, out_idx + COL_OFFSET);
-                #[cfg(not(feature = "col2row"))]
-                let (row_idx, col_idx) = (out_idx + ROW_OFFSET, in_idx + COL_OFFSET);
-
-                // Check input pins and debounce
-                let debounce_state = self.debouncer.detect_change_with_debounce(
-                    in_idx,
-                    out_idx,
-                    in_pin.is_high().ok().unwrap_or_default(),
-                    &self.key_states[row_idx][col_idx],
-                );
-
-                match debounce_state {
-                    DebounceState::Debounced => {
-                        self.key_states[row_idx][col_idx].toggle_pressed();
-                        self.key_states[row_idx][col_idx].changed = true;
-                    }
-                    _ => self.key_states[row_idx][col_idx].changed = false,
-                }
-
-                // If there's key changed or pressed, always refresh the self.scan_start
-                if self.key_states[row_idx][col_idx].changed
-                    || self.key_states[row_idx][col_idx].pressed
-                {
-                    #[cfg(feature = "async_matrix")]
-                    {
-                        self.scan_start = Some(Instant::now());
-                    }
-                }
-            }
-            out_pin.set_low().ok();
-        }
-    }
-
-    /// Read key state OF CURRENT BOARD at position (row, col)
-    pub(crate) fn get_key_state_current_board(
-        &mut self,
-        out_idx: usize,
-        in_idx: usize,
-    ) -> KeyState {
-        #[cfg(feature = "col2row")]
-        return self.key_states[in_idx + ROW_OFFSET][out_idx + COL_OFFSET];
-        #[cfg(not(feature = "col2row"))]
-        return self.key_states[out_idx + ROW_OFFSET][in_idx + COL_OFFSET];
-    }
 }
diff --git a/rmk/src/split/driver.rs b/rmk/src/split/driver.rs
index fa1fc099..6216dad1 100644
--- a/rmk/src/split/driver.rs
+++ b/rmk/src/split/driver.rs
@@ -1,16 +1,9 @@
 ///! The abstracted driver layer of the split keyboard.
 ///!
-///!
-use crate::split::SYNC_SIGNALS;
-#[cfg(feature = "async_matrix")]
-use crate::{
-    split::{KeySyncSignal, SCAN_SIGNAL},
-    KEYBOARD_STATE,
-};
+use crate::keyboard::{key_event_channel, KeyEvent};
 
-use super::{KeySyncMessage, SplitMessage, CENTRAL_SYNC_CHANNELS};
-use defmt::debug;
-use embassy_futures::select::{select, Either};
+use super::SplitMessage;
+use defmt::{debug, error};
 
 #[derive(Debug, Clone, Copy, defmt::Format)]
 pub(crate) enum SplitDriverError {
@@ -45,8 +38,6 @@ pub(crate) struct PeripheralMatrixMonitor<
     const COL_OFFSET: usize,
     R: SplitReader,
 > {
-    /// Key state cache matrix
-    pressed: [[bool; COL]; ROW],
     /// Receiver
     receiver: R,
     /// Peripheral id
@@ -62,60 +53,33 @@ impl<
     > PeripheralMatrixMonitor<ROW, COL, ROW_OFFSET, COL_OFFSET, R>
 {
     pub(crate) fn new(receiver: R, id: usize) -> Self {
-        Self {
-            pressed: [[false; COL]; ROW],
-            receiver,
-            id,
-        }
+        Self { receiver, id }
     }
 
     /// Run the monitor.
     ///
-    /// The monitor receives from the peripheral and waits for the sync message from the matrix scanning thread.
-    /// If a sync message is received from matrix scanning thread, it sends the key state matrix back.
+    /// The monitor receives from the peripheral and forward the message to key_event_channel.
     pub(crate) async fn run(mut self) -> ! {
         loop {
-            let receive_fut = self.receiver.read();
-            let sync_fut = SYNC_SIGNALS[self.id].wait();
-            match select(receive_fut, sync_fut).await {
-                Either::First(received_message) => {
-                    debug!("Receveid peripheral message: {}", received_message);
-                    if let Ok(message) = received_message {
-                        // Update the key state matrix
-                        if let SplitMessage::Key(row, col, pressed) = message {
-                            self.pressed[row as usize][col as usize] = pressed;
-                        }
-                        // In async matrix mode, signal to start matrix scanning
-                        #[cfg(feature = "async_matrix")]
-                        if KEYBOARD_STATE.load(core::sync::atomic::Ordering::Relaxed) {
-                            SCAN_SIGNAL.signal(KeySyncSignal::Start);
-                        }
-                    }
-                }
-                Either::Second(_sync_signal) => {
-                    // Start synchronizing key state matrix
-
-                    // First, send the number of states to be sent
-                    CENTRAL_SYNC_CHANNELS[self.id]
-                        .send(KeySyncMessage::StartSend((ROW * COL) as u16))
-                        .await;
-                    // Send the key state matrix
-                    // TODO: Optimize: send only the changed key states
-                    for row in 0..ROW {
-                        for col in 0..COL {
-                            CENTRAL_SYNC_CHANNELS[self.id]
-                                .send(KeySyncMessage::Key(
-                                    (row + ROW_OFFSET) as u8,
-                                    (col + COL_OFFSET) as u8,
-                                    self.pressed[row][col],
-                                ))
-                                .await;
+            match self.receiver.read().await {
+                Ok(received_message) => {
+                    debug!("Received peripheral message: {}", received_message);
+                    if let SplitMessage::Key(e) = received_message {
+                        // Check row/col
+                        if e.row as usize > ROW || e.col as usize > COL {
+                            error!("Invalid peripheral row/col: {} {}", e.row, e.col);
+                            continue;
                         }
+                        key_event_channel
+                            .send(KeyEvent {
+                                row: e.row + ROW_OFFSET as u8,
+                                col: e.col + COL_OFFSET as u8,
+                                pressed: e.pressed,
+                            })
+                            .await;
                     }
-
-                    // Reset key signal, enable next scan
-                    SYNC_SIGNALS[self.id].reset();
                 }
+                Err(e) => error!("Peripheral message read error: {:?}", e),
             }
         }
     }
diff --git a/rmk/src/split/mod.rs b/rmk/src/split/mod.rs
index 1368770e..43c5fe70 100644
--- a/rmk/src/split/mod.rs
+++ b/rmk/src/split/mod.rs
@@ -1,9 +1,8 @@
-use embassy_sync::{
-    blocking_mutex::raw::CriticalSectionRawMutex, channel::Channel, signal::Signal,
-};
 use postcard::experimental::max_size::MaxSize;
 use serde::{Deserialize, Serialize};
 
+use crate::keyboard::KeyEvent;
+
 pub mod central;
 /// Common abstraction layer of split driver
 pub(crate) mod driver;
@@ -16,21 +15,12 @@ pub(crate) mod serial;
 /// Maximum size of a split message
 pub const SPLIT_MESSAGE_MAX_SIZE: usize = SplitMessage::POSTCARD_MAX_SIZE + 4;
 
-/// Channels for synchronization between central and peripheral threads
-const SYNC_SIGNAL_VALUE: Signal<CriticalSectionRawMutex, KeySyncSignal> = Signal::new();
-pub(crate) static SYNC_SIGNALS: [Signal<CriticalSectionRawMutex, KeySyncSignal>; 4] =
-    [SYNC_SIGNAL_VALUE; 4];
-pub(crate) static SCAN_SIGNAL: Signal<CriticalSectionRawMutex, KeySyncSignal> = SYNC_SIGNAL_VALUE;
-const SYNC_CHANNEL_VALUE: Channel<CriticalSectionRawMutex, KeySyncMessage, 8> = Channel::new();
-pub(crate) static CENTRAL_SYNC_CHANNELS: [Channel<CriticalSectionRawMutex, KeySyncMessage, 8>; 4] =
-    [SYNC_CHANNEL_VALUE; 4];
-
 /// Message used from central & peripheral communication
 #[derive(Serialize, Deserialize, Debug, Clone, Copy, MaxSize, defmt::Format)]
 #[repr(u8)]
 pub(crate) enum SplitMessage {
-    /// Activated key info (row, col, pressed), from peripheral to central
-    Key(u8, u8, bool),
+    /// Key event from peripheral to central
+    Key(KeyEvent),
     /// Led state, on/off
     LedState(bool),
 }
diff --git a/rmk/src/split/nrf/central.rs b/rmk/src/split/nrf/central.rs
index 4cc9391a..018d3bac 100644
--- a/rmk/src/split/nrf/central.rs
+++ b/rmk/src/split/nrf/central.rs
@@ -4,7 +4,7 @@ use defmt::{error, info};
 use embassy_executor::Spawner;
 use embassy_futures::{
     join::join,
-    select::{select, select4, Either, Either4},
+    select::{select, Either},
 };
 use embassy_sync::{
     blocking_mutex::raw::CriticalSectionRawMutex,
@@ -15,7 +15,6 @@ use embassy_usb::driver::Driver;
 use embedded_hal::digital::{InputPin, OutputPin};
 #[cfg(feature = "async_matrix")]
 use embedded_hal_async::digital::Wait;
-use futures::pin_mut;
 use heapless::FnvIndexMap;
 use nrf_softdevice::{
     ble::{
@@ -41,20 +40,19 @@ use crate::{
         server::BleServer,
         softdevice_task, BONDED_DEVICE_NUM,
     },
-    keyboard::{Keyboard, KeyboardReportMessage},
+    keyboard::{keyboard_report_channel, Keyboard},
     keymap::KeyMap,
     light::LightService,
+    run_usb_keyboard,
     split::{
         driver::{PeripheralMatrixMonitor, SplitDriverError, SplitReader},
         SplitMessage, SPLIT_MESSAGE_MAX_SIZE,
     },
     storage::{get_bond_info_key, Storage, StorageData},
-    usb::{wait_for_usb_configured, wait_for_usb_suspend, KeyboardUsbDevice, USB_DEVICE_ENABLED},
+    usb::{wait_for_usb_suspend, KeyboardUsbDevice, USB_DEVICE_ENABLED},
     via::process::VialService,
 };
 use crate::{debounce::DebouncerTrait, split::central::CentralMatrix};
-#[cfg(not(feature = "_no_usb"))]
-use crate::{keyboard::communication_task, keyboard_task, light::led_hid_task, via::vial_task};
 
 /// Gatt client used in split central to receive split message from peripherals
 #[nrf_softdevice::gatt_client(uuid = "4dd5fbaa-18e5-4b07-bf0a-353698659946")]
@@ -260,32 +258,31 @@ pub(crate) async fn initialize_ble_split_central_and_run<
     let debouncer: DefaultDebouncer<CENTRAL_COL, CENTRAL_ROW> = DefaultDebouncer::new();
 
     #[cfg(feature = "col2row")]
-    let matrix = CentralMatrix::<
+    let mut matrix = CentralMatrix::<
         In,
         Out,
         _,
-        TOTAL_ROW,
-        TOTAL_COL,
         CENTRAL_ROW_OFFSET,
         CENTRAL_COL_OFFSET,
         CENTRAL_ROW,
         CENTRAL_COL,
     >::new(input_pins, output_pins, debouncer);
     #[cfg(not(feature = "col2row"))]
-    let matrix = CentralMatrix::<
+    let mut matrix = CentralMatrix::<
         In,
         Out,
         _,
-        TOTAL_ROW,
-        TOTAL_COL,
         CENTRAL_ROW_OFFSET,
         CENTRAL_COL_OFFSET,
         CENTRAL_COL,
         CENTRAL_ROW,
     >::new(input_pins, output_pins, debouncer);
 
+    let keyboard_report_sender = keyboard_report_channel.sender();
+    let keyboard_report_receiver = keyboard_report_channel.receiver();
+
     // Keyboard services
-    let mut keyboard = Keyboard::new(matrix, &keymap);
+    let mut keyboard = Keyboard::new(&keymap, &keyboard_report_sender);
     #[cfg(not(feature = "_no_usb"))]
     let (mut usb_device, mut vial_service) = (
         KeyboardUsbDevice::new(usb_driver, keyboard_config.usb_config),
@@ -293,11 +290,6 @@ pub(crate) async fn initialize_ble_split_central_and_run<
     );
     let mut light_service = LightService::from_config(keyboard_config.light_config);
 
-    static keyboard_channel: Channel<CriticalSectionRawMutex, KeyboardReportMessage, 8> =
-        Channel::new();
-    let keyboard_report_sender = keyboard_channel.sender();
-    let keyboard_report_receiver = keyboard_channel.receiver();
-
     // Main loop
     loop {
         // Init BLE advertising data
@@ -318,66 +310,41 @@ pub(crate) async fn initialize_ble_split_central_and_run<
             // Check and run via USB first
             if USB_DEVICE_ENABLED.load(Ordering::SeqCst) {
                 // Run usb keyboard
-                let usb_fut = async {
-                    let usb_fut = usb_device.device.run();
-                    let keyboard_fut = keyboard_task(&mut keyboard, &keyboard_report_sender);
-                    let communication_fut = communication_task(
-                        &keyboard_report_receiver,
-                        &mut usb_device.keyboard_hid_writer,
-                        &mut usb_device.other_hid_writer,
-                    );
-                    let led_fut =
-                        led_hid_task(&mut usb_device.keyboard_hid_reader, &mut light_service);
-                    let via_fut = vial_task(&mut usb_device.via_hid, &mut vial_service);
-                    pin_mut!(usb_fut);
-                    pin_mut!(keyboard_fut);
-                    pin_mut!(led_fut);
-                    pin_mut!(via_fut);
-                    pin_mut!(communication_fut);
-
-                    match select4(
-                        select(usb_fut, keyboard_fut),
-                        via_fut,
-                        led_fut,
-                        communication_fut,
-                    )
-                    .await
-                    {
-                        Either4::First(_) => error!("Usb task or keyboard task exited"),
-                        Either4::Second(_) => error!("Storage or vial task has died"),
-                        Either4::Third(_) => error!("Led task has died"),
-                        Either4::Fourth(_) => error!("Communication task has died"),
-                    }
-                };
+                let usb_fut = run_usb_keyboard(
+                    &mut usb_device,
+                    &mut keyboard,
+                    &mut matrix,
+                    &mut storage,
+                    &mut light_service,
+                    &mut vial_service,
+                    &keyboard_report_receiver,
+                );
                 info!("Running USB keyboard!");
                 select(usb_fut, wait_for_usb_suspend()).await;
             }
 
             // Usb device have to be started to check if usb is configured
-            let usb_fut = usb_device.device.run();
-            let usb_configured = wait_for_usb_configured();
             info!("USB suspended, BLE Advertising");
 
             // Wait for BLE or USB connection
-            match select(adv_fut, select(usb_fut, usb_configured)).await {
+            match select(adv_fut, usb_device.wait_for_usb_configured()).await {
                 Either::First(re) => match re {
                     Ok(conn) => {
                         info!("Connected to BLE");
                         bonder.load_sys_attrs(&conn);
-                        let usb_configured = wait_for_usb_configured();
-                        let usb_fut = usb_device.device.run();
                         match select(
                             run_ble_keyboard(
                                 &conn,
                                 &ble_server,
                                 &mut keyboard,
+                                &mut matrix,
                                 &mut storage,
                                 &mut light_service,
+                                &mut vial_service,
                                 &mut keyboard_config.ble_battery_config,
                                 &keyboard_report_receiver,
-                                &keyboard_report_sender,
                             ),
-                            select(usb_fut, usb_configured),
+                            usb_device.wait_for_usb_configured(),
                         )
                         .await
                         {
@@ -406,11 +373,11 @@ pub(crate) async fn initialize_ble_split_central_and_run<
                     &conn,
                     &ble_server,
                     &mut keyboard,
+                    &mut matrix,
                     &mut storage,
                     &mut light_service,
                     &mut keyboard_config.ble_battery_config,
                     &keyboard_report_receiver,
-                    &keyboard_report_sender,
                 )
                 .await
             }
diff --git a/rmk/src/split/peripheral.rs b/rmk/src/split/peripheral.rs
index bc4f94c6..f4ad9714 100644
--- a/rmk/src/split/peripheral.rs
+++ b/rmk/src/split/peripheral.rs
@@ -2,6 +2,7 @@
 use crate::debounce::default_bouncer::DefaultDebouncer;
 #[cfg(feature = "rapid_debouncer")]
 use crate::debounce::fast_debouncer::RapidDebouncer;
+use crate::keyboard::key_event_channel;
 use crate::matrix::{Matrix, MatrixTrait};
 use embedded_hal::digital::{InputPin, OutputPin};
 #[cfg(feature = "async_matrix")]
@@ -98,7 +99,7 @@ pub(crate) async fn initialize_nrf_ble_split_peripheral_and_run<
     spawner: Spawner,
 ) -> ! {
     use defmt::info;
-    use embassy_futures::select::select;
+    use embassy_futures::select::select3;
     use nrf_softdevice::ble::gatt_server;
 
     use crate::split::nrf::peripheral::{
@@ -201,10 +202,10 @@ pub(crate) async fn initialize_nrf_ble_split_peripheral_and_run<
             },
         });
 
-        let mut peripheral =
-            SplitPeripheral::new(&mut matrix, BleSplitPeripheralDriver::new(&server, &conn));
+        let mut peripheral = SplitPeripheral::new(BleSplitPeripheralDriver::new(&server, &conn));
         let peripheral_fut = peripheral.run();
-        select(server_fut, peripheral_fut).await;
+        let matrix_fut = matrix.scan();
+        select3(matrix_fut, server_fut, peripheral_fut).await;
     }
 }
 
@@ -230,6 +231,7 @@ pub(crate) async fn initialize_serial_split_peripheral_and_run<
     #[cfg(not(feature = "col2row"))] output_pins: [Out; ROW],
     serial: S,
 ) -> ! {
+    use embassy_futures::select::select;
     // Keyboard matrix, use COL2ROW by default
     #[cfg(all(feature = "col2row", feature = "rapid_debouncer"))]
     let mut matrix =
@@ -244,22 +246,20 @@ pub(crate) async fn initialize_serial_split_peripheral_and_run<
     let mut matrix =
         Matrix::<_, _, DefaultDebouncer<COL, ROW>, COL, ROW>::new(input_pins, output_pins);
 
-    let mut peripheral = SplitPeripheral::new(&mut matrix, SerialSplitDriver::new(serial));
-    peripheral.run().await
+    let mut peripheral = SplitPeripheral::new(SerialSplitDriver::new(serial));
+    loop {
+        select(matrix.scan(), peripheral.run()).await;
+    }
 }
 
 /// The split peripheral instance.
-pub(crate) struct SplitPeripheral<'a, M: MatrixTrait, S: SplitWriter + SplitReader> {
-    matrix: &'a mut M,
+pub(crate) struct SplitPeripheral<S: SplitWriter + SplitReader> {
     split_driver: S,
 }
 
-impl<'a, M: MatrixTrait, S: SplitWriter + SplitReader> SplitPeripheral<'a, M, S> {
-    pub(crate) fn new(matrix: &'a mut M, split_driver: S) -> Self {
-        Self {
-            matrix,
-            split_driver,
-        }
+impl<S: SplitWriter + SplitReader> SplitPeripheral<S> {
+    pub(crate) fn new(split_driver: S) -> Self {
+        Self { split_driver }
     }
 
     /// Run the peripheral keyboard service.
@@ -268,63 +268,12 @@ impl<'a, M: MatrixTrait, S: SplitWriter + SplitReader> SplitPeripheral<'a, M, S>
     /// If also receives split messages from the central through `SplitReader`.
     pub(crate) async fn run(&mut self) -> ! {
         loop {
-            self.matrix.scan().await;
+            let e = key_event_channel.receive().await;
 
-            for row_idx in 0..self.matrix.get_row_num() {
-                for col_idx in 0..self.matrix.get_col_num() {
-                    let key_state = self.matrix.get_key_state(row_idx, col_idx);
-                    if key_state.changed {
-                        let _ = self
-                            .split_driver
-                            .write(&SplitMessage::Key(
-                                row_idx as u8,
-                                col_idx as u8,
-                                key_state.pressed,
-                            ))
-                            .await;
-                    }
-                }
-            }
+            self.split_driver.write(&SplitMessage::Key(e)).await.ok();
 
             // 10KHZ scan rate
             embassy_time::Timer::after_micros(10).await;
         }
     }
 }
-
-/// Run the peripheral keyboard service.
-///
-/// The peripheral uses the general matrix, does scanning and send the key events through `SplitWriter`.
-/// If also receives split messages from the central through `SplitReader`.
-pub(crate) async fn run_peripheral<
-    M: MatrixTrait,
-    S: SplitWriter + SplitReader,
-    const ROW: usize,
-    const COL: usize,
->(
-    matrix: &mut M,
-    mut split_driver: S,
-) -> ! {
-    loop {
-        matrix.scan().await;
-
-        // Send key events to host
-        for row_idx in 0..matrix.get_row_num() {
-            for col_idx in 0..matrix.get_col_num() {
-                let key_state = matrix.get_key_state(row_idx, col_idx);
-                if key_state.changed {
-                    let _ = split_driver
-                        .write(&SplitMessage::Key(
-                            row_idx as u8,
-                            col_idx as u8,
-                            key_state.pressed,
-                        ))
-                        .await;
-                }
-            }
-        }
-
-        // 10KHZ scan rate
-        embassy_time::Timer::after_micros(10).await;
-    }
-}
diff --git a/rmk/src/usb/mod.rs b/rmk/src/usb/mod.rs
index a74ec4a9..d6dcdffd 100644
--- a/rmk/src/usb/mod.rs
+++ b/rmk/src/usb/mod.rs
@@ -2,6 +2,7 @@ pub(crate) mod descriptor;
 
 use core::sync::atomic::{AtomicBool, Ordering};
 use defmt::info;
+use embassy_futures::select::select;
 use embassy_time::Timer;
 use embassy_usb::{
     class::hid::{Config, HidReaderWriter, HidWriter, ReportId, RequestHandler, State},
@@ -29,19 +30,8 @@ pub(crate) async fn wait_for_usb_suspend() {
         if suspended || (!enabled) {
             break;
         }
-        // Check usb suspended state every 10ms
-        Timer::after_millis(10).await
-    }
-}
-
-pub(crate) async fn wait_for_usb_configured() {
-    loop {
-        let suspended = USB_CONFIGURED.load(core::sync::atomic::Ordering::Acquire);
-        if suspended {
-            break;
-        }
-        // Check usb configured state every 10ms
-        Timer::after_millis(10).await
+        // Check usb suspended state every 500ms
+        Timer::after_millis(500).await
     }
 }
 
@@ -147,6 +137,21 @@ impl<D: Driver<'static>> KeyboardUsbDevice<'static, D> {
             via_hid: UsbHidReaderWriter::new(via_hid),
         }
     }
+
+    pub(crate) async fn wait_for_usb_configured(&mut self) {
+        let run = self.device.run();
+        let wait_for_configured = async {
+            loop {
+                let suspended = USB_CONFIGURED.load(core::sync::atomic::Ordering::Acquire);
+                if suspended {
+                    break;
+                }
+                // Check usb configured state every 500ms
+                Timer::after_millis(500).await
+            }
+        };
+        select(run, wait_for_configured).await;
+    }
 }
 
 struct UsbRequestHandler {}


Test patch: