diff --git a/README.md b/README.md
index 420a143..8fe3f5e 100644
--- a/README.md
+++ b/README.md
@@ -504,5 +504,28 @@ An RP2350 button driver written entirely in RISC-V Assembler.
+# Drivers
+Self-contained C driver libraries for the Raspberry Pi Pico 2 (RP2350). Each driver folder contains a thin demo (`0xNN_name.c`), a reusable library implementation (`name.c`), and a library header with full Doxygen docstrings (`name.h`).
+
+| Driver | Description | Key Hardware |
+|--------|-------------|--------------|
+| [0x01_uart](drivers/0x01_uart) | Raw UART transmit / receive | `hardware_uart` |
+| [0x02_blink](drivers/0x02_blink) | GPIO output LED blink | `pico_stdlib` |
+| [0x03_button](drivers/0x03_button) | GPIO input with debounce | `pico_stdlib` |
+| [0x04_pwm](drivers/0x04_pwm) | PWM output with frequency & duty control | `hardware_pwm`, `hardware_clocks` |
+| [0x05_servo](drivers/0x05_servo) | SG90 servo angle & pulse control | `hardware_pwm`, `hardware_clocks` |
+| [0x06_adc](drivers/0x06_adc) | ADC voltage & on-chip temperature reading | `hardware_adc` |
+| [0x07_i2c](drivers/0x07_i2c) | I2C bus init, probe & scan | `hardware_i2c` |
+| [0x08_lcd1602](drivers/0x08_lcd1602) | 1602 LCD over I2C (PCF8574 backpack) | `hardware_i2c` |
+| [0x09_dht11](drivers/0x09_dht11) | DHT11 temperature & humidity (single-wire) | `pico_stdlib` |
+| [0x0a_ir](drivers/0x0a_ir) | IR remote NEC protocol decoder | `pico_stdlib` |
+| [0x0b_spi](drivers/0x0b_spi) | SPI bus init & bidirectional transfer | `hardware_spi` |
+| [0x0c_multicore](drivers/0x0c_multicore) | Dual-core launch & FIFO messaging | `pico_multicore` |
+| [0x0d_timer](drivers/0x0d_timer) | Repeating timer callbacks | `pico_stdlib` |
+| [0x0e_watchdog](drivers/0x0e_watchdog) | Watchdog enable, feed & reboot detection | `hardware_watchdog` |
+| [0x0f_flash](drivers/0x0f_flash) | On-board flash erase, write & read | `hardware_flash`, `hardware_sync` |
+
+
+
# License
[Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0)
diff --git a/drivers/0x01_uart/0x01_uart.c b/drivers/0x01_uart/0x01_uart.c
new file mode 100644
index 0000000..c375604
--- /dev/null
+++ b/drivers/0x01_uart/0x01_uart.c
@@ -0,0 +1,78 @@
+/**
+ * @file 0x01_uart.c
+ * @brief UART demonstration: echo received characters back in uppercase
+ * @author Kevin Thomas
+ * @date 2025
+ *
+ * MIT License
+ *
+ * Copyright (c) 2025 Kevin Thomas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Demonstrates hardware UART0 using the uart driver (uart.h / uart.c).
+ * Characters typed into a terminal via a USB-to-UART adapter are echoed
+ * back in uppercase, illustrating full-duplex raw UART operation without
+ * going through the stdio layer.
+ *
+ * Wiring:
+ * GPIO0 (TX) -> USB-to-UART adapter RX
+ * GPIO1 (RX) -> USB-to-UART adapter TX
+ * GND -> USB-to-UART adapter GND
+ */
+
+#include "pico/stdlib.h"
+#include "uart.h"
+
+#define UART_TX_PIN 0
+#define UART_RX_PIN 1
+#define UART_BAUD 115200
+
+/**
+ * @brief Convert a lowercase ASCII character to uppercase
+ *
+ * Returns the uppercase equivalent if the character is in 'a'-'z';
+ * all other characters are passed through unchanged.
+ *
+ * @param c Input character
+ * @return char Uppercase equivalent, or the original character
+ */
+static char to_upper(char c) {
+ if (c >= 'a' && c <= 'z') {
+ return (char)(c - 32);
+ }
+ return c;
+}
+
+int main(void) {
+ uart_driver_init(UART_TX_PIN, UART_RX_PIN, UART_BAUD);
+
+ uart_driver_puts("UART driver ready (115200 8N1)\r\n");
+ uart_driver_puts("Type characters to echo them back in UPPERCASE:\r\n");
+
+ while (true) {
+ if (uart_driver_is_readable()) {
+ char c = uart_driver_getchar();
+ char upper = to_upper(c);
+ uart_driver_putchar(upper);
+ }
+ }
+}
diff --git a/drivers/0x01_uart/CMakeLists.txt b/drivers/0x01_uart/CMakeLists.txt
new file mode 100644
index 0000000..6816052
--- /dev/null
+++ b/drivers/0x01_uart/CMakeLists.txt
@@ -0,0 +1,57 @@
+# Generated Cmake Pico project file
+
+cmake_minimum_required(VERSION 3.13)
+
+set(CMAKE_C_STANDARD 11)
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+
+# Initialise pico_sdk from installed location
+# (note this can come from environment, CMake cache etc)
+
+# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work ==
+if(WIN32)
+ set(USERHOME $ENV{USERPROFILE})
+else()
+ set(USERHOME $ENV{HOME})
+endif()
+set(sdkVersion 2.2.0)
+set(toolchainVersion 14_2_Rel1)
+set(picotoolVersion 2.2.0)
+set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake)
+if (EXISTS ${picoVscode})
+ include(${picoVscode})
+endif()
+# ====================================================================================
+set(PICO_BOARD pico2 CACHE STRING "Board type")
+
+# Pull in Raspberry Pi Pico SDK (must be before project)
+include(pico_sdk_import.cmake)
+
+project(0x01_uart C CXX ASM)
+
+# Initialise the Raspberry Pi Pico SDK
+pico_sdk_init()
+
+# Add executable. Default name is the project name, version 0.1
+
+add_executable(0x01_uart 0x01_uart.c uart.c)
+
+pico_set_program_name(0x01_uart "0x01_uart")
+pico_set_program_version(0x01_uart "0.1")
+
+# Disable stdio routing - driver uses hardware UART0 directly (GPIO0/GPIO1)
+pico_enable_stdio_uart(0x01_uart 0)
+pico_enable_stdio_usb(0x01_uart 0)
+
+# Add the standard library to the build
+target_link_libraries(0x01_uart
+ pico_stdlib
+ hardware_uart)
+
+# Add the standard include files to the build
+target_include_directories(0x01_uart PRIVATE
+ ${CMAKE_CURRENT_LIST_DIR}
+)
+
+pico_add_extra_outputs(0x01_uart)
diff --git a/drivers/0x01_uart/pico_sdk_import.cmake b/drivers/0x01_uart/pico_sdk_import.cmake
new file mode 100644
index 0000000..d493cc2
--- /dev/null
+++ b/drivers/0x01_uart/pico_sdk_import.cmake
@@ -0,0 +1,121 @@
+# This is a copy of /external/pico_sdk_import.cmake
+
+# This can be dropped into an external project to help locate this SDK
+# It should be include()ed prior to project()
+
+# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd.
+#
+# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+# following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+# disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
+ set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
+ message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
+ set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
+ message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
+ set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
+ message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG))
+ set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG})
+ message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')")
+endif ()
+
+if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG)
+ set(PICO_SDK_FETCH_FROM_GIT_TAG "master")
+ message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG")
+endif()
+
+set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
+set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
+set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
+set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK")
+
+if (NOT PICO_SDK_PATH)
+ if (PICO_SDK_FETCH_FROM_GIT)
+ include(FetchContent)
+ set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
+ if (PICO_SDK_FETCH_FROM_GIT_PATH)
+ get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
+ endif ()
+ FetchContent_Declare(
+ pico_sdk
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+ )
+
+ if (NOT pico_sdk)
+ message("Downloading Raspberry Pi Pico SDK")
+ # GIT_SUBMODULES_RECURSE was added in 3.17
+ if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
+ FetchContent_Populate(
+ pico_sdk
+ QUIET
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+ GIT_SUBMODULES_RECURSE FALSE
+
+ SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
+ BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
+ SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
+ )
+ else ()
+ FetchContent_Populate(
+ pico_sdk
+ QUIET
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+
+ SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
+ BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
+ SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
+ )
+ endif ()
+
+ set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
+ endif ()
+ set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
+ else ()
+ message(FATAL_ERROR
+ "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
+ )
+ endif ()
+endif ()
+
+get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
+if (NOT EXISTS ${PICO_SDK_PATH})
+ message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
+endif ()
+
+set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
+if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
+ message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
+endif ()
+
+set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
+
+include(${PICO_SDK_INIT_CMAKE_FILE})
diff --git a/drivers/0x01_uart/uart.c b/drivers/0x01_uart/uart.c
new file mode 100644
index 0000000..0dfbf51
--- /dev/null
+++ b/drivers/0x01_uart/uart.c
@@ -0,0 +1,59 @@
+/**
+ * @file uart.c
+ * @brief Implementation of the hardware UART0 driver
+ * @author Kevin Thomas
+ * @date 2025
+ *
+ * MIT License
+ *
+ * Copyright (c) 2025 Kevin Thomas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "uart.h"
+#include "pico/stdlib.h"
+#include "hardware/uart.h"
+#include "hardware/gpio.h"
+
+#define UART_INST uart0
+
+void uart_driver_init(uint32_t tx_pin, uint32_t rx_pin, uint32_t baud_rate) {
+ uart_init(UART_INST, baud_rate);
+ gpio_set_function(tx_pin, GPIO_FUNC_UART);
+ gpio_set_function(rx_pin, GPIO_FUNC_UART);
+}
+
+bool uart_driver_is_readable(void) {
+ return uart_is_readable(UART_INST);
+}
+
+char uart_driver_getchar(void) {
+ return (char)uart_getc(UART_INST);
+}
+
+void uart_driver_putchar(char c) {
+ uart_putc_raw(UART_INST, c);
+}
+
+void uart_driver_puts(const char *str) {
+ while (*str) {
+ uart_putc_raw(UART_INST, *str++);
+ }
+}
diff --git a/drivers/0x01_uart/uart.h b/drivers/0x01_uart/uart.h
new file mode 100644
index 0000000..f366a05
--- /dev/null
+++ b/drivers/0x01_uart/uart.h
@@ -0,0 +1,89 @@
+/**
+ * @file uart.h
+ * @brief Header for hardware UART0 driver (raw TX/RX, GPIO 0/1)
+ * @author Kevin Thomas
+ * @date 2025
+ *
+ * MIT License
+ *
+ * Copyright (c) 2025 Kevin Thomas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef UART_H
+#define UART_H
+
+#include
+#include
+
+/**
+ * @brief Initialize hardware UART0 on the specified TX and RX GPIO pins
+ *
+ * Configures UART0 at the requested baud rate, sets the GPIO alternate
+ * functions for TX and RX, and enables 8N1 framing. Must be called once
+ * before using any other uart_driver_* functions.
+ *
+ * @param tx_pin GPIO pin number to use as UART0 TX (typically 0)
+ * @param rx_pin GPIO pin number to use as UART0 RX (typically 1)
+ * @param baud_rate Desired baud rate in bits per second (e.g. 115200)
+ */
+void uart_driver_init(uint32_t tx_pin, uint32_t rx_pin, uint32_t baud_rate);
+
+/**
+ * @brief Check whether a received character is waiting in the UART FIFO
+ *
+ * Returns immediately without blocking. Use this to poll for incoming
+ * data before calling uart_driver_getchar().
+ *
+ * @return bool true if at least one byte is available to read, false otherwise
+ */
+bool uart_driver_is_readable(void);
+
+/**
+ * @brief Read one character from UART0 (blocking)
+ *
+ * Blocks until a byte arrives in the receive FIFO, then returns it.
+ * Prefer pairing with uart_driver_is_readable() to avoid indefinite blocking.
+ *
+ * @return char The received character
+ */
+char uart_driver_getchar(void);
+
+/**
+ * @brief Transmit one character over UART0 (blocking)
+ *
+ * Waits until the transmit FIFO has space, then places the character into the
+ * FIFO. Returns once the byte has been accepted by the hardware.
+ *
+ * @param c Character to transmit
+ */
+void uart_driver_putchar(char c);
+
+/**
+ * @brief Transmit a null-terminated string over UART0
+ *
+ * Calls uart_driver_putchar() for every character in the string up to and
+ * not including the null terminator.
+ *
+ * @param str Pointer to the null-terminated ASCII string to send
+ */
+void uart_driver_puts(const char *str);
+
+#endif // UART_H
diff --git a/drivers/0x02_blink/0x02_blink.c b/drivers/0x02_blink/0x02_blink.c
new file mode 100644
index 0000000..16fb1ae
--- /dev/null
+++ b/drivers/0x02_blink/0x02_blink.c
@@ -0,0 +1,57 @@
+/**
+ * @file 0x02_blink.c
+ * @brief Blink demonstration: toggle onboard LED every 500 ms
+ * @author Kevin Thomas
+ * @date 2025
+ *
+ * MIT License
+ *
+ * Copyright (c) 2025 Kevin Thomas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Demonstrates GPIO output control using the blink driver (blink.h / blink.c).
+ * The onboard LED on GPIO 25 is toggled every BLINK_DELAY_MS milliseconds and
+ * the current state is reported over UART.
+ *
+ * Wiring:
+ * GPIO25 -> Onboard LED (no external wiring needed)
+ */
+
+#include
+#include "pico/stdlib.h"
+#include "blink.h"
+
+#define LED_PIN 25
+#define BLINK_DELAY_MS 500
+
+int main(void) {
+ stdio_init_all();
+ blink_init(LED_PIN);
+
+ printf("Blink driver initialized on GPIO %d\r\n", LED_PIN);
+
+ while (true) {
+ blink_toggle(LED_PIN);
+ printf("LED: %s\r\n", blink_get_state(LED_PIN) ? "ON" : "OFF");
+ sleep_ms(BLINK_DELAY_MS);
+ }
+}
diff --git a/drivers/0x02_blink/CMakeLists.txt b/drivers/0x02_blink/CMakeLists.txt
new file mode 100644
index 0000000..adcca84
--- /dev/null
+++ b/drivers/0x02_blink/CMakeLists.txt
@@ -0,0 +1,56 @@
+# Generated Cmake Pico project file
+
+cmake_minimum_required(VERSION 3.13)
+
+set(CMAKE_C_STANDARD 11)
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+
+# Initialise pico_sdk from installed location
+# (note this can come from environment, CMake cache etc)
+
+# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work ==
+if(WIN32)
+ set(USERHOME $ENV{USERPROFILE})
+else()
+ set(USERHOME $ENV{HOME})
+endif()
+set(sdkVersion 2.2.0)
+set(toolchainVersion 14_2_Rel1)
+set(picotoolVersion 2.2.0)
+set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake)
+if (EXISTS ${picoVscode})
+ include(${picoVscode})
+endif()
+# ====================================================================================
+set(PICO_BOARD pico2 CACHE STRING "Board type")
+
+# Pull in Raspberry Pi Pico SDK (must be before project)
+include(pico_sdk_import.cmake)
+
+project(0x02_blink C CXX ASM)
+
+# Initialise the Raspberry Pi Pico SDK
+pico_sdk_init()
+
+# Add executable. Default name is the project name, version 0.1
+
+add_executable(0x02_blink 0x02_blink.c blink.c)
+
+pico_set_program_name(0x02_blink "0x02_blink")
+pico_set_program_version(0x02_blink "0.1")
+
+# Modify the below lines to enable/disable output over UART/USB
+pico_enable_stdio_uart(0x02_blink 1)
+pico_enable_stdio_usb(0x02_blink 0)
+
+# Add the standard library to the build
+target_link_libraries(0x02_blink
+ pico_stdlib)
+
+# Add the standard include files to the build
+target_include_directories(0x02_blink PRIVATE
+ ${CMAKE_CURRENT_LIST_DIR}
+)
+
+pico_add_extra_outputs(0x02_blink)
diff --git a/drivers/0x02_blink/blink.c b/drivers/0x02_blink/blink.c
new file mode 100644
index 0000000..d3f0af4
--- /dev/null
+++ b/drivers/0x02_blink/blink.c
@@ -0,0 +1,54 @@
+/**
+ * @file blink.c
+ * @brief Implementation of the GPIO output / LED blink driver
+ * @author Kevin Thomas
+ * @date 2025
+ *
+ * MIT License
+ *
+ * Copyright (c) 2025 Kevin Thomas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "blink.h"
+#include "pico/stdlib.h"
+#include "hardware/gpio.h"
+
+void blink_init(uint32_t pin) {
+ gpio_init(pin);
+ gpio_set_dir(pin, GPIO_OUT);
+ gpio_put(pin, false);
+}
+
+void blink_on(uint32_t pin) {
+ gpio_put(pin, true);
+}
+
+void blink_off(uint32_t pin) {
+ gpio_put(pin, false);
+}
+
+void blink_toggle(uint32_t pin) {
+ gpio_put(pin, !gpio_get(pin));
+}
+
+bool blink_get_state(uint32_t pin) {
+ return (bool)gpio_get(pin);
+}
diff --git a/drivers/0x02_blink/blink.h b/drivers/0x02_blink/blink.h
new file mode 100644
index 0000000..a373a5d
--- /dev/null
+++ b/drivers/0x02_blink/blink.h
@@ -0,0 +1,78 @@
+/**
+ * @file blink.h
+ * @brief Header for GPIO output / LED blink driver
+ * @author Kevin Thomas
+ * @date 2025
+ *
+ * MIT License
+ *
+ * Copyright (c) 2025 Kevin Thomas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef BLINK_H
+#define BLINK_H
+
+#include
+#include
+
+/**
+ * @brief Initialize a GPIO pin as a push-pull digital output
+ *
+ * Calls gpio_init() and gpio_set_dir() to configure the pin for output.
+ * The initial drive level is low (LED off).
+ *
+ * @param pin GPIO pin number to configure as a digital output
+ */
+void blink_init(uint32_t pin);
+
+/**
+ * @brief Drive the output pin high (LED on)
+ *
+ * @param pin GPIO pin number previously initialized with blink_init()
+ */
+void blink_on(uint32_t pin);
+
+/**
+ * @brief Drive the output pin low (LED off)
+ *
+ * @param pin GPIO pin number previously initialized with blink_init()
+ */
+void blink_off(uint32_t pin);
+
+/**
+ * @brief Toggle the current state of the output pin
+ *
+ * Reads the current GPIO output level and inverts it. If the LED was on
+ * it is turned off, and vice versa.
+ *
+ * @param pin GPIO pin number previously initialized with blink_init()
+ */
+void blink_toggle(uint32_t pin);
+
+/**
+ * @brief Query the current drive state of the output pin
+ *
+ * @param pin GPIO pin number previously initialized with blink_init()
+ * @return bool true if the pin is driven high, false if driven low
+ */
+bool blink_get_state(uint32_t pin);
+
+#endif // BLINK_H
diff --git a/drivers/0x02_blink/pico_sdk_import.cmake b/drivers/0x02_blink/pico_sdk_import.cmake
new file mode 100644
index 0000000..d493cc2
--- /dev/null
+++ b/drivers/0x02_blink/pico_sdk_import.cmake
@@ -0,0 +1,121 @@
+# This is a copy of /external/pico_sdk_import.cmake
+
+# This can be dropped into an external project to help locate this SDK
+# It should be include()ed prior to project()
+
+# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd.
+#
+# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+# following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+# disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
+ set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
+ message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
+ set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
+ message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
+ set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
+ message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG))
+ set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG})
+ message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')")
+endif ()
+
+if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG)
+ set(PICO_SDK_FETCH_FROM_GIT_TAG "master")
+ message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG")
+endif()
+
+set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
+set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
+set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
+set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK")
+
+if (NOT PICO_SDK_PATH)
+ if (PICO_SDK_FETCH_FROM_GIT)
+ include(FetchContent)
+ set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
+ if (PICO_SDK_FETCH_FROM_GIT_PATH)
+ get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
+ endif ()
+ FetchContent_Declare(
+ pico_sdk
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+ )
+
+ if (NOT pico_sdk)
+ message("Downloading Raspberry Pi Pico SDK")
+ # GIT_SUBMODULES_RECURSE was added in 3.17
+ if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
+ FetchContent_Populate(
+ pico_sdk
+ QUIET
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+ GIT_SUBMODULES_RECURSE FALSE
+
+ SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
+ BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
+ SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
+ )
+ else ()
+ FetchContent_Populate(
+ pico_sdk
+ QUIET
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+
+ SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
+ BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
+ SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
+ )
+ endif ()
+
+ set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
+ endif ()
+ set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
+ else ()
+ message(FATAL_ERROR
+ "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
+ )
+ endif ()
+endif ()
+
+get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
+if (NOT EXISTS ${PICO_SDK_PATH})
+ message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
+endif ()
+
+set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
+if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
+ message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
+endif ()
+
+set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
+
+include(${PICO_SDK_INIT_CMAKE_FILE})
diff --git a/drivers/0x03_button/0x03_button.c b/drivers/0x03_button/0x03_button.c
new file mode 100644
index 0000000..aa1b558
--- /dev/null
+++ b/drivers/0x03_button/0x03_button.c
@@ -0,0 +1,71 @@
+/**
+ * @file 0x03_button.c
+ * @brief Button demonstration: debounced press mirrors to LED + UART report
+ * @author Kevin Thomas
+ * @date 2025
+ *
+ * MIT License
+ *
+ * Copyright (c) 2025 Kevin Thomas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Demonstrates GPIO input using the button driver (button.h / button.c).
+ * The onboard LED mirrors the button state and every edge transition is
+ * reported over UART.
+ *
+ * Wiring:
+ * GPIO15 -> One leg of push button
+ * GND -> Other leg of push button
+ * GPIO25 -> Onboard LED (no external wiring needed)
+ */
+
+#include
+#include "pico/stdlib.h"
+#include "button.h"
+
+#define BUTTON_PIN 15
+#define LED_PIN 25
+#define DEBOUNCE_MS 20
+
+int main(void) {
+ stdio_init_all();
+ button_init(BUTTON_PIN, DEBOUNCE_MS);
+ button_led_init(LED_PIN);
+
+ printf("Button driver initialized: button=GPIO%d led=GPIO%d\r\n",
+ BUTTON_PIN, LED_PIN);
+
+ bool last_state = false;
+
+ while (true) {
+ bool pressed = button_is_pressed(BUTTON_PIN);
+
+ button_led_set(LED_PIN, pressed);
+
+ if (pressed != last_state) {
+ printf("Button: %s\r\n", pressed ? "PRESSED" : "RELEASED");
+ last_state = pressed;
+ }
+
+ sleep_ms(10);
+ }
+}
diff --git a/drivers/0x03_button/CMakeLists.txt b/drivers/0x03_button/CMakeLists.txt
new file mode 100644
index 0000000..b6442bd
--- /dev/null
+++ b/drivers/0x03_button/CMakeLists.txt
@@ -0,0 +1,56 @@
+# Generated Cmake Pico project file
+
+cmake_minimum_required(VERSION 3.13)
+
+set(CMAKE_C_STANDARD 11)
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+
+# Initialise pico_sdk from installed location
+# (note this can come from environment, CMake cache etc)
+
+# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work ==
+if(WIN32)
+ set(USERHOME $ENV{USERPROFILE})
+else()
+ set(USERHOME $ENV{HOME})
+endif()
+set(sdkVersion 2.2.0)
+set(toolchainVersion 14_2_Rel1)
+set(picotoolVersion 2.2.0)
+set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake)
+if (EXISTS ${picoVscode})
+ include(${picoVscode})
+endif()
+# ====================================================================================
+set(PICO_BOARD pico2 CACHE STRING "Board type")
+
+# Pull in Raspberry Pi Pico SDK (must be before project)
+include(pico_sdk_import.cmake)
+
+project(0x03_button C CXX ASM)
+
+# Initialise the Raspberry Pi Pico SDK
+pico_sdk_init()
+
+# Add executable. Default name is the project name, version 0.1
+
+add_executable(0x03_button 0x03_button.c button.c)
+
+pico_set_program_name(0x03_button "0x03_button")
+pico_set_program_version(0x03_button "0.1")
+
+# Modify the below lines to enable/disable output over UART/USB
+pico_enable_stdio_uart(0x03_button 1)
+pico_enable_stdio_usb(0x03_button 0)
+
+# Add the standard library to the build
+target_link_libraries(0x03_button
+ pico_stdlib)
+
+# Add the standard include files to the build
+target_include_directories(0x03_button PRIVATE
+ ${CMAKE_CURRENT_LIST_DIR}
+)
+
+pico_add_extra_outputs(0x03_button)
diff --git a/drivers/0x03_button/button.c b/drivers/0x03_button/button.c
new file mode 100644
index 0000000..e0bb2df
--- /dev/null
+++ b/drivers/0x03_button/button.c
@@ -0,0 +1,73 @@
+/**
+ * @file button.c
+ * @brief Implementation of the push-button GPIO input driver with debounce
+ * @author Kevin Thomas
+ * @date 2025
+ *
+ * MIT License
+ *
+ * Copyright (c) 2025 Kevin Thomas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "button.h"
+#include "pico/stdlib.h"
+#include "hardware/gpio.h"
+
+static uint32_t debounce_delay_ms = 20;
+
+/**
+ * @brief Confirm a raw active-low pin read by waiting for the debounce period
+ *
+ * After an initial low reading on the pin, this helper re-samples the pin
+ * after debounce_delay_ms milliseconds to confirm the reading is stable.
+ * This eliminates false triggers caused by mechanical contact bounce.
+ *
+ * @param pin GPIO pin number to re-sample
+ * @return bool true if the pin is still low after the debounce delay
+ */
+static bool debounce_confirm(uint32_t pin) {
+ sleep_ms(debounce_delay_ms);
+ return !gpio_get(pin);
+}
+
+void button_init(uint32_t pin, uint32_t debounce_ms) {
+ debounce_delay_ms = debounce_ms;
+ gpio_init(pin);
+ gpio_set_dir(pin, GPIO_IN);
+ gpio_pull_up(pin);
+}
+
+bool button_is_pressed(uint32_t pin) {
+ if (!gpio_get(pin)) {
+ return debounce_confirm(pin);
+ }
+ return false;
+}
+
+void button_led_init(uint32_t pin) {
+ gpio_init(pin);
+ gpio_set_dir(pin, GPIO_OUT);
+ gpio_put(pin, false);
+}
+
+void button_led_set(uint32_t pin, bool on) {
+ gpio_put(pin, on);
+}
diff --git a/drivers/0x03_button/button.h b/drivers/0x03_button/button.h
new file mode 100644
index 0000000..7bc1de5
--- /dev/null
+++ b/drivers/0x03_button/button.h
@@ -0,0 +1,80 @@
+/**
+ * @file button.h
+ * @brief Header for push-button GPIO input driver with debounce
+ * @author Kevin Thomas
+ * @date 2025
+ *
+ * MIT License
+ *
+ * Copyright (c) 2025 Kevin Thomas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef BUTTON_H
+#define BUTTON_H
+
+#include
+#include
+
+/**
+ * @brief Initialize a GPIO pin as an active-low button input with pull-up
+ *
+ * Configures the pin as an input and enables the internal pull-up resistor.
+ * The pin reads logic high when the button is open and logic low when the
+ * button connects the pin to GND.
+ *
+ * @param pin GPIO pin number to configure as a button input
+ * @param debounce_ms Debounce settling time in milliseconds (e.g. 20)
+ */
+void button_init(uint32_t pin, uint32_t debounce_ms);
+
+/**
+ * @brief Read the debounced state of the button
+ *
+ * Returns true only when the pin reads low continuously for the debounce
+ * period configured in button_init(). This prevents mechanical contact
+ * bounce from registering as multiple rapid presses.
+ *
+ * @param pin GPIO pin number previously initialized with button_init()
+ * @return bool true if the button is firmly pressed, false if released
+ */
+bool button_is_pressed(uint32_t pin);
+
+/**
+ * @brief Initialize a GPIO pin as a push-pull digital output for an indicator LED
+ *
+ * Configures the pin as a digital output and drives it low (LED off). Use
+ * together with button_led_set() to mirror button state visually.
+ *
+ * @param pin GPIO pin number to configure as an LED output
+ */
+void button_led_init(uint32_t pin);
+
+/**
+ * @brief Set the indicator LED state
+ *
+ * Drives the output pin high (LED on) or low (LED off).
+ *
+ * @param pin GPIO pin number previously initialized with button_led_init()
+ * @param on true to turn the LED on, false to turn it off
+ */
+void button_led_set(uint32_t pin, bool on);
+
+#endif // BUTTON_H
diff --git a/drivers/0x03_button/pico_sdk_import.cmake b/drivers/0x03_button/pico_sdk_import.cmake
new file mode 100644
index 0000000..d493cc2
--- /dev/null
+++ b/drivers/0x03_button/pico_sdk_import.cmake
@@ -0,0 +1,121 @@
+# This is a copy of /external/pico_sdk_import.cmake
+
+# This can be dropped into an external project to help locate this SDK
+# It should be include()ed prior to project()
+
+# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd.
+#
+# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+# following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+# disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
+ set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
+ message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
+ set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
+ message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
+ set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
+ message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG))
+ set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG})
+ message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')")
+endif ()
+
+if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG)
+ set(PICO_SDK_FETCH_FROM_GIT_TAG "master")
+ message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG")
+endif()
+
+set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
+set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
+set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
+set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK")
+
+if (NOT PICO_SDK_PATH)
+ if (PICO_SDK_FETCH_FROM_GIT)
+ include(FetchContent)
+ set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
+ if (PICO_SDK_FETCH_FROM_GIT_PATH)
+ get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
+ endif ()
+ FetchContent_Declare(
+ pico_sdk
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+ )
+
+ if (NOT pico_sdk)
+ message("Downloading Raspberry Pi Pico SDK")
+ # GIT_SUBMODULES_RECURSE was added in 3.17
+ if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
+ FetchContent_Populate(
+ pico_sdk
+ QUIET
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+ GIT_SUBMODULES_RECURSE FALSE
+
+ SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
+ BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
+ SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
+ )
+ else ()
+ FetchContent_Populate(
+ pico_sdk
+ QUIET
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+
+ SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
+ BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
+ SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
+ )
+ endif ()
+
+ set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
+ endif ()
+ set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
+ else ()
+ message(FATAL_ERROR
+ "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
+ )
+ endif ()
+endif ()
+
+get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
+if (NOT EXISTS ${PICO_SDK_PATH})
+ message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
+endif ()
+
+set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
+if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
+ message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
+endif ()
+
+set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
+
+include(${PICO_SDK_INIT_CMAKE_FILE})
diff --git a/drivers/0x04_pwm/0x04_pwm.c b/drivers/0x04_pwm/0x04_pwm.c
new file mode 100644
index 0000000..1688519
--- /dev/null
+++ b/drivers/0x04_pwm/0x04_pwm.c
@@ -0,0 +1,65 @@
+/**
+ * @file 0x04_pwm.c
+ * @brief PWM demonstration: LED breathing effect via duty-cycle sweep
+ * @author Kevin Thomas
+ * @date 2025
+ *
+ * MIT License
+ *
+ * Copyright (c) 2025 Kevin Thomas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Demonstrates PWM output using the pwm driver (pwm.h / pwm.c). A 1 kHz
+ * signal on GPIO 0 sweeps its duty cycle from 0% to 100% and back to
+ * produce a smooth LED breathing effect. The current duty is reported
+ * over UART.
+ *
+ * Wiring:
+ * GPIO0 -> LED anode (with 330 ohm series resistor to GND)
+ */
+
+#include
+#include "pico/stdlib.h"
+#include "pwm.h"
+
+#define PWM_PIN 0
+#define PWM_FREQ_HZ 1000
+
+int main(void) {
+ stdio_init_all();
+ pwm_driver_init(PWM_PIN, PWM_FREQ_HZ);
+
+ printf("PWM driver initialized: GPIO%d @ %d Hz\r\n", PWM_PIN, PWM_FREQ_HZ);
+
+ while (true) {
+ for (int duty = 0; duty <= 100; duty += 5) {
+ pwm_driver_set_duty_percent((uint8_t)duty);
+ printf("Duty: %3d%%\r\n", duty);
+ sleep_ms(50);
+ }
+ for (int duty = 100; duty >= 0; duty -= 5) {
+ pwm_driver_set_duty_percent((uint8_t)duty);
+ printf("Duty: %3d%%\r\n", duty);
+ sleep_ms(50);
+ }
+ }
+}
diff --git a/drivers/0x04_pwm/CMakeLists.txt b/drivers/0x04_pwm/CMakeLists.txt
new file mode 100644
index 0000000..f9fa8ef
--- /dev/null
+++ b/drivers/0x04_pwm/CMakeLists.txt
@@ -0,0 +1,58 @@
+# Generated Cmake Pico project file
+
+cmake_minimum_required(VERSION 3.13)
+
+set(CMAKE_C_STANDARD 11)
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+
+# Initialise pico_sdk from installed location
+# (note this can come from environment, CMake cache etc)
+
+# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work ==
+if(WIN32)
+ set(USERHOME $ENV{USERPROFILE})
+else()
+ set(USERHOME $ENV{HOME})
+endif()
+set(sdkVersion 2.2.0)
+set(toolchainVersion 14_2_Rel1)
+set(picotoolVersion 2.2.0)
+set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake)
+if (EXISTS ${picoVscode})
+ include(${picoVscode})
+endif()
+# ====================================================================================
+set(PICO_BOARD pico2 CACHE STRING "Board type")
+
+# Pull in Raspberry Pi Pico SDK (must be before project)
+include(pico_sdk_import.cmake)
+
+project(0x04_pwm C CXX ASM)
+
+# Initialise the Raspberry Pi Pico SDK
+pico_sdk_init()
+
+# Add executable. Default name is the project name, version 0.1
+
+add_executable(0x04_pwm 0x04_pwm.c pwm.c)
+
+pico_set_program_name(0x04_pwm "0x04_pwm")
+pico_set_program_version(0x04_pwm "0.1")
+
+# Modify the below lines to enable/disable output over UART/USB
+pico_enable_stdio_uart(0x04_pwm 1)
+pico_enable_stdio_usb(0x04_pwm 0)
+
+# Add the standard library to the build
+target_link_libraries(0x04_pwm
+ pico_stdlib
+ hardware_pwm
+ hardware_clocks)
+
+# Add the standard include files to the build
+target_include_directories(0x04_pwm PRIVATE
+ ${CMAKE_CURRENT_LIST_DIR}
+)
+
+pico_add_extra_outputs(0x04_pwm)
diff --git a/drivers/0x04_pwm/pico_sdk_import.cmake b/drivers/0x04_pwm/pico_sdk_import.cmake
new file mode 100644
index 0000000..d493cc2
--- /dev/null
+++ b/drivers/0x04_pwm/pico_sdk_import.cmake
@@ -0,0 +1,121 @@
+# This is a copy of /external/pico_sdk_import.cmake
+
+# This can be dropped into an external project to help locate this SDK
+# It should be include()ed prior to project()
+
+# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd.
+#
+# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+# following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+# disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
+ set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
+ message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
+ set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
+ message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
+ set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
+ message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG))
+ set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG})
+ message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')")
+endif ()
+
+if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG)
+ set(PICO_SDK_FETCH_FROM_GIT_TAG "master")
+ message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG")
+endif()
+
+set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
+set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
+set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
+set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK")
+
+if (NOT PICO_SDK_PATH)
+ if (PICO_SDK_FETCH_FROM_GIT)
+ include(FetchContent)
+ set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
+ if (PICO_SDK_FETCH_FROM_GIT_PATH)
+ get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
+ endif ()
+ FetchContent_Declare(
+ pico_sdk
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+ )
+
+ if (NOT pico_sdk)
+ message("Downloading Raspberry Pi Pico SDK")
+ # GIT_SUBMODULES_RECURSE was added in 3.17
+ if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
+ FetchContent_Populate(
+ pico_sdk
+ QUIET
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+ GIT_SUBMODULES_RECURSE FALSE
+
+ SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
+ BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
+ SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
+ )
+ else ()
+ FetchContent_Populate(
+ pico_sdk
+ QUIET
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+
+ SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
+ BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
+ SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
+ )
+ endif ()
+
+ set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
+ endif ()
+ set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
+ else ()
+ message(FATAL_ERROR
+ "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
+ )
+ endif ()
+endif ()
+
+get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
+if (NOT EXISTS ${PICO_SDK_PATH})
+ message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
+endif ()
+
+set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
+if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
+ message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
+endif ()
+
+set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
+
+include(${PICO_SDK_INIT_CMAKE_FILE})
diff --git a/drivers/0x04_pwm/pwm.c b/drivers/0x04_pwm/pwm.c
new file mode 100644
index 0000000..541132a
--- /dev/null
+++ b/drivers/0x04_pwm/pwm.c
@@ -0,0 +1,74 @@
+/**
+ * @file pwm.c
+ * @brief Implementation of the generic PWM output driver
+ * @author Kevin Thomas
+ * @date 2025
+ *
+ * MIT License
+ *
+ * Copyright (c) 2025 Kevin Thomas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "pwm.h"
+#include "pico/stdlib.h"
+#include "hardware/pwm.h"
+#include "hardware/clocks.h"
+
+static uint pwm_slice;
+static uint pwm_chan;
+static uint32_t pwm_wrap;
+
+/**
+ * @brief Compute the PWM clock divider that yields the target frequency
+ *
+ * Derives a floating-point divider from the current system clock frequency,
+ * the desired output frequency, and the chosen wrap value so that the PWM
+ * counter overflows exactly freq_hz times per second.
+ *
+ * @param freq_hz Desired PWM output frequency in Hz
+ * @param wrap_val Chosen PWM counter wrap value (period - 1)
+ * @return float Clock divider to program into the PWM slice
+ */
+static float calc_clk_div(uint32_t freq_hz, uint32_t wrap_val) {
+ uint32_t sys_hz = clock_get_hz(clk_sys);
+ return (float)sys_hz / ((float)freq_hz * (float)(wrap_val + 1));
+}
+
+void pwm_driver_init(uint32_t pin, uint32_t freq_hz) {
+ gpio_set_function(pin, GPIO_FUNC_PWM);
+ pwm_slice = pwm_gpio_to_slice_num(pin);
+ pwm_chan = pwm_gpio_to_channel(pin);
+ pwm_wrap = 10000 - 1; // resolution: 0.01% steps
+
+ pwm_config cfg = pwm_get_default_config();
+ pwm_config_set_clkdiv(&cfg, calc_clk_div(freq_hz, pwm_wrap));
+ pwm_config_set_wrap(&cfg, pwm_wrap);
+ pwm_init(pwm_slice, &cfg, true);
+ pwm_set_chan_level(pwm_slice, pwm_chan, 0);
+}
+
+void pwm_driver_set_duty_percent(uint8_t percent) {
+ if (percent > 100) {
+ percent = 100;
+ }
+ uint32_t level = ((uint32_t)percent * (pwm_wrap + 1)) / 100;
+ pwm_set_chan_level(pwm_slice, pwm_chan, level);
+}
diff --git a/drivers/0x04_pwm/pwm.h b/drivers/0x04_pwm/pwm.h
new file mode 100644
index 0000000..fc9e0a2
--- /dev/null
+++ b/drivers/0x04_pwm/pwm.h
@@ -0,0 +1,59 @@
+/**
+ * @file pwm.h
+ * @brief Header for generic PWM output driver
+ * @author Kevin Thomas
+ * @date 2025
+ *
+ * MIT License
+ *
+ * Copyright (c) 2025 Kevin Thomas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef PWM_H
+#define PWM_H
+
+#include
+
+/**
+ * @brief Initialize PWM output on the specified GPIO pin at a target frequency
+ *
+ * Assigns the GPIO pin to the PWM function, calculates the clock divider and
+ * wrap value from the system clock to achieve the requested frequency, then
+ * starts the PWM slice with a 0% duty cycle. Must be called once before using
+ * pwm_driver_set_duty_percent().
+ *
+ * @param pin GPIO pin number to drive with PWM output
+ * @param freq_hz Desired PWM frequency in Hz (e.g. 1000 for 1 kHz)
+ */
+void pwm_driver_init(uint32_t pin, uint32_t freq_hz);
+
+/**
+ * @brief Set the PWM duty cycle as an integer percentage
+ *
+ * Maps the percentage value to the internal PWM counter range and writes
+ * the result to the channel level register. Values above 100 are clamped
+ * to 100.
+ *
+ * @param percent Duty cycle from 0 (always low) to 100 (always high)
+ */
+void pwm_driver_set_duty_percent(uint8_t percent);
+
+#endif // PWM_H
diff --git a/drivers/0x05_servo/0x05_servo.c b/drivers/0x05_servo/0x05_servo.c
new file mode 100644
index 0000000..3e7a41a
--- /dev/null
+++ b/drivers/0x05_servo/0x05_servo.c
@@ -0,0 +1,70 @@
+/**
+ * @file 0x05_servo.c
+ * @brief SG90 servo motor driver for the Raspberry Pi Pico 2
+ * @author Kevin Thomas
+ * @date 2025
+ *
+ * MIT License
+ *
+ * Copyright (c) 2025 Kevin Thomas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * This driver demonstrates SG90 servo control using the servo.c/servo.h
+ * driver (PWM at 50 Hz on GPIO 6). The servo sweeps from 0 degrees to
+ * 180 degrees and back in 10-degree increments, printing each angle over
+ * UART.
+ *
+ * Wiring:
+ * GPIO6 -> Servo signal wire (orange or yellow)
+ * 5V -> Servo power wire (red) -- use external 5 V supply for load
+ * GND -> Servo ground wire (brown or black)
+ */
+
+#include
+#include "pico/stdlib.h"
+#include "servo.h"
+
+#define SERVO_GPIO 6
+#define STEP_DEGREES 10
+#define STEP_DELAY_MS 150
+
+int main(void) {
+ stdio_init_all();
+ servo_init(SERVO_GPIO);
+
+ printf("Servo driver initialized on GPIO %d\r\n", SERVO_GPIO);
+ printf("Sweeping 0 -> 180 -> 0 degrees in %d-degree steps\r\n",
+ STEP_DEGREES);
+
+ while (true) {
+ for (int angle = 0; angle <= 180; angle += STEP_DEGREES) {
+ servo_set_angle((float)angle);
+ printf("Angle: %3d deg\r\n", angle);
+ sleep_ms(STEP_DELAY_MS);
+ }
+ for (int angle = 180; angle >= 0; angle -= STEP_DEGREES) {
+ servo_set_angle((float)angle);
+ printf("Angle: %3d deg\r\n", angle);
+ sleep_ms(STEP_DELAY_MS);
+ }
+ }
+}
diff --git a/drivers/0x05_servo/CMakeLists.txt b/drivers/0x05_servo/CMakeLists.txt
new file mode 100644
index 0000000..55266bd
--- /dev/null
+++ b/drivers/0x05_servo/CMakeLists.txt
@@ -0,0 +1,58 @@
+# Generated Cmake Pico project file
+
+cmake_minimum_required(VERSION 3.13)
+
+set(CMAKE_C_STANDARD 11)
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+
+# Initialise pico_sdk from installed location
+# (note this can come from environment, CMake cache etc)
+
+# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work ==
+if(WIN32)
+ set(USERHOME $ENV{USERPROFILE})
+else()
+ set(USERHOME $ENV{HOME})
+endif()
+set(sdkVersion 2.2.0)
+set(toolchainVersion 14_2_Rel1)
+set(picotoolVersion 2.2.0)
+set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake)
+if (EXISTS ${picoVscode})
+ include(${picoVscode})
+endif()
+# ====================================================================================
+set(PICO_BOARD pico2 CACHE STRING "Board type")
+
+# Pull in Raspberry Pi Pico SDK (must be before project)
+include(pico_sdk_import.cmake)
+
+project(0x05_servo C CXX ASM)
+
+# Initialise the Raspberry Pi Pico SDK
+pico_sdk_init()
+
+# Add executable. Default name is the project name, version 0.1
+
+add_executable(0x05_servo 0x05_servo.c servo.c)
+
+pico_set_program_name(0x05_servo "0x05_servo")
+pico_set_program_version(0x05_servo "0.1")
+
+# Modify the below lines to enable/disable output over UART/USB
+pico_enable_stdio_uart(0x05_servo 1)
+pico_enable_stdio_usb(0x05_servo 0)
+
+# Add the standard library to the build
+target_link_libraries(0x05_servo
+ pico_stdlib
+ hardware_pwm
+ hardware_clocks)
+
+# Add the standard include files to the build
+target_include_directories(0x05_servo PRIVATE
+ ${CMAKE_CURRENT_LIST_DIR}
+)
+
+pico_add_extra_outputs(0x05_servo)
diff --git a/drivers/0x05_servo/pico_sdk_import.cmake b/drivers/0x05_servo/pico_sdk_import.cmake
new file mode 100644
index 0000000..d493cc2
--- /dev/null
+++ b/drivers/0x05_servo/pico_sdk_import.cmake
@@ -0,0 +1,121 @@
+# This is a copy of /external/pico_sdk_import.cmake
+
+# This can be dropped into an external project to help locate this SDK
+# It should be include()ed prior to project()
+
+# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd.
+#
+# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+# following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+# disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
+ set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
+ message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
+ set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
+ message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
+ set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
+ message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG))
+ set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG})
+ message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')")
+endif ()
+
+if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG)
+ set(PICO_SDK_FETCH_FROM_GIT_TAG "master")
+ message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG")
+endif()
+
+set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
+set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
+set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
+set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK")
+
+if (NOT PICO_SDK_PATH)
+ if (PICO_SDK_FETCH_FROM_GIT)
+ include(FetchContent)
+ set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
+ if (PICO_SDK_FETCH_FROM_GIT_PATH)
+ get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
+ endif ()
+ FetchContent_Declare(
+ pico_sdk
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+ )
+
+ if (NOT pico_sdk)
+ message("Downloading Raspberry Pi Pico SDK")
+ # GIT_SUBMODULES_RECURSE was added in 3.17
+ if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
+ FetchContent_Populate(
+ pico_sdk
+ QUIET
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+ GIT_SUBMODULES_RECURSE FALSE
+
+ SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
+ BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
+ SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
+ )
+ else ()
+ FetchContent_Populate(
+ pico_sdk
+ QUIET
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+
+ SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
+ BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
+ SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
+ )
+ endif ()
+
+ set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
+ endif ()
+ set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
+ else ()
+ message(FATAL_ERROR
+ "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
+ )
+ endif ()
+endif ()
+
+get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
+if (NOT EXISTS ${PICO_SDK_PATH})
+ message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
+endif ()
+
+set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
+if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
+ message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
+endif ()
+
+set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
+
+include(${PICO_SDK_INIT_CMAKE_FILE})
diff --git a/drivers/0x05_servo/servo.c b/drivers/0x05_servo/servo.c
new file mode 100644
index 0000000..4cf3481
--- /dev/null
+++ b/drivers/0x05_servo/servo.c
@@ -0,0 +1,98 @@
+/**
+ * @file servo.c
+ * @brief Implementation of a simple SG90 servo driver using PWM (50Hz)
+ * @author Kevin Thomas
+ * @date 2025
+ *
+ * MIT License
+ *
+ * Copyright (c) 2025 Kevin Thomas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "servo.h"
+#include "pico/stdlib.h"
+#include "hardware/pwm.h"
+#include "hardware/clocks.h"
+
+// Default servo pulse range (SG90 typical)
+static const uint16_t SERVO_DEFAULT_MIN_US = 1000;
+static const uint16_t SERVO_DEFAULT_MAX_US = 2000;
+
+// internal state
+static uint8_t servo_pin = 0;
+static uint servo_slice = 0;
+static uint servo_chan = 0;
+static uint32_t servo_wrap = 20000 - 1; // wrap to map microseconds for 50Hz
+static float servo_hz = 50.0f;
+static bool servo_initialized = false;
+
+/**
+ * @brief Convert a pulse width in microseconds to a PWM counter level
+ *
+ * Uses the configured PWM wrap and servo frequency to map pulse time
+ * into the channel compare value expected by the PWM hardware.
+ *
+ * @param pulse_us Pulse width in microseconds
+ * @return uint32_t PWM level suitable for pwm_set_chan_level()
+ */
+static uint32_t pulse_us_to_level(uint32_t pulse_us) {
+ const float period_us = 1000000.0f / servo_hz; // 20000us
+ float counts_per_us = (servo_wrap + 1) / period_us;
+ return (uint32_t)(pulse_us * counts_per_us + 0.5f);
+}
+
+void servo_init(uint8_t pin) {
+ servo_pin = pin;
+
+ // Configure GPIO for PWM
+ gpio_set_function(servo_pin, GPIO_FUNC_PWM);
+ servo_slice = pwm_gpio_to_slice_num(servo_pin);
+ servo_chan = pwm_gpio_to_channel(servo_pin);
+
+ pwm_config config = pwm_get_default_config();
+
+ // Calculate clock divider to achieve 50 Hz with our chosen wrap
+ const uint32_t sys_clock_hz = clock_get_hz(clk_sys);
+ float clock_div = (float)sys_clock_hz / (servo_hz * (servo_wrap + 1));
+
+ pwm_config_set_clkdiv(&config, clock_div);
+ pwm_config_set_wrap(&config, servo_wrap);
+ pwm_init(servo_slice, &config, true);
+ servo_initialized = true;
+}
+
+void servo_set_pulse_us(uint16_t pulse_us) {
+ if (!servo_initialized) return; // not initialized
+ // clamp to defaults
+ if (pulse_us < SERVO_DEFAULT_MIN_US) pulse_us = SERVO_DEFAULT_MIN_US;
+ if (pulse_us > SERVO_DEFAULT_MAX_US) pulse_us = SERVO_DEFAULT_MAX_US;
+ uint32_t level = pulse_us_to_level(pulse_us);
+ pwm_set_chan_level(servo_slice, servo_chan, level);
+}
+
+void servo_set_angle(float degrees) {
+ if (degrees < 0.0f) degrees = 0.0f;
+ if (degrees > 180.0f) degrees = 180.0f;
+ // linear map 0..180 -> min_us..max_us
+ float ratio = degrees / 180.0f;
+ uint16_t pulse = (uint16_t)(SERVO_DEFAULT_MIN_US + ratio * (SERVO_DEFAULT_MAX_US - SERVO_DEFAULT_MIN_US) + 0.5f);
+ servo_set_pulse_us(pulse);
+}
\ No newline at end of file
diff --git a/drivers/0x05_servo/servo.h b/drivers/0x05_servo/servo.h
new file mode 100644
index 0000000..878d029
--- /dev/null
+++ b/drivers/0x05_servo/servo.h
@@ -0,0 +1,62 @@
+/**
+ * @file servo.h
+ * @brief Header for SG90 servo driver (PWM)
+ * @author Kevin Thomas
+ * @date 2025
+ *
+ * MIT License
+ *
+ * Copyright (c) 2025 Kevin Thomas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef SERVO_H
+#define SERVO_H
+
+#include
+#include
+
+/**
+ * @brief Initialize servo driver on a given GPIO pin
+ *
+ * Configures PWM for a 50 Hz servo (SG90). Call once before using other API.
+ *
+ * @param pin GPIO pin number to use for servo PWM
+ */
+void servo_init(uint8_t pin);
+
+/**
+ * @brief Set servo pulse width in microseconds (typical 1000-2000)
+ *
+ * @param pulse_us Pulse width in microseconds
+ */
+void servo_set_pulse_us(uint16_t pulse_us);
+
+/**
+ * @brief Set servo angle in degrees (0 to 180)
+ *
+ * Maps 0..180 degrees to the configured pulse range (default 1000..2000 us).
+ * Values outside [0,180] will be clamped.
+ *
+ * @param degrees Angle in degrees
+ */
+void servo_set_angle(float degrees);
+
+#endif // SERVO_H
diff --git a/drivers/0x06_adc/0x06_adc.c b/drivers/0x06_adc/0x06_adc.c
new file mode 100644
index 0000000..750db77
--- /dev/null
+++ b/drivers/0x06_adc/0x06_adc.c
@@ -0,0 +1,64 @@
+/**
+ * @file 0x06_adc.c
+ * @brief ADC demonstration: potentiometer voltage + on-chip temperature
+ * @author Kevin Thomas
+ * @date 2025
+ *
+ * MIT License
+ *
+ * Copyright (c) 2025 Kevin Thomas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Demonstrates 12-bit ADC using the adc driver (adc.h / adc.c). Reads
+ * ADC channel 0 (GPIO 26) and reports the voltage in millivolts alongside
+ * the on-chip temperature sensor reading every 500 ms over UART.
+ *
+ * Wiring:
+ * GPIO26 -> Wiper of a 10 kohm potentiometer
+ * 3.3V -> One end of the potentiometer
+ * GND -> Other end of the potentiometer
+ */
+
+#include
+#include "pico/stdlib.h"
+#include "adc.h"
+
+#define ADC_GPIO 26
+#define ADC_CHANNEL 0
+
+int main(void) {
+ stdio_init_all();
+ adc_driver_init(ADC_GPIO, ADC_CHANNEL);
+
+ printf("ADC driver initialized: GPIO%d (channel %d)\r\n",
+ ADC_GPIO, ADC_CHANNEL);
+
+ while (true) {
+ uint32_t voltage_mv = adc_driver_read_mv();
+ float temp_c = adc_driver_read_temp_celsius();
+
+ printf("ADC0: %4lu mV | Chip temp: %.1f C\r\n",
+ voltage_mv, temp_c);
+
+ sleep_ms(500);
+ }
+}
diff --git a/drivers/0x06_adc/CMakeLists.txt b/drivers/0x06_adc/CMakeLists.txt
new file mode 100644
index 0000000..72fe0a2
--- /dev/null
+++ b/drivers/0x06_adc/CMakeLists.txt
@@ -0,0 +1,57 @@
+# Generated Cmake Pico project file
+
+cmake_minimum_required(VERSION 3.13)
+
+set(CMAKE_C_STANDARD 11)
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+
+# Initialise pico_sdk from installed location
+# (note this can come from environment, CMake cache etc)
+
+# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work ==
+if(WIN32)
+ set(USERHOME $ENV{USERPROFILE})
+else()
+ set(USERHOME $ENV{HOME})
+endif()
+set(sdkVersion 2.2.0)
+set(toolchainVersion 14_2_Rel1)
+set(picotoolVersion 2.2.0)
+set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake)
+if (EXISTS ${picoVscode})
+ include(${picoVscode})
+endif()
+# ====================================================================================
+set(PICO_BOARD pico2 CACHE STRING "Board type")
+
+# Pull in Raspberry Pi Pico SDK (must be before project)
+include(pico_sdk_import.cmake)
+
+project(0x06_adc C CXX ASM)
+
+# Initialise the Raspberry Pi Pico SDK
+pico_sdk_init()
+
+# Add executable. Default name is the project name, version 0.1
+
+add_executable(0x06_adc 0x06_adc.c adc.c)
+
+pico_set_program_name(0x06_adc "0x06_adc")
+pico_set_program_version(0x06_adc "0.1")
+
+# Modify the below lines to enable/disable output over UART/USB
+pico_enable_stdio_uart(0x06_adc 1)
+pico_enable_stdio_usb(0x06_adc 0)
+
+# Add the standard library to the build
+target_link_libraries(0x06_adc
+ pico_stdlib
+ hardware_adc)
+
+# Add the standard include files to the build
+target_include_directories(0x06_adc PRIVATE
+ ${CMAKE_CURRENT_LIST_DIR}
+)
+
+pico_add_extra_outputs(0x06_adc)
diff --git a/drivers/0x06_adc/adc.c b/drivers/0x06_adc/adc.c
new file mode 100644
index 0000000..5e19e06
--- /dev/null
+++ b/drivers/0x06_adc/adc.c
@@ -0,0 +1,82 @@
+/**
+ * @file adc.c
+ * @brief Implementation of the 12-bit ADC driver
+ * @author Kevin Thomas
+ * @date 2025
+ *
+ * MIT License
+ *
+ * Copyright (c) 2025 Kevin Thomas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "adc.h"
+#include "pico/stdlib.h"
+#include "hardware/adc.h"
+
+#define ADC_VREF_MV 3300
+#define ADC_FULL_SCALE 4095
+
+static uint8_t active_channel = 0;
+
+/**
+ * @brief Convert a raw 12-bit ADC value to millivolts
+ *
+ * Scales the raw value linearly against the 3.3 V reference.
+ *
+ * @param raw 12-bit ADC conversion result (0 - 4095)
+ * @return uint32_t Equivalent voltage in millivolts (0 - 3300)
+ */
+static uint32_t raw_to_mv(uint16_t raw) {
+ return (uint32_t)raw * ADC_VREF_MV / ADC_FULL_SCALE;
+}
+
+/**
+ * @brief Convert a raw temperature-sensor ADC value to degrees Celsius
+ *
+ * Applies the RP2350 datasheet formula:
+ * T = 27 - (V - 0.706) / 0.001721
+ *
+ * @param raw 12-bit ADC result from the internal temperature sensor (channel 4)
+ * @return float Die temperature in degrees Celsius
+ */
+static float raw_to_celsius(uint16_t raw) {
+ float voltage = (float)raw * 3.3f / (float)ADC_FULL_SCALE;
+ return 27.0f - (voltage - 0.706f) / 0.001721f;
+}
+
+void adc_driver_init(uint32_t gpio, uint8_t channel) {
+ active_channel = channel;
+ adc_init();
+ adc_gpio_init(gpio);
+ adc_set_temp_sensor_enabled(true);
+ adc_select_input(channel);
+}
+
+uint32_t adc_driver_read_mv(void) {
+ return raw_to_mv(adc_read());
+}
+
+float adc_driver_read_temp_celsius(void) {
+ adc_select_input(4);
+ float result = raw_to_celsius(adc_read());
+ adc_select_input(active_channel);
+ return result;
+}
diff --git a/drivers/0x06_adc/adc.h b/drivers/0x06_adc/adc.h
new file mode 100644
index 0000000..b607551
--- /dev/null
+++ b/drivers/0x06_adc/adc.h
@@ -0,0 +1,69 @@
+/**
+ * @file adc.h
+ * @brief Header for 12-bit ADC driver (analog pin + internal temperature sensor)
+ * @author Kevin Thomas
+ * @date 2025
+ *
+ * MIT License
+ *
+ * Copyright (c) 2025 Kevin Thomas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef ADC_H
+#define ADC_H
+
+#include
+
+/**
+ * @brief Initialize the ADC peripheral and configure an analog GPIO pin
+ *
+ * Powers on the ADC block, configures the specified GPIO as an analog input,
+ * enables the internal temperature sensor on channel 4, and selects the
+ * given channel as the active input. Must be called once before using any
+ * other adc_driver_* functions.
+ *
+ * @param gpio GPIO pin number for the analog input (26 = ch0, 27 = ch1, 28 = ch2)
+ * @param channel ADC channel number corresponding to the GPIO (0, 1, or 2)
+ */
+void adc_driver_init(uint32_t gpio, uint8_t channel);
+
+/**
+ * @brief Perform a single ADC conversion on the active channel and return millivolts
+ *
+ * Reads the 12-bit raw value from the currently selected channel and scales
+ * it to millivolts using the 3.3 V (3300 mV) full-scale reference.
+ *
+ * @return uint32_t Measured voltage in millivolts (0 to 3300)
+ */
+uint32_t adc_driver_read_mv(void);
+
+/**
+ * @brief Read the on-chip temperature sensor and return degrees Celsius
+ *
+ * Temporarily switches to ADC channel 4 (internal temperature sensor),
+ * performs a conversion, applies the formula from the RP2350 datasheet,
+ * then restores the channel that was active before the call.
+ *
+ * @return float Die temperature in degrees Celsius
+ */
+float adc_driver_read_temp_celsius(void);
+
+#endif // ADC_H
diff --git a/drivers/0x06_adc/pico_sdk_import.cmake b/drivers/0x06_adc/pico_sdk_import.cmake
new file mode 100644
index 0000000..d493cc2
--- /dev/null
+++ b/drivers/0x06_adc/pico_sdk_import.cmake
@@ -0,0 +1,121 @@
+# This is a copy of /external/pico_sdk_import.cmake
+
+# This can be dropped into an external project to help locate this SDK
+# It should be include()ed prior to project()
+
+# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd.
+#
+# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+# following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+# disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
+ set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
+ message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
+ set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
+ message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
+ set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
+ message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG))
+ set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG})
+ message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')")
+endif ()
+
+if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG)
+ set(PICO_SDK_FETCH_FROM_GIT_TAG "master")
+ message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG")
+endif()
+
+set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
+set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
+set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
+set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK")
+
+if (NOT PICO_SDK_PATH)
+ if (PICO_SDK_FETCH_FROM_GIT)
+ include(FetchContent)
+ set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
+ if (PICO_SDK_FETCH_FROM_GIT_PATH)
+ get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
+ endif ()
+ FetchContent_Declare(
+ pico_sdk
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+ )
+
+ if (NOT pico_sdk)
+ message("Downloading Raspberry Pi Pico SDK")
+ # GIT_SUBMODULES_RECURSE was added in 3.17
+ if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
+ FetchContent_Populate(
+ pico_sdk
+ QUIET
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+ GIT_SUBMODULES_RECURSE FALSE
+
+ SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
+ BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
+ SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
+ )
+ else ()
+ FetchContent_Populate(
+ pico_sdk
+ QUIET
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+
+ SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
+ BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
+ SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
+ )
+ endif ()
+
+ set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
+ endif ()
+ set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
+ else ()
+ message(FATAL_ERROR
+ "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
+ )
+ endif ()
+endif ()
+
+get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
+if (NOT EXISTS ${PICO_SDK_PATH})
+ message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
+endif ()
+
+set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
+if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
+ message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
+endif ()
+
+set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
+
+include(${PICO_SDK_INIT_CMAKE_FILE})
diff --git a/drivers/0x07_i2c/0x07_i2c.c b/drivers/0x07_i2c/0x07_i2c.c
new file mode 100644
index 0000000..cf47b80
--- /dev/null
+++ b/drivers/0x07_i2c/0x07_i2c.c
@@ -0,0 +1,63 @@
+/**
+ * @file 0x07_i2c.c
+ * @brief I2C demonstration: scan all 7-bit addresses and report devices
+ * @author Kevin Thomas
+ * @date 2025
+ *
+ * MIT License
+ *
+ * Copyright (c) 2025 Kevin Thomas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Demonstrates I2C bus scanning using the i2c driver (i2c.h / i2c.c). I2C1
+ * is configured at 100 kHz on SDA=GPIO2 / SCL=GPIO3. A formatted hex table
+ * of all responding device addresses is printed over UART and repeated
+ * every 5 seconds.
+ *
+ * Wiring:
+ * GPIO2 (SDA) -> I2C device SDA (4.7 kohm pull-up to 3.3 V recommended)
+ * GPIO3 (SCL) -> I2C device SCL (4.7 kohm pull-up to 3.3 V recommended)
+ * 3.3V -> I2C device VCC
+ * GND -> I2C device GND
+ */
+
+#include
+#include "pico/stdlib.h"
+#include "i2c.h"
+
+#define I2C_PORT 1
+#define I2C_SDA_PIN 2
+#define I2C_SCL_PIN 3
+#define I2C_BAUD 100000
+
+int main(void) {
+ stdio_init_all();
+ i2c_driver_init(I2C_PORT, I2C_SDA_PIN, I2C_SCL_PIN, I2C_BAUD);
+
+ printf("I2C driver initialized: I2C%d @ %d Hz SDA=GPIO%d SCL=GPIO%d\r\n",
+ I2C_PORT, I2C_BAUD, I2C_SDA_PIN, I2C_SCL_PIN);
+
+ while (true) {
+ i2c_driver_scan(I2C_PORT);
+ sleep_ms(5000);
+ }
+}
diff --git a/drivers/0x07_i2c/CMakeLists.txt b/drivers/0x07_i2c/CMakeLists.txt
new file mode 100644
index 0000000..30a6ff6
--- /dev/null
+++ b/drivers/0x07_i2c/CMakeLists.txt
@@ -0,0 +1,57 @@
+# Generated Cmake Pico project file
+
+cmake_minimum_required(VERSION 3.13)
+
+set(CMAKE_C_STANDARD 11)
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+
+# Initialise pico_sdk from installed location
+# (note this can come from environment, CMake cache etc)
+
+# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work ==
+if(WIN32)
+ set(USERHOME $ENV{USERPROFILE})
+else()
+ set(USERHOME $ENV{HOME})
+endif()
+set(sdkVersion 2.2.0)
+set(toolchainVersion 14_2_Rel1)
+set(picotoolVersion 2.2.0)
+set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake)
+if (EXISTS ${picoVscode})
+ include(${picoVscode})
+endif()
+# ====================================================================================
+set(PICO_BOARD pico2 CACHE STRING "Board type")
+
+# Pull in Raspberry Pi Pico SDK (must be before project)
+include(pico_sdk_import.cmake)
+
+project(0x07_i2c C CXX ASM)
+
+# Initialise the Raspberry Pi Pico SDK
+pico_sdk_init()
+
+# Add executable. Default name is the project name, version 0.1
+
+add_executable(0x07_i2c 0x07_i2c.c i2c.c)
+
+pico_set_program_name(0x07_i2c "0x07_i2c")
+pico_set_program_version(0x07_i2c "0.1")
+
+# Modify the below lines to enable/disable output over UART/USB
+pico_enable_stdio_uart(0x07_i2c 1)
+pico_enable_stdio_usb(0x07_i2c 0)
+
+# Add the standard library to the build
+target_link_libraries(0x07_i2c
+ pico_stdlib
+ hardware_i2c)
+
+# Add the standard include files to the build
+target_include_directories(0x07_i2c PRIVATE
+ ${CMAKE_CURRENT_LIST_DIR}
+)
+
+pico_add_extra_outputs(0x07_i2c)
diff --git a/drivers/0x07_i2c/i2c.c b/drivers/0x07_i2c/i2c.c
new file mode 100644
index 0000000..1a09d4f
--- /dev/null
+++ b/drivers/0x07_i2c/i2c.c
@@ -0,0 +1,75 @@
+/**
+ * @file i2c.c
+ * @brief Implementation of the I2C bus driver
+ * @author Kevin Thomas
+ * @date 2025
+ *
+ * MIT License
+ *
+ * Copyright (c) 2025 Kevin Thomas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "i2c.h"
+#include
+#include "pico/stdlib.h"
+#include "hardware/i2c.h"
+#include "hardware/gpio.h"
+
+static i2c_inst_t *get_i2c_inst(uint8_t port) {
+ return port == 0 ? i2c0 : i2c1;
+}
+
+void i2c_driver_init(uint8_t port, uint32_t sda_pin, uint32_t scl_pin,
+ uint32_t baud_hz) {
+ i2c_inst_t *inst = get_i2c_inst(port);
+ i2c_init(inst, baud_hz);
+ gpio_set_function(sda_pin, GPIO_FUNC_I2C);
+ gpio_set_function(scl_pin, GPIO_FUNC_I2C);
+ gpio_pull_up(sda_pin);
+ gpio_pull_up(scl_pin);
+}
+
+bool i2c_driver_probe(uint8_t port, uint8_t addr) {
+ i2c_inst_t *inst = get_i2c_inst(port);
+ uint8_t dummy;
+ return i2c_read_blocking(inst, addr, &dummy, 1, false) >= 0;
+}
+
+void i2c_driver_scan(uint8_t port) {
+ printf("\r\nI2C bus scan:\r\n");
+ printf(" 0 1 2 3 4 5 6 7 8 9 A B C D E F\r\n");
+
+ for (uint8_t addr = 0; addr < 128; addr++) {
+ if (addr % 16 == 0) {
+ printf("%02X: ", addr);
+ }
+ if (addr < 0x08 || addr > 0x77) {
+ printf(" ");
+ } else if (i2c_driver_probe(port, addr)) {
+ printf("%02X ", addr);
+ } else {
+ printf("-- ");
+ }
+ if (addr % 16 == 15) {
+ printf("\r\n");
+ }
+ }
+}
diff --git a/drivers/0x07_i2c/i2c.h b/drivers/0x07_i2c/i2c.h
new file mode 100644
index 0000000..c5add58
--- /dev/null
+++ b/drivers/0x07_i2c/i2c.h
@@ -0,0 +1,75 @@
+/**
+ * @file i2c.h
+ * @brief Header for I2C bus driver (init + 7-bit address scanner)
+ * @author Kevin Thomas
+ * @date 2025
+ *
+ * MIT License
+ *
+ * Copyright (c) 2025 Kevin Thomas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef I2C_H
+#define I2C_H
+
+#include
+#include
+
+/**
+ * @brief Initialize an I2C peripheral at the requested baud rate
+ *
+ * Calls i2c_init() for the given instance, assigns the GPIO alternate
+ * functions for SDA and SCL, and enables the internal pull-ups on both
+ * pins so that short buses operate without external resistors.
+ *
+ * @param port I2C port number (0 for i2c0, 1 for i2c1)
+ * @param sda_pin GPIO pin number for SDA
+ * @param scl_pin GPIO pin number for SCL
+ * @param baud_hz Bus clock frequency in Hz (e.g. 100000 for standard mode)
+ */
+void i2c_driver_init(uint8_t port, uint32_t sda_pin, uint32_t scl_pin,
+ uint32_t baud_hz);
+
+/**
+ * @brief Probe a 7-bit I2C address and return whether a device responds
+ *
+ * Attempts a 1-byte read to the target address. A non-negative return from
+ * i2c_read_blocking() means the device sent an ACK. A negative return means
+ * the address is unoccupied.
+ *
+ * @param port I2C port number (0 for i2c0, 1 for i2c1)
+ * @param addr 7-bit I2C address to probe (0x00 - 0x7F)
+ * @return bool true if a device acknowledged, false otherwise
+ */
+bool i2c_driver_probe(uint8_t port, uint8_t addr);
+
+/**
+ * @brief Scan all valid 7-bit addresses and print a formatted table over UART
+ *
+ * Iterates addresses 0x08 through 0x77, probes each one via i2c_driver_probe(),
+ * and prints a 16-column hex grid showing discovered device addresses.
+ * The reserved ranges (0x00-0x07 and 0x78-0x7F) are shown as blank.
+ *
+ * @param port I2C port number (0 for i2c0, 1 for i2c1)
+ */
+void i2c_driver_scan(uint8_t port);
+
+#endif // I2C_H
diff --git a/drivers/0x07_i2c/pico_sdk_import.cmake b/drivers/0x07_i2c/pico_sdk_import.cmake
new file mode 100644
index 0000000..d493cc2
--- /dev/null
+++ b/drivers/0x07_i2c/pico_sdk_import.cmake
@@ -0,0 +1,121 @@
+# This is a copy of /external/pico_sdk_import.cmake
+
+# This can be dropped into an external project to help locate this SDK
+# It should be include()ed prior to project()
+
+# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd.
+#
+# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+# following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+# disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
+ set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
+ message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
+ set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
+ message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
+ set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
+ message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG))
+ set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG})
+ message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')")
+endif ()
+
+if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG)
+ set(PICO_SDK_FETCH_FROM_GIT_TAG "master")
+ message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG")
+endif()
+
+set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
+set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
+set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
+set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK")
+
+if (NOT PICO_SDK_PATH)
+ if (PICO_SDK_FETCH_FROM_GIT)
+ include(FetchContent)
+ set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
+ if (PICO_SDK_FETCH_FROM_GIT_PATH)
+ get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
+ endif ()
+ FetchContent_Declare(
+ pico_sdk
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+ )
+
+ if (NOT pico_sdk)
+ message("Downloading Raspberry Pi Pico SDK")
+ # GIT_SUBMODULES_RECURSE was added in 3.17
+ if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
+ FetchContent_Populate(
+ pico_sdk
+ QUIET
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+ GIT_SUBMODULES_RECURSE FALSE
+
+ SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
+ BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
+ SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
+ )
+ else ()
+ FetchContent_Populate(
+ pico_sdk
+ QUIET
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+
+ SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
+ BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
+ SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
+ )
+ endif ()
+
+ set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
+ endif ()
+ set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
+ else ()
+ message(FATAL_ERROR
+ "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
+ )
+ endif ()
+endif ()
+
+get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
+if (NOT EXISTS ${PICO_SDK_PATH})
+ message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
+endif ()
+
+set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
+if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
+ message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
+endif ()
+
+set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
+
+include(${PICO_SDK_INIT_CMAKE_FILE})
diff --git a/drivers/0x08_lcd1602/0x08_lcd1602.c b/drivers/0x08_lcd1602/0x08_lcd1602.c
new file mode 100644
index 0000000..6a1ca59
--- /dev/null
+++ b/drivers/0x08_lcd1602/0x08_lcd1602.c
@@ -0,0 +1,76 @@
+/**
+ * @file 0x08_lcd1602.c
+ * @brief HD44780 16x2 LCD (PCF8574 I2C backpack) driver for the Raspberry Pi Pico 2
+ * @author Kevin Thomas
+ * @date 2025
+ *
+ * MIT License
+ *
+ * Copyright (c) 2025 Kevin Thomas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * This driver demonstrates how to drive a 16x2 HD44780 LCD connected via a
+ * PCF8574 I2C backpack using the lcd1602.c/lcd1602.h driver. Line 0 shows
+ * a static title and line 1 displays a live up-counter that increments every
+ * second. The counter value is also printed over UART for debugging.
+ *
+ * Wiring:
+ * GPIO2 (SDA) -> PCF8574 backpack SDA (4.7 kohm pull-up to 3.3 V)
+ * GPIO3 (SCL) -> PCF8574 backpack SCL (4.7 kohm pull-up to 3.3 V)
+ * 3.3V or 5V -> PCF8574 backpack VCC
+ * GND -> PCF8574 backpack GND
+ */
+
+#include
+#include
+#include "pico/stdlib.h"
+#include "lcd1602.h"
+
+#define I2C_PORT 1
+#define I2C_SDA_PIN 2
+#define I2C_SCL_PIN 3
+#define I2C_BAUD_HZ 100000
+#define LCD_I2C_ADDR 0x27
+
+int main(void) {
+ stdio_init_all();
+
+ lcd_init(I2C_PORT, I2C_SDA_PIN, I2C_SCL_PIN, I2C_BAUD_HZ,
+ LCD_I2C_ADDR, 4, 0x08);
+
+ lcd_set_cursor(0, 0);
+ lcd_puts("Reverse Eng.");
+
+ printf("LCD 1602 driver initialized at I2C addr 0x%02X\r\n", LCD_I2C_ADDR);
+
+ uint32_t count = 0;
+ char buf[17];
+
+ while (true) {
+ snprintf(buf, sizeof(buf), "Count: %6lu", count++);
+ lcd_set_cursor(1, 0);
+ lcd_puts(buf);
+
+ printf("%s\r\n", buf);
+ sleep_ms(1000);
+ }
+}
diff --git a/drivers/0x08_lcd1602/CMakeLists.txt b/drivers/0x08_lcd1602/CMakeLists.txt
new file mode 100644
index 0000000..7767c8b
--- /dev/null
+++ b/drivers/0x08_lcd1602/CMakeLists.txt
@@ -0,0 +1,57 @@
+# Generated Cmake Pico project file
+
+cmake_minimum_required(VERSION 3.13)
+
+set(CMAKE_C_STANDARD 11)
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+
+# Initialise pico_sdk from installed location
+# (note this can come from environment, CMake cache etc)
+
+# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work ==
+if(WIN32)
+ set(USERHOME $ENV{USERPROFILE})
+else()
+ set(USERHOME $ENV{HOME})
+endif()
+set(sdkVersion 2.2.0)
+set(toolchainVersion 14_2_Rel1)
+set(picotoolVersion 2.2.0)
+set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake)
+if (EXISTS ${picoVscode})
+ include(${picoVscode})
+endif()
+# ====================================================================================
+set(PICO_BOARD pico2 CACHE STRING "Board type")
+
+# Pull in Raspberry Pi Pico SDK (must be before project)
+include(pico_sdk_import.cmake)
+
+project(0x08_lcd1602 C CXX ASM)
+
+# Initialise the Raspberry Pi Pico SDK
+pico_sdk_init()
+
+# Add executable. Default name is the project name, version 0.1
+
+add_executable(0x08_lcd1602 0x08_lcd1602.c lcd1602.c)
+
+pico_set_program_name(0x08_lcd1602 "0x08_lcd1602")
+pico_set_program_version(0x08_lcd1602 "0.1")
+
+# Modify the below lines to enable/disable output over UART/USB
+pico_enable_stdio_uart(0x08_lcd1602 1)
+pico_enable_stdio_usb(0x08_lcd1602 0)
+
+# Add the standard library to the build
+target_link_libraries(0x08_lcd1602
+ pico_stdlib
+ hardware_i2c)
+
+# Add the standard include files to the build
+target_include_directories(0x08_lcd1602 PRIVATE
+ ${CMAKE_CURRENT_LIST_DIR}
+)
+
+pico_add_extra_outputs(0x08_lcd1602)
diff --git a/drivers/0x08_lcd1602/lcd1602.c b/drivers/0x08_lcd1602/lcd1602.c
new file mode 100644
index 0000000..01bca34
--- /dev/null
+++ b/drivers/0x08_lcd1602/lcd1602.c
@@ -0,0 +1,155 @@
+/**
+ * @file lcd1602.c
+ * @brief Implementation of PCF8574-backed HD44780 (16x2) LCD driver
+ * @author Kevin Thomas
+ * @date 2025
+ *
+ * MIT License
+ *
+ * Copyright (c) 2025 Kevin Thomas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "lcd1602.h"
+#include
+#include
+#include "pico/stdlib.h"
+#include "hardware/i2c.h"
+
+static i2c_inst_t *get_i2c_inst(uint8_t port) {
+ return port == 0 ? i2c0 : i2c1;
+}
+
+static i2c_inst_t *lcd_i2c = NULL;
+static uint8_t lcd_addr = 0x27;
+static int lcd_nibble_shift = 4;
+static uint8_t lcd_backlight_mask = 0x08;
+
+/* PCF8574 -> LCD control pins */
+#define PIN_RS 0x01
+#define PIN_RW 0x02
+#define PIN_EN 0x04
+
+/**
+ * @brief Write one raw byte to the PCF8574 expander over I2C
+ *
+ * @param data Output byte to send to the expander
+ */
+static void pcf_write_byte(uint8_t data) {
+ if (!lcd_i2c) return;
+ i2c_write_blocking(lcd_i2c, lcd_addr, &data, 1, false);
+}
+
+/**
+ * @brief Toggle EN to latch a nibble into the LCD controller
+ *
+ * @param data Current control/data bus byte (with RS and backlight already set)
+ */
+static void pcf_pulse_enable(uint8_t data) {
+ pcf_write_byte(data | PIN_EN);
+ sleep_us(1);
+ pcf_write_byte(data & ~PIN_EN);
+ sleep_us(50);
+}
+
+/**
+ * @brief Write one 4-bit nibble to the LCD
+ *
+ * @param nibble Lower 4 bits to write
+ * @param mode 0 for command, non-zero for character data
+ */
+static void lcd_write4(uint8_t nibble, uint8_t mode) {
+ uint8_t data = (nibble & 0x0F) << lcd_nibble_shift;
+ data |= mode ? PIN_RS : 0;
+ data |= lcd_backlight_mask;
+ pcf_pulse_enable(data);
+}
+
+/**
+ * @brief Send one full 8-bit command/data value as two nibbles
+ *
+ * @param value Byte to send to the LCD
+ * @param mode 0 for command, non-zero for character data
+ */
+static void lcd_send(uint8_t value, uint8_t mode) {
+ lcd_write4((value >> 4) & 0x0F, mode);
+ lcd_write4(value & 0x0F, mode);
+}
+
+void lcd_init(uint8_t i2c_port, uint32_t sda_pin, uint32_t scl_pin,
+ uint32_t baud_hz, uint8_t pcf_addr, int nibble_shift,
+ uint8_t backlight_mask) {
+ i2c_inst_t *i2c = get_i2c_inst(i2c_port);
+ i2c_init(i2c, baud_hz);
+ gpio_set_function(sda_pin, GPIO_FUNC_I2C);
+ gpio_set_function(scl_pin, GPIO_FUNC_I2C);
+ gpio_pull_up(sda_pin);
+ gpio_pull_up(scl_pin);
+
+ lcd_i2c_init(i2c_port, pcf_addr, nibble_shift, backlight_mask);
+}
+
+void lcd_i2c_init(uint8_t i2c_port, uint8_t pcf_addr, int nibble_shift, uint8_t backlight_mask) {
+ lcd_i2c = get_i2c_inst(i2c_port);
+ lcd_addr = pcf_addr;
+ lcd_nibble_shift = nibble_shift;
+ lcd_backlight_mask = backlight_mask;
+
+ // Follow init sequence for HD44780 in 4-bit mode
+ lcd_write4(0x03, 0);
+ sleep_ms(5);
+ lcd_write4(0x03, 0);
+ sleep_us(150);
+ lcd_write4(0x03, 0);
+ sleep_us(150);
+ lcd_write4(0x02, 0);
+ sleep_us(150);
+
+ // Function set: 4-bit, 2 lines
+ lcd_send(0x28, 0);
+
+ // Display on, cursor off
+ lcd_send(0x0C, 0);
+
+ // Clear
+ lcd_send(0x01, 0);
+ sleep_ms(2);
+
+ // Entry mode set
+ lcd_send(0x06, 0);
+}
+
+void lcd_clear(void) {
+ lcd_send(0x01, 0);
+ sleep_ms(2);
+}
+
+void lcd_set_cursor(int line, int position) {
+ const uint8_t row_offsets[] = {0x00, 0x40};
+
+ if (line > 1) line = 1;
+ lcd_send(0x80 | (position + row_offsets[line]), 0);
+}
+
+void lcd_puts(const char *s) {
+ while (*s) {
+ lcd_send((uint8_t)*s++, 1);
+ }
+}
diff --git a/drivers/0x08_lcd1602/lcd1602.h b/drivers/0x08_lcd1602/lcd1602.h
new file mode 100644
index 0000000..d99ad8f
--- /dev/null
+++ b/drivers/0x08_lcd1602/lcd1602.h
@@ -0,0 +1,93 @@
+/**
+ * @file lcd1602.h
+ * @brief Header for PCF8574-backed HD44780 (16x2) LCD driver
+ * @author Kevin Thomas
+ * @date 2025
+ *
+ * MIT License
+ *
+ * Copyright (c) 2025 Kevin Thomas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef LCD1602_H
+#define LCD1602_H
+
+#include
+#include
+
+/**
+ * @brief Initialize I2C bus and the LCD driver in one call
+ *
+ * Configures the I2C peripheral at the given baud rate, assigns GPIO
+ * alternate functions for SDA and SCL with internal pull-ups, and then
+ * performs the full HD44780 4-bit initialization sequence through the
+ * PCF8574 backpack. This is a convenience wrapper around lcd_i2c_init().
+ *
+ * @param i2c_port I2C port number (0 for i2c0, 1 for i2c1)
+ * @param sda_pin GPIO pin number for SDA
+ * @param scl_pin GPIO pin number for SCL
+ * @param baud_hz I2C clock rate in Hz (e.g. 100000)
+ * @param pcf_addr PCF8574 I2C address (commonly 0x27 or 0x3F)
+ * @param nibble_shift Bit shift applied to 4-bit nibbles (commonly 4 or 0)
+ * @param backlight_mask PCF8574 bit mask that controls the backlight
+ */
+void lcd_init(uint8_t i2c_port, uint32_t sda_pin, uint32_t scl_pin,
+ uint32_t baud_hz, uint8_t pcf_addr, int nibble_shift,
+ uint8_t backlight_mask);
+
+/**
+ * @brief Initialize the LCD driver over I2C
+ *
+ * Configures the internal driver state and performs the HD44780 initialization
+ * sequence. The driver does not configure I2C pins or call i2c_init; that
+ * must be done by the caller prior to calling this function.
+ *
+ * @param i2c_port I2C port number (0 for i2c0, 1 for i2c1)
+ * @param pcf_addr PCF8574 I2C address (commonly 0x27 or 0x3F)
+ * @param nibble_shift Bit shift applied to 4-bit nibbles (commonly 4 or 0)
+ * @param backlight_mask PCF8574 bit mask that controls the backlight
+ */
+void lcd_i2c_init(uint8_t i2c_port, uint8_t pcf_addr, int nibble_shift, uint8_t backlight_mask);
+
+/**
+ * @brief Clear the LCD display
+ *
+ * Clears the display and returns the cursor to the home position. This
+ * call blocks for the duration required by the HD44780 controller.
+ */
+void lcd_clear(void);
+
+/**
+ * @brief Set the cursor position
+ *
+ * @param line Line number (0 or 1)
+ * @param position Column (0..15)
+ */
+void lcd_set_cursor(int line, int position);
+
+/**
+ * @brief Write a null-terminated string to the display
+ *
+ * @param s The string to write (ASCII)
+ */
+void lcd_puts(const char *s);
+
+#endif // LCD1602_H
diff --git a/drivers/0x08_lcd1602/pico_sdk_import.cmake b/drivers/0x08_lcd1602/pico_sdk_import.cmake
new file mode 100644
index 0000000..d493cc2
--- /dev/null
+++ b/drivers/0x08_lcd1602/pico_sdk_import.cmake
@@ -0,0 +1,121 @@
+# This is a copy of /external/pico_sdk_import.cmake
+
+# This can be dropped into an external project to help locate this SDK
+# It should be include()ed prior to project()
+
+# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd.
+#
+# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+# following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+# disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
+ set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
+ message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
+ set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
+ message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
+ set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
+ message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG))
+ set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG})
+ message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')")
+endif ()
+
+if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG)
+ set(PICO_SDK_FETCH_FROM_GIT_TAG "master")
+ message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG")
+endif()
+
+set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
+set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
+set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
+set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK")
+
+if (NOT PICO_SDK_PATH)
+ if (PICO_SDK_FETCH_FROM_GIT)
+ include(FetchContent)
+ set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
+ if (PICO_SDK_FETCH_FROM_GIT_PATH)
+ get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
+ endif ()
+ FetchContent_Declare(
+ pico_sdk
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+ )
+
+ if (NOT pico_sdk)
+ message("Downloading Raspberry Pi Pico SDK")
+ # GIT_SUBMODULES_RECURSE was added in 3.17
+ if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
+ FetchContent_Populate(
+ pico_sdk
+ QUIET
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+ GIT_SUBMODULES_RECURSE FALSE
+
+ SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
+ BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
+ SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
+ )
+ else ()
+ FetchContent_Populate(
+ pico_sdk
+ QUIET
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+
+ SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
+ BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
+ SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
+ )
+ endif ()
+
+ set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
+ endif ()
+ set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
+ else ()
+ message(FATAL_ERROR
+ "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
+ )
+ endif ()
+endif ()
+
+get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
+if (NOT EXISTS ${PICO_SDK_PATH})
+ message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
+endif ()
+
+set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
+if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
+ message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
+endif ()
+
+set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
+
+include(${PICO_SDK_INIT_CMAKE_FILE})
diff --git a/drivers/0x09_dht11/0x09_dht11.c b/drivers/0x09_dht11/0x09_dht11.c
new file mode 100644
index 0000000..12fbc24
--- /dev/null
+++ b/drivers/0x09_dht11/0x09_dht11.c
@@ -0,0 +1,68 @@
+/**
+ * @file 0x09_dht11.c
+ * @brief DHT11 temperature and humidity sensor driver for the Raspberry Pi Pico 2
+ * @author Kevin Thomas
+ * @date 2025
+ *
+ * MIT License
+ *
+ * Copyright (c) 2025 Kevin Thomas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * This driver demonstrates how to read temperature and humidity from a DHT11
+ * sensor using the dht11.c/dht11.h driver. The sensor is polled every 2
+ * seconds (the minimum safe interval for the DHT11) and the results are
+ * printed over UART. A failed read is reported so wiring issues are visible.
+ *
+ * Wiring:
+ * GPIO4 -> DHT11 DATA pin (10 kohm pull-up to 3.3 V recommended)
+ * 3.3V -> DHT11 VCC
+ * GND -> DHT11 GND
+ */
+
+#include
+#include "pico/stdlib.h"
+#include "dht11.h"
+
+#define DHT11_GPIO 4
+
+int main(void) {
+ stdio_init_all();
+ dht11_init(DHT11_GPIO);
+
+ printf("DHT11 driver initialized on GPIO %d\r\n", DHT11_GPIO);
+
+ while (true) {
+ float humidity = 0.0f;
+ float temperature = 0.0f;
+
+ if (dht11_read(&humidity, &temperature)) {
+ printf("Humidity: %.1f%% Temperature: %.1f C\r\n",
+ humidity, temperature);
+ } else {
+ printf("DHT11 read failed - check wiring on GPIO %d\r\n",
+ DHT11_GPIO);
+ }
+
+ sleep_ms(2000);
+ }
+}
diff --git a/drivers/0x09_dht11/CMakeLists.txt b/drivers/0x09_dht11/CMakeLists.txt
new file mode 100644
index 0000000..e739b77
--- /dev/null
+++ b/drivers/0x09_dht11/CMakeLists.txt
@@ -0,0 +1,56 @@
+# Generated Cmake Pico project file
+
+cmake_minimum_required(VERSION 3.13)
+
+set(CMAKE_C_STANDARD 11)
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+
+# Initialise pico_sdk from installed location
+# (note this can come from environment, CMake cache etc)
+
+# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work ==
+if(WIN32)
+ set(USERHOME $ENV{USERPROFILE})
+else()
+ set(USERHOME $ENV{HOME})
+endif()
+set(sdkVersion 2.2.0)
+set(toolchainVersion 14_2_Rel1)
+set(picotoolVersion 2.2.0)
+set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake)
+if (EXISTS ${picoVscode})
+ include(${picoVscode})
+endif()
+# ====================================================================================
+set(PICO_BOARD pico2 CACHE STRING "Board type")
+
+# Pull in Raspberry Pi Pico SDK (must be before project)
+include(pico_sdk_import.cmake)
+
+project(0x09_dht11 C CXX ASM)
+
+# Initialise the Raspberry Pi Pico SDK
+pico_sdk_init()
+
+# Add executable. Default name is the project name, version 0.1
+
+add_executable(0x09_dht11 0x09_dht11.c dht11.c)
+
+pico_set_program_name(0x09_dht11 "0x09_dht11")
+pico_set_program_version(0x09_dht11 "0.1")
+
+# Modify the below lines to enable/disable output over UART/USB
+pico_enable_stdio_uart(0x09_dht11 1)
+pico_enable_stdio_usb(0x09_dht11 0)
+
+# Add the standard library to the build
+target_link_libraries(0x09_dht11
+ pico_stdlib)
+
+# Add the standard include files to the build
+target_include_directories(0x09_dht11 PRIVATE
+ ${CMAKE_CURRENT_LIST_DIR}
+)
+
+pico_add_extra_outputs(0x09_dht11)
diff --git a/drivers/0x09_dht11/dht11.c b/drivers/0x09_dht11/dht11.c
new file mode 100644
index 0000000..9a52ba8
--- /dev/null
+++ b/drivers/0x09_dht11/dht11.c
@@ -0,0 +1,79 @@
+/**
+ * @file dht11.c
+ * @brief Implementation of DHT11 temperature and humidity sensor driver
+ * @author Kevin Thomas
+ * @date 2025
+ *
+ * MIT License
+ *
+ * Copyright (c) 2025 Kevin Thomas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "dht11.h"
+#include "hardware/gpio.h"
+#include "pico/time.h"
+
+static uint dht_pin;
+
+void dht11_init(uint8_t pin) {
+ dht_pin = pin;
+ gpio_init(pin);
+ gpio_pull_up(pin);
+}
+
+bool dht11_read(float *humidity, float *temperature) {
+ uint8_t data[5] = {0};
+
+ // Start signal
+ gpio_set_dir(dht_pin, GPIO_OUT);
+ gpio_put(dht_pin, 0);
+ sleep_ms(18);
+ gpio_put(dht_pin, 1);
+ sleep_us(40);
+ gpio_set_dir(dht_pin, GPIO_IN);
+
+ // Wait for response
+ uint32_t timeout = 10000;
+ while (gpio_get(dht_pin) == 1) if (--timeout == 0) return false;
+ timeout = 10000;
+ while (gpio_get(dht_pin) == 0) if (--timeout == 0) return false;
+ timeout = 10000;
+ while (gpio_get(dht_pin) == 1) if (--timeout == 0) return false;
+
+ // Read 40 bits
+ for (int i = 0; i < 40; i++) {
+ timeout = 10000;
+ while (gpio_get(dht_pin) == 0) if (--timeout == 0) return false;
+ uint32_t start = time_us_32();
+ timeout = 10000;
+ while (gpio_get(dht_pin) == 1) if (--timeout == 0) return false;
+ uint32_t duration = time_us_32() - start;
+ data[i / 8] <<= 1;
+ if (duration > 40) data[i / 8] |= 1;
+ }
+
+ // Check checksum
+ if (data[4] != ((data[0] + data[1] + data[2] + data[3]) & 0xFF)) return false;
+
+ *humidity = data[0] + data[1] * 0.1f;
+ *temperature = data[2] + data[3] * 0.1f;
+ return true;
+}
diff --git a/drivers/0x09_dht11/dht11.h b/drivers/0x09_dht11/dht11.h
new file mode 100644
index 0000000..30e298c
--- /dev/null
+++ b/drivers/0x09_dht11/dht11.h
@@ -0,0 +1,57 @@
+/**
+ * @file dht11.h
+ * @brief Header for DHT11 temperature and humidity sensor driver
+ * @author Kevin Thomas
+ * @date 2025
+ *
+ * MIT License
+ *
+ * Copyright (c) 2025 Kevin Thomas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef DHT11_H
+#define DHT11_H
+
+#include
+#include
+
+/**
+ * @brief Initialize the DHT11 driver
+ *
+ * Configures the GPIO pin for the DHT11 sensor. This must be called before
+ * using dht11_read().
+ *
+ * @param pin GPIO pin number connected to DHT11 signal
+ */
+void dht11_init(uint8_t pin);
+
+/**
+ * @brief Read temperature and humidity from DHT11 sensor
+ *
+ * Performs the DHT11 communication protocol to read sensor data.
+ *
+ * @param humidity Pointer to store humidity value (0-100%)
+ * @param temperature Pointer to store temperature value in Celsius
+ * @return true if read successful, false on error or timeout
+ */
+bool dht11_read(float *humidity, float *temperature);
+
+#endif // DHT11_H
diff --git a/drivers/0x09_dht11/pico_sdk_import.cmake b/drivers/0x09_dht11/pico_sdk_import.cmake
new file mode 100644
index 0000000..d493cc2
--- /dev/null
+++ b/drivers/0x09_dht11/pico_sdk_import.cmake
@@ -0,0 +1,121 @@
+# This is a copy of /external/pico_sdk_import.cmake
+
+# This can be dropped into an external project to help locate this SDK
+# It should be include()ed prior to project()
+
+# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd.
+#
+# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+# following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+# disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
+ set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
+ message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
+ set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
+ message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
+ set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
+ message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG))
+ set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG})
+ message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')")
+endif ()
+
+if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG)
+ set(PICO_SDK_FETCH_FROM_GIT_TAG "master")
+ message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG")
+endif()
+
+set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
+set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
+set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
+set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK")
+
+if (NOT PICO_SDK_PATH)
+ if (PICO_SDK_FETCH_FROM_GIT)
+ include(FetchContent)
+ set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
+ if (PICO_SDK_FETCH_FROM_GIT_PATH)
+ get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
+ endif ()
+ FetchContent_Declare(
+ pico_sdk
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+ )
+
+ if (NOT pico_sdk)
+ message("Downloading Raspberry Pi Pico SDK")
+ # GIT_SUBMODULES_RECURSE was added in 3.17
+ if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
+ FetchContent_Populate(
+ pico_sdk
+ QUIET
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+ GIT_SUBMODULES_RECURSE FALSE
+
+ SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
+ BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
+ SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
+ )
+ else ()
+ FetchContent_Populate(
+ pico_sdk
+ QUIET
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+
+ SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
+ BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
+ SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
+ )
+ endif ()
+
+ set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
+ endif ()
+ set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
+ else ()
+ message(FATAL_ERROR
+ "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
+ )
+ endif ()
+endif ()
+
+get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
+if (NOT EXISTS ${PICO_SDK_PATH})
+ message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
+endif ()
+
+set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
+if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
+ message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
+endif ()
+
+set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
+
+include(${PICO_SDK_INIT_CMAKE_FILE})
diff --git a/drivers/0x0a_ir/0x0a_ir.c b/drivers/0x0a_ir/0x0a_ir.c
new file mode 100644
index 0000000..36aa2dc
--- /dev/null
+++ b/drivers/0x0a_ir/0x0a_ir.c
@@ -0,0 +1,61 @@
+/**
+ * @file 0x0a_ir.c
+ * @brief NEC IR (infrared) receiver driver for the Raspberry Pi Pico 2
+ * @author Kevin Thomas
+ * @date 2025
+ *
+ * MIT License
+ *
+ * Copyright (c) 2025 Kevin Thomas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * This driver demonstrates NEC infrared remote decoding using the
+ * ir.c/ir.h driver. The IR receiver module output is connected to GPIO 5.
+ * ir_getkey() blocks until a valid NEC frame is received or a timeout
+ * occurs. Each decoded command byte is printed over UART in hex and decimal.
+ *
+ * Wiring:
+ * GPIO5 -> IR receiver module OUT pin (e.g. VS1838B or TSOP4838)
+ * 3.3V -> IR receiver module VCC
+ * GND -> IR receiver module GND
+ */
+
+#include
+#include "pico/stdlib.h"
+#include "ir.h"
+
+#define IR_GPIO 5
+
+int main(void) {
+ stdio_init_all();
+ ir_init(IR_GPIO);
+
+ printf("NEC IR driver initialized on GPIO %d\r\n", IR_GPIO);
+ printf("Press a button on your NEC remote...\r\n");
+
+ while (true) {
+ int command = ir_getkey();
+ if (command >= 0) {
+ printf("NEC command: 0x%02X (%d)\r\n", command, command);
+ }
+ }
+}
diff --git a/drivers/0x0a_ir/CMakeLists.txt b/drivers/0x0a_ir/CMakeLists.txt
new file mode 100644
index 0000000..e28c847
--- /dev/null
+++ b/drivers/0x0a_ir/CMakeLists.txt
@@ -0,0 +1,56 @@
+# Generated Cmake Pico project file
+
+cmake_minimum_required(VERSION 3.13)
+
+set(CMAKE_C_STANDARD 11)
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+
+# Initialise pico_sdk from installed location
+# (note this can come from environment, CMake cache etc)
+
+# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work ==
+if(WIN32)
+ set(USERHOME $ENV{USERPROFILE})
+else()
+ set(USERHOME $ENV{HOME})
+endif()
+set(sdkVersion 2.2.0)
+set(toolchainVersion 14_2_Rel1)
+set(picotoolVersion 2.2.0)
+set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake)
+if (EXISTS ${picoVscode})
+ include(${picoVscode})
+endif()
+# ====================================================================================
+set(PICO_BOARD pico2 CACHE STRING "Board type")
+
+# Pull in Raspberry Pi Pico SDK (must be before project)
+include(pico_sdk_import.cmake)
+
+project(0x0a_ir C CXX ASM)
+
+# Initialise the Raspberry Pi Pico SDK
+pico_sdk_init()
+
+# Add executable. Default name is the project name, version 0.1
+
+add_executable(0x0a_ir 0x0a_ir.c ir.c)
+
+pico_set_program_name(0x0a_ir "0x0a_ir")
+pico_set_program_version(0x0a_ir "0.1")
+
+# Modify the below lines to enable/disable output over UART/USB
+pico_enable_stdio_uart(0x0a_ir 1)
+pico_enable_stdio_usb(0x0a_ir 0)
+
+# Add the standard library to the build
+target_link_libraries(0x0a_ir
+ pico_stdlib)
+
+# Add the standard include files to the build
+target_include_directories(0x0a_ir PRIVATE
+ ${CMAKE_CURRENT_LIST_DIR}
+)
+
+pico_add_extra_outputs(0x0a_ir)
diff --git a/drivers/0x0a_ir/ir.c b/drivers/0x0a_ir/ir.c
new file mode 100644
index 0000000..b89bd4e
--- /dev/null
+++ b/drivers/0x0a_ir/ir.c
@@ -0,0 +1,80 @@
+/**
+ * @file ir.c
+ * @brief Implementation of NEC IR receiver (decoder)
+ * @author Kevin Thomas
+ * @date 2025
+ *
+ * MIT License
+ *
+ * Copyright (c) 2025 Kevin Thomas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "ir.h"
+#include "pico/stdlib.h"
+#include "pico/time.h"
+#include "hardware/gpio.h"
+
+static unsigned int ir_pin = 0;
+
+// Wait for 'gpio' to reach 'level' or timeout (microseconds). Return elapsed us or -1.
+static int64_t wait_for_level(unsigned int gpio, bool level, uint32_t timeout_us) {
+ absolute_time_t start = get_absolute_time();
+ while (gpio_get(gpio) != level) {
+ if (absolute_time_diff_us(start, get_absolute_time()) > (int64_t)timeout_us) {
+ return -1;
+ }
+ }
+ return absolute_time_diff_us(start, get_absolute_time());
+}
+
+void ir_init(uint8_t pin) {
+ ir_pin = pin;
+ gpio_init(pin);
+ gpio_set_dir(pin, GPIO_IN);
+ gpio_pull_up(pin);
+}
+
+int ir_getkey(void) {
+ // leader low (~9 ms)
+ if (wait_for_level(ir_pin, 0, 150000) < 0) return -1;
+
+ int64_t t = wait_for_level(ir_pin, 1, 12000);
+ if (t < 8000 || t > 10000) return -1;
+
+ t = wait_for_level(ir_pin, 0, 7000);
+ if (t < 3500 || t > 5000) return -1;
+
+ uint8_t data[4] = {0, 0, 0, 0};
+ for (int i = 0; i < 32; ++i) {
+ if (wait_for_level(ir_pin, 1, 1000) < 0) return -1;
+ t = wait_for_level(ir_pin, 0, 2500);
+ if (t < 200) return -1;
+ int byte_idx = i / 8;
+ int bit_idx = i % 8;
+ if (t > 1200) data[byte_idx] |= (1 << bit_idx); // logical '1'
+ }
+
+ // Validate address/data inverted pairs
+ if ((uint8_t)(data[0] + data[1]) == 0xFF && (uint8_t)(data[2] + data[3]) == 0xFF) {
+ return data[2];
+ }
+ return -1;
+}
diff --git a/drivers/0x0a_ir/ir.h b/drivers/0x0a_ir/ir.h
new file mode 100644
index 0000000..fba6521
--- /dev/null
+++ b/drivers/0x0a_ir/ir.h
@@ -0,0 +1,56 @@
+/**
+ * @file ir.h
+ * @brief Header for NEC IR receiver (decoder) API
+ * @author Kevin Thomas
+ * @date 2025
+ *
+ * MIT License
+ *
+ * Copyright (c) 2025 Kevin Thomas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef IR_H
+#define IR_H
+
+#include
+#include
+
+/**
+ * @brief Initialize the IR receiver GPIO
+ *
+ * Configures the given `pin` as an input with pull-up for the NEC IR receiver.
+ * Call this once during board initialization before calling `ir_getkey()`.
+ *
+ * @param pin GPIO pin number connected to IR receiver output
+ */
+void ir_init(uint8_t pin);
+
+/**
+ * @brief Blocking NEC IR decoder
+ *
+ * Blocks while waiting for a complete NEC frame. On success returns the
+ * decoded command byte (0..255). On timeout or protocol error returns -1.
+ *
+ * @return decoded command byte (0..255) or -1 on failure
+ */
+int ir_getkey(void);
+
+#endif // IR_H
diff --git a/drivers/0x0a_ir/pico_sdk_import.cmake b/drivers/0x0a_ir/pico_sdk_import.cmake
new file mode 100644
index 0000000..d493cc2
--- /dev/null
+++ b/drivers/0x0a_ir/pico_sdk_import.cmake
@@ -0,0 +1,121 @@
+# This is a copy of /external/pico_sdk_import.cmake
+
+# This can be dropped into an external project to help locate this SDK
+# It should be include()ed prior to project()
+
+# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd.
+#
+# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+# following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+# disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
+ set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
+ message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
+ set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
+ message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
+ set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
+ message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG))
+ set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG})
+ message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')")
+endif ()
+
+if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG)
+ set(PICO_SDK_FETCH_FROM_GIT_TAG "master")
+ message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG")
+endif()
+
+set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
+set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
+set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
+set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK")
+
+if (NOT PICO_SDK_PATH)
+ if (PICO_SDK_FETCH_FROM_GIT)
+ include(FetchContent)
+ set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
+ if (PICO_SDK_FETCH_FROM_GIT_PATH)
+ get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
+ endif ()
+ FetchContent_Declare(
+ pico_sdk
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+ )
+
+ if (NOT pico_sdk)
+ message("Downloading Raspberry Pi Pico SDK")
+ # GIT_SUBMODULES_RECURSE was added in 3.17
+ if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
+ FetchContent_Populate(
+ pico_sdk
+ QUIET
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+ GIT_SUBMODULES_RECURSE FALSE
+
+ SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
+ BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
+ SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
+ )
+ else ()
+ FetchContent_Populate(
+ pico_sdk
+ QUIET
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+
+ SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
+ BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
+ SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
+ )
+ endif ()
+
+ set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
+ endif ()
+ set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
+ else ()
+ message(FATAL_ERROR
+ "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
+ )
+ endif ()
+endif ()
+
+get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
+if (NOT EXISTS ${PICO_SDK_PATH})
+ message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
+endif ()
+
+set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
+if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
+ message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
+endif ()
+
+set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
+
+include(${PICO_SDK_INIT_CMAKE_FILE})
diff --git a/drivers/0x0b_spi/0x0b_spi.c b/drivers/0x0b_spi/0x0b_spi.c
new file mode 100644
index 0000000..0b71d19
--- /dev/null
+++ b/drivers/0x0b_spi/0x0b_spi.c
@@ -0,0 +1,72 @@
+/**
+ * @file 0x0b_spi.c
+ * @brief SPI loopback demo using spi.c/spi.h driver
+ * @author Kevin Thomas
+ * @date 2025
+ *
+ * MIT License
+ *
+ * Copyright (c) 2025 Kevin Thomas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Demonstrates SPI in master mode using the spi driver (spi.h / spi.c).
+ * A loopback wiring (MOSI -> MISO) is used to verify full-duplex transfer.
+ * Transmitted and received data is printed over UART every second.
+ *
+ * Wiring (loopback test):
+ * GPIO19 (MOSI) -> GPIO16 (MISO)
+ * GPIO18 (SCK) -> logic analyzer or slave SCK
+ * GPIO17 (CS) -> slave CS (active-low)
+ */
+
+#include
+#include
+#include "pico/stdlib.h"
+#include "spi.h"
+
+#define SPI_PORT 0
+#define SPI_BAUD_HZ (1000 * 1000)
+#define PIN_MISO 16
+#define PIN_CS 17
+#define PIN_SCK 18
+#define PIN_MOSI 19
+
+int main(void) {
+ stdio_init_all();
+
+ spi_driver_init(SPI_PORT, PIN_MOSI, PIN_MISO, PIN_SCK, PIN_CS, SPI_BAUD_HZ);
+
+ const uint8_t tx_buf[] = "SPI loopback OK";
+ uint8_t rx_buf[sizeof(tx_buf)] = {0};
+
+ while (true) {
+ spi_driver_cs_select(PIN_CS);
+ spi_driver_transfer(SPI_PORT, tx_buf, rx_buf, sizeof(tx_buf));
+ spi_driver_cs_deselect(PIN_CS);
+
+ printf("TX: %s\r\n", tx_buf);
+ printf("RX: %s\r\n\r\n", rx_buf);
+
+ memset(rx_buf, 0, sizeof(rx_buf));
+ sleep_ms(1000);
+ }
+}
diff --git a/drivers/0x0b_spi/CMakeLists.txt b/drivers/0x0b_spi/CMakeLists.txt
new file mode 100644
index 0000000..8c19e5c
--- /dev/null
+++ b/drivers/0x0b_spi/CMakeLists.txt
@@ -0,0 +1,57 @@
+# Generated Cmake Pico project file
+
+cmake_minimum_required(VERSION 3.13)
+
+set(CMAKE_C_STANDARD 11)
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+
+# Initialise pico_sdk from installed location
+# (note this can come from environment, CMake cache etc)
+
+# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work ==
+if(WIN32)
+ set(USERHOME $ENV{USERPROFILE})
+else()
+ set(USERHOME $ENV{HOME})
+endif()
+set(sdkVersion 2.2.0)
+set(toolchainVersion 14_2_Rel1)
+set(picotoolVersion 2.2.0)
+set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake)
+if (EXISTS ${picoVscode})
+ include(${picoVscode})
+endif()
+# ====================================================================================
+set(PICO_BOARD pico2 CACHE STRING "Board type")
+
+# Pull in Raspberry Pi Pico SDK (must be before project)
+include(pico_sdk_import.cmake)
+
+project(0x0b_spi C CXX ASM)
+
+# Initialise the Raspberry Pi Pico SDK
+pico_sdk_init()
+
+# Add executable. Default name is the project name, version 0.1
+
+add_executable(0x0b_spi 0x0b_spi.c spi.c)
+
+pico_set_program_name(0x0b_spi "0x0b_spi")
+pico_set_program_version(0x0b_spi "0.1")
+
+# Modify the below lines to enable/disable output over UART/USB
+pico_enable_stdio_uart(0x0b_spi 1)
+pico_enable_stdio_usb(0x0b_spi 0)
+
+# Add the standard library to the build
+target_link_libraries(0x0b_spi
+ pico_stdlib
+ hardware_spi)
+
+# Add the standard include files to the build
+target_include_directories(0x0b_spi PRIVATE
+ ${CMAKE_CURRENT_LIST_DIR}
+)
+
+pico_add_extra_outputs(0x0b_spi)
diff --git a/drivers/0x0b_spi/pico_sdk_import.cmake b/drivers/0x0b_spi/pico_sdk_import.cmake
new file mode 100644
index 0000000..d493cc2
--- /dev/null
+++ b/drivers/0x0b_spi/pico_sdk_import.cmake
@@ -0,0 +1,121 @@
+# This is a copy of /external/pico_sdk_import.cmake
+
+# This can be dropped into an external project to help locate this SDK
+# It should be include()ed prior to project()
+
+# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd.
+#
+# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+# following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+# disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
+ set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
+ message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
+ set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
+ message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
+ set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
+ message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG))
+ set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG})
+ message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')")
+endif ()
+
+if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG)
+ set(PICO_SDK_FETCH_FROM_GIT_TAG "master")
+ message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG")
+endif()
+
+set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
+set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
+set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
+set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK")
+
+if (NOT PICO_SDK_PATH)
+ if (PICO_SDK_FETCH_FROM_GIT)
+ include(FetchContent)
+ set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
+ if (PICO_SDK_FETCH_FROM_GIT_PATH)
+ get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
+ endif ()
+ FetchContent_Declare(
+ pico_sdk
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+ )
+
+ if (NOT pico_sdk)
+ message("Downloading Raspberry Pi Pico SDK")
+ # GIT_SUBMODULES_RECURSE was added in 3.17
+ if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
+ FetchContent_Populate(
+ pico_sdk
+ QUIET
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+ GIT_SUBMODULES_RECURSE FALSE
+
+ SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
+ BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
+ SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
+ )
+ else ()
+ FetchContent_Populate(
+ pico_sdk
+ QUIET
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+
+ SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
+ BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
+ SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
+ )
+ endif ()
+
+ set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
+ endif ()
+ set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
+ else ()
+ message(FATAL_ERROR
+ "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
+ )
+ endif ()
+endif ()
+
+get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
+if (NOT EXISTS ${PICO_SDK_PATH})
+ message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
+endif ()
+
+set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
+if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
+ message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
+endif ()
+
+set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
+
+include(${PICO_SDK_INIT_CMAKE_FILE})
diff --git a/drivers/0x0b_spi/spi.c b/drivers/0x0b_spi/spi.c
new file mode 100644
index 0000000..baba27f
--- /dev/null
+++ b/drivers/0x0b_spi/spi.c
@@ -0,0 +1,64 @@
+/**
+ * @file spi.c
+ * @brief Implementation of SPI bus driver (master mode)
+ * @author Kevin Thomas
+ * @date 2025
+ *
+ * MIT License
+ *
+ * Copyright (c) 2025 Kevin Thomas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "spi.h"
+#include "pico/stdlib.h"
+#include "hardware/spi.h"
+
+static spi_inst_t *get_spi_inst(uint8_t port) {
+ return port == 0 ? spi0 : spi1;
+}
+
+void spi_driver_init(uint8_t port, uint32_t mosi, uint32_t miso,
+ uint32_t sck, uint32_t cs, uint32_t baud_hz) {
+ spi_inst_t *spi = get_spi_inst(port);
+ spi_init(spi, baud_hz);
+
+ gpio_set_function(mosi, GPIO_FUNC_SPI);
+ gpio_set_function(miso, GPIO_FUNC_SPI);
+ gpio_set_function(sck, GPIO_FUNC_SPI);
+
+ gpio_init(cs);
+ gpio_set_dir(cs, GPIO_OUT);
+ gpio_put(cs, 1);
+}
+
+void spi_driver_cs_select(uint32_t cs) {
+ gpio_put(cs, 0);
+}
+
+void spi_driver_cs_deselect(uint32_t cs) {
+ gpio_put(cs, 1);
+}
+
+void spi_driver_transfer(uint8_t port, const uint8_t *tx, uint8_t *rx,
+ uint32_t len) {
+ spi_inst_t *spi = get_spi_inst(port);
+ spi_write_read_blocking(spi, tx, rx, (size_t)len);
+}
diff --git a/drivers/0x0b_spi/spi.h b/drivers/0x0b_spi/spi.h
new file mode 100644
index 0000000..2ff3bba
--- /dev/null
+++ b/drivers/0x0b_spi/spi.h
@@ -0,0 +1,82 @@
+/**
+ * @file spi.h
+ * @brief Header for SPI bus driver (full-duplex transfer with CS control)
+ * @author Kevin Thomas
+ * @date 2025
+ *
+ * MIT License
+ *
+ * Copyright (c) 2025 Kevin Thomas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef SPI_H
+#define SPI_H
+
+#include
+#include
+
+/**
+ * @brief Initialize an SPI peripheral in master mode
+ *
+ * Configures the SPI instance at the requested baud rate, assigns GPIO
+ * alternate functions for MOSI, MISO, and SCK, and configures the chip
+ * select pin as a GPIO output deasserted (high) by default.
+ *
+ * @param port SPI port number (0 for spi0, 1 for spi1)
+ * @param mosi GPIO pin number for MOSI (controller TX)
+ * @param miso GPIO pin number for MISO (controller RX)
+ * @param sck GPIO pin number for SCK (clock)
+ * @param cs GPIO pin number for chip select (active-low)
+ * @param baud_hz Desired SPI clock frequency in Hz (e.g. 1000000 for 1 MHz)
+ */
+void spi_driver_init(uint8_t port, uint32_t mosi, uint32_t miso,
+ uint32_t sck, uint32_t cs, uint32_t baud_hz);
+
+/**
+ * @brief Assert the chip-select line (drive CS low)
+ *
+ * @param cs GPIO pin number of the chip-select output
+ */
+void spi_driver_cs_select(uint32_t cs);
+
+/**
+ * @brief Deassert the chip-select line (drive CS high)
+ *
+ * @param cs GPIO pin number of the chip-select output
+ */
+void spi_driver_cs_deselect(uint32_t cs);
+
+/**
+ * @brief Perform a full-duplex SPI transfer
+ *
+ * Sends @p len bytes from @p tx while simultaneously receiving @p len bytes
+ * into @p rx. Both buffers must hold at least @p len bytes. The caller is
+ * responsible for asserting and deasserting CS around this call.
+ *
+ * @param port SPI port number (0 for spi0, 1 for spi1)
+ * @param tx Pointer to the transmit buffer (must be @p len bytes)
+ * @param rx Pointer to the receive buffer (must be @p len bytes)
+ * @param len Number of bytes to transfer
+ */
+void spi_driver_transfer(uint8_t port, const uint8_t *tx, uint8_t *rx,
+ uint32_t len);
+
+#endif // SPI_H
diff --git a/drivers/0x0c_multicore/0x0c_multicore.c b/drivers/0x0c_multicore/0x0c_multicore.c
new file mode 100644
index 0000000..b68c6b2
--- /dev/null
+++ b/drivers/0x0c_multicore/0x0c_multicore.c
@@ -0,0 +1,64 @@
+/**
+ * @file 0x0c_multicore.c
+ * @brief Multicore FIFO messaging demo using multicore.c/multicore.h
+ * @author Kevin Thomas
+ * @date 2025
+ *
+ * MIT License
+ *
+ * Copyright (c) 2025 Kevin Thomas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Demonstrates dual-core operation using the multicore driver
+ * (multicore.h / multicore.c). Core 0 sends an incrementing counter to
+ * core 1 via the FIFO; core 1 returns the value plus one. Both values
+ * are printed over UART every second.
+ *
+ * Wiring:
+ * No external wiring required (dual-core communication is on-chip)
+ */
+
+#include
+#include "pico/stdlib.h"
+#include "multicore.h"
+
+static void core1_main(void) {
+ while (true) {
+ uint32_t value = multicore_driver_pop();
+ multicore_driver_push(value + 1u);
+ }
+}
+
+int main(void) {
+ stdio_init_all();
+ multicore_driver_launch(core1_main);
+
+ uint32_t counter = 0;
+ while (true) {
+ multicore_driver_push(counter);
+ uint32_t response = multicore_driver_pop();
+ printf("core0 sent: %lu, core1 returned: %lu\r\n",
+ (unsigned long)counter, (unsigned long)response);
+ counter++;
+ sleep_ms(1000);
+ }
+}
diff --git a/drivers/0x0c_multicore/CMakeLists.txt b/drivers/0x0c_multicore/CMakeLists.txt
new file mode 100644
index 0000000..75b2ad2
--- /dev/null
+++ b/drivers/0x0c_multicore/CMakeLists.txt
@@ -0,0 +1,57 @@
+# Generated Cmake Pico project file
+
+cmake_minimum_required(VERSION 3.13)
+
+set(CMAKE_C_STANDARD 11)
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+
+# Initialise pico_sdk from installed location
+# (note this can come from environment, CMake cache etc)
+
+# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work ==
+if(WIN32)
+ set(USERHOME $ENV{USERPROFILE})
+else()
+ set(USERHOME $ENV{HOME})
+endif()
+set(sdkVersion 2.2.0)
+set(toolchainVersion 14_2_Rel1)
+set(picotoolVersion 2.2.0)
+set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake)
+if (EXISTS ${picoVscode})
+ include(${picoVscode})
+endif()
+# ====================================================================================
+set(PICO_BOARD pico2 CACHE STRING "Board type")
+
+# Pull in Raspberry Pi Pico SDK (must be before project)
+include(pico_sdk_import.cmake)
+
+project(0x0c_multicore C CXX ASM)
+
+# Initialise the Raspberry Pi Pico SDK
+pico_sdk_init()
+
+# Add executable. Default name is the project name, version 0.1
+
+add_executable(0x0c_multicore 0x0c_multicore.c multicore.c)
+
+pico_set_program_name(0x0c_multicore "0x0c_multicore")
+pico_set_program_version(0x0c_multicore "0.1")
+
+# Modify the below lines to enable/disable output over UART/USB
+pico_enable_stdio_uart(0x0c_multicore 1)
+pico_enable_stdio_usb(0x0c_multicore 0)
+
+# Add the standard library to the build
+target_link_libraries(0x0c_multicore
+ pico_stdlib
+ pico_multicore)
+
+# Add the standard include files to the build
+target_include_directories(0x0c_multicore PRIVATE
+ ${CMAKE_CURRENT_LIST_DIR}
+)
+
+pico_add_extra_outputs(0x0c_multicore)
diff --git a/drivers/0x0c_multicore/multicore.c b/drivers/0x0c_multicore/multicore.c
new file mode 100644
index 0000000..d0a93f9
--- /dev/null
+++ b/drivers/0x0c_multicore/multicore.c
@@ -0,0 +1,43 @@
+/**
+ * @file multicore.c
+ * @brief Implementation of multicore FIFO driver for RP2350
+ * @author Kevin Thomas
+ * @date 2025
+ *
+ * MIT License
+ *
+ * Copyright (c) 2025 Kevin Thomas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "multicore.h"
+#include "pico/multicore.h"
+
+void multicore_driver_launch(void (*core1_entry)(void)) {
+ multicore_launch_core1(core1_entry);
+}
+
+void multicore_driver_push(uint32_t data) {
+ multicore_fifo_push_blocking(data);
+}
+
+uint32_t multicore_driver_pop(void) {
+ return multicore_fifo_pop_blocking();
+}
diff --git a/drivers/0x0c_multicore/multicore.h b/drivers/0x0c_multicore/multicore.h
new file mode 100644
index 0000000..2618038
--- /dev/null
+++ b/drivers/0x0c_multicore/multicore.h
@@ -0,0 +1,66 @@
+/**
+ * @file multicore.h
+ * @brief Header for dual-core (multicore) driver using the FIFO mailbox
+ * @author Kevin Thomas
+ * @date 2025
+ *
+ * MIT License
+ *
+ * Copyright (c) 2025 Kevin Thomas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef MULTICORE_H
+#define MULTICORE_H
+
+#include
+
+/**
+ * @brief Start the provided function on core 1
+ *
+ * Launches the given entry function on the second RP2350 core using the
+ * Pico SDK multicore_launch_core1() API. The function will run
+ * concurrently with core 0 from the moment this call returns.
+ *
+ * @param core1_entry Pointer to the void(void) function to run on core 1
+ */
+void multicore_driver_launch(void (*core1_entry)(void));
+
+/**
+ * @brief Push a 32-bit value into the inter-core FIFO (blocking)
+ *
+ * Writes @p data to the FIFO that core 1 reads from. Blocks until
+ * there is space in the hardware FIFO.
+ *
+ * @param data 32-bit value to send to core 1
+ */
+void multicore_driver_push(uint32_t data);
+
+/**
+ * @brief Pop a 32-bit value from the inter-core FIFO (blocking)
+ *
+ * Reads one entry from the FIFO that core 1 writes into. Blocks until
+ * a value is available.
+ *
+ * @return uint32_t Value received from core 1
+ */
+uint32_t multicore_driver_pop(void);
+
+#endif // MULTICORE_H
diff --git a/drivers/0x0c_multicore/pico_sdk_import.cmake b/drivers/0x0c_multicore/pico_sdk_import.cmake
new file mode 100644
index 0000000..d493cc2
--- /dev/null
+++ b/drivers/0x0c_multicore/pico_sdk_import.cmake
@@ -0,0 +1,121 @@
+# This is a copy of /external/pico_sdk_import.cmake
+
+# This can be dropped into an external project to help locate this SDK
+# It should be include()ed prior to project()
+
+# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd.
+#
+# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+# following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+# disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
+ set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
+ message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
+ set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
+ message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
+ set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
+ message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG))
+ set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG})
+ message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')")
+endif ()
+
+if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG)
+ set(PICO_SDK_FETCH_FROM_GIT_TAG "master")
+ message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG")
+endif()
+
+set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
+set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
+set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
+set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK")
+
+if (NOT PICO_SDK_PATH)
+ if (PICO_SDK_FETCH_FROM_GIT)
+ include(FetchContent)
+ set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
+ if (PICO_SDK_FETCH_FROM_GIT_PATH)
+ get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
+ endif ()
+ FetchContent_Declare(
+ pico_sdk
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+ )
+
+ if (NOT pico_sdk)
+ message("Downloading Raspberry Pi Pico SDK")
+ # GIT_SUBMODULES_RECURSE was added in 3.17
+ if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
+ FetchContent_Populate(
+ pico_sdk
+ QUIET
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+ GIT_SUBMODULES_RECURSE FALSE
+
+ SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
+ BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
+ SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
+ )
+ else ()
+ FetchContent_Populate(
+ pico_sdk
+ QUIET
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+
+ SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
+ BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
+ SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
+ )
+ endif ()
+
+ set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
+ endif ()
+ set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
+ else ()
+ message(FATAL_ERROR
+ "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
+ )
+ endif ()
+endif ()
+
+get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
+if (NOT EXISTS ${PICO_SDK_PATH})
+ message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
+endif ()
+
+set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
+if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
+ message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
+endif ()
+
+set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
+
+include(${PICO_SDK_INIT_CMAKE_FILE})
diff --git a/drivers/0x0d_timer/0x0d_timer.c b/drivers/0x0d_timer/0x0d_timer.c
new file mode 100644
index 0000000..400df78
--- /dev/null
+++ b/drivers/0x0d_timer/0x0d_timer.c
@@ -0,0 +1,56 @@
+/**
+ * @file 0x0d_timer.c
+ * @brief Repeating timer demo using timer.c/timer.h
+ * @author Kevin Thomas
+ * @date 2025
+ *
+ * MIT License
+ *
+ * Copyright (c) 2025 Kevin Thomas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Demonstrates repeating timer callbacks using the timer driver
+ * (timer.h / timer.c). A one-second heartbeat timer prints a message
+ * over UART to confirm the timer is firing.
+ *
+ * Wiring:
+ * No external wiring required
+ */
+
+#include
+#include "pico/stdlib.h"
+#include "timer.h"
+
+static bool heartbeat_callback(void) {
+ printf("Timer heartbeat\r\n");
+ return true;
+}
+
+int main(void) {
+ stdio_init_all();
+
+ timer_driver_start(1000, heartbeat_callback);
+
+ while (true) {
+ tight_loop_contents();
+ }
+}
diff --git a/drivers/0x0d_timer/CMakeLists.txt b/drivers/0x0d_timer/CMakeLists.txt
new file mode 100644
index 0000000..3306ced
--- /dev/null
+++ b/drivers/0x0d_timer/CMakeLists.txt
@@ -0,0 +1,56 @@
+# Generated Cmake Pico project file
+
+cmake_minimum_required(VERSION 3.13)
+
+set(CMAKE_C_STANDARD 11)
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+
+# Initialise pico_sdk from installed location
+# (note this can come from environment, CMake cache etc)
+
+# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work ==
+if(WIN32)
+ set(USERHOME $ENV{USERPROFILE})
+else()
+ set(USERHOME $ENV{HOME})
+endif()
+set(sdkVersion 2.2.0)
+set(toolchainVersion 14_2_Rel1)
+set(picotoolVersion 2.2.0)
+set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake)
+if (EXISTS ${picoVscode})
+ include(${picoVscode})
+endif()
+# ====================================================================================
+set(PICO_BOARD pico2 CACHE STRING "Board type")
+
+# Pull in Raspberry Pi Pico SDK (must be before project)
+include(pico_sdk_import.cmake)
+
+project(0x0d_timer C CXX ASM)
+
+# Initialise the Raspberry Pi Pico SDK
+pico_sdk_init()
+
+# Add executable. Default name is the project name, version 0.1
+
+add_executable(0x0d_timer 0x0d_timer.c timer.c)
+
+pico_set_program_name(0x0d_timer "0x0d_timer")
+pico_set_program_version(0x0d_timer "0.1")
+
+# Modify the below lines to enable/disable output over UART/USB
+pico_enable_stdio_uart(0x0d_timer 1)
+pico_enable_stdio_usb(0x0d_timer 0)
+
+# Add the standard library to the build
+target_link_libraries(0x0d_timer
+ pico_stdlib)
+
+# Add the standard include files to the build
+target_include_directories(0x0d_timer PRIVATE
+ ${CMAKE_CURRENT_LIST_DIR}
+)
+
+pico_add_extra_outputs(0x0d_timer)
diff --git a/drivers/0x0d_timer/pico_sdk_import.cmake b/drivers/0x0d_timer/pico_sdk_import.cmake
new file mode 100644
index 0000000..d493cc2
--- /dev/null
+++ b/drivers/0x0d_timer/pico_sdk_import.cmake
@@ -0,0 +1,121 @@
+# This is a copy of /external/pico_sdk_import.cmake
+
+# This can be dropped into an external project to help locate this SDK
+# It should be include()ed prior to project()
+
+# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd.
+#
+# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+# following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+# disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
+ set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
+ message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
+ set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
+ message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
+ set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
+ message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG))
+ set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG})
+ message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')")
+endif ()
+
+if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG)
+ set(PICO_SDK_FETCH_FROM_GIT_TAG "master")
+ message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG")
+endif()
+
+set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
+set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
+set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
+set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK")
+
+if (NOT PICO_SDK_PATH)
+ if (PICO_SDK_FETCH_FROM_GIT)
+ include(FetchContent)
+ set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
+ if (PICO_SDK_FETCH_FROM_GIT_PATH)
+ get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
+ endif ()
+ FetchContent_Declare(
+ pico_sdk
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+ )
+
+ if (NOT pico_sdk)
+ message("Downloading Raspberry Pi Pico SDK")
+ # GIT_SUBMODULES_RECURSE was added in 3.17
+ if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
+ FetchContent_Populate(
+ pico_sdk
+ QUIET
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+ GIT_SUBMODULES_RECURSE FALSE
+
+ SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
+ BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
+ SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
+ )
+ else ()
+ FetchContent_Populate(
+ pico_sdk
+ QUIET
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+
+ SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
+ BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
+ SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
+ )
+ endif ()
+
+ set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
+ endif ()
+ set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
+ else ()
+ message(FATAL_ERROR
+ "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
+ )
+ endif ()
+endif ()
+
+get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
+if (NOT EXISTS ${PICO_SDK_PATH})
+ message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
+endif ()
+
+set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
+if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
+ message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
+endif ()
+
+set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
+
+include(${PICO_SDK_INIT_CMAKE_FILE})
diff --git a/drivers/0x0d_timer/timer.c b/drivers/0x0d_timer/timer.c
new file mode 100644
index 0000000..f8ce692
--- /dev/null
+++ b/drivers/0x0d_timer/timer.c
@@ -0,0 +1,62 @@
+/**
+ * @file timer.c
+ * @brief Implementation of repeating timer driver
+ * @author Kevin Thomas
+ * @date 2025
+ *
+ * MIT License
+ *
+ * Copyright (c) 2025 Kevin Thomas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "timer.h"
+#include "pico/time.h"
+
+static repeating_timer_t g_timer;
+static bool g_timer_active = false;
+static timer_driver_callback_t g_user_callback = NULL;
+
+static bool timer_shim(repeating_timer_t *rt) {
+ (void)rt;
+ if (g_user_callback) {
+ return g_user_callback();
+ }
+ return false;
+}
+
+void timer_driver_start(int32_t period_ms, timer_driver_callback_t callback) {
+ if (g_timer_active) {
+ cancel_repeating_timer(&g_timer);
+ g_timer_active = false;
+ }
+
+ g_user_callback = callback;
+ g_timer_active = add_repeating_timer_ms(period_ms, timer_shim, NULL, &g_timer);
+}
+
+void timer_driver_cancel(void) {
+ if (!g_timer_active) {
+ return;
+ }
+
+ cancel_repeating_timer(&g_timer);
+ g_timer_active = false;
+}
diff --git a/drivers/0x0d_timer/timer.h b/drivers/0x0d_timer/timer.h
new file mode 100644
index 0000000..2141a62
--- /dev/null
+++ b/drivers/0x0d_timer/timer.h
@@ -0,0 +1,64 @@
+/**
+ * @file timer.h
+ * @brief Header for repeating hardware timer driver
+ * @author Kevin Thomas
+ * @date 2025
+ *
+ * MIT License
+ *
+ * Copyright (c) 2025 Kevin Thomas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef TIMER_H
+#define TIMER_H
+
+#include
+#include
+
+/**
+ * @brief Timer callback function signature
+ *
+ * Must return true to keep the timer repeating, or false to stop.
+ */
+typedef bool (*timer_driver_callback_t)(void);
+
+/**
+ * @brief Start a repeating hardware timer that fires the given callback
+ *
+ * Schedules @p callback to be invoked every @p period_ms milliseconds using
+ * the Pico SDK add_repeating_timer_ms() API. The timer continues firing
+ * until timer_driver_cancel() is called.
+ *
+ * @param period_ms Interval between callbacks in milliseconds (positive value)
+ * @param callback Function to call on each timer expiry; must return true to
+ * continue repeating, false to stop
+ */
+void timer_driver_start(int32_t period_ms, timer_driver_callback_t callback);
+
+/**
+ * @brief Cancel the active repeating timer
+ *
+ * Stops the timer started by timer_driver_start(). Safe to call even if the
+ * timer has already fired and self-cancelled.
+ */
+void timer_driver_cancel(void);
+
+#endif // TIMER_H
diff --git a/drivers/0x0e_watchdog/0x0e_watchdog.c b/drivers/0x0e_watchdog/0x0e_watchdog.c
new file mode 100644
index 0000000..0b9d556
--- /dev/null
+++ b/drivers/0x0e_watchdog/0x0e_watchdog.c
@@ -0,0 +1,61 @@
+/**
+ * @file 0x0e_watchdog.c
+ * @brief Watchdog feed demo using watchdog.c/watchdog.h
+ * @author Kevin Thomas
+ * @date 2025
+ *
+ * MIT License
+ *
+ * Copyright (c) 2025 Kevin Thomas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Demonstrates the hardware watchdog using the watchdog driver
+ * (watchdog.h / watchdog.c). The watchdog is enabled with a 3-second
+ * timeout and fed every second. If the feed loop were removed, the
+ * chip would automatically reboot after 3 seconds.
+ *
+ * Wiring:
+ * No external wiring required
+ */
+
+#include
+#include "pico/stdlib.h"
+#include "watchdog.h"
+
+int main(void) {
+ stdio_init_all();
+
+ if (watchdog_driver_caused_reboot()) {
+ printf("System rebooted by watchdog timeout\r\n");
+ } else {
+ printf("Normal power-on reset\r\n");
+ }
+
+ watchdog_driver_enable(3000);
+ printf("Watchdog enabled (3s timeout). Feeding every 1s...\r\n");
+
+ while (true) {
+ watchdog_driver_feed();
+ printf("Watchdog fed\r\n");
+ sleep_ms(1000);
+ }
+}
diff --git a/drivers/0x0e_watchdog/CMakeLists.txt b/drivers/0x0e_watchdog/CMakeLists.txt
new file mode 100644
index 0000000..a4a5159
--- /dev/null
+++ b/drivers/0x0e_watchdog/CMakeLists.txt
@@ -0,0 +1,57 @@
+# Generated Cmake Pico project file
+
+cmake_minimum_required(VERSION 3.13)
+
+set(CMAKE_C_STANDARD 11)
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+
+# Initialise pico_sdk from installed location
+# (note this can come from environment, CMake cache etc)
+
+# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work ==
+if(WIN32)
+ set(USERHOME $ENV{USERPROFILE})
+else()
+ set(USERHOME $ENV{HOME})
+endif()
+set(sdkVersion 2.2.0)
+set(toolchainVersion 14_2_Rel1)
+set(picotoolVersion 2.2.0)
+set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake)
+if (EXISTS ${picoVscode})
+ include(${picoVscode})
+endif()
+# ====================================================================================
+set(PICO_BOARD pico2 CACHE STRING "Board type")
+
+# Pull in Raspberry Pi Pico SDK (must be before project)
+include(pico_sdk_import.cmake)
+
+project(0x0e_watchdog C CXX ASM)
+
+# Initialise the Raspberry Pi Pico SDK
+pico_sdk_init()
+
+# Add executable. Default name is the project name, version 0.1
+
+add_executable(0x0e_watchdog 0x0e_watchdog.c watchdog.c)
+
+pico_set_program_name(0x0e_watchdog "0x0e_watchdog")
+pico_set_program_version(0x0e_watchdog "0.1")
+
+# Modify the below lines to enable/disable output over UART/USB
+pico_enable_stdio_uart(0x0e_watchdog 1)
+pico_enable_stdio_usb(0x0e_watchdog 0)
+
+# Add the standard library to the build
+target_link_libraries(0x0e_watchdog
+ pico_stdlib
+ hardware_watchdog)
+
+# Add the standard include files to the build
+target_include_directories(0x0e_watchdog PRIVATE
+ ${CMAKE_CURRENT_LIST_DIR}
+)
+
+pico_add_extra_outputs(0x0e_watchdog)
diff --git a/drivers/0x0e_watchdog/pico_sdk_import.cmake b/drivers/0x0e_watchdog/pico_sdk_import.cmake
new file mode 100644
index 0000000..d493cc2
--- /dev/null
+++ b/drivers/0x0e_watchdog/pico_sdk_import.cmake
@@ -0,0 +1,121 @@
+# This is a copy of /external/pico_sdk_import.cmake
+
+# This can be dropped into an external project to help locate this SDK
+# It should be include()ed prior to project()
+
+# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd.
+#
+# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+# following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+# disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
+ set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
+ message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
+ set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
+ message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
+ set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
+ message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG))
+ set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG})
+ message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')")
+endif ()
+
+if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG)
+ set(PICO_SDK_FETCH_FROM_GIT_TAG "master")
+ message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG")
+endif()
+
+set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
+set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
+set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
+set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK")
+
+if (NOT PICO_SDK_PATH)
+ if (PICO_SDK_FETCH_FROM_GIT)
+ include(FetchContent)
+ set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
+ if (PICO_SDK_FETCH_FROM_GIT_PATH)
+ get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
+ endif ()
+ FetchContent_Declare(
+ pico_sdk
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+ )
+
+ if (NOT pico_sdk)
+ message("Downloading Raspberry Pi Pico SDK")
+ # GIT_SUBMODULES_RECURSE was added in 3.17
+ if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
+ FetchContent_Populate(
+ pico_sdk
+ QUIET
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+ GIT_SUBMODULES_RECURSE FALSE
+
+ SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
+ BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
+ SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
+ )
+ else ()
+ FetchContent_Populate(
+ pico_sdk
+ QUIET
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+
+ SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
+ BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
+ SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
+ )
+ endif ()
+
+ set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
+ endif ()
+ set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
+ else ()
+ message(FATAL_ERROR
+ "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
+ )
+ endif ()
+endif ()
+
+get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
+if (NOT EXISTS ${PICO_SDK_PATH})
+ message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
+endif ()
+
+set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
+if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
+ message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
+endif ()
+
+set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
+
+include(${PICO_SDK_INIT_CMAKE_FILE})
diff --git a/drivers/0x0e_watchdog/watchdog.c b/drivers/0x0e_watchdog/watchdog.c
new file mode 100644
index 0000000..fb677de
--- /dev/null
+++ b/drivers/0x0e_watchdog/watchdog.c
@@ -0,0 +1,43 @@
+/**
+ * @file watchdog.c
+ * @brief Implementation of hardware watchdog driver
+ * @author Kevin Thomas
+ * @date 2025
+ *
+ * MIT License
+ *
+ * Copyright (c) 2025 Kevin Thomas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "watchdog.h"
+#include "hardware/watchdog.h"
+
+void watchdog_driver_enable(uint32_t timeout_ms) {
+ watchdog_enable(timeout_ms, true);
+}
+
+void watchdog_driver_feed(void) {
+ watchdog_update();
+}
+
+bool watchdog_driver_caused_reboot(void) {
+ return watchdog_caused_reboot();
+}
diff --git a/drivers/0x0e_watchdog/watchdog.h b/drivers/0x0e_watchdog/watchdog.h
new file mode 100644
index 0000000..3ff42cf
--- /dev/null
+++ b/drivers/0x0e_watchdog/watchdog.h
@@ -0,0 +1,65 @@
+/**
+ * @file watchdog.h
+ * @brief Header for hardware watchdog timer driver
+ * @author Kevin Thomas
+ * @date 2025
+ *
+ * MIT License
+ *
+ * Copyright (c) 2025 Kevin Thomas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef WATCHDOG_H
+#define WATCHDOG_H
+
+#include
+#include
+
+/**
+ * @brief Enable the hardware watchdog with the specified timeout
+ *
+ * Starts the watchdog timer. If watchdog_driver_feed() is not called within
+ * @p timeout_ms milliseconds, the RP2350 will perform a hard reset.
+ * The maximum supported timeout is 8388 ms.
+ *
+ * @param timeout_ms Watchdog timeout in milliseconds (1 - 8388)
+ */
+void watchdog_driver_enable(uint32_t timeout_ms);
+
+/**
+ * @brief Reset ("feed") the watchdog timer to prevent a reboot
+ *
+ * Must be called periodically within the timeout window configured by
+ * watchdog_driver_enable(). Each call restarts the countdown.
+ */
+void watchdog_driver_feed(void);
+
+/**
+ * @brief Check whether the last reset was caused by the watchdog
+ *
+ * Reads the reset reason register to determine if the watchdog timer
+ * expired and forced the most recent reboot.
+ *
+ * @return bool true if the watchdog triggered the last reset, false otherwise
+ */
+bool watchdog_driver_caused_reboot(void);
+
+#endif // WATCHDOG_H
diff --git a/drivers/0x0f_flash/0x0f_flash.c b/drivers/0x0f_flash/0x0f_flash.c
new file mode 100644
index 0000000..2231f57
--- /dev/null
+++ b/drivers/0x0f_flash/0x0f_flash.c
@@ -0,0 +1,65 @@
+/**
+ * @file 0x0f_flash.c
+ * @brief On-chip flash write/read demo using flash.c/flash.h
+ * @author Kevin Thomas
+ * @date 2025
+ *
+ * MIT License
+ *
+ * Copyright (c) 2025 Kevin Thomas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Demonstrates on-chip flash read/write using the flash driver
+ * (flash.h / flash.c). A string is written to the last sector of flash
+ * and then read back to verify. The result is printed over UART.
+ *
+ * Wiring:
+ * No external wiring required
+ */
+
+#include
+#include
+#include "pico/stdlib.h"
+#include "flash.h"
+
+#define FLASH_TARGET_OFFSET (FLASH_DRIVER_SIZE_BYTES - FLASH_DRIVER_SECTOR_SIZE)
+#define FLASH_WRITE_LEN FLASH_DRIVER_PAGE_SIZE
+
+int main(void) {
+ stdio_init_all();
+
+ static uint8_t write_buf[FLASH_WRITE_LEN];
+ static uint8_t read_buf[FLASH_WRITE_LEN];
+
+ memset(write_buf, 0xFF, sizeof(write_buf));
+ const char *msg = "Embedded Hacking flash driver demo";
+ memcpy(write_buf, msg, strlen(msg));
+
+ flash_driver_write(FLASH_TARGET_OFFSET, write_buf, FLASH_WRITE_LEN);
+ flash_driver_read(FLASH_TARGET_OFFSET, read_buf, FLASH_WRITE_LEN);
+
+ printf("Flash readback: %s\r\n", read_buf);
+
+ while (true) {
+ tight_loop_contents();
+ }
+}
diff --git a/drivers/0x0f_flash/CMakeLists.txt b/drivers/0x0f_flash/CMakeLists.txt
new file mode 100644
index 0000000..636b971
--- /dev/null
+++ b/drivers/0x0f_flash/CMakeLists.txt
@@ -0,0 +1,58 @@
+# Generated Cmake Pico project file
+
+cmake_minimum_required(VERSION 3.13)
+
+set(CMAKE_C_STANDARD 11)
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+
+# Initialise pico_sdk from installed location
+# (note this can come from environment, CMake cache etc)
+
+# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work ==
+if(WIN32)
+ set(USERHOME $ENV{USERPROFILE})
+else()
+ set(USERHOME $ENV{HOME})
+endif()
+set(sdkVersion 2.2.0)
+set(toolchainVersion 14_2_Rel1)
+set(picotoolVersion 2.2.0)
+set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake)
+if (EXISTS ${picoVscode})
+ include(${picoVscode})
+endif()
+# ====================================================================================
+set(PICO_BOARD pico2 CACHE STRING "Board type")
+
+# Pull in Raspberry Pi Pico SDK (must be before project)
+include(pico_sdk_import.cmake)
+
+project(0x0f_flash C CXX ASM)
+
+# Initialise the Raspberry Pi Pico SDK
+pico_sdk_init()
+
+# Add executable. Default name is the project name, version 0.1
+
+add_executable(0x0f_flash 0x0f_flash.c flash.c)
+
+pico_set_program_name(0x0f_flash "0x0f_flash")
+pico_set_program_version(0x0f_flash "0.1")
+
+# Modify the below lines to enable/disable output over UART/USB
+pico_enable_stdio_uart(0x0f_flash 1)
+pico_enable_stdio_usb(0x0f_flash 0)
+
+# Add the standard library to the build
+target_link_libraries(0x0f_flash
+ pico_stdlib
+ hardware_flash
+ hardware_sync)
+
+# Add the standard include files to the build
+target_include_directories(0x0f_flash PRIVATE
+ ${CMAKE_CURRENT_LIST_DIR}
+)
+
+pico_add_extra_outputs(0x0f_flash)
diff --git a/drivers/0x0f_flash/flash.c b/drivers/0x0f_flash/flash.c
new file mode 100644
index 0000000..ad010cb
--- /dev/null
+++ b/drivers/0x0f_flash/flash.c
@@ -0,0 +1,46 @@
+/**
+ * @file flash.c
+ * @brief Implementation of on-chip flash read/write driver
+ * @author Kevin Thomas
+ * @date 2025
+ *
+ * MIT License
+ *
+ * Copyright (c) 2025 Kevin Thomas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "flash.h"
+#include
+#include "hardware/flash.h"
+#include "hardware/sync.h"
+#include "pico/stdlib.h"
+
+void flash_driver_write(uint32_t flash_offset, const uint8_t *data, uint32_t len) {
+ uint32_t ints = save_and_disable_interrupts();
+ flash_range_erase(flash_offset, FLASH_SECTOR_SIZE);
+ flash_range_program(flash_offset, data, len);
+ restore_interrupts(ints);
+}
+
+void flash_driver_read(uint32_t flash_offset, uint8_t *out, uint32_t len) {
+ const uint8_t *flash_target_contents = (const uint8_t *)(XIP_BASE + flash_offset);
+ memcpy(out, flash_target_contents, len);
+}
diff --git a/drivers/0x0f_flash/flash.h b/drivers/0x0f_flash/flash.h
new file mode 100644
index 0000000..19241f9
--- /dev/null
+++ b/drivers/0x0f_flash/flash.h
@@ -0,0 +1,68 @@
+/**
+ * @file flash.h
+ * @brief Header for on-chip flash read/write driver (XIP memory-mapped)
+ * @author Kevin Thomas
+ * @date 2025
+ *
+ * MIT License
+ *
+ * Copyright (c) 2025 Kevin Thomas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef FLASH_H
+#define FLASH_H
+
+#include
+#include
+
+#define FLASH_DRIVER_SIZE_BYTES (4 * 1024 * 1024)
+#define FLASH_DRIVER_SECTOR_SIZE 4096
+#define FLASH_DRIVER_PAGE_SIZE 256
+
+/**
+ * @brief Erase one 4096-byte sector and write data to on-chip flash
+ *
+ * The target address must be aligned to a 4096-byte sector boundary.
+ * The function disables interrupts, erases the containing sector,
+ * programs up to @p len bytes from @p data, and re-enables interrupts. The
+ * write length must be a multiple of FLASH_DRIVER_PAGE_SIZE (256 bytes);
+ * pad with 0xFF if necessary.
+ *
+ * @param flash_offset Byte offset from the start of flash (must be sector-aligned)
+ * @param data Pointer to the data buffer to write
+ * @param len Number of bytes to write (multiple of FLASH_DRIVER_PAGE_SIZE)
+ */
+void flash_driver_write(uint32_t flash_offset, const uint8_t *data, uint32_t len);
+
+/**
+ * @brief Read bytes from on-chip flash via the XIP memory map
+ *
+ * Flash is memory-mapped starting at XIP_BASE (0x10000000). This function
+ * copies @p len bytes beginning at @p flash_offset into @p out using the
+ * XIP read path, which is always available without erasing.
+ *
+ * @param flash_offset Byte offset from the start of flash
+ * @param out Pointer to the destination buffer (must be @p len bytes)
+ * @param len Number of bytes to read
+ */
+void flash_driver_read(uint32_t flash_offset, uint8_t *out, uint32_t len);
+
+#endif // FLASH_H
diff --git a/drivers/0x0f_flash/pico_sdk_import.cmake b/drivers/0x0f_flash/pico_sdk_import.cmake
new file mode 100644
index 0000000..d493cc2
--- /dev/null
+++ b/drivers/0x0f_flash/pico_sdk_import.cmake
@@ -0,0 +1,121 @@
+# This is a copy of /external/pico_sdk_import.cmake
+
+# This can be dropped into an external project to help locate this SDK
+# It should be include()ed prior to project()
+
+# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd.
+#
+# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+# following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+# disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
+ set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
+ message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
+ set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
+ message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
+ set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
+ message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
+endif ()
+
+if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG))
+ set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG})
+ message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')")
+endif ()
+
+if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG)
+ set(PICO_SDK_FETCH_FROM_GIT_TAG "master")
+ message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG")
+endif()
+
+set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
+set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
+set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
+set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK")
+
+if (NOT PICO_SDK_PATH)
+ if (PICO_SDK_FETCH_FROM_GIT)
+ include(FetchContent)
+ set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
+ if (PICO_SDK_FETCH_FROM_GIT_PATH)
+ get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
+ endif ()
+ FetchContent_Declare(
+ pico_sdk
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+ )
+
+ if (NOT pico_sdk)
+ message("Downloading Raspberry Pi Pico SDK")
+ # GIT_SUBMODULES_RECURSE was added in 3.17
+ if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
+ FetchContent_Populate(
+ pico_sdk
+ QUIET
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+ GIT_SUBMODULES_RECURSE FALSE
+
+ SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
+ BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
+ SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
+ )
+ else ()
+ FetchContent_Populate(
+ pico_sdk
+ QUIET
+ GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+ GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
+
+ SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
+ BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
+ SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
+ )
+ endif ()
+
+ set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
+ endif ()
+ set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
+ else ()
+ message(FATAL_ERROR
+ "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
+ )
+ endif ()
+endif ()
+
+get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
+if (NOT EXISTS ${PICO_SDK_PATH})
+ message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
+endif ()
+
+set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
+if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
+ message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
+endif ()
+
+set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
+
+include(${PICO_SDK_INIT_CMAKE_FILE})