diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 000000000..4db9edae7
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,41 @@
+* text=auto
+
+*.adoc     text
+*.html     text
+*.in       text
+*.json     text
+*.md       text
+*.proto    text
+*.py       text
+*.rs       text
+*.service  text
+*.sh       text
+*.toml     text
+*.txt      text
+*.x        text
+*.yml      text
+
+*.raw    binary
+*.bin    binary
+*.png    binary
+*.jpg    binary
+*.jpeg   binary
+*.gif    binary
+*.ico    binary
+*.mov    binary
+*.mp4    binary
+*.mp3    binary
+*.flv    binary
+*.fla    binary
+*.swf    binary
+*.gz     binary
+*.zip    binary
+*.7z     binary
+*.ttf    binary
+*.eot    binary
+*.woff   binary
+*.pyc    binary
+*.pdf    binary
+*.ez     binary
+*.bz2    binary
+*.swp    binary
\ No newline at end of file
diff --git a/.github/ci/crlf.sh b/.github/ci/crlf.sh
new file mode 100755
index 000000000..457510407
--- /dev/null
+++ b/.github/ci/crlf.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+## on push branch~=gh-readonly-queue/main/.*
+## on pull_request
+
+set -euo pipefail
+
+FILES_WITH_CRLF=$(find  ! -path "./.git/*" -not -type d | xargs file -N | (grep " CRLF " || true))
+
+if [ -z "$FILES_WITH_CRLF" ]; then
+  echo -e "No files with CRLF endings found."
+  exit 0
+else
+  NR_FILES=$(echo "$FILES_WITH_CRLF" | wc -l)
+  echo -e "ERROR: Found ${NR_FILES} files with CRLF endings."
+  echo "$FILES_WITH_CRLF"
+  exit "$NR_FILES"
+fi
\ No newline at end of file
diff --git a/.github/ci/doc.sh b/.github/ci/doc.sh
index 1402e742f..06c6fa00b 100755
--- a/.github/ci/doc.sh
+++ b/.github/ci/doc.sh
@@ -6,7 +6,7 @@ set -euo pipefail
 export RUSTUP_HOME=/ci/cache/rustup
 export CARGO_HOME=/ci/cache/cargo
 export CARGO_TARGET_DIR=/ci/cache/target
-export BUILDER_THREADS=6
+export BUILDER_THREADS=4
 export BUILDER_COMPRESS=true
 
 # force rustup to download the toolchain before starting building.
@@ -15,30 +15,32 @@ export BUILDER_COMPRESS=true
 # which makes rustup very sad
 rustc --version > /dev/null
 
-docserver-builder -i ./embassy-stm32 -o crates/embassy-stm32/git.zup
-docserver-builder -i ./embassy-boot/boot -o crates/embassy-boot/git.zup
-docserver-builder -i ./embassy-boot/nrf -o crates/embassy-boot-nrf/git.zup
-docserver-builder -i ./embassy-boot/rp -o crates/embassy-boot-rp/git.zup
-docserver-builder -i ./embassy-boot/stm32 -o crates/embassy-boot-stm32/git.zup
-docserver-builder -i ./embassy-embedded-hal -o crates/embassy-embedded-hal/git.zup
-docserver-builder -i ./embassy-executor -o crates/embassy-executor/git.zup
-docserver-builder -i ./embassy-futures -o crates/embassy-futures/git.zup
-docserver-builder -i ./embassy-lora -o crates/embassy-lora/git.zup
-docserver-builder -i ./embassy-net -o crates/embassy-net/git.zup
-docserver-builder -i ./embassy-net-driver -o crates/embassy-net-driver/git.zup
-docserver-builder -i ./embassy-net-driver-channel -o crates/embassy-net-driver-channel/git.zup
-docserver-builder -i ./embassy-nrf -o crates/embassy-nrf/git.zup
-docserver-builder -i ./embassy-rp -o crates/embassy-rp/git.zup
-docserver-builder -i ./embassy-sync -o crates/embassy-sync/git.zup
-docserver-builder -i ./embassy-time -o crates/embassy-time/git.zup
-docserver-builder -i ./embassy-usb -o crates/embassy-usb/git.zup
-docserver-builder -i ./embassy-usb-driver -o crates/embassy-usb-driver/git.zup
-docserver-builder -i ./embassy-usb-logger -o crates/embassy-usb-logger/git.zup
-docserver-builder -i ./cyw43 -o crates/cyw43/git.zup
-docserver-builder -i ./cyw43-pio -o crates/cyw43-pio/git.zup
-docserver-builder -i ./embassy-net-w5500 -o crates/embassy-net-w5500/git.zup
-docserver-builder -i ./embassy-stm32-wpan -o crates/embassy-stm32-wpan/git.zup
+docserver-builder -i ./embassy-stm32 -o webroot/crates/embassy-stm32/git.zup
+docserver-builder -i ./embassy-boot/boot -o webroot/crates/embassy-boot/git.zup
+docserver-builder -i ./embassy-boot/nrf -o webroot/crates/embassy-boot-nrf/git.zup
+docserver-builder -i ./embassy-boot/rp -o webroot/crates/embassy-boot-rp/git.zup
+docserver-builder -i ./embassy-boot/stm32 -o webroot/crates/embassy-boot-stm32/git.zup
+docserver-builder -i ./embassy-embedded-hal -o webroot/crates/embassy-embedded-hal/git.zup
+docserver-builder -i ./embassy-executor -o webroot/crates/embassy-executor/git.zup
+docserver-builder -i ./embassy-futures -o webroot/crates/embassy-futures/git.zup
+docserver-builder -i ./embassy-lora -o webroot/crates/embassy-lora/git.zup
+docserver-builder -i ./embassy-net -o webroot/crates/embassy-net/git.zup
+docserver-builder -i ./embassy-net-driver -o webroot/crates/embassy-net-driver/git.zup
+docserver-builder -i ./embassy-net-driver-channel -o webroot/crates/embassy-net-driver-channel/git.zup
+docserver-builder -i ./embassy-nrf -o webroot/crates/embassy-nrf/git.zup
+docserver-builder -i ./embassy-rp -o webroot/crates/embassy-rp/git.zup
+docserver-builder -i ./embassy-sync -o webroot/crates/embassy-sync/git.zup
+docserver-builder -i ./embassy-time -o webroot/crates/embassy-time/git.zup
+docserver-builder -i ./embassy-usb -o webroot/crates/embassy-usb/git.zup
+docserver-builder -i ./embassy-usb-driver -o webroot/crates/embassy-usb-driver/git.zup
+docserver-builder -i ./embassy-usb-logger -o webroot/crates/embassy-usb-logger/git.zup
+docserver-builder -i ./cyw43 -o webroot/crates/cyw43/git.zup
+docserver-builder -i ./cyw43-pio -o webroot/crates/cyw43-pio/git.zup
+docserver-builder -i ./embassy-net-w5500 -o webroot/crates/embassy-net-w5500/git.zup
+docserver-builder -i ./embassy-net-esp-hosted -o webroot/crates/embassy-net-esp-hosted/git.zup
+docserver-builder -i ./embassy-stm32-wpan -o webroot/crates/embassy-stm32-wpan/git.zup --output-static webroot/static
 
 export KUBECONFIG=/ci/secrets/kubeconfig.yml
 POD=$(kubectl -n embassy get po -l app=docserver -o jsonpath={.items[0].metadata.name})
-kubectl cp crates $POD:/data
+kubectl cp webroot/crates $POD:/data
+kubectl cp webroot/static $POD:/data
\ No newline at end of file
diff --git a/ci.sh b/ci.sh
index ec6304f16..6c10a0630 100755
--- a/ci.sh
+++ b/ci.sh
@@ -5,10 +5,6 @@ set -euo pipefail
 export RUSTFLAGS=-Dwarnings
 export DEFMT_LOG=trace,embassy_net_esp_hosted=debug,cyw43=info,cyw43_pio=info,smoltcp=info
 
-# needed by wifi examples
-export WIFI_NETWORK=x
-export WIFI_PASSWORD=x
-
 TARGET=$(rustc -vV | sed -n 's|host: ||p')
 
 BUILD_EXTRA=""
diff --git a/cyw43/Cargo.toml b/cyw43/Cargo.toml
index 61caa0272..50fb7c5db 100644
--- a/cyw43/Cargo.toml
+++ b/cyw43/Cargo.toml
@@ -11,7 +11,7 @@ log = ["dep:log"]
 firmware-logs = []
 
 [dependencies]
-embassy-time = { version = "0.1.0", path = "../embassy-time"}
+embassy-time = { version = "0.1.2", path = "../embassy-time"}
 embassy-sync = { version = "0.2.0", path = "../embassy-sync"}
 embassy-futures = { version = "0.1.0", path = "../embassy-futures"}
 embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-channel"}
@@ -24,7 +24,7 @@ cortex-m = "0.7.6"
 cortex-m-rt = "0.7.0"
 futures = { version = "0.3.17", default-features = false, features = ["async-await", "cfg-target-has-atomic", "unstable"] }
 
-embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.10" }
+embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.11" }
 num_enum = { version = "0.5.7", default-features = false }
 
 [package.metadata.embassy_docs]
diff --git a/cyw43/README.md b/cyw43/README.md
index e4a81410d..5b8f3cf40 100644
--- a/cyw43/README.md
+++ b/cyw43/README.md
@@ -30,7 +30,7 @@ TODO:
 ### Example 2: Create an access point (IP and credentials in the code)
 - `cargo run --release --bin wifi_ap_tcp_server`
 ### Example 3: Connect to an existing network and create a server
-- `WIFI_NETWORK=MyWifiNetwork WIFI_PASSWORD=MyWifiPassword cargo run --release --bin wifi_tcp_server`
+- `cargo run --release --bin wifi_tcp_server`
 
 After a few seconds, you should see that DHCP picks up an IP address like this
 ```
diff --git a/cyw43/src/runner.rs b/cyw43/src/runner.rs
index 5706696b4..1c187faa5 100644
--- a/cyw43/src/runner.rs
+++ b/cyw43/src/runner.rs
@@ -345,7 +345,9 @@ where
     }
 
     fn rx(&mut self, packet: &mut [u8]) {
-        let Some((sdpcm_header, payload)) = SdpcmHeader::parse(packet) else { return };
+        let Some((sdpcm_header, payload)) = SdpcmHeader::parse(packet) else {
+            return;
+        };
 
         self.update_credit(&sdpcm_header);
 
@@ -353,7 +355,9 @@ where
 
         match channel {
             CHANNEL_TYPE_CONTROL => {
-                let Some((cdc_header, response)) = CdcHeader::parse(payload) else { return; };
+                let Some((cdc_header, response)) = CdcHeader::parse(payload) else {
+                    return;
+                };
                 trace!("    {:?}", cdc_header);
 
                 if cdc_header.id == self.ioctl_id {
@@ -417,8 +421,12 @@ where
                     let status = event_packet.msg.status;
                     let event_payload = match evt_type {
                         Event::ESCAN_RESULT if status == EStatus::PARTIAL => {
-                            let Some((_, bss_info)) = ScanResults::parse(evt_data) else { return };
-                            let Some(bss_info) = BssInfo::parse(bss_info) else { return };
+                            let Some((_, bss_info)) = ScanResults::parse(evt_data) else {
+                                return;
+                            };
+                            let Some(bss_info) = BssInfo::parse(bss_info) else {
+                                return;
+                            };
                             events::Payload::BssInfo(*bss_info)
                         }
                         Event::ESCAN_RESULT => events::Payload::None,
@@ -439,7 +447,9 @@ where
                 }
             }
             CHANNEL_TYPE_DATA => {
-                let Some((_, packet)) = BdcHeader::parse(payload) else { return };
+                let Some((_, packet)) = BdcHeader::parse(payload) else {
+                    return;
+                };
                 trace!("rx pkt {:02x}", Bytes(&packet[..packet.len().min(48)]));
 
                 match self.ch.try_rx_buf() {
diff --git a/embassy-embedded-hal/Cargo.toml b/embassy-embedded-hal/Cargo.toml
index 35c70bb63..bba3d48be 100644
--- a/embassy-embedded-hal/Cargo.toml
+++ b/embassy-embedded-hal/Cargo.toml
@@ -15,15 +15,18 @@ target = "x86_64-unknown-linux-gnu"
 std = []
 # Enable nightly-only features
 nightly = ["embassy-futures", "embedded-hal-async", "embedded-storage-async"]
+time = ["dep:embassy-time"]
+default = ["time"]
 
 [dependencies]
 embassy-futures = { version = "0.1.0", path = "../embassy-futures", optional = true }
 embassy-sync = { version = "0.2.0", path = "../embassy-sync" }
+embassy-time = { version = "0.1.2", path = "../embassy-time", optional = true }
 embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = [
     "unproven",
 ] }
-embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.10" }
-embedded-hal-async = { version = "=0.2.0-alpha.1", optional = true }
+embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.11" }
+embedded-hal-async = { version = "=0.2.0-alpha.2", optional = true }
 embedded-storage = "0.3.0"
 embedded-storage-async = { version = "0.4.0", optional = true }
 nb = "1.0.0"
diff --git a/embassy-embedded-hal/src/adapter/blocking_async.rs b/embassy-embedded-hal/src/adapter/blocking_async.rs
index b996d6a75..98ae2b02c 100644
--- a/embassy-embedded-hal/src/adapter/blocking_async.rs
+++ b/embassy-embedded-hal/src/adapter/blocking_async.rs
@@ -74,7 +74,21 @@ where
     E: embedded_hal_1::spi::Error + 'static,
     T: blocking::spi::Transfer<u8, Error = E> + blocking::spi::Write<u8, Error = E>,
 {
-    async fn transfer<'a>(&'a mut self, read: &'a mut [u8], write: &'a [u8]) -> Result<(), Self::Error> {
+    async fn flush(&mut self) -> Result<(), Self::Error> {
+        Ok(())
+    }
+
+    async fn write(&mut self, data: &[u8]) -> Result<(), Self::Error> {
+        self.wrapped.write(data)?;
+        Ok(())
+    }
+
+    async fn read(&mut self, data: &mut [u8]) -> Result<(), Self::Error> {
+        self.wrapped.transfer(data)?;
+        Ok(())
+    }
+
+    async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> {
         // Ensure we write the expected bytes
         for i in 0..core::cmp::min(read.len(), write.len()) {
             read[i] = write[i].clone();
@@ -83,38 +97,7 @@ where
         Ok(())
     }
 
-    async fn transfer_in_place<'a>(&'a mut self, _: &'a mut [u8]) -> Result<(), Self::Error> {
-        todo!()
-    }
-}
-
-impl<T, E> embedded_hal_async::spi::SpiBusFlush for BlockingAsync<T>
-where
-    E: embedded_hal_1::spi::Error + 'static,
-    T: blocking::spi::Transfer<u8, Error = E> + blocking::spi::Write<u8, Error = E>,
-{
-    async fn flush(&mut self) -> Result<(), Self::Error> {
-        Ok(())
-    }
-}
-
-impl<T, E> embedded_hal_async::spi::SpiBusWrite<u8> for BlockingAsync<T>
-where
-    E: embedded_hal_1::spi::Error + 'static,
-    T: blocking::spi::Transfer<u8, Error = E> + blocking::spi::Write<u8, Error = E>,
-{
-    async fn write(&mut self, data: &[u8]) -> Result<(), Self::Error> {
-        self.wrapped.write(data)?;
-        Ok(())
-    }
-}
-
-impl<T, E> embedded_hal_async::spi::SpiBusRead<u8> for BlockingAsync<T>
-where
-    E: embedded_hal_1::spi::Error + 'static,
-    T: blocking::spi::Transfer<u8, Error = E> + blocking::spi::Write<u8, Error = E>,
-{
-    async fn read(&mut self, data: &mut [u8]) -> Result<(), Self::Error> {
+    async fn transfer_in_place(&mut self, data: &mut [u8]) -> Result<(), Self::Error> {
         self.wrapped.transfer(data)?;
         Ok(())
     }
diff --git a/embassy-embedded-hal/src/adapter/yielding_async.rs b/embassy-embedded-hal/src/adapter/yielding_async.rs
index f51e4076f..fe9c9c341 100644
--- a/embassy-embedded-hal/src/adapter/yielding_async.rs
+++ b/embassy-embedded-hal/src/adapter/yielding_async.rs
@@ -69,54 +69,39 @@ where
     type Error = T::Error;
 }
 
-impl<T> embedded_hal_async::spi::SpiBus<u8> for YieldingAsync<T>
+impl<T, Word: 'static + Copy> embedded_hal_async::spi::SpiBus<Word> for YieldingAsync<T>
 where
-    T: embedded_hal_async::spi::SpiBus,
-{
-    async fn transfer<'a>(&'a mut self, read: &'a mut [u8], write: &'a [u8]) -> Result<(), Self::Error> {
-        self.wrapped.transfer(read, write).await?;
-        yield_now().await;
-        Ok(())
-    }
-
-    async fn transfer_in_place<'a>(&'a mut self, words: &'a mut [u8]) -> Result<(), Self::Error> {
-        self.wrapped.transfer_in_place(words).await?;
-        yield_now().await;
-        Ok(())
-    }
-}
-
-impl<T> embedded_hal_async::spi::SpiBusFlush for YieldingAsync<T>
-where
-    T: embedded_hal_async::spi::SpiBusFlush,
+    T: embedded_hal_async::spi::SpiBus<Word>,
 {
     async fn flush(&mut self) -> Result<(), Self::Error> {
         self.wrapped.flush().await?;
         yield_now().await;
         Ok(())
     }
-}
 
-impl<T> embedded_hal_async::spi::SpiBusWrite<u8> for YieldingAsync<T>
-where
-    T: embedded_hal_async::spi::SpiBusWrite<u8>,
-{
-    async fn write(&mut self, data: &[u8]) -> Result<(), Self::Error> {
+    async fn write(&mut self, data: &[Word]) -> Result<(), Self::Error> {
         self.wrapped.write(data).await?;
         yield_now().await;
         Ok(())
     }
-}
 
-impl<T> embedded_hal_async::spi::SpiBusRead<u8> for YieldingAsync<T>
-where
-    T: embedded_hal_async::spi::SpiBusRead<u8>,
-{
-    async fn read(&mut self, data: &mut [u8]) -> Result<(), Self::Error> {
+    async fn read(&mut self, data: &mut [Word]) -> Result<(), Self::Error> {
         self.wrapped.read(data).await?;
         yield_now().await;
         Ok(())
     }
+
+    async fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> {
+        self.wrapped.transfer(read, write).await?;
+        yield_now().await;
+        Ok(())
+    }
+
+    async fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error> {
+        self.wrapped.transfer_in_place(words).await?;
+        yield_now().await;
+        Ok(())
+    }
 }
 
 ///
diff --git a/embassy-embedded-hal/src/shared_bus/asynch/spi.rs b/embassy-embedded-hal/src/shared_bus/asynch/spi.rs
index b5549a6cd..030392183 100644
--- a/embassy-embedded-hal/src/shared_bus/asynch/spi.rs
+++ b/embassy-embedded-hal/src/shared_bus/asynch/spi.rs
@@ -56,62 +56,6 @@ where
     type Error = SpiDeviceError<BUS::Error, CS::Error>;
 }
 
-impl<M, BUS, CS> spi::SpiDeviceRead for SpiDevice<'_, M, BUS, CS>
-where
-    M: RawMutex,
-    BUS: spi::SpiBusRead,
-    CS: OutputPin,
-{
-    async fn read_transaction(&mut self, operations: &mut [&mut [u8]]) -> Result<(), Self::Error> {
-        let mut bus = self.bus.lock().await;
-        self.cs.set_low().map_err(SpiDeviceError::Cs)?;
-
-        let op_res: Result<(), BUS::Error> = try {
-            for buf in operations {
-                bus.read(buf).await?;
-            }
-        };
-
-        // On failure, it's important to still flush and deassert CS.
-        let flush_res = bus.flush().await;
-        let cs_res = self.cs.set_high();
-
-        let op_res = op_res.map_err(SpiDeviceError::Spi)?;
-        flush_res.map_err(SpiDeviceError::Spi)?;
-        cs_res.map_err(SpiDeviceError::Cs)?;
-
-        Ok(op_res)
-    }
-}
-
-impl<M, BUS, CS> spi::SpiDeviceWrite for SpiDevice<'_, M, BUS, CS>
-where
-    M: RawMutex,
-    BUS: spi::SpiBusWrite,
-    CS: OutputPin,
-{
-    async fn write_transaction(&mut self, operations: &[&[u8]]) -> Result<(), Self::Error> {
-        let mut bus = self.bus.lock().await;
-        self.cs.set_low().map_err(SpiDeviceError::Cs)?;
-
-        let op_res: Result<(), BUS::Error> = try {
-            for buf in operations {
-                bus.write(buf).await?;
-            }
-        };
-
-        // On failure, it's important to still flush and deassert CS.
-        let flush_res = bus.flush().await;
-        let cs_res = self.cs.set_high();
-
-        let op_res = op_res.map_err(SpiDeviceError::Spi)?;
-        flush_res.map_err(SpiDeviceError::Spi)?;
-        cs_res.map_err(SpiDeviceError::Cs)?;
-
-        Ok(op_res)
-    }
-}
-
 impl<M, BUS, CS> spi::SpiDevice for SpiDevice<'_, M, BUS, CS>
 where
     M: RawMutex,
@@ -129,6 +73,12 @@ where
                     Operation::Write(buf) => bus.write(buf).await?,
                     Operation::Transfer(read, write) => bus.transfer(read, write).await?,
                     Operation::TransferInPlace(buf) => bus.transfer_in_place(buf).await?,
+                    #[cfg(not(feature = "time"))]
+                    Operation::DelayUs(_) => return Err(SpiDeviceError::DelayUsNotSupported),
+                    #[cfg(feature = "time")]
+                    Operation::DelayUs(us) => {
+                        embassy_time::Timer::after(embassy_time::Duration::from_micros(*us as _)).await
+                    }
                 }
             }
         };
@@ -172,64 +122,6 @@ where
     type Error = SpiDeviceError<BUS::Error, CS::Error>;
 }
 
-impl<M, BUS, CS> spi::SpiDeviceWrite for SpiDeviceWithConfig<'_, M, BUS, CS>
-where
-    M: RawMutex,
-    BUS: spi::SpiBusWrite + SetConfig,
-    CS: OutputPin,
-{
-    async fn write_transaction(&mut self, operations: &[&[u8]]) -> Result<(), Self::Error> {
-        let mut bus = self.bus.lock().await;
-        bus.set_config(&self.config);
-        self.cs.set_low().map_err(SpiDeviceError::Cs)?;
-
-        let op_res: Result<(), BUS::Error> = try {
-            for buf in operations {
-                bus.write(buf).await?;
-            }
-        };
-
-        // On failure, it's important to still flush and deassert CS.
-        let flush_res = bus.flush().await;
-        let cs_res = self.cs.set_high();
-
-        let op_res = op_res.map_err(SpiDeviceError::Spi)?;
-        flush_res.map_err(SpiDeviceError::Spi)?;
-        cs_res.map_err(SpiDeviceError::Cs)?;
-
-        Ok(op_res)
-    }
-}
-
-impl<M, BUS, CS> spi::SpiDeviceRead for SpiDeviceWithConfig<'_, M, BUS, CS>
-where
-    M: RawMutex,
-    BUS: spi::SpiBusRead + SetConfig,
-    CS: OutputPin,
-{
-    async fn read_transaction(&mut self, operations: &mut [&mut [u8]]) -> Result<(), Self::Error> {
-        let mut bus = self.bus.lock().await;
-        bus.set_config(&self.config);
-        self.cs.set_low().map_err(SpiDeviceError::Cs)?;
-
-        let op_res: Result<(), BUS::Error> = try {
-            for buf in operations {
-                bus.read(buf).await?;
-            }
-        };
-
-        // On failure, it's important to still flush and deassert CS.
-        let flush_res = bus.flush().await;
-        let cs_res = self.cs.set_high();
-
-        let op_res = op_res.map_err(SpiDeviceError::Spi)?;
-        flush_res.map_err(SpiDeviceError::Spi)?;
-        cs_res.map_err(SpiDeviceError::Cs)?;
-
-        Ok(op_res)
-    }
-}
-
 impl<M, BUS, CS> spi::SpiDevice for SpiDeviceWithConfig<'_, M, BUS, CS>
 where
     M: RawMutex,
@@ -248,6 +140,12 @@ where
                     Operation::Write(buf) => bus.write(buf).await?,
                     Operation::Transfer(read, write) => bus.transfer(read, write).await?,
                     Operation::TransferInPlace(buf) => bus.transfer_in_place(buf).await?,
+                    #[cfg(not(feature = "time"))]
+                    Operation::DelayUs(_) => return Err(SpiDeviceError::DelayUsNotSupported),
+                    #[cfg(feature = "time")]
+                    Operation::DelayUs(us) => {
+                        embassy_time::Timer::after(embassy_time::Duration::from_micros(*us as _)).await
+                    }
                 }
             }
         };
diff --git a/embassy-embedded-hal/src/shared_bus/blocking/spi.rs b/embassy-embedded-hal/src/shared_bus/blocking/spi.rs
index 22e013be9..6d03d6263 100644
--- a/embassy-embedded-hal/src/shared_bus/blocking/spi.rs
+++ b/embassy-embedded-hal/src/shared_bus/blocking/spi.rs
@@ -22,7 +22,7 @@ use core::cell::RefCell;
 use embassy_sync::blocking_mutex::raw::RawMutex;
 use embassy_sync::blocking_mutex::Mutex;
 use embedded_hal_1::digital::OutputPin;
-use embedded_hal_1::spi::{self, Operation, SpiBus, SpiBusRead, SpiBusWrite};
+use embedded_hal_1::spi::{self, Operation, SpiBus};
 
 use crate::shared_bus::SpiDeviceError;
 use crate::SetConfig;
@@ -48,58 +48,6 @@ where
     type Error = SpiDeviceError<BUS::Error, CS::Error>;
 }
 
-impl<BUS, M, CS> embedded_hal_1::spi::SpiDeviceRead for SpiDevice<'_, M, BUS, CS>
-where
-    M: RawMutex,
-    BUS: SpiBusRead,
-    CS: OutputPin,
-{
-    fn read_transaction(&mut self, operations: &mut [&mut [u8]]) -> Result<(), Self::Error> {
-        self.bus.lock(|bus| {
-            let mut bus = bus.borrow_mut();
-            self.cs.set_low().map_err(SpiDeviceError::Cs)?;
-
-            let op_res = operations.iter_mut().try_for_each(|buf| bus.read(buf));
-
-            // On failure, it's important to still flush and deassert CS.
-            let flush_res = bus.flush();
-            let cs_res = self.cs.set_high();
-
-            let op_res = op_res.map_err(SpiDeviceError::Spi)?;
-            flush_res.map_err(SpiDeviceError::Spi)?;
-            cs_res.map_err(SpiDeviceError::Cs)?;
-
-            Ok(op_res)
-        })
-    }
-}
-
-impl<BUS, M, CS> embedded_hal_1::spi::SpiDeviceWrite for SpiDevice<'_, M, BUS, CS>
-where
-    M: RawMutex,
-    BUS: SpiBusWrite,
-    CS: OutputPin,
-{
-    fn write_transaction(&mut self, operations: &[&[u8]]) -> Result<(), Self::Error> {
-        self.bus.lock(|bus| {
-            let mut bus = bus.borrow_mut();
-            self.cs.set_low().map_err(SpiDeviceError::Cs)?;
-
-            let op_res = operations.iter().try_for_each(|buf| bus.write(buf));
-
-            // On failure, it's important to still flush and deassert CS.
-            let flush_res = bus.flush();
-            let cs_res = self.cs.set_high();
-
-            let op_res = op_res.map_err(SpiDeviceError::Spi)?;
-            flush_res.map_err(SpiDeviceError::Spi)?;
-            cs_res.map_err(SpiDeviceError::Cs)?;
-
-            Ok(op_res)
-        })
-    }
-}
-
 impl<BUS, M, CS> embedded_hal_1::spi::SpiDevice for SpiDevice<'_, M, BUS, CS>
 where
     M: RawMutex,
@@ -116,6 +64,13 @@ where
                 Operation::Write(buf) => bus.write(buf),
                 Operation::Transfer(read, write) => bus.transfer(read, write),
                 Operation::TransferInPlace(buf) => bus.transfer_in_place(buf),
+                #[cfg(not(feature = "time"))]
+                Operation::DelayUs(_) => Err(SpiDeviceError::DelayUsNotSupported),
+                #[cfg(feature = "time")]
+                Operation::DelayUs(us) => {
+                    embassy_time::block_for(embassy_time::Duration::from_micros(*us as _));
+                    Ok(())
+                }
             });
 
             // On failure, it's important to still flush and deassert CS.
@@ -199,58 +154,6 @@ where
     type Error = SpiDeviceError<BUS::Error, CS::Error>;
 }
 
-impl<BUS, M, CS> embedded_hal_1::spi::SpiDeviceRead for SpiDeviceWithConfig<'_, M, BUS, CS>
-where
-    M: RawMutex,
-    BUS: SpiBusRead + SetConfig,
-    CS: OutputPin,
-{
-    fn read_transaction(&mut self, operations: &mut [&mut [u8]]) -> Result<(), Self::Error> {
-        self.bus.lock(|bus| {
-            let mut bus = bus.borrow_mut();
-            bus.set_config(&self.config);
-            self.cs.set_low().map_err(SpiDeviceError::Cs)?;
-
-            let op_res = operations.iter_mut().try_for_each(|buf| bus.read(buf));
-
-            // On failure, it's important to still flush and deassert CS.
-            let flush_res = bus.flush();
-            let cs_res = self.cs.set_high();
-
-            let op_res = op_res.map_err(SpiDeviceError::Spi)?;
-            flush_res.map_err(SpiDeviceError::Spi)?;
-            cs_res.map_err(SpiDeviceError::Cs)?;
-            Ok(op_res)
-        })
-    }
-}
-
-impl<BUS, M, CS> embedded_hal_1::spi::SpiDeviceWrite for SpiDeviceWithConfig<'_, M, BUS, CS>
-where
-    M: RawMutex,
-    BUS: SpiBusWrite + SetConfig,
-    CS: OutputPin,
-{
-    fn write_transaction(&mut self, operations: &[&[u8]]) -> Result<(), Self::Error> {
-        self.bus.lock(|bus| {
-            let mut bus = bus.borrow_mut();
-            bus.set_config(&self.config);
-            self.cs.set_low().map_err(SpiDeviceError::Cs)?;
-
-            let op_res = operations.iter().try_for_each(|buf| bus.write(buf));
-
-            // On failure, it's important to still flush and deassert CS.
-            let flush_res = bus.flush();
-            let cs_res = self.cs.set_high();
-
-            let op_res = op_res.map_err(SpiDeviceError::Spi)?;
-            flush_res.map_err(SpiDeviceError::Spi)?;
-            cs_res.map_err(SpiDeviceError::Cs)?;
-            Ok(op_res)
-        })
-    }
-}
-
 impl<BUS, M, CS> embedded_hal_1::spi::SpiDevice for SpiDeviceWithConfig<'_, M, BUS, CS>
 where
     M: RawMutex,
@@ -268,6 +171,13 @@ where
                 Operation::Write(buf) => bus.write(buf),
                 Operation::Transfer(read, write) => bus.transfer(read, write),
                 Operation::TransferInPlace(buf) => bus.transfer_in_place(buf),
+                #[cfg(not(feature = "time"))]
+                Operation::DelayUs(_) => Err(SpiDeviceError::DelayUsNotSupported),
+                #[cfg(feature = "time")]
+                Operation::DelayUs(us) => {
+                    embassy_time::block_for(embassy_time::Duration::from_micros(*us as _));
+                    Ok(())
+                }
             });
 
             // On failure, it's important to still flush and deassert CS.
diff --git a/embassy-embedded-hal/src/shared_bus/mod.rs b/embassy-embedded-hal/src/shared_bus/mod.rs
index 617d921e9..79a90bd52 100644
--- a/embassy-embedded-hal/src/shared_bus/mod.rs
+++ b/embassy-embedded-hal/src/shared_bus/mod.rs
@@ -30,11 +30,14 @@ where
 /// Error returned by SPI device implementations in this crate.
 #[derive(Copy, Clone, Eq, PartialEq, Debug)]
 #[cfg_attr(feature = "defmt", derive(defmt::Format))]
+#[non_exhaustive]
 pub enum SpiDeviceError<BUS, CS> {
     /// An operation on the inner SPI bus failed.
     Spi(BUS),
     /// Setting the value of the Chip Select (CS) pin failed.
     Cs(CS),
+    /// DelayUs operations are not supported when the `time` Cargo feature is not enabled.
+    DelayUsNotSupported,
 }
 
 impl<BUS, CS> spi::Error for SpiDeviceError<BUS, CS>
@@ -46,6 +49,7 @@ where
         match self {
             Self::Spi(e) => e.kind(),
             Self::Cs(_) => spi::ErrorKind::Other,
+            Self::DelayUsNotSupported => spi::ErrorKind::Other,
         }
     }
 }
diff --git a/embassy-executor/Cargo.toml b/embassy-executor/Cargo.toml
index 1e5494ef8..590718e3e 100644
--- a/embassy-executor/Cargo.toml
+++ b/embassy-executor/Cargo.toml
@@ -61,8 +61,8 @@ log = { version = "0.4.14", optional = true }
 rtos-trace = { version = "0.1.2", optional = true }
 
 futures-util = { version = "0.3.17", default-features = false }
-embassy-macros  = { version = "0.2.0", path = "../embassy-macros" }
-embassy-time  = { version = "0.1.0", path = "../embassy-time", optional = true}
+embassy-macros = { version = "0.2.0", path = "../embassy-macros" }
+embassy-time = { version = "0.1.2", path = "../embassy-time", optional = true}
 atomic-polyfill = "1.0.1"
 critical-section = "1.1"
 static_cell = "1.1"
diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs
index bd0cff26b..f3760f589 100644
--- a/embassy-executor/src/raw/mod.rs
+++ b/embassy-executor/src/raw/mod.rs
@@ -165,6 +165,9 @@ impl<F: Future + 'static> TaskStorage<F> {
             Poll::Ready(_) => {
                 this.future.drop_in_place();
                 this.raw.state.fetch_and(!STATE_SPAWNED, Ordering::AcqRel);
+
+                #[cfg(feature = "integrated-timers")]
+                this.raw.expires_at.set(Instant::MAX);
             }
             Poll::Pending => {}
         }
diff --git a/embassy-hal-common/src/peripheral.rs b/embassy-hal-common/src/peripheral.rs
index c7133bac6..38b4c452e 100644
--- a/embassy-hal-common/src/peripheral.rs
+++ b/embassy-hal-common/src/peripheral.rs
@@ -161,7 +161,7 @@ pub trait Peripheral: Sized {
     }
 }
 
-impl<'b, T: Deref> Peripheral for T
+impl<'b, T: DerefMut> Peripheral for T
 where
     T::Target: Peripheral,
 {
diff --git a/embassy-lora/Cargo.toml b/embassy-lora/Cargo.toml
index 05b6fa2d0..e4524af5b 100644
--- a/embassy-lora/Cargo.toml
+++ b/embassy-lora/Cargo.toml
@@ -20,11 +20,11 @@ defmt = ["dep:defmt", "lorawan-device/defmt"]
 defmt = { version = "0.3", optional = true }
 log = { version = "0.4.14", optional = true }
 
-embassy-time = { version = "0.1.0", path = "../embassy-time" }
+embassy-time = { version = "0.1.2", path = "../embassy-time" }
 embassy-sync = { version = "0.2.0", path = "../embassy-sync" }
 embassy-stm32 = { version = "0.1.0", path = "../embassy-stm32", default-features = false, optional = true }
-embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.10" }
-embedded-hal-async = { version = "=0.2.0-alpha.1" }
+embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.11" }
+embedded-hal-async = { version = "=0.2.0-alpha.2" }
 embassy-hal-common = { version = "0.1.0", path = "../embassy-hal-common", default-features = false }
 futures = { version = "0.3.17", default-features = false, features = [ "async-await" ] }
 embedded-hal = { version = "0.2", features = ["unproven"] }
@@ -32,3 +32,6 @@ bit_field = { version = "0.10" }
 
 lora-phy = { version = "1" }
 lorawan-device = { version = "0.10.0", default-features = false, features = ["async"] }
+
+[patch.crates-io]
+lora-phy = { git = "https://github.com/embassy-rs/lora-phy", rev = "ad289428fd44b02788e2fa2116445cc8f640a265" }
diff --git a/embassy-net-driver/src/lib.rs b/embassy-net-driver/src/lib.rs
index 4149bf4a4..09def20c4 100644
--- a/embassy-net-driver/src/lib.rs
+++ b/embassy-net-driver/src/lib.rs
@@ -164,6 +164,9 @@ pub enum Medium {
     ///
     /// Examples of devices of this type are the Linux `tun`, PPP interfaces, VPNs in tun (layer 3) mode.
     Ip,
+
+    /// IEEE 802_15_4 medium
+    Ieee802154,
 }
 
 impl Default for Medium {
diff --git a/embassy-net-esp-hosted/Cargo.toml b/embassy-net-esp-hosted/Cargo.toml
index a7e18ee09..0053c49a4 100644
--- a/embassy-net-esp-hosted/Cargo.toml
+++ b/embassy-net-esp-hosted/Cargo.toml
@@ -7,14 +7,20 @@ edition = "2021"
 defmt = { version = "0.3", optional = true }
 log = { version = "0.4.14", optional = true }
 
-embassy-time = { version = "0.1.0", path = "../embassy-time" }
+embassy-time = { version = "0.1.2", path = "../embassy-time" }
 embassy-sync = { version = "0.2.0", path = "../embassy-sync"}
 embassy-futures = { version = "0.1.0", path = "../embassy-futures"}
 embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-channel"}
 
-embedded-hal = { version = "1.0.0-alpha.10" }
-embedded-hal-async = { version = "=0.2.0-alpha.1" }
+embedded-hal = { version = "1.0.0-alpha.11" }
+embedded-hal-async = { version = "=0.2.0-alpha.2" }
 
 noproto = { git="https://github.com/embassy-rs/noproto", default-features = false, features = ["derive"] }
 #noproto = { version = "0.1", path = "/home/dirbaio/noproto", default-features = false, features = ["derive"] }
 heapless = "0.7.16"
+
+[package.metadata.embassy_docs]
+src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-esp-hosted-v$VERSION/embassy-net-esp-hosted/src/"
+src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-esp-hosted/src/"
+target = "thumbv7em-none-eabi"
+features = ["defmt"]
\ No newline at end of file
diff --git a/embassy-net-esp-hosted/src/control.rs b/embassy-net-esp-hosted/src/control.rs
index fce82ade7..79f8cde7b 100644
--- a/embassy-net-esp-hosted/src/control.rs
+++ b/embassy-net-esp-hosted/src/control.rs
@@ -54,7 +54,9 @@ impl<'a> Control<'a> {
             })),
         };
         let resp = self.ioctl(req).await;
-        let proto::CtrlMsgPayload::RespConnectAp(resp) = resp.payload.unwrap() else { panic!("unexpected resp") };
+        let proto::CtrlMsgPayload::RespConnectAp(resp) = resp.payload.unwrap() else {
+            panic!("unexpected resp")
+        };
         debug!("======= {:?}", Debug2Format(&resp));
         assert_eq!(resp.resp, 0);
         self.state_ch.set_link_state(LinkState::Up);
@@ -71,7 +73,9 @@ impl<'a> Control<'a> {
             )),
         };
         let resp = self.ioctl(req).await;
-        let proto::CtrlMsgPayload::RespGetMacAddress(resp) = resp.payload.unwrap() else { panic!("unexpected resp") };
+        let proto::CtrlMsgPayload::RespGetMacAddress(resp) = resp.payload.unwrap() else {
+            panic!("unexpected resp")
+        };
         assert_eq!(resp.resp, 0);
 
         // WHY IS THIS A STRING? WHYYYY
@@ -100,7 +104,9 @@ impl<'a> Control<'a> {
             payload: Some(proto::CtrlMsgPayload::ReqSetWifiMode(proto::CtrlMsgReqSetMode { mode })),
         };
         let resp = self.ioctl(req).await;
-        let proto::CtrlMsgPayload::RespSetWifiMode(resp) = resp.payload.unwrap() else { panic!("unexpected resp") };
+        let proto::CtrlMsgPayload::RespSetWifiMode(resp) = resp.payload.unwrap() else {
+            panic!("unexpected resp")
+        };
         assert_eq!(resp.resp, 0);
     }
 
diff --git a/embassy-net-esp-hosted/src/lib.rs b/embassy-net-esp-hosted/src/lib.rs
index 44dfbe89c..a35adfca0 100644
--- a/embassy-net-esp-hosted/src/lib.rs
+++ b/embassy-net-esp-hosted/src/lib.rs
@@ -311,14 +311,14 @@ where
     fn handle_event(&self, data: &[u8]) {
         let Ok(event) = noproto::read::<CtrlMsg>(data) else {
             warn!("failed to parse event");
-            return
+            return;
         };
 
         debug!("event: {:?}", &event);
 
         let Some(payload) = &event.payload else {
             warn!("event without payload?");
-            return
+            return;
         };
 
         match payload {
diff --git a/embassy-net-w5500/Cargo.toml b/embassy-net-w5500/Cargo.toml
index 37d15c7ac..8972b814a 100644
--- a/embassy-net-w5500/Cargo.toml
+++ b/embassy-net-w5500/Cargo.toml
@@ -8,11 +8,11 @@ license = "MIT OR Apache-2.0"
 edition = "2021"
 
 [dependencies]
-embedded-hal = { version = "1.0.0-alpha.10" }
-embedded-hal-async = { version = "=0.2.0-alpha.1" }
-embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-channel"}
-embassy-time = { version = "0.1.0" }
-embassy-futures = { version = "0.1.0" }
+embedded-hal = { version = "1.0.0-alpha.11" }
+embedded-hal-async = { version = "=0.2.0-alpha.2" }
+embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-channel" }
+embassy-time = { version = "0.1.2", path = "../embassy-time" }
+embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
 defmt = { version = "0.3", optional = true }
 
 [package.metadata.embassy_docs]
diff --git a/embassy-net-w5500/src/spi.rs b/embassy-net-w5500/src/spi.rs
index 6cd52c44d..07749d6be 100644
--- a/embassy-net-w5500/src/spi.rs
+++ b/embassy-net-w5500/src/spi.rs
@@ -22,7 +22,11 @@ impl<SPI: SpiDevice> SpiInterface<SPI> {
         let address_phase = address.to_be_bytes();
         let control_phase = [(block as u8) << 3 | 0b0000_0100];
         let data_phase = data;
-        let operations = &[&address_phase[..], &control_phase, &data_phase];
-        self.0.write_transaction(operations).await
+        let operations = &mut [
+            Operation::Write(&address_phase[..]),
+            Operation::Write(&control_phase),
+            Operation::Write(&data_phase),
+        ];
+        self.0.transaction(operations).await
     }
 }
diff --git a/embassy-net/Cargo.toml b/embassy-net/Cargo.toml
index e89039daa..9b6a11c16 100644
--- a/embassy-net/Cargo.toml
+++ b/embassy-net/Cargo.toml
@@ -37,6 +37,7 @@ proto-ipv4 = ["smoltcp/proto-ipv4"]
 proto-ipv6 = ["smoltcp/proto-ipv6"]
 medium-ethernet = ["smoltcp/medium-ethernet"]
 medium-ip = ["smoltcp/medium-ip"]
+medium-ieee802154 = ["smoltcp/medium-ieee802154"]
 igmp = ["smoltcp/proto-igmp"]
 
 [dependencies]
@@ -50,7 +51,7 @@ smoltcp = { version = "0.10.0", default-features = false, features = [
 ] }
 
 embassy-net-driver = { version = "0.1.0", path = "../embassy-net-driver" }
-embassy-time = { version = "0.1.0", path = "../embassy-time" }
+embassy-time = { version = "0.1.2", path = "../embassy-time" }
 embassy-sync = { version = "0.2.0", path = "../embassy-sync" }
 embedded-io = { version = "0.4.0", optional = true }
 
diff --git a/embassy-net/src/device.rs b/embassy-net/src/device.rs
index 4513c86d3..d29ab8970 100644
--- a/embassy-net/src/device.rs
+++ b/embassy-net/src/device.rs
@@ -51,6 +51,8 @@ where
             Medium::Ethernet => phy::Medium::Ethernet,
             #[cfg(feature = "medium-ip")]
             Medium::Ip => phy::Medium::Ip,
+            #[cfg(feature = "medium-ieee802154")]
+            Medium::Ieee802154 => phy::Medium::Ieee802154,
             #[allow(unreachable_patterns)]
             _ => panic!(
                 "Unsupported medium {:?}. Make sure to enable it in embassy-net's Cargo features.",
diff --git a/embassy-net/src/lib.rs b/embassy-net/src/lib.rs
index 840d7a09a..81c751a2c 100644
--- a/embassy-net/src/lib.rs
+++ b/embassy-net/src/lib.rs
@@ -24,9 +24,11 @@ use embassy_net_driver::{Driver, LinkState, Medium};
 use embassy_sync::waitqueue::WakerRegistration;
 use embassy_time::{Instant, Timer};
 use futures::pin_mut;
+#[allow(unused_imports)]
 use heapless::Vec;
 #[cfg(feature = "igmp")]
 pub use smoltcp::iface::MulticastError;
+#[allow(unused_imports)]
 use smoltcp::iface::{Interface, SocketHandle, SocketSet, SocketStorage};
 #[cfg(feature = "dhcpv4")]
 use smoltcp::socket::dhcpv4::{self, RetryConfig};
@@ -34,7 +36,9 @@ use smoltcp::socket::dhcpv4::{self, RetryConfig};
 pub use smoltcp::wire::IpListenEndpoint;
 #[cfg(feature = "medium-ethernet")]
 pub use smoltcp::wire::{EthernetAddress, HardwareAddress};
-pub use smoltcp::wire::{IpAddress, IpCidr};
+#[cfg(feature = "medium-ieee802154")]
+pub use smoltcp::wire::{HardwareAddress, Ieee802154Address};
+pub use smoltcp::wire::{IpAddress, IpCidr, IpEndpoint};
 #[cfg(feature = "proto-ipv4")]
 pub use smoltcp::wire::{Ipv4Address, Ipv4Cidr};
 #[cfg(feature = "proto-ipv6")]
@@ -232,7 +236,7 @@ impl<D: Driver + 'static> Stack<D> {
         resources: &'static mut StackResources<SOCK>,
         random_seed: u64,
     ) -> Self {
-        #[cfg(feature = "medium-ethernet")]
+        #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
         let medium = device.capabilities().medium;
 
         let hardware_addr = match medium {
@@ -240,6 +244,8 @@ impl<D: Driver + 'static> Stack<D> {
             Medium::Ethernet => HardwareAddress::Ethernet(EthernetAddress(device.ethernet_address())),
             #[cfg(feature = "medium-ip")]
             Medium::Ip => HardwareAddress::Ip,
+            #[cfg(feature = "medium-ieee802154")]
+            Medium::Ieee802154 => HardwareAddress::Ieee802154(Ieee802154Address::Absent),
             #[allow(unreachable_patterns)]
             _ => panic!(
                 "Unsupported medium {:?}. Make sure to enable it in embassy-net's Cargo features.",
@@ -262,6 +268,7 @@ impl<D: Driver + 'static> Stack<D> {
 
         let next_local_port = (random_seed % (LOCAL_PORT_MAX - LOCAL_PORT_MIN) as u64) as u16 + LOCAL_PORT_MIN;
 
+        #[cfg_attr(feature = "medium-ieee802154", allow(unused_mut))]
         let mut socket = SocketStack {
             sockets,
             iface,
@@ -269,6 +276,7 @@ impl<D: Driver + 'static> Stack<D> {
             next_local_port,
         };
 
+        #[cfg_attr(feature = "medium-ieee802154", allow(unused_mut))]
         let mut inner = Inner {
             device,
             link_up: false,
@@ -287,6 +295,9 @@ impl<D: Driver + 'static> Stack<D> {
             dns_waker: WakerRegistration::new(),
         };
 
+        #[cfg(feature = "medium-ieee802154")]
+        let _ = config;
+
         #[cfg(feature = "proto-ipv4")]
         match config.ipv4 {
             ConfigV4::Static(config) => {
@@ -479,30 +490,78 @@ impl<D: Driver + 'static> Stack<D> {
 }
 
 #[cfg(feature = "igmp")]
-impl<D: Driver + smoltcp::phy::Device + 'static> Stack<D> {
+impl<D: Driver + 'static> Stack<D> {
     /// Join a multicast group.
-    pub fn join_multicast_group<T>(&self, addr: T) -> Result<bool, MulticastError>
+    pub async fn join_multicast_group<T>(&self, addr: T) -> Result<bool, MulticastError>
+    where
+        T: Into<IpAddress>,
+    {
+        let addr = addr.into();
+
+        poll_fn(move |cx| self.poll_join_multicast_group(addr, cx)).await
+    }
+
+    /// Join a multicast group.
+    ///
+    /// When the send queue is full, this method will return `Poll::Pending`
+    /// and register the current task to be notified when the queue has space available.
+    pub fn poll_join_multicast_group<T>(&self, addr: T, cx: &mut Context<'_>) -> Poll<Result<bool, MulticastError>>
     where
         T: Into<IpAddress>,
     {
         let addr = addr.into();
 
         self.with_mut(|s, i| {
-            s.iface
-                .join_multicast_group(&mut i.device, addr, instant_to_smoltcp(Instant::now()))
+            let mut smoldev = DriverAdapter {
+                cx: Some(cx),
+                inner: &mut i.device,
+            };
+
+            match s
+                .iface
+                .join_multicast_group(&mut smoldev, addr, instant_to_smoltcp(Instant::now()))
+            {
+                Ok(announce_sent) => Poll::Ready(Ok(announce_sent)),
+                Err(MulticastError::Exhausted) => Poll::Pending,
+                Err(other) => Poll::Ready(Err(other)),
+            }
         })
     }
 
     /// Leave a multicast group.
-    pub fn leave_multicast_group<T>(&self, addr: T) -> Result<bool, MulticastError>
+    pub async fn leave_multicast_group<T>(&self, addr: T) -> Result<bool, MulticastError>
+    where
+        T: Into<IpAddress>,
+    {
+        let addr = addr.into();
+
+        poll_fn(move |cx| self.poll_leave_multicast_group(addr, cx)).await
+    }
+
+    /// Leave a multicast group.
+    ///
+    /// When the send queue is full, this method will return `Poll::Pending`
+    /// and register the current task to be notified when the queue has space available.
+    pub fn poll_leave_multicast_group<T>(&self, addr: T, cx: &mut Context<'_>) -> Poll<Result<bool, MulticastError>>
     where
         T: Into<IpAddress>,
     {
         let addr = addr.into();
 
         self.with_mut(|s, i| {
-            s.iface
-                .leave_multicast_group(&mut i.device, addr, instant_to_smoltcp(Instant::now()))
+            let mut smoldev = DriverAdapter {
+                cx: Some(cx),
+                inner: &mut i.device,
+            };
+
+            match s
+                .iface
+                .leave_multicast_group(&mut smoldev, addr, instant_to_smoltcp(Instant::now()))
+            {
+                Ok(leave_sent) => Poll::Ready(Ok(leave_sent)),
+                Err(MulticastError::Exhausted) => Poll::Pending,
+                Err(other) => Poll::Ready(Err(other)),
+            }
         })
     }
 
@@ -531,11 +590,14 @@ impl<D: Driver + 'static> Inner<D> {
 
         debug!("   IP address:      {}", config.address);
         s.iface.update_ip_addrs(|addrs| {
-            if addrs.is_empty() {
-                addrs.push(IpCidr::Ipv4(config.address)).unwrap();
-            } else {
-                addrs[0] = IpCidr::Ipv4(config.address);
+            if let Some((index, _)) = addrs
+                .iter()
+                .enumerate()
+                .find(|(_, &addr)| matches!(addr, IpCidr::Ipv4(_)))
+            {
+                addrs.remove(index);
             }
+            addrs.push(IpCidr::Ipv4(config.address)).unwrap();
         });
 
         #[cfg(feature = "medium-ethernet")]
@@ -570,11 +632,14 @@ impl<D: Driver + 'static> Inner<D> {
 
         debug!("   IP address:      {}", config.address);
         s.iface.update_ip_addrs(|addrs| {
-            if addrs.is_empty() {
-                addrs.push(IpCidr::Ipv6(config.address)).unwrap();
-            } else {
-                addrs[0] = IpCidr::Ipv6(config.address);
+            if let Some((index, _)) = addrs
+                .iter()
+                .enumerate()
+                .find(|(_, &addr)| matches!(addr, IpCidr::Ipv6(_)))
+            {
+                addrs.remove(index);
             }
+            addrs.push(IpCidr::Ipv6(config.address)).unwrap();
         });
 
         #[cfg(feature = "medium-ethernet")]
@@ -642,13 +707,21 @@ impl<D: Driver + 'static> Inner<D> {
         socket.set_retry_config(config.retry_config);
     }
 
-    #[allow(unused)] // used only with dhcp
-    fn unapply_config(&mut self, s: &mut SocketStack) {
+    #[cfg(feature = "dhcpv4")]
+    fn unapply_config_v4(&mut self, s: &mut SocketStack) {
         #[cfg(feature = "medium-ethernet")]
         let medium = self.device.capabilities().medium;
-
         debug!("Lost IP configuration");
-        s.iface.update_ip_addrs(|ip_addrs| ip_addrs.clear());
+        s.iface.update_ip_addrs(|ip_addrs| {
+            #[cfg(feature = "proto-ipv4")]
+            if let Some((index, _)) = ip_addrs
+                .iter()
+                .enumerate()
+                .find(|(_, &addr)| matches!(addr, IpCidr::Ipv4(_)))
+            {
+                ip_addrs.remove(index);
+            }
+        });
         #[cfg(feature = "medium-ethernet")]
         if medium == Medium::Ethernet {
             #[cfg(feature = "proto-ipv4")]
@@ -695,7 +768,7 @@ impl<D: Driver + 'static> Inner<D> {
             if self.link_up {
                 match socket.poll() {
                     None => {}
-                    Some(dhcpv4::Event::Deconfigured) => self.unapply_config(s),
+                    Some(dhcpv4::Event::Deconfigured) => self.unapply_config_v4(s),
                     Some(dhcpv4::Event::Configured(config)) => {
                         let config = StaticConfigV4 {
                             address: config.address,
@@ -707,7 +780,7 @@ impl<D: Driver + 'static> Inner<D> {
                 }
             } else if old_link_up {
                 socket.reset();
-                self.unapply_config(s);
+                self.unapply_config_v4(s);
             }
         }
         //if old_link_up || self.link_up {
diff --git a/embassy-net/src/udp.rs b/embassy-net/src/udp.rs
index 36f8d06f2..0d97b6db1 100644
--- a/embassy-net/src/udp.rs
+++ b/embassy-net/src/udp.rs
@@ -3,7 +3,7 @@
 use core::cell::RefCell;
 use core::future::poll_fn;
 use core::mem;
-use core::task::Poll;
+use core::task::{Context, Poll};
 
 use embassy_net_driver::Driver;
 use smoltcp::iface::{Interface, SocketHandle};
@@ -102,37 +102,61 @@ impl<'a> UdpSocket<'a> {
     ///
     /// Returns the number of bytes received and the remote endpoint.
     pub async fn recv_from(&self, buf: &mut [u8]) -> Result<(usize, IpEndpoint), Error> {
-        poll_fn(move |cx| {
-            self.with_mut(|s, _| match s.recv_slice(buf) {
-                Ok((n, meta)) => Poll::Ready(Ok((n, meta.endpoint))),
-                // No data ready
-                Err(udp::RecvError::Exhausted) => {
-                    s.register_recv_waker(cx.waker());
-                    Poll::Pending
-                }
-            })
+        poll_fn(move |cx| self.poll_recv_from(buf, cx)).await
+    }
+
+    /// Receive a datagram.
+    ///
+    /// When no datagram is available, this method will return `Poll::Pending` and
+    /// register the current task to be notified when a datagram is received.
+    ///
+    /// When a datagram is received, this method will return `Poll::Ready` with the
+    /// number of bytes received and the remote endpoint.
+    pub fn poll_recv_from(&self, buf: &mut [u8], cx: &mut Context<'_>) -> Poll<Result<(usize, IpEndpoint), Error>> {
+        self.with_mut(|s, _| match s.recv_slice(buf) {
+            Ok((n, meta)) => Poll::Ready(Ok((n, meta.endpoint))),
+            // No data ready
+            Err(udp::RecvError::Exhausted) => {
+                s.register_recv_waker(cx.waker());
+                Poll::Pending
+            }
         })
-        .await
     }
 
     /// Send a datagram to the specified remote endpoint.
+    ///
+    /// This method will wait until the datagram has been sent.
+    ///
+    /// When the remote endpoint is not reachable, this method will return `Err(Error::NoRoute)`
     pub async fn send_to<T>(&self, buf: &[u8], remote_endpoint: T) -> Result<(), Error>
     where
         T: Into<IpEndpoint>,
     {
-        let remote_endpoint = remote_endpoint.into();
-        poll_fn(move |cx| {
-            self.with_mut(|s, _| match s.send_slice(buf, remote_endpoint) {
-                // Entire datagram has been sent
-                Ok(()) => Poll::Ready(Ok(())),
-                Err(udp::SendError::BufferFull) => {
-                    s.register_send_waker(cx.waker());
-                    Poll::Pending
-                }
-                Err(udp::SendError::Unaddressable) => Poll::Ready(Err(Error::NoRoute)),
-            })
+        let remote_endpoint: IpEndpoint = remote_endpoint.into();
+        poll_fn(move |cx| self.poll_send_to(buf, remote_endpoint, cx)).await
+    }
+
+    /// Send a datagram to the specified remote endpoint.
+    ///
+    /// When the datagram has been sent, this method will return `Poll::Ready(Ok())`.
+    ///
+    /// When the socket's send buffer is full, this method will return `Poll::Pending`
+    /// and register the current task to be notified when the buffer has space available.
+    ///
+    /// When the remote endpoint is not reachable, this method will return `Poll::Ready(Err(Error::NoRoute))`.
+    pub fn poll_send_to<T>(&self, buf: &[u8], remote_endpoint: T, cx: &mut Context<'_>) -> Poll<Result<(), Error>>
+    where
+        T: Into<IpEndpoint>,
+    {
+        self.with_mut(|s, _| match s.send_slice(buf, remote_endpoint) {
+            // Entire datagram has been sent
+            Ok(()) => Poll::Ready(Ok(())),
+            Err(udp::SendError::BufferFull) => {
+                s.register_send_waker(cx.waker());
+                Poll::Pending
+            }
+            Err(udp::SendError::Unaddressable) => Poll::Ready(Err(Error::NoRoute)),
         })
-        .await
     }
 
     /// Returns the local endpoint of the socket.
diff --git a/embassy-nrf/Cargo.toml b/embassy-nrf/Cargo.toml
index 3e858f854..57dd22f1c 100644
--- a/embassy-nrf/Cargo.toml
+++ b/embassy-nrf/Cargo.toml
@@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0"
 src_base = "https://github.com/embassy-rs/embassy/blob/embassy-nrf-v$VERSION/embassy-nrf/src/"
 src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-nrf/src/"
 
-features = ["nightly", "defmt", "unstable-pac", "unstable-traits", "gpiote", "time-driver-rtc1"]
+features = ["nightly", "time", "defmt", "unstable-pac", "unstable-traits", "gpiote", "time-driver-rtc1"]
 flavors = [
     { regex_feature = "nrf52.*", target = "thumbv7em-none-eabihf" },
     { regex_feature = "nrf53.*", target = "thumbv8m.main-none-eabihf" },
@@ -91,22 +91,22 @@ _dppi = []
 _gpio-p1 = []
 
 [dependencies]
-embassy-time = { version = "0.1.0", path = "../embassy-time", optional = true }
+embassy-time = { version = "0.1.2", path = "../embassy-time", optional = true }
 embassy-sync = { version = "0.2.0", path = "../embassy-sync" }
 embassy-hal-common = {version = "0.1.0", path = "../embassy-hal-common", features = ["cortex-m", "prio-bits-3"] }
 embassy-embedded-hal = {version = "0.1.0", path = "../embassy-embedded-hal" }
 embassy-usb-driver = {version = "0.1.0", path = "../embassy-usb-driver", optional=true }
 
 embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] }
-embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.10", optional = true}
-embedded-hal-async = { version = "=0.2.0-alpha.1", optional = true}
+embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.11", optional = true}
+embedded-hal-async = { version = "=0.2.0-alpha.2", optional = true}
 embedded-io = { version = "0.4.0", features = ["async"], optional = true }
 
 defmt = { version = "0.3", optional = true }
 log = { version = "0.4.14", optional = true }
 cortex-m-rt = ">=0.6.15,<0.8"
 cortex-m = "0.7.6"
-futures     = { version = "0.3.17", default-features = false }
+futures = { version = "0.3.17", default-features = false }
 critical-section = "1.1"
 rand_core = "0.6.3"
 fixed = "1.10.0"
@@ -114,13 +114,13 @@ embedded-storage = "0.3.0"
 embedded-storage-async = { version = "0.4.0", optional = true }
 cfg-if = "1.0.0"
 
-nrf52805-pac  = { version = "0.12.0", optional = true }
-nrf52810-pac  = { version = "0.12.0", optional = true }
-nrf52811-pac  = { version = "0.12.0", optional = true }
-nrf52820-pac  = { version = "0.12.0", optional = true }
-nrf52832-pac  = { version = "0.12.0", optional = true }
-nrf52833-pac  = { version = "0.12.0", optional = true }
-nrf52840-pac  = { version = "0.12.0", optional = true }
+nrf52805-pac = { version = "0.12.0", optional = true }
+nrf52810-pac = { version = "0.12.0", optional = true }
+nrf52811-pac = { version = "0.12.0", optional = true }
+nrf52820-pac = { version = "0.12.0", optional = true }
+nrf52832-pac = { version = "0.12.0", optional = true }
+nrf52833-pac = { version = "0.12.0", optional = true }
+nrf52840-pac = { version = "0.12.0", optional = true }
 nrf5340-app-pac = { version = "0.12.0", optional = true }
 nrf5340-net-pac = { version = "0.12.0", optional = true }
 nrf9160-pac = { version = "0.12.0", optional = true }
diff --git a/embassy-nrf/src/gpiote.rs b/embassy-nrf/src/gpiote.rs
index 21d0d9564..6550f2abd 100644
--- a/embassy-nrf/src/gpiote.rs
+++ b/embassy-nrf/src/gpiote.rs
@@ -221,7 +221,7 @@ impl<'d, C: Channel, T: GpioPin> InputChannel<'d, C, T> {
     }
 
     /// Returns the IN event, for use with PPI.
-    pub fn event_in(&self) -> Event {
+    pub fn event_in(&self) -> Event<'d> {
         let g = regs();
         Event::from_reg(&g.events_in[self.ch.number()])
     }
@@ -292,21 +292,21 @@ impl<'d, C: Channel, T: GpioPin> OutputChannel<'d, C, T> {
     }
 
     /// Returns the OUT task, for use with PPI.
-    pub fn task_out(&self) -> Task {
+    pub fn task_out(&self) -> Task<'d> {
         let g = regs();
         Task::from_reg(&g.tasks_out[self.ch.number()])
     }
 
     /// Returns the CLR task, for use with PPI.
     #[cfg(not(feature = "nrf51"))]
-    pub fn task_clr(&self) -> Task {
+    pub fn task_clr(&self) -> Task<'d> {
         let g = regs();
         Task::from_reg(&g.tasks_clr[self.ch.number()])
     }
 
     /// Returns the SET task, for use with PPI.
     #[cfg(not(feature = "nrf51"))]
-    pub fn task_set(&self) -> Task {
+    pub fn task_set(&self) -> Task<'d> {
         let g = regs();
         Task::from_reg(&g.tasks_set[self.ch.number()])
     }
diff --git a/embassy-nrf/src/pdm.rs b/embassy-nrf/src/pdm.rs
index 0e30f7002..217884d1c 100644
--- a/embassy-nrf/src/pdm.rs
+++ b/embassy-nrf/src/pdm.rs
@@ -1,294 +1,500 @@
-//! Pulse Density Modulation (PDM) mirophone driver.
-
-#![macro_use]
-
-use core::marker::PhantomData;
-use core::sync::atomic::{compiler_fence, Ordering};
-use core::task::Poll;
-
-use embassy_hal_common::drop::OnDrop;
-use embassy_hal_common::{into_ref, PeripheralRef};
-use futures::future::poll_fn;
-
-use crate::chip::EASY_DMA_SIZE;
-use crate::gpio::sealed::Pin;
-use crate::gpio::{AnyPin, Pin as GpioPin};
-use crate::interrupt::typelevel::Interrupt;
-use crate::{interrupt, Peripheral};
-
-/// Interrupt handler.
-pub struct InterruptHandler<T: Instance> {
-    _phantom: PhantomData<T>,
-}
-
-impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandler<T> {
-    unsafe fn on_interrupt() {
-        T::regs().intenclr.write(|w| w.end().clear());
-        T::state().waker.wake();
-    }
-}
-
-/// PDM microphone interface
-pub struct Pdm<'d, T: Instance> {
-    _peri: PeripheralRef<'d, T>,
-}
-
-/// PDM error.
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-#[non_exhaustive]
-pub enum Error {
-    /// Buffer is too long.
-    BufferTooLong,
-    /// Buffer is empty
-    BufferZeroLength,
-    /// PDM is not running
-    NotRunning,
-}
-
-static DUMMY_BUFFER: [i16; 1] = [0; 1];
-
-impl<'d, T: Instance> Pdm<'d, T> {
-    /// Create PDM driver
-    pub fn new(
-        pdm: impl Peripheral<P = T> + 'd,
-        _irq: impl interrupt::typelevel::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
-        clk: impl Peripheral<P = impl GpioPin> + 'd,
-        din: impl Peripheral<P = impl GpioPin> + 'd,
-        config: Config,
-    ) -> Self {
-        into_ref!(pdm, clk, din);
-        Self::new_inner(pdm, clk.map_into(), din.map_into(), config)
-    }
-
-    fn new_inner(
-        pdm: PeripheralRef<'d, T>,
-        clk: PeripheralRef<'d, AnyPin>,
-        din: PeripheralRef<'d, AnyPin>,
-        config: Config,
-    ) -> Self {
-        into_ref!(pdm);
-
-        let r = T::regs();
-
-        // setup gpio pins
-        din.conf().write(|w| w.input().set_bit());
-        r.psel.din.write(|w| unsafe { w.bits(din.psel_bits()) });
-        clk.set_low();
-        clk.conf().write(|w| w.dir().output());
-        r.psel.clk.write(|w| unsafe { w.bits(clk.psel_bits()) });
-
-        // configure
-        // use default for
-        // - gain right
-        // - gain left
-        // - clk
-        // - ratio
-        r.mode.write(|w| {
-            w.edge().bit(config.edge == Edge::LeftRising);
-            w.operation().bit(config.operation_mode == OperationMode::Mono);
-            w
-        });
-        r.gainl.write(|w| w.gainl().default_gain());
-        r.gainr.write(|w| w.gainr().default_gain());
-
-        // IRQ
-        T::Interrupt::unpend();
-        unsafe { T::Interrupt::enable() };
-
-        r.enable.write(|w| w.enable().set_bit());
-
-        Self { _peri: pdm }
-    }
-
-    /// Start sampling microphon data into a dummy buffer
-    /// Usefull to start the microphon and keep it active between recording samples
-    pub async fn start(&mut self) {
-        let r = T::regs();
-
-        // start dummy sampling because microphon needs some setup time
-        r.sample
-            .ptr
-            .write(|w| unsafe { w.sampleptr().bits(DUMMY_BUFFER.as_ptr() as u32) });
-        r.sample
-            .maxcnt
-            .write(|w| unsafe { w.buffsize().bits(DUMMY_BUFFER.len() as _) });
-
-        r.tasks_start.write(|w| unsafe { w.bits(1) });
-    }
-
-    /// Stop sampling microphon data inta a dummy buffer
-    pub async fn stop(&mut self) {
-        let r = T::regs();
-        r.tasks_stop.write(|w| unsafe { w.bits(1) });
-        r.events_started.reset();
-    }
-
-    /// Sample data into the given buffer.
-    pub async fn sample(&mut self, buffer: &mut [i16]) -> Result<(), Error> {
-        if buffer.len() == 0 {
-            return Err(Error::BufferZeroLength);
-        }
-        if buffer.len() > EASY_DMA_SIZE {
-            return Err(Error::BufferTooLong);
-        }
-
-        let r = T::regs();
-
-        if r.events_started.read().bits() == 0 {
-            return Err(Error::NotRunning);
-        }
-
-        let drop = OnDrop::new(move || {
-            r.intenclr.write(|w| w.end().clear());
-            r.events_stopped.reset();
-
-            // reset to dummy buffer
-            r.sample
-                .ptr
-                .write(|w| unsafe { w.sampleptr().bits(DUMMY_BUFFER.as_ptr() as u32) });
-            r.sample
-                .maxcnt
-                .write(|w| unsafe { w.buffsize().bits(DUMMY_BUFFER.len() as _) });
-
-            while r.events_stopped.read().bits() == 0 {}
-        });
-
-        // setup user buffer
-        let ptr = buffer.as_ptr();
-        let len = buffer.len();
-        r.sample.ptr.write(|w| unsafe { w.sampleptr().bits(ptr as u32) });
-        r.sample.maxcnt.write(|w| unsafe { w.buffsize().bits(len as _) });
-
-        // wait till the current sample is finished and the user buffer sample is started
-        Self::wait_for_sample().await;
-
-        // reset the buffer back to the dummy buffer
-        r.sample
-            .ptr
-            .write(|w| unsafe { w.sampleptr().bits(DUMMY_BUFFER.as_ptr() as u32) });
-        r.sample
-            .maxcnt
-            .write(|w| unsafe { w.buffsize().bits(DUMMY_BUFFER.len() as _) });
-
-        // wait till the user buffer is sampled
-        Self::wait_for_sample().await;
-
-        drop.defuse();
-
-        Ok(())
-    }
-
-    async fn wait_for_sample() {
-        let r = T::regs();
-
-        r.events_end.reset();
-        r.intenset.write(|w| w.end().set());
-
-        compiler_fence(Ordering::SeqCst);
-
-        poll_fn(|cx| {
-            T::state().waker.register(cx.waker());
-            if r.events_end.read().bits() != 0 {
-                return Poll::Ready(());
-            }
-            Poll::Pending
-        })
-        .await;
-
-        compiler_fence(Ordering::SeqCst);
-    }
-}
-
-/// PDM microphone driver Config
-pub struct Config {
-    /// Use stero or mono operation
-    pub operation_mode: OperationMode,
-    /// On which edge the left channel should be samples
-    pub edge: Edge,
-}
-
-impl Default for Config {
-    fn default() -> Self {
-        Self {
-            operation_mode: OperationMode::Mono,
-            edge: Edge::LeftFalling,
-        }
-    }
-}
-
-/// PDM operation mode.
-#[derive(PartialEq)]
-pub enum OperationMode {
-    /// Mono (1 channel)
-    Mono,
-    /// Stereo (2 channels)
-    Stereo,
-}
-
-/// PDM edge polarity
-#[derive(PartialEq)]
-pub enum Edge {
-    /// Left edge is rising
-    LeftRising,
-    /// Left edge is falling
-    LeftFalling,
-}
-
-impl<'d, T: Instance> Drop for Pdm<'d, T> {
-    fn drop(&mut self) {
-        let r = T::regs();
-
-        r.tasks_stop.write(|w| unsafe { w.bits(1) });
-
-        r.enable.write(|w| w.enable().disabled());
-
-        r.psel.din.reset();
-        r.psel.clk.reset();
-    }
-}
-
-pub(crate) mod sealed {
-    use embassy_sync::waitqueue::AtomicWaker;
-
-    /// Peripheral static state
-    pub struct State {
-        pub waker: AtomicWaker,
-    }
-
-    impl State {
-        pub const fn new() -> Self {
-            Self {
-                waker: AtomicWaker::new(),
-            }
-        }
-    }
-
-    pub trait Instance {
-        fn regs() -> &'static crate::pac::pdm::RegisterBlock;
-        fn state() -> &'static State;
-    }
-}
-
-/// PDM peripheral instance.
-pub trait Instance: Peripheral<P = Self> + sealed::Instance + 'static + Send {
-    /// Interrupt for this peripheral.
-    type Interrupt: interrupt::typelevel::Interrupt;
-}
-
-macro_rules! impl_pdm {
-    ($type:ident, $pac_type:ident, $irq:ident) => {
-        impl crate::pdm::sealed::Instance for peripherals::$type {
-            fn regs() -> &'static crate::pac::pdm::RegisterBlock {
-                unsafe { &*pac::$pac_type::ptr() }
-            }
-            fn state() -> &'static crate::pdm::sealed::State {
-                static STATE: crate::pdm::sealed::State = crate::pdm::sealed::State::new();
-                &STATE
-            }
-        }
-        impl crate::pdm::Instance for peripherals::$type {
-            type Interrupt = crate::interrupt::typelevel::$irq;
-        }
-    };
-}
+//! Pulse Density Modulation (PDM) mirophone driver.
+
+#![macro_use]
+
+use core::marker::PhantomData;
+use core::sync::atomic::{compiler_fence, Ordering};
+use core::task::Poll;
+
+use embassy_hal_common::drop::OnDrop;
+use embassy_hal_common::{into_ref, PeripheralRef};
+use fixed::types::I7F1;
+use futures::future::poll_fn;
+
+use crate::chip::EASY_DMA_SIZE;
+use crate::gpio::sealed::Pin;
+use crate::gpio::{AnyPin, Pin as GpioPin};
+use crate::interrupt::typelevel::Interrupt;
+use crate::pac::pdm::mode::{EDGE_A, OPERATION_A};
+pub use crate::pac::pdm::pdmclkctrl::FREQ_A as Frequency;
+#[cfg(any(
+    feature = "nrf52840",
+    feature = "nrf52833",
+    feature = "_nrf5340-app",
+    feature = "_nrf9160",
+))]
+pub use crate::pac::pdm::ratio::RATIO_A as Ratio;
+use crate::{interrupt, Peripheral};
+
+/// Interrupt handler.
+pub struct InterruptHandler<T: Instance> {
+    _phantom: PhantomData<T>,
+}
+
+impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandler<T> {
+    unsafe fn on_interrupt() {
+        let r = T::regs();
+
+        if r.events_end.read().bits() != 0 {
+            r.intenclr.write(|w| w.end().clear());
+        }
+
+        if r.events_started.read().bits() != 0 {
+            r.intenclr.write(|w| w.started().clear());
+        }
+
+        if r.events_stopped.read().bits() != 0 {
+            r.intenclr.write(|w| w.stopped().clear());
+        }
+
+        T::state().waker.wake();
+    }
+}
+
+/// PDM microphone interface
+pub struct Pdm<'d, T: Instance> {
+    _peri: PeripheralRef<'d, T>,
+}
+
+/// PDM error.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+#[non_exhaustive]
+pub enum Error {
+    /// Buffer is too long.
+    BufferTooLong,
+    /// Buffer is empty
+    BufferZeroLength,
+    /// PDM is not running
+    NotRunning,
+    /// PDM is already running
+    AlreadyRunning,
+}
+
+static DUMMY_BUFFER: [i16; 1] = [0; 1];
+
+/// The state of a continuously running sampler. While it reflects
+/// the progress of a sampler, it also signals what should be done
+/// next. For example, if the sampler has stopped then the Pdm implementation
+/// can then tear down its infrastructure.
+#[derive(PartialEq)]
+pub enum SamplerState {
+    /// The sampler processed the samples and is ready for more.
+    Sampled,
+    /// The sampler is done processing samples.
+    Stopped,
+}
+
+impl<'d, T: Instance> Pdm<'d, T> {
+    /// Create PDM driver
+    pub fn new(
+        pdm: impl Peripheral<P = T> + 'd,
+        _irq: impl interrupt::typelevel::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
+        clk: impl Peripheral<P = impl GpioPin> + 'd,
+        din: impl Peripheral<P = impl GpioPin> + 'd,
+        config: Config,
+    ) -> Self {
+        into_ref!(pdm, clk, din);
+        Self::new_inner(pdm, clk.map_into(), din.map_into(), config)
+    }
+
+    fn new_inner(
+        pdm: PeripheralRef<'d, T>,
+        clk: PeripheralRef<'d, AnyPin>,
+        din: PeripheralRef<'d, AnyPin>,
+        config: Config,
+    ) -> Self {
+        into_ref!(pdm);
+
+        let r = T::regs();
+
+        // setup gpio pins
+        din.conf().write(|w| w.input().set_bit());
+        r.psel.din.write(|w| unsafe { w.bits(din.psel_bits()) });
+        clk.set_low();
+        clk.conf().write(|w| w.dir().output());
+        r.psel.clk.write(|w| unsafe { w.bits(clk.psel_bits()) });
+
+        // configure
+        r.pdmclkctrl.write(|w| w.freq().variant(config.frequency));
+        #[cfg(any(
+            feature = "nrf52840",
+            feature = "nrf52833",
+            feature = "_nrf5340-app",
+            feature = "_nrf9160",
+        ))]
+        r.ratio.write(|w| w.ratio().variant(config.ratio));
+        r.mode.write(|w| {
+            w.operation().variant(config.operation_mode.into());
+            w.edge().variant(config.edge.into());
+            w
+        });
+
+        Self::_set_gain(r, config.gain_left, config.gain_right);
+
+        // Disable all events interrupts
+        r.intenclr.write(|w| unsafe { w.bits(0x003F_FFFF) });
+
+        // IRQ
+        T::Interrupt::unpend();
+        unsafe { T::Interrupt::enable() };
+
+        r.enable.write(|w| w.enable().set_bit());
+
+        Self { _peri: pdm }
+    }
+
+    fn _set_gain(r: &crate::pac::pdm::RegisterBlock, gain_left: I7F1, gain_right: I7F1) {
+        let gain_left = gain_left
+            .saturating_add(I7F1::from_bits(40))
+            .saturating_to_num::<u8>()
+            .clamp(0, 0x50);
+        let gain_right = gain_right
+            .saturating_add(I7F1::from_bits(40))
+            .saturating_to_num::<u8>()
+            .clamp(0, 0x50);
+
+        r.gainl.write(|w| unsafe { w.gainl().bits(gain_left) });
+        r.gainr.write(|w| unsafe { w.gainr().bits(gain_right) });
+    }
+
+    /// Adjust the gain of the PDM microphone on the fly
+    pub fn set_gain(&mut self, gain_left: I7F1, gain_right: I7F1) {
+        Self::_set_gain(T::regs(), gain_left, gain_right)
+    }
+
+    /// Start sampling microphon data into a dummy buffer
+    /// Usefull to start the microphon and keep it active between recording samples
+    pub async fn start(&mut self) {
+        let r = T::regs();
+
+        // start dummy sampling because microphon needs some setup time
+        r.sample
+            .ptr
+            .write(|w| unsafe { w.sampleptr().bits(DUMMY_BUFFER.as_ptr() as u32) });
+        r.sample
+            .maxcnt
+            .write(|w| unsafe { w.buffsize().bits(DUMMY_BUFFER.len() as _) });
+
+        r.tasks_start.write(|w| unsafe { w.bits(1) });
+    }
+
+    /// Stop sampling microphon data inta a dummy buffer
+    pub async fn stop(&mut self) {
+        let r = T::regs();
+        r.tasks_stop.write(|w| unsafe { w.bits(1) });
+        r.events_started.reset();
+    }
+
+    /// Sample data into the given buffer.
+    pub async fn sample(&mut self, buffer: &mut [i16]) -> Result<(), Error> {
+        if buffer.len() == 0 {
+            return Err(Error::BufferZeroLength);
+        }
+        if buffer.len() > EASY_DMA_SIZE {
+            return Err(Error::BufferTooLong);
+        }
+
+        let r = T::regs();
+
+        if r.events_started.read().bits() == 0 {
+            return Err(Error::NotRunning);
+        }
+
+        let drop = OnDrop::new(move || {
+            r.intenclr.write(|w| w.end().clear());
+            r.events_stopped.reset();
+
+            // reset to dummy buffer
+            r.sample
+                .ptr
+                .write(|w| unsafe { w.sampleptr().bits(DUMMY_BUFFER.as_ptr() as u32) });
+            r.sample
+                .maxcnt
+                .write(|w| unsafe { w.buffsize().bits(DUMMY_BUFFER.len() as _) });
+
+            while r.events_stopped.read().bits() == 0 {}
+        });
+
+        // setup user buffer
+        let ptr = buffer.as_ptr();
+        let len = buffer.len();
+        r.sample.ptr.write(|w| unsafe { w.sampleptr().bits(ptr as u32) });
+        r.sample.maxcnt.write(|w| unsafe { w.buffsize().bits(len as _) });
+
+        // wait till the current sample is finished and the user buffer sample is started
+        Self::wait_for_sample().await;
+
+        // reset the buffer back to the dummy buffer
+        r.sample
+            .ptr
+            .write(|w| unsafe { w.sampleptr().bits(DUMMY_BUFFER.as_ptr() as u32) });
+        r.sample
+            .maxcnt
+            .write(|w| unsafe { w.buffsize().bits(DUMMY_BUFFER.len() as _) });
+
+        // wait till the user buffer is sampled
+        Self::wait_for_sample().await;
+
+        drop.defuse();
+
+        Ok(())
+    }
+
+    async fn wait_for_sample() {
+        let r = T::regs();
+
+        r.events_end.reset();
+        r.intenset.write(|w| w.end().set());
+
+        compiler_fence(Ordering::SeqCst);
+
+        poll_fn(|cx| {
+            T::state().waker.register(cx.waker());
+            if r.events_end.read().bits() != 0 {
+                return Poll::Ready(());
+            }
+            Poll::Pending
+        })
+        .await;
+
+        compiler_fence(Ordering::SeqCst);
+    }
+
+    /// Continuous sampling with double buffers.
+    ///
+    /// A sampler closure is provided that receives the buffer of samples, noting
+    /// that the size of this buffer can be less than the original buffer's size.
+    /// A command is return from the closure that indicates whether the sampling
+    /// should continue or stop.
+    ///
+    /// NOTE: The time spent within the callback supplied should not exceed the time
+    /// taken to acquire the samples into a single buffer. You should measure the
+    /// time taken by the callback and set the sample buffer size accordingly.
+    /// Exceeding this time can lead to samples becoming dropped.
+    pub async fn run_task_sampler<S, const N: usize>(
+        &mut self,
+        bufs: &mut [[i16; N]; 2],
+        mut sampler: S,
+    ) -> Result<(), Error>
+    where
+        S: FnMut(&[i16; N]) -> SamplerState,
+    {
+        let r = T::regs();
+
+        if r.events_started.read().bits() != 0 {
+            return Err(Error::AlreadyRunning);
+        }
+
+        r.sample
+            .ptr
+            .write(|w| unsafe { w.sampleptr().bits(bufs[0].as_mut_ptr() as u32) });
+        r.sample.maxcnt.write(|w| unsafe { w.buffsize().bits(N as _) });
+
+        // Reset and enable the events
+        r.events_end.reset();
+        r.events_started.reset();
+        r.events_stopped.reset();
+        r.intenset.write(|w| {
+            w.end().set();
+            w.started().set();
+            w.stopped().set();
+            w
+        });
+
+        // Don't reorder the start event before the previous writes. Hopefully self
+        // wouldn't happen anyway.
+        compiler_fence(Ordering::SeqCst);
+
+        r.tasks_start.write(|w| unsafe { w.bits(1) });
+
+        let mut current_buffer = 0;
+
+        let mut done = false;
+
+        let drop = OnDrop::new(|| {
+            r.tasks_stop.write(|w| unsafe { w.bits(1) });
+            // N.B. It would be better if this were async, but Drop only support sync code.
+            while r.events_stopped.read().bits() != 0 {}
+        });
+
+        // Wait for events and complete when the sampler indicates it has had enough.
+        poll_fn(|cx| {
+            let r = T::regs();
+
+            T::state().waker.register(cx.waker());
+
+            if r.events_end.read().bits() != 0 {
+                compiler_fence(Ordering::SeqCst);
+
+                r.events_end.reset();
+                r.intenset.write(|w| w.end().set());
+
+                if !done {
+                    // Discard the last buffer after the user requested a stop.
+                    if sampler(&bufs[current_buffer]) == SamplerState::Sampled {
+                        let next_buffer = 1 - current_buffer;
+                        current_buffer = next_buffer;
+                    } else {
+                        r.tasks_stop.write(|w| unsafe { w.bits(1) });
+                        done = true;
+                    };
+                };
+            }
+
+            if r.events_started.read().bits() != 0 {
+                r.events_started.reset();
+                r.intenset.write(|w| w.started().set());
+
+                let next_buffer = 1 - current_buffer;
+                r.sample
+                    .ptr
+                    .write(|w| unsafe { w.sampleptr().bits(bufs[next_buffer].as_mut_ptr() as u32) });
+            }
+
+            if r.events_stopped.read().bits() != 0 {
+                return Poll::Ready(());
+            }
+
+            Poll::Pending
+        })
+        .await;
+        drop.defuse();
+        Ok(())
+    }
+}
+
+/// PDM microphone driver Config
+pub struct Config {
+    /// Use stero or mono operation
+    pub operation_mode: OperationMode,
+    /// On which edge the left channel should be samples
+    pub edge: Edge,
+    /// Clock frequency
+    pub frequency: Frequency,
+    /// Clock ratio
+    #[cfg(any(
+        feature = "nrf52840",
+        feature = "nrf52833",
+        feature = "_nrf5340-app",
+        feature = "_nrf9160",
+    ))]
+    pub ratio: Ratio,
+    /// Gain left in dB
+    pub gain_left: I7F1,
+    /// Gain right in dB
+    pub gain_right: I7F1,
+}
+
+impl Default for Config {
+    fn default() -> Self {
+        Self {
+            operation_mode: OperationMode::Mono,
+            edge: Edge::LeftFalling,
+            frequency: Frequency::DEFAULT,
+            #[cfg(any(
+                feature = "nrf52840",
+                feature = "nrf52833",
+                feature = "_nrf5340-app",
+                feature = "_nrf9160",
+            ))]
+            ratio: Ratio::RATIO80,
+            gain_left: I7F1::ZERO,
+            gain_right: I7F1::ZERO,
+        }
+    }
+}
+
+/// PDM operation mode.
+#[derive(PartialEq)]
+pub enum OperationMode {
+    /// Mono (1 channel)
+    Mono,
+    /// Stereo (2 channels)
+    Stereo,
+}
+
+impl From<OperationMode> for OPERATION_A {
+    fn from(mode: OperationMode) -> Self {
+        match mode {
+            OperationMode::Mono => OPERATION_A::MONO,
+            OperationMode::Stereo => OPERATION_A::STEREO,
+        }
+    }
+}
+
+/// PDM edge polarity
+#[derive(PartialEq)]
+pub enum Edge {
+    /// Left edge is rising
+    LeftRising,
+    /// Left edge is falling
+    LeftFalling,
+}
+
+impl From<Edge> for EDGE_A {
+    fn from(edge: Edge) -> Self {
+        match edge {
+            Edge::LeftRising => EDGE_A::LEFT_RISING,
+            Edge::LeftFalling => EDGE_A::LEFT_FALLING,
+        }
+    }
+}
+
+impl<'d, T: Instance> Drop for Pdm<'d, T> {
+    fn drop(&mut self) {
+        let r = T::regs();
+
+        r.tasks_stop.write(|w| unsafe { w.bits(1) });
+
+        r.enable.write(|w| w.enable().disabled());
+
+        r.psel.din.reset();
+        r.psel.clk.reset();
+    }
+}
+
+pub(crate) mod sealed {
+    use embassy_sync::waitqueue::AtomicWaker;
+
+    /// Peripheral static state
+    pub struct State {
+        pub waker: AtomicWaker,
+    }
+
+    impl State {
+        pub const fn new() -> Self {
+            Self {
+                waker: AtomicWaker::new(),
+            }
+        }
+    }
+
+    pub trait Instance {
+        fn regs() -> &'static crate::pac::pdm::RegisterBlock;
+        fn state() -> &'static State;
+    }
+}
+
+/// PDM peripheral instance.
+pub trait Instance: Peripheral<P = Self> + sealed::Instance + 'static + Send {
+    /// Interrupt for this peripheral.
+    type Interrupt: interrupt::typelevel::Interrupt;
+}
+
+macro_rules! impl_pdm {
+    ($type:ident, $pac_type:ident, $irq:ident) => {
+        impl crate::pdm::sealed::Instance for peripherals::$type {
+            fn regs() -> &'static crate::pac::pdm::RegisterBlock {
+                unsafe { &*pac::$pac_type::ptr() }
+            }
+            fn state() -> &'static crate::pdm::sealed::State {
+                static STATE: crate::pdm::sealed::State = crate::pdm::sealed::State::new();
+                &STATE
+            }
+        }
+        impl crate::pdm::Instance for peripherals::$type {
+            type Interrupt = crate::interrupt::typelevel::$irq;
+        }
+    };
+}
diff --git a/embassy-nrf/src/ppi/dppi.rs b/embassy-nrf/src/ppi/dppi.rs
index 3a1e7f170..40ccb2f09 100644
--- a/embassy-nrf/src/ppi/dppi.rs
+++ b/embassy-nrf/src/ppi/dppi.rs
@@ -12,14 +12,14 @@ pub(crate) fn regs() -> &'static pac::dppic::RegisterBlock {
 
 impl<'d, C: ConfigurableChannel> Ppi<'d, C, 1, 1> {
     /// Configure PPI channel to trigger `task` on `event`.
-    pub fn new_one_to_one(ch: impl Peripheral<P = C> + 'd, event: Event, task: Task) -> Self {
+    pub fn new_one_to_one(ch: impl Peripheral<P = C> + 'd, event: Event<'d>, task: Task<'d>) -> Self {
         Ppi::new_many_to_many(ch, [event], [task])
     }
 }
 
 impl<'d, C: ConfigurableChannel> Ppi<'d, C, 1, 2> {
     /// Configure PPI channel to trigger both `task1` and `task2` on `event`.
-    pub fn new_one_to_two(ch: impl Peripheral<P = C> + 'd, event: Event, task1: Task, task2: Task) -> Self {
+    pub fn new_one_to_two(ch: impl Peripheral<P = C> + 'd, event: Event<'d>, task1: Task<'d>, task2: Task<'d>) -> Self {
         Ppi::new_many_to_many(ch, [event], [task1, task2])
     }
 }
@@ -30,8 +30,8 @@ impl<'d, C: ConfigurableChannel, const EVENT_COUNT: usize, const TASK_COUNT: usi
     /// Configure a DPPI channel to trigger all `tasks` when any of the `events` fires.
     pub fn new_many_to_many(
         ch: impl Peripheral<P = C> + 'd,
-        events: [Event; EVENT_COUNT],
-        tasks: [Task; TASK_COUNT],
+        events: [Event<'d>; EVENT_COUNT],
+        tasks: [Task<'d>; TASK_COUNT],
     ) -> Self {
         into_ref!(ch);
 
diff --git a/embassy-nrf/src/ppi/mod.rs b/embassy-nrf/src/ppi/mod.rs
index 76757a248..ff6593bd5 100644
--- a/embassy-nrf/src/ppi/mod.rs
+++ b/embassy-nrf/src/ppi/mod.rs
@@ -15,6 +15,7 @@
 //! many tasks and events, but any single task or event can only be coupled with one channel.
 //!
 
+use core::marker::PhantomData;
 use core::ptr::NonNull;
 
 use embassy_hal_common::{impl_peripheral, into_ref, PeripheralRef};
@@ -30,9 +31,9 @@ pub(crate) use _version::*;
 pub struct Ppi<'d, C: Channel, const EVENT_COUNT: usize, const TASK_COUNT: usize> {
     ch: PeripheralRef<'d, C>,
     #[cfg(feature = "_dppi")]
-    events: [Event; EVENT_COUNT],
+    events: [Event<'d>; EVENT_COUNT],
     #[cfg(feature = "_dppi")]
-    tasks: [Task; TASK_COUNT],
+    tasks: [Task<'d>; TASK_COUNT],
 }
 
 /// PPI channel group driver.
@@ -95,7 +96,7 @@ impl<'d, G: Group> PpiGroup<'d, G> {
     /// Get a reference to the "enable all" task.
     ///
     /// When triggered, it will enable all the channels in this group.
-    pub fn task_enable_all(&self) -> Task {
+    pub fn task_enable_all(&self) -> Task<'d> {
         let n = self.g.number();
         Task::from_reg(&regs().tasks_chg[n].en)
     }
@@ -103,7 +104,7 @@ impl<'d, G: Group> PpiGroup<'d, G> {
     /// Get a reference to the "disable all" task.
     ///
     /// When triggered, it will disable all the channels in this group.
-    pub fn task_disable_all(&self) -> Task {
+    pub fn task_disable_all(&self) -> Task<'d> {
         let n = self.g.number();
         Task::from_reg(&regs().tasks_chg[n].dis)
     }
@@ -125,16 +126,16 @@ const REGISTER_DPPI_CONFIG_OFFSET: usize = 0x80 / core::mem::size_of::<u32>();
 /// When a task is subscribed to a PPI channel, it will run when the channel is triggered by
 /// a published event.
 #[derive(PartialEq, Eq, Clone, Copy)]
-pub struct Task(NonNull<u32>);
+pub struct Task<'d>(NonNull<u32>, PhantomData<&'d ()>);
 
-impl Task {
+impl<'d> Task<'d> {
     /// Create a new `Task` from a task register pointer
     ///
     /// # Safety
     ///
     /// `ptr` must be a pointer to a valid `TASKS_*` register from an nRF peripheral.
     pub unsafe fn new_unchecked(ptr: NonNull<u32>) -> Self {
-        Self(ptr)
+        Self(ptr, PhantomData)
     }
 
     /// Triggers this task.
@@ -143,7 +144,10 @@ impl Task {
     }
 
     pub(crate) fn from_reg<T>(reg: &T) -> Self {
-        Self(unsafe { NonNull::new_unchecked(reg as *const _ as *mut _) })
+        Self(
+            unsafe { NonNull::new_unchecked(reg as *const _ as *mut _) },
+            PhantomData,
+        )
     }
 
     /// Address of subscription register for this task.
@@ -156,26 +160,29 @@ impl Task {
 /// # Safety
 ///
 /// NonNull is not send, but this event is only allowed to point at registers and those exist in any context on the same core.
-unsafe impl Send for Task {}
+unsafe impl Send for Task<'_> {}
 
 /// Represents an event that a peripheral can publish.
 ///
 /// An event can be set to publish on a PPI channel when the event happens.
 #[derive(PartialEq, Eq, Clone, Copy)]
-pub struct Event(NonNull<u32>);
+pub struct Event<'d>(NonNull<u32>, PhantomData<&'d ()>);
 
-impl Event {
+impl<'d> Event<'d> {
     /// Create a new `Event` from an event register pointer
     ///
     /// # Safety
     ///
     /// `ptr` must be a pointer to a valid `EVENTS_*` register from an nRF peripheral.
     pub unsafe fn new_unchecked(ptr: NonNull<u32>) -> Self {
-        Self(ptr)
+        Self(ptr, PhantomData)
     }
 
-    pub(crate) fn from_reg<T>(reg: &T) -> Self {
-        Self(unsafe { NonNull::new_unchecked(reg as *const _ as *mut _) })
+    pub(crate) fn from_reg<T>(reg: &'d T) -> Self {
+        Self(
+            unsafe { NonNull::new_unchecked(reg as *const _ as *mut _) },
+            PhantomData,
+        )
     }
 
     /// Describes whether this Event is currently in a triggered state.
@@ -198,7 +205,7 @@ impl Event {
 /// # Safety
 ///
 /// NonNull is not send, but this event is only allowed to point at registers and those exist in any context on the same core.
-unsafe impl Send for Event {}
+unsafe impl Send for Event<'_> {}
 
 // ======================
 //       traits
diff --git a/embassy-nrf/src/ppi/ppi.rs b/embassy-nrf/src/ppi/ppi.rs
index f1eeaee1e..1fe898625 100644
--- a/embassy-nrf/src/ppi/ppi.rs
+++ b/embassy-nrf/src/ppi/ppi.rs
@@ -3,12 +3,12 @@ use embassy_hal_common::into_ref;
 use super::{Channel, ConfigurableChannel, Event, Ppi, StaticChannel, Task};
 use crate::{pac, Peripheral};
 
-impl Task {
+impl<'d> Task<'d> {
     fn reg_val(&self) -> u32 {
         self.0.as_ptr() as _
     }
 }
-impl Event {
+impl<'d> Event<'d> {
     fn reg_val(&self) -> u32 {
         self.0.as_ptr() as _
     }
@@ -34,7 +34,7 @@ impl<'d, C: StaticChannel> Ppi<'d, C, 0, 1> {
 
 impl<'d, C: ConfigurableChannel> Ppi<'d, C, 1, 1> {
     /// Configure PPI channel to trigger `task` on `event`.
-    pub fn new_one_to_one(ch: impl Peripheral<P = C> + 'd, event: Event, task: Task) -> Self {
+    pub fn new_one_to_one(ch: impl Peripheral<P = C> + 'd, event: Event<'d>, task: Task<'d>) -> Self {
         into_ref!(ch);
 
         let r = regs();
@@ -49,7 +49,7 @@ impl<'d, C: ConfigurableChannel> Ppi<'d, C, 1, 1> {
 #[cfg(not(feature = "nrf51"))] // Not for nrf51 because of the fork task
 impl<'d, C: ConfigurableChannel> Ppi<'d, C, 1, 2> {
     /// Configure PPI channel to trigger both `task1` and `task2` on `event`.
-    pub fn new_one_to_two(ch: impl Peripheral<P = C> + 'd, event: Event, task1: Task, task2: Task) -> Self {
+    pub fn new_one_to_two(ch: impl Peripheral<P = C> + 'd, event: Event<'d>, task1: Task<'d>, task2: Task<'d>) -> Self {
         into_ref!(ch);
 
         let r = regs();
diff --git a/embassy-nrf/src/pwm.rs b/embassy-nrf/src/pwm.rs
index 363a255d5..c8c81fa01 100644
--- a/embassy-nrf/src/pwm.rs
+++ b/embassy-nrf/src/pwm.rs
@@ -181,7 +181,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> {
 
     /// Returns reference to `Stopped` event endpoint for PPI.
     #[inline(always)]
-    pub fn event_stopped(&self) -> Event {
+    pub fn event_stopped(&self) -> Event<'d> {
         let r = T::regs();
 
         Event::from_reg(&r.events_stopped)
@@ -189,7 +189,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> {
 
     /// Returns reference to `LoopsDone` event endpoint for PPI.
     #[inline(always)]
-    pub fn event_loops_done(&self) -> Event {
+    pub fn event_loops_done(&self) -> Event<'d> {
         let r = T::regs();
 
         Event::from_reg(&r.events_loopsdone)
@@ -197,7 +197,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> {
 
     /// Returns reference to `PwmPeriodEnd` event endpoint for PPI.
     #[inline(always)]
-    pub fn event_pwm_period_end(&self) -> Event {
+    pub fn event_pwm_period_end(&self) -> Event<'d> {
         let r = T::regs();
 
         Event::from_reg(&r.events_pwmperiodend)
@@ -205,7 +205,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> {
 
     /// Returns reference to `Seq0 End` event endpoint for PPI.
     #[inline(always)]
-    pub fn event_seq_end(&self) -> Event {
+    pub fn event_seq_end(&self) -> Event<'d> {
         let r = T::regs();
 
         Event::from_reg(&r.events_seqend[0])
@@ -213,7 +213,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> {
 
     /// Returns reference to `Seq1 End` event endpoint for PPI.
     #[inline(always)]
-    pub fn event_seq1_end(&self) -> Event {
+    pub fn event_seq1_end(&self) -> Event<'d> {
         let r = T::regs();
 
         Event::from_reg(&r.events_seqend[1])
@@ -221,7 +221,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> {
 
     /// Returns reference to `Seq0 Started` event endpoint for PPI.
     #[inline(always)]
-    pub fn event_seq0_started(&self) -> Event {
+    pub fn event_seq0_started(&self) -> Event<'d> {
         let r = T::regs();
 
         Event::from_reg(&r.events_seqstarted[0])
@@ -229,7 +229,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> {
 
     /// Returns reference to `Seq1 Started` event endpoint for PPI.
     #[inline(always)]
-    pub fn event_seq1_started(&self) -> Event {
+    pub fn event_seq1_started(&self) -> Event<'d> {
         let r = T::regs();
 
         Event::from_reg(&r.events_seqstarted[1])
@@ -240,7 +240,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> {
     ///
     /// Interacting with the sequence while it runs puts it in an unknown state
     #[inline(always)]
-    pub unsafe fn task_start_seq0(&self) -> Task {
+    pub unsafe fn task_start_seq0(&self) -> Task<'d> {
         let r = T::regs();
 
         Task::from_reg(&r.tasks_seqstart[0])
@@ -251,7 +251,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> {
     ///
     /// Interacting with the sequence while it runs puts it in an unknown state
     #[inline(always)]
-    pub unsafe fn task_start_seq1(&self) -> Task {
+    pub unsafe fn task_start_seq1(&self) -> Task<'d> {
         let r = T::regs();
 
         Task::from_reg(&r.tasks_seqstart[1])
@@ -262,7 +262,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> {
     ///
     /// Interacting with the sequence while it runs puts it in an unknown state
     #[inline(always)]
-    pub unsafe fn task_next_step(&self) -> Task {
+    pub unsafe fn task_next_step(&self) -> Task<'d> {
         let r = T::regs();
 
         Task::from_reg(&r.tasks_nextstep)
@@ -273,7 +273,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> {
     ///
     /// Interacting with the sequence while it runs puts it in an unknown state
     #[inline(always)]
-    pub unsafe fn task_stop(&self) -> Task {
+    pub unsafe fn task_stop(&self) -> Task<'d> {
         let r = T::regs();
 
         Task::from_reg(&r.tasks_stop)
diff --git a/embassy-nrf/src/saadc.rs b/embassy-nrf/src/saadc.rs
index cf3fb9993..23292924c 100644
--- a/embassy-nrf/src/saadc.rs
+++ b/embassy-nrf/src/saadc.rs
@@ -320,7 +320,9 @@ impl<'d, const N: usize> Saadc<'d, N> {
         timer.cc(0).write(sample_counter);
         timer.cc(0).short_compare_clear();
 
-        let mut sample_ppi = Ppi::new_one_to_one(ppi_ch2, timer.cc(0).event_compare(), Task::from_reg(&r.tasks_sample));
+        let timer_cc = timer.cc(0);
+
+        let mut sample_ppi = Ppi::new_one_to_one(ppi_ch2, timer_cc.event_compare(), Task::from_reg(&r.tasks_sample));
 
         timer.start();
 
diff --git a/embassy-nrf/src/spim.rs b/embassy-nrf/src/spim.rs
index 66bbd1a8f..b7dc332e9 100644
--- a/embassy-nrf/src/spim.rs
+++ b/embassy-nrf/src/spim.rs
@@ -468,25 +468,19 @@ mod eh1 {
         type Error = Error;
     }
 
-    impl<'d, T: Instance> embedded_hal_1::spi::SpiBusFlush for Spim<'d, T> {
+    impl<'d, T: Instance> embedded_hal_1::spi::SpiBus<u8> for Spim<'d, T> {
         fn flush(&mut self) -> Result<(), Self::Error> {
             Ok(())
         }
-    }
 
-    impl<'d, T: Instance> embedded_hal_1::spi::SpiBusRead<u8> for Spim<'d, T> {
         fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
             self.blocking_transfer(words, &[])
         }
-    }
 
-    impl<'d, T: Instance> embedded_hal_1::spi::SpiBusWrite<u8> for Spim<'d, T> {
         fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> {
             self.blocking_write(words)
         }
-    }
 
-    impl<'d, T: Instance> embedded_hal_1::spi::SpiBus<u8> for Spim<'d, T> {
         fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> {
             self.blocking_transfer(read, write)
         }
@@ -502,25 +496,19 @@ mod eha {
 
     use super::*;
 
-    impl<'d, T: Instance> embedded_hal_async::spi::SpiBusFlush for Spim<'d, T> {
+    impl<'d, T: Instance> embedded_hal_async::spi::SpiBus<u8> for Spim<'d, T> {
         async fn flush(&mut self) -> Result<(), Error> {
             Ok(())
         }
-    }
 
-    impl<'d, T: Instance> embedded_hal_async::spi::SpiBusRead<u8> for Spim<'d, T> {
         async fn read(&mut self, words: &mut [u8]) -> Result<(), Error> {
             self.read(words).await
         }
-    }
 
-    impl<'d, T: Instance> embedded_hal_async::spi::SpiBusWrite<u8> for Spim<'d, T> {
         async fn write(&mut self, data: &[u8]) -> Result<(), Error> {
             self.write(data).await
         }
-    }
 
-    impl<'d, T: Instance> embedded_hal_async::spi::SpiBus<u8> for Spim<'d, T> {
         async fn transfer(&mut self, rx: &mut [u8], tx: &[u8]) -> Result<(), Error> {
             self.transfer(rx, tx).await
         }
diff --git a/embassy-nrf/src/timer.rs b/embassy-nrf/src/timer.rs
index dc3757856..04748238d 100644
--- a/embassy-nrf/src/timer.rs
+++ b/embassy-nrf/src/timer.rs
@@ -168,21 +168,21 @@ impl<'d, T: Instance> Timer<'d, T> {
     /// Returns the START task, for use with PPI.
     ///
     /// When triggered, this task starts the timer.
-    pub fn task_start(&self) -> Task {
+    pub fn task_start(&self) -> Task<'d> {
         Task::from_reg(&T::regs().tasks_start)
     }
 
     /// Returns the STOP task, for use with PPI.
     ///
     /// When triggered, this task stops the timer.
-    pub fn task_stop(&self) -> Task {
+    pub fn task_stop(&self) -> Task<'d> {
         Task::from_reg(&T::regs().tasks_stop)
     }
 
     /// Returns the CLEAR task, for use with PPI.
     ///
     /// When triggered, this task resets the timer's counter to 0.
-    pub fn task_clear(&self) -> Task {
+    pub fn task_clear(&self) -> Task<'d> {
         Task::from_reg(&T::regs().tasks_clear)
     }
 
@@ -190,7 +190,7 @@ impl<'d, T: Instance> Timer<'d, T> {
     ///
     /// When triggered, this task increments the timer's counter by 1.
     /// Only works in counter mode.
-    pub fn task_count(&self) -> Task {
+    pub fn task_count(&self) -> Task<'d> {
         Task::from_reg(&T::regs().tasks_count)
     }
 
@@ -258,14 +258,14 @@ impl<'d, T: Instance> Cc<'d, T> {
     /// Returns this CC register's CAPTURE task, for use with PPI.
     ///
     /// When triggered, this task will capture the current value of the timer's counter in this register.
-    pub fn task_capture(&self) -> Task {
+    pub fn task_capture(&self) -> Task<'d> {
         Task::from_reg(&T::regs().tasks_capture)
     }
 
     /// Returns this CC register's COMPARE event, for use with PPI.
     ///
     /// This event will fire when the timer's counter reaches the value in this CC register.
-    pub fn event_compare(&self) -> Event {
+    pub fn event_compare(&self) -> Event<'d> {
         Event::from_reg(&T::regs().events_compare[self.n])
     }
 
diff --git a/embassy-rp/Cargo.toml b/embassy-rp/Cargo.toml
index 66823771a..8f3ed885d 100644
--- a/embassy-rp/Cargo.toml
+++ b/embassy-rp/Cargo.toml
@@ -56,7 +56,7 @@ unstable-traits = ["embedded-hal-1", "embedded-hal-nb"]
 
 [dependencies]
 embassy-sync = { version = "0.2.0", path = "../embassy-sync" }
-embassy-time = { version = "0.1.0", path = "../embassy-time", features = [ "tick-hz-1_000_000" ] }
+embassy-time = { version = "0.1.2", path = "../embassy-time", features = [ "tick-hz-1_000_000" ] }
 embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
 embassy-hal-common = {version = "0.1.0", path = "../embassy-hal-common", features = ["cortex-m", "prio-bits-2"] }
 embassy-embedded-hal = {version = "0.1.0", path = "../embassy-embedded-hal" }
@@ -79,9 +79,9 @@ fixed = "1.23.1"
 rp-pac = { version = "6" }
 
 embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] }
-embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.10", optional = true}
-embedded-hal-async = { version = "=0.2.0-alpha.1", optional = true}
-embedded-hal-nb = { version = "=1.0.0-alpha.2", optional = true}
+embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.11", optional = true}
+embedded-hal-async = { version = "=0.2.0-alpha.2", optional = true}
+embedded-hal-nb = { version = "=1.0.0-alpha.3", optional = true}
 
 paste = "1.0"
 pio-proc = {version= "0.2" }
diff --git a/embassy-rp/src/adc.rs b/embassy-rp/src/adc.rs
index 699a0d61d..95780c068 100644
--- a/embassy-rp/src/adc.rs
+++ b/embassy-rp/src/adc.rs
@@ -3,22 +3,17 @@ use core::marker::PhantomData;
 use core::sync::atomic::{compiler_fence, Ordering};
 use core::task::Poll;
 
+use embassy_hal_common::{into_ref, PeripheralRef};
 use embassy_sync::waitqueue::AtomicWaker;
-use embedded_hal_02::adc::{Channel, OneShot};
 
-use crate::gpio::Pin;
+use crate::gpio::sealed::Pin as GpioPin;
+use crate::gpio::{self, AnyPin, Pull};
 use crate::interrupt::typelevel::Binding;
 use crate::interrupt::InterruptExt;
 use crate::peripherals::ADC;
 use crate::{interrupt, pac, peripherals, Peripheral};
-static WAKER: AtomicWaker = AtomicWaker::new();
 
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-#[non_exhaustive]
-pub enum Error {
-    // No errors for now
-}
+static WAKER: AtomicWaker = AtomicWaker::new();
 
 #[non_exhaustive]
 pub struct Config {}
@@ -28,11 +23,75 @@ impl Default for Config {
         Self {}
     }
 }
-pub struct Adc<'d> {
-    phantom: PhantomData<&'d ADC>,
+
+pub struct Pin<'p> {
+    pin: PeripheralRef<'p, AnyPin>,
 }
 
-impl<'d> Adc<'d> {
+impl<'p> Pin<'p> {
+    pub fn new(pin: impl Peripheral<P = impl AdcPin + 'p> + 'p, pull: Pull) -> Self {
+        into_ref!(pin);
+        pin.pad_ctrl().modify(|w| {
+            // manual says:
+            //
+            // > When using an ADC input shared with a GPIO pin, the pin’s
+            // > digital functions must be disabled by setting IE low and OD
+            // > high in the pin’s pad control register
+            w.set_ie(false);
+            w.set_od(true);
+            w.set_pue(pull == Pull::Up);
+            w.set_pde(pull == Pull::Down);
+        });
+        Self { pin: pin.map_into() }
+    }
+
+    fn channel(&self) -> u8 {
+        // this requires adc pins to be sequential and matching the adc channels,
+        // which is the case for rp2040
+        self.pin._pin() - 26
+    }
+}
+
+impl<'d> Drop for Pin<'d> {
+    fn drop(&mut self) {
+        self.pin.pad_ctrl().modify(|w| {
+            w.set_ie(true);
+            w.set_od(false);
+            w.set_pue(false);
+            w.set_pde(true);
+        });
+    }
+}
+
+#[derive(Debug, Eq, PartialEq, Copy, Clone)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum Error {
+    ConversionFailed,
+}
+
+pub trait Mode {}
+
+pub struct Async;
+impl Mode for Async {}
+
+pub struct Blocking;
+impl Mode for Blocking {}
+
+pub struct Adc<'d, M: Mode> {
+    phantom: PhantomData<(&'d ADC, M)>,
+}
+
+impl<'d, M: Mode> Drop for Adc<'d, M> {
+    fn drop(&mut self) {
+        let r = Self::regs();
+        // disable ADC. leaving it enabled comes with a ~150µA static
+        // current draw. the temperature sensor has already been disabled
+        // by the temperature-reading methods, so we don't need to touch that.
+        r.cs().write(|w| w.set_en(false));
+    }
+}
+
+impl<'d, M: Mode> Adc<'d, M> {
     #[inline]
     fn regs() -> pac::adc::Adc {
         pac::ADC
@@ -45,11 +104,7 @@ impl<'d> Adc<'d> {
         ret
     }
 
-    pub fn new(
-        _inner: impl Peripheral<P = ADC> + 'd,
-        _irq: impl Binding<interrupt::typelevel::ADC_IRQ_FIFO, InterruptHandler>,
-        _config: Config,
-    ) -> Self {
+    fn setup() {
         let reset = Self::reset();
         crate::reset::reset(reset);
         crate::reset::unreset_wait(reset);
@@ -58,6 +113,43 @@ impl<'d> Adc<'d> {
         r.cs().write(|w| w.set_en(true));
         // Wait for ADC ready
         while !r.cs().read().ready() {}
+    }
+
+    fn sample_blocking(channel: u8) -> Result<u16, Error> {
+        let r = Self::regs();
+        r.cs().modify(|w| {
+            w.set_ainsel(channel);
+            w.set_start_once(true);
+            w.set_err(true);
+        });
+        while !r.cs().read().ready() {}
+        match r.cs().read().err() {
+            true => Err(Error::ConversionFailed),
+            false => Ok(r.result().read().result().into()),
+        }
+    }
+
+    pub fn blocking_read(&mut self, pin: &mut Pin) -> Result<u16, Error> {
+        Self::sample_blocking(pin.channel())
+    }
+
+    pub fn blocking_read_temperature(&mut self) -> Result<u16, Error> {
+        let r = Self::regs();
+        r.cs().modify(|w| w.set_ts_en(true));
+        while !r.cs().read().ready() {}
+        let result = Self::sample_blocking(4);
+        r.cs().modify(|w| w.set_ts_en(false));
+        result
+    }
+}
+
+impl<'d> Adc<'d, Async> {
+    pub fn new(
+        _inner: impl Peripheral<P = ADC> + 'd,
+        _irq: impl Binding<interrupt::typelevel::ADC_IRQ_FIFO, InterruptHandler>,
+        _config: Config,
+    ) -> Self {
+        Self::setup();
 
         // Setup IRQ
         interrupt::ADC_IRQ_FIFO.unpend();
@@ -80,76 +172,42 @@ impl<'d> Adc<'d> {
         .await;
     }
 
-    pub async fn read<PIN: Channel<Adc<'d>, ID = u8> + Pin>(&mut self, pin: &mut PIN) -> u16 {
+    async fn sample_async(channel: u8) -> Result<u16, Error> {
         let r = Self::regs();
-        // disable pull-down and pull-up resistors
-        // pull-down resistors are enabled by default
-        pin.pad_ctrl().modify(|w| {
-            w.set_ie(true);
-            let (pu, pd) = (false, false);
-            w.set_pue(pu);
-            w.set_pde(pd);
-        });
         r.cs().modify(|w| {
-            w.set_ainsel(PIN::channel());
-            w.set_start_once(true)
+            w.set_ainsel(channel);
+            w.set_start_once(true);
+            w.set_err(true);
         });
         Self::wait_for_ready().await;
-        r.result().read().result().into()
+        match r.cs().read().err() {
+            true => Err(Error::ConversionFailed),
+            false => Ok(r.result().read().result().into()),
+        }
     }
 
-    pub async fn read_temperature(&mut self) -> u16 {
+    pub async fn read(&mut self, pin: &mut Pin<'_>) -> Result<u16, Error> {
+        Self::sample_async(pin.channel()).await
+    }
+
+    pub async fn read_temperature(&mut self) -> Result<u16, Error> {
         let r = Self::regs();
         r.cs().modify(|w| w.set_ts_en(true));
         if !r.cs().read().ready() {
             Self::wait_for_ready().await;
         }
-        r.cs().modify(|w| {
-            w.set_ainsel(4);
-            w.set_start_once(true)
-        });
-        Self::wait_for_ready().await;
-        r.result().read().result().into()
-    }
-
-    pub fn blocking_read<PIN: Channel<Adc<'d>, ID = u8> + Pin>(&mut self, pin: &mut PIN) -> u16 {
-        let r = Self::regs();
-        pin.pad_ctrl().modify(|w| {
-            w.set_ie(true);
-            let (pu, pd) = (false, false);
-            w.set_pue(pu);
-            w.set_pde(pd);
-        });
-        r.cs().modify(|w| {
-            w.set_ainsel(PIN::channel());
-            w.set_start_once(true)
-        });
-        while !r.cs().read().ready() {}
-        r.result().read().result().into()
-    }
-
-    pub fn blocking_read_temperature(&mut self) -> u16 {
-        let r = Self::regs();
-        r.cs().modify(|w| w.set_ts_en(true));
-        while !r.cs().read().ready() {}
-        r.cs().modify(|w| {
-            w.set_ainsel(4);
-            w.set_start_once(true)
-        });
-        while !r.cs().read().ready() {}
-        r.result().read().result().into()
+        let result = Self::sample_async(4).await;
+        r.cs().modify(|w| w.set_ts_en(false));
+        result
     }
 }
 
-macro_rules! impl_pin {
-    ($pin:ident, $channel:expr) => {
-        impl Channel<Adc<'static>> for peripherals::$pin {
-            type ID = u8;
-            fn channel() -> u8 {
-                $channel
-            }
-        }
-    };
+impl<'d> Adc<'d, Blocking> {
+    pub fn new_blocking(_inner: impl Peripheral<P = ADC> + 'd, _config: Config) -> Self {
+        Self::setup();
+
+        Self { phantom: PhantomData }
+    }
 }
 
 pub struct InterruptHandler {
@@ -158,24 +216,33 @@ pub struct InterruptHandler {
 
 impl interrupt::typelevel::Handler<interrupt::typelevel::ADC_IRQ_FIFO> for InterruptHandler {
     unsafe fn on_interrupt() {
-        let r = Adc::regs();
+        let r = Adc::<Async>::regs();
         r.inte().write(|w| w.set_fifo(false));
         WAKER.wake();
     }
 }
 
+mod sealed {
+    pub trait AdcPin: crate::gpio::sealed::Pin {
+        fn channel(&mut self) -> u8;
+    }
+}
+
+pub trait AdcPin: sealed::AdcPin + gpio::Pin {}
+
+macro_rules! impl_pin {
+    ($pin:ident, $channel:expr) => {
+        impl sealed::AdcPin for peripherals::$pin {
+            fn channel(&mut self) -> u8 {
+                $channel
+            }
+        }
+
+        impl AdcPin for peripherals::$pin {}
+    };
+}
+
 impl_pin!(PIN_26, 0);
 impl_pin!(PIN_27, 1);
 impl_pin!(PIN_28, 2);
 impl_pin!(PIN_29, 3);
-
-impl<WORD, PIN> OneShot<Adc<'static>, WORD, PIN> for Adc<'static>
-where
-    WORD: From<u16>,
-    PIN: Channel<Adc<'static>, ID = u8> + Pin,
-{
-    type Error = ();
-    fn read(&mut self, pin: &mut PIN) -> nb::Result<WORD, Self::Error> {
-        Ok(self.blocking_read(pin).into())
-    }
-}
diff --git a/embassy-rp/src/clocks.rs b/embassy-rp/src/clocks.rs
index ddd61d224..acb21dce5 100644
--- a/embassy-rp/src/clocks.rs
+++ b/embassy-rp/src/clocks.rs
@@ -308,6 +308,7 @@ pub(crate) unsafe fn init(config: ClockConfig) {
     // - QSPI (we're using it to run this code!)
     // - PLLs (it may be suicide if that's what's clocking us)
     // - USB, SYSCFG (breaks usb-to-swd on core1)
+    // - RTC (else there would be no more time...)
     let mut peris = reset::ALL_PERIPHERALS;
     peris.set_io_qspi(false);
     // peris.set_io_bank0(false); // might be suicide if we're clocked from gpin
@@ -317,6 +318,7 @@ pub(crate) unsafe fn init(config: ClockConfig) {
     // TODO investigate if usb should be unreset here
     peris.set_usbctrl(false);
     peris.set_syscfg(false);
+    peris.set_rtc(false);
     reset::reset(peris);
 
     // Disable resus that may be enabled from previous software
diff --git a/embassy-rp/src/gpio.rs b/embassy-rp/src/gpio.rs
index f8048a4dd..a3d330cdc 100644
--- a/embassy-rp/src/gpio.rs
+++ b/embassy-rp/src/gpio.rs
@@ -41,7 +41,7 @@ impl From<Level> for bool {
 }
 
 /// Represents a pull setting for an input.
-#[derive(Debug, Eq, PartialEq)]
+#[derive(Debug, Clone, Copy, Eq, PartialEq)]
 pub enum Pull {
     None,
     Up,
@@ -566,13 +566,13 @@ impl<'d, T: Pin> Flex<'d, T> {
     /// Is the output level high?
     #[inline]
     pub fn is_set_high(&self) -> bool {
-        (self.pin.sio_out().value().read() & self.bit()) == 0
+        !self.is_set_low()
     }
 
     /// Is the output level low?
     #[inline]
     pub fn is_set_low(&self) -> bool {
-        !self.is_set_high()
+        (self.pin.sio_out().value().read() & self.bit()) == 0
     }
 
     /// What level output is set to
diff --git a/embassy-rp/src/i2c.rs b/embassy-rp/src/i2c.rs
index 791c64554..9b85b2345 100644
--- a/embassy-rp/src/i2c.rs
+++ b/embassy-rp/src/i2c.rs
@@ -716,6 +716,9 @@ mod nightly {
         async fn transaction(&mut self, address: A, operations: &mut [Operation<'_>]) -> Result<(), Self::Error> {
             let addr: u16 = address.into();
 
+            if operations.len() > 0 {
+                Self::setup(addr)?;
+            }
             let mut iterator = operations.iter_mut();
 
             while let Some(op) = iterator.next() {
@@ -723,11 +726,9 @@ mod nightly {
 
                 match op {
                     Operation::Read(buffer) => {
-                        Self::setup(addr)?;
                         self.read_async_internal(buffer, false, last).await?;
                     }
                     Operation::Write(buffer) => {
-                        Self::setup(addr)?;
                         self.write_async_internal(buffer.into_iter().cloned(), last).await?;
                     }
                 }
diff --git a/embassy-rp/src/lib.rs b/embassy-rp/src/lib.rs
index 4fd3cb46a..4f205a16e 100644
--- a/embassy-rp/src/lib.rs
+++ b/embassy-rp/src/lib.rs
@@ -252,7 +252,6 @@ pub fn init(config: config::Config) -> Peripherals {
         #[cfg(feature = "time-driver")]
         timer::init();
         dma::init();
-        pio::init();
         gpio::init();
     }
 
diff --git a/embassy-rp/src/pio.rs b/embassy-rp/src/pio.rs
index 30648e8ea..72a2f44ed 100644
--- a/embassy-rp/src/pio.rs
+++ b/embassy-rp/src/pio.rs
@@ -16,12 +16,12 @@ use pio::{SideSet, Wrap};
 use crate::dma::{Channel, Transfer, Word};
 use crate::gpio::sealed::Pin as SealedPin;
 use crate::gpio::{self, AnyPin, Drive, Level, Pull, SlewRate};
-use crate::interrupt::InterruptExt;
+use crate::interrupt::typelevel::{Binding, Handler, Interrupt};
 use crate::pac::dma::vals::TreqSel;
 use crate::relocate::RelocatedProgram;
-use crate::{interrupt, pac, peripherals, pio_instr_util, RegExt};
+use crate::{pac, peripherals, pio_instr_util, RegExt};
 
-struct Wakers([AtomicWaker; 12]);
+pub struct Wakers([AtomicWaker; 12]);
 
 impl Wakers {
     #[inline(always)]
@@ -38,10 +38,6 @@ impl Wakers {
     }
 }
 
-const NEW_AW: AtomicWaker = AtomicWaker::new();
-const PIO_WAKERS_INIT: Wakers = Wakers([NEW_AW; 12]);
-static WAKERS: [Wakers; 2] = [PIO_WAKERS_INIT; 2];
-
 #[derive(Clone, Copy, PartialEq, Eq, Default, Debug)]
 #[cfg_attr(feature = "defmt", derive(defmt::Format))]
 #[repr(u8)]
@@ -85,42 +81,20 @@ const RXNEMPTY_MASK: u32 = 1 << 0;
 const TXNFULL_MASK: u32 = 1 << 4;
 const SMIRQ_MASK: u32 = 1 << 8;
 
-#[cfg(feature = "rt")]
-#[interrupt]
-fn PIO0_IRQ_0() {
-    use crate::pac;
-    let ints = pac::PIO0.irqs(0).ints().read().0;
-    for bit in 0..12 {
-        if ints & (1 << bit) != 0 {
-            WAKERS[0].0[bit].wake();
-        }
-    }
-    pac::PIO0.irqs(0).inte().write_clear(|m| m.0 = ints);
+pub struct InterruptHandler<PIO: Instance> {
+    _pio: PhantomData<PIO>,
 }
 
-#[cfg(feature = "rt")]
-#[interrupt]
-fn PIO1_IRQ_0() {
-    use crate::pac;
-    let ints = pac::PIO1.irqs(0).ints().read().0;
-    for bit in 0..12 {
-        if ints & (1 << bit) != 0 {
-            WAKERS[1].0[bit].wake();
+impl<PIO: Instance> Handler<PIO::Interrupt> for InterruptHandler<PIO> {
+    unsafe fn on_interrupt() {
+        let ints = PIO::PIO.irqs(0).ints().read().0;
+        for bit in 0..12 {
+            if ints & (1 << bit) != 0 {
+                PIO::wakers().0[bit].wake();
+            }
         }
+        PIO::PIO.irqs(0).inte().write_clear(|m| m.0 = ints);
     }
-    pac::PIO1.irqs(0).inte().write_clear(|m| m.0 = ints);
-}
-
-pub(crate) unsafe fn init() {
-    interrupt::PIO0_IRQ_0.disable();
-    interrupt::PIO0_IRQ_0.set_priority(interrupt::Priority::P3);
-    pac::PIO0.irqs(0).inte().write(|m| m.0 = 0);
-    interrupt::PIO0_IRQ_0.enable();
-
-    interrupt::PIO1_IRQ_0.disable();
-    interrupt::PIO1_IRQ_0.set_priority(interrupt::Priority::P3);
-    pac::PIO1.irqs(0).inte().write(|m| m.0 = 0);
-    interrupt::PIO1_IRQ_0.enable();
 }
 
 /// Future that waits for TX-FIFO to become writable
@@ -144,7 +118,7 @@ impl<'a, 'd, PIO: Instance, const SM: usize> Future for FifoOutFuture<'a, 'd, PI
         if self.get_mut().sm_tx.try_push(value) {
             Poll::Ready(())
         } else {
-            WAKERS[PIO::PIO_NO as usize].fifo_out()[SM].register(cx.waker());
+            PIO::wakers().fifo_out()[SM].register(cx.waker());
             PIO::PIO.irqs(0).inte().write_set(|m| {
                 m.0 = TXNFULL_MASK << SM;
             });
@@ -181,7 +155,7 @@ impl<'a, 'd, PIO: Instance, const SM: usize> Future for FifoInFuture<'a, 'd, PIO
         if let Some(v) = self.sm_rx.try_pull() {
             Poll::Ready(v)
         } else {
-            WAKERS[PIO::PIO_NO as usize].fifo_in()[SM].register(cx.waker());
+            PIO::wakers().fifo_in()[SM].register(cx.waker());
             PIO::PIO.irqs(0).inte().write_set(|m| {
                 m.0 = RXNEMPTY_MASK << SM;
             });
@@ -217,7 +191,7 @@ impl<'a, 'd, PIO: Instance> Future for IrqFuture<'a, 'd, PIO> {
             return Poll::Ready(());
         }
 
-        WAKERS[PIO::PIO_NO as usize].irq()[self.irq_no as usize].register(cx.waker());
+        PIO::wakers().irq()[self.irq_no as usize].register(cx.waker());
         PIO::PIO.irqs(0).inte().write_set(|m| {
             m.0 = SMIRQ_MASK << self.irq_no;
         });
@@ -949,9 +923,11 @@ pub struct Pio<'d, PIO: Instance> {
 }
 
 impl<'d, PIO: Instance> Pio<'d, PIO> {
-    pub fn new(_pio: impl Peripheral<P = PIO> + 'd) -> Self {
+    pub fn new(_pio: impl Peripheral<P = PIO> + 'd, _irq: impl Binding<PIO::Interrupt, InterruptHandler<PIO>>) -> Self {
         PIO::state().users.store(5, Ordering::Release);
         PIO::state().used_pins.store(0, Ordering::Release);
+        PIO::Interrupt::unpend();
+        unsafe { PIO::Interrupt::enable() };
         Self {
             common: Common {
                 instructions_used: 0,
@@ -1017,6 +993,15 @@ mod sealed {
         const PIO_NO: u8;
         const PIO: &'static crate::pac::pio::Pio;
         const FUNCSEL: crate::pac::io::vals::Gpio0ctrlFuncsel;
+        type Interrupt: crate::interrupt::typelevel::Interrupt;
+
+        #[inline]
+        fn wakers() -> &'static Wakers {
+            const NEW_AW: AtomicWaker = AtomicWaker::new();
+            static WAKERS: Wakers = Wakers([NEW_AW; 12]);
+
+            &WAKERS
+        }
 
         #[inline]
         fn state() -> &'static State {
@@ -1033,18 +1018,19 @@ mod sealed {
 pub trait Instance: sealed::Instance + Sized + Unpin {}
 
 macro_rules! impl_pio {
-    ($name:ident, $pio:expr, $pac:ident, $funcsel:ident) => {
+    ($name:ident, $pio:expr, $pac:ident, $funcsel:ident, $irq:ident) => {
         impl sealed::Instance for peripherals::$name {
             const PIO_NO: u8 = $pio;
             const PIO: &'static pac::pio::Pio = &pac::$pac;
             const FUNCSEL: pac::io::vals::Gpio0ctrlFuncsel = pac::io::vals::Gpio0ctrlFuncsel::$funcsel;
+            type Interrupt = crate::interrupt::typelevel::$irq;
         }
         impl Instance for peripherals::$name {}
     };
 }
 
-impl_pio!(PIO0, 0, PIO0, PIO0_0);
-impl_pio!(PIO1, 1, PIO1, PIO1_0);
+impl_pio!(PIO0, 0, PIO0, PIO0_0, PIO0_IRQ_0);
+impl_pio!(PIO1, 1, PIO1, PIO1_0, PIO1_IRQ_0);
 
 pub trait PioPin: sealed::PioPin + gpio::Pin {}
 
diff --git a/embassy-rp/src/rtc/datetime_no_deps.rs b/embassy-rp/src/rtc/datetime_no_deps.rs
index 92770e984..ea899c339 100644
--- a/embassy-rp/src/rtc/datetime_no_deps.rs
+++ b/embassy-rp/src/rtc/datetime_no_deps.rs
@@ -25,6 +25,7 @@ pub enum Error {
 }
 
 /// Structure containing date and time information
+#[derive(Clone, Debug)]
 pub struct DateTime {
     /// 0..4095
     pub year: u16,
diff --git a/embassy-rp/src/rtc/mod.rs b/embassy-rp/src/rtc/mod.rs
index b18f12fc4..1b33fdf8d 100644
--- a/embassy-rp/src/rtc/mod.rs
+++ b/embassy-rp/src/rtc/mod.rs
@@ -12,26 +12,24 @@ pub use self::datetime::{DateTime, DayOfWeek, Error as DateTimeError};
 use crate::clocks::clk_rtc_freq;
 
 /// A reference to the real time clock of the system
-pub struct RealTimeClock<'d, T: Instance> {
+pub struct Rtc<'d, T: Instance> {
     inner: PeripheralRef<'d, T>,
 }
 
-impl<'d, T: Instance> RealTimeClock<'d, T> {
+impl<'d, T: Instance> Rtc<'d, T> {
     /// Create a new instance of the real time clock, with the given date as an initial value.
     ///
     /// # Errors
     ///
     /// Will return `RtcError::InvalidDateTime` if the datetime is not a valid range.
-    pub fn new(inner: impl Peripheral<P = T> + 'd, initial_date: DateTime) -> Result<Self, RtcError> {
+    pub fn new(inner: impl Peripheral<P = T> + 'd) -> Self {
         into_ref!(inner);
 
         // Set the RTC divider
         inner.regs().clkdiv_m1().write(|w| w.set_clkdiv_m1(clk_rtc_freq() - 1));
 
-        let mut result = Self { inner };
-        result.set_leap_year_check(true); // should be on by default, make sure this is the case.
-        result.set_datetime(initial_date)?;
-        Ok(result)
+        let result = Self { inner };
+        result
     }
 
     /// Enable or disable the leap year check. The rp2040 chip will always add a Feb 29th on every year that is divisable by 4, but this may be incorrect (e.g. on century years). This function allows you to disable this check.
@@ -43,7 +41,37 @@ impl<'d, T: Instance> RealTimeClock<'d, T> {
         });
     }
 
-    /// Checks to see if this RealTimeClock is running
+    /// Set the time from internal format
+    pub fn restore(&mut self, ymd: rp_pac::rtc::regs::Rtc1, hms: rp_pac::rtc::regs::Rtc0) {
+        // disable RTC while we configure it
+        self.inner.regs().ctrl().modify(|w| w.set_rtc_enable(false));
+        while self.inner.regs().ctrl().read().rtc_active() {
+            core::hint::spin_loop();
+        }
+
+        self.inner.regs().setup_0().write(|w| {
+            *w = rp_pac::rtc::regs::Setup0(ymd.0);
+        });
+        self.inner.regs().setup_1().write(|w| {
+            *w = rp_pac::rtc::regs::Setup1(hms.0);
+        });
+
+        // Load the new datetime and re-enable RTC
+        self.inner.regs().ctrl().write(|w| w.set_load(true));
+        self.inner.regs().ctrl().write(|w| w.set_rtc_enable(true));
+        while !self.inner.regs().ctrl().read().rtc_active() {
+            core::hint::spin_loop();
+        }
+    }
+
+    /// Get the time in internal format
+    pub fn save(&mut self) -> (rp_pac::rtc::regs::Rtc1, rp_pac::rtc::regs::Rtc0) {
+        let rtc_0: rp_pac::rtc::regs::Rtc0 = self.inner.regs().rtc_0().read();
+        let rtc_1 = self.inner.regs().rtc_1().read();
+        (rtc_1, rtc_0)
+    }
+
+    /// Checks to see if this Rtc is running
     pub fn is_running(&self) -> bool {
         self.inner.regs().ctrl().read().rtc_active()
     }
@@ -113,8 +141,8 @@ impl<'d, T: Instance> RealTimeClock<'d, T> {
     /// # fn main() { }
     /// # #[cfg(not(feature = "chrono"))]
     /// # fn main() {
-    /// # use embassy_rp::rtc::{RealTimeClock, DateTimeFilter};
-    /// # let mut real_time_clock: RealTimeClock<embassy_rp::peripherals::RTC> = unsafe { core::mem::zeroed() };
+    /// # use embassy_rp::rtc::{Rtc, DateTimeFilter};
+    /// # let mut real_time_clock: Rtc<embassy_rp::peripherals::RTC> = unsafe { core::mem::zeroed() };
     /// let now = real_time_clock.now().unwrap();
     /// real_time_clock.schedule_alarm(
     ///     DateTimeFilter::default()
@@ -150,7 +178,7 @@ impl<'d, T: Instance> RealTimeClock<'d, T> {
     }
 }
 
-/// Errors that can occur on methods on [RealTimeClock]
+/// Errors that can occur on methods on [Rtc]
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub enum RtcError {
     /// An invalid DateTime was given or stored on the hardware.
diff --git a/embassy-rp/src/spi.rs b/embassy-rp/src/spi.rs
index e817d074e..af101cf4a 100644
--- a/embassy-rp/src/spi.rs
+++ b/embassy-rp/src/spi.rs
@@ -545,25 +545,19 @@ mod eh1 {
         type Error = Error;
     }
 
-    impl<'d, T: Instance, M: Mode> embedded_hal_1::spi::SpiBusFlush for Spi<'d, T, M> {
+    impl<'d, T: Instance, M: Mode> embedded_hal_1::spi::SpiBus<u8> for Spi<'d, T, M> {
         fn flush(&mut self) -> Result<(), Self::Error> {
             Ok(())
         }
-    }
 
-    impl<'d, T: Instance, M: Mode> embedded_hal_1::spi::SpiBusRead<u8> for Spi<'d, T, M> {
         fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
             self.blocking_transfer(words, &[])
         }
-    }
 
-    impl<'d, T: Instance, M: Mode> embedded_hal_1::spi::SpiBusWrite<u8> for Spi<'d, T, M> {
         fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> {
             self.blocking_write(words)
         }
-    }
 
-    impl<'d, T: Instance, M: Mode> embedded_hal_1::spi::SpiBus<u8> for Spi<'d, T, M> {
         fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> {
             self.blocking_transfer(read, write)
         }
@@ -578,30 +572,24 @@ mod eh1 {
 mod eha {
     use super::*;
 
-    impl<'d, T: Instance> embedded_hal_async::spi::SpiBusFlush for Spi<'d, T, Async> {
+    impl<'d, T: Instance> embedded_hal_async::spi::SpiBus<u8> for Spi<'d, T, Async> {
         async fn flush(&mut self) -> Result<(), Self::Error> {
             Ok(())
         }
-    }
 
-    impl<'d, T: Instance> embedded_hal_async::spi::SpiBusWrite<u8> for Spi<'d, T, Async> {
         async fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> {
             self.write(words).await
         }
-    }
 
-    impl<'d, T: Instance> embedded_hal_async::spi::SpiBusRead<u8> for Spi<'d, T, Async> {
         async fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
             self.read(words).await
         }
-    }
 
-    impl<'d, T: Instance> embedded_hal_async::spi::SpiBus<u8> for Spi<'d, T, Async> {
-        async fn transfer<'a>(&'a mut self, read: &'a mut [u8], write: &'a [u8]) -> Result<(), Self::Error> {
+        async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> {
             self.transfer(read, write).await
         }
 
-        async fn transfer_in_place<'a>(&'a mut self, words: &'a mut [u8]) -> Result<(), Self::Error> {
+        async fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
             self.transfer_in_place(words).await
         }
     }
diff --git a/embassy-rp/src/usb.rs b/embassy-rp/src/usb.rs
index b3f3bd927..4ab881f6e 100644
--- a/embassy-rp/src/usb.rs
+++ b/embassy-rp/src/usb.rs
@@ -361,6 +361,7 @@ impl<'d, T: Instance> driver::Bus for Bus<'d, T> {
 
             let regs = T::regs();
             let siestatus = regs.sie_status().read();
+            let intrstatus = regs.intr().read();
 
             if siestatus.resume() {
                 regs.sie_status().write(|w| w.set_resume(true));
@@ -389,7 +390,7 @@ impl<'d, T: Instance> driver::Bus for Bus<'d, T> {
                 return Poll::Ready(Event::Reset);
             }
 
-            if siestatus.suspended() {
+            if siestatus.suspended() && intrstatus.dev_suspend() {
                 regs.sie_status().write(|w| w.set_suspended(true));
                 return Poll::Ready(Event::Suspend);
             }
diff --git a/embassy-rp/src/watchdog.rs b/embassy-rp/src/watchdog.rs
index d37795cc9..f1e986ec7 100644
--- a/embassy-rp/src/watchdog.rs
+++ b/embassy-rp/src/watchdog.rs
@@ -107,4 +107,36 @@ impl Watchdog {
             w.set_trigger(true);
         })
     }
+
+    /// Store data in scratch register
+    pub fn set_scratch(&mut self, index: usize, value: u32) {
+        let watchdog = pac::WATCHDOG;
+        match index {
+            0 => watchdog.scratch0().write(|w| *w = value),
+            1 => watchdog.scratch1().write(|w| *w = value),
+            2 => watchdog.scratch2().write(|w| *w = value),
+            3 => watchdog.scratch3().write(|w| *w = value),
+            4 => watchdog.scratch4().write(|w| *w = value),
+            5 => watchdog.scratch5().write(|w| *w = value),
+            6 => watchdog.scratch6().write(|w| *w = value),
+            7 => watchdog.scratch7().write(|w| *w = value),
+            _ => panic!("Invalid watchdog scratch index"),
+        }
+    }
+
+    /// Read data from scratch register
+    pub fn get_scratch(&mut self, index: usize) -> u32 {
+        let watchdog = pac::WATCHDOG;
+        match index {
+            0 => watchdog.scratch0().read(),
+            1 => watchdog.scratch1().read(),
+            2 => watchdog.scratch2().read(),
+            3 => watchdog.scratch3().read(),
+            4 => watchdog.scratch4().read(),
+            5 => watchdog.scratch5().read(),
+            6 => watchdog.scratch6().read(),
+            7 => watchdog.scratch7().read(),
+            _ => panic!("Invalid watchdog scratch index"),
+        }
+    }
 }
diff --git a/embassy-stm32-wpan/Cargo.toml b/embassy-stm32-wpan/Cargo.toml
index 4b830cab3..6cd122200 100644
--- a/embassy-stm32-wpan/Cargo.toml
+++ b/embassy-stm32-wpan/Cargo.toml
@@ -5,18 +5,19 @@ edition = "2021"
 license = "MIT OR Apache-2.0"
 
 [package.metadata.embassy_docs]
-src_base = "https://github.com/embassy-rs/embassy/blob/embassy-stm32-wpan-v$VERSION/embassy-stm32-wpan/src"
-src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-stm32-wpan/src"
+src_base = "https://github.com/embassy-rs/embassy/blob/embassy-stm32-wpan-v$VERSION/embassy-stm32-wpan/src/"
+src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-stm32-wpan/src/"
 target = "thumbv7em-none-eabihf"
 features = ["stm32wb55rg"]
 
 [dependencies]
 embassy-stm32 = { version = "0.1.0", path = "../embassy-stm32" }
 embassy-sync = { version = "0.2.0", path = "../embassy-sync" }
-embassy-time = { version = "0.1.0", path = "../embassy-time", optional = true }
+embassy-time = { version = "0.1.2", path = "../embassy-time", optional = true }
 embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
 embassy-hal-common = { version = "0.1.0", path = "../embassy-hal-common" }
 embassy-embedded-hal = { version = "0.1.0", path = "../embassy-embedded-hal" }
+embassy-net-driver = { version = "0.1.0", path = "../embassy-net-driver", optional=true }
 
 defmt = { version = "0.3", optional = true }
 cortex-m = "0.7.6"
@@ -25,13 +26,15 @@ aligned = "0.4.1"
 
 bit_field = "0.10.2"
 stm32-device-signature = { version = "0.3.3", features = ["stm32wb5x"] }
-stm32wb-hci = { version = "0.1.2", features = ["version-5-0"], optional = true }
+stm32wb-hci = { version = "0.1.3", optional = true }
+futures = { version = "0.3.17", default-features = false, features = ["async-await"] }
+bitflags = { version = "2.3.3", optional = true }
 
 [features]
-defmt = ["dep:defmt", "embassy-sync/defmt", "embassy-embedded-hal/defmt", "embassy-hal-common/defmt"]
+defmt = ["dep:defmt", "embassy-sync/defmt", "embassy-embedded-hal/defmt", "embassy-hal-common/defmt", "stm32wb-hci?/defmt"]
 
 ble = ["dep:stm32wb-hci"]
-mac = []
+mac = ["dep:bitflags", "dep:embassy-net-driver" ]
 
 stm32wb10cc = [ "embassy-stm32/stm32wb10cc" ]
 stm32wb15cc = [ "embassy-stm32/stm32wb15cc" ]
@@ -48,4 +51,4 @@ stm32wb55rg = [ "embassy-stm32/stm32wb55rg" ]
 stm32wb55vc = [ "embassy-stm32/stm32wb55vc" ]
 stm32wb55ve = [ "embassy-stm32/stm32wb55ve" ]
 stm32wb55vg = [ "embassy-stm32/stm32wb55vg" ]
-stm32wb55vy = [ "embassy-stm32/stm32wb55vy" ]
\ No newline at end of file
+stm32wb55vy = [ "embassy-stm32/stm32wb55vy" ]
diff --git a/embassy-stm32-wpan/src/cmd.rs b/embassy-stm32-wpan/src/cmd.rs
index 8428b6ffc..928357384 100644
--- a/embassy-stm32-wpan/src/cmd.rs
+++ b/embassy-stm32-wpan/src/cmd.rs
@@ -37,7 +37,7 @@ pub struct CmdSerialStub {
 }
 
 #[derive(Copy, Clone, Default)]
-#[repr(C, packed(4))]
+#[repr(C, packed)]
 pub struct CmdPacket {
     pub header: PacketHeader,
     pub cmdserial: CmdSerial,
diff --git a/embassy-stm32-wpan/src/consts.rs b/embassy-stm32-wpan/src/consts.rs
index f234151d7..bd70851ea 100644
--- a/embassy-stm32-wpan/src/consts.rs
+++ b/embassy-stm32-wpan/src/consts.rs
@@ -6,6 +6,8 @@ use crate::PacketHeader;
 #[derive(Debug)]
 #[repr(C)]
 pub enum TlPacketType {
+    MacCmd = 0x00,
+
     BleCmd = 0x01,
     AclData = 0x02,
     BleEvt = 0x04,
@@ -79,6 +81,7 @@ pub const CFG_TL_BLE_MOST_EVENT_PAYLOAD_SIZE: usize = 255;
 pub const TL_BLE_EVENT_FRAME_SIZE: usize = TL_EVT_HEADER_SIZE + CFG_TL_BLE_MOST_EVENT_PAYLOAD_SIZE;
 
 pub const POOL_SIZE: usize = CFG_TL_BLE_EVT_QUEUE_LENGTH * 4 * divc(TL_PACKET_HEADER_SIZE + TL_BLE_EVENT_FRAME_SIZE, 4);
+pub const C_SIZE_CMD_STRING: usize = 256;
 
 pub const fn divc(x: usize, y: usize) -> usize {
     (x + y - 1) / y
diff --git a/embassy-stm32-wpan/src/lib.rs b/embassy-stm32-wpan/src/lib.rs
index 99c610583..6836d7a8b 100644
--- a/embassy-stm32-wpan/src/lib.rs
+++ b/embassy-stm32-wpan/src/lib.rs
@@ -1,5 +1,6 @@
 #![no_std]
-#![cfg_attr(feature = "ble", feature(async_fn_in_trait))]
+#![cfg_attr(any(feature = "ble", feature = "mac"), feature(async_fn_in_trait))]
+#![cfg_attr(feature = "mac", feature(type_alias_impl_trait, concat_bytes))]
 
 // This must go FIRST so that all the other modules see its macros.
 pub mod fmt;
@@ -26,6 +27,9 @@ pub mod sub;
 pub mod tables;
 pub mod unsafe_linked_list;
 
+#[cfg(feature = "mac")]
+pub mod mac;
+
 #[cfg(feature = "ble")]
 pub use crate::sub::ble::hci;
 
@@ -60,9 +64,9 @@ impl<'d> TlMbox<'d> {
                 mem_manager_table: TL_MEM_MANAGER_TABLE.as_ptr(),
                 traces_table: TL_TRACES_TABLE.as_ptr(),
                 mac_802_15_4_table: TL_MAC_802_15_4_TABLE.as_ptr(),
-                // zigbee_table: TL_ZIGBEE_TABLE.as_ptr(),
-                // lld_tests_table: TL_LLD_TESTS_TABLE.as_ptr(),
-                // ble_lld_table: TL_BLE_LLD_TABLE.as_ptr(),
+                zigbee_table: TL_ZIGBEE_TABLE.as_ptr(),
+                lld_tests_table: TL_LLD_TESTS_TABLE.as_ptr(),
+                ble_lld_table: TL_BLE_LLD_TABLE.as_ptr(),
             });
 
             TL_SYS_TABLE
@@ -87,15 +91,15 @@ impl<'d> TlMbox<'d> {
             TL_MAC_802_15_4_TABLE
                 .as_mut_ptr()
                 .write_volatile(MaybeUninit::zeroed().assume_init());
-            //            TL_ZIGBEE_TABLE
-            //                .as_mut_ptr()
-            //                .write_volatile(MaybeUninit::zeroed().assume_init());
-            //            TL_LLD_TESTS_TABLE
-            //                .as_mut_ptr()
-            //                .write_volatile(MaybeUninit::zeroed().assume_init());
-            //            TL_BLE_LLD_TABLE
-            //                .as_mut_ptr()
-            //                .write_volatile(MaybeUninit::zeroed().assume_init());
+            TL_ZIGBEE_TABLE
+                .as_mut_ptr()
+                .write_volatile(MaybeUninit::zeroed().assume_init());
+            TL_LLD_TESTS_TABLE
+                .as_mut_ptr()
+                .write_volatile(MaybeUninit::zeroed().assume_init());
+            TL_BLE_LLD_TABLE
+                .as_mut_ptr()
+                .write_volatile(MaybeUninit::zeroed().assume_init());
 
             EVT_POOL
                 .as_mut_ptr()
@@ -103,18 +107,30 @@ impl<'d> TlMbox<'d> {
             SYS_SPARE_EVT_BUF
                 .as_mut_ptr()
                 .write_volatile(MaybeUninit::zeroed().assume_init());
-            BLE_SPARE_EVT_BUF
+            CS_BUFFER
                 .as_mut_ptr()
                 .write_volatile(MaybeUninit::zeroed().assume_init());
 
+            #[cfg(feature = "ble")]
             {
+                BLE_SPARE_EVT_BUF
+                    .as_mut_ptr()
+                    .write_volatile(MaybeUninit::zeroed().assume_init());
+
                 BLE_CMD_BUFFER
                     .as_mut_ptr()
                     .write_volatile(MaybeUninit::zeroed().assume_init());
                 HCI_ACL_DATA_BUFFER
                     .as_mut_ptr()
                     .write_volatile(MaybeUninit::zeroed().assume_init());
-                CS_BUFFER
+            }
+
+            #[cfg(feature = "mac")]
+            {
+                MAC_802_15_4_CMD_BUFFER
+                    .as_mut_ptr()
+                    .write_volatile(MaybeUninit::zeroed().assume_init());
+                MAC_802_15_4_NOTIF_RSP_EVT_BUFFER
                     .as_mut_ptr()
                     .write_volatile(MaybeUninit::zeroed().assume_init());
             }
diff --git a/embassy-stm32-wpan/src/mac/commands.rs b/embassy-stm32-wpan/src/mac/commands.rs
new file mode 100644
index 000000000..8f6dcbbbc
--- /dev/null
+++ b/embassy-stm32-wpan/src/mac/commands.rs
@@ -0,0 +1,476 @@
+use core::{mem, slice};
+
+use super::opcodes::OpcodeM4ToM0;
+use super::typedefs::{
+    AddressMode, Capabilities, DisassociationReason, GtsCharacteristics, KeyIdMode, MacAddress, MacChannel, MacStatus,
+    PanId, PibId, ScanType, SecurityLevel,
+};
+
+pub trait MacCommand: Sized {
+    const OPCODE: OpcodeM4ToM0;
+
+    fn payload<'a>(&'a self) -> &'a [u8] {
+        unsafe { slice::from_raw_parts(self as *const _ as *const u8, mem::size_of::<Self>()) }
+    }
+}
+
+/// MLME ASSOCIATE Request used to request an association
+#[repr(C)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct AssociateRequest {
+    /// the logical channel on which to attempt association
+    pub channel_number: MacChannel,
+    /// the channel page on which to attempt association
+    pub channel_page: u8,
+    /// coordinator addressing mode
+    pub coord_addr_mode: AddressMode,
+    /// operational capabilities of the associating device
+    pub capability_information: Capabilities,
+    /// the identifier of the PAN with which to associate
+    pub coord_pan_id: PanId,
+    /// the security level to be used
+    pub security_level: SecurityLevel,
+    /// the mode used to identify the key to be used
+    pub key_id_mode: KeyIdMode,
+    /// the originator of the key to be used
+    pub key_source: [u8; 8],
+    /// Coordinator address
+    pub coord_address: MacAddress,
+    /// the index of the key to be used
+    pub key_index: u8,
+}
+
+impl MacCommand for AssociateRequest {
+    const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmeAssociateReq;
+}
+
+/// MLME DISASSOCIATE Request sed to request a disassociation
+#[repr(C)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct DisassociateRequest {
+    /// device addressing mode used
+    pub device_addr_mode: AddressMode,
+    /// the identifier of the PAN of the device
+    pub device_pan_id: PanId,
+    /// the reason for the disassociation
+    pub disassociation_reason: DisassociationReason,
+    /// device address
+    pub device_address: MacAddress,
+    /// `true` if the disassociation notification command is to be sent indirectly
+    pub tx_indirect: bool,
+    /// the security level to be used
+    pub security_level: SecurityLevel,
+    /// the mode to be used to indetify the key to be used
+    pub key_id_mode: KeyIdMode,
+    /// the index of the key to be used
+    pub key_index: u8,
+    /// the originator of the key to be used
+    pub key_source: [u8; 8],
+}
+
+impl MacCommand for DisassociateRequest {
+    const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmeDisassociateReq;
+}
+
+/// MLME GET Request used to request a PIB value
+#[repr(C)]
+#[derive(Default)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct GetRequest {
+    /// the name of the PIB attribute to read
+    pub pib_attribute: PibId,
+
+    /// byte stuffing to keep 32 bit alignment
+    pub a_stuffing: [u8; 3],
+}
+
+impl MacCommand for GetRequest {
+    const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmeGetReq;
+}
+
+/// MLME GTS Request used to request and maintain GTSs
+#[repr(C)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct GtsRequest {
+    /// the characteristics of the GTS
+    pub characteristics: GtsCharacteristics,
+    /// the security level to be used
+    pub security_level: SecurityLevel,
+    /// the mode used to identify the key to be used
+    pub key_id_mode: KeyIdMode,
+    /// the index of the key to be used
+    pub key_index: u8,
+    /// the originator of the key to be used
+    pub key_source: [u8; 8],
+}
+
+impl MacCommand for GtsRequest {
+    const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmeGetReq;
+}
+
+#[repr(C)]
+#[derive(Default)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct ResetRequest {
+    /// MAC PIB attributes are set to their default values or not during reset
+    pub set_default_pib: bool,
+    /// byte stuffing to keep 32 bit alignment
+    pub a_stuffing: [u8; 3],
+}
+
+impl MacCommand for ResetRequest {
+    const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmeResetReq;
+}
+
+/// MLME RX ENABLE Request used to request that the receiver is either enabled
+/// for a finite period of time or disabled
+#[repr(C)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct RxEnableRequest {
+    /// the request operation can be deferred or not
+    pub defer_permit: bool,
+    /// configure the transceiver to RX with ranging for a value of
+    /// RANGING_ON or to not enable ranging for RANGING_OFF
+    pub ranging_rx_control: u8,
+    /// byte stuffing to keep 32 bit alignment
+    pub a_stuffing: [u8; 2],
+    /// number of symbols measured before the receiver is to be enabled or disabled
+    pub rx_on_time: [u8; 4],
+    /// number of symbols for which the receiver is to be enabled
+    pub rx_on_duration: [u8; 4],
+}
+
+impl MacCommand for RxEnableRequest {
+    const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmeRxEnableReq;
+}
+
+/// MLME SCAN Request used to initiate a channel scan over a given list of channels
+#[repr(C)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct ScanRequest {
+    /// the type of scan to be performed
+    pub scan_type: ScanType,
+    /// the time spent on scanning each channel
+    pub scan_duration: u8,
+    /// channel page on which to perform the scan
+    pub channel_page: u8,
+    /// security level to be used
+    pub security_level: SecurityLevel,
+    /// indicate which channels are to be scanned
+    pub scan_channels: [u8; 4],
+    /// originator the key to be used
+    pub key_source: [u8; 8],
+    /// mode used to identify the key to be used
+    pub key_id_mode: KeyIdMode,
+    /// index of the key to be used
+    pub key_index: u8,
+    /// byte stuffing to keep 32 bit alignment
+    pub a_stuffing: [u8; 2],
+}
+
+impl MacCommand for ScanRequest {
+    const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmeScanReq;
+}
+
+/// MLME SET Request used to attempt to write the given value to the indicated PIB attribute
+#[repr(C)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct SetRequest {
+    /// the pointer to the value of the PIB attribute to set
+    pub pib_attribute_ptr: *const u8,
+    /// the name of the PIB attribute to set
+    pub pib_attribute: PibId,
+}
+
+impl MacCommand for SetRequest {
+    const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmeSetReq;
+}
+
+/// MLME START Request used by the FFDs to intiate a new PAN or to begin using a new superframe
+/// configuration
+#[repr(C)]
+#[derive(Default)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct StartRequest {
+    /// PAN indentifier to used by the device
+    pub pan_id: PanId,
+    /// logical channel on which to begin
+    pub channel_number: MacChannel,
+    /// channel page on which to begin
+    pub channel_page: u8,
+    /// time at which to begin transmitting beacons
+    pub start_time: [u8; 4],
+    /// indicated how often the beacon is to be transmitted
+    pub beacon_order: u8,
+    /// length of the active portion of the superframe
+    pub superframe_order: u8,
+    /// indicated wheter the device is a PAN coordinator or not
+    pub pan_coordinator: bool,
+    /// indicates if the receiver of the beaconing device is disabled or not
+    pub battery_life_extension: bool,
+    /// indicated if the coordinator realignment command is to be trasmitted
+    pub coord_realignment: u8,
+    /// indicated if the coordinator realignment command is to be trasmitted
+    pub coord_realign_security_level: SecurityLevel,
+    /// index of the key to be used
+    pub coord_realign_key_id_index: u8,
+    /// originator of the key to be used
+    pub coord_realign_key_source: [u8; 8],
+    /// security level to be used for beacon frames
+    pub beacon_security_level: SecurityLevel,
+    /// mode used to identify the key to be used
+    pub beacon_key_id_mode: KeyIdMode,
+    /// index of the key to be used
+    pub beacon_key_index: u8,
+    /// originator of the key to be used
+    pub beacon_key_source: [u8; 8],
+}
+
+impl MacCommand for StartRequest {
+    const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmeStartReq;
+}
+
+/// MLME SYNC Request used to synchronize with the coordinator by acquiring and, if
+/// specified, tracking its beacons
+#[repr(C)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct SyncRequest {
+    /// the channel number on which to attempt coordinator synchronization
+    pub channel_number: MacChannel,
+    /// the channel page on which to attempt coordinator synchronization
+    pub channel_page: u8,
+    /// `true` if the MLME is to synchronize with the next beacon and attempts
+    /// to track all future beacons.
+    ///
+    /// `false` if the MLME is to synchronize with only the next beacon
+    pub track_beacon: bool,
+    /// byte stuffing to keep 32 bit alignment
+    pub a_stuffing: [u8; 1],
+}
+
+impl MacCommand for SyncRequest {
+    const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmeSyncReq;
+}
+
+/// MLME POLL Request propmts the device to request data from the coordinator
+#[repr(C)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct PollRequest {
+    /// addressing mode of the coordinator
+    pub coord_addr_mode: AddressMode,
+    /// security level to be used
+    pub security_level: SecurityLevel,
+    /// mode used to identify the key to be used
+    pub key_id_mode: KeyIdMode,
+    /// index of the key to be used
+    pub key_index: u8,
+    /// coordinator address
+    pub coord_address: MacAddress,
+    /// originator of the key to be used
+    pub key_source: [u8; 8],
+    /// PAN identifier of the coordinator
+    pub coord_pan_id: PanId,
+    /// byte stuffing to keep 32 bit alignment
+    pub a_stuffing: [u8; 2],
+}
+
+impl MacCommand for PollRequest {
+    const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmePollReq;
+}
+
+/// MLME DPS Request allows the next higher layer to request that the PHY utilize a
+/// given pair of preamble codes for a single use pending expiration of the DPSIndexDuration
+#[repr(C)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct DpsRequest {
+    /// the index value for the transmitter
+    tx_dps_index: u8,
+    /// the index value of the receiver
+    rx_dps_index: u8,
+    /// the number of symbols for which the transmitter and receiver will utilize the
+    /// respective DPS indices
+    dps_index_duration: u8,
+    /// byte stuffing to keep 32 bit alignment
+    pub a_stuffing: [u8; 1],
+}
+
+impl MacCommand for DpsRequest {
+    const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmeDpsReq;
+}
+
+/// MLME SOUNDING request primitive which is used by the next higher layer to request that
+/// the PHY respond with channel sounding information
+#[repr(C)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct SoundingRequest {
+    /// byte stuffing to keep 32 bit alignment
+    pub a_stuffing: [u8; 4],
+}
+
+impl MacCommand for SoundingRequest {
+    const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmeSoundingReq;
+}
+
+/// MLME CALIBRATE request primitive which used  to obtain the results of a ranging
+/// calibration request from an RDEV
+#[repr(C)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct CalibrateRequest {
+    /// byte stuffing to keep 32 bit alignment
+    pub a_stuffing: [u8; 4],
+}
+
+impl MacCommand for CalibrateRequest {
+    const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmeCalibrateReq;
+}
+
+/// MCPS DATA Request used for MAC data related requests from the application
+#[repr(C)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct DataRequest {
+    /// the handle assocated with the MSDU to be transmitted
+    pub msdu_ptr: *const u8,
+    /// source addressing mode used
+    pub src_addr_mode: AddressMode,
+    /// destination addressing mode used
+    pub dst_addr_mode: AddressMode,
+    /// destination PAN Id
+    pub dst_pan_id: PanId,
+    /// destination address
+    pub dst_address: MacAddress,
+    /// the number of octets contained in the MSDU
+    pub msdu_length: u8,
+    /// the handle assocated with the MSDU to be transmitted
+    pub msdu_handle: u8,
+    /// the ACK transmittion options for the MSDU
+    pub ack_tx: u8,
+    /// `true` if a GTS is to be used for transmission
+    ///
+    /// `false` indicates that the CAP will be used
+    pub gts_tx: bool,
+    /// the pending bit transmission options for the MSDU
+    pub indirect_tx: u8,
+    /// the security level to be used
+    pub security_level: SecurityLevel,
+    /// the mode used to indentify the key to be used
+    pub key_id_mode: KeyIdMode,
+    /// the index of the key to be used
+    pub key_index: u8,
+    /// the originator of the key to be used
+    pub key_source: [u8; 8],
+    /// 2011 - the pulse repitition value
+    pub uwbprf: u8,
+    /// 2011 - the ranging configuration
+    pub ranging: u8,
+    /// 2011 - the preamble symbol repititions
+    pub uwb_preamble_symbol_repetitions: u8,
+    /// 2011 - indicates the data rate
+    pub datrate: u8,
+}
+
+impl DataRequest {
+    pub fn set_buffer<'a>(&'a mut self, buf: &'a [u8]) -> &mut Self {
+        self.msdu_ptr = buf as *const _ as *const u8;
+        self.msdu_length = buf.len() as u8;
+
+        self
+    }
+}
+
+impl Default for DataRequest {
+    fn default() -> Self {
+        Self {
+            msdu_ptr: 0 as *const u8,
+            src_addr_mode: AddressMode::NoAddress,
+            dst_addr_mode: AddressMode::NoAddress,
+            dst_pan_id: PanId([0, 0]),
+            dst_address: MacAddress { short: [0, 0] },
+            msdu_length: 0,
+            msdu_handle: 0,
+            ack_tx: 0,
+            gts_tx: false,
+            indirect_tx: 0,
+            security_level: SecurityLevel::Unsecure,
+            key_id_mode: KeyIdMode::Implicite,
+            key_index: 0,
+            key_source: [0u8; 8],
+            uwbprf: 0,
+            ranging: 0,
+            uwb_preamble_symbol_repetitions: 0,
+            datrate: 0,
+        }
+    }
+}
+
+impl MacCommand for DataRequest {
+    const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::McpsDataReq;
+}
+
+/// for MCPS PURGE Request used to purge an MSDU from the transaction queue
+#[repr(C)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct PurgeRequest {
+    /// the handle associated with the MSDU to be purged from the transaction
+    /// queue
+    pub msdu_handle: u8,
+    /// byte stuffing to keep 32 bit alignment
+    pub a_stuffing: [u8; 3],
+}
+
+impl MacCommand for PurgeRequest {
+    const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::McpsPurgeReq;
+}
+
+/// MLME ASSOCIATE Response used to initiate a response to an MLME-ASSOCIATE.indication
+#[repr(C)]
+#[derive(Default)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct AssociateResponse {
+    /// extended address of the device requesting association
+    pub device_address: [u8; 8],
+    /// 16-bitshort device address allocated by the coordinator on successful
+    /// association
+    pub assoc_short_address: [u8; 2],
+    /// status of the association attempt
+    pub status: MacStatus,
+    /// security level to be used
+    pub security_level: SecurityLevel,
+    /// the originator of the key to be used
+    pub key_source: [u8; 8],
+    /// the mode used to identify the key to be used
+    pub key_id_mode: KeyIdMode,
+    /// the index of the key to be used
+    pub key_index: u8,
+    /// byte stuffing to keep 32 bit alignment
+    pub a_stuffing: [u8; 2],
+}
+
+impl MacCommand for AssociateResponse {
+    const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmeAssociateRes;
+}
+
+/// MLME ORPHAN Response used to respond to the MLME ORPHAN Indication
+#[repr(C)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct OrphanResponse {
+    /// extended address of the orphaned device
+    pub orphan_address: [u8; 8],
+    /// short address allocated to the orphaned device
+    pub short_address: [u8; 2],
+    /// if the orphaned device is associated with coordinator or not
+    pub associated_member: bool,
+    /// security level to be used
+    pub security_level: SecurityLevel,
+    /// the originator of the key to be used
+    pub key_source: [u8; 8],
+    /// the mode used to identify the key to be used
+    pub key_id_mode: KeyIdMode,
+    /// the index of the key to be used
+    pub key_index: u8,
+    /// byte stuffing to keep 32 bit alignment
+    pub a_stuffing: [u8; 2],
+}
+
+impl MacCommand for OrphanResponse {
+    const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmeOrphanRes;
+}
diff --git a/embassy-stm32-wpan/src/mac/consts.rs b/embassy-stm32-wpan/src/mac/consts.rs
new file mode 100644
index 000000000..56903d980
--- /dev/null
+++ b/embassy-stm32-wpan/src/mac/consts.rs
@@ -0,0 +1,4 @@
+pub const MAX_PAN_DESC_SUPPORTED: usize = 6;
+pub const MAX_SOUNDING_LIST_SUPPORTED: usize = 6;
+pub const MAX_PENDING_ADDRESS: usize = 7;
+pub const MAX_ED_SCAN_RESULTS_SUPPORTED: usize = 16;
diff --git a/embassy-stm32-wpan/src/mac/control.rs b/embassy-stm32-wpan/src/mac/control.rs
new file mode 100644
index 000000000..8a13de81c
--- /dev/null
+++ b/embassy-stm32-wpan/src/mac/control.rs
@@ -0,0 +1,95 @@
+use core::future::Future;
+use core::task;
+use core::task::Poll;
+
+use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
+use embassy_sync::mutex::MutexGuard;
+use embassy_sync::signal::Signal;
+use futures::FutureExt;
+
+use super::commands::MacCommand;
+use super::event::MacEvent;
+use super::typedefs::MacError;
+use crate::mac::runner::Runner;
+
+pub struct Control<'a> {
+    runner: &'a Runner<'a>,
+}
+
+impl<'a> Control<'a> {
+    pub(crate) fn new(runner: &'a Runner<'a>) -> Self {
+        Self { runner: runner }
+    }
+
+    pub async fn send_command<T>(&self, cmd: &T) -> Result<(), MacError>
+    where
+        T: MacCommand,
+    {
+        let _wm = self.runner.write_mutex.lock().await;
+
+        self.runner.mac_subsystem.send_command(cmd).await
+    }
+
+    pub async fn send_command_and_get_response<T>(&self, cmd: &T) -> Result<EventToken<'a>, MacError>
+    where
+        T: MacCommand,
+    {
+        let rm = self.runner.read_mutex.lock().await;
+        let _wm = self.runner.write_mutex.lock().await;
+        let token = EventToken::new(self.runner, rm);
+
+        self.runner.mac_subsystem.send_command(cmd).await?;
+
+        Ok(token)
+    }
+}
+
+pub struct EventToken<'a> {
+    runner: &'a Runner<'a>,
+    _mutex_guard: MutexGuard<'a, CriticalSectionRawMutex, ()>,
+}
+
+impl<'a> EventToken<'a> {
+    pub(crate) fn new(runner: &'a Runner<'a>, mutex_guard: MutexGuard<'a, CriticalSectionRawMutex, ()>) -> Self {
+        // Enable event receiving
+        runner.rx_event_channel.lock(|s| {
+            *s.borrow_mut() = Some(Signal::new());
+        });
+
+        Self {
+            runner: runner,
+            _mutex_guard: mutex_guard,
+        }
+    }
+}
+
+impl<'a> Future for EventToken<'a> {
+    type Output = MacEvent<'a>;
+
+    fn poll(self: core::pin::Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
+        self.get_mut().runner.rx_event_channel.lock(|s| {
+            let signal = s.borrow_mut();
+            let signal = match &*signal {
+                Some(s) => s,
+                _ => unreachable!(),
+            };
+
+            let result = match signal.wait().poll_unpin(cx) {
+                Poll::Ready(mac_event) => Poll::Ready(mac_event),
+                Poll::Pending => Poll::Pending,
+            };
+
+            result
+        })
+    }
+}
+
+impl<'a> Drop for EventToken<'a> {
+    fn drop(&mut self) {
+        // Disable event receiving
+        // This will also drop the contained event, if it exists, and will free up receiving the next event
+        self.runner.rx_event_channel.lock(|s| {
+            *s.borrow_mut() = None;
+        });
+    }
+}
diff --git a/embassy-stm32-wpan/src/mac/driver.rs b/embassy-stm32-wpan/src/mac/driver.rs
new file mode 100644
index 000000000..fffbb9edc
--- /dev/null
+++ b/embassy-stm32-wpan/src/mac/driver.rs
@@ -0,0 +1,122 @@
+#![allow(incomplete_features)]
+#![deny(unused_must_use)]
+
+use core::task::Context;
+
+use embassy_net_driver::{Capabilities, LinkState, Medium};
+use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
+use embassy_sync::channel::Channel;
+
+use crate::mac::event::MacEvent;
+use crate::mac::runner::Runner;
+use crate::mac::MTU;
+
+pub struct Driver<'d> {
+    runner: &'d Runner<'d>,
+}
+
+impl<'d> Driver<'d> {
+    pub(crate) fn new(runner: &'d Runner<'d>) -> Self {
+        Self { runner: runner }
+    }
+}
+
+impl<'d> embassy_net_driver::Driver for Driver<'d> {
+    // type RxToken<'a> = RxToken<'a, 'd> where Self: 'a;
+    // type TxToken<'a> = TxToken<'a, 'd> where Self: 'a;
+    type RxToken<'a> = RxToken<'d> where Self: 'a;
+    type TxToken<'a> = TxToken<'d> where Self: 'a;
+
+    fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
+        if self.runner.rx_channel.poll_ready_to_receive(cx) && self.runner.tx_buf_channel.poll_ready_to_receive(cx) {
+            Some((
+                RxToken {
+                    rx: &self.runner.rx_channel,
+                },
+                TxToken {
+                    tx: &self.runner.tx_channel,
+                    tx_buf: &self.runner.tx_buf_channel,
+                },
+            ))
+        } else {
+            None
+        }
+    }
+
+    fn transmit(&mut self, cx: &mut Context) -> Option<Self::TxToken<'_>> {
+        if self.runner.tx_buf_channel.poll_ready_to_receive(cx) {
+            Some(TxToken {
+                tx: &self.runner.tx_channel,
+                tx_buf: &self.runner.tx_buf_channel,
+            })
+        } else {
+            None
+        }
+    }
+
+    fn capabilities(&self) -> Capabilities {
+        let mut caps = Capabilities::default();
+        caps.max_transmission_unit = MTU;
+        // caps.max_burst_size = Some(self.tx.len());
+
+        caps.medium = Medium::Ieee802154;
+        caps
+    }
+
+    fn link_state(&mut self, _cx: &mut Context) -> LinkState {
+        //        if self.phy.poll_link(&mut self.station_management, cx) {
+        //            LinkState::Up
+        //        } else {
+        //            LinkState::Down
+        //        }
+
+        LinkState::Down
+    }
+
+    fn ethernet_address(&self) -> [u8; 6] {
+        // self.mac_addr
+
+        [0; 6]
+    }
+}
+
+pub struct RxToken<'d> {
+    rx: &'d Channel<CriticalSectionRawMutex, MacEvent<'d>, 1>,
+}
+
+impl<'d> embassy_net_driver::RxToken for RxToken<'d> {
+    fn consume<R, F>(self, f: F) -> R
+    where
+        F: FnOnce(&mut [u8]) -> R,
+    {
+        // Only valid data events should be put into the queue
+
+        let data_event = match self.rx.try_recv().unwrap() {
+            MacEvent::McpsDataInd(data_event) => data_event,
+            _ => unreachable!(),
+        };
+
+        f(&mut data_event.payload())
+    }
+}
+
+pub struct TxToken<'d> {
+    tx: &'d Channel<CriticalSectionRawMutex, (&'d mut [u8; MTU], usize), 5>,
+    tx_buf: &'d Channel<CriticalSectionRawMutex, &'d mut [u8; MTU], 5>,
+}
+
+impl<'d> embassy_net_driver::TxToken for TxToken<'d> {
+    fn consume<R, F>(self, len: usize, f: F) -> R
+    where
+        F: FnOnce(&mut [u8]) -> R,
+    {
+        // Only valid tx buffers should be put into the queue
+        let buf = self.tx_buf.try_recv().unwrap();
+        let r = f(&mut buf[..len]);
+
+        // The tx channel should always be of equal capacity to the tx_buf channel
+        self.tx.try_send((buf, len)).unwrap();
+
+        r
+    }
+}
diff --git a/embassy-stm32-wpan/src/mac/event.rs b/embassy-stm32-wpan/src/mac/event.rs
new file mode 100644
index 000000000..9ca4f5a2a
--- /dev/null
+++ b/embassy-stm32-wpan/src/mac/event.rs
@@ -0,0 +1,153 @@
+use core::{mem, ptr};
+
+use super::indications::{
+    AssociateIndication, BeaconNotifyIndication, CommStatusIndication, DataIndication, DisassociateIndication,
+    DpsIndication, GtsIndication, OrphanIndication, PollIndication, SyncLossIndication,
+};
+use super::responses::{
+    AssociateConfirm, CalibrateConfirm, DataConfirm, DisassociateConfirm, DpsConfirm, GetConfirm, GtsConfirm,
+    PollConfirm, PurgeConfirm, ResetConfirm, RxEnableConfirm, ScanConfirm, SetConfirm, SoundingConfirm, StartConfirm,
+};
+use crate::evt::{EvtBox, MemoryManager};
+use crate::mac::opcodes::OpcodeM0ToM4;
+use crate::sub::mac::{self, Mac};
+
+pub(crate) trait ParseableMacEvent: Sized {
+    fn from_buffer<'a>(buf: &'a [u8]) -> Result<&'a Self, ()> {
+        if buf.len() < mem::size_of::<Self>() {
+            Err(())
+        } else {
+            Ok(unsafe { &*(buf as *const _ as *const Self) })
+        }
+    }
+}
+
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+#[derive(Debug)]
+pub enum MacEvent<'a> {
+    MlmeAssociateCnf(&'a AssociateConfirm),
+    MlmeDisassociateCnf(&'a DisassociateConfirm),
+    MlmeGetCnf(&'a GetConfirm),
+    MlmeGtsCnf(&'a GtsConfirm),
+    MlmeResetCnf(&'a ResetConfirm),
+    MlmeRxEnableCnf(&'a RxEnableConfirm),
+    MlmeScanCnf(&'a ScanConfirm),
+    MlmeSetCnf(&'a SetConfirm),
+    MlmeStartCnf(&'a StartConfirm),
+    MlmePollCnf(&'a PollConfirm),
+    MlmeDpsCnf(&'a DpsConfirm),
+    MlmeSoundingCnf(&'a SoundingConfirm),
+    MlmeCalibrateCnf(&'a CalibrateConfirm),
+    McpsDataCnf(&'a DataConfirm),
+    McpsPurgeCnf(&'a PurgeConfirm),
+    MlmeAssociateInd(&'a AssociateIndication),
+    MlmeDisassociateInd(&'a DisassociateIndication),
+    MlmeBeaconNotifyInd(&'a BeaconNotifyIndication),
+    MlmeCommStatusInd(&'a CommStatusIndication),
+    MlmeGtsInd(&'a GtsIndication),
+    MlmeOrphanInd(&'a OrphanIndication),
+    MlmeSyncLossInd(&'a SyncLossIndication),
+    MlmeDpsInd(&'a DpsIndication),
+    McpsDataInd(&'a DataIndication),
+    MlmePollInd(&'a PollIndication),
+}
+
+impl<'a> MacEvent<'a> {
+    pub(crate) fn new(event_box: EvtBox<Mac>) -> Result<Self, ()> {
+        let payload = event_box.payload();
+        let opcode = u16::from_le_bytes(payload[0..2].try_into().unwrap());
+
+        let opcode = OpcodeM0ToM4::try_from(opcode)?;
+        let buf = &payload[2..];
+
+        // To avoid re-parsing the opcode, we store the result of the parse
+        // this requires use of unsafe because rust cannot assume that a reference will become
+        // invalid when the underlying result is moved. However, because we refer to a "heap"
+        // allocation, the underlying reference will not move until the struct is dropped.
+
+        let mac_event = match opcode {
+            OpcodeM0ToM4::MlmeAssociateCnf => {
+                MacEvent::MlmeAssociateCnf(unsafe { &*(AssociateConfirm::from_buffer(buf)? as *const _) })
+            }
+            OpcodeM0ToM4::MlmeDisassociateCnf => {
+                MacEvent::MlmeDisassociateCnf(unsafe { &*(DisassociateConfirm::from_buffer(buf)? as *const _) })
+            }
+            OpcodeM0ToM4::MlmeGetCnf => MacEvent::MlmeGetCnf(unsafe { &*(GetConfirm::from_buffer(buf)? as *const _) }),
+            OpcodeM0ToM4::MlmeGtsCnf => MacEvent::MlmeGtsCnf(unsafe { &*(GtsConfirm::from_buffer(buf)? as *const _) }),
+            OpcodeM0ToM4::MlmeResetCnf => {
+                MacEvent::MlmeResetCnf(unsafe { &*(ResetConfirm::from_buffer(buf)? as *const _) })
+            }
+            OpcodeM0ToM4::MlmeRxEnableCnf => {
+                MacEvent::MlmeRxEnableCnf(unsafe { &*(RxEnableConfirm::from_buffer(buf)? as *const _) })
+            }
+            OpcodeM0ToM4::MlmeScanCnf => {
+                MacEvent::MlmeScanCnf(unsafe { &*(ScanConfirm::from_buffer(buf)? as *const _) })
+            }
+            OpcodeM0ToM4::MlmeSetCnf => MacEvent::MlmeSetCnf(unsafe { &*(SetConfirm::from_buffer(buf)? as *const _) }),
+            OpcodeM0ToM4::MlmeStartCnf => {
+                MacEvent::MlmeStartCnf(unsafe { &*(StartConfirm::from_buffer(buf)? as *const _) })
+            }
+            OpcodeM0ToM4::MlmePollCnf => {
+                MacEvent::MlmePollCnf(unsafe { &*(PollConfirm::from_buffer(buf)? as *const _) })
+            }
+            OpcodeM0ToM4::MlmeDpsCnf => MacEvent::MlmeDpsCnf(unsafe { &*(DpsConfirm::from_buffer(buf)? as *const _) }),
+            OpcodeM0ToM4::MlmeSoundingCnf => {
+                MacEvent::MlmeSoundingCnf(unsafe { &*(SoundingConfirm::from_buffer(buf)? as *const _) })
+            }
+            OpcodeM0ToM4::MlmeCalibrateCnf => {
+                MacEvent::MlmeCalibrateCnf(unsafe { &*(CalibrateConfirm::from_buffer(buf)? as *const _) })
+            }
+            OpcodeM0ToM4::McpsDataCnf => {
+                MacEvent::McpsDataCnf(unsafe { &*(DataConfirm::from_buffer(buf)? as *const _) })
+            }
+            OpcodeM0ToM4::McpsPurgeCnf => {
+                MacEvent::McpsPurgeCnf(unsafe { &*(PurgeConfirm::from_buffer(buf)? as *const _) })
+            }
+            OpcodeM0ToM4::MlmeAssociateInd => {
+                MacEvent::MlmeAssociateInd(unsafe { &*(AssociateIndication::from_buffer(buf)? as *const _) })
+            }
+            OpcodeM0ToM4::MlmeDisassociateInd => {
+                MacEvent::MlmeDisassociateInd(unsafe { &*(DisassociateIndication::from_buffer(buf)? as *const _) })
+            }
+            OpcodeM0ToM4::MlmeBeaconNotifyInd => {
+                MacEvent::MlmeBeaconNotifyInd(unsafe { &*(BeaconNotifyIndication::from_buffer(buf)? as *const _) })
+            }
+            OpcodeM0ToM4::MlmeCommStatusInd => {
+                MacEvent::MlmeCommStatusInd(unsafe { &*(CommStatusIndication::from_buffer(buf)? as *const _) })
+            }
+            OpcodeM0ToM4::MlmeGtsInd => {
+                MacEvent::MlmeGtsInd(unsafe { &*(GtsIndication::from_buffer(buf)? as *const _) })
+            }
+            OpcodeM0ToM4::MlmeOrphanInd => {
+                MacEvent::MlmeOrphanInd(unsafe { &*(OrphanIndication::from_buffer(buf)? as *const _) })
+            }
+            OpcodeM0ToM4::MlmeSyncLossInd => {
+                MacEvent::MlmeSyncLossInd(unsafe { &*(SyncLossIndication::from_buffer(buf)? as *const _) })
+            }
+            OpcodeM0ToM4::MlmeDpsInd => {
+                MacEvent::MlmeDpsInd(unsafe { &*(DpsIndication::from_buffer(buf)? as *const _) })
+            }
+            OpcodeM0ToM4::McpsDataInd => {
+                MacEvent::McpsDataInd(unsafe { &*(DataIndication::from_buffer(buf)? as *const _) })
+            }
+            OpcodeM0ToM4::MlmePollInd => {
+                MacEvent::MlmePollInd(unsafe { &*(PollIndication::from_buffer(buf)? as *const _) })
+            }
+        };
+
+        // Forget the event box so that drop isn't called
+        // We want to handle the lifetime ourselves
+
+        mem::forget(event_box);
+
+        Ok(mac_event)
+    }
+}
+
+unsafe impl<'a> Send for MacEvent<'a> {}
+
+impl<'a> Drop for MacEvent<'a> {
+    fn drop(&mut self) {
+        unsafe { mac::Mac::drop_event_packet(ptr::null_mut()) };
+    }
+}
diff --git a/embassy-stm32-wpan/src/mac/indications.rs b/embassy-stm32-wpan/src/mac/indications.rs
new file mode 100644
index 000000000..c0b86d745
--- /dev/null
+++ b/embassy-stm32-wpan/src/mac/indications.rs
@@ -0,0 +1,265 @@
+use core::slice;
+
+use super::consts::MAX_PENDING_ADDRESS;
+use super::event::ParseableMacEvent;
+use super::typedefs::{
+    AddressMode, Capabilities, DisassociationReason, KeyIdMode, MacAddress, MacChannel, MacStatus, PanDescriptor,
+    PanId, SecurityLevel,
+};
+
+/// MLME ASSOCIATE Indication which will be used by the MAC
+/// to indicate the reception of an association request command
+#[repr(C)]
+#[derive(Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct AssociateIndication {
+    /// Extended address of the device requesting association
+    pub device_address: [u8; 8],
+    /// Operational capabilities of the device requesting association
+    pub capability_information: Capabilities,
+    /// Security level purportedly used by the received MAC command frame
+    pub security_level: SecurityLevel,
+    /// The mode used to identify the key used by the originator of frame
+    pub key_id_mode: KeyIdMode,
+    /// Index of the key used by the originator of the received frame
+    pub key_index: u8,
+    /// The originator of the key used by the originator of the received frame
+    pub key_source: [u8; 8],
+}
+
+impl ParseableMacEvent for AssociateIndication {}
+
+/// MLME DISASSOCIATE indication which will be used to send
+/// disassociation indication to the application.
+#[repr(C)]
+#[derive(Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct DisassociateIndication {
+    /// Extended address of the device requesting association
+    pub device_address: [u8; 8],
+    /// The reason for the disassociation
+    pub disassociation_reason: DisassociationReason,
+    /// The security level to be used
+    pub security_level: SecurityLevel,
+    /// The mode used to identify the key to be used
+    pub key_id_mode: KeyIdMode,
+    /// The index of the key to be used
+    pub key_index: u8,
+    /// The originator of the key to be used
+    pub key_source: [u8; 8],
+}
+
+impl ParseableMacEvent for DisassociateIndication {}
+
+/// MLME BEACON NOTIIFY Indication which is used to send parameters contained
+/// within a beacon frame received by the MAC to the application
+#[repr(C)]
+#[derive(Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct BeaconNotifyIndication {
+    /// he set of octets comprising the beacon payload to be transferred
+    /// from the MAC sublayer entity to the next higher layer
+    pub sdu_ptr: *const u8,
+    /// The PAN Descriptor for the received beacon
+    pub pan_descriptor: PanDescriptor,
+    /// The list of addresses of the devices
+    pub addr_list: [MacAddress; MAX_PENDING_ADDRESS],
+    /// Beacon Sequence Number
+    pub bsn: u8,
+    /// The beacon pending address specification
+    pub pend_addr_spec: u8,
+    /// Number of octets contained in the beacon payload of the beacon frame
+    pub sdu_length: u8,
+}
+
+impl ParseableMacEvent for BeaconNotifyIndication {}
+
+/// MLME COMM STATUS Indication which is used by the MAC to indicate a communications status
+#[repr(C)]
+#[derive(Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct CommStatusIndication {
+    /// The 16-bit PAN identifier of the device from which the frame
+    /// was received or to which the frame was being sent
+    pub pan_id: PanId,
+    /// Source addressing mode
+    pub src_addr_mode: AddressMode,
+    /// Destination addressing mode
+    pub dst_addr_mode: AddressMode,
+    /// Source address
+    pub src_address: MacAddress,
+    /// Destination address
+    pub dst_address: MacAddress,
+    /// The communications status
+    pub status: MacStatus,
+    /// Security level to be used
+    pub security_level: SecurityLevel,
+    /// Mode used to identify the key to be used
+    pub key_id_mode: KeyIdMode,
+    /// Index of the key to be used
+    pub key_index: u8,
+    /// Originator of the key to be used
+    pub key_source: [u8; 8],
+}
+
+impl ParseableMacEvent for CommStatusIndication {}
+
+/// MLME GTS Indication indicates that a GTS has been allocated or that a
+/// previously allocated GTS has been deallocated
+#[repr(C)]
+#[derive(Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct GtsIndication {
+    /// The short address of the device that has been allocated or deallocated a GTS
+    pub device_address: [u8; 2],
+    /// The characteristics of the GTS
+    pub gts_characteristics: u8,
+    /// Security level to be used
+    pub security_level: SecurityLevel,
+    /// Mode used to identify the key to be used
+    pub key_id_mode: KeyIdMode,
+    /// Index of the key to be used
+    pub key_index: u8,
+    /// byte stuffing to keep 32 bit alignment
+    a_stuffing: [u8; 2],
+    /// Originator of the key to be used
+    pub key_source: [u8; 8],
+}
+
+impl ParseableMacEvent for GtsIndication {}
+
+/// MLME ORPHAN Indication which is used by the coordinator to notify the
+/// application of the presence of an orphaned device
+#[repr(C)]
+#[derive(Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct OrphanIndication {
+    /// Extended address of the orphaned device
+    pub orphan_address: [u8; 8],
+    /// Originator of the key used by the originator of the received frame
+    pub key_source: [u8; 8],
+    /// Security level purportedly used by the received MAC command frame
+    pub security_level: SecurityLevel,
+    /// Mode used to identify the key used by originator of received frame
+    pub key_id_mode: KeyIdMode,
+    /// Index of the key used by the originator of the received frame
+    pub key_index: u8,
+    /// byte stuffing to keep 32 bit alignment
+    a_stuffing: [u8; 1],
+}
+
+impl ParseableMacEvent for OrphanIndication {}
+
+/// MLME SYNC LOSS Indication which is used by the MAC to indicate the loss
+/// of synchronization with the coordinator
+#[repr(C)]
+#[derive(Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct SyncLossIndication {
+    /// The PAN identifier with which the device lost synchronization or to which it was realigned
+    pub pan_id: PanId,
+    /// The reason that synchronization was lost
+    pub loss_reason: u8,
+    /// The logical channel on which the device lost synchronization or to whi
+    pub channel_number: MacChannel,
+    /// The channel page on which the device lost synchronization or to which
+    pub channel_page: u8,
+    /// The security level used by the received MAC frame
+    pub security_level: SecurityLevel,
+    /// Mode used to identify the key used by originator of received frame
+    pub key_id_mode: KeyIdMode,
+    /// Index of the key used by the originator of the received frame
+    pub key_index: u8,
+    /// Originator of the key used by the originator of the received frame
+    pub key_source: [u8; 8],
+}
+
+impl ParseableMacEvent for SyncLossIndication {}
+
+/// MLME DPS Indication which indicates the expiration of the DPSIndexDuration
+///  and the resetting of the DPS values in the PHY
+#[repr(C)]
+#[derive(Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct DpsIndication {
+    /// byte stuffing to keep 32 bit alignment
+    a_stuffing: [u8; 4],
+}
+
+impl ParseableMacEvent for DpsIndication {}
+
+#[repr(C)]
+#[derive(Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct DataIndication {
+    /// Pointer to the set of octets forming the MSDU being indicated  
+    pub msdu_ptr: *const u8,
+    /// Source addressing mode used  
+    pub src_addr_mode: AddressMode,
+    /// Source PAN ID   
+    pub src_pan_id: PanId,
+    /// Source address  
+    pub src_address: MacAddress,
+    /// Destination addressing mode used  
+    pub dst_addr_mode: AddressMode,
+    /// Destination PAN ID   
+    pub dst_pan_id: PanId,
+    /// Destination address  
+    pub dst_address: MacAddress,
+    /// The number of octets contained in the MSDU being indicated  
+    pub msdu_length: u8,
+    /// QI value measured during reception of the MPDU
+    pub mpdu_link_quality: u8,
+    /// The data sequence number of the received data frame  
+    pub dsn: u8,
+    /// The time, in symbols, at which the data were received  
+    pub time_stamp: [u8; 4],
+    /// The security level purportedly used by the received data frame  
+    security_level: SecurityLevel,
+    /// Mode used to identify the key used by originator of received frame  
+    key_id_mode: KeyIdMode,
+    /// The originator of the key  
+    pub key_source: [u8; 8],
+    /// The index of the key  
+    pub key_index: u8,
+    /// he pulse repetition value of the received PPDU
+    pub uwbprf: u8,
+    /// The preamble symbol repetitions of the UWB PHY frame  
+    pub uwn_preamble_symbol_repetitions: u8,
+    /// Indicates the data rate
+    pub datrate: u8,
+    /// time units corresponding to an RMARKER at the antenna at the end of a ranging exchange,  
+    pub ranging_received: u8,
+    pub ranging_counter_start: u32,
+    pub ranging_counter_stop: u32,
+    /// ime units in a message exchange over which the tracking offset was measured
+    pub ranging_tracking_interval: u32,
+    /// time units slipped or advanced by the radio tracking system  
+    pub ranging_offset: u32,
+    /// The FoM characterizing the ranging measurement
+    pub ranging_fom: u8,
+    /// The Received Signal Strength Indicator measured
+    pub rssi: u8,
+}
+
+impl ParseableMacEvent for DataIndication {}
+
+impl DataIndication {
+    pub fn payload<'a>(&'a self) -> &'a mut [u8] {
+        unsafe { slice::from_raw_parts_mut(self.msdu_ptr as *mut _, self.msdu_length as usize) }
+    }
+}
+
+/// MLME POLL Indication which will be used for indicating the Data Request
+/// reception to upper layer as defined in Zigbee r22 - D.8.2
+#[repr(C)]
+#[derive(Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct PollIndication {
+    /// addressing mode used
+    pub addr_mode: AddressMode,
+    /// Poll requester address
+    pub request_address: MacAddress,
+}
+
+impl ParseableMacEvent for PollIndication {}
diff --git a/embassy-stm32-wpan/src/mac/macros.rs b/embassy-stm32-wpan/src/mac/macros.rs
new file mode 100644
index 000000000..1a988a779
--- /dev/null
+++ b/embassy-stm32-wpan/src/mac/macros.rs
@@ -0,0 +1,32 @@
+#[macro_export]
+macro_rules! numeric_enum {
+    (#[repr($repr:ident)]
+     $(#$attrs:tt)* $vis:vis enum $name:ident {
+        $($(#$enum_attrs:tt)* $enum:ident = $constant:expr),* $(,)?
+    } ) => {
+        #[repr($repr)]
+        $(#$attrs)*
+        $vis enum $name {
+            $($(#$enum_attrs)* $enum = $constant),*
+        }
+
+        impl ::core::convert::TryFrom<$repr> for $name {
+            type Error = ();
+
+            fn try_from(value: $repr) -> ::core::result::Result<Self, ()> {
+                match value {
+                    $($constant => Ok( $name :: $enum ),)*
+                    _ => Err(())
+                }
+            }
+        }
+
+        impl ::core::convert::From<$name> for $repr {
+            fn from(value: $name) -> $repr {
+                match value {
+                    $($name :: $enum => $constant,)*
+                }
+            }
+        }
+    }
+}
diff --git a/embassy-stm32-wpan/src/mac/mod.rs b/embassy-stm32-wpan/src/mac/mod.rs
new file mode 100644
index 000000000..c847a5cca
--- /dev/null
+++ b/embassy-stm32-wpan/src/mac/mod.rs
@@ -0,0 +1,21 @@
+pub mod commands;
+mod consts;
+pub mod control;
+mod driver;
+pub mod event;
+pub mod indications;
+mod macros;
+mod opcodes;
+pub mod responses;
+pub mod runner;
+pub mod typedefs;
+
+pub use crate::mac::control::Control;
+use crate::mac::driver::Driver;
+pub use crate::mac::runner::Runner;
+
+const MTU: usize = 127;
+
+pub async fn new<'a>(runner: &'a Runner<'a>) -> (Control<'a>, Driver<'a>) {
+    (Control::new(runner), Driver::new(runner))
+}
diff --git a/embassy-stm32-wpan/src/mac/opcodes.rs b/embassy-stm32-wpan/src/mac/opcodes.rs
new file mode 100644
index 000000000..fd7011873
--- /dev/null
+++ b/embassy-stm32-wpan/src/mac/opcodes.rs
@@ -0,0 +1,92 @@
+const ST_VENDOR_OGF: u16 = 0x3F;
+const MAC_802_15_4_CMD_OPCODE_OFFSET: u16 = 0x280;
+
+const fn opcode(ocf: u16) -> isize {
+    ((ST_VENDOR_OGF << 9) | (MAC_802_15_4_CMD_OPCODE_OFFSET + ocf)) as isize
+}
+
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum OpcodeM4ToM0 {
+    MlmeAssociateReq = opcode(0x00),
+    MlmeAssociateRes = opcode(0x01),
+    MlmeDisassociateReq = opcode(0x02),
+    MlmeGetReq = opcode(0x03),
+    MlmeGtsReq = opcode(0x04),
+    MlmeOrphanRes = opcode(0x05),
+    MlmeResetReq = opcode(0x06),
+    MlmeRxEnableReq = opcode(0x07),
+    MlmeScanReq = opcode(0x08),
+    MlmeSetReq = opcode(0x09),
+    MlmeStartReq = opcode(0x0A),
+    MlmeSyncReq = opcode(0x0B),
+    MlmePollReq = opcode(0x0C),
+    MlmeDpsReq = opcode(0x0D),
+    MlmeSoundingReq = opcode(0x0E),
+    MlmeCalibrateReq = opcode(0x0F),
+    McpsDataReq = opcode(0x10),
+    McpsPurgeReq = opcode(0x11),
+}
+
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum OpcodeM0ToM4 {
+    MlmeAssociateCnf = 0x00,
+    MlmeDisassociateCnf,
+    MlmeGetCnf,
+    MlmeGtsCnf,
+    MlmeResetCnf,
+    MlmeRxEnableCnf,
+    MlmeScanCnf,
+    MlmeSetCnf,
+    MlmeStartCnf,
+    MlmePollCnf,
+    MlmeDpsCnf,
+    MlmeSoundingCnf,
+    MlmeCalibrateCnf,
+    McpsDataCnf,
+    McpsPurgeCnf,
+    MlmeAssociateInd,
+    MlmeDisassociateInd,
+    MlmeBeaconNotifyInd,
+    MlmeCommStatusInd,
+    MlmeGtsInd,
+    MlmeOrphanInd,
+    MlmeSyncLossInd,
+    MlmeDpsInd,
+    McpsDataInd,
+    MlmePollInd,
+}
+
+impl TryFrom<u16> for OpcodeM0ToM4 {
+    type Error = ();
+
+    fn try_from(value: u16) -> Result<Self, Self::Error> {
+        match value {
+            0 => Ok(Self::MlmeAssociateCnf),
+            1 => Ok(Self::MlmeDisassociateCnf),
+            2 => Ok(Self::MlmeGetCnf),
+            3 => Ok(Self::MlmeGtsCnf),
+            4 => Ok(Self::MlmeResetCnf),
+            5 => Ok(Self::MlmeRxEnableCnf),
+            6 => Ok(Self::MlmeScanCnf),
+            7 => Ok(Self::MlmeSetCnf),
+            8 => Ok(Self::MlmeStartCnf),
+            9 => Ok(Self::MlmePollCnf),
+            10 => Ok(Self::MlmeDpsCnf),
+            11 => Ok(Self::MlmeSoundingCnf),
+            12 => Ok(Self::MlmeCalibrateCnf),
+            13 => Ok(Self::McpsDataCnf),
+            14 => Ok(Self::McpsPurgeCnf),
+            15 => Ok(Self::MlmeAssociateInd),
+            16 => Ok(Self::MlmeDisassociateInd),
+            17 => Ok(Self::MlmeBeaconNotifyInd),
+            18 => Ok(Self::MlmeCommStatusInd),
+            19 => Ok(Self::MlmeGtsInd),
+            20 => Ok(Self::MlmeOrphanInd),
+            21 => Ok(Self::MlmeSyncLossInd),
+            22 => Ok(Self::MlmeDpsInd),
+            23 => Ok(Self::McpsDataInd),
+            24 => Ok(Self::MlmePollInd),
+            _ => Err(()),
+        }
+    }
+}
diff --git a/embassy-stm32-wpan/src/mac/responses.rs b/embassy-stm32-wpan/src/mac/responses.rs
new file mode 100644
index 000000000..544fdaae8
--- /dev/null
+++ b/embassy-stm32-wpan/src/mac/responses.rs
@@ -0,0 +1,273 @@
+use super::consts::{MAX_ED_SCAN_RESULTS_SUPPORTED, MAX_PAN_DESC_SUPPORTED, MAX_SOUNDING_LIST_SUPPORTED};
+use super::event::ParseableMacEvent;
+use super::typedefs::{
+    AddressMode, AssociationStatus, KeyIdMode, MacAddress, MacStatus, PanDescriptor, PanId, PibId, ScanType,
+    SecurityLevel,
+};
+
+/// MLME ASSOCIATE Confirm used to inform of the initiating device whether
+/// its request to associate was successful or unsuccessful
+#[repr(C)]
+#[derive(Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct AssociateConfirm {
+    /// short address allocated by the coordinator on successful association
+    pub assoc_short_address: [u8; 2],
+    /// status of the association request
+    pub status: AssociationStatus,
+    /// security level to be used
+    pub security_level: SecurityLevel,
+    /// the originator of the key to be used
+    pub key_source: [u8; 8],
+    /// the mode used to identify the key to be used
+    pub key_id_mode: KeyIdMode,
+    /// the index of the key to be used
+    pub key_index: u8,
+    /// byte stuffing to keep 32 bit alignment
+    a_stuffing: [u8; 2],
+}
+
+impl ParseableMacEvent for AssociateConfirm {}
+
+/// MLME DISASSOCIATE Confirm used to send disassociation Confirmation to the application.
+#[repr(C)]
+#[derive(Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct DisassociateConfirm {
+    /// status of the disassociation attempt
+    pub status: MacStatus,
+    /// device addressing mode used
+    pub device_addr_mode: AddressMode,
+    /// the identifier of the PAN of the device
+    pub device_pan_id: PanId,
+    /// device address
+    pub device_address: MacAddress,
+}
+
+impl ParseableMacEvent for DisassociateConfirm {}
+
+///  MLME GET Confirm which requests information about a given PIB attribute
+#[repr(C)]
+#[derive(Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct GetConfirm {
+    /// The pointer to the value of the PIB attribute attempted to read
+    pub pib_attribute_value_ptr: *const u8,
+    /// Status of the GET attempt
+    pub status: MacStatus,
+    /// The name of the PIB attribute attempted to read
+    pub pib_attribute: PibId,
+    /// The lenght of the PIB attribute Value return
+    pub pib_attribute_value_len: u8,
+    /// byte stuffing to keep 32 bit alignment
+    a_stuffing: [u8; 1],
+}
+
+impl ParseableMacEvent for GetConfirm {}
+
+/// MLME GTS Confirm which eports the results of a request to allocate a new GTS
+/// or to deallocate an existing GTS
+#[repr(C)]
+#[derive(Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct GtsConfirm {
+    /// The characteristics of the GTS
+    pub gts_characteristics: u8,
+    /// The status of the GTS reques
+    pub status: MacStatus,
+    /// byte stuffing to keep 32 bit alignment
+    a_stuffing: [u8; 2],
+}
+
+impl ParseableMacEvent for GtsConfirm {}
+
+/// MLME RESET Confirm which is used to report the results of the reset operation
+#[repr(C)]
+#[derive(Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct ResetConfirm {
+    /// The result of the reset operation
+    pub status: MacStatus,
+    /// byte stuffing to keep 32 bit alignment
+    a_stuffing: [u8; 3],
+}
+
+impl ParseableMacEvent for ResetConfirm {}
+
+/// MLME RX ENABLE Confirm which is used to report the results of the attempt
+/// to enable or disable the receiver
+#[repr(C)]
+#[derive(Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct RxEnableConfirm {
+    /// Result of the request to enable or disable the receiver
+    pub status: MacStatus,
+    /// byte stuffing to keep 32 bit alignment
+    a_stuffing: [u8; 3],
+}
+
+impl ParseableMacEvent for RxEnableConfirm {}
+
+/// MLME SCAN Confirm which is used to report the result of the channel scan request
+#[repr(C)]
+#[derive(Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct ScanConfirm {
+    /// Status of the scan request
+    pub status: MacStatus,
+    /// The type of scan performed
+    pub scan_type: ScanType,
+    /// Channel page on which the scan was performed
+    pub channel_page: u8,
+    /// Channels given in the request which were not scanned
+    pub unscanned_channels: [u8; 4],
+    /// Number of elements returned in the appropriate result lists
+    pub result_list_size: u8,
+    /// List of energy measurements
+    pub energy_detect_list: [u8; MAX_ED_SCAN_RESULTS_SUPPORTED],
+    /// List of PAN descriptors
+    pub pan_descriptor_list: [PanDescriptor; MAX_PAN_DESC_SUPPORTED],
+    /// Categorization of energy detected in channel
+    pub detected_category: u8,
+    ///  For UWB PHYs, the list of energy measurements taken
+    pub uwb_energy_detect_list: [u8; MAX_ED_SCAN_RESULTS_SUPPORTED],
+}
+
+impl ParseableMacEvent for ScanConfirm {}
+
+/// MLME SET Confirm which reports the result of an attempt to write a value to a PIB attribute
+#[repr(C)]
+#[derive(Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct SetConfirm {
+    /// The result of the set operation
+    pub status: MacStatus,
+    /// The name of the PIB attribute that was written
+    pub pin_attribute: PibId,
+    /// byte stuffing to keep 32 bit alignment
+    a_stuffing: [u8; 2],
+}
+
+impl ParseableMacEvent for SetConfirm {}
+
+/// MLME START Confirm which is used to report the results of the attempt to
+/// start using a new superframe configuration
+#[repr(C)]
+#[derive(Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct StartConfirm {
+    /// Result of the attempt to start using an updated superframe configuration
+    pub status: MacStatus,
+    /// byte stuffing to keep 32 bit alignment
+    a_stuffing: [u8; 3],
+}
+
+impl ParseableMacEvent for StartConfirm {}
+
+/// MLME POLL Confirm which is used to report the result of a request to poll the coordinator for data
+#[repr(C)]
+#[derive(Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct PollConfirm {
+    /// The status of the data request
+    pub status: MacStatus,
+    /// byte stuffing to keep 32 bit alignment
+    a_stuffing: [u8; 3],
+}
+
+impl ParseableMacEvent for PollConfirm {}
+
+/// MLME DPS Confirm which  reports the results of the attempt to enable or disable the DPS
+#[repr(C)]
+#[derive(Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct DpsConfirm {
+    /// The status of the DPS request
+    pub status: MacStatus,
+    /// byte stuffing to keep 32 bit alignment
+    a_stuffing: [u8; 3],
+}
+
+impl ParseableMacEvent for DpsConfirm {}
+
+/// MLME SOUNDING Confirm which  reports the result of a request to the PHY to provide
+/// channel sounding information
+#[repr(C)]
+#[derive(Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct SoundingConfirm {
+    /// Results of the sounding measurement
+    pub sounding_list: [u8; MAX_SOUNDING_LIST_SUPPORTED],
+
+    status: u8,
+}
+
+impl ParseableMacEvent for SoundingConfirm {}
+
+/// MLME CALIBRATE Confirm which reports the result of a request to the PHY
+/// to provide internal propagation path information
+#[repr(C)]
+#[derive(Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct CalibrateConfirm {
+    /// The status of the attempt to return sounding data
+    pub status: MacStatus,
+    /// byte stuffing to keep 32 bit alignment
+    a_stuffing: [u8; 3],
+    /// A count of the propagation time from the ranging counter
+    /// to the transmit antenna
+    pub cal_tx_rmaker_offset: u32,
+    /// A count of the propagation time from the receive antenna
+    /// to the ranging counter
+    pub cal_rx_rmaker_offset: u32,
+}
+
+impl ParseableMacEvent for CalibrateConfirm {}
+
+/// MCPS DATA Confirm which will be used for reporting the results of
+/// MAC data related requests from the application
+#[repr(C)]
+#[derive(Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct DataConfirm {
+    /// The handle associated with the MSDU being confirmed
+    pub msdu_handle: u8,
+    /// The time, in symbols, at which the data were transmitted
+    pub time_stamp: [u8; 4],
+    /// ranging status
+    pub ranging_received: u8,
+    /// The status of the last MSDU transmission
+    pub status: MacStatus,
+    /// time units corresponding to an RMARKER at the antenna at
+    /// the beginning of a ranging exchange
+    pub ranging_counter_start: u32,
+    /// time units corresponding to an RMARKER at the antenna
+    /// at the end of a ranging exchange
+    pub ranging_counter_stop: u32,
+    /// time units in a message exchange over which the tracking offset was measured
+    pub ranging_tracking_interval: u32,
+    /// time units slipped or advanced by the radio tracking system
+    pub ranging_offset: u32,
+    /// The FoM characterizing the ranging measurement
+    pub ranging_fom: u8,
+    /// byte stuffing to keep 32 bit alignment
+    a_stuffing: [u8; 3],
+}
+
+impl ParseableMacEvent for DataConfirm {}
+
+/// MCPS PURGE Confirm which will be used by the  MAC to notify the application of
+/// the status of its request to purge an MSDU from the transaction queue
+#[repr(C)]
+#[derive(Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct PurgeConfirm {
+    /// Handle associated with the MSDU requested to be purged from the transaction queue
+    pub msdu_handle: u8,
+    /// The status of the request
+    pub status: MacStatus,
+    /// byte stuffing to keep 32 bit alignment
+    a_stuffing: [u8; 2],
+}
+
+impl ParseableMacEvent for PurgeConfirm {}
diff --git a/embassy-stm32-wpan/src/mac/runner.rs b/embassy-stm32-wpan/src/mac/runner.rs
new file mode 100644
index 000000000..1be6df8a4
--- /dev/null
+++ b/embassy-stm32-wpan/src/mac/runner.rs
@@ -0,0 +1,109 @@
+use core::cell::RefCell;
+
+use embassy_futures::join;
+use embassy_sync::blocking_mutex;
+use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex};
+use embassy_sync::channel::Channel;
+use embassy_sync::mutex::Mutex;
+use embassy_sync::signal::Signal;
+
+use crate::mac::commands::DataRequest;
+use crate::mac::event::MacEvent;
+use crate::mac::typedefs::{AddressMode, MacAddress, PanId, SecurityLevel};
+use crate::mac::MTU;
+use crate::sub::mac::Mac;
+
+type ZeroCopyPubSub<M, T> = blocking_mutex::Mutex<M, RefCell<Option<Signal<NoopRawMutex, T>>>>;
+
+pub struct Runner<'a> {
+    pub(crate) mac_subsystem: Mac,
+    // rx event backpressure is already provided through the MacEvent drop mechanism
+    // therefore, we don't need to worry about overwriting events
+    pub(crate) rx_event_channel: ZeroCopyPubSub<CriticalSectionRawMutex, MacEvent<'a>>,
+    pub(crate) read_mutex: Mutex<CriticalSectionRawMutex, ()>,
+    pub(crate) write_mutex: Mutex<CriticalSectionRawMutex, ()>,
+    pub(crate) rx_channel: Channel<CriticalSectionRawMutex, MacEvent<'a>, 1>,
+    pub(crate) tx_channel: Channel<CriticalSectionRawMutex, (&'a mut [u8; MTU], usize), 5>,
+    pub(crate) tx_buf_channel: Channel<CriticalSectionRawMutex, &'a mut [u8; MTU], 5>,
+}
+
+impl<'a> Runner<'a> {
+    pub fn new(mac: Mac, tx_buf_queue: [&'a mut [u8; MTU]; 5]) -> Self {
+        let this = Self {
+            mac_subsystem: mac,
+            rx_event_channel: blocking_mutex::Mutex::new(RefCell::new(None)),
+            read_mutex: Mutex::new(()),
+            write_mutex: Mutex::new(()),
+            rx_channel: Channel::new(),
+            tx_channel: Channel::new(),
+            tx_buf_channel: Channel::new(),
+        };
+
+        for buf in tx_buf_queue {
+            this.tx_buf_channel.try_send(buf).unwrap();
+        }
+
+        this
+    }
+
+    pub async fn run(&'a self) -> ! {
+        join::join(
+            async {
+                loop {
+                    if let Ok(mac_event) = self.mac_subsystem.read().await {
+                        match mac_event {
+                            MacEvent::McpsDataInd(_) => {
+                                self.rx_channel.send(mac_event).await;
+                            }
+                            _ => {
+                                self.rx_event_channel.lock(|s| {
+                                    match &*s.borrow() {
+                                        Some(signal) => {
+                                            signal.signal(mac_event);
+                                        }
+                                        None => {}
+                                    };
+                                });
+                            }
+                        }
+                    }
+                }
+            },
+            async {
+                let mut msdu_handle = 0x02;
+
+                loop {
+                    let (buf, len) = self.tx_channel.recv().await;
+                    let _wm = self.write_mutex.lock().await;
+
+                    // The mutex should be dropped on the next loop iteration
+                    self.mac_subsystem
+                        .send_command(
+                            DataRequest {
+                                src_addr_mode: AddressMode::Short,
+                                dst_addr_mode: AddressMode::Short,
+                                dst_pan_id: PanId([0x1A, 0xAA]),
+                                dst_address: MacAddress::BROADCAST,
+                                msdu_handle: msdu_handle,
+                                ack_tx: 0x00,
+                                gts_tx: false,
+                                security_level: SecurityLevel::Unsecure,
+                                ..Default::default()
+                            }
+                            .set_buffer(&buf[..len]),
+                        )
+                        .await
+                        .unwrap();
+
+                    msdu_handle = msdu_handle.wrapping_add(1);
+
+                    // The tx channel should always be of equal capacity to the tx_buf channel
+                    self.tx_buf_channel.try_send(buf).unwrap();
+                }
+            },
+        )
+        .await;
+
+        loop {}
+    }
+}
diff --git a/embassy-stm32-wpan/src/mac/typedefs.rs b/embassy-stm32-wpan/src/mac/typedefs.rs
new file mode 100644
index 000000000..0552b8ea1
--- /dev/null
+++ b/embassy-stm32-wpan/src/mac/typedefs.rs
@@ -0,0 +1,381 @@
+use core::fmt::Debug;
+
+use crate::numeric_enum;
+
+#[derive(Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum MacError {
+    Error = 0x01,
+    NotImplemented = 0x02,
+    NotSupported = 0x03,
+    HardwareNotSupported = 0x04,
+    Undefined = 0x05,
+}
+
+impl From<u8> for MacError {
+    fn from(value: u8) -> Self {
+        match value {
+            0x01 => Self::Error,
+            0x02 => Self::NotImplemented,
+            0x03 => Self::NotSupported,
+            0x04 => Self::HardwareNotSupported,
+            0x05 => Self::Undefined,
+            _ => Self::Undefined,
+        }
+    }
+}
+
+numeric_enum! {
+    #[repr(u8)]
+    #[derive(Debug, Default)]
+    #[cfg_attr(feature = "defmt", derive(defmt::Format))]
+    pub enum MacStatus {
+        #[default]
+        Success = 0x00,
+        Failure = 0xFF
+    }
+}
+
+numeric_enum! {
+    #[repr(u8)]
+    /// this enum contains all the MAC PIB Ids
+    #[derive(Default, Debug)]
+    #[cfg_attr(feature = "defmt", derive(defmt::Format))]
+    pub enum PibId {
+        // PHY
+        #[default]
+        CurrentChannel = 0x00,
+        ChannelsSupported = 0x01,
+        TransmitPower = 0x02,
+        CCAMode = 0x03,
+        CurrentPage = 0x04,
+        MaxFrameDuration = 0x05,
+        SHRDuration = 0x06,
+        SymbolsPerOctet = 0x07,
+
+        // MAC
+        AckWaitDuration = 0x40,
+        AssociationPermit = 0x41,
+        AutoRequest = 0x42,
+        BeaconPayload = 0x45,
+        BeaconPayloadLength = 0x46,
+        BeaconOrder = 0x47,
+        Bsn = 0x49,
+        CoordExtendedAdddress = 0x4A,
+        CoordShortAddress = 0x4B,
+        Dsn = 0x4C,
+        MaxFrameTotalWaitTime = 0x58,
+        MaxFrameRetries = 0x59,
+        PanId = 0x50,
+        ResponseWaitTime = 0x5A,
+        RxOnWhenIdle = 0x52,
+        SecurityEnabled = 0x5D,
+        ShortAddress = 0x53,
+        SuperframeOrder = 0x54,
+        TimestampSupported = 0x5C,
+        TransactionPersistenceTime = 0x55,
+        MaxBe = 0x57,
+        LifsPeriod = 0x5E,
+        SifsPeriod = 0x5F,
+        MaxCsmaBackoffs = 0x4E,
+        MinBe = 0x4F,
+        PanCoordinator = 0x10,
+        AssocPanCoordinator = 0x11,
+        ExtendedAddress = 0x6F,
+        AclEntryDescriptor = 0x70,
+        AclEntryDescriptorSize = 0x71,
+        DefaultSecurity = 0x72,
+        DefaultSecurityMaterialLength = 0x73,
+        DefaultSecurityMaterial = 0x74,
+        DefaultSecuritySuite = 0x75,
+        SecurityMode = 0x76,
+        CurrentAclEntries = 0x80,
+        DefaultSecurityExtendedAddress = 0x81,
+        AssociatedPanCoordinator = 0x56,
+        PromiscuousMode = 0x51,
+    }
+}
+
+numeric_enum! {
+    #[repr(u8)]
+    #[derive(Default, Clone, Copy, Debug)]
+    #[cfg_attr(feature = "defmt", derive(defmt::Format))]
+    pub enum AddressMode {
+        #[default]
+        NoAddress = 0x00,
+        Reserved = 0x01,
+        Short = 0x02,
+        Extended = 0x03,
+}
+}
+
+#[derive(Clone, Copy)]
+pub union MacAddress {
+    pub short: [u8; 2],
+    pub extended: [u8; 8],
+}
+
+impl Debug for MacAddress {
+    fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        unsafe {
+            write!(
+                fmt,
+                "MacAddress {{ short: {:?}, extended: {:?} }}",
+                self.short, self.extended
+            )
+        }
+    }
+}
+
+#[cfg(feature = "defmt")]
+impl defmt::Format for MacAddress {
+    fn format(&self, fmt: defmt::Formatter) {
+        unsafe {
+            defmt::write!(
+                fmt,
+                "MacAddress {{ short: {}, extended: {} }}",
+                self.short,
+                self.extended
+            )
+        }
+    }
+}
+
+impl Default for MacAddress {
+    fn default() -> Self {
+        Self { short: [0, 0] }
+    }
+}
+
+impl MacAddress {
+    pub const BROADCAST: Self = Self { short: [0xFF, 0xFF] };
+}
+
+impl TryFrom<&[u8]> for MacAddress {
+    type Error = ();
+
+    fn try_from(buf: &[u8]) -> Result<Self, Self::Error> {
+        const SIZE: usize = 8;
+        if buf.len() < SIZE {
+            return Err(());
+        }
+
+        Ok(Self {
+            extended: [buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7]],
+        })
+    }
+}
+
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct GtsCharacteristics {
+    pub fields: u8,
+}
+
+/// MAC PAN Descriptor which contains the network details of the device from
+/// which the beacon is received
+#[derive(Default, Clone, Copy, Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct PanDescriptor {
+    /// PAN identifier of the coordinator
+    pub coord_pan_id: PanId,
+    /// Coordinator addressing mode
+    pub coord_addr_mode: AddressMode,
+    /// The current logical channel occupied by the network
+    pub logical_channel: MacChannel,
+    /// Coordinator address
+    pub coord_addr: MacAddress,
+    /// The current channel page occupied by the network
+    pub channel_page: u8,
+    /// PAN coordinator is accepting GTS requests or not
+    pub gts_permit: bool,
+    /// Superframe specification as specified in the received beacon frame
+    pub superframe_spec: [u8; 2],
+    /// The time at which the beacon frame was received, in symbols
+    pub time_stamp: [u8; 4],
+    /// The LQI at which the network beacon was received
+    pub link_quality: u8,
+    /// Security level purportedly used by the received beacon frame
+    pub security_level: u8,
+}
+
+impl TryFrom<&[u8]> for PanDescriptor {
+    type Error = ();
+
+    fn try_from(buf: &[u8]) -> Result<Self, Self::Error> {
+        const SIZE: usize = 22;
+        if buf.len() < SIZE {
+            return Err(());
+        }
+
+        let coord_addr_mode = AddressMode::try_from(buf[2])?;
+        let coord_addr = match coord_addr_mode {
+            AddressMode::NoAddress => MacAddress { short: [0, 0] },
+            AddressMode::Reserved => MacAddress { short: [0, 0] },
+            AddressMode::Short => MacAddress {
+                short: [buf[4], buf[5]],
+            },
+            AddressMode::Extended => MacAddress {
+                extended: [buf[4], buf[5], buf[6], buf[7], buf[8], buf[9], buf[10], buf[11]],
+            },
+        };
+
+        Ok(Self {
+            coord_pan_id: PanId([buf[0], buf[1]]),
+            coord_addr_mode,
+            logical_channel: MacChannel::try_from(buf[3])?,
+            coord_addr,
+            channel_page: buf[12],
+            gts_permit: buf[13] != 0,
+            superframe_spec: [buf[14], buf[15]],
+            time_stamp: [buf[16], buf[17], buf[18], buf[19]],
+            link_quality: buf[20],
+            security_level: buf[21],
+            // 2 byte stuffing
+        })
+    }
+}
+
+numeric_enum! {
+    #[repr(u8)]
+    #[derive(Default, Clone, Copy, Debug)]
+    #[cfg_attr(feature = "defmt", derive(defmt::Format))]
+    /// Building wireless applications with STM32WB series MCUs - Application note 13.10.3
+    pub enum MacChannel {
+        Channel11 = 0x0B,
+        Channel12 = 0x0C,
+        Channel13 = 0x0D,
+        Channel14 = 0x0E,
+        Channel15 = 0x0F,
+        #[default]
+        Channel16 = 0x10,
+        Channel17 = 0x11,
+        Channel18 = 0x12,
+        Channel19 = 0x13,
+        Channel20 = 0x14,
+        Channel21 = 0x15,
+        Channel22 = 0x16,
+        Channel23 = 0x17,
+        Channel24 = 0x18,
+        Channel25 = 0x19,
+        Channel26 = 0x1A,
+    }
+}
+
+#[cfg(not(feature = "defmt"))]
+bitflags::bitflags! {
+    pub struct Capabilities: u8 {
+        /// 1 if the device is capabaleof becoming a PAN coordinator
+        const IS_COORDINATOR_CAPABLE = 0b00000001;
+        /// 1 if the device is an FFD, 0 if it is an RFD
+        const IS_FFD = 0b00000010;
+        /// 1 if the device is receiving power from mains, 0 if it is battery-powered
+        const IS_MAINS_POWERED = 0b00000100;
+        /// 1 if the device does not disable its receiver to conserver power during idle periods
+        const RECEIVER_ON_WHEN_IDLE = 0b00001000;
+        // 0b00010000 reserved
+        // 0b00100000 reserved
+        /// 1 if the device is capable of sending and receiving secured MAC frames
+        const IS_SECURE = 0b01000000;
+        /// 1 if the device wishes the coordinator to allocate a short address as a result of the association
+        const ALLOCATE_ADDRESS = 0b10000000;
+    }
+}
+
+#[cfg(feature = "defmt")]
+defmt::bitflags! {
+    pub struct Capabilities: u8 {
+        /// 1 if the device is capabaleof becoming a PAN coordinator
+        const IS_COORDINATOR_CAPABLE = 0b00000001;
+        /// 1 if the device is an FFD, 0 if it is an RFD
+        const IS_FFD = 0b00000010;
+        /// 1 if the device is receiving power from mains, 0 if it is battery-powered
+        const IS_MAINS_POWERED = 0b00000100;
+        /// 1 if the device does not disable its receiver to conserver power during idle periods
+        const RECEIVER_ON_WHEN_IDLE = 0b00001000;
+        // 0b00010000 reserved
+        // 0b00100000 reserved
+        /// 1 if the device is capable of sending and receiving secured MAC frames
+        const IS_SECURE = 0b01000000;
+        /// 1 if the device wishes the coordinator to allocate a short address as a result of the association
+        const ALLOCATE_ADDRESS = 0b10000000;
+    }
+}
+
+numeric_enum! {
+    #[repr(u8)]
+    #[derive(Default, Clone, Copy, Debug)]
+    #[cfg_attr(feature = "defmt", derive(defmt::Format))]
+    pub enum KeyIdMode {
+        #[default]
+        /// the key is determined implicitly from the originator and recipient(s) of the frame
+        Implicite = 0x00,
+        /// the key is determined explicitly using a 1 bytes key source and a 1 byte key index
+        Explicite1Byte = 0x01,
+        /// the key is determined explicitly using a 4 bytes key source and a 1 byte key index
+        Explicite4Byte = 0x02,
+        /// the key is determined explicitly using a 8 bytes key source and a 1 byte key index
+        Explicite8Byte = 0x03,
+    }
+}
+
+numeric_enum! {
+    #[repr(u8)]
+    #[derive(Debug)]
+    #[cfg_attr(feature = "defmt", derive(defmt::Format))]
+    pub enum AssociationStatus {
+        /// Association successful
+        Success = 0x00,
+        /// PAN at capacity
+        PanAtCapacity = 0x01,
+        /// PAN access denied
+        PanAccessDenied = 0x02
+    }
+}
+
+numeric_enum! {
+    #[repr(u8)]
+    #[derive(Clone, Copy, Debug)]
+    #[cfg_attr(feature = "defmt", derive(defmt::Format))]
+    pub enum DisassociationReason {
+        /// The coordinator wishes the device to leave the PAN.
+        CoordRequested = 0x01,
+        /// The device wishes to leave the PAN.
+        DeviceRequested = 0x02,
+    }
+}
+
+numeric_enum! {
+    #[repr(u8)]
+    #[derive(Default, Clone, Copy, Debug)]
+    #[cfg_attr(feature = "defmt", derive(defmt::Format))]
+    pub enum SecurityLevel {
+        /// MAC Unsecured Mode Security
+        #[default]
+        Unsecure = 0x00,
+        /// MAC ACL Mode Security
+        AclMode = 0x01,
+        /// MAC Secured Mode Security
+        Secured = 0x02,
+    }
+}
+
+numeric_enum! {
+    #[repr(u8)]
+    #[derive(Debug)]
+    #[cfg_attr(feature = "defmt", derive(defmt::Format))]
+    pub enum ScanType {
+        EdScan = 0x00,
+        Active = 0x01,
+        Passive = 0x02,
+        Orphan = 0x03
+    }
+}
+
+/// newtype for Pan Id
+#[derive(Default, Clone, Copy, Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct PanId(pub [u8; 2]);
+
+impl PanId {
+    pub const BROADCAST: Self = Self([0xFF, 0xFF]);
+}
diff --git a/embassy-stm32-wpan/src/sub/mac.rs b/embassy-stm32-wpan/src/sub/mac.rs
index fd8af8609..b0cf0248a 100644
--- a/embassy-stm32-wpan/src/sub/mac.rs
+++ b/embassy-stm32-wpan/src/sub/mac.rs
@@ -11,9 +11,10 @@ use embassy_sync::waitqueue::AtomicWaker;
 use crate::cmd::CmdPacket;
 use crate::consts::TlPacketType;
 use crate::evt::{EvtBox, EvtPacket};
-use crate::tables::{
-    Mac802_15_4Table, MAC_802_15_4_CMD_BUFFER, MAC_802_15_4_NOTIF_RSP_EVT_BUFFER, TL_MAC_802_15_4_TABLE,
-};
+use crate::mac::commands::MacCommand;
+use crate::mac::event::MacEvent;
+use crate::mac::typedefs::MacError;
+use crate::tables::{MAC_802_15_4_CMD_BUFFER, MAC_802_15_4_NOTIF_RSP_EVT_BUFFER};
 use crate::{channels, evt};
 
 static MAC_WAKER: AtomicWaker = AtomicWaker::new();
@@ -25,21 +26,13 @@ pub struct Mac {
 
 impl Mac {
     pub(crate) fn new() -> Self {
-        unsafe {
-            TL_MAC_802_15_4_TABLE.as_mut_ptr().write_volatile(Mac802_15_4Table {
-                p_cmdrsp_buffer: MAC_802_15_4_CMD_BUFFER.as_mut_ptr().cast(),
-                p_notack_buffer: MAC_802_15_4_NOTIF_RSP_EVT_BUFFER.as_mut_ptr().cast(),
-                evt_queue: ptr::null_mut(),
-            });
-        }
-
         Self { phantom: PhantomData }
     }
 
     /// `HW_IPCC_MAC_802_15_4_EvtNot`
     ///
     /// This function will stall if the previous `EvtBox` has not been dropped
-    pub async fn read(&self) -> EvtBox<Self> {
+    pub async fn tl_read(&self) -> EvtBox<Self> {
         // Wait for the last event box to be dropped
         poll_fn(|cx| {
             MAC_WAKER.register(cx.waker());
@@ -63,9 +56,9 @@ impl Mac {
     }
 
     /// `HW_IPCC_MAC_802_15_4_CmdEvtNot`
-    pub async fn write_and_get_response(&self, opcode: u16, payload: &[u8]) -> u8 {
-        self.write(opcode, payload).await;
-        Ipcc::flush(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL).await;
+    pub async fn tl_write_and_get_response(&self, opcode: u16, payload: &[u8]) -> u8 {
+        self.tl_write(opcode, payload).await;
+        Ipcc::flush(channels::cpu1::IPCC_MAC_802_15_4_CMD_RSP_CHANNEL).await;
 
         unsafe {
             let p_event_packet = MAC_802_15_4_CMD_BUFFER.as_ptr() as *const EvtPacket;
@@ -76,22 +69,41 @@ impl Mac {
     }
 
     /// `TL_MAC_802_15_4_SendCmd`
-    pub async fn write(&self, opcode: u16, payload: &[u8]) {
+    pub async fn tl_write(&self, opcode: u16, payload: &[u8]) {
         Ipcc::send(channels::cpu1::IPCC_MAC_802_15_4_CMD_RSP_CHANNEL, || unsafe {
             CmdPacket::write_into(
                 MAC_802_15_4_CMD_BUFFER.as_mut_ptr(),
-                TlPacketType::OtCmd,
+                TlPacketType::MacCmd,
                 opcode,
                 payload,
             );
         })
         .await;
     }
+
+    pub async fn send_command<T>(&self, cmd: &T) -> Result<(), MacError>
+    where
+        T: MacCommand,
+    {
+        let response = self.tl_write_and_get_response(T::OPCODE as u16, cmd.payload()).await;
+
+        if response == 0x00 {
+            Ok(())
+        } else {
+            Err(MacError::from(response))
+        }
+    }
+
+    pub async fn read(&self) -> Result<MacEvent<'_>, ()> {
+        MacEvent::new(self.tl_read().await)
+    }
 }
 
 impl evt::MemoryManager for Mac {
     /// SAFETY: passing a pointer to something other than a managed event packet is UB
     unsafe fn drop_event_packet(_: *mut EvtPacket) {
+        trace!("mac drop event");
+
         // Write the ack
         CmdPacket::write_into(
             MAC_802_15_4_NOTIF_RSP_EVT_BUFFER.as_mut_ptr() as *mut _,
@@ -101,7 +113,7 @@ impl evt::MemoryManager for Mac {
         );
 
         // Clear the rx flag
-        let _ = poll_once(Ipcc::receive::<bool>(
+        let _ = poll_once(Ipcc::receive::<()>(
             channels::cpu2::IPCC_MAC_802_15_4_NOTIFICATION_ACK_CHANNEL,
             || None,
         ));
diff --git a/embassy-stm32-wpan/src/sub/mm.rs b/embassy-stm32-wpan/src/sub/mm.rs
index 1f2ecac2e..da05ad1dd 100644
--- a/embassy-stm32-wpan/src/sub/mm.rs
+++ b/embassy-stm32-wpan/src/sub/mm.rs
@@ -4,20 +4,21 @@ use core::marker::PhantomData;
 use core::mem::MaybeUninit;
 use core::task::Poll;
 
+use aligned::{Aligned, A4};
 use cortex_m::interrupt;
 use embassy_stm32::ipcc::Ipcc;
 use embassy_sync::waitqueue::AtomicWaker;
 
 use crate::consts::POOL_SIZE;
 use crate::evt::EvtPacket;
-use crate::tables::{
-    MemManagerTable, BLE_SPARE_EVT_BUF, EVT_POOL, FREE_BUF_QUEUE, SYS_SPARE_EVT_BUF, TL_MEM_MANAGER_TABLE,
-};
+#[cfg(feature = "ble")]
+use crate::tables::BLE_SPARE_EVT_BUF;
+use crate::tables::{MemManagerTable, EVT_POOL, FREE_BUF_QUEUE, SYS_SPARE_EVT_BUF, TL_MEM_MANAGER_TABLE};
 use crate::unsafe_linked_list::LinkedListNode;
 use crate::{channels, evt};
 
 static MM_WAKER: AtomicWaker = AtomicWaker::new();
-static mut LOCAL_FREE_BUF_QUEUE: MaybeUninit<LinkedListNode> = MaybeUninit::uninit();
+static mut LOCAL_FREE_BUF_QUEUE: Aligned<A4, MaybeUninit<LinkedListNode>> = Aligned(MaybeUninit::uninit());
 
 pub struct MemoryManager {
     phantom: PhantomData<MemoryManager>,
@@ -30,7 +31,10 @@ impl MemoryManager {
             LinkedListNode::init_head(LOCAL_FREE_BUF_QUEUE.as_mut_ptr());
 
             TL_MEM_MANAGER_TABLE.as_mut_ptr().write_volatile(MemManagerTable {
+                #[cfg(feature = "ble")]
                 spare_ble_buffer: BLE_SPARE_EVT_BUF.as_ptr().cast(),
+                #[cfg(not(feature = "ble"))]
+                spare_ble_buffer: core::ptr::null(),
                 spare_sys_buffer: SYS_SPARE_EVT_BUF.as_ptr().cast(),
                 blepool: EVT_POOL.as_ptr().cast(),
                 blepoolsize: POOL_SIZE as u32,
diff --git a/embassy-stm32-wpan/src/sub/sys.rs b/embassy-stm32-wpan/src/sub/sys.rs
index af652860d..c17fd531d 100644
--- a/embassy-stm32-wpan/src/sub/sys.rs
+++ b/embassy-stm32-wpan/src/sub/sys.rs
@@ -50,7 +50,7 @@ impl Sys {
     }
 
     /// `HW_IPCC_SYS_CmdEvtNot`
-    pub async fn write_and_get_response(&self, opcode: ShciOpcode, payload: &[u8]) -> SchiCommandStatus {
+    pub async fn write_and_get_response(&self, opcode: ShciOpcode, payload: &[u8]) -> Result<SchiCommandStatus, ()> {
         self.write(opcode, payload).await;
         Ipcc::flush(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL).await;
 
@@ -59,17 +59,36 @@ impl Sys {
             let p_command_event = &((*p_event_packet).evt_serial.evt.payload) as *const _ as *const CcEvt;
             let p_payload = &((*p_command_event).payload) as *const u8;
 
-            ptr::read_volatile(p_payload).try_into().unwrap()
+            ptr::read_volatile(p_payload).try_into()
         }
     }
 
     #[cfg(feature = "mac")]
-    pub async fn shci_c2_mac_802_15_4_init(&self) -> SchiCommandStatus {
+    pub async fn shci_c2_mac_802_15_4_init(&self) -> Result<SchiCommandStatus, ()> {
+        use crate::tables::{
+            Mac802_15_4Table, TracesTable, MAC_802_15_4_CMD_BUFFER, MAC_802_15_4_NOTIF_RSP_EVT_BUFFER,
+            TL_MAC_802_15_4_TABLE, TL_TRACES_TABLE, TRACES_EVT_QUEUE,
+        };
+
+        unsafe {
+            LinkedListNode::init_head(TRACES_EVT_QUEUE.as_mut_ptr() as *mut _);
+
+            TL_TRACES_TABLE.as_mut_ptr().write_volatile(TracesTable {
+                traces_queue: TRACES_EVT_QUEUE.as_ptr() as *const _,
+            });
+
+            TL_MAC_802_15_4_TABLE.as_mut_ptr().write_volatile(Mac802_15_4Table {
+                p_cmdrsp_buffer: MAC_802_15_4_CMD_BUFFER.as_mut_ptr().cast(),
+                p_notack_buffer: MAC_802_15_4_NOTIF_RSP_EVT_BUFFER.as_mut_ptr().cast(),
+                evt_queue: core::ptr::null_mut(),
+            });
+        };
+
         self.write_and_get_response(ShciOpcode::Mac802_15_4Init, &[]).await
     }
 
     #[cfg(feature = "ble")]
-    pub async fn shci_c2_ble_init(&self, param: ShciBleInitCmdParam) -> SchiCommandStatus {
+    pub async fn shci_c2_ble_init(&self, param: ShciBleInitCmdParam) -> Result<SchiCommandStatus, ()> {
         self.write_and_get_response(ShciOpcode::BleInit, param.payload()).await
     }
 
diff --git a/embassy-stm32-wpan/src/tables.rs b/embassy-stm32-wpan/src/tables.rs
index 1b5dcdf2e..f2c250527 100644
--- a/embassy-stm32-wpan/src/tables.rs
+++ b/embassy-stm32-wpan/src/tables.rs
@@ -4,6 +4,8 @@ use aligned::{Aligned, A4};
 use bit_field::BitField;
 
 use crate::cmd::{AclDataPacket, CmdPacket};
+#[cfg(feature = "mac")]
+use crate::consts::C_SIZE_CMD_STRING;
 use crate::consts::{POOL_SIZE, TL_CS_EVT_SIZE, TL_EVT_HEADER_SIZE, TL_PACKET_HEADER_SIZE};
 use crate::unsafe_linked_list::LinkedListNode;
 
@@ -80,7 +82,7 @@ impl WirelessFwInfoTable {
 }
 
 #[derive(Debug, Clone)]
-#[repr(C, align(4))]
+#[repr(C)]
 pub struct DeviceInfoTable {
     pub safe_boot_info_table: SafeBootInfoTable,
     pub rss_info_table: RssInfoTable,
@@ -88,7 +90,7 @@ pub struct DeviceInfoTable {
 }
 
 #[derive(Debug)]
-#[repr(C, align(4))]
+#[repr(C)]
 pub struct BleTable {
     pub pcmd_buffer: *mut CmdPacket,
     pub pcs_buffer: *const u8,
@@ -97,16 +99,15 @@ pub struct BleTable {
 }
 
 #[derive(Debug)]
-#[repr(C, align(4))]
+#[repr(C)]
 pub struct ThreadTable {
     pub nostack_buffer: *const u8,
     pub clicmdrsp_buffer: *const u8,
     pub otcmdrsp_buffer: *const u8,
 }
 
-// TODO: use later
 #[derive(Debug)]
-#[repr(C, align(4))]
+#[repr(C)]
 pub struct LldTestsTable {
     pub clicmdrsp_buffer: *const u8,
     pub m0cmd_buffer: *const u8,
@@ -114,7 +115,7 @@ pub struct LldTestsTable {
 
 // TODO: use later
 #[derive(Debug)]
-#[repr(C, align(4))]
+#[repr(C)]
 pub struct BleLldTable {
     pub cmdrsp_buffer: *const u8,
     pub m0cmd_buffer: *const u8,
@@ -122,7 +123,7 @@ pub struct BleLldTable {
 
 // TODO: use later
 #[derive(Debug)]
-#[repr(C, align(4))]
+#[repr(C)]
 pub struct ZigbeeTable {
     pub notif_m0_to_m4_buffer: *const u8,
     pub appli_cmd_m4_to_m0_bufer: *const u8,
@@ -130,14 +131,14 @@ pub struct ZigbeeTable {
 }
 
 #[derive(Debug)]
-#[repr(C, align(4))]
+#[repr(C)]
 pub struct SysTable {
     pub pcmd_buffer: *mut CmdPacket,
     pub sys_queue: *const LinkedListNode,
 }
 
 #[derive(Debug)]
-#[repr(C, align(4))]
+#[repr(C)]
 pub struct MemManagerTable {
     pub spare_ble_buffer: *const u8,
     pub spare_sys_buffer: *const u8,
@@ -152,13 +153,13 @@ pub struct MemManagerTable {
 }
 
 #[derive(Debug)]
-#[repr(C, align(4))]
+#[repr(C)]
 pub struct TracesTable {
     pub traces_queue: *const u8,
 }
 
 #[derive(Debug)]
-#[repr(C, align(4))]
+#[repr(C)]
 pub struct Mac802_15_4Table {
     pub p_cmdrsp_buffer: *const u8,
     pub p_notack_buffer: *const u8,
@@ -176,6 +177,9 @@ pub struct RefTable {
     pub mem_manager_table: *const MemManagerTable,
     pub traces_table: *const TracesTable,
     pub mac_802_15_4_table: *const Mac802_15_4Table,
+    pub zigbee_table: *const ZigbeeTable,
+    pub lld_tests_table: *const LldTestsTable,
+    pub ble_lld_table: *const BleLldTable,
 }
 
 // --------------------- ref table ---------------------
@@ -183,57 +187,57 @@ pub struct RefTable {
 pub static mut TL_REF_TABLE: MaybeUninit<RefTable> = MaybeUninit::uninit();
 
 #[link_section = "MB_MEM1"]
-pub static mut TL_DEVICE_INFO_TABLE: MaybeUninit<DeviceInfoTable> = MaybeUninit::uninit();
+pub static mut TL_DEVICE_INFO_TABLE: Aligned<A4, MaybeUninit<DeviceInfoTable>> = Aligned(MaybeUninit::uninit());
 
 #[link_section = "MB_MEM1"]
-pub static mut TL_BLE_TABLE: MaybeUninit<BleTable> = MaybeUninit::uninit();
+pub static mut TL_BLE_TABLE: Aligned<A4, MaybeUninit<BleTable>> = Aligned(MaybeUninit::uninit());
 
 #[link_section = "MB_MEM1"]
-pub static mut TL_THREAD_TABLE: MaybeUninit<ThreadTable> = MaybeUninit::uninit();
-
-// #[link_section = "MB_MEM1"]
-// pub static mut TL_LLD_TESTS_TABLE: MaybeUninit<LldTestTable> = MaybeUninit::uninit();
-
-// #[link_section = "MB_MEM1"]
-// pub static mut TL_BLE_LLD_TABLE: MaybeUninit<BleLldTable> = MaybeUninit::uninit();
+pub static mut TL_THREAD_TABLE: Aligned<A4, MaybeUninit<ThreadTable>> = Aligned(MaybeUninit::uninit());
 
 #[link_section = "MB_MEM1"]
-pub static mut TL_SYS_TABLE: MaybeUninit<SysTable> = MaybeUninit::uninit();
+pub static mut TL_LLD_TESTS_TABLE: Aligned<A4, MaybeUninit<LldTestsTable>> = Aligned(MaybeUninit::uninit());
 
 #[link_section = "MB_MEM1"]
-pub static mut TL_MEM_MANAGER_TABLE: MaybeUninit<MemManagerTable> = MaybeUninit::uninit();
+pub static mut TL_BLE_LLD_TABLE: Aligned<A4, MaybeUninit<BleLldTable>> = Aligned(MaybeUninit::uninit());
 
 #[link_section = "MB_MEM1"]
-pub static mut TL_TRACES_TABLE: MaybeUninit<TracesTable> = MaybeUninit::uninit();
+pub static mut TL_SYS_TABLE: Aligned<A4, MaybeUninit<SysTable>> = Aligned(MaybeUninit::uninit());
 
 #[link_section = "MB_MEM1"]
-pub static mut TL_MAC_802_15_4_TABLE: MaybeUninit<Mac802_15_4Table> = MaybeUninit::uninit();
+pub static mut TL_MEM_MANAGER_TABLE: Aligned<A4, MaybeUninit<MemManagerTable>> = Aligned(MaybeUninit::uninit());
 
-// #[link_section = "MB_MEM1"]
-// pub static mut TL_ZIGBEE_TABLE: MaybeUninit<ZigbeeTable> = MaybeUninit::uninit();
+#[link_section = "MB_MEM1"]
+pub static mut TL_TRACES_TABLE: Aligned<A4, MaybeUninit<TracesTable>> = Aligned(MaybeUninit::uninit());
+
+#[link_section = "MB_MEM1"]
+pub static mut TL_MAC_802_15_4_TABLE: Aligned<A4, MaybeUninit<Mac802_15_4Table>> = Aligned(MaybeUninit::uninit());
+
+#[link_section = "MB_MEM1"]
+pub static mut TL_ZIGBEE_TABLE: Aligned<A4, MaybeUninit<ZigbeeTable>> = Aligned(MaybeUninit::uninit());
 
 // --------------------- tables ---------------------
 #[link_section = "MB_MEM1"]
-pub static mut FREE_BUF_QUEUE: MaybeUninit<LinkedListNode> = MaybeUninit::uninit();
+pub static mut FREE_BUF_QUEUE: Aligned<A4, MaybeUninit<LinkedListNode>> = Aligned(MaybeUninit::uninit());
 
 #[allow(dead_code)]
 #[link_section = "MB_MEM1"]
-pub static mut TRACES_EVT_QUEUE: MaybeUninit<LinkedListNode> = MaybeUninit::uninit();
+pub static mut TRACES_EVT_QUEUE: Aligned<A4, MaybeUninit<LinkedListNode>> = Aligned(MaybeUninit::uninit());
 
 #[link_section = "MB_MEM2"]
-pub static mut CS_BUFFER: MaybeUninit<Aligned<A4, [u8; TL_PACKET_HEADER_SIZE + TL_EVT_HEADER_SIZE + TL_CS_EVT_SIZE]>> =
-    MaybeUninit::uninit();
+pub static mut CS_BUFFER: Aligned<A4, MaybeUninit<[u8; TL_PACKET_HEADER_SIZE + TL_EVT_HEADER_SIZE + TL_CS_EVT_SIZE]>> =
+    Aligned(MaybeUninit::uninit());
 
 #[link_section = "MB_MEM2"]
-pub static mut EVT_QUEUE: MaybeUninit<LinkedListNode> = MaybeUninit::uninit();
+pub static mut EVT_QUEUE: Aligned<A4, MaybeUninit<LinkedListNode>> = Aligned(MaybeUninit::uninit());
 
 #[link_section = "MB_MEM2"]
-pub static mut SYSTEM_EVT_QUEUE: MaybeUninit<LinkedListNode> = MaybeUninit::uninit();
+pub static mut SYSTEM_EVT_QUEUE: Aligned<A4, MaybeUninit<LinkedListNode>> = Aligned(MaybeUninit::uninit());
 
 // --------------------- app tables ---------------------
 #[cfg(feature = "mac")]
 #[link_section = "MB_MEM2"]
-pub static mut MAC_802_15_4_CMD_BUFFER: MaybeUninit<CmdPacket> = MaybeUninit::uninit();
+pub static mut MAC_802_15_4_CMD_BUFFER: Aligned<A4, MaybeUninit<CmdPacket>> = Aligned(MaybeUninit::uninit());
 
 #[cfg(feature = "mac")]
 #[link_section = "MB_MEM2"]
@@ -242,23 +246,31 @@ pub static mut MAC_802_15_4_NOTIF_RSP_EVT_BUFFER: MaybeUninit<
 > = MaybeUninit::uninit();
 
 #[link_section = "MB_MEM2"]
-pub static mut EVT_POOL: MaybeUninit<Aligned<A4, [u8; POOL_SIZE]>> = MaybeUninit::uninit();
+pub static mut EVT_POOL: Aligned<A4, MaybeUninit<[u8; POOL_SIZE]>> = Aligned(MaybeUninit::uninit());
 
 #[link_section = "MB_MEM2"]
-pub static mut SYS_CMD_BUF: MaybeUninit<CmdPacket> = MaybeUninit::uninit();
+pub static mut SYS_CMD_BUF: Aligned<A4, MaybeUninit<CmdPacket>> = Aligned(MaybeUninit::uninit());
 
 #[link_section = "MB_MEM2"]
-pub static mut SYS_SPARE_EVT_BUF: MaybeUninit<Aligned<A4, [u8; TL_PACKET_HEADER_SIZE + TL_EVT_HEADER_SIZE + 255]>> =
-    MaybeUninit::uninit();
+pub static mut SYS_SPARE_EVT_BUF: Aligned<A4, MaybeUninit<[u8; TL_PACKET_HEADER_SIZE + TL_EVT_HEADER_SIZE + 255]>> =
+    Aligned(MaybeUninit::uninit());
 
+#[cfg(feature = "mac")]
+#[link_section = "MB_MEM2"]
+pub static mut MAC_802_15_4_CNFINDNOT: Aligned<A4, MaybeUninit<[u8; C_SIZE_CMD_STRING]>> =
+    Aligned(MaybeUninit::uninit());
+
+#[cfg(feature = "ble")]
 #[link_section = "MB_MEM1"]
-pub static mut BLE_CMD_BUFFER: MaybeUninit<CmdPacket> = MaybeUninit::uninit();
+pub static mut BLE_CMD_BUFFER: Aligned<A4, MaybeUninit<CmdPacket>> = Aligned(MaybeUninit::uninit());
 
+#[cfg(feature = "ble")]
 #[link_section = "MB_MEM2"]
-pub static mut BLE_SPARE_EVT_BUF: MaybeUninit<Aligned<A4, [u8; TL_PACKET_HEADER_SIZE + TL_EVT_HEADER_SIZE + 255]>> =
-    MaybeUninit::uninit();
+pub static mut BLE_SPARE_EVT_BUF: Aligned<A4, MaybeUninit<[u8; TL_PACKET_HEADER_SIZE + TL_EVT_HEADER_SIZE + 255]>> =
+    Aligned(MaybeUninit::uninit());
 
+#[cfg(feature = "ble")]
 #[link_section = "MB_MEM2"]
 //                                 fuck these "magic" numbers from ST ---v---v
-pub static mut HCI_ACL_DATA_BUFFER: MaybeUninit<Aligned<A4, [u8; TL_PACKET_HEADER_SIZE + 5 + 251]>> =
-    MaybeUninit::uninit();
+pub static mut HCI_ACL_DATA_BUFFER: Aligned<A4, MaybeUninit<[u8; TL_PACKET_HEADER_SIZE + 5 + 251]>> =
+    Aligned(MaybeUninit::uninit());
diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml
index b3fe9c1f5..0fb6fdb56 100644
--- a/embassy-stm32/Cargo.toml
+++ b/embassy-stm32/Cargo.toml
@@ -32,7 +32,7 @@ flavors = [
 
 [dependencies]
 embassy-sync = { version = "0.2.0", path = "../embassy-sync" }
-embassy-time = { version = "0.1.0", path = "../embassy-time", optional = true }
+embassy-time = { version = "0.1.2", path = "../embassy-time", optional = true }
 embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
 embassy-hal-common = {version = "0.1.0", path = "../embassy-hal-common", features = ["cortex-m", "prio-bits-4"] }
 embassy-embedded-hal = {version = "0.1.0", path = "../embassy-embedded-hal" }
@@ -40,9 +40,9 @@ embassy-net-driver = { version = "0.1.0", path = "../embassy-net-driver" }
 embassy-usb-driver = {version = "0.1.0", path = "../embassy-usb-driver", optional = true }
 
 embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] }
-embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.10", optional = true}
-embedded-hal-async = { version = "=0.2.0-alpha.1", optional = true}
-embedded-hal-nb = { version = "=1.0.0-alpha.2", optional = true}
+embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.11", optional = true}
+embedded-hal-async = { version = "=0.2.0-alpha.2", optional = true}
+embedded-hal-nb = { version = "=1.0.0-alpha.3", optional = true}
 
 embedded-storage = "0.3.0"
 embedded-storage-async = { version = "0.4.0", optional = true }
@@ -57,7 +57,7 @@ sdio-host = "0.5.0"
 embedded-sdmmc = { git = "https://github.com/embassy-rs/embedded-sdmmc-rs", rev = "a4f293d3a6f72158385f79c98634cb8a14d0d2fc", optional = true }
 critical-section = "1.1"
 atomic-polyfill = "1.0.1"
-stm32-metapac = "12"
+stm32-metapac = "13"
 vcell = "0.1.3"
 bxcan = "0.7.0"
 nb = "1.0.0"
@@ -74,7 +74,7 @@ critical-section = { version = "1.1", features = ["std"] }
 [build-dependencies]
 proc-macro2 = "1.0.36"
 quote = "1.0.15"
-stm32-metapac = { version = "12", default-features = false, features = ["metadata"]}
+stm32-metapac = { version = "13", default-features = false, features = ["metadata"]}
 
 [features]
 default = ["rt"]
diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs
index d628bcf04..d2b1cfd05 100644
--- a/embassy-stm32/build.rs
+++ b/embassy-stm32/build.rs
@@ -348,9 +348,7 @@ fn main() {
             g.extend(quote! {
                 impl crate::rcc::sealed::RccPeripheral for peripherals::#pname {
                     fn frequency() -> crate::time::Hertz {
-                        critical_section::with(|_| unsafe {
-                            crate::rcc::get_freqs().#clk
-                        })
+                        unsafe { crate::rcc::get_freqs().#clk }
                     }
                     fn enable() {
                         critical_section::with(|_| {
diff --git a/embassy-stm32/src/adc/mod.rs b/embassy-stm32/src/adc/mod.rs
index 56ecd63ca..94a8538bf 100644
--- a/embassy-stm32/src/adc/mod.rs
+++ b/embassy-stm32/src/adc/mod.rs
@@ -1,5 +1,6 @@
 #![macro_use]
 
+#[cfg(not(adc_f3))]
 #[cfg_attr(adc_f1, path = "f1.rs")]
 #[cfg_attr(adc_v1, path = "v1.rs")]
 #[cfg_attr(adc_v2, path = "v2.rs")]
@@ -7,14 +8,16 @@
 #[cfg_attr(adc_v4, path = "v4.rs")]
 mod _version;
 
-#[cfg(not(adc_f1))]
+#[cfg(not(any(adc_f1, adc_f3)))]
 mod resolution;
 mod sample_time;
 
+#[cfg(not(adc_f3))]
 #[allow(unused)]
 pub use _version::*;
-#[cfg(not(adc_f1))]
+#[cfg(not(any(adc_f1, adc_f3)))]
 pub use resolution::Resolution;
+#[cfg(not(adc_f3))]
 pub use sample_time::SampleTime;
 
 use crate::peripherals;
@@ -22,13 +25,14 @@ use crate::peripherals;
 pub struct Adc<'d, T: Instance> {
     #[allow(unused)]
     adc: crate::PeripheralRef<'d, T>,
+    #[cfg(not(adc_f3))]
     sample_time: SampleTime,
 }
 
 pub(crate) mod sealed {
     pub trait Instance {
         fn regs() -> crate::pac::adc::Adc;
-        #[cfg(all(not(adc_f1), not(adc_v1)))]
+        #[cfg(not(any(adc_f1, adc_v1, adc_f3)))]
         fn common_regs() -> crate::pac::adccommon::AdcCommon;
     }
 
@@ -56,7 +60,7 @@ foreach_peripheral!(
             fn regs() -> crate::pac::adc::Adc {
                 crate::pac::$inst
             }
-            #[cfg(all(not(adc_f1), not(adc_v1)))]
+            #[cfg(not(any(adc_f1, adc_v1, adc_f3)))]
             fn common_regs() -> crate::pac::adccommon::AdcCommon {
                 foreach_peripheral!{
                     (adccommon, $common_inst:ident) => {
diff --git a/embassy-stm32/src/adc/sample_time.rs b/embassy-stm32/src/adc/sample_time.rs
index 0faa1e3c0..df0525560 100644
--- a/embassy-stm32/src/adc/sample_time.rs
+++ b/embassy-stm32/src/adc/sample_time.rs
@@ -1,3 +1,4 @@
+#[cfg(not(adc_f3))]
 macro_rules! impl_sample_time {
     ($default_doc:expr, $default:ident, ($(($doc:expr, $variant:ident, $pac_variant:ident)),*)) => {
         #[doc = concat!("ADC sample time\n\nThe default setting is ", $default_doc, " ADC clock cycles.")]
diff --git a/embassy-stm32/src/can/bxcan.rs b/embassy-stm32/src/can/bxcan.rs
index 73861776a..5a0153464 100644
--- a/embassy-stm32/src/can/bxcan.rs
+++ b/embassy-stm32/src/can/bxcan.rs
@@ -1,3 +1,4 @@
+use core::cell::{RefCell, RefMut};
 use core::future::poll_fn;
 use core::marker::PhantomData;
 use core::ops::{Deref, DerefMut};
@@ -72,7 +73,7 @@ impl<T: Instance> interrupt::typelevel::Handler<T::SCEInterrupt> for SceInterrup
 }
 
 pub struct Can<'d, T: Instance> {
-    can: bxcan::Can<BxcanInstance<'d, T>>,
+    pub can: RefCell<bxcan::Can<BxcanInstance<'d, T>>>,
 }
 
 #[derive(Debug)]
@@ -147,19 +148,24 @@ impl<'d, T: Instance> Can<'d, T> {
         tx.set_as_af(tx.af_num(), AFType::OutputPushPull);
 
         let can = bxcan::Can::builder(BxcanInstance(peri)).leave_disabled();
-        Self { can }
+        let can_ref_cell = RefCell::new(can);
+        Self { can: can_ref_cell }
     }
 
     pub fn set_bitrate(&mut self, bitrate: u32) {
         let bit_timing = Self::calc_bxcan_timings(T::frequency(), bitrate).unwrap();
-        self.can.modify_config().set_bit_timing(bit_timing).leave_disabled();
+        self.can
+            .borrow_mut()
+            .modify_config()
+            .set_bit_timing(bit_timing)
+            .leave_disabled();
     }
 
     /// Queues the message to be sent but exerts backpressure
     pub async fn write(&mut self, frame: &Frame) -> bxcan::TransmitStatus {
         poll_fn(|cx| {
             T::state().tx_waker.register(cx.waker());
-            if let Ok(status) = self.can.transmit(frame) {
+            if let Ok(status) = self.can.borrow_mut().transmit(frame) {
                 return Poll::Ready(status);
             }
 
@@ -341,6 +347,79 @@ impl<'d, T: Instance> Can<'d, T> {
         // Pack into BTR register values
         Some((sjw - 1) << 24 | (bs1 as u32 - 1) << 16 | (bs2 as u32 - 1) << 20 | (prescaler as u32 - 1))
     }
+
+    pub fn split<'c>(&'c self) -> (CanTx<'c, 'd, T>, CanRx<'c, 'd, T>) {
+        (CanTx { can: &self.can }, CanRx { can: &self.can })
+    }
+
+    pub fn as_mut(&self) -> RefMut<'_, bxcan::Can<BxcanInstance<'d, T>>> {
+        self.can.borrow_mut()
+    }
+}
+
+pub struct CanTx<'c, 'd, T: Instance> {
+    can: &'c RefCell<bxcan::Can<BxcanInstance<'d, T>>>,
+}
+
+impl<'c, 'd, T: Instance> CanTx<'c, 'd, T> {
+    pub async fn write(&mut self, frame: &Frame) -> bxcan::TransmitStatus {
+        poll_fn(|cx| {
+            T::state().tx_waker.register(cx.waker());
+            if let Ok(status) = self.can.borrow_mut().transmit(frame) {
+                return Poll::Ready(status);
+            }
+
+            Poll::Pending
+        })
+        .await
+    }
+
+    pub async fn flush(&self, mb: bxcan::Mailbox) {
+        poll_fn(|cx| {
+            T::state().tx_waker.register(cx.waker());
+            if T::regs().tsr().read().tme(mb.index()) {
+                return Poll::Ready(());
+            }
+
+            Poll::Pending
+        })
+        .await;
+    }
+}
+
+#[allow(dead_code)]
+pub struct CanRx<'c, 'd, T: Instance> {
+    can: &'c RefCell<bxcan::Can<BxcanInstance<'d, T>>>,
+}
+
+impl<'c, 'd, T: Instance> CanRx<'c, 'd, T> {
+    pub async fn read(&mut self) -> Result<(u16, bxcan::Frame), BusError> {
+        poll_fn(|cx| {
+            T::state().err_waker.register(cx.waker());
+            if let Poll::Ready((time, frame)) = T::state().rx_queue.recv().poll_unpin(cx) {
+                return Poll::Ready(Ok((time, frame)));
+            } else if let Some(err) = self.curr_error() {
+                return Poll::Ready(Err(err));
+            }
+
+            Poll::Pending
+        })
+        .await
+    }
+
+    fn curr_error(&self) -> Option<BusError> {
+        let err = { T::regs().esr().read() };
+        if err.boff() {
+            return Some(BusError::BusOff);
+        } else if err.epvf() {
+            return Some(BusError::BusPassive);
+        } else if err.ewgf() {
+            return Some(BusError::BusWarning);
+        } else if let Some(err) = err.lec().into_bus_err() {
+            return Some(err);
+        }
+        None
+    }
 }
 
 enum RxFifo {
@@ -358,7 +437,7 @@ impl<'d, T: Instance> Drop for Can<'d, T> {
 }
 
 impl<'d, T: Instance> Deref for Can<'d, T> {
-    type Target = bxcan::Can<BxcanInstance<'d, T>>;
+    type Target = RefCell<bxcan::Can<BxcanInstance<'d, T>>>;
 
     fn deref(&self) -> &Self::Target {
         &self.can
diff --git a/embassy-stm32/src/can/fdcan.rs b/embassy-stm32/src/can/fdcan.rs
new file mode 100644
index 000000000..c31a7fc63
--- /dev/null
+++ b/embassy-stm32/src/can/fdcan.rs
@@ -0,0 +1,66 @@
+pub use bxcan;
+use embassy_hal_common::PeripheralRef;
+
+use crate::peripherals;
+
+pub(crate) mod sealed {
+    use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
+    use embassy_sync::channel::Channel;
+    use embassy_sync::waitqueue::AtomicWaker;
+
+    pub struct State {
+        pub tx_waker: AtomicWaker,
+        pub err_waker: AtomicWaker,
+        pub rx_queue: Channel<CriticalSectionRawMutex, (u16, bxcan::Frame), 32>,
+    }
+
+    impl State {
+        pub const fn new() -> Self {
+            Self {
+                tx_waker: AtomicWaker::new(),
+                err_waker: AtomicWaker::new(),
+                rx_queue: Channel::new(),
+            }
+        }
+    }
+
+    pub trait Instance {
+        const REGISTERS: *mut bxcan::RegisterBlock;
+
+        fn regs() -> &'static crate::pac::can::Fdcan;
+        fn state() -> &'static State;
+    }
+}
+
+pub trait InterruptableInstance {}
+pub trait Instance: sealed::Instance + InterruptableInstance + 'static {}
+
+pub struct BxcanInstance<'a, T>(PeripheralRef<'a, T>);
+
+unsafe impl<'d, T: Instance> bxcan::Instance for BxcanInstance<'d, T> {
+    const REGISTERS: *mut bxcan::RegisterBlock = T::REGISTERS;
+}
+
+foreach_peripheral!(
+    (can, $inst:ident) => {
+        impl sealed::Instance for peripherals::$inst {
+            const REGISTERS: *mut bxcan::RegisterBlock = crate::pac::$inst.as_ptr() as *mut _;
+
+            fn regs() -> &'static crate::pac::can::Fdcan {
+                &crate::pac::$inst
+            }
+
+            fn state() -> &'static sealed::State {
+                static STATE: sealed::State = sealed::State::new();
+                &STATE
+            }
+        }
+
+        impl Instance for peripherals::$inst {}
+
+        impl InterruptableInstance for peripherals::$inst {}
+    };
+);
+
+pin_trait!(RxPin, Instance);
+pin_trait!(TxPin, Instance);
diff --git a/embassy-stm32/src/can/mod.rs b/embassy-stm32/src/can/mod.rs
index c7e2e620a..4ff5aa0de 100644
--- a/embassy-stm32/src/can/mod.rs
+++ b/embassy-stm32/src/can/mod.rs
@@ -1,5 +1,6 @@
 #![macro_use]
 
 #[cfg_attr(can_bxcan, path = "bxcan.rs")]
+#[cfg_attr(can_fdcan, path = "fdcan.rs")]
 mod _version;
 pub use _version::*;
diff --git a/embassy-stm32/src/dac/mod.rs b/embassy-stm32/src/dac/mod.rs
index 1dc13949d..3d58914b3 100644
--- a/embassy-stm32/src/dac/mod.rs
+++ b/embassy-stm32/src/dac/mod.rs
@@ -51,7 +51,10 @@ impl Ch1Trigger {
     fn tsel(&self) -> dac::vals::Tsel1 {
         match self {
             Ch1Trigger::Tim6 => dac::vals::Tsel1::TIM6_TRGO,
+            #[cfg(not(dac_v3))]
             Ch1Trigger::Tim3 => dac::vals::Tsel1::TIM3_TRGO,
+            #[cfg(dac_v3)]
+            Ch1Trigger::Tim3 => dac::vals::Tsel1::TIM1_TRGO,
             Ch1Trigger::Tim7 => dac::vals::Tsel1::TIM7_TRGO,
             Ch1Trigger::Tim15 => dac::vals::Tsel1::TIM15_TRGO,
             Ch1Trigger::Tim2 => dac::vals::Tsel1::TIM2_TRGO,
@@ -264,7 +267,7 @@ impl<'d, T: Instance, Tx> DacCh1<'d, T, Tx> {
         });
 
         let tx_request = self.dma.request();
-        let dma_channel = &self.dma;
+        let dma_channel = &mut self.dma;
 
         let tx_options = crate::dma::TransferOptions {
             circular,
@@ -376,7 +379,7 @@ impl<'d, T: Instance, Tx> DacCh2<'d, T, Tx> {
         });
 
         let tx_request = self.dma.request();
-        let dma_channel = &self.dma;
+        let dma_channel = &mut self.dma;
 
         let tx_options = crate::dma::TransferOptions {
             circular,
diff --git a/embassy-stm32/src/dma/dma.rs b/embassy-stm32/src/dma/dma.rs
index 8abe541d3..58d438af8 100644
--- a/embassy-stm32/src/dma/dma.rs
+++ b/embassy-stm32/src/dma/dma.rs
@@ -1,10 +1,9 @@
 use core::future::Future;
 use core::marker::PhantomData;
 use core::pin::Pin;
-use core::sync::atomic::{fence, Ordering};
+use core::sync::atomic::{fence, AtomicUsize, Ordering};
 use core::task::{Context, Poll, Waker};
 
-use atomic_polyfill::AtomicUsize;
 use embassy_hal_common::{into_ref, Peripheral, PeripheralRef};
 use embassy_sync::waitqueue::AtomicWaker;
 
diff --git a/embassy-stm32/src/eth/generic_smi.rs b/embassy-stm32/src/eth/generic_smi.rs
index 968256046..2ed46ca2c 100644
--- a/embassy-stm32/src/eth/generic_smi.rs
+++ b/embassy-stm32/src/eth/generic_smi.rs
@@ -1,5 +1,11 @@
 //! Generic SMI Ethernet PHY
 
+#[cfg(feature = "time")]
+use embassy_time::{Duration, Timer};
+use futures::task::Context;
+#[cfg(feature = "time")]
+use futures::FutureExt;
+
 use super::{StationManagement, PHY};
 
 #[allow(dead_code)]
@@ -36,25 +42,47 @@ mod phy_consts {
 use self::phy_consts::*;
 
 /// Generic SMI Ethernet PHY
-pub struct GenericSMI;
+pub struct GenericSMI {
+    #[cfg(feature = "time")]
+    poll_interval: Duration,
+    #[cfg(not(feature = "time"))]
+    _private: (),
+}
+
+impl GenericSMI {
+    pub fn new() -> Self {
+        Self {
+            #[cfg(feature = "time")]
+            poll_interval: Duration::from_millis(500),
+            #[cfg(not(feature = "time"))]
+            _private: (),
+        }
+    }
+}
 
 unsafe impl PHY for GenericSMI {
     /// Reset PHY and wait for it to come out of reset.
-    fn phy_reset<S: StationManagement>(sm: &mut S) {
+    fn phy_reset<S: StationManagement>(&mut self, sm: &mut S) {
         sm.smi_write(PHY_REG_BCR, PHY_REG_BCR_RESET);
         while sm.smi_read(PHY_REG_BCR) & PHY_REG_BCR_RESET == PHY_REG_BCR_RESET {}
     }
 
     /// PHY initialisation.
-    fn phy_init<S: StationManagement>(sm: &mut S) {
+    fn phy_init<S: StationManagement>(&mut self, sm: &mut S) {
         // Clear WU CSR
-        Self::smi_write_ext(sm, PHY_REG_WUCSR, 0);
+        self.smi_write_ext(sm, PHY_REG_WUCSR, 0);
 
         // Enable auto-negotiation
         sm.smi_write(PHY_REG_BCR, PHY_REG_BCR_AN | PHY_REG_BCR_ANRST | PHY_REG_BCR_100M);
     }
 
-    fn poll_link<S: StationManagement>(sm: &mut S) -> bool {
+    fn poll_link<S: StationManagement>(&mut self, sm: &mut S, cx: &mut Context) -> bool {
+        #[cfg(not(feature = "time"))]
+        cx.waker().wake_by_ref();
+
+        #[cfg(feature = "time")]
+        let _ = Timer::after(self.poll_interval).poll_unpin(cx);
+
         let bsr = sm.smi_read(PHY_REG_BSR);
 
         // No link without autonegotiate
@@ -73,8 +101,13 @@ unsafe impl PHY for GenericSMI {
 
 /// Public functions for the PHY
 impl GenericSMI {
+    #[cfg(feature = "time")]
+    pub fn set_poll_interval(&mut self, poll_interval: Duration) {
+        self.poll_interval = poll_interval
+    }
+
     // Writes a value to an extended PHY register in MMD address space
-    fn smi_write_ext<S: StationManagement>(sm: &mut S, reg_addr: u16, reg_data: u16) {
+    fn smi_write_ext<S: StationManagement>(&mut self, sm: &mut S, reg_addr: u16, reg_data: u16) {
         sm.smi_write(PHY_REG_CTL, 0x0003); // set address
         sm.smi_write(PHY_REG_ADDAR, reg_addr);
         sm.smi_write(PHY_REG_CTL, 0x4003); // set data
diff --git a/embassy-stm32/src/eth/mod.rs b/embassy-stm32/src/eth/mod.rs
index 4989e17c7..1687cb319 100644
--- a/embassy-stm32/src/eth/mod.rs
+++ b/embassy-stm32/src/eth/mod.rs
@@ -81,9 +81,7 @@ impl<'d, T: Instance, P: PHY> embassy_net_driver::Driver for Ethernet<'d, T, P>
     }
 
     fn link_state(&mut self, cx: &mut Context) -> LinkState {
-        // TODO: wake cx.waker on link state change
-        cx.waker().wake_by_ref();
-        if P::poll_link(self) {
+        if self.phy.poll_link(&mut self.station_management, cx) {
             LinkState::Up
         } else {
             LinkState::Down
@@ -148,11 +146,11 @@ pub unsafe trait StationManagement {
 /// The methods cannot move S
 pub unsafe trait PHY {
     /// Reset PHY and wait for it to come out of reset.
-    fn phy_reset<S: StationManagement>(sm: &mut S);
+    fn phy_reset<S: StationManagement>(&mut self, sm: &mut S);
     /// PHY initialisation.
-    fn phy_init<S: StationManagement>(sm: &mut S);
+    fn phy_init<S: StationManagement>(&mut self, sm: &mut S);
     /// Poll link to see if it is up and FD with 100Mbps
-    fn poll_link<S: StationManagement>(sm: &mut S) -> bool;
+    fn poll_link<S: StationManagement>(&mut self, sm: &mut S, cx: &mut Context) -> bool;
 }
 
 pub(crate) mod sealed {
diff --git a/embassy-stm32/src/eth/v1/mod.rs b/embassy-stm32/src/eth/v1/mod.rs
index b53c2d0fa..2a6ea35ff 100644
--- a/embassy-stm32/src/eth/v1/mod.rs
+++ b/embassy-stm32/src/eth/v1/mod.rs
@@ -3,6 +3,7 @@
 mod rx_desc;
 mod tx_desc;
 
+use core::marker::PhantomData;
 use core::sync::atomic::{fence, Ordering};
 
 use embassy_hal_common::{into_ref, PeripheralRef};
@@ -48,9 +49,8 @@ pub struct Ethernet<'d, T: Instance, P: PHY> {
     pub(crate) rx: RDesRing<'d>,
 
     pins: [PeripheralRef<'d, AnyPin>; 9],
-    _phy: P,
-    clock_range: Cr,
-    phy_addr: u8,
+    pub(crate) phy: P,
+    pub(crate) station_management: EthernetStationManagement<T>,
     pub(crate) mac_addr: [u8; 6],
 }
 
@@ -224,9 +224,12 @@ impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> {
         let mut this = Self {
             _peri: peri,
             pins,
-            _phy: phy,
-            clock_range,
-            phy_addr,
+            phy: phy,
+            station_management: EthernetStationManagement {
+                peri: PhantomData,
+                clock_range: clock_range,
+                phy_addr: phy_addr,
+            },
             mac_addr,
             tx: TDesRing::new(&mut queue.tx_desc, &mut queue.tx_buf),
             rx: RDesRing::new(&mut queue.rx_desc, &mut queue.rx_buf),
@@ -256,8 +259,8 @@ impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> {
             w.set_tie(true);
         });
 
-        P::phy_reset(&mut this);
-        P::phy_init(&mut this);
+        this.phy.phy_reset(&mut this.station_management);
+        this.phy.phy_init(&mut this.station_management);
 
         interrupt::ETH.unpend();
         unsafe { interrupt::ETH.enable() };
@@ -266,7 +269,13 @@ impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> {
     }
 }
 
-unsafe impl<'d, T: Instance, P: PHY> StationManagement for Ethernet<'d, T, P> {
+pub struct EthernetStationManagement<T: Instance> {
+    peri: PhantomData<T>,
+    clock_range: Cr,
+    phy_addr: u8,
+}
+
+unsafe impl<T: Instance> StationManagement for EthernetStationManagement<T> {
     fn smi_read(&mut self, reg: u8) -> u16 {
         let mac = ETH.ethernet_mac();
 
diff --git a/embassy-stm32/src/eth/v2/mod.rs b/embassy-stm32/src/eth/v2/mod.rs
index 600e1d3bc..bb681c42b 100644
--- a/embassy-stm32/src/eth/v2/mod.rs
+++ b/embassy-stm32/src/eth/v2/mod.rs
@@ -1,5 +1,6 @@
 mod descriptors;
 
+use core::marker::PhantomData;
 use core::sync::atomic::{fence, Ordering};
 
 use embassy_hal_common::{into_ref, PeripheralRef};
@@ -40,9 +41,8 @@ pub struct Ethernet<'d, T: Instance, P: PHY> {
     pub(crate) tx: TDesRing<'d>,
     pub(crate) rx: RDesRing<'d>,
     pins: [PeripheralRef<'d, AnyPin>; 9],
-    _phy: P,
-    clock_range: u8,
-    phy_addr: u8,
+    pub(crate) phy: P,
+    pub(crate) station_management: EthernetStationManagement<T>,
     pub(crate) mac_addr: [u8; 6],
 }
 
@@ -201,9 +201,12 @@ impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> {
             tx: TDesRing::new(&mut queue.tx_desc, &mut queue.tx_buf),
             rx: RDesRing::new(&mut queue.rx_desc, &mut queue.rx_buf),
             pins,
-            _phy: phy,
-            clock_range,
-            phy_addr,
+            phy: phy,
+            station_management: EthernetStationManagement {
+                peri: PhantomData,
+                clock_range: clock_range,
+                phy_addr: phy_addr,
+            },
             mac_addr,
         };
 
@@ -229,8 +232,8 @@ impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> {
             w.set_tie(true);
         });
 
-        P::phy_reset(&mut this);
-        P::phy_init(&mut this);
+        this.phy.phy_reset(&mut this.station_management);
+        this.phy.phy_init(&mut this.station_management);
 
         interrupt::ETH.unpend();
         unsafe { interrupt::ETH.enable() };
@@ -239,7 +242,13 @@ impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> {
     }
 }
 
-unsafe impl<'d, T: Instance, P: PHY> StationManagement for Ethernet<'d, T, P> {
+pub struct EthernetStationManagement<T: Instance> {
+    peri: PhantomData<T>,
+    clock_range: u8,
+    phy_addr: u8,
+}
+
+unsafe impl<T: Instance> StationManagement for EthernetStationManagement<T> {
     fn smi_read(&mut self, reg: u8) -> u16 {
         let mac = ETH.ethernet_mac();
 
diff --git a/embassy-stm32/src/flash/asynch.rs b/embassy-stm32/src/flash/asynch.rs
index 70a5ded62..f175349cd 100644
--- a/embassy-stm32/src/flash/asynch.rs
+++ b/embassy-stm32/src/flash/asynch.rs
@@ -1,6 +1,6 @@
 use core::marker::PhantomData;
+use core::sync::atomic::{fence, Ordering};
 
-use atomic_polyfill::{fence, Ordering};
 use embassy_hal_common::drop::OnDrop;
 use embassy_hal_common::into_ref;
 use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
diff --git a/embassy-stm32/src/flash/common.rs b/embassy-stm32/src/flash/common.rs
index c6cdc574b..2a374733d 100644
--- a/embassy-stm32/src/flash/common.rs
+++ b/embassy-stm32/src/flash/common.rs
@@ -1,6 +1,6 @@
 use core::marker::PhantomData;
+use core::sync::atomic::{fence, Ordering};
 
-use atomic_polyfill::{fence, Ordering};
 use embassy_hal_common::drop::OnDrop;
 use embassy_hal_common::{into_ref, PeripheralRef};
 use stm32_metapac::FLASH_BASE;
diff --git a/embassy-stm32/src/flash/f0.rs b/embassy-stm32/src/flash/f0.rs
index 02bd4cc1f..ec8343e7c 100644
--- a/embassy-stm32/src/flash/f0.rs
+++ b/embassy-stm32/src/flash/f0.rs
@@ -1,7 +1,6 @@
 use core::convert::TryInto;
 use core::ptr::write_volatile;
-
-use atomic_polyfill::{fence, Ordering};
+use core::sync::atomic::{fence, Ordering};
 
 use super::{FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE};
 use crate::flash::Error;
diff --git a/embassy-stm32/src/flash/f3.rs b/embassy-stm32/src/flash/f3.rs
index b093a7837..40335d643 100644
--- a/embassy-stm32/src/flash/f3.rs
+++ b/embassy-stm32/src/flash/f3.rs
@@ -1,7 +1,6 @@
 use core::convert::TryInto;
 use core::ptr::write_volatile;
-
-use atomic_polyfill::{fence, Ordering};
+use core::sync::atomic::{fence, Ordering};
 
 use super::{FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE};
 use crate::flash::Error;
diff --git a/embassy-stm32/src/flash/f4.rs b/embassy-stm32/src/flash/f4.rs
index 242d99278..4cb39e033 100644
--- a/embassy-stm32/src/flash/f4.rs
+++ b/embassy-stm32/src/flash/f4.rs
@@ -1,8 +1,7 @@
 use core::convert::TryInto;
 use core::ptr::write_volatile;
-use core::sync::atomic::{fence, Ordering};
+use core::sync::atomic::{fence, AtomicBool, Ordering};
 
-use atomic_polyfill::AtomicBool;
 use embassy_sync::waitqueue::AtomicWaker;
 use pac::flash::regs::Sr;
 use pac::FLASH_SIZE;
diff --git a/embassy-stm32/src/flash/h7.rs b/embassy-stm32/src/flash/h7.rs
index 9baf059ee..bf17b5b18 100644
--- a/embassy-stm32/src/flash/h7.rs
+++ b/embassy-stm32/src/flash/h7.rs
@@ -1,7 +1,6 @@
 use core::convert::TryInto;
 use core::ptr::write_volatile;
-
-use atomic_polyfill::{fence, Ordering};
+use core::sync::atomic::{fence, Ordering};
 
 use super::{FlashRegion, FlashSector, BANK1_REGION, FLASH_REGIONS, WRITE_SIZE};
 use crate::flash::Error;
diff --git a/embassy-stm32/src/flash/l.rs b/embassy-stm32/src/flash/l.rs
index deefd05ed..243c8b51d 100644
--- a/embassy-stm32/src/flash/l.rs
+++ b/embassy-stm32/src/flash/l.rs
@@ -1,6 +1,5 @@
 use core::ptr::write_volatile;
-
-use atomic_polyfill::{fence, Ordering};
+use core::sync::atomic::{fence, Ordering};
 
 use super::{FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE};
 use crate::flash::Error;
diff --git a/embassy-stm32/src/fmc.rs b/embassy-stm32/src/fmc.rs
index a4f3b9686..60d7a00ee 100644
--- a/embassy-stm32/src/fmc.rs
+++ b/embassy-stm32/src/fmc.rs
@@ -86,6 +86,24 @@ macro_rules! fmc_sdram_constructor {
 }
 
 impl<'d, T: Instance> Fmc<'d, T> {
+    fmc_sdram_constructor!(sdram_a12bits_d16bits_4banks_bank1: (
+        bank: stm32_fmc::SdramTargetBank::Bank1,
+        addr: [
+            (a0: A0Pin), (a1: A1Pin), (a2: A2Pin), (a3: A3Pin), (a4: A4Pin), (a5: A5Pin), (a6: A6Pin), (a7: A7Pin), (a8: A8Pin), (a9: A9Pin), (a10: A10Pin), (a11: A11Pin)
+        ],
+        ba: [(ba0: BA0Pin), (ba1: BA1Pin)],
+        d: [
+            (d0: D0Pin), (d1: D1Pin), (d2: D2Pin), (d3: D3Pin), (d4: D4Pin), (d5: D5Pin), (d6: D6Pin), (d7: D7Pin),
+            (d8: D8Pin), (d9: D9Pin), (d10: D10Pin), (d11: D11Pin), (d12: D12Pin), (d13: D13Pin), (d14: D14Pin), (d15: D15Pin)
+        ],
+        nbl: [
+            (nbl0: NBL0Pin), (nbl1: NBL1Pin)
+        ],
+        ctrl: [
+            (sdcke: SDCKE0Pin), (sdclk: SDCLKPin), (sdncas: SDNCASPin), (sdne: SDNE0Pin), (sdnras: SDNRASPin), (sdnwe: SDNWEPin)
+        ]
+    ));
+
     fmc_sdram_constructor!(sdram_a12bits_d32bits_4banks_bank1: (
         bank: stm32_fmc::SdramTargetBank::Bank1,
         addr: [
diff --git a/embassy-stm32/src/i2c/v2.rs b/embassy-stm32/src/i2c/v2.rs
index 1f036d55c..208d1527d 100644
--- a/embassy-stm32/src/i2c/v2.rs
+++ b/embassy-stm32/src/i2c/v2.rs
@@ -382,13 +382,18 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
         // I2C start
         //
         // ST SAD+W
-        Self::master_write(
+        if let Err(err) = Self::master_write(
             address,
             write.len().min(255),
             Stop::Software,
             last_chunk_idx != 0,
             &check_timeout,
-        )?;
+        ) {
+            if send_stop {
+                self.master_stop();
+            }
+            return Err(err);
+        }
 
         for (number, chunk) in write.chunks(255).enumerate() {
             if number != 0 {
@@ -399,18 +404,22 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
                 // Wait until we are allowed to send data
                 // (START has been ACKed or last byte when
                 // through)
-                self.wait_txe(&check_timeout)?;
+                if let Err(err) = self.wait_txe(&check_timeout) {
+                    if send_stop {
+                        self.master_stop();
+                    }
+                    return Err(err);
+                }
 
                 T::regs().txdr().write(|w| w.set_txdata(*byte));
             }
         }
         // Wait until the write finishes
-        self.wait_tc(&check_timeout)?;
-
+        let result = self.wait_tc(&check_timeout);
         if send_stop {
             self.master_stop();
         }
-        Ok(())
+        result
     }
 
     async fn write_dma_internal(
@@ -707,13 +716,16 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
         let first_length = write[0].len();
         let last_slice_index = write.len() - 1;
 
-        Self::master_write(
+        if let Err(err) = Self::master_write(
             address,
             first_length.min(255),
             Stop::Software,
             (first_length > 255) || (last_slice_index != 0),
             &check_timeout,
-        )?;
+        ) {
+            self.master_stop();
+            return Err(err);
+        }
 
         for (idx, slice) in write.iter().enumerate() {
             let slice_len = slice.len();
@@ -726,27 +738,36 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
             let last_chunk_idx = total_chunks.saturating_sub(1);
 
             if idx != 0 {
-                Self::master_continue(
+                if let Err(err) = Self::master_continue(
                     slice_len.min(255),
                     (idx != last_slice_index) || (slice_len > 255),
                     &check_timeout,
-                )?;
+                ) {
+                    self.master_stop();
+                    return Err(err);
+                }
             }
 
             for (number, chunk) in slice.chunks(255).enumerate() {
                 if number != 0 {
-                    Self::master_continue(
+                    if let Err(err) = Self::master_continue(
                         chunk.len(),
                         (number != last_chunk_idx) || (idx != last_slice_index),
                         &check_timeout,
-                    )?;
+                    ) {
+                        self.master_stop();
+                        return Err(err);
+                    }
                 }
 
                 for byte in chunk {
                     // Wait until we are allowed to send data
                     // (START has been ACKed or last byte when
                     // through)
-                    self.wait_txe(&check_timeout)?;
+                    if let Err(err) = self.wait_txe(&check_timeout) {
+                        self.master_stop();
+                        return Err(err);
+                    }
 
                     // Put byte on the wire
                     //self.i2c.txdr.write(|w| w.txdata().bits(*byte));
@@ -755,10 +776,9 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
             }
         }
         // Wait until the write finishes
-        self.wait_tc(&check_timeout)?;
+        let result = self.wait_tc(&check_timeout);
         self.master_stop();
-
-        Ok(())
+        result
     }
 
     pub fn blocking_write_vectored(&mut self, address: u8, write: &[&[u8]]) -> Result<(), Error> {
diff --git a/embassy-stm32/src/ipcc.rs b/embassy-stm32/src/ipcc.rs
index 37f840c73..a24cba9f0 100644
--- a/embassy-stm32/src/ipcc.rs
+++ b/embassy-stm32/src/ipcc.rs
@@ -1,8 +1,7 @@
 use core::future::poll_fn;
+use core::sync::atomic::{compiler_fence, Ordering};
 use core::task::Poll;
 
-use atomic_polyfill::{compiler_fence, Ordering};
-
 use self::sealed::Instance;
 use crate::interrupt;
 use crate::interrupt::typelevel::Interrupt;
diff --git a/embassy-stm32/src/qspi/mod.rs b/embassy-stm32/src/qspi/mod.rs
index e9db934bf..31b676088 100644
--- a/embassy-stm32/src/qspi/mod.rs
+++ b/embassy-stm32/src/qspi/mod.rs
@@ -1,332 +1,332 @@
-#![macro_use]
-
-pub mod enums;
-
-use embassy_hal_common::{into_ref, PeripheralRef};
-use enums::*;
-
-use crate::dma::Transfer;
-use crate::gpio::sealed::AFType;
-use crate::gpio::AnyPin;
-use crate::pac::quadspi::Quadspi as Regs;
-use crate::rcc::RccPeripheral;
-use crate::{peripherals, Peripheral};
-
-pub struct TransferConfig {
-    /// Instraction width (IMODE)
-    pub iwidth: QspiWidth,
-    /// Address width (ADMODE)
-    pub awidth: QspiWidth,
-    /// Data width (DMODE)
-    pub dwidth: QspiWidth,
-    /// Instruction Id
-    pub instruction: u8,
-    /// Flash memory address
-    pub address: Option<u32>,
-    /// Number of dummy cycles (DCYC)
-    pub dummy: DummyCycles,
-    /// Length of data
-    pub data_len: Option<usize>,
-}
-
-impl Default for TransferConfig {
-    fn default() -> Self {
-        Self {
-            iwidth: QspiWidth::NONE,
-            awidth: QspiWidth::NONE,
-            dwidth: QspiWidth::NONE,
-            instruction: 0,
-            address: None,
-            dummy: DummyCycles::_0,
-            data_len: None,
-        }
-    }
-}
-
-pub struct Config {
-    /// Flash memory size representend as 2^[0-32], as reasonable minimum 1KiB(9) was chosen.
-    /// If you need other value the whose predefined use `Other` variant.
-    pub memory_size: MemorySize,
-    /// Address size (8/16/24/32-bit)
-    pub address_size: AddressSize,
-    /// Scalar factor for generating CLK [0-255]
-    pub prescaler: u8,
-    /// Number of bytes to trigger FIFO threshold flag.
-    pub fifo_threshold: FIFOThresholdLevel,
-    /// Minimum number of cycles that chip select must be high between issued commands
-    pub cs_high_time: ChipSelectHightTime,
-}
-
-impl Default for Config {
-    fn default() -> Self {
-        Self {
-            memory_size: MemorySize::Other(0),
-            address_size: AddressSize::_24bit,
-            prescaler: 128,
-            fifo_threshold: FIFOThresholdLevel::_17Bytes,
-            cs_high_time: ChipSelectHightTime::_5Cycle,
-        }
-    }
-}
-
-#[allow(dead_code)]
-pub struct Qspi<'d, T: Instance, Dma> {
-    _peri: PeripheralRef<'d, T>,
-    sck: Option<PeripheralRef<'d, AnyPin>>,
-    d0: Option<PeripheralRef<'d, AnyPin>>,
-    d1: Option<PeripheralRef<'d, AnyPin>>,
-    d2: Option<PeripheralRef<'d, AnyPin>>,
-    d3: Option<PeripheralRef<'d, AnyPin>>,
-    nss: Option<PeripheralRef<'d, AnyPin>>,
-    dma: PeripheralRef<'d, Dma>,
-    config: Config,
-}
-
-impl<'d, T: Instance, Dma> Qspi<'d, T, Dma> {
-    pub fn new(
-        peri: impl Peripheral<P = T> + 'd,
-        d0: impl Peripheral<P = impl D0Pin<T>> + 'd,
-        d1: impl Peripheral<P = impl D1Pin<T>> + 'd,
-        d2: impl Peripheral<P = impl D2Pin<T>> + 'd,
-        d3: impl Peripheral<P = impl D3Pin<T>> + 'd,
-        sck: impl Peripheral<P = impl SckPin<T>> + 'd,
-        nss: impl Peripheral<P = impl NSSPin<T>> + 'd,
-        dma: impl Peripheral<P = Dma> + 'd,
-        config: Config,
-    ) -> Self {
-        into_ref!(peri, d0, d1, d2, d3, sck, nss);
-
-        sck.set_as_af(sck.af_num(), AFType::OutputPushPull);
-        sck.set_speed(crate::gpio::Speed::VeryHigh);
-        nss.set_as_af(nss.af_num(), AFType::OutputPushPull);
-        nss.set_speed(crate::gpio::Speed::VeryHigh);
-        d0.set_as_af(d0.af_num(), AFType::OutputPushPull);
-        d0.set_speed(crate::gpio::Speed::VeryHigh);
-        d1.set_as_af(d1.af_num(), AFType::OutputPushPull);
-        d1.set_speed(crate::gpio::Speed::VeryHigh);
-        d2.set_as_af(d2.af_num(), AFType::OutputPushPull);
-        d2.set_speed(crate::gpio::Speed::VeryHigh);
-        d3.set_as_af(d3.af_num(), AFType::OutputPushPull);
-        d3.set_speed(crate::gpio::Speed::VeryHigh);
-
-        Self::new_inner(
-            peri,
-            Some(d0.map_into()),
-            Some(d1.map_into()),
-            Some(d2.map_into()),
-            Some(d3.map_into()),
-            Some(sck.map_into()),
-            Some(nss.map_into()),
-            dma,
-            config,
-        )
-    }
-
-    fn new_inner(
-        peri: impl Peripheral<P = T> + 'd,
-        d0: Option<PeripheralRef<'d, AnyPin>>,
-        d1: Option<PeripheralRef<'d, AnyPin>>,
-        d2: Option<PeripheralRef<'d, AnyPin>>,
-        d3: Option<PeripheralRef<'d, AnyPin>>,
-        sck: Option<PeripheralRef<'d, AnyPin>>,
-        nss: Option<PeripheralRef<'d, AnyPin>>,
-        dma: impl Peripheral<P = Dma> + 'd,
-        config: Config,
-    ) -> Self {
-        into_ref!(peri, dma);
-
-        T::enable();
-        T::REGS.cr().write(|w| w.set_fthres(config.fifo_threshold.into()));
-
-        while T::REGS.sr().read().busy() {}
-
-        T::REGS.cr().write(|w| {
-            w.set_prescaler(config.prescaler);
-            w.set_en(true);
-        });
-        T::REGS.dcr().write(|w| {
-            w.set_fsize(config.memory_size.into());
-            w.set_csht(config.cs_high_time.into());
-            w.set_ckmode(false);
-        });
-
-        Self {
-            _peri: peri,
-            sck,
-            d0,
-            d1,
-            d2,
-            d3,
-            nss,
-            dma,
-            config,
-        }
-    }
-
-    pub fn command(&mut self, transaction: TransferConfig) {
-        T::REGS.cr().modify(|v| v.set_dmaen(false));
-        self.setup_transaction(QspiMode::IndirectWrite, &transaction);
-
-        while !T::REGS.sr().read().tcf() {}
-        T::REGS.fcr().modify(|v| v.set_ctcf(true));
-    }
-
-    pub fn blocking_read(&mut self, buf: &mut [u8], transaction: TransferConfig) {
-        T::REGS.cr().modify(|v| v.set_dmaen(false));
-        self.setup_transaction(QspiMode::IndirectWrite, &transaction);
-
-        if let Some(len) = transaction.data_len {
-            let current_ar = T::REGS.ar().read().address();
-            T::REGS.ccr().modify(|v| {
-                v.set_fmode(QspiMode::IndirectRead.into());
-            });
-            T::REGS.ar().write(|v| {
-                v.set_address(current_ar);
-            });
-
-            for idx in 0..len {
-                while !T::REGS.sr().read().tcf() && !T::REGS.sr().read().ftf() {}
-                buf[idx] = unsafe { (T::REGS.dr().as_ptr() as *mut u8).read_volatile() };
-            }
-        }
-
-        while !T::REGS.sr().read().tcf() {}
-        T::REGS.fcr().modify(|v| v.set_ctcf(true));
-    }
-
-    pub fn blocking_write(&mut self, buf: &[u8], transaction: TransferConfig) {
-        T::REGS.cr().modify(|v| v.set_dmaen(false));
-        self.setup_transaction(QspiMode::IndirectWrite, &transaction);
-
-        if let Some(len) = transaction.data_len {
-            T::REGS.ccr().modify(|v| {
-                v.set_fmode(QspiMode::IndirectWrite.into());
-            });
-
-            for idx in 0..len {
-                while !T::REGS.sr().read().ftf() {}
-                unsafe { (T::REGS.dr().as_ptr() as *mut u8).write_volatile(buf[idx]) };
-            }
-        }
-
-        while !T::REGS.sr().read().tcf() {}
-        T::REGS.fcr().modify(|v| v.set_ctcf(true));
-    }
-
-    pub fn blocking_read_dma(&mut self, buf: &mut [u8], transaction: TransferConfig)
-    where
-        Dma: QuadDma<T>,
-    {
-        self.setup_transaction(QspiMode::IndirectWrite, &transaction);
-
-        T::REGS.ccr().modify(|v| {
-            v.set_fmode(QspiMode::IndirectRead.into());
-        });
-        let current_ar = T::REGS.ar().read().address();
-        T::REGS.ar().write(|v| {
-            v.set_address(current_ar);
-        });
-
-        let request = self.dma.request();
-        let transfer = unsafe {
-            Transfer::new_read(
-                &mut self.dma,
-                request,
-                T::REGS.dr().as_ptr() as *mut u8,
-                buf,
-                Default::default(),
-            )
-        };
-
-        T::REGS.cr().modify(|v| v.set_dmaen(true));
-
-        transfer.blocking_wait();
-    }
-
-    pub fn blocking_write_dma(&mut self, buf: &[u8], transaction: TransferConfig)
-    where
-        Dma: QuadDma<T>,
-    {
-        self.setup_transaction(QspiMode::IndirectWrite, &transaction);
-
-        T::REGS.ccr().modify(|v| {
-            v.set_fmode(QspiMode::IndirectWrite.into());
-        });
-
-        let request = self.dma.request();
-        let transfer = unsafe {
-            Transfer::new_write(
-                &mut self.dma,
-                request,
-                buf,
-                T::REGS.dr().as_ptr() as *mut u8,
-                Default::default(),
-            )
-        };
-
-        T::REGS.cr().modify(|v| v.set_dmaen(true));
-
-        transfer.blocking_wait();
-    }
-
-    fn setup_transaction(&mut self, fmode: QspiMode, transaction: &TransferConfig) {
-        T::REGS.fcr().modify(|v| {
-            v.set_csmf(true);
-            v.set_ctcf(true);
-            v.set_ctef(true);
-            v.set_ctof(true);
-        });
-
-        while T::REGS.sr().read().busy() {}
-
-        if let Some(len) = transaction.data_len {
-            T::REGS.dlr().write(|v| v.set_dl(len as u32 - 1));
-        }
-
-        T::REGS.ccr().write(|v| {
-            v.set_fmode(fmode.into());
-            v.set_imode(transaction.iwidth.into());
-            v.set_instruction(transaction.instruction);
-            v.set_admode(transaction.awidth.into());
-            v.set_adsize(self.config.address_size.into());
-            v.set_dmode(transaction.dwidth.into());
-            v.set_abmode(QspiWidth::NONE.into());
-            v.set_dcyc(transaction.dummy.into());
-        });
-
-        if let Some(addr) = transaction.address {
-            T::REGS.ar().write(|v| {
-                v.set_address(addr);
-            });
-        }
-    }
-}
-
-pub(crate) mod sealed {
-    use super::*;
-
-    pub trait Instance {
-        const REGS: Regs;
-    }
-}
-
-pub trait Instance: Peripheral<P = Self> + sealed::Instance + RccPeripheral {}
-
-pin_trait!(SckPin, Instance);
-pin_trait!(D0Pin, Instance);
-pin_trait!(D1Pin, Instance);
-pin_trait!(D2Pin, Instance);
-pin_trait!(D3Pin, Instance);
-pin_trait!(NSSPin, Instance);
-
-dma_trait!(QuadDma, Instance);
-
-foreach_peripheral!(
-    (quadspi, $inst:ident) => {
-        impl sealed::Instance for peripherals::$inst {
-            const REGS: Regs = crate::pac::$inst;
-        }
-
-        impl Instance for peripherals::$inst {}
-    };
-);
+#![macro_use]
+
+pub mod enums;
+
+use embassy_hal_common::{into_ref, PeripheralRef};
+use enums::*;
+
+use crate::dma::Transfer;
+use crate::gpio::sealed::AFType;
+use crate::gpio::AnyPin;
+use crate::pac::quadspi::Quadspi as Regs;
+use crate::rcc::RccPeripheral;
+use crate::{peripherals, Peripheral};
+
+pub struct TransferConfig {
+    /// Instraction width (IMODE)
+    pub iwidth: QspiWidth,
+    /// Address width (ADMODE)
+    pub awidth: QspiWidth,
+    /// Data width (DMODE)
+    pub dwidth: QspiWidth,
+    /// Instruction Id
+    pub instruction: u8,
+    /// Flash memory address
+    pub address: Option<u32>,
+    /// Number of dummy cycles (DCYC)
+    pub dummy: DummyCycles,
+    /// Length of data
+    pub data_len: Option<usize>,
+}
+
+impl Default for TransferConfig {
+    fn default() -> Self {
+        Self {
+            iwidth: QspiWidth::NONE,
+            awidth: QspiWidth::NONE,
+            dwidth: QspiWidth::NONE,
+            instruction: 0,
+            address: None,
+            dummy: DummyCycles::_0,
+            data_len: None,
+        }
+    }
+}
+
+pub struct Config {
+    /// Flash memory size representend as 2^[0-32], as reasonable minimum 1KiB(9) was chosen.
+    /// If you need other value the whose predefined use `Other` variant.
+    pub memory_size: MemorySize,
+    /// Address size (8/16/24/32-bit)
+    pub address_size: AddressSize,
+    /// Scalar factor for generating CLK [0-255]
+    pub prescaler: u8,
+    /// Number of bytes to trigger FIFO threshold flag.
+    pub fifo_threshold: FIFOThresholdLevel,
+    /// Minimum number of cycles that chip select must be high between issued commands
+    pub cs_high_time: ChipSelectHightTime,
+}
+
+impl Default for Config {
+    fn default() -> Self {
+        Self {
+            memory_size: MemorySize::Other(0),
+            address_size: AddressSize::_24bit,
+            prescaler: 128,
+            fifo_threshold: FIFOThresholdLevel::_17Bytes,
+            cs_high_time: ChipSelectHightTime::_5Cycle,
+        }
+    }
+}
+
+#[allow(dead_code)]
+pub struct Qspi<'d, T: Instance, Dma> {
+    _peri: PeripheralRef<'d, T>,
+    sck: Option<PeripheralRef<'d, AnyPin>>,
+    d0: Option<PeripheralRef<'d, AnyPin>>,
+    d1: Option<PeripheralRef<'d, AnyPin>>,
+    d2: Option<PeripheralRef<'d, AnyPin>>,
+    d3: Option<PeripheralRef<'d, AnyPin>>,
+    nss: Option<PeripheralRef<'d, AnyPin>>,
+    dma: PeripheralRef<'d, Dma>,
+    config: Config,
+}
+
+impl<'d, T: Instance, Dma> Qspi<'d, T, Dma> {
+    pub fn new(
+        peri: impl Peripheral<P = T> + 'd,
+        d0: impl Peripheral<P = impl D0Pin<T>> + 'd,
+        d1: impl Peripheral<P = impl D1Pin<T>> + 'd,
+        d2: impl Peripheral<P = impl D2Pin<T>> + 'd,
+        d3: impl Peripheral<P = impl D3Pin<T>> + 'd,
+        sck: impl Peripheral<P = impl SckPin<T>> + 'd,
+        nss: impl Peripheral<P = impl NSSPin<T>> + 'd,
+        dma: impl Peripheral<P = Dma> + 'd,
+        config: Config,
+    ) -> Self {
+        into_ref!(peri, d0, d1, d2, d3, sck, nss);
+
+        sck.set_as_af(sck.af_num(), AFType::OutputPushPull);
+        sck.set_speed(crate::gpio::Speed::VeryHigh);
+        nss.set_as_af(nss.af_num(), AFType::OutputPushPull);
+        nss.set_speed(crate::gpio::Speed::VeryHigh);
+        d0.set_as_af(d0.af_num(), AFType::OutputPushPull);
+        d0.set_speed(crate::gpio::Speed::VeryHigh);
+        d1.set_as_af(d1.af_num(), AFType::OutputPushPull);
+        d1.set_speed(crate::gpio::Speed::VeryHigh);
+        d2.set_as_af(d2.af_num(), AFType::OutputPushPull);
+        d2.set_speed(crate::gpio::Speed::VeryHigh);
+        d3.set_as_af(d3.af_num(), AFType::OutputPushPull);
+        d3.set_speed(crate::gpio::Speed::VeryHigh);
+
+        Self::new_inner(
+            peri,
+            Some(d0.map_into()),
+            Some(d1.map_into()),
+            Some(d2.map_into()),
+            Some(d3.map_into()),
+            Some(sck.map_into()),
+            Some(nss.map_into()),
+            dma,
+            config,
+        )
+    }
+
+    fn new_inner(
+        peri: impl Peripheral<P = T> + 'd,
+        d0: Option<PeripheralRef<'d, AnyPin>>,
+        d1: Option<PeripheralRef<'d, AnyPin>>,
+        d2: Option<PeripheralRef<'d, AnyPin>>,
+        d3: Option<PeripheralRef<'d, AnyPin>>,
+        sck: Option<PeripheralRef<'d, AnyPin>>,
+        nss: Option<PeripheralRef<'d, AnyPin>>,
+        dma: impl Peripheral<P = Dma> + 'd,
+        config: Config,
+    ) -> Self {
+        into_ref!(peri, dma);
+
+        T::enable();
+        T::REGS.cr().write(|w| w.set_fthres(config.fifo_threshold.into()));
+
+        while T::REGS.sr().read().busy() {}
+
+        T::REGS.cr().write(|w| {
+            w.set_prescaler(config.prescaler);
+            w.set_en(true);
+        });
+        T::REGS.dcr().write(|w| {
+            w.set_fsize(config.memory_size.into());
+            w.set_csht(config.cs_high_time.into());
+            w.set_ckmode(false);
+        });
+
+        Self {
+            _peri: peri,
+            sck,
+            d0,
+            d1,
+            d2,
+            d3,
+            nss,
+            dma,
+            config,
+        }
+    }
+
+    pub fn command(&mut self, transaction: TransferConfig) {
+        T::REGS.cr().modify(|v| v.set_dmaen(false));
+        self.setup_transaction(QspiMode::IndirectWrite, &transaction);
+
+        while !T::REGS.sr().read().tcf() {}
+        T::REGS.fcr().modify(|v| v.set_ctcf(true));
+    }
+
+    pub fn blocking_read(&mut self, buf: &mut [u8], transaction: TransferConfig) {
+        T::REGS.cr().modify(|v| v.set_dmaen(false));
+        self.setup_transaction(QspiMode::IndirectWrite, &transaction);
+
+        if let Some(len) = transaction.data_len {
+            let current_ar = T::REGS.ar().read().address();
+            T::REGS.ccr().modify(|v| {
+                v.set_fmode(QspiMode::IndirectRead.into());
+            });
+            T::REGS.ar().write(|v| {
+                v.set_address(current_ar);
+            });
+
+            for idx in 0..len {
+                while !T::REGS.sr().read().tcf() && !T::REGS.sr().read().ftf() {}
+                buf[idx] = unsafe { (T::REGS.dr().as_ptr() as *mut u8).read_volatile() };
+            }
+        }
+
+        while !T::REGS.sr().read().tcf() {}
+        T::REGS.fcr().modify(|v| v.set_ctcf(true));
+    }
+
+    pub fn blocking_write(&mut self, buf: &[u8], transaction: TransferConfig) {
+        T::REGS.cr().modify(|v| v.set_dmaen(false));
+        self.setup_transaction(QspiMode::IndirectWrite, &transaction);
+
+        if let Some(len) = transaction.data_len {
+            T::REGS.ccr().modify(|v| {
+                v.set_fmode(QspiMode::IndirectWrite.into());
+            });
+
+            for idx in 0..len {
+                while !T::REGS.sr().read().ftf() {}
+                unsafe { (T::REGS.dr().as_ptr() as *mut u8).write_volatile(buf[idx]) };
+            }
+        }
+
+        while !T::REGS.sr().read().tcf() {}
+        T::REGS.fcr().modify(|v| v.set_ctcf(true));
+    }
+
+    pub fn blocking_read_dma(&mut self, buf: &mut [u8], transaction: TransferConfig)
+    where
+        Dma: QuadDma<T>,
+    {
+        self.setup_transaction(QspiMode::IndirectWrite, &transaction);
+
+        T::REGS.ccr().modify(|v| {
+            v.set_fmode(QspiMode::IndirectRead.into());
+        });
+        let current_ar = T::REGS.ar().read().address();
+        T::REGS.ar().write(|v| {
+            v.set_address(current_ar);
+        });
+
+        let request = self.dma.request();
+        let transfer = unsafe {
+            Transfer::new_read(
+                &mut self.dma,
+                request,
+                T::REGS.dr().as_ptr() as *mut u8,
+                buf,
+                Default::default(),
+            )
+        };
+
+        T::REGS.cr().modify(|v| v.set_dmaen(true));
+
+        transfer.blocking_wait();
+    }
+
+    pub fn blocking_write_dma(&mut self, buf: &[u8], transaction: TransferConfig)
+    where
+        Dma: QuadDma<T>,
+    {
+        self.setup_transaction(QspiMode::IndirectWrite, &transaction);
+
+        T::REGS.ccr().modify(|v| {
+            v.set_fmode(QspiMode::IndirectWrite.into());
+        });
+
+        let request = self.dma.request();
+        let transfer = unsafe {
+            Transfer::new_write(
+                &mut self.dma,
+                request,
+                buf,
+                T::REGS.dr().as_ptr() as *mut u8,
+                Default::default(),
+            )
+        };
+
+        T::REGS.cr().modify(|v| v.set_dmaen(true));
+
+        transfer.blocking_wait();
+    }
+
+    fn setup_transaction(&mut self, fmode: QspiMode, transaction: &TransferConfig) {
+        T::REGS.fcr().modify(|v| {
+            v.set_csmf(true);
+            v.set_ctcf(true);
+            v.set_ctef(true);
+            v.set_ctof(true);
+        });
+
+        while T::REGS.sr().read().busy() {}
+
+        if let Some(len) = transaction.data_len {
+            T::REGS.dlr().write(|v| v.set_dl(len as u32 - 1));
+        }
+
+        T::REGS.ccr().write(|v| {
+            v.set_fmode(fmode.into());
+            v.set_imode(transaction.iwidth.into());
+            v.set_instruction(transaction.instruction);
+            v.set_admode(transaction.awidth.into());
+            v.set_adsize(self.config.address_size.into());
+            v.set_dmode(transaction.dwidth.into());
+            v.set_abmode(QspiWidth::NONE.into());
+            v.set_dcyc(transaction.dummy.into());
+        });
+
+        if let Some(addr) = transaction.address {
+            T::REGS.ar().write(|v| {
+                v.set_address(addr);
+            });
+        }
+    }
+}
+
+pub(crate) mod sealed {
+    use super::*;
+
+    pub trait Instance {
+        const REGS: Regs;
+    }
+}
+
+pub trait Instance: Peripheral<P = Self> + sealed::Instance + RccPeripheral {}
+
+pin_trait!(SckPin, Instance);
+pin_trait!(D0Pin, Instance);
+pin_trait!(D1Pin, Instance);
+pin_trait!(D2Pin, Instance);
+pin_trait!(D3Pin, Instance);
+pin_trait!(NSSPin, Instance);
+
+dma_trait!(QuadDma, Instance);
+
+foreach_peripheral!(
+    (quadspi, $inst:ident) => {
+        impl sealed::Instance for peripherals::$inst {
+            const REGS: Regs = crate::pac::$inst;
+        }
+
+        impl Instance for peripherals::$inst {}
+    };
+);
diff --git a/embassy-stm32/src/rcc/h5.rs b/embassy-stm32/src/rcc/h5.rs
index 4025a4e05..7e2f75ab7 100644
--- a/embassy-stm32/src/rcc/h5.rs
+++ b/embassy-stm32/src/rcc/h5.rs
@@ -473,11 +473,11 @@ fn init_pll(num: usize, config: Option<Pll>, input: &PllInput) -> PllOutput {
             w.set_divm(0);
         });
 
-        return PllOutput{
+        return PllOutput {
             p: None,
             q: None,
             r: None,
-        }
+        };
     };
 
     assert!(1 <= config.prediv && config.prediv <= 63);
diff --git a/embassy-stm32/src/rcc/h7.rs b/embassy-stm32/src/rcc/h7.rs
index f3a98c794..7e5cd0d1a 100644
--- a/embassy-stm32/src/rcc/h7.rs
+++ b/embassy-stm32/src/rcc/h7.rs
@@ -740,7 +740,7 @@ mod pll {
             }
         };
 
-        let vco_ck = output + pll_x_p;
+        let vco_ck = output * pll_x_p;
 
         assert!(pll_x_p < 128);
         assert!(vco_ck >= VCO_MIN);
diff --git a/embassy-stm32/src/rcc/l4.rs b/embassy-stm32/src/rcc/l4.rs
index 20cb8c91c..8a9b4adbf 100644
--- a/embassy-stm32/src/rcc/l4.rs
+++ b/embassy-stm32/src/rcc/l4.rs
@@ -1,6 +1,7 @@
 use core::marker::PhantomData;
 
 use embassy_hal_common::into_ref;
+use stm32_metapac::rcc::regs::Cfgr;
 use stm32_metapac::rcc::vals::{Lsedrv, Mcopre, Mcosel};
 
 use crate::gpio::sealed::AFType;
@@ -439,6 +440,26 @@ impl<'d, T: McoInstance> Mco<'d, T> {
 }
 
 pub(crate) unsafe fn init(config: Config) {
+    // Switch to MSI to prevent problems with PLL configuration.
+    if !RCC.cr().read().msion() {
+        // Turn on MSI and configure it to 4MHz.
+        RCC.cr().modify(|w| {
+            w.set_msirgsel(true); // MSI Range is provided by MSIRANGE[3:0].
+            w.set_msirange(MSIRange::default().into());
+            w.set_msipllen(false);
+            w.set_msion(true)
+        });
+
+        // Wait until MSI is running
+        while !RCC.cr().read().msirdy() {}
+    }
+    if RCC.cfgr().read().sws() != Sw::MSI {
+        // Set MSI as a clock source, reset prescalers.
+        RCC.cfgr().write_value(Cfgr::default());
+        // Wait for clock switch status bits to change.
+        while RCC.cfgr().read().sws() != Sw::MSI {}
+    }
+
     match config.rtc_mux {
         RtcClockSource::LSE32 => {
             // 1. Unlock the backup domain
@@ -660,6 +681,8 @@ pub(crate) unsafe fn init(config: Config) {
         }
     };
 
+    RCC.apb1enr1().modify(|w| w.set_pwren(true));
+
     set_freqs(Clocks {
         sys: Hertz(sys_clk),
         ahb1: Hertz(ahb_freq),
diff --git a/embassy-stm32/src/rcc/mod.rs b/embassy-stm32/src/rcc/mod.rs
index d6816d6a8..886fc0b93 100644
--- a/embassy-stm32/src/rcc/mod.rs
+++ b/embassy-stm32/src/rcc/mod.rs
@@ -83,12 +83,12 @@ static mut CLOCK_FREQS: MaybeUninit<Clocks> = MaybeUninit::uninit();
 /// Safety: Sets a mutable global.
 pub(crate) unsafe fn set_freqs(freqs: Clocks) {
     debug!("rcc: {:?}", freqs);
-    CLOCK_FREQS.as_mut_ptr().write(freqs);
+    CLOCK_FREQS = MaybeUninit::new(freqs);
 }
 
 /// Safety: Reads a mutable global.
 pub(crate) unsafe fn get_freqs() -> &'static Clocks {
-    &*CLOCK_FREQS.as_ptr()
+    CLOCK_FREQS.assume_init_ref()
 }
 
 #[cfg(feature = "unstable-pac")]
diff --git a/embassy-stm32/src/rcc/wl.rs b/embassy-stm32/src/rcc/wl.rs
index 7072db984..6b69bb1cb 100644
--- a/embassy-stm32/src/rcc/wl.rs
+++ b/embassy-stm32/src/rcc/wl.rs
@@ -1,4 +1,5 @@
-use crate::pac::{FLASH, RCC};
+use crate::pac::pwr::vals::Dbp;
+use crate::pac::{FLASH, PWR, RCC};
 use crate::rcc::{set_freqs, Clocks};
 use crate::time::Hertz;
 
@@ -184,6 +185,8 @@ pub struct Config {
     pub apb1_pre: APBPrescaler,
     pub apb2_pre: APBPrescaler,
     pub enable_lsi: bool,
+    pub enable_rtc_apb: bool,
+    pub rtc_mux: RtcClockSource,
 }
 
 impl Default for Config {
@@ -196,10 +199,25 @@ impl Default for Config {
             apb1_pre: APBPrescaler::NotDivided,
             apb2_pre: APBPrescaler::NotDivided,
             enable_lsi: false,
+            enable_rtc_apb: false,
+            rtc_mux: RtcClockSource::LSI32,
         }
     }
 }
 
+pub enum RtcClockSource {
+    LSE32,
+    LSI32,
+}
+
+#[repr(u8)]
+pub enum Lsedrv {
+    Low = 0,
+    MediumLow = 1,
+    MediumHigh = 2,
+    High = 3,
+}
+
 pub(crate) unsafe fn init(config: Config) {
     let (sys_clk, sw, vos) = match config.mux {
         ClockSrc::HSI16 => (HSI_FREQ.0, 0x01, VoltageScale::Range2),
@@ -266,6 +284,32 @@ pub(crate) unsafe fn init(config: Config) {
 
     while FLASH.acr().read().latency() != ws {}
 
+    match config.rtc_mux {
+        RtcClockSource::LSE32 => {
+            // 1. Unlock the backup domain
+            PWR.cr1().modify(|w| w.set_dbp(Dbp::ENABLED));
+
+            // 2. Setup the LSE
+            RCC.bdcr().modify(|w| {
+                // Enable LSE
+                w.set_lseon(true);
+                // Max drive strength
+                // TODO: should probably be settable
+                w.set_lsedrv(Lsedrv::High as u8); //---// PAM - should not be commented
+            });
+
+            // Wait until LSE is running
+            while !RCC.bdcr().read().lserdy() {}
+        }
+        RtcClockSource::LSI32 => {
+            // Turn on the internal 32 kHz LSI oscillator
+            RCC.csr().modify(|w| w.set_lsion(true));
+
+            // Wait until LSI is running
+            while !RCC.csr().read().lsirdy() {}
+        }
+    }
+
     match config.mux {
         ClockSrc::HSI16 => {
             // Enable HSI16
@@ -287,11 +331,26 @@ pub(crate) unsafe fn init(config: Config) {
                 w.set_msirgsel(true);
                 w.set_msirange(range.into());
                 w.set_msion(true);
+
+                if let RtcClockSource::LSE32 = config.rtc_mux {
+                    // If LSE is enabled, enable calibration of MSI
+                    w.set_msipllen(true);
+                } else {
+                    w.set_msipllen(false);
+                }
             });
             while !RCC.cr().read().msirdy() {}
         }
     }
 
+    if config.enable_rtc_apb {
+        // enable peripheral clock for communication
+        crate::pac::RCC.apb1enr1().modify(|w| w.set_rtcapben(true));
+
+        // read to allow the pwr clock to enable
+        crate::pac::PWR.cr1().read();
+    }
+
     RCC.extcfgr().modify(|w| {
         if config.shd_ahb_pre == AHBPrescaler::NotDivided {
             w.set_shdhpre(0);
diff --git a/embassy-stm32/src/rtc/v3.rs b/embassy-stm32/src/rtc/v3.rs
index 7e5c64d90..8ef0ec51d 100644
--- a/embassy-stm32/src/rtc/v3.rs
+++ b/embassy-stm32/src/rtc/v3.rs
@@ -172,6 +172,7 @@ impl sealed::Instance for crate::peripherals::RTC {
     const BACKUP_REGISTER_COUNT: usize = 32;
 
     fn read_backup_register(_rtc: &Rtc, register: usize) -> Option<u32> {
+        #[allow(clippy::if_same_then_else)]
         if register < Self::BACKUP_REGISTER_COUNT {
             //Some(rtc.bkpr()[register].read().bits())
             None // RTC3 backup registers come from the TAMP peripe=heral, not RTC. Not() even in the L412 PAC
diff --git a/embassy-stm32/src/spi/mod.rs b/embassy-stm32/src/spi/mod.rs
index c3224073d..d5f63f84e 100644
--- a/embassy-stm32/src/spi/mod.rs
+++ b/embassy-stm32/src/spi/mod.rs
@@ -852,25 +852,19 @@ mod eh1 {
         type Error = Error;
     }
 
-    impl<'d, T: Instance, Tx, Rx> embedded_hal_1::spi::SpiBusFlush for Spi<'d, T, Tx, Rx> {
+    impl<'d, T: Instance, W: Word, Tx, Rx> embedded_hal_1::spi::SpiBus<W> for Spi<'d, T, Tx, Rx> {
         fn flush(&mut self) -> Result<(), Self::Error> {
             Ok(())
         }
-    }
 
-    impl<'d, T: Instance, W: Word, Tx, Rx> embedded_hal_1::spi::SpiBusRead<W> for Spi<'d, T, Tx, Rx> {
         fn read(&mut self, words: &mut [W]) -> Result<(), Self::Error> {
             self.blocking_read(words)
         }
-    }
 
-    impl<'d, T: Instance, W: Word, Tx, Rx> embedded_hal_1::spi::SpiBusWrite<W> for Spi<'d, T, Tx, Rx> {
         fn write(&mut self, words: &[W]) -> Result<(), Self::Error> {
             self.blocking_write(words)
         }
-    }
 
-    impl<'d, T: Instance, W: Word, Tx, Rx> embedded_hal_1::spi::SpiBus<W> for Spi<'d, T, Tx, Rx> {
         fn transfer(&mut self, read: &mut [W], write: &[W]) -> Result<(), Self::Error> {
             self.blocking_transfer(read, write)
         }
@@ -895,32 +889,25 @@ mod eh1 {
 #[cfg(all(feature = "unstable-traits", feature = "nightly"))]
 mod eha {
     use super::*;
-    impl<'d, T: Instance, Tx, Rx> embedded_hal_async::spi::SpiBusFlush for Spi<'d, T, Tx, Rx> {
+
+    impl<'d, T: Instance, Tx: TxDma<T>, Rx: RxDma<T>, W: Word> embedded_hal_async::spi::SpiBus<W> for Spi<'d, T, Tx, Rx> {
         async fn flush(&mut self) -> Result<(), Self::Error> {
             Ok(())
         }
-    }
 
-    impl<'d, T: Instance, Tx: TxDma<T>, Rx, W: Word> embedded_hal_async::spi::SpiBusWrite<W> for Spi<'d, T, Tx, Rx> {
         async fn write(&mut self, words: &[W]) -> Result<(), Self::Error> {
             self.write(words).await
         }
-    }
 
-    impl<'d, T: Instance, Tx: TxDma<T>, Rx: RxDma<T>, W: Word> embedded_hal_async::spi::SpiBusRead<W>
-        for Spi<'d, T, Tx, Rx>
-    {
         async fn read(&mut self, words: &mut [W]) -> Result<(), Self::Error> {
             self.read(words).await
         }
-    }
 
-    impl<'d, T: Instance, Tx: TxDma<T>, Rx: RxDma<T>, W: Word> embedded_hal_async::spi::SpiBus<W> for Spi<'d, T, Tx, Rx> {
-        async fn transfer<'a>(&'a mut self, read: &'a mut [W], write: &'a [W]) -> Result<(), Self::Error> {
+        async fn transfer(&mut self, read: &mut [W], write: &[W]) -> Result<(), Self::Error> {
             self.transfer(read, write).await
         }
 
-        async fn transfer_in_place<'a>(&'a mut self, words: &'a mut [W]) -> Result<(), Self::Error> {
+        async fn transfer_in_place(&mut self, words: &mut [W]) -> Result<(), Self::Error> {
             self.transfer_in_place(words).await
         }
     }
diff --git a/embassy-stm32/src/usart/mod.rs b/embassy-stm32/src/usart/mod.rs
index c97efbf0a..ea8e525ea 100644
--- a/embassy-stm32/src/usart/mod.rs
+++ b/embassy-stm32/src/usart/mod.rs
@@ -116,6 +116,10 @@ pub struct Config {
     /// but will effectively disable noise detection.
     #[cfg(not(usart_v1))]
     pub assume_noise_free: bool,
+
+    /// Set this to true to swap the RX and TX pins.
+    #[cfg(any(usart_v3, usart_v4))]
+    pub swap_rx_tx: bool,
 }
 
 impl Default for Config {
@@ -129,6 +133,8 @@ impl Default for Config {
             detect_previous_overrun: false,
             #[cfg(not(usart_v1))]
             assume_noise_free: false,
+            #[cfg(any(usart_v3, usart_v4))]
+            swap_rx_tx: false,
         }
     }
 }
@@ -688,8 +694,22 @@ impl<'d, T: BasicInstance, TxDma, RxDma> Uart<'d, T, TxDma, RxDma> {
 
         let r = T::regs();
 
-        rx.set_as_af(rx.af_num(), AFType::Input);
-        tx.set_as_af(tx.af_num(), AFType::OutputPushPull);
+        // Some chips do not have swap_rx_tx bit
+        cfg_if::cfg_if! {
+            if #[cfg(any(usart_v3, usart_v4))] {
+                if config.swap_rx_tx {
+                    let (rx, tx) = (tx, rx);
+                    rx.set_as_af(rx.af_num(), AFType::Input);
+                    tx.set_as_af(tx.af_num(), AFType::OutputPushPull);
+                } else {
+                    rx.set_as_af(rx.af_num(), AFType::Input);
+                    tx.set_as_af(tx.af_num(), AFType::OutputPushPull);
+                }
+            } else {
+                rx.set_as_af(rx.af_num(), AFType::Input);
+                tx.set_as_af(tx.af_num(), AFType::OutputPushPull);
+            }
+        }
 
         configure(r, &config, T::frequency(), T::KIND, true, true);
 
@@ -847,6 +867,9 @@ fn configure(r: Regs, config: &Config, pclk_freq: Hertz, kind: Kind, enable_rx:
             StopBits::STOP1P5 => vals::Stop::STOP1P5,
             StopBits::STOP2 => vals::Stop::STOP2,
         });
+
+        #[cfg(any(usart_v3, usart_v4))]
+        w.set_swap(config.swap_rx_tx);
     });
     r.cr1().write(|w| {
         // enable uart
diff --git a/embassy-stm32/src/usb_otg/usb.rs b/embassy-stm32/src/usb_otg/usb.rs
index 6783db28d..fd0e22adf 100644
--- a/embassy-stm32/src/usb_otg/usb.rs
+++ b/embassy-stm32/src/usb_otg/usb.rs
@@ -1,8 +1,8 @@
 use core::cell::UnsafeCell;
 use core::marker::PhantomData;
+use core::sync::atomic::{AtomicBool, AtomicU16, Ordering};
 use core::task::Poll;
 
-use atomic_polyfill::{AtomicBool, AtomicU16, Ordering};
 use embassy_hal_common::{into_ref, Peripheral};
 use embassy_sync::waitqueue::AtomicWaker;
 use embassy_usb_driver::{
@@ -648,7 +648,7 @@ impl<'d, T: Instance> Bus<'d, T> {
 
         let r = T::regs();
         let core_id = r.cid().read().0;
-        info!("Core id {:08x}", core_id);
+        trace!("Core id {:08x}", core_id);
 
         // Wait for AHB ready.
         while !r.grstctl().read().ahbidl() {}
@@ -1154,14 +1154,22 @@ impl<'d, T: Instance> embassy_usb_driver::EndpointOut for Endpoint<'d, T, Out> {
         trace!("read start len={}", buf.len());
 
         poll_fn(|cx| {
+            let r = T::regs();
             let index = self.info.addr.index();
             let state = T::state();
 
             state.ep_out_wakers[index].register(cx.waker());
 
+            let doepctl = r.doepctl(index).read();
+            trace!("read ep={:?}: doepctl {:08x}", self.info.addr, doepctl.0,);
+            if !doepctl.usbaep() {
+                trace!("read ep={:?} error disabled", self.info.addr);
+                return Poll::Ready(Err(EndpointError::Disabled));
+            }
+
             let len = state.ep_out_size[index].load(Ordering::Relaxed);
             if len != EP_OUT_BUFFER_EMPTY {
-                trace!("read done len={}", len);
+                trace!("read ep={:?} done len={}", self.info.addr, len);
 
                 if len as usize > buf.len() {
                     return Poll::Ready(Err(EndpointError::BufferOverflow));
@@ -1214,7 +1222,12 @@ impl<'d, T: Instance> embassy_usb_driver::EndpointIn for Endpoint<'d, T, In> {
 
             let diepctl = r.diepctl(index).read();
             let dtxfsts = r.dtxfsts(index).read();
-            info!("diepctl {:08x} ftxfsts {:08x}", diepctl.0, dtxfsts.0);
+            trace!(
+                "write ep={:?}: diepctl {:08x} ftxfsts {:08x}",
+                self.info.addr,
+                diepctl.0,
+                dtxfsts.0
+            );
             if !diepctl.usbaep() {
                 trace!("write ep={:?} wait for prev: error disabled", self.info.addr);
                 Poll::Ready(Err(EndpointError::Disabled))
diff --git a/embassy-sync/src/channel.rs b/embassy-sync/src/channel.rs
index 77352874d..f421af392 100644
--- a/embassy-sync/src/channel.rs
+++ b/embassy-sync/src/channel.rs
@@ -335,6 +335,12 @@ impl<T, const N: usize> ChannelState<T, N> {
         }
     }
 
+    fn poll_ready_to_receive(&mut self, cx: &mut Context<'_>) -> bool {
+        self.receiver_waker.register(cx.waker());
+
+        !self.queue.is_empty()
+    }
+
     fn try_send(&mut self, message: T) -> Result<(), TrySendError<T>> {
         self.try_send_with_context(message, None)
     }
@@ -353,6 +359,12 @@ impl<T, const N: usize> ChannelState<T, N> {
             }
         }
     }
+
+    fn poll_ready_to_send(&mut self, cx: &mut Context<'_>) -> bool {
+        self.senders_waker.register(cx.waker());
+
+        !self.queue.is_full()
+    }
 }
 
 /// A bounded channel for communicating between asynchronous tasks
@@ -401,6 +413,16 @@ where
         self.lock(|c| c.try_send_with_context(m, cx))
     }
 
+    /// Allows a poll_fn to poll until the channel is ready to receive
+    pub fn poll_ready_to_receive(&self, cx: &mut Context<'_>) -> bool {
+        self.lock(|c| c.poll_ready_to_receive(cx))
+    }
+
+    /// Allows a poll_fn to poll until the channel is ready to send
+    pub fn poll_ready_to_send(&self, cx: &mut Context<'_>) -> bool {
+        self.lock(|c| c.poll_ready_to_send(cx))
+    }
+
     /// Get a sender for this channel.
     pub fn sender(&self) -> Sender<'_, M, T, N> {
         Sender { channel: self }
diff --git a/embassy-time/CHANGELOG.md b/embassy-time/CHANGELOG.md
index f4a7860e6..26640d930 100644
--- a/embassy-time/CHANGELOG.md
+++ b/embassy-time/CHANGELOG.md
@@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## 0.1.2 - 2023-07-05
+
+- Update `embedded-hal-async` to `0.2.0-alpha.2`.
+- Update `embedded-hal v1` to `1.0.0-alpha.11`. (Note: v0.2 support is kept unchanged).
+
 ## 0.1.1 - 2023-04-13
 
 - Update `embedded-hal-async` to `0.2.0-alpha.1` (uses `async fn` in traits).
diff --git a/embassy-time/Cargo.toml b/embassy-time/Cargo.toml
index 857da5467..0afb1103d 100644
--- a/embassy-time/Cargo.toml
+++ b/embassy-time/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "embassy-time"
-version = "0.1.1"
+version = "0.1.2"
 edition = "2021"
 description = "Instant and Duration for embedded no-std systems, with async timer support"
 repository = "https://github.com/embassy-rs/embassy"
@@ -23,7 +23,7 @@ target = "x86_64-unknown-linux-gnu"
 features = ["nightly", "defmt", "unstable-traits", "std"]
 
 [features]
-std = ["tick-hz-1_000_000"]
+std = ["tick-hz-1_000_000", "critical-section/std"]
 wasm = ["dep:wasm-bindgen", "dep:js-sys", "dep:wasm-timer", "tick-hz-1_000_000"]
 
 # Enable nightly-only features
@@ -152,8 +152,8 @@ defmt = { version = "0.3", optional = true }
 log = { version = "0.4.14", optional = true }
 
 embedded-hal-02 = { package = "embedded-hal", version = "0.2.6" }
-embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.10", optional = true}
-embedded-hal-async = { version = "=0.2.0-alpha.1", optional = true}
+embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.11", optional = true}
+embedded-hal-async = { version = "=0.2.0-alpha.2", optional = true}
 
 futures-util = { version = "0.3.17", default-features = false }
 atomic-polyfill = "1.0.1"
diff --git a/embassy-time/src/driver.rs b/embassy-time/src/driver.rs
index d6436369b..5fe7becaf 100644
--- a/embassy-time/src/driver.rs
+++ b/embassy-time/src/driver.rs
@@ -36,7 +36,7 @@
 //! ```
 //! use embassy_time::driver::{Driver, AlarmHandle};
 //!
-//! struct MyDriver{}; // not public!
+//! struct MyDriver{} // not public!
 //! embassy_time::time_driver_impl!(static DRIVER: MyDriver = MyDriver{});
 //!
 //! impl Driver for MyDriver {
diff --git a/embassy-time/src/driver_std.rs b/embassy-time/src/driver_std.rs
index 9f8c57b5c..32db47a37 100644
--- a/embassy-time/src/driver_std.rs
+++ b/embassy-time/src/driver_std.rs
@@ -1,10 +1,10 @@
+use core::sync::atomic::{AtomicU8, Ordering};
 use std::cell::{RefCell, UnsafeCell};
 use std::mem::MaybeUninit;
 use std::sync::{Condvar, Mutex, Once};
 use std::time::{Duration as StdDuration, Instant as StdInstant};
 use std::{mem, ptr, thread};
 
-use atomic_polyfill::{AtomicU8, Ordering};
 use critical_section::Mutex as CsMutex;
 
 use crate::driver::{AlarmHandle, Driver};
diff --git a/embassy-time/src/driver_wasm.rs b/embassy-time/src/driver_wasm.rs
index 63d049897..0f672dc75 100644
--- a/embassy-time/src/driver_wasm.rs
+++ b/embassy-time/src/driver_wasm.rs
@@ -1,9 +1,9 @@
+use core::sync::atomic::{AtomicU8, Ordering};
 use std::cell::UnsafeCell;
 use std::mem::MaybeUninit;
 use std::ptr;
 use std::sync::{Mutex, Once};
 
-use atomic_polyfill::{AtomicU8, Ordering};
 use wasm_bindgen::prelude::*;
 use wasm_timer::Instant as StdInstant;
 
diff --git a/embassy-time/src/queue_generic.rs b/embassy-time/src/queue_generic.rs
index 4795eb2f3..77947ab29 100644
--- a/embassy-time/src/queue_generic.rs
+++ b/embassy-time/src/queue_generic.rs
@@ -16,7 +16,7 @@ const QUEUE_SIZE: usize = 16;
 #[cfg(feature = "generic-queue-32")]
 const QUEUE_SIZE: usize = 32;
 #[cfg(feature = "generic-queue-64")]
-const QUEUE_SIZE: usize = 32;
+const QUEUE_SIZE: usize = 64;
 #[cfg(feature = "generic-queue-128")]
 const QUEUE_SIZE: usize = 128;
 #[cfg(not(any(
diff --git a/examples/boot/application/nrf/Cargo.toml b/examples/boot/application/nrf/Cargo.toml
index b98f73f39..2a0cf7818 100644
--- a/examples/boot/application/nrf/Cargo.toml
+++ b/examples/boot/application/nrf/Cargo.toml
@@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0"
 [dependencies]
 embassy-sync = { version = "0.2.0", path = "../../../../embassy-sync" }
 embassy-executor = { version = "0.2.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "nightly", "integrated-timers", "arch-cortex-m", "executor-thread"] }
-embassy-time = { version = "0.1.0", path = "../../../../embassy-time", features = ["nightly"] }
+embassy-time = { version = "0.1.2", path = "../../../../embassy-time", features = ["nightly"] }
 embassy-nrf = { version = "0.1.0", path = "../../../../embassy-nrf", features = ["time-driver-rtc1", "gpiote", "nightly"] }
 embassy-boot = { version = "0.1.0", path = "../../../../embassy-boot/boot", features = ["nightly"] }
 embassy-boot-nrf = { version = "0.1.0", path = "../../../../embassy-boot/nrf", features = ["nightly"] }
diff --git a/examples/boot/application/rp/Cargo.toml b/examples/boot/application/rp/Cargo.toml
index 007b6839c..95b2da954 100644
--- a/examples/boot/application/rp/Cargo.toml
+++ b/examples/boot/application/rp/Cargo.toml
@@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0"
 [dependencies]
 embassy-sync = { version = "0.2.0", path = "../../../../embassy-sync" }
 embassy-executor = { version = "0.2.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "nightly", "integrated-timers", "arch-cortex-m", "executor-thread"] }
-embassy-time = { version = "0.1.0", path = "../../../../embassy-time", features = ["nightly"] }
+embassy-time = { version = "0.1.2", path = "../../../../embassy-time", features = ["nightly"] }
 embassy-rp = { version = "0.1.0", path = "../../../../embassy-rp", features = ["time-driver", "unstable-traits", "nightly"] }
 embassy-boot-rp = { version = "0.1.0", path = "../../../../embassy-boot/rp", features = ["nightly"] }
 embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" }
diff --git a/examples/boot/application/stm32f3/Cargo.toml b/examples/boot/application/stm32f3/Cargo.toml
index 5b3faf8f8..3b0fc4d9d 100644
--- a/examples/boot/application/stm32f3/Cargo.toml
+++ b/examples/boot/application/stm32f3/Cargo.toml
@@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0"
 [dependencies]
 embassy-sync = { version = "0.2.0", path = "../../../../embassy-sync", features = ["defmt"] }
 embassy-executor = { version = "0.2.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "nightly", "integrated-timers"] }
-embassy-time = { version = "0.1.0", path = "../../../../embassy-time", features = ["nightly", "tick-hz-32_768"] }
+embassy-time = { version = "0.1.2", path = "../../../../embassy-time", features = ["nightly", "tick-hz-32_768"] }
 embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["unstable-traits", "nightly", "stm32f303re", "time-driver-any", "exti"]  }
 embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32", features = ["nightly"] }
 embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" }
diff --git a/examples/boot/application/stm32f7/Cargo.toml b/examples/boot/application/stm32f7/Cargo.toml
index b6a6f9cd8..323b4ab2c 100644
--- a/examples/boot/application/stm32f7/Cargo.toml
+++ b/examples/boot/application/stm32f7/Cargo.toml
@@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0"
 [dependencies]
 embassy-sync = { version = "0.2.0", path = "../../../../embassy-sync", features = ["defmt"] }
 embassy-executor = { version = "0.2.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "nightly", "integrated-timers"] }
-embassy-time = { version = "0.1.0", path = "../../../../embassy-time", features = ["nightly", "tick-hz-32_768"] }
+embassy-time = { version = "0.1.2", path = "../../../../embassy-time", features = ["nightly", "tick-hz-32_768"] }
 embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["unstable-traits", "nightly", "stm32f767zi", "time-driver-any", "exti"]  }
 embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32", features = ["nightly"] }
 embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" }
diff --git a/examples/boot/application/stm32h7/Cargo.toml b/examples/boot/application/stm32h7/Cargo.toml
index 0a7e19b1d..b2abdc891 100644
--- a/examples/boot/application/stm32h7/Cargo.toml
+++ b/examples/boot/application/stm32h7/Cargo.toml
@@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0"
 [dependencies]
 embassy-sync = { version = "0.2.0", path = "../../../../embassy-sync" }
 embassy-executor = { version = "0.2.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "nightly", "integrated-timers"] }
-embassy-time = { version = "0.1.0", path = "../../../../embassy-time", features = ["nightly", "tick-hz-32_768"] }
+embassy-time = { version = "0.1.2", path = "../../../../embassy-time", features = ["nightly", "tick-hz-32_768"] }
 embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["unstable-traits", "nightly", "stm32h743zi", "time-driver-any", "exti"]  }
 embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32", features = ["nightly"] }
 embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" }
diff --git a/examples/boot/application/stm32l0/Cargo.toml b/examples/boot/application/stm32l0/Cargo.toml
index 998df4dc0..0b7e72d5e 100644
--- a/examples/boot/application/stm32l0/Cargo.toml
+++ b/examples/boot/application/stm32l0/Cargo.toml
@@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0"
 [dependencies]
 embassy-sync = { version = "0.2.0", path = "../../../../embassy-sync", features = ["defmt"] }
 embassy-executor = { version = "0.2.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "nightly", "integrated-timers"] }
-embassy-time = { version = "0.1.0", path = "../../../../embassy-time", features = ["nightly", "tick-hz-32_768"] }
+embassy-time = { version = "0.1.2", path = "../../../../embassy-time", features = ["nightly", "tick-hz-32_768"] }
 embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["unstable-traits", "nightly", "stm32l072cz", "time-driver-any", "exti", "memory-x"]  }
 embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32", features = ["nightly"] }
 embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" }
diff --git a/examples/boot/application/stm32l1/Cargo.toml b/examples/boot/application/stm32l1/Cargo.toml
index 10b58c172..5f3f365c1 100644
--- a/examples/boot/application/stm32l1/Cargo.toml
+++ b/examples/boot/application/stm32l1/Cargo.toml
@@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0"
 [dependencies]
 embassy-sync = { version = "0.2.0", path = "../../../../embassy-sync", features = ["defmt"] }
 embassy-executor = { version = "0.2.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "nightly", "integrated-timers"] }
-embassy-time = { version = "0.1.0", path = "../../../../embassy-time", features = ["nightly", "tick-hz-32_768"] }
+embassy-time = { version = "0.1.2", path = "../../../../embassy-time", features = ["nightly", "tick-hz-32_768"] }
 embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["unstable-traits", "nightly", "stm32l151cb-a", "time-driver-any", "exti"]  }
 embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32", features = ["nightly"] }
 embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" }
diff --git a/examples/boot/application/stm32l4/Cargo.toml b/examples/boot/application/stm32l4/Cargo.toml
index 713a6527e..44eb5aba8 100644
--- a/examples/boot/application/stm32l4/Cargo.toml
+++ b/examples/boot/application/stm32l4/Cargo.toml
@@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0"
 [dependencies]
 embassy-sync = { version = "0.2.0", path = "../../../../embassy-sync", features = ["defmt"] }
 embassy-executor = { version = "0.2.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "nightly", "integrated-timers"] }
-embassy-time = { version = "0.1.0", path = "../../../../embassy-time", features = ["nightly", "tick-hz-32_768"] }
+embassy-time = { version = "0.1.2", path = "../../../../embassy-time", features = ["nightly", "tick-hz-32_768"] }
 embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["unstable-traits", "nightly", "stm32l475vg", "time-driver-any", "exti"]  }
 embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32", features = ["nightly"] }
 embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" }
diff --git a/examples/boot/application/stm32wl/Cargo.toml b/examples/boot/application/stm32wl/Cargo.toml
index 4c8bbd73f..fdad55060 100644
--- a/examples/boot/application/stm32wl/Cargo.toml
+++ b/examples/boot/application/stm32wl/Cargo.toml
@@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0"
 [dependencies]
 embassy-sync = { version = "0.2.0", path = "../../../../embassy-sync", features = ["defmt"] }
 embassy-executor = { version = "0.2.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "nightly", "integrated-timers"] }
-embassy-time = { version = "0.1.0", path = "../../../../embassy-time", features = ["nightly", "tick-hz-32_768"] }
+embassy-time = { version = "0.1.2", path = "../../../../embassy-time", features = ["nightly", "tick-hz-32_768"] }
 embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["unstable-traits", "nightly", "stm32wl55jc-cm4", "time-driver-any", "exti"]  }
 embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32", features = ["nightly"] }
 embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" }
diff --git a/examples/nrf-rtos-trace/Cargo.toml b/examples/nrf-rtos-trace/Cargo.toml
index a3acc56b8..30b67b7b2 100644
--- a/examples/nrf-rtos-trace/Cargo.toml
+++ b/examples/nrf-rtos-trace/Cargo.toml
@@ -18,7 +18,7 @@ log = [
 [dependencies]
 embassy-sync = { version = "0.2.0", path = "../../embassy-sync" }
 embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "rtos-trace", "rtos-trace-interrupt", "integrated-timers"] }
-embassy-time = { version = "0.1.0", path = "../../embassy-time" }
+embassy-time = { version = "0.1.2", path = "../../embassy-time" }
 embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac"] }
 
 cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] }
diff --git a/examples/nrf52840-rtic/Cargo.toml b/examples/nrf52840-rtic/Cargo.toml
index 0f9048b0f..ded3b7db8 100644
--- a/examples/nrf52840-rtic/Cargo.toml
+++ b/examples/nrf52840-rtic/Cargo.toml
@@ -9,7 +9,7 @@ rtic = { version = "2", features = ["thumbv7-backend"] }
 
 embassy-futures = { version = "0.1.0", path = "../../embassy-futures" }
 embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] }
-embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["nightly", "unstable-traits", "defmt", "defmt-timestamp-uptime", "generic-queue"] }
+embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["nightly", "unstable-traits", "defmt", "defmt-timestamp-uptime", "generic-queue"] }
 embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["nightly", "unstable-traits", "defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] }
 
 defmt = "0.3"
diff --git a/examples/nrf52840/Cargo.toml b/examples/nrf52840/Cargo.toml
index 8c4175966..9b41ec5ab 100644
--- a/examples/nrf52840/Cargo.toml
+++ b/examples/nrf52840/Cargo.toml
@@ -29,7 +29,7 @@ nightly = [
 embassy-futures = { version = "0.1.0", path = "../../embassy-futures" }
 embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] }
 embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] }
-embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] }
+embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] }
 embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] }
 embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet"], optional = true }
 embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt", "msos-descriptor",], optional = true }
@@ -43,6 +43,7 @@ embassy-net-esp-hosted = { version = "0.1.0", path = "../../embassy-net-esp-host
 defmt = "0.3"
 defmt-rtt = "0.4"
 
+fixed = "1.10.0"
 static_cell = "1.1"
 cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] }
 cortex-m-rt = "0.7.0"
@@ -52,4 +53,9 @@ rand = { version = "0.8.4", default-features = false }
 embedded-storage = "0.3.0"
 usbd-hid = "0.6.0"
 serde = { version = "1.0.136", default-features = false }
-embedded-hal-async = { version = "0.2.0-alpha.1", optional = true }
+embedded-hal-async = { version = "0.2.0-alpha.2", optional = true }
+num-integer = { version = "0.1.45", default-features = false }
+microfft = "0.5.0"
+
+[patch.crates-io]
+lora-phy = { git = "https://github.com/embassy-rs/lora-phy", rev = "ad289428fd44b02788e2fa2116445cc8f640a265" }
diff --git a/examples/nrf52840/src/bin/pdm.rs b/examples/nrf52840/src/bin/pdm.rs
index 6b41320ca..444b9137f 100644
--- a/examples/nrf52840/src/bin/pdm.rs
+++ b/examples/nrf52840/src/bin/pdm.rs
@@ -7,6 +7,8 @@ use embassy_executor::Spawner;
 use embassy_nrf::pdm::{self, Config, Pdm};
 use embassy_nrf::{bind_interrupts, peripherals};
 use embassy_time::{Duration, Timer};
+use fixed::types::I7F1;
+use num_integer::Roots;
 use {defmt_rtt as _, panic_probe as _};
 
 bind_interrupts!(struct Irqs {
@@ -20,18 +22,36 @@ async fn main(_p: Spawner) {
     let mut pdm = Pdm::new(p.PDM, Irqs, p.P0_01, p.P0_00, config);
 
     loop {
-        pdm.start().await;
+        for gain in [I7F1::from_num(-20), I7F1::from_num(0), I7F1::from_num(20)] {
+            pdm.set_gain(gain, gain);
+            info!("Gain = {} dB", defmt::Debug2Format(&gain));
+            pdm.start().await;
 
-        // wait some time till the microphon settled
-        Timer::after(Duration::from_millis(1000)).await;
+            // wait some time till the microphon settled
+            Timer::after(Duration::from_millis(1000)).await;
 
-        const SAMPLES: usize = 2048;
-        let mut buf = [0i16; SAMPLES];
-        pdm.sample(&mut buf).await.unwrap();
+            const SAMPLES: usize = 2048;
+            let mut buf = [0i16; SAMPLES];
+            pdm.sample(&mut buf).await.unwrap();
 
-        info!("samples: {:?}", &buf);
+            let mean = (buf.iter().map(|v| i32::from(*v)).sum::<i32>() / buf.len() as i32) as i16;
+            info!(
+                "{} samples, min {=i16}, max {=i16}, mean {=i16}, AC RMS {=i16}",
+                buf.len(),
+                buf.iter().min().unwrap(),
+                buf.iter().max().unwrap(),
+                mean,
+                (buf.iter()
+                    .map(|v| i32::from(*v - mean).pow(2))
+                    .fold(0i32, |a, b| a.saturating_add(b))
+                    / buf.len() as i32)
+                    .sqrt() as i16,
+            );
 
-        pdm.stop().await;
-        Timer::after(Duration::from_millis(100)).await;
+            info!("samples: {:?}", &buf);
+
+            pdm.stop().await;
+            Timer::after(Duration::from_millis(100)).await;
+        }
     }
 }
diff --git a/examples/nrf52840/src/bin/pdm_continuous.rs b/examples/nrf52840/src/bin/pdm_continuous.rs
new file mode 100644
index 000000000..7d8531475
--- /dev/null
+++ b/examples/nrf52840/src/bin/pdm_continuous.rs
@@ -0,0 +1,81 @@
+#![no_std]
+#![no_main]
+#![feature(type_alias_impl_trait)]
+
+use core::cmp::Ordering;
+
+use defmt::info;
+use embassy_executor::Spawner;
+use embassy_nrf::pdm::{self, Config, Frequency, OperationMode, Pdm, Ratio, SamplerState};
+use embassy_nrf::{bind_interrupts, peripherals};
+use fixed::types::I7F1;
+use microfft::real::rfft_1024;
+use num_integer::Roots;
+use {defmt_rtt as _, panic_probe as _};
+
+// Demonstrates both continuous sampling and scanning multiple channels driven by a PPI linked timer
+
+bind_interrupts!(struct Irqs {
+    PDM => pdm::InterruptHandler<peripherals::PDM>;
+});
+
+#[embassy_executor::main]
+async fn main(_p: Spawner) {
+    let mut p = embassy_nrf::init(Default::default());
+    let mut config = Config::default();
+    // Pins are correct for the onboard microphone on the Feather nRF52840 Sense.
+    config.frequency = Frequency::_1280K; // 16 kHz sample rate
+    config.ratio = Ratio::RATIO80;
+    config.operation_mode = OperationMode::Mono;
+    config.gain_left = I7F1::from_bits(5); // 2.5 dB
+    let mut pdm = Pdm::new(p.PDM, Irqs, &mut p.P0_00, &mut p.P0_01, config);
+
+    let mut bufs = [[0; 1024]; 2];
+
+    pdm.run_task_sampler(&mut bufs, move |buf| {
+        // NOTE: It is important that the time spent within this callback
+        // does not exceed the time taken to acquire the 1500 samples we
+        // have in this example, which would be 10us + 2us per
+        // sample * 1500 = 18ms. You need to measure the time taken here
+        // and set the sample buffer size accordingly. Exceeding this
+        // time can lead to the peripheral re-writing the other buffer.
+        let mean = (buf.iter().map(|v| i32::from(*v)).sum::<i32>() / buf.len() as i32) as i16;
+        let (peak_freq_index, peak_mag) = fft_peak_freq(&buf);
+        let peak_freq = peak_freq_index * 16000 / buf.len();
+        info!(
+            "{} samples, min {=i16}, max {=i16}, mean {=i16}, AC RMS {=i16}, peak {} @ {} Hz",
+            buf.len(),
+            buf.iter().min().unwrap(),
+            buf.iter().max().unwrap(),
+            mean,
+            (buf.iter()
+                .map(|v| i32::from(*v - mean).pow(2))
+                .fold(0i32, |a, b| a.saturating_add(b))
+                / buf.len() as i32)
+                .sqrt() as i16,
+            peak_mag,
+            peak_freq,
+        );
+        SamplerState::Sampled
+    })
+    .await
+    .unwrap();
+}
+
+fn fft_peak_freq(input: &[i16; 1024]) -> (usize, u32) {
+    let mut f = [0f32; 1024];
+    for i in 0..input.len() {
+        f[i] = (input[i] as f32) / 32768.0;
+    }
+    // N.B. rfft_1024 does the FFT in-place so result is actually also a reference to f.
+    let result = rfft_1024(&mut f);
+    result[0].im = 0.0;
+
+    result
+        .iter()
+        .map(|c| c.norm_sqr())
+        .enumerate()
+        .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(Ordering::Equal))
+        .map(|(i, v)| (i, ((v * 32768.0) as u32).sqrt()))
+        .unwrap()
+}
diff --git a/examples/nrf52840/src/bin/wifi_esp_hosted.rs b/examples/nrf52840/src/bin/wifi_esp_hosted.rs
index 4eb31b105..112e41bcd 100644
--- a/examples/nrf52840/src/bin/wifi_esp_hosted.rs
+++ b/examples/nrf52840/src/bin/wifi_esp_hosted.rs
@@ -10,11 +10,15 @@ use embassy_nrf::gpio::{AnyPin, Input, Level, Output, OutputDrive, Pin, Pull};
 use embassy_nrf::rng::Rng;
 use embassy_nrf::spim::{self, Spim};
 use embassy_nrf::{bind_interrupts, peripherals};
+use embassy_time::Delay;
 use embedded_hal_async::spi::ExclusiveDevice;
 use embedded_io::asynch::Write;
 use static_cell::make_static;
 use {defmt_rtt as _, embassy_net_esp_hosted as hosted, panic_probe as _};
 
+const WIFI_NETWORK: &str = "EmbassyTest";
+const WIFI_PASSWORD: &str = "V8YxhKt5CdIAJFud";
+
 bind_interrupts!(struct Irqs {
     SPIM3 => spim::InterruptHandler<peripherals::SPI3>;
     RNG => embassy_nrf::rng::InterruptHandler<peripherals::RNG>;
@@ -24,7 +28,7 @@ bind_interrupts!(struct Irqs {
 async fn wifi_task(
     runner: hosted::Runner<
         'static,
-        ExclusiveDevice<Spim<'static, peripherals::SPI3>, Output<'static, peripherals::P0_31>>,
+        ExclusiveDevice<Spim<'static, peripherals::SPI3>, Output<'static, peripherals::P0_31>, Delay>,
         Input<'static, AnyPin>,
         Output<'static, peripherals::P1_05>,
     >,
@@ -55,7 +59,7 @@ async fn main(spawner: Spawner) {
     config.frequency = spim::Frequency::M32;
     config.mode = spim::MODE_2; // !!!
     let spi = spim::Spim::new(p.SPI3, Irqs, sck, miso, mosi, config);
-    let spi = ExclusiveDevice::new(spi, cs);
+    let spi = ExclusiveDevice::new(spi, cs, Delay);
 
     let (device, mut control, runner) = embassy_net_esp_hosted::new(
         make_static!(embassy_net_esp_hosted::State::new()),
@@ -69,7 +73,7 @@ async fn main(spawner: Spawner) {
     unwrap!(spawner.spawn(wifi_task(runner)));
 
     control.init().await;
-    control.join(env!("WIFI_NETWORK"), env!("WIFI_PASSWORD")).await;
+    control.join(WIFI_NETWORK, WIFI_PASSWORD).await;
 
     let config = embassy_net::Config::dhcpv4(Default::default());
     // let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 {
diff --git a/examples/nrf5340/Cargo.toml b/examples/nrf5340/Cargo.toml
index efb66bae6..f1d45f336 100644
--- a/examples/nrf5340/Cargo.toml
+++ b/examples/nrf5340/Cargo.toml
@@ -14,7 +14,7 @@ embassy-executor = { version = "0.2.0", path = "../../embassy-executor", feature
     "defmt",
     "integrated-timers",
 ] }
-embassy-time = { version = "0.1.0", path = "../../embassy-time", features = [
+embassy-time = { version = "0.1.2", path = "../../embassy-time", features = [
     "defmt",
     "defmt-timestamp-uptime",
 ] }
diff --git a/examples/rp/Cargo.toml b/examples/rp/Cargo.toml
index 48f3a26bb..c812cb3ee 100644
--- a/examples/rp/Cargo.toml
+++ b/examples/rp/Cargo.toml
@@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0"
 embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal", features = ["defmt"] }
 embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] }
 embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] }
-embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["nightly", "unstable-traits", "defmt", "defmt-timestamp-uptime"] }
+embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["nightly", "unstable-traits", "defmt", "defmt-timestamp-uptime"] }
 embassy-rp = { version = "0.1.0", path = "../../embassy-rp", features = ["defmt", "unstable-traits", "nightly", "unstable-pac", "time-driver", "critical-section-impl"] }
 embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] }
 embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "udp", "dhcpv4", "medium-ethernet"] }
@@ -40,9 +40,10 @@ display-interface = "0.4.1"
 byte-slice-cast = { version = "1.2.0", default-features = false }
 smart-leds = "0.3.0"
 heapless = "0.7.15"
+usbd-hid = "0.6.1"
 
-embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.10" }
-embedded-hal-async = "0.2.0-alpha.1"
+embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.11" }
+embedded-hal-async = "0.2.0-alpha.2"
 embedded-io = { version = "0.4.0", features = ["async", "defmt"] }
 embedded-storage = { version = "0.3" }
 static_cell = { version = "1.1", features = ["nightly"]}
@@ -53,3 +54,6 @@ rand = { version = "0.8.5", default-features = false }
 
 [profile.release]
 debug = true
+
+[patch.crates-io]
+lora-phy = { git = "https://github.com/embassy-rs/lora-phy", rev = "ad289428fd44b02788e2fa2116445cc8f640a265" }
diff --git a/examples/rp/src/bin/adc.rs b/examples/rp/src/bin/adc.rs
index 7c2ca19f7..81a8b8340 100644
--- a/examples/rp/src/bin/adc.rs
+++ b/examples/rp/src/bin/adc.rs
@@ -1,11 +1,15 @@
+//! This example test the ADC (Analog to Digital Conversion) of the RS2040 pin 26, 27 and 28.
+//! It also reads the temperature sensor in the chip.
+
 #![no_std]
 #![no_main]
 #![feature(type_alias_impl_trait)]
 
 use defmt::*;
 use embassy_executor::Spawner;
-use embassy_rp::adc::{Adc, Config, InterruptHandler};
+use embassy_rp::adc::{Adc, Config, InterruptHandler, Pin};
 use embassy_rp::bind_interrupts;
+use embassy_rp::gpio::Pull;
 use embassy_time::{Duration, Timer};
 use {defmt_rtt as _, panic_probe as _};
 
@@ -18,18 +22,18 @@ async fn main(_spawner: Spawner) {
     let p = embassy_rp::init(Default::default());
     let mut adc = Adc::new(p.ADC, Irqs, Config::default());
 
-    let mut p26 = p.PIN_26;
-    let mut p27 = p.PIN_27;
-    let mut p28 = p.PIN_28;
+    let mut p26 = Pin::new(p.PIN_26, Pull::None);
+    let mut p27 = Pin::new(p.PIN_27, Pull::None);
+    let mut p28 = Pin::new(p.PIN_28, Pull::None);
 
     loop {
-        let level = adc.read(&mut p26).await;
+        let level = adc.read(&mut p26).await.unwrap();
         info!("Pin 26 ADC: {}", level);
-        let level = adc.read(&mut p27).await;
+        let level = adc.read(&mut p27).await.unwrap();
         info!("Pin 27 ADC: {}", level);
-        let level = adc.read(&mut p28).await;
+        let level = adc.read(&mut p28).await.unwrap();
         info!("Pin 28 ADC: {}", level);
-        let temp = adc.read_temperature().await;
+        let temp = adc.read_temperature().await.unwrap();
         info!("Temp: {} degrees", convert_to_celsius(temp));
         Timer::after(Duration::from_secs(1)).await;
     }
@@ -37,5 +41,8 @@ async fn main(_spawner: Spawner) {
 
 fn convert_to_celsius(raw_temp: u16) -> f32 {
     // According to chapter 4.9.5. Temperature Sensor in RP2040 datasheet
-    27.0 - (raw_temp as f32 * 3.3 / 4096.0 - 0.706) / 0.001721 as f32
+    let temp = 27.0 - (raw_temp as f32 * 3.3 / 4096.0 - 0.706) / 0.001721;
+    let sign = if temp < 0.0 { -1.0 } else { 1.0 };
+    let rounded_temp_x10: i16 = ((temp * 10.0) + 0.5 * sign) as i16;
+    (rounded_temp_x10 as f32) / 10.0
 }
diff --git a/examples/rp/src/bin/blinky.rs b/examples/rp/src/bin/blinky.rs
index 7aa36a19f..295b000f3 100644
--- a/examples/rp/src/bin/blinky.rs
+++ b/examples/rp/src/bin/blinky.rs
@@ -1,3 +1,7 @@
+//! This example test the RP Pico on board LED.
+//!
+//! It does not work with the RP Pico W board. See wifi_blinky.rs.
+
 #![no_std]
 #![no_main]
 #![feature(type_alias_impl_trait)]
diff --git a/examples/rp/src/bin/button.rs b/examples/rp/src/bin/button.rs
index 0d246c093..d7aa89410 100644
--- a/examples/rp/src/bin/button.rs
+++ b/examples/rp/src/bin/button.rs
@@ -1,3 +1,7 @@
+//! This example uses the RP Pico on board LED to test input pin 28. This is not the button on the board.
+//!
+//! It does not work with the RP Pico W board. Use wifi_blinky.rs and add input pin.
+
 #![no_std]
 #![no_main]
 #![feature(type_alias_impl_trait)]
diff --git a/examples/rp/src/bin/ethernet_w5500_multisocket.rs b/examples/rp/src/bin/ethernet_w5500_multisocket.rs
index 82568254a..e81da177b 100644
--- a/examples/rp/src/bin/ethernet_w5500_multisocket.rs
+++ b/examples/rp/src/bin/ethernet_w5500_multisocket.rs
@@ -15,7 +15,7 @@ use embassy_rp::clocks::RoscRng;
 use embassy_rp::gpio::{Input, Level, Output, Pull};
 use embassy_rp::peripherals::{PIN_17, PIN_20, PIN_21, SPI0};
 use embassy_rp::spi::{Async, Config as SpiConfig, Spi};
-use embassy_time::Duration;
+use embassy_time::{Delay, Duration};
 use embedded_hal_async::spi::ExclusiveDevice;
 use embedded_io::asynch::Write;
 use rand::RngCore;
@@ -26,7 +26,7 @@ use {defmt_rtt as _, panic_probe as _};
 async fn ethernet_task(
     runner: Runner<
         'static,
-        ExclusiveDevice<Spi<'static, SPI0, Async>, Output<'static, PIN_17>>,
+        ExclusiveDevice<Spi<'static, SPI0, Async>, Output<'static, PIN_17>, Delay>,
         Input<'static, PIN_21>,
         Output<'static, PIN_20>,
     >,
@@ -54,8 +54,14 @@ async fn main(spawner: Spawner) {
 
     let mac_addr = [0x02, 0x00, 0x00, 0x00, 0x00, 0x00];
     let state = make_static!(State::<8, 8>::new());
-    let (device, runner) =
-        embassy_net_w5500::new(mac_addr, state, ExclusiveDevice::new(spi, cs), w5500_int, w5500_reset).await;
+    let (device, runner) = embassy_net_w5500::new(
+        mac_addr,
+        state,
+        ExclusiveDevice::new(spi, cs, Delay),
+        w5500_int,
+        w5500_reset,
+    )
+    .await;
     unwrap!(spawner.spawn(ethernet_task(runner)));
 
     // Generate random seed
diff --git a/examples/rp/src/bin/ethernet_w5500_tcp_client.rs b/examples/rp/src/bin/ethernet_w5500_tcp_client.rs
index d562defad..9dd7ae973 100644
--- a/examples/rp/src/bin/ethernet_w5500_tcp_client.rs
+++ b/examples/rp/src/bin/ethernet_w5500_tcp_client.rs
@@ -17,7 +17,7 @@ use embassy_rp::clocks::RoscRng;
 use embassy_rp::gpio::{Input, Level, Output, Pull};
 use embassy_rp::peripherals::{PIN_17, PIN_20, PIN_21, SPI0};
 use embassy_rp::spi::{Async, Config as SpiConfig, Spi};
-use embassy_time::{Duration, Timer};
+use embassy_time::{Delay, Duration, Timer};
 use embedded_hal_async::spi::ExclusiveDevice;
 use embedded_io::asynch::Write;
 use rand::RngCore;
@@ -28,7 +28,7 @@ use {defmt_rtt as _, panic_probe as _};
 async fn ethernet_task(
     runner: Runner<
         'static,
-        ExclusiveDevice<Spi<'static, SPI0, Async>, Output<'static, PIN_17>>,
+        ExclusiveDevice<Spi<'static, SPI0, Async>, Output<'static, PIN_17>, Delay>,
         Input<'static, PIN_21>,
         Output<'static, PIN_20>,
     >,
@@ -57,8 +57,14 @@ async fn main(spawner: Spawner) {
 
     let mac_addr = [0x02, 0x00, 0x00, 0x00, 0x00, 0x00];
     let state = make_static!(State::<8, 8>::new());
-    let (device, runner) =
-        embassy_net_w5500::new(mac_addr, state, ExclusiveDevice::new(spi, cs), w5500_int, w5500_reset).await;
+    let (device, runner) = embassy_net_w5500::new(
+        mac_addr,
+        state,
+        ExclusiveDevice::new(spi, cs, Delay),
+        w5500_int,
+        w5500_reset,
+    )
+    .await;
     unwrap!(spawner.spawn(ethernet_task(runner)));
 
     // Generate random seed
diff --git a/examples/rp/src/bin/ethernet_w5500_tcp_server.rs b/examples/rp/src/bin/ethernet_w5500_tcp_server.rs
index 7f521cdb4..db21c2b6f 100644
--- a/examples/rp/src/bin/ethernet_w5500_tcp_server.rs
+++ b/examples/rp/src/bin/ethernet_w5500_tcp_server.rs
@@ -16,7 +16,7 @@ use embassy_rp::clocks::RoscRng;
 use embassy_rp::gpio::{Input, Level, Output, Pull};
 use embassy_rp::peripherals::{PIN_17, PIN_20, PIN_21, SPI0};
 use embassy_rp::spi::{Async, Config as SpiConfig, Spi};
-use embassy_time::Duration;
+use embassy_time::{Delay, Duration};
 use embedded_hal_async::spi::ExclusiveDevice;
 use embedded_io::asynch::Write;
 use rand::RngCore;
@@ -26,7 +26,7 @@ use {defmt_rtt as _, panic_probe as _};
 async fn ethernet_task(
     runner: Runner<
         'static,
-        ExclusiveDevice<Spi<'static, SPI0, Async>, Output<'static, PIN_17>>,
+        ExclusiveDevice<Spi<'static, SPI0, Async>, Output<'static, PIN_17>, Delay>,
         Input<'static, PIN_21>,
         Output<'static, PIN_20>,
     >,
@@ -55,8 +55,14 @@ async fn main(spawner: Spawner) {
 
     let mac_addr = [0x02, 0x00, 0x00, 0x00, 0x00, 0x00];
     let state = make_static!(State::<8, 8>::new());
-    let (device, runner) =
-        embassy_net_w5500::new(mac_addr, state, ExclusiveDevice::new(spi, cs), w5500_int, w5500_reset).await;
+    let (device, runner) = embassy_net_w5500::new(
+        mac_addr,
+        state,
+        ExclusiveDevice::new(spi, cs, Delay),
+        w5500_int,
+        w5500_reset,
+    )
+    .await;
     unwrap!(spawner.spawn(ethernet_task(runner)));
 
     // Generate random seed
diff --git a/examples/rp/src/bin/ethernet_w5500_udp.rs b/examples/rp/src/bin/ethernet_w5500_udp.rs
index ada86ae55..038432b17 100644
--- a/examples/rp/src/bin/ethernet_w5500_udp.rs
+++ b/examples/rp/src/bin/ethernet_w5500_udp.rs
@@ -16,6 +16,7 @@ use embassy_rp::clocks::RoscRng;
 use embassy_rp::gpio::{Input, Level, Output, Pull};
 use embassy_rp::peripherals::{PIN_17, PIN_20, PIN_21, SPI0};
 use embassy_rp::spi::{Async, Config as SpiConfig, Spi};
+use embassy_time::Delay;
 use embedded_hal_async::spi::ExclusiveDevice;
 use rand::RngCore;
 use static_cell::make_static;
@@ -24,7 +25,7 @@ use {defmt_rtt as _, panic_probe as _};
 async fn ethernet_task(
     runner: Runner<
         'static,
-        ExclusiveDevice<Spi<'static, SPI0, Async>, Output<'static, PIN_17>>,
+        ExclusiveDevice<Spi<'static, SPI0, Async>, Output<'static, PIN_17>, Delay>,
         Input<'static, PIN_21>,
         Output<'static, PIN_20>,
     >,
@@ -52,8 +53,14 @@ async fn main(spawner: Spawner) {
 
     let mac_addr = [0x02, 0x00, 0x00, 0x00, 0x00, 0x00];
     let state = make_static!(State::<8, 8>::new());
-    let (device, runner) =
-        embassy_net_w5500::new(mac_addr, state, ExclusiveDevice::new(spi, cs), w5500_int, w5500_reset).await;
+    let (device, runner) = embassy_net_w5500::new(
+        mac_addr,
+        state,
+        ExclusiveDevice::new(spi, cs, Delay),
+        w5500_int,
+        w5500_reset,
+    )
+    .await;
     unwrap!(spawner.spawn(ethernet_task(runner)));
 
     // Generate random seed
diff --git a/examples/rp/src/bin/flash.rs b/examples/rp/src/bin/flash.rs
index 19076150c..4c4982acc 100644
--- a/examples/rp/src/bin/flash.rs
+++ b/examples/rp/src/bin/flash.rs
@@ -1,3 +1,5 @@
+//! This example test the flash connected to the RP2040 chip.
+
 #![no_std]
 #![no_main]
 #![feature(type_alias_impl_trait)]
diff --git a/examples/rp/src/bin/gpio_async.rs b/examples/rp/src/bin/gpio_async.rs
index 52d13a9d5..bf58044d5 100644
--- a/examples/rp/src/bin/gpio_async.rs
+++ b/examples/rp/src/bin/gpio_async.rs
@@ -1,3 +1,7 @@
+//! This example shows how async gpio can be used with a RP2040.
+//!
+//! The LED on the RP Pico W board is connected differently. See wifi_blinky.rs.
+
 #![no_std]
 #![no_main]
 #![feature(type_alias_impl_trait)]
@@ -9,8 +13,6 @@ use embassy_time::{Duration, Timer};
 use gpio::{Input, Level, Output, Pull};
 use {defmt_rtt as _, panic_probe as _};
 
-/// This example shows how async gpio can be used with a RP2040.
-///
 /// It requires an external signal to be manually triggered on PIN 16. For
 /// example, this could be accomplished using an external power source with a
 /// button so that it is possible to toggle the signal from low to high.
diff --git a/examples/rp/src/bin/gpout.rs b/examples/rp/src/bin/gpout.rs
index 64461fc5f..0a3b5fa98 100644
--- a/examples/rp/src/bin/gpout.rs
+++ b/examples/rp/src/bin/gpout.rs
@@ -1,3 +1,7 @@
+//! This example shows how GPOUT (General purpose clock outputs) can toggle a output pin.
+//!
+//! The LED on the RP Pico W board is connected differently. Add a LED and resistor to another pin.
+
 #![no_std]
 #![no_main]
 #![feature(type_alias_impl_trait)]
diff --git a/examples/rp/src/bin/i2c_async.rs b/examples/rp/src/bin/i2c_async.rs
index cf3cf742c..93224bc43 100644
--- a/examples/rp/src/bin/i2c_async.rs
+++ b/examples/rp/src/bin/i2c_async.rs
@@ -1,3 +1,8 @@
+//! This example shows how to communicate asynchronous using i2c with external chips.
+//!
+//! Example written for the [`MCP23017 16-Bit I2C I/O Expander with Serial Interface`] chip.
+//! (https://www.microchip.com/en-us/product/mcp23017)
+
 #![no_std]
 #![no_main]
 #![feature(type_alias_impl_trait)]
diff --git a/examples/rp/src/bin/i2c_blocking.rs b/examples/rp/src/bin/i2c_blocking.rs
index 7623e33c8..1c8c2039d 100644
--- a/examples/rp/src/bin/i2c_blocking.rs
+++ b/examples/rp/src/bin/i2c_blocking.rs
@@ -1,3 +1,8 @@
+//! This example shows how to communicate using i2c with external chips.
+//!
+//! Example written for the [`MCP23017 16-Bit I2C I/O Expander with Serial Interface`] chip.
+//! (https://www.microchip.com/en-us/product/mcp23017)
+
 #![no_std]
 #![no_main]
 #![feature(type_alias_impl_trait)]
diff --git a/examples/rp/src/bin/lora_lorawan.rs b/examples/rp/src/bin/lora_lorawan.rs
index a9c84bf95..d631fafa1 100644
--- a/examples/rp/src/bin/lora_lorawan.rs
+++ b/examples/rp/src/bin/lora_lorawan.rs
@@ -1,5 +1,6 @@
 //! This example runs on the Raspberry Pi Pico with a Waveshare board containing a Semtech Sx1262 radio.
 //! It demonstrates LoRaWAN join functionality.
+
 #![no_std]
 #![no_main]
 #![macro_use]
diff --git a/examples/rp/src/bin/lora_p2p_receive.rs b/examples/rp/src/bin/lora_p2p_receive.rs
index 250419202..396d669de 100644
--- a/examples/rp/src/bin/lora_p2p_receive.rs
+++ b/examples/rp/src/bin/lora_p2p_receive.rs
@@ -1,5 +1,6 @@
 //! This example runs on the Raspberry Pi Pico with a Waveshare board containing a Semtech Sx1262 radio.
 //! It demonstrates LORA P2P receive functionality in conjunction with the lora_p2p_send example.
+
 #![no_std]
 #![no_main]
 #![macro_use]
diff --git a/examples/rp/src/bin/lora_p2p_send.rs b/examples/rp/src/bin/lora_p2p_send.rs
index 3a0544b17..a0f70fa5c 100644
--- a/examples/rp/src/bin/lora_p2p_send.rs
+++ b/examples/rp/src/bin/lora_p2p_send.rs
@@ -1,5 +1,6 @@
 //! This example runs on the Raspberry Pi Pico with a Waveshare board containing a Semtech Sx1262 radio.
 //! It demonstrates LORA P2P send functionality.
+
 #![no_std]
 #![no_main]
 #![macro_use]
diff --git a/examples/rp/src/bin/lora_p2p_send_multicore.rs b/examples/rp/src/bin/lora_p2p_send_multicore.rs
index eef2f7a53..89a62818d 100644
--- a/examples/rp/src/bin/lora_p2p_send_multicore.rs
+++ b/examples/rp/src/bin/lora_p2p_send_multicore.rs
@@ -1,5 +1,6 @@
 //! This example runs on the Raspberry Pi Pico with a Waveshare board containing a Semtech Sx1262 radio.
 //! It demonstrates LORA P2P send functionality using the second core, with data provided by the first core.
+
 #![no_std]
 #![no_main]
 #![macro_use]
diff --git a/examples/rp/src/bin/multicore.rs b/examples/rp/src/bin/multicore.rs
index 57278dd6c..893b724bf 100644
--- a/examples/rp/src/bin/multicore.rs
+++ b/examples/rp/src/bin/multicore.rs
@@ -1,3 +1,7 @@
+//! This example shows how to send messages between the two cores in the RP2040 chip.
+//!
+//! The LED on the RP Pico W board is connected differently. See wifi_blinky.rs.
+
 #![no_std]
 #![no_main]
 #![feature(type_alias_impl_trait)]
diff --git a/examples/rp/src/bin/pio_async.rs b/examples/rp/src/bin/pio_async.rs
index 79eda1a09..c001d6440 100644
--- a/examples/rp/src/bin/pio_async.rs
+++ b/examples/rp/src/bin/pio_async.rs
@@ -1,15 +1,22 @@
+//! This example shows powerful PIO module in the RP2040 chip.
+
 #![no_std]
 #![no_main]
 #![feature(type_alias_impl_trait)]
 use defmt::info;
 use embassy_executor::Spawner;
+use embassy_rp::bind_interrupts;
 use embassy_rp::peripherals::PIO0;
-use embassy_rp::pio::{Common, Config, Irq, Pio, PioPin, ShiftDirection, StateMachine};
+use embassy_rp::pio::{Common, Config, InterruptHandler, Irq, Pio, PioPin, ShiftDirection, StateMachine};
 use embassy_rp::relocate::RelocatedProgram;
 use fixed::traits::ToFixed;
 use fixed_macro::types::U56F8;
 use {defmt_rtt as _, panic_probe as _};
 
+bind_interrupts!(struct Irqs {
+    PIO0_IRQ_0 => InterruptHandler<PIO0>;
+});
+
 fn setup_pio_task_sm0<'a>(pio: &mut Common<'a, PIO0>, sm: &mut StateMachine<'a, PIO0, 0>, pin: impl PioPin) {
     // Setup sm0
 
@@ -49,7 +56,14 @@ fn setup_pio_task_sm1<'a>(pio: &mut Common<'a, PIO0>, sm: &mut StateMachine<'a,
     // Setupm sm1
 
     // Read 0b10101 repeatedly until ISR is full
-    let prg = pio_proc::pio_asm!(".origin 8", "set x, 0x15", ".wrap_target", "in x, 5 [31]", ".wrap",);
+    let prg = pio_proc::pio_asm!(
+        //
+        ".origin 8",
+        "set x, 0x15",
+        ".wrap_target",
+        "in x, 5 [31]",
+        ".wrap",
+    );
 
     let relocated = RelocatedProgram::new(&prg.program);
     let mut cfg = Config::default();
@@ -110,7 +124,7 @@ async fn main(spawner: Spawner) {
         mut sm1,
         mut sm2,
         ..
-    } = Pio::new(pio);
+    } = Pio::new(pio, Irqs);
 
     setup_pio_task_sm0(&mut common, &mut sm0, p.PIN_0);
     setup_pio_task_sm1(&mut common, &mut sm1);
diff --git a/examples/rp/src/bin/pio_dma.rs b/examples/rp/src/bin/pio_dma.rs
index 05c0ebb16..9ab72e1f3 100644
--- a/examples/rp/src/bin/pio_dma.rs
+++ b/examples/rp/src/bin/pio_dma.rs
@@ -1,16 +1,23 @@
+//! This example shows powerful PIO module in the RP2040 chip.
+
 #![no_std]
 #![no_main]
 #![feature(type_alias_impl_trait)]
 use defmt::info;
 use embassy_executor::Spawner;
 use embassy_futures::join::join;
-use embassy_rp::pio::{Config, Pio, ShiftConfig, ShiftDirection};
+use embassy_rp::peripherals::PIO0;
+use embassy_rp::pio::{Config, InterruptHandler, Pio, ShiftConfig, ShiftDirection};
 use embassy_rp::relocate::RelocatedProgram;
-use embassy_rp::Peripheral;
+use embassy_rp::{bind_interrupts, Peripheral};
 use fixed::traits::ToFixed;
 use fixed_macro::types::U56F8;
 use {defmt_rtt as _, panic_probe as _};
 
+bind_interrupts!(struct Irqs {
+    PIO0_IRQ_0 => InterruptHandler<PIO0>;
+});
+
 fn swap_nibbles(v: u32) -> u32 {
     let v = (v & 0x0f0f_0f0f) << 4 | (v & 0xf0f0_f0f0) >> 4;
     let v = (v & 0x00ff_00ff) << 8 | (v & 0xff00_ff00) >> 8;
@@ -25,7 +32,7 @@ async fn main(_spawner: Spawner) {
         mut common,
         sm0: mut sm,
         ..
-    } = Pio::new(pio);
+    } = Pio::new(pio, Irqs);
 
     let prg = pio_proc::pio_asm!(
         ".origin 0",
diff --git a/examples/rp/src/bin/pio_hd44780.rs b/examples/rp/src/bin/pio_hd44780.rs
index bfc6c9908..8aedd24b6 100644
--- a/examples/rp/src/bin/pio_hd44780.rs
+++ b/examples/rp/src/bin/pio_hd44780.rs
@@ -1,3 +1,6 @@
+//! This example shows powerful PIO module in the RP2040 chip to communicate with a HD44780 display.
+//! See (https://www.sparkfun.com/datasheets/LCD/HD44780.pdf)
+
 #![no_std]
 #![no_main]
 #![feature(type_alias_impl_trait)]
@@ -7,13 +10,19 @@ use core::fmt::Write;
 use embassy_executor::Spawner;
 use embassy_rp::dma::{AnyChannel, Channel};
 use embassy_rp::peripherals::PIO0;
-use embassy_rp::pio::{Config, Direction, FifoJoin, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine};
+use embassy_rp::pio::{
+    Config, Direction, FifoJoin, InterruptHandler, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine,
+};
 use embassy_rp::pwm::{self, Pwm};
 use embassy_rp::relocate::RelocatedProgram;
-use embassy_rp::{into_ref, Peripheral, PeripheralRef};
+use embassy_rp::{bind_interrupts, into_ref, Peripheral, PeripheralRef};
 use embassy_time::{Duration, Instant, Timer};
 use {defmt_rtt as _, panic_probe as _};
 
+bind_interrupts!(pub struct Irqs {
+    PIO0_IRQ_0 => InterruptHandler<PIO0>;
+});
+
 #[embassy_executor::main]
 async fn main(_spawner: Spawner) {
     // this test assumes a 2x16 HD44780 display attached as follow:
@@ -37,7 +46,7 @@ async fn main(_spawner: Spawner) {
     });
 
     let mut hd = HD44780::new(
-        p.PIO0, p.DMA_CH3, p.PIN_0, p.PIN_1, p.PIN_2, p.PIN_3, p.PIN_4, p.PIN_5, p.PIN_6,
+        p.PIO0, Irqs, p.DMA_CH3, p.PIN_0, p.PIN_1, p.PIN_2, p.PIN_3, p.PIN_4, p.PIN_5, p.PIN_6,
     )
     .await;
 
@@ -72,6 +81,7 @@ pub struct HD44780<'l> {
 impl<'l> HD44780<'l> {
     pub async fn new(
         pio: impl Peripheral<P = PIO0> + 'l,
+        irq: Irqs,
         dma: impl Peripheral<P = impl Channel> + 'l,
         rs: impl PioPin,
         rw: impl PioPin,
@@ -88,7 +98,7 @@ impl<'l> HD44780<'l> {
             mut irq0,
             mut sm0,
             ..
-        } = Pio::new(pio);
+        } = Pio::new(pio, irq);
 
         // takes command words (<wait:24> <command:4> <0:4>)
         let prg = pio_proc::pio_asm!(
diff --git a/examples/rp/src/bin/pio_ws2812.rs b/examples/rp/src/bin/pio_ws2812.rs
index 26422421f..3de2bd48d 100644
--- a/examples/rp/src/bin/pio_ws2812.rs
+++ b/examples/rp/src/bin/pio_ws2812.rs
@@ -1,3 +1,6 @@
+//! This example shows powerful PIO module in the RP2040 chip to communicate with WS2812 LED modules.
+//! See (https://www.sparkfun.com/categories/tags/ws2812)
+
 #![no_std]
 #![no_main]
 #![feature(type_alias_impl_trait)]
@@ -5,15 +8,22 @@
 use defmt::*;
 use embassy_executor::Spawner;
 use embassy_rp::dma::{AnyChannel, Channel};
-use embassy_rp::pio::{Common, Config, FifoJoin, Instance, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine};
+use embassy_rp::peripherals::PIO0;
+use embassy_rp::pio::{
+    Common, Config, FifoJoin, Instance, InterruptHandler, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine,
+};
 use embassy_rp::relocate::RelocatedProgram;
-use embassy_rp::{clocks, into_ref, Peripheral, PeripheralRef};
+use embassy_rp::{bind_interrupts, clocks, into_ref, Peripheral, PeripheralRef};
 use embassy_time::{Duration, Timer};
 use fixed::types::U24F8;
 use fixed_macro::fixed;
 use smart_leds::RGB8;
 use {defmt_rtt as _, panic_probe as _};
 
+bind_interrupts!(struct Irqs {
+    PIO0_IRQ_0 => InterruptHandler<PIO0>;
+});
+
 pub struct Ws2812<'d, P: Instance, const S: usize, const N: usize> {
     dma: PeripheralRef<'d, AnyChannel>,
     sm: StateMachine<'d, P, S>,
@@ -123,7 +133,7 @@ async fn main(_spawner: Spawner) {
     info!("Start");
     let p = embassy_rp::init(Default::default());
 
-    let Pio { mut common, sm0, .. } = Pio::new(p.PIO0);
+    let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs);
 
     // This is the number of leds in the string. Helpfully, the sparkfun thing plus and adafruit
     // feather boards for the 2040 both have one built in.
diff --git a/examples/rp/src/bin/pwm.rs b/examples/rp/src/bin/pwm.rs
index 2b3d5d97a..9d919287c 100644
--- a/examples/rp/src/bin/pwm.rs
+++ b/examples/rp/src/bin/pwm.rs
@@ -1,3 +1,7 @@
+//! This example shows how to use PWM (Pulse Width Modulation) in the RP2040 chip.
+//!
+//! The LED on the RP Pico W board is connected differently. Add a LED and resistor to another pin.
+
 #![no_std]
 #![no_main]
 #![feature(type_alias_impl_trait)]
diff --git a/examples/rp/src/bin/rtc.rs b/examples/rp/src/bin/rtc.rs
new file mode 100644
index 000000000..15aa8243f
--- /dev/null
+++ b/examples/rp/src/bin/rtc.rs
@@ -0,0 +1,46 @@
+//! This example shows how to use RTC (Real Time Clock) in the RP2040 chip.
+
+#![no_std]
+#![no_main]
+#![feature(type_alias_impl_trait)]
+
+use defmt::*;
+use embassy_executor::Spawner;
+use embassy_rp::rtc::{DateTime, DayOfWeek, Rtc};
+use embassy_time::{Duration, Timer};
+use {defmt_rtt as _, panic_probe as _};
+
+#[embassy_executor::main]
+async fn main(_spawner: Spawner) {
+    let p = embassy_rp::init(Default::default());
+    info!("Wait for 20s");
+
+    let mut rtc = Rtc::new(p.RTC);
+
+    if !rtc.is_running() {
+        info!("Start RTC");
+        let now = DateTime {
+            year: 2000,
+            month: 1,
+            day: 1,
+            day_of_week: DayOfWeek::Saturday,
+            hour: 0,
+            minute: 0,
+            second: 0,
+        };
+        rtc.set_datetime(now).unwrap();
+    }
+
+    Timer::after(Duration::from_millis(20000)).await;
+
+    if let Ok(dt) = rtc.now() {
+        info!(
+            "Now: {}-{:02}-{:02} {}:{:02}:{:02}",
+            dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second,
+        );
+    }
+
+    info!("Reboot.");
+    Timer::after(Duration::from_millis(200)).await;
+    cortex_m::peripheral::SCB::sys_reset();
+}
diff --git a/examples/rp/src/bin/spi.rs b/examples/rp/src/bin/spi.rs
index a830a17a2..602348f7a 100644
--- a/examples/rp/src/bin/spi.rs
+++ b/examples/rp/src/bin/spi.rs
@@ -1,3 +1,7 @@
+//! This example shows how to use SPI (Serial Peripheral Interface) in the RP2040 chip.
+//!
+//! Example for resistive touch sensor in Waveshare Pico-ResTouch
+
 #![no_std]
 #![no_main]
 #![feature(type_alias_impl_trait)]
diff --git a/examples/rp/src/bin/spi_async.rs b/examples/rp/src/bin/spi_async.rs
index 671a9caaf..328074e8b 100644
--- a/examples/rp/src/bin/spi_async.rs
+++ b/examples/rp/src/bin/spi_async.rs
@@ -1,3 +1,6 @@
+//! This example shows how to use SPI (Serial Peripheral Interface) in the RP2040 chip.
+//! No specific hardware is specified in this example. If you connect pin 11 and 12 you should get the same data back.
+
 #![no_std]
 #![no_main]
 #![feature(type_alias_impl_trait)]
diff --git a/examples/rp/src/bin/spi_display.rs b/examples/rp/src/bin/spi_display.rs
index 85a19ce07..26c258e1c 100644
--- a/examples/rp/src/bin/spi_display.rs
+++ b/examples/rp/src/bin/spi_display.rs
@@ -1,3 +1,8 @@
+//! This example shows how to use SPI (Serial Peripheral Interface) in the RP2040 chip.
+//!
+//! Example written for a display using the ST7789 chip. Possibly the Waveshare Pico-ResTouch
+//! (https://www.waveshare.com/wiki/Pico-ResTouch-LCD-2.8)
+
 #![no_std]
 #![no_main]
 #![feature(type_alias_impl_trait)]
@@ -175,7 +180,7 @@ mod touch {
 mod my_display_interface {
     use display_interface::{DataFormat, DisplayError, WriteOnlyDataCommand};
     use embedded_hal_1::digital::OutputPin;
-    use embedded_hal_1::spi::SpiDeviceWrite;
+    use embedded_hal_1::spi::SpiDevice;
 
     /// SPI display interface.
     ///
@@ -187,7 +192,7 @@ mod my_display_interface {
 
     impl<SPI, DC> SPIDeviceInterface<SPI, DC>
     where
-        SPI: SpiDeviceWrite,
+        SPI: SpiDevice,
         DC: OutputPin,
     {
         /// Create new SPI interface for communciation with a display driver
@@ -198,7 +203,7 @@ mod my_display_interface {
 
     impl<SPI, DC> WriteOnlyDataCommand for SPIDeviceInterface<SPI, DC>
     where
-        SPI: SpiDeviceWrite,
+        SPI: SpiDevice,
         DC: OutputPin,
     {
         fn send_commands(&mut self, cmds: DataFormat<'_>) -> Result<(), DisplayError> {
@@ -218,7 +223,7 @@ mod my_display_interface {
         }
     }
 
-    fn send_u8<T: SpiDeviceWrite>(spi: &mut T, words: DataFormat<'_>) -> Result<(), T::Error> {
+    fn send_u8<T: SpiDevice>(spi: &mut T, words: DataFormat<'_>) -> Result<(), T::Error> {
         match words {
             DataFormat::U8(slice) => spi.write(slice),
             DataFormat::U16(slice) => {
diff --git a/examples/rp/src/bin/uart.rs b/examples/rp/src/bin/uart.rs
index 05177a6b4..451c3c396 100644
--- a/examples/rp/src/bin/uart.rs
+++ b/examples/rp/src/bin/uart.rs
@@ -1,3 +1,9 @@
+//! This example shows how to use UART (Universal asynchronous receiver-transmitter) in the RP2040 chip.
+//!
+//! No specific hardware is specified in this example. Only output on pin 0 is tested.
+//! The Raspberry Pi Debug Probe (https://www.raspberrypi.com/products/debug-probe/) could be used
+//! with its UART port.
+
 #![no_std]
 #![no_main]
 #![feature(type_alias_impl_trait)]
diff --git a/examples/rp/src/bin/uart_buffered_split.rs b/examples/rp/src/bin/uart_buffered_split.rs
index 9df99bd58..735201718 100644
--- a/examples/rp/src/bin/uart_buffered_split.rs
+++ b/examples/rp/src/bin/uart_buffered_split.rs
@@ -1,3 +1,9 @@
+//! This example shows how to use UART (Universal asynchronous receiver-transmitter) in the RP2040 chip.
+//!
+//! No specific hardware is specified in this example. If you connect pin 0 and 1 you should get the same data back.
+//! The Raspberry Pi Debug Probe (https://www.raspberrypi.com/products/debug-probe/) could be used
+//! with its UART port.
+
 #![no_std]
 #![no_main]
 #![feature(type_alias_impl_trait)]
diff --git a/examples/rp/src/bin/uart_unidir.rs b/examples/rp/src/bin/uart_unidir.rs
index c0943a1b8..c1515a911 100644
--- a/examples/rp/src/bin/uart_unidir.rs
+++ b/examples/rp/src/bin/uart_unidir.rs
@@ -1,5 +1,9 @@
-//! test TX-only and RX-only UARTs. You need to connect GPIO0 to GPIO5 for
+//! This example shows how to use UART (Universal asynchronous receiver-transmitter) in the RP2040 chip.
+//!
+//! Test TX-only and RX-only on two different UARTs. You need to connect GPIO0 to GPIO5 for
 //! this to work
+//! The Raspberry Pi Debug Probe (https://www.raspberrypi.com/products/debug-probe/) could be used
+//! with its UART port.
 
 #![no_std]
 #![no_main]
diff --git a/examples/rp/src/bin/usb_ethernet.rs b/examples/rp/src/bin/usb_ethernet.rs
index 91d1ec8e7..0a08f667e 100644
--- a/examples/rp/src/bin/usb_ethernet.rs
+++ b/examples/rp/src/bin/usb_ethernet.rs
@@ -1,3 +1,7 @@
+//! This example shows how to use USB (Universal Serial Bus) in the RP2040 chip.
+//!
+//! This is a CDC-NCM class implementation, aka Ethernet over USB.
+
 #![no_std]
 #![no_main]
 #![feature(type_alias_impl_trait)]
diff --git a/examples/rp/src/bin/usb_hid_keyboard.rs b/examples/rp/src/bin/usb_hid_keyboard.rs
new file mode 100644
index 000000000..99af1f02f
--- /dev/null
+++ b/examples/rp/src/bin/usb_hid_keyboard.rs
@@ -0,0 +1,188 @@
+#![no_std]
+#![no_main]
+#![feature(type_alias_impl_trait)]
+
+use core::sync::atomic::{AtomicBool, Ordering};
+
+use defmt::*;
+use embassy_executor::Spawner;
+use embassy_futures::join::join;
+use embassy_rp::bind_interrupts;
+use embassy_rp::gpio::{Input, Pull};
+use embassy_rp::peripherals::USB;
+use embassy_rp::usb::{Driver, InterruptHandler};
+use embassy_usb::class::hid::{HidReaderWriter, ReportId, RequestHandler, State};
+use embassy_usb::control::OutResponse;
+use embassy_usb::{Builder, Config, Handler};
+use usbd_hid::descriptor::{KeyboardReport, SerializedDescriptor};
+use {defmt_rtt as _, panic_probe as _};
+
+bind_interrupts!(struct Irqs {
+    USBCTRL_IRQ => InterruptHandler<USB>;
+});
+
+#[embassy_executor::main]
+async fn main(_spawner: Spawner) {
+    let p = embassy_rp::init(Default::default());
+    // Create the driver, from the HAL.
+    let driver = Driver::new(p.USB, Irqs);
+
+    // Create embassy-usb Config
+    let mut config = Config::new(0xc0de, 0xcafe);
+    config.manufacturer = Some("Embassy");
+    config.product = Some("HID keyboard example");
+    config.serial_number = Some("12345678");
+    config.max_power = 100;
+    config.max_packet_size_0 = 64;
+
+    // Create embassy-usb DeviceBuilder using the driver and config.
+    // It needs some buffers for building the descriptors.
+    let mut device_descriptor = [0; 256];
+    let mut config_descriptor = [0; 256];
+    let mut bos_descriptor = [0; 256];
+    // You can also add a Microsoft OS descriptor.
+    // let mut msos_descriptor = [0; 256];
+    let mut control_buf = [0; 64];
+    let request_handler = MyRequestHandler {};
+    let mut device_handler = MyDeviceHandler::new();
+
+    let mut state = State::new();
+
+    let mut builder = Builder::new(
+        driver,
+        config,
+        &mut device_descriptor,
+        &mut config_descriptor,
+        &mut bos_descriptor,
+        // &mut msos_descriptor,
+        &mut control_buf,
+    );
+
+    builder.handler(&mut device_handler);
+
+    // Create classes on the builder.
+    let config = embassy_usb::class::hid::Config {
+        report_descriptor: KeyboardReport::desc(),
+        request_handler: Some(&request_handler),
+        poll_ms: 60,
+        max_packet_size: 64,
+    };
+    let hid = HidReaderWriter::<_, 1, 8>::new(&mut builder, &mut state, config);
+
+    // Build the builder.
+    let mut usb = builder.build();
+
+    // Run the USB device.
+    let usb_fut = usb.run();
+
+    // Set up the signal pin that will be used to trigger the keyboard.
+    let mut signal_pin = Input::new(p.PIN_16, Pull::None);
+
+    let (reader, mut writer) = hid.split();
+
+    // Do stuff with the class!
+    let in_fut = async {
+        loop {
+            info!("Waiting for HIGH on pin 16");
+            signal_pin.wait_for_high().await;
+            info!("HIGH DETECTED");
+            // Create a report with the A key pressed. (no shift modifier)
+            let report = KeyboardReport {
+                keycodes: [4, 0, 0, 0, 0, 0],
+                leds: 0,
+                modifier: 0,
+                reserved: 0,
+            };
+            // Send the report.
+            match writer.write_serialize(&report).await {
+                Ok(()) => {}
+                Err(e) => warn!("Failed to send report: {:?}", e),
+            };
+            signal_pin.wait_for_low().await;
+            info!("LOW DETECTED");
+            let report = KeyboardReport {
+                keycodes: [0, 0, 0, 0, 0, 0],
+                leds: 0,
+                modifier: 0,
+                reserved: 0,
+            };
+            match writer.write_serialize(&report).await {
+                Ok(()) => {}
+                Err(e) => warn!("Failed to send report: {:?}", e),
+            };
+        }
+    };
+
+    let out_fut = async {
+        reader.run(false, &request_handler).await;
+    };
+
+    // Run everything concurrently.
+    // If we had made everything `'static` above instead, we could do this using separate tasks instead.
+    join(usb_fut, join(in_fut, out_fut)).await;
+}
+
+struct MyRequestHandler {}
+
+impl RequestHandler for MyRequestHandler {
+    fn get_report(&self, id: ReportId, _buf: &mut [u8]) -> Option<usize> {
+        info!("Get report for {:?}", id);
+        None
+    }
+
+    fn set_report(&self, id: ReportId, data: &[u8]) -> OutResponse {
+        info!("Set report for {:?}: {=[u8]}", id, data);
+        OutResponse::Accepted
+    }
+
+    fn set_idle_ms(&self, id: Option<ReportId>, dur: u32) {
+        info!("Set idle rate for {:?} to {:?}", id, dur);
+    }
+
+    fn get_idle_ms(&self, id: Option<ReportId>) -> Option<u32> {
+        info!("Get idle rate for {:?}", id);
+        None
+    }
+}
+
+struct MyDeviceHandler {
+    configured: AtomicBool,
+}
+
+impl MyDeviceHandler {
+    fn new() -> Self {
+        MyDeviceHandler {
+            configured: AtomicBool::new(false),
+        }
+    }
+}
+
+impl Handler for MyDeviceHandler {
+    fn enabled(&mut self, enabled: bool) {
+        self.configured.store(false, Ordering::Relaxed);
+        if enabled {
+            info!("Device enabled");
+        } else {
+            info!("Device disabled");
+        }
+    }
+
+    fn reset(&mut self) {
+        self.configured.store(false, Ordering::Relaxed);
+        info!("Bus reset, the Vbus current limit is 100mA");
+    }
+
+    fn addressed(&mut self, addr: u8) {
+        self.configured.store(false, Ordering::Relaxed);
+        info!("USB address set to: {}", addr);
+    }
+
+    fn configured(&mut self, configured: bool) {
+        self.configured.store(configured, Ordering::Relaxed);
+        if configured {
+            info!("Device configured, it may now draw up to the configured current limit from Vbus.")
+        } else {
+            info!("Device is no longer configured, the Vbus current limit is 100mA.");
+        }
+    }
+}
diff --git a/examples/rp/src/bin/usb_logger.rs b/examples/rp/src/bin/usb_logger.rs
index 7c90d0ca3..9c5e6897d 100644
--- a/examples/rp/src/bin/usb_logger.rs
+++ b/examples/rp/src/bin/usb_logger.rs
@@ -1,3 +1,7 @@
+//! This example shows how to use USB (Universal Serial Bus) in the RP2040 chip.
+//!
+//! This creates the possibility to send log::info/warn/error/debug! to USB serial port.
+
 #![no_std]
 #![no_main]
 #![feature(type_alias_impl_trait)]
diff --git a/examples/rp/src/bin/usb_serial.rs b/examples/rp/src/bin/usb_serial.rs
index ca728536c..164e2052d 100644
--- a/examples/rp/src/bin/usb_serial.rs
+++ b/examples/rp/src/bin/usb_serial.rs
@@ -1,3 +1,7 @@
+//! This example shows how to use USB (Universal Serial Bus) in the RP2040 chip.
+//!
+//! This creates a USB serial port that echos.
+
 #![no_std]
 #![no_main]
 #![feature(type_alias_impl_trait)]
diff --git a/examples/rp/src/bin/watchdog.rs b/examples/rp/src/bin/watchdog.rs
index ece5cfe38..fe5eaf926 100644
--- a/examples/rp/src/bin/watchdog.rs
+++ b/examples/rp/src/bin/watchdog.rs
@@ -1,3 +1,7 @@
+//! This example shows how to use Watchdog in the RP2040 chip.
+//!
+//! It does not work with the RP Pico W board. See wifi_blinky.rs or connect external LED and resistor.
+
 #![no_std]
 #![no_main]
 #![feature(type_alias_impl_trait)]
diff --git a/examples/rp/src/bin/wifi_ap_tcp_server.rs b/examples/rp/src/bin/wifi_ap_tcp_server.rs
index 310e84d92..e3e393445 100644
--- a/examples/rp/src/bin/wifi_ap_tcp_server.rs
+++ b/examples/rp/src/bin/wifi_ap_tcp_server.rs
@@ -1,3 +1,6 @@
+//! This example uses the RP Pico W board Wifi chip (cyw43).
+//! Creates an Access point Wifi network and creates a TCP endpoint on port 1234.
+
 #![no_std]
 #![no_main]
 #![feature(type_alias_impl_trait)]
@@ -11,14 +14,19 @@ use defmt::*;
 use embassy_executor::Spawner;
 use embassy_net::tcp::TcpSocket;
 use embassy_net::{Config, Stack, StackResources};
+use embassy_rp::bind_interrupts;
 use embassy_rp::gpio::{Level, Output};
 use embassy_rp::peripherals::{DMA_CH0, PIN_23, PIN_25, PIO0};
-use embassy_rp::pio::Pio;
+use embassy_rp::pio::{InterruptHandler, Pio};
 use embassy_time::Duration;
 use embedded_io::asynch::Write;
 use static_cell::make_static;
 use {defmt_rtt as _, panic_probe as _};
 
+bind_interrupts!(struct Irqs {
+    PIO0_IRQ_0 => InterruptHandler<PIO0>;
+});
+
 #[embassy_executor::task]
 async fn wifi_task(
     runner: cyw43::Runner<'static, Output<'static, PIN_23>, PioSpi<'static, PIN_25, PIO0, 0, DMA_CH0>>,
@@ -49,7 +57,7 @@ async fn main(spawner: Spawner) {
 
     let pwr = Output::new(p.PIN_23, Level::Low);
     let cs = Output::new(p.PIN_25, Level::High);
-    let mut pio = Pio::new(p.PIO0);
+    let mut pio = Pio::new(p.PIO0, Irqs);
     let spi = PioSpi::new(&mut pio.common, pio.sm0, pio.irq0, cs, p.PIN_24, p.PIN_29, p.DMA_CH0);
 
     let state = make_static!(cyw43::State::new());
diff --git a/examples/rp/src/bin/wifi_blinky.rs b/examples/rp/src/bin/wifi_blinky.rs
index bbcb1b5ec..33d43788c 100644
--- a/examples/rp/src/bin/wifi_blinky.rs
+++ b/examples/rp/src/bin/wifi_blinky.rs
@@ -1,3 +1,7 @@
+//! This example test the RP Pico W on board LED.
+//!
+//! It does not work with the RP Pico board. See blinky.rs.
+
 #![no_std]
 #![no_main]
 #![feature(type_alias_impl_trait)]
@@ -5,13 +9,18 @@
 use cyw43_pio::PioSpi;
 use defmt::*;
 use embassy_executor::Spawner;
+use embassy_rp::bind_interrupts;
 use embassy_rp::gpio::{Level, Output};
 use embassy_rp::peripherals::{DMA_CH0, PIN_23, PIN_25, PIO0};
-use embassy_rp::pio::Pio;
+use embassy_rp::pio::{InterruptHandler, Pio};
 use embassy_time::{Duration, Timer};
 use static_cell::make_static;
 use {defmt_rtt as _, panic_probe as _};
 
+bind_interrupts!(struct Irqs {
+    PIO0_IRQ_0 => InterruptHandler<PIO0>;
+});
+
 #[embassy_executor::task]
 async fn wifi_task(
     runner: cyw43::Runner<'static, Output<'static, PIN_23>, PioSpi<'static, PIN_25, PIO0, 0, DMA_CH0>>,
@@ -34,7 +43,7 @@ async fn main(spawner: Spawner) {
 
     let pwr = Output::new(p.PIN_23, Level::Low);
     let cs = Output::new(p.PIN_25, Level::High);
-    let mut pio = Pio::new(p.PIO0);
+    let mut pio = Pio::new(p.PIO0, Irqs);
     let spi = PioSpi::new(&mut pio.common, pio.sm0, pio.irq0, cs, p.PIN_24, p.PIN_29, p.DMA_CH0);
 
     let state = make_static!(cyw43::State::new());
diff --git a/examples/rp/src/bin/wifi_scan.rs b/examples/rp/src/bin/wifi_scan.rs
index 391e12282..743fab617 100644
--- a/examples/rp/src/bin/wifi_scan.rs
+++ b/examples/rp/src/bin/wifi_scan.rs
@@ -1,3 +1,6 @@
+//! This example uses the RP Pico W board Wifi chip (cyw43).
+//! Scans Wifi for ssid names.
+
 #![no_std]
 #![no_main]
 #![feature(type_alias_impl_trait)]
@@ -10,12 +13,17 @@ use cyw43_pio::PioSpi;
 use defmt::*;
 use embassy_executor::Spawner;
 use embassy_net::Stack;
+use embassy_rp::bind_interrupts;
 use embassy_rp::gpio::{Level, Output};
 use embassy_rp::peripherals::{DMA_CH0, PIN_23, PIN_25, PIO0};
-use embassy_rp::pio::Pio;
+use embassy_rp::pio::{InterruptHandler, Pio};
 use static_cell::make_static;
 use {defmt_rtt as _, panic_probe as _};
 
+bind_interrupts!(struct Irqs {
+    PIO0_IRQ_0 => InterruptHandler<PIO0>;
+});
+
 #[embassy_executor::task]
 async fn wifi_task(
     runner: cyw43::Runner<'static, Output<'static, PIN_23>, PioSpi<'static, PIN_25, PIO0, 0, DMA_CH0>>,
@@ -46,7 +54,7 @@ async fn main(spawner: Spawner) {
 
     let pwr = Output::new(p.PIN_23, Level::Low);
     let cs = Output::new(p.PIN_25, Level::High);
-    let mut pio = Pio::new(p.PIO0);
+    let mut pio = Pio::new(p.PIO0, Irqs);
     let spi = PioSpi::new(&mut pio.common, pio.sm0, pio.irq0, cs, p.PIN_24, p.PIN_29, p.DMA_CH0);
 
     let state = make_static!(cyw43::State::new());
diff --git a/examples/rp/src/bin/wifi_tcp_server.rs b/examples/rp/src/bin/wifi_tcp_server.rs
index e9d1079a6..0223a3636 100644
--- a/examples/rp/src/bin/wifi_tcp_server.rs
+++ b/examples/rp/src/bin/wifi_tcp_server.rs
@@ -1,3 +1,6 @@
+//! This example uses the RP Pico W board Wifi chip (cyw43).
+//! Connects to specified Wifi network and creates a TCP endpoint on port 1234.
+
 #![no_std]
 #![no_main]
 #![feature(type_alias_impl_trait)]
@@ -11,14 +14,22 @@ use defmt::*;
 use embassy_executor::Spawner;
 use embassy_net::tcp::TcpSocket;
 use embassy_net::{Config, Stack, StackResources};
+use embassy_rp::bind_interrupts;
 use embassy_rp::gpio::{Level, Output};
 use embassy_rp::peripherals::{DMA_CH0, PIN_23, PIN_25, PIO0};
-use embassy_rp::pio::Pio;
+use embassy_rp::pio::{InterruptHandler, Pio};
 use embassy_time::Duration;
 use embedded_io::asynch::Write;
 use static_cell::make_static;
 use {defmt_rtt as _, panic_probe as _};
 
+bind_interrupts!(struct Irqs {
+    PIO0_IRQ_0 => InterruptHandler<PIO0>;
+});
+
+const WIFI_NETWORK: &str = "EmbassyTest";
+const WIFI_PASSWORD: &str = "V8YxhKt5CdIAJFud";
+
 #[embassy_executor::task]
 async fn wifi_task(
     runner: cyw43::Runner<'static, Output<'static, PIN_23>, PioSpi<'static, PIN_25, PIO0, 0, DMA_CH0>>,
@@ -49,7 +60,7 @@ async fn main(spawner: Spawner) {
 
     let pwr = Output::new(p.PIN_23, Level::Low);
     let cs = Output::new(p.PIN_25, Level::High);
-    let mut pio = Pio::new(p.PIO0);
+    let mut pio = Pio::new(p.PIO0, Irqs);
     let spi = PioSpi::new(&mut pio.common, pio.sm0, pio.irq0, cs, p.PIN_24, p.PIN_29, p.DMA_CH0);
 
     let state = make_static!(cyw43::State::new());
@@ -82,8 +93,8 @@ async fn main(spawner: Spawner) {
     unwrap!(spawner.spawn(net_task(stack)));
 
     loop {
-        //control.join_open(env!("WIFI_NETWORK")).await;
-        match control.join_wpa2(env!("WIFI_NETWORK"), env!("WIFI_PASSWORD")).await {
+        //control.join_open(WIFI_NETWORK).await;
+        match control.join_wpa2(WIFI_NETWORK, WIFI_PASSWORD).await {
             Ok(_) => break,
             Err(err) => {
                 info!("join failed with status={}", err.status);
diff --git a/examples/std/Cargo.toml b/examples/std/Cargo.toml
index 878ad8c5a..92933ab50 100644
--- a/examples/std/Cargo.toml
+++ b/examples/std/Cargo.toml
@@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0"
 [dependencies]
 embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["log"] }
 embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["arch-std", "executor-thread", "log", "nightly", "integrated-timers"] }
-embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["log", "std", "nightly"] }
+embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["log", "std", "nightly"] }
 embassy-net = { version = "0.1.0", path = "../../embassy-net", features=[ "std", "nightly", "log", "medium-ethernet", "tcp", "udp", "dns", "dhcpv4", "unstable-traits", "proto-ipv6"] }
 embassy-net-driver = { version = "0.1.0", path = "../../embassy-net-driver" }
 embedded-io = { version = "0.4.0", features = ["async", "std", "futures"] }
diff --git a/examples/stm32c0/Cargo.toml b/examples/stm32c0/Cargo.toml
index 43f432520..26837abef 100644
--- a/examples/stm32c0/Cargo.toml
+++ b/examples/stm32c0/Cargo.toml
@@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0"
 [dependencies]
 embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] }
 embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] }
-embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
+embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
 embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "time-driver-any", "stm32c031c6", "memory-x", "unstable-pac", "exti"]  }
 
 defmt = "0.3"
diff --git a/examples/stm32f0/Cargo.toml b/examples/stm32f0/Cargo.toml
index 8d2248ed0..b7b5eaa99 100644
--- a/examples/stm32f0/Cargo.toml
+++ b/examples/stm32f0/Cargo.toml
@@ -14,6 +14,6 @@ defmt-rtt = "0.4"
 panic-probe = "0.3"
 embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] }
 embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] }
-embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
+embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
 embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "memory-x", "stm32f091rc", "time-driver-any", "exti", "unstable-pac"] }
 static_cell = { version = "1.1", features = ["nightly"]}
diff --git a/examples/stm32f1/Cargo.toml b/examples/stm32f1/Cargo.toml
index d34fd439a..29cad5b67 100644
--- a/examples/stm32f1/Cargo.toml
+++ b/examples/stm32f1/Cargo.toml
@@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0"
 [dependencies]
 embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] }
 embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] }
-embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
+embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
 embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32f103c8", "unstable-pac", "memory-x", "time-driver-any", "unstable-traits" ]  }
 embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] }
 embassy-futures = { version = "0.1.0", path = "../../embassy-futures" }
diff --git a/examples/stm32f2/Cargo.toml b/examples/stm32f2/Cargo.toml
index 5e3e0d0f7..652210c7f 100644
--- a/examples/stm32f2/Cargo.toml
+++ b/examples/stm32f2/Cargo.toml
@@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0"
 [dependencies]
 embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] }
 embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] }
-embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
+embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
 embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32f207zg", "unstable-pac", "memory-x", "time-driver-any", "exti"]  }
 
 defmt = "0.3"
diff --git a/examples/stm32f3/Cargo.toml b/examples/stm32f3/Cargo.toml
index 29ab2009c..489d0ff4c 100644
--- a/examples/stm32f3/Cargo.toml
+++ b/examples/stm32f3/Cargo.toml
@@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0"
 [dependencies]
 embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] }
 embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] }
-embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
+embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
 embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32f303ze", "unstable-pac", "memory-x", "time-driver-any", "exti"]  }
 embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] }
 embassy-futures = { version = "0.1.0", path = "../../embassy-futures" }
diff --git a/examples/stm32f4/Cargo.toml b/examples/stm32f4/Cargo.toml
index 7ecb64fce..c1c821364 100644
--- a/examples/stm32f4/Cargo.toml
+++ b/examples/stm32f4/Cargo.toml
@@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0"
 [dependencies]
 embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] }
 embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers", "arch-cortex-m", "executor-thread", "executor-interrupt"] }
-embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "unstable-traits", "tick-hz-32_768"] }
+embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "unstable-traits", "tick-hz-32_768"] }
 embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "unstable-traits", "defmt", "stm32f429zi", "unstable-pac", "memory-x", "time-driver-any", "exti", "embedded-sdmmc", "chrono"]  }
 embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] }
 embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "nightly"] }
diff --git a/examples/stm32f4/src/bin/can.rs b/examples/stm32f4/src/bin/can.rs
index da8955053..08bed88db 100644
--- a/examples/stm32f4/src/bin/can.rs
+++ b/examples/stm32f4/src/bin/can.rs
@@ -2,8 +2,8 @@
 #![no_main]
 #![feature(type_alias_impl_trait)]
 
-use cortex_m_rt::entry;
 use defmt::*;
+use embassy_executor::Spawner;
 use embassy_stm32::bind_interrupts;
 use embassy_stm32::can::bxcan::filter::Mask32;
 use embassy_stm32::can::bxcan::{Fifo, Frame, StandardId};
@@ -19,8 +19,8 @@ bind_interrupts!(struct Irqs {
     CAN1_TX => TxInterruptHandler<CAN1>;
 });
 
-#[entry]
-fn main() -> ! {
+#[embassy_executor::main]
+async fn main(_spawner: Spawner) {
     info!("Hello World!");
 
     let mut p = embassy_stm32::init(Default::default());
@@ -34,9 +34,12 @@ fn main() -> ! {
 
     let mut can = Can::new(p.CAN1, p.PA11, p.PA12, Irqs);
 
-    can.modify_filters().enable_bank(0, Fifo::Fifo0, Mask32::accept_all());
+    can.as_mut()
+        .modify_filters()
+        .enable_bank(0, Fifo::Fifo0, Mask32::accept_all());
 
-    can.modify_config()
+    can.as_mut()
+        .modify_config()
         .set_bit_timing(0x001c0003) // http://www.bittiming.can-wiki.info/
         .set_loopback(true) // Receive own frames
         .set_silent(true)
@@ -45,9 +48,8 @@ fn main() -> ! {
     let mut i: u8 = 0;
     loop {
         let tx_frame = Frame::new_data(unwrap!(StandardId::new(i as _)), [i]);
-        unwrap!(nb::block!(can.transmit(&tx_frame)));
-        while !can.is_transmitter_idle() {}
-        let rx_frame = unwrap!(nb::block!(can.receive()));
+        can.write(&tx_frame).await;
+        let (_, rx_frame) = can.read().await.unwrap();
         info!("loopback frame {=u8}", unwrap!(rx_frame.data())[0]);
         i += 1;
     }
diff --git a/examples/stm32f4/src/bin/eth.rs b/examples/stm32f4/src/bin/eth.rs
new file mode 100644
index 000000000..d0b164393
--- /dev/null
+++ b/examples/stm32f4/src/bin/eth.rs
@@ -0,0 +1,111 @@
+#![no_std]
+#![no_main]
+#![feature(type_alias_impl_trait)]
+
+use defmt::*;
+use embassy_executor::Spawner;
+use embassy_net::tcp::TcpSocket;
+use embassy_net::{Ipv4Address, Stack, StackResources};
+use embassy_stm32::eth::generic_smi::GenericSMI;
+use embassy_stm32::eth::{Ethernet, PacketQueue};
+use embassy_stm32::peripherals::ETH;
+use embassy_stm32::rng::Rng;
+use embassy_stm32::time::mhz;
+use embassy_stm32::{bind_interrupts, eth, Config};
+use embassy_time::{Duration, Timer};
+use embedded_io::asynch::Write;
+use static_cell::make_static;
+use {defmt_rtt as _, panic_probe as _};
+bind_interrupts!(struct Irqs {
+    ETH => eth::InterruptHandler;
+});
+
+type Device = Ethernet<'static, ETH, GenericSMI>;
+
+#[embassy_executor::task]
+async fn net_task(stack: &'static Stack<Device>) -> ! {
+    stack.run().await
+}
+
+#[embassy_executor::main]
+async fn main(spawner: Spawner) -> ! {
+    let mut config = Config::default();
+    config.rcc.sys_ck = Some(mhz(200));
+    let p = embassy_stm32::init(config);
+
+    info!("Hello World!");
+
+    // Generate random seed.
+    let mut rng = Rng::new(p.RNG);
+    let mut seed = [0; 8];
+    let _ = rng.async_fill_bytes(&mut seed).await;
+    let seed = u64::from_le_bytes(seed);
+
+    let mac_addr = [0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF];
+
+    let device = Ethernet::new(
+        make_static!(PacketQueue::<16, 16>::new()),
+        p.ETH,
+        Irqs,
+        p.PA1,
+        p.PA2,
+        p.PC1,
+        p.PA7,
+        p.PC4,
+        p.PC5,
+        p.PG13,
+        p.PB13,
+        p.PG11,
+        GenericSMI::new(),
+        mac_addr,
+        0,
+    );
+
+    let config = embassy_net::Config::dhcpv4(Default::default());
+    //let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 {
+    //    address: Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24),
+    //    dns_servers: Vec::new(),
+    //    gateway: Some(Ipv4Address::new(10, 42, 0, 1)),
+    //});
+
+    // Init network stack
+    let stack = &*make_static!(Stack::new(
+        device,
+        config,
+        make_static!(StackResources::<2>::new()),
+        seed
+    ));
+
+    // Launch network task
+    unwrap!(spawner.spawn(net_task(&stack)));
+
+    info!("Network task initialized");
+
+    // Then we can use it!
+    let mut rx_buffer = [0; 4096];
+    let mut tx_buffer = [0; 4096];
+
+    loop {
+        let mut socket = TcpSocket::new(&stack, &mut rx_buffer, &mut tx_buffer);
+
+        socket.set_timeout(Some(embassy_time::Duration::from_secs(10)));
+
+        let remote_endpoint = (Ipv4Address::new(10, 42, 0, 1), 8000);
+        info!("connecting...");
+        let r = socket.connect(remote_endpoint).await;
+        if let Err(e) = r {
+            info!("connect error: {:?}", e);
+            continue;
+        }
+        info!("connected!");
+        let buf = [0; 1024];
+        loop {
+            let r = socket.write_all(&buf).await;
+            if let Err(e) = r {
+                info!("write error: {:?}", e);
+                continue;
+            }
+            Timer::after(Duration::from_secs(1)).await;
+        }
+    }
+}
diff --git a/examples/stm32f7/Cargo.toml b/examples/stm32f7/Cargo.toml
index 657251c50..84d7b79c5 100644
--- a/examples/stm32f7/Cargo.toml
+++ b/examples/stm32f7/Cargo.toml
@@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0"
 [dependencies]
 embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] }
 embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] }
-embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
+embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
 embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32f767zi", "unstable-pac", "time-driver-any", "exti"]  }
 embassy-net = { path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet"] }
 embedded-io = { version = "0.4.0", features = ["async"] }
diff --git a/examples/stm32f7/src/bin/can.rs b/examples/stm32f7/src/bin/can.rs
new file mode 100644
index 000000000..1b5b377ea
--- /dev/null
+++ b/examples/stm32f7/src/bin/can.rs
@@ -0,0 +1,66 @@
+#![no_std]
+#![no_main]
+#![feature(type_alias_impl_trait)]
+
+use defmt::*;
+use embassy_executor::Spawner;
+use embassy_stm32::bind_interrupts;
+use embassy_stm32::can::bxcan::filter::Mask32;
+use embassy_stm32::can::bxcan::{Fifo, Frame, StandardId};
+use embassy_stm32::can::{
+    Can, CanTx, Rx0InterruptHandler, Rx1InterruptHandler, SceInterruptHandler, TxInterruptHandler,
+};
+use embassy_stm32::gpio::{Input, Pull};
+use embassy_stm32::peripherals::CAN3;
+use {defmt_rtt as _, panic_probe as _};
+
+bind_interrupts!(struct Irqs {
+    CAN3_RX0 => Rx0InterruptHandler<CAN3>;
+    CAN3_RX1 => Rx1InterruptHandler<CAN3>;
+    CAN3_SCE => SceInterruptHandler<CAN3>;
+    CAN3_TX => TxInterruptHandler<CAN3>;
+});
+
+#[embassy_executor::task]
+pub async fn send_can_message(tx: &'static mut CanTx<'static, 'static, CAN3>) {
+    loop {
+        let frame = Frame::new_data(unwrap!(StandardId::new(0 as _)), [0]);
+        tx.write(&frame).await;
+        embassy_time::Timer::after(embassy_time::Duration::from_secs(1)).await;
+    }
+}
+
+#[embassy_executor::main]
+async fn main(spawner: Spawner) {
+    info!("Hello World!");
+
+    let mut p = embassy_stm32::init(Default::default());
+
+    // The next two lines are a workaround for testing without transceiver.
+    // To synchronise to the bus the RX input needs to see a high level.
+    // Use `mem::forget()` to release the borrow on the pin but keep the
+    // pull-up resistor enabled.
+    let rx_pin = Input::new(&mut p.PA15, Pull::Up);
+    core::mem::forget(rx_pin);
+
+    let can: &'static mut Can<'static, CAN3> = static_cell::make_static!(Can::new(p.CAN3, p.PA8, p.PA15, Irqs));
+    can.as_mut()
+        .modify_filters()
+        .enable_bank(0, Fifo::Fifo0, Mask32::accept_all());
+
+    can.as_mut()
+        .modify_config()
+        .set_bit_timing(0x001c0001) // http://www.bittiming.can-wiki.info/
+        .set_loopback(true)
+        .enable();
+
+    let (tx, mut rx) = can.split();
+
+    let tx: &'static mut CanTx<'static, 'static, CAN3> = static_cell::make_static!(tx);
+    spawner.spawn(send_can_message(tx)).unwrap();
+
+    loop {
+        let frame = rx.read().await.unwrap();
+        println!("Received: {:?}", frame);
+    }
+}
diff --git a/examples/stm32f7/src/bin/eth.rs b/examples/stm32f7/src/bin/eth.rs
index fde6a7576..c6b2ba45c 100644
--- a/examples/stm32f7/src/bin/eth.rs
+++ b/examples/stm32f7/src/bin/eth.rs
@@ -57,7 +57,7 @@ async fn main(spawner: Spawner) -> ! {
         p.PG13,
         p.PB13,
         p.PG11,
-        GenericSMI,
+        GenericSMI::new(),
         mac_addr,
         0,
     );
diff --git a/examples/stm32g0/Cargo.toml b/examples/stm32g0/Cargo.toml
index c5245757b..c88282d91 100644
--- a/examples/stm32g0/Cargo.toml
+++ b/examples/stm32g0/Cargo.toml
@@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0"
 [dependencies]
 embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] }
 embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] }
-embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
+embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
 embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "time-driver-any", "stm32g071rb", "memory-x", "unstable-pac", "exti"]  }
 
 defmt = "0.3"
diff --git a/examples/stm32g4/Cargo.toml b/examples/stm32g4/Cargo.toml
index fbfbc6408..18bd03c39 100644
--- a/examples/stm32g4/Cargo.toml
+++ b/examples/stm32g4/Cargo.toml
@@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0"
 [dependencies]
 embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] }
 embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] }
-embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
+embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
 embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "time-driver-any", "stm32g491re", "memory-x", "unstable-pac", "exti"]  }
 embassy-hal-common = {version = "0.1.0", path = "../../embassy-hal-common" }
 embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] }
diff --git a/examples/stm32h5/Cargo.toml b/examples/stm32h5/Cargo.toml
index ebe511347..227bc28b4 100644
--- a/examples/stm32h5/Cargo.toml
+++ b/examples/stm32h5/Cargo.toml
@@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0"
 [dependencies]
 embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] }
 embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] }
-embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "unstable-traits", "tick-hz-32_768"] }
+embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "unstable-traits", "tick-hz-32_768"] }
 embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32h563zi", "time-driver-any", "exti", "unstable-pac", "unstable-traits"] }
 embassy-net = { path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet", "unstable-traits", "proto-ipv6"] }
 embedded-io = { version = "0.4.0", features = ["async"] }
@@ -19,8 +19,8 @@ defmt-rtt = "0.4"
 cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] }
 cortex-m-rt = "0.7.0"
 embedded-hal = "0.2.6"
-embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.10" }
-embedded-hal-async = { version = "=0.2.0-alpha.1" }
+embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.11" }
+embedded-hal-async = { version = "=0.2.0-alpha.2" }
 embedded-nal-async = "0.4.0"
 panic-probe = { version = "0.3", features = ["print-defmt"] }
 futures = { version = "0.3.17", default-features = false, features = ["async-await"] }
diff --git a/examples/stm32h5/src/bin/eth.rs b/examples/stm32h5/src/bin/eth.rs
index 78c8282a6..0bff85ed8 100644
--- a/examples/stm32h5/src/bin/eth.rs
+++ b/examples/stm32h5/src/bin/eth.rs
@@ -76,7 +76,7 @@ async fn main(spawner: Spawner) -> ! {
         p.PG13,
         p.PB15,
         p.PG11,
-        GenericSMI,
+        GenericSMI::new(),
         mac_addr,
         0,
     );
diff --git a/examples/stm32h7/Cargo.toml b/examples/stm32h7/Cargo.toml
index 62ef5e9e4..768702fa9 100644
--- a/examples/stm32h7/Cargo.toml
+++ b/examples/stm32h7/Cargo.toml
@@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0"
 [dependencies]
 embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] }
 embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] }
-embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "unstable-traits", "tick-hz-32_768"] }
+embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "unstable-traits", "tick-hz-32_768"] }
 embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32h743bi", "time-driver-any", "exti", "unstable-pac", "unstable-traits"] }
 embassy-net = { path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet", "unstable-traits", "proto-ipv6"] }
 embedded-io = { version = "0.4.0", features = ["async"] }
@@ -19,8 +19,8 @@ defmt-rtt = "0.4"
 cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] }
 cortex-m-rt = "0.7.0"
 embedded-hal = "0.2.6"
-embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.10" }
-embedded-hal-async = { version = "=0.2.0-alpha.1" }
+embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.11" }
+embedded-hal-async = { version = "=0.2.0-alpha.2" }
 embedded-nal-async = "0.4.0"
 panic-probe = { version = "0.3", features = ["print-defmt"] }
 futures = { version = "0.3.17", default-features = false, features = ["async-await"] }
diff --git a/examples/stm32h7/src/bin/eth.rs b/examples/stm32h7/src/bin/eth.rs
index 12d37f7a4..cfafcaed1 100644
--- a/examples/stm32h7/src/bin/eth.rs
+++ b/examples/stm32h7/src/bin/eth.rs
@@ -58,7 +58,7 @@ async fn main(spawner: Spawner) -> ! {
         p.PG13,
         p.PB13,
         p.PG11,
-        GenericSMI,
+        GenericSMI::new(),
         mac_addr,
         0,
     );
diff --git a/examples/stm32h7/src/bin/eth_client.rs b/examples/stm32h7/src/bin/eth_client.rs
index 6078fc3fe..4ed737578 100644
--- a/examples/stm32h7/src/bin/eth_client.rs
+++ b/examples/stm32h7/src/bin/eth_client.rs
@@ -59,7 +59,7 @@ async fn main(spawner: Spawner) -> ! {
         p.PG13,
         p.PB13,
         p.PG11,
-        GenericSMI,
+        GenericSMI::new(),
         mac_addr,
         0,
     );
diff --git a/examples/stm32l0/Cargo.toml b/examples/stm32l0/Cargo.toml
index 2ead714e4..747cec7bf 100644
--- a/examples/stm32l0/Cargo.toml
+++ b/examples/stm32l0/Cargo.toml
@@ -12,7 +12,7 @@ nightly = ["embassy-stm32/nightly", "embassy-time/nightly", "embassy-time/unstab
 [dependencies]
 embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] }
 embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] }
-embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
+embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
 embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32l072cz", "time-driver-any", "exti", "unstable-traits", "memory-x"]  }
 embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["time", "defmt"], optional = true }
 lora-phy = { version = "1", optional = true }
@@ -32,3 +32,6 @@ futures = { version = "0.3.17", default-features = false, features = ["async-awa
 heapless = { version = "0.7.5", default-features = false }
 embedded-hal = "0.2.6"
 static_cell = "1.1"
+
+[patch.crates-io]
+lora-phy = { git = "https://github.com/embassy-rs/lora-phy", rev = "ad289428fd44b02788e2fa2116445cc8f640a265" }
diff --git a/examples/stm32l1/Cargo.toml b/examples/stm32l1/Cargo.toml
index 93d48abeb..dcca1cc3d 100644
--- a/examples/stm32l1/Cargo.toml
+++ b/examples/stm32l1/Cargo.toml
@@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0"
 [dependencies]
 embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] }
 embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] }
-embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
+embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
 embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32l151cb-a", "time-driver-any", "memory-x"]  }
 
 defmt = "0.3"
diff --git a/examples/stm32l4/Cargo.toml b/examples/stm32l4/Cargo.toml
index 3bb473ef5..c55558518 100644
--- a/examples/stm32l4/Cargo.toml
+++ b/examples/stm32l4/Cargo.toml
@@ -7,9 +7,9 @@ license = "MIT OR Apache-2.0"
 [dependencies]
 embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] }
 embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] }
-embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
+embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
 embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" }
-embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "unstable-pac", "stm32l4s5vi", "time-driver-any", "exti", "unstable-traits"]  }
+embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "unstable-pac", "stm32l4s5vi", "time-driver-any", "exti", "unstable-traits", "chrono"]  }
 embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] }
 
 defmt = "0.3"
@@ -18,10 +18,11 @@ defmt-rtt = "0.4"
 cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] }
 cortex-m-rt = "0.7.0"
 embedded-hal = "0.2.6"
-embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.10" }
-embedded-hal-async = { version = "=0.2.0-alpha.1" }
+embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.11" }
+embedded-hal-async = { version = "=0.2.0-alpha.2" }
 panic-probe = { version = "0.3", features = ["print-defmt"] }
 futures = { version = "0.3.17", default-features = false, features = ["async-await"] }
 heapless = { version = "0.7.5", default-features = false }
+chrono = { version = "^0.4", default-features = false }
 
 micromath = "2.0.0"
diff --git a/examples/stm32l4/src/bin/rtc.rs b/examples/stm32l4/src/bin/rtc.rs
new file mode 100644
index 000000000..d72d5ddb6
--- /dev/null
+++ b/examples/stm32l4/src/bin/rtc.rs
@@ -0,0 +1,49 @@
+#![no_std]
+#![no_main]
+#![feature(type_alias_impl_trait)]
+
+use chrono::{NaiveDate, NaiveDateTime};
+use defmt::*;
+use embassy_executor::Spawner;
+use embassy_stm32::rcc::{self, ClockSrc, PLLClkDiv, PLLMul, PLLSource, PLLSrcDiv};
+use embassy_stm32::rtc::{Rtc, RtcConfig};
+use embassy_stm32::time::Hertz;
+use embassy_stm32::Config;
+use embassy_time::{Duration, Timer};
+use {defmt_rtt as _, panic_probe as _};
+
+#[embassy_executor::main]
+async fn main(_spawner: Spawner) {
+    let p = {
+        let mut config = Config::default();
+        config.rcc.mux = ClockSrc::PLL(
+            PLLSource::HSE(Hertz::mhz(8)),
+            PLLClkDiv::Div2,
+            PLLSrcDiv::Div1,
+            PLLMul::Mul20,
+            None,
+        );
+        config.rcc.rtc_mux = rcc::RtcClockSource::LSE32;
+        embassy_stm32::init(config)
+    };
+    info!("Hello World!");
+
+    let now = NaiveDate::from_ymd_opt(2020, 5, 15)
+        .unwrap()
+        .and_hms_opt(10, 30, 15)
+        .unwrap();
+
+    let mut rtc = Rtc::new(
+        p.RTC,
+        RtcConfig::default().clock_config(embassy_stm32::rtc::RtcClockSource::LSE),
+    );
+    info!("Got RTC! {:?}", now.timestamp());
+
+    rtc.set_datetime(now.into()).expect("datetime not set");
+
+    // In reality the delay would be much longer
+    Timer::after(Duration::from_millis(20000)).await;
+
+    let then: NaiveDateTime = rtc.now().unwrap().into();
+    info!("Got RTC! {:?}", then.timestamp());
+}
diff --git a/examples/stm32l5/Cargo.toml b/examples/stm32l5/Cargo.toml
index 6035c291f..54911482e 100644
--- a/examples/stm32l5/Cargo.toml
+++ b/examples/stm32l5/Cargo.toml
@@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0"
 [dependencies]
 embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] }
 embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] }
-embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
+embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
 embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "unstable-pac", "stm32l552ze", "time-driver-any", "exti", "unstable-traits", "memory-x"]  }
 embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] }
 embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet"] }
diff --git a/examples/stm32u5/Cargo.toml b/examples/stm32u5/Cargo.toml
index e2318c3d6..835e32940 100644
--- a/examples/stm32u5/Cargo.toml
+++ b/examples/stm32u5/Cargo.toml
@@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0"
 [dependencies]
 embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] }
 embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] }
-embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
+embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
 embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "unstable-pac", "stm32u585ai", "time-driver-any", "memory-x" ]  }
 embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] }
 
diff --git a/examples/stm32wb/.cargo/config.toml b/examples/stm32wb/.cargo/config.toml
index 8b6d6d754..51c499ee7 100644
--- a/examples/stm32wb/.cargo/config.toml
+++ b/examples/stm32wb/.cargo/config.toml
@@ -1,6 +1,6 @@
 [target.'cfg(all(target_arch = "arm", target_os = "none"))']
 # replace STM32WB55CCUx with your chip as listed in `probe-rs chip list`
-# runner = "probe-rs run --chip STM32WB55RGVx --speed 1000 --connect-under-reset"
+# runner = "probe-run --chip STM32WB55RGVx --speed 1000 --connect-under-reset"
 runner = "teleprobe local run --chip STM32WB55RG --elf"
 
 [build]
diff --git a/examples/stm32wb/Cargo.toml b/examples/stm32wb/Cargo.toml
index fbb2d918b..7c0b83e65 100644
--- a/examples/stm32wb/Cargo.toml
+++ b/examples/stm32wb/Cargo.toml
@@ -7,9 +7,10 @@ license = "MIT OR Apache-2.0"
 [dependencies]
 embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] }
 embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] }
-embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
+embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
 embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32wb55rg", "time-driver-any", "memory-x", "exti"]  }
 embassy-stm32-wpan = { version = "0.1.0", path = "../../embassy-stm32-wpan", features = ["defmt", "stm32wb55rg"] }
+embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "udp", "medium-ieee802154", "nightly"], optional=true }
 
 defmt = "0.3"
 defmt-rtt = "0.4"
@@ -20,11 +21,11 @@ embedded-hal = "0.2.6"
 panic-probe = { version = "0.3", features = ["print-defmt"] }
 futures = { version = "0.3.17", default-features = false, features = ["async-await"] }
 heapless = { version = "0.7.5", default-features = false }
-
+static_cell = { version = "1.1", features = ["nightly"]}
 
 [features]
-default = ["ble"]
-mac = ["embassy-stm32-wpan/mac"]
+default = ["ble", "mac"]
+mac = ["embassy-stm32-wpan/mac", "dep:embassy-net"]
 ble = ["embassy-stm32-wpan/ble"]
 
 [[bin]] 
@@ -35,9 +36,18 @@ required-features = ["ble"]
 name = "tl_mbox_mac"
 required-features = ["mac"]
 
+[[bin]] 
+name = "mac_ffd"
+required-features = ["mac"]
+
+[[bin]] 
+name = "mac_ffd_net"
+required-features = ["mac"]
+
 [[bin]] 
 name = "eddystone_beacon"
 required-features = ["ble"]
 
-[patch.crates-io]
-stm32wb-hci = { git = "https://github.com/OueslatiGhaith/stm32wb-hci", rev = "9f663be"}
\ No newline at end of file
+[[bin]] 
+name = "gatt_server"
+required-features = ["ble"]
\ No newline at end of file
diff --git a/examples/stm32wb/src/bin/eddystone_beacon.rs b/examples/stm32wb/src/bin/eddystone_beacon.rs
index b99f8cb2e..451bd7d29 100644
--- a/examples/stm32wb/src/bin/eddystone_beacon.rs
+++ b/examples/stm32wb/src/bin/eddystone_beacon.rs
@@ -63,7 +63,7 @@ async fn main(_spawner: Spawner) {
     let sys_event = mbox.sys_subsystem.read().await;
     info!("sys event: {}", sys_event.payload());
 
-    mbox.sys_subsystem.shci_c2_ble_init(Default::default()).await;
+    let _ = mbox.sys_subsystem.shci_c2_ble_init(Default::default()).await;
 
     info!("resetting BLE...");
     mbox.ble_subsystem.reset().await;
diff --git a/examples/stm32wb/src/bin/gatt_server.rs b/examples/stm32wb/src/bin/gatt_server.rs
new file mode 100644
index 000000000..0f6419d45
--- /dev/null
+++ b/examples/stm32wb/src/bin/gatt_server.rs
@@ -0,0 +1,397 @@
+#![no_std]
+#![no_main]
+#![feature(type_alias_impl_trait)]
+
+use core::time::Duration;
+
+use defmt::*;
+use embassy_executor::Spawner;
+use embassy_stm32::bind_interrupts;
+use embassy_stm32::ipcc::{Config, ReceiveInterruptHandler, TransmitInterruptHandler};
+use embassy_stm32_wpan::hci::event::command::{CommandComplete, ReturnParameters};
+use embassy_stm32_wpan::hci::host::uart::{Packet, UartHci};
+use embassy_stm32_wpan::hci::host::{AdvertisingFilterPolicy, EncryptionKey, HostHci, OwnAddressType};
+use embassy_stm32_wpan::hci::types::AdvertisingType;
+use embassy_stm32_wpan::hci::vendor::stm32wb::command::gap::{
+    AddressType, AuthenticationRequirements, DiscoverableParameters, GapCommands, IoCapability, LocalName, Pin, Role,
+    SecureConnectionSupport,
+};
+use embassy_stm32_wpan::hci::vendor::stm32wb::command::gatt::{
+    AddCharacteristicParameters, AddServiceParameters, CharacteristicEvent, CharacteristicPermission,
+    CharacteristicProperty, EncryptionKeySize, GattCommands, ServiceType, UpdateCharacteristicValueParameters, Uuid,
+    WriteResponseParameters,
+};
+use embassy_stm32_wpan::hci::vendor::stm32wb::command::hal::{ConfigData, HalCommands, PowerLevel};
+use embassy_stm32_wpan::hci::vendor::stm32wb::event::{self, AttributeHandle, Stm32Wb5xEvent};
+use embassy_stm32_wpan::hci::{BdAddr, Event};
+use embassy_stm32_wpan::lhci::LhciC1DeviceInformationCcrp;
+use embassy_stm32_wpan::sub::ble::Ble;
+use embassy_stm32_wpan::TlMbox;
+use {defmt_rtt as _, panic_probe as _};
+
+bind_interrupts!(struct Irqs{
+    IPCC_C1_RX => ReceiveInterruptHandler;
+    IPCC_C1_TX => TransmitInterruptHandler;
+});
+
+const BLE_GAP_DEVICE_NAME_LENGTH: u8 = 7;
+
+#[embassy_executor::main]
+async fn main(_spawner: Spawner) {
+    /*
+        How to make this work:
+
+        - Obtain a NUCLEO-STM32WB55 from your preferred supplier.
+        - Download and Install STM32CubeProgrammer.
+        - Download stm32wb5x_FUS_fw.bin, stm32wb5x_BLE_Stack_full_fw.bin, and Release_Notes.html from
+          gh:STMicroelectronics/STM32CubeWB@2234d97/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x
+        - Open STM32CubeProgrammer
+        - On the right-hand pane, click "firmware upgrade" to upgrade the st-link firmware.
+        - Once complete, click connect to connect to the device.
+        - On the left hand pane, click the RSS signal icon to open "Firmware Upgrade Services".
+        - In the Release_Notes.html, find the memory address that corresponds to your device for the stm32wb5x_FUS_fw.bin file
+        - Select that file, the memory address, "verify download", and then "Firmware Upgrade".
+        - Once complete, in the Release_Notes.html, find the memory address that corresponds to your device for the
+          stm32wb5x_BLE_Stack_full_fw.bin file. It should not be the same memory address.
+        - Select that file, the memory address, "verify download", and then "Firmware Upgrade".
+        - Select "Start Wireless Stack".
+        - Disconnect from the device.
+        - In the examples folder for stm32wb, modify the memory.x file to match your target device.
+        - Run this example.
+
+        Note: extended stack versions are not supported at this time. Do not attempt to install a stack with "extended" in the name.
+    */
+
+    let p = embassy_stm32::init(Default::default());
+    info!("Hello World!");
+
+    let config = Config::default();
+    let mut mbox = TlMbox::init(p.IPCC, Irqs, config);
+
+    let sys_event = mbox.sys_subsystem.read().await;
+    info!("sys event: {}", sys_event.payload());
+
+    let _ = mbox.sys_subsystem.shci_c2_ble_init(Default::default()).await;
+
+    info!("resetting BLE...");
+    mbox.ble_subsystem.reset().await;
+    let response = mbox.ble_subsystem.read().await;
+    defmt::debug!("{}", response);
+
+    info!("config public address...");
+    mbox.ble_subsystem
+        .write_config_data(&ConfigData::public_address(get_bd_addr()).build())
+        .await;
+    let response = mbox.ble_subsystem.read().await;
+    defmt::debug!("{}", response);
+
+    info!("config random address...");
+    mbox.ble_subsystem
+        .write_config_data(&ConfigData::random_address(get_random_addr()).build())
+        .await;
+    let response = mbox.ble_subsystem.read().await;
+    defmt::debug!("{}", response);
+
+    info!("config identity root...");
+    mbox.ble_subsystem
+        .write_config_data(&ConfigData::identity_root(&get_irk()).build())
+        .await;
+    let response = mbox.ble_subsystem.read().await;
+    defmt::debug!("{}", response);
+
+    info!("config encryption root...");
+    mbox.ble_subsystem
+        .write_config_data(&ConfigData::encryption_root(&get_erk()).build())
+        .await;
+    let response = mbox.ble_subsystem.read().await;
+    defmt::debug!("{}", response);
+
+    info!("config tx power level...");
+    mbox.ble_subsystem.set_tx_power_level(PowerLevel::ZerodBm).await;
+    let response = mbox.ble_subsystem.read().await;
+    defmt::debug!("{}", response);
+
+    info!("GATT init...");
+    mbox.ble_subsystem.init_gatt().await;
+    let response = mbox.ble_subsystem.read().await;
+    defmt::debug!("{}", response);
+
+    info!("GAP init...");
+    mbox.ble_subsystem
+        .init_gap(Role::PERIPHERAL, false, BLE_GAP_DEVICE_NAME_LENGTH)
+        .await;
+    let response = mbox.ble_subsystem.read().await;
+    defmt::debug!("{}", response);
+
+    info!("set IO capabilities...");
+    mbox.ble_subsystem.set_io_capability(IoCapability::DisplayConfirm).await;
+    let response = mbox.ble_subsystem.read().await;
+    defmt::debug!("{}", response);
+
+    info!("set authentication requirements...");
+    mbox.ble_subsystem
+        .set_authentication_requirement(&AuthenticationRequirements {
+            bonding_required: false,
+            keypress_notification_support: false,
+            mitm_protection_required: false,
+            encryption_key_size_range: (8, 16),
+            fixed_pin: Pin::Requested,
+            identity_address_type: AddressType::Public,
+            secure_connection_support: SecureConnectionSupport::Optional,
+        })
+        .await
+        .unwrap();
+    let response = mbox.ble_subsystem.read().await;
+    defmt::debug!("{}", response);
+
+    info!("set scan response data...");
+    mbox.ble_subsystem.le_set_scan_response_data(b"TXTX").await.unwrap();
+    let response = mbox.ble_subsystem.read().await;
+    defmt::debug!("{}", response);
+
+    info!("set scan response data...");
+    mbox.ble_subsystem.le_set_scan_response_data(b"TXTX").await.unwrap();
+    let response = mbox.ble_subsystem.read().await;
+    defmt::debug!("{}", response);
+
+    defmt::info!("initializing services and characteristics...");
+    let mut ble_context = init_gatt_services(&mut mbox.ble_subsystem).await.unwrap();
+    defmt::info!("{}", ble_context);
+
+    let discovery_params = DiscoverableParameters {
+        advertising_type: AdvertisingType::ConnectableUndirected,
+        advertising_interval: Some((Duration::from_millis(100), Duration::from_millis(100))),
+        address_type: OwnAddressType::Public,
+        filter_policy: AdvertisingFilterPolicy::AllowConnectionAndScan,
+        local_name: Some(LocalName::Complete(b"TXTX")),
+        advertising_data: &[],
+        conn_interval: (None, None),
+    };
+
+    info!("set discoverable...");
+    mbox.ble_subsystem.set_discoverable(&discovery_params).await.unwrap();
+    let response = mbox.ble_subsystem.read().await;
+    defmt::debug!("{}", response);
+
+    loop {
+        let response = mbox.ble_subsystem.read().await;
+        defmt::debug!("{}", response);
+
+        if let Ok(Packet::Event(event)) = response {
+            match event {
+                Event::LeConnectionComplete(_) => {
+                    defmt::info!("connected");
+                }
+                Event::DisconnectionComplete(_) => {
+                    defmt::info!("disconnected");
+                    ble_context.is_subscribed = false;
+                    mbox.ble_subsystem.set_discoverable(&discovery_params).await.unwrap();
+                }
+                Event::Vendor(vendor_event) => match vendor_event {
+                    Stm32Wb5xEvent::AttReadPermitRequest(read_req) => {
+                        defmt::info!("read request received {}, allowing", read_req);
+                        mbox.ble_subsystem.allow_read(read_req.conn_handle).await
+                    }
+                    Stm32Wb5xEvent::AttWritePermitRequest(write_req) => {
+                        defmt::info!("write request received {}, allowing", write_req);
+                        mbox.ble_subsystem
+                            .write_response(&WriteResponseParameters {
+                                conn_handle: write_req.conn_handle,
+                                attribute_handle: write_req.attribute_handle,
+                                status: Ok(()),
+                                value: write_req.value(),
+                            })
+                            .await
+                            .unwrap()
+                    }
+                    Stm32Wb5xEvent::GattAttributeModified(attribute) => {
+                        defmt::info!("{}", ble_context);
+                        if attribute.attr_handle.0 == ble_context.chars.notify.0 + 2 {
+                            if attribute.data()[0] == 0x01 {
+                                defmt::info!("subscribed");
+                                ble_context.is_subscribed = true;
+                            } else {
+                                defmt::info!("unsubscribed");
+                                ble_context.is_subscribed = false;
+                            }
+                        }
+                    }
+                    _ => {}
+                },
+                _ => {}
+            }
+        }
+    }
+}
+
+fn get_bd_addr() -> BdAddr {
+    let mut bytes = [0u8; 6];
+
+    let lhci_info = LhciC1DeviceInformationCcrp::new();
+    bytes[0] = (lhci_info.uid64 & 0xff) as u8;
+    bytes[1] = ((lhci_info.uid64 >> 8) & 0xff) as u8;
+    bytes[2] = ((lhci_info.uid64 >> 16) & 0xff) as u8;
+    bytes[3] = lhci_info.device_type_id;
+    bytes[4] = (lhci_info.st_company_id & 0xff) as u8;
+    bytes[5] = (lhci_info.st_company_id >> 8 & 0xff) as u8;
+
+    BdAddr(bytes)
+}
+
+fn get_random_addr() -> BdAddr {
+    let mut bytes = [0u8; 6];
+
+    let lhci_info = LhciC1DeviceInformationCcrp::new();
+    bytes[0] = (lhci_info.uid64 & 0xff) as u8;
+    bytes[1] = ((lhci_info.uid64 >> 8) & 0xff) as u8;
+    bytes[2] = ((lhci_info.uid64 >> 16) & 0xff) as u8;
+    bytes[3] = 0;
+    bytes[4] = 0x6E;
+    bytes[5] = 0xED;
+
+    BdAddr(bytes)
+}
+
+const BLE_CFG_IRK: [u8; 16] = [
+    0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0,
+];
+const BLE_CFG_ERK: [u8; 16] = [
+    0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21, 0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21,
+];
+
+fn get_irk() -> EncryptionKey {
+    EncryptionKey(BLE_CFG_IRK)
+}
+
+fn get_erk() -> EncryptionKey {
+    EncryptionKey(BLE_CFG_ERK)
+}
+
+#[derive(defmt::Format)]
+pub struct BleContext {
+    pub service_handle: AttributeHandle,
+    pub chars: CharHandles,
+    pub is_subscribed: bool,
+}
+
+#[derive(defmt::Format)]
+pub struct CharHandles {
+    pub read: AttributeHandle,
+    pub write: AttributeHandle,
+    pub notify: AttributeHandle,
+}
+
+pub async fn init_gatt_services(ble_subsystem: &mut Ble) -> Result<BleContext, ()> {
+    let service_handle = gatt_add_service(ble_subsystem, Uuid::Uuid16(0x500)).await?;
+
+    let read = gatt_add_char(
+        ble_subsystem,
+        service_handle,
+        Uuid::Uuid16(0x501),
+        CharacteristicProperty::READ,
+        Some(b"Hello from embassy!"),
+    )
+    .await?;
+
+    let write = gatt_add_char(
+        ble_subsystem,
+        service_handle,
+        Uuid::Uuid16(0x502),
+        CharacteristicProperty::WRITE_WITHOUT_RESPONSE | CharacteristicProperty::WRITE | CharacteristicProperty::READ,
+        None,
+    )
+    .await?;
+
+    let notify = gatt_add_char(
+        ble_subsystem,
+        service_handle,
+        Uuid::Uuid16(0x503),
+        CharacteristicProperty::NOTIFY | CharacteristicProperty::READ,
+        None,
+    )
+    .await?;
+
+    Ok(BleContext {
+        service_handle,
+        is_subscribed: false,
+        chars: CharHandles { read, write, notify },
+    })
+}
+
+async fn gatt_add_service(ble_subsystem: &mut Ble, uuid: Uuid) -> Result<AttributeHandle, ()> {
+    ble_subsystem
+        .add_service(&AddServiceParameters {
+            uuid,
+            service_type: ServiceType::Primary,
+            max_attribute_records: 8,
+        })
+        .await;
+    let response = ble_subsystem.read().await;
+    defmt::debug!("{}", response);
+
+    if let Ok(Packet::Event(Event::CommandComplete(CommandComplete {
+        return_params:
+            ReturnParameters::Vendor(event::command::ReturnParameters::GattAddService(event::command::GattService {
+                service_handle,
+                ..
+            })),
+        ..
+    }))) = response
+    {
+        Ok(service_handle)
+    } else {
+        Err(())
+    }
+}
+
+async fn gatt_add_char(
+    ble_subsystem: &mut Ble,
+    service_handle: AttributeHandle,
+    characteristic_uuid: Uuid,
+    characteristic_properties: CharacteristicProperty,
+    default_value: Option<&[u8]>,
+) -> Result<AttributeHandle, ()> {
+    ble_subsystem
+        .add_characteristic(&AddCharacteristicParameters {
+            service_handle,
+            characteristic_uuid,
+            characteristic_properties,
+            characteristic_value_len: 32,
+            security_permissions: CharacteristicPermission::empty(),
+            gatt_event_mask: CharacteristicEvent::all(),
+            encryption_key_size: EncryptionKeySize::with_value(7).unwrap(),
+            is_variable: true,
+        })
+        .await;
+    let response = ble_subsystem.read().await;
+    defmt::debug!("{}", response);
+
+    if let Ok(Packet::Event(Event::CommandComplete(CommandComplete {
+        return_params:
+            ReturnParameters::Vendor(event::command::ReturnParameters::GattAddCharacteristic(
+                event::command::GattCharacteristic {
+                    characteristic_handle, ..
+                },
+            )),
+        ..
+    }))) = response
+    {
+        if let Some(value) = default_value {
+            ble_subsystem
+                .update_characteristic_value(&UpdateCharacteristicValueParameters {
+                    service_handle,
+                    characteristic_handle,
+                    offset: 0,
+                    value,
+                })
+                .await
+                .unwrap();
+
+            let response = ble_subsystem.read().await;
+            defmt::debug!("{}", response);
+        }
+        Ok(characteristic_handle)
+    } else {
+        Err(())
+    }
+}
diff --git a/examples/stm32wb/src/bin/mac_ffd.rs b/examples/stm32wb/src/bin/mac_ffd.rs
new file mode 100644
index 000000000..1379ac6ba
--- /dev/null
+++ b/examples/stm32wb/src/bin/mac_ffd.rs
@@ -0,0 +1,185 @@
+#![no_std]
+#![no_main]
+#![feature(type_alias_impl_trait)]
+
+use defmt::*;
+use embassy_executor::Spawner;
+use embassy_stm32::bind_interrupts;
+use embassy_stm32::ipcc::{Config, ReceiveInterruptHandler, TransmitInterruptHandler};
+use embassy_stm32_wpan::mac::commands::{AssociateResponse, ResetRequest, SetRequest, StartRequest};
+use embassy_stm32_wpan::mac::event::MacEvent;
+use embassy_stm32_wpan::mac::typedefs::{MacChannel, MacStatus, PanId, PibId, SecurityLevel};
+use embassy_stm32_wpan::sub::mm;
+use embassy_stm32_wpan::TlMbox;
+use {defmt_rtt as _, panic_probe as _};
+
+bind_interrupts!(struct Irqs{
+    IPCC_C1_RX => ReceiveInterruptHandler;
+    IPCC_C1_TX => TransmitInterruptHandler;
+});
+
+#[embassy_executor::task]
+async fn run_mm_queue(memory_manager: mm::MemoryManager) {
+    memory_manager.run_queue().await;
+}
+
+#[embassy_executor::main]
+async fn main(spawner: Spawner) {
+    /*
+        How to make this work:
+
+        - Obtain a NUCLEO-STM32WB55 from your preferred supplier.
+        - Download and Install STM32CubeProgrammer.
+        - Download stm32wb5x_FUS_fw.bin, stm32wb5x_BLE_Stack_full_fw.bin, and Release_Notes.html from
+          gh:STMicroelectronics/STM32CubeWB@2234d97/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x
+        - Open STM32CubeProgrammer
+        - On the right-hand pane, click "firmware upgrade" to upgrade the st-link firmware.
+        - Once complete, click connect to connect to the device.
+        - On the left hand pane, click the RSS signal icon to open "Firmware Upgrade Services".
+        - In the Release_Notes.html, find the memory address that corresponds to your device for the stm32wb5x_FUS_fw.bin file
+        - Select that file, the memory address, "verify download", and then "Firmware Upgrade".
+        - Once complete, in the Release_Notes.html, find the memory address that corresponds to your device for the
+          stm32wb5x_BLE_Stack_full_fw.bin file. It should not be the same memory address.
+        - Select that file, the memory address, "verify download", and then "Firmware Upgrade".
+        - Select "Start Wireless Stack".
+        - Disconnect from the device.
+        - In the examples folder for stm32wb, modify the memory.x file to match your target device.
+        - Run this example.
+
+        Note: extended stack versions are not supported at this time. Do not attempt to install a stack with "extended" in the name.
+    */
+
+    let p = embassy_stm32::init(Default::default());
+    info!("Hello World!");
+
+    let config = Config::default();
+    let mbox = TlMbox::init(p.IPCC, Irqs, config);
+
+    spawner.spawn(run_mm_queue(mbox.mm_subsystem)).unwrap();
+
+    let sys_event = mbox.sys_subsystem.read().await;
+    info!("sys event: {}", sys_event.payload());
+
+    core::mem::drop(sys_event);
+
+    let result = mbox.sys_subsystem.shci_c2_mac_802_15_4_init().await;
+    info!("initialized mac: {}", result);
+
+    info!("resetting");
+    mbox.mac_subsystem
+        .send_command(&ResetRequest {
+            set_default_pib: true,
+            ..Default::default()
+        })
+        .await
+        .unwrap();
+    defmt::info!("{:#x}", mbox.mac_subsystem.read().await.unwrap());
+
+    info!("setting extended address");
+    let extended_address: u64 = 0xACDE480000000001;
+    mbox.mac_subsystem
+        .send_command(&SetRequest {
+            pib_attribute_ptr: &extended_address as *const _ as *const u8,
+            pib_attribute: PibId::ExtendedAddress,
+        })
+        .await
+        .unwrap();
+    defmt::info!("{:#x}", mbox.mac_subsystem.read().await.unwrap());
+
+    info!("setting short address");
+    let short_address: u16 = 0x1122;
+    mbox.mac_subsystem
+        .send_command(&SetRequest {
+            pib_attribute_ptr: &short_address as *const _ as *const u8,
+            pib_attribute: PibId::ShortAddress,
+        })
+        .await
+        .unwrap();
+    defmt::info!("{:#x}", mbox.mac_subsystem.read().await.unwrap());
+
+    info!("setting association permit");
+    let association_permit: bool = true;
+    mbox.mac_subsystem
+        .send_command(&SetRequest {
+            pib_attribute_ptr: &association_permit as *const _ as *const u8,
+            pib_attribute: PibId::AssociationPermit,
+        })
+        .await
+        .unwrap();
+    defmt::info!("{:#x}", mbox.mac_subsystem.read().await.unwrap());
+
+    info!("setting TX power");
+    let transmit_power: i8 = 2;
+    mbox.mac_subsystem
+        .send_command(&SetRequest {
+            pib_attribute_ptr: &transmit_power as *const _ as *const u8,
+            pib_attribute: PibId::TransmitPower,
+        })
+        .await
+        .unwrap();
+    defmt::info!("{:#x}", mbox.mac_subsystem.read().await.unwrap());
+
+    info!("starting FFD device");
+    mbox.mac_subsystem
+        .send_command(&StartRequest {
+            pan_id: PanId([0x1A, 0xAA]),
+            channel_number: MacChannel::Channel16,
+            beacon_order: 0x0F,
+            superframe_order: 0x0F,
+            pan_coordinator: true,
+            battery_life_extension: false,
+            ..Default::default()
+        })
+        .await
+        .unwrap();
+    defmt::info!("{:#x}", mbox.mac_subsystem.read().await.unwrap());
+
+    info!("setting RX on when idle");
+    let rx_on_while_idle: bool = true;
+    mbox.mac_subsystem
+        .send_command(&SetRequest {
+            pib_attribute_ptr: &rx_on_while_idle as *const _ as *const u8,
+            pib_attribute: PibId::RxOnWhenIdle,
+        })
+        .await
+        .unwrap();
+    defmt::info!("{:#x}", mbox.mac_subsystem.read().await.unwrap());
+
+    loop {
+        let evt = mbox.mac_subsystem.read().await;
+        if let Ok(evt) = evt {
+            defmt::info!("parsed mac event");
+            defmt::info!("{:#x}", evt);
+
+            match evt {
+                MacEvent::MlmeAssociateInd(association) => mbox
+                    .mac_subsystem
+                    .send_command(&AssociateResponse {
+                        device_address: association.device_address,
+                        assoc_short_address: [0x33, 0x44],
+                        status: MacStatus::Success,
+                        security_level: SecurityLevel::Unsecure,
+                        ..Default::default()
+                    })
+                    .await
+                    .unwrap(),
+                MacEvent::McpsDataInd(data_ind) => {
+                    let payload = data_ind.payload();
+                    let ref_payload = b"Hello from embassy!";
+                    info!("{}", payload);
+
+                    if payload == ref_payload {
+                        info!("success");
+                    } else {
+                        info!("ref payload: {}", ref_payload);
+                    }
+                }
+                _ => {
+                    defmt::info!("other mac event");
+                }
+            }
+        } else {
+            defmt::info!("failed to parse mac event");
+        }
+    }
+}
diff --git a/examples/stm32wb/src/bin/mac_ffd_net.rs b/examples/stm32wb/src/bin/mac_ffd_net.rs
new file mode 100644
index 000000000..bbcd0a70f
--- /dev/null
+++ b/examples/stm32wb/src/bin/mac_ffd_net.rs
@@ -0,0 +1,170 @@
+#![no_std]
+#![no_main]
+#![feature(type_alias_impl_trait)]
+
+use defmt::*;
+use embassy_executor::Spawner;
+use embassy_stm32::bind_interrupts;
+use embassy_stm32::ipcc::{Config, ReceiveInterruptHandler, TransmitInterruptHandler};
+use embassy_stm32_wpan::mac::commands::{ResetRequest, SetRequest, StartRequest};
+use embassy_stm32_wpan::mac::typedefs::{MacChannel, PanId, PibId};
+use embassy_stm32_wpan::mac::{self, Runner};
+use embassy_stm32_wpan::sub::mm;
+use embassy_stm32_wpan::TlMbox;
+use static_cell::make_static;
+use {defmt_rtt as _, panic_probe as _};
+
+bind_interrupts!(struct Irqs{
+    IPCC_C1_RX => ReceiveInterruptHandler;
+    IPCC_C1_TX => TransmitInterruptHandler;
+});
+
+#[embassy_executor::task]
+async fn run_mm_queue(memory_manager: mm::MemoryManager) {
+    memory_manager.run_queue().await;
+}
+
+#[embassy_executor::task]
+async fn run_mac(runner: &'static Runner<'static>) {
+    runner.run().await;
+}
+
+#[embassy_executor::main]
+async fn main(spawner: Spawner) {
+    /*
+        How to make this work:
+
+        - Obtain a NUCLEO-STM32WB55 from your preferred supplier.
+        - Download and Install STM32CubeProgrammer.
+        - Download stm32wb5x_FUS_fw.bin, stm32wb5x_BLE_Stack_full_fw.bin, and Release_Notes.html from
+          gh:STMicroelectronics/STM32CubeWB@2234d97/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x
+        - Open STM32CubeProgrammer
+        - On the right-hand pane, click "firmware upgrade" to upgrade the st-link firmware.
+        - Once complete, click connect to connect to the device.
+        - On the left hand pane, click the RSS signal icon to open "Firmware Upgrade Services".
+        - In the Release_Notes.html, find the memory address that corresponds to your device for the stm32wb5x_FUS_fw.bin file
+        - Select that file, the memory address, "verify download", and then "Firmware Upgrade".
+        - Once complete, in the Release_Notes.html, find the memory address that corresponds to your device for the
+          stm32wb5x_BLE_Stack_full_fw.bin file. It should not be the same memory address.
+        - Select that file, the memory address, "verify download", and then "Firmware Upgrade".
+        - Select "Start Wireless Stack".
+        - Disconnect from the device.
+        - In the examples folder for stm32wb, modify the memory.x file to match your target device.
+        - Run this example.
+
+        Note: extended stack versions are not supported at this time. Do not attempt to install a stack with "extended" in the name.
+    */
+
+    let p = embassy_stm32::init(Default::default());
+    info!("Hello World!");
+
+    let config = Config::default();
+    let mbox = TlMbox::init(p.IPCC, Irqs, config);
+
+    spawner.spawn(run_mm_queue(mbox.mm_subsystem)).unwrap();
+
+    let sys_event = mbox.sys_subsystem.read().await;
+    info!("sys event: {}", sys_event.payload());
+
+    core::mem::drop(sys_event);
+
+    let result = mbox.sys_subsystem.shci_c2_mac_802_15_4_init().await;
+    info!("initialized mac: {}", result);
+
+    info!("resetting");
+    mbox.mac_subsystem
+        .send_command(&ResetRequest {
+            set_default_pib: true,
+            ..Default::default()
+        })
+        .await
+        .unwrap();
+    defmt::info!("{:#x}", mbox.mac_subsystem.read().await.unwrap());
+
+    info!("setting extended address");
+    let extended_address: u64 = 0xACDE480000000001;
+    mbox.mac_subsystem
+        .send_command(&SetRequest {
+            pib_attribute_ptr: &extended_address as *const _ as *const u8,
+            pib_attribute: PibId::ExtendedAddress,
+        })
+        .await
+        .unwrap();
+    defmt::info!("{:#x}", mbox.mac_subsystem.read().await.unwrap());
+
+    info!("setting short address");
+    let short_address: u16 = 0x1122;
+    mbox.mac_subsystem
+        .send_command(&SetRequest {
+            pib_attribute_ptr: &short_address as *const _ as *const u8,
+            pib_attribute: PibId::ShortAddress,
+        })
+        .await
+        .unwrap();
+    defmt::info!("{:#x}", mbox.mac_subsystem.read().await.unwrap());
+
+    info!("setting association permit");
+    let association_permit: bool = true;
+    mbox.mac_subsystem
+        .send_command(&SetRequest {
+            pib_attribute_ptr: &association_permit as *const _ as *const u8,
+            pib_attribute: PibId::AssociationPermit,
+        })
+        .await
+        .unwrap();
+    defmt::info!("{:#x}", mbox.mac_subsystem.read().await.unwrap());
+
+    info!("setting TX power");
+    let transmit_power: i8 = 2;
+    mbox.mac_subsystem
+        .send_command(&SetRequest {
+            pib_attribute_ptr: &transmit_power as *const _ as *const u8,
+            pib_attribute: PibId::TransmitPower,
+        })
+        .await
+        .unwrap();
+    defmt::info!("{:#x}", mbox.mac_subsystem.read().await.unwrap());
+
+    info!("starting FFD device");
+    mbox.mac_subsystem
+        .send_command(&StartRequest {
+            pan_id: PanId([0x1A, 0xAA]),
+            channel_number: MacChannel::Channel16,
+            beacon_order: 0x0F,
+            superframe_order: 0x0F,
+            pan_coordinator: true,
+            battery_life_extension: false,
+            ..Default::default()
+        })
+        .await
+        .unwrap();
+    defmt::info!("{:#x}", mbox.mac_subsystem.read().await.unwrap());
+
+    info!("setting RX on when idle");
+    let rx_on_while_idle: bool = true;
+    mbox.mac_subsystem
+        .send_command(&SetRequest {
+            pib_attribute_ptr: &rx_on_while_idle as *const _ as *const u8,
+            pib_attribute: PibId::RxOnWhenIdle,
+        })
+        .await
+        .unwrap();
+    defmt::info!("{:#x}", mbox.mac_subsystem.read().await.unwrap());
+
+    let tx_queue = [
+        make_static!([0u8; 127]),
+        make_static!([0u8; 127]),
+        make_static!([0u8; 127]),
+        make_static!([0u8; 127]),
+        make_static!([0u8; 127]),
+    ];
+
+    let runner = make_static!(Runner::new(mbox.mac_subsystem, tx_queue));
+
+    spawner.spawn(run_mac(runner)).unwrap();
+
+    let (driver, control) = mac::new(runner).await;
+
+    let _ = driver;
+    let _ = control;
+}
diff --git a/examples/stm32wb/src/bin/mac_rfd.rs b/examples/stm32wb/src/bin/mac_rfd.rs
new file mode 100644
index 000000000..4d8b6601a
--- /dev/null
+++ b/examples/stm32wb/src/bin/mac_rfd.rs
@@ -0,0 +1,182 @@
+#![no_std]
+#![no_main]
+#![feature(type_alias_impl_trait)]
+
+use defmt::*;
+use embassy_executor::Spawner;
+use embassy_stm32::bind_interrupts;
+use embassy_stm32::ipcc::{Config, ReceiveInterruptHandler, TransmitInterruptHandler};
+use embassy_stm32_wpan::mac::commands::{AssociateRequest, DataRequest, GetRequest, ResetRequest, SetRequest};
+use embassy_stm32_wpan::mac::event::MacEvent;
+use embassy_stm32_wpan::mac::typedefs::{
+    AddressMode, Capabilities, KeyIdMode, MacAddress, MacChannel, PanId, PibId, SecurityLevel,
+};
+use embassy_stm32_wpan::sub::mm;
+use embassy_stm32_wpan::TlMbox;
+use {defmt_rtt as _, panic_probe as _};
+
+bind_interrupts!(struct Irqs{
+    IPCC_C1_RX => ReceiveInterruptHandler;
+    IPCC_C1_TX => TransmitInterruptHandler;
+});
+
+#[embassy_executor::task]
+async fn run_mm_queue(memory_manager: mm::MemoryManager) {
+    memory_manager.run_queue().await;
+}
+
+#[embassy_executor::main]
+async fn main(spawner: Spawner) {
+    /*
+        How to make this work:
+
+        - Obtain a NUCLEO-STM32WB55 from your preferred supplier.
+        - Download and Install STM32CubeProgrammer.
+        - Download stm32wb5x_FUS_fw.bin, stm32wb5x_BLE_Stack_full_fw.bin, and Release_Notes.html from
+          gh:STMicroelectronics/STM32CubeWB@2234d97/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x
+        - Open STM32CubeProgrammer
+        - On the right-hand pane, click "firmware upgrade" to upgrade the st-link firmware.
+        - Once complete, click connect to connect to the device.
+        - On the left hand pane, click the RSS signal icon to open "Firmware Upgrade Services".
+        - In the Release_Notes.html, find the memory address that corresponds to your device for the stm32wb5x_FUS_fw.bin file
+        - Select that file, the memory address, "verify download", and then "Firmware Upgrade".
+        - Once complete, in the Release_Notes.html, find the memory address that corresponds to your device for the
+          stm32wb5x_BLE_Stack_full_fw.bin file. It should not be the same memory address.
+        - Select that file, the memory address, "verify download", and then "Firmware Upgrade".
+        - Select "Start Wireless Stack".
+        - Disconnect from the device.
+        - In the examples folder for stm32wb, modify the memory.x file to match your target device.
+        - Run this example.
+
+        Note: extended stack versions are not supported at this time. Do not attempt to install a stack with "extended" in the name.
+    */
+
+    let p = embassy_stm32::init(Default::default());
+    info!("Hello World!");
+
+    let config = Config::default();
+    let mbox = TlMbox::init(p.IPCC, Irqs, config);
+
+    spawner.spawn(run_mm_queue(mbox.mm_subsystem)).unwrap();
+
+    let sys_event = mbox.sys_subsystem.read().await;
+    info!("sys event: {}", sys_event.payload());
+
+    core::mem::drop(sys_event);
+
+    let result = mbox.sys_subsystem.shci_c2_mac_802_15_4_init().await;
+    info!("initialized mac: {}", result);
+
+    info!("resetting");
+    mbox.mac_subsystem
+        .send_command(&ResetRequest {
+            set_default_pib: true,
+            ..Default::default()
+        })
+        .await
+        .unwrap();
+    defmt::info!("{:#x}", mbox.mac_subsystem.read().await.unwrap());
+
+    info!("setting extended address");
+    let extended_address: u64 = 0xACDE480000000002;
+    mbox.mac_subsystem
+        .send_command(&SetRequest {
+            pib_attribute_ptr: &extended_address as *const _ as *const u8,
+            pib_attribute: PibId::ExtendedAddress,
+        })
+        .await
+        .unwrap();
+    defmt::info!("{:#x}", mbox.mac_subsystem.read().await.unwrap());
+
+    info!("getting extended address");
+    mbox.mac_subsystem
+        .send_command(&GetRequest {
+            pib_attribute: PibId::ExtendedAddress,
+            ..Default::default()
+        })
+        .await
+        .unwrap();
+
+    {
+        let evt = mbox.mac_subsystem.read().await.unwrap();
+        info!("{:#x}", evt);
+
+        if let MacEvent::MlmeGetCnf(evt) = evt {
+            if evt.pib_attribute_value_len == 8 {
+                let value = unsafe { core::ptr::read_unaligned(evt.pib_attribute_value_ptr as *const u64) };
+
+                info!("value {:#x}", value)
+            }
+        }
+    }
+
+    info!("assocation request");
+    let a = AssociateRequest {
+        channel_number: MacChannel::Channel16,
+        channel_page: 0,
+        coord_addr_mode: AddressMode::Short,
+        coord_address: MacAddress { short: [34, 17] },
+        capability_information: Capabilities::ALLOCATE_ADDRESS,
+        coord_pan_id: PanId([0x1A, 0xAA]),
+        security_level: SecurityLevel::Unsecure,
+        key_id_mode: KeyIdMode::Implicite,
+        key_source: [0; 8],
+        key_index: 152,
+    };
+    info!("{}", a);
+    mbox.mac_subsystem.send_command(&a).await.unwrap();
+    let short_addr = {
+        let evt = mbox.mac_subsystem.read().await.unwrap();
+        info!("{:#x}", evt);
+
+        if let MacEvent::MlmeAssociateCnf(conf) = evt {
+            conf.assoc_short_address
+        } else {
+            defmt::panic!()
+        }
+    };
+
+    info!("setting short address");
+    mbox.mac_subsystem
+        .send_command(&SetRequest {
+            pib_attribute_ptr: &short_addr as *const _ as *const u8,
+            pib_attribute: PibId::ShortAddress,
+        })
+        .await
+        .unwrap();
+    {
+        let evt = mbox.mac_subsystem.read().await.unwrap();
+        info!("{:#x}", evt);
+    }
+
+    info!("sending data");
+    let data = b"Hello from embassy!";
+    mbox.mac_subsystem
+        .send_command(
+            DataRequest {
+                src_addr_mode: AddressMode::Short,
+                dst_addr_mode: AddressMode::Short,
+                dst_pan_id: PanId([0x1A, 0xAA]),
+                dst_address: MacAddress::BROADCAST,
+                msdu_handle: 0x02,
+                ack_tx: 0x00,
+                gts_tx: false,
+                security_level: SecurityLevel::Unsecure,
+                ..Default::default()
+            }
+            .set_buffer(data),
+        )
+        .await
+        .unwrap();
+    {
+        let evt = mbox.mac_subsystem.read().await.unwrap();
+        info!("{:#x}", evt);
+    }
+
+    loop {
+        match mbox.mac_subsystem.read().await {
+            Ok(evt) => info!("{:#x}", evt),
+            _ => continue,
+        };
+    }
+}
diff --git a/examples/stm32wb/src/bin/tl_mbox_ble.rs b/examples/stm32wb/src/bin/tl_mbox_ble.rs
index a511e89aa..90349422e 100644
--- a/examples/stm32wb/src/bin/tl_mbox_ble.rs
+++ b/examples/stm32wb/src/bin/tl_mbox_ble.rs
@@ -49,7 +49,7 @@ async fn main(_spawner: Spawner) {
     let sys_event = mbox.sys_subsystem.read().await;
     info!("sys event: {}", sys_event.payload());
 
-    mbox.sys_subsystem.shci_c2_ble_init(Default::default()).await;
+    let _ = mbox.sys_subsystem.shci_c2_ble_init(Default::default()).await;
 
     info!("starting ble...");
     mbox.ble_subsystem.tl_write(0x0c, &[]).await;
diff --git a/examples/stm32wb/src/bin/tl_mbox_mac.rs b/examples/stm32wb/src/bin/tl_mbox_mac.rs
index f67be4682..5931c392b 100644
--- a/examples/stm32wb/src/bin/tl_mbox_mac.rs
+++ b/examples/stm32wb/src/bin/tl_mbox_mac.rs
@@ -6,6 +6,7 @@ use defmt::*;
 use embassy_executor::Spawner;
 use embassy_stm32::bind_interrupts;
 use embassy_stm32::ipcc::{Config, ReceiveInterruptHandler, TransmitInterruptHandler};
+use embassy_stm32_wpan::sub::mm;
 use embassy_stm32_wpan::TlMbox;
 use {defmt_rtt as _, panic_probe as _};
 
@@ -14,8 +15,13 @@ bind_interrupts!(struct Irqs{
     IPCC_C1_TX => TransmitInterruptHandler;
 });
 
+#[embassy_executor::task]
+async fn run_mm_queue(memory_manager: mm::MemoryManager) {
+    memory_manager.run_queue().await;
+}
+
 #[embassy_executor::main]
-async fn main(_spawner: Spawner) {
+async fn main(spawner: Spawner) {
     /*
         How to make this work:
 
@@ -46,9 +52,13 @@ async fn main(_spawner: Spawner) {
     let config = Config::default();
     let mbox = TlMbox::init(p.IPCC, Irqs, config);
 
+    spawner.spawn(run_mm_queue(mbox.mm_subsystem)).unwrap();
+
     let sys_event = mbox.sys_subsystem.read().await;
     info!("sys event: {}", sys_event.payload());
 
+    core::mem::drop(sys_event);
+
     let result = mbox.sys_subsystem.shci_c2_mac_802_15_4_init().await;
     info!("initialized mac: {}", result);
 
diff --git a/examples/stm32wl/.cargo/config.toml b/examples/stm32wl/.cargo/config.toml
index 4f8094ff2..ee416fcbc 100644
--- a/examples/stm32wl/.cargo/config.toml
+++ b/examples/stm32wl/.cargo/config.toml
@@ -3,7 +3,7 @@
 runner = "probe-rs run --chip STM32WLE5JCIx"
 
 [build]
-target = "thumbv7em-none-eabihf"
+target = "thumbv7em-none-eabi"
 
 [env]
 DEFMT_LOG = "trace"
diff --git a/examples/stm32wl/Cargo.toml b/examples/stm32wl/Cargo.toml
index 260f9afa1..e2c66f456 100644
--- a/examples/stm32wl/Cargo.toml
+++ b/examples/stm32wl/Cargo.toml
@@ -7,9 +7,9 @@ license = "MIT OR Apache-2.0"
 [dependencies]
 embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] }
 embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] }
-embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["nightly", "unstable-traits", "defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
-embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "unstable-traits", "defmt", "stm32wl55jc-cm4", "time-driver-any", "memory-x", "unstable-pac", "exti"]  }
-embassy-embedded-hal = {version = "0.1.0", path = "../../embassy-embedded-hal" }
+embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["nightly", "unstable-traits", "defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
+embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "unstable-traits", "defmt", "stm32wl55jc-cm4", "time-driver-any", "memory-x", "unstable-pac", "exti", "chrono"] }
+embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" }
 embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["stm32wl", "time", "defmt"] }
 lora-phy = { version = "1" }
 lorawan-device = { version = "0.10.0", default-features = false, features = ["async", "external-lora-phy"] }
@@ -25,3 +25,7 @@ embedded-storage = "0.3.0"
 panic-probe = { version = "0.3", features = ["print-defmt"] }
 futures = { version = "0.3.17", default-features = false, features = ["async-await"] }
 heapless = { version = "0.7.5", default-features = false }
+chrono = { version = "^0.4", default-features = false }
+
+[patch.crates-io]
+lora-phy = { git = "https://github.com/embassy-rs/lora-phy", rev = "ad289428fd44b02788e2fa2116445cc8f640a265" }
diff --git a/examples/stm32wl/src/bin/rtc.rs b/examples/stm32wl/src/bin/rtc.rs
new file mode 100644
index 000000000..e11825499
--- /dev/null
+++ b/examples/stm32wl/src/bin/rtc.rs
@@ -0,0 +1,43 @@
+#![no_std]
+#![no_main]
+#![feature(type_alias_impl_trait)]
+
+use chrono::{NaiveDate, NaiveDateTime};
+use defmt::*;
+use embassy_executor::Spawner;
+use embassy_stm32::rcc::{self, ClockSrc};
+use embassy_stm32::rtc::{Rtc, RtcConfig};
+use embassy_stm32::Config;
+use embassy_time::{Duration, Timer};
+use {defmt_rtt as _, panic_probe as _};
+
+#[embassy_executor::main]
+async fn main(_spawner: Spawner) {
+    let p = {
+        let mut config = Config::default();
+        config.rcc.mux = ClockSrc::HSE32;
+        config.rcc.rtc_mux = rcc::RtcClockSource::LSE32;
+        config.rcc.enable_rtc_apb = true;
+        embassy_stm32::init(config)
+    };
+    info!("Hello World!");
+
+    let now = NaiveDate::from_ymd_opt(2020, 5, 15)
+        .unwrap()
+        .and_hms_opt(10, 30, 15)
+        .unwrap();
+
+    let mut rtc = Rtc::new(
+        p.RTC,
+        RtcConfig::default().clock_config(embassy_stm32::rtc::RtcClockSource::LSE),
+    );
+    info!("Got RTC! {:?}", now.timestamp());
+
+    rtc.set_datetime(now.into()).expect("datetime not set");
+
+    // In reality the delay would be much longer
+    Timer::after(Duration::from_millis(20000)).await;
+
+    let then: NaiveDateTime = rtc.now().unwrap().into();
+    info!("Got RTC! {:?}", then.timestamp());
+}
diff --git a/examples/wasm/Cargo.toml b/examples/wasm/Cargo.toml
index 437e443a7..3679e3857 100644
--- a/examples/wasm/Cargo.toml
+++ b/examples/wasm/Cargo.toml
@@ -10,7 +10,7 @@ crate-type = ["cdylib"]
 [dependencies]
 embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["log"] }
 embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["arch-wasm", "executor-thread", "log", "nightly", "integrated-timers"] }
-embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["log", "wasm", "nightly"] }
+embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["log", "wasm", "nightly"] }
 
 wasm-logger = "0.2.0"
 wasm-bindgen = "0.2"
diff --git a/rust-toolchain.toml b/rust-toolchain.toml
index fd454db26..179ed1d6a 100644
--- a/rust-toolchain.toml
+++ b/rust-toolchain.toml
@@ -1,7 +1,7 @@
 # Before upgrading check that everything is available on all tier1 targets here:
 # https://rust-lang.github.io/rustup-components-history
 [toolchain]
-channel = "nightly-2023-05-18"
+channel = "nightly-2023-06-28"
 components = [ "rust-src", "rustfmt", "llvm-tools-preview" ]
 targets = [
     "thumbv7em-none-eabi",
@@ -11,4 +11,4 @@ targets = [
     "thumbv8m.main-none-eabihf",
     "riscv32imac-unknown-none-elf",
     "wasm32-unknown-unknown",
-]
+]
\ No newline at end of file
diff --git a/tests/nrf/Cargo.toml b/tests/nrf/Cargo.toml
index 4f9ecc47a..7ce51aa5e 100644
--- a/tests/nrf/Cargo.toml
+++ b/tests/nrf/Cargo.toml
@@ -10,12 +10,12 @@ teleprobe-meta = "1"
 embassy-futures = { version = "0.1.0", path = "../../embassy-futures" }
 embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt", "nightly"] }
 embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "nightly", "integrated-timers"] }
-embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "nightly", "defmt-timestamp-uptime"] }
+embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "nightly", "unstable-traits", "defmt-timestamp-uptime"] }
 embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nightly", "unstable-traits", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac"] }
 embedded-io = { version = "0.4.0", features = ["async"] }
 embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "nightly"] }
 embassy-net-esp-hosted = { version = "0.1.0", path = "../../embassy-net-esp-hosted", features = ["defmt"] }
-embedded-hal-async = { version = "0.2.0-alpha.1" }
+embedded-hal-async = { version = "0.2.0-alpha.2" }
 static_cell = { version = "1.1", features = [ "nightly" ] }
 
 defmt = "0.3"
diff --git a/tests/nrf/src/bin/wifi_esp_hosted_perf.rs b/tests/nrf/src/bin/wifi_esp_hosted_perf.rs
index 277b985c5..398ab9d27 100644
--- a/tests/nrf/src/bin/wifi_esp_hosted_perf.rs
+++ b/tests/nrf/src/bin/wifi_esp_hosted_perf.rs
@@ -14,7 +14,7 @@ use embassy_nrf::gpio::{AnyPin, Input, Level, Output, OutputDrive, Pin, Pull};
 use embassy_nrf::rng::Rng;
 use embassy_nrf::spim::{self, Spim};
 use embassy_nrf::{bind_interrupts, peripherals};
-use embassy_time::{with_timeout, Duration, Timer};
+use embassy_time::{with_timeout, Delay, Duration, Timer};
 use embedded_hal_async::spi::ExclusiveDevice;
 use static_cell::make_static;
 use {defmt_rtt as _, embassy_net_esp_hosted as hosted, panic_probe as _};
@@ -30,7 +30,7 @@ bind_interrupts!(struct Irqs {
 async fn wifi_task(
     runner: hosted::Runner<
         'static,
-        ExclusiveDevice<Spim<'static, peripherals::SPI3>, Output<'static, peripherals::P0_31>>,
+        ExclusiveDevice<Spim<'static, peripherals::SPI3>, Output<'static, peripherals::P0_31>, Delay>,
         Input<'static, AnyPin>,
         Output<'static, peripherals::P1_05>,
     >,
@@ -63,7 +63,7 @@ async fn main(spawner: Spawner) {
     config.frequency = spim::Frequency::M32;
     config.mode = spim::MODE_2; // !!!
     let spi = spim::Spim::new(p.SPI3, Irqs, sck, miso, mosi, config);
-    let spi = ExclusiveDevice::new(spi, cs);
+    let spi = ExclusiveDevice::new(spi, cs, Delay);
 
     let (device, mut control, runner) = embassy_net_esp_hosted::new(
         make_static!(embassy_net_esp_hosted::State::new()),
diff --git a/tests/riscv32/Cargo.toml b/tests/riscv32/Cargo.toml
index 81bfdfab6..61f886c0c 100644
--- a/tests/riscv32/Cargo.toml
+++ b/tests/riscv32/Cargo.toml
@@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0"
 critical-section = { version = "1.1.1", features = ["restore-state-bool"] }
 embassy-sync = { version = "0.2.0", path = "../../embassy-sync" }
 embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["arch-riscv32", "nightly", "executor-thread"] }
-embassy-time = { version = "0.1.0", path = "../../embassy-time" }
+embassy-time = { version = "0.1.2", path = "../../embassy-time" }
 embassy-futures = { version = "0.1.0", path = "../../embassy-futures" }
 
 riscv-rt = "0.11"
diff --git a/tests/rp/Cargo.toml b/tests/rp/Cargo.toml
index 180d0ebbe..f2c902787 100644
--- a/tests/rp/Cargo.toml
+++ b/tests/rp/Cargo.toml
@@ -9,7 +9,7 @@ teleprobe-meta = "1.1"
 
 embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] }
 embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] }
-embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt"] }
+embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt"] }
 embassy-rp = { version = "0.1.0", path = "../../embassy-rp", features = ["nightly", "defmt", "unstable-pac", "unstable-traits", "time-driver", "critical-section-impl", "intrinsics", "rom-v2-intrinsics", "run-from-ram"]  }
 embassy-futures = { version = "0.1.0", path = "../../embassy-futures" }
 embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "udp", "dhcpv4", "medium-ethernet"] }
@@ -22,13 +22,15 @@ defmt-rtt = "0.4"
 cortex-m = { version = "0.7.6" }
 cortex-m-rt = "0.7.0"
 embedded-hal = "0.2.6"
-embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.10" }
-embedded-hal-async = { version = "=0.2.0-alpha.1" }
+embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.11" }
+embedded-hal-async = { version = "=0.2.0-alpha.2" }
 panic-probe = { version = "0.3.0", features = ["print-defmt"] }
 futures = { version = "0.3.17", default-features = false, features = ["async-await"] }
 embedded-io = { version = "0.4.0", features = ["async"] }
 embedded-storage = { version = "0.3" }
 static_cell = { version = "1.1", features = ["nightly"]}
+pio = "0.2"
+pio-proc = "0.2"
 
 [profile.dev]
 debug = 2
diff --git a/tests/rp/src/bin/adc.rs b/tests/rp/src/bin/adc.rs
new file mode 100644
index 000000000..e659844ae
--- /dev/null
+++ b/tests/rp/src/bin/adc.rs
@@ -0,0 +1,86 @@
+#![no_std]
+#![no_main]
+#![feature(type_alias_impl_trait)]
+#[path = "../common.rs"]
+mod common;
+
+use defmt::*;
+use embassy_executor::Spawner;
+use embassy_rp::adc::{Adc, Config, InterruptHandler, Pin};
+use embassy_rp::bind_interrupts;
+use embassy_rp::gpio::Pull;
+use {defmt_rtt as _, panic_probe as _};
+
+bind_interrupts!(struct Irqs {
+    ADC_IRQ_FIFO => InterruptHandler;
+});
+
+#[embassy_executor::main]
+async fn main(_spawner: Spawner) {
+    let mut p = embassy_rp::init(Default::default());
+    let mut adc = Adc::new(p.ADC, Irqs, Config::default());
+
+    {
+        {
+            let mut p = Pin::new(&mut p.PIN_26, Pull::Down);
+            defmt::assert!(adc.blocking_read(&mut p).unwrap() < 0b01_0000_0000);
+            defmt::assert!(adc.read(&mut p).await.unwrap() < 0b01_0000_0000);
+        }
+        {
+            let mut p = Pin::new(&mut p.PIN_26, Pull::Up);
+            defmt::assert!(adc.blocking_read(&mut p).unwrap() > 0b11_0000_0000);
+            defmt::assert!(adc.read(&mut p).await.unwrap() > 0b11_0000_0000);
+        }
+    }
+    // not bothering with async reads from now on
+    {
+        {
+            let mut p = Pin::new(&mut p.PIN_27, Pull::Down);
+            defmt::assert!(adc.blocking_read(&mut p).unwrap() < 0b01_0000_0000);
+        }
+        {
+            let mut p = Pin::new(&mut p.PIN_27, Pull::Up);
+            defmt::assert!(adc.blocking_read(&mut p).unwrap() > 0b11_0000_0000);
+        }
+    }
+    {
+        {
+            let mut p = Pin::new(&mut p.PIN_28, Pull::Down);
+            defmt::assert!(adc.blocking_read(&mut p).unwrap() < 0b01_0000_0000);
+        }
+        {
+            let mut p = Pin::new(&mut p.PIN_28, Pull::Up);
+            defmt::assert!(adc.blocking_read(&mut p).unwrap() > 0b11_0000_0000);
+        }
+    }
+    {
+        // gp29 is connected to vsys through a 200k/100k divider,
+        // adding pulls should change the value
+        let low = {
+            let mut p = Pin::new(&mut p.PIN_29, Pull::Down);
+            adc.blocking_read(&mut p).unwrap()
+        };
+        let none = {
+            let mut p = Pin::new(&mut p.PIN_29, Pull::None);
+            adc.blocking_read(&mut p).unwrap()
+        };
+        let up = {
+            let mut p = Pin::new(&mut p.PIN_29, Pull::Up);
+            adc.blocking_read(&mut p).unwrap()
+        };
+        defmt::assert!(low < none);
+        defmt::assert!(none < up);
+    }
+
+    let temp = convert_to_celsius(adc.read_temperature().await.unwrap());
+    defmt::assert!(temp > 0.0);
+    defmt::assert!(temp < 60.0);
+
+    info!("Test OK");
+    cortex_m::asm::bkpt();
+}
+
+fn convert_to_celsius(raw_temp: u16) -> f32 {
+    // According to chapter 4.9.5. Temperature Sensor in RP2040 datasheet
+    27.0 - (raw_temp as f32 * 3.3 / 4096.0 - 0.706) / 0.001721 as f32
+}
diff --git a/tests/rp/src/bin/cyw43-perf.rs b/tests/rp/src/bin/cyw43-perf.rs
index 1ecaab266..bc127e2e5 100644
--- a/tests/rp/src/bin/cyw43-perf.rs
+++ b/tests/rp/src/bin/cyw43-perf.rs
@@ -12,12 +12,16 @@ use embassy_net::tcp::TcpSocket;
 use embassy_net::{Config, Ipv4Address, Stack, StackResources};
 use embassy_rp::gpio::{Level, Output};
 use embassy_rp::peripherals::{DMA_CH0, PIN_23, PIN_25, PIO0};
-use embassy_rp::pio::Pio;
-use embassy_rp::rom_data;
+use embassy_rp::pio::{InterruptHandler, Pio};
+use embassy_rp::{bind_interrupts, rom_data};
 use embassy_time::{with_timeout, Duration, Timer};
 use static_cell::make_static;
 use {defmt_rtt as _, panic_probe as _};
 
+bind_interrupts!(struct Irqs {
+    PIO0_IRQ_0 => InterruptHandler<PIO0>;
+});
+
 teleprobe_meta::timeout!(120);
 
 #[embassy_executor::task]
@@ -51,7 +55,7 @@ async fn main(spawner: Spawner) {
 
     let pwr = Output::new(p.PIN_23, Level::Low);
     let cs = Output::new(p.PIN_25, Level::High);
-    let mut pio = Pio::new(p.PIO0);
+    let mut pio = Pio::new(p.PIO0, Irqs);
     let spi = PioSpi::new(&mut pio.common, pio.sm0, pio.irq0, cs, p.PIN_24, p.PIN_29, p.DMA_CH0);
 
     let state = make_static!(cyw43::State::new());
diff --git a/tests/rp/src/bin/gpio.rs b/tests/rp/src/bin/gpio.rs
index 51112d319..946b7dc88 100644
--- a/tests/rp/src/bin/gpio.rs
+++ b/tests/rp/src/bin/gpio.rs
@@ -21,14 +21,46 @@ async fn main(_spawner: Spawner) {
         let b = Input::new(&mut b, Pull::None);
 
         {
-            let _a = Output::new(&mut a, Level::Low);
+            let a = Output::new(&mut a, Level::Low);
             delay();
             assert!(b.is_low());
+            assert!(!b.is_high());
+            assert!(a.is_set_low());
+            assert!(!a.is_set_high());
         }
         {
-            let _a = Output::new(&mut a, Level::High);
+            let mut a = Output::new(&mut a, Level::High);
+            delay();
+            assert!(!b.is_low());
+            assert!(b.is_high());
+            assert!(!a.is_set_low());
+            assert!(a.is_set_high());
+
+            // Test is_set_low / is_set_high
+            a.set_low();
+            delay();
+            assert!(b.is_low());
+            assert!(a.is_set_low());
+            assert!(!a.is_set_high());
+
+            a.set_high();
             delay();
             assert!(b.is_high());
+            assert!(!a.is_set_low());
+            assert!(a.is_set_high());
+
+            // Test toggle
+            a.toggle();
+            delay();
+            assert!(b.is_low());
+            assert!(a.is_set_low());
+            assert!(!a.is_set_high());
+
+            a.toggle();
+            delay();
+            assert!(b.is_high());
+            assert!(!a.is_set_low());
+            assert!(a.is_set_high());
         }
     }
 
diff --git a/tests/rp/src/bin/pio_irq.rs b/tests/rp/src/bin/pio_irq.rs
new file mode 100644
index 000000000..45004424a
--- /dev/null
+++ b/tests/rp/src/bin/pio_irq.rs
@@ -0,0 +1,55 @@
+#![no_std]
+#![no_main]
+#![feature(type_alias_impl_trait)]
+#[path = "../common.rs"]
+mod common;
+
+use defmt::info;
+use embassy_executor::Spawner;
+use embassy_rp::bind_interrupts;
+use embassy_rp::peripherals::PIO0;
+use embassy_rp::pio::{Config, InterruptHandler, Pio};
+use embassy_rp::relocate::RelocatedProgram;
+use {defmt_rtt as _, panic_probe as _};
+
+bind_interrupts!(struct Irqs {
+    PIO0_IRQ_0 => InterruptHandler<PIO0>;
+});
+
+#[embassy_executor::main]
+async fn main(_spawner: Spawner) {
+    let p = embassy_rp::init(Default::default());
+    let pio = p.PIO0;
+    let Pio {
+        mut common,
+        sm0: mut sm,
+        irq_flags,
+        ..
+    } = Pio::new(pio, Irqs);
+
+    let prg = pio_proc::pio_asm!(
+        "irq set 0",
+        "irq wait 0",
+        "irq set 1",
+        // pause execution here
+        "irq wait 1",
+    );
+
+    let relocated = RelocatedProgram::new(&prg.program);
+    let mut cfg = Config::default();
+    cfg.use_program(&common.load_program(&relocated), &[]);
+    sm.set_config(&cfg);
+    sm.set_enable(true);
+
+    // not using the wait futures on purpose because they clear the irq bits,
+    // and we want to see in which order they are set.
+    while !irq_flags.check(0) {}
+    cortex_m::asm::nop();
+    assert!(!irq_flags.check(1));
+    irq_flags.clear(0);
+    cortex_m::asm::nop();
+    assert!(irq_flags.check(1));
+
+    info!("Test OK");
+    cortex_m::asm::bkpt();
+}
diff --git a/tests/stm32/Cargo.toml b/tests/stm32/Cargo.toml
index c2422f7bc..3007cd1e6 100644
--- a/tests/stm32/Cargo.toml
+++ b/tests/stm32/Cargo.toml
@@ -12,14 +12,16 @@ stm32g071rb = ["embassy-stm32/stm32g071rb", "not-gpdma"]     # Nucleo
 stm32c031c6 = ["embassy-stm32/stm32c031c6", "not-gpdma"]     # Nucleo
 stm32g491re = ["embassy-stm32/stm32g491re", "not-gpdma"]     # Nucleo
 stm32h755zi = ["embassy-stm32/stm32h755zi-cm7", "not-gpdma"] # Nucleo
-stm32wb55rg = ["embassy-stm32/stm32wb55rg", "not-gpdma", "ble" ]     # Nucleo
+stm32wb55rg = ["embassy-stm32/stm32wb55rg", "not-gpdma", "ble", "mac" ]     # Nucleo
 stm32h563zi = ["embassy-stm32/stm32h563zi"]     # Nucleo
 stm32u585ai = ["embassy-stm32/stm32u585ai"]     # IoT board
 
 sdmmc = []
 chrono = ["embassy-stm32/chrono", "dep:chrono"]
 can = []
-ble = ["dep:embassy-stm32-wpan"]
+ble = ["dep:embassy-stm32-wpan", "embassy-stm32-wpan/ble"]
+mac = ["dep:embassy-stm32-wpan", "embassy-stm32-wpan/mac"]
+embassy-stm32-wpan = []
 not-gpdma = []
 
 [dependencies]
@@ -27,7 +29,7 @@ teleprobe-meta = "1"
 
 embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] }
 embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] }
-embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "tick-hz-32_768", "defmt-timestamp-uptime"] }
+embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "tick-hz-32_768", "defmt-timestamp-uptime"] }
 embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "unstable-pac", "memory-x", "time-driver-any"]  }
 embassy-futures = { version = "0.1.0", path = "../../embassy-futures" }
 embassy-stm32-wpan = { version = "0.1.0", path = "../../embassy-stm32-wpan", optional = true, features = ["defmt", "stm32wb55rg", "ble"] }
@@ -38,24 +40,16 @@ defmt-rtt = "0.4"
 cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] }
 cortex-m-rt = "0.7.0"
 embedded-hal = "0.2.6"
-embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.10" }
-embedded-hal-async = { version = "=0.2.0-alpha.1" }
+embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.11" }
+embedded-hal-async = { version = "=0.2.0-alpha.2" }
 panic-probe = { version = "0.3.0", features = ["print-defmt"] }
 rand_core = { version = "0.6", default-features = false }
 rand_chacha = { version = "0.3", default-features = false }
 
 chrono = { version = "^0.4", default-features = false, optional = true}
 
-[patch.crates-io]
-stm32wb-hci = { git = "https://github.com/OueslatiGhaith/stm32wb-hci", rev = "9f663be"}
-
 # BEGIN TESTS
 # Generated by gen_test.py. DO NOT EDIT.
-[[bin]]
-name = "tl_mbox"
-path = "src/bin/tl_mbox.rs"
-required-features = [ "ble",]
-
 [[bin]]
 name = "can"
 path = "src/bin/can.rs"
@@ -106,6 +100,16 @@ name = "usart_rx_ringbuffered"
 path = "src/bin/usart_rx_ringbuffered.rs"
 required-features = [ "not-gpdma",]
 
+[[bin]]
+name = "wpan_ble"
+path = "src/bin/wpan_ble.rs"
+required-features = [ "ble",]
+
+[[bin]]
+name = "wpan_mac"
+path = "src/bin/wpan_mac.rs"
+required-features = [ "mac",]
+
 # END TESTS
 
 [profile.dev]
diff --git a/tests/stm32/src/bin/can.rs b/tests/stm32/src/bin/can.rs
index 33d63d546..8bdd3c24f 100644
--- a/tests/stm32/src/bin/can.rs
+++ b/tests/stm32/src/bin/can.rs
@@ -43,10 +43,13 @@ async fn main(_spawner: Spawner) {
 
     info!("Configuring can...");
 
-    can.modify_filters().enable_bank(0, Fifo::Fifo0, Mask32::accept_all());
+    can.as_mut()
+        .modify_filters()
+        .enable_bank(0, Fifo::Fifo0, Mask32::accept_all());
 
     can.set_bitrate(1_000_000);
-    can.modify_config()
+    can.as_mut()
+        .modify_config()
         .set_loopback(true) // Receive own frames
         .set_silent(true)
         // .set_bit_timing(0x001c0003)
diff --git a/tests/stm32/src/bin/gpio.rs b/tests/stm32/src/bin/gpio.rs
index 67f44317e..aad174431 100644
--- a/tests/stm32/src/bin/gpio.rs
+++ b/tests/stm32/src/bin/gpio.rs
@@ -40,14 +40,46 @@ async fn main(_spawner: Spawner) {
         let b = Input::new(&mut b, Pull::None);
 
         {
-            let _a = Output::new(&mut a, Level::Low, Speed::Low);
+            let a = Output::new(&mut a, Level::Low, Speed::Low);
             delay();
             assert!(b.is_low());
+            assert!(!b.is_high());
+            assert!(a.is_set_low());
+            assert!(!a.is_set_high());
         }
         {
-            let _a = Output::new(&mut a, Level::High, Speed::Low);
+            let mut a = Output::new(&mut a, Level::High, Speed::Low);
+            delay();
+            assert!(!b.is_low());
+            assert!(b.is_high());
+            assert!(!a.is_set_low());
+            assert!(a.is_set_high());
+
+            // Test is_set_low / is_set_high
+            a.set_low();
+            delay();
+            assert!(b.is_low());
+            assert!(a.is_set_low());
+            assert!(!a.is_set_high());
+
+            a.set_high();
             delay();
             assert!(b.is_high());
+            assert!(!a.is_set_low());
+            assert!(a.is_set_high());
+
+            // Test toggle
+            a.toggle();
+            delay();
+            assert!(b.is_low());
+            assert!(a.is_set_low());
+            assert!(!a.is_set_high());
+
+            a.toggle();
+            delay();
+            assert!(b.is_high());
+            assert!(!a.is_set_low());
+            assert!(a.is_set_high());
         }
     }
 
diff --git a/tests/stm32/src/bin/rtc.rs b/tests/stm32/src/bin/rtc.rs
index 582df5753..194b153d5 100644
--- a/tests/stm32/src/bin/rtc.rs
+++ b/tests/stm32/src/bin/rtc.rs
@@ -1,3 +1,5 @@
+// required-features: chrono
+
 #![no_std]
 #![no_main]
 #![feature(type_alias_impl_trait)]
diff --git a/tests/stm32/src/bin/tl_mbox.rs b/tests/stm32/src/bin/wpan_ble.rs
similarity index 99%
rename from tests/stm32/src/bin/tl_mbox.rs
rename to tests/stm32/src/bin/wpan_ble.rs
index af3832709..3ad8aca4e 100644
--- a/tests/stm32/src/bin/tl_mbox.rs
+++ b/tests/stm32/src/bin/wpan_ble.rs
@@ -64,7 +64,7 @@ async fn main(spawner: Spawner) {
         version_major, version_minor, subversion, sram2a_size, sram2b_size
     );
 
-    mbox.sys_subsystem.shci_c2_ble_init(Default::default()).await;
+    let _ = mbox.sys_subsystem.shci_c2_ble_init(Default::default()).await;
 
     info!("resetting BLE...");
     mbox.ble_subsystem.reset().await;
diff --git a/tests/stm32/src/bin/wpan_mac.rs b/tests/stm32/src/bin/wpan_mac.rs
new file mode 100644
index 000000000..b04a19ee9
--- /dev/null
+++ b/tests/stm32/src/bin/wpan_mac.rs
@@ -0,0 +1,124 @@
+// required-features: mac
+
+#![no_std]
+#![no_main]
+#![feature(type_alias_impl_trait)]
+#[path = "../common.rs"]
+mod common;
+
+use common::*;
+use embassy_executor::Spawner;
+use embassy_stm32::bind_interrupts;
+use embassy_stm32::ipcc::{Config, ReceiveInterruptHandler, TransmitInterruptHandler};
+use embassy_stm32_wpan::mac::commands::{AssociateRequest, GetRequest, ResetRequest, SetRequest};
+use embassy_stm32_wpan::mac::event::MacEvent;
+use embassy_stm32_wpan::mac::typedefs::{
+    AddressMode, Capabilities, KeyIdMode, MacAddress, MacChannel, PanId, PibId, SecurityLevel,
+};
+use embassy_stm32_wpan::sub::mm;
+use embassy_stm32_wpan::TlMbox;
+use {defmt_rtt as _, panic_probe as _};
+
+bind_interrupts!(struct Irqs{
+    IPCC_C1_RX => ReceiveInterruptHandler;
+    IPCC_C1_TX => TransmitInterruptHandler;
+});
+
+#[embassy_executor::task]
+async fn run_mm_queue(memory_manager: mm::MemoryManager) {
+    memory_manager.run_queue().await;
+}
+
+#[embassy_executor::main]
+async fn main(spawner: Spawner) {
+    let p = embassy_stm32::init(config());
+    info!("Hello World!");
+
+    let config = Config::default();
+    let mbox = TlMbox::init(p.IPCC, Irqs, config);
+
+    spawner.spawn(run_mm_queue(mbox.mm_subsystem)).unwrap();
+
+    let sys_event = mbox.sys_subsystem.read().await;
+    info!("sys event: {}", sys_event.payload());
+
+    core::mem::drop(sys_event);
+
+    let result = mbox.sys_subsystem.shci_c2_mac_802_15_4_init().await;
+    info!("initialized mac: {}", result);
+
+    info!("resetting");
+    mbox.mac_subsystem
+        .send_command(&ResetRequest {
+            set_default_pib: true,
+            ..Default::default()
+        })
+        .await
+        .unwrap();
+    {
+        let evt = mbox.mac_subsystem.read().await.unwrap();
+        info!("{:#x}", evt);
+    }
+
+    info!("setting extended address");
+    let extended_address: u64 = 0xACDE480000000002;
+    mbox.mac_subsystem
+        .send_command(&SetRequest {
+            pib_attribute_ptr: &extended_address as *const _ as *const u8,
+            pib_attribute: PibId::ExtendedAddress,
+        })
+        .await
+        .unwrap();
+    {
+        let evt = mbox.mac_subsystem.read().await.unwrap();
+        info!("{:#x}", evt);
+    }
+
+    info!("getting extended address");
+    mbox.mac_subsystem
+        .send_command(&GetRequest {
+            pib_attribute: PibId::ExtendedAddress,
+            ..Default::default()
+        })
+        .await
+        .unwrap();
+
+    {
+        let evt = mbox.mac_subsystem.read().await.unwrap();
+        info!("{:#x}", evt);
+
+        if let MacEvent::MlmeGetCnf(evt) = evt {
+            if evt.pib_attribute_value_len == 8 {
+                let value = unsafe { core::ptr::read_unaligned(evt.pib_attribute_value_ptr as *const u64) };
+
+                info!("value {:#x}", value)
+            }
+        }
+    }
+
+    info!("assocation request");
+    let a = AssociateRequest {
+        channel_number: MacChannel::Channel16,
+        channel_page: 0,
+        coord_addr_mode: AddressMode::Short,
+        coord_address: MacAddress { short: [34, 17] },
+        capability_information: Capabilities::ALLOCATE_ADDRESS,
+        coord_pan_id: PanId([0x1A, 0xAA]),
+        security_level: SecurityLevel::Unsecure,
+        key_id_mode: KeyIdMode::Implicite,
+        key_source: [0; 8],
+        key_index: 152,
+    };
+    info!("{}", a);
+    mbox.mac_subsystem.send_command(&a).await.unwrap();
+    let short_addr = if let MacEvent::MlmeAssociateCnf(conf) = mbox.mac_subsystem.read().await.unwrap() {
+        conf.assoc_short_address
+    } else {
+        defmt::panic!()
+    };
+
+    info!("{}", short_addr);
+
+    info!("Test OK");
+    cortex_m::asm::bkpt();
+}