From ac91630738db783daa6847a7050eba8f5025b7b0 Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Sat, 3 May 2025 20:30:09 -0700 Subject: [PATCH 01/55] wrote basic gpio support. Need to test eventually --- CMakeLists.txt | 40 +++++++- conanfile.py | 3 + .../libhal-arm-mcu/rp_generic/input_pin.hpp | 22 +++++ .../libhal-arm-mcu/rp_generic/output_pin.hpp | 23 +++++ src/rp_generic/gpio.cpp | 92 +++++++++++++++++++ 5 files changed, 175 insertions(+), 5 deletions(-) create mode 100644 include/libhal-arm-mcu/rp_generic/input_pin.hpp create mode 100644 include/libhal-arm-mcu/rp_generic/output_pin.hpp create mode 100644 src/rp_generic/gpio.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d7908d97..c66138ce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,12 +14,17 @@ cmake_minimum_required(VERSION 3.15) -project(libhal-arm-mcu LANGUAGES CXX) +if(DEFINED ENV{PICO_SDK_PATH}) +include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake) +endif() -libhal_test_and_make_library( - LIBRARY_NAME libhal-arm-mcu +project(libhal-arm-mcu LANGUAGES CXX C ASM) - SOURCES +# Should probably refine this to actually not compile +# drivers for all platforms all the time, but for now this +# serves my purposes +if(NOT DEFINED ENV{PICO_SDK_PATH}) +set(source_list src/system_controller.cpp src/dwt_counter.cpp src/interrupt.cpp @@ -85,8 +90,10 @@ libhal_test_and_make_library( # stm32f40 src/stm32f40/output_pin.cpp +) - TEST_SOURCES +set( + test_sources # cortex_m tests/dwt_counter.test.cpp tests/interrupt.test.cpp @@ -116,6 +123,27 @@ libhal_test_and_make_library( # stm32f411 tests/stm32f411/output_pin.test.cpp tests/stm32f411/spi.test.cpp +) + +else() +set(source_list + src/rp_generic/gpio.cpp +) + +pico_sdk_init() + +set(pico_dep hardware_gpio) + +endif() + +libhal_test_and_make_library( + LIBRARY_NAME libhal-arm-mcu + + SOURCES + ${source_list} + + TEST_SOURCES + ${test_sources} PACKAGES libhal @@ -128,4 +156,6 @@ libhal_test_and_make_library( libhal::util nonstd::ring-span-lite nonstd::scope-lite + ${pico_dep} ) + diff --git a/conanfile.py b/conanfile.py index 845cab1d..dd321fc9 100644 --- a/conanfile.py +++ b/conanfile.py @@ -64,6 +64,9 @@ def requirements(self): if self.options.use_picolibc: compiler_version = str(self.settings.compiler.version) self.requires("prebuilt-picolibc/" + compiler_version) + if str(self.options.platform).startswith("rp2"): + self.requires("picosdk/2.1.1") + self.tool_requires("pioasm/2.1.1") def handle_stm32f1_linker_scripts(self): linker_script_name = list(str(self.options.platform)) diff --git a/include/libhal-arm-mcu/rp_generic/input_pin.hpp b/include/libhal-arm-mcu/rp_generic/input_pin.hpp new file mode 100644 index 00000000..4269ab53 --- /dev/null +++ b/include/libhal-arm-mcu/rp_generic/input_pin.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include + +namespace hal::rp::generic { +// Clang-tidy is wrong about the namespace nesting thing because the v1 +// namespace is inline +inline int fakevavr = 0; +inline namespace v1 { +struct input_pin final : public hal::input_pin +{ + input_pin(u8, settings const&); + +private: + void driver_configure(settings const&) override; + bool driver_level() override; + + u8 m_pin{}; +}; +} // namespace v1 + +} // namespace hal::rp::generic diff --git a/include/libhal-arm-mcu/rp_generic/output_pin.hpp b/include/libhal-arm-mcu/rp_generic/output_pin.hpp new file mode 100644 index 00000000..df0d1224 --- /dev/null +++ b/include/libhal-arm-mcu/rp_generic/output_pin.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include + +namespace hal::rp::generic { +// Clang-tidy is wrong about the namespace nesting thing because the v1 +// namespace is inline +inline int fake = 0; +inline namespace v1 { +struct output_pin final : public hal::output_pin +{ + output_pin(u8, settings const&); + +private: + void driver_configure(settings const&) override; + bool driver_level() override; + void driver_level(bool) override; + + u8 m_pin{}; +}; +} // namespace v1 + +} // namespace hal::rp::generic diff --git a/src/rp_generic/gpio.cpp b/src/rp_generic/gpio.cpp new file mode 100644 index 00000000..a7dad52d --- /dev/null +++ b/src/rp_generic/gpio.cpp @@ -0,0 +1,92 @@ +#include "libhal-arm-mcu/rp_generic/input_pin.hpp" +#include "libhal-arm-mcu/rp_generic/output_pin.hpp" +#include "libhal/error.hpp" + +#include +#include + +namespace hal::rp::generic { + +v1::input_pin::input_pin(u8 pin, settings const& settings) + : m_pin(pin) +{ + if (pin >= NUM_BANK0_GPIOS) { + hal::safe_throw(hal::argument_out_of_domain(this)); + } + + gpio_init(pin); + gpio_set_function(pin, gpio_function_t::GPIO_FUNC_SIO); + gpio_set_dir(pin, GPIO_IN); + + driver_configure(settings); +} + +void v1::input_pin::driver_configure(settings const& p_settings) +{ + switch (p_settings.resistor) { + case pin_resistor::pull_down: + gpio_pull_down(m_pin); + break; + case pin_resistor::pull_up: + gpio_pull_up(m_pin); + break; + default: + [[fallthrough]]; + case pin_resistor::none: + gpio_disable_pulls(m_pin); + break; + } +} + +bool v1::input_pin::driver_level() +{ + return gpio_get(m_pin); +} + +v1::output_pin::output_pin(u8 pin, settings const& options) +{ + if (pin >= NUM_BANK0_GPIOS) { + hal::safe_throw(hal::argument_out_of_domain(this)); + } + + gpio_init(pin); + gpio_set_function(pin, gpio_function_t::GPIO_FUNC_SIO); + gpio_set_dir(pin, GPIO_OUT); + + driver_configure(options); +} + +void v1::output_pin::driver_configure(settings const& options) +{ + // RP2* series chips don't seem to have any explicit support for + // open drain mode, so we fail loud rather than silently + if (options.open_drain) { + hal::safe_throw(hal::argument_out_of_domain(this)); + } + + switch (options.resistor) { + case pin_resistor::pull_down: + gpio_pull_down(m_pin); + break; + case pin_resistor::pull_up: + gpio_pull_up(m_pin); + break; + default: + [[fallthrough]]; + case pin_resistor::none: + gpio_disable_pulls(m_pin); + break; + } +} + +void v1::output_pin::driver_level(bool level) +{ + gpio_put(m_pin, level); +} + +bool v1::output_pin::driver_level() +{ + return gpio_get(m_pin); +} + +} // namespace hal::rp::generic From a5edbd4a1d520e15393794861ef2ef8c1c2ec1f5 Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Mon, 12 May 2025 19:53:14 -0700 Subject: [PATCH 02/55] finally got gpio to work! --- CMakeLists.txt | 6 +- conanfile.py | 1 + create.sh | 4 + demos/CMakeLists.txt | 77 +++++++----- demos/conanfile.py | 22 +++- demos/main.cpp | 118 ++++++++++-------- demos/run.sh | 8 ++ .../{rp_generic => rp/generic}/input_pin.hpp | 0 .../{rp_generic => rp/generic}/output_pin.hpp | 4 + iterate.sh | 12 ++ src/rp_generic/gpio.cpp | 14 ++- 11 files changed, 179 insertions(+), 87 deletions(-) create mode 100644 create.sh create mode 100644 demos/run.sh rename include/libhal-arm-mcu/{rp_generic => rp/generic}/input_pin.hpp (100%) rename include/libhal-arm-mcu/{rp_generic => rp/generic}/output_pin.hpp (87%) create mode 100755 iterate.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index c66138ce..08b8ab41 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,6 +15,10 @@ cmake_minimum_required(VERSION 3.15) if(DEFINED ENV{PICO_SDK_PATH}) +set(PICO_CXX_ENABLE_EXCEPTIONS 1) +set(PICO_BOARD adafruit_feather_rp2350) +set(CMAKE_ASM_FLAGS_INIT "-mcpu=cortex-m33 -mfloat-abi=soft") +set(PICO_NO_PICOTOOL 1) include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake) endif() @@ -132,7 +136,7 @@ set(source_list pico_sdk_init() -set(pico_dep hardware_gpio) +set(pico_dep hardware_gpio_headers pico_time_headers) endif() diff --git a/conanfile.py b/conanfile.py index dd321fc9..2ca27cca 100644 --- a/conanfile.py +++ b/conanfile.py @@ -15,6 +15,7 @@ # limitations under the License. from conan import ConanFile +from conan.tools.cmake import CMake import os diff --git a/create.sh b/create.sh new file mode 100644 index 00000000..56a8eb3b --- /dev/null +++ b/create.sh @@ -0,0 +1,4 @@ +#!/usr/bin/sh + +conan remove libhal-arm-mcu +conan create . -pr:h=rp2350 -pr:h=arm-gcc-12.3 --version=latest diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt index 9e594e6d..b6d0b1e8 100644 --- a/demos/CMakeLists.txt +++ b/demos/CMakeLists.txt @@ -14,33 +14,50 @@ cmake_minimum_required(VERSION 3.20) -project(demos LANGUAGES CXX) - -libhal_build_demos( - DEMOS - adc - blinker - can - dac - gpio - i2c - interrupt_pin - uart - pwm - pwm16 - spi - blank - watchdog - # stream_dac - timer - usb_cdc_raw - - PACKAGES - minimp3 - - INCLUDES - . - - LINK_LIBRARIES - minimp3::minimp3 -) +if(DEFINED ENV{PICO_SDK_PATH}) +include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake) +endif() + +set(CMAKE_EXECUTABLE_SUFFIX_ASM .elf) +set(CMAKE_EXECUTABLE_SUFFIX_C .elf) +set(CMAKE_EXECUTABLE_SUFFIX_CXX .elf) + +project(demos LANGUAGES CXX C ASM) + +find_package(libhal-arm-mcu CONFIG REQUIRED) + +pico_sdk_init() + +add_executable(test_package main.cpp) +target_link_libraries(test_package PRIVATE libhal::arm-mcu pico_stdlib) +pico_add_extra_outputs(test_package) +pico_enable_stdio_usb(test_package 1) +pico_enable_stdio_uart(test_package 0) + +# set(ENV{LIBHAL_PLATFORM_LIBRARY} arm-mcu) + +# libhal_build_demos( +# DEMOS +# adc +# blinker +# can +# dac +# gpio +# i2c +# interrupt_pin +# uart +# pwm +# pwm16 +# spi +# blank +# # stream_dac + +# PACKAGES +# minimp3 + +# INCLUDES +# . + +# LINK_LIBRARIES +# minimp3::minimp3 +# ) diff --git a/demos/conanfile.py b/demos/conanfile.py index 0c26a547..ea93cfd6 100644 --- a/demos/conanfile.py +++ b/demos/conanfile.py @@ -15,7 +15,7 @@ # limitations under the License. from conan import ConanFile - +from conan.tools.cmake import CMake class demos(ConanFile): python_requires = "libhal-bootstrap/[>=4.3.0 <5]" @@ -23,5 +23,21 @@ class demos(ConanFile): def requirements(self): self.requires("libhal-util/[^5.4.0]") - self.requires("libhal-arm-mcu/[1.17.1 || latest]") - self.requires("minimp3/cci.20211201") + self.requires("libhal-arm-mcu/[1.9.1 || latest]") + self.tool_requires("picotool/2.1.1") + # self.requires("minimp3/cci.20211201") + + # This is kinda sketch, but needs to be done manually until https://github.com/conan-io/conan/issues/13372 + # gets implemented + def build(self): + cmake = CMake(self) + defs = { + "CMAKE_ASM_FLAGS_INIT": "-mcpu=cortex-m33 -mfloat-abi=soft", + # "PICO_PLATFORM": "rp2350-arm-s", + # For some reason even if I set PICO_FLASH_SIZE with PICO_BOARD=none, + # it still doesn't work. I can't explain why. + "PICO_BOARD": "adafruit_feather_rp2350", + "PICO_CXX_ENABLE_EXCEPTIONS": "1" + } + cmake.configure(variables = defs) + cmake.build() diff --git a/demos/main.cpp b/demos/main.cpp index 34f50423..a142f072 100644 --- a/demos/main.cpp +++ b/demos/main.cpp @@ -12,68 +12,88 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include -#include -#include +// #include +// #include +// #include -#include +// #include +#include "hardware/gpio.h" +#include "libhal-arm-mcu/rp/generic/output_pin.hpp" +#include "pico/stdio.h" +#include "pico/time.h" +#include +#include int main() { - initialize_platform(); - application(); - std::terminate(); -} + namespace rp = hal::rp::generic; + stdio_init_all(); + rp::output_pin led(7, { .resistor = hal::pin_resistor::none }); -// libhal-arm-mcu specific APIs defined to reduce code size -extern "C" -{ - // This gets rid of an issue with libhal-exceptions in Debug mode. - void __assert_func() // NOLINT - { + while (true) { + printf("On!\n"); + led.level(true); + rp::sleep_ms(500); + printf("Off!\n"); + led.level(false); + rp::sleep_ms(500); } } -// Override global new operator -void* operator new(std::size_t) -{ - throw std::bad_alloc(); -} +// resource_list resources{}; -// Override global new[] operator -void* operator new[](std::size_t) -{ - throw std::bad_alloc(); -} +// [[noreturn]] void terminate_handler() noexcept +// { +// if (resources.console) { +// hal::print(*resources.console.value(), "☠️ APPLICATION TERMINATED ☠️\n\n"); +// } -void* operator new(unsigned int, std::align_val_t) -{ - throw std::bad_alloc(); -} +// if (resources.status_led && resources.clock) { +// auto& led = *resources.status_led.value(); +// auto& clock = *resources.clock.value(); -// Override global delete operator -void operator delete(void*) noexcept -{ -} +// while (true) { +// using namespace std::chrono_literals; +// led.level(false); +// hal::delay(clock, 100ms); +// led.level(true); +// hal::delay(clock, 100ms); +// led.level(false); +// hal::delay(clock, 100ms); +// led.level(true); +// hal::delay(clock, 1000ms); +// } +// } -// Override global delete[] operator -void operator delete[](void*) noexcept -{ -} +// // spin here forever +// while (true) { +// continue; +// } +// } -// Optional: Override sized delete operators (C++14 and later) -void operator delete(void*, std::size_t) noexcept -{ -} +// int main() +// { +// hal::set_terminate(terminate_handler); -void operator delete[](void*, std::size_t) noexcept -{ -} +// initialize_platform(resources); -void operator delete[](void*, std::align_val_t) noexcept -{ -} +// try { +// application(resources); +// } catch (std::bad_optional_access const& e) { +// if (resources.console) { +// hal::print(*resources.console.value(), +// "A resource required by the application was not +// available!\n" "Calling terminate!\n"); +// } +// } // Allow any other exceptions to terminate the application -void operator delete(void*, std::align_val_t) noexcept -{ -} +// std::terminate(); +// } + +// extern "C" +// { +// // This gets rid of an issue with libhal-exceptions in Debug mode. +// void __assert_func() // NOLINT +// { +// } +// } diff --git a/demos/run.sh b/demos/run.sh new file mode 100644 index 00000000..674d9efb --- /dev/null +++ b/demos/run.sh @@ -0,0 +1,8 @@ +#!/usr/bin/sh + +source build/rp2350-arm-s/RelWithDebInfo/generators/conanbuild.sh + +picotool load build/rp2350-arm-s/RelWithDebInfo/test_package.uf2 -f +sleep 1 +plink -serial /dev/ttyACM0 -sercfg 115200,8,1,n,X + diff --git a/include/libhal-arm-mcu/rp_generic/input_pin.hpp b/include/libhal-arm-mcu/rp/generic/input_pin.hpp similarity index 100% rename from include/libhal-arm-mcu/rp_generic/input_pin.hpp rename to include/libhal-arm-mcu/rp/generic/input_pin.hpp diff --git a/include/libhal-arm-mcu/rp_generic/output_pin.hpp b/include/libhal-arm-mcu/rp/generic/output_pin.hpp similarity index 87% rename from include/libhal-arm-mcu/rp_generic/output_pin.hpp rename to include/libhal-arm-mcu/rp/generic/output_pin.hpp index df0d1224..538ba08a 100644 --- a/include/libhal-arm-mcu/rp_generic/output_pin.hpp +++ b/include/libhal-arm-mcu/rp/generic/output_pin.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include namespace hal::rp::generic { @@ -18,6 +19,9 @@ struct output_pin final : public hal::output_pin u8 m_pin{}; }; + +// temporary remove later +void sleep_ms(uint32_t); } // namespace v1 } // namespace hal::rp::generic diff --git a/iterate.sh b/iterate.sh new file mode 100755 index 00000000..209d67f7 --- /dev/null +++ b/iterate.sh @@ -0,0 +1,12 @@ +#!/usr/bin/sh + +set -ex + +yes | conan remove libhal-arm-mcu +conan create . -pr:h=rp2350 -pr:h=arm-gcc-12.3 --version=latest +rm -rdf demos/build +conan build demos -pr:h=rp2350 -pr:h=arm-gcc-12.3 --build=missing +source demos/build/rp2350-arm-s/MinSizeRel/generators/conanbuild.sh +picotool load demos/build/rp2350-arm-s/MinSizeRel/test_package.uf2 -f +sleep 1 +plink -serial /dev/ttyACM0 -sercfg 115200,8,1,n,X diff --git a/src/rp_generic/gpio.cpp b/src/rp_generic/gpio.cpp index a7dad52d..b12508f8 100644 --- a/src/rp_generic/gpio.cpp +++ b/src/rp_generic/gpio.cpp @@ -1,13 +1,18 @@ -#include "libhal-arm-mcu/rp_generic/input_pin.hpp" -#include "libhal-arm-mcu/rp_generic/output_pin.hpp" +#include "libhal-arm-mcu/rp/generic/input_pin.hpp" +#include "libhal-arm-mcu/rp/generic/output_pin.hpp" #include "libhal/error.hpp" #include #include +#include namespace hal::rp::generic { +void v1::sleep_ms(uint32_t ms) +{ + ::sleep_ms(ms); +} -v1::input_pin::input_pin(u8 pin, settings const& settings) +v1::input_pin::input_pin(u8 pin, settings const& options) : m_pin(pin) { if (pin >= NUM_BANK0_GPIOS) { @@ -18,7 +23,7 @@ v1::input_pin::input_pin(u8 pin, settings const& settings) gpio_set_function(pin, gpio_function_t::GPIO_FUNC_SIO); gpio_set_dir(pin, GPIO_IN); - driver_configure(settings); + driver_configure(options); } void v1::input_pin::driver_configure(settings const& p_settings) @@ -44,6 +49,7 @@ bool v1::input_pin::driver_level() } v1::output_pin::output_pin(u8 pin, settings const& options) + : m_pin(pin) { if (pin >= NUM_BANK0_GPIOS) { hal::safe_throw(hal::argument_out_of_domain(this)); From 1721765734a085e0afb3b639495a664cb42a0e44 Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Mon, 12 May 2025 19:54:17 -0700 Subject: [PATCH 03/55] probably not actually going to use this, but it's fairly universal and the test_package needs it --- CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 08b8ab41..dd08d829 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -132,6 +132,10 @@ set( else() set(source_list src/rp_generic/gpio.cpp + src/system_controller.cpp + src/dwt_counter.cpp + src/interrupt.cpp + src/systick_timer.cpp ) pico_sdk_init() From f500b23bca7f64ac68553a87d96cb9777f783de7 Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Mon, 12 May 2025 21:09:14 -0700 Subject: [PATCH 04/55] usb serial works! --- CMakeLists.txt | 3 +- create.sh | 2 +- demos/main.cpp | 11 +++-- include/libhal-arm-mcu/rp/generic/serial.hpp | 36 ++++++++++++++++ src/rp_generic/serial.cpp | 43 ++++++++++++++++++++ 5 files changed, 87 insertions(+), 8 deletions(-) create mode 100644 include/libhal-arm-mcu/rp/generic/serial.hpp create mode 100644 src/rp_generic/serial.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index dd08d829..3b9f1cba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -132,6 +132,7 @@ set( else() set(source_list src/rp_generic/gpio.cpp + src/rp_generic/serial.cpp src/system_controller.cpp src/dwt_counter.cpp src/interrupt.cpp @@ -140,7 +141,7 @@ set(source_list pico_sdk_init() -set(pico_dep hardware_gpio_headers pico_time_headers) +set(pico_dep hardware_gpio_headers pico_time_headers pico_stdio_headers) endif() diff --git a/create.sh b/create.sh index 56a8eb3b..d5e36828 100644 --- a/create.sh +++ b/create.sh @@ -1,4 +1,4 @@ #!/usr/bin/sh -conan remove libhal-arm-mcu +yes | conan remove libhal-arm-mcu conan create . -pr:h=rp2350 -pr:h=arm-gcc-12.3 --version=latest diff --git a/demos/main.cpp b/demos/main.cpp index a142f072..c5f40e38 100644 --- a/demos/main.cpp +++ b/demos/main.cpp @@ -17,24 +17,23 @@ // #include // #include -#include "hardware/gpio.h" #include "libhal-arm-mcu/rp/generic/output_pin.hpp" -#include "pico/stdio.h" -#include "pico/time.h" +#include "libhal-arm-mcu/rp/generic/serial.hpp" +#include #include #include int main() { namespace rp = hal::rp::generic; - stdio_init_all(); rp::output_pin led(7, { .resistor = hal::pin_resistor::none }); + rp::stdio_serial console; while (true) { - printf("On!\n"); + hal::print(console, "On!\n"); led.level(true); rp::sleep_ms(500); - printf("Off!\n"); + hal::print(console, "Off!\n"); led.level(false); rp::sleep_ms(500); } diff --git a/include/libhal-arm-mcu/rp/generic/serial.hpp b/include/libhal-arm-mcu/rp/generic/serial.hpp new file mode 100644 index 00000000..bf6479bc --- /dev/null +++ b/include/libhal-arm-mcu/rp/generic/serial.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include + +namespace hal::rp::generic { +inline int fake_serial = 0; +inline namespace v1 { +/* +The RP series chips use a native ROM USB bootloader, +meaning most setups use USB directly, necessitating +using TinyUSB serial instead of a serial-USB converter +This means that DTR has no effect, and also web serial behaves +very weirdly + +The stdio serial actually wraps stdio, and may or may not +buffer or be asynchronous. Do not depend on the asynchronality +of this class as it is not guaranteed. +*/ +struct stdio_serial final : public hal::serial +{ + stdio_serial(); + ~stdio_serial() override = default; + +private: + // This function is a sham that does nothing + void driver_configure(settings const&) override; + write_t driver_write(std::span) override; + // The stdio interface doesn't actually provide us with how many + // bytes are left. It's not too hard to implement an actual buffer + // system, but honestly most people aren't going to use this anyways + read_t driver_read(std::span) override; + void driver_flush() override; +}; + +} // namespace v1 +} // namespace hal::rp::generic diff --git a/src/rp_generic/serial.cpp b/src/rp_generic/serial.cpp new file mode 100644 index 00000000..62edff9a --- /dev/null +++ b/src/rp_generic/serial.cpp @@ -0,0 +1,43 @@ +#include "libhal-arm-mcu/rp/generic/serial.hpp" + +#include +#include + +namespace hal::rp::generic { +v1::stdio_serial::stdio_serial() +{ + stdio_init_all(); +} + +void v1::stdio_serial::driver_configure(settings const&) +{ +} + +void v1::stdio_serial::driver_flush() +{ + stdio_flush(); +} + +serial::write_t v1::stdio_serial::driver_write(std::span in) +{ + int write_length = stdio_put_string(reinterpret_cast(in.data()), + static_cast(in.size_bytes()), + false, + false); + return write_t{ in.subspan(0, write_length) }; +} + +serial::read_t v1::stdio_serial::driver_read(std::span output) +{ + // time in microseconds + auto now = get_absolute_time(); + // 500 microseconds is 0.5 ms, which is probably reasonable + int len = stdio_get_until(reinterpret_cast(output.data()), + static_cast(output.size_bytes()), + now + 500); + return read_t{ .data = output.subspan(0, len), + .available = 0, + .capacity = 1 }; +} + +} // namespace hal::rp::generic From 35ba73c290cc723e1332910195eabe4df80366ee Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Tue, 13 May 2025 08:56:13 -0700 Subject: [PATCH 05/55] implemented interrupt pins --- CMakeLists.txt | 2 +- .../libhal-arm-mcu/rp/generic/input_pin.hpp | 11 +- .../rp/generic/interrupt_pin.hpp | 23 +++ .../libhal-arm-mcu/rp/generic/output_pin.hpp | 11 +- src/rp_generic/gpio.cpp | 179 +++++++++++++++++- 5 files changed, 210 insertions(+), 16 deletions(-) create mode 100644 include/libhal-arm-mcu/rp/generic/interrupt_pin.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 3b9f1cba..ed80f374 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -141,7 +141,7 @@ set(source_list pico_sdk_init() -set(pico_dep hardware_gpio_headers pico_time_headers pico_stdio_headers) +set(pico_dep hardware_gpio_headers pico_time_headers pico_stdio_headers hardware_sync_headers) endif() diff --git a/include/libhal-arm-mcu/rp/generic/input_pin.hpp b/include/libhal-arm-mcu/rp/generic/input_pin.hpp index 4269ab53..065ab0ab 100644 --- a/include/libhal-arm-mcu/rp/generic/input_pin.hpp +++ b/include/libhal-arm-mcu/rp/generic/input_pin.hpp @@ -2,11 +2,8 @@ #include -namespace hal::rp::generic { -// Clang-tidy is wrong about the namespace nesting thing because the v1 -// namespace is inline -inline int fakevavr = 0; -inline namespace v1 { + +namespace hal::rp::generic::inline v1 { struct input_pin final : public hal::input_pin { input_pin(u8, settings const&); @@ -17,6 +14,6 @@ struct input_pin final : public hal::input_pin u8 m_pin{}; }; -} // namespace v1 +} // namespace hal::rp::generic::inline v1 + -} // namespace hal::rp::generic diff --git a/include/libhal-arm-mcu/rp/generic/interrupt_pin.hpp b/include/libhal-arm-mcu/rp/generic/interrupt_pin.hpp new file mode 100644 index 00000000..2bb65e8b --- /dev/null +++ b/include/libhal-arm-mcu/rp/generic/interrupt_pin.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +namespace hal::rp::generic::inline v1 { +/* +Interrupt pin uses hidden globals to implement interrupts because +the interrupt callback function doesn't quite match the hal::handler type. +Because of that, it is unsafe to construct multiple interrupts across cores +or in an interrupt. Not that you'd do that anyways. +*/ +struct interrupt_pin final : public hal::interrupt_pin +{ + interrupt_pin(u8 pin, hal::callback, settings const& = {}); + ~interrupt_pin() override; +private: + void driver_configure(settings const&) override; + void driver_on_trigger(hal::callback) override; + u8 m_pin; +}; +} // namespace hal::rp::generic::inline v1 + diff --git a/include/libhal-arm-mcu/rp/generic/output_pin.hpp b/include/libhal-arm-mcu/rp/generic/output_pin.hpp index 538ba08a..c142752e 100644 --- a/include/libhal-arm-mcu/rp/generic/output_pin.hpp +++ b/include/libhal-arm-mcu/rp/generic/output_pin.hpp @@ -3,11 +3,8 @@ #include #include -namespace hal::rp::generic { -// Clang-tidy is wrong about the namespace nesting thing because the v1 -// namespace is inline -inline int fake = 0; -inline namespace v1 { + +namespace hal::rp::generic::inline v1 { struct output_pin final : public hal::output_pin { output_pin(u8, settings const&); @@ -22,6 +19,6 @@ struct output_pin final : public hal::output_pin // temporary remove later void sleep_ms(uint32_t); -} // namespace v1 +} // namespace hal::rp::generic::inline v1 + -} // namespace hal::rp::generic diff --git a/src/rp_generic/gpio.cpp b/src/rp_generic/gpio.cpp index b12508f8..8a2ed6dc 100644 --- a/src/rp_generic/gpio.cpp +++ b/src/rp_generic/gpio.cpp @@ -1,10 +1,124 @@ #include "libhal-arm-mcu/rp/generic/input_pin.hpp" +#include "libhal-arm-mcu/rp/generic/interrupt_pin.hpp" #include "libhal-arm-mcu/rp/generic/output_pin.hpp" #include "libhal/error.hpp" +#include +#include #include -#include +#include +#include +#include +#include +#include #include +#include +#include + +namespace { +struct interrupt_manager +{ + + struct lock + { + + interrupt_manager* operator->() + { + return m_ptr; + } + + interrupt_manager& operator*() + { + return *m_ptr; + } + + // locks are uncopiable + lock(lock const&) = delete; + // locks are immovable to prevent moved-from states + lock(lock&&) = delete; + + ~lock() + { + restore_interrupts_from_disabled(m_status); + } + + private: + interrupt_manager* m_ptr; + uint32_t m_status; + lock(interrupt_manager* m) + : m_ptr(m) + , m_status(save_and_disable_interrupts()) + { + } + friend struct interrupt_manager; + }; + + struct interrupt + { + hal::callback callback; + hal::interrupt_pin::trigger_edge edge; + }; + + static lock get() + { + if (!global) { + global = new interrupt_manager(); + gpio_set_irq_callback(&irq); + } + return { global }; + } + + void insert(hal::u8 pin, interrupt handler) + { + m_handlers.insert({ pin, std::move(handler) }); + } + + interrupt& at(hal::u8 pin) + { + return m_handlers.at(pin); + } + + void remove(hal::u8 pin) + { + m_handlers.erase(pin); + } + + // I don't control the callback types clang-tidy + static void irq(uint gpio, std::uint32_t event) // NOLINT + { + using enum hal::interrupt_pin::trigger_edge; + auto g = get(); + + auto& handler = g->m_handlers.at(gpio); + + bool should_activate = false; + switch (handler.edge) { + case rising: + should_activate = event & GPIO_IRQ_EDGE_RISE; + break; + case falling: + should_activate = event & GPIO_IRQ_EDGE_FALL; + break; + case both: + should_activate = event & (GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL); + break; + } + if (should_activate) { + bool value = gpio_get(gpio); + handler.callback(value); + } + } + +private: + // I would use std::hive but that doesn't exist yet + std::unordered_map m_handlers; + interrupt_manager() = default; + ~interrupt_manager() = default; + // todo check if this should be thread_local + static inline interrupt_manager* global = nullptr; +}; + +} // namespace namespace hal::rp::generic { void v1::sleep_ms(uint32_t ms) @@ -95,4 +209,67 @@ bool v1::output_pin::driver_level() return gpio_get(m_pin); } +v1::interrupt_pin::interrupt_pin(u8 pin, + hal::callback callback, + settings const& options) + : m_pin(pin) +{ + gpio_init(pin); + gpio_set_dir(pin, false); + gpio_set_function(pin, gpio_function_t::GPIO_FUNC_SIO); + + auto g = interrupt_manager::get(); + g->insert(m_pin, + { .callback = std::move(callback), .edge = options.trigger }); + + // can't use driver_configure because it would cause the lock to be taken + // twice + switch (options.resistor) { + case pin_resistor::none: + gpio_disable_pulls(m_pin); + break; + case pin_resistor::pull_up: + gpio_pull_up(m_pin); + break; + // pulldown seems reasonable, although this should never trigger anyways + default: + [[fallthrough]]; + case pin_resistor::pull_down: + gpio_pull_down(m_pin); + break; + } +} + +v1::interrupt_pin::~interrupt_pin() +{ + auto g = interrupt_manager::get(); + g->remove(m_pin); +} + +void v1::interrupt_pin::driver_configure(settings const& options) +{ + auto g = interrupt_manager::get(); + switch (options.resistor) { + case pin_resistor::none: + gpio_disable_pulls(m_pin); + break; + case pin_resistor::pull_up: + gpio_pull_up(m_pin); + break; + // pulldown seems reasonable, although this should never trigger anyways + default: + [[fallthrough]]; + case pin_resistor::pull_down: + gpio_pull_down(m_pin); + break; + } + g->at(m_pin).edge = options.trigger; +} + +void v1::interrupt_pin::driver_on_trigger(hal::callback callback) +{ + auto g = interrupt_manager::get(); + std::swap(g->at(m_pin).callback, callback); +} + } // namespace hal::rp::generic From 7c544a75f77c0168856a75ac259641ea0fb04e37 Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Tue, 13 May 2025 09:09:56 -0700 Subject: [PATCH 06/55] rename files to be less verbose --- include/libhal-arm-mcu/rp/{generic => }/input_pin.hpp | 0 include/libhal-arm-mcu/rp/{generic => }/interrupt_pin.hpp | 0 include/libhal-arm-mcu/rp/{generic => }/output_pin.hpp | 0 include/libhal-arm-mcu/rp/{generic => }/serial.hpp | 0 src/rp_generic/gpio.cpp | 6 +++--- src/rp_generic/serial.cpp | 2 +- 6 files changed, 4 insertions(+), 4 deletions(-) rename include/libhal-arm-mcu/rp/{generic => }/input_pin.hpp (100%) rename include/libhal-arm-mcu/rp/{generic => }/interrupt_pin.hpp (100%) rename include/libhal-arm-mcu/rp/{generic => }/output_pin.hpp (100%) rename include/libhal-arm-mcu/rp/{generic => }/serial.hpp (100%) diff --git a/include/libhal-arm-mcu/rp/generic/input_pin.hpp b/include/libhal-arm-mcu/rp/input_pin.hpp similarity index 100% rename from include/libhal-arm-mcu/rp/generic/input_pin.hpp rename to include/libhal-arm-mcu/rp/input_pin.hpp diff --git a/include/libhal-arm-mcu/rp/generic/interrupt_pin.hpp b/include/libhal-arm-mcu/rp/interrupt_pin.hpp similarity index 100% rename from include/libhal-arm-mcu/rp/generic/interrupt_pin.hpp rename to include/libhal-arm-mcu/rp/interrupt_pin.hpp diff --git a/include/libhal-arm-mcu/rp/generic/output_pin.hpp b/include/libhal-arm-mcu/rp/output_pin.hpp similarity index 100% rename from include/libhal-arm-mcu/rp/generic/output_pin.hpp rename to include/libhal-arm-mcu/rp/output_pin.hpp diff --git a/include/libhal-arm-mcu/rp/generic/serial.hpp b/include/libhal-arm-mcu/rp/serial.hpp similarity index 100% rename from include/libhal-arm-mcu/rp/generic/serial.hpp rename to include/libhal-arm-mcu/rp/serial.hpp diff --git a/src/rp_generic/gpio.cpp b/src/rp_generic/gpio.cpp index 8a2ed6dc..8f202722 100644 --- a/src/rp_generic/gpio.cpp +++ b/src/rp_generic/gpio.cpp @@ -1,6 +1,6 @@ -#include "libhal-arm-mcu/rp/generic/input_pin.hpp" -#include "libhal-arm-mcu/rp/generic/interrupt_pin.hpp" -#include "libhal-arm-mcu/rp/generic/output_pin.hpp" +#include "libhal-arm-mcu/rp/input_pin.hpp" +#include "libhal-arm-mcu/rp/interrupt_pin.hpp" +#include "libhal-arm-mcu/rp/output_pin.hpp" #include "libhal/error.hpp" #include diff --git a/src/rp_generic/serial.cpp b/src/rp_generic/serial.cpp index 62edff9a..28192ab0 100644 --- a/src/rp_generic/serial.cpp +++ b/src/rp_generic/serial.cpp @@ -1,4 +1,4 @@ -#include "libhal-arm-mcu/rp/generic/serial.hpp" +#include "libhal-arm-mcu/rp/serial.hpp" #include #include From abbb33ef0a55d71beac6bce866fd4369216cac0c Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Tue, 13 May 2025 09:10:54 -0700 Subject: [PATCH 07/55] rename namespace to be less verbose --- include/libhal-arm-mcu/rp/input_pin.hpp | 2 +- include/libhal-arm-mcu/rp/interrupt_pin.hpp | 2 +- include/libhal-arm-mcu/rp/output_pin.hpp | 2 +- include/libhal-arm-mcu/rp/serial.hpp | 8 +++----- src/rp_generic/gpio.cpp | 2 +- src/rp_generic/serial.cpp | 2 +- 6 files changed, 8 insertions(+), 10 deletions(-) diff --git a/include/libhal-arm-mcu/rp/input_pin.hpp b/include/libhal-arm-mcu/rp/input_pin.hpp index 065ab0ab..a3c72fb8 100644 --- a/include/libhal-arm-mcu/rp/input_pin.hpp +++ b/include/libhal-arm-mcu/rp/input_pin.hpp @@ -3,7 +3,7 @@ #include -namespace hal::rp::generic::inline v1 { +namespace hal::rp::inline v1 { struct input_pin final : public hal::input_pin { input_pin(u8, settings const&); diff --git a/include/libhal-arm-mcu/rp/interrupt_pin.hpp b/include/libhal-arm-mcu/rp/interrupt_pin.hpp index 2bb65e8b..ca555a64 100644 --- a/include/libhal-arm-mcu/rp/interrupt_pin.hpp +++ b/include/libhal-arm-mcu/rp/interrupt_pin.hpp @@ -3,7 +3,7 @@ #include #include -namespace hal::rp::generic::inline v1 { +namespace hal::rp::inline v1 { /* Interrupt pin uses hidden globals to implement interrupts because the interrupt callback function doesn't quite match the hal::handler type. diff --git a/include/libhal-arm-mcu/rp/output_pin.hpp b/include/libhal-arm-mcu/rp/output_pin.hpp index c142752e..326e29f4 100644 --- a/include/libhal-arm-mcu/rp/output_pin.hpp +++ b/include/libhal-arm-mcu/rp/output_pin.hpp @@ -4,7 +4,7 @@ #include -namespace hal::rp::generic::inline v1 { +namespace hal::rp::inline v1 { struct output_pin final : public hal::output_pin { output_pin(u8, settings const&); diff --git a/include/libhal-arm-mcu/rp/serial.hpp b/include/libhal-arm-mcu/rp/serial.hpp index bf6479bc..930d4475 100644 --- a/include/libhal-arm-mcu/rp/serial.hpp +++ b/include/libhal-arm-mcu/rp/serial.hpp @@ -2,9 +2,8 @@ #include -namespace hal::rp::generic { -inline int fake_serial = 0; -inline namespace v1 { + +namespace hal::rp::inline v1 { /* The RP series chips use a native ROM USB bootloader, meaning most setups use USB directly, necessitating @@ -32,5 +31,4 @@ struct stdio_serial final : public hal::serial void driver_flush() override; }; -} // namespace v1 -} // namespace hal::rp::generic +} // namespace hal::rp::inline v1 diff --git a/src/rp_generic/gpio.cpp b/src/rp_generic/gpio.cpp index 8f202722..c2989694 100644 --- a/src/rp_generic/gpio.cpp +++ b/src/rp_generic/gpio.cpp @@ -120,7 +120,7 @@ struct interrupt_manager } // namespace -namespace hal::rp::generic { +namespace hal::rp { void v1::sleep_ms(uint32_t ms) { ::sleep_ms(ms); diff --git a/src/rp_generic/serial.cpp b/src/rp_generic/serial.cpp index 28192ab0..f1984ce7 100644 --- a/src/rp_generic/serial.cpp +++ b/src/rp_generic/serial.cpp @@ -3,7 +3,7 @@ #include #include -namespace hal::rp::generic { +namespace hal::rp { v1::stdio_serial::stdio_serial() { stdio_init_all(); From 0d1158f56087a3b99bddb84669e37cbfe719e185 Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Tue, 13 May 2025 09:54:05 -0700 Subject: [PATCH 08/55] renamed files and started on i2c --- CMakeLists.txt | 13 +++-- include/libhal-arm-mcu/rp/i2c.hpp | 80 +++++++++++++++++++++++++++++++ src/{rp_generic => rp}/gpio.cpp | 0 src/rp/i2c.cpp | 1 + src/{rp_generic => rp}/serial.cpp | 0 5 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 include/libhal-arm-mcu/rp/i2c.hpp rename src/{rp_generic => rp}/gpio.cpp (100%) create mode 100644 src/rp/i2c.cpp rename src/{rp_generic => rp}/serial.cpp (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index ed80f374..dfe112fa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -131,8 +131,9 @@ set( else() set(source_list - src/rp_generic/gpio.cpp - src/rp_generic/serial.cpp + src/rp/gpio.cpp + src/rp/serial.cpp + src/rp/i2c.cpp src/system_controller.cpp src/dwt_counter.cpp src/interrupt.cpp @@ -141,7 +142,13 @@ set(source_list pico_sdk_init() -set(pico_dep hardware_gpio_headers pico_time_headers pico_stdio_headers hardware_sync_headers) +set(pico_dep + hardware_gpio_headers + pico_time_headers + pico_stdio_headers + hardware_sync_headers + hardware_i2c_headers + ) endif() diff --git a/include/libhal-arm-mcu/rp/i2c.hpp b/include/libhal-arm-mcu/rp/i2c.hpp new file mode 100644 index 00000000..0ef57f0e --- /dev/null +++ b/include/libhal-arm-mcu/rp/i2c.hpp @@ -0,0 +1,80 @@ +#pragma once + +#include +#include + +namespace hal::rp::inline v1 { + +// might be better to place in another header file if this gets reused +#ifdef PICO_RP2350A +constexpr u8 pin_max = 30; +#else +constexpr u8 pin_max = 48; +#endif + +struct i2c final : public hal::i2c +{ + + enum i2c_channel : u8 + { + i2c0, + i2c1 + }; + + struct i2c_config_data + { + u8 const sda_pin; + u8 const scl_pin; + i2c_channel const chan_pin; + + protected: + // clang lint does not know that this constructor is purposefully obstructed + // to prevent misuse + constexpr i2c_config_data(u8 sda, u8 scl, i2c_channel chan) // NOLINT + : sda_pin(sda) + , scl_pin(scl) + , chan_pin(chan) + { + } + }; + + // yes, it is entirely valid to have sda and scl on pins that aren't next to + // each other because it's all muxed + template + struct i2c_config : public i2c_config_data + { + constexpr i2c_config() + : i2c_config_data(sda, scl, channel) + { + } + static consteval bool check() + { + if constexpr (channel == i2c0) { + static_assert(sda % 4 == 0 && sda < pin_max, + "SDA pin for I2C0 is invalid!"); + static_assert(scl % 4 == 1 && scl < pin_max, + "SCL pin for I2C0 is invalid!"); + } else if constexpr (channel == i2c1) { + static_assert(sda % 4 == 2 && sda < pin_max, + "SDA pin for I2C1 is invalid!"); + static_assert(scl % 4 == 3 && scl < pin_max, + "SCL pin for I2C1 is invalid!"); + } + return true; + } + static_assert(check()); + }; + + i2c(i2c_config_data); + ~i2c() override; + +private: + void driver_configure(settings const&) override; + + void driver_transaction(hal::byte, + std::span, + std::span, + hal::function_ref) override; +}; + +} // namespace hal::rp::inline v1 diff --git a/src/rp_generic/gpio.cpp b/src/rp/gpio.cpp similarity index 100% rename from src/rp_generic/gpio.cpp rename to src/rp/gpio.cpp diff --git a/src/rp/i2c.cpp b/src/rp/i2c.cpp new file mode 100644 index 00000000..5245c228 --- /dev/null +++ b/src/rp/i2c.cpp @@ -0,0 +1 @@ +#include "libhal-arm-mcu/rp/i2c.hpp" \ No newline at end of file diff --git a/src/rp_generic/serial.cpp b/src/rp/serial.cpp similarity index 100% rename from src/rp_generic/serial.cpp rename to src/rp/serial.cpp From 0069dea34fa5047bef23576e5ce5f5ecdf761532 Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Wed, 14 May 2025 08:52:40 -0700 Subject: [PATCH 09/55] i2c has been written. todo actually test later --- include/libhal-arm-mcu/rp/i2c.hpp | 26 +++++++--- src/rp/i2c.cpp | 83 ++++++++++++++++++++++++++++++- 2 files changed, 100 insertions(+), 9 deletions(-) diff --git a/include/libhal-arm-mcu/rp/i2c.hpp b/include/libhal-arm-mcu/rp/i2c.hpp index 0ef57f0e..8f6f9b2d 100644 --- a/include/libhal-arm-mcu/rp/i2c.hpp +++ b/include/libhal-arm-mcu/rp/i2c.hpp @@ -6,12 +6,15 @@ namespace hal::rp::inline v1 { // might be better to place in another header file if this gets reused -#ifdef PICO_RP2350A +#if defined(PICO_RP2040) || defined(PICO_RP2350A) constexpr u8 pin_max = 30; #else constexpr u8 pin_max = 48; #endif +/* +RP2350 supports a baud rate of up to 1 MHz +*/ struct i2c final : public hal::i2c { @@ -25,15 +28,15 @@ struct i2c final : public hal::i2c { u8 const sda_pin; u8 const scl_pin; - i2c_channel const chan_pin; + i2c_channel const chan; protected: // clang lint does not know that this constructor is purposefully obstructed // to prevent misuse - constexpr i2c_config_data(u8 sda, u8 scl, i2c_channel chan) // NOLINT + constexpr i2c_config_data(u8 sda, u8 scl, i2c_channel c) // NOLINT : sda_pin(sda) , scl_pin(scl) - , chan_pin(chan) + , chan(c) { } }; @@ -65,16 +68,23 @@ struct i2c final : public hal::i2c static_assert(check()); }; - i2c(i2c_config_data); + i2c(i2c_config_data, settings const&); ~i2c() override; private: void driver_configure(settings const&) override; - void driver_transaction(hal::byte, - std::span, - std::span, + /* + This function does not correctly use the timeout function, and will + throw it's own timeout exceptions if a transaction takes any longer + than 10 ms. + */ + void driver_transaction(hal::byte addr, + std::span out, + std::span in, hal::function_ref) override; + + i2c_config_data m_config; }; } // namespace hal::rp::inline v1 diff --git a/src/rp/i2c.cpp b/src/rp/i2c.cpp index 5245c228..e55d719c 100644 --- a/src/rp/i2c.cpp +++ b/src/rp/i2c.cpp @@ -1 +1,82 @@ -#include "libhal-arm-mcu/rp/i2c.hpp" \ No newline at end of file +#include "libhal-arm-mcu/rp/i2c.hpp" + +#include +#include +#include +#include + +// pico macros interfere with ours +#undef i2c0 +#undef i2c1 + +namespace { +auto inst(hal::rp::i2c::i2c_channel c, void* instance) +{ + switch (c) { + case hal::rp::i2c::i2c0: + return &i2c0_inst; + case hal::rp::i2c::i2c1: + return &i2c1_inst; + default: + // realistically should never happen + hal::safe_throw(hal::io_error(instance)); + } +} +} // namespace + +namespace hal::rp::inline v1 { +i2c::i2c(i2c_config_data data, settings const& options) + : m_config(data) +{ + gpio_pull_up(data.scl_pin); + gpio_pull_up(data.sda_pin); + gpio_set_function(data.scl_pin, gpio_function_t::GPIO_FUNC_I2C); + gpio_set_function(data.sda_pin, gpio_function_t::GPIO_FUNC_I2C); + i2c_init(inst(data.chan, this), static_cast(options.clock_rate)); +} + +i2c::~i2c() +{ + gpio_deinit(m_config.scl_pin); + gpio_deinit(m_config.sda_pin); + i2c_deinit(inst(m_config.chan, this)); +} + +void i2c::driver_configure(settings const& options) +{ + i2c_set_baudrate(inst(m_config.chan, this), + static_cast(options.clock_rate)); +} + +void i2c::driver_transaction(hal::byte addr, + std::span out, + std::span in, + hal::function_ref timeout) +{ + bool is_read = in.size() != 0; + int write_result = i2c_write_timeout_us(inst(m_config.chan, this), + addr, + out.data(), + out.size_bytes(), + is_read, + 5000); + if (write_result == PICO_ERROR_GENERIC) { + safe_throw(no_such_device(addr, this)); + } + if (write_result == PICO_ERROR_TIMEOUT) { + safe_throw(timed_out(this)); + } + if (is_read) { + int read_result = i2c_read_timeout_us( + inst(m_config.chan, this), addr, in.data(), in.size_bytes(), false, 5000); + if (read_result == PICO_ERROR_GENERIC) { + safe_throw(no_such_device(addr, this)); + } + if (read_result == PICO_ERROR_TIMEOUT) { + safe_throw(timed_out(this)); + } + } + timeout(); +} + +} // namespace hal::rp::inline v1 From 528b049da148b0180a8ba8cf11b2f458efe7a927 Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Sun, 18 May 2025 13:30:12 -0700 Subject: [PATCH 10/55] moved info to it's own file --- include/libhal-arm-mcu/rp/i2c.hpp | 17 +++++------------ include/libhal-arm-mcu/rp/rp.hpp | 13 +++++++++++++ 2 files changed, 18 insertions(+), 12 deletions(-) create mode 100644 include/libhal-arm-mcu/rp/rp.hpp diff --git a/include/libhal-arm-mcu/rp/i2c.hpp b/include/libhal-arm-mcu/rp/i2c.hpp index 8f6f9b2d..f9530835 100644 --- a/include/libhal-arm-mcu/rp/i2c.hpp +++ b/include/libhal-arm-mcu/rp/i2c.hpp @@ -1,17 +1,10 @@ #pragma once +#include "rp.hpp" #include #include namespace hal::rp::inline v1 { - -// might be better to place in another header file if this gets reused -#if defined(PICO_RP2040) || defined(PICO_RP2350A) -constexpr u8 pin_max = 30; -#else -constexpr u8 pin_max = 48; -#endif - /* RP2350 supports a baud rate of up to 1 MHz */ @@ -53,14 +46,14 @@ struct i2c final : public hal::i2c static consteval bool check() { if constexpr (channel == i2c0) { - static_assert(sda % 4 == 0 && sda < pin_max, + static_assert(sda % 4 == 0 && sda < internal::pin_max, "SDA pin for I2C0 is invalid!"); - static_assert(scl % 4 == 1 && scl < pin_max, + static_assert(scl % 4 == 1 && scl < internal::pin_max, "SCL pin for I2C0 is invalid!"); } else if constexpr (channel == i2c1) { - static_assert(sda % 4 == 2 && sda < pin_max, + static_assert(sda % 4 == 2 && sda < internal::pin_max, "SDA pin for I2C1 is invalid!"); - static_assert(scl % 4 == 3 && scl < pin_max, + static_assert(scl % 4 == 3 && scl < internal::pin_max, "SCL pin for I2C1 is invalid!"); } return true; diff --git a/include/libhal-arm-mcu/rp/rp.hpp b/include/libhal-arm-mcu/rp/rp.hpp new file mode 100644 index 00000000..1bf3eef7 --- /dev/null +++ b/include/libhal-arm-mcu/rp/rp.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include + +namespace hal::rp::internal { + +#if defined(PICO_RP2040) || defined(PICO_RP2350A) +constexpr u8 pin_max = 30; +#else +constexpr u8 pin_max = 48; +#endif + +} // namespace hal::rp From 10bd17de7dd3fb2130fa52d7a9cb5e7bcc424ae5 Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Sun, 18 May 2025 14:39:24 -0700 Subject: [PATCH 11/55] added pwm drivers --- CMakeLists.txt | 2 + include/libhal-arm-mcu/rp/pwm.hpp | 123 +++++++++++++++++++++++++++++ include/libhal-arm-mcu/rp/rp.hpp | 16 +++- src/rp/pwm.cpp | 127 ++++++++++++++++++++++++++++++ 4 files changed, 267 insertions(+), 1 deletion(-) create mode 100644 include/libhal-arm-mcu/rp/pwm.hpp create mode 100644 src/rp/pwm.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index dfe112fa..0a28f43f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -134,6 +134,7 @@ set(source_list src/rp/gpio.cpp src/rp/serial.cpp src/rp/i2c.cpp + src/rp/pwm.cpp src/system_controller.cpp src/dwt_counter.cpp src/interrupt.cpp @@ -148,6 +149,7 @@ set(pico_dep pico_stdio_headers hardware_sync_headers hardware_i2c_headers + hardware_pwm_headers ) endif() diff --git a/include/libhal-arm-mcu/rp/pwm.hpp b/include/libhal-arm-mcu/rp/pwm.hpp new file mode 100644 index 00000000..5b372bfd --- /dev/null +++ b/include/libhal-arm-mcu/rp/pwm.hpp @@ -0,0 +1,123 @@ +#pragma once + +#include "rp.hpp" +#include + +namespace hal::rp::inline v1 { + +enum struct pwm_ch : u8 +{ + a, + b +}; + +struct pwm_pin; + +/* +Somewhat unusually, the pico contains 12 pwm +"groups" with 2 input pins each. These "groups" +are called slices, and are actually very independent +of one other. This slightly complicates synchronization, +but also allows for more independence between slices. +*/ +struct pwm_slice final : hal::pwm_group_manager +{ + pwm_slice(u8 slice_num); + + struct configuration + { + u8 pin; + u16 duty_cycle = 0; + bool autostart = true; + }; + pwm_pin get_pin(configuration); + +private: + /* + At frequencies above ~2288 Hz, the wrap counter + begins to lose resolution, reducing the duty cycle resolution + down from its theoretical 16 bits of resolution. + */ + void driver_frequency(u32 p_frequency) override; + u8 m_number; +}; + +/* +The pico has a thing called phase correct mode where +its phase is centered. No idea if it's useful, may add +if somebody needs it. +*/ +struct pwm_pin final : hal::pwm16_channel +{ + // A false disables the timer. + void enable(bool = true); + + friend pwm_slice; + +private: + u32 driver_frequency() override; + + /* + When set to a 0% duty cycle, it disables the timer + completely. + */ + void driver_duty_cycle(u16 p_duty_cycle) override; + + pwm_pin(u8 slice, pwm_slice::configuration); + + u8 m_pin; + u8 m_slice; + bool m_autostart; +}; + +/* This globally starts all of the timers at once, which may be useful for +aligning their phases. Set argument to false to stop all of them at once */ +void enable_all_pwm(bool start = true); + +struct pwm_pin_config_data +{ + u8 const pin; + pwm_ch const channel; + u8 const slice_num; + +protected: + constexpr pwm_pin_config_data(u8 p, pwm_ch c, u8 slice) + : pin(p) + , channel(c) + , slice_num(slice) + { + } +}; + +constexpr pwm_ch get_channel(u8 pin_num) +{ + if (pin_num % 2 == 0) { + return pwm_ch::a; + } else { + return pwm_ch::b; + } +} + +constexpr u8 get_slice_number(u8 pin_num) +{ + // this snippet of code was copied from Pico SDK + if ((pin_num) < 32) { + return ((pin_num) >> 1u) & 7u; + } else { + return 8u + (((pin_num) >> 1u) & 3u); + } +} +/* Do not allocate two pwm's of the same slice. */ +template +struct pwm_pin_config : pwm_pin_config_data +{ + consteval pwm_pin_config() + : pwm_pin_config_data(pin_num, + get_channel(pin_num), + get_slice_number(pin_num)) + { + } + static_assert(pin_num < internal::pin_max, "Pin number is invalid!"); +}; + +} // namespace hal::rp::inline v1 diff --git a/include/libhal-arm-mcu/rp/rp.hpp b/include/libhal-arm-mcu/rp/rp.hpp index 1bf3eef7..979248e3 100644 --- a/include/libhal-arm-mcu/rp/rp.hpp +++ b/include/libhal-arm-mcu/rp/rp.hpp @@ -10,4 +10,18 @@ constexpr u8 pin_max = 30; constexpr u8 pin_max = 48; #endif -} // namespace hal::rp +enum struct processor_type: uint8_t +{ + rp2040, + rp2350 +}; + +#if defined(PICO_RP2040) +constexpr inline type t = type::rp2040; +#elif defined(PICO_RP2350A) || defined(PICO_RP2350B) || defined(PICO_RP2350) +constexpr inline processor_type type = processor_type::rp2350; +#else +#error "Unknown Pico model" +#endif + +} // namespace hal::rp::internal diff --git a/src/rp/pwm.cpp b/src/rp/pwm.cpp new file mode 100644 index 00000000..8abe2ad9 --- /dev/null +++ b/src/rp/pwm.cpp @@ -0,0 +1,127 @@ +#include "libhal-arm-mcu/rp/pwm.hpp" +#include "libhal-arm-mcu/rp/rp.hpp" + +#include +#include +#include +#include +#include + +/* +TODO: Specify SYS_CLK_HZ in a board header so that pico actually knows what +frequency it should run at + +*/ + +namespace hal::rp::inline v1 { + +pwm_slice::pwm_slice(u8 num) + : m_number(num) +{ + if (num >= NUM_PWM_SLICES) { + hal::safe_throw(argument_out_of_domain(this)); + } +} + +void pwm_slice::driver_frequency(u32 f) +{ + // frequency too high + if (f > SYS_CLK_HZ) { + hal::safe_throw(argument_out_of_domain(this)); + } + + auto frequency = static_cast(f); + auto clock = static_cast(SYS_CLK_HZ); + // We try to adjust clock divider to maximize counter resolution + float wrap_val = std::numeric_limits::max(); + float clock_div = clock / frequency / wrap_val; + // cap the clock divider at 256 + if (clock_div > 256) { + clock_div = 256; + wrap_val = clock / frequency / 256; + } + // maintain a minimum clock divider of 1 + if (clock_div < 1.0) { + clock_div = 1; + wrap_val = clock / frequency; + } + + // frequency too low + if (wrap_val > std::numeric_limits::max()) { + hal::safe_throw(argument_out_of_domain(this)); + } + + pwm_set_wrap(m_number, static_cast(wrap_val)); + pwm_set_clkdiv(m_number, clock_div); +} + +pwm_pin pwm_slice::get_pin(configuration c) +{ + return { m_number, c }; +} + +pwm_pin::pwm_pin(u8 number, pwm_slice::configuration c) + : m_pin(c.pin) + , m_slice(number) + , m_autostart(c.autostart) +{ + driver_duty_cycle(c.duty_cycle); +} + +void pwm_pin::driver_duty_cycle(u16 duty) +{ + if (duty == 0) { + pwm_set_chan_level(m_slice, pwm_gpio_to_channel(m_pin), 0); + pwm_set_enabled(m_slice, false); + return; + } + + float percentage = float(duty) / std::numeric_limits::max(); + u16 top = pwm_hw->slice[m_slice].top; + pwm_set_chan_level( + m_slice, pwm_gpio_to_channel(m_pin), u16(float(top) * percentage)); + if (m_autostart) { + pwm_set_enabled(m_slice, true); + } +} + +void enable_all_pwm(bool start) +{ + // Literally no idea if this is required, but let's be cautious and only + // enable slices that exist + uint32_t enable_mask; + if constexpr (rp::internal::type == rp::internal::processor_type::rp2040) { + enable_mask = 0b1111'1111; + } else if constexpr (rp::internal::type == + rp::internal::processor_type::rp2350) { + enable_mask = 0b1111'1111'1111; + } + + if (start) { + pwm_set_mask_enabled(enable_mask); + } else { + pwm_set_mask_enabled(0x0); + } +} + +void pwm_pin::enable(bool enable) +{ + pwm_set_enabled(m_slice, enable); +} + +u32 pwm_pin::driver_frequency() +{ + uint8_t num = + hal::bit_extract<{ .position = PWM_CH0_DIV_INT_LSB, .width = 8 }>( + pwm_hw->slice[m_slice].div); + uint8_t den = + hal::bit_extract<{ .position = PWM_CH0_DIV_FRAC_LSB, .width = 4 }>( + pwm_hw->slice[m_slice].div); + + float divider = float(num) / float(den); + u16 top = pwm_hw->slice[m_slice].top; + + return static_cast(static_cast(top) * divider); +} + +} // namespace hal::rp::inline v1 From 41298202d48167ad65018602e15a812d5b10766a Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Sun, 18 May 2025 16:51:48 -0700 Subject: [PATCH 12/55] wrote the ADC code --- CMakeLists.txt | 2 ++ include/libhal-arm-mcu/rp/adc.hpp | 19 +++++++++++++++++++ src/rp/adc.cpp | 31 +++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+) create mode 100644 include/libhal-arm-mcu/rp/adc.hpp create mode 100644 src/rp/adc.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 0a28f43f..806c1524 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -135,6 +135,7 @@ set(source_list src/rp/serial.cpp src/rp/i2c.cpp src/rp/pwm.cpp + src/rp/adc.cpp src/system_controller.cpp src/dwt_counter.cpp src/interrupt.cpp @@ -150,6 +151,7 @@ set(pico_dep hardware_sync_headers hardware_i2c_headers hardware_pwm_headers + hardware_adc_headers ) endif() diff --git a/include/libhal-arm-mcu/rp/adc.hpp b/include/libhal-arm-mcu/rp/adc.hpp new file mode 100644 index 00000000..db482d13 --- /dev/null +++ b/include/libhal-arm-mcu/rp/adc.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include + +namespace hal::rp::inline v1 { + +// The ADC is 12 bit +struct adc final : public hal::adc16 +{ + adc(u8 gpio); + +private: + /*Because the rp chips only have one ADC that's muxed + across different pins, we just initialize and mux the ADC + every time we want to read. */ + u16 driver_read() override; + u8 m_pin; +}; +} // namespace hal::rp::inline v1 diff --git a/src/rp/adc.cpp b/src/rp/adc.cpp new file mode 100644 index 00000000..9ac8e046 --- /dev/null +++ b/src/rp/adc.cpp @@ -0,0 +1,31 @@ +#include "libhal-arm-mcu/rp/adc.hpp" + +#include "hardware/adc.h" +#include +#include +#include + +namespace hal::rp::inline v1 { + +adc::adc(u8 pin) + : m_pin(pin) +{ + adc_init(); + if (pin < ADC_BASE_PIN || pin >= ADC_BASE_PIN + NUM_ADC_CHANNELS - 1) { + hal::safe_throw(hal::argument_out_of_domain(this)); + } + adc_gpio_init(pin); +} + +u16 adc::driver_read() +{ + adc_select_input(m_pin - ADC_BASE_PIN); + u16 result = adc_read(); + // weirdly enough the sdk doesn't provide a function to read the error bits + if (adc_hw->cs & ADC_CS_ERR_BITS) { + hal::safe_throw(hal::io_error(this)); + } + return result; +} + +} // namespace hal::rp::inline v1 From d131ed10b69e3d408a8d3977b5c4ed6d8d96ac66 Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Sun, 18 May 2025 19:49:43 -0700 Subject: [PATCH 13/55] wrote the spi driver --- CMakeLists.txt | 2 + include/libhal-arm-mcu/rp/spi.hpp | 28 ++++++++ src/rp/spi.cpp | 111 ++++++++++++++++++++++++++++++ 3 files changed, 141 insertions(+) create mode 100644 include/libhal-arm-mcu/rp/spi.hpp create mode 100644 src/rp/spi.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 806c1524..fda54b46 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -136,6 +136,7 @@ set(source_list src/rp/i2c.cpp src/rp/pwm.cpp src/rp/adc.cpp + src/rp/spi.cpp src/system_controller.cpp src/dwt_counter.cpp src/interrupt.cpp @@ -152,6 +153,7 @@ set(pico_dep hardware_i2c_headers hardware_pwm_headers hardware_adc_headers + hardware_spi_headers ) endif() diff --git a/include/libhal-arm-mcu/rp/spi.hpp b/include/libhal-arm-mcu/rp/spi.hpp new file mode 100644 index 00000000..cb03860a --- /dev/null +++ b/include/libhal-arm-mcu/rp/spi.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include + +namespace hal::rp::inline v1 { +/* +RP chips suppport 16 bit transfers. It may be worthwhile to add an option +to transfer 16 bits as a time. +*/ +struct spi final : public hal::spi_channel +{ + // todo constrain later + spi(u8 bus, u8 tx, u8 rx, u8 sck, u8 cs, spi::settings const&); + ~spi() override; + +private: + void driver_configure(settings const&) override; + u32 driver_clock_rate() override; + void driver_chip_select(bool p_select) override; + + void driver_transfer(std::span out, + std::span in, + byte) override; + + u8 m_bus, m_tx, m_rx, m_sck, m_cs; +}; + +} // namespace hal::rp::inline v1 diff --git a/src/rp/spi.cpp b/src/rp/spi.cpp new file mode 100644 index 00000000..76c4cdaf --- /dev/null +++ b/src/rp/spi.cpp @@ -0,0 +1,111 @@ +#include "libhal-arm-mcu/rp/spi.hpp" + +#include +#include +#include +#include + +namespace { +auto spi_bus(hal::u8 busnum) +{ + switch (busnum) { + case 0: + return spi0; + case 1: + return spi1; + default: + hal::safe_throw(hal::io_error(nullptr)); + } +} +} // namespace +namespace hal::rp::inline v1 { +// TODO shuddup clang-tidy I'll fix it later +spi::spi(u8 bus, u8 tx, u8 rx, u8 sck, u8 cs, spi::settings const& s) // NOLINT + : m_bus(bus) + , m_tx(tx) + , m_rx(rx) + , m_sck(sck) + , m_cs(cs) +{ + gpio_set_function(tx, GPIO_FUNC_SPI); + gpio_set_function(rx, GPIO_FUNC_SPI); + gpio_set_function(sck, GPIO_FUNC_SPI); + gpio_init(cs); + gpio_set_dir(cs, GPIO_OUT); + gpio_put(cs, true); + driver_configure(s); +} + +void spi::driver_configure(spi::settings const& s) +{ + spi_cpol_t polarity = SPI_CPOL_0; + spi_cpha_t phase = SPI_CPHA_0; + switch (s.bus_mode) { + case spi_channel::mode::m0: + polarity = SPI_CPOL_0; + phase = SPI_CPHA_0; + break; + case spi_channel::mode::m1: + polarity = SPI_CPOL_0; + phase = SPI_CPHA_1; + break; + case spi_channel::mode::m2: + polarity = SPI_CPOL_1; + phase = SPI_CPHA_0; + break; + case spi_channel::mode::m3: + polarity = SPI_CPOL_1; + phase = SPI_CPHA_1; + break; + default: + break; + } + spi_init(spi_bus(m_bus), s.clock_rate); + spi_set_format(spi_bus(m_bus), 8, polarity, phase, SPI_MSB_FIRST); +} + +u32 spi::driver_clock_rate() +{ + return spi_get_baudrate(spi_bus(m_bus)); +} + +void spi::driver_chip_select(bool sel) +{ + // The examples in + // https://github.com/raspberrypi/pico-examples/tree/master/spi seem to add a + // mysterious nop sequence before and after the assertion. Apparently the + // engineer who wrote this 15 years ago doesn't remember + // (https://forums.raspberrypi.com/viewtopic.php?t=332078) so its necessity is + // really unclear. In any case, there's a nonzero chance just the overhead of + // calling a function will mask this bug. If SPI seems a bit flaky, then + // consider adding asm nops here. + gpio_put(m_cs, !sel); +} + +void spi::driver_transfer(std::span out, + std::span in, + byte filler) +{ + auto out_size = out.size_bytes(); + auto in_size = in.size_bytes(); + auto size = std::min(out_size, in_size); + spi_write_read_blocking(spi_bus(m_bus), out.data(), in.data(), size); + if (out_size > in_size) { + spi_write_blocking( + spi_bus(m_bus), out.data() + in_size, out_size - in_size); + } else if (in_size > out_size) { + spi_read_blocking( + spi_bus(m_bus), filler, in.data() + out_size, in_size - out_size); + } +} + +spi::~spi() +{ + spi_deinit(spi_bus(m_bus)); + gpio_deinit(m_tx); + gpio_deinit(m_rx); + gpio_deinit(m_sck); + gpio_deinit(m_cs); +} + +} // namespace hal::rp::inline v1 From 04b281df9cde5e545cf345ddb4f64289291bc6d6 Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Sun, 18 May 2025 20:53:04 -0700 Subject: [PATCH 14/55] cleaned up some stuff, make it more idiomatic to libhal --- include/libhal-arm-mcu/rp/adc.hpp | 12 ++- include/libhal-arm-mcu/rp/i2c.hpp | 66 +++----------- include/libhal-arm-mcu/rp/input_pin.hpp | 15 ++-- include/libhal-arm-mcu/rp/interrupt_pin.hpp | 13 ++- include/libhal-arm-mcu/rp/output_pin.hpp | 20 ++--- include/libhal-arm-mcu/rp/pwm.hpp | 95 +++++++++++---------- include/libhal-arm-mcu/rp/rp.hpp | 15 +++- src/rp/adc.cpp | 6 ++ src/rp/gpio.cpp | 32 +++---- src/rp/i2c.cpp | 41 +++++---- src/rp/pwm.cpp | 16 ++-- src/rp/serial.cpp | 14 +-- 12 files changed, 170 insertions(+), 175 deletions(-) diff --git a/include/libhal-arm-mcu/rp/adc.hpp b/include/libhal-arm-mcu/rp/adc.hpp index db482d13..7f21935b 100644 --- a/include/libhal-arm-mcu/rp/adc.hpp +++ b/include/libhal-arm-mcu/rp/adc.hpp @@ -1,5 +1,6 @@ #pragma once +#include "libhal-arm-mcu/rp/rp.hpp" #include namespace hal::rp::inline v1 { @@ -7,9 +8,18 @@ namespace hal::rp::inline v1 { // The ADC is 12 bit struct adc final : public hal::adc16 { - adc(u8 gpio); + adc(pin_param auto pin) + : adc(pin()) + { + static_assert(internal::pin_max != 30 || (pin() >= 26 && pin() < 30), + "ADC pin is invalid!"); + static_assert(internal::pin_max != 48 || (pin() >= 40 && pin() < 48), + "ADC pin is invalid!"); + } + ~adc() override; private: + adc(u8 gpio); /*Because the rp chips only have one ADC that's muxed across different pins, we just initialize and mux the ADC every time we want to read. */ diff --git a/include/libhal-arm-mcu/rp/i2c.hpp b/include/libhal-arm-mcu/rp/i2c.hpp index f9530835..54147db5 100644 --- a/include/libhal-arm-mcu/rp/i2c.hpp +++ b/include/libhal-arm-mcu/rp/i2c.hpp @@ -2,6 +2,7 @@ #include "rp.hpp" #include +#include #include namespace hal::rp::inline v1 { @@ -10,61 +11,22 @@ RP2350 supports a baud rate of up to 1 MHz */ struct i2c final : public hal::i2c { - - enum i2c_channel : u8 - { - i2c0, - i2c1 - }; - - struct i2c_config_data + i2c(pin_param auto sda, + pin_param auto scl, + bus_param auto bus, + settings const& s) + : i2c(sda(), scl(), bus(), s) { - u8 const sda_pin; - u8 const scl_pin; - i2c_channel const chan; - - protected: - // clang lint does not know that this constructor is purposefully obstructed - // to prevent misuse - constexpr i2c_config_data(u8 sda, u8 scl, i2c_channel c) // NOLINT - : sda_pin(sda) - , scl_pin(scl) - , chan(c) - { - } - }; - - // yes, it is entirely valid to have sda and scl on pins that aren't next to - // each other because it's all muxed - template - struct i2c_config : public i2c_config_data - { - constexpr i2c_config() - : i2c_config_data(sda, scl, channel) - { - } - static consteval bool check() - { - if constexpr (channel == i2c0) { - static_assert(sda % 4 == 0 && sda < internal::pin_max, - "SDA pin for I2C0 is invalid!"); - static_assert(scl % 4 == 1 && scl < internal::pin_max, - "SCL pin for I2C0 is invalid!"); - } else if constexpr (channel == i2c1) { - static_assert(sda % 4 == 2 && sda < internal::pin_max, - "SDA pin for I2C1 is invalid!"); - static_assert(scl % 4 == 3 && scl < internal::pin_max, - "SCL pin for I2C1 is invalid!"); - } - return true; - } - static_assert(check()); - }; - - i2c(i2c_config_data, settings const&); + static_assert(bus() == 0 || bus() == 1, "Invalid bus selected!"); + static_assert(sda() % 4 == 0 || bus() != 0, "SDA pin for I2C0 is invalid!"); + static_assert(scl() % 4 == 1 || bus() != 0, "SCL pin for I2C0 is invalid!"); + static_assert(sda() % 4 == 2 || bus() != 1, "SDA pin for I2C1 is invalid!"); + static_assert(scl() % 4 == 3 || bus() != 1, "SCL pin for I2C1 is invalid!"); + } ~i2c() override; private: + i2c(u8 sda, u8 scl, u8 chan, settings const&); void driver_configure(settings const&) override; /* @@ -77,7 +39,7 @@ struct i2c final : public hal::i2c std::span in, hal::function_ref) override; - i2c_config_data m_config; + u8 m_sda, m_scl, m_chan; }; } // namespace hal::rp::inline v1 diff --git a/include/libhal-arm-mcu/rp/input_pin.hpp b/include/libhal-arm-mcu/rp/input_pin.hpp index a3c72fb8..1fae7cee 100644 --- a/include/libhal-arm-mcu/rp/input_pin.hpp +++ b/include/libhal-arm-mcu/rp/input_pin.hpp @@ -1,19 +1,22 @@ #pragma once +#include "libhal-arm-mcu/rp/rp.hpp" #include - namespace hal::rp::inline v1 { struct input_pin final : public hal::input_pin { - input_pin(u8, settings const&); - + + input_pin(pin_param auto pin, settings const&) + : input_pin(pin()) + { + } + private: + input_pin(u8, settings const&); void driver_configure(settings const&) override; bool driver_level() override; u8 m_pin{}; }; -} // namespace hal::rp::generic::inline v1 - - +} // namespace hal::rp::inline v1 diff --git a/include/libhal-arm-mcu/rp/interrupt_pin.hpp b/include/libhal-arm-mcu/rp/interrupt_pin.hpp index ca555a64..a6e674e7 100644 --- a/include/libhal-arm-mcu/rp/interrupt_pin.hpp +++ b/include/libhal-arm-mcu/rp/interrupt_pin.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -12,12 +13,18 @@ or in an interrupt. Not that you'd do that anyways. */ struct interrupt_pin final : public hal::interrupt_pin { - interrupt_pin(u8 pin, hal::callback, settings const& = {}); + interrupt_pin(pin_param auto pin, + hal::callback callback, + settings const& options = {}) + : interrupt_pin(pin(), callback, options) + { + } ~interrupt_pin() override; + private: + interrupt_pin(u8 pin, hal::callback, settings const&); void driver_configure(settings const&) override; void driver_on_trigger(hal::callback) override; u8 m_pin; }; -} // namespace hal::rp::generic::inline v1 - +} // namespace hal::rp::inline v1 diff --git a/include/libhal-arm-mcu/rp/output_pin.hpp b/include/libhal-arm-mcu/rp/output_pin.hpp index 326e29f4..0bc8897f 100644 --- a/include/libhal-arm-mcu/rp/output_pin.hpp +++ b/include/libhal-arm-mcu/rp/output_pin.hpp @@ -1,24 +1,24 @@ #pragma once -#include +#include "libhal-arm-mcu/rp/rp.hpp" #include - namespace hal::rp::inline v1 { struct output_pin final : public hal::output_pin { - output_pin(u8, settings const&); - + + output_pin(pin_param auto pin, settings const& options) + : output_pin(pin(), options) + { + } + private: + output_pin(u8, settings const&); + void driver_configure(settings const&) override; bool driver_level() override; void driver_level(bool) override; u8 m_pin{}; }; - -// temporary remove later -void sleep_ms(uint32_t); -} // namespace hal::rp::generic::inline v1 - - +} // namespace hal::rp::inline v1 diff --git a/include/libhal-arm-mcu/rp/pwm.hpp b/include/libhal-arm-mcu/rp/pwm.hpp index 5b372bfd..c4430f55 100644 --- a/include/libhal-arm-mcu/rp/pwm.hpp +++ b/include/libhal-arm-mcu/rp/pwm.hpp @@ -1,6 +1,8 @@ #pragma once #include "rp.hpp" +#include +#include #include namespace hal::rp::inline v1 { @@ -22,17 +24,53 @@ but also allows for more independence between slices. */ struct pwm_slice final : hal::pwm_group_manager { - pwm_slice(u8 slice_num); + static constexpr u8 max_slices() + { + using enum hal::rp::internal::processor_type; + if (hal::rp::internal::type == rp2040) { + return 8; + } else if (hal::rp::internal::type == rp2350) { + return 12; + } + return 0; + } + + static constexpr u8 get_slice_number(pin_param auto pin) + { + if (pin() < 32) { + return (pin / 2) % 8; + } else { + return pin / 2 - 8; + } + } + + pwm_slice(channel_param auto slice) + : pwm_slice(slice()) + { + static_assert(slice() < max_slices(), "Invalid PWM slice!"); + } struct configuration { - u8 pin; u16 duty_cycle = 0; bool autostart = true; }; - pwm_pin get_pin(configuration); + + pwm_pin get_pin(hal::runtime, pin_param auto pin, configuration const&); + + // copied from pico sdk + static constexpr u8 pin_from_slice(u8 gpio) + { + if ((gpio) < 32) { + return ((gpio) >> 1u) & 7u; + } else { + return 8u + (((gpio) >> 1u) & 3u); + } + } private: + pwm_pin get_pin_raw(u8 pin, configuration const&); + pwm_slice(u8 slice_num); /* At frequencies above ~2288 Hz, the wrap counter begins to lose resolution, reducing the duty cycle resolution @@ -63,7 +101,7 @@ struct pwm_pin final : hal::pwm16_channel */ void driver_duty_cycle(u16 p_duty_cycle) override; - pwm_pin(u8 slice, pwm_slice::configuration); + pwm_pin(u8 pin, pwm_slice::configuration const&); u8 m_pin; u8 m_slice; @@ -74,50 +112,15 @@ struct pwm_pin final : hal::pwm16_channel aligning their phases. Set argument to false to stop all of them at once */ void enable_all_pwm(bool start = true); -struct pwm_pin_config_data +pwm_pin pwm_slice::get_pin(hal::runtime, + pin_param auto pin, + pwm_slice::configuration const&config) { - u8 const pin; - pwm_ch const channel; - u8 const slice_num; - -protected: - constexpr pwm_pin_config_data(u8 p, pwm_ch c, u8 slice) - : pin(p) - , channel(c) - , slice_num(slice) - { - } -}; - -constexpr pwm_ch get_channel(u8 pin_num) -{ - if (pin_num % 2 == 0) { - return pwm_ch::a; - } else { - return pwm_ch::b; + u8 slice = get_slice_number(pin()); + if (slice != m_number) { + hal::safe_throw(hal::argument_out_of_domain(this)); } + get_pin_raw(pin(), config); } -constexpr u8 get_slice_number(u8 pin_num) -{ - // this snippet of code was copied from Pico SDK - if ((pin_num) < 32) { - return ((pin_num) >> 1u) & 7u; - } else { - return 8u + (((pin_num) >> 1u) & 3u); - } -} -/* Do not allocate two pwm's of the same slice. */ -template -struct pwm_pin_config : pwm_pin_config_data -{ - consteval pwm_pin_config() - : pwm_pin_config_data(pin_num, - get_channel(pin_num), - get_slice_number(pin_num)) - { - } - static_assert(pin_num < internal::pin_max, "Pin number is invalid!"); -}; - } // namespace hal::rp::inline v1 diff --git a/include/libhal-arm-mcu/rp/rp.hpp b/include/libhal-arm-mcu/rp/rp.hpp index 979248e3..b02e7187 100644 --- a/include/libhal-arm-mcu/rp/rp.hpp +++ b/include/libhal-arm-mcu/rp/rp.hpp @@ -1,8 +1,11 @@ #pragma once +#include #include -namespace hal::rp::internal { +namespace hal::rp { + +namespace internal { #if defined(PICO_RP2040) || defined(PICO_RP2350A) constexpr u8 pin_max = 30; @@ -10,7 +13,7 @@ constexpr u8 pin_max = 30; constexpr u8 pin_max = 48; #endif -enum struct processor_type: uint8_t +enum struct processor_type : uint8_t { rp2040, rp2350 @@ -18,10 +21,14 @@ enum struct processor_type: uint8_t #if defined(PICO_RP2040) constexpr inline type t = type::rp2040; -#elif defined(PICO_RP2350A) || defined(PICO_RP2350B) || defined(PICO_RP2350) +#elif defined(PICO_RP2350A) || defined(PICO_RP2350B) || defined(PICO_RP2350) constexpr inline processor_type type = processor_type::rp2350; #else #error "Unknown Pico model" #endif -} // namespace hal::rp::internal +} // namespace internal +template +concept pin_param = + std::is_same_v, T> && (T::val < internal::pin_max); +} // namespace hal::rp diff --git a/src/rp/adc.cpp b/src/rp/adc.cpp index 9ac8e046..2ccebfa2 100644 --- a/src/rp/adc.cpp +++ b/src/rp/adc.cpp @@ -2,6 +2,7 @@ #include "hardware/adc.h" #include +#include #include #include @@ -17,6 +18,11 @@ adc::adc(u8 pin) adc_gpio_init(pin); } +adc::~adc() +{ + gpio_deinit(m_pin); +} + u16 adc::driver_read() { adc_select_input(m_pin - ADC_BASE_PIN); diff --git a/src/rp/gpio.cpp b/src/rp/gpio.cpp index c2989694..fdef373d 100644 --- a/src/rp/gpio.cpp +++ b/src/rp/gpio.cpp @@ -120,13 +120,13 @@ struct interrupt_manager } // namespace -namespace hal::rp { -void v1::sleep_ms(uint32_t ms) +namespace hal::rp::inline v1 { +void sleep_ms(uint32_t ms) { ::sleep_ms(ms); } -v1::input_pin::input_pin(u8 pin, settings const& options) +input_pin::input_pin(u8 pin, settings const& options) : m_pin(pin) { if (pin >= NUM_BANK0_GPIOS) { @@ -140,7 +140,7 @@ v1::input_pin::input_pin(u8 pin, settings const& options) driver_configure(options); } -void v1::input_pin::driver_configure(settings const& p_settings) +void input_pin::driver_configure(settings const& p_settings) { switch (p_settings.resistor) { case pin_resistor::pull_down: @@ -157,12 +157,12 @@ void v1::input_pin::driver_configure(settings const& p_settings) } } -bool v1::input_pin::driver_level() +bool input_pin::driver_level() { return gpio_get(m_pin); } -v1::output_pin::output_pin(u8 pin, settings const& options) +output_pin::output_pin(u8 pin, settings const& options) : m_pin(pin) { if (pin >= NUM_BANK0_GPIOS) { @@ -176,7 +176,7 @@ v1::output_pin::output_pin(u8 pin, settings const& options) driver_configure(options); } -void v1::output_pin::driver_configure(settings const& options) +void output_pin::driver_configure(settings const& options) { // RP2* series chips don't seem to have any explicit support for // open drain mode, so we fail loud rather than silently @@ -199,19 +199,19 @@ void v1::output_pin::driver_configure(settings const& options) } } -void v1::output_pin::driver_level(bool level) +void output_pin::driver_level(bool level) { gpio_put(m_pin, level); } -bool v1::output_pin::driver_level() +bool output_pin::driver_level() { return gpio_get(m_pin); } -v1::interrupt_pin::interrupt_pin(u8 pin, - hal::callback callback, - settings const& options) +interrupt_pin::interrupt_pin(u8 pin, + hal::callback callback, + settings const& options) : m_pin(pin) { gpio_init(pin); @@ -240,13 +240,13 @@ v1::interrupt_pin::interrupt_pin(u8 pin, } } -v1::interrupt_pin::~interrupt_pin() +interrupt_pin::~interrupt_pin() { auto g = interrupt_manager::get(); g->remove(m_pin); } -void v1::interrupt_pin::driver_configure(settings const& options) +void interrupt_pin::driver_configure(settings const& options) { auto g = interrupt_manager::get(); switch (options.resistor) { @@ -266,10 +266,10 @@ void v1::interrupt_pin::driver_configure(settings const& options) g->at(m_pin).edge = options.trigger; } -void v1::interrupt_pin::driver_on_trigger(hal::callback callback) +void interrupt_pin::driver_on_trigger(hal::callback callback) { auto g = interrupt_manager::get(); std::swap(g->at(m_pin).callback, callback); } -} // namespace hal::rp::generic +} // namespace hal::rp::inline v1 diff --git a/src/rp/i2c.cpp b/src/rp/i2c.cpp index e55d719c..4c571332 100644 --- a/src/rp/i2c.cpp +++ b/src/rp/i2c.cpp @@ -10,12 +10,12 @@ #undef i2c1 namespace { -auto inst(hal::rp::i2c::i2c_channel c, void* instance) +auto inst(hal::u8 c, void* instance) { switch (c) { - case hal::rp::i2c::i2c0: + case 0: return &i2c0_inst; - case hal::rp::i2c::i2c1: + case 1: return &i2c1_inst; default: // realistically should never happen @@ -25,27 +25,28 @@ auto inst(hal::rp::i2c::i2c_channel c, void* instance) } // namespace namespace hal::rp::inline v1 { -i2c::i2c(i2c_config_data data, settings const& options) - : m_config(data) +i2c::i2c(u8 sda, u8 scl, u8 chan, settings const& options) // NOLINT + : m_sda(sda) + , m_scl(scl) + , m_chan(chan) { - gpio_pull_up(data.scl_pin); - gpio_pull_up(data.sda_pin); - gpio_set_function(data.scl_pin, gpio_function_t::GPIO_FUNC_I2C); - gpio_set_function(data.sda_pin, gpio_function_t::GPIO_FUNC_I2C); - i2c_init(inst(data.chan, this), static_cast(options.clock_rate)); + gpio_pull_up(sda); + gpio_pull_up(scl); + gpio_set_function(sda, gpio_function_t::GPIO_FUNC_I2C); + gpio_set_function(scl, gpio_function_t::GPIO_FUNC_I2C); + i2c_init(inst(chan, this), static_cast(options.clock_rate)); } i2c::~i2c() { - gpio_deinit(m_config.scl_pin); - gpio_deinit(m_config.sda_pin); - i2c_deinit(inst(m_config.chan, this)); + gpio_deinit(m_sda); + gpio_deinit(m_scl); + i2c_deinit(inst(m_chan, this)); } void i2c::driver_configure(settings const& options) { - i2c_set_baudrate(inst(m_config.chan, this), - static_cast(options.clock_rate)); + i2c_set_baudrate(inst(m_chan, this), static_cast(options.clock_rate)); } void i2c::driver_transaction(hal::byte addr, @@ -54,12 +55,8 @@ void i2c::driver_transaction(hal::byte addr, hal::function_ref timeout) { bool is_read = in.size() != 0; - int write_result = i2c_write_timeout_us(inst(m_config.chan, this), - addr, - out.data(), - out.size_bytes(), - is_read, - 5000); + int write_result = i2c_write_timeout_us( + inst(m_chan, this), addr, out.data(), out.size_bytes(), is_read, 5000); if (write_result == PICO_ERROR_GENERIC) { safe_throw(no_such_device(addr, this)); } @@ -68,7 +65,7 @@ void i2c::driver_transaction(hal::byte addr, } if (is_read) { int read_result = i2c_read_timeout_us( - inst(m_config.chan, this), addr, in.data(), in.size_bytes(), false, 5000); + inst(m_chan, this), addr, in.data(), in.size_bytes(), false, 5000); if (read_result == PICO_ERROR_GENERIC) { safe_throw(no_such_device(addr, this)); } diff --git a/src/rp/pwm.cpp b/src/rp/pwm.cpp index 8abe2ad9..d280c39d 100644 --- a/src/rp/pwm.cpp +++ b/src/rp/pwm.cpp @@ -55,14 +55,14 @@ void pwm_slice::driver_frequency(u32 f) pwm_set_clkdiv(m_number, clock_div); } -pwm_pin pwm_slice::get_pin(configuration c) +pwm_pin pwm_slice::get_pin_raw(u8 pin, configuration const& c) { - return { m_number, c }; + return { pin, c }; } -pwm_pin::pwm_pin(u8 number, pwm_slice::configuration c) - : m_pin(c.pin) - , m_slice(number) +pwm_pin::pwm_pin(u8 pin, pwm_slice::configuration const& c) + : m_pin(pin) + , m_slice(pwm_gpio_to_slice_num(pin)) , m_autostart(c.autostart) { driver_duty_cycle(c.duty_cycle); @@ -70,16 +70,16 @@ pwm_pin::pwm_pin(u8 number, pwm_slice::configuration c) void pwm_pin::driver_duty_cycle(u16 duty) { + auto channel = pwm_gpio_to_channel(m_pin); if (duty == 0) { - pwm_set_chan_level(m_slice, pwm_gpio_to_channel(m_pin), 0); + pwm_set_chan_level(m_slice, channel, 0); pwm_set_enabled(m_slice, false); return; } float percentage = float(duty) / std::numeric_limits::max(); u16 top = pwm_hw->slice[m_slice].top; - pwm_set_chan_level( - m_slice, pwm_gpio_to_channel(m_pin), u16(float(top) * percentage)); + pwm_set_chan_level(m_slice, channel, u16(float(top) * percentage)); if (m_autostart) { pwm_set_enabled(m_slice, true); } diff --git a/src/rp/serial.cpp b/src/rp/serial.cpp index f1984ce7..81cf0c45 100644 --- a/src/rp/serial.cpp +++ b/src/rp/serial.cpp @@ -3,22 +3,22 @@ #include #include -namespace hal::rp { -v1::stdio_serial::stdio_serial() +namespace hal::rp::inline v1 { +stdio_serial::stdio_serial() { stdio_init_all(); } -void v1::stdio_serial::driver_configure(settings const&) +void stdio_serial::driver_configure(settings const&) { } -void v1::stdio_serial::driver_flush() +void stdio_serial::driver_flush() { stdio_flush(); } -serial::write_t v1::stdio_serial::driver_write(std::span in) +serial::write_t stdio_serial::driver_write(std::span in) { int write_length = stdio_put_string(reinterpret_cast(in.data()), static_cast(in.size_bytes()), @@ -27,7 +27,7 @@ serial::write_t v1::stdio_serial::driver_write(std::span in) return write_t{ in.subspan(0, write_length) }; } -serial::read_t v1::stdio_serial::driver_read(std::span output) +serial::read_t stdio_serial::driver_read(std::span output) { // time in microseconds auto now = get_absolute_time(); @@ -40,4 +40,4 @@ serial::read_t v1::stdio_serial::driver_read(std::span output) .capacity = 1 }; } -} // namespace hal::rp::generic +} // namespace hal::rp::inline v1 From 5edf309051db51e7db5581ae64a978307a94c22b Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Sun, 18 May 2025 21:07:54 -0700 Subject: [PATCH 15/55] constrain spi api using compile time --- include/libhal-arm-mcu/rp/spi.hpp | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/include/libhal-arm-mcu/rp/spi.hpp b/include/libhal-arm-mcu/rp/spi.hpp index cb03860a..9e3b0c42 100644 --- a/include/libhal-arm-mcu/rp/spi.hpp +++ b/include/libhal-arm-mcu/rp/spi.hpp @@ -1,5 +1,6 @@ #pragma once +#include "libhal-arm-mcu/rp/rp.hpp" #include namespace hal::rp::inline v1 { @@ -9,11 +10,23 @@ to transfer 16 bits as a time. */ struct spi final : public hal::spi_channel { - // todo constrain later - spi(u8 bus, u8 tx, u8 rx, u8 sck, u8 cs, spi::settings const&); + // Yes the spi pins can be completely seperate pins + spi(pin_param auto copi, + pin_param auto cipo, + pin_param auto sck, + pin_param auto cs, + spi::settings const& options) + : spi(bus_from_tx_pin(copi()), copi(), cipo(), sck(), cs(), options) + { + // CS is a normal chip select + static_assert(cipo() % 4 == 0, "SPI RX pin is invalid"); + static_assert(cs() % 4 == 1, "SPI CS pin is invalid"); + static_assert(sck() % 4 == 2, "SPI SCK pin is invalid"); + } ~spi() override; private: + spi(u8 bus, u8 copi, u8 cipo, u8 sck, u8 cs, spi::settings const&); void driver_configure(settings const&) override; u32 driver_clock_rate() override; void driver_chip_select(bool p_select) override; @@ -23,6 +36,8 @@ struct spi final : public hal::spi_channel byte) override; u8 m_bus, m_tx, m_rx, m_sck, m_cs; + + constexpr u8 bus_from_tx_pin(u8); }; } // namespace hal::rp::inline v1 From a79d23c9bf3b2c519adec76aa5f197c7452b2456 Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Sun, 18 May 2025 22:10:14 -0700 Subject: [PATCH 16/55] changed header reference to be less verbose --- include/libhal-arm-mcu/rp/adc.hpp | 2 +- include/libhal-arm-mcu/rp/input_pin.hpp | 2 +- include/libhal-arm-mcu/rp/interrupt_pin.hpp | 2 +- include/libhal-arm-mcu/rp/output_pin.hpp | 2 +- include/libhal-arm-mcu/rp/spi.hpp | 2 +- src/rp/pwm.cpp | 1 - 6 files changed, 5 insertions(+), 6 deletions(-) diff --git a/include/libhal-arm-mcu/rp/adc.hpp b/include/libhal-arm-mcu/rp/adc.hpp index 7f21935b..5c536730 100644 --- a/include/libhal-arm-mcu/rp/adc.hpp +++ b/include/libhal-arm-mcu/rp/adc.hpp @@ -1,6 +1,6 @@ #pragma once -#include "libhal-arm-mcu/rp/rp.hpp" +#include "rp.hpp" #include namespace hal::rp::inline v1 { diff --git a/include/libhal-arm-mcu/rp/input_pin.hpp b/include/libhal-arm-mcu/rp/input_pin.hpp index 1fae7cee..b2a1468a 100644 --- a/include/libhal-arm-mcu/rp/input_pin.hpp +++ b/include/libhal-arm-mcu/rp/input_pin.hpp @@ -1,6 +1,6 @@ #pragma once -#include "libhal-arm-mcu/rp/rp.hpp" +#include "rp.hpp" #include namespace hal::rp::inline v1 { diff --git a/include/libhal-arm-mcu/rp/interrupt_pin.hpp b/include/libhal-arm-mcu/rp/interrupt_pin.hpp index a6e674e7..a35248e8 100644 --- a/include/libhal-arm-mcu/rp/interrupt_pin.hpp +++ b/include/libhal-arm-mcu/rp/interrupt_pin.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include "rp.hpp" #include #include diff --git a/include/libhal-arm-mcu/rp/output_pin.hpp b/include/libhal-arm-mcu/rp/output_pin.hpp index 0bc8897f..9e7c0ad8 100644 --- a/include/libhal-arm-mcu/rp/output_pin.hpp +++ b/include/libhal-arm-mcu/rp/output_pin.hpp @@ -1,6 +1,6 @@ #pragma once -#include "libhal-arm-mcu/rp/rp.hpp" +#include "rp.hpp" #include namespace hal::rp::inline v1 { diff --git a/include/libhal-arm-mcu/rp/spi.hpp b/include/libhal-arm-mcu/rp/spi.hpp index 9e3b0c42..a48a6e30 100644 --- a/include/libhal-arm-mcu/rp/spi.hpp +++ b/include/libhal-arm-mcu/rp/spi.hpp @@ -1,6 +1,6 @@ #pragma once -#include "libhal-arm-mcu/rp/rp.hpp" +#include "rp.hpp" #include namespace hal::rp::inline v1 { diff --git a/src/rp/pwm.cpp b/src/rp/pwm.cpp index d280c39d..bfbf1d3a 100644 --- a/src/rp/pwm.cpp +++ b/src/rp/pwm.cpp @@ -1,5 +1,4 @@ #include "libhal-arm-mcu/rp/pwm.hpp" -#include "libhal-arm-mcu/rp/rp.hpp" #include #include From 743e4f0f59e7291c59d1aff6530b20f5ec28b623 Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Sun, 18 May 2025 22:12:27 -0700 Subject: [PATCH 17/55] wrote the uart driver --- CMakeLists.txt | 1 + include/libhal-arm-mcu/rp/serial.hpp | 38 +++++++++++- src/rp/serial.cpp | 86 ++++++++++++++++++++++++++++ 3 files changed, 122 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fda54b46..59bd21ab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -154,6 +154,7 @@ set(pico_dep hardware_pwm_headers hardware_adc_headers hardware_spi_headers + hardware_uart_headers ) endif() diff --git a/include/libhal-arm-mcu/rp/serial.hpp b/include/libhal-arm-mcu/rp/serial.hpp index 930d4475..9a278030 100644 --- a/include/libhal-arm-mcu/rp/serial.hpp +++ b/include/libhal-arm-mcu/rp/serial.hpp @@ -1,8 +1,8 @@ #pragma once +#include "rp.hpp" #include - namespace hal::rp::inline v1 { /* The RP series chips use a native ROM USB bootloader, @@ -18,7 +18,6 @@ of this class as it is not guaranteed. struct stdio_serial final : public hal::serial { stdio_serial(); - ~stdio_serial() override = default; private: // This function is a sham that does nothing @@ -31,4 +30,37 @@ struct stdio_serial final : public hal::serial void driver_flush() override; }; -} // namespace hal::rp::inline v1 +// RP chips support CTS and RTS, but libhal has no support for them currently +// This is not really intended for debug purposes, and as such is blocking +// and has no internal buffering. +struct uart final : public hal::serial +{ + + uart(bus_param auto bus, + pin_param auto tx, + pin_param auto rx, + settings const& options) + : uart(bus(), tx(), rx(), options) + { + static_assert(bus() == 0 || bus() == 1, "Invalid UART bus selected!"); + static_assert(tx() % 4 == 0, "UART TX pin is invalid!"); + static_assert(rx() % 4 == 1, "UART TX pin is invalid!"); + static_assert(((tx() + 4) / 8) % 2 == bus(), + "UART TX pin and bus do not match!"); + static_assert(((rx() + 4) / 8) % 2 == bus(), + "UART RX pin and bus do not match!"); + } + ~uart() override; + +private: + uart(u8 bus, u8 tx, u8 rx, settings const&); + void driver_configure(settings const&) override; + write_t driver_write(std::span) override; + // available bytes is always read as 0 or 1, since there's no actual way + // to read number of bytes in the fifo + read_t driver_read(std::span) override; + void driver_flush() override; + u8 m_tx, m_rx, m_bus; +}; + +} // namespace hal::rp::inline v1 diff --git a/src/rp/serial.cpp b/src/rp/serial.cpp index 81cf0c45..ac6d51cb 100644 --- a/src/rp/serial.cpp +++ b/src/rp/serial.cpp @@ -1,8 +1,27 @@ #include "libhal-arm-mcu/rp/serial.hpp" +#include +#include +#include +#include +#include #include #include +namespace { +auto get_uart(hal::u8 bus) +{ + switch (bus) { + case 0: + return uart0; + case 1: + return uart1; + default: + hal::safe_throw(hal::io_error(nullptr)); + } +} +} // namespace + namespace hal::rp::inline v1 { stdio_serial::stdio_serial() { @@ -40,4 +59,71 @@ serial::read_t stdio_serial::driver_read(std::span output) .capacity = 1 }; } +uart::uart(u8 bus, u8 tx, u8 rx, settings const& options) // NOLINT + : m_tx(tx) + , m_rx(rx) + , m_bus(bus) +{ + + auto func = (bus == 0) ? GPIO_FUNC_UART : GPIO_FUNC_UART_AUX; + + gpio_set_function(tx, func); + gpio_set_function(rx, func); + + driver_configure(options); +} + +uart::~uart() +{ + uart_deinit(get_uart(m_bus)); + gpio_deinit(m_tx); + gpio_deinit(m_rx); +} + +void uart::driver_configure(settings const& options) +{ + uint stop = 1; + switch (options.stop) { + case serial::settings::stop_bits::one: + stop = 1; + break; + case serial::settings::stop_bits::two: + stop = 2; + break; + } + uart_parity_t parity = UART_PARITY_NONE; + switch (options.parity) { + case serial::settings::parity::none: + parity = UART_PARITY_NONE; + break; + case serial::settings::parity::odd: + parity = UART_PARITY_ODD; + break; + case serial::settings::parity::even: + parity = UART_PARITY_EVEN; + break; + case serial::settings::parity::forced1: + case serial::settings::parity::forced0: + hal::safe_throw(operation_not_supported(this)); + } + + uart_init(get_uart(m_bus), uint(options.baud_rate)); + uart_set_format(get_uart(m_bus), 8, stop, parity); +} + +serial::write_t uart::driver_write(std::span in) +{ + uart_write_blocking(get_uart(m_bus), in.data(), in.size_bytes()); + // always writes everything + return { in }; +} + +serial::read_t uart::driver_read(std::span out) +{ + uart_read_blocking(get_uart(m_bus), out.data(), out.size_bytes()); + return { .data = out, + .available = uart_is_readable(get_uart(m_bus)), + .capacity = 32 }; +} + } // namespace hal::rp::inline v1 From 2599762385899c8867aac27e9ebd93ce2ad19fa9 Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Sun, 18 May 2025 22:20:43 -0700 Subject: [PATCH 18/55] added destructors to be consistent --- include/libhal-arm-mcu/rp/input_pin.hpp | 1 + include/libhal-arm-mcu/rp/output_pin.hpp | 1 + include/libhal-arm-mcu/rp/pwm.hpp | 2 ++ src/rp/gpio.cpp | 10 ++++++++++ src/rp/pwm.cpp | 10 ++++++++++ 5 files changed, 24 insertions(+) diff --git a/include/libhal-arm-mcu/rp/input_pin.hpp b/include/libhal-arm-mcu/rp/input_pin.hpp index b2a1468a..a8520d07 100644 --- a/include/libhal-arm-mcu/rp/input_pin.hpp +++ b/include/libhal-arm-mcu/rp/input_pin.hpp @@ -11,6 +11,7 @@ struct input_pin final : public hal::input_pin : input_pin(pin()) { } + ~input_pin() override; private: input_pin(u8, settings const&); diff --git a/include/libhal-arm-mcu/rp/output_pin.hpp b/include/libhal-arm-mcu/rp/output_pin.hpp index 9e7c0ad8..5085a212 100644 --- a/include/libhal-arm-mcu/rp/output_pin.hpp +++ b/include/libhal-arm-mcu/rp/output_pin.hpp @@ -11,6 +11,7 @@ struct output_pin final : public hal::output_pin : output_pin(pin(), options) { } + ~output_pin() override; private: output_pin(u8, settings const&); diff --git a/include/libhal-arm-mcu/rp/pwm.hpp b/include/libhal-arm-mcu/rp/pwm.hpp index c4430f55..a3339752 100644 --- a/include/libhal-arm-mcu/rp/pwm.hpp +++ b/include/libhal-arm-mcu/rp/pwm.hpp @@ -87,6 +87,8 @@ if somebody needs it. */ struct pwm_pin final : hal::pwm16_channel { + + ~pwm_pin() override; // A false disables the timer. void enable(bool = true); diff --git a/src/rp/gpio.cpp b/src/rp/gpio.cpp index fdef373d..a898d3c0 100644 --- a/src/rp/gpio.cpp +++ b/src/rp/gpio.cpp @@ -140,6 +140,11 @@ input_pin::input_pin(u8 pin, settings const& options) driver_configure(options); } +input_pin::~input_pin() +{ + gpio_deinit(m_pin); +} + void input_pin::driver_configure(settings const& p_settings) { switch (p_settings.resistor) { @@ -176,6 +181,11 @@ output_pin::output_pin(u8 pin, settings const& options) driver_configure(options); } +output_pin::~output_pin() +{ + gpio_deinit(m_pin); +} + void output_pin::driver_configure(settings const& options) { // RP2* series chips don't seem to have any explicit support for diff --git a/src/rp/pwm.cpp b/src/rp/pwm.cpp index bfbf1d3a..53699f95 100644 --- a/src/rp/pwm.cpp +++ b/src/rp/pwm.cpp @@ -1,5 +1,6 @@ #include "libhal-arm-mcu/rp/pwm.hpp" +#include #include #include #include @@ -20,6 +21,8 @@ pwm_slice::pwm_slice(u8 num) if (num >= NUM_PWM_SLICES) { hal::safe_throw(argument_out_of_domain(this)); } + auto config = pwm_get_default_config(); + pwm_init(num, &config, false); } void pwm_slice::driver_frequency(u32 f) @@ -64,9 +67,16 @@ pwm_pin::pwm_pin(u8 pin, pwm_slice::configuration const& c) , m_slice(pwm_gpio_to_slice_num(pin)) , m_autostart(c.autostart) { + gpio_set_function(m_pin, GPIO_FUNC_PWM); driver_duty_cycle(c.duty_cycle); } +pwm_pin::~pwm_pin() +{ + pwm_set_enabled(m_slice, false); + gpio_deinit(m_pin); +} + void pwm_pin::driver_duty_cycle(u16 duty) { auto channel = pwm_gpio_to_channel(m_pin); From d1edce84fb29d0c3c8332a9866f234dbcb591c3e Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Mon, 19 May 2025 07:34:14 -0700 Subject: [PATCH 19/55] apparently the demos init the peripheral before the pins --- src/rp/i2c.cpp | 4 ++-- src/rp/serial.cpp | 5 ++--- src/rp/spi.cpp | 4 ++-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/rp/i2c.cpp b/src/rp/i2c.cpp index 4c571332..d9b5c523 100644 --- a/src/rp/i2c.cpp +++ b/src/rp/i2c.cpp @@ -30,18 +30,18 @@ i2c::i2c(u8 sda, u8 scl, u8 chan, settings const& options) // NOLINT , m_scl(scl) , m_chan(chan) { + i2c_init(inst(chan, this), static_cast(options.clock_rate)); gpio_pull_up(sda); gpio_pull_up(scl); gpio_set_function(sda, gpio_function_t::GPIO_FUNC_I2C); gpio_set_function(scl, gpio_function_t::GPIO_FUNC_I2C); - i2c_init(inst(chan, this), static_cast(options.clock_rate)); } i2c::~i2c() { + i2c_deinit(inst(m_chan, this)); gpio_deinit(m_sda); gpio_deinit(m_scl); - i2c_deinit(inst(m_chan, this)); } void i2c::driver_configure(settings const& options) diff --git a/src/rp/serial.cpp b/src/rp/serial.cpp index ac6d51cb..4f3a3b6a 100644 --- a/src/rp/serial.cpp +++ b/src/rp/serial.cpp @@ -67,17 +67,16 @@ uart::uart(u8 bus, u8 tx, u8 rx, settings const& options) // NOLINT auto func = (bus == 0) ? GPIO_FUNC_UART : GPIO_FUNC_UART_AUX; + driver_configure(options); gpio_set_function(tx, func); gpio_set_function(rx, func); - - driver_configure(options); } uart::~uart() { - uart_deinit(get_uart(m_bus)); gpio_deinit(m_tx); gpio_deinit(m_rx); + uart_deinit(get_uart(m_bus)); } void uart::driver_configure(settings const& options) diff --git a/src/rp/spi.cpp b/src/rp/spi.cpp index 76c4cdaf..1f03f32b 100644 --- a/src/rp/spi.cpp +++ b/src/rp/spi.cpp @@ -27,13 +27,13 @@ spi::spi(u8 bus, u8 tx, u8 rx, u8 sck, u8 cs, spi::settings const& s) // NOLINT , m_sck(sck) , m_cs(cs) { + driver_configure(s); gpio_set_function(tx, GPIO_FUNC_SPI); gpio_set_function(rx, GPIO_FUNC_SPI); gpio_set_function(sck, GPIO_FUNC_SPI); gpio_init(cs); gpio_set_dir(cs, GPIO_OUT); gpio_put(cs, true); - driver_configure(s); } void spi::driver_configure(spi::settings const& s) @@ -101,11 +101,11 @@ void spi::driver_transfer(std::span out, spi::~spi() { - spi_deinit(spi_bus(m_bus)); gpio_deinit(m_tx); gpio_deinit(m_rx); gpio_deinit(m_sck); gpio_deinit(m_cs); + spi_deinit(spi_bus(m_bus)); } } // namespace hal::rp::inline v1 From 79f46576c6a891454de03f038d484f5e51765908 Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Mon, 19 May 2025 08:03:32 -0700 Subject: [PATCH 20/55] change interrupt handler to use a giant array because memory is basically infinite --- src/rp/gpio.cpp | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/rp/gpio.cpp b/src/rp/gpio.cpp index a898d3c0..43cdf516 100644 --- a/src/rp/gpio.cpp +++ b/src/rp/gpio.cpp @@ -1,15 +1,18 @@ #include "libhal-arm-mcu/rp/input_pin.hpp" #include "libhal-arm-mcu/rp/interrupt_pin.hpp" #include "libhal-arm-mcu/rp/output_pin.hpp" +#include "libhal-arm-mcu/rp/rp.hpp" #include "libhal/error.hpp" #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -55,8 +58,8 @@ struct interrupt_manager struct interrupt { - hal::callback callback; hal::interrupt_pin::trigger_edge edge; + hal::callback callback; }; static lock get() @@ -70,17 +73,17 @@ struct interrupt_manager void insert(hal::u8 pin, interrupt handler) { - m_handlers.insert({ pin, std::move(handler) }); + m_interrupts[pin] = std::move(handler); } interrupt& at(hal::u8 pin) { - return m_handlers.at(pin); + return m_interrupts[pin].value(); } void remove(hal::u8 pin) { - m_handlers.erase(pin); + m_interrupts[pin].reset(); } // I don't control the callback types clang-tidy @@ -89,7 +92,12 @@ struct interrupt_manager using enum hal::interrupt_pin::trigger_edge; auto g = get(); - auto& handler = g->m_handlers.at(gpio); + // If there is no handler then we don't do anything + if (!g->m_interrupts[gpio].has_value()) { + return; + } + + auto& handler = *g->m_interrupts[gpio]; bool should_activate = false; switch (handler.edge) { @@ -110,8 +118,9 @@ struct interrupt_manager } private: - // I would use std::hive but that doesn't exist yet - std::unordered_map m_handlers; + // yes I could save 8 * 48 bytes by doing union stuff instead of + // std::optional no I'm too lazy to do that + std::array, hal::rp::internal::pin_max> m_interrupts; interrupt_manager() = default; ~interrupt_manager() = default; // todo check if this should be thread_local @@ -191,7 +200,7 @@ void output_pin::driver_configure(settings const& options) // RP2* series chips don't seem to have any explicit support for // open drain mode, so we fail loud rather than silently if (options.open_drain) { - hal::safe_throw(hal::argument_out_of_domain(this)); + hal::safe_throw(hal::operation_not_supported(this)); } switch (options.resistor) { @@ -230,7 +239,7 @@ interrupt_pin::interrupt_pin(u8 pin, auto g = interrupt_manager::get(); g->insert(m_pin, - { .callback = std::move(callback), .edge = options.trigger }); + { .edge = options.trigger, .callback = std::move(callback) }); // can't use driver_configure because it would cause the lock to be taken // twice From 4c959438dec99e423855859da1eb1a05c4f77bac Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Fri, 23 May 2025 10:37:36 -0700 Subject: [PATCH 21/55] made a timer class, got demos to compile again --- CMakeLists.txt | 5 ++-- conanfile.py | 38 +++++++++++++++++++++++++++++- demos/CMakeLists.txt | 18 ++++++++------ demos/conanfile.py | 3 +-- demos/main.cpp | 32 ++++++++++++++++++------- demos/platforms/rp2350-arm-s.cpp | 20 ++++++++++++++++ demos/run.sh | 15 ++++++++++-- include/libhal-arm-mcu/rp/rp.hpp | 10 ++++---- include/libhal-arm-mcu/rp/time.hpp | 15 ++++++++++++ src/rp/time.cpp | 19 +++++++++++++++ 10 files changed, 148 insertions(+), 27 deletions(-) create mode 100644 demos/platforms/rp2350-arm-s.cpp create mode 100644 include/libhal-arm-mcu/rp/time.hpp create mode 100644 src/rp/time.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 59bd21ab..9329e4ef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -137,10 +137,10 @@ set(source_list src/rp/pwm.cpp src/rp/adc.cpp src/rp/spi.cpp + src/rp/time.cpp src/system_controller.cpp src/dwt_counter.cpp src/interrupt.cpp - src/systick_timer.cpp ) pico_sdk_init() @@ -155,7 +155,8 @@ set(pico_dep hardware_adc_headers hardware_spi_headers hardware_uart_headers - ) + hardware_timer_headers +) endif() diff --git a/conanfile.py b/conanfile.py index 2ca27cca..eecf0b22 100644 --- a/conanfile.py +++ b/conanfile.py @@ -15,7 +15,9 @@ # limitations under the License. from conan import ConanFile -from conan.tools.cmake import CMake +from conan.tools.cmake import CMakeDeps, CMakeToolchain +from conan.tools.env import VirtualBuildEnv +from conan.errors import ConanInvalidConfiguration import os @@ -43,6 +45,7 @@ class libhal_arm_mcu_conan(ConanFile): "use_picolibc": [True, False], "platform": ["ANY"], "use_default_linker_script": [True, False], + "variant": [None, "ANY"] } default_options = { @@ -50,6 +53,7 @@ class libhal_arm_mcu_conan(ConanFile): "use_picolibc": True, "platform": "ANY", "use_default_linker_script": True, + "variant": None } def requirements(self): @@ -82,6 +86,32 @@ def handle_stm32f1_linker_scripts(self): "-T" + os.path.join("libhal-stm32f1", linker_script_name + ".ld"), ] + def _macro(self, string): + return string.upper().replace("-", "_") + + # I don't really feel like exposing picosdk macros to consumers, so we do this + # to replace certain macros + # TODO: modify libhal-conan-bootstrap to inject variables instead of this jank + def generate(self): + virt = VirtualBuildEnv(self) + virt.generate() + tc = CMakeToolchain(self) + if self.options.variant: + tc.preprocessor_definitions["LIBHAL_VARIANT_" + self._macro(str(self.options.variant))] = "1" + tc.preprocessor_definitions["LIBHAL_PLATFORM_" + self._macro(str(self.options.platform))] = "1" + tc.generate() + cmake = CMakeDeps(self) + cmake.generate() + + def validate(self): + if str(self.options.platform).startswith("rp2"): + if "rp2350" in str(self.options.platform): + if not self.options.variant: + raise ConanInvalidConfiguration("RP2350 variant not specified") + if self.options.variant not in ["rp2350a", "rp2350b"]: + raise ConanInvalidConfiguration("Invalid RP2350 variant specified") + super().validate() + def package_info(self): self.cpp_info.libs = ["libhal-arm-mcu"] self.cpp_info.set_property("cmake_target_name", "libhal::arm-mcu") @@ -95,6 +125,12 @@ def package_info(self): platform = str(self.options.platform) self.buildenv_info.define("LIBHAL_PLATFORM", platform) self.buildenv_info.define("LIBHAL_PLATFORM_LIBRARY", "arm-mcu") + if str(self.options.platform).startswith("rp2"): + defines = [] + if self.options.variant: + defines.append("LIBHAL_VARIANT_" + self._macro(str(self.options.variant)) + "=1") + defines.append("LIBHAL_PLATFORM_" + self._macro(str(self.options.platform)) + "=1") + self.cpp_info.defines = defines if (self.settings.os == "baremetal" and self.options.use_default_linker_script): diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt index b6d0b1e8..a25b4070 100644 --- a/demos/CMakeLists.txt +++ b/demos/CMakeLists.txt @@ -18,23 +18,27 @@ if(DEFINED ENV{PICO_SDK_PATH}) include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake) endif() -set(CMAKE_EXECUTABLE_SUFFIX_ASM .elf) -set(CMAKE_EXECUTABLE_SUFFIX_C .elf) -set(CMAKE_EXECUTABLE_SUFFIX_CXX .elf) - project(demos LANGUAGES CXX C ASM) find_package(libhal-arm-mcu CONFIG REQUIRED) +if(DEFINED ENV{PICO_SDK_PATH}) pico_sdk_init() add_executable(test_package main.cpp) -target_link_libraries(test_package PRIVATE libhal::arm-mcu pico_stdlib) -pico_add_extra_outputs(test_package) +target_link_libraries(libhal::arm-mcu INTERFACE + pico_stdlib + hardware_gpio + pico_stdio +) +target_link_libraries(test_package + libhal::arm-mcu +) pico_enable_stdio_usb(test_package 1) pico_enable_stdio_uart(test_package 0) +endif() -# set(ENV{LIBHAL_PLATFORM_LIBRARY} arm-mcu) +set(ENV{LIBHAL_PLATFORM_LIBRARY} arm-mcu) # libhal_build_demos( # DEMOS diff --git a/demos/conanfile.py b/demos/conanfile.py index ea93cfd6..ed770aaf 100644 --- a/demos/conanfile.py +++ b/demos/conanfile.py @@ -25,7 +25,7 @@ def requirements(self): self.requires("libhal-util/[^5.4.0]") self.requires("libhal-arm-mcu/[1.9.1 || latest]") self.tool_requires("picotool/2.1.1") - # self.requires("minimp3/cci.20211201") + self.requires("minimp3/cci.20211201") # This is kinda sketch, but needs to be done manually until https://github.com/conan-io/conan/issues/13372 # gets implemented @@ -33,7 +33,6 @@ def build(self): cmake = CMake(self) defs = { "CMAKE_ASM_FLAGS_INIT": "-mcpu=cortex-m33 -mfloat-abi=soft", - # "PICO_PLATFORM": "rp2350-arm-s", # For some reason even if I set PICO_FLASH_SIZE with PICO_BOARD=none, # it still doesn't work. I can't explain why. "PICO_BOARD": "adafruit_feather_rp2350", diff --git a/demos/main.cpp b/demos/main.cpp index c5f40e38..fbd4506d 100644 --- a/demos/main.cpp +++ b/demos/main.cpp @@ -17,25 +17,39 @@ // #include // #include -#include "libhal-arm-mcu/rp/generic/output_pin.hpp" -#include "libhal-arm-mcu/rp/generic/serial.hpp" -#include +#include "libhal-arm-mcu/rp/output_pin.hpp" +#include "libhal-arm-mcu/rp/serial.hpp" +#include #include +#include +#include +#include +#include #include +#include int main() { - namespace rp = hal::rp::generic; - rp::output_pin led(7, { .resistor = hal::pin_resistor::none }); + namespace rp = hal::rp; + using namespace std::chrono_literals; + + rp::output_pin led(hal::pin<7>, { .resistor = hal::pin_resistor::none }); rp::stdio_serial console; + static auto clock = rp::clock(); + + std::array strbuf = {}; while (true) { - hal::print(console, "On!\n"); + snprintf( + strbuf.data(), 49, "On! Current timestamp: %llu\n", clock.uptime()); + hal::print(console, std::string_view(strbuf)); led.level(true); - rp::sleep_ms(500); - hal::print(console, "Off!\n"); + hal::delay(clock, 500ms); + snprintf( + strbuf.data(), 49, "Off! Current timestamp: %llu\n", clock.uptime()); + hal::print(console, std::string_view(strbuf)); led.level(false); - rp::sleep_ms(500); + hal::delay(clock, 500ms); } } diff --git a/demos/platforms/rp2350-arm-s.cpp b/demos/platforms/rp2350-arm-s.cpp new file mode 100644 index 00000000..e973127e --- /dev/null +++ b/demos/platforms/rp2350-arm-s.cpp @@ -0,0 +1,20 @@ +#include +#include +#include +#include +#include + +namespace rp = hal::rp; + +void initialize_platform(resource_list& resource) +{ + using namespace hal::literals; + static auto serial = rp::stdio_serial(); + resource.console = &serial; + + static auto led = rp::output_pin(hal::pin<7>, {}); + resource.status_led = &led; + static auto counter = rp::clock(); + resource.clock = &counter; + +} diff --git a/demos/run.sh b/demos/run.sh index 674d9efb..a265237f 100644 --- a/demos/run.sh +++ b/demos/run.sh @@ -1,8 +1,19 @@ #!/usr/bin/sh -source build/rp2350-arm-s/RelWithDebInfo/generators/conanbuild.sh +set -ex -picotool load build/rp2350-arm-s/RelWithDebInfo/test_package.uf2 -f +BUILDDIR=build/rp2350-arm-s/RelWithDebInfo + +conan build . -pr:h=rp2350 -pr:h=arm-gcc-12.3 --build=missing + +source $BUILDDIR/generators/conanbuild.sh + +cp $BUILDDIR/test_package $BUILDDIR/test_package.elf + +file $BUILDDIR/test_package.elf + +picotool uf2 convert $BUILDDIR/test_package.elf $BUILDDIR/test_package.uf2 +picotool load $BUILDDIR/test_package.uf2 -f sleep 1 plink -serial /dev/ttyACM0 -sercfg 115200,8,1,n,X diff --git a/include/libhal-arm-mcu/rp/rp.hpp b/include/libhal-arm-mcu/rp/rp.hpp index b02e7187..e4b16db7 100644 --- a/include/libhal-arm-mcu/rp/rp.hpp +++ b/include/libhal-arm-mcu/rp/rp.hpp @@ -7,10 +7,12 @@ namespace hal::rp { namespace internal { -#if defined(PICO_RP2040) || defined(PICO_RP2350A) +#if defined(LIBHAL_PLATFORM_RP2040) || defined(LIBHAL_VARIANT_RP2350A) constexpr u8 pin_max = 30; -#else +#elif defined(LIBHAL_VARIANT_RP2350B) constexpr u8 pin_max = 48; +#else +#error "Unknown pico variant" #endif enum struct processor_type : uint8_t @@ -19,9 +21,9 @@ enum struct processor_type : uint8_t rp2350 }; -#if defined(PICO_RP2040) +#if defined(LIBHAL_PLATFORM_RP2040) constexpr inline type t = type::rp2040; -#elif defined(PICO_RP2350A) || defined(PICO_RP2350B) || defined(PICO_RP2350) +#elif defined(LIBHAL_PLATFORM_RP2350_ARM_S) constexpr inline processor_type type = processor_type::rp2350; #else #error "Unknown Pico model" diff --git a/include/libhal-arm-mcu/rp/time.hpp b/include/libhal-arm-mcu/rp/time.hpp new file mode 100644 index 00000000..c1cc2bf0 --- /dev/null +++ b/include/libhal-arm-mcu/rp/time.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include + +namespace hal::rp::inline v1 { + +// This has to be the least interesting clock ever +struct clock final : public hal::steady_clock +{ + clock() = default; + hertz driver_frequency() override; + u64 driver_uptime() override; +}; + +} // namespace hal::rp::inline v1 diff --git a/src/rp/time.cpp b/src/rp/time.cpp new file mode 100644 index 00000000..2f608102 --- /dev/null +++ b/src/rp/time.cpp @@ -0,0 +1,19 @@ +#include "libhal-arm-mcu/rp/time.hpp" +#include + +#include +#include + +namespace hal::rp::inline v1 { + +hertz clock::driver_frequency() +{ + return 1'000'000; +} + +u64 clock::driver_uptime() +{ + return time_us_64(); +} + +} // namespace hal::rp::inline v1 From 8743e623b1891eab6666885c453b94ec5074fc67 Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Fri, 23 May 2025 10:42:31 -0700 Subject: [PATCH 22/55] demo works! --- demos/CMakeLists.txt | 58 +++++++++--------- demos/main.cpp | 140 ++++++++++++++++--------------------------- demos/run.sh | 8 +-- 3 files changed, 82 insertions(+), 124 deletions(-) diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt index a25b4070..70c51778 100644 --- a/demos/CMakeLists.txt +++ b/demos/CMakeLists.txt @@ -25,43 +25,39 @@ find_package(libhal-arm-mcu CONFIG REQUIRED) if(DEFINED ENV{PICO_SDK_PATH}) pico_sdk_init() -add_executable(test_package main.cpp) target_link_libraries(libhal::arm-mcu INTERFACE pico_stdlib hardware_gpio pico_stdio ) -target_link_libraries(test_package - libhal::arm-mcu -) -pico_enable_stdio_usb(test_package 1) -pico_enable_stdio_uart(test_package 0) +pico_enable_stdio_usb(libhal::arm-mcu 1) +pico_enable_stdio_uart(libhal::arm-mcu 0) endif() set(ENV{LIBHAL_PLATFORM_LIBRARY} arm-mcu) -# libhal_build_demos( -# DEMOS -# adc -# blinker -# can -# dac -# gpio -# i2c -# interrupt_pin -# uart -# pwm -# pwm16 -# spi -# blank -# # stream_dac - -# PACKAGES -# minimp3 - -# INCLUDES -# . - -# LINK_LIBRARIES -# minimp3::minimp3 -# ) +libhal_build_demos( + DEMOS + # adc + blinker + # can + # dac + # gpio + # i2c + # interrupt_pin + # uart + # pwm + # pwm16 + # spi + # blank + # # stream_dac + + # PACKAGES + # minimp3 + + INCLUDES + . + + # LINK_LIBRARIES + # minimp3::minimp3 +) diff --git a/demos/main.cpp b/demos/main.cpp index fbd4506d..7fa7ccb8 100644 --- a/demos/main.cpp +++ b/demos/main.cpp @@ -12,101 +12,67 @@ // See the License for the specific language governing permissions and // limitations under the License. -// #include -// #include -// #include - -// #include -#include "libhal-arm-mcu/rp/output_pin.hpp" -#include "libhal-arm-mcu/rp/serial.hpp" -#include -#include -#include +#include #include #include -#include -#include -#include -int main() -{ - namespace rp = hal::rp; - using namespace std::chrono_literals; +#include "resource_list.hpp" - rp::output_pin led(hal::pin<7>, { .resistor = hal::pin_resistor::none }); - rp::stdio_serial console; - static auto clock = rp::clock(); +resource_list resources{}; - std::array strbuf = {}; +[[noreturn]] void terminate_handler() noexcept +{ + if (resources.console) { + hal::print(*resources.console.value(), "☠️ APPLICATION TERMINATED ☠️\n\n"); + } + if (resources.status_led && resources.clock) { + auto& led = *resources.status_led.value(); + auto& clock = *resources.clock.value(); + + while (true) { + using namespace std::chrono_literals; + led.level(false); + hal::delay(clock, 100ms); + led.level(true); + hal::delay(clock, 100ms); + led.level(false); + hal::delay(clock, 100ms); + led.level(true); + hal::delay(clock, 1000ms); + } + } + + // spin here forever while (true) { - snprintf( - strbuf.data(), 49, "On! Current timestamp: %llu\n", clock.uptime()); - hal::print(console, std::string_view(strbuf)); - led.level(true); - hal::delay(clock, 500ms); - snprintf( - strbuf.data(), 49, "Off! Current timestamp: %llu\n", clock.uptime()); - hal::print(console, std::string_view(strbuf)); - led.level(false); - hal::delay(clock, 500ms); + continue; } } -// resource_list resources{}; - -// [[noreturn]] void terminate_handler() noexcept -// { -// if (resources.console) { -// hal::print(*resources.console.value(), "☠️ APPLICATION TERMINATED ☠️\n\n"); -// } - -// if (resources.status_led && resources.clock) { -// auto& led = *resources.status_led.value(); -// auto& clock = *resources.clock.value(); - -// while (true) { -// using namespace std::chrono_literals; -// led.level(false); -// hal::delay(clock, 100ms); -// led.level(true); -// hal::delay(clock, 100ms); -// led.level(false); -// hal::delay(clock, 100ms); -// led.level(true); -// hal::delay(clock, 1000ms); -// } -// } - -// // spin here forever -// while (true) { -// continue; -// } -// } - -// int main() -// { -// hal::set_terminate(terminate_handler); - -// initialize_platform(resources); - -// try { -// application(resources); -// } catch (std::bad_optional_access const& e) { -// if (resources.console) { -// hal::print(*resources.console.value(), -// "A resource required by the application was not -// available!\n" "Calling terminate!\n"); -// } -// } // Allow any other exceptions to terminate the application - -// std::terminate(); -// } +int main() +{ + hal::set_terminate(terminate_handler); + + initialize_platform(resources); + + try { + application(resources); + } catch (std::bad_optional_access const& e) { + if (resources.console) { + hal::print(*resources.console.value(), + "A resource required by the application was not" + "available!\n" + "Calling terminate!\n"); + } + } // Allow any other exceptions to terminate the application + + std::terminate(); +} -// extern "C" -// { -// // This gets rid of an issue with libhal-exceptions in Debug mode. -// void __assert_func() // NOLINT -// { -// } -// } +extern "C" +{ + // This gets rid of an issue with libhal-exceptions in Debug mode. + void __assert_func() // NOLINT + { + } +} diff --git a/demos/run.sh b/demos/run.sh index a265237f..9f96b4da 100644 --- a/demos/run.sh +++ b/demos/run.sh @@ -8,12 +8,8 @@ conan build . -pr:h=rp2350 -pr:h=arm-gcc-12.3 --build=missing source $BUILDDIR/generators/conanbuild.sh -cp $BUILDDIR/test_package $BUILDDIR/test_package.elf - -file $BUILDDIR/test_package.elf - -picotool uf2 convert $BUILDDIR/test_package.elf $BUILDDIR/test_package.uf2 -picotool load $BUILDDIR/test_package.uf2 -f +picotool uf2 convert $BUILDDIR/blinker.elf $BUILDDIR/blinker.uf2 +picotool load $BUILDDIR/blinker.uf2 -f sleep 1 plink -serial /dev/ttyACM0 -sercfg 115200,8,1,n,X From fcdeb938b43a5e6109209c3cdf1f23d8d949961f Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Fri, 23 May 2025 10:59:15 -0700 Subject: [PATCH 23/55] Added default params for convenience --- include/libhal-arm-mcu/rp/i2c.hpp | 2 +- include/libhal-arm-mcu/rp/input_pin.hpp | 4 ++-- include/libhal-arm-mcu/rp/output_pin.hpp | 2 +- include/libhal-arm-mcu/rp/pwm.hpp | 16 ++++++++++------ include/libhal-arm-mcu/rp/serial.hpp | 2 +- include/libhal-arm-mcu/rp/spi.hpp | 2 +- 6 files changed, 16 insertions(+), 12 deletions(-) diff --git a/include/libhal-arm-mcu/rp/i2c.hpp b/include/libhal-arm-mcu/rp/i2c.hpp index 54147db5..a256a64a 100644 --- a/include/libhal-arm-mcu/rp/i2c.hpp +++ b/include/libhal-arm-mcu/rp/i2c.hpp @@ -14,7 +14,7 @@ struct i2c final : public hal::i2c i2c(pin_param auto sda, pin_param auto scl, bus_param auto bus, - settings const& s) + settings const& s = {}) : i2c(sda(), scl(), bus(), s) { static_assert(bus() == 0 || bus() == 1, "Invalid bus selected!"); diff --git a/include/libhal-arm-mcu/rp/input_pin.hpp b/include/libhal-arm-mcu/rp/input_pin.hpp index a8520d07..4f6fd898 100644 --- a/include/libhal-arm-mcu/rp/input_pin.hpp +++ b/include/libhal-arm-mcu/rp/input_pin.hpp @@ -7,8 +7,8 @@ namespace hal::rp::inline v1 { struct input_pin final : public hal::input_pin { - input_pin(pin_param auto pin, settings const&) - : input_pin(pin()) + input_pin(pin_param auto pin, settings const& s = {}) + : input_pin(pin(), s) { } ~input_pin() override; diff --git a/include/libhal-arm-mcu/rp/output_pin.hpp b/include/libhal-arm-mcu/rp/output_pin.hpp index 5085a212..f9a2bea7 100644 --- a/include/libhal-arm-mcu/rp/output_pin.hpp +++ b/include/libhal-arm-mcu/rp/output_pin.hpp @@ -7,7 +7,7 @@ namespace hal::rp::inline v1 { struct output_pin final : public hal::output_pin { - output_pin(pin_param auto pin, settings const& options) + output_pin(pin_param auto pin, settings const& options = {}) : output_pin(pin(), options) { } diff --git a/include/libhal-arm-mcu/rp/pwm.hpp b/include/libhal-arm-mcu/rp/pwm.hpp index a3339752..5c731a9d 100644 --- a/include/libhal-arm-mcu/rp/pwm.hpp +++ b/include/libhal-arm-mcu/rp/pwm.hpp @@ -15,6 +15,14 @@ enum struct pwm_ch : u8 struct pwm_pin; +// See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=88165 + + struct pwm_pin_configuration + { + u16 duty_cycle = 0; + bool autostart = true; + }; + /* Somewhat unusually, the pico contains 12 pwm "groups" with 2 input pins each. These "groups" @@ -50,13 +58,9 @@ struct pwm_slice final : hal::pwm_group_manager static_assert(slice() < max_slices(), "Invalid PWM slice!"); } - struct configuration - { - u16 duty_cycle = 0; - bool autostart = true; - }; + using configuration = pwm_pin_configuration; - pwm_pin get_pin(hal::runtime, pin_param auto pin, configuration const&); + pwm_pin get_pin(hal::runtime, pin_param auto pin, configuration const& = {}); // copied from pico sdk static constexpr u8 pin_from_slice(u8 gpio) diff --git a/include/libhal-arm-mcu/rp/serial.hpp b/include/libhal-arm-mcu/rp/serial.hpp index 9a278030..c5a53317 100644 --- a/include/libhal-arm-mcu/rp/serial.hpp +++ b/include/libhal-arm-mcu/rp/serial.hpp @@ -39,7 +39,7 @@ struct uart final : public hal::serial uart(bus_param auto bus, pin_param auto tx, pin_param auto rx, - settings const& options) + settings const& options = {}) : uart(bus(), tx(), rx(), options) { static_assert(bus() == 0 || bus() == 1, "Invalid UART bus selected!"); diff --git a/include/libhal-arm-mcu/rp/spi.hpp b/include/libhal-arm-mcu/rp/spi.hpp index a48a6e30..c695545e 100644 --- a/include/libhal-arm-mcu/rp/spi.hpp +++ b/include/libhal-arm-mcu/rp/spi.hpp @@ -15,7 +15,7 @@ struct spi final : public hal::spi_channel pin_param auto cipo, pin_param auto sck, pin_param auto cs, - spi::settings const& options) + spi::settings const& options = {}) : spi(bus_from_tx_pin(copi()), copi(), cipo(), sck(), cs(), options) { // CS is a normal chip select From 68c553466eeb6c267945c6ba10e910ff03ee1222 Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Fri, 23 May 2025 15:53:17 -0700 Subject: [PATCH 24/55] most of the demos compile. todo check they actually work --- demos/CMakeLists.txt | 74 ++++++++++++++++++++++--------- demos/platforms/rp2350-arm-s.cpp | 38 ++++++++++++++-- include/libhal-arm-mcu/rp/pwm.hpp | 20 ++++----- src/rp/adc.cpp | 3 ++ 4 files changed, 101 insertions(+), 34 deletions(-) diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt index 70c51778..f61498b8 100644 --- a/demos/CMakeLists.txt +++ b/demos/CMakeLists.txt @@ -25,39 +25,71 @@ find_package(libhal-arm-mcu CONFIG REQUIRED) if(DEFINED ENV{PICO_SDK_PATH}) pico_sdk_init() -target_link_libraries(libhal::arm-mcu INTERFACE +pico_enable_stdio_usb(libhal::arm-mcu 1) +pico_enable_stdio_uart(libhal::arm-mcu 0) + +set(demo_deps pico_stdlib hardware_gpio pico_stdio + hardware_i2c + hardware_adc + hardware_spi +) + +# not all of the demos work at the moment +set(demos + # ADC demo needs to be updated to ADC16 + # adc + blinker + # No canbus until I can figure out software can + # can + gpio + i2c + interrupt_pin + uart + pwm16 + spi + blank + # stream_dac + +) + +else() + +set(demos + adc + blinker + can + dac + gpio + i2c + interrupt_pin + uart + pwm + pwm16 + spi + blank + stream_dac +) + +set(demo_deps + minimp3::minimp3 ) -pico_enable_stdio_usb(libhal::arm-mcu 1) -pico_enable_stdio_uart(libhal::arm-mcu 0) endif() set(ENV{LIBHAL_PLATFORM_LIBRARY} arm-mcu) libhal_build_demos( DEMOS - # adc - blinker - # can - # dac - # gpio - # i2c - # interrupt_pin - # uart - # pwm - # pwm16 - # spi - # blank - # # stream_dac - - # PACKAGES - # minimp3 + ${demos} + + PACKAGES + minimp3 INCLUDES . - # LINK_LIBRARIES - # minimp3::minimp3 + LINK_LIBRARIES + ${demo_deps} ) diff --git a/demos/platforms/rp2350-arm-s.cpp b/demos/platforms/rp2350-arm-s.cpp index e973127e..c6df0e67 100644 --- a/demos/platforms/rp2350-arm-s.cpp +++ b/demos/platforms/rp2350-arm-s.cpp @@ -1,11 +1,21 @@ +#include +#include +#include +#include #include +#include #include +#include #include #include #include namespace rp = hal::rp; +void do_nothing(bool) +{ +} + void initialize_platform(resource_list& resource) { using namespace hal::literals; @@ -14,7 +24,29 @@ void initialize_platform(resource_list& resource) static auto led = rp::output_pin(hal::pin<7>, {}); resource.status_led = &led; - static auto counter = rp::clock(); - resource.clock = &counter; - + + static auto clock = rp::clock(); + resource.clock = &clock; + + // the hal demos don't use adc16 yet + // static auto adc = rp::adc(hal::pin<27>); + // resource.adc = &adc; + + static auto button = rp::input_pin(hal::pin<0>, {}); + resource.input_pin = &button; + + static auto i2c = rp::i2c(hal::pin<6>, hal::pin<7>, hal::bus<1>); + resource.i2c = &i2c; + + static auto interrupt = rp::interrupt_pin(hal::pin<1>, &do_nothing); + resource.interrupt_pin = &interrupt; + + static auto slice = rp::pwm_slice(hal::channel<1>); + resource.pwm_frequency = &slice; + static auto chan = slice.get_pin({}, hal::pin<2>); + resource.pwm_channel = &chan; + + // I wrote spi as spi_channel. Maybe rewrite to support multi-peripheral + // support later. + // static auto spi = rp::spi() } diff --git a/include/libhal-arm-mcu/rp/pwm.hpp b/include/libhal-arm-mcu/rp/pwm.hpp index 5c731a9d..f53ddc27 100644 --- a/include/libhal-arm-mcu/rp/pwm.hpp +++ b/include/libhal-arm-mcu/rp/pwm.hpp @@ -17,11 +17,11 @@ struct pwm_pin; // See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=88165 - struct pwm_pin_configuration - { - u16 duty_cycle = 0; - bool autostart = true; - }; +struct pwm_pin_configuration +{ + u16 duty_cycle = 0; + bool autostart = true; +}; /* Somewhat unusually, the pico contains 12 pwm @@ -46,9 +46,9 @@ struct pwm_slice final : hal::pwm_group_manager static constexpr u8 get_slice_number(pin_param auto pin) { if (pin() < 32) { - return (pin / 2) % 8; + return (pin() / 2) % 8; } else { - return pin / 2 - 8; + return pin() / 2 - 8; } } @@ -120,13 +120,13 @@ void enable_all_pwm(bool start = true); pwm_pin pwm_slice::get_pin(hal::runtime, pin_param auto pin, - pwm_slice::configuration const&config) + pwm_slice::configuration const& config) { - u8 slice = get_slice_number(pin()); + u8 slice = get_slice_number(pin); if (slice != m_number) { hal::safe_throw(hal::argument_out_of_domain(this)); } - get_pin_raw(pin(), config); + return get_pin_raw(pin(), config); } } // namespace hal::rp::inline v1 diff --git a/src/rp/adc.cpp b/src/rp/adc.cpp index 2ccebfa2..0d58585e 100644 --- a/src/rp/adc.cpp +++ b/src/rp/adc.cpp @@ -31,6 +31,9 @@ u16 adc::driver_read() if (adc_hw->cs & ADC_CS_ERR_BITS) { hal::safe_throw(hal::io_error(this)); } + // TODO: Use hal::upscale to actually make this 16 bit + // for some reason the docs reference a function that + // doesn't exist yet return result; } From a5be0036361fccc8eef573f7b1114c6966997a7f Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Fri, 23 May 2025 16:30:17 -0700 Subject: [PATCH 25/55] fixed spi check --- include/libhal-arm-mcu/rp/spi.hpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/include/libhal-arm-mcu/rp/spi.hpp b/include/libhal-arm-mcu/rp/spi.hpp index c695545e..3d7f9546 100644 --- a/include/libhal-arm-mcu/rp/spi.hpp +++ b/include/libhal-arm-mcu/rp/spi.hpp @@ -18,10 +18,11 @@ struct spi final : public hal::spi_channel spi::settings const& options = {}) : spi(bus_from_tx_pin(copi()), copi(), cipo(), sck(), cs(), options) { - // CS is a normal chip select static_assert(cipo() % 4 == 0, "SPI RX pin is invalid"); - static_assert(cs() % 4 == 1, "SPI CS pin is invalid"); + // CS is a normal gpio static_assert(sck() % 4 == 2, "SPI SCK pin is invalid"); + static_assert(copi() % 4 == 3, "SPI CS pin is invalid"); + } ~spi() override; From 4a9c3272e0cd05d57f4ce39aae332b259a548adf Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Fri, 23 May 2025 16:30:27 -0700 Subject: [PATCH 26/55] moved pwm to compiletime checking --- demos/platforms/rp2350-arm-s.cpp | 2 +- include/libhal-arm-mcu/rp/pwm.hpp | 50 ++++++++++++++++++++----------- src/rp/pwm.cpp | 8 ++--- 3 files changed, 37 insertions(+), 23 deletions(-) diff --git a/demos/platforms/rp2350-arm-s.cpp b/demos/platforms/rp2350-arm-s.cpp index c6df0e67..45b68332 100644 --- a/demos/platforms/rp2350-arm-s.cpp +++ b/demos/platforms/rp2350-arm-s.cpp @@ -43,7 +43,7 @@ void initialize_platform(resource_list& resource) static auto slice = rp::pwm_slice(hal::channel<1>); resource.pwm_frequency = &slice; - static auto chan = slice.get_pin({}, hal::pin<2>); + static auto chan = slice.get_pin(hal::pin<2>); resource.pwm_channel = &chan; // I wrote spi as spi_channel. Maybe rewrite to support multi-peripheral diff --git a/include/libhal-arm-mcu/rp/pwm.hpp b/include/libhal-arm-mcu/rp/pwm.hpp index f53ddc27..f2a1ed18 100644 --- a/include/libhal-arm-mcu/rp/pwm.hpp +++ b/include/libhal-arm-mcu/rp/pwm.hpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace hal::rp::inline v1 { @@ -30,7 +31,7 @@ are called slices, and are actually very independent of one other. This slightly complicates synchronization, but also allows for more independence between slices. */ -struct pwm_slice final : hal::pwm_group_manager +struct pwm_slice_runtime : hal::pwm_group_manager { static constexpr u8 max_slices() { @@ -52,16 +53,14 @@ struct pwm_slice final : hal::pwm_group_manager } } - pwm_slice(channel_param auto slice) - : pwm_slice(slice()) + pwm_slice_runtime(channel_param auto slice) + : pwm_slice_runtime(slice()) { static_assert(slice() < max_slices(), "Invalid PWM slice!"); } using configuration = pwm_pin_configuration; - pwm_pin get_pin(hal::runtime, pin_param auto pin, configuration const& = {}); - // copied from pico sdk static constexpr u8 pin_from_slice(u8 gpio) { @@ -72,15 +71,15 @@ struct pwm_slice final : hal::pwm_group_manager } } -private: +protected: pwm_pin get_pin_raw(u8 pin, configuration const&); - pwm_slice(u8 slice_num); + pwm_slice_runtime(u8 slice_num); /* At frequencies above ~2288 Hz, the wrap counter begins to lose resolution, reducing the duty cycle resolution down from its theoretical 16 bits of resolution. */ - void driver_frequency(u32 p_frequency) override; + void driver_frequency(u32 p_frequency) final; u8 m_number; }; @@ -96,7 +95,7 @@ struct pwm_pin final : hal::pwm16_channel // A false disables the timer. void enable(bool = true); - friend pwm_slice; + friend pwm_slice_runtime; private: u32 driver_frequency() override; @@ -107,7 +106,7 @@ struct pwm_pin final : hal::pwm16_channel */ void driver_duty_cycle(u16 p_duty_cycle) override; - pwm_pin(u8 pin, pwm_slice::configuration const&); + pwm_pin(u8 pin, pwm_slice_runtime::configuration const&); u8 m_pin; u8 m_slice; @@ -118,15 +117,30 @@ struct pwm_pin final : hal::pwm16_channel aligning their phases. Set argument to false to stop all of them at once */ void enable_all_pwm(bool start = true); -pwm_pin pwm_slice::get_pin(hal::runtime, - pin_param auto pin, - pwm_slice::configuration const& config) +template +struct pwm_slice : pwm_slice_runtime { - u8 slice = get_slice_number(pin); - if (slice != m_number) { - hal::safe_throw(hal::argument_out_of_domain(this)); + + pwm_slice(channel_param auto ch) + : pwm_slice_runtime(ch()) + { + using enum internal::processor_type; + static_assert(internal::type == rp2040 || internal::type == rp2350, + "Update PWM channels for new RP!"); + static_assert(ch() < 8 || internal::pin_max != 30, + "PWM channel is invalid!"); + static_assert(ch() < 11 || internal::pin_max != 48, + "PWM channel is invalid!"); } - return get_pin_raw(pin(), config); -} + + pwm_pin get_pin(pin_param auto pin, + pwm_slice_runtime::configuration const& config = {}) + { + static_assert(get_slice_number(pin) == chan, "Slice pin is incorrect!"); + return get_pin_raw(pin(), config); + } +}; +template +pwm_slice(p pin) -> pwm_slice; } // namespace hal::rp::inline v1 diff --git a/src/rp/pwm.cpp b/src/rp/pwm.cpp index 53699f95..a9c7d1e6 100644 --- a/src/rp/pwm.cpp +++ b/src/rp/pwm.cpp @@ -15,7 +15,7 @@ frequency it should run at namespace hal::rp::inline v1 { -pwm_slice::pwm_slice(u8 num) +pwm_slice_runtime::pwm_slice_runtime(u8 num) : m_number(num) { if (num >= NUM_PWM_SLICES) { @@ -25,7 +25,7 @@ pwm_slice::pwm_slice(u8 num) pwm_init(num, &config, false); } -void pwm_slice::driver_frequency(u32 f) +void pwm_slice_runtime::driver_frequency(u32 f) { // frequency too high if (f > SYS_CLK_HZ) { @@ -57,12 +57,12 @@ void pwm_slice::driver_frequency(u32 f) pwm_set_clkdiv(m_number, clock_div); } -pwm_pin pwm_slice::get_pin_raw(u8 pin, configuration const& c) +pwm_pin pwm_slice_runtime::get_pin_raw(u8 pin, configuration const& c) { return { pin, c }; } -pwm_pin::pwm_pin(u8 pin, pwm_slice::configuration const& c) +pwm_pin::pwm_pin(u8 pin, pwm_slice_runtime::configuration const& c) : m_pin(pin) , m_slice(pwm_gpio_to_slice_num(pin)) , m_autostart(c.autostart) From c544a6a2bb5c4f6fefdcb8f24d90da41a9aebae1 Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Tue, 9 Sep 2025 15:42:49 -0700 Subject: [PATCH 27/55] update to 2.2.0, amongst minor fixes --- conanfile.py | 10 +++++----- include/libhal-arm-mcu/rp/rp.hpp | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/conanfile.py b/conanfile.py index eecf0b22..ad80d370 100644 --- a/conanfile.py +++ b/conanfile.py @@ -70,8 +70,8 @@ def requirements(self): compiler_version = str(self.settings.compiler.version) self.requires("prebuilt-picolibc/" + compiler_version) if str(self.options.platform).startswith("rp2"): - self.requires("picosdk/2.1.1") - self.tool_requires("pioasm/2.1.1") + self.requires("picosdk/2.2.0") + self.tool_requires("pioasm/2.2.0") def handle_stm32f1_linker_scripts(self): linker_script_name = list(str(self.options.platform)) @@ -89,13 +89,12 @@ def handle_stm32f1_linker_scripts(self): def _macro(self, string): return string.upper().replace("-", "_") - # I don't really feel like exposing picosdk macros to consumers, so we do this - # to replace certain macros - # TODO: modify libhal-conan-bootstrap to inject variables instead of this jank def generate(self): virt = VirtualBuildEnv(self) virt.generate() tc = CMakeToolchain(self) + if str(self.options.platform).startswith("rp2"): + tc.cache_variables["DO_NOT_BUILD_BOOT_HAL"] = True if self.options.variant: tc.preprocessor_definitions["LIBHAL_VARIANT_" + self._macro(str(self.options.variant))] = "1" tc.preprocessor_definitions["LIBHAL_PLATFORM_" + self._macro(str(self.options.platform))] = "1" @@ -119,6 +118,7 @@ def package_info(self): "libhal::lpc40", "libhal::stm32f1", "libhal::stm32f4", + "libhal::rp2350" ]) self.cpp_info.exelinkflags = [] diff --git a/include/libhal-arm-mcu/rp/rp.hpp b/include/libhal-arm-mcu/rp/rp.hpp index e4b16db7..f80d113d 100644 --- a/include/libhal-arm-mcu/rp/rp.hpp +++ b/include/libhal-arm-mcu/rp/rp.hpp @@ -22,7 +22,7 @@ enum struct processor_type : uint8_t }; #if defined(LIBHAL_PLATFORM_RP2040) -constexpr inline type t = type::rp2040; +constexpr inline processor_type type = type::rp2040; #elif defined(LIBHAL_PLATFORM_RP2350_ARM_S) constexpr inline processor_type type = processor_type::rp2350; #else From cc2f4b40ec6df15723daf721a79527bcf8a67b8a Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Fri, 19 Sep 2025 19:32:51 -0700 Subject: [PATCH 28/55] updating resource_list to be modern --- demos/platforms/rp2350-arm-s.cpp | 134 ++++++++++++++++++++++++------ demos/resource_list.hpp | 7 +- include/libhal-arm-mcu/rp/adc.hpp | 29 +++++-- include/libhal-arm-mcu/rp/pwm.hpp | 98 +++++++++------------- src/rp/adc.cpp | 33 +++++++- src/rp/pwm.cpp | 18 ++-- 6 files changed, 217 insertions(+), 102 deletions(-) diff --git a/demos/platforms/rp2350-arm-s.cpp b/demos/platforms/rp2350-arm-s.cpp index 45b68332..836cf1cc 100644 --- a/demos/platforms/rp2350-arm-s.cpp +++ b/demos/platforms/rp2350-arm-s.cpp @@ -8,45 +8,129 @@ #include #include #include +#include +#include +#include #include namespace rp = hal::rp; -void do_nothing(bool) +namespace resources { +namespace h5 = hal::v5; +static std::array buffer = {}; +std::pmr::monotonic_buffer_resource memory(buffer.data(), + buffer.size(), + std::pmr::null_memory_resource()); + +std::pmr::polymorphic_allocator<> driver_allocator() +{ + return &memory; +} +template +using opt = h5::optional_ptr; + +template +using sptr = h5::strong_ptr; + +opt clk; +sptr clock() +{ + if (not clk) { + clk = h5::make_strong_ptr(&memory); + } + return clk; +} + +opt out; +sptr console() +{ + if (not out) { + out = h5::make_strong_ptr(&memory); + } + return out; +} + +opt led; +sptr status_led() { + if (not led) { + // Actually for adafruit board. Change once micromods arrive. + led = h5::make_strong_ptr(&memory, hal::pin<7>); + } + return led; } -void initialize_platform(resource_list& resource) +opt adc0; +sptr adc() { - using namespace hal::literals; - static auto serial = rp::stdio_serial(); - resource.console = &serial; + if (not adc0) { + adc0 = h5::make_strong_ptr(&memory, hal::pin<26>); + } + return adc0; +} - static auto led = rp::output_pin(hal::pin<7>, {}); - resource.status_led = &led; +opt g0; +sptr input_pin() +{ + if (not g0) { + g0 = h5::make_strong_ptr(&memory, hal::pin<0>); + } + return g0; +} - static auto clock = rp::clock(); - resource.clock = &clock; +opt i2c0; +sptr i2c() +{ + if (not i2c0) { + i2c0 = h5::make_strong_ptr( + &memory, hal::pin<2>, hal::pin<3>, hal::bus<1>); + } + return i2c0; +} - // the hal demos don't use adc16 yet - // static auto adc = rp::adc(hal::pin<27>); - // resource.adc = &adc; +opt g1; +hal::callback do_nothing = [](bool) {}; +sptr interrupt_pin() +{ + if (not g1) { + g1 = + h5::make_strong_ptr(&memory, hal::pin<1>, do_nothing); + } + return g1; +} - static auto button = rp::input_pin(hal::pin<0>, {}); - resource.input_pin = &button; +// TODO: add timer +sptr timed_interrupt(); - static auto i2c = rp::i2c(hal::pin<6>, hal::pin<7>, hal::bus<1>); - resource.i2c = &i2c; +opt slice; +opt pwm_pin; - static auto interrupt = rp::interrupt_pin(hal::pin<1>, &do_nothing); - resource.interrupt_pin = &interrupt; +sptr pwm_channel() +{ + (void)pwm_frequency(); // Need to initalize this first + if (not pwm_pin) { + pwm_pin = h5::make_strong_ptr( + &memory, 5, hal::rp::pwm_pin_configuration{}, hal::unsafe{}); + } + return pwm_pin; +} - static auto slice = rp::pwm_slice(hal::channel<1>); - resource.pwm_frequency = &slice; - static auto chan = slice.get_pin(hal::pin<2>); - resource.pwm_channel = &chan; +sptr pwm_frequency() +{ + if (not slice) { + slice = h5::make_strong_ptr>(&memory, hal::channel<2>); + } + return slice; +} - // I wrote spi as spi_channel. Maybe rewrite to support multi-peripheral - // support later. - // static auto spi = rp::spi() +opt spi0; +sptr spi() +{ + if (not spi0) { + spi0 = h5::make_strong_ptr( + &memory, hal::pin<23>, hal::pin<20>, hal::pin<22>, hal::pin<25>); + } + return spi0; } + +} // namespace resources diff --git a/demos/resource_list.hpp b/demos/resource_list.hpp index c9f93347..3f2d8dbc 100644 --- a/demos/resource_list.hpp +++ b/demos/resource_list.hpp @@ -85,10 +85,9 @@ hal::v5::strong_ptr i2c(); hal::v5::strong_ptr interrupt_pin(); hal::v5::strong_ptr pwm(); hal::v5::strong_ptr timed_interrupt(); -hal::v5::strong_ptr pwm_channel(); -hal::v5::strong_ptr pwm_frequency(); -hal::v5::strong_ptr spi(); -hal::v5::strong_ptr spi_chip_select(); +hal::v5::strong_ptr pwm_channel(); +hal::v5::strong_ptr pwm_frequency(); +hal::v5::strong_ptr spi(); hal::v5::strong_ptr stream_dac(); hal::v5::strong_ptr dac(); hal::v5::strong_ptr watchdog(); diff --git a/include/libhal-arm-mcu/rp/adc.hpp b/include/libhal-arm-mcu/rp/adc.hpp index 5c536730..f440c57b 100644 --- a/include/libhal-arm-mcu/rp/adc.hpp +++ b/include/libhal-arm-mcu/rp/adc.hpp @@ -6,24 +6,43 @@ namespace hal::rp::inline v1 { // The ADC is 12 bit -struct adc final : public hal::adc16 +struct adc16 final : public hal::adc16 { - adc(pin_param auto pin) - : adc(pin()) + adc16(pin_param auto pin) + : adc16(pin()) { static_assert(internal::pin_max != 30 || (pin() >= 26 && pin() < 30), "ADC pin is invalid!"); static_assert(internal::pin_max != 48 || (pin() >= 40 && pin() < 48), "ADC pin is invalid!"); } - ~adc() override; + ~adc16() override; private: - adc(u8 gpio); + adc16(u8 gpio); /*Because the rp chips only have one ADC that's muxed across different pins, we just initialize and mux the ADC every time we want to read. */ u16 driver_read() override; u8 m_pin; }; + +struct adc final : public hal::adc +{ + adc(pin_param auto pin) + : adc(pin()) + { + static_assert(internal::pin_max != 30 || (pin() >= 26 && pin() < 30), + "ADC pin is invalid!"); + static_assert(internal::pin_max != 48 || (pin() >= 40 && pin() < 48), + "ADC pin is invalid!"); + } + + ~adc() override; + +private: + adc(u8 gpio); + float driver_read() override; + u8 m_pin; +}; } // namespace hal::rp::inline v1 diff --git a/include/libhal-arm-mcu/rp/pwm.hpp b/include/libhal-arm-mcu/rp/pwm.hpp index f2a1ed18..d4fec57f 100644 --- a/include/libhal-arm-mcu/rp/pwm.hpp +++ b/include/libhal-arm-mcu/rp/pwm.hpp @@ -24,56 +24,17 @@ struct pwm_pin_configuration bool autostart = true; }; -/* -Somewhat unusually, the pico contains 12 pwm -"groups" with 2 input pins each. These "groups" -are called slices, and are actually very independent -of one other. This slightly complicates synchronization, -but also allows for more independence between slices. -*/ -struct pwm_slice_runtime : hal::pwm_group_manager +/* This is the base runtime class for PWM. It cannot +be instantiated normally */ +struct pwm_slice_runtime : hal::v5::pwm_group_manager { - static constexpr u8 max_slices() - { - using enum hal::rp::internal::processor_type; - if (hal::rp::internal::type == rp2040) { - return 8; - } else if (hal::rp::internal::type == rp2350) { - return 12; - } - return 0; - } - static constexpr u8 get_slice_number(pin_param auto pin) - { - if (pin() < 32) { - return (pin() / 2) % 8; - } else { - return pin() / 2 - 8; - } - } - - pwm_slice_runtime(channel_param auto slice) - : pwm_slice_runtime(slice()) - { - static_assert(slice() < max_slices(), "Invalid PWM slice!"); - } - - using configuration = pwm_pin_configuration; - - // copied from pico sdk - static constexpr u8 pin_from_slice(u8 gpio) - { - if ((gpio) < 32) { - return ((gpio) >> 1u) & 7u; - } else { - return 8u + (((gpio) >> 1u) & 3u); - } - } + ~pwm_slice_runtime() override; protected: - pwm_pin get_pin_raw(u8 pin, configuration const&); pwm_slice_runtime(u8 slice_num); + + pwm_pin get_pin_raw(u8 pin, pwm_pin_configuration const&); /* At frequencies above ~2288 Hz, the wrap counter begins to lose resolution, reducing the duty cycle resolution @@ -83,20 +44,19 @@ struct pwm_slice_runtime : hal::pwm_group_manager u8 m_number; }; -/* -The pico has a thing called phase correct mode where -its phase is centered. No idea if it's useful, may add -if somebody needs it. -*/ -struct pwm_pin final : hal::pwm16_channel +/* This cannot be constructed normally, and needs to be + * obtained from a pwm_slice type. */ +struct pwm_pin final : hal::v5::pwm16_channel { ~pwm_pin() override; - // A false disables the timer. - void enable(bool = true); friend pwm_slice_runtime; + // not intended to be normally used. Use pwm_slice::get_pin() whenever + // possible + pwm_pin(u8 pin, pwm_pin_configuration const& c, hal::unsafe); + private: u32 driver_frequency() override; @@ -106,8 +66,6 @@ struct pwm_pin final : hal::pwm16_channel */ void driver_duty_cycle(u16 p_duty_cycle) override; - pwm_pin(u8 pin, pwm_slice_runtime::configuration const&); - u8 m_pin; u8 m_slice; bool m_autostart; @@ -117,8 +75,10 @@ struct pwm_pin final : hal::pwm16_channel aligning their phases. Set argument to false to stop all of them at once */ void enable_all_pwm(bool start = true); +/* This is the actual pwm channel type that's meant to be used. It is also + * necessary to get the pins since pins cannot be constructed on their own.*/ template -struct pwm_slice : pwm_slice_runtime +struct pwm_slice final : pwm_slice_runtime { pwm_slice(channel_param auto ch) @@ -131,14 +91,36 @@ struct pwm_slice : pwm_slice_runtime "PWM channel is invalid!"); static_assert(ch() < 11 || internal::pin_max != 48, "PWM channel is invalid!"); + static_assert(ch() < max_slices(), "Invalid PWM slice!"); } - pwm_pin get_pin(pin_param auto pin, - pwm_slice_runtime::configuration const& config = {}) + void enable(bool enable = true); + + pwm_pin get_pin(pin_param auto pin, pwm_pin_configuration const& config = {}) { static_assert(get_slice_number(pin) == chan, "Slice pin is incorrect!"); return get_pin_raw(pin(), config); } + + static constexpr u8 max_slices() + { + using enum hal::rp::internal::processor_type; + if constexpr (hal::rp::internal::type == rp2040) { + return 8; + } else if constexpr (hal::rp::internal::type == rp2350) { + return 12; + } + static_assert("Unknown RP type"); + } + + static constexpr u8 get_slice_number(pin_param auto pin) + { + if (pin() < 32) { + return (pin() / 2) % 8; + } else { + return pin() / 2 - 8; + } + } }; template pwm_slice(p pin) -> pwm_slice; diff --git a/src/rp/adc.cpp b/src/rp/adc.cpp index 0d58585e..387d6b37 100644 --- a/src/rp/adc.cpp +++ b/src/rp/adc.cpp @@ -8,7 +8,7 @@ namespace hal::rp::inline v1 { -adc::adc(u8 pin) +adc16::adc16(u8 pin) : m_pin(pin) { adc_init(); @@ -18,12 +18,12 @@ adc::adc(u8 pin) adc_gpio_init(pin); } -adc::~adc() +adc16::~adc16() { gpio_deinit(m_pin); } -u16 adc::driver_read() +u16 adc16::driver_read() { adc_select_input(m_pin - ADC_BASE_PIN); u16 result = adc_read(); @@ -37,4 +37,31 @@ u16 adc::driver_read() return result; } +adc::adc(u8 pin):m_pin(pin){ + + adc_init(); + if (pin < ADC_BASE_PIN || pin >= ADC_BASE_PIN + NUM_ADC_CHANNELS - 1) { + hal::safe_throw(hal::argument_out_of_domain(this)); + } + adc_gpio_init(pin); +} + +adc::~adc() +{ + gpio_deinit(m_pin); +} + +float adc::driver_read() +{ + adc_select_input(m_pin - ADC_BASE_PIN); + u16 result = adc_read(); + // weirdly enough the sdk doesn't provide a function to read the error bits + if (adc_hw->cs & ADC_CS_ERR_BITS) { + hal::safe_throw(hal::io_error(this)); + } + + return float(result) / float((1 << 12) - 1); +} + + } // namespace hal::rp::inline v1 diff --git a/src/rp/pwm.cpp b/src/rp/pwm.cpp index a9c7d1e6..188d5ecc 100644 --- a/src/rp/pwm.cpp +++ b/src/rp/pwm.cpp @@ -25,6 +25,11 @@ pwm_slice_runtime::pwm_slice_runtime(u8 num) pwm_init(num, &config, false); } +pwm_slice_runtime::~pwm_slice_runtime() +{ + pwm_set_enabled(m_number, false); +} + void pwm_slice_runtime::driver_frequency(u32 f) { // frequency too high @@ -57,12 +62,12 @@ void pwm_slice_runtime::driver_frequency(u32 f) pwm_set_clkdiv(m_number, clock_div); } -pwm_pin pwm_slice_runtime::get_pin_raw(u8 pin, configuration const& c) +pwm_pin pwm_slice_runtime::get_pin_raw(u8 pin, pwm_pin_configuration const& c) { - return { pin, c }; + return pwm_pin(pin, c, {}); } -pwm_pin::pwm_pin(u8 pin, pwm_slice_runtime::configuration const& c) +pwm_pin::pwm_pin(u8 pin, pwm_pin_configuration const& c, hal::unsafe) : m_pin(pin) , m_slice(pwm_gpio_to_slice_num(pin)) , m_autostart(c.autostart) @@ -73,7 +78,6 @@ pwm_pin::pwm_pin(u8 pin, pwm_slice_runtime::configuration const& c) pwm_pin::~pwm_pin() { - pwm_set_enabled(m_slice, false); gpio_deinit(m_pin); } @@ -82,7 +86,6 @@ void pwm_pin::driver_duty_cycle(u16 duty) auto channel = pwm_gpio_to_channel(m_pin); if (duty == 0) { pwm_set_chan_level(m_slice, channel, 0); - pwm_set_enabled(m_slice, false); return; } @@ -113,9 +116,10 @@ void enable_all_pwm(bool start) } } -void pwm_pin::enable(bool enable) +template +void pwm_slice::enable(bool enable) { - pwm_set_enabled(m_slice, enable); + pwm_set_enabled(s, enable); } u32 pwm_pin::driver_frequency() From 6ed262f22147af3488a5a6331e2c50f14bcd06fc Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Fri, 19 Sep 2025 19:38:42 -0700 Subject: [PATCH 29/55] create a header file to get stuff to build --- CMakeLists.txt | 3 ++- create.sh | 2 +- src/rp/boards/rp2350b_w25q080.h | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 src/rp/boards/rp2350b_w25q080.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 9329e4ef..25c32a09 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,9 +16,10 @@ cmake_minimum_required(VERSION 3.15) if(DEFINED ENV{PICO_SDK_PATH}) set(PICO_CXX_ENABLE_EXCEPTIONS 1) -set(PICO_BOARD adafruit_feather_rp2350) set(CMAKE_ASM_FLAGS_INIT "-mcpu=cortex-m33 -mfloat-abi=soft") set(PICO_NO_PICOTOOL 1) +list(APPEND PICO_BOARD_HEADER_DIRS ${CMAKE_SOURCE_DIR}/src/rp/boards) +set(PICO_BOARD rp2350b_w25q080) include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake) endif() diff --git a/create.sh b/create.sh index d5e36828..f4236800 100644 --- a/create.sh +++ b/create.sh @@ -1,4 +1,4 @@ #!/usr/bin/sh yes | conan remove libhal-arm-mcu -conan create . -pr:h=rp2350 -pr:h=arm-gcc-12.3 --version=latest +conan create . -pr:h=rp2350b -pr:h=arm-gcc-12.3 --version=latest diff --git a/src/rp/boards/rp2350b_w25q080.h b/src/rp/boards/rp2350b_w25q080.h new file mode 100644 index 00000000..54c71e5c --- /dev/null +++ b/src/rp/boards/rp2350b_w25q080.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2025 Shin Umeda + * + * This is a board file that gets pico-sdk headers to work at the bare minimum. + * This doesn't define much by itself, and nothing should depend too much on definitions + * here. + * + * */ + +#ifndef _rp2350_micromod_h +#define _rp2350_micromod_h + +pico_board_cmake_set(PICO_PLATFORM, rp2350) + +#define PICO_RP2350A 0 // use RP2350B + +#define PICO_BOOT_STAGE2_CHOOSE_W25Q080 1 + +#ifndef PICO_FLASH_SPI_CLKDIV +#define PICO_FLASH_SPI_CLKDIV 2 +#endif + +pico_board_cmake_set_default(PICO_FLASH_SIZE_BYTES, (8 * 1024 * 1024)) +#ifndef PICO_FLASH_SIZE_BYTES +#define PICO_FLASH_SIZE_BYTES (8 * 1024 * 1024) +#endif + +pico_board_cmake_set_default(PICO_RP2350_A2_SUPPORTED, 1) +#ifndef PICO_RP2350_A2_SUPPORTED +#define PICO_RP2350_A2_SUPPORTED 1 +#endif + +#endif From d2dc6c6fdedda2b6cf5051ba6c5d024fa015964d Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Fri, 26 Sep 2025 13:35:04 -0700 Subject: [PATCH 30/55] switch certain interfaces to v4, and switch namespace default to v4 instead of v1. --- include/libhal-arm-mcu/rp/adc.hpp | 38 +++++++++++-------- include/libhal-arm-mcu/rp/i2c.hpp | 4 +- include/libhal-arm-mcu/rp/input_pin.hpp | 4 +- include/libhal-arm-mcu/rp/interrupt_pin.hpp | 4 +- include/libhal-arm-mcu/rp/output_pin.hpp | 4 +- include/libhal-arm-mcu/rp/pwm.hpp | 6 +-- include/libhal-arm-mcu/rp/serial.hpp | 4 +- include/libhal-arm-mcu/rp/spi.hpp | 42 ++++++++++++++++++--- include/libhal-arm-mcu/rp/time.hpp | 4 +- src/rp/adc.cpp | 38 +++++++++++-------- src/rp/gpio.cpp | 31 ++++----------- src/rp/i2c.cpp | 5 ++- src/rp/pwm.cpp | 4 +- src/rp/serial.cpp | 4 +- src/rp/spi.cpp | 30 +++++++++++++-- src/rp/time.cpp | 4 +- 16 files changed, 138 insertions(+), 88 deletions(-) diff --git a/include/libhal-arm-mcu/rp/adc.hpp b/include/libhal-arm-mcu/rp/adc.hpp index f440c57b..e32b7eba 100644 --- a/include/libhal-arm-mcu/rp/adc.hpp +++ b/include/libhal-arm-mcu/rp/adc.hpp @@ -3,46 +3,52 @@ #include "rp.hpp" #include -namespace hal::rp::inline v1 { +namespace hal::rp { -// The ADC is 12 bit -struct adc16 final : public hal::adc16 +inline namespace v4 { +struct adc final : public hal::adc { - adc16(pin_param auto pin) - : adc16(pin()) + adc(pin_param auto pin) + : adc(pin()) { static_assert(internal::pin_max != 30 || (pin() >= 26 && pin() < 30), "ADC pin is invalid!"); static_assert(internal::pin_max != 48 || (pin() >= 40 && pin() < 48), "ADC pin is invalid!"); } - ~adc16() override; + ~adc() override; private: - adc16(u8 gpio); + adc(u8 pin); /*Because the rp chips only have one ADC that's muxed across different pins, we just initialize and mux the ADC every time we want to read. */ - u16 driver_read() override; + float driver_read() override; u8 m_pin; }; +} // namespace v4 -struct adc final : public hal::adc +namespace v5 { +// The ADC is 12 bit +struct adc16 final : public hal::adc16 { - adc(pin_param auto pin) - : adc(pin()) + adc16(pin_param auto pin) + : adc16(pin()) { static_assert(internal::pin_max != 30 || (pin() >= 26 && pin() < 30), "ADC pin is invalid!"); static_assert(internal::pin_max != 48 || (pin() >= 40 && pin() < 48), "ADC pin is invalid!"); } - - ~adc() override; + ~adc16() override; private: - adc(u8 gpio); - float driver_read() override; + adc16(u8 gpio); + /*Because the rp chips only have one ADC that's muxed + across different pins, we just initialize and mux the ADC + every time we want to read. */ + u16 driver_read() override; u8 m_pin; }; -} // namespace hal::rp::inline v1 +} // namespace v5 +} // namespace hal::rp diff --git a/include/libhal-arm-mcu/rp/i2c.hpp b/include/libhal-arm-mcu/rp/i2c.hpp index a256a64a..1edeed05 100644 --- a/include/libhal-arm-mcu/rp/i2c.hpp +++ b/include/libhal-arm-mcu/rp/i2c.hpp @@ -5,7 +5,7 @@ #include #include -namespace hal::rp::inline v1 { +namespace hal::rp::inline v4 { /* RP2350 supports a baud rate of up to 1 MHz */ @@ -42,4 +42,4 @@ struct i2c final : public hal::i2c u8 m_sda, m_scl, m_chan; }; -} // namespace hal::rp::inline v1 +} // namespace hal::rp::inline v4 diff --git a/include/libhal-arm-mcu/rp/input_pin.hpp b/include/libhal-arm-mcu/rp/input_pin.hpp index 4f6fd898..de524b52 100644 --- a/include/libhal-arm-mcu/rp/input_pin.hpp +++ b/include/libhal-arm-mcu/rp/input_pin.hpp @@ -3,7 +3,7 @@ #include "rp.hpp" #include -namespace hal::rp::inline v1 { +namespace hal::rp::inline v4 { struct input_pin final : public hal::input_pin { @@ -20,4 +20,4 @@ struct input_pin final : public hal::input_pin u8 m_pin{}; }; -} // namespace hal::rp::inline v1 +} // namespace hal::rp::inline v4 diff --git a/include/libhal-arm-mcu/rp/interrupt_pin.hpp b/include/libhal-arm-mcu/rp/interrupt_pin.hpp index a35248e8..8dda01f5 100644 --- a/include/libhal-arm-mcu/rp/interrupt_pin.hpp +++ b/include/libhal-arm-mcu/rp/interrupt_pin.hpp @@ -4,7 +4,7 @@ #include #include -namespace hal::rp::inline v1 { +namespace hal::rp::inline v4 { /* Interrupt pin uses hidden globals to implement interrupts because the interrupt callback function doesn't quite match the hal::handler type. @@ -27,4 +27,4 @@ struct interrupt_pin final : public hal::interrupt_pin void driver_on_trigger(hal::callback) override; u8 m_pin; }; -} // namespace hal::rp::inline v1 +} // namespace hal::rp::inline v4 diff --git a/include/libhal-arm-mcu/rp/output_pin.hpp b/include/libhal-arm-mcu/rp/output_pin.hpp index f9a2bea7..3a7c4c95 100644 --- a/include/libhal-arm-mcu/rp/output_pin.hpp +++ b/include/libhal-arm-mcu/rp/output_pin.hpp @@ -3,7 +3,7 @@ #include "rp.hpp" #include -namespace hal::rp::inline v1 { +namespace hal::rp::inline v4 { struct output_pin final : public hal::output_pin { @@ -22,4 +22,4 @@ struct output_pin final : public hal::output_pin u8 m_pin{}; }; -} // namespace hal::rp::inline v1 +} // namespace hal::rp::inline v4 diff --git a/include/libhal-arm-mcu/rp/pwm.hpp b/include/libhal-arm-mcu/rp/pwm.hpp index d4fec57f..01d91c88 100644 --- a/include/libhal-arm-mcu/rp/pwm.hpp +++ b/include/libhal-arm-mcu/rp/pwm.hpp @@ -6,8 +6,7 @@ #include #include -namespace hal::rp::inline v1 { - +namespace hal::rp::v5 { enum struct pwm_ch : u8 { a, @@ -124,5 +123,4 @@ struct pwm_slice final : pwm_slice_runtime }; template pwm_slice(p pin) -> pwm_slice; - -} // namespace hal::rp::inline v1 +} // namespace hal::rp::v5 diff --git a/include/libhal-arm-mcu/rp/serial.hpp b/include/libhal-arm-mcu/rp/serial.hpp index c5a53317..0f033e06 100644 --- a/include/libhal-arm-mcu/rp/serial.hpp +++ b/include/libhal-arm-mcu/rp/serial.hpp @@ -3,7 +3,7 @@ #include "rp.hpp" #include -namespace hal::rp::inline v1 { +namespace hal::rp::inline v4 { /* The RP series chips use a native ROM USB bootloader, meaning most setups use USB directly, necessitating @@ -63,4 +63,4 @@ struct uart final : public hal::serial u8 m_tx, m_rx, m_bus; }; -} // namespace hal::rp::inline v1 +} // namespace hal::rp::inline v4 diff --git a/include/libhal-arm-mcu/rp/spi.hpp b/include/libhal-arm-mcu/rp/spi.hpp index 3d7f9546..24543e2a 100644 --- a/include/libhal-arm-mcu/rp/spi.hpp +++ b/include/libhal-arm-mcu/rp/spi.hpp @@ -3,7 +3,40 @@ #include "rp.hpp" #include -namespace hal::rp::inline v1 { +namespace hal::rp { +constexpr u8 bus_from_tx_pin(u8 tx) +{ + return (tx / 8) % 2; +} + +inline namespace v4 { +// Backfill because much of libhal will stay on v4 for the time being +struct spi final : public hal::spi +{ + spi(pin_param auto copi, + pin_param auto cipo, + pin_param auto sck, + spi::settings const& options = {}) + : spi(bus_from_tx_pin(copi()), copi(), cipo(), sck(), options) + { + static_assert(cipo() % 4 == 0, "SPI RX pin is invalid"); + static_assert(sck() % 4 == 2, "SPI SCK pin is invalid"); + static_assert(copi() % 4 == 3, "SPI CS pin is invalid"); + } + ~spi() override; + +private: + spi(u8 bus, u8 copi, u8 cipo, u8 sck, spi::settings const&); + void driver_configure(settings const&) override; + + void driver_transfer(std::span out, + std::span in, + byte) override; + + u8 m_bus, m_tx, m_rx, m_sck; +}; +} // namespace v4 +namespace v5 { /* RP chips suppport 16 bit transfers. It may be worthwhile to add an option to transfer 16 bits as a time. @@ -22,7 +55,6 @@ struct spi final : public hal::spi_channel // CS is a normal gpio static_assert(sck() % 4 == 2, "SPI SCK pin is invalid"); static_assert(copi() % 4 == 3, "SPI CS pin is invalid"); - } ~spi() override; @@ -37,8 +69,6 @@ struct spi final : public hal::spi_channel byte) override; u8 m_bus, m_tx, m_rx, m_sck, m_cs; - - constexpr u8 bus_from_tx_pin(u8); }; - -} // namespace hal::rp::inline v1 +} // namespace v5 +} // namespace hal::rp diff --git a/include/libhal-arm-mcu/rp/time.hpp b/include/libhal-arm-mcu/rp/time.hpp index c1cc2bf0..9fdc6780 100644 --- a/include/libhal-arm-mcu/rp/time.hpp +++ b/include/libhal-arm-mcu/rp/time.hpp @@ -2,7 +2,7 @@ #include -namespace hal::rp::inline v1 { +namespace hal::rp::inline v4 { // This has to be the least interesting clock ever struct clock final : public hal::steady_clock @@ -12,4 +12,4 @@ struct clock final : public hal::steady_clock u64 driver_uptime() override; }; -} // namespace hal::rp::inline v1 +} // namespace hal::rp::inline v4 diff --git a/src/rp/adc.cpp b/src/rp/adc.cpp index 387d6b37..46367aa3 100644 --- a/src/rp/adc.cpp +++ b/src/rp/adc.cpp @@ -5,10 +5,12 @@ #include #include #include +#include -namespace hal::rp::inline v1 { +namespace hal::rp { -adc16::adc16(u8 pin) +inline namespace v4 { +adc::adc(u8 pin) : m_pin(pin) { adc_init(); @@ -18,12 +20,12 @@ adc16::adc16(u8 pin) adc_gpio_init(pin); } -adc16::~adc16() +adc::~adc() { gpio_deinit(m_pin); } -u16 adc16::driver_read() +float adc::driver_read() { adc_select_input(m_pin - ADC_BASE_PIN); u16 result = adc_read(); @@ -31,14 +33,16 @@ u16 adc16::driver_read() if (adc_hw->cs & ADC_CS_ERR_BITS) { hal::safe_throw(hal::io_error(this)); } - // TODO: Use hal::upscale to actually make this 16 bit - // for some reason the docs reference a function that - // doesn't exist yet - return result; + + return float(result) / float((1 << 12) - 1); } +}; // namespace v4 + +namespace v5 { -adc::adc(u8 pin):m_pin(pin){ - +adc16::adc16(u8 pin) + : m_pin(pin) +{ adc_init(); if (pin < ADC_BASE_PIN || pin >= ADC_BASE_PIN + NUM_ADC_CHANNELS - 1) { hal::safe_throw(hal::argument_out_of_domain(this)); @@ -46,12 +50,12 @@ adc::adc(u8 pin):m_pin(pin){ adc_gpio_init(pin); } -adc::~adc() +adc16::~adc16() { gpio_deinit(m_pin); } -float adc::driver_read() +u16 adc16::driver_read() { adc_select_input(m_pin - ADC_BASE_PIN); u16 result = adc_read(); @@ -59,9 +63,11 @@ float adc::driver_read() if (adc_hw->cs & ADC_CS_ERR_BITS) { hal::safe_throw(hal::io_error(this)); } - - return float(result) / float((1 << 12) - 1); + // TODO: Use hal::upscale to actually make this 16 bit + // for some reason the docs reference a function that + // doesn't exist yet + return result; } - -} // namespace hal::rp::inline v1 +} // namespace v5 +} // namespace hal::rp diff --git a/src/rp/gpio.cpp b/src/rp/gpio.cpp index 43cdf516..85e8ae5d 100644 --- a/src/rp/gpio.cpp +++ b/src/rp/gpio.cpp @@ -15,7 +15,6 @@ #include #include #include -#include #include namespace { @@ -129,7 +128,7 @@ struct interrupt_manager } // namespace -namespace hal::rp::inline v1 { +namespace hal::rp::inline v4 { void sleep_ms(uint32_t ms) { ::sleep_ms(ms); @@ -236,27 +235,13 @@ interrupt_pin::interrupt_pin(u8 pin, gpio_init(pin); gpio_set_dir(pin, false); gpio_set_function(pin, gpio_function_t::GPIO_FUNC_SIO); - - auto g = interrupt_manager::get(); - g->insert(m_pin, - { .edge = options.trigger, .callback = std::move(callback) }); - - // can't use driver_configure because it would cause the lock to be taken - // twice - switch (options.resistor) { - case pin_resistor::none: - gpio_disable_pulls(m_pin); - break; - case pin_resistor::pull_up: - gpio_pull_up(m_pin); - break; - // pulldown seems reasonable, although this should never trigger anyways - default: - [[fallthrough]]; - case pin_resistor::pull_down: - gpio_pull_down(m_pin); - break; + { + auto g = interrupt_manager::get(); + g->insert(m_pin, + { .edge = options.trigger, .callback = std::move(callback) }); + // drop the lock } + driver_configure(options); } interrupt_pin::~interrupt_pin() @@ -291,4 +276,4 @@ void interrupt_pin::driver_on_trigger(hal::callback callback) std::swap(g->at(m_pin).callback, callback); } -} // namespace hal::rp::inline v1 +} // namespace hal::rp::inline v4 diff --git a/src/rp/i2c.cpp b/src/rp/i2c.cpp index d9b5c523..4f77daae 100644 --- a/src/rp/i2c.cpp +++ b/src/rp/i2c.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include // pico macros interfere with ours @@ -24,7 +25,7 @@ auto inst(hal::u8 c, void* instance) } } // namespace -namespace hal::rp::inline v1 { +namespace hal::rp::inline v4 { i2c::i2c(u8 sda, u8 scl, u8 chan, settings const& options) // NOLINT : m_sda(sda) , m_scl(scl) @@ -76,4 +77,4 @@ void i2c::driver_transaction(hal::byte addr, timeout(); } -} // namespace hal::rp::inline v1 +} // namespace hal::rp::inline v4 diff --git a/src/rp/pwm.cpp b/src/rp/pwm.cpp index 188d5ecc..33b9b5f6 100644 --- a/src/rp/pwm.cpp +++ b/src/rp/pwm.cpp @@ -13,7 +13,7 @@ frequency it should run at */ -namespace hal::rp::inline v1 { +namespace hal::rp::v5 { pwm_slice_runtime::pwm_slice_runtime(u8 num) : m_number(num) @@ -137,4 +137,4 @@ u32 pwm_pin::driver_frequency() return static_cast(static_cast(top) * divider); } -} // namespace hal::rp::inline v1 +} // namespace hal::rp::v5 diff --git a/src/rp/serial.cpp b/src/rp/serial.cpp index 4f3a3b6a..4aebf6c9 100644 --- a/src/rp/serial.cpp +++ b/src/rp/serial.cpp @@ -22,7 +22,7 @@ auto get_uart(hal::u8 bus) } } // namespace -namespace hal::rp::inline v1 { +namespace hal::rp::inline v4 { stdio_serial::stdio_serial() { stdio_init_all(); @@ -125,4 +125,4 @@ serial::read_t uart::driver_read(std::span out) .capacity = 32 }; } -} // namespace hal::rp::inline v1 +} // namespace hal::rp::inline v4 diff --git a/src/rp/spi.cpp b/src/rp/spi.cpp index 1f03f32b..d1f9c640 100644 --- a/src/rp/spi.cpp +++ b/src/rp/spi.cpp @@ -18,8 +18,32 @@ auto spi_bus(hal::u8 busnum) } } } // namespace -namespace hal::rp::inline v1 { +namespace hal::rp { + +inline namespace v4 { // TODO shuddup clang-tidy I'll fix it later +spi::spi(u8 bus, u8 tx, u8 rx, u8 sck, spi::settings const& s) // NOLINT + : m_bus(bus) + , m_tx(tx) + , m_rx(rx) + , m_sck(sck) +{ + driver_configure(s); + gpio_set_function(tx, GPIO_FUNC_SPI); + gpio_set_function(rx, GPIO_FUNC_SPI); + gpio_set_function(sck, GPIO_FUNC_SPI); +} +void spi::driver_configure(spi::settings const& s) +{ + spi_cpol_t polarity = s.clock_polarity ? SPI_CPOL_1 : SPI_CPOL_0; + spi_cpha_t phase = s.clock_phase ? SPI_CPHA_1 : SPI_CPHA_0; + + spi_init(spi_bus(m_bus), static_cast(s.clock_rate)); + spi_set_format(spi_bus(m_bus), 8, polarity, phase, SPI_MSB_FIRST); +} +} // namespace v4 + +namespace v5 { spi::spi(u8 bus, u8 tx, u8 rx, u8 sck, u8 cs, spi::settings const& s) // NOLINT : m_bus(bus) , m_tx(tx) @@ -107,5 +131,5 @@ spi::~spi() gpio_deinit(m_cs); spi_deinit(spi_bus(m_bus)); } - -} // namespace hal::rp::inline v1 +} // namespace v5 +} // namespace hal::rp diff --git a/src/rp/time.cpp b/src/rp/time.cpp index 2f608102..cbfb8ba8 100644 --- a/src/rp/time.cpp +++ b/src/rp/time.cpp @@ -4,7 +4,7 @@ #include #include -namespace hal::rp::inline v1 { +namespace hal::rp::inline v4 { hertz clock::driver_frequency() { @@ -16,4 +16,4 @@ u64 clock::driver_uptime() return time_us_64(); } -} // namespace hal::rp::inline v1 +} // namespace hal::rp::inline v4 From 99fb1b55153beb1a1e318055a299b51ac23bfe94 Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Fri, 26 Sep 2025 13:57:55 -0700 Subject: [PATCH 31/55] ported pwm to v4 --- include/libhal-arm-mcu/rp/pwm.hpp | 28 +++++++++++++-- src/rp/pwm.cpp | 59 +++++++++++++++++++++++++++++-- 2 files changed, 83 insertions(+), 4 deletions(-) diff --git a/include/libhal-arm-mcu/rp/pwm.hpp b/include/libhal-arm-mcu/rp/pwm.hpp index 01d91c88..b25e0c07 100644 --- a/include/libhal-arm-mcu/rp/pwm.hpp +++ b/include/libhal-arm-mcu/rp/pwm.hpp @@ -6,7 +6,30 @@ #include #include -namespace hal::rp::v5 { +namespace hal::rp { + +inline namespace v4 { +// this a bad backport to v4, since the pwm interface cannot +// guarantee two pwm's won't interfere +struct pwm_pin final : hal::pwm +{ + // By constructing this class you forfeit all claims to any + // warranty, implied or otherwise. You acknowledge you will + // read the relevant datasheet (RP2350 or RP2040 datasheet) + // and verify they are on different PWM slices. You waive + // any right to complain about two different PWM pins interfering + // with each other because they are on the same slice. + pwm_pin(hal::unsafe, u8 pin); + +private: + void driver_frequency(hertz p_frequency) override; + void driver_duty_cycle(float p_duty_cycle) override; + + u8 m_pin; +}; +} // namespace v4 + +namespace v5 { enum struct pwm_ch : u8 { a, @@ -123,4 +146,5 @@ struct pwm_slice final : pwm_slice_runtime }; template pwm_slice(p pin) -> pwm_slice; -} // namespace hal::rp::v5 +} // namespace v5 +} // namespace hal::rp diff --git a/src/rp/pwm.cpp b/src/rp/pwm.cpp index 33b9b5f6..77e41c50 100644 --- a/src/rp/pwm.cpp +++ b/src/rp/pwm.cpp @@ -13,7 +13,61 @@ frequency it should run at */ -namespace hal::rp::v5 { +namespace hal::rp { +inline namespace v4 { +pwm_pin::pwm_pin(hal::unsafe, u8 pin) + : m_pin(pin) +{ +} + +void pwm_pin::driver_duty_cycle(float duty) +{ + uint channel = pwm_gpio_to_channel(m_pin); + uint slice = pwm_gpio_to_slice_num(m_pin); + if (duty == 0) { + pwm_set_chan_level(slice, channel, 0); + return; + } + + float percentage = float(duty) / std::numeric_limits::max(); + u16 top = pwm_hw->slice[slice].top; + pwm_set_chan_level(slice, channel, u16(float(top) * percentage)); +} + +void pwm_pin::driver_frequency(float f) +{ + if (f > SYS_CLK_HZ) { + hal::safe_throw(argument_out_of_domain(this)); + } + + auto frequency = static_cast(f); + auto clock = static_cast(SYS_CLK_HZ); + // We try to adjust clock divider to maximize counter resolution + float wrap_val = std::numeric_limits::max(); + float clock_div = clock / frequency / wrap_val; + // cap the clock divider at 256 + if (clock_div > 256) { + clock_div = 256; + wrap_val = clock / frequency / 256; + } + // maintain a minimum clock divider of 1 + if (clock_div < 1.0) { + clock_div = 1; + wrap_val = clock / frequency; + } + + // frequency too low + if (wrap_val > std::numeric_limits::max()) { + hal::safe_throw(argument_out_of_domain(this)); + } + + uint slice = pwm_gpio_to_slice_num(m_pin); + pwm_set_wrap(slice, static_cast(wrap_val)); + pwm_set_clkdiv(slice, clock_div); +} +} // namespace v4 + +namespace v5 { pwm_slice_runtime::pwm_slice_runtime(u8 num) : m_number(num) @@ -137,4 +191,5 @@ u32 pwm_pin::driver_frequency() return static_cast(static_cast(top) * divider); } -} // namespace hal::rp::v5 +} // namespace v5 +} // namespace hal::rp From 11cd5e2c545c8d7a8bd3a285152315873e4a0dfc Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Fri, 26 Sep 2025 14:08:53 -0700 Subject: [PATCH 32/55] ported pwm to v4 interface --- include/libhal-arm-mcu/rp/pwm.hpp | 5 +++-- src/rp/pwm.cpp | 9 +++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/include/libhal-arm-mcu/rp/pwm.hpp b/include/libhal-arm-mcu/rp/pwm.hpp index b25e0c07..a75fcfd5 100644 --- a/include/libhal-arm-mcu/rp/pwm.hpp +++ b/include/libhal-arm-mcu/rp/pwm.hpp @@ -20,6 +20,7 @@ struct pwm_pin final : hal::pwm // any right to complain about two different PWM pins interfering // with each other because they are on the same slice. pwm_pin(hal::unsafe, u8 pin); + ~pwm_pin() override; private: void driver_frequency(hertz p_frequency) override; @@ -48,7 +49,7 @@ struct pwm_pin_configuration /* This is the base runtime class for PWM. It cannot be instantiated normally */ -struct pwm_slice_runtime : hal::v5::pwm_group_manager +struct pwm_slice_runtime : hal::pwm_group_manager { ~pwm_slice_runtime() override; @@ -68,7 +69,7 @@ struct pwm_slice_runtime : hal::v5::pwm_group_manager /* This cannot be constructed normally, and needs to be * obtained from a pwm_slice type. */ -struct pwm_pin final : hal::v5::pwm16_channel +struct pwm_pin final : hal::pwm16_channel { ~pwm_pin() override; diff --git a/src/rp/pwm.cpp b/src/rp/pwm.cpp index 77e41c50..6c35b4c4 100644 --- a/src/rp/pwm.cpp +++ b/src/rp/pwm.cpp @@ -18,6 +18,14 @@ inline namespace v4 { pwm_pin::pwm_pin(hal::unsafe, u8 pin) : m_pin(pin) { + gpio_set_function(m_pin, GPIO_FUNC_PWM); + auto config = pwm_get_default_config(); + pwm_init(pwm_gpio_to_slice_num(m_pin), &config, false); +} + +pwm_pin::~pwm_pin() +{ + gpio_deinit(m_pin); } void pwm_pin::driver_duty_cycle(float duty) @@ -32,6 +40,7 @@ void pwm_pin::driver_duty_cycle(float duty) float percentage = float(duty) / std::numeric_limits::max(); u16 top = pwm_hw->slice[slice].top; pwm_set_chan_level(slice, channel, u16(float(top) * percentage)); + pwm_set_enabled(slice, true); } void pwm_pin::driver_frequency(float f) From 7d6cc4a02ae48d06fead5de365f55776b568646e Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Fri, 26 Sep 2025 14:26:28 -0700 Subject: [PATCH 33/55] fix spi v4 implementation --- src/rp/spi.cpp | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/rp/spi.cpp b/src/rp/spi.cpp index d1f9c640..813f9830 100644 --- a/src/rp/spi.cpp +++ b/src/rp/spi.cpp @@ -33,6 +33,7 @@ spi::spi(u8 bus, u8 tx, u8 rx, u8 sck, spi::settings const& s) // NOLINT gpio_set_function(rx, GPIO_FUNC_SPI); gpio_set_function(sck, GPIO_FUNC_SPI); } + void spi::driver_configure(spi::settings const& s) { spi_cpol_t polarity = s.clock_polarity ? SPI_CPOL_1 : SPI_CPOL_0; @@ -41,6 +42,32 @@ void spi::driver_configure(spi::settings const& s) spi_init(spi_bus(m_bus), static_cast(s.clock_rate)); spi_set_format(spi_bus(m_bus), 8, polarity, phase, SPI_MSB_FIRST); } + +void spi::driver_transfer(std::span out, + std::span in, + byte filler) +{ + auto out_size = out.size_bytes(); + auto in_size = in.size_bytes(); + auto size = std::min(out_size, in_size); + spi_write_read_blocking(spi_bus(m_bus), out.data(), in.data(), size); + if (out_size > in_size) { + spi_write_blocking( + spi_bus(m_bus), out.data() + in_size, out_size - in_size); + } else if (in_size > out_size) { + spi_read_blocking( + spi_bus(m_bus), filler, in.data() + out_size, in_size - out_size); + } +} + +spi::~spi() +{ + gpio_deinit(m_tx); + gpio_deinit(m_rx); + gpio_deinit(m_sck); + spi_deinit(spi_bus(m_bus)); +} + } // namespace v4 namespace v5 { From 7a460e4f2445166890e67dba8cb5b270a1e97a5b Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Fri, 26 Sep 2025 15:25:02 -0700 Subject: [PATCH 34/55] builds, does not seem to work. Todo build openocd --- demos/CMakeLists.txt | 6 ++-- demos/conanfile.py | 7 +++-- demos/flash.sh | 11 +++++++ demos/main.cpp | 41 ++++++------------------- demos/platforms/rp2350-arm-s.cpp | 52 ++++++++++++++++++++++---------- demos/resource_list.hpp | 8 +++-- demos/run.sh | 2 +- 7 files changed, 69 insertions(+), 58 deletions(-) create mode 100755 demos/flash.sh diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt index f61498b8..af868473 100644 --- a/demos/CMakeLists.txt +++ b/demos/CMakeLists.txt @@ -39,15 +39,15 @@ set(demo_deps # not all of the demos work at the moment set(demos - # ADC demo needs to be updated to ADC16 - # adc + adc blinker - # No canbus until I can figure out software can # can + # dac gpio i2c interrupt_pin uart + pwm pwm16 spi blank diff --git a/demos/conanfile.py b/demos/conanfile.py index ed770aaf..59761798 100644 --- a/demos/conanfile.py +++ b/demos/conanfile.py @@ -14,6 +14,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +import os + from conan import ConanFile from conan.tools.cmake import CMake @@ -26,6 +28,7 @@ def requirements(self): self.requires("libhal-arm-mcu/[1.9.1 || latest]") self.tool_requires("picotool/2.1.1") self.requires("minimp3/cci.20211201") + self.requires("micromod-header/1.0.0") # This is kinda sketch, but needs to be done manually until https://github.com/conan-io/conan/issues/13372 # gets implemented @@ -33,9 +36,7 @@ def build(self): cmake = CMake(self) defs = { "CMAKE_ASM_FLAGS_INIT": "-mcpu=cortex-m33 -mfloat-abi=soft", - # For some reason even if I set PICO_FLASH_SIZE with PICO_BOARD=none, - # it still doesn't work. I can't explain why. - "PICO_BOARD": "adafruit_feather_rp2350", + "PICO_BOARD": "rp2350_micromod", "PICO_CXX_ENABLE_EXCEPTIONS": "1" } cmake.configure(variables = defs) diff --git a/demos/flash.sh b/demos/flash.sh new file mode 100755 index 00000000..645e735c --- /dev/null +++ b/demos/flash.sh @@ -0,0 +1,11 @@ +#!/usr/bin/sh + +set -e + +BUILDDIR=build/rp2350-arm-s/RelWithDebInfo +source $BUILDDIR/generators/conanbuild.sh + +picotool uf2 convert $BUILDDIR/$1.elf $BUILDDIR/$1.uf2 +picotool load $BUILDDIR/$1.uf2 -f +sleep 1 +plink -serial /dev/ttyACM0 -sercfg 115200,8,1,n,X diff --git a/demos/main.cpp b/demos/main.cpp index 7fa7ccb8..2cfa3b23 100644 --- a/demos/main.cpp +++ b/demos/main.cpp @@ -15,33 +15,12 @@ #include #include #include +#include #include "resource_list.hpp" -resource_list resources{}; - [[noreturn]] void terminate_handler() noexcept { - if (resources.console) { - hal::print(*resources.console.value(), "☠️ APPLICATION TERMINATED ☠️\n\n"); - } - - if (resources.status_led && resources.clock) { - auto& led = *resources.status_led.value(); - auto& clock = *resources.clock.value(); - - while (true) { - using namespace std::chrono_literals; - led.level(false); - hal::delay(clock, 100ms); - led.level(true); - hal::delay(clock, 100ms); - led.level(false); - hal::delay(clock, 100ms); - led.level(true); - hal::delay(clock, 1000ms); - } - } // spin here forever while (true) { @@ -53,17 +32,15 @@ int main() { hal::set_terminate(terminate_handler); - initialize_platform(resources); - try { - application(resources); - } catch (std::bad_optional_access const& e) { - if (resources.console) { - hal::print(*resources.console.value(), - "A resource required by the application was not" - "available!\n" - "Calling terminate!\n"); - } + application(); + } catch (hal::bad_optional_ptr_access const& e) { + auto console = resources::console(); + hal::print(*console, + "A resource required by the application was not" + "available!\n" + "Calling terminate!\n"); + } // Allow any other exceptions to terminate the application std::terminate(); diff --git a/demos/platforms/rp2350-arm-s.cpp b/demos/platforms/rp2350-arm-s.cpp index 836cf1cc..c6379a0f 100644 --- a/demos/platforms/rp2350-arm-s.cpp +++ b/demos/platforms/rp2350-arm-s.cpp @@ -1,3 +1,5 @@ +#include "resource_list.hpp" + #include #include #include @@ -8,10 +10,10 @@ #include #include #include +#include #include #include #include -#include namespace rp = hal::rp; @@ -55,7 +57,7 @@ sptr status_led() { if (not led) { // Actually for adafruit board. Change once micromods arrive. - led = h5::make_strong_ptr(&memory, hal::pin<7>); + led = h5::make_strong_ptr(&memory, hal::pin<46>); } return led; } @@ -64,7 +66,7 @@ opt adc0; sptr adc() { if (not adc0) { - adc0 = h5::make_strong_ptr(&memory, hal::pin<26>); + adc0 = h5::make_strong_ptr(&memory, hal::pin<40>); } return adc0; } @@ -83,7 +85,7 @@ sptr i2c() { if (not i2c0) { i2c0 = h5::make_strong_ptr( - &memory, hal::pin<2>, hal::pin<3>, hal::bus<1>); + &memory, hal::pin<16>, hal::pin<17>, hal::bus<0>); } return i2c0; } @@ -102,35 +104,53 @@ sptr interrupt_pin() // TODO: add timer sptr timed_interrupt(); -opt slice; -opt pwm_pin; +opt slice; +opt pwm_pin; + +sptr pwm_frequency() +{ + if (not slice) { + slice = h5::make_strong_ptr>(&memory, hal::channel<7>); + } + return slice; +} -sptr pwm_channel() +sptr pwm_channel() { (void)pwm_frequency(); // Need to initalize this first if (not pwm_pin) { - pwm_pin = h5::make_strong_ptr( - &memory, 5, hal::rp::pwm_pin_configuration{}, hal::unsafe{}); + pwm_pin = h5::make_strong_ptr( + &memory, 31, hal::rp::v5::pwm_pin_configuration{}, hal::unsafe{}); } return pwm_pin; } -sptr pwm_frequency() +opt ppin; +sptr pwm() { - if (not slice) { - slice = h5::make_strong_ptr>(&memory, hal::channel<2>); + if (not ppin) { + ppin = h5::make_strong_ptr(&memory, hal::unsafe{}, 32); } - return slice; + return ppin; } opt spi0; -sptr spi() +sptr spi() { if (not spi0) { - spi0 = h5::make_strong_ptr( - &memory, hal::pin<23>, hal::pin<20>, hal::pin<22>, hal::pin<25>); + spi0 = h5::make_strong_ptr( + &memory, hal::pin<35>, hal::pin<36>, hal::pin<34>); } return spi0; } +opt spi_cs0; +sptr spi_chip_select() +{ + if (not spi_cs0) { + spi_cs0 = h5::make_strong_ptr(&memory, hal::pin<33>); + } + return spi_cs0; +} + } // namespace resources diff --git a/demos/resource_list.hpp b/demos/resource_list.hpp index 3f2d8dbc..e76aa6e7 100644 --- a/demos/resource_list.hpp +++ b/demos/resource_list.hpp @@ -85,9 +85,10 @@ hal::v5::strong_ptr i2c(); hal::v5::strong_ptr interrupt_pin(); hal::v5::strong_ptr pwm(); hal::v5::strong_ptr timed_interrupt(); -hal::v5::strong_ptr pwm_channel(); -hal::v5::strong_ptr pwm_frequency(); -hal::v5::strong_ptr spi(); +hal::v5::strong_ptr pwm_channel(); +hal::v5::strong_ptr pwm_frequency(); +hal::v5::strong_ptr spi(); +hal::v5::strong_ptr spi_chip_select(); hal::v5::strong_ptr stream_dac(); hal::v5::strong_ptr dac(); hal::v5::strong_ptr watchdog(); @@ -123,3 +124,4 @@ inline void sleep(hal::time_duration p_duration) // Application function is implemented by one of the .cpp files. void initialize_platform(); void application(); + diff --git a/demos/run.sh b/demos/run.sh index 9f96b4da..8ec999f7 100644 --- a/demos/run.sh +++ b/demos/run.sh @@ -4,7 +4,7 @@ set -ex BUILDDIR=build/rp2350-arm-s/RelWithDebInfo -conan build . -pr:h=rp2350 -pr:h=arm-gcc-12.3 --build=missing +conan build . -pr:h=rp2350b -pr:h=arm-gcc-12.3 --build=missing source $BUILDDIR/generators/conanbuild.sh From f24e0fa5d686b1a7156a153396f8f2824f0995d7 Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Sun, 8 Feb 2026 21:07:14 -0800 Subject: [PATCH 35/55] fix versioning --- conanfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conanfile.py b/conanfile.py index ad80d370..de96081f 100644 --- a/conanfile.py +++ b/conanfile.py @@ -57,7 +57,7 @@ class libhal_arm_mcu_conan(ConanFile): } def requirements(self): - self.requires("libhal/[>4.17.0 <=4.18.0]", transitive_headers=True) + self.requires("libhal/[^4.18.0]", transitive_headers=True) self.requires("libhal-util/[^5.7.0]", transitive_headers=True) self.requires("ring-span-lite/[^0.7.0]", transitive_headers=True) self.requires("scope-lite/0.2.0") From 1641d1841ad3831c5f45003a2bfde65775bfb1c0 Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Thu, 12 Feb 2026 19:15:56 -0800 Subject: [PATCH 36/55] experimenting with async: todo test --- CMakeLists.txt | 1 + include/libhal-arm-mcu/rp/adc.hpp | 71 +++++++++++++++++++++++ include/libhal-arm-mcu/rp/time.hpp | 3 + src/rp/adc.cpp | 93 +++++++++++++++++++++++++++++- 4 files changed, 167 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 25c32a09..249b6f92 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -157,6 +157,7 @@ set(pico_dep hardware_spi_headers hardware_uart_headers hardware_timer_headers + hardware_dma_headers ) endif() diff --git a/include/libhal-arm-mcu/rp/adc.hpp b/include/libhal-arm-mcu/rp/adc.hpp index e32b7eba..8aaa5e92 100644 --- a/include/libhal-arm-mcu/rp/adc.hpp +++ b/include/libhal-arm-mcu/rp/adc.hpp @@ -1,7 +1,10 @@ #pragma once #include "rp.hpp" +#include "time.hpp" #include +#include +#include namespace hal::rp { @@ -51,4 +54,72 @@ struct adc16 final : public hal::adc16 u8 m_pin; }; } // namespace v5 + +namespace nonstandard { +struct adc16_pack +{ + struct read_session; + + template + adc16_pack(Pins... ps) + : adc16_pack((to_mask(ps) | ...)) + { + } + adc16_pack(adc16_pack const&) = delete; + adc16_pack(adc16_pack&&) = + delete ("ADC pack needs to be immovable to store transaction"); + + void read_many_now(std::span); + read_session async(); + +private: + adc16_pack(u8 pin_mask); + u8 to_mask(pin_param auto pin) + { + if constexpr (internal::pin_max == 30) { + static_assert(pin() >= 26 && pin() < 30); + return 1 << (pin() - 26); + } else if constexpr (internal::pin_max == 48) { + static_assert(pin() >= 40 && pin() < 48); + return 1 << (pin() - 40); + } + } + u8 m_read_size, m_first_pin; +}; + +struct adc16_pack::read_session +{ + struct promise; + promise read(std::span); + ~read_session(); + read_session(read_session const&) = delete; + read_session(read_session&&) = delete; + + struct promise + { + microseconds poll(); + promise(promise const&) = default; + + private: + friend read_session; + promise(u8 dma) + : m_dma(dma) + { + } + u8 m_dma; + }; + +private: + friend adc16_pack; + read_session(u8 dma, u8 read_size, u8 first_mask) // NOLINT + : m_dma(dma) + , m_read_size(read_size) + , m_first_mask(first_mask) + { + } + u8 m_dma, m_read_size, m_first_mask; +}; + +} // namespace nonstandard + } // namespace hal::rp diff --git a/include/libhal-arm-mcu/rp/time.hpp b/include/libhal-arm-mcu/rp/time.hpp index 9fdc6780..4cc24f18 100644 --- a/include/libhal-arm-mcu/rp/time.hpp +++ b/include/libhal-arm-mcu/rp/time.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include namespace hal::rp::inline v4 { @@ -12,4 +13,6 @@ struct clock final : public hal::steady_clock u64 driver_uptime() override; }; +using microseconds = std::chrono::duration; + } // namespace hal::rp::inline v4 diff --git a/src/rp/adc.cpp b/src/rp/adc.cpp index 46367aa3..d8d38c16 100644 --- a/src/rp/adc.cpp +++ b/src/rp/adc.cpp @@ -1,9 +1,13 @@ #include "libhal-arm-mcu/rp/adc.hpp" +#include "libhal-arm-mcu/rp/time.hpp" -#include "hardware/adc.h" +#include #include +#include #include #include + +#include #include #include @@ -70,4 +74,91 @@ u16 adc16::driver_read() } } // namespace v5 + +namespace nonstandard { +adc16_pack::adc16_pack(u8 mask) +{ + u32 base_pin = 0; + if constexpr (internal::pin_max == 30) { + base_pin = 26; + } else if constexpr (internal::pin_max == 48) { + base_pin = 40; + } + u8 pinnum = 0; + bool first_selected = false; + for (u32 i = 0; i < 8; ++i) { + if (mask & (1 << i)) { + gpio_init(i + base_pin); + pinnum += 1; + if (!first_selected) { + adc_select_input(i); + m_first_pin = i; + first_selected = true; + } + } + } + adc_set_round_robin(mask); + m_read_size = pinnum; +} + +void adc16_pack::read_many_now(std::span s) +{ + if (s.size() < m_read_size) + safe_throw(hal::io_error(this)); + for (u8 i = 0; i < m_read_size; ++i) { + s[i] = adc_read(); + } +} + +adc16_pack::read_session adc16_pack::async() +{ + int read_dma = dma_claim_unused_channel(false); + if (read_dma == -1) { + hal::safe_throw(hal::device_or_resource_busy(this)); + } + + adc_hw->fcs |= ADC_FCS_DREQ_EN_BITS | ADC_FCS_EN_BITS; + dma_channel_config_t read_cfg = dma_channel_get_default_config(read_dma); + channel_config_set_read_increment(&read_cfg, false); + channel_config_set_write_increment(&read_cfg, true); + channel_config_set_transfer_data_size(&read_cfg, DMA_SIZE_16); + dma_channel_set_config(read_dma, &read_cfg, false); + dma_channel_set_read_addr(read_dma, &adc_hw->fifo, false); + hw_write_masked(&dma_channel_hw_addr(read_dma)->ctrl_trig, + DREQ_ADC, + DMA_CH0_CTRL_TRIG_TREQ_SEL_BITS); + hw_write_masked(&adc_hw->fcs, 1, ADC_FCS_THRESH_BITS); + return { static_cast(read_dma), + m_read_size, + static_cast(1 << m_first_pin) }; +} +adc16_pack::read_session::~read_session() +{ + adc_hw->fcs &= ADC_FCS_DREQ_EN_BITS; + dma_channel_config_t read_cfg = dma_get_channel_config(m_dma); + channel_config_set_enable(&read_cfg, false); + dma_channel_set_config(m_dma, &read_cfg, false); + dma_channel_unclaim(m_dma); +} + +adc16_pack::read_session::promise adc16_pack::read_session::read( + std::span span) +{ + dma_channel_set_write_addr(m_dma, span.data(), false); + dma_channel_set_transfer_count( + m_dma, dma_encode_transfer_count(span.size()), false); + hw_set_bits(&adc_hw->cs, ADC_CS_START_ONCE_BITS); + return promise{ m_dma }; +} + +microseconds adc16_pack::read_session::promise::poll() +{ + u32 transfers = (dma_channel_hw_addr(m_dma)->transfer_count & + DMA_CH0_TRANS_COUNT_COUNT_BITS) >> + DMA_CH0_TRANS_COUNT_COUNT_LSB; + return transfers * microseconds(2); +} + +} // namespace nonstandard + } // namespace hal::rp From 618625dc867d5d55de02d493bbadacfa34299678 Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Sat, 14 Feb 2026 08:39:14 -0800 Subject: [PATCH 37/55] do not hardcode PICO_BOARD --- CMakeLists.txt | 2 -- conanfile.py | 10 ++++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 249b6f92..aa836234 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,10 +16,8 @@ cmake_minimum_required(VERSION 3.15) if(DEFINED ENV{PICO_SDK_PATH}) set(PICO_CXX_ENABLE_EXCEPTIONS 1) -set(CMAKE_ASM_FLAGS_INIT "-mcpu=cortex-m33 -mfloat-abi=soft") set(PICO_NO_PICOTOOL 1) list(APPEND PICO_BOARD_HEADER_DIRS ${CMAKE_SOURCE_DIR}/src/rp/boards) -set(PICO_BOARD rp2350b_w25q080) include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake) endif() diff --git a/conanfile.py b/conanfile.py index de96081f..d4caa080 100644 --- a/conanfile.py +++ b/conanfile.py @@ -45,7 +45,8 @@ class libhal_arm_mcu_conan(ConanFile): "use_picolibc": [True, False], "platform": ["ANY"], "use_default_linker_script": [True, False], - "variant": [None, "ANY"] + "variant": [None, "ANY"], + "board": [None, "ANY"] } default_options = { @@ -53,7 +54,8 @@ class libhal_arm_mcu_conan(ConanFile): "use_picolibc": True, "platform": "ANY", "use_default_linker_script": True, - "variant": None + "variant": None, + "board": None, } def requirements(self): @@ -95,6 +97,8 @@ def generate(self): tc = CMakeToolchain(self) if str(self.options.platform).startswith("rp2"): tc.cache_variables["DO_NOT_BUILD_BOOT_HAL"] = True + if self.options.board: + tc.cache_variables["PICO_BOARD"] = str(self.options.board) if self.options.variant: tc.preprocessor_definitions["LIBHAL_VARIANT_" + self._macro(str(self.options.variant))] = "1" tc.preprocessor_definitions["LIBHAL_PLATFORM_" + self._macro(str(self.options.platform))] = "1" @@ -104,6 +108,8 @@ def generate(self): def validate(self): if str(self.options.platform).startswith("rp2"): + if not self.options.board: + raise ConanInvalidConfiguration("RP board not specified") if "rp2350" in str(self.options.platform): if not self.options.variant: raise ConanInvalidConfiguration("RP2350 variant not specified") From b383471e02d06bc4f278cbb585646a82366fe4ad Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Sat, 14 Feb 2026 08:39:58 -0800 Subject: [PATCH 38/55] fix adc async so it actually works --- include/libhal-arm-mcu/rp/adc.hpp | 15 +++---- include/libhal-arm-mcu/rp/time.hpp | 2 +- src/rp/adc.cpp | 63 ++++++++++++++++++------------ 3 files changed, 48 insertions(+), 32 deletions(-) diff --git a/include/libhal-arm-mcu/rp/adc.hpp b/include/libhal-arm-mcu/rp/adc.hpp index 8aaa5e92..c16eb420 100644 --- a/include/libhal-arm-mcu/rp/adc.hpp +++ b/include/libhal-arm-mcu/rp/adc.hpp @@ -66,10 +66,10 @@ struct adc16_pack { } adc16_pack(adc16_pack const&) = delete; - adc16_pack(adc16_pack&&) = - delete ("ADC pack needs to be immovable to store transaction"); + adc16_pack(adc16_pack&&) = delete; void read_many_now(std::span); + // Uses up 1 DMA channel read_session async(); private: @@ -102,22 +102,23 @@ struct adc16_pack::read_session private: friend read_session; - promise(u8 dma) + promise(u8 dma, u8 first_pin) // NOLINT : m_dma(dma) + , m_first_pin(first_pin) { } - u8 m_dma; + u8 m_dma, m_first_pin; }; private: friend adc16_pack; - read_session(u8 dma, u8 read_size, u8 first_mask) // NOLINT + read_session(u8 dma, u8 read_size, u8 first_pin) // NOLINT : m_dma(dma) , m_read_size(read_size) - , m_first_mask(first_mask) + , m_first_pin(first_pin) { } - u8 m_dma, m_read_size, m_first_mask; + u8 m_dma, m_read_size, m_first_pin; }; } // namespace nonstandard diff --git a/include/libhal-arm-mcu/rp/time.hpp b/include/libhal-arm-mcu/rp/time.hpp index 4cc24f18..30311f67 100644 --- a/include/libhal-arm-mcu/rp/time.hpp +++ b/include/libhal-arm-mcu/rp/time.hpp @@ -13,6 +13,6 @@ struct clock final : public hal::steady_clock u64 driver_uptime() override; }; -using microseconds = std::chrono::duration; +using microseconds = std::chrono::duration; } // namespace hal::rp::inline v4 diff --git a/src/rp/adc.cpp b/src/rp/adc.cpp index d8d38c16..f6a8fb4f 100644 --- a/src/rp/adc.cpp +++ b/src/rp/adc.cpp @@ -1,4 +1,5 @@ #include "libhal-arm-mcu/rp/adc.hpp" +#include "libhal-arm-mcu/rp/rp.hpp" #include "libhal-arm-mcu/rp/time.hpp" #include @@ -17,10 +18,11 @@ inline namespace v4 { adc::adc(u8 pin) : m_pin(pin) { - adc_init(); if (pin < ADC_BASE_PIN || pin >= ADC_BASE_PIN + NUM_ADC_CHANNELS - 1) { - hal::safe_throw(hal::argument_out_of_domain(this)); + hal::safe_throw( + hal::argument_out_of_domain(reinterpret_cast(pin))); // NOLINT } + adc_init(); adc_gpio_init(pin); } @@ -49,7 +51,8 @@ adc16::adc16(u8 pin) { adc_init(); if (pin < ADC_BASE_PIN || pin >= ADC_BASE_PIN + NUM_ADC_CHANNELS - 1) { - hal::safe_throw(hal::argument_out_of_domain(this)); + hal::safe_throw( + hal::argument_out_of_domain(reinterpret_cast(pin))); // NOLINT } adc_gpio_init(pin); } @@ -78,17 +81,19 @@ u16 adc16::driver_read() namespace nonstandard { adc16_pack::adc16_pack(u8 mask) { + adc_init(); u32 base_pin = 0; if constexpr (internal::pin_max == 30) { base_pin = 26; } else if constexpr (internal::pin_max == 48) { base_pin = 40; } + static_assert(internal::pin_max == 30 || internal::pin_max == 48); u8 pinnum = 0; bool first_selected = false; for (u32 i = 0; i < 8; ++i) { if (mask & (1 << i)) { - gpio_init(i + base_pin); + adc_gpio_init(i + base_pin); pinnum += 1; if (!first_selected) { adc_select_input(i); @@ -116,24 +121,25 @@ adc16_pack::read_session adc16_pack::async() if (read_dma == -1) { hal::safe_throw(hal::device_or_resource_busy(this)); } - - adc_hw->fcs |= ADC_FCS_DREQ_EN_BITS | ADC_FCS_EN_BITS; - dma_channel_config_t read_cfg = dma_channel_get_default_config(read_dma); - channel_config_set_read_increment(&read_cfg, false); - channel_config_set_write_increment(&read_cfg, true); - channel_config_set_transfer_data_size(&read_cfg, DMA_SIZE_16); - dma_channel_set_config(read_dma, &read_cfg, false); - dma_channel_set_read_addr(read_dma, &adc_hw->fifo, false); - hw_write_masked(&dma_channel_hw_addr(read_dma)->ctrl_trig, - DREQ_ADC, - DMA_CH0_CTRL_TRIG_TREQ_SEL_BITS); - hw_write_masked(&adc_hw->fcs, 1, ADC_FCS_THRESH_BITS); - return { static_cast(read_dma), - m_read_size, - static_cast(1 << m_first_pin) }; + adc_fifo_setup( + true, // Write each completed conversion to the sample FIFO + true, // Enable DMA data request (DREQ) + 1, // DREQ (and IRQ) asserted when at least 1 sample present + false, // We won't see the ERR bit because of 8 bit reads; disable. + false // Shift each sample to 8 bits when pushing to FIFO + ); + + dma_channel_config cfg = dma_channel_get_default_config(read_dma); + channel_config_set_transfer_data_size(&cfg, DMA_SIZE_16); + channel_config_set_read_increment(&cfg, false); + channel_config_set_write_increment(&cfg, true); + channel_config_set_dreq(&cfg, DREQ_ADC); + dma_channel_set_config(read_dma, &cfg, false); + return { static_cast(read_dma), m_read_size, m_first_pin }; } adc16_pack::read_session::~read_session() { + adc_run(false); adc_hw->fcs &= ADC_FCS_DREQ_EN_BITS; dma_channel_config_t read_cfg = dma_get_channel_config(m_dma); channel_config_set_enable(&read_cfg, false); @@ -144,11 +150,17 @@ adc16_pack::read_session::~read_session() adc16_pack::read_session::promise adc16_pack::read_session::read( std::span span) { - dma_channel_set_write_addr(m_dma, span.data(), false); - dma_channel_set_transfer_count( - m_dma, dma_encode_transfer_count(span.size()), false); - hw_set_bits(&adc_hw->cs, ADC_CS_START_ONCE_BITS); - return promise{ m_dma }; + auto chan = dma_get_channel_config(m_dma); + dma_channel_configure(m_dma, + &chan, + span.data(), // dst + &adc_hw->fifo, // src + span.size(), // transfer count + true // start immediately + ); + adc_select_input(m_first_pin); + adc_run(true); + return promise{ m_dma, m_first_pin }; } microseconds adc16_pack::read_session::promise::poll() @@ -156,6 +168,9 @@ microseconds adc16_pack::read_session::promise::poll() u32 transfers = (dma_channel_hw_addr(m_dma)->transfer_count & DMA_CH0_TRANS_COUNT_COUNT_BITS) >> DMA_CH0_TRANS_COUNT_COUNT_LSB; + if (transfers == 0) { + adc_run(false); + } return transfers * microseconds(2); } From f92e62efe5865788842ee49c2dac8ddfe973b59e Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Sat, 14 Feb 2026 09:59:08 -0800 Subject: [PATCH 39/55] workaround puts() lto issues by not wrapping libc calls --- conanfile.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/conanfile.py b/conanfile.py index d4caa080..e7bf12dc 100644 --- a/conanfile.py +++ b/conanfile.py @@ -97,6 +97,7 @@ def generate(self): tc = CMakeToolchain(self) if str(self.options.platform).startswith("rp2"): tc.cache_variables["DO_NOT_BUILD_BOOT_HAL"] = True + tc.preprocessor_definitions["PICO_STDIO_SHORT_CIRCUIT_CLIB_FUNCS"] = "0" if self.options.board: tc.cache_variables["PICO_BOARD"] = str(self.options.board) if self.options.variant: @@ -136,6 +137,7 @@ def package_info(self): if self.options.variant: defines.append("LIBHAL_VARIANT_" + self._macro(str(self.options.variant)) + "=1") defines.append("LIBHAL_PLATFORM_" + self._macro(str(self.options.platform)) + "=1") + defines.append("PICO_STDIO_SHORT_CIRCUIT_CLIB_FUNCS=0") self.cpp_info.defines = defines if (self.settings.os == "baremetal" and From 5000a35a8f9ef7fdc889c191dea5b26f2102b31d Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Sat, 14 Feb 2026 10:02:11 -0800 Subject: [PATCH 40/55] add function to get core clock --- include/libhal-arm-mcu/rp/time.hpp | 3 +++ src/rp/time.cpp | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/include/libhal-arm-mcu/rp/time.hpp b/include/libhal-arm-mcu/rp/time.hpp index 30311f67..1f519fa4 100644 --- a/include/libhal-arm-mcu/rp/time.hpp +++ b/include/libhal-arm-mcu/rp/time.hpp @@ -13,6 +13,9 @@ struct clock final : public hal::steady_clock u64 driver_uptime() override; }; +// returns configured clock speed for use with dwt_counter +hertz core_clock(); + using microseconds = std::chrono::duration; } // namespace hal::rp::inline v4 diff --git a/src/rp/time.cpp b/src/rp/time.cpp index cbfb8ba8..923eb1bc 100644 --- a/src/rp/time.cpp +++ b/src/rp/time.cpp @@ -16,4 +16,9 @@ u64 clock::driver_uptime() return time_us_64(); } +hertz core_clock() +{ + return SYS_CLK_HZ; +} + } // namespace hal::rp::inline v4 From 7ac35f87a0fe79579c93de0164341ddb535b6af7 Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Fri, 6 Mar 2026 14:45:03 -0800 Subject: [PATCH 41/55] fix phase issues --- src/rp/adc.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/rp/adc.cpp b/src/rp/adc.cpp index f6a8fb4f..7ca12b0c 100644 --- a/src/rp/adc.cpp +++ b/src/rp/adc.cpp @@ -121,12 +121,11 @@ adc16_pack::read_session adc16_pack::async() if (read_dma == -1) { hal::safe_throw(hal::device_or_resource_busy(this)); } - adc_fifo_setup( - true, // Write each completed conversion to the sample FIFO - true, // Enable DMA data request (DREQ) - 1, // DREQ (and IRQ) asserted when at least 1 sample present - false, // We won't see the ERR bit because of 8 bit reads; disable. - false // Shift each sample to 8 bits when pushing to FIFO + adc_fifo_setup(false, // do not enable FIFO yet + true, // Enable DMA data request (DREQ) + 1, // DREQ (and IRQ) asserted when at least 1 sample present + true, // Enable ADC errors + false // Shift each sample to 8 bits when pushing to FIFO ); dma_channel_config cfg = dma_channel_get_default_config(read_dma); @@ -158,7 +157,10 @@ adc16_pack::read_session::promise adc16_pack::read_session::read( span.size(), // transfer count true // start immediately ); + adc_run(false); + adc_fifo_drain(); adc_select_input(m_first_pin); + adc_hw->fcs |= bool_to_bit(true) << ADC_FCS_EN_LSB; adc_run(true); return promise{ m_dma, m_first_pin }; } @@ -169,6 +171,7 @@ microseconds adc16_pack::read_session::promise::poll() DMA_CH0_TRANS_COUNT_COUNT_BITS) >> DMA_CH0_TRANS_COUNT_COUNT_LSB; if (transfers == 0) { + adc_hw->fcs &= ~ADC_FCS_EN_BITS; adc_run(false); } return transfers * microseconds(2); From fa0fd46cea8c9591f3ee5773d4567f2e45d85d5c Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Fri, 6 Mar 2026 16:07:09 -0800 Subject: [PATCH 42/55] do not hardcode board --- CMakeLists.txt | 2 -- conanfile.py | 13 +++++++++++-- src/rp/boards/rp2350b_w25q080.h | 33 --------------------------------- 3 files changed, 11 insertions(+), 37 deletions(-) delete mode 100644 src/rp/boards/rp2350b_w25q080.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 25c32a09..f6dfb814 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,8 +18,6 @@ if(DEFINED ENV{PICO_SDK_PATH}) set(PICO_CXX_ENABLE_EXCEPTIONS 1) set(CMAKE_ASM_FLAGS_INIT "-mcpu=cortex-m33 -mfloat-abi=soft") set(PICO_NO_PICOTOOL 1) -list(APPEND PICO_BOARD_HEADER_DIRS ${CMAKE_SOURCE_DIR}/src/rp/boards) -set(PICO_BOARD rp2350b_w25q080) include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake) endif() diff --git a/conanfile.py b/conanfile.py index de96081f..d6bd2ce4 100644 --- a/conanfile.py +++ b/conanfile.py @@ -45,7 +45,8 @@ class libhal_arm_mcu_conan(ConanFile): "use_picolibc": [True, False], "platform": ["ANY"], "use_default_linker_script": [True, False], - "variant": [None, "ANY"] + "variant": [None, "ANY"], + "board": [None, "ANY"], } default_options = { @@ -53,7 +54,8 @@ class libhal_arm_mcu_conan(ConanFile): "use_picolibc": True, "platform": "ANY", "use_default_linker_script": True, - "variant": None + "variant": None, + "board": None, } def requirements(self): @@ -72,6 +74,9 @@ def requirements(self): if str(self.options.platform).startswith("rp2"): self.requires("picosdk/2.2.0") self.tool_requires("pioasm/2.2.0") + if self.options.board.value.startswith("libhal_"): + board = self.options.board.value.removeprefix('libhal_').replace('_', '-') + self.requires(f"rp-board-header-{board}/latest", visible=True) def handle_stm32f1_linker_scripts(self): linker_script_name = list(str(self.options.platform)) @@ -95,6 +100,8 @@ def generate(self): tc = CMakeToolchain(self) if str(self.options.platform).startswith("rp2"): tc.cache_variables["DO_NOT_BUILD_BOOT_HAL"] = True + if self.options.board: + tc.cache_variables["PICO_BOARD"] = str(self.options.board) if self.options.variant: tc.preprocessor_definitions["LIBHAL_VARIANT_" + self._macro(str(self.options.variant))] = "1" tc.preprocessor_definitions["LIBHAL_PLATFORM_" + self._macro(str(self.options.platform))] = "1" @@ -109,6 +116,8 @@ def validate(self): raise ConanInvalidConfiguration("RP2350 variant not specified") if self.options.variant not in ["rp2350a", "rp2350b"]: raise ConanInvalidConfiguration("Invalid RP2350 variant specified") + if not self.options.board: + raise ConanInvalidConfiguration("Board must be specified during build") super().validate() def package_info(self): diff --git a/src/rp/boards/rp2350b_w25q080.h b/src/rp/boards/rp2350b_w25q080.h deleted file mode 100644 index 54c71e5c..00000000 --- a/src/rp/boards/rp2350b_w25q080.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2025 Shin Umeda - * - * This is a board file that gets pico-sdk headers to work at the bare minimum. - * This doesn't define much by itself, and nothing should depend too much on definitions - * here. - * - * */ - -#ifndef _rp2350_micromod_h -#define _rp2350_micromod_h - -pico_board_cmake_set(PICO_PLATFORM, rp2350) - -#define PICO_RP2350A 0 // use RP2350B - -#define PICO_BOOT_STAGE2_CHOOSE_W25Q080 1 - -#ifndef PICO_FLASH_SPI_CLKDIV -#define PICO_FLASH_SPI_CLKDIV 2 -#endif - -pico_board_cmake_set_default(PICO_FLASH_SIZE_BYTES, (8 * 1024 * 1024)) -#ifndef PICO_FLASH_SIZE_BYTES -#define PICO_FLASH_SIZE_BYTES (8 * 1024 * 1024) -#endif - -pico_board_cmake_set_default(PICO_RP2350_A2_SUPPORTED, 1) -#ifndef PICO_RP2350_A2_SUPPORTED -#define PICO_RP2350_A2_SUPPORTED 1 -#endif - -#endif From f8fbbd2586c00554f9b4e4947602793eca148f5a Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Fri, 6 Mar 2026 16:07:45 -0800 Subject: [PATCH 43/55] move enable to runtime, fix minor math errors --- include/libhal-arm-mcu/rp/pwm.hpp | 6 +++--- src/rp/pwm.cpp | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/include/libhal-arm-mcu/rp/pwm.hpp b/include/libhal-arm-mcu/rp/pwm.hpp index a75fcfd5..c1670c4b 100644 --- a/include/libhal-arm-mcu/rp/pwm.hpp +++ b/include/libhal-arm-mcu/rp/pwm.hpp @@ -53,6 +53,8 @@ struct pwm_slice_runtime : hal::pwm_group_manager { ~pwm_slice_runtime() override; + + void enable(bool enable = true); protected: pwm_slice_runtime(u8 slice_num); @@ -117,8 +119,6 @@ struct pwm_slice final : pwm_slice_runtime static_assert(ch() < max_slices(), "Invalid PWM slice!"); } - void enable(bool enable = true); - pwm_pin get_pin(pin_param auto pin, pwm_pin_configuration const& config = {}) { static_assert(get_slice_number(pin) == chan, "Slice pin is incorrect!"); @@ -141,7 +141,7 @@ struct pwm_slice final : pwm_slice_runtime if (pin() < 32) { return (pin() / 2) % 8; } else { - return pin() / 2 - 8; + return 8 + ((pin() >> 1) & 3); } } }; diff --git a/src/rp/pwm.cpp b/src/rp/pwm.cpp index 6c35b4c4..ffaceeff 100644 --- a/src/rp/pwm.cpp +++ b/src/rp/pwm.cpp @@ -179,10 +179,9 @@ void enable_all_pwm(bool start) } } -template -void pwm_slice::enable(bool enable) +void pwm_slice_runtime::enable(bool enable) { - pwm_set_enabled(s, enable); + pwm_set_enabled(m_number, enable); } u32 pwm_pin::driver_frequency() From 573e01628b69d8580b96a55623503eb2d792be85 Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Sat, 7 Mar 2026 12:50:02 -0800 Subject: [PATCH 44/55] Add todo regarding fixing spi --- include/libhal-arm-mcu/rp/spi.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/libhal-arm-mcu/rp/spi.hpp b/include/libhal-arm-mcu/rp/spi.hpp index 24543e2a..24cc5160 100644 --- a/include/libhal-arm-mcu/rp/spi.hpp +++ b/include/libhal-arm-mcu/rp/spi.hpp @@ -39,7 +39,7 @@ struct spi final : public hal::spi namespace v5 { /* RP chips suppport 16 bit transfers. It may be worthwhile to add an option -to transfer 16 bits as a time. +to transfer 16 bits as a time. TODO fix to add spi channel manager */ struct spi final : public hal::spi_channel { From 89104632afe8f80e1e8ff898b2c1b1b3144fb5ee Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Sat, 7 Mar 2026 12:53:41 -0800 Subject: [PATCH 45/55] Generally delete move constructors just in case --- include/libhal-arm-mcu/rp/adc.hpp | 1 + include/libhal-arm-mcu/rp/i2c.hpp | 1 + include/libhal-arm-mcu/rp/input_pin.hpp | 1 + include/libhal-arm-mcu/rp/interrupt_pin.hpp | 1 + include/libhal-arm-mcu/rp/output_pin.hpp | 1 + include/libhal-arm-mcu/rp/pwm.hpp | 4 ++++ include/libhal-arm-mcu/rp/serial.hpp | 2 ++ include/libhal-arm-mcu/rp/spi.hpp | 1 + include/libhal-arm-mcu/rp/time.hpp | 1 + 9 files changed, 13 insertions(+) diff --git a/include/libhal-arm-mcu/rp/adc.hpp b/include/libhal-arm-mcu/rp/adc.hpp index e32b7eba..466a1d6d 100644 --- a/include/libhal-arm-mcu/rp/adc.hpp +++ b/include/libhal-arm-mcu/rp/adc.hpp @@ -16,6 +16,7 @@ struct adc final : public hal::adc static_assert(internal::pin_max != 48 || (pin() >= 40 && pin() < 48), "ADC pin is invalid!"); } + adc(adc&&) = delete; ~adc() override; private: diff --git a/include/libhal-arm-mcu/rp/i2c.hpp b/include/libhal-arm-mcu/rp/i2c.hpp index 1edeed05..3bc9d98e 100644 --- a/include/libhal-arm-mcu/rp/i2c.hpp +++ b/include/libhal-arm-mcu/rp/i2c.hpp @@ -23,6 +23,7 @@ struct i2c final : public hal::i2c static_assert(sda() % 4 == 2 || bus() != 1, "SDA pin for I2C1 is invalid!"); static_assert(scl() % 4 == 3 || bus() != 1, "SCL pin for I2C1 is invalid!"); } + i2c(i2c&&) = delete; ~i2c() override; private: diff --git a/include/libhal-arm-mcu/rp/input_pin.hpp b/include/libhal-arm-mcu/rp/input_pin.hpp index de524b52..05291d89 100644 --- a/include/libhal-arm-mcu/rp/input_pin.hpp +++ b/include/libhal-arm-mcu/rp/input_pin.hpp @@ -11,6 +11,7 @@ struct input_pin final : public hal::input_pin : input_pin(pin(), s) { } + input_pin(input_pin&&) = delete; ~input_pin() override; private: diff --git a/include/libhal-arm-mcu/rp/interrupt_pin.hpp b/include/libhal-arm-mcu/rp/interrupt_pin.hpp index 8dda01f5..2d5686c1 100644 --- a/include/libhal-arm-mcu/rp/interrupt_pin.hpp +++ b/include/libhal-arm-mcu/rp/interrupt_pin.hpp @@ -19,6 +19,7 @@ struct interrupt_pin final : public hal::interrupt_pin : interrupt_pin(pin(), callback, options) { } + interrupt_pin(interrupt_pin&&) = delete; ~interrupt_pin() override; private: diff --git a/include/libhal-arm-mcu/rp/output_pin.hpp b/include/libhal-arm-mcu/rp/output_pin.hpp index 3a7c4c95..bca1c69e 100644 --- a/include/libhal-arm-mcu/rp/output_pin.hpp +++ b/include/libhal-arm-mcu/rp/output_pin.hpp @@ -11,6 +11,7 @@ struct output_pin final : public hal::output_pin : output_pin(pin(), options) { } + output_pin(output_pin&&) = delete; ~output_pin() override; private: diff --git a/include/libhal-arm-mcu/rp/pwm.hpp b/include/libhal-arm-mcu/rp/pwm.hpp index c1670c4b..a2dd2f42 100644 --- a/include/libhal-arm-mcu/rp/pwm.hpp +++ b/include/libhal-arm-mcu/rp/pwm.hpp @@ -20,6 +20,7 @@ struct pwm_pin final : hal::pwm // any right to complain about two different PWM pins interfering // with each other because they are on the same slice. pwm_pin(hal::unsafe, u8 pin); + pwm_pin(pwm_pin&&) = delete; ~pwm_pin() override; private: @@ -52,6 +53,7 @@ be instantiated normally */ struct pwm_slice_runtime : hal::pwm_group_manager { + pwm_slice_runtime(pwm_slice_runtime&&) = delete; ~pwm_slice_runtime() override; void enable(bool enable = true); @@ -74,6 +76,7 @@ struct pwm_slice_runtime : hal::pwm_group_manager struct pwm_pin final : hal::pwm16_channel { + pwm_pin(pwm_pin&&) = delete; ~pwm_pin() override; friend pwm_slice_runtime; @@ -106,6 +109,7 @@ template struct pwm_slice final : pwm_slice_runtime { + pwm_slice(pwm_slice&&) = delete; pwm_slice(channel_param auto ch) : pwm_slice_runtime(ch()) { diff --git a/include/libhal-arm-mcu/rp/serial.hpp b/include/libhal-arm-mcu/rp/serial.hpp index 0f033e06..ed815c75 100644 --- a/include/libhal-arm-mcu/rp/serial.hpp +++ b/include/libhal-arm-mcu/rp/serial.hpp @@ -18,6 +18,7 @@ of this class as it is not guaranteed. struct stdio_serial final : public hal::serial { stdio_serial(); + stdio_serial(stdio_serial&&) = delete; private: // This function is a sham that does nothing @@ -50,6 +51,7 @@ struct uart final : public hal::serial static_assert(((rx() + 4) / 8) % 2 == bus(), "UART RX pin and bus do not match!"); } + uart(uart&&) = delete; ~uart() override; private: diff --git a/include/libhal-arm-mcu/rp/spi.hpp b/include/libhal-arm-mcu/rp/spi.hpp index 24cc5160..f4a4b5bd 100644 --- a/include/libhal-arm-mcu/rp/spi.hpp +++ b/include/libhal-arm-mcu/rp/spi.hpp @@ -23,6 +23,7 @@ struct spi final : public hal::spi static_assert(sck() % 4 == 2, "SPI SCK pin is invalid"); static_assert(copi() % 4 == 3, "SPI CS pin is invalid"); } + spi(spi&&) = delete; ~spi() override; private: diff --git a/include/libhal-arm-mcu/rp/time.hpp b/include/libhal-arm-mcu/rp/time.hpp index 9fdc6780..c7db83f6 100644 --- a/include/libhal-arm-mcu/rp/time.hpp +++ b/include/libhal-arm-mcu/rp/time.hpp @@ -8,6 +8,7 @@ namespace hal::rp::inline v4 { struct clock final : public hal::steady_clock { clock() = default; + clock(clock const&) = default; hertz driver_frequency() override; u64 driver_uptime() override; }; From a52ae8cf5f90f34a656494ec46b3a0f09eabf910 Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Sat, 7 Mar 2026 12:58:19 -0800 Subject: [PATCH 46/55] change picosdk to a version with hardfp support --- conanfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conanfile.py b/conanfile.py index 01b0aa9e..bce4242a 100644 --- a/conanfile.py +++ b/conanfile.py @@ -72,7 +72,7 @@ def requirements(self): compiler_version = str(self.settings.compiler.version) self.requires("prebuilt-picolibc/" + compiler_version) if str(self.options.platform).startswith("rp2"): - self.requires("picosdk/2.2.0") + self.requires("picosdk/2.2.1-alpha") self.tool_requires("pioasm/2.2.0") if self.options.board.value.startswith("libhal_"): board = self.options.board.value.removeprefix('libhal_').replace('_', '-') From ce5f6e81f52cfb0d0ec132eca3ca0fc7049176ce Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Sun, 8 Mar 2026 10:54:31 -0700 Subject: [PATCH 47/55] Expose sleep interface --- include/libhal-arm-mcu/rp/time.hpp | 6 ++++++ src/rp/time.cpp | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/include/libhal-arm-mcu/rp/time.hpp b/include/libhal-arm-mcu/rp/time.hpp index bc4789e0..e724c9ae 100644 --- a/include/libhal-arm-mcu/rp/time.hpp +++ b/include/libhal-arm-mcu/rp/time.hpp @@ -19,4 +19,10 @@ hertz core_clock(); using microseconds = std::chrono::duration; +void sleep(std::chrono::duration time) noexcept; +inline void sleep(std::chrono::duration time) noexcept +{ + sleep(std::chrono::duration(time.count())); +} + } // namespace hal::rp::inline v4 diff --git a/src/rp/time.cpp b/src/rp/time.cpp index 923eb1bc..78e1f1be 100644 --- a/src/rp/time.cpp +++ b/src/rp/time.cpp @@ -3,6 +3,7 @@ #include #include +#include namespace hal::rp::inline v4 { @@ -21,4 +22,9 @@ hertz core_clock() return SYS_CLK_HZ; } +void sleep(std::chrono::duration time) noexcept +{ + sleep_us(time.count()); +} + } // namespace hal::rp::inline v4 From 95c5b079895b0b7f57a68bed04bb5cec184586e5 Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Mon, 9 Mar 2026 13:51:28 -0700 Subject: [PATCH 48/55] expose phase-correct output --- include/libhal-arm-mcu/rp/pwm.hpp | 15 +++++++++++---- src/rp/pwm.cpp | 7 ++++++- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/include/libhal-arm-mcu/rp/pwm.hpp b/include/libhal-arm-mcu/rp/pwm.hpp index a2dd2f42..fd0d70a4 100644 --- a/include/libhal-arm-mcu/rp/pwm.hpp +++ b/include/libhal-arm-mcu/rp/pwm.hpp @@ -53,13 +53,18 @@ be instantiated normally */ struct pwm_slice_runtime : hal::pwm_group_manager { + struct configuration + { + bool phase_correct = false; + }; + pwm_slice_runtime(pwm_slice_runtime&&) = delete; ~pwm_slice_runtime() override; - + void enable(bool enable = true); protected: - pwm_slice_runtime(u8 slice_num); + pwm_slice_runtime(u8 slice_num, configuration const&); pwm_pin get_pin_raw(u8 pin, pwm_pin_configuration const&); /* @@ -69,6 +74,7 @@ struct pwm_slice_runtime : hal::pwm_group_manager */ void driver_frequency(u32 p_frequency) final; u8 m_number; + bool m_phase_correct; }; /* This cannot be constructed normally, and needs to be @@ -110,8 +116,9 @@ struct pwm_slice final : pwm_slice_runtime { pwm_slice(pwm_slice&&) = delete; - pwm_slice(channel_param auto ch) - : pwm_slice_runtime(ch()) + pwm_slice(channel_param auto ch, + pwm_slice_runtime::configuration const& cfg = {}) + : pwm_slice_runtime(ch(), cfg) { using enum internal::processor_type; static_assert(internal::type == rp2040 || internal::type == rp2350, diff --git a/src/rp/pwm.cpp b/src/rp/pwm.cpp index ffaceeff..16c9ba2f 100644 --- a/src/rp/pwm.cpp +++ b/src/rp/pwm.cpp @@ -78,8 +78,9 @@ void pwm_pin::driver_frequency(float f) namespace v5 { -pwm_slice_runtime::pwm_slice_runtime(u8 num) +pwm_slice_runtime::pwm_slice_runtime(u8 num, configuration const& cfg) : m_number(num) + , m_phase_correct(cfg.phase_correct) { if (num >= NUM_PWM_SLICES) { hal::safe_throw(argument_out_of_domain(this)); @@ -101,6 +102,10 @@ void pwm_slice_runtime::driver_frequency(u32 f) } auto frequency = static_cast(f); + // phase correct mode halfs output frequency, so we need to double it to + // compensate + if (m_phase_correct) + frequency *= 2; auto clock = static_cast(SYS_CLK_HZ); // We try to adjust clock divider to maximize counter resolution float wrap_val = std::numeric_limits::max(); From bdbf54003b2cb3d372d2f2eb3f3790780bbbbbbf Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Thu, 12 Mar 2026 10:58:26 -0700 Subject: [PATCH 49/55] fix overloading not working for chrono literals --- include/libhal-arm-mcu/rp/time.hpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/include/libhal-arm-mcu/rp/time.hpp b/include/libhal-arm-mcu/rp/time.hpp index e724c9ae..cc31b94e 100644 --- a/include/libhal-arm-mcu/rp/time.hpp +++ b/include/libhal-arm-mcu/rp/time.hpp @@ -20,9 +20,5 @@ hertz core_clock(); using microseconds = std::chrono::duration; void sleep(std::chrono::duration time) noexcept; -inline void sleep(std::chrono::duration time) noexcept -{ - sleep(std::chrono::duration(time.count())); -} } // namespace hal::rp::inline v4 From 6ad3a40226f71a37ff56a32b1a9c883379a25836 Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Thu, 12 Mar 2026 10:59:36 -0700 Subject: [PATCH 50/55] remove misc dev files --- create.sh | 4 ---- iterate.sh | 12 ------------ 2 files changed, 16 deletions(-) delete mode 100644 create.sh delete mode 100755 iterate.sh diff --git a/create.sh b/create.sh deleted file mode 100644 index f4236800..00000000 --- a/create.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/sh - -yes | conan remove libhal-arm-mcu -conan create . -pr:h=rp2350b -pr:h=arm-gcc-12.3 --version=latest diff --git a/iterate.sh b/iterate.sh deleted file mode 100755 index 209d67f7..00000000 --- a/iterate.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/sh - -set -ex - -yes | conan remove libhal-arm-mcu -conan create . -pr:h=rp2350 -pr:h=arm-gcc-12.3 --version=latest -rm -rdf demos/build -conan build demos -pr:h=rp2350 -pr:h=arm-gcc-12.3 --build=missing -source demos/build/rp2350-arm-s/MinSizeRel/generators/conanbuild.sh -picotool load demos/build/rp2350-arm-s/MinSizeRel/test_package.uf2 -f -sleep 1 -plink -serial /dev/ttyACM0 -sercfg 115200,8,1,n,X From a36a69be90094e11b562fffdf9cb317b3ac06946 Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Thu, 12 Mar 2026 11:45:58 -0700 Subject: [PATCH 51/55] add back header --- demos/main.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/demos/main.cpp b/demos/main.cpp index 32ce4954..5fd3e7ed 100644 --- a/demos/main.cpp +++ b/demos/main.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include "resource_list.hpp" From f15f3ec24c788c93ac47ecef6585988014ba77e0 Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Tue, 17 Mar 2026 15:29:55 -0700 Subject: [PATCH 52/55] disable default linker scripts, picosdk ships their own --- conanfile.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/conanfile.py b/conanfile.py index b5506bd2..d9f72f97 100644 --- a/conanfile.py +++ b/conanfile.py @@ -132,6 +132,8 @@ def generate(self): def validate(self): if str(self.options.platform).startswith("rp2"): + if self.options.use_default_linker_script: + raise ConanInvalidConfiguration("Default linker scripts are not compatible with RP chips, use pico-sdk linker scripts instead") if not self.options.board: raise ConanInvalidConfiguration("RP board not specified") if "rp2350" in str(self.options.platform): From 8c7a32e7d2d2326d9229bd53f5a52e33745d7c39 Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Thu, 19 Mar 2026 14:10:01 -0700 Subject: [PATCH 53/55] disable waiting to allow for faster polling --- src/rp/serial.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/rp/serial.cpp b/src/rp/serial.cpp index 4aebf6c9..4bd958d2 100644 --- a/src/rp/serial.cpp +++ b/src/rp/serial.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -48,12 +49,9 @@ serial::write_t stdio_serial::driver_write(std::span in) serial::read_t stdio_serial::driver_read(std::span output) { - // time in microseconds - auto now = get_absolute_time(); - // 500 microseconds is 0.5 ms, which is probably reasonable int len = stdio_get_until(reinterpret_cast(output.data()), static_cast(output.size_bytes()), - now + 500); + 0); return read_t{ .data = output.subspan(0, len), .available = 0, .capacity = 1 }; From 29ceffdc154b35b327e181f2a47ca134df34b9f0 Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Tue, 24 Mar 2026 21:25:11 -0700 Subject: [PATCH 54/55] correctly handle timeout for usb serial --- src/rp/serial.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/rp/serial.cpp b/src/rp/serial.cpp index 4bd958d2..a9b9c0e7 100644 --- a/src/rp/serial.cpp +++ b/src/rp/serial.cpp @@ -52,6 +52,10 @@ serial::read_t stdio_serial::driver_read(std::span output) int len = stdio_get_until(reinterpret_cast(output.data()), static_cast(output.size_bytes()), 0); + if (len == PICO_ERROR_TIMEOUT) + len = 0; + if (len < 0) + hal::safe_throw(hal::io_error(this)); return read_t{ .data = output.subspan(0, len), .available = 0, .capacity = 1 }; From 492255b283b50585bf6e8307e65cccd8f1040c4c Mon Sep 17 00:00:00 2001 From: Shin Umeda Date: Wed, 1 Apr 2026 17:08:43 -0700 Subject: [PATCH 55/55] port SPI implementation to V5 properly. --- include/libhal-arm-mcu/rp/spi.hpp | 113 +++++++++++++++++++++++++----- src/rp/spi.cpp | 105 +++++++++++++++++---------- 2 files changed, 164 insertions(+), 54 deletions(-) diff --git a/include/libhal-arm-mcu/rp/spi.hpp b/include/libhal-arm-mcu/rp/spi.hpp index f4a4b5bd..dbf4c343 100644 --- a/include/libhal-arm-mcu/rp/spi.hpp +++ b/include/libhal-arm-mcu/rp/spi.hpp @@ -1,7 +1,10 @@ #pragma once #include "rp.hpp" +#include +#include #include +#include namespace hal::rp { constexpr u8 bus_from_tx_pin(u8 tx) @@ -38,29 +41,78 @@ struct spi final : public hal::spi }; } // namespace v4 namespace v5 { -/* -RP chips suppport 16 bit transfers. It may be worthwhile to add an option -to transfer 16 bits as a time. TODO fix to add spi channel manager -*/ -struct spi final : public hal::spi_channel +struct spi_channel; +template +struct spi_bus; +template<> +struct spi_bus { - // Yes the spi pins can be completely seperate pins - spi(pin_param auto copi, - pin_param auto cipo, - pin_param auto sck, - pin_param auto cs, - spi::settings const& options = {}) - : spi(bus_from_tx_pin(copi()), copi(), cipo(), sck(), cs(), options) + spi_bus(bus_param auto bus, + pin_param auto copi, + pin_param auto cipo, + pin_param auto sck) + : spi_bus(bus(), copi(), cipo(), sck()) { + static_assert(bus() == bus_from_tx_pin(copi()), + "Bus parameter does not match pins"); static_assert(cipo() % 4 == 0, "SPI RX pin is invalid"); - // CS is a normal gpio static_assert(sck() % 4 == 2, "SPI SCK pin is invalid"); static_assert(copi() % 4 == 3, "SPI CS pin is invalid"); } - ~spi() override; + ~spi_bus(); + + /* Acquires a device assciated with a certain pin. Due to usage of software + * CS, a steady clock is necessary to wait the prerequesite time since SPI + * Peripheral unsets itself a little early */ + spi_channel acquire_device(pin_param auto pin, + hal::steady_clock&, + hal::v5::spi_channel::settings const& settings); + +protected: + spi_channel acquire_device(u8 pin, + hal::pollable_lock* lock, + hal::steady_clock&, + hal::v5::spi_channel::settings const& settings); + spi_bus(u8 bus, u8 tx, u8 rx, u8 sck); + u8 m_bus, m_tx, m_rx, m_sck; +}; + +template +struct spi_bus : spi_bus +{ + template + spi_bus(bus_param auto bus, + pin_param auto copi, + pin_param auto cipo, + pin_param auto sck, + Ts... args) + : spi_bus(bus, copi, cipo, sck) + , m_lock(std::forward(args)...) + { + } + + spi_channel acquire_device(pin_param auto pin, + hal::steady_clock&, + hal::v5::spi_channel::settings const& settings); + + Lock m_lock; +}; + +/* +RP chips suppport 16 bit transfers. It may be worthwhile to add an option +to transfer 16 bits as a time. TODO fix to add spi channel manager +*/ +struct spi_channel final : public hal::spi_channel +{ + friend spi_bus; + ~spi_channel() override; private: - spi(u8 bus, u8 copi, u8 cipo, u8 sck, u8 cs, spi::settings const&); + spi_channel(u8 bus, + u8 cs, + hal::pollable_lock*, + hal::steady_clock*, + settings const&); void driver_configure(settings const&) override; u32 driver_clock_rate() override; void driver_chip_select(bool p_select) override; @@ -68,8 +120,35 @@ struct spi final : public hal::spi_channel void driver_transfer(std::span out, std::span in, byte) override; - - u8 m_bus, m_tx, m_rx, m_sck, m_cs; + hal::steady_clock* m_clk; + hal::pollable_lock* m_lock; + u8 m_bus, m_cs; }; + +inline spi_channel spi_bus::acquire_device( + pin_param auto pin, + hal::steady_clock& clk, + hal::v5::spi_channel::settings const& s) +{ + return acquire_device(pin(), nullptr, clk, s); +} +template +spi_channel spi_bus::acquire_device( + pin_param auto pin, + hal::steady_clock& clk, + hal::v5::spi_channel::settings const& s) +{ + return acquire_device(pin(), &m_lock, clk, s); +} + +inline spi_channel spi_bus::acquire_device( + u8 pin, + hal::pollable_lock* lock, + hal::steady_clock& clk, + hal::v5::spi_channel::settings const& settings) +{ + return { m_bus, pin, lock, &clk, settings }; +} + } // namespace v5 } // namespace hal::rp diff --git a/src/rp/spi.cpp b/src/rp/spi.cpp index 813f9830..9af4cbe3 100644 --- a/src/rp/spi.cpp +++ b/src/rp/spi.cpp @@ -3,10 +3,12 @@ #include #include #include +#include #include +#include namespace { -auto spi_bus(hal::u8 busnum) +auto get_bus(hal::u8 busnum) { switch (busnum) { case 0: @@ -39,8 +41,8 @@ void spi::driver_configure(spi::settings const& s) spi_cpol_t polarity = s.clock_polarity ? SPI_CPOL_1 : SPI_CPOL_0; spi_cpha_t phase = s.clock_phase ? SPI_CPHA_1 : SPI_CPHA_0; - spi_init(spi_bus(m_bus), static_cast(s.clock_rate)); - spi_set_format(spi_bus(m_bus), 8, polarity, phase, SPI_MSB_FIRST); + spi_init(get_bus(m_bus), static_cast(s.clock_rate)); + spi_set_format(get_bus(m_bus), 8, polarity, phase, SPI_MSB_FIRST); } void spi::driver_transfer(std::span out, @@ -50,14 +52,18 @@ void spi::driver_transfer(std::span out, auto out_size = out.size_bytes(); auto in_size = in.size_bytes(); auto size = std::min(out_size, in_size); - spi_write_read_blocking(spi_bus(m_bus), out.data(), in.data(), size); + spi_write_read_blocking(get_bus(m_bus), out.data(), in.data(), size); if (out_size > in_size) { spi_write_blocking( - spi_bus(m_bus), out.data() + in_size, out_size - in_size); + get_bus(m_bus), out.data() + in_size, out_size - in_size); } else if (in_size > out_size) { spi_read_blocking( - spi_bus(m_bus), filler, in.data() + out_size, in_size - out_size); + get_bus(m_bus), filler, in.data() + out_size, in_size - out_size); } + // Does this wreck performance? probably + // Is this a bad idea? probably + // Is it necessary? Absolutely. See below for implementation details + sleep_us(10); } spi::~spi() @@ -65,29 +71,48 @@ spi::~spi() gpio_deinit(m_tx); gpio_deinit(m_rx); gpio_deinit(m_sck); - spi_deinit(spi_bus(m_bus)); + spi_deinit(get_bus(m_bus)); } } // namespace v4 namespace v5 { -spi::spi(u8 bus, u8 tx, u8 rx, u8 sck, u8 cs, spi::settings const& s) // NOLINT + +spi_bus::spi_bus(u8 bus, u8 tx, u8 rx, u8 sck) // NOLINT : m_bus(bus) , m_tx(tx) , m_rx(rx) , m_sck(sck) - , m_cs(cs) { - driver_configure(s); gpio_set_function(tx, GPIO_FUNC_SPI); gpio_set_function(rx, GPIO_FUNC_SPI); gpio_set_function(sck, GPIO_FUNC_SPI); +} + +spi_bus::~spi_bus() +{ + gpio_deinit(m_tx); + gpio_deinit(m_rx); + gpio_deinit(m_sck); + spi_deinit(get_bus(m_bus)); +} + +spi_channel::spi_channel(u8 bus, // NOLINT + u8 cs, + hal::pollable_lock* lock, + hal::steady_clock* clock, + settings const& s) + : m_clk(clock) + , m_lock(lock) + , m_bus(bus) + , m_cs(cs) +{ gpio_init(cs); gpio_set_dir(cs, GPIO_OUT); - gpio_put(cs, true); + driver_configure(s); } -void spi::driver_configure(spi::settings const& s) +void spi_channel::driver_configure(spi_channel::settings const& s) { spi_cpol_t polarity = SPI_CPOL_0; spi_cpha_t phase = SPI_CPHA_0; @@ -111,52 +136,58 @@ void spi::driver_configure(spi::settings const& s) default: break; } - spi_init(spi_bus(m_bus), s.clock_rate); - spi_set_format(spi_bus(m_bus), 8, polarity, phase, SPI_MSB_FIRST); + spi_init(get_bus(m_bus), s.clock_rate); + spi_set_format(get_bus(m_bus), 8, polarity, phase, SPI_MSB_FIRST); } -u32 spi::driver_clock_rate() +u32 spi_channel::driver_clock_rate() { - return spi_get_baudrate(spi_bus(m_bus)); + return spi_get_baudrate(get_bus(m_bus)); } -void spi::driver_chip_select(bool sel) +void spi_channel::driver_chip_select(bool sel) { - // The examples in - // https://github.com/raspberrypi/pico-examples/tree/master/spi seem to add a - // mysterious nop sequence before and after the assertion. Apparently the - // engineer who wrote this 15 years ago doesn't remember - // (https://forums.raspberrypi.com/viewtopic.php?t=332078) so its necessity is - // really unclear. In any case, there's a nonzero chance just the overhead of - // calling a function will mask this bug. If SPI seems a bit flaky, then - // consider adding asm nops here. - gpio_put(m_cs, !sel); + // https://github.com/raspberrypi/pico-examples/tree/master/spi + // The pico examples add NOPs explicitly, which seems to be to guard against + // the CS deasserting early. We wait explicitly, so that won't be necessary + using namespace std::chrono_literals; + if (sel) { + if (m_lock) + m_lock->lock(); + gpio_put(m_cs, false); + } else if (!sel) { + // Arm Primecell Synchronous Serial Port :tm: unsets the busy bit early, + // necessitating this + auto freq = this->driver_clock_rate(); + auto sleep_time = std::chrono::duration(1'000'000 / freq); + hal::delay(*m_clk, sleep_time); + gpio_put(m_cs, true); + + if (m_lock) + m_lock->unlock(); + } } -void spi::driver_transfer(std::span out, - std::span in, - byte filler) +void spi_channel::driver_transfer(std::span out, + std::span in, + byte filler) { auto out_size = out.size_bytes(); auto in_size = in.size_bytes(); auto size = std::min(out_size, in_size); - spi_write_read_blocking(spi_bus(m_bus), out.data(), in.data(), size); + spi_write_read_blocking(get_bus(m_bus), out.data(), in.data(), size); if (out_size > in_size) { spi_write_blocking( - spi_bus(m_bus), out.data() + in_size, out_size - in_size); + get_bus(m_bus), out.data() + in_size, out_size - in_size); } else if (in_size > out_size) { spi_read_blocking( - spi_bus(m_bus), filler, in.data() + out_size, in_size - out_size); + get_bus(m_bus), filler, in.data() + out_size, in_size - out_size); } } -spi::~spi() +spi_channel::~spi_channel() { - gpio_deinit(m_tx); - gpio_deinit(m_rx); - gpio_deinit(m_sck); gpio_deinit(m_cs); - spi_deinit(spi_bus(m_bus)); } } // namespace v5 } // namespace hal::rp