diff --git a/README.md b/README.md
index d3d2cc8..fee4f74 100644
--- a/README.md
+++ b/README.md
@@ -510,6 +510,7 @@ Register-level C drivers for the RP2350 with no SDK dependencies. Each driver fo
| [0x0b_spi_cbm](drivers/0x0b_spi_cbm) | SPI bus init & bidirectional transfer | SPI0 |
| [0x0c_multicore_cbm](drivers/0x0c_multicore_cbm) | Dual-core launch & FIFO messaging | SIO FIFO, PSM |
| [0x0d_timer_cbm](drivers/0x0d_timer_cbm) | Repeating timer alarm callbacks | TIMER0, TICKS |
+| [0x0e_watchdog_cbm](drivers/0x0e_watchdog_cbm) | Watchdog enable, feed & reboot detection | WATCHDOG, PSM |
diff --git a/drivers/0x0e_watchdog_cbm/Inc/rp2350.h b/drivers/0x0e_watchdog_cbm/Inc/rp2350.h
new file mode 100644
index 0000000..09663c7
--- /dev/null
+++ b/drivers/0x0e_watchdog_cbm/Inc/rp2350.h
@@ -0,0 +1,279 @@
+/**
+ ******************************************************************************
+ * @file rp2350.h
+ * @author Kevin Thomas
+ * @brief RP2350 Device Peripheral Access Layer Header File.
+ *
+ * Memory-mapped register structures and peripheral base addresses
+ * for the RP2350 microcontroller (Cortex-M33 dual-core). All
+ * register offsets verified against the RP2350 datasheet
+ * (RP-008373-DS-2).
+ *
+ ******************************************************************************
+ * @attention
+ *
+ * Copyright (c) 2026 Kevin Thomas.
+ * All rights reserved.
+ *
+ * This software is licensed under terms that can be found in the LICENSE file
+ * in the root directory of this software component.
+ * If no LICENSE file comes with this software, it is provided AS-IS.
+ *
+ ******************************************************************************
+ */
+
+#ifndef __RP2350_H
+#define __RP2350_H
+
+#include
+#include
+
+/*!< Defines 'read / write' permissions */
+#define __IO volatile
+
+/*!< Stack addresses */
+#define STACK_TOP 0x20082000UL
+#define STACK_LIMIT 0x2007A000UL
+
+/*!< Memory map */
+#define XIP_BASE 0x10000000UL
+#define SRAM_BASE 0x20000000UL
+#define SIO_BASE 0xD0000000UL
+#define PPB_BASE 0xE0000000UL
+
+/*!< APB peripherals */
+#define PSM_BASE 0x40018000UL
+#define CLOCKS_BASE 0x40010000UL
+#define RESETS_BASE 0x40020000UL
+#define IO_BANK0_BASE 0x40028000UL
+#define PADS_BANK0_BASE 0x40038000UL
+#define XOSC_BASE 0x40048000UL
+#define UART0_BASE 0x40070000UL
+#define WATCHDOG_BASE 0x400D8000UL
+#define TICKS_BASE 0x40108000UL
+
+/*!< Atomic register alias offsets */
+#define ATOMIC_SET_OFFSET 0x2000UL
+#define ATOMIC_CLR_OFFSET 0x3000UL
+
+/**
+ * @brief XOSC (External Crystal Oscillator)
+ */
+typedef struct
+{
+ __IO uint32_t CTRL; // Control register Address offset: 0x00
+ __IO uint32_t STATUS; // Status register Address offset: 0x04
+ __IO uint32_t DORMANT; // Dormant mode Address offset: 0x08
+ __IO uint32_t STARTUP; // Startup delay Address offset: 0x0C
+ __IO uint32_t COUNT; // Frequency count Address offset: 0x10
+} XOSC_TypeDef;
+
+/**
+ * @brief CLOCKS
+ */
+typedef struct
+{
+ __IO uint32_t RESERVED0[12]; // GPOUT0..GPOUT3 registers Address offset: 0x00-0x2C
+ __IO uint32_t CLK_REF_CTRL; // Reference clock control Address offset: 0x30
+ __IO uint32_t RESERVED1[5]; // CLK_REF_DIV..CLK_SYS_SELECTED Address offset: 0x34-0x44
+ __IO uint32_t CLK_PERI_CTRL; // Peripheral clock control Address offset: 0x48
+} CLOCKS_TypeDef;
+
+/**
+ * @brief RESETS
+ */
+typedef struct
+{
+ __IO uint32_t RESET; // Reset control Address offset: 0x00
+ __IO uint32_t WDSEL; // Watchdog select Address offset: 0x04
+ __IO uint32_t RESET_DONE; // Reset done status Address offset: 0x08
+} RESETS_TypeDef;
+
+/**
+ * @brief IO_BANK0 GPIO Control (one per GPIO)
+ */
+typedef struct
+{
+ __IO uint32_t STATUS; // GPIO status Address offset: 0x00
+ __IO uint32_t CTRL; // GPIO control Address offset: 0x04
+} IO_BANK0_GPIO_TypeDef;
+
+/**
+ * @brief IO_BANK0
+ */
+typedef struct
+{
+ IO_BANK0_GPIO_TypeDef GPIO[30]; // GPIO 0-29 status/ctrl pairs Address offset: 0x000-0x0E8
+} IO_BANK0_TypeDef;
+
+/**
+ * @brief PADS_BANK0
+ */
+typedef struct
+{
+ __IO uint32_t VOLTAGE_SELECT; // Voltage select Address offset: 0x00
+ __IO uint32_t GPIO[30]; // GPIO 0-29 pad control Address offset: 0x04-0x78
+} PADS_BANK0_TypeDef;
+
+/**
+ * @brief PSM (Power-on State Machine)
+ */
+typedef struct
+{
+ __IO uint32_t FRCE_ON; // Force block out of reset Address offset: 0x00
+ __IO uint32_t FRCE_OFF; // Force block into reset Address offset: 0x04
+ __IO uint32_t WDSEL; // Watchdog select Address offset: 0x08
+ __IO uint32_t DONE; // Subsystem ready status Address offset: 0x0C
+} PSM_TypeDef;
+
+/**
+ * @brief WATCHDOG
+ */
+typedef struct
+{
+ __IO uint32_t CTRL; // Watchdog control Address offset: 0x00
+ __IO uint32_t LOAD; // Load the watchdog timer (bits 23:0) Address offset: 0x04
+ __IO uint32_t REASON; // Reason for last reset Address offset: 0x08
+ __IO uint32_t SCRATCH[8]; // Scratch registers 0-7 Address offset: 0x0C-0x28
+} WATCHDOG_TypeDef;
+
+/**
+ * @brief TICKS (Tick Generator entry, one per source)
+ */
+typedef struct
+{
+ __IO uint32_t CTRL; // Enable tick generator (bit 0) Address offset: 0x00
+ __IO uint32_t CYCLES; // Number of CLK_REF cycles per tick (bits 8:0) Address offset: 0x04
+ __IO uint32_t COUNT; // Running counter of ticks (read-only) Address offset: 0x08
+} TICKS_Entry_TypeDef;
+
+/**
+ * @brief Peripheral Definitions
+ */
+#define PSM ((PSM_TypeDef *) PSM_BASE)
+#define XOSC ((XOSC_TypeDef *) XOSC_BASE)
+#define CLOCKS ((CLOCKS_TypeDef *) CLOCKS_BASE)
+#define RESETS ((RESETS_TypeDef *) RESETS_BASE)
+#define IO_BANK0 ((IO_BANK0_TypeDef *) IO_BANK0_BASE)
+#define PADS_BANK0 ((PADS_BANK0_TypeDef *) PADS_BANK0_BASE)
+#define SIO ((volatile uint32_t *) SIO_BASE)
+#define CPACR ((volatile uint32_t *) (PPB_BASE + 0x0ED88UL))
+#define WATCHDOG ((WATCHDOG_TypeDef *) WATCHDOG_BASE)
+
+/**
+ * @brief TICKS entries (indexed from TICKS_BASE, 12-byte stride)
+ */
+#define TICKS_WATCHDOG ((TICKS_Entry_TypeDef *) (TICKS_BASE + 0x00UL))
+
+/**
+ * @brief XOSC bit definitions
+ */
+#define XOSC_STATUS_STABLE_SHIFT 31U
+
+/**
+ * @brief CPACR bit definitions
+ */
+#define CPACR_CP0_SHIFT 0U
+#define CPACR_CP1_SHIFT 1U
+
+/**
+ * @brief CLK_REF bit definitions
+ */
+#define CLK_REF_CTRL_SRC_XOSC 2U
+
+/**
+ * @brief CLOCKS bit definitions
+ */
+#define CLK_PERI_CTRL_ENABLE_SHIFT 11U
+#define CLK_PERI_CTRL_AUXSRC_SHIFT 5U
+#define CLK_PERI_CTRL_AUXSRC_MASK (0x07U << CLK_PERI_CTRL_AUXSRC_SHIFT)
+#define CLK_PERI_CTRL_AUXSRC_XOSC 4U
+
+/**
+ * @brief RESETS bit definitions
+ */
+#define RESETS_RESET_IO_BANK0_SHIFT 6U
+#define RESETS_RESET_PADS_BANK0_SHIFT 9U
+#define RESETS_RESET_UART0_SHIFT 26U
+
+/**
+ * @brief IO_BANK0 bit definitions
+ */
+#define IO_BANK0_CTRL_FUNCSEL_MASK 0x1FU
+#define IO_BANK0_CTRL_FUNCSEL_UART 0x02U
+#define IO_BANK0_CTRL_FUNCSEL_SIO 0x05U
+#define IO_BANK0_CTRL_FUNCSEL_NULL 0x1FU
+
+/**
+ * @brief PADS_BANK0 bit definitions
+ */
+#define PADS_BANK0_OD_SHIFT 7U
+#define PADS_BANK0_IE_SHIFT 6U
+#define PADS_BANK0_ISO_SHIFT 8U
+#define PADS_BANK0_PUE_SHIFT 3U
+#define PADS_BANK0_PDE_SHIFT 2U
+
+/**
+ * @brief SIO GPIO register offsets (word indices from SIO_BASE)
+ */
+#define SIO_GPIO_OUT_SET_OFFSET (0x018U / 4U)
+#define SIO_GPIO_OUT_CLR_OFFSET (0x020U / 4U)
+#define SIO_GPIO_OE_SET_OFFSET (0x038U / 4U)
+
+/**
+ * @brief UART register offsets (word indices from UART0_BASE)
+ */
+#define UART_DR_OFFSET (0x000U / 4U)
+#define UART_FR_OFFSET (0x018U / 4U)
+#define UART_IBRD_OFFSET (0x024U / 4U)
+#define UART_FBRD_OFFSET (0x028U / 4U)
+#define UART_LCR_H_OFFSET (0x02CU / 4U)
+#define UART_CR_OFFSET (0x030U / 4U)
+
+/**
+ * @brief UART flag register bit definitions
+ */
+#define UART_FR_TXFF_MASK 32U
+#define UART_FR_RXFE_MASK 16U
+
+/**
+ * @brief UART line control and enable values
+ */
+#define UART_LCR_H_8N1_FIFO 0x70U
+#define UART_CR_ENABLE ((3U << 8) | 1U)
+
+/**
+ * @brief PSM bit definitions
+ */
+#define PSM_WDSEL_ALL_MASK 0x01FFFFFFUL
+#define PSM_WDSEL_ROSC_SHIFT 2U
+#define PSM_WDSEL_XOSC_SHIFT 3U
+
+/**
+ * @brief WATCHDOG CTRL bit definitions
+ */
+#define WATCHDOG_CTRL_TRIGGER_SHIFT 31U
+#define WATCHDOG_CTRL_ENABLE_SHIFT 30U
+#define WATCHDOG_CTRL_PAUSE_DBG1_SHIFT 26U
+#define WATCHDOG_CTRL_PAUSE_DBG0_SHIFT 25U
+#define WATCHDOG_CTRL_PAUSE_JTAG_SHIFT 24U
+#define WATCHDOG_CTRL_TIME_MASK 0x00FFFFFFUL
+
+/**
+ * @brief WATCHDOG LOAD bit definitions
+ */
+#define WATCHDOG_LOAD_MAX 0x00FFFFFFUL
+
+/**
+ * @brief WATCHDOG REASON bit definitions
+ */
+#define WATCHDOG_REASON_FORCE_SHIFT 1U
+#define WATCHDOG_REASON_TIMER_SHIFT 0U
+
+/**
+ * @brief TICKS bit definitions
+ */
+#define TICKS_CTRL_ENABLE_SHIFT 0U
+#define TICKS_CYCLES_12MHZ 12U
+
+#endif /* __RP2350_H */
diff --git a/drivers/0x0e_watchdog_cbm/Inc/rp2350_coprocessor.h b/drivers/0x0e_watchdog_cbm/Inc/rp2350_coprocessor.h
new file mode 100644
index 0000000..6e26fa0
--- /dev/null
+++ b/drivers/0x0e_watchdog_cbm/Inc/rp2350_coprocessor.h
@@ -0,0 +1,33 @@
+/**
+ ******************************************************************************
+ * @file rp2350_coprocessor.h
+ * @author Kevin Thomas
+ * @brief Coprocessor access control driver header for RP2350.
+ *
+ * Enables coprocessor access via the CPACR register.
+ *
+ ******************************************************************************
+ * @attention
+ *
+ * Copyright (c) 2026 Kevin Thomas.
+ * All rights reserved.
+ *
+ * This software is licensed under terms that can be found in the LICENSE file
+ * in the root directory of this software component.
+ * If no LICENSE file comes with this software, it is provided AS-IS.
+ *
+ ******************************************************************************
+ */
+
+#ifndef __RP2350_COPROCESSOR_H
+#define __RP2350_COPROCESSOR_H
+
+#include "rp2350.h"
+
+/**
+ * @brief Enable coprocessor access via CPACR with DSB/ISB barriers.
+ * @retval None
+ */
+void coprocessor_enable(void);
+
+#endif /* __RP2350_COPROCESSOR_H */
diff --git a/drivers/0x0e_watchdog_cbm/Inc/rp2350_delay.h b/drivers/0x0e_watchdog_cbm/Inc/rp2350_delay.h
new file mode 100644
index 0000000..185f919
--- /dev/null
+++ b/drivers/0x0e_watchdog_cbm/Inc/rp2350_delay.h
@@ -0,0 +1,42 @@
+/**
+ ******************************************************************************
+ * @file rp2350_delay.h
+ * @author Kevin Thomas
+ * @brief Delay driver header for RP2350.
+ *
+ * Millisecond and microsecond busy-wait delays calibrated for
+ * a 12 MHz clock.
+ *
+ ******************************************************************************
+ * @attention
+ *
+ * Copyright (c) 2026 Kevin Thomas.
+ * All rights reserved.
+ *
+ * This software is licensed under terms that can be found in the LICENSE file
+ * in the root directory of this software component.
+ * If no LICENSE file comes with this software, it is provided AS-IS.
+ *
+ ******************************************************************************
+ */
+
+#ifndef __RP2350_DELAY_H
+#define __RP2350_DELAY_H
+
+#include "rp2350.h"
+
+/**
+ * @brief Delay for the specified number of milliseconds.
+ * @param ms number of milliseconds to delay
+ * @retval None
+ */
+void delay_ms(uint32_t ms);
+
+/**
+ * @brief Delay for the specified number of microseconds.
+ * @param us number of microseconds to delay
+ * @retval None
+ */
+void delay_us(uint32_t us);
+
+#endif /* __RP2350_DELAY_H */
diff --git a/drivers/0x0e_watchdog_cbm/Inc/rp2350_reset.h b/drivers/0x0e_watchdog_cbm/Inc/rp2350_reset.h
new file mode 100644
index 0000000..4a10831
--- /dev/null
+++ b/drivers/0x0e_watchdog_cbm/Inc/rp2350_reset.h
@@ -0,0 +1,33 @@
+/**
+ ******************************************************************************
+ * @file rp2350_reset.h
+ * @author Kevin Thomas
+ * @brief Reset controller driver header for RP2350.
+ *
+ * Provides subsystem reset release for IO_BANK0.
+ *
+ ******************************************************************************
+ * @attention
+ *
+ * Copyright (c) 2026 Kevin Thomas.
+ * All rights reserved.
+ *
+ * This software is licensed under terms that can be found in the LICENSE file
+ * in the root directory of this software component.
+ * If no LICENSE file comes with this software, it is provided AS-IS.
+ *
+ ******************************************************************************
+ */
+
+#ifndef __RP2350_RESET_H
+#define __RP2350_RESET_H
+
+#include "rp2350.h"
+
+/**
+ * @brief Release IO_BANK0 from reset and wait until ready.
+ * @retval None
+ */
+void reset_init_subsystem(void);
+
+#endif /* __RP2350_RESET_H */
diff --git a/drivers/0x0e_watchdog_cbm/Inc/rp2350_reset_handler.h b/drivers/0x0e_watchdog_cbm/Inc/rp2350_reset_handler.h
new file mode 100644
index 0000000..2565e29
--- /dev/null
+++ b/drivers/0x0e_watchdog_cbm/Inc/rp2350_reset_handler.h
@@ -0,0 +1,33 @@
+/**
+ ******************************************************************************
+ * @file rp2350_reset_handler.h
+ * @author Kevin Thomas
+ * @brief Reset handler header for RP2350.
+ *
+ * Entry point after reset. Performs stack initialization, XOSC
+ * setup, subsystem reset release, UART initialization,
+ * coprocessor enable, and branches to main().
+ *
+ ******************************************************************************
+ * @attention
+ *
+ * Copyright (c) 2026 Kevin Thomas.
+ * All rights reserved.
+ *
+ * This software is licensed under terms that can be found in the LICENSE file
+ * in the root directory of this software component.
+ * If no LICENSE file comes with this software, it is provided AS-IS.
+ *
+ ******************************************************************************
+ */
+
+#ifndef __RP2350_RESET_HANDLER_H
+#define __RP2350_RESET_HANDLER_H
+
+/**
+ * @brief Reset handler entry point (naked, noreturn).
+ * @retval None
+ */
+void Reset_Handler(void) __attribute__((noreturn));
+
+#endif /* __RP2350_RESET_HANDLER_H */
diff --git a/drivers/0x0e_watchdog_cbm/Inc/rp2350_stack.h b/drivers/0x0e_watchdog_cbm/Inc/rp2350_stack.h
new file mode 100644
index 0000000..952d26e
--- /dev/null
+++ b/drivers/0x0e_watchdog_cbm/Inc/rp2350_stack.h
@@ -0,0 +1,34 @@
+/**
+ ******************************************************************************
+ * @file rp2350_stack.h
+ * @author Kevin Thomas
+ * @brief Stack pointer initialization header for RP2350.
+ *
+ * Sets MSP, PSP, MSPLIM, and PSPLIM from the STACK_TOP and
+ * STACK_LIMIT values defined in rp2350.h.
+ *
+ ******************************************************************************
+ * @attention
+ *
+ * Copyright (c) 2026 Kevin Thomas.
+ * All rights reserved.
+ *
+ * This software is licensed under terms that can be found in the LICENSE file
+ * in the root directory of this software component.
+ * If no LICENSE file comes with this software, it is provided AS-IS.
+ *
+ ******************************************************************************
+ */
+
+#ifndef __RP2350_STACK_H
+#define __RP2350_STACK_H
+
+#include "rp2350.h"
+
+/**
+ * @brief Initialize MSP, PSP, MSPLIM, and PSPLIM stack pointers.
+ * @retval None
+ */
+void stack_init(void);
+
+#endif /* __RP2350_STACK_H */
diff --git a/drivers/0x0e_watchdog_cbm/Inc/rp2350_uart.h b/drivers/0x0e_watchdog_cbm/Inc/rp2350_uart.h
new file mode 100644
index 0000000..6e9df10
--- /dev/null
+++ b/drivers/0x0e_watchdog_cbm/Inc/rp2350_uart.h
@@ -0,0 +1,73 @@
+/**
+ ******************************************************************************
+ * @file rp2350_uart.h
+ * @author Kevin Thomas
+ * @brief UART0 driver header for RP2350.
+ *
+ * Bare-metal UART0 driver supporting TX/RX on GPIO 0/1 at
+ * 115200 baud (12 MHz XOSC clock).
+ *
+ ******************************************************************************
+ * @attention
+ *
+ * Copyright (c) 2026 Kevin Thomas.
+ * All rights reserved.
+ *
+ * This software is licensed under terms that can be found in the LICENSE file
+ * in the root directory of this software component.
+ * If no LICENSE file comes with this software, it is provided AS-IS.
+ *
+ ******************************************************************************
+ */
+
+#ifndef __RP2350_UART_H
+#define __RP2350_UART_H
+
+#include "rp2350.h"
+
+/**
+ * @brief Release UART0 from reset and wait until ready.
+ * @retval None
+ */
+void uart_release_reset(void);
+
+/**
+ * @brief Initialize UART0 pins, baud rate, line control, and enable.
+ * @retval None
+ */
+void uart_init(void);
+
+/**
+ * @brief Check whether a received byte is waiting in the UART FIFO.
+ * @retval bool true if at least one byte is available
+ */
+bool uart_is_readable(void);
+
+/**
+ * @brief Read one character from UART0 (blocking).
+ * @retval char the received character
+ */
+char uart_getchar(void);
+
+/**
+ * @brief Transmit one character over UART0 (blocking).
+ * @param c character to transmit
+ * @retval None
+ */
+void uart_putchar(char c);
+
+/**
+ * @brief Transmit a null-terminated string over UART0.
+ * @param str pointer to the string to send
+ * @retval None
+ */
+void uart_puts(const char *str);
+
+/**
+ * @brief Convert a lowercase ASCII character to uppercase.
+ * @param c input character
+ * @retval char uppercase equivalent or original character
+ */
+char uart_to_upper(char c);
+
+#endif /* __RP2350_UART_H */
diff --git a/drivers/0x0e_watchdog_cbm/Inc/rp2350_watchdog.h b/drivers/0x0e_watchdog_cbm/Inc/rp2350_watchdog.h
new file mode 100644
index 0000000..f016898
--- /dev/null
+++ b/drivers/0x0e_watchdog_cbm/Inc/rp2350_watchdog.h
@@ -0,0 +1,54 @@
+/**
+ ******************************************************************************
+ * @file rp2350_watchdog.h
+ * @author Kevin Thomas
+ * @brief Watchdog timer driver header for RP2350.
+ *
+ * Bare-metal watchdog driver providing enable, feed, and
+ * reboot-cause detection. The watchdog tick generator is
+ * configured for 1 us resolution from the 12 MHz CLK_REF.
+ *
+ ******************************************************************************
+ * @attention
+ *
+ * Copyright (c) 2026 Kevin Thomas.
+ * All rights reserved.
+ *
+ * This software is licensed under terms that can be found in the LICENSE file
+ * in the root directory of this software component.
+ * If no LICENSE file comes with this software, it is provided AS-IS.
+ *
+ ******************************************************************************
+ */
+
+#ifndef __RP2350_WATCHDOG_H
+#define __RP2350_WATCHDOG_H
+
+#include "rp2350.h"
+
+/**
+ * @brief Start the watchdog tick generator at 1 us resolution.
+ * @retval None
+ */
+void watchdog_tick_init(void);
+
+/**
+ * @brief Enable the watchdog with the specified timeout.
+ * @param timeout_ms watchdog timeout in milliseconds (1-16777)
+ * @retval None
+ */
+void watchdog_enable(uint32_t timeout_ms);
+
+/**
+ * @brief Feed the watchdog to prevent a reset.
+ * @retval None
+ */
+void watchdog_feed(void);
+
+/**
+ * @brief Check whether the last reset was caused by the watchdog.
+ * @retval bool true if the watchdog triggered the last reset
+ */
+bool watchdog_caused_reboot(void);
+
+#endif /* __RP2350_WATCHDOG_H */
diff --git a/drivers/0x0e_watchdog_cbm/Inc/rp2350_xosc.h b/drivers/0x0e_watchdog_cbm/Inc/rp2350_xosc.h
new file mode 100644
index 0000000..0dd40d8
--- /dev/null
+++ b/drivers/0x0e_watchdog_cbm/Inc/rp2350_xosc.h
@@ -0,0 +1,46 @@
+/**
+ ******************************************************************************
+ * @file rp2350_xosc.h
+ * @author Kevin Thomas
+ * @brief XOSC driver header for RP2350.
+ *
+ * External crystal oscillator initialization and peripheral
+ * clock enable using the XOSC registers.
+ *
+ ******************************************************************************
+ * @attention
+ *
+ * Copyright (c) 2026 Kevin Thomas.
+ * All rights reserved.
+ *
+ * This software is licensed under terms that can be found in the LICENSE file
+ * in the root directory of this software component.
+ * If no LICENSE file comes with this software, it is provided AS-IS.
+ *
+ ******************************************************************************
+ */
+
+#ifndef __RP2350_XOSC_H
+#define __RP2350_XOSC_H
+
+#include "rp2350.h"
+
+/**
+ * @brief Initialize the external crystal oscillator and wait until stable.
+ * @retval None
+ */
+void xosc_init(void);
+
+/**
+ * @brief Enable the XOSC peripheral clock via CLK_PERI_CTRL.
+ * @retval None
+ */
+void xosc_enable_peri_clk(void);
+
+/**
+ * @brief Switch CLK_REF source to XOSC for a stable 12 MHz clk_sys.
+ * @retval None
+ */
+void xosc_set_clk_ref(void);
+
+#endif /* __RP2350_XOSC_H */
diff --git a/drivers/0x0e_watchdog_cbm/Makefile b/drivers/0x0e_watchdog_cbm/Makefile
new file mode 100644
index 0000000..9d0be1b
--- /dev/null
+++ b/drivers/0x0e_watchdog_cbm/Makefile
@@ -0,0 +1,79 @@
+# ------------------------------------------------------------------------------
+# @file Makefile
+# @author Kevin Thomas
+# @brief Build script for RP2350 bare-metal C watchdog driver.
+#
+# Compiles, links, and generates UF2 firmware for the RP2350.
+# ------------------------------------------------------------------------------
+
+# Toolchain
+CC = arm-none-eabi-gcc
+OBJCOPY = arm-none-eabi-objcopy
+SIZE = arm-none-eabi-size
+
+# Target
+TARGET = watchdog
+
+# Directories
+SRC_DIR = Src
+INC_DIR = Inc
+BUILD_DIR = build
+
+# CPU flags
+CPU_FLAGS = -mcpu=cortex-m33 -mthumb
+
+# Compiler flags
+CFLAGS = $(CPU_FLAGS) -Og -g3 -Wall -Wextra \
+ -ffunction-sections -fdata-sections \
+ -I$(INC_DIR)
+
+# Linker flags
+LDFLAGS = $(CPU_FLAGS) -T linker.ld -nostdlib -Wl,--gc-sections
+
+# Source files
+SRCS = $(SRC_DIR)/vector_table.c \
+ $(SRC_DIR)/rp2350_reset_handler.c \
+ $(SRC_DIR)/rp2350_stack.c \
+ $(SRC_DIR)/rp2350_xosc.c \
+ $(SRC_DIR)/rp2350_reset.c \
+ $(SRC_DIR)/rp2350_coprocessor.c \
+ $(SRC_DIR)/rp2350_uart.c \
+ $(SRC_DIR)/rp2350_delay.c \
+ $(SRC_DIR)/rp2350_watchdog.c \
+ $(SRC_DIR)/main.c \
+ $(SRC_DIR)/image_def.c
+
+# Object files
+OBJS = $(patsubst $(SRC_DIR)/%.c,$(BUILD_DIR)/%.o,$(SRCS))
+
+# Rules
+.PHONY: all clean flash
+
+all: $(BUILD_DIR)/$(TARGET).uf2
+ @echo "==================================="
+ @echo "SUCCESS! Created $(TARGET).bin and $(TARGET).uf2"
+ @echo "==================================="
+
+$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c | $(BUILD_DIR)
+ $(CC) $(CFLAGS) -c $< -o $@
+
+$(BUILD_DIR)/$(TARGET).elf: $(OBJS)
+ $(CC) $(LDFLAGS) $(OBJS) -o $@
+ $(SIZE) $@
+
+$(BUILD_DIR)/$(TARGET).bin: $(BUILD_DIR)/$(TARGET).elf
+ $(OBJCOPY) -O binary $< $@
+
+$(BUILD_DIR)/$(TARGET).uf2: $(BUILD_DIR)/$(TARGET).bin
+ python3 uf2conv.py -b 0x10000000 -f 0xe48bff59 -o $@ $<
+
+$(BUILD_DIR):
+ mkdir -p $(BUILD_DIR)
+
+clean:
+ rm -rf $(BUILD_DIR)
+
+flash: $(BUILD_DIR)/$(TARGET).elf
+ openocd -f interface/cmsis-dap.cfg -f target/rp2350.cfg \
+ -c "adapter speed 5000" \
+ -c "program $< verify reset exit"
diff --git a/drivers/0x0e_watchdog_cbm/Src/image_def.c b/drivers/0x0e_watchdog_cbm/Src/image_def.c
new file mode 100644
index 0000000..9a34198
--- /dev/null
+++ b/drivers/0x0e_watchdog_cbm/Src/image_def.c
@@ -0,0 +1,35 @@
+/**
+ ******************************************************************************
+ * @file image_def.c
+ * @author Kevin Thomas
+ * @brief RP2350 IMAGE_DEF block for boot ROM image recognition.
+ *
+ * Must appear within the first 4 KB of flash for the boot ROM
+ * to accept the image.
+ *
+ ******************************************************************************
+ * @attention
+ *
+ * Copyright (c) 2026 Kevin Thomas.
+ * All rights reserved.
+ *
+ * This software is licensed under terms that can be found in the LICENSE file
+ * in the root directory of this software component.
+ * If no LICENSE file comes with this software, it is provided AS-IS.
+ *
+ ******************************************************************************
+ */
+
+#include
+
+/**
+ * @brief IMAGE_DEF block structure placed in flash
+ */
+__attribute__((section(".embedded_block"), used))
+const uint8_t picobin_block[] = {
+ 0xD3, 0xDE, 0xFF, 0xFF,
+ 0x42, 0x01, 0x21, 0x10,
+ 0xFF, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x79, 0x35, 0x12, 0xAB
+};
diff --git a/drivers/0x0e_watchdog_cbm/Src/main.c b/drivers/0x0e_watchdog_cbm/Src/main.c
new file mode 100644
index 0000000..59c1ed8
--- /dev/null
+++ b/drivers/0x0e_watchdog_cbm/Src/main.c
@@ -0,0 +1,67 @@
+/**
+ ******************************************************************************
+ * @file main.c
+ * @author Kevin Thomas
+ * @brief Watchdog feed demonstration.
+ *
+ * Enables the watchdog with a 3-second timeout and feeds it
+ * every 1 second. Reports whether the system booted from a
+ * watchdog reset or a normal power-on.
+ *
+ * Wiring:
+ * GPIO0 -> UART TX (USB-to-UART adapter RX)
+ * GPIO1 -> UART RX (USB-to-UART adapter TX)
+ * No external components required
+ *
+ ******************************************************************************
+ * @attention
+ *
+ * Copyright (c) 2026 Kevin Thomas.
+ * All rights reserved.
+ *
+ * This software is licensed under terms that can be found in the LICENSE file
+ * in the root directory of this software component.
+ * If no LICENSE file comes with this software, it is provided AS-IS.
+ *
+ ******************************************************************************
+ */
+
+#include "rp2350_watchdog.h"
+#include "rp2350_uart.h"
+#include "rp2350_delay.h"
+
+/**
+ * @brief Print the reset reason over UART.
+ * @retval None
+ */
+static void _print_reset_reason(void)
+{
+ if (watchdog_caused_reboot())
+ uart_puts("System rebooted by watchdog timeout\r\n");
+ else
+ uart_puts("Normal power-on reset\r\n");
+}
+
+/**
+ * @brief Feed the watchdog, report over UART, and delay 1 second.
+ * @retval None
+ */
+static void _feed_and_report(void)
+{
+ watchdog_feed();
+ uart_puts("Watchdog fed\r\n");
+ delay_ms(1000);
+}
+
+/**
+ * @brief Application entry point for the watchdog demo.
+ * @retval int does not return
+ */
+int main(void)
+{
+ _print_reset_reason();
+ watchdog_enable(3000);
+ uart_puts("Watchdog enabled (3s timeout). Feeding every 1s...\r\n");
+ while (1)
+ _feed_and_report();
+}
diff --git a/drivers/0x0e_watchdog_cbm/Src/rp2350_coprocessor.c b/drivers/0x0e_watchdog_cbm/Src/rp2350_coprocessor.c
new file mode 100644
index 0000000..b637b36
--- /dev/null
+++ b/drivers/0x0e_watchdog_cbm/Src/rp2350_coprocessor.c
@@ -0,0 +1,34 @@
+/**
+ ******************************************************************************
+ * @file rp2350_coprocessor.c
+ * @author Kevin Thomas
+ * @brief Coprocessor access control driver implementation for RP2350.
+ *
+ * Grants access to coprocessors 0 and 1 by setting the
+ * corresponding bits in CPACR with DSB/ISB barriers.
+ *
+ ******************************************************************************
+ * @attention
+ *
+ * Copyright (c) 2026 Kevin Thomas.
+ * All rights reserved.
+ *
+ * This software is licensed under terms that can be found in the LICENSE file
+ * in the root directory of this software component.
+ * If no LICENSE file comes with this software, it is provided AS-IS.
+ *
+ ******************************************************************************
+ */
+
+#include "rp2350_coprocessor.h"
+
+void coprocessor_enable(void)
+{
+ uint32_t value;
+ value = *CPACR;
+ value |= (1U << CPACR_CP1_SHIFT);
+ value |= (1U << CPACR_CP0_SHIFT);
+ *CPACR = value;
+ __asm__ volatile ("dsb");
+ __asm__ volatile ("isb");
+}
diff --git a/drivers/0x0e_watchdog_cbm/Src/rp2350_delay.c b/drivers/0x0e_watchdog_cbm/Src/rp2350_delay.c
new file mode 100644
index 0000000..f2fe8de
--- /dev/null
+++ b/drivers/0x0e_watchdog_cbm/Src/rp2350_delay.c
@@ -0,0 +1,55 @@
+/**
+ ******************************************************************************
+ * @file rp2350_delay.c
+ * @author Kevin Thomas
+ * @brief Delay driver implementation for RP2350.
+ *
+ * Busy-wait millisecond and microsecond delays calibrated for
+ * a 12 MHz clock.
+ *
+ ******************************************************************************
+ * @attention
+ *
+ * Copyright (c) 2026 Kevin Thomas.
+ * All rights reserved.
+ *
+ * This software is licensed under terms that can be found in the LICENSE file
+ * in the root directory of this software component.
+ * If no LICENSE file comes with this software, it is provided AS-IS.
+ *
+ ******************************************************************************
+ */
+
+#include "rp2350_delay.h"
+
+void delay_ms(uint32_t ms)
+{
+ if (ms == 0)
+ return;
+ __asm__ volatile (
+ "mov r4, #3600\n\t"
+ "mul r5, %0, r4\n\t"
+ "1:\n\t"
+ "subs r5, #1\n\t"
+ "bne 1b\n\t"
+ :
+ : "r" (ms)
+ : "r4", "r5", "cc"
+ );
+}
+
+void delay_us(uint32_t us)
+{
+ if (us == 0)
+ return;
+ __asm__ volatile (
+ "mov r4, #4\n\t"
+ "mul r5, %0, r4\n\t"
+ "1:\n\t"
+ "subs r5, #1\n\t"
+ "bne 1b\n\t"
+ :
+ : "r" (us)
+ : "r4", "r5", "cc"
+ );
+}
diff --git a/drivers/0x0e_watchdog_cbm/Src/rp2350_reset.c b/drivers/0x0e_watchdog_cbm/Src/rp2350_reset.c
new file mode 100644
index 0000000..b0e18e8
--- /dev/null
+++ b/drivers/0x0e_watchdog_cbm/Src/rp2350_reset.c
@@ -0,0 +1,33 @@
+/**
+ ******************************************************************************
+ * @file rp2350_reset.c
+ * @author Kevin Thomas
+ * @brief Reset controller driver implementation for RP2350.
+ *
+ * Releases IO_BANK0 from reset and waits until the subsystem
+ * is ready.
+ *
+ ******************************************************************************
+ * @attention
+ *
+ * Copyright (c) 2026 Kevin Thomas.
+ * All rights reserved.
+ *
+ * This software is licensed under terms that can be found in the LICENSE file
+ * in the root directory of this software component.
+ * If no LICENSE file comes with this software, it is provided AS-IS.
+ *
+ ******************************************************************************
+ */
+
+#include "rp2350_reset.h"
+
+void reset_init_subsystem(void)
+{
+ uint32_t value;
+ value = RESETS->RESET;
+ value &= ~(1U << RESETS_RESET_IO_BANK0_SHIFT);
+ RESETS->RESET = value;
+ while ((RESETS->RESET_DONE & (1U << RESETS_RESET_IO_BANK0_SHIFT)) == 0) {
+ }
+}
diff --git a/drivers/0x0e_watchdog_cbm/Src/rp2350_reset_handler.c b/drivers/0x0e_watchdog_cbm/Src/rp2350_reset_handler.c
new file mode 100644
index 0000000..9b47a85
--- /dev/null
+++ b/drivers/0x0e_watchdog_cbm/Src/rp2350_reset_handler.c
@@ -0,0 +1,47 @@
+/**
+ ******************************************************************************
+ * @file rp2350_reset_handler.c
+ * @author Kevin Thomas
+ * @brief Reset handler implementation for RP2350.
+ *
+ * Entry point after power-on or system reset. Initializes the
+ * stack, XOSC, subsystem resets, UART, watchdog tick generator,
+ * coprocessor, then branches to main().
+ *
+ ******************************************************************************
+ * @attention
+ *
+ * Copyright (c) 2026 Kevin Thomas.
+ * All rights reserved.
+ *
+ * This software is licensed under terms that can be found in the LICENSE file
+ * in the root directory of this software component.
+ * If no LICENSE file comes with this software, it is provided AS-IS.
+ *
+ ******************************************************************************
+ */
+
+#include "rp2350_reset_handler.h"
+#include "rp2350_stack.h"
+#include "rp2350_xosc.h"
+#include "rp2350_reset.h"
+#include "rp2350_uart.h"
+#include "rp2350_watchdog.h"
+#include "rp2350_coprocessor.h"
+
+extern int main(void);
+
+void __attribute__((naked, noreturn)) Reset_Handler(void)
+{
+ __asm__ volatile (
+ "bl stack_init\n\t"
+ "bl xosc_init\n\t"
+ "bl xosc_enable_peri_clk\n\t"
+ "bl reset_init_subsystem\n\t"
+ "bl uart_release_reset\n\t"
+ "bl uart_init\n\t"
+ "bl watchdog_tick_init\n\t"
+ "bl coprocessor_enable\n\t"
+ "b main\n\t"
+ );
+}
diff --git a/drivers/0x0e_watchdog_cbm/Src/rp2350_stack.c b/drivers/0x0e_watchdog_cbm/Src/rp2350_stack.c
new file mode 100644
index 0000000..9c328dc
--- /dev/null
+++ b/drivers/0x0e_watchdog_cbm/Src/rp2350_stack.c
@@ -0,0 +1,38 @@
+/**
+ ******************************************************************************
+ * @file rp2350_stack.c
+ * @author Kevin Thomas
+ * @brief Stack pointer initialization for RP2350.
+ *
+ * Sets MSP, PSP, MSPLIM, and PSPLIM using inline assembly.
+ *
+ ******************************************************************************
+ * @attention
+ *
+ * Copyright (c) 2026 Kevin Thomas.
+ * All rights reserved.
+ *
+ * This software is licensed under terms that can be found in the LICENSE file
+ * in the root directory of this software component.
+ * If no LICENSE file comes with this software, it is provided AS-IS.
+ *
+ ******************************************************************************
+ */
+
+#include "rp2350_stack.h"
+
+void stack_init(void)
+{
+ __asm__ volatile (
+ "ldr r0, =%0\n\t"
+ "msr PSP, r0\n\t"
+ "ldr r0, =%1\n\t"
+ "msr MSPLIM, r0\n\t"
+ "msr PSPLIM, r0\n\t"
+ "ldr r0, =%0\n\t"
+ "msr MSP, r0\n\t"
+ :
+ : "i" (STACK_TOP), "i" (STACK_LIMIT)
+ : "r0"
+ );
+}
diff --git a/drivers/0x0e_watchdog_cbm/Src/rp2350_uart.c b/drivers/0x0e_watchdog_cbm/Src/rp2350_uart.c
new file mode 100644
index 0000000..44e67a9
--- /dev/null
+++ b/drivers/0x0e_watchdog_cbm/Src/rp2350_uart.c
@@ -0,0 +1,126 @@
+/**
+ ******************************************************************************
+ * @file rp2350_uart.c
+ * @author Kevin Thomas
+ * @brief UART0 driver implementation for RP2350.
+ *
+ * Configures UART0 on GPIO 0 (TX) and GPIO 1 (RX) at 115200
+ * baud using the 12 MHz XOSC clock.
+ *
+ ******************************************************************************
+ * @attention
+ *
+ * Copyright (c) 2026 Kevin Thomas.
+ * All rights reserved.
+ *
+ * This software is licensed under terms that can be found in the LICENSE file
+ * in the root directory of this software component.
+ * If no LICENSE file comes with this software, it is provided AS-IS.
+ *
+ ******************************************************************************
+ */
+
+#include "rp2350_uart.h"
+
+#define UART_BASE ((volatile uint32_t *) UART0_BASE)
+
+/**
+ * @brief Clear the UART0 reset bit in the reset controller.
+ * @retval None
+ */
+static void _uart_clear_reset_bit(void)
+{
+ uint32_t value;
+ value = RESETS->RESET;
+ value &= ~(1U << RESETS_RESET_UART0_SHIFT);
+ RESETS->RESET = value;
+}
+
+/**
+ * @brief Wait until the UART0 block is out of reset.
+ * @retval None
+ */
+static void _uart_wait_reset_done(void)
+{
+ while ((RESETS->RESET_DONE & (1U << RESETS_RESET_UART0_SHIFT)) == 0) {
+ }
+}
+
+/**
+ * @brief Configure GPIO pins 0 (TX) and 1 (RX) for UART function.
+ * @retval None
+ */
+static void _uart_configure_pins(void)
+{
+ IO_BANK0->GPIO[0].CTRL = IO_BANK0_CTRL_FUNCSEL_UART;
+ IO_BANK0->GPIO[1].CTRL = IO_BANK0_CTRL_FUNCSEL_UART;
+ PADS_BANK0->GPIO[0] = 0x04;
+ PADS_BANK0->GPIO[1] = 0x40;
+}
+
+/**
+ * @brief Set UART0 baud rate divisors for 115200 at 12 MHz.
+ * @retval None
+ */
+static void _uart_set_baud(void)
+{
+ UART_BASE[UART_CR_OFFSET] = 0;
+ UART_BASE[UART_IBRD_OFFSET] = 6;
+ UART_BASE[UART_FBRD_OFFSET] = 33;
+}
+
+/**
+ * @brief Configure line control and enable UART0.
+ * @retval None
+ */
+static void _uart_enable(void)
+{
+ UART_BASE[UART_LCR_H_OFFSET] = UART_LCR_H_8N1_FIFO;
+ UART_BASE[UART_CR_OFFSET] = UART_CR_ENABLE;
+}
+
+void uart_release_reset(void)
+{
+ _uart_clear_reset_bit();
+ _uart_wait_reset_done();
+}
+
+void uart_init(void)
+{
+ _uart_configure_pins();
+ _uart_set_baud();
+ _uart_enable();
+}
+
+bool uart_is_readable(void)
+{
+ return (UART_BASE[UART_FR_OFFSET] & UART_FR_RXFE_MASK) == 0;
+}
+
+char uart_getchar(void)
+{
+ while (UART_BASE[UART_FR_OFFSET] & UART_FR_RXFE_MASK) {
+ }
+ return (char)(UART_BASE[UART_DR_OFFSET] & 0xFF);
+}
+
+void uart_putchar(char c)
+{
+ while (UART_BASE[UART_FR_OFFSET] & UART_FR_TXFF_MASK) {
+ }
+ UART_BASE[UART_DR_OFFSET] = (uint32_t)c;
+}
+
+void uart_puts(const char *str)
+{
+ while (*str) {
+ uart_putchar(*str++);
+ }
+}
+
+char uart_to_upper(char c)
+{
+ if (c >= 'a' && c <= 'z')
+ return (char)(c - 32);
+ return c;
+}
diff --git a/drivers/0x0e_watchdog_cbm/Src/rp2350_watchdog.c b/drivers/0x0e_watchdog_cbm/Src/rp2350_watchdog.c
new file mode 100644
index 0000000..6c9b5ee
--- /dev/null
+++ b/drivers/0x0e_watchdog_cbm/Src/rp2350_watchdog.c
@@ -0,0 +1,117 @@
+/**
+ ******************************************************************************
+ * @file rp2350_watchdog.c
+ * @author Kevin Thomas
+ * @brief Watchdog timer driver implementation for RP2350.
+ *
+ * Configures the watchdog tick generator for 1 us resolution
+ * from the 12 MHz CLK_REF, enables the watchdog countdown,
+ * and provides feed and reboot-cause detection.
+ *
+ ******************************************************************************
+ * @attention
+ *
+ * Copyright (c) 2026 Kevin Thomas.
+ * All rights reserved.
+ *
+ * This software is licensed under terms that can be found in the LICENSE file
+ * in the root directory of this software component.
+ * If no LICENSE file comes with this software, it is provided AS-IS.
+ *
+ ******************************************************************************
+ */
+
+#include "rp2350_watchdog.h"
+
+/**
+ * @brief Saved load value for feeding the watchdog.
+ */
+static uint32_t _load_value;
+
+/**
+ * @brief Set the watchdog tick generator cycle count to 12.
+ * @retval None
+ */
+static void _watchdog_set_tick_cycles(void)
+{
+ TICKS_WATCHDOG->CYCLES = TICKS_CYCLES_12MHZ;
+}
+
+/**
+ * @brief Enable the watchdog tick generator.
+ * @retval None
+ */
+static void _watchdog_enable_tick(void)
+{
+ TICKS_WATCHDOG->CTRL = (1U << TICKS_CTRL_ENABLE_SHIFT);
+}
+
+/**
+ * @brief Disable the watchdog before reconfiguring.
+ * @retval None
+ */
+static void _watchdog_disable(void)
+{
+ WATCHDOG->CTRL &= ~(1U << WATCHDOG_CTRL_ENABLE_SHIFT);
+}
+
+/**
+ * @brief Configure PSM WDSEL to reset everything except oscillators.
+ * @retval None
+ */
+static void _watchdog_set_psm_wdsel(void)
+{
+ uint32_t mask;
+ mask = PSM_WDSEL_ALL_MASK;
+ mask &= ~(1U << PSM_WDSEL_ROSC_SHIFT);
+ mask &= ~(1U << PSM_WDSEL_XOSC_SHIFT);
+ PSM->WDSEL = mask;
+}
+
+/**
+ * @brief Set pause-on-debug bits in CTRL so debugger halts the timer.
+ * @retval None
+ */
+static void _watchdog_set_pause_debug(void)
+{
+ WATCHDOG->CTRL |= (1U << WATCHDOG_CTRL_PAUSE_DBG0_SHIFT);
+ WATCHDOG->CTRL |= (1U << WATCHDOG_CTRL_PAUSE_DBG1_SHIFT);
+ WATCHDOG->CTRL |= (1U << WATCHDOG_CTRL_PAUSE_JTAG_SHIFT);
+}
+
+/**
+ * @brief Load and enable the watchdog countdown.
+ * @retval None
+ */
+static void _watchdog_load_and_enable(void)
+{
+ WATCHDOG->LOAD = _load_value;
+ WATCHDOG->CTRL |= (1U << WATCHDOG_CTRL_ENABLE_SHIFT);
+}
+
+void watchdog_tick_init(void)
+{
+ _watchdog_set_tick_cycles();
+ _watchdog_enable_tick();
+}
+
+void watchdog_enable(uint32_t timeout_ms)
+{
+ _load_value = timeout_ms * 1000U;
+ if (_load_value > WATCHDOG_LOAD_MAX)
+ _load_value = WATCHDOG_LOAD_MAX;
+ _watchdog_disable();
+ _watchdog_set_psm_wdsel();
+ _watchdog_set_pause_debug();
+ _watchdog_load_and_enable();
+}
+
+void watchdog_feed(void)
+{
+ WATCHDOG->LOAD = _load_value;
+}
+
+bool watchdog_caused_reboot(void)
+{
+ return (WATCHDOG->REASON & ((1U << WATCHDOG_REASON_TIMER_SHIFT) | (1U << WATCHDOG_REASON_FORCE_SHIFT))) != 0;
+}
diff --git a/drivers/0x0e_watchdog_cbm/Src/rp2350_xosc.c b/drivers/0x0e_watchdog_cbm/Src/rp2350_xosc.c
new file mode 100644
index 0000000..3383559
--- /dev/null
+++ b/drivers/0x0e_watchdog_cbm/Src/rp2350_xosc.c
@@ -0,0 +1,46 @@
+/**
+ ******************************************************************************
+ * @file rp2350_xosc.c
+ * @author Kevin Thomas
+ * @brief XOSC driver implementation for RP2350.
+ *
+ * Configures the external crystal oscillator and enables the
+ * peripheral clock sourced from XOSC.
+ *
+ ******************************************************************************
+ * @attention
+ *
+ * Copyright (c) 2026 Kevin Thomas.
+ * All rights reserved.
+ *
+ * This software is licensed under terms that can be found in the LICENSE file
+ * in the root directory of this software component.
+ * If no LICENSE file comes with this software, it is provided AS-IS.
+ *
+ ******************************************************************************
+ */
+
+#include "rp2350_xosc.h"
+
+void xosc_init(void)
+{
+ XOSC->STARTUP = 0x00C4U;
+ XOSC->CTRL = 0x00FABAA0U;
+ while ((XOSC->STATUS & (1U << XOSC_STATUS_STABLE_SHIFT)) == 0) {
+ }
+}
+
+void xosc_enable_peri_clk(void)
+{
+ uint32_t value;
+ value = CLOCKS->CLK_PERI_CTRL;
+ value &= ~CLK_PERI_CTRL_AUXSRC_MASK;
+ value |= (1U << CLK_PERI_CTRL_ENABLE_SHIFT);
+ value |= (CLK_PERI_CTRL_AUXSRC_XOSC << CLK_PERI_CTRL_AUXSRC_SHIFT);
+ CLOCKS->CLK_PERI_CTRL = value;
+}
+
+void xosc_set_clk_ref(void)
+{
+ CLOCKS->CLK_REF_CTRL = CLK_REF_CTRL_SRC_XOSC;
+}
diff --git a/drivers/0x0e_watchdog_cbm/Src/vector_table.c b/drivers/0x0e_watchdog_cbm/Src/vector_table.c
new file mode 100644
index 0000000..5d234c5
--- /dev/null
+++ b/drivers/0x0e_watchdog_cbm/Src/vector_table.c
@@ -0,0 +1,38 @@
+/**
+ ******************************************************************************
+ * @file vector_table.c
+ * @author Kevin Thomas
+ * @brief Vector table with initial stack pointer and reset handler.
+ *
+ * Placed in the .vectors section at the start of flash.
+ * The Thumb bit (bit 0 = 1) is automatically set by the
+ * linker for function pointers in Thumb mode.
+ *
+ ******************************************************************************
+ * @attention
+ *
+ * Copyright (c) 2026 Kevin Thomas.
+ * All rights reserved.
+ *
+ * This software is licensed under terms that can be found in the LICENSE file
+ * in the root directory of this software component.
+ * If no LICENSE file comes with this software, it is provided AS-IS.
+ *
+ ******************************************************************************
+ */
+
+#include
+
+extern uint32_t _stack_top;
+extern void Reset_Handler(void);
+
+typedef void (*vector_func_t)(void);
+
+/**
+ * @brief Vector table placed in .vectors section
+ */
+__attribute__((section(".vectors"), used))
+const void *_vectors[2] = {
+ &_stack_top,
+ Reset_Handler
+};
diff --git a/drivers/0x0e_watchdog_cbm/linker.ld b/drivers/0x0e_watchdog_cbm/linker.ld
new file mode 100644
index 0000000..15d3ac3
--- /dev/null
+++ b/drivers/0x0e_watchdog_cbm/linker.ld
@@ -0,0 +1,125 @@
+/**
+ ******************************************************************************
+ * @file linker.ld
+ * @author Kevin Thomas
+ * @brief Minimal linker script for bare-metal RP2350 development.
+ *
+ * Defines FLASH (XIP 32 MB) and RAM (520 kB SRAM) regions.
+ * The vector table is placed at the start of flash (0x10000000).
+ *
+ ******************************************************************************
+ * @attention
+ *
+ * Copyright (c) 2026 Kevin Thomas.
+ * All rights reserved.
+ *
+ * This software is licensed under terms that can be found in the LICENSE file
+ * in the root directory of this software component.
+ * If no LICENSE file comes with this software, it is provided AS-IS.
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Entry point.
+ */
+ENTRY(Reset_Handler)
+
+/**
+ * Define memory regions.
+ */
+__XIP_BASE = 0x10000000;
+__XIP_SIZE = 32M;
+
+__SRAM_BASE = 0x20000000;
+__SRAM_SIZE = 520K;
+__STACK_SIZE = 32K;
+
+/**
+ * Memory layout.
+ */
+MEMORY
+{
+ RAM (rwx) : ORIGIN = __SRAM_BASE, LENGTH = __SRAM_SIZE
+ FLASH (rx) : ORIGIN = __XIP_BASE, LENGTH = __XIP_SIZE
+}
+
+/**
+ * Program headers.
+ */
+PHDRS
+{
+ text PT_LOAD FLAGS(5);
+}
+
+/**
+ * Section placement.
+ */
+SECTIONS
+{
+ . = ORIGIN(FLASH);
+
+ /**
+ * Vector table MUST be first at 0x10000000.
+ */
+ .vectors :
+ {
+ KEEP(*(.vectors))
+ } > FLASH :text
+
+ /**
+ * Verify vector table placement.
+ */
+ ASSERT((ADDR(.vectors) == ORIGIN(FLASH)),
+ "Vector table must be at start of flash (0x10000000)")
+
+ /**
+ * Text and read-only data.
+ */
+ .text :
+ {
+ . = ALIGN(4);
+ *(.text*)
+ *(.rodata*)
+ KEEP(*(.init))
+ KEEP(*(.fini))
+ KEEP(*(.ARM.attributes))
+ } > FLASH :text
+
+ /**
+ * IMAGE_DEF block at end of code.
+ */
+ .embedded_block :
+ {
+ KEEP(*(.embedded_block))
+ } > FLASH :text
+
+ /**
+ * Uninitialized data (BSS) in RAM.
+ */
+ .bss (NOLOAD) :
+ {
+ . = ALIGN(4);
+ *(.bss*)
+ *(COMMON)
+ . = ALIGN(4);
+ } > RAM
+
+ /**
+ * Non-secure stack symbols.
+ */
+ __StackTop = ORIGIN(RAM) + LENGTH(RAM);
+ __StackLimit = __StackTop - __STACK_SIZE;
+ __stack = __StackTop;
+ _stack_top = __StackTop;
+
+ /**
+ * Stack section (no load).
+ */
+ .stack (NOLOAD) : { . = ALIGN(8); } > RAM
+
+ /**
+ * Provide vector table symbol to startup code.
+ */
+ PROVIDE(__Vectors = ADDR(.vectors));
+}
diff --git a/drivers/0x0e_watchdog_cbm/uf2conv.py b/drivers/0x0e_watchdog_cbm/uf2conv.py
new file mode 100644
index 0000000..529dd96
--- /dev/null
+++ b/drivers/0x0e_watchdog_cbm/uf2conv.py
@@ -0,0 +1,438 @@
+#!/usr/bin/env python3
+import sys
+import struct
+import subprocess
+import re
+import os
+import os.path
+import argparse
+import json
+from time import sleep
+
+
+UF2_MAGIC_START0 = 0x0A324655 # "UF2\n"
+UF2_MAGIC_START1 = 0x9E5D5157 # Randomly selected
+UF2_MAGIC_END = 0x0AB16F30 # Ditto
+
+INFO_FILE = "/INFO_UF2.TXT"
+
+appstartaddr = 0x2000
+familyid = 0x0
+
+
+def is_uf2(buf):
+ w = struct.unpack(" 476:
+ assert False, "Invalid UF2 data size at " + ptr
+ newaddr = hd[3]
+ if (hd[2] & 0x2000) and (currfamilyid == None):
+ currfamilyid = hd[7]
+ if curraddr == None or ((hd[2] & 0x2000) and hd[7] != currfamilyid):
+ currfamilyid = hd[7]
+ curraddr = newaddr
+ if familyid == 0x0 or familyid == hd[7]:
+ appstartaddr = newaddr
+ padding = newaddr - curraddr
+ if padding < 0:
+ assert False, "Block out of order at " + ptr
+ if padding > 10 * 1024 * 1024:
+ assert False, "More than 10M of padding needed at " + ptr
+ if padding % 4 != 0:
+ assert False, "Non-word padding size at " + ptr
+ while padding > 0:
+ padding -= 4
+ outp.append(b"\x00\x00\x00\x00")
+ if familyid == 0x0 or ((hd[2] & 0x2000) and familyid == hd[7]):
+ outp.append(block[32 : 32 + datalen])
+ curraddr = newaddr + datalen
+ if hd[2] & 0x2000:
+ if hd[7] in families_found.keys():
+ if families_found[hd[7]] > newaddr:
+ families_found[hd[7]] = newaddr
+ else:
+ families_found[hd[7]] = newaddr
+ if prev_flag == None:
+ prev_flag = hd[2]
+ if prev_flag != hd[2]:
+ all_flags_same = False
+ if blockno == (numblocks - 1):
+ print("--- UF2 File Header Info ---")
+ families = load_families()
+ for family_hex in families_found.keys():
+ family_short_name = ""
+ for name, value in families.items():
+ if value == family_hex:
+ family_short_name = name
+ print(
+ "Family ID is {:s}, hex value is 0x{:08x}".format(
+ family_short_name, family_hex
+ )
+ )
+ print("Target Address is 0x{:08x}".format(families_found[family_hex]))
+ if all_flags_same:
+ print("All block flag values consistent, 0x{:04x}".format(hd[2]))
+ else:
+ print("Flags were not all the same")
+ print("----------------------------")
+ if len(families_found) > 1 and familyid == 0x0:
+ outp = []
+ appstartaddr = 0x0
+ return b"".join(outp)
+
+
+def convert_to_carray(file_content):
+ outp = "const unsigned long bindata_len = %d;\n" % len(file_content)
+ outp += "const unsigned char bindata[] __attribute__((aligned(16))) = {"
+ for i in range(len(file_content)):
+ if i % 16 == 0:
+ outp += "\n"
+ outp += "0x%02x, " % file_content[i]
+ outp += "\n};\n"
+ return bytes(outp, "utf-8")
+
+
+def convert_to_uf2(file_content):
+ global familyid
+ datapadding = b""
+ while len(datapadding) < 512 - 256 - 32 - 4:
+ datapadding += b"\x00\x00\x00\x00"
+ numblocks = (len(file_content) + 255) // 256
+ outp = []
+ for blockno in range(numblocks):
+ ptr = 256 * blockno
+ chunk = file_content[ptr : ptr + 256]
+ flags = 0x0
+ if familyid:
+ flags |= 0x2000
+ hd = struct.pack(
+ b"