diff --git a/board-tests/src/main.rs b/board-tests/src/main.rs
index 17afbce..d59e3ac 100644
--- a/board-tests/src/main.rs
+++ b/board-tests/src/main.rs
@@ -17,7 +17,7 @@ use va108xx_hal::{
     pac::{self, interrupt},
     prelude::*,
     time::Hertz,
-    timer::{default_ms_irq_handler, set_up_ms_tick, CountdownTimer, IrqCfg},
+    timer::{default_ms_irq_handler, set_up_ms_tick, CountdownTimer, InterruptConfig},
 };
 
 #[allow(dead_code)]
@@ -155,7 +155,7 @@ fn main() -> ! {
         }
         TestCase::DelayMs => {
             let mut ms_timer = set_up_ms_tick(
-                IrqCfg::new(pac::Interrupt::OC0, true, true),
+                InterruptConfig::new(pac::Interrupt::OC0, true, true),
                 &mut dp.sysconfig,
                 Some(&mut dp.irqsel),
                 50.MHz(),
diff --git a/examples/embassy/Cargo.toml b/examples/embassy/Cargo.toml
index 717adab..d00303a 100644
--- a/examples/embassy/Cargo.toml
+++ b/examples/embassy/Cargo.toml
@@ -9,6 +9,7 @@ cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
 cortex-m-rt = "0.7"
 embedded-hal = "1"
 embedded-hal-async = "1"
+embedded-io-async = "0.6"
 
 rtt-target = "0.6"
 panic-rtt-target = "0.2"
diff --git a/examples/embassy/src/bin/async-gpio.rs b/examples/embassy/src/bin/async-gpio.rs
index 5f8ce86..32e4e79 100644
--- a/examples/embassy/src/bin/async-gpio.rs
+++ b/examples/embassy/src/bin/async-gpio.rs
@@ -14,7 +14,7 @@ use embedded_hal_async::digital::Wait;
 use panic_rtt_target as _;
 use rtt_target::{rprintln, rtt_init_print};
 use va108xx_embassy::embassy;
-use va108xx_hal::gpio::{handle_interrupt_for_async_gpio, InputDynPinAsync, InputPinAsync, PinsB};
+use va108xx_hal::gpio::{on_interrupt_for_asynch_gpio, InputDynPinAsync, InputPinAsync, PinsB};
 use va108xx_hal::{
     gpio::{DynPin, PinsA},
     pac::{self, interrupt},
@@ -248,11 +248,11 @@ async fn output_task(
 #[interrupt]
 #[allow(non_snake_case)]
 fn OC10() {
-    handle_interrupt_for_async_gpio();
+    on_interrupt_for_asynch_gpio();
 }
 
 #[interrupt]
 #[allow(non_snake_case)]
 fn OC11() {
-    handle_interrupt_for_async_gpio();
+    on_interrupt_for_asynch_gpio();
 }
diff --git a/examples/embassy/src/bin/async-uart.rs b/examples/embassy/src/bin/async-uart.rs
new file mode 100644
index 0000000..b84ba33
--- /dev/null
+++ b/examples/embassy/src/bin/async-uart.rs
@@ -0,0 +1,87 @@
+#![no_std]
+#![no_main]
+use embassy_executor::Spawner;
+use embassy_time::{Duration, Instant, Ticker};
+use embedded_hal::digital::StatefulOutputPin;
+use embedded_io_async::Write;
+use panic_rtt_target as _;
+use rtt_target::{rprintln, rtt_init_print};
+use va108xx_embassy::embassy;
+use va108xx_hal::{
+    gpio::PinsA,
+    pac::{self, interrupt},
+    prelude::*,
+    uart::{self, on_interrupt_uart_a_tx, TxAsync},
+    InterruptConfig,
+};
+
+const SYSCLK_FREQ: Hertz = Hertz::from_raw(50_000_000);
+
+const STR_LIST: &[&str] = &[
+    "Hello World\r\n",
+    "Smoll\r\n",
+    "A string which is larger than the FIFO size\r\n",
+    "A really large string which is significantly larger than the FIFO size\r\n",
+];
+
+// main is itself an async function.
+#[embassy_executor::main]
+async fn main(_spawner: Spawner) {
+    rtt_init_print!();
+    rprintln!("-- VA108xx Embassy Demo --");
+
+    let mut dp = pac::Peripherals::take().unwrap();
+
+    // Safety: Only called once here.
+    unsafe {
+        embassy::init(
+            &mut dp.sysconfig,
+            &dp.irqsel,
+            SYSCLK_FREQ,
+            dp.tim23,
+            dp.tim22,
+        );
+    }
+
+    let porta = PinsA::new(&mut dp.sysconfig, dp.porta);
+    let mut led0 = porta.pa10.into_readable_push_pull_output();
+    let mut led1 = porta.pa7.into_readable_push_pull_output();
+    let mut led2 = porta.pa6.into_readable_push_pull_output();
+
+    let tx = porta.pa9.into_funsel_2();
+    let rx = porta.pa8.into_funsel_2();
+
+    let uarta = uart::Uart::new_with_interrupt(
+        &mut dp.sysconfig,
+        50.MHz(),
+        dp.uarta,
+        (tx, rx),
+        115200.Hz(),
+        InterruptConfig::new(pac::Interrupt::OC2, true, true),
+    );
+    let (tx, _rx) = uarta.split();
+    let mut async_tx = TxAsync::new(tx);
+    let mut ticker = Ticker::every(Duration::from_secs(1));
+    let mut idx = 0;
+    loop {
+        rprintln!("Current time: {}", Instant::now().as_secs());
+        led0.toggle().ok();
+        led1.toggle().ok();
+        led2.toggle().ok();
+        let _written = async_tx
+            .write(STR_LIST[idx].as_bytes())
+            .await
+            .expect("writing failed");
+        idx += 1;
+        if idx == STR_LIST.len() {
+            idx = 0;
+        }
+        ticker.next().await;
+    }
+}
+
+#[interrupt]
+#[allow(non_snake_case)]
+fn OC2() {
+    on_interrupt_uart_a_tx();
+}
diff --git a/examples/rtic/Cargo.toml b/examples/rtic/Cargo.toml
index 8df5541..f91e3ae 100644
--- a/examples/rtic/Cargo.toml
+++ b/examples/rtic/Cargo.toml
@@ -22,5 +22,5 @@ rtic-sync = { version = "1.3", features = ["defmt-03"] }
 once_cell = {version = "1", default-features = false, features = ["critical-section"]}
 ringbuf = { version = "0.4.7", default-features = false, features = ["portable-atomic"] }
 
-va108xx-hal = "0.8"
+va108xx-hal = { version = "0.8", path = "../../va108xx-hal" }
 vorago-reb1 = { path = "../../vorago-reb1" }
diff --git a/examples/rtic/src/bin/blinky-button-rtic.rs b/examples/rtic/src/bin/blinky-button-rtic.rs
index e844230..548a59e 100644
--- a/examples/rtic/src/bin/blinky-button-rtic.rs
+++ b/examples/rtic/src/bin/blinky-button-rtic.rs
@@ -12,7 +12,7 @@ mod app {
         gpio::{FilterType, InterruptEdge, PinsA},
         pac,
         prelude::*,
-        timer::{default_ms_irq_handler, set_up_ms_tick, IrqCfg},
+        timer::{default_ms_irq_handler, set_up_ms_tick, InterruptConfig},
     };
     use vorago_reb1::button::Button;
     use vorago_reb1::leds::Leds;
@@ -61,23 +61,24 @@ mod app {
         rprintln!("Using {:?} mode", mode);
 
         let mut dp = cx.device;
-        let pinsa = PinsA::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.porta);
+        let pinsa = PinsA::new(&mut dp.sysconfig, dp.porta);
         let edge_irq = match mode {
             PressMode::Toggle => InterruptEdge::HighToLow,
             PressMode::Keep => InterruptEdge::BothEdges,
         };
 
         // Configure an edge interrupt on the button and route it to interrupt vector 15
-        let mut button = Button::new(pinsa.pa11.into_floating_input()).edge_irq(
+        let mut button = Button::new(pinsa.pa11.into_floating_input());
+        button.configure_edge_interrupt(
             edge_irq,
-            IrqCfg::new(pac::interrupt::OC15, true, true),
+            InterruptConfig::new(pac::interrupt::OC15, true, true),
             Some(&mut dp.sysconfig),
             Some(&mut dp.irqsel),
         );
 
         if mode == PressMode::Toggle {
             // This filter debounces the switch for edge based interrupts
-            button = button.filter_type(FilterType::FilterFourClockCycles, FilterClkSel::Clk1);
+            button.configure_filter_type(FilterType::FilterFourClockCycles, FilterClkSel::Clk1);
             set_clk_div_register(&mut dp.sysconfig, FilterClkSel::Clk1, 50_000);
         }
         let mut leds = Leds::new(
@@ -89,7 +90,7 @@ mod app {
             led.off();
         }
         set_up_ms_tick(
-            IrqCfg::new(pac::Interrupt::OC0, true, true),
+            InterruptConfig::new(pac::Interrupt::OC0, true, true),
             &mut dp.sysconfig,
             Some(&mut dp.irqsel),
             50.MHz(),
diff --git a/examples/rtic/src/bin/uart-echo-rtic.rs b/examples/rtic/src/bin/uart-echo-rtic.rs
index 2a71a05..296e66e 100644
--- a/examples/rtic/src/bin/uart-echo-rtic.rs
+++ b/examples/rtic/src/bin/uart-echo-rtic.rs
@@ -23,12 +23,13 @@ mod app {
         gpio::PinsA,
         pac,
         prelude::*,
-        uart::{self, RxWithIrq, Tx},
+        uart::{self, RxWithInterrupt, Tx},
+        InterruptConfig,
     };
 
     #[local]
     struct Local {
-        rx: RxWithIrq<pac::Uarta>,
+        rx: RxWithInterrupt<pac::Uarta>,
         tx: Tx<pac::Uarta>,
     }
 
@@ -47,19 +48,20 @@ mod app {
         Mono::start(cx.core.SYST, SYSCLK_FREQ.raw());
 
         let mut dp = cx.device;
-        let gpioa = PinsA::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.porta);
+        let gpioa = PinsA::new(&mut dp.sysconfig, dp.porta);
         let tx = gpioa.pa9.into_funsel_2();
         let rx = gpioa.pa8.into_funsel_2();
 
-        let irq_uart = uart::Uart::new(
+        let irq_uart = uart::Uart::new_with_interrupt(
             &mut dp.sysconfig,
             SYSCLK_FREQ,
             dp.uarta,
             (tx, rx),
             115200.Hz(),
+            InterruptConfig::new(pac::Interrupt::OC3, true, true),
         );
         let (tx, rx) = irq_uart.split();
-        let mut rx = rx.into_rx_with_irq(&mut dp.sysconfig, &mut dp.irqsel, pac::interrupt::OC3);
+        let mut rx = rx.into_rx_with_irq();
 
         rx.start();
 
@@ -90,7 +92,7 @@ mod app {
     fn reception_task(mut cx: reception_task::Context) {
         let mut buf: [u8; 16] = [0; 16];
         let mut ringbuf_full = false;
-        let result = cx.local.rx.irq_handler(&mut buf);
+        let result = cx.local.rx.on_interrupt(&mut buf);
         if result.bytes_read > 0 && result.errors.is_none() {
             cx.shared.rb.lock(|rb| {
                 if rb.vacant_len() < result.bytes_read {
diff --git a/examples/rtic/src/main.rs b/examples/rtic/src/main.rs
index 699dcd6..41540fa 100644
--- a/examples/rtic/src/main.rs
+++ b/examples/rtic/src/main.rs
@@ -35,11 +35,7 @@ mod app {
 
         Mono::start(cx.core.SYST, SYSCLK_FREQ.raw());
 
-        let porta = PinsA::new(
-            &mut cx.device.sysconfig,
-            Some(cx.device.ioconfig),
-            cx.device.porta,
-        );
+        let porta = PinsA::new(&mut cx.device.sysconfig, cx.device.porta);
         let led0 = porta.pa10.into_readable_push_pull_output();
         let led1 = porta.pa7.into_readable_push_pull_output();
         let led2 = porta.pa6.into_readable_push_pull_output();
diff --git a/examples/simple/Cargo.toml b/examples/simple/Cargo.toml
index 7f6969b..ed1b536 100644
--- a/examples/simple/Cargo.toml
+++ b/examples/simple/Cargo.toml
@@ -16,6 +16,7 @@ embedded-io = "0.6"
 cortex-m-semihosting = "0.5.0"
 
 [dependencies.va108xx-hal]
+path = "../../va108xx-hal"
 version = "0.8"
 features = ["rt", "defmt"]
 
diff --git a/examples/simple/examples/blinky.rs b/examples/simple/examples/blinky.rs
index bee4a96..ae7385f 100644
--- a/examples/simple/examples/blinky.rs
+++ b/examples/simple/examples/blinky.rs
@@ -18,14 +18,14 @@ use va108xx_hal::{
     prelude::*,
     timer::DelayMs,
     timer::{default_ms_irq_handler, set_up_ms_tick, CountdownTimer},
-    IrqCfg,
+    InterruptConfig,
 };
 
 #[entry]
 fn main() -> ! {
     let mut dp = pac::Peripherals::take().unwrap();
     let mut delay_ms = DelayMs::new(set_up_ms_tick(
-        IrqCfg::new(interrupt::OC0, true, true),
+        InterruptConfig::new(interrupt::OC0, true, true),
         &mut dp.sysconfig,
         Some(&mut dp.irqsel),
         50.MHz(),
@@ -33,7 +33,7 @@ fn main() -> ! {
     ))
     .unwrap();
     let mut delay_tim1 = CountdownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim1);
-    let porta = PinsA::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.porta);
+    let porta = PinsA::new(&mut dp.sysconfig, dp.porta);
     let mut led1 = porta.pa10.into_readable_push_pull_output();
     let mut led2 = porta.pa7.into_readable_push_pull_output();
     let mut led3 = porta.pa6.into_readable_push_pull_output();
diff --git a/examples/simple/examples/cascade.rs b/examples/simple/examples/cascade.rs
index 6d96240..bac4745 100644
--- a/examples/simple/examples/cascade.rs
+++ b/examples/simple/examples/cascade.rs
@@ -17,7 +17,7 @@ use va108xx_hal::{
     prelude::*,
     timer::{
         default_ms_irq_handler, set_up_ms_delay_provider, CascadeCtrl, CascadeSource,
-        CountdownTimer, Event, IrqCfg,
+        CountdownTimer, Event, InterruptConfig,
     },
 };
 
@@ -39,7 +39,7 @@ fn main() -> ! {
         CountdownTimer::new(&mut dp.sysconfig, 50.MHz(), dp.tim3).auto_disable(true);
     cascade_triggerer.listen(
         Event::TimeOut,
-        IrqCfg::new(pac::Interrupt::OC1, true, false),
+        InterruptConfig::new(pac::Interrupt::OC1, true, false),
         Some(&mut dp.irqsel),
         Some(&mut dp.sysconfig),
     );
@@ -62,7 +62,7 @@ fn main() -> ! {
     // the timer expires
     cascade_target_1.listen(
         Event::TimeOut,
-        IrqCfg::new(pac::Interrupt::OC2, true, false),
+        InterruptConfig::new(pac::Interrupt::OC2, true, false),
         Some(&mut dp.irqsel),
         Some(&mut dp.sysconfig),
     );
@@ -88,7 +88,7 @@ fn main() -> ! {
     // the timer expires
     cascade_target_2.listen(
         Event::TimeOut,
-        IrqCfg::new(pac::Interrupt::OC3, true, false),
+        InterruptConfig::new(pac::Interrupt::OC3, true, false),
         Some(&mut dp.irqsel),
         Some(&mut dp.sysconfig),
     );
diff --git a/examples/simple/examples/pwm.rs b/examples/simple/examples/pwm.rs
index e51b5b0..57acac5 100644
--- a/examples/simple/examples/pwm.rs
+++ b/examples/simple/examples/pwm.rs
@@ -19,7 +19,7 @@ fn main() -> ! {
     rtt_init_print!();
     rprintln!("-- VA108xx PWM example application--");
     let mut dp = pac::Peripherals::take().unwrap();
-    let pinsa = PinsA::new(&mut dp.sysconfig, None, dp.porta);
+    let pinsa = PinsA::new(&mut dp.sysconfig, dp.porta);
     let mut pwm = pwm::PwmPin::new(
         &mut dp.sysconfig,
         50.MHz(),
diff --git a/examples/simple/examples/spi.rs b/examples/simple/examples/spi.rs
index 11be074..7869b5a 100644
--- a/examples/simple/examples/spi.rs
+++ b/examples/simple/examples/spi.rs
@@ -17,7 +17,7 @@ use va108xx_hal::{
     prelude::*,
     spi::{self, Spi, SpiBase, SpiClkConfig, TransferConfigWithHwcs},
     timer::{default_ms_irq_handler, set_up_ms_tick},
-    IrqCfg,
+    InterruptConfig,
 };
 
 #[derive(PartialEq, Debug)]
@@ -47,7 +47,7 @@ fn main() -> ! {
     rprintln!("-- VA108xx SPI example application--");
     let mut dp = pac::Peripherals::take().unwrap();
     let mut delay = set_up_ms_tick(
-        IrqCfg::new(interrupt::OC0, true, true),
+        InterruptConfig::new(interrupt::OC0, true, true),
         &mut dp.sysconfig,
         Some(&mut dp.irqsel),
         50.MHz(),
@@ -58,8 +58,8 @@ fn main() -> ! {
         .expect("creating SPI clock config failed");
     let spia_ref: RefCell<Option<SpiBase<pac::Spia, u8>>> = RefCell::new(None);
     let spib_ref: RefCell<Option<SpiBase<pac::Spib, u8>>> = RefCell::new(None);
-    let pinsa = PinsA::new(&mut dp.sysconfig, None, dp.porta);
-    let pinsb = PinsB::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.portb);
+    let pinsa = PinsA::new(&mut dp.sysconfig, dp.porta);
+    let pinsb = PinsB::new(&mut dp.sysconfig, dp.portb);
 
     let mut spi_cfg = spi::SpiConfig::default();
     if EXAMPLE_SEL == ExampleSelect::Loopback {
diff --git a/examples/simple/examples/timer-ticks.rs b/examples/simple/examples/timer-ticks.rs
index 1c0cb84..3b4b275 100644
--- a/examples/simple/examples/timer-ticks.rs
+++ b/examples/simple/examples/timer-ticks.rs
@@ -12,7 +12,9 @@ use va108xx_hal::{
     pac::{self, interrupt},
     prelude::*,
     time::Hertz,
-    timer::{default_ms_irq_handler, set_up_ms_tick, CountdownTimer, Event, IrqCfg, MS_COUNTER},
+    timer::{
+        default_ms_irq_handler, set_up_ms_tick, CountdownTimer, Event, InterruptConfig, MS_COUNTER,
+    },
 };
 
 #[allow(dead_code)]
@@ -65,7 +67,7 @@ fn main() -> ! {
         }
         LibType::Hal => {
             set_up_ms_tick(
-                IrqCfg::new(interrupt::OC0, true, true),
+                InterruptConfig::new(interrupt::OC0, true, true),
                 &mut dp.sysconfig,
                 Some(&mut dp.irqsel),
                 50.MHz(),
@@ -75,7 +77,7 @@ fn main() -> ! {
                 CountdownTimer::new(&mut dp.sysconfig, get_sys_clock().unwrap(), dp.tim1);
             second_timer.listen(
                 Event::TimeOut,
-                IrqCfg::new(interrupt::OC1, true, true),
+                InterruptConfig::new(interrupt::OC1, true, true),
                 Some(&mut dp.irqsel),
                 Some(&mut dp.sysconfig),
             );
diff --git a/examples/simple/examples/uart.rs b/examples/simple/examples/uart.rs
index de94bed..72119bb 100644
--- a/examples/simple/examples/uart.rs
+++ b/examples/simple/examples/uart.rs
@@ -24,11 +24,17 @@ fn main() -> ! {
 
     let mut dp = pac::Peripherals::take().unwrap();
 
-    let gpioa = PinsA::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.porta);
+    let gpioa = PinsA::new(&mut dp.sysconfig, dp.porta);
     let tx = gpioa.pa9.into_funsel_2();
     let rx = gpioa.pa8.into_funsel_2();
 
-    let uarta = uart::Uart::new(&mut dp.sysconfig, 50.MHz(), dp.uarta, (tx, rx), 115200.Hz());
+    let uarta = uart::Uart::new_without_interrupt(
+        &mut dp.sysconfig,
+        50.MHz(),
+        dp.uarta,
+        (tx, rx),
+        115200.Hz(),
+    );
     let (mut tx, mut rx) = uarta.split();
     writeln!(tx, "Hello World\r").unwrap();
     loop {
diff --git a/flashloader/src/main.rs b/flashloader/src/main.rs
index 0abd324..a87a669 100644
--- a/flashloader/src/main.rs
+++ b/flashloader/src/main.rs
@@ -71,7 +71,7 @@ mod app {
     };
     use va108xx_hal::gpio::PinsA;
     use va108xx_hal::uart::IrqContextTimeoutOrMaxSize;
-    use va108xx_hal::{pac, uart};
+    use va108xx_hal::{pac, uart, InterruptConfig};
     use vorago_reb1::m95m01::M95M01;
 
     #[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
@@ -84,7 +84,7 @@ mod app {
 
     #[local]
     struct Local {
-        uart_rx: uart::RxWithIrq<pac::Uarta>,
+        uart_rx: uart::RxWithInterrupt<pac::Uarta>,
         uart_tx: uart::Tx<pac::Uarta>,
         rx_context: IrqContextTimeoutOrMaxSize,
         verif_reporter: VerificationReportCreator,
@@ -114,15 +114,17 @@ mod app {
         let tx = gpioa.pa9.into_funsel_2();
         let rx = gpioa.pa8.into_funsel_2();
 
-        let irq_uart = uart::Uart::new(
+        let irq_uart = uart::Uart::new_with_interrupt(
             &mut dp.sysconfig,
             SYSCLK_FREQ,
             dp.uarta,
             (tx, rx),
             UART_BAUDRATE.Hz(),
+            InterruptConfig::new(pac::Interrupt::OC0, true, true),
         );
         let (tx, rx) = irq_uart.split();
-        let mut rx = rx.into_rx_with_irq(&mut dp.sysconfig, &mut dp.irqsel, pac::interrupt::OC0);
+        // Unwrap is okay, we explicitely set the interrupt ID.
+        let mut rx = rx.into_rx_with_irq();
 
         let verif_reporter = VerificationReportCreator::new(0).unwrap();
 
@@ -175,7 +177,7 @@ mod app {
         match cx
             .local
             .uart_rx
-            .irq_handler_max_size_or_timeout_based(cx.local.rx_context, cx.local.rx_buf)
+            .on_interrupt_max_size_or_timeout_based(cx.local.rx_context, cx.local.rx_buf)
         {
             Ok(result) => {
                 if RX_DEBUGGING {
diff --git a/va108xx-embassy/src/lib.rs b/va108xx-embassy/src/lib.rs
index 171fc49..26b9980 100644
--- a/va108xx-embassy/src/lib.rs
+++ b/va108xx-embassy/src/lib.rs
@@ -43,7 +43,7 @@ use once_cell::sync::OnceCell;
 use va108xx_hal::pac::interrupt;
 use va108xx_hal::{
     clock::enable_peripheral_clock,
-    enable_interrupt, pac,
+    enable_nvic_interrupt, pac,
     prelude::*,
     timer::{enable_tim_clk, get_tim_raw, TimRegInterface},
     PeripheralSelect,
@@ -221,7 +221,7 @@ impl TimerDriver {
             .tim0(timekeeper_tim.tim_id() as usize)
             .write(|w| unsafe { w.bits(timekeeper_irq as u32) });
         unsafe {
-            enable_interrupt(timekeeper_irq);
+            enable_nvic_interrupt(timekeeper_irq);
         }
         timekeeper_reg_block
             .ctrl()
@@ -239,7 +239,7 @@ impl TimerDriver {
         });
         // Enable general interrupts. The IRQ enable of the peripheral remains cleared.
         unsafe {
-            enable_interrupt(alarm_irq);
+            enable_nvic_interrupt(alarm_irq);
         }
         irqsel
             .tim0(alarm_tim.tim_id() as usize)
diff --git a/va108xx-hal/CHANGELOG.md b/va108xx-hal/CHANGELOG.md
index 13cc885..ff275ba 100644
--- a/va108xx-hal/CHANGELOG.md
+++ b/va108xx-hal/CHANGELOG.md
@@ -24,12 +24,29 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 - I2C `TimingCfg` constructor now returns explicit error instead of generic Error.
   Removed the timing configuration error type from the generic I2C error enumeration.
 - `PinsA` and `PinsB` constructor do not expect an optional `pac::Ioconfig` argument anymore.
+- `IrqCfg` renamed to `InterruptConfig`, kept alias for old name.
+- All library provided interrupt handlers now start with common prefix `on_interrupt_*`
+- `RxWithIrq` renamed to `RxWithInterrupt`
+- `Rx::into_rx_with_irq` does not expect any arguments any more.
+- `filter_type` renamed to `configure_filter_type`.
+- `level_irq` renamed to `configure_level_interrupt`.
+- `edge_irq` renamed to `configure_edge_interrupt`.
+- `PinsA` and `PinsB` constructor do not expect an optional IOCONFIG argument anymore.
+- UART interrupt management is now handled by the main constructor instead of later stages to
+  statically ensure one interrupt vector for the UART peripheral. `Uart::new` expects an
+  optional `InterruptConfig` argument.
+- `enable_interrupt` and `disable_interrupt` renamed to `enable_nvic_interrupt` and
+  `disable_nvic_interrupt` to distinguish them from peripheral interrupts more clearly.
+- `port_mux` renamed to `port_function_select`
 
 ## Added
 
 - Add `downgrade` method for `Pin` and `upgrade` method for `DynPin` as explicit conversion
   methods.
+- Asynchronous GPIO support.
+- Asynchronous UART TX support.
 - Add new `get_tim_raw` unsafe method to retrieve TIM peripheral blocks.
+- `Uart::with_with_interrupt` and `Uart::new_without_interrupt`
 
 ## [v0.8.0] 2024-09-30
 
diff --git a/va108xx-hal/Cargo.toml b/va108xx-hal/Cargo.toml
index bba5650..fcb9ba9 100644
--- a/va108xx-hal/Cargo.toml
+++ b/va108xx-hal/Cargo.toml
@@ -19,6 +19,7 @@ embedded-hal = "1"
 embedded-hal-async = "1"
 embedded-hal-nb = "1"
 embedded-io = "0.6"
+embedded-io-async = "0.6"
 fugit = "0.3"
 typenum = "1"
 critical-section = "1"
diff --git a/va108xx-hal/src/gpio/asynch.rs b/va108xx-hal/src/gpio/asynch.rs
index cca921b..34eeb0b 100644
--- a/va108xx-hal/src/gpio/asynch.rs
+++ b/va108xx-hal/src/gpio/asynch.rs
@@ -5,11 +5,11 @@
 //! on GPIO pins. Please note that this module does not specify/declare the interrupt handlers
 //! which must be provided for async support to work. However, it provides one generic
 //! [handler][handle_interrupt_for_async_gpio] which should be called in ALL user interrupt handlers
-//! for which handle GPIO interrupts.
+//! which handle GPIO interrupts.
 //!
 //! # Example
 //!
-//! - [Async GPIO example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/async-gpio/examples/embassy/src/bin/async-gpio.rs)
+//! - [Async GPIO example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/embassy/src/bin/async-gpio.rs)
 use core::future::Future;
 
 use embassy_sync::waitqueue::AtomicWaker;
@@ -18,7 +18,7 @@ use embedded_hal_async::digital::Wait;
 use portable_atomic::AtomicBool;
 use va108xx::{self as pac, Irqsel, Sysconfig};
 
-use crate::IrqCfg;
+use crate::InterruptConfig;
 
 use super::{
     pin, DynGroup, DynPin, DynPinId, InputConfig, InterruptEdge, InvalidPinTypeError, Pin, PinId,
@@ -44,7 +44,7 @@ fn pin_id_to_offset(dyn_pin_id: DynPinId) -> usize {
 /// complete async operations. The user should call this function in ALL interrupt handlers
 /// which handle any GPIO interrupts.
 #[inline]
-pub fn handle_interrupt_for_async_gpio() {
+pub fn on_interrupt_for_asynch_gpio() {
     let periphs = unsafe { pac::Peripherals::steal() };
 
     handle_interrupt_for_gpio_and_port(
@@ -117,7 +117,7 @@ impl InputPinFuture {
             .store(false, core::sync::atomic::Ordering::Relaxed);
         pin.interrupt_edge(
             edge,
-            IrqCfg::new(irq, true, true),
+            InterruptConfig::new(irq, true, true),
             Some(sys_cfg),
             Some(irq_sel),
         )
@@ -148,9 +148,9 @@ impl InputPinFuture {
     ) -> Self {
         EDGE_DETECTION[pin_id_to_offset(pin.id())]
             .store(false, core::sync::atomic::Ordering::Relaxed);
-        pin.interrupt_edge(
+        pin.configure_edge_interrupt(
             edge,
-            IrqCfg::new(irq, true, true),
+            InterruptConfig::new(irq, true, true),
             Some(sys_cfg),
             Some(irq_sel),
         );
diff --git a/va108xx-hal/src/gpio/dynpin.rs b/va108xx-hal/src/gpio/dynpin.rs
index 8dde283..57c7b6d 100644
--- a/va108xx-hal/src/gpio/dynpin.rs
+++ b/va108xx-hal/src/gpio/dynpin.rs
@@ -61,7 +61,7 @@ use super::{
     reg::RegisterInterface,
     InputDynPinAsync,
 };
-use crate::{clock::FilterClkSel, enable_interrupt, pac, FunSel, IrqCfg};
+use crate::{clock::FilterClkSel, enable_nvic_interrupt, pac, FunSel, InterruptConfig};
 
 //==================================================================================================
 //  DynPinMode configurations
@@ -351,7 +351,7 @@ impl DynPin {
 
     pub(crate) fn irq_enb(
         &mut self,
-        irq_cfg: crate::IrqCfg,
+        irq_cfg: crate::InterruptConfig,
         syscfg: Option<&mut va108xx::Sysconfig>,
         irqsel: Option<&mut va108xx::Irqsel>,
     ) {
@@ -366,18 +366,18 @@ impl DynPin {
                     DynGroup::A => {
                         irqsel
                             .porta0(self.regs.id().num as usize)
-                            .write(|w| unsafe { w.bits(irq_cfg.irq as u32) });
+                            .write(|w| unsafe { w.bits(irq_cfg.id as u32) });
                     }
                     DynGroup::B => {
                         irqsel
                             .portb0(self.regs.id().num as usize)
-                            .write(|w| unsafe { w.bits(irq_cfg.irq as u32) });
+                            .write(|w| unsafe { w.bits(irq_cfg.id as u32) });
                     }
                 }
             }
         }
-        if irq_cfg.enable {
-            unsafe { enable_interrupt(irq_cfg.irq) };
+        if irq_cfg.enable_in_nvic {
+            unsafe { enable_nvic_interrupt(irq_cfg.id) };
         }
     }
 
@@ -435,7 +435,7 @@ impl DynPin {
     pub fn interrupt_edge(
         &mut self,
         edge_type: InterruptEdge,
-        irq_cfg: IrqCfg,
+        irq_cfg: InterruptConfig,
         syscfg: Option<&mut pac::Sysconfig>,
         irqsel: Option<&mut pac::Irqsel>,
     ) -> Result<(), InvalidPinTypeError> {
@@ -453,7 +453,7 @@ impl DynPin {
     pub fn interrupt_level(
         &mut self,
         level_type: InterruptLevel,
-        irq_cfg: IrqCfg,
+        irq_cfg: InterruptConfig,
         syscfg: Option<&mut pac::Sysconfig>,
         irqsel: Option<&mut pac::Irqsel>,
     ) -> Result<(), InvalidPinTypeError> {
diff --git a/va108xx-hal/src/gpio/pin.rs b/va108xx-hal/src/gpio/pin.rs
index 32f8653..2e11f86 100644
--- a/va108xx-hal/src/gpio/pin.rs
+++ b/va108xx-hal/src/gpio/pin.rs
@@ -76,7 +76,7 @@ use super::{DynPin, InputPinAsync};
 use crate::{
     pac::{Irqsel, Porta, Portb, Sysconfig},
     typelevel::Sealed,
-    IrqCfg,
+    InterruptConfig,
 };
 use core::convert::Infallible;
 use core::marker::PhantomData;
@@ -454,7 +454,7 @@ impl<I: PinId, M: PinMode> Pin<I, M> {
 
     fn irq_enb(
         &mut self,
-        irq_cfg: crate::IrqCfg,
+        irq_cfg: crate::InterruptConfig,
         syscfg: Option<&mut va108xx::Sysconfig>,
         irqsel: Option<&mut va108xx::Irqsel>,
     ) {
@@ -581,10 +581,10 @@ impl<I: PinId, C: InputConfig> Pin<I, Input<C>> {
         InputPinAsync::new(self, irq)
     }
 
-    pub fn interrupt_edge(
+    pub fn configure_edge_interrupt(
         &mut self,
         edge_type: InterruptEdge,
-        irq_cfg: IrqCfg,
+        irq_cfg: InterruptConfig,
         syscfg: Option<&mut Sysconfig>,
         irqsel: Option<&mut Irqsel>,
     ) {
@@ -592,10 +592,10 @@ impl<I: PinId, C: InputConfig> Pin<I, Input<C>> {
         self.irq_enb(irq_cfg, syscfg, irqsel);
     }
 
-    pub fn interrupt_level(
+    pub fn configure_level_interrupt(
         &mut self,
         level_type: InterruptLevel,
-        irq_cfg: IrqCfg,
+        irq_cfg: InterruptConfig,
         syscfg: Option<&mut Sysconfig>,
         irqsel: Option<&mut Irqsel>,
     ) {
@@ -631,7 +631,7 @@ impl<I: PinId, C: OutputConfig> Pin<I, Output<C>> {
     pub fn interrupt_edge(
         &mut self,
         edge_type: InterruptEdge,
-        irq_cfg: IrqCfg,
+        irq_cfg: InterruptConfig,
         syscfg: Option<&mut Sysconfig>,
         irqsel: Option<&mut Irqsel>,
     ) {
@@ -642,7 +642,7 @@ impl<I: PinId, C: OutputConfig> Pin<I, Output<C>> {
     pub fn interrupt_level(
         &mut self,
         level_type: InterruptLevel,
-        irq_cfg: IrqCfg,
+        irq_cfg: InterruptConfig,
         syscfg: Option<&mut Sysconfig>,
         irqsel: Option<&mut Irqsel>,
     ) {
@@ -654,7 +654,7 @@ impl<I: PinId, C: OutputConfig> Pin<I, Output<C>> {
 impl<I: PinId, C: InputConfig> Pin<I, Input<C>> {
     /// See p.37 and p.38 of the programmers guide for more information.
     #[inline]
-    pub fn filter_type(&mut self, filter: FilterType, clksel: FilterClkSel) {
+    pub fn configure_filter_type(&mut self, filter: FilterType, clksel: FilterClkSel) {
         self.inner.regs.filter_type(filter, clksel);
     }
 }
diff --git a/va108xx-hal/src/lib.rs b/va108xx-hal/src/lib.rs
index b61335d..b12f853 100644
--- a/va108xx-hal/src/lib.rs
+++ b/va108xx-hal/src/lib.rs
@@ -25,12 +25,14 @@ pub enum FunSel {
 }
 
 #[derive(Debug, Copy, Clone, PartialEq, Eq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
 pub enum PortSel {
     PortA,
     PortB,
 }
 
 #[derive(Copy, Clone, PartialEq, Eq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
 pub enum PeripheralSelect {
     PortA = 0,
     PortB = 1,
@@ -47,31 +49,38 @@ pub enum PeripheralSelect {
     Gpio = 24,
 }
 
-/// Generic IRQ config which can be used to specify whether the HAL driver will
+/// Generic interrupt config which can be used to specify whether the HAL driver will
 /// use the IRQSEL register to route an interrupt, and whether the IRQ will be unmasked in the
 /// Cortex-M0 NVIC. Both are generally necessary for IRQs to work, but the user might perform
 /// this steps themselves
 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub struct IrqCfg {
+pub struct InterruptConfig {
     /// Interrupt target vector. Should always be set, might be required for disabling IRQs
-    pub irq: pac::Interrupt,
-    /// Specfiy whether IRQ should be routed to an IRQ vector using the IRQSEL peripheral
+    pub id: pac::Interrupt,
+    /// Specfiy whether IRQ should be routed to an IRQ vector using the IRQSEL peripheral.
     pub route: bool,
-    /// Specify whether the IRQ is unmasked in the Cortex-M NVIC
-    pub enable: bool,
+    /// Specify whether the IRQ is unmasked in the Cortex-M NVIC. If an interrupt is used for
+    /// multiple purposes, the user can enable the interrupts themselves.
+    pub enable_in_nvic: bool,
 }
 
-impl IrqCfg {
-    pub fn new(irq: pac::Interrupt, route: bool, enable: bool) -> Self {
-        IrqCfg { irq, route, enable }
+impl InterruptConfig {
+    pub fn new(id: pac::Interrupt, route: bool, enable_in_nvic: bool) -> Self {
+        InterruptConfig {
+            id,
+            route,
+            enable_in_nvic,
+        }
     }
 }
 
+pub type IrqCfg = InterruptConfig;
+
 #[derive(Debug, PartialEq, Eq)]
 pub struct InvalidPin(pub(crate) ());
 
 /// Can be used to manually manipulate the function select of port pins
-pub fn port_mux(
+pub fn port_function_select(
     ioconfig: &mut pac::Ioconfig,
     port: PortSel,
     pin: u8,
@@ -105,7 +114,7 @@ pub fn port_mux(
 ///
 /// This function is `unsafe` because it can break mask-based critical sections.
 #[inline]
-pub unsafe fn enable_interrupt(irq: pac::Interrupt) {
+pub unsafe fn enable_nvic_interrupt(irq: pac::Interrupt) {
     unsafe {
         cortex_m::peripheral::NVIC::unmask(irq);
     }
@@ -113,6 +122,6 @@ pub unsafe fn enable_interrupt(irq: pac::Interrupt) {
 
 /// Disable a specific interrupt using the NVIC peripheral.
 #[inline]
-pub fn disable_interrupt(irq: pac::Interrupt) {
+pub fn disable_nvic_interrupt(irq: pac::Interrupt) {
     cortex_m::peripheral::NVIC::mask(irq);
 }
diff --git a/va108xx-hal/src/pwm.rs b/va108xx-hal/src/pwm.rs
index 7afdaf7..5fa487b 100644
--- a/va108xx-hal/src/pwm.rs
+++ b/va108xx-hal/src/pwm.rs
@@ -80,7 +80,7 @@ where
         pin
     }
 
-    pub fn reduce(self) -> ReducedPwmPin<Mode> {
+    pub fn downgrade(self) -> ReducedPwmPin<Mode> {
         self.inner
     }
 
@@ -277,6 +277,24 @@ impl<Mode> ReducedPwmPin<Mode> {
     }
 }
 
+impl<Pin: TimPin, Tim: ValidTim> From<PwmPin<Pin, Tim, PwmA>> for ReducedPwmPin<PwmA>
+where
+    (Pin, Tim): ValidTimAndPin<Pin, Tim>,
+{
+    fn from(value: PwmPin<Pin, Tim, PwmA>) -> Self {
+        value.downgrade()
+    }
+}
+
+impl<Pin: TimPin, Tim: ValidTim> From<PwmPin<Pin, Tim, PwmB>> for ReducedPwmPin<PwmB>
+where
+    (Pin, Tim): ValidTimAndPin<Pin, Tim>,
+{
+    fn from(value: PwmPin<Pin, Tim, PwmB>) -> Self {
+        value.downgrade()
+    }
+}
+
 impl From<ReducedPwmPin<PwmA>> for ReducedPwmPin<PwmB> {
     fn from(other: ReducedPwmPin<PwmA>) -> Self {
         let mut pwmb = Self {
diff --git a/va108xx-hal/src/timer.rs b/va108xx-hal/src/timer.rs
index 117ffa6..8bf10f3 100644
--- a/va108xx-hal/src/timer.rs
+++ b/va108xx-hal/src/timer.rs
@@ -4,10 +4,10 @@
 //!
 //! - [MS and second tick implementation](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/simple/examples/timer-ticks.rs)
 //! - [Cascade feature example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/simple/examples/cascade.rs)
-pub use crate::IrqCfg;
+pub use crate::InterruptConfig;
 use crate::{
     clock::{enable_peripheral_clock, PeripheralClocks},
-    enable_interrupt,
+    enable_nvic_interrupt,
     gpio::{
         AltFunc1, AltFunc2, AltFunc3, DynPinId, Pin, PinId, PA0, PA1, PA10, PA11, PA12, PA13, PA14,
         PA15, PA2, PA24, PA25, PA26, PA27, PA28, PA29, PA3, PA30, PA31, PA4, PA5, PA6, PA7, PA8,
@@ -362,7 +362,7 @@ unsafe impl TimRegInterface for TimDynRegister {
 pub struct CountdownTimer<Tim: ValidTim> {
     tim: Tim,
     curr_freq: Hertz,
-    irq_cfg: Option<IrqCfg>,
+    irq_cfg: Option<InterruptConfig>,
     sys_clk: Hertz,
     rst_val: u32,
     last_cnt: u32,
@@ -415,13 +415,13 @@ impl<Tim: ValidTim> CountdownTimer<Tim> {
     pub fn listen(
         &mut self,
         event: Event,
-        irq_cfg: IrqCfg,
+        irq_cfg: InterruptConfig,
         irq_sel: Option<&mut pac::Irqsel>,
         sys_cfg: Option<&mut pac::Sysconfig>,
     ) {
         match event {
             Event::TimeOut => {
-                cortex_m::peripheral::NVIC::mask(irq_cfg.irq);
+                cortex_m::peripheral::NVIC::mask(irq_cfg.id);
                 self.irq_cfg = Some(irq_cfg);
                 if irq_cfg.route {
                     if let Some(sys_cfg) = sys_cfg {
@@ -430,7 +430,7 @@ impl<Tim: ValidTim> CountdownTimer<Tim> {
                     if let Some(irq_sel) = irq_sel {
                         irq_sel
                             .tim0(Tim::TIM_ID as usize)
-                            .write(|w| unsafe { w.bits(irq_cfg.irq as u32) });
+                            .write(|w| unsafe { w.bits(irq_cfg.id as u32) });
                     }
                 }
                 self.listening = true;
@@ -520,8 +520,8 @@ impl<Tim: ValidTim> CountdownTimer<Tim> {
     pub fn enable(&mut self) {
         if let Some(irq_cfg) = self.irq_cfg {
             self.enable_interrupt();
-            if irq_cfg.enable {
-                unsafe { enable_interrupt(irq_cfg.irq) };
+            if irq_cfg.enable_in_nvic {
+                unsafe { enable_nvic_interrupt(irq_cfg.id) };
             }
         }
         self.tim
@@ -719,7 +719,7 @@ impl<TIM: ValidTim> embedded_hal::delay::DelayNs for CountdownTimer<TIM> {
 // Set up a millisecond timer on TIM0. Please note that the user still has to provide an IRQ handler
 // which should call [default_ms_irq_handler].
 pub fn set_up_ms_tick<TIM: ValidTim>(
-    irq_cfg: IrqCfg,
+    irq_cfg: InterruptConfig,
     sys_cfg: &mut pac::Sysconfig,
     irq_sel: Option<&mut pac::Irqsel>,
     sys_clk: impl Into<Hertz>,
diff --git a/va108xx-hal/src/uart/asynch.rs b/va108xx-hal/src/uart/asynch.rs
new file mode 100644
index 0000000..97c24f7
--- /dev/null
+++ b/va108xx-hal/src/uart/asynch.rs
@@ -0,0 +1,264 @@
+//! # Async GPIO functionality for the VA108xx family.
+//!
+//! This module provides the [TxAsync] struct which implements the [embedded_io_async::Write] trait.
+//! This trait allows for asynchronous sending of data streams. Please note that this module does
+//! not specify/declare the interrupt handlers which must be provided for async support to work.
+//! However, it provides two interrupt handlers:
+//!
+//! - [on_interrupt_uart_a_tx]
+//! - [on_interrupt_uart_b_tx]
+//!
+//! Those should be called in ALL user interrupt handlers which handle UART TX interrupts,
+//! depending on which UARTs are used.
+//!
+//! # Example
+//!
+//! - [Async UART example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/async-gpio/examples/embassy/src/bin/async-uart.rs)
+use core::{cell::RefCell, future::Future};
+
+use critical_section::Mutex;
+use embassy_sync::waitqueue::AtomicWaker;
+use embedded_io_async::Write;
+use portable_atomic::AtomicBool;
+
+use super::*;
+
+static UART_WAKERS: [AtomicWaker; 2] = [const { AtomicWaker::new() }; 2];
+static TX_CONTEXTS: [Mutex<RefCell<TxContext>>; 2] =
+    [const { Mutex::new(RefCell::new(TxContext::new())) }; 2];
+// Completion flag. Kept outside of the context structure as an atomic to avoid
+// critical section.
+static TX_DONE: [AtomicBool; 2] = [const { AtomicBool::new(false) }; 2];
+
+/// This is a generic interrupt handler to handle asynchronous UART TX operations. The user
+/// has to call this once in the interrupt handler responsible for UART A TX interrupts for
+/// asynchronous operations to work.
+pub fn on_interrupt_uart_a_tx() {
+    on_interrupt_uart_tx(unsafe { pac::Uarta::steal() });
+}
+
+/// This is a generic interrupt handler to handle asynchronous UART TX operations. The user
+/// has to call this once in the interrupt handler responsible for UART B TX interrupts for
+/// asynchronous operations to work.
+pub fn on_interrupt_uart_b_tx() {
+    on_interrupt_uart_tx(unsafe { pac::Uartb::steal() });
+}
+
+fn on_interrupt_uart_tx<Uart: Instance>(uart: Uart) {
+    let irq_enb = uart.irq_enb().read();
+    // IRQ is not related to TX.
+    if irq_enb.irq_tx().bit_is_clear() || irq_enb.irq_tx_empty().bit_is_clear() {
+        return;
+    }
+
+    let tx_status = uart.txstatus().read();
+    let unexpected_overrun = tx_status.wrlost().bit_is_set();
+    let mut context = critical_section::with(|cs| {
+        let context_ref = TX_CONTEXTS[Uart::IDX as usize].borrow(cs);
+        *context_ref.borrow()
+    });
+    context.tx_overrun = unexpected_overrun;
+    if context.progress >= context.slice.len && !tx_status.wrbusy().bit_is_set() {
+        uart.irq_enb().modify(|_, w| {
+            w.irq_tx().clear_bit();
+            w.irq_tx_empty().clear_bit();
+            w.irq_tx_status().clear_bit()
+        });
+        uart.enable().modify(|_, w| w.txenable().clear_bit());
+        // Write back updated context structure.
+        critical_section::with(|cs| {
+            let context_ref = TX_CONTEXTS[Uart::IDX as usize].borrow(cs);
+            *context_ref.borrow_mut() = context;
+        });
+        // Transfer is done.
+        TX_DONE[Uart::IDX as usize].store(true, core::sync::atomic::Ordering::Relaxed);
+        UART_WAKERS[Uart::IDX as usize].wake();
+        return;
+    }
+    // Safety: We documented that the user provided slice must outlive the future, so we convert
+    // the raw pointer back to the slice here.
+    let slice = unsafe { core::slice::from_raw_parts(context.slice.data, context.slice.len) };
+    while context.progress < context.slice.len {
+        let wrrdy = uart.txstatus().read().wrrdy().bit_is_set();
+        if !wrrdy {
+            break;
+        }
+        // Safety: TX structure is owned by the future which does not write into the the data
+        // register, so we can assume we are the only one writing to the data register.
+        uart.data()
+            .write(|w| unsafe { w.bits(slice[context.progress] as u32) });
+        context.progress += 1;
+    }
+
+    // Write back updated context structure.
+    critical_section::with(|cs| {
+        let context_ref = TX_CONTEXTS[Uart::IDX as usize].borrow(cs);
+        *context_ref.borrow_mut() = context;
+    });
+}
+
+#[derive(Debug, Copy, Clone)]
+pub struct TxContext {
+    progress: usize,
+    tx_overrun: bool,
+    slice: RawBufSlice,
+}
+
+#[allow(clippy::new_without_default)]
+impl TxContext {
+    pub const fn new() -> Self {
+        Self {
+            progress: 0,
+            tx_overrun: false,
+            slice: RawBufSlice::new_empty(),
+        }
+    }
+}
+
+#[derive(Debug, Copy, Clone)]
+struct RawBufSlice {
+    data: *const u8,
+    len: usize,
+}
+
+/// Safety: This type MUST be used with mutex to ensure concurrent access is valid.
+unsafe impl Send for RawBufSlice {}
+
+impl RawBufSlice {
+    /// # Safety
+    ///
+    /// This function stores the raw pointer of the passed data slice. The user MUST ensure
+    /// that the slice outlives the data structure.
+    #[allow(dead_code)]
+    const unsafe fn new(data: &[u8]) -> Self {
+        Self {
+            data: data.as_ptr(),
+            len: data.len(),
+        }
+    }
+
+    const fn new_empty() -> Self {
+        Self {
+            data: core::ptr::null(),
+            len: 0,
+        }
+    }
+
+    /// # Safety
+    ///
+    /// This function stores the raw pointer of the passed data slice. The user MUST ensure
+    /// that the slice outlives the data structure.
+    pub unsafe fn set(&mut self, data: &[u8]) {
+        self.data = data.as_ptr();
+        self.len = data.len();
+    }
+}
+
+pub struct TxFuture {
+    uart_idx: usize,
+}
+
+impl TxFuture {
+    /// # Safety
+    ///
+    /// This function stores the raw pointer of the passed data slice. The user MUST ensure
+    /// that the slice outlives the data structure.
+    pub unsafe fn new<Uart: Instance>(tx: &mut Tx<Uart>, data: &[u8]) -> Self {
+        TX_DONE[Uart::IDX as usize].store(false, core::sync::atomic::Ordering::Relaxed);
+        tx.disable_interrupts();
+        tx.disable();
+        tx.clear_fifo();
+
+        let uart_tx = unsafe { tx.uart() };
+        let init_fill_count = core::cmp::min(data.len(), 16);
+        // We fill the FIFO.
+        for data in data.iter().take(init_fill_count) {
+            uart_tx.data().write(|w| unsafe { w.bits(*data as u32) });
+        }
+        critical_section::with(|cs| {
+            let context_ref = TX_CONTEXTS[Uart::IDX as usize].borrow(cs);
+            let mut context = context_ref.borrow_mut();
+            context.slice.set(data);
+            context.progress = init_fill_count;
+
+            // Ensure those are enabled inside a critical section at the same time. Can lead to
+            // weird glitches otherwise.
+            tx.enable_interrupts();
+            tx.enable();
+        });
+        Self {
+            uart_idx: Uart::IDX as usize,
+        }
+    }
+}
+
+impl Future for TxFuture {
+    type Output = Result<usize, TxOverrunError>;
+
+    fn poll(
+        self: core::pin::Pin<&mut Self>,
+        cx: &mut core::task::Context<'_>,
+    ) -> core::task::Poll<Self::Output> {
+        UART_WAKERS[self.uart_idx].register(cx.waker());
+        if TX_DONE[self.uart_idx].swap(false, core::sync::atomic::Ordering::Relaxed) {
+            let progress = critical_section::with(|cs| {
+                TX_CONTEXTS[self.uart_idx].borrow(cs).borrow().progress
+            });
+            return core::task::Poll::Ready(Ok(progress));
+        }
+        core::task::Poll::Pending
+    }
+}
+
+impl Drop for TxFuture {
+    fn drop(&mut self) {
+        let reg_block = match self.uart_idx {
+            0 => unsafe { pac::Uarta::reg_block() },
+            1 => unsafe { pac::Uartb::reg_block() },
+            _ => unreachable!(),
+        };
+
+        disable_tx_interrupts(reg_block);
+        disable_tx(reg_block);
+    }
+}
+
+pub struct TxAsync<Uart: Instance> {
+    tx: Tx<Uart>,
+}
+
+impl<Uart: Instance> TxAsync<Uart> {
+    pub fn new(tx: Tx<Uart>) -> Self {
+        Self { tx }
+    }
+
+    pub fn release(self) -> Tx<Uart> {
+        self.tx
+    }
+}
+
+#[derive(Debug, thiserror::Error)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+#[error("TX overrun error")]
+pub struct TxOverrunError;
+
+impl embedded_io_async::Error for TxOverrunError {
+    fn kind(&self) -> embedded_io_async::ErrorKind {
+        embedded_io_async::ErrorKind::Other
+    }
+}
+
+impl<Uart: Instance> embedded_io::ErrorType for TxAsync<Uart> {
+    type Error = TxOverrunError;
+}
+
+impl<Uart: Instance> Write for TxAsync<Uart> {
+    /// Write a buffer asynchronously.
+    ///
+    /// This implementation is not side effect free, and a started future might have already
+    /// written part of the passed buffer.
+    async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
+        let fut = unsafe { TxFuture::new(&mut self.tx, buf) };
+        fut.await
+    }
+}
diff --git a/va108xx-hal/src/uart.rs b/va108xx-hal/src/uart/mod.rs
similarity index 85%
rename from va108xx-hal/src/uart.rs
rename to va108xx-hal/src/uart/mod.rs
index fa6daa5..944b994 100644
--- a/va108xx-hal/src/uart.rs
+++ b/va108xx-hal/src/uart/mod.rs
@@ -9,10 +9,10 @@ use core::{convert::Infallible, ops::Deref};
 use fugit::RateExtU32;
 use va108xx::Uarta;
 
-pub use crate::IrqCfg;
+pub use crate::InterruptConfig;
 use crate::{
     clock::enable_peripheral_clock,
-    enable_interrupt,
+    enable_nvic_interrupt,
     gpio::pin::{
         AltFunc1, AltFunc2, AltFunc3, Pin, PA16, PA17, PA18, PA19, PA2, PA26, PA27, PA3, PA30,
         PA31, PA8, PA9, PB18, PB19, PB20, PB21, PB22, PB23, PB6, PB7, PB8, PB9,
@@ -48,6 +48,11 @@ impl Pins<pac::Uartb> for (Pin<PB21, AltFunc1>, Pin<PB20, AltFunc1>) {}
 // Regular Definitions
 //==================================================================================================
 
+#[derive(Debug, PartialEq, Eq, thiserror::Error)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+#[error("no interrupt ID was set")]
+pub struct NoInterruptIdWasSet;
+
 #[derive(Debug, PartialEq, Eq, thiserror::Error)]
 #[cfg_attr(feature = "defmt", derive(defmt::Format))]
 #[error("transer is pending")]
@@ -373,6 +378,16 @@ pub trait Instance: Deref<Target = uart_base::RegisterBlock> {
     /// This circumvents the safety guarantees of the HAL.
     unsafe fn steal() -> Self;
     fn ptr() -> *const uart_base::RegisterBlock;
+
+    /// Retrieve the type erased peripheral register block.
+    ///
+    /// # Safety
+    ///
+    /// This circumvents the safety guarantees of the HAL.
+    #[inline(always)]
+    unsafe fn reg_block() -> &'static uart_base::RegisterBlock {
+        unsafe { &(*Self::ptr()) }
+    }
 }
 
 impl Instance for pac::Uarta {
@@ -380,9 +395,11 @@ impl Instance for pac::Uarta {
 
     const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Uart0;
 
+    #[inline(always)]
     unsafe fn steal() -> Self {
         pac::Peripherals::steal().uarta
     }
+    #[inline(always)]
     fn ptr() -> *const uart_base::RegisterBlock {
         Uarta::ptr() as *const _
     }
@@ -393,9 +410,11 @@ impl Instance for pac::Uartb {
 
     const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Uart1;
 
+    #[inline(always)]
     unsafe fn steal() -> Self {
         pac::Peripherals::steal().uartb
     }
+    #[inline(always)]
     fn ptr() -> *const uart_base::RegisterBlock {
         Uarta::ptr() as *const _
     }
@@ -592,15 +611,51 @@ where
     UartInstance: Instance,
     PinsInstance: Pins<UartInstance>,
 {
-    pub fn new(
+    /// Calls [Self::new] with the interrupt configuration to some valid value.
+    pub fn new_with_interrupt(
+        syscfg: &mut va108xx::Sysconfig,
+        sys_clk: impl Into<Hertz>,
+        uart: UartInstance,
+        pins: PinsInstance,
+        config: impl Into<Config>,
+        irq_cfg: InterruptConfig,
+    ) -> Self {
+        Self::new(syscfg, sys_clk, uart, pins, config, Some(irq_cfg))
+    }
+
+    /// Calls [Self::new] with the interrupt configuration to [None].
+    pub fn new_without_interrupt(
         syscfg: &mut va108xx::Sysconfig,
         sys_clk: impl Into<Hertz>,
         uart: UartInstance,
         pins: PinsInstance,
         config: impl Into<Config>,
+    ) -> Self {
+        Self::new(syscfg, sys_clk, uart, pins, config, None)
+    }
+
+    /// Create a new UART peripheral with an interrupt configuration.
+    ///
+    /// # Arguments
+    ///
+    /// - `syscfg`: The system configuration register block
+    /// - `sys_clk`: The system clock frequency
+    /// - `uart`: The concrete UART peripheral instance.
+    /// - `pins`: UART TX and RX pin tuple.
+    /// - `config`: UART specific configuration parameters like baudrate.
+    /// - `irq_cfg`: Optional interrupt configuration. This should be a valid value if the plan
+    ///    is to use TX or RX functionality relying on interrupts. If only the blocking API without
+    ///    any interrupt support is used, this can be [None].
+    pub fn new(
+        syscfg: &mut va108xx::Sysconfig,
+        sys_clk: impl Into<Hertz>,
+        uart: UartInstance,
+        pins: PinsInstance,
+        config: impl Into<Config>,
+        opt_irq_cfg: Option<InterruptConfig>,
     ) -> Self {
         crate::clock::enable_peripheral_clock(syscfg, UartInstance::PERIPH_SEL);
-        Uart {
+        let uart = Uart {
             inner: UartBase {
                 uart,
                 tx: Tx::new(unsafe { UartInstance::steal() }),
@@ -608,7 +663,21 @@ where
             },
             pins,
         }
-        .init(config.into(), sys_clk.into())
+        .init(config.into(), sys_clk.into());
+
+        if let Some(irq_cfg) = opt_irq_cfg {
+            if irq_cfg.route {
+                enable_peripheral_clock(syscfg, PeripheralSelect::Irqsel);
+                unsafe { pac::Irqsel::steal() }
+                    .uart0(UartInstance::IDX as usize)
+                    .write(|w| unsafe { w.bits(irq_cfg.id as u32) });
+            }
+            if irq_cfg.enable_in_nvic {
+                // Safety: User has specifically configured this.
+                unsafe { enable_nvic_interrupt(irq_cfg.id) };
+            }
+        }
+        uart
     }
 
     /// This function assumes that the peripheral clock was alredy enabled
@@ -686,11 +755,13 @@ where
 /// Serial receiver.
 ///
 /// Can be created by using the [Uart::split] or [UartBase::split] API.
-pub struct Rx<Uart>(Uart);
+pub struct Rx<Uart> {
+    uart: Uart,
+}
 
 impl<Uart: Instance> Rx<Uart> {
     fn new(uart: Uart) -> Self {
-        Self(uart)
+        Self { uart }
     }
 
     /// Direct access to the peripheral structure.
@@ -699,22 +770,22 @@ impl<Uart: Instance> Rx<Uart> {
     ///
     /// You must ensure that only registers related to the operation of the RX side are used.
     pub unsafe fn uart(&self) -> &Uart {
-        &self.0
+        &self.uart
     }
 
     #[inline]
     pub fn clear_fifo(&self) {
-        self.0.fifo_clr().write(|w| w.rxfifo().set_bit());
+        self.uart.fifo_clr().write(|w| w.rxfifo().set_bit());
     }
 
     #[inline]
     pub fn enable(&mut self) {
-        self.0.enable().modify(|_, w| w.rxenable().set_bit());
+        self.uart.enable().modify(|_, w| w.rxenable().set_bit());
     }
 
     #[inline]
     pub fn disable(&mut self) {
-        self.0.enable().modify(|_, w| w.rxenable().clear_bit());
+        self.uart.enable().modify(|_, w| w.rxenable().clear_bit());
     }
 
     /// Low level function to read a word from the UART FIFO.
@@ -725,7 +796,7 @@ impl<Uart: Instance> Rx<Uart> {
     /// value if you use the manual parity mode. See chapter 4.6.2 for more information.
     #[inline(always)]
     pub fn read_fifo(&self) -> nb::Result<u32, Infallible> {
-        if self.0.rxstatus().read().rdavl().bit_is_clear() {
+        if self.uart.rxstatus().read().rdavl().bit_is_clear() {
             return Err(nb::Error::WouldBlock);
         }
         Ok(self.read_fifo_unchecked())
@@ -741,20 +812,15 @@ impl<Uart: Instance> Rx<Uart> {
     /// value if you use the manual parity mode. See chapter 4.6.2 for more information.
     #[inline(always)]
     pub fn read_fifo_unchecked(&self) -> u32 {
-        self.0.data().read().bits()
+        self.uart.data().read().bits()
     }
 
-    pub fn into_rx_with_irq(
-        self,
-        sysconfig: &mut pac::Sysconfig,
-        irqsel: &mut pac::Irqsel,
-        interrupt: pac::Interrupt,
-    ) -> RxWithIrq<Uart> {
-        RxWithIrq::new(self, sysconfig, irqsel, interrupt)
+    pub fn into_rx_with_irq(self) -> RxWithInterrupt<Uart> {
+        RxWithInterrupt::new(self)
     }
 
     pub fn release(self) -> Uart {
-        self.0
+        self.uart
     }
 }
 
@@ -811,14 +877,51 @@ impl<Uart: Instance> embedded_io::Read for Rx<Uart> {
     }
 }
 
+pub fn enable_tx(uart: &uart_base::RegisterBlock) {
+    uart.enable().modify(|_, w| w.txenable().set_bit());
+}
+
+pub fn disable_tx(uart: &uart_base::RegisterBlock) {
+    uart.enable().modify(|_, w| w.txenable().clear_bit());
+}
+
+pub fn enable_tx_interrupts(uart: &uart_base::RegisterBlock) {
+    uart.irq_enb().modify(|_, w| {
+        w.irq_tx().set_bit();
+        w.irq_tx_status().set_bit();
+        w.irq_tx_empty().set_bit()
+    });
+}
+
+pub fn disable_tx_interrupts(uart: &uart_base::RegisterBlock) {
+    uart.irq_enb().modify(|_, w| {
+        w.irq_tx().clear_bit();
+        w.irq_tx_status().clear_bit();
+        w.irq_tx_empty().clear_bit()
+    });
+}
+
 /// Serial transmitter
 ///
 /// Can be created by using the [Uart::split] or [UartBase::split] API.
-pub struct Tx<Uart>(Uart);
+pub struct Tx<Uart> {
+    uart: Uart,
+}
 
 impl<Uart: Instance> Tx<Uart> {
+    /// Retrieve a TX pin without expecting an explicit UART structure
+    ///
+    /// # Safety
+    ///
+    /// Circumvents the HAL safety guarantees.
+    pub unsafe fn steal() -> Self {
+        Self {
+            uart: Uart::steal(),
+        }
+    }
+
     fn new(uart: Uart) -> Self {
-        Self(uart)
+        Self { uart }
     }
 
     /// Direct access to the peripheral structure.
@@ -827,22 +930,45 @@ impl<Uart: Instance> Tx<Uart> {
     ///
     /// You must ensure that only registers related to the operation of the TX side are used.
     pub unsafe fn uart(&self) -> &Uart {
-        &self.0
+        &self.uart
     }
 
     #[inline]
     pub fn clear_fifo(&self) {
-        self.0.fifo_clr().write(|w| w.txfifo().set_bit());
+        self.uart.fifo_clr().write(|w| w.txfifo().set_bit());
     }
 
     #[inline]
     pub fn enable(&mut self) {
-        self.0.enable().modify(|_, w| w.txenable().set_bit());
+        // Safety: We own the UART structure
+        enable_tx(unsafe { Uart::reg_block() });
     }
 
     #[inline]
     pub fn disable(&mut self) {
-        self.0.enable().modify(|_, w| w.txenable().clear_bit());
+        // Safety: We own the UART structure
+        disable_tx(unsafe { Uart::reg_block() });
+    }
+
+    /// Enables the IRQ_TX, IRQ_TX_STATUS and IRQ_TX_EMPTY interrupts.
+    ///
+    /// - The IRQ_TX interrupt is generated when the TX FIFO is at least half empty.
+    /// - The IRQ_TX_STATUS interrupt is generated when write data is lost due to a FIFO overflow
+    /// - The IRQ_TX_EMPTY interrupt is generated when the TX FIFO is empty and the TXBUSY signal
+    ///   is 0
+    #[inline]
+    pub fn enable_interrupts(&self) {
+        // Safety: We own the UART structure
+        enable_tx_interrupts(unsafe { Uart::reg_block() });
+    }
+
+    /// Disables the IRQ_TX, IRQ_TX_STATUS and IRQ_TX_EMPTY interrupts.
+    ///
+    /// [Self::enable_interrupts] documents the interrupts.
+    #[inline]
+    pub fn disable_interrupts(&self) {
+        // Safety: We own the UART structure
+        disable_tx_interrupts(unsafe { Uart::reg_block() });
     }
 
     /// Low level function to write a word to the UART FIFO.
@@ -853,7 +979,7 @@ impl<Uart: Instance> Tx<Uart> {
     /// value if you use the manual parity mode. See chapter 11.4.1 for more information.
     #[inline(always)]
     pub fn write_fifo(&self, data: u32) -> nb::Result<(), Infallible> {
-        if self.0.txstatus().read().wrrdy().bit_is_clear() {
+        if self.uart.txstatus().read().wrrdy().bit_is_clear() {
             return Err(nb::Error::WouldBlock);
         }
         self.write_fifo_unchecked(data);
@@ -868,7 +994,11 @@ impl<Uart: Instance> Tx<Uart> {
     /// API.
     #[inline(always)]
     pub fn write_fifo_unchecked(&self, data: u32) {
-        self.0.data().write(|w| unsafe { w.bits(data) });
+        self.uart.data().write(|w| unsafe { w.bits(data) });
+    }
+
+    pub fn into_async(self) -> TxAsync<Uart> {
+        TxAsync::new(self)
     }
 }
 
@@ -931,36 +1061,23 @@ impl<Uart: Instance> embedded_io::Write for Tx<Uart> {
 ///     then call the [Self::irq_handler_max_size_or_timeout_based] in the interrupt service
 ///     routine. You have to call [Self::read_fixed_len_or_timeout_based_using_irq] in the ISR to
 ///     start reading the next packet.
-pub struct RxWithIrq<Uart> {
-    pub rx: Rx<Uart>,
-    pub interrupt: pac::Interrupt,
-}
+pub struct RxWithInterrupt<Uart>(Rx<Uart>);
 
-impl<Uart: Instance> RxWithIrq<Uart> {
-    pub fn new(
-        rx: Rx<Uart>,
-        syscfg: &mut pac::Sysconfig,
-        irqsel: &mut pac::Irqsel,
-        interrupt: pac::Interrupt,
-    ) -> Self {
-        enable_peripheral_clock(syscfg, PeripheralSelect::Irqsel);
-        irqsel
-            .uart0(Uart::IDX as usize)
-            .write(|w| unsafe { w.bits(interrupt as u32) });
-        Self { rx, interrupt }
+impl<Uart: Instance> RxWithInterrupt<Uart> {
+    pub fn new(rx: Rx<Uart>) -> Self {
+        Self(rx)
     }
 
     /// This function should be called once at initialization time if the regular
     /// [Self::irq_handler] is used to read the UART receiver to enable and start the receiver.
     pub fn start(&mut self) {
-        self.rx.enable();
+        self.0.enable();
         self.enable_rx_irq_sources(true);
-        unsafe { enable_interrupt(self.interrupt) };
     }
 
     #[inline(always)]
     pub fn uart(&self) -> &Uart {
-        &self.rx.0
+        &self.0.uart
     }
 
     /// This function is used together with the [Self::irq_handler_max_size_or_timeout_based]
@@ -1006,7 +1123,7 @@ impl<Uart: Instance> RxWithIrq<Uart> {
 
     pub fn cancel_transfer(&mut self) {
         self.disable_rx_irq_sources();
-        self.rx.clear_fifo();
+        self.0.clear_fifo();
     }
 
     /// This function should be called in the user provided UART interrupt handler.
@@ -1017,7 +1134,7 @@ impl<Uart: Instance> RxWithIrq<Uart> {
     /// This function will not disable the RX interrupts, so you don't need to call any other
     /// API after calling this function to continue emptying the FIFO. RX errors are handled
     /// as partial errors and are returned as part of the [IrqResult].
-    pub fn irq_handler(&mut self, buf: &mut [u8; 16]) -> IrqResult {
+    pub fn on_interrupt(&mut self, buf: &mut [u8; 16]) -> IrqResult {
         let mut result = IrqResult::default();
 
         let irq_end = self.uart().irq_end().read();
@@ -1040,7 +1157,7 @@ impl<Uart: Instance> RxWithIrq<Uart> {
         if irq_end.irq_rx_to().bit_is_set() {
             loop {
                 // While there is data in the FIFO, write it into the reception buffer
-                let read_result = self.rx.read();
+                let read_result = self.0.read();
                 if let Some(byte) = self.read_handler(&mut result.errors, &read_result) {
                     buf[result.bytes_read] = byte;
                     result.bytes_read += 1;
@@ -1074,7 +1191,7 @@ impl<Uart: Instance> RxWithIrq<Uart> {
     /// If passed buffer is equal to or larger than the specified maximum length, an
     /// [BufferTooShortError] will be returned. Other RX errors are treated as partial errors
     /// and returned inside the [IrqResultMaxSizeOrTimeout] structure.
-    pub fn irq_handler_max_size_or_timeout_based(
+    pub fn on_interrupt_max_size_or_timeout_based(
         &mut self,
         context: &mut IrqContextTimeoutOrMaxSize,
         buf: &mut [u8],
@@ -1123,7 +1240,7 @@ impl<Uart: Instance> RxWithIrq<Uart> {
                 if context.rx_idx == context.max_len {
                     break;
                 }
-                let read_result = self.rx.read();
+                let read_result = self.0.read();
                 if let Some(byte) = self.read_handler(&mut result.errors, &read_result) {
                     buf[context.rx_idx] = byte;
                     context.rx_idx += 1;
@@ -1197,7 +1314,7 @@ impl<Uart: Instance> RxWithIrq<Uart> {
         context: &mut IrqContextTimeoutOrMaxSize,
     ) {
         self.disable_rx_irq_sources();
-        self.rx.disable();
+        self.0.disable();
         res.bytes_read = context.rx_idx;
         res.complete = true;
         context.mode = IrqReceptionMode::Idle;
@@ -1210,6 +1327,9 @@ impl<Uart: Instance> RxWithIrq<Uart> {
     /// The user must ensure that these instances are not used to create multiple overlapping
     /// UART drivers.
     pub unsafe fn release(self) -> Uart {
-        self.rx.release()
+        self.0.release()
     }
 }
+
+pub mod asynch;
+pub use asynch::*;
diff --git a/vorago-reb1/Cargo.toml b/vorago-reb1/Cargo.toml
index 31ee76e..6b64996 100644
--- a/vorago-reb1/Cargo.toml
+++ b/vorago-reb1/Cargo.toml
@@ -19,6 +19,7 @@ bitfield = ">=0.17, <=0.18"
 max116xx-10bit = "0.3"
 
 [dependencies.va108xx-hal]
+path = "../va108xx-hal"
 version = ">=0.8, <0.9"
 features = ["rt"]
 
diff --git a/vorago-reb1/examples/adxl343-accelerometer.rs b/vorago-reb1/examples/adxl343-accelerometer.rs
index ce4c3f5..8f99e4d 100644
--- a/vorago-reb1/examples/adxl343-accelerometer.rs
+++ b/vorago-reb1/examples/adxl343-accelerometer.rs
@@ -31,7 +31,7 @@ fn main() -> ! {
     rprintln!("-- Vorago Accelerometer Example --");
     let mut dp = pac::Peripherals::take().unwrap();
     let mut delay = set_up_ms_delay_provider(&mut dp.sysconfig, 50.MHz(), dp.tim0);
-    let pinsa = PinsA::new(&mut dp.sysconfig, None, dp.porta);
+    let pinsa = PinsA::new(&mut dp.sysconfig, dp.porta);
     let (sck, mosi, miso) = (
         pinsa.pa20.into_funsel_2(),
         pinsa.pa19.into_funsel_2(),
diff --git a/vorago-reb1/examples/blinky-button-irq.rs b/vorago-reb1/examples/blinky-button-irq.rs
index 0f29f5f..986a10b 100644
--- a/vorago-reb1/examples/blinky-button-irq.rs
+++ b/vorago-reb1/examples/blinky-button-irq.rs
@@ -13,7 +13,7 @@ use va108xx_hal::{
     gpio::{FilterType, InterruptEdge, PinsA},
     pac::{self, interrupt},
     prelude::*,
-    timer::{default_ms_irq_handler, set_up_ms_tick, IrqCfg},
+    timer::{default_ms_irq_handler, set_up_ms_tick, InterruptConfig},
 };
 use vorago_reb1::button::Button;
 use vorago_reb1::leds::Leds;
@@ -35,28 +35,29 @@ fn main() -> ! {
     rtt_init_print!();
     rprintln!("-- Vorago Button IRQ Example --");
     let mut dp = pac::Peripherals::take().unwrap();
-    let pinsa = PinsA::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.porta);
+    let pinsa = PinsA::new(&mut dp.sysconfig, dp.porta);
     let edge_irq = match PRESS_MODE {
         PressMode::Toggle => InterruptEdge::HighToLow,
         PressMode::Keep => InterruptEdge::BothEdges,
     };
 
     // Configure an edge interrupt on the button and route it to interrupt vector 15
-    let mut button = Button::new(pinsa.pa11.into_floating_input()).edge_irq(
+    let mut button = Button::new(pinsa.pa11.into_floating_input());
+    button.configure_edge_interrupt(
         edge_irq,
-        IrqCfg::new(pac::interrupt::OC15, true, true),
+        InterruptConfig::new(pac::interrupt::OC15, true, true),
         Some(&mut dp.sysconfig),
         Some(&mut dp.irqsel),
     );
 
     if PRESS_MODE == PressMode::Toggle {
         // This filter debounces the switch for edge based interrupts
-        button = button.filter_type(FilterType::FilterFourClockCycles, FilterClkSel::Clk1);
+        button.configure_filter_type(FilterType::FilterFourClockCycles, FilterClkSel::Clk1);
         set_clk_div_register(&mut dp.sysconfig, FilterClkSel::Clk1, 50_000);
     }
 
     set_up_ms_tick(
-        IrqCfg::new(pac::Interrupt::OC0, true, true),
+        InterruptConfig::new(pac::Interrupt::OC0, true, true),
         &mut dp.sysconfig,
         Some(&mut dp.irqsel),
         50.MHz(),
diff --git a/vorago-reb1/examples/blinky-leds.rs b/vorago-reb1/examples/blinky-leds.rs
index 0e0e0cf..2650b09 100644
--- a/vorago-reb1/examples/blinky-leds.rs
+++ b/vorago-reb1/examples/blinky-leds.rs
@@ -61,7 +61,7 @@ fn main() -> ! {
             }
         }
         LibType::Hal => {
-            let pins = PinsA::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.porta);
+            let pins = PinsA::new(&mut dp.sysconfig, dp.porta);
             let mut led1 = pins.pa10.into_readable_push_pull_output();
             let mut led2 = pins.pa7.into_readable_push_pull_output();
             let mut led3 = pins.pa6.into_readable_push_pull_output();
@@ -87,27 +87,25 @@ fn main() -> ! {
             }
         }
         LibType::Bsp => {
-            let pinsa = PinsA::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.porta);
+            let pinsa = PinsA::new(&mut dp.sysconfig, dp.porta);
             let mut leds = Leds::new(
                 pinsa.pa10.into_push_pull_output(),
                 pinsa.pa7.into_push_pull_output(),
                 pinsa.pa6.into_push_pull_output(),
             );
             let mut delay = set_up_ms_delay_provider(&mut dp.sysconfig, 50.MHz(), dp.tim0);
-            loop {
-                for _ in 0..10 {
-                    // Blink all LEDs quickly
-                    for led in leds.iter_mut() {
-                        led.toggle();
-                    }
-                    delay.delay_ms(500);
+            for _ in 0..10 {
+                // Blink all LEDs quickly
+                for led in leds.iter_mut() {
+                    led.toggle();
                 }
-                // Now use a wave pattern
-                loop {
-                    for led in leds.iter_mut() {
-                        led.toggle();
-                        delay.delay_ms(200);
-                    }
+                delay.delay_ms(500);
+            }
+            // Now use a wave pattern
+            loop {
+                for led in leds.iter_mut() {
+                    led.toggle();
+                    delay.delay_ms(200);
                 }
             }
         }
diff --git a/vorago-reb1/examples/max11619-adc.rs b/vorago-reb1/examples/max11619-adc.rs
index 5936e68..e400096 100644
--- a/vorago-reb1/examples/max11619-adc.rs
+++ b/vorago-reb1/examples/max11619-adc.rs
@@ -22,9 +22,9 @@ use va108xx_hal::{
     pac::{self, interrupt},
     prelude::*,
     spi::{Spi, SpiBase, SpiConfig},
-    timer::{default_ms_irq_handler, set_up_ms_tick, DelayMs, IrqCfg},
+    timer::{default_ms_irq_handler, set_up_ms_tick, DelayMs, InterruptConfig},
 };
-use va108xx_hal::{port_mux, FunSel, PortSel};
+use va108xx_hal::{port_function_select, FunSel, PortSel};
 use vorago_reb1::max11619::{
     max11619_externally_clocked_no_wakeup, max11619_externally_clocked_with_wakeup,
     max11619_internally_clocked, EocPin, AN2_CHANNEL, POTENTIOMETER_CHANNEL,
@@ -112,7 +112,7 @@ fn main() -> ! {
 
     let mut dp = pac::Peripherals::take().unwrap();
     let tim0 = set_up_ms_tick(
-        IrqCfg::new(pac::Interrupt::OC0, true, true),
+        InterruptConfig::new(pac::Interrupt::OC0, true, true),
         &mut dp.sysconfig,
         Some(&mut dp.irqsel),
         SYS_CLK,
@@ -123,7 +123,7 @@ fn main() -> ! {
         cortex_m::peripheral::NVIC::unmask(pac::Interrupt::OC0);
     }
 
-    let pinsa = PinsA::new(&mut dp.sysconfig, None, dp.porta);
+    let pinsa = PinsA::new(&mut dp.sysconfig, dp.porta);
     let spi_cfg = SpiConfig::default()
         .clk_cfg(SpiClkConfig::from_clk(SYS_CLK, 3.MHz()).unwrap())
         .mode(MODE_0)
@@ -135,10 +135,10 @@ fn main() -> ! {
     );
 
     if MUX_MODE == MuxMode::PortB19to17 {
-        port_mux(&mut dp.ioconfig, PortSel::PortB, 19, FunSel::Sel1).ok();
-        port_mux(&mut dp.ioconfig, PortSel::PortB, 18, FunSel::Sel2).ok();
-        port_mux(&mut dp.ioconfig, PortSel::PortB, 17, FunSel::Sel1).ok();
-        port_mux(&mut dp.ioconfig, PortSel::PortB, 16, FunSel::Sel1).ok();
+        port_function_select(&mut dp.ioconfig, PortSel::PortB, 19, FunSel::Sel1).ok();
+        port_function_select(&mut dp.ioconfig, PortSel::PortB, 18, FunSel::Sel2).ok();
+        port_function_select(&mut dp.ioconfig, PortSel::PortB, 17, FunSel::Sel1).ok();
+        port_function_select(&mut dp.ioconfig, PortSel::PortB, 16, FunSel::Sel1).ok();
     }
     // Set the accelerometer chip select low in case the board slot is populated
     let mut accel_cs = pinsa.pa16.into_push_pull_output();
diff --git a/vorago-reb1/src/button.rs b/vorago-reb1/src/button.rs
index 4c7a03d..81e1b87 100644
--- a/vorago-reb1/src/button.rs
+++ b/vorago-reb1/src/button.rs
@@ -7,7 +7,7 @@
 use embedded_hal::digital::InputPin;
 use va108xx_hal::{
     gpio::{FilterClkSel, FilterType, InputFloating, InterruptEdge, InterruptLevel, Pin, PA11},
-    pac, IrqCfg,
+    pac, InterruptConfig,
 };
 
 pub struct Button {
@@ -30,37 +30,34 @@ impl Button {
     }
 
     /// Configures an IRQ on edge.
-    pub fn edge_irq(
-        mut self,
+    pub fn configure_edge_interrupt(
+        &mut self,
         edge_type: InterruptEdge,
-        irq_cfg: IrqCfg,
+        irq_cfg: InterruptConfig,
         syscfg: Option<&mut pac::Sysconfig>,
         irqsel: Option<&mut pac::Irqsel>,
-    ) -> Self {
-        self.button = self
-            .button
-            .interrupt_edge(edge_type, irq_cfg, syscfg, irqsel);
-        self
+    ) {
+        self.button
+            .configure_edge_interrupt(edge_type, irq_cfg, syscfg, irqsel);
     }
 
     /// Configures an IRQ on level.
-    pub fn level_irq(
-        mut self,
+    pub fn configure_level_interrupt(
+        &mut self,
         level: InterruptLevel,
-        irq_cfg: IrqCfg,
+        irq_cfg: InterruptConfig,
         syscfg: Option<&mut pac::Sysconfig>,
         irqsel: Option<&mut pac::Irqsel>,
-    ) -> Self {
-        self.button = self.button.interrupt_level(level, irq_cfg, syscfg, irqsel);
-        self
+    ) {
+        self.button
+            .configure_level_interrupt(level, irq_cfg, syscfg, irqsel);
     }
 
     /// Configures a filter on the button. This can be useful for debouncing the switch.
     ///
     /// Please note that you still have to set a clock divisor yourself using the
     /// [`va108xx_hal::clock::set_clk_div_register`] function in order for this to work.
-    pub fn filter_type(mut self, filter: FilterType, clksel: FilterClkSel) -> Self {
-        self.button = self.button.filter_type(filter, clksel);
-        self
+    pub fn configure_filter_type(&mut self, filter: FilterType, clksel: FilterClkSel) {
+        self.button.configure_filter_type(filter, clksel);
     }
 }
diff --git a/vscode/launch.json b/vscode/launch.json
index 37982c6..20edd1a 100644
--- a/vscode/launch.json
+++ b/vscode/launch.json
@@ -363,8 +363,8 @@
             "cwd": "${workspaceRoot}",
             "device": "Cortex-M0",
             "svdFile": "./va108xx/svd/va108xx.svd.patched",
-            "preLaunchTask": "rust: cargo build uart irq",
-            "executable": "${workspaceFolder}/target/thumbv6m-none-eabi/debug/uart-rtic",
+            "preLaunchTask": "uart-echo-rtic-example",
+            "executable": "${workspaceFolder}/target/thumbv6m-none-eabi/debug/uart-echo-rtic",
             "interface": "jtag",
             "runToEntryPoint": "main",
             "rttConfig": {
@@ -523,5 +523,29 @@
                 ]
             }
         },
+        {
+            "type": "cortex-debug",
+            "request": "launch",
+            "name": "Async UART",
+            "servertype": "jlink",
+            "cwd": "${workspaceRoot}",
+            "device": "Cortex-M0",
+            "svdFile": "./va108xx/svd/va108xx.svd.patched",
+            "preLaunchTask": "async-uart",
+            "executable": "${workspaceFolder}/target/thumbv6m-none-eabi/debug/async-uart",
+            "interface": "jtag",
+            "runToEntryPoint": "main",
+            "rttConfig": {
+                "enabled": true,
+                "address": "auto",
+                "decoders": [
+                    {
+                        "port": 0,
+                        "timestamp": true,
+                        "type": "console"
+                    }
+                ]
+            }
+        },
     ]
 }
\ No newline at end of file
diff --git a/vscode/tasks.json b/vscode/tasks.json
index 796f555..671efe2 100644
--- a/vscode/tasks.json
+++ b/vscode/tasks.json
@@ -276,6 +276,16 @@
         "async-gpio"
       ]
     },
+    {
+      "label": "async-uart",
+      "type": "shell",
+      "command": "~/.cargo/bin/cargo", // note: full path to the cargo
+      "args": [
+        "build",
+        "--bin",
+        "async-uart"
+      ]
+    },
     {
       "label": "bootloader",
       "type": "shell",