From 78fbc3dac62f17c8158d4e29c303b03d8c19b09b Mon Sep 17 00:00:00 2001 From: Nehal Patel Date: Sat, 29 Aug 2026 15:29:49 +0000 Subject: [PATCH 01/17] fix(build): repair the clang-tidy header filter and fix what it found The -header-filter regex ended with a stray '}', which no header path can match, so clang-tidy silently skipped every project header while the docs claimed header enforcement. Fix the regex (and use PROJECT_SOURCE_DIR so the filter stays correct if the project is ever embedded in a superbuild). Fix the diagnostics the working filter surfaced: - give every enum an explicit std::uint8_t base - use 'override' on derived-interface destructors in pin.hpp - add missing [[nodiscard]] (Dispatcher::Dispatch, HostUart query methods) - drop the default argument on the virtual Uart::Receive (no caller used it) - remove redundant {} member initializers - rename nlohmann adl_serializer parameters, and suppress, with a stated reason, the checks that the NLOHMANN_* macro expansions cannot satisfy Co-Authored-By: Claude Fable 5 --- CMakeLists.txt | 2 +- src/libs/board/host/host_board.hpp | 16 ++++++------- src/libs/common/error.hpp | 2 +- src/libs/common/logger.hpp | 3 ++- src/libs/mcu/host/dispatcher.hpp | 2 +- .../host/emulator_message_json_encoder.hpp | 24 +++++++++++++++---- src/libs/mcu/host/host_emulator_messages.hpp | 6 ++--- src/libs/mcu/host/host_pin.hpp | 2 +- src/libs/mcu/host/host_uart.hpp | 12 +++++----- src/libs/mcu/pin.hpp | 11 +++++---- src/libs/mcu/uart.hpp | 2 +- 11 files changed, 49 insertions(+), 33 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 69fc388..1784296 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -120,7 +120,7 @@ endif() # should get the same guard rails. include("${PROJECT_SOURCE_DIR}/cmake/format.cmake") -clang_tidy("-header-filter=${CMAKE_CURRENT_SOURCE_DIR}/src/.*}") +clang_tidy("-header-filter=${PROJECT_SOURCE_DIR}/src/.*") add_subdirectory(src) add_subdirectory(test) reset_clang_tidy() diff --git a/src/libs/board/host/host_board.hpp b/src/libs/board/host/host_board.hpp index 53f2f85..e0d68f0 100644 --- a/src/libs/board/host/host_board.hpp +++ b/src/libs/board/host/host_board.hpp @@ -49,15 +49,15 @@ class HostBoard : public Board { Endpoints endpoints_{}; // Store components (order matters for destruction) - std::unique_ptr user_led_1_{}; - std::unique_ptr user_led_2_{}; - std::unique_ptr user_button_1_{}; - std::unique_ptr uart_1_{}; - std::unique_ptr i2c_1_{}; + std::unique_ptr user_led_1_; + std::unique_ptr user_led_2_; + std::unique_ptr user_button_1_; + std::unique_ptr uart_1_; + std::unique_ptr i2c_1_; // Receiver map and dispatcher (built in Init() after components exist) - mcu::ReceiverMap receiver_map_{}; - std::optional dispatcher_{}; - std::unique_ptr zmq_transport_{}; + mcu::ReceiverMap receiver_map_; + std::optional dispatcher_; + std::unique_ptr zmq_transport_; }; } // namespace board diff --git a/src/libs/common/error.hpp b/src/libs/common/error.hpp index 270eb38..e898e35 100644 --- a/src/libs/common/error.hpp +++ b/src/libs/common/error.hpp @@ -4,7 +4,7 @@ namespace common { -enum class Error : uint32_t { +enum class Error : std::uint8_t { kOk = 1, kUnknown, kInvalidArgument, diff --git a/src/libs/common/logger.hpp b/src/libs/common/logger.hpp index 4e36e9b..009d752 100644 --- a/src/libs/common/logger.hpp +++ b/src/libs/common/logger.hpp @@ -1,10 +1,11 @@ #pragma once +#include #include namespace common { -enum class LogLevel { kDebug, kInfo, kWarning, kError }; +enum class LogLevel : std::uint8_t { kDebug, kInfo, kWarning, kError }; // Abstract logging interface class Logger { diff --git a/src/libs/mcu/host/dispatcher.hpp b/src/libs/mcu/host/dispatcher.hpp index 3bb7d18..86dd501 100644 --- a/src/libs/mcu/host/dispatcher.hpp +++ b/src/libs/mcu/host/dispatcher.hpp @@ -25,7 +25,7 @@ class Dispatcher { auto operator=(const Dispatcher&) -> Dispatcher& = delete; auto operator=(Dispatcher&&) -> Dispatcher& = delete; - auto Dispatch(const std::string_view& message) const + [[nodiscard]] auto Dispatch(const std::string_view& message) const -> std::expected { for (const auto& [predicate, receiver_ref] : receivers_) { if (predicate(message)) { diff --git a/src/libs/mcu/host/emulator_message_json_encoder.hpp b/src/libs/mcu/host/emulator_message_json_encoder.hpp index 80bd1a6..0cd6588 100644 --- a/src/libs/mcu/host/emulator_message_json_encoder.hpp +++ b/src/libs/mcu/host/emulator_message_json_encoder.hpp @@ -10,20 +10,30 @@ #include "libs/mcu/host/host_emulator_messages.hpp" #include "libs/mcu/pin.hpp" -// Custom JSON serialization for std::byte +// Custom JSON serialization for std::byte. The function names and signatures +// are nlohmann's ADL contract, so the project naming convention does not apply. namespace nlohmann { template <> struct adl_serializer { - static void to_json(json& j, const std::byte& b) { - j = std::to_integer(b); + // NOLINTNEXTLINE(readability-identifier-naming) + static void to_json(json& encoded, const std::byte& value) { + encoded = std::to_integer(value); } - static void from_json(const json& j, std::byte& b) { - b = static_cast(j.get()); + // NOLINTNEXTLINE(readability-identifier-naming) + static void from_json(const json& encoded, std::byte& value) { + value = static_cast(encoded.get()); } }; } // namespace nlohmann +// The NLOHMANN_* macros below expand to code that cannot satisfy the project's +// clang-tidy checks (short names, C arrays, pre-C++17 type traits); suppress +// those checks for the macro expansions only. +// NOLINTBEGIN(readability-identifier-length) +// NOLINTBEGIN(modernize-avoid-c-arrays) +// NOLINTBEGIN(modernize-type-traits) + namespace common { NLOHMANN_JSON_SERIALIZE_ENUM(Error, @@ -73,6 +83,10 @@ NLOHMANN_JSON_SERIALIZE_ENUM(ObjectType, { {ObjectType::kI2C, "I2C"}, }) +// NOLINTEND(modernize-type-traits) +// NOLINTEND(modernize-avoid-c-arrays) +// NOLINTEND(readability-identifier-length) + NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(PinEmulatorRequest, type, object, name, operation, state) diff --git a/src/libs/mcu/host/host_emulator_messages.hpp b/src/libs/mcu/host/host_emulator_messages.hpp index 854f4a8..fda8937 100644 --- a/src/libs/mcu/host/host_emulator_messages.hpp +++ b/src/libs/mcu/host/host_emulator_messages.hpp @@ -12,9 +12,9 @@ namespace mcu { -enum class MessageType { kRequest = 1, kResponse }; -enum class OperationType { kSet = 1, kGet, kSend, kReceive }; -enum class ObjectType { kPin = 1, kUart, kI2C }; +enum class MessageType : std::uint8_t { kRequest = 1, kResponse }; +enum class OperationType : std::uint8_t { kSet = 1, kGet, kSend, kReceive }; +enum class ObjectType : std::uint8_t { kPin = 1, kUart, kI2C }; struct PinEmulatorRequest { MessageType type{MessageType::kRequest}; diff --git a/src/libs/mcu/host/host_pin.hpp b/src/libs/mcu/host/host_pin.hpp index b28a916..aa49c77 100644 --- a/src/libs/mcu/host/host_pin.hpp +++ b/src/libs/mcu/host/host_pin.hpp @@ -42,7 +42,7 @@ class HostPin final : public BidirectionalPin, public Receiver { PinDirection direction_{PinDirection::kOutput}; PinState state_{PinState::kHighZ}; PinTransition transition_{PinTransition::kBoth}; - std::function handler_{}; + std::function handler_; }; } // namespace mcu diff --git a/src/libs/mcu/host/host_uart.hpp b/src/libs/mcu/host/host_uart.hpp index 2a849d3..21628eb 100644 --- a/src/libs/mcu/host/host_uart.hpp +++ b/src/libs/mcu/host/host_uart.hpp @@ -40,8 +40,8 @@ class HostUart final : public Uart, public Receiver { std::function)> callback) -> std::expected override; - auto IsBusy() const -> bool override; - auto Available() const -> size_t override; + [[nodiscard]] auto IsBusy() const -> bool override; + [[nodiscard]] auto Available() const -> size_t override; auto Flush() -> std::expected override; auto SetRxHandler(std::function handler) @@ -59,14 +59,14 @@ class HostUart final : public Uart, public Receiver { bool busy_{false}; // Async callback storage - std::function)> send_callback_{}; - std::function)> receive_callback_{}; + std::function)> send_callback_; + std::function)> receive_callback_; // Receive handler for unsolicited incoming data - std::function rx_handler_{}; + std::function rx_handler_; // Receive buffer for async operations - std::vector receive_buffer_{}; + std::vector receive_buffer_; }; } // namespace mcu diff --git a/src/libs/mcu/pin.hpp b/src/libs/mcu/pin.hpp index 466a089..f67ee38 100644 --- a/src/libs/mcu/pin.hpp +++ b/src/libs/mcu/pin.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -7,9 +8,9 @@ namespace mcu { -enum class PinDirection { kInput = 1, kOutput }; -enum class PinState { kLow = 1, kHigh, kHighZ }; -enum class PinTransition { kRising = 1, kFalling, kBoth }; +enum class PinDirection : std::uint8_t { kInput = 1, kOutput }; +enum class PinState : std::uint8_t { kLow = 1, kHigh, kHighZ }; +enum class PinTransition : std::uint8_t { kRising = 1, kFalling, kBoth }; class InputPin { public: @@ -23,7 +24,7 @@ class InputPin { class OutputPin : public virtual InputPin { public: - virtual ~OutputPin() = default; + ~OutputPin() override = default; [[nodiscard]] virtual auto SetHigh() -> std::expected = 0; @@ -33,7 +34,7 @@ class OutputPin : public virtual InputPin { class BidirectionalPin : public virtual InputPin, public virtual OutputPin { public: - virtual ~BidirectionalPin() = default; + ~BidirectionalPin() override = default; [[nodiscard]] virtual auto Configure(PinDirection direction) -> std::expected = 0; diff --git a/src/libs/mcu/uart.hpp b/src/libs/mcu/uart.hpp index 46b72a9..97ced8a 100644 --- a/src/libs/mcu/uart.hpp +++ b/src/libs/mcu/uart.hpp @@ -61,7 +61,7 @@ class Uart { /// @param timeout_ms Timeout in milliseconds (0 = wait forever) /// @return Number of bytes received or error [[nodiscard]] virtual auto Receive(std::span buffer, - uint32_t timeout_ms = 0) + uint32_t timeout_ms) -> std::expected = 0; /// @brief Send data asynchronously From 49c9e59bc0cccc25e137678c610774063a2d3cdb Mon Sep 17 00:00:00 2001 From: Nehal Patel Date: Sat, 29 Aug 2026 15:30:36 +0000 Subject: [PATCH 02/17] chore: untrack workspace clutter and regenerate compile_commands link MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit src/.DS_Store was Finder metadata with no reason to be tracked, and compile_commands.json was a tracked symlink into the gitignored build/ tree — dangling on a fresh clone until the exact 'host' preset was configured, and wrong for every other preset. Ignore both, and have configure recreate the symlink pointing at the build tree configured last, which is what IDE tooling actually wants. Narrow the *.pdf ignore to the one reference book (plus its extracted text) so real documentation can't be silently swallowed. Co-Authored-By: Claude Fable 5 --- .gitignore | 8 +++++++- CMakeLists.txt | 6 ++++++ compile_commands.json | 1 - src/.DS_Store | Bin 6148 -> 0 bytes 4 files changed, 13 insertions(+), 2 deletions(-) delete mode 120000 compile_commands.json delete mode 100644 src/.DS_Store diff --git a/.gitignore b/.gitignore index 55bf81b..c7ccd45 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,11 @@ CMakeUserPresets.json .vscode/ .cache/ .claude/settings.local.json +.DS_Store + +# Symlink into the configured build tree, recreated at configure time by the +# root CMakeLists.txt. +/compile_commands.json # Python __pycache__/ @@ -22,4 +27,5 @@ __pycache__/ .ruff_cache/ # Reference material (not source) -*.pdf +/ProfessionalCMake_21st_Edition.pdf +/ProfessionalCMake_21st_Edition.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 1784296..3c1315d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,12 @@ set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +# Keep a compile_commands.json symlink at the repo root so clangd and IDEs find +# the database of whichever build tree was configured last. The link is +# regenerated on every configure and is gitignored. +file(CREATE_LINK "${PROJECT_BINARY_DIR}/compile_commands.json" + "${PROJECT_SOURCE_DIR}/compile_commands.json" SYMBOLIC) + # Warnings only. Optimization and debug-info flags are per-configuration and must # not be set here: these options are appended after the per-config flags, and the # last -O on the command line wins, so a global -Os silently overrode Release's diff --git a/compile_commands.json b/compile_commands.json deleted file mode 120000 index e4aabf7..0000000 --- a/compile_commands.json +++ /dev/null @@ -1 +0,0 @@ -./build/host/compile_commands.json \ No newline at end of file diff --git a/src/.DS_Store b/src/.DS_Store deleted file mode 100644 index c1591cb20d9272f0de0722d1cf1b3f7e81b82418..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~K?=e^3`G;|LU7Zi%h`AUZ!n0SpcfDkL?{&$UC+_|$ppdbT10*z`IF3q(zoa| zBBJZtVI|Uv$P8{OOA8}Y&JhRx4X|sK7L)2aQ(s zF~sWL4oz_`hnA|fT{MOdjVG&3F)*#|q6rC1vkLuHpsU4g1L!&>UK-q5|WOfMZ}Ffv*yH E00xl~y#N3J From e6740b6375a4aa6bcf569d67cc809fc460389403 Mon Sep 17 00:00:00 2001 From: Nehal Patel Date: Sat, 29 Aug 2026 15:32:07 +0000 Subject: [PATCH 03/17] fix(toolchain): stop host-clang.cmake overriding user-chosen compilers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The toolchain file set CMAKE_{C,CXX,ASM}_COMPILER with plain set(), which shadows the cache variables a CMakeUserPresets.json entry provides — so the documented macOS/Homebrew flow silently fell back to clang-18, which does not exist there. Guard each compiler assignment with if(NOT DEFINED ...) so an explicit choice always wins and HOST_TOOLCHAIN_PATH/-VERSION remain the fallback. Drop CMAKE_LINKER from the example presets (it names a linker, not the compiler driver; CMake links through the compiler) and the two custom tool variables nothing consumed. LLVM_COV_PATH/LLVM_PROFDATA_PATH stay: the code-coverage module reads them. Co-Authored-By: Claude Fable 5 --- CMakeUserPresets.json.example | 3 +-- cmake/toolchain/host-clang.cmake | 36 +++++++++++++++++++------------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/CMakeUserPresets.json.example b/CMakeUserPresets.json.example index 22b5084..e130b5c 100644 --- a/CMakeUserPresets.json.example +++ b/CMakeUserPresets.json.example @@ -14,8 +14,7 @@ "cacheVariables": { "CMAKE_PRESET": "host", "CMAKE_C_COMPILER": "/opt/homebrew/opt/llvm/bin/clang", - "CMAKE_CXX_COMPILER": "/opt/homebrew/opt/llvm/bin/clang++", - "CMAKE_LINKER": "/opt/homebrew/opt/llvm/bin/clang" + "CMAKE_CXX_COMPILER": "/opt/homebrew/opt/llvm/bin/clang++" } }, { diff --git a/cmake/toolchain/host-clang.cmake b/cmake/toolchain/host-clang.cmake index 4bc447a..ba0f211 100644 --- a/cmake/toolchain/host-clang.cmake +++ b/cmake/toolchain/host-clang.cmake @@ -1,35 +1,41 @@ -# Host toolchain for clang/LLVM -# Compiler path is set via CMakeUserPresets.json for machine-specific configuration -# This allows each developer to set their own path without modifying the project files -# (e.g., Homebrew LLVM, system clang, or custom installation) - +# Host toolchain for clang/LLVM. +# +# Two ways to point at a specific installation, both via CMakeUserPresets.json +# (see CMakeUserPresets.json.example): +# - set CMAKE_C_COMPILER / CMAKE_CXX_COMPILER cache variables directly, or +# - set HOST_TOOLCHAIN_PATH (and optionally HOST_TOOLCHAIN_VERSION) and let +# this file derive every tool from them. +# Explicitly set compilers always win: this file only fills in what the user +# has not chosen, so a preset's cache variables are never silently overridden. if(DEFINED HOST_TOOLCHAIN_PATH) set(TOOLCHAIN_PREFIX "${HOST_TOOLCHAIN_PATH}/") set(CMAKE_FIND_ROOT_PATH ${HOST_TOOLCHAIN_PATH}) else() set(TOOLCHAIN_PREFIX "") - message(STATUS "HOST_TOOLCHAIN_PATH not set, searching for clang in PATH") endif() -if (NOT DEFINED HOST_TOOLCHAIN_VERSION) +if(NOT DEFINED HOST_TOOLCHAIN_VERSION) set(TOOLCHAIN_SUFFIX "-18") # Default to version 18 if not specified else() set(TOOLCHAIN_SUFFIX "-${HOST_TOOLCHAIN_VERSION}") endif() -# Standard LLVM toolchain utilities -set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}clang${TOOLCHAIN_SUFFIX}) -set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}clang++${TOOLCHAIN_SUFFIX}) -set(CMAKE_ASM_COMPILER ${TOOLCHAIN_PREFIX}clang${TOOLCHAIN_SUFFIX}) -set(CMAKE_SIZE_UTIL ${TOOLCHAIN_PREFIX}llvm-size${TOOLCHAIN_SUFFIX}) +if(NOT DEFINED CMAKE_C_COMPILER) + set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}clang${TOOLCHAIN_SUFFIX}) +endif() +if(NOT DEFINED CMAKE_CXX_COMPILER) + set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}clang++${TOOLCHAIN_SUFFIX}) +endif() +if(NOT DEFINED CMAKE_ASM_COMPILER) + set(CMAKE_ASM_COMPILER ${TOOLCHAIN_PREFIX}clang${TOOLCHAIN_SUFFIX}) +endif() + set(CMAKE_OBJCOPY ${TOOLCHAIN_PREFIX}llvm-objcopy${TOOLCHAIN_SUFFIX}) set(CMAKE_OBJDUMP ${TOOLCHAIN_PREFIX}llvm-objdump${TOOLCHAIN_SUFFIX}) -set(CMAKE_NM_UTIL ${TOOLCHAIN_PREFIX}llvm-nm${TOOLCHAIN_SUFFIX}) set(CMAKE_AR ${TOOLCHAIN_PREFIX}llvm-ar${TOOLCHAIN_SUFFIX}) set(CMAKE_RANLIB ${TOOLCHAIN_PREFIX}llvm-ranlib${TOOLCHAIN_SUFFIX}) -# Code coverage tools +# Consumed by the code-coverage module (StableCoder cmake-scripts) set(LLVM_COV_PATH ${TOOLCHAIN_PREFIX}llvm-cov${TOOLCHAIN_SUFFIX}) set(LLVM_PROFDATA_PATH ${TOOLCHAIN_PREFIX}llvm-profdata${TOOLCHAIN_SUFFIX}) - From e9adc932d57c73fe21726d3e7621be90eef6250a Mon Sep 17 00:00:00 2001 From: Nehal Patel Date: Sat, 29 Aug 2026 15:33:36 +0000 Subject: [PATCH 04/17] chore: delete dead directories and leftover fragments - external/ held only an empty CMakeLists.txt yet was wired into the build and into three coverage-exclusion patterns - src/libs/board/nrf52832_dk/ contained a single 0-byte CMakeLists.txt and was referenced by nothing - test/ held no targets, only a placeholder comment (now in CLAUDE.md's Testing section) and byte-identical copies of src/'s .clang-format and .clang-tidy - py/CMakeLists.txt was a pure pass-through; the root now adds py/host-emulator directly - remove a '# board.cpp)' remnant and two commented-out statements in host_pin.cpp whose explanation already lives in the adjacent prose Co-Authored-By: Claude Fable 5 --- CLAUDE.md | 1 + CMakeLists.txt | 11 ++++------- external/CMakeLists.txt | 0 py/CMakeLists.txt | 2 -- src/libs/board/CMakeLists.txt | 2 +- src/libs/board/nrf52832_dk/CMakeLists.txt | 0 src/libs/mcu/host/host_pin.cpp | 2 -- test/.clang-format | 1 - test/.clang-tidy | 1 - test/CMakeLists.txt | 5 ----- test/README.md | 1 - 11 files changed, 6 insertions(+), 20 deletions(-) delete mode 100644 external/CMakeLists.txt delete mode 100644 py/CMakeLists.txt delete mode 100644 src/libs/board/nrf52832_dk/CMakeLists.txt delete mode 120000 test/.clang-format delete mode 120000 test/.clang-tidy delete mode 100644 test/CMakeLists.txt delete mode 100644 test/README.md diff --git a/CLAUDE.md b/CLAUDE.md index a7d82c2..e97807a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -102,6 +102,7 @@ class MyApp { - **C++ unit tests**: Colocated with code (`src/libs/mcu/host/test_*.cpp`), use Google Test - **Python integration tests**: `py/host-emulator/tests/`, use pytest with fixtures that manage emulator/app lifecycle. CTest builds a uv venv under `build/host/host_emulator_venv` and runs them as the `host_emulator_test` target +- **System tests**: none yet — end-to-end coverage lives in the Python integration tests. Add a dedicated harness only when a test doesn't fit the emulator harness - **clang-tidy**: Runs automatically during build, no separate step needed - **Python tooling**: uv + ruff + strict mypy, all configured in `py/host-emulator/pyproject.toml` diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c1315d..c5d2d04 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -91,8 +91,6 @@ FetchContent_Declare(json GIT_REPOSITORY https://github.com/nlohmann/json GIT_TA FetchContent_MakeAvailable(etl) -add_subdirectory(external) - include(tools) include(code-coverage) @@ -100,9 +98,9 @@ include(code-coverage) if(CODE_COVERAGE) # Create 'ccov-all' target and set global exclusions add_code_coverage_all_targets( - EXCLUDE "test_*.cpp" "*/test/*" "*/_deps/*" "*/googletest/*" "*/gtest/*" "*/external/*" - LLVM_EXCLUDE ".*/test_.*\\.cpp" ".*/test/.*" ".*/_deps/.*" ".*/googletest/.*" ".*/gtest/.*" ".*/external/.*" - LCOV_EXCLUDE "*/test_*.cpp" "*/test/*" "*/_deps/*" "*/googletest/*" "*/gtest/*" "*/external/*" + EXCLUDE "test_*.cpp" "*/test/*" "*/_deps/*" "*/googletest/*" "*/gtest/*" + LLVM_EXCLUDE ".*/test_.*\\.cpp" ".*/test/.*" ".*/_deps/.*" ".*/googletest/.*" ".*/gtest/.*" + LCOV_EXCLUDE "*/test_*.cpp" "*/test/*" "*/_deps/*" "*/googletest/*" "*/gtest/*" ) endif() @@ -128,6 +126,5 @@ include("${PROJECT_SOURCE_DIR}/cmake/format.cmake") clang_tidy("-header-filter=${PROJECT_SOURCE_DIR}/src/.*") add_subdirectory(src) -add_subdirectory(test) reset_clang_tidy() -add_subdirectory(py) # No clang-tidy for python +add_subdirectory(py/host-emulator) # No clang-tidy for python diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt deleted file mode 100644 index e69de29..0000000 diff --git a/py/CMakeLists.txt b/py/CMakeLists.txt deleted file mode 100644 index ee35c1d..0000000 --- a/py/CMakeLists.txt +++ /dev/null @@ -1,2 +0,0 @@ - -add_subdirectory(host-emulator) diff --git a/src/libs/board/CMakeLists.txt b/src/libs/board/CMakeLists.txt index 1912753..8610ccb 100644 --- a/src/libs/board/CMakeLists.txt +++ b/src/libs/board/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(board INTERFACE board.hpp) # board.cpp) +add_library(board INTERFACE board.hpp) target_compile_options(board INTERFACE ${COMMON_COMPILE_OPTIONS}) set_target_properties(board PROPERTIES LINKER_LANGUAGE CXX) target_link_libraries(board INTERFACE mcu error) diff --git a/src/libs/board/nrf52832_dk/CMakeLists.txt b/src/libs/board/nrf52832_dk/CMakeLists.txt deleted file mode 100644 index e69de29..0000000 diff --git a/src/libs/mcu/host/host_pin.cpp b/src/libs/mcu/host/host_pin.cpp index dc6f51a..a131b2d 100644 --- a/src/libs/mcu/host/host_pin.cpp +++ b/src/libs/mcu/host/host_pin.cpp @@ -108,9 +108,7 @@ auto HostPin::GetState() -> std::expected { } // If the MCU is polling the input, then it should NOT be configured // for interrupts. Therefore, we should not invoke the handler. - // const PinState prev_state{state_}; state_ = resp->state; - // CheckAndInvokeHandler(prev_state, resp.state); return resp->state; }); } diff --git a/test/.clang-format b/test/.clang-format deleted file mode 120000 index ef1c8ae..0000000 --- a/test/.clang-format +++ /dev/null @@ -1 +0,0 @@ -../src/.clang-format \ No newline at end of file diff --git a/test/.clang-tidy b/test/.clang-tidy deleted file mode 120000 index f3d0373..0000000 --- a/test/.clang-tidy +++ /dev/null @@ -1 +0,0 @@ -../src/.clang-tidy \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt deleted file mode 100644 index 52c3120..0000000 --- a/test/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -# System-level tests. -# -# Currently empty: end-to-end coverage lives in py/host-emulator/tests/ and runs -# via the `host_emulator_test` CTest target. This directory is a placeholder for -# system tests that don't fit the Python emulator harness. diff --git a/test/README.md b/test/README.md deleted file mode 100644 index fe37e15..0000000 --- a/test/README.md +++ /dev/null @@ -1 +0,0 @@ -# System-level tests \ No newline at end of file From 0bd022f6fdb998f81a7140c360e9a3085fe66855 Mon Sep 17 00:00:00 2001 From: Nehal Patel Date: Sat, 29 Aug 2026 15:33:45 +0000 Subject: [PATCH 05/17] docs: state the Python version the project actually requires pyproject.toml pins requires-python >=3.14 and .python-version selects 3.14; a reader following the README's 3.11+ would install a Python that uv sync rejects. Co-Authored-By: Claude Fable 5 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 94edd67..737521a 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ docker compose run --rm host-debug ### Local Build -**Requirements**: CMake 3.27+, Ninja, Clang 18+, Python 3.11+, [uv](https://docs.astral.sh/uv/), ZeroMQ (libzmq3-dev) +**Requirements**: CMake 3.27+, Ninja, Clang 18+, Python 3.14+, [uv](https://docs.astral.sh/uv/), ZeroMQ (libzmq3-dev) ```bash cmake --workflow --preset=host-debug # Configure + build + test From 4bf45c238178a2dfc21e6f4435b9285edbacbff7 Mon Sep 17 00:00:00 2001 From: Nehal Patel Date: Sat, 29 Aug 2026 15:37:32 +0000 Subject: [PATCH 06/17] refactor(build): park unbuilt hardware scaffolding behind clear guards The STM32 CubeMX vendor trees (~4,700 lines of generated C, linker scripts, and startup assembly) were never compiled: the ARM presets cannot configure while only the host MCU backend exists, and the two copies of stm32f3xx_hal_conf.h had already drifted apart. Remove them; git history preserves them for when hardware work starts, and regenerating from CubeMX will be fresher then anyway. What remains is the honest teaching artifact: the ARM toolchain files and hidden configure presets. Configuring an ARM preset now stops with a message naming the unimplemented backend instead of a raw add_subdirectory error, and the broken ARM build/workflow presets that advertised runnable builds are gone. Toolchain cleanups in armgcc.cmake: - move warning policy out (a project decision, not a toolchain one) - fix the CMAKE_CXX_ASM_FLAGS_DEBUG_INIT typo so ARM Debug gets -O0 - drop the undefined LD_FLAGS expansion, the manual CMAKE_CROSSCOMPILING (derived from CMAKE_SYSTEM_NAME), unused tool variables, and commented-out code - add FIND_ROOT_PATH_MODE PROGRAM/LIBRARY so cross builds cannot pick up host programs or libraries Preset cleanups: drop the empty include list, the no-op hidden 'arm' preset, the duplicated test-preset execution block, and the stm32cubef7 dependency declaration nothing could reach. CMAKE_RUNTIME_OUTPUT_DIRECTORY moves from the presets into the top-level CMakeLists.txt where build layout belongs. Docs updated to match. Co-Authored-By: Claude Fable 5 --- CLAUDE.md | 10 +- CMakeLists.txt | 15 +- CMakePresets.json | 88 +-- README.md | 15 +- cmake/toolchain/armgcc.cmake | 33 +- src/libs/board/CMakeLists.txt | 5 + .../board/stm32f3_discovery/CMakeLists.txt | 47 -- src/libs/board/stm32f3_discovery/Inc/main.h | 32 - .../Inc/stm32f3xx_hal_conf.h | 333 ---------- .../stm32f3_discovery/Inc/stm32f3xx_it.h | 47 -- .../stm32f3_discovery/STM32F303VCTx_FLASH.ld | 189 ------ src/libs/board/stm32f3_discovery/Src/main.c | 160 ----- .../stm32f3_discovery/Src/stm32f3xx_hal_msp.c | 91 --- .../stm32f3_discovery/Src/stm32f3xx_it.c | 166 ----- .../stm32f3_discovery/Src/system_stm32f3xx.c | 294 --------- .../board/stm32f3_discovery/newlib_nano.c | 85 --- .../stm32f3_discovery/startup_stm32f303xc.s | 460 ------------- .../stm32f3_discovery/stm32f3xx_hal_conf.h | 356 ---------- src/libs/board/stm32f3_discovery/syscalls.c | 194 ------ .../board/stm32f767zi_nucleo/CMakeLists.txt | 0 src/libs/board/stm32f767zi_nucleo/Inc/main.h | 32 - .../Inc/stm32f7xx_hal_conf.h | 475 -------------- .../stm32f767zi_nucleo/Inc/stm32f7xx_it.h | 48 -- .../stm32f767zi_nucleo/STM32F767ZITx_FLASH.ld | 169 ----- .../STM32F767ZITx_ITCM_FLASH.ld | 169 ----- src/libs/board/stm32f767zi_nucleo/Src/main.c | 241 ------- .../Src/stm32f7xx_hal_msp.c | 88 --- .../stm32f767zi_nucleo/Src/stm32f7xx_it.c | 167 ----- .../stm32f767zi_nucleo/Src/system_stm32f7xx.c | 260 -------- .../stm32f767zi_nucleo/startup_stm32f767xx.s | 613 ------------------ src/libs/mcu/CMakeLists.txt | 7 +- 31 files changed, 51 insertions(+), 4838 deletions(-) delete mode 100644 src/libs/board/stm32f3_discovery/CMakeLists.txt delete mode 100644 src/libs/board/stm32f3_discovery/Inc/main.h delete mode 100644 src/libs/board/stm32f3_discovery/Inc/stm32f3xx_hal_conf.h delete mode 100644 src/libs/board/stm32f3_discovery/Inc/stm32f3xx_it.h delete mode 100644 src/libs/board/stm32f3_discovery/STM32F303VCTx_FLASH.ld delete mode 100644 src/libs/board/stm32f3_discovery/Src/main.c delete mode 100644 src/libs/board/stm32f3_discovery/Src/stm32f3xx_hal_msp.c delete mode 100644 src/libs/board/stm32f3_discovery/Src/stm32f3xx_it.c delete mode 100644 src/libs/board/stm32f3_discovery/Src/system_stm32f3xx.c delete mode 100644 src/libs/board/stm32f3_discovery/newlib_nano.c delete mode 100644 src/libs/board/stm32f3_discovery/startup_stm32f303xc.s delete mode 100644 src/libs/board/stm32f3_discovery/stm32f3xx_hal_conf.h delete mode 100644 src/libs/board/stm32f3_discovery/syscalls.c delete mode 100644 src/libs/board/stm32f767zi_nucleo/CMakeLists.txt delete mode 100644 src/libs/board/stm32f767zi_nucleo/Inc/main.h delete mode 100644 src/libs/board/stm32f767zi_nucleo/Inc/stm32f7xx_hal_conf.h delete mode 100644 src/libs/board/stm32f767zi_nucleo/Inc/stm32f7xx_it.h delete mode 100644 src/libs/board/stm32f767zi_nucleo/STM32F767ZITx_FLASH.ld delete mode 100644 src/libs/board/stm32f767zi_nucleo/STM32F767ZITx_ITCM_FLASH.ld delete mode 100644 src/libs/board/stm32f767zi_nucleo/Src/main.c delete mode 100644 src/libs/board/stm32f767zi_nucleo/Src/stm32f7xx_hal_msp.c delete mode 100644 src/libs/board/stm32f767zi_nucleo/Src/stm32f7xx_it.c delete mode 100644 src/libs/board/stm32f767zi_nucleo/Src/system_stm32f7xx.c delete mode 100644 src/libs/board/stm32f767zi_nucleo/startup_stm32f767xx.s diff --git a/CLAUDE.md b/CLAUDE.md index e97807a..a579585 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -39,11 +39,11 @@ cmake --build build/host --target format-check # Python type-check (not covered by format.sh - types are not formatting) cd py/host-emulator && uv run mypy src -# Cross-compile for ARM - not yet functional. Presets and toolchain files exist, -# but src/libs/mcu/CMakeLists.txt does add_subdirectory(${EMBEDDED_CPP_MCU}) and -# only the `host` implementation exists, so configure fails on the missing -# arm_cm4/ directory. Host build and emulation come first; hardware follows. -cmake --workflow --preset=stm32f3_discovery-release +# Cross-compile for ARM - not yet functional. Toolchain files and configure +# presets exist, but only the `host` MCU/board implementations do; configuring +# an ARM preset stops with a message saying the backend is not implemented. +# Host build and emulation come first; hardware follows. +cmake --preset=stm32f3_discovery # Docker alternative docker compose run --rm host-debug diff --git a/CMakeLists.txt b/CMakeLists.txt index c5d2d04..47da6bc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,11 @@ set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +# One bin/ directory per build tree (with per-config subdirectories under the +# multi-config generator). A build-layout decision, so it lives here rather +# than in the presets. +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin") + # Keep a compile_commands.json symlink at the repo root so clangd and IDEs find # the database of whichever build tree was configured last. The link is # regenerated on every configure and is gitignored. @@ -61,12 +66,6 @@ FetchContent_Declare( GIT_TAG v1.14.0 ) -FetchContent_Declare( - stm32cubef7 - GIT_REPOSITORY https://github.com/STMicroelectronics/STM32CubeF7 - GIT_TAG v1.17.1 -) - FetchContent_Declare( CmakeScripts GIT_REPOSITORY https://github.com/StableCoder/cmake-scripts.git @@ -115,10 +114,6 @@ if(CMAKE_PRESET STREQUAL "host") enable_testing() endif() -if(CMAKE_PRESET STREQUAL "arm-cm7") - FetchContent_MakeAvailable(stm32cubef7) -endif() - # `format` / `format-check` targets, and the pre-commit hook. Included for every # preset: formatting is not host-specific, and a cross-compiling developer # should get the same guard rails. diff --git a/CMakePresets.json b/CMakePresets.json index df9ae8f..5b1a5db 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -5,7 +5,6 @@ "minor": 27, "patch": 0 }, - "include": [], "configurePresets": [ { "name": "default", @@ -14,8 +13,7 @@ "generator": "Ninja Multi-Config", "binaryDir": "${sourceDir}/build/${presetName}", "cacheVariables": { - "CMAKE_PRESET": "${presetName}", - "CMAKE_RUNTIME_OUTPUT_DIRECTORY": "${sourceDir}/build/${presetName}/bin" + "CMAKE_PRESET": "${presetName}" }, "hidden": true }, @@ -33,24 +31,14 @@ "CODE_COVERAGE": "ON" } }, - { - "name": "arm", - "displayName": "ARM", - "description": "Build for ARM using Ninja", - "hidden": true, - "inherits": [ - "default" - ] - }, { "name": "arm-cm4", "displayName": "ARM CM4", - "description": "Build for ARM CM4 using Ninja", + "description": "Cross-compile for ARM Cortex-M4. Configures the toolchain only: no Cortex-M4 MCU backend exists yet, so configuring stops with a message saying so.", "toolchainFile": "cmake/toolchain/armgcc-cm4.cmake", "hidden": true, "inherits": [ - "default", - "arm" + "default" ], "cacheVariables": { "EMBEDDED_CPP_MCU": "arm_cm4" @@ -59,12 +47,11 @@ { "name": "arm-cm7", "displayName": "ARM CM7", - "description": "Build for ARM CM7 using Ninja", + "description": "Cross-compile for ARM Cortex-M7. Configures the toolchain only: no Cortex-M7 MCU backend exists yet, so configuring stops with a message saying so.", "toolchainFile": "cmake/toolchain/armgcc-cm7.cmake", "hidden": true, "inherits": [ - "default", - "arm" + "default" ], "cacheVariables": { "EMBEDDED_CPP_MCU": "arm_cm7" @@ -73,7 +60,7 @@ { "name": "stm32f3_discovery", "displayName": "STM32F3 Discovery", - "description": "Build for STM32F3 Discovery using Ninja", + "description": "Future hardware target; not implemented yet (see CLAUDE.md)", "inherits": [ "arm-cm4" ], @@ -117,32 +104,6 @@ "description": "Host build (RelWithDebInfo) using Ninja", "inherits": "host", "configuration": "RelWithDebInfo" - }, - { - "name": "stm32f3_discovery", - "displayName": "STM32F3 Discovery", - "description": "STM32F3 Discovery build using Ninja", - "configurePreset": "stm32f3_discovery" - }, - { - "name": "stm32f3_discovery-release", - "displayName": "STM32F3 Discovery Release", - "description": "STM32F3 Discovery build (Release) using Ninja", - "configurePreset": "stm32f3_discovery", - "configuration": "Release" - }, - { - "name": "arm-cm7", - "displayName": "ARM CM7", - "description": "ARM CM7 build using Ninja", - "configurePreset": "arm-cm7" - }, - { - "name": "arm-cm7-release", - "displayName": "ARM CM7 Release", - "description": "ARM CM7 build (Release) using Ninja", - "configurePreset": "arm-cm7", - "configuration": "Release" } ], "testPresets": [ @@ -163,13 +124,6 @@ { "name": "host", "configurePreset": "host", - "output": { - "outputOnFailure": true - }, - "execution": { - "noTestsAction": "error", - "stopOnFailure": true - }, "hidden": true, "inherits": "default" }, @@ -252,36 +206,6 @@ "name": "host-relwithdebinfo" } ] - }, - { - "name": "stm32f3_discovery-release", - "displayName": "STM32F3 Discovery - Build", - "description": "Configure and build (Release) for STM32F3 Discovery", - "steps": [ - { - "type": "configure", - "name": "stm32f3_discovery" - }, - { - "type": "build", - "name": "stm32f3_discovery-release" - } - ] - }, - { - "name": "arm-cm7-release", - "displayName": "ARM CM7 - Build", - "description": "Configure and build (Release) for ARM Cortex-M7", - "steps": [ - { - "type": "configure", - "name": "arm-cm7" - }, - { - "type": "build", - "name": "arm-cm7-release" - } - ] } ] } diff --git a/README.md b/README.md index 737521a..73c7387 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ Application (apps/) → Board (libs/board/) → MCU (libs/mcu/) → Platfo - **apps/**: Example applications (blinky, uart_echo, i2c_demo) - **libs/mcu/**: Hardware abstractions (Pin, UART, I2C, Delay) with host emulation -- **libs/board/**: Board-specific implementations (host, STM32F3, STM32F7, nRF52) +- **libs/board/**: Board-specific implementations (host today; hardware boards planned) - **py/host-emulator/**: Python hardware simulator for desktop testing ## Build Commands @@ -57,9 +57,10 @@ cmake --workflow --preset=host-debug cmake --workflow --preset=host-release # ARM targets - not yet functional (see Implementation Status below). -# The presets and toolchain files are in place, but configuring fails until -# the MCU layer lands in src/libs/mcu/arm_cm4/ (and arm_cm7/ for the F7). -cmake --workflow --preset=stm32f3_discovery-release +# Toolchain files and configure presets are in place, but configuring stops +# with a clear message until the MCU layer lands in src/libs/mcu/arm_cm4/ +# (and arm_cm7/ for Cortex-M7 parts). +cmake --preset=stm32f3_discovery ``` ## Running Tests @@ -94,7 +95,7 @@ cd py/host-emulator && uv run python -m host_emulator.emulator | Compilers | Clang 18 (host), ARM GCC (embedded) | | Testing | Google Test, pytest | | IPC | ZeroMQ + JSON | -| Targets | STM32F3, STM32F7, nRF52832 | +| Targets | Host emulation (hardware targets planned) | ## Code Quality @@ -113,8 +114,8 @@ cd py/host-emulator && uv run python -m host_emulator.emulator | Python integration tests | ✅ Working | | Docker/DevContainer | ✅ Working | | CI/CD | ✅ Working | -| STM32F3/F7 | 🚧 Partial | -| nRF52832 | ⚠️ Placeholder | +| ARM cross-compile toolchain | 🚧 Toolchain/presets only | +| Hardware boards (STM32, nRF52) | 📋 Planned | ## Resources diff --git a/cmake/toolchain/armgcc.cmake b/cmake/toolchain/armgcc.cmake index 49796ca..271b927 100644 --- a/cmake/toolchain/armgcc.cmake +++ b/cmake/toolchain/armgcc.cmake @@ -1,16 +1,19 @@ -# ARM GCC Toolchain file for embedded targets -# Toolchain path is set via ARM_TOOLCHAIN_PATH environment in CMakeUserPresets.json -# This allows each developer to set their own path without modifying the project files +# ARM GCC toolchain for embedded targets. Expects MCPU_FLAGS / VFP_FLAGS from +# the including per-core file (armgcc-cm4.cmake, armgcc-cm7.cmake). +# +# Toolchain path is set via ARM_TOOLCHAIN_PATH in CMakeUserPresets.json so each +# developer can point at their own installation without modifying the project. +# +# This file carries only what the tools need: target selection, codegen flags, +# and per-configuration optimization levels. Warning policy is a project +# decision and lives with the project's targets, not here. set(CMAKE_SYSTEM_NAME Generic) set(CMAKE_SYSTEM_PROCESSOR arm) -set(CMAKE_CROSSCOMPILING 1) set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) set(TARGET_TRIPLET "arm-none-eabi") - - # If ARM_TOOLCHAIN_PATH is set, use it; otherwise rely on PATH if(DEFINED ARM_TOOLCHAIN_PATH) set(TOOLCHAIN_PREFIX "${ARM_TOOLCHAIN_PATH}/${TARGET_TRIPLET}-") @@ -23,29 +26,28 @@ endif() set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}gcc) set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}g++) set(CMAKE_ASM_COMPILER ${TOOLCHAIN_PREFIX}gcc) -set(CMAKE_LINKER ${TOOLCHAIN_PREFIX}gcc) -set(CMAKE_SIZE_UTIL ${TOOLCHAIN_PREFIX}size) set(CMAKE_OBJCOPY ${TOOLCHAIN_PREFIX}objcopy) set(CMAKE_OBJDUMP ${TOOLCHAIN_PREFIX}objdump) -set(CMAKE_NM_UTIL ${TOOLCHAIN_PREFIX}nm) set(CMAKE_AR ${TOOLCHAIN_PREFIX}ar) set(CMAKE_RANLIB ${TOOLCHAIN_PREFIX}ranlib) - +# Cross builds must never pick up host programs or libraries. +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) # Compiler and linker flags -set(CMAKE_COMMON_FLAGS "${MCPU_FLAGS} ${VFP_FLAGS} -g3 -fstack-usage -ffunction-sections -fdata-sections -fno-strict-aliasing -fno-builtin -fno-common -Wall -Wshadow -Wdouble-promotion -Werror -Wundef -Wformat=2 -Wno-unused-parameter") +set(CMAKE_COMMON_FLAGS "${MCPU_FLAGS} ${VFP_FLAGS} -g3 -fstack-usage -ffunction-sections -fdata-sections -fno-strict-aliasing -fno-builtin -fno-common") -SET(CMAKE_ASM_OPTIONS "-x assembler-with-cpp") +set(CMAKE_ASM_OPTIONS "-x assembler-with-cpp") set(CMAKE_C_FLAGS_INIT "${CMAKE_COMMON_FLAGS}") set(CMAKE_CXX_FLAGS_INIT "${CMAKE_COMMON_FLAGS}") set(CMAKE_ASM_FLAGS_INIT "${CMAKE_COMMON_FLAGS} ${CMAKE_ASM_OPTIONS}") -set(CMAKE_EXE_LINKER_FLAGS_INIT "${LD_FLAGS} --specs=nano.specs -Wl,--gc-sections,-print-memory-usage,--no-warn-rwx-segments") +set(CMAKE_EXE_LINKER_FLAGS_INIT "--specs=nano.specs -Wl,--gc-sections,-print-memory-usage,--no-warn-rwx-segments") set(CMAKE_C_FLAGS_DEBUG_INIT "-O0") -set(CMAKE_CXX_ASM_FLAGS_DEBUG_INIT "-O0") +set(CMAKE_CXX_FLAGS_DEBUG_INIT "-O0") set(CMAKE_ASM_FLAGS_DEBUG_INIT "") set(CMAKE_EXE_LINKER_FLAGS_DEBUG_INIT "") @@ -53,6 +55,3 @@ set(CMAKE_C_FLAGS_RELEASE_INIT "-Os -flto") set(CMAKE_CXX_FLAGS_RELEASE_INIT "-Os -flto") set(CMAKE_ASM_FLAGS_RELEASE_INIT "") set(CMAKE_EXE_LINKER_FLAGS_RELEASE_INIT "-flto") - -# // Make sure the executable comes after the shared libaries, for symbol resolution -# set(CMAKE_CXX_LINK_EXECUTABLE " -o ") diff --git a/src/libs/board/CMakeLists.txt b/src/libs/board/CMakeLists.txt index 8610ccb..19a9b65 100644 --- a/src/libs/board/CMakeLists.txt +++ b/src/libs/board/CMakeLists.txt @@ -3,4 +3,9 @@ target_compile_options(board INTERFACE ${COMMON_COMPILE_OPTIONS}) set_target_properties(board PROPERTIES LINKER_LANGUAGE CXX) target_link_libraries(board INTERFACE mcu error) +if(NOT IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${EMBEDDED_CPP_BOARD}") + message(FATAL_ERROR + "Board '${EMBEDDED_CPP_BOARD}' is not implemented yet. " + "Available: host. Hardware boards will be added later; see CLAUDE.md.") +endif() add_subdirectory(${EMBEDDED_CPP_BOARD}) \ No newline at end of file diff --git a/src/libs/board/stm32f3_discovery/CMakeLists.txt b/src/libs/board/stm32f3_discovery/CMakeLists.txt deleted file mode 100644 index f9604b2..0000000 --- a/src/libs/board/stm32f3_discovery/CMakeLists.txt +++ /dev/null @@ -1,47 +0,0 @@ -if(CMAKE_PRESET STREQUAL "stm32f3_discovery") - include(FetchContent) - FetchContent_Declare( - stm32cubef3 - GIT_REPOSITORY https://github.com/STMicroelectronics/STM32CubeF3 - GIT_TAG v1.11.4 - ) - FetchContent_MakeAvailable(stm32cubef3) - - file(GLOB HAL_SRC - "${stm32cubef3_SOURCE_DIR}/Drivers/STM32F3xx_HAL_Driver/Src/*.c" - ) - - add_library(stm32f3_hal STATIC - ${HAL_SRC} - startup_stm32f303xc.s - ${stm32cubef3_SOURCE_DIR}/Drivers/CMSIS/Device/ST/STM32F3xx/Source/Templates/system_stm32f3xx.c - ) -target_compile_options(stm32f3_hal INTERFACE ${COMMON_COMPILE_OPTIONS}) - add_library(stm32f3_sys STATIC - syscalls.c - main.cpp - ) -target_compile_options(stm32f3_sys INTERFACE ${COMMON_COMPILE_OPTIONS}) - # target_link_options(stm32f3_sys PUBLIC -lc -lstdc++) - - target_include_directories(stm32f3_hal INTERFACE - ${stm32cubef3_SOURCE_DIR}/Drivers/STM32F3xx_HAL_Driver/Inc - ${stm32cubef3_SOURCE_DIR}/Drivers/CMSIS/Device/ST/STM32F3xx/Include - ${stm32cubef3_SOURCE_DIR}/Drivers/CMSIS/Core/Include - ${stm32cubef3_SOURCE_DIR}/Drivers/CMSIS/Include - PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR} - ) - target_compile_definitions(stm32f3_hal PUBLIC STM32F3 STM32F303xC USE_HAL_DRIVER) - - # target_include_directories(stm32f3_sys PUBLIC ${stm32cubef3_SOURCE_DIR}/Drivers/STM32F3xx_HAL_Driver/Inc - # ${stm32cubef3_SOURCE_DIR}/Drivers/CMSIS/Device/ST/STM32F3xx/Include - # ${stm32cubef3_SOURCE_DIR}/Drivers/CMSIS/Core/Include - # ${stm32cubef3_SOURCE_DIR}/Drivers/CMSIS/Include - # PRIVATE - # ${CMAKE_CURRENT_SOURCE_DIR} - # ) - # target_compile_definitions(stm32f3_sys PUBLIC STM32F3 STM32F303xC USE_HAL_DRIVER) - - target_link_options(stm32f3_hal PUBLIC -TSTM32F303VCTx_FLASH.ld -L${CMAKE_CURRENT_SOURCE_DIR}) -endif() diff --git a/src/libs/board/stm32f3_discovery/Inc/main.h b/src/libs/board/stm32f3_discovery/Inc/main.h deleted file mode 100644 index 4ea73e4..0000000 --- a/src/libs/board/stm32f3_discovery/Inc/main.h +++ /dev/null @@ -1,32 +0,0 @@ -/** - ****************************************************************************** - * @file Templates/Inc/main.h - * @author MCD Application Team - * @brief Header for main.c module - ****************************************************************************** - * @attention - * - * Copyright (c) 2016 STMicroelectronics. - * All rights reserved. - * - * This software is licensed under terms that can be found in the LICENSE file - * in the root directory of this software component. - * If no LICENSE file comes with this software, it is provided AS-IS. - * - ****************************************************************************** - */ - -/* Define to prevent recursive inclusion -------------------------------------*/ -#ifndef __MAIN_H -#define __MAIN_H - -/* Includes ------------------------------------------------------------------*/ -#include "stm32f3xx_hal.h" -#include "stm32f3_discovery.h" - -/* Exported types ------------------------------------------------------------*/ -/* Exported constants --------------------------------------------------------*/ -/* Exported macro ------------------------------------------------------------*/ -/* Exported functions ------------------------------------------------------- */ - -#endif /* __MAIN_H */ diff --git a/src/libs/board/stm32f3_discovery/Inc/stm32f3xx_hal_conf.h b/src/libs/board/stm32f3_discovery/Inc/stm32f3xx_hal_conf.h deleted file mode 100644 index 3ba5112..0000000 --- a/src/libs/board/stm32f3_discovery/Inc/stm32f3xx_hal_conf.h +++ /dev/null @@ -1,333 +0,0 @@ -/** - ****************************************************************************** - * @file stm32f3xx_hal_conf.h - * @author MCD Application Team - * @brief HAL configuration file. - ****************************************************************************** - * @attention - * - * Copyright (c) 2016 STMicroelectronics. - * All rights reserved. - * - * This software is licensed under terms that can be found in the LICENSE file - * in the root directory of this software component. - * If no LICENSE file comes with this software, it is provided AS-IS. - * - ****************************************************************************** - */ - -/* Define to prevent recursive inclusion -------------------------------------*/ -#ifndef __STM32F3xx_HAL_CONF_H -#define __STM32F3xx_HAL_CONF_H - -#ifdef __cplusplus - extern "C" { -#endif - -/* Exported types ------------------------------------------------------------*/ -/* Exported constants --------------------------------------------------------*/ - -/* ########################## Module Selection ############################## */ -/** - * @brief This is the list of modules to be used in the HAL driver - */ -#define HAL_MODULE_ENABLED -#define HAL_ADC_MODULE_ENABLED -#define HAL_CAN_MODULE_ENABLED -/* #define HAL_CAN_LEGACY_MODULE_ENABLED */ -#define HAL_CEC_MODULE_ENABLED -#define HAL_COMP_MODULE_ENABLED -#define HAL_CORTEX_MODULE_ENABLED -#define HAL_CRC_MODULE_ENABLED -#define HAL_DAC_MODULE_ENABLED -#define HAL_DMA_MODULE_ENABLED -#define HAL_FLASH_MODULE_ENABLED -#define HAL_GPIO_MODULE_ENABLED -#define HAL_EXTI_MODULE_ENABLED -#define HAL_HRTIM_MODULE_ENABLED -#define HAL_I2C_MODULE_ENABLED -#define HAL_I2S_MODULE_ENABLED -#define HAL_IRDA_MODULE_ENABLED -#define HAL_IWDG_MODULE_ENABLED -#define HAL_OPAMP_MODULE_ENABLED -#define HAL_PCD_MODULE_ENABLED -#define HAL_PWR_MODULE_ENABLED -#define HAL_RCC_MODULE_ENABLED -#define HAL_RTC_MODULE_ENABLED -#define HAL_SDADC_MODULE_ENABLED -#define HAL_SMARTCARD_MODULE_ENABLED -#define HAL_SMBUS_MODULE_ENABLED -#define HAL_SPI_MODULE_ENABLED -#define HAL_TIM_MODULE_ENABLED -#define HAL_TSC_MODULE_ENABLED -#define HAL_UART_MODULE_ENABLED -#define HAL_USART_MODULE_ENABLED -#define HAL_WWDG_MODULE_ENABLED - -/* ########################## HSE/HSI Values adaptation ##################### */ -/** - * @brief Adjust the value of External High Speed oscillator (HSE) used in your application. - * This value is used by the RCC HAL module to compute the system frequency - * (when HSE is used as system clock source, directly or through the PLL). - */ -#if !defined (HSE_VALUE) - #define HSE_VALUE (8000000U) /*!< Value of the External oscillator in Hz */ -#endif /* HSE_VALUE */ - -/** - * @brief In the following line adjust the External High Speed oscillator (HSE) Startup - * Timeout value - */ -#if !defined (HSE_STARTUP_TIMEOUT) - #define HSE_STARTUP_TIMEOUT (100U) /*!< Time out for HSE start up, in ms */ -#endif /* HSE_STARTUP_TIMEOUT */ - -/** - * @brief Internal High Speed oscillator (HSI) value. - * This value is used by the RCC HAL module to compute the system frequency - * (when HSI is used as system clock source, directly or through the PLL). - */ -#if !defined (HSI_VALUE) - #define HSI_VALUE (8000000U) /*!< Value of the Internal oscillator in Hz*/ -#endif /* HSI_VALUE */ - -/** - * @brief In the following line adjust the Internal High Speed oscillator (HSI) Startup - * Timeout value - */ -#if !defined (HSI_STARTUP_TIMEOUT) - #define HSI_STARTUP_TIMEOUT (5000U) /*!< Time out for HSI start up */ -#endif /* HSI_STARTUP_TIMEOUT */ - -/** - * @brief Internal Low Speed oscillator (LSI) value. - */ -#if !defined (LSI_VALUE) - #define LSI_VALUE (40000U) -#endif /* LSI_VALUE */ /*!< Value of the Internal Low Speed oscillator in Hz - The real value may vary depending on the variations - in voltage and temperature. */ -/** - * @brief External Low Speed oscillator (LSE) value. - */ -#if !defined (LSE_VALUE) - #define LSE_VALUE (32768U) /*!< Value of the External Low Speed oscillator in Hz */ -#endif /* LSE_VALUE */ - -/** - * @brief Time out for LSE start up value in ms. - */ -#if !defined (LSE_STARTUP_TIMEOUT) - #define LSE_STARTUP_TIMEOUT (5000U) /*!< Time out for LSE start up, in ms */ -#endif /* LSE_STARTUP_TIMEOUT */ - -/** - * @brief External clock source for I2S peripheral - * This value is used by the I2S HAL module to compute the I2S clock source - * frequency, this source is inserted directly through I2S_CKIN pad. - * - External clock generated through external PLL component on EVAL 303 (based on MCO or crystal) - * - External clock not generated on EVAL 373 - */ -#if !defined (EXTERNAL_CLOCK_VALUE) - #define EXTERNAL_CLOCK_VALUE (8000000U) /*!< Value of the External oscillator in Hz*/ -#endif /* EXTERNAL_CLOCK_VALUE */ - -/* Tip: To avoid modifying this file each time you need to use different HSE, - === you can define the HSE value in your toolchain compiler preprocessor. */ - -/* ########################### System Configuration ######################### */ -/** - * @brief This is the HAL system configuration section - */ -#define VDD_VALUE (3300U) /*!< Value of VDD in mv */ -#define TICK_INT_PRIORITY ((uint32_t)(1U<<__NVIC_PRIO_BITS) - 1U) /*!< tick interrupt priority (lowest by default) */ -#define USE_RTOS 0U -#define PREFETCH_ENABLE 1U -#define INSTRUCTION_CACHE_ENABLE 0U -#define DATA_CACHE_ENABLE 0U -#define USE_SPI_CRC 1U - -#define USE_HAL_ADC_REGISTER_CALLBACKS 0U /* ADC register callback disabled */ -#define USE_HAL_CAN_REGISTER_CALLBACKS 0U /* CAN register callback disabled */ -#define USE_HAL_COMP_REGISTER_CALLBACKS 0U /* COMP register callback disabled */ -#define USE_HAL_CEC_REGISTER_CALLBACKS 0U /* CEC register callback disabled */ -#define USE_HAL_DAC_REGISTER_CALLBACKS 0U /* DAC register callback disabled */ -#define USE_HAL_SRAM_REGISTER_CALLBACKS 0U /* SRAM register callback disabled */ -#define USE_HAL_SMBUS_REGISTER_CALLBACKS 0U /* SMBUS register callback disabled */ -#define USE_HAL_SDADC_REGISTER_CALLBACKS 0U /* SDADC register callback disabled */ -#define USE_HAL_NAND_REGISTER_CALLBACKS 0U /* NAND register callback disabled */ -#define USE_HAL_NOR_REGISTER_CALLBACKS 0U /* NOR register callback disabled */ -#define USE_HAL_PCCARD_REGISTER_CALLBACKS 0U /* PCCARD register callback disabled */ -#define USE_HAL_HRTIM_REGISTER_CALLBACKS 0U /* HRTIM register callback disabled */ -#define USE_HAL_I2C_REGISTER_CALLBACKS 0U /* I2C register callback disabled */ -#define USE_HAL_UART_REGISTER_CALLBACKS 0U /* UART register callback disabled */ -#define USE_HAL_USART_REGISTER_CALLBACKS 0U /* USART register callback disabled */ -#define USE_HAL_IRDA_REGISTER_CALLBACKS 0U /* IRDA register callback disabled */ -#define USE_HAL_SMARTCARD_REGISTER_CALLBACKS 0U /* SMARTCARD register callback disabled */ -#define USE_HAL_WWDG_REGISTER_CALLBACKS 0U /* WWDG register callback disabled */ -#define USE_HAL_OPAMP_REGISTER_CALLBACKS 0U /* OPAMP register callback disabled */ -#define USE_HAL_RTC_REGISTER_CALLBACKS 0U /* RTC register callback disabled */ -#define USE_HAL_SPI_REGISTER_CALLBACKS 0U /* SPI register callback disabled */ -#define USE_HAL_I2S_REGISTER_CALLBACKS 0U /* I2S register callback disabled */ -#define USE_HAL_TIM_REGISTER_CALLBACKS 0U /* TIM register callback disabled */ -#define USE_HAL_TSC_REGISTER_CALLBACKS 0U /* TSC register callback disabled */ -#define USE_HAL_PCD_REGISTER_CALLBACKS 0U /* PCD register callback disabled */ - -/* ########################## Assert Selection ############################## */ -/** - * @brief Uncomment the line below to expanse the "assert_param" macro in the - * HAL drivers code - */ -/*#define USE_FULL_ASSERT 1*/ - -/* Includes ------------------------------------------------------------------*/ -/** - * @brief Include module's header file - */ - -#ifdef HAL_RCC_MODULE_ENABLED - #include "stm32f3xx_hal_rcc.h" -#endif /* HAL_RCC_MODULE_ENABLED */ - -#ifdef HAL_GPIO_MODULE_ENABLED - #include "stm32f3xx_hal_gpio.h" -#endif /* HAL_GPIO_MODULE_ENABLED */ - -#ifdef HAL_EXTI_MODULE_ENABLED - #include "stm32f3xx_hal_exti.h" -#endif /* HAL_EXTI_MODULE_ENABLED */ - -#ifdef HAL_DMA_MODULE_ENABLED - #include "stm32f3xx_hal_dma.h" -#endif /* HAL_DMA_MODULE_ENABLED */ - -#ifdef HAL_CORTEX_MODULE_ENABLED - #include "stm32f3xx_hal_cortex.h" -#endif /* HAL_CORTEX_MODULE_ENABLED */ - -#ifdef HAL_ADC_MODULE_ENABLED - #include "stm32f3xx_hal_adc.h" -#endif /* HAL_ADC_MODULE_ENABLED */ - -#ifdef HAL_CAN_MODULE_ENABLED - #include "stm32f3xx_hal_can.h" -#endif /* HAL_CAN_MODULE_ENABLED */ - -#ifdef HAL_CAN_LEGACY_MODULE_ENABLED - #include "stm32f3xx_hal_can_legacy.h" -#endif /* HAL_CAN_LEGACY_MODULE_ENABLED */ - -#ifdef HAL_CEC_MODULE_ENABLED - #include "stm32f3xx_hal_cec.h" -#endif /* HAL_CEC_MODULE_ENABLED */ - -#ifdef HAL_COMP_MODULE_ENABLED - #include "stm32f3xx_hal_comp.h" -#endif /* HAL_COMP_MODULE_ENABLED */ - -#ifdef HAL_CRC_MODULE_ENABLED - #include "stm32f3xx_hal_crc.h" -#endif /* HAL_CRC_MODULE_ENABLED */ - -#ifdef HAL_DAC_MODULE_ENABLED - #include "stm32f3xx_hal_dac.h" -#endif /* HAL_DAC_MODULE_ENABLED */ - -#ifdef HAL_FLASH_MODULE_ENABLED - #include "stm32f3xx_hal_flash.h" -#endif /* HAL_FLASH_MODULE_ENABLED */ - -#ifdef HAL_HRTIM_MODULE_ENABLED - #include "stm32f3xx_hal_hrtim.h" -#endif /* HAL_HRTIM_MODULE_ENABLED */ - -#ifdef HAL_I2C_MODULE_ENABLED - #include "stm32f3xx_hal_i2c.h" -#endif /* HAL_I2C_MODULE_ENABLED */ - -#ifdef HAL_I2S_MODULE_ENABLED - #include "stm32f3xx_hal_i2s.h" -#endif /* HAL_I2S_MODULE_ENABLED */ - -#ifdef HAL_IRDA_MODULE_ENABLED - #include "stm32f3xx_hal_irda.h" -#endif /* HAL_IRDA_MODULE_ENABLED */ - -#ifdef HAL_IWDG_MODULE_ENABLED - #include "stm32f3xx_hal_iwdg.h" -#endif /* HAL_IWDG_MODULE_ENABLED */ - -#ifdef HAL_OPAMP_MODULE_ENABLED - #include "stm32f3xx_hal_opamp.h" -#endif /* HAL_OPAMP_MODULE_ENABLED */ - -#ifdef HAL_PCD_MODULE_ENABLED - #include "stm32f3xx_hal_pcd.h" -#endif /* HAL_PCD_MODULE_ENABLED */ - -#ifdef HAL_PWR_MODULE_ENABLED - #include "stm32f3xx_hal_pwr.h" -#endif /* HAL_PWR_MODULE_ENABLED */ - -#ifdef HAL_RTC_MODULE_ENABLED - #include "stm32f3xx_hal_rtc.h" -#endif /* HAL_RTC_MODULE_ENABLED */ - -#ifdef HAL_SDADC_MODULE_ENABLED - #include "stm32f3xx_hal_sdadc.h" -#endif /* HAL_SDADC_MODULE_ENABLED */ - -#ifdef HAL_SMARTCARD_MODULE_ENABLED - #include "stm32f3xx_hal_smartcard.h" -#endif /* HAL_SMARTCARD_MODULE_ENABLED */ - -#ifdef HAL_SMBUS_MODULE_ENABLED - #include "stm32f3xx_hal_smbus.h" -#endif /* HAL_SMBUS_MODULE_ENABLED */ - -#ifdef HAL_SPI_MODULE_ENABLED - #include "stm32f3xx_hal_spi.h" -#endif /* HAL_SPI_MODULE_ENABLED */ - -#ifdef HAL_TIM_MODULE_ENABLED - #include "stm32f3xx_hal_tim.h" -#endif /* HAL_TIM_MODULE_ENABLED */ - -#ifdef HAL_TSC_MODULE_ENABLED - #include "stm32f3xx_hal_tsc.h" -#endif /* HAL_TSC_MODULE_ENABLED */ - -#ifdef HAL_UART_MODULE_ENABLED - #include "stm32f3xx_hal_uart.h" -#endif /* HAL_UART_MODULE_ENABLED */ - -#ifdef HAL_USART_MODULE_ENABLED - #include "stm32f3xx_hal_usart.h" -#endif /* HAL_USART_MODULE_ENABLED */ - -#ifdef HAL_WWDG_MODULE_ENABLED - #include "stm32f3xx_hal_wwdg.h" -#endif /* HAL_WWDG_MODULE_ENABLED */ - -/* Exported macro ------------------------------------------------------------*/ -#ifdef USE_FULL_ASSERT -/** - * @brief The assert_param macro is used for function's parameters check. - * @param expr: If expr is false, it calls assert_failed function - * which reports the name of the source file and the source - * line number of the call that failed. - * If expr is true, it returns no value. - * @retval None - */ - #define assert_param(expr) ((expr) ? (void)0U : assert_failed((uint8_t *)__FILE__, __LINE__)) -/* Exported functions ------------------------------------------------------- */ - void assert_failed(uint8_t* file, uint32_t line); -#else - #define assert_param(expr) ((void)0U) -#endif /* USE_FULL_ASSERT */ - -#ifdef __cplusplus -} -#endif - -#endif /* __STM32F3xx_HAL_CONF_H */ diff --git a/src/libs/board/stm32f3_discovery/Inc/stm32f3xx_it.h b/src/libs/board/stm32f3_discovery/Inc/stm32f3xx_it.h deleted file mode 100644 index d62807f..0000000 --- a/src/libs/board/stm32f3_discovery/Inc/stm32f3xx_it.h +++ /dev/null @@ -1,47 +0,0 @@ -/** - ****************************************************************************** - * @file Templates/Inc/stm32f3xx_it.h - * @author MCD Application Team - * @brief This file contains the headers of the interrupt handlers. - ****************************************************************************** - * @attention - * - * Copyright (c) 2016 STMicroelectronics. - * All rights reserved. - * - * This software is licensed under terms that can be found in the LICENSE file - * in the root directory of this software component. - * If no LICENSE file comes with this software, it is provided AS-IS. - * - ****************************************************************************** - */ - -/* Define to prevent recursive inclusion -------------------------------------*/ -#ifndef __STM32F3xx_IT_H -#define __STM32F3xx_IT_H - -#ifdef __cplusplus - extern "C" { -#endif - -/* Includes ------------------------------------------------------------------*/ -/* Exported types ------------------------------------------------------------*/ -/* Exported constants --------------------------------------------------------*/ -/* Exported macro ------------------------------------------------------------*/ -/* Exported functions ------------------------------------------------------- */ - -void NMI_Handler(void); -void HardFault_Handler(void); -void MemManage_Handler(void); -void BusFault_Handler(void); -void UsageFault_Handler(void); -void SVC_Handler(void); -void DebugMon_Handler(void); -void PendSV_Handler(void); -void SysTick_Handler(void); - -#ifdef __cplusplus -} -#endif - -#endif /* __STM32F3xx_IT_H */ diff --git a/src/libs/board/stm32f3_discovery/STM32F303VCTx_FLASH.ld b/src/libs/board/stm32f3_discovery/STM32F303VCTx_FLASH.ld deleted file mode 100644 index ec223a7..0000000 --- a/src/libs/board/stm32f3_discovery/STM32F303VCTx_FLASH.ld +++ /dev/null @@ -1,189 +0,0 @@ -/* -***************************************************************************** -** - -** File : LinkerScript.ld -** -** Abstract : Linker script for STM32F303VCTx Device with -** 256KByte FLASH, 40KByte RAM -** -** Set heap size, stack size and stack location according -** to application requirements. -** -** Set memory bank area and size if external memory is used. -** -** Target : STMicroelectronics STM32 -** -** -** Distribution: The file is distributed as is, without any warranty -** of any kind. -** -** (c)Copyright Ac6. -** You may use this file as-is or modify it according to the needs of your -** project. Distribution of this file (unmodified or modified) is not -** permitted. Ac6 permit registered System Workbench for MCU users the -** rights to distribute the assembled, compiled & linked contents of this -** file as part of an application binary file, provided that it is built -** using the System Workbench for MCU toolchain. -** -***************************************************************************** -*/ - -/* Entry Point */ -ENTRY(Reset_Handler) - -/* Highest address of the user mode stack */ -_estack = 0x2000A000; /* end of RAM */ -/* Generate a link error if heap and stack don't fit into RAM */ -_Min_Heap_Size = 0x200; /* required amount of heap */ -_Min_Stack_Size = 0x400; /* required amount of stack */ - -/* Specify the memory areas */ -MEMORY -{ -FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 256K -RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 40K -CCMRAM (xrw) : ORIGIN = 0x10000000, LENGTH = 8K -} - -/* Define output sections */ -SECTIONS -{ - /* The startup code goes first into FLASH */ - .isr_vector : - { - . = ALIGN(4); - KEEP(*(.isr_vector)) /* Startup code */ - . = ALIGN(4); - } >FLASH - - /* The program code and other data goes into FLASH */ - .text : - { - . = ALIGN(4); - *(.text) /* .text sections (code) */ - *(.text*) /* .text* sections (code) */ - *(.glue_7) /* glue arm to thumb code */ - *(.glue_7t) /* glue thumb to arm code */ - *(.eh_frame) - - KEEP (*(.init)) - KEEP (*(.fini)) - - . = ALIGN(4); - _etext = .; /* define a global symbols at end of code */ - } >FLASH - - /* Constant data goes into FLASH */ - .rodata : - { - . = ALIGN(4); - *(.rodata) /* .rodata sections (constants, strings, etc.) */ - *(.rodata*) /* .rodata* sections (constants, strings, etc.) */ - . = ALIGN(4); - } >FLASH - - .ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >FLASH - .ARM : { - __exidx_start = .; - *(.ARM.exidx*) - __exidx_end = .; - } >FLASH - - .preinit_array : - { - PROVIDE_HIDDEN (__preinit_array_start = .); - KEEP (*(.preinit_array*)) - PROVIDE_HIDDEN (__preinit_array_end = .); - } >FLASH - .init_array : - { - PROVIDE_HIDDEN (__init_array_start = .); - KEEP (*(SORT(.init_array.*))) - KEEP (*(.init_array*)) - PROVIDE_HIDDEN (__init_array_end = .); - } >FLASH - .fini_array : - { - PROVIDE_HIDDEN (__fini_array_start = .); - KEEP (*(SORT(.fini_array.*))) - KEEP (*(.fini_array*)) - PROVIDE_HIDDEN (__fini_array_end = .); - } >FLASH - - /* used by the startup to initialize data */ - _sidata = LOADADDR(.data); - - /* Initialized data sections goes into RAM, load LMA copy after code */ - .data : - { - . = ALIGN(4); - _sdata = .; /* create a global symbol at data start */ - *(.data) /* .data sections */ - *(.data*) /* .data* sections */ - - . = ALIGN(4); - _edata = .; /* define a global symbol at data end */ - } >RAM AT> FLASH - - _siccmram = LOADADDR(.ccmram); - - /* CCM-RAM section - * - * IMPORTANT NOTE! - * If initialized variables will be placed in this section, - * the startup code needs to be modified to copy the init-values. - */ - .ccmram : - { - . = ALIGN(4); - _sccmram = .; /* create a global symbol at ccmram start */ - *(.ccmram) - *(.ccmram*) - - . = ALIGN(4); - _eccmram = .; /* create a global symbol at ccmram end */ - } >CCMRAM AT> FLASH - - - /* Uninitialized data section */ - . = ALIGN(4); - .bss : - { - /* This is used by the startup in order to initialize the .bss section */ - _sbss = .; /* define a global symbol at bss start */ - __bss_start__ = _sbss; - *(.bss) - *(.bss*) - *(COMMON) - - . = ALIGN(4); - _ebss = .; /* define a global symbol at bss end */ - __bss_end__ = _ebss; - } >RAM - - /* User_heap_stack section, used to check that there is enough RAM left */ - ._user_heap_stack : - { - . = ALIGN(8); - PROVIDE ( end = . ); - PROVIDE ( _end = . ); - . = . + _Min_Heap_Size; - . = . + _Min_Stack_Size; - . = ALIGN(8); - } >RAM - - - - /* Remove information from the standard libraries */ - /DISCARD/ : - { - libc.a ( * ) - libm.a ( * ) - libgcc.a ( * ) - } - - .ARM.attributes 0 : { *(.ARM.attributes) } -} - - diff --git a/src/libs/board/stm32f3_discovery/Src/main.c b/src/libs/board/stm32f3_discovery/Src/main.c deleted file mode 100644 index 6990257..0000000 --- a/src/libs/board/stm32f3_discovery/Src/main.c +++ /dev/null @@ -1,160 +0,0 @@ -/** - ****************************************************************************** - * @file Templates/Src/main.c - * @author MCD Application Team - * @brief Main program body - ****************************************************************************** - * @attention - * - * Copyright (c) 2016 STMicroelectronics. - * All rights reserved. - * - * This software is licensed under terms that can be found in the LICENSE file - * in the root directory of this software component. - * If no LICENSE file comes with this software, it is provided AS-IS. - * - ****************************************************************************** - */ - -/* Includes ------------------------------------------------------------------*/ -#include "main.h" - -/** @addtogroup STM32F3xx_HAL_Examples - * @{ - */ - -/** @addtogroup Templates - * @{ - */ - -/* Private typedef -----------------------------------------------------------*/ -/* Private define ------------------------------------------------------------*/ -/* Private macro -------------------------------------------------------------*/ -/* Private variables ---------------------------------------------------------*/ -/* Private function prototypes -----------------------------------------------*/ -static void SystemClock_Config(void); -static void Error_Handler(void); - -/* Private functions ---------------------------------------------------------*/ - -/** - * @brief Main program - * @param None - * @retval None - */ -int main(void) -{ - - /* STM32F3xx HAL library initialization: - - Configure the Flash prefetch - - Systick timer is configured by default as source of time base, but user - can eventually implement his proper time base source (a general purpose - timer for example or other time source), keeping in mind that Time base - duration should be kept 1ms since PPP_TIMEOUT_VALUEs are defined and - handled in milliseconds basis. - - Set NVIC Group Priority to 4 - - Low Level Initialization - */ - HAL_Init(); - - /* Configure the system clock to have a system clock = 72 Mhz */ - SystemClock_Config(); - - - /* Add your application code here - */ - - - /* Infinite loop */ - while (1) - { - } -} - -/** - * @brief System Clock Configuration - * The system Clock is configured as follow : - * System Clock source = PLL (HSE) - * SYSCLK(Hz) = 72000000 - * HCLK(Hz) = 72000000 - * AHB Prescaler = 1 - * APB1 Prescaler = 2 - * APB2 Prescaler = 1 - * HSE Frequency(Hz) = 8000000 - * HSE PREDIV = 1 - * PLLMUL = RCC_PLL_MUL9 (9) - * Flash Latency(WS) = 2 - * @param None - * @retval None - */ -static void SystemClock_Config(void) -{ - RCC_ClkInitTypeDef RCC_ClkInitStruct; - RCC_OscInitTypeDef RCC_OscInitStruct; - - /* Enable HSE Oscillator and activate PLL with HSE as source */ - RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; - RCC_OscInitStruct.HSEState = RCC_HSE_ON; - RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; - RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; - RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; - RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; - if (HAL_RCC_OscConfig(&RCC_OscInitStruct)!= HAL_OK) - { - Error_Handler(); - } - - /* Select PLL as system clock source and configure the HCLK, PCLK1 and PCLK2 - clocks dividers */ - RCC_ClkInitStruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2); - RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; - RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; - RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; - RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; - if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2)!= HAL_OK) - { - Error_Handler(); - } -} - -/** - * @brief This function is executed in case of error occurrence. - * @param None - * @retval None - */ -static void Error_Handler(void) -{ - /* User may add here some code to deal with this error */ - while(1) - { - } -} - -#ifdef USE_FULL_ASSERT - -/** - * @brief Reports the name of the source file and the source line number - * where the assert_param error has occurred. - * @param file: pointer to the source file name - * @param line: assert_param error line source number - * @retval None - */ -void assert_failed(uint8_t* file, uint32_t line) -{ - /* User can add his own implementation to report the file name and line number, - ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ - - /* Infinite loop */ - while (1) - { - } -} -#endif - -/** - * @} - */ - -/** - * @} - */ diff --git a/src/libs/board/stm32f3_discovery/Src/stm32f3xx_hal_msp.c b/src/libs/board/stm32f3_discovery/Src/stm32f3xx_hal_msp.c deleted file mode 100644 index 44b64b0..0000000 --- a/src/libs/board/stm32f3_discovery/Src/stm32f3xx_hal_msp.c +++ /dev/null @@ -1,91 +0,0 @@ -/** - ****************************************************************************** - * @file Templates/Src/stm32f3xx_hal_msp.c - * @author MCD Application Team - * @brief HAL MSP module. - ****************************************************************************** - * @attention - * - * Copyright (c) 2016 STMicroelectronics. - * All rights reserved. - * - * This software is licensed under terms that can be found in the LICENSE file - * in the root directory of this software component. - * If no LICENSE file comes with this software, it is provided AS-IS. - * - ****************************************************************************** - */ - -/* Includes ------------------------------------------------------------------*/ -#include "main.h" - -/** @addtogroup STM32F3xx_HAL_Examples - * @{ - */ - -/** @addtogroup Templates - * @{ - */ - -/* Private typedef -----------------------------------------------------------*/ -/* Private define ------------------------------------------------------------*/ -/* Private macro -------------------------------------------------------------*/ -/* Private variables ---------------------------------------------------------*/ -/* Private function prototypes -----------------------------------------------*/ -/* Private functions ---------------------------------------------------------*/ - -/** @defgroup HAL_MSP_Private_Functions - * @{ - */ - -/** - * @brief Initializes the Global MSP. - * @param None - * @retval None - */ -void HAL_MspInit(void) -{ - -} - -/** - * @brief DeInitializes the Global MSP. - * @param None - * @retval None - */ -void HAL_MspDeInit(void) -{ - -} - -/** - * @brief Initializes the PPP MSP. - * @param None - * @retval None - */ -//void HAL_PPP_MspInit(void) -//{ - -//} - -/** - * @brief DeInitializes the PPP MSP. - * @param None - * @retval None - */ -//void HAL_PPP_MspDeInit(void) -//{ - -//} - -/** - * @} - */ - -/** - * @} - */ - -/** - * @} - */ diff --git a/src/libs/board/stm32f3_discovery/Src/stm32f3xx_it.c b/src/libs/board/stm32f3_discovery/Src/stm32f3xx_it.c deleted file mode 100644 index e0647c2..0000000 --- a/src/libs/board/stm32f3_discovery/Src/stm32f3xx_it.c +++ /dev/null @@ -1,166 +0,0 @@ -/** - ****************************************************************************** - * @file Templates/Src/stm32f3xx_it.c - * @author MCD Application Team - * @brief Main Interrupt Service Routines. - * This file provides template for all exceptions handler and - * peripherals interrupt service routine. - ****************************************************************************** - * @attention - * - * Copyright (c) 2016 STMicroelectronics. - * All rights reserved. - * - * This software is licensed under terms that can be found in the LICENSE file - * in the root directory of this software component. - * If no LICENSE file comes with this software, it is provided AS-IS. - * - ****************************************************************************** - */ - -/* Includes ------------------------------------------------------------------*/ -#include "main.h" -#include "stm32f3xx_it.h" - - -/** @addtogroup STM32F3xx_HAL_Examples - * @{ - */ - -/** @addtogroup Templates - * @{ - */ - -/* Private typedef -----------------------------------------------------------*/ -/* Private define ------------------------------------------------------------*/ -/* Private macro -------------------------------------------------------------*/ -/* Private variables ---------------------------------------------------------*/ -/* Private function prototypes -----------------------------------------------*/ -/* Private functions ---------------------------------------------------------*/ - -/******************************************************************************/ -/* Cortex-M4 Processor Exceptions Handlers */ -/******************************************************************************/ - -/** - * @brief This function handles NMI exception. - * @param None - * @retval None - */ -void NMI_Handler(void) -{ -} - -/** - * @brief This function handles Hard Fault exception. - * @param None - * @retval None - */ -void HardFault_Handler(void) -{ - /* Go to infinite loop when Hard Fault exception occurs */ - while (1) - { - } -} - -/** - * @brief This function handles Memory Manage exception. - * @param None - * @retval None - */ -void MemManage_Handler(void) -{ - /* Go to infinite loop when Memory Manage exception occurs */ - while (1) - { - } -} - -/** - * @brief This function handles Bus Fault exception. - * @param None - * @retval None - */ -void BusFault_Handler(void) -{ - /* Go to infinite loop when Bus Fault exception occurs */ - while (1) - { - } -} - -/** - * @brief This function handles Usage Fault exception. - * @param None - * @retval None - */ -void UsageFault_Handler(void) -{ - /* Go to infinite loop when Usage Fault exception occurs */ - while (1) - { - } -} - -/** - * @brief This function handles SVCall exception. - * @param None - * @retval None - */ -void SVC_Handler(void) -{ -} - -/** - * @brief This function handles Debug Monitor exception. - * @param None - * @retval None - */ -void DebugMon_Handler(void) -{ -} - -/** - * @brief This function handles PendSVC exception. - * @param None - * @retval None - */ -void PendSV_Handler(void) -{ -} - -/** - * @brief This function handles SysTick Handler. - * @param None - * @retval None - */ -void SysTick_Handler(void) -{ - HAL_IncTick(); -} - -/******************************************************************************/ -/* STM32F3xx Peripherals Interrupt Handlers */ -/* Add here the Interrupt Handler for the used peripheral(s) , for the */ -/* available peripheral interrupt handler's name please refer to the startup */ -/* file (startup_stm32f3xx.s). */ -/******************************************************************************/ - -/** - * @brief This function handles PPP interrupt request. - * @param None - * @retval None - */ -/*void PPP_IRQHandler(void) -{ -}*/ - - -/** - * @} - */ - -/** - * @} - */ diff --git a/src/libs/board/stm32f3_discovery/Src/system_stm32f3xx.c b/src/libs/board/stm32f3_discovery/Src/system_stm32f3xx.c deleted file mode 100644 index 35faae4..0000000 --- a/src/libs/board/stm32f3_discovery/Src/system_stm32f3xx.c +++ /dev/null @@ -1,294 +0,0 @@ -/** - ****************************************************************************** - * @file system_stm32f3xx.c - * @author MCD Application Team - * @brief CMSIS Cortex-M4 Device Peripheral Access Layer System Source File. - * - * 1. This file provides two functions and one global variable to be called from - * user application: - * - SystemInit(): This function is called at startup just after reset and - * before branch to main program. This call is made inside - * the "startup_stm32f3xx.s" file. - * - * - SystemCoreClock variable: Contains the core clock (HCLK), it can be used - * by the user application to setup the SysTick - * timer or configure other parameters. - * - * - SystemCoreClockUpdate(): Updates the variable SystemCoreClock and must - * be called whenever the core clock is changed - * during program execution. - * - * 2. After each device reset the HSI (8 MHz) is used as system clock source. - * Then SystemInit() function is called, in "startup_stm32f3xx.s" file, to - * configure the system clock before to branch to main program. - * - * 3. This file configures the system clock as follows: - *============================================================================= - * Supported STM32F3xx device - *----------------------------------------------------------------------------- - * System Clock source | HSI - *----------------------------------------------------------------------------- - * SYSCLK(Hz) | 8000000 - *----------------------------------------------------------------------------- - * HCLK(Hz) | 8000000 - *----------------------------------------------------------------------------- - * AHB Prescaler | 1 - *----------------------------------------------------------------------------- - * APB2 Prescaler | 1 - *----------------------------------------------------------------------------- - * APB1 Prescaler | 1 - *----------------------------------------------------------------------------- - * USB Clock | DISABLE - *----------------------------------------------------------------------------- - *============================================================================= - ****************************************************************************** - * @attention - * - * Copyright (c) 2016 STMicroelectronics. - * All rights reserved. - * - * This software is licensed under terms that can be found in the LICENSE file - * in the root directory of this software component. - * If no LICENSE file comes with this software, it is provided AS-IS. - * - ****************************************************************************** - */ - -/** @addtogroup CMSIS - * @{ - */ - -/** @addtogroup stm32f3xx_system - * @{ - */ - -/** @addtogroup STM32F3xx_System_Private_Includes - * @{ - */ - -#include "stm32f3xx.h" - -/** - * @} - */ - -/** @addtogroup STM32F3xx_System_Private_TypesDefinitions - * @{ - */ - -/** - * @} - */ - -/** @addtogroup STM32F3xx_System_Private_Defines - * @{ - */ -#if !defined (HSE_VALUE) - #define HSE_VALUE ((uint32_t)8000000) /*!< Default value of the External oscillator in Hz. - This value can be provided and adapted by the user application. */ -#endif /* HSE_VALUE */ - -#if !defined (HSI_VALUE) - #define HSI_VALUE ((uint32_t)8000000) /*!< Default value of the Internal oscillator in Hz. - This value can be provided and adapted by the user application. */ -#endif /* HSI_VALUE */ - -/*!< Uncomment the following line if you need to relocate your vector Table in - Internal SRAM. */ -/* #define VECT_TAB_SRAM */ -#define VECT_TAB_OFFSET 0x0 /*!< Vector Table base offset field. - This value must be a multiple of 0x200. */ -/** - * @} - */ - -/** @addtogroup STM32F3xx_System_Private_Macros - * @{ - */ - -/** - * @} - */ - -/** @addtogroup STM32F3xx_System_Private_Variables - * @{ - */ - /* This variable is updated in three ways: - 1) by calling CMSIS function SystemCoreClockUpdate() - 2) by calling HAL API function HAL_RCC_GetHCLKFreq() - 3) each time HAL_RCC_ClockConfig() is called to configure the system clock frequency - Note: If you use this function to configure the system clock there is no need to - call the 2 first functions listed above, since SystemCoreClock variable is - updated automatically. - */ -uint32_t SystemCoreClock = 8000000; - -const uint8_t AHBPrescTable[16] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 6, 7, 8, 9}; -const uint8_t APBPrescTable[8] = {0, 0, 0, 0, 1, 2, 3, 4}; - -/** - * @} - */ - -/** @addtogroup STM32F3xx_System_Private_FunctionPrototypes - * @{ - */ - -/** - * @} - */ - -/** @addtogroup STM32F3xx_System_Private_Functions - * @{ - */ - -/** - * @brief Setup the microcontroller system - * Initialize the FPU setting, vector table location and the PLL configuration is reset. - * @param None - * @retval None - */ -void SystemInit(void) -{ - /* FPU settings ------------------------------------------------------------*/ - #if (__FPU_PRESENT == 1) && (__FPU_USED == 1) - SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2)); /* set CP10 and CP11 Full Access */ - #endif - - /* Reset the RCC clock configuration to the default reset state ------------*/ - /* Set HSION bit */ - RCC->CR |= (uint32_t)0x00000001; - - /* Reset CFGR register */ - RCC->CFGR &= 0xF87FC00C; - - /* Reset HSEON, CSSON and PLLON bits */ - RCC->CR &= (uint32_t)0xFEF6FFFF; - - /* Reset HSEBYP bit */ - RCC->CR &= (uint32_t)0xFFFBFFFF; - - /* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE bits */ - RCC->CFGR &= (uint32_t)0xFF80FFFF; - - /* Reset PREDIV1[3:0] bits */ - RCC->CFGR2 &= (uint32_t)0xFFFFFFF0; - - /* Reset USARTSW[1:0], I2CSW and TIMs bits */ - RCC->CFGR3 &= (uint32_t)0xFF00FCCC; - - /* Disable all interrupts */ - RCC->CIR = 0x00000000; - -#ifdef VECT_TAB_SRAM - SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */ -#else - SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */ -#endif -} - -/** - * @brief Update SystemCoreClock variable according to Clock Register Values. - * The SystemCoreClock variable contains the core clock (HCLK), it can - * be used by the user application to setup the SysTick timer or configure - * other parameters. - * - * @note Each time the core clock (HCLK) changes, this function must be called - * to update SystemCoreClock variable value. Otherwise, any configuration - * based on this variable will be incorrect. - * - * @note - The system frequency computed by this function is not the real - * frequency in the chip. It is calculated based on the predefined - * constant and the selected clock source: - * - * - If SYSCLK source is HSI, SystemCoreClock will contain the HSI_VALUE(*) - * - * - If SYSCLK source is HSE, SystemCoreClock will contain the HSE_VALUE(**) - * - * - If SYSCLK source is PLL, SystemCoreClock will contain the HSE_VALUE(**) - * or HSI_VALUE(*) multiplied/divided by the PLL factors. - * - * (*) HSI_VALUE is a constant defined in stm32f3xx_hal.h file (default value - * 8 MHz) but the real value may vary depending on the variations - * in voltage and temperature. - * - * (**) HSE_VALUE is a constant defined in stm32f3xx_hal.h file (default value - * 8 MHz), user has to ensure that HSE_VALUE is same as the real - * frequency of the crystal used. Otherwise, this function may - * have wrong result. - * - * - The result of this function could be not correct when using fractional - * value for HSE crystal. - * - * @param None - * @retval None - */ -void SystemCoreClockUpdate (void) -{ - uint32_t tmp = 0, pllmull = 0, pllsource = 0, predivfactor = 0; - - /* Get SYSCLK source -------------------------------------------------------*/ - tmp = RCC->CFGR & RCC_CFGR_SWS; - - switch (tmp) - { - case RCC_CFGR_SWS_HSI: /* HSI used as system clock */ - SystemCoreClock = HSI_VALUE; - break; - case RCC_CFGR_SWS_HSE: /* HSE used as system clock */ - SystemCoreClock = HSE_VALUE; - break; - case RCC_CFGR_SWS_PLL: /* PLL used as system clock */ - /* Get PLL clock source and multiplication factor ----------------------*/ - pllmull = RCC->CFGR & RCC_CFGR_PLLMUL; - pllsource = RCC->CFGR & RCC_CFGR_PLLSRC; - pllmull = ( pllmull >> 18) + 2; - -#if defined (STM32F302xE) || defined (STM32F303xE) || defined (STM32F398xx) - predivfactor = (RCC->CFGR2 & RCC_CFGR2_PREDIV) + 1; - if (pllsource == RCC_CFGR_PLLSRC_HSE_PREDIV) - { - /* HSE oscillator clock selected as PREDIV1 clock entry */ - SystemCoreClock = (HSE_VALUE / predivfactor) * pllmull; - } - else - { - /* HSI oscillator clock selected as PREDIV1 clock entry */ - SystemCoreClock = (HSI_VALUE / predivfactor) * pllmull; - } -#else - if (pllsource == RCC_CFGR_PLLSRC_HSI_DIV2) - { - /* HSI oscillator clock divided by 2 selected as PLL clock entry */ - SystemCoreClock = (HSI_VALUE >> 1) * pllmull; - } - else - { - predivfactor = (RCC->CFGR2 & RCC_CFGR2_PREDIV) + 1; - /* HSE oscillator clock selected as PREDIV1 clock entry */ - SystemCoreClock = (HSE_VALUE / predivfactor) * pllmull; - } -#endif /* STM32F302xE || STM32F303xE || STM32F398xx */ - break; - default: /* HSI used as system clock */ - SystemCoreClock = HSI_VALUE; - break; - } - /* Compute HCLK clock frequency ----------------*/ - /* Get HCLK prescaler */ - tmp = AHBPrescTable[((RCC->CFGR & RCC_CFGR_HPRE) >> 4)]; - /* HCLK clock frequency */ - SystemCoreClock >>= tmp; -} - -/** - * @} - */ - -/** - * @} - */ - -/** - * @} - */ diff --git a/src/libs/board/stm32f3_discovery/newlib_nano.c b/src/libs/board/stm32f3_discovery/newlib_nano.c deleted file mode 100644 index a0d242b..0000000 --- a/src/libs/board/stm32f3_discovery/newlib_nano.c +++ /dev/null @@ -1,85 +0,0 @@ -/* Copyright (c) Microsoft Corporation. - Licensed under the MIT License. */ -#ifdef __GNUC__ - -#include -#include -#include -#include - -#include - -extern int errno; -extern int _end; - -void* _sbrk(int incr) -{ - static unsigned char* heap = NULL; - unsigned char* prev_heap; - - if (heap == NULL) - { - heap = (unsigned char*)&_end; - } - prev_heap = heap; - - heap += incr; - - return prev_heap; -} - -int _close(int file) -{ - return -1; -} - -int _fstat(int file, struct stat* st) -{ - st->st_mode = S_IFCHR; - return 0; -} - -int _isatty(int file) -{ - return 1; -} - -int _lseek(int file, int ptr, int dir) -{ - return 0; -} - -void _exit(int status) -{ - printf("Exiting with status %d.\n", status); - while (1) - ; -} - -void _kill(int pid, int sig) -{ - return; -} - -int _getpid(void) -{ - return -1; -} - -int _write(int fd, char* ptr, int len) { - (void)fd; - for (int i = 0; i < len; i++) { - printf("%c", ptr[i]); - } - return len; -} - -// function aliases to support different runtimes -int lseek(int file, int ptr, int dir) __attribute__((weak, alias("_lseek"))); -int fstat(int file, struct stat* st) __attribute__((weak, alias("_fstat"))); -int close(int file) __attribute__((weak, alias("_close"))); -int isatty(int file) __attribute__((weak, alias("_isatty"))); -int getpid(void) __attribute__((weak, alias("_getpid"))); -void kill(int pid, int sig) __attribute__((weak, alias("_kill"))); - -#endif // __GNUC__ \ No newline at end of file diff --git a/src/libs/board/stm32f3_discovery/startup_stm32f303xc.s b/src/libs/board/stm32f3_discovery/startup_stm32f303xc.s deleted file mode 100644 index ac67d05..0000000 --- a/src/libs/board/stm32f3_discovery/startup_stm32f303xc.s +++ /dev/null @@ -1,460 +0,0 @@ -/** - ****************************************************************************** - * @file startup_stm32f303xc.s - * @author MCD Application Team - * @brief STM32F303xB/STM32F303xC devices vector table for GCC toolchain. - * This module performs: - * - Set the initial SP - * - Set the initial PC == Reset_Handler, - * - Set the vector table entries with the exceptions ISR address, - * - Configure the clock system - * - Branches to main in the C library (which eventually - * calls main()). - * After Reset the Cortex-M4 processor is in Thread mode, - * priority is Privileged, and the Stack is set to Main. - ****************************************************************************** - * @attention - * - * Copyright (c) 2016 STMicroelectronics. - * All rights reserved. - * - * This software is licensed under terms that can be found in the LICENSE file - * in the root directory of this software component. - * If no LICENSE file comes with this software, it is provided AS-IS. - * - ****************************************************************************** - */ - - .syntax unified - .cpu cortex-m4 - .fpu softvfp - .thumb - -.global g_pfnVectors -.global Default_Handler - -/* start address for the initialization values of the .data section. -defined in linker script */ -.word _sidata -/* start address for the .data section. defined in linker script */ -.word _sdata -/* end address for the .data section. defined in linker script */ -.word _edata -/* start address for the .bss section. defined in linker script */ -.word _sbss -/* end address for the .bss section. defined in linker script */ -.word _ebss - -.equ BootRAM, 0xF1E0F85F -/** - * @brief This is the code that gets called when the processor first - * starts execution following a reset event. Only the absolutely - * necessary set is performed, after which the application - * supplied main() routine is called. - * @param None - * @retval : None -*/ - - .section .text.Reset_Handler - .weak Reset_Handler - .type Reset_Handler, %function -Reset_Handler: - ldr sp, =_estack /* Atollic update: set stack pointer */ - -/* Copy the data segment initializers from flash to SRAM */ - movs r1, #0 - b LoopCopyDataInit - -CopyDataInit: - ldr r3, =_sidata - ldr r3, [r3, r1] - str r3, [r0, r1] - adds r1, r1, #4 - -LoopCopyDataInit: - ldr r0, =_sdata - ldr r3, =_edata - adds r2, r0, r1 - cmp r2, r3 - bcc CopyDataInit - ldr r2, =_sbss - b LoopFillZerobss -/* Zero fill the bss segment. */ -FillZerobss: - movs r3, #0 - str r3, [r2], #4 - -LoopFillZerobss: - ldr r3, = _ebss - cmp r2, r3 - bcc FillZerobss - -/* Call the clock system initialization function.*/ - bl SystemInit -/* Call static constructors */ - bl __libc_init_array -/* Call the application's entry point.*/ - bl main - -LoopForever: - b LoopForever - -.size Reset_Handler, .-Reset_Handler - -/** - * @brief This is the code that gets called when the processor receives an - * unexpected interrupt. This simply enters an infinite loop, preserving - * the system state for examination by a debugger. - * - * @param None - * @retval : None -*/ - .section .text.Default_Handler,"ax",%progbits -Default_Handler: -Infinite_Loop: - b Infinite_Loop - .size Default_Handler, .-Default_Handler -/****************************************************************************** -* -* The minimal vector table for a Cortex-M4. Note that the proper constructs -* must be placed on this to ensure that it ends up at physical address -* 0x0000.0000. -* -******************************************************************************/ - .section .isr_vector,"a",%progbits - .type g_pfnVectors, %object - .size g_pfnVectors, .-g_pfnVectors - - -g_pfnVectors: - .word _estack - .word Reset_Handler - .word NMI_Handler - .word HardFault_Handler - .word MemManage_Handler - .word BusFault_Handler - .word UsageFault_Handler - .word 0 - .word 0 - .word 0 - .word 0 - .word SVC_Handler - .word DebugMon_Handler - .word 0 - .word PendSV_Handler - .word SysTick_Handler - .word WWDG_IRQHandler - .word PVD_IRQHandler - .word TAMP_STAMP_IRQHandler - .word RTC_WKUP_IRQHandler - .word FLASH_IRQHandler - .word RCC_IRQHandler - .word EXTI0_IRQHandler - .word EXTI1_IRQHandler - .word EXTI2_TSC_IRQHandler - .word EXTI3_IRQHandler - .word EXTI4_IRQHandler - .word DMA1_Channel1_IRQHandler - .word DMA1_Channel2_IRQHandler - .word DMA1_Channel3_IRQHandler - .word DMA1_Channel4_IRQHandler - .word DMA1_Channel5_IRQHandler - .word DMA1_Channel6_IRQHandler - .word DMA1_Channel7_IRQHandler - .word ADC1_2_IRQHandler - .word USB_HP_CAN_TX_IRQHandler - .word USB_LP_CAN_RX0_IRQHandler - .word CAN_RX1_IRQHandler - .word CAN_SCE_IRQHandler - .word EXTI9_5_IRQHandler - .word TIM1_BRK_TIM15_IRQHandler - .word TIM1_UP_TIM16_IRQHandler - .word TIM1_TRG_COM_TIM17_IRQHandler - .word TIM1_CC_IRQHandler - .word TIM2_IRQHandler - .word TIM3_IRQHandler - .word TIM4_IRQHandler - .word I2C1_EV_IRQHandler - .word I2C1_ER_IRQHandler - .word I2C2_EV_IRQHandler - .word I2C2_ER_IRQHandler - .word SPI1_IRQHandler - .word SPI2_IRQHandler - .word USART1_IRQHandler - .word USART2_IRQHandler - .word USART3_IRQHandler - .word EXTI15_10_IRQHandler - .word RTC_Alarm_IRQHandler - .word USBWakeUp_IRQHandler - .word TIM8_BRK_IRQHandler - .word TIM8_UP_IRQHandler - .word TIM8_TRG_COM_IRQHandler - .word TIM8_CC_IRQHandler - .word ADC3_IRQHandler - .word 0 - .word 0 - .word 0 - .word SPI3_IRQHandler - .word UART4_IRQHandler - .word UART5_IRQHandler - .word TIM6_DAC_IRQHandler - .word TIM7_IRQHandler - .word DMA2_Channel1_IRQHandler - .word DMA2_Channel2_IRQHandler - .word DMA2_Channel3_IRQHandler - .word DMA2_Channel4_IRQHandler - .word DMA2_Channel5_IRQHandler - .word ADC4_IRQHandler - .word 0 - .word 0 - .word COMP1_2_3_IRQHandler - .word COMP4_5_6_IRQHandler - .word COMP7_IRQHandler - .word 0 - .word 0 - .word 0 - .word 0 - .word 0 - .word 0 - .word 0 - .word USB_HP_IRQHandler - .word USB_LP_IRQHandler - .word USBWakeUp_RMP_IRQHandler - .word 0 - .word 0 - .word 0 - .word 0 - .word FPU_IRQHandler - -/******************************************************************************* -* -* Provide weak aliases for each Exception handler to the Default_Handler. -* As they are weak aliases, any function with the same name will override -* this definition. -* -*******************************************************************************/ - - .weak NMI_Handler - .thumb_set NMI_Handler,Default_Handler - - .weak HardFault_Handler - .thumb_set HardFault_Handler,Default_Handler - - .weak MemManage_Handler - .thumb_set MemManage_Handler,Default_Handler - - .weak BusFault_Handler - .thumb_set BusFault_Handler,Default_Handler - - .weak UsageFault_Handler - .thumb_set UsageFault_Handler,Default_Handler - - .weak SVC_Handler - .thumb_set SVC_Handler,Default_Handler - - .weak DebugMon_Handler - .thumb_set DebugMon_Handler,Default_Handler - - .weak PendSV_Handler - .thumb_set PendSV_Handler,Default_Handler - - .weak SysTick_Handler - .thumb_set SysTick_Handler,Default_Handler - - .weak WWDG_IRQHandler - .thumb_set WWDG_IRQHandler,Default_Handler - - .weak PVD_IRQHandler - .thumb_set PVD_IRQHandler,Default_Handler - - .weak TAMP_STAMP_IRQHandler - .thumb_set TAMP_STAMP_IRQHandler,Default_Handler - - .weak RTC_WKUP_IRQHandler - .thumb_set RTC_WKUP_IRQHandler,Default_Handler - - .weak FLASH_IRQHandler - .thumb_set FLASH_IRQHandler,Default_Handler - - .weak RCC_IRQHandler - .thumb_set RCC_IRQHandler,Default_Handler - - .weak EXTI0_IRQHandler - .thumb_set EXTI0_IRQHandler,Default_Handler - - .weak EXTI1_IRQHandler - .thumb_set EXTI1_IRQHandler,Default_Handler - - .weak EXTI2_TSC_IRQHandler - .thumb_set EXTI2_TSC_IRQHandler,Default_Handler - - .weak EXTI3_IRQHandler - .thumb_set EXTI3_IRQHandler,Default_Handler - - .weak EXTI4_IRQHandler - .thumb_set EXTI4_IRQHandler,Default_Handler - - .weak DMA1_Channel1_IRQHandler - .thumb_set DMA1_Channel1_IRQHandler,Default_Handler - - .weak DMA1_Channel2_IRQHandler - .thumb_set DMA1_Channel2_IRQHandler,Default_Handler - - .weak DMA1_Channel3_IRQHandler - .thumb_set DMA1_Channel3_IRQHandler,Default_Handler - - .weak DMA1_Channel4_IRQHandler - .thumb_set DMA1_Channel4_IRQHandler,Default_Handler - - .weak DMA1_Channel5_IRQHandler - .thumb_set DMA1_Channel5_IRQHandler,Default_Handler - - .weak DMA1_Channel6_IRQHandler - .thumb_set DMA1_Channel6_IRQHandler,Default_Handler - - .weak DMA1_Channel7_IRQHandler - .thumb_set DMA1_Channel7_IRQHandler,Default_Handler - - .weak ADC1_2_IRQHandler - .thumb_set ADC1_2_IRQHandler,Default_Handler - - .weak USB_HP_CAN_TX_IRQHandler - .thumb_set USB_HP_CAN_TX_IRQHandler,Default_Handler - - .weak USB_LP_CAN_RX0_IRQHandler - .thumb_set USB_LP_CAN_RX0_IRQHandler,Default_Handler - - .weak CAN_RX1_IRQHandler - .thumb_set CAN_RX1_IRQHandler,Default_Handler - - .weak CAN_SCE_IRQHandler - .thumb_set CAN_SCE_IRQHandler,Default_Handler - - .weak EXTI9_5_IRQHandler - .thumb_set EXTI9_5_IRQHandler,Default_Handler - - .weak TIM1_BRK_TIM15_IRQHandler - .thumb_set TIM1_BRK_TIM15_IRQHandler,Default_Handler - - .weak TIM1_UP_TIM16_IRQHandler - .thumb_set TIM1_UP_TIM16_IRQHandler,Default_Handler - - .weak TIM1_TRG_COM_TIM17_IRQHandler - .thumb_set TIM1_TRG_COM_TIM17_IRQHandler,Default_Handler - - .weak TIM1_CC_IRQHandler - .thumb_set TIM1_CC_IRQHandler,Default_Handler - - .weak TIM2_IRQHandler - .thumb_set TIM2_IRQHandler,Default_Handler - - .weak TIM3_IRQHandler - .thumb_set TIM3_IRQHandler,Default_Handler - - .weak TIM4_IRQHandler - .thumb_set TIM4_IRQHandler,Default_Handler - - .weak I2C1_EV_IRQHandler - .thumb_set I2C1_EV_IRQHandler,Default_Handler - - .weak I2C1_ER_IRQHandler - .thumb_set I2C1_ER_IRQHandler,Default_Handler - - .weak I2C2_EV_IRQHandler - .thumb_set I2C2_EV_IRQHandler,Default_Handler - - .weak I2C2_ER_IRQHandler - .thumb_set I2C2_ER_IRQHandler,Default_Handler - - .weak SPI1_IRQHandler - .thumb_set SPI1_IRQHandler,Default_Handler - - .weak SPI2_IRQHandler - .thumb_set SPI2_IRQHandler,Default_Handler - - .weak USART1_IRQHandler - .thumb_set USART1_IRQHandler,Default_Handler - - .weak USART2_IRQHandler - .thumb_set USART2_IRQHandler,Default_Handler - - .weak USART3_IRQHandler - .thumb_set USART3_IRQHandler,Default_Handler - - .weak EXTI15_10_IRQHandler - .thumb_set EXTI15_10_IRQHandler,Default_Handler - - .weak RTC_Alarm_IRQHandler - .thumb_set RTC_Alarm_IRQHandler,Default_Handler - - .weak USBWakeUp_IRQHandler - .thumb_set USBWakeUp_IRQHandler,Default_Handler - - .weak TIM8_BRK_IRQHandler - .thumb_set TIM8_BRK_IRQHandler,Default_Handler - - .weak TIM8_UP_IRQHandler - .thumb_set TIM8_UP_IRQHandler,Default_Handler - - .weak TIM8_TRG_COM_IRQHandler - .thumb_set TIM8_TRG_COM_IRQHandler,Default_Handler - - .weak TIM8_CC_IRQHandler - .thumb_set TIM8_CC_IRQHandler,Default_Handler - - .weak ADC3_IRQHandler - .thumb_set ADC3_IRQHandler,Default_Handler - - .weak SPI3_IRQHandler - .thumb_set SPI3_IRQHandler,Default_Handler - - .weak UART4_IRQHandler - .thumb_set UART4_IRQHandler,Default_Handler - - .weak UART5_IRQHandler - .thumb_set UART5_IRQHandler,Default_Handler - - .weak TIM6_DAC_IRQHandler - .thumb_set TIM6_DAC_IRQHandler,Default_Handler - - .weak TIM7_IRQHandler - .thumb_set TIM7_IRQHandler,Default_Handler - - .weak DMA2_Channel1_IRQHandler - .thumb_set DMA2_Channel1_IRQHandler,Default_Handler - - .weak DMA2_Channel2_IRQHandler - .thumb_set DMA2_Channel2_IRQHandler,Default_Handler - - .weak DMA2_Channel3_IRQHandler - .thumb_set DMA2_Channel3_IRQHandler,Default_Handler - - .weak DMA2_Channel4_IRQHandler - .thumb_set DMA2_Channel4_IRQHandler,Default_Handler - - .weak DMA2_Channel5_IRQHandler - .thumb_set DMA2_Channel5_IRQHandler,Default_Handler - - .weak ADC4_IRQHandler - .thumb_set ADC4_IRQHandler,Default_Handler - - .weak COMP1_2_3_IRQHandler - .thumb_set COMP1_2_3_IRQHandler,Default_Handler - - .weak COMP4_5_6_IRQHandler - .thumb_set COMP4_5_6_IRQHandler,Default_Handler - - .weak COMP7_IRQHandler - .thumb_set COMP7_IRQHandler,Default_Handler - - .weak USB_HP_IRQHandler - .thumb_set USB_HP_IRQHandler,Default_Handler - - .weak USB_LP_IRQHandler - .thumb_set USB_LP_IRQHandler,Default_Handler - - .weak USBWakeUp_RMP_IRQHandler - .thumb_set USBWakeUp_RMP_IRQHandler,Default_Handler - - .weak FPU_IRQHandler - .thumb_set FPU_IRQHandler,Default_Handler diff --git a/src/libs/board/stm32f3_discovery/stm32f3xx_hal_conf.h b/src/libs/board/stm32f3_discovery/stm32f3xx_hal_conf.h deleted file mode 100644 index 2358932..0000000 --- a/src/libs/board/stm32f3_discovery/stm32f3xx_hal_conf.h +++ /dev/null @@ -1,356 +0,0 @@ -/** - ****************************************************************************** - * @file stm32f3xx_hal_conf.h - * @author MCD Application Team - * @brief HAL configuration file. - ****************************************************************************** - * @attention - * - * Copyright (c) 2016 STMicroelectronics. - * All rights reserved. - * - * This software is licensed under terms that can be found in the LICENSE file - * in the root directory of this software component. - * If no LICENSE file comes with this software, it is provided AS-IS. - * - ****************************************************************************** - */ - -/* Define to prevent recursive inclusion -------------------------------------*/ -#ifndef __STM32F3xx_HAL_CONF_H -#define __STM32F3xx_HAL_CONF_H - -#ifdef __cplusplus - extern "C" { -#endif - -/* Exported types ------------------------------------------------------------*/ -/* Exported constants --------------------------------------------------------*/ - -/* ########################## Module Selection ############################## */ -/** - * @brief This is the list of modules to be used in the HAL driver - */ -#define HAL_MODULE_ENABLED -#define HAL_ADC_MODULE_ENABLED -#define HAL_CAN_MODULE_ENABLED -/* #define HAL_CAN_LEGACY_MODULE_ENABLED */ -#define HAL_CEC_MODULE_ENABLED -#define HAL_COMP_MODULE_ENABLED -#define HAL_CORTEX_MODULE_ENABLED -#define HAL_CRC_MODULE_ENABLED -#define HAL_DAC_MODULE_ENABLED -#define HAL_DMA_MODULE_ENABLED -#define HAL_FLASH_MODULE_ENABLED -#define HAL_SRAM_MODULE_ENABLED -#define HAL_NOR_MODULE_ENABLED -#define HAL_NAND_MODULE_ENABLED -#define HAL_PCCARD_MODULE_ENABLED -#define HAL_GPIO_MODULE_ENABLED -#define HAL_EXTI_MODULE_ENABLED -#define HAL_HRTIM_MODULE_ENABLED -#define HAL_I2C_MODULE_ENABLED -#define HAL_I2S_MODULE_ENABLED -#define HAL_IRDA_MODULE_ENABLED -#define HAL_IWDG_MODULE_ENABLED -#define HAL_OPAMP_MODULE_ENABLED -#define HAL_PCD_MODULE_ENABLED -#define HAL_PWR_MODULE_ENABLED -#define HAL_RCC_MODULE_ENABLED -#define HAL_RTC_MODULE_ENABLED -#define HAL_SDADC_MODULE_ENABLED -#define HAL_SMARTCARD_MODULE_ENABLED -#define HAL_SMBUS_MODULE_ENABLED -#define HAL_SPI_MODULE_ENABLED -#define HAL_TIM_MODULE_ENABLED -#define HAL_TSC_MODULE_ENABLED -#define HAL_UART_MODULE_ENABLED -#define HAL_USART_MODULE_ENABLED -#define HAL_WWDG_MODULE_ENABLED - -/* ########################## HSE/HSI Values adaptation ##################### */ -/** - * @brief Adjust the value of External High Speed oscillator (HSE) used in your application. - * This value is used by the RCC HAL module to compute the system frequency - * (when HSE is used as system clock source, directly or through the PLL). - */ -#if !defined (HSE_VALUE) - #define HSE_VALUE (8000000U) /*!< Value of the External oscillator in Hz */ -#endif /* HSE_VALUE */ - -/** - * @brief In the following line adjust the External High Speed oscillator (HSE) Startup - * Timeout value - */ -#if !defined (HSE_STARTUP_TIMEOUT) - #define HSE_STARTUP_TIMEOUT (100U) /*!< Time out for HSE start up, in ms */ -#endif /* HSE_STARTUP_TIMEOUT */ - -/** - * @brief Internal High Speed oscillator (HSI) value. - * This value is used by the RCC HAL module to compute the system frequency - * (when HSI is used as system clock source, directly or through the PLL). - */ -#if !defined (HSI_VALUE) - #define HSI_VALUE (8000000U) /*!< Value of the Internal oscillator in Hz*/ -#endif /* HSI_VALUE */ - -/** - * @brief In the following line adjust the Internal High Speed oscillator (HSI) Startup - * Timeout value - */ -#if !defined (HSI_STARTUP_TIMEOUT) - #define HSI_STARTUP_TIMEOUT (5000U) /*!< Time out for HSI start up */ -#endif /* HSI_STARTUP_TIMEOUT */ - -/** - * @brief Internal Low Speed oscillator (LSI) value. - */ -#if !defined (LSI_VALUE) - #define LSI_VALUE (40000U) -#endif /* LSI_VALUE */ /*!< Value of the Internal Low Speed oscillator in Hz - The real value may vary depending on the variations - in voltage and temperature. */ -/** - * @brief External Low Speed oscillator (LSE) value. - */ -#if !defined (LSE_VALUE) - #define LSE_VALUE (32768U) /*!< Value of the External Low Speed oscillator in Hz */ -#endif /* LSE_VALUE */ - -/** - * @brief Time out for LSE start up value in ms. - */ -#if !defined (LSE_STARTUP_TIMEOUT) - #define LSE_STARTUP_TIMEOUT (5000U) /*!< Time out for LSE start up, in ms */ -#endif /* LSE_STARTUP_TIMEOUT */ - -/** - * @brief External clock source for I2S peripheral - * This value is used by the I2S HAL module to compute the I2S clock source - * frequency, this source is inserted directly through I2S_CKIN pad. - * - External clock generated through external PLL component on EVAL 303 (based on MCO or crystal) - * - External clock not generated on EVAL 373 - */ -#if !defined (EXTERNAL_CLOCK_VALUE) - #define EXTERNAL_CLOCK_VALUE (8000000U) /*!< Value of the External oscillator in Hz*/ -#endif /* EXTERNAL_CLOCK_VALUE */ - -/* Tip: To avoid modifying this file each time you need to use different HSE, - === you can define the HSE value in your toolchain compiler preprocessor. */ - -/* ########################### System Configuration ######################### */ -/** - * @brief This is the HAL system configuration section - */ -#define VDD_VALUE (3300U) /*!< Value of VDD in mv */ -#define TICK_INT_PRIORITY ((uint32_t)(1U<<__NVIC_PRIO_BITS) - 1U) /*!< tick interrupt priority (lowest by default) */ -#define USE_RTOS 0U -#define PREFETCH_ENABLE 1U -#define INSTRUCTION_CACHE_ENABLE 0U -#define DATA_CACHE_ENABLE 0U -#define USE_SPI_CRC 1U - -#define USE_HAL_ADC_REGISTER_CALLBACKS 0U /* ADC register callback disabled */ -#define USE_HAL_CAN_REGISTER_CALLBACKS 0U /* CAN register callback disabled */ -#define USE_HAL_COMP_REGISTER_CALLBACKS 0U /* COMP register callback disabled */ -#define USE_HAL_CEC_REGISTER_CALLBACKS 0U /* CEC register callback disabled */ -#define USE_HAL_DAC_REGISTER_CALLBACKS 0U /* DAC register callback disabled */ -#define USE_HAL_SRAM_REGISTER_CALLBACKS 0U /* SRAM register callback disabled */ -#define USE_HAL_SMBUS_REGISTER_CALLBACKS 0U /* SMBUS register callback disabled */ -#define USE_HAL_SDADC_REGISTER_CALLBACKS 0U /* SDADC register callback disabled */ -#define USE_HAL_NAND_REGISTER_CALLBACKS 0U /* NAND register callback disabled */ -#define USE_HAL_NOR_REGISTER_CALLBACKS 0U /* NOR register callback disabled */ -#define USE_HAL_PCCARD_REGISTER_CALLBACKS 0U /* PCCARD register callback disabled */ -#define USE_HAL_HRTIM_REGISTER_CALLBACKS 0U /* HRTIM register callback disabled */ -#define USE_HAL_I2C_REGISTER_CALLBACKS 0U /* I2C register callback disabled */ -#define USE_HAL_UART_REGISTER_CALLBACKS 0U /* UART register callback disabled */ -#define USE_HAL_USART_REGISTER_CALLBACKS 0U /* USART register callback disabled */ -#define USE_HAL_IRDA_REGISTER_CALLBACKS 0U /* IRDA register callback disabled */ -#define USE_HAL_SMARTCARD_REGISTER_CALLBACKS 0U /* SMARTCARD register callback disabled */ -#define USE_HAL_WWDG_REGISTER_CALLBACKS 0U /* WWDG register callback disabled */ -#define USE_HAL_OPAMP_REGISTER_CALLBACKS 0U /* OPAMP register callback disabled */ -#define USE_HAL_RTC_REGISTER_CALLBACKS 0U /* RTC register callback disabled */ -#define USE_HAL_SPI_REGISTER_CALLBACKS 0U /* SPI register callback disabled */ -#define USE_HAL_I2S_REGISTER_CALLBACKS 0U /* I2S register callback disabled */ -#define USE_HAL_TIM_REGISTER_CALLBACKS 0U /* TIM register callback disabled */ -#define USE_HAL_TSC_REGISTER_CALLBACKS 0U /* TSC register callback disabled */ -#define USE_HAL_PCD_REGISTER_CALLBACKS 0U /* PCD register callback disabled */ - -/* ########################## Assert Selection ############################## */ -/** - * @brief Uncomment the line below to expanse the "assert_param" macro in the - * HAL drivers code - */ -/*#define USE_FULL_ASSERT 1U*/ - -/* Includes ------------------------------------------------------------------*/ -/** - * @brief Include module's header file - */ - -#ifdef HAL_RCC_MODULE_ENABLED - #include "stm32f3xx_hal_rcc.h" -#endif /* HAL_RCC_MODULE_ENABLED */ - -#ifdef HAL_GPIO_MODULE_ENABLED - #include "stm32f3xx_hal_gpio.h" -#endif /* HAL_GPIO_MODULE_ENABLED */ - -#ifdef HAL_EXTI_MODULE_ENABLED - #include "stm32f3xx_hal_exti.h" -#endif /* HAL_EXTI_MODULE_ENABLED */ - -#ifdef HAL_DMA_MODULE_ENABLED - #include "stm32f3xx_hal_dma.h" -#endif /* HAL_DMA_MODULE_ENABLED */ - -#ifdef HAL_CORTEX_MODULE_ENABLED - #include "stm32f3xx_hal_cortex.h" -#endif /* HAL_CORTEX_MODULE_ENABLED */ - -#ifdef HAL_ADC_MODULE_ENABLED - #include "stm32f3xx_hal_adc.h" -#endif /* HAL_ADC_MODULE_ENABLED */ - -#ifdef HAL_CAN_MODULE_ENABLED - #include "stm32f3xx_hal_can.h" -#endif /* HAL_CAN_MODULE_ENABLED */ - -#ifdef HAL_CAN_LEGACY_MODULE_ENABLED - #include "stm32f3xx_hal_can_legacy.h" -#endif /* HAL_CAN_LEGACY_MODULE_ENABLED */ - -#ifdef HAL_CEC_MODULE_ENABLED - #include "stm32f3xx_hal_cec.h" -#endif /* HAL_CEC_MODULE_ENABLED */ - -#ifdef HAL_COMP_MODULE_ENABLED - #include "stm32f3xx_hal_comp.h" -#endif /* HAL_COMP_MODULE_ENABLED */ - -#ifdef HAL_CRC_MODULE_ENABLED - #include "stm32f3xx_hal_crc.h" -#endif /* HAL_CRC_MODULE_ENABLED */ - -#ifdef HAL_DAC_MODULE_ENABLED - #include "stm32f3xx_hal_dac.h" -#endif /* HAL_DAC_MODULE_ENABLED */ - -#ifdef HAL_FLASH_MODULE_ENABLED - #include "stm32f3xx_hal_flash.h" -#endif /* HAL_FLASH_MODULE_ENABLED */ - -#ifdef HAL_SRAM_MODULE_ENABLED - #include "stm32f3xx_hal_sram.h" -#endif /* HAL_SRAM_MODULE_ENABLED */ - -#ifdef HAL_NOR_MODULE_ENABLED - #include "stm32f3xx_hal_nor.h" -#endif /* HAL_NOR_MODULE_ENABLED */ - -#ifdef HAL_NAND_MODULE_ENABLED - #include "stm32f3xx_hal_nand.h" -#endif /* HAL_NAND_MODULE_ENABLED */ - -#ifdef HAL_PCCARD_MODULE_ENABLED - #include "stm32f3xx_hal_pccard.h" -#endif /* HAL_PCCARD_MODULE_ENABLED */ - -#ifdef HAL_HRTIM_MODULE_ENABLED - #include "stm32f3xx_hal_hrtim.h" -#endif /* HAL_HRTIM_MODULE_ENABLED */ - -#ifdef HAL_I2C_MODULE_ENABLED - #include "stm32f3xx_hal_i2c.h" -#endif /* HAL_I2C_MODULE_ENABLED */ - -#ifdef HAL_I2S_MODULE_ENABLED - #include "stm32f3xx_hal_i2s.h" -#endif /* HAL_I2S_MODULE_ENABLED */ - -#ifdef HAL_IRDA_MODULE_ENABLED - #include "stm32f3xx_hal_irda.h" -#endif /* HAL_IRDA_MODULE_ENABLED */ - -#ifdef HAL_IWDG_MODULE_ENABLED - #include "stm32f3xx_hal_iwdg.h" -#endif /* HAL_IWDG_MODULE_ENABLED */ - -#ifdef HAL_OPAMP_MODULE_ENABLED - #include "stm32f3xx_hal_opamp.h" -#endif /* HAL_OPAMP_MODULE_ENABLED */ - -#ifdef HAL_PCD_MODULE_ENABLED - #include "stm32f3xx_hal_pcd.h" -#endif /* HAL_PCD_MODULE_ENABLED */ - -#ifdef HAL_PWR_MODULE_ENABLED - #include "stm32f3xx_hal_pwr.h" -#endif /* HAL_PWR_MODULE_ENABLED */ - -#ifdef HAL_RTC_MODULE_ENABLED - #include "stm32f3xx_hal_rtc.h" -#endif /* HAL_RTC_MODULE_ENABLED */ - -#ifdef HAL_SDADC_MODULE_ENABLED - #include "stm32f3xx_hal_sdadc.h" -#endif /* HAL_SDADC_MODULE_ENABLED */ - -#ifdef HAL_SMARTCARD_MODULE_ENABLED - #include "stm32f3xx_hal_smartcard.h" -#endif /* HAL_SMARTCARD_MODULE_ENABLED */ - -#ifdef HAL_SMBUS_MODULE_ENABLED - #include "stm32f3xx_hal_smbus.h" -#endif /* HAL_SMBUS_MODULE_ENABLED */ - -#ifdef HAL_SPI_MODULE_ENABLED - #include "stm32f3xx_hal_spi.h" -#endif /* HAL_SPI_MODULE_ENABLED */ - -#ifdef HAL_TIM_MODULE_ENABLED - #include "stm32f3xx_hal_tim.h" -#endif /* HAL_TIM_MODULE_ENABLED */ - -#ifdef HAL_TSC_MODULE_ENABLED - #include "stm32f3xx_hal_tsc.h" -#endif /* HAL_TSC_MODULE_ENABLED */ - -#ifdef HAL_UART_MODULE_ENABLED - #include "stm32f3xx_hal_uart.h" -#endif /* HAL_UART_MODULE_ENABLED */ - -#ifdef HAL_USART_MODULE_ENABLED - #include "stm32f3xx_hal_usart.h" -#endif /* HAL_USART_MODULE_ENABLED */ - -#ifdef HAL_WWDG_MODULE_ENABLED - #include "stm32f3xx_hal_wwdg.h" -#endif /* HAL_WWDG_MODULE_ENABLED */ - -/* Exported macro ------------------------------------------------------------*/ -#ifdef USE_FULL_ASSERT -/** - * @brief The assert_param macro is used for function's parameters check. - * @param expr If expr is false, it calls assert_failed function - * which reports the name of the source file and the source - * line number of the call that failed. - * If expr is true, it returns no value. - * @retval None - */ - #define assert_param(expr) ((expr) ? (void)0U : assert_failed((uint8_t *)__FILE__, __LINE__)) -/* Exported functions ------------------------------------------------------- */ - void assert_failed(uint8_t* file, uint32_t line); -#else - #define assert_param(expr) ((void)0U) -#endif /* USE_FULL_ASSERT */ - -#ifdef __cplusplus -} -#endif - -#endif /* __STM32F3xx_HAL_CONF_H */ - - - diff --git a/src/libs/board/stm32f3_discovery/syscalls.c b/src/libs/board/stm32f3_discovery/syscalls.c deleted file mode 100644 index db2fa91..0000000 --- a/src/libs/board/stm32f3_discovery/syscalls.c +++ /dev/null @@ -1,194 +0,0 @@ -/** -***************************************************************************** -** -** File : syscalls.c -** -** Abstract : System Workbench Minimal System calls file -** -** For more information about which c-functions -** need which of these lowlevel functions -** please consult the Newlib libc-manual -** -** Environment : System Workbench for MCU -** -** Distribution: The file is distributed �as is,� without any warranty -** of any kind. -** -** (c)Copyright System Workbench for MCU. -** You may use this file as-is or modify it according to the needs of your -** project. Distribution of this file (unmodified or modified) is not -** permitted. System Workbench for MCU permit registered System Workbench(R) users the -** rights to distribute the assembled, compiled & linked contents of this -** file as part of an application binary file, provided that it is built -** using the System Workbench for MCU toolchain. -** -***************************************************************************** -*/ - -/* Includes */ -#include -#include -#include -#include -#include -#include -#include -#include - - -/* Variables */ -//#undef errno -extern int errno; -#define FreeRTOS -#define MAX_STACK_SIZE 0x2000 - -extern int __io_putchar(int ch) __attribute__((weak)); -extern int __io_getchar(void) __attribute__((weak)); - -#ifndef FreeRTOS - register char * stack_ptr asm("sp"); -#endif - - -/*register*/ char * stack_ptr asm("sp"); - -char *__env[1] = { 0 }; -char **environ = __env; - - -/* Functions */ -void initialise_monitor_handles() -{ -} - -int _getpid(void) -{ - return 1; -} - -int _kill(int pid, int sig) -{ - errno = EINVAL; - return -1; -} - -void _exit (int status) -{ - _kill(status, -1); - while (1) {} /* Make sure we hang here */ -} - -int _read (int file, char *ptr, int len) -{ - int DataIdx; - - for (DataIdx = 0; DataIdx < len; DataIdx++) - { - *ptr++ = __io_getchar(); - } - -return len; -} - -int _write(int file, char *ptr, int len) -{ - int DataIdx; - - for (DataIdx = 0; DataIdx < len; DataIdx++) - { - __io_putchar(*ptr++); - } - return len; -} - -caddr_t _sbrk(int incr) -{ - extern char end asm("end"); - static char *heap_end; - char *prev_heap_end; - - if (heap_end == 0) - heap_end = &end; - - prev_heap_end = heap_end; - if (heap_end + incr > stack_ptr) - { -// write(1, "Heap and stack collision\n", 25); -// abort(); - errno = ENOMEM; - return (caddr_t) -1; - } - - heap_end += incr; - - return (caddr_t) prev_heap_end; -} - -int _close(int file) -{ - return -1; -} - - -int _fstat(int file, struct stat *st) -{ - st->st_mode = S_IFCHR; - return 0; -} - -int _isatty(int file) -{ - return 1; -} - -int _lseek(int file, int ptr, int dir) -{ - return 0; -} - -int _open(char *path, int flags, ...) -{ - /* Pretend like we always fail */ - return -1; -} - -int _wait(int *status) -{ - errno = ECHILD; - return -1; -} - -int _unlink(char *name) -{ - errno = ENOENT; - return -1; -} - -int _times(struct tms *buf) -{ - return -1; -} - -int _stat(char *file, struct stat *st) -{ - st->st_mode = S_IFCHR; - return 0; -} - -int _link(char *old, char *new) -{ - errno = EMLINK; - return -1; -} - -int _fork(void) -{ - errno = EAGAIN; - return -1; -} - -int _execve(char *name, char **argv, char **env) -{ - errno = ENOMEM; - return -1; -} diff --git a/src/libs/board/stm32f767zi_nucleo/CMakeLists.txt b/src/libs/board/stm32f767zi_nucleo/CMakeLists.txt deleted file mode 100644 index e69de29..0000000 diff --git a/src/libs/board/stm32f767zi_nucleo/Inc/main.h b/src/libs/board/stm32f767zi_nucleo/Inc/main.h deleted file mode 100644 index 9e724cd..0000000 --- a/src/libs/board/stm32f767zi_nucleo/Inc/main.h +++ /dev/null @@ -1,32 +0,0 @@ -/** - ****************************************************************************** - * @file Templates/Inc/main.h - * @author MCD Application Team - * @brief Header for main.c module - ****************************************************************************** - * @attention - * - * Copyright (c) 2016 STMicroelectronics. - * All rights reserved. - * - * This software is licensed under terms that can be found in the LICENSE file - * in the root directory of this software component. - * If no LICENSE file comes with this software, it is provided AS-IS. - * - ****************************************************************************** - */ - -/* Define to prevent recursive inclusion -------------------------------------*/ -#ifndef __MAIN_H -#define __MAIN_H - -/* Includes ------------------------------------------------------------------*/ -#include "stm32f7xx_hal.h" -#include "stm32f7xx_nucleo_144.h" -/* Exported types ------------------------------------------------------------*/ -/* Exported constants --------------------------------------------------------*/ -/* Exported macro ------------------------------------------------------------*/ -/* Exported functions ------------------------------------------------------- */ - -#endif /* __MAIN_H */ - diff --git a/src/libs/board/stm32f767zi_nucleo/Inc/stm32f7xx_hal_conf.h b/src/libs/board/stm32f767zi_nucleo/Inc/stm32f7xx_hal_conf.h deleted file mode 100644 index 02bf0d7..0000000 --- a/src/libs/board/stm32f767zi_nucleo/Inc/stm32f7xx_hal_conf.h +++ /dev/null @@ -1,475 +0,0 @@ -/** - ****************************************************************************** - * @file stm32f7xx_hal_conf.h - * @author MCD Application Team - * @brief HAL configuration file. - ****************************************************************************** - * @attention - * - * Copyright (c) 2016 STMicroelectronics. - * All rights reserved. - * - * This software is licensed under terms that can be found in the LICENSE file - * in the root directory of this software component. - * If no LICENSE file comes with this software, it is provided AS-IS. - * - ****************************************************************************** - */ - -/* Define to prevent recursive inclusion -------------------------------------*/ -#ifndef __STM32F7xx_HAL_CONF_H -#define __STM32F7xx_HAL_CONF_H - -#ifdef __cplusplus - extern "C" { -#endif - -/* Exported types ------------------------------------------------------------*/ -/* Exported constants --------------------------------------------------------*/ - -/* ########################## Module Selection ############################## */ -/** - * @brief This is the list of modules to be used in the HAL driver - */ -#define HAL_MODULE_ENABLED -#define HAL_ADC_MODULE_ENABLED -#define HAL_CAN_MODULE_ENABLED -/* #define HAL_CAN_LEGACY_MODULE_ENABLED */ -#define HAL_CEC_MODULE_ENABLED -#define HAL_CRC_MODULE_ENABLED -/*#define HAL_CRYP_MODULE_ENABLED*/ -#define HAL_DAC_MODULE_ENABLED -#define HAL_DCMI_MODULE_ENABLED -#define HAL_DMA_MODULE_ENABLED -#define HAL_DMA2D_MODULE_ENABLED -#define HAL_ETH_MODULE_ENABLED -#define HAL_EXTI_MODULE_ENABLED -#define HAL_FLASH_MODULE_ENABLED -#define HAL_NAND_MODULE_ENABLED -#define HAL_NOR_MODULE_ENABLED -#define HAL_SRAM_MODULE_ENABLED -#define HAL_SDRAM_MODULE_ENABLED -/*#define HAL_HASH_MODULE_ENABLED*/ -#define HAL_GPIO_MODULE_ENABLED -#define HAL_I2C_MODULE_ENABLED -#define HAL_I2S_MODULE_ENABLED -#define HAL_IWDG_MODULE_ENABLED -#define HAL_LPTIM_MODULE_ENABLED -#define HAL_LTDC_MODULE_ENABLED -#define HAL_PWR_MODULE_ENABLED -#define HAL_QSPI_MODULE_ENABLED -#define HAL_RCC_MODULE_ENABLED -#define HAL_RNG_MODULE_ENABLED -#define HAL_RTC_MODULE_ENABLED -#define HAL_SAI_MODULE_ENABLED -#define HAL_SD_MODULE_ENABLED -#define HAL_SPDIFRX_MODULE_ENABLED -#define HAL_SPI_MODULE_ENABLED -#define HAL_TIM_MODULE_ENABLED -#define HAL_UART_MODULE_ENABLED -#define HAL_USART_MODULE_ENABLED -#define HAL_IRDA_MODULE_ENABLED -#define HAL_SMARTCARD_MODULE_ENABLED -#define HAL_WWDG_MODULE_ENABLED -#define HAL_CORTEX_MODULE_ENABLED -#define HAL_PCD_MODULE_ENABLED -#define HAL_HCD_MODULE_ENABLED -#define HAL_DFSDM_MODULE_ENABLED -/* #define HAL_DSI_MODULE_ENABLED */ -#define HAL_JPEG_MODULE_ENABLED -#define HAL_MDIOS_MODULE_ENABLED - - -/* ########################## HSE/HSI Values adaptation ##################### */ -/** - * @brief Adjust the value of External High Speed oscillator (HSE) used in your application. - * This value is used by the RCC HAL module to compute the system frequency - * (when HSE is used as system clock source, directly or through the PLL). - */ -#if !defined (HSE_VALUE) - #define HSE_VALUE (8000000U) /*!< Value of the External oscillator in Hz */ -#endif /* HSE_VALUE */ - -#if !defined (HSE_STARTUP_TIMEOUT) - #define HSE_STARTUP_TIMEOUT 100U /*!< Time out for HSE start up, in ms */ -#endif /* HSE_STARTUP_TIMEOUT */ - -/** - * @brief Internal High Speed oscillator (HSI) value. - * This value is used by the RCC HAL module to compute the system frequency - * (when HSI is used as system clock source, directly or through the PLL). - */ -#if !defined (HSI_VALUE) - #define HSI_VALUE 16000000U /*!< Value of the Internal oscillator in Hz*/ -#endif /* HSI_VALUE */ - -/** - * @brief Internal Low Speed oscillator (LSI) value. - */ -#if !defined (LSI_VALUE) - #define LSI_VALUE 32000U /*!< LSI Typical Value in Hz*/ -#endif /* LSI_VALUE */ /*!< Value of the Internal Low Speed oscillator in Hz - The real value may vary depending on the variations - in voltage and temperature. */ -/** - * @brief External Low Speed oscillator (LSE) value. - */ -#if !defined (LSE_VALUE) - #define LSE_VALUE 32768U /*!< Value of the External Low Speed oscillator in Hz */ -#endif /* LSE_VALUE */ - -#if !defined (LSE_STARTUP_TIMEOUT) - #define LSE_STARTUP_TIMEOUT 5000U /*!< Time out for LSE start up, in ms */ -#endif /* LSE_STARTUP_TIMEOUT */ - -/** - * @brief External clock source for I2S peripheral - * This value is used by the I2S HAL module to compute the I2S clock source - * frequency, this source is inserted directly through I2S_CKIN pad. - */ -#if !defined (EXTERNAL_CLOCK_VALUE) - #define EXTERNAL_CLOCK_VALUE 12288000U /*!< Value of the Internal oscillator in Hz*/ -#endif /* EXTERNAL_CLOCK_VALUE */ - -/* Tip: To avoid modifying this file each time you need to use different HSE, - === you can define the HSE value in your toolchain compiler preprocessor. */ - -/* ########################### System Configuration ######################### */ -/** - * @brief This is the HAL system configuration section - */ -#define VDD_VALUE 3300U /*!< Value of VDD in mv */ -#define TICK_INT_PRIORITY 0x0FU /*!< tick interrupt priority */ -#define USE_RTOS 0U -#define PREFETCH_ENABLE 1U -#define ART_ACCELERATOR_ENABLE 1U /* To enable instruction cache and prefetch */ - -#define USE_HAL_ADC_REGISTER_CALLBACKS 0U /* ADC register callback disabled */ -#define USE_HAL_CAN_REGISTER_CALLBACKS 0U /* CAN register callback disabled */ -#define USE_HAL_CEC_REGISTER_CALLBACKS 0U /* CEC register callback disabled */ -#define USE_HAL_CRYP_REGISTER_CALLBACKS 0U /* CRYP register callback disabled */ -#define USE_HAL_DAC_REGISTER_CALLBACKS 0U /* DAC register callback disabled */ -#define USE_HAL_DCMI_REGISTER_CALLBACKS 0U /* DCMI register callback disabled */ -#define USE_HAL_DFSDM_REGISTER_CALLBACKS 0U /* DFSDM register callback disabled */ -#define USE_HAL_DMA2D_REGISTER_CALLBACKS 0U /* DMA2D register callback disabled */ -#define USE_HAL_DSI_REGISTER_CALLBACKS 0U /* DSI register callback disabled */ -#define USE_HAL_ETH_REGISTER_CALLBACKS 0U /* ETH register callback disabled */ -#define USE_HAL_HASH_REGISTER_CALLBACKS 0U /* HASH register callback disabled */ -#define USE_HAL_HCD_REGISTER_CALLBACKS 0U /* HCD register callback disabled */ -#define USE_HAL_I2C_REGISTER_CALLBACKS 0U /* I2C register callback disabled */ -#define USE_HAL_I2S_REGISTER_CALLBACKS 0U /* I2S register callback disabled */ -#define USE_HAL_IRDA_REGISTER_CALLBACKS 0U /* IRDA register callback disabled */ -#define USE_HAL_JPEG_REGISTER_CALLBACKS 0U /* JPEG register callback disabled */ -#define USE_HAL_LPTIM_REGISTER_CALLBACKS 0U /* LPTIM register callback disabled */ -#define USE_HAL_LTDC_REGISTER_CALLBACKS 0U /* LTDC register callback disabled */ -#define USE_HAL_MDIOS_REGISTER_CALLBACKS 0U /* MDIOS register callback disabled */ -#define USE_HAL_MMC_REGISTER_CALLBACKS 0U /* MMC register callback disabled */ -#define USE_HAL_NAND_REGISTER_CALLBACKS 0U /* NAND register callback disabled */ -#define USE_HAL_NOR_REGISTER_CALLBACKS 0U /* NOR register callback disabled */ -#define USE_HAL_PCD_REGISTER_CALLBACKS 0U /* PCD register callback disabled */ -#define USE_HAL_QSPI_REGISTER_CALLBACKS 0U /* QSPI register callback disabled */ -#define USE_HAL_RNG_REGISTER_CALLBACKS 0U /* RNG register callback disabled */ -#define USE_HAL_RTC_REGISTER_CALLBACKS 0U /* RTC register callback disabled */ -#define USE_HAL_SAI_REGISTER_CALLBACKS 0U /* SAI register callback disabled */ -#define USE_HAL_SD_REGISTER_CALLBACKS 0U /* SD register callback disabled */ -#define USE_HAL_SMARTCARD_REGISTER_CALLBACKS 0U /* SMARTCARD register callback disabled */ -#define USE_HAL_SDRAM_REGISTER_CALLBACKS 0U /* SDRAM register callback disabled */ -#define USE_HAL_SRAM_REGISTER_CALLBACKS 0U /* SRAM register callback disabled */ -#define USE_HAL_SPDIFRX_REGISTER_CALLBACKS 0U /* SPDIFRX register callback disabled */ -#define USE_HAL_SMBUS_REGISTER_CALLBACKS 0U /* SMBUS register callback disabled */ -#define USE_HAL_SPI_REGISTER_CALLBACKS 0U /* SPI register callback disabled */ -#define USE_HAL_TIM_REGISTER_CALLBACKS 0U /* TIM register callback disabled */ -#define USE_HAL_UART_REGISTER_CALLBACKS 0U /* UART register callback disabled */ -#define USE_HAL_USART_REGISTER_CALLBACKS 0U /* USART register callback disabled */ -#define USE_HAL_WWDG_REGISTER_CALLBACKS 0U /* WWDG register callback disabled */ - -/* ########################## Assert Selection ############################## */ -/** - * @brief Uncomment the line below to expanse the "assert_param" macro in the - * HAL drivers code - */ -/* #define USE_FULL_ASSERT 1U */ - -/* ################## Ethernet peripheral configuration for NUCLEO 144 board ##################### */ - -/* Section 1 : Ethernet peripheral configuration */ - -/* MAC ADDRESS: MAC_ADDR0:MAC_ADDR1:MAC_ADDR2:MAC_ADDR3:MAC_ADDR4:MAC_ADDR5 */ -#define MAC_ADDR0 2U -#define MAC_ADDR1 0U -#define MAC_ADDR2 0U -#define MAC_ADDR3 0U -#define MAC_ADDR4 0U -#define MAC_ADDR5 0U - -/* Definition of the Ethernet driver buffers size and count */ -#define ETH_RX_BUF_SIZE 1528U /* ETH Max buffer size for receive */ -#define ETH_TX_BUF_SIZE 1528U /* ETH Max buffer size for transmit */ -#define ETH_RXBUFNB 5U /* 5 Rx buffers of size ETH_RX_BUF_SIZE */ -#define ETH_TXBUFNB 5U /* 5 Tx buffers of size ETH_TX_BUF_SIZE */ - -/* Section 2: PHY configuration section */ -/* LAN8742A PHY Address*/ -#define LAN8742A_PHY_ADDRESS 0x00U -/* PHY Reset delay these values are based on a 1 ms Systick interrupt*/ -#define PHY_RESET_DELAY 0x00000FFFU -/* PHY Configuration delay */ -#define PHY_CONFIG_DELAY 0x00000FFFU - -#define PHY_READ_TO 0x0000FFFFU -#define PHY_WRITE_TO 0x0000FFFFU - -/* Section 3: Common PHY Registers */ - -#define PHY_BCR ((uint16_t)0x0000U) /*!< Transceiver Basic Control Register */ -#define PHY_BSR ((uint16_t)0x0001U) /*!< Transceiver Basic Status Register */ - -#define PHY_RESET ((uint16_t)0x8000U) /*!< PHY Reset */ -#define PHY_LOOPBACK ((uint16_t)0x4000U) /*!< Select loop-back mode */ -#define PHY_FULLDUPLEX_100M ((uint16_t)0x2100U) /*!< Set the full-duplex mode at 100 Mb/s */ -#define PHY_HALFDUPLEX_100M ((uint16_t)0x2000U) /*!< Set the half-duplex mode at 100 Mb/s */ -#define PHY_FULLDUPLEX_10M ((uint16_t)0x0100U) /*!< Set the full-duplex mode at 10 Mb/s */ -#define PHY_HALFDUPLEX_10M ((uint16_t)0x0000U) /*!< Set the half-duplex mode at 10 Mb/s */ -#define PHY_AUTONEGOTIATION ((uint16_t)0x1000U) /*!< Enable auto-negotiation function */ -#define PHY_RESTART_AUTONEGOTIATION ((uint16_t)0x0200U) /*!< Restart auto-negotiation function */ -#define PHY_POWERDOWN ((uint16_t)0x0800U) /*!< Select the power down mode */ -#define PHY_ISOLATE ((uint16_t)0x0400U) /*!< Isolate PHY from MII */ - -#define PHY_AUTONEGO_COMPLETE ((uint16_t)0x0020U) /*!< Auto-Negotiation process completed */ -#define PHY_LINKED_STATUS ((uint16_t)0x0004U) /*!< Valid link established */ -#define PHY_JABBER_DETECTION ((uint16_t)0x0002U) /*!< Jabber condition detected */ - -/* Section 4: Extended PHY Registers */ - -#define PHY_SR ((uint16_t)0x001FU) /*!< PHY special control/ status register Offset */ - -#define PHY_SPEED_STATUS ((uint16_t)0x0004U) /*!< PHY Speed mask */ -#define PHY_DUPLEX_STATUS ((uint16_t)0x0010U) /*!< PHY Duplex mask */ - - -#define PHY_ISFR ((uint16_t)0x1D) /*!< PHY Interrupt Source Flag register Offset */ -#define PHY_ISFR_INT4 ((uint16_t)0x0010) /*!< PHY Link down inturrupt */ - -/* ################## SPI peripheral configuration ########################## */ - -/* CRC FEATURE: Use to activate CRC feature inside HAL SPI Driver -* Activated: CRC code is present inside driver -* Deactivated: CRC code cleaned from driver -*/ - -#define USE_SPI_CRC 1U - -/* Includes ------------------------------------------------------------------*/ -/** - * @brief Include module's header file - */ - -#ifdef HAL_RCC_MODULE_ENABLED - #include "stm32f7xx_hal_rcc.h" -#endif /* HAL_RCC_MODULE_ENABLED */ - -#ifdef HAL_EXTI_MODULE_ENABLED - #include "stm32f7xx_hal_exti.h" -#endif /* HAL_EXTI_MODULE_ENABLED */ - -#ifdef HAL_GPIO_MODULE_ENABLED - #include "stm32f7xx_hal_gpio.h" -#endif /* HAL_GPIO_MODULE_ENABLED */ - -#ifdef HAL_DMA_MODULE_ENABLED - #include "stm32f7xx_hal_dma.h" -#endif /* HAL_DMA_MODULE_ENABLED */ - -#ifdef HAL_CORTEX_MODULE_ENABLED - #include "stm32f7xx_hal_cortex.h" -#endif /* HAL_CORTEX_MODULE_ENABLED */ - -#ifdef HAL_ADC_MODULE_ENABLED - #include "stm32f7xx_hal_adc.h" -#endif /* HAL_ADC_MODULE_ENABLED */ - -#ifdef HAL_CAN_MODULE_ENABLED - #include "stm32f7xx_hal_can.h" -#endif /* HAL_CAN_MODULE_ENABLED */ - -#ifdef HAL_CAN_LEGACY_MODULE_ENABLED - #include "stm32f7xx_hal_can_legacy.h" -#endif /* HAL_CAN_LEGACY_MODULE_ENABLED */ - -#ifdef HAL_CEC_MODULE_ENABLED - #include "stm32f7xx_hal_cec.h" -#endif /* HAL_CEC_MODULE_ENABLED */ - -#ifdef HAL_CRC_MODULE_ENABLED - #include "stm32f7xx_hal_crc.h" -#endif /* HAL_CRC_MODULE_ENABLED */ - -#ifdef HAL_CRYP_MODULE_ENABLED - #include "stm32f7xx_hal_cryp.h" -#endif /* HAL_CRYP_MODULE_ENABLED */ - -#ifdef HAL_DMA2D_MODULE_ENABLED - #include "stm32f7xx_hal_dma2d.h" -#endif /* HAL_DMA2D_MODULE_ENABLED */ - -#ifdef HAL_DAC_MODULE_ENABLED - #include "stm32f7xx_hal_dac.h" -#endif /* HAL_DAC_MODULE_ENABLED */ - -#ifdef HAL_DCMI_MODULE_ENABLED - #include "stm32f7xx_hal_dcmi.h" -#endif /* HAL_DCMI_MODULE_ENABLED */ - -#ifdef HAL_ETH_MODULE_ENABLED - #include "stm32f7xx_hal_eth.h" -#endif /* HAL_ETH_MODULE_ENABLED */ - -#ifdef HAL_FLASH_MODULE_ENABLED - #include "stm32f7xx_hal_flash.h" -#endif /* HAL_FLASH_MODULE_ENABLED */ - -#ifdef HAL_SRAM_MODULE_ENABLED - #include "stm32f7xx_hal_sram.h" -#endif /* HAL_SRAM_MODULE_ENABLED */ - -#ifdef HAL_NOR_MODULE_ENABLED - #include "stm32f7xx_hal_nor.h" -#endif /* HAL_NOR_MODULE_ENABLED */ - -#ifdef HAL_NAND_MODULE_ENABLED - #include "stm32f7xx_hal_nand.h" -#endif /* HAL_NAND_MODULE_ENABLED */ - -#ifdef HAL_SDRAM_MODULE_ENABLED - #include "stm32f7xx_hal_sdram.h" -#endif /* HAL_SDRAM_MODULE_ENABLED */ - -#ifdef HAL_HASH_MODULE_ENABLED - #include "stm32f7xx_hal_hash.h" -#endif /* HAL_HASH_MODULE_ENABLED */ - -#ifdef HAL_I2C_MODULE_ENABLED - #include "stm32f7xx_hal_i2c.h" -#endif /* HAL_I2C_MODULE_ENABLED */ - -#ifdef HAL_I2S_MODULE_ENABLED - #include "stm32f7xx_hal_i2s.h" -#endif /* HAL_I2S_MODULE_ENABLED */ - -#ifdef HAL_IWDG_MODULE_ENABLED - #include "stm32f7xx_hal_iwdg.h" -#endif /* HAL_IWDG_MODULE_ENABLED */ - -#ifdef HAL_LPTIM_MODULE_ENABLED - #include "stm32f7xx_hal_lptim.h" -#endif /* HAL_LPTIM_MODULE_ENABLED */ - -#ifdef HAL_LTDC_MODULE_ENABLED - #include "stm32f7xx_hal_ltdc.h" -#endif /* HAL_LTDC_MODULE_ENABLED */ - -#ifdef HAL_PWR_MODULE_ENABLED - #include "stm32f7xx_hal_pwr.h" -#endif /* HAL_PWR_MODULE_ENABLED */ - -#ifdef HAL_QSPI_MODULE_ENABLED - #include "stm32f7xx_hal_qspi.h" -#endif /* HAL_QSPI_MODULE_ENABLED */ - -#ifdef HAL_RNG_MODULE_ENABLED - #include "stm32f7xx_hal_rng.h" -#endif /* HAL_RNG_MODULE_ENABLED */ - -#ifdef HAL_RTC_MODULE_ENABLED - #include "stm32f7xx_hal_rtc.h" -#endif /* HAL_RTC_MODULE_ENABLED */ - -#ifdef HAL_SAI_MODULE_ENABLED - #include "stm32f7xx_hal_sai.h" -#endif /* HAL_SAI_MODULE_ENABLED */ - -#ifdef HAL_SD_MODULE_ENABLED - #include "stm32f7xx_hal_sd.h" -#endif /* HAL_SD_MODULE_ENABLED */ - -#ifdef HAL_SPDIFRX_MODULE_ENABLED - #include "stm32f7xx_hal_spdifrx.h" -#endif /* HAL_SPDIFRX_MODULE_ENABLED */ - -#ifdef HAL_SPI_MODULE_ENABLED - #include "stm32f7xx_hal_spi.h" -#endif /* HAL_SPI_MODULE_ENABLED */ - -#ifdef HAL_TIM_MODULE_ENABLED - #include "stm32f7xx_hal_tim.h" -#endif /* HAL_TIM_MODULE_ENABLED */ - -#ifdef HAL_UART_MODULE_ENABLED - #include "stm32f7xx_hal_uart.h" -#endif /* HAL_UART_MODULE_ENABLED */ - -#ifdef HAL_USART_MODULE_ENABLED - #include "stm32f7xx_hal_usart.h" -#endif /* HAL_USART_MODULE_ENABLED */ - -#ifdef HAL_IRDA_MODULE_ENABLED - #include "stm32f7xx_hal_irda.h" -#endif /* HAL_IRDA_MODULE_ENABLED */ - -#ifdef HAL_SMARTCARD_MODULE_ENABLED - #include "stm32f7xx_hal_smartcard.h" -#endif /* HAL_SMARTCARD_MODULE_ENABLED */ - -#ifdef HAL_WWDG_MODULE_ENABLED - #include "stm32f7xx_hal_wwdg.h" -#endif /* HAL_WWDG_MODULE_ENABLED */ - -#ifdef HAL_PCD_MODULE_ENABLED - #include "stm32f7xx_hal_pcd.h" -#endif /* HAL_PCD_MODULE_ENABLED */ - -#ifdef HAL_HCD_MODULE_ENABLED - #include "stm32f7xx_hal_hcd.h" -#endif /* HAL_HCD_MODULE_ENABLED */ - -#ifdef HAL_DFSDM_MODULE_ENABLED - #include "stm32f7xx_hal_dfsdm.h" -#endif /* HAL_DFSDM_MODULE_ENABLED */ - -#ifdef HAL_DSI_MODULE_ENABLED - #include "stm32f7xx_hal_dsi.h" -#endif /* HAL_DSI_MODULE_ENABLED */ - -#ifdef HAL_JPEG_MODULE_ENABLED - #include "stm32f7xx_hal_jpeg.h" -#endif /* HAL_JPEG_MODULE_ENABLED */ - -#ifdef HAL_MDIOS_MODULE_ENABLED - #include "stm32f7xx_hal_mdios.h" -#endif /* HAL_MDIOS_MODULE_ENABLED */ - -/* Exported macro ------------------------------------------------------------*/ -#ifdef USE_FULL_ASSERT -/** - * @brief The assert_param macro is used for function's parameters check. - * @param expr: If expr is false, it calls assert_failed function - * which reports the name of the source file and the source - * line number of the call that failed. - * If expr is true, it returns no value. - * @retval None - */ - #define assert_param(expr) ((expr) ? (void)0 : assert_failed((uint8_t *)__FILE__, __LINE__)) -/* Exported functions ------------------------------------------------------- */ - void assert_failed(uint8_t* file, uint32_t line); -#else - #define assert_param(expr) ((void)0U) -#endif /* USE_FULL_ASSERT */ - - -#ifdef __cplusplus -} -#endif - -#endif /* __STM32F7xx_HAL_CONF_H */ - - diff --git a/src/libs/board/stm32f767zi_nucleo/Inc/stm32f7xx_it.h b/src/libs/board/stm32f767zi_nucleo/Inc/stm32f7xx_it.h deleted file mode 100644 index 91e513c..0000000 --- a/src/libs/board/stm32f767zi_nucleo/Inc/stm32f7xx_it.h +++ /dev/null @@ -1,48 +0,0 @@ -/** - ****************************************************************************** - * @file Templates/Inc/stm32f7xx_it.h - * @author MCD Application Team - * @brief This file contains the headers of the interrupt handlers. - ****************************************************************************** - * @attention - * - * Copyright (c) 2016 STMicroelectronics. - * All rights reserved. - * - * This software is licensed under terms that can be found in the LICENSE file - * in the root directory of this software component. - * If no LICENSE file comes with this software, it is provided AS-IS. - * - ****************************************************************************** - */ - -/* Define to prevent recursive inclusion -------------------------------------*/ -#ifndef __STM32F7xx_IT_H -#define __STM32F7xx_IT_H - -#ifdef __cplusplus - extern "C" { -#endif - -/* Includes ------------------------------------------------------------------*/ -/* Exported types ------------------------------------------------------------*/ -/* Exported constants --------------------------------------------------------*/ -/* Exported macro ------------------------------------------------------------*/ -/* Exported functions ------------------------------------------------------- */ - -void NMI_Handler(void); -void HardFault_Handler(void); -void MemManage_Handler(void); -void BusFault_Handler(void); -void UsageFault_Handler(void); -void SVC_Handler(void); -void DebugMon_Handler(void); -void PendSV_Handler(void); -void SysTick_Handler(void); - -#ifdef __cplusplus -} -#endif - -#endif /* __STM32F7xx_IT_H */ - diff --git a/src/libs/board/stm32f767zi_nucleo/STM32F767ZITx_FLASH.ld b/src/libs/board/stm32f767zi_nucleo/STM32F767ZITx_FLASH.ld deleted file mode 100644 index 14b27ab..0000000 --- a/src/libs/board/stm32f767zi_nucleo/STM32F767ZITx_FLASH.ld +++ /dev/null @@ -1,169 +0,0 @@ -/* -***************************************************************************** -** - -** File : LinkerScript.ld -** -** Abstract : Linker script for STM32F767ZITx Device with -** 2048KByte FLASH, 320KByte RAM -** -** Set heap size, stack size and stack location according -** to application requirements. -** -** Set memory bank area and size if external memory is used. -** -** Target : STMicroelectronics STM32 -** -** -** Distribution: The file is distributed as is, without any warranty -** of any kind. -** -** (c)Copyright Ac6. -** You may use this file as-is or modify it according to the needs of your -** project. Distribution of this file (unmodified or modified) is not -** permitted. Ac6 permit registered System Workbench for MCU users the -** rights to distribute the assembled, compiled & linked contents of this -** file as part of an application binary file, provided that it is built -** using the System Workbench for MCU toolchain. -** -***************************************************************************** -*/ - -/* Entry Point */ -ENTRY(Reset_Handler) - -/* Highest address of the user mode stack */ -_estack = 0x20050000; /* end of RAM */ -/* Generate a link error if heap and stack don't fit into RAM */ -_Min_Heap_Size = 0x200; /* required amount of heap */ -_Min_Stack_Size = 0x400; /* required amount of stack */ - -/* Specify the memory areas */ -MEMORY -{ -FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2048K -RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 320K -} - -/* Define output sections */ -SECTIONS -{ - /* The startup code goes first into FLASH */ - .isr_vector : - { - . = ALIGN(4); - KEEP(*(.isr_vector)) /* Startup code */ - . = ALIGN(4); - } >FLASH - - /* The program code and other data goes into FLASH */ - .text : - { - . = ALIGN(4); - *(.text) /* .text sections (code) */ - *(.text*) /* .text* sections (code) */ - *(.glue_7) /* glue arm to thumb code */ - *(.glue_7t) /* glue thumb to arm code */ - *(.eh_frame) - - KEEP (*(.init)) - KEEP (*(.fini)) - - . = ALIGN(4); - _etext = .; /* define a global symbols at end of code */ - } >FLASH - - /* Constant data goes into FLASH */ - .rodata : - { - . = ALIGN(4); - *(.rodata) /* .rodata sections (constants, strings, etc.) */ - *(.rodata*) /* .rodata* sections (constants, strings, etc.) */ - . = ALIGN(4); - } >FLASH - - .ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >FLASH - .ARM : { - __exidx_start = .; - *(.ARM.exidx*) - __exidx_end = .; - } >FLASH - - .preinit_array : - { - PROVIDE_HIDDEN (__preinit_array_start = .); - KEEP (*(.preinit_array*)) - PROVIDE_HIDDEN (__preinit_array_end = .); - } >FLASH - .init_array : - { - PROVIDE_HIDDEN (__init_array_start = .); - KEEP (*(SORT(.init_array.*))) - KEEP (*(.init_array*)) - PROVIDE_HIDDEN (__init_array_end = .); - } >FLASH - .fini_array : - { - PROVIDE_HIDDEN (__fini_array_start = .); - KEEP (*(SORT(.fini_array.*))) - KEEP (*(.fini_array*)) - PROVIDE_HIDDEN (__fini_array_end = .); - } >FLASH - - /* used by the startup to initialize data */ - _sidata = LOADADDR(.data); - - /* Initialized data sections goes into RAM, load LMA copy after code */ - .data : - { - . = ALIGN(4); - _sdata = .; /* create a global symbol at data start */ - *(.data) /* .data sections */ - *(.data*) /* .data* sections */ - - . = ALIGN(4); - _edata = .; /* define a global symbol at data end */ - } >RAM AT> FLASH - - - /* Uninitialized data section */ - . = ALIGN(4); - .bss : - { - /* This is used by the startup in order to initialize the .bss secion */ - _sbss = .; /* define a global symbol at bss start */ - __bss_start__ = _sbss; - *(.bss) - *(.bss*) - *(COMMON) - - . = ALIGN(4); - _ebss = .; /* define a global symbol at bss end */ - __bss_end__ = _ebss; - } >RAM - - /* User_heap_stack section, used to check that there is enough RAM left */ - ._user_heap_stack : - { - . = ALIGN(8); - PROVIDE ( end = . ); - PROVIDE ( _end = . ); - . = . + _Min_Heap_Size; - . = . + _Min_Stack_Size; - . = ALIGN(8); - } >RAM - - - - /* Remove information from the standard libraries */ - /DISCARD/ : - { - libc.a ( * ) - libm.a ( * ) - libgcc.a ( * ) - } - - .ARM.attributes 0 : { *(.ARM.attributes) } -} - - diff --git a/src/libs/board/stm32f767zi_nucleo/STM32F767ZITx_ITCM_FLASH.ld b/src/libs/board/stm32f767zi_nucleo/STM32F767ZITx_ITCM_FLASH.ld deleted file mode 100644 index 14b27ab..0000000 --- a/src/libs/board/stm32f767zi_nucleo/STM32F767ZITx_ITCM_FLASH.ld +++ /dev/null @@ -1,169 +0,0 @@ -/* -***************************************************************************** -** - -** File : LinkerScript.ld -** -** Abstract : Linker script for STM32F767ZITx Device with -** 2048KByte FLASH, 320KByte RAM -** -** Set heap size, stack size and stack location according -** to application requirements. -** -** Set memory bank area and size if external memory is used. -** -** Target : STMicroelectronics STM32 -** -** -** Distribution: The file is distributed as is, without any warranty -** of any kind. -** -** (c)Copyright Ac6. -** You may use this file as-is or modify it according to the needs of your -** project. Distribution of this file (unmodified or modified) is not -** permitted. Ac6 permit registered System Workbench for MCU users the -** rights to distribute the assembled, compiled & linked contents of this -** file as part of an application binary file, provided that it is built -** using the System Workbench for MCU toolchain. -** -***************************************************************************** -*/ - -/* Entry Point */ -ENTRY(Reset_Handler) - -/* Highest address of the user mode stack */ -_estack = 0x20050000; /* end of RAM */ -/* Generate a link error if heap and stack don't fit into RAM */ -_Min_Heap_Size = 0x200; /* required amount of heap */ -_Min_Stack_Size = 0x400; /* required amount of stack */ - -/* Specify the memory areas */ -MEMORY -{ -FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2048K -RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 320K -} - -/* Define output sections */ -SECTIONS -{ - /* The startup code goes first into FLASH */ - .isr_vector : - { - . = ALIGN(4); - KEEP(*(.isr_vector)) /* Startup code */ - . = ALIGN(4); - } >FLASH - - /* The program code and other data goes into FLASH */ - .text : - { - . = ALIGN(4); - *(.text) /* .text sections (code) */ - *(.text*) /* .text* sections (code) */ - *(.glue_7) /* glue arm to thumb code */ - *(.glue_7t) /* glue thumb to arm code */ - *(.eh_frame) - - KEEP (*(.init)) - KEEP (*(.fini)) - - . = ALIGN(4); - _etext = .; /* define a global symbols at end of code */ - } >FLASH - - /* Constant data goes into FLASH */ - .rodata : - { - . = ALIGN(4); - *(.rodata) /* .rodata sections (constants, strings, etc.) */ - *(.rodata*) /* .rodata* sections (constants, strings, etc.) */ - . = ALIGN(4); - } >FLASH - - .ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >FLASH - .ARM : { - __exidx_start = .; - *(.ARM.exidx*) - __exidx_end = .; - } >FLASH - - .preinit_array : - { - PROVIDE_HIDDEN (__preinit_array_start = .); - KEEP (*(.preinit_array*)) - PROVIDE_HIDDEN (__preinit_array_end = .); - } >FLASH - .init_array : - { - PROVIDE_HIDDEN (__init_array_start = .); - KEEP (*(SORT(.init_array.*))) - KEEP (*(.init_array*)) - PROVIDE_HIDDEN (__init_array_end = .); - } >FLASH - .fini_array : - { - PROVIDE_HIDDEN (__fini_array_start = .); - KEEP (*(SORT(.fini_array.*))) - KEEP (*(.fini_array*)) - PROVIDE_HIDDEN (__fini_array_end = .); - } >FLASH - - /* used by the startup to initialize data */ - _sidata = LOADADDR(.data); - - /* Initialized data sections goes into RAM, load LMA copy after code */ - .data : - { - . = ALIGN(4); - _sdata = .; /* create a global symbol at data start */ - *(.data) /* .data sections */ - *(.data*) /* .data* sections */ - - . = ALIGN(4); - _edata = .; /* define a global symbol at data end */ - } >RAM AT> FLASH - - - /* Uninitialized data section */ - . = ALIGN(4); - .bss : - { - /* This is used by the startup in order to initialize the .bss secion */ - _sbss = .; /* define a global symbol at bss start */ - __bss_start__ = _sbss; - *(.bss) - *(.bss*) - *(COMMON) - - . = ALIGN(4); - _ebss = .; /* define a global symbol at bss end */ - __bss_end__ = _ebss; - } >RAM - - /* User_heap_stack section, used to check that there is enough RAM left */ - ._user_heap_stack : - { - . = ALIGN(8); - PROVIDE ( end = . ); - PROVIDE ( _end = . ); - . = . + _Min_Heap_Size; - . = . + _Min_Stack_Size; - . = ALIGN(8); - } >RAM - - - - /* Remove information from the standard libraries */ - /DISCARD/ : - { - libc.a ( * ) - libm.a ( * ) - libgcc.a ( * ) - } - - .ARM.attributes 0 : { *(.ARM.attributes) } -} - - diff --git a/src/libs/board/stm32f767zi_nucleo/Src/main.c b/src/libs/board/stm32f767zi_nucleo/Src/main.c deleted file mode 100644 index 0b52e75..0000000 --- a/src/libs/board/stm32f767zi_nucleo/Src/main.c +++ /dev/null @@ -1,241 +0,0 @@ -/** - ****************************************************************************** - * @file Templates/Src/main.c - * @author MCD Application Team - * @brief Main program body - ****************************************************************************** - * @attention - * - * Copyright (c) 2016 STMicroelectronics. - * All rights reserved. - * - * This software is licensed under terms that can be found in the LICENSE file - * in the root directory of this software component. - * If no LICENSE file comes with this software, it is provided AS-IS. - * - ****************************************************************************** - */ - -/* Includes ------------------------------------------------------------------*/ -#include "main.h" - -/** @addtogroup STM32F7xx_HAL_Examples - * @{ - */ - -/** @addtogroup Templates - * @{ - */ - -/* Private typedef -----------------------------------------------------------*/ -/* Private define ------------------------------------------------------------*/ -/* Private macro -------------------------------------------------------------*/ -/* Private variables ---------------------------------------------------------*/ -/* Private function prototypes -----------------------------------------------*/ -static void MPU_Config(void); -static void SystemClock_Config(void); -static void Error_Handler(void); -static void CPU_CACHE_Enable(void); - -/* Private functions ---------------------------------------------------------*/ - -/** - * @brief Main program - * @param None - * @retval None - */ -int main(void) -{ - /* This project template calls firstly CPU_CACHE_Enable() in order to enable the CPU Cache. - This function is provided as template implementation that User may integrate - in his application, to enhance the performance in case of use of AXI interface - with several masters. */ - - /* Configure the MPU attributes */ - MPU_Config(); - - /* Enable the CPU Cache */ - CPU_CACHE_Enable(); - - /* STM32F7xx HAL library initialization: - - Configure the Flash ART accelerator on ITCM interface - - Configure the Systick to generate an interrupt each 1 msec - - Set NVIC Group Priority to 4 - - Low Level Initialization - */ - HAL_Init(); - - /* Configure the system clock to 216 MHz */ - SystemClock_Config(); - - - /* Add your application code here */ - - - /* Infinite loop */ - while (1) - { - } -} - -/** - * @brief System Clock Configuration - * The system Clock is configured as follow : - * System Clock source = PLL (HSE) - * SYSCLK(Hz) = 216000000 - * HCLK(Hz) = 216000000 - * AHB Prescaler = 1 - * APB1 Prescaler = 4 - * APB2 Prescaler = 2 - * HSE Frequency(Hz) = 25000000 - * PLL_M = 25 - * PLL_N = 432 - * PLL_P = 2 - * PLL_Q = 9 - * PLL_R = 7 - * VDD(V) = 3.3 - * Main regulator output voltage = Scale1 mode - * Flash Latency(WS) = 7 - * @param None - * @retval None - */ -static void SystemClock_Config(void) -{ - RCC_ClkInitTypeDef RCC_ClkInitStruct; - RCC_OscInitTypeDef RCC_OscInitStruct; - HAL_StatusTypeDef ret = HAL_OK; - - /* Enable Power Control clock */ - __HAL_RCC_PWR_CLK_ENABLE(); - - /* The voltage scaling allows optimizing the power consumption when the device is - clocked below the maximum system frequency, to update the voltage scaling value - regarding system frequency refer to product datasheet. */ - __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1); - - /* Enable HSE Oscillator and activate PLL with HSE as source */ - RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; - RCC_OscInitStruct.HSEState = RCC_HSE_ON; - RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; - RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; - RCC_OscInitStruct.PLL.PLLM = 25; - RCC_OscInitStruct.PLL.PLLN = 432; - RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2; - RCC_OscInitStruct.PLL.PLLQ = 9; - RCC_OscInitStruct.PLL.PLLR = 7; - - ret = HAL_RCC_OscConfig(&RCC_OscInitStruct); - if(ret != HAL_OK) - { - Error_Handler(); - } - - /* Activate the OverDrive to reach the 216 MHz Frequency */ - ret = HAL_PWREx_EnableOverDrive(); - if(ret != HAL_OK) - { - Error_Handler(); - } - - /* Select PLL as system clock source and configure the HCLK, PCLK1 and PCLK2 clocks dividers */ - RCC_ClkInitStruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2); - RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; - RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; - RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4; - RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2; - - ret = HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_7); - if(ret != HAL_OK) - { - Error_Handler(); - } -} - -/** - * @brief This function is executed in case of error occurrence. - * @param None - * @retval None - */ -static void Error_Handler(void) -{ - /* User may add here some code to deal with this error */ - while(1) - { - } -} - -/** - * @brief CPU L1-Cache enable. - * @param None - * @retval None - */ -static void CPU_CACHE_Enable(void) -{ - /* Enable I-Cache */ - SCB_EnableICache(); - - /* Enable D-Cache */ - SCB_EnableDCache(); -} - - -/** - * @brief Configure the MPU attributes - * @param None - * @retval None - */ -static void MPU_Config(void) -{ - MPU_Region_InitTypeDef MPU_InitStruct; - - /* Disable the MPU */ - HAL_MPU_Disable(); - - /* Configure the MPU as Strongly ordered for not defined regions */ - MPU_InitStruct.Enable = MPU_REGION_ENABLE; - MPU_InitStruct.BaseAddress = 0x00; - MPU_InitStruct.Size = MPU_REGION_SIZE_4GB; - MPU_InitStruct.AccessPermission = MPU_REGION_NO_ACCESS; - MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE; - MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE; - MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE; - MPU_InitStruct.Number = MPU_REGION_NUMBER0; - MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0; - MPU_InitStruct.SubRegionDisable = 0x87; - MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE; - - HAL_MPU_ConfigRegion(&MPU_InitStruct); - - /* Enable the MPU */ - HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT); -} - -#ifdef USE_FULL_ASSERT - -/** - * @brief Reports the name of the source file and the source line number - * where the assert_param error has occurred. - * @param file: pointer to the source file name - * @param line: assert_param error line source number - * @retval None - */ -void assert_failed(uint8_t* file, uint32_t line) -{ - /* User can add his own implementation to report the file name and line number, - ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ - - /* Infinite loop */ - while (1) - { - } -} -#endif - -/** - * @} - */ - -/** - * @} - */ - diff --git a/src/libs/board/stm32f767zi_nucleo/Src/stm32f7xx_hal_msp.c b/src/libs/board/stm32f767zi_nucleo/Src/stm32f7xx_hal_msp.c deleted file mode 100644 index 182ca3e..0000000 --- a/src/libs/board/stm32f767zi_nucleo/Src/stm32f7xx_hal_msp.c +++ /dev/null @@ -1,88 +0,0 @@ -/** - ****************************************************************************** - * @file Templates/Src/stm32f7xx_hal_msp.c - * @author MCD Application Team - * @brief HAL MSP module. - ****************************************************************************** - * @attention - * - * Copyright (c) 2016 STMicroelectronics. - * All rights reserved. - * - * This software is licensed under terms that can be found in the LICENSE file - * in the root directory of this software component. - * If no LICENSE file comes with this software, it is provided AS-IS. - * - ****************************************************************************** - */ - -/* Includes ------------------------------------------------------------------*/ -#include "main.h" - -/** @addtogroup STM32F7xx_HAL_Examples - * @{ - */ - -/** @addtogroup Templates - * @{ - */ - -/* Private typedef -----------------------------------------------------------*/ -/* Private define ------------------------------------------------------------*/ -/* Private macro -------------------------------------------------------------*/ -/* Private variables ---------------------------------------------------------*/ -/* Private function prototypes -----------------------------------------------*/ -/* Private functions ---------------------------------------------------------*/ - -/** @defgroup HAL_MSP_Private_Functions - * @{ - */ - -/** - * @brief Initializes the Global MSP. - * @param None - * @retval None - */ -void HAL_MspInit(void) -{ -} - -/** - * @brief DeInitializes the Global MSP. - * @param None - * @retval None - */ -void HAL_MspDeInit(void) -{ -} - -/** - * @brief Initializes the PPP MSP. - * @param None - * @retval None - */ -/*void HAL_PPP_MspInit(void) -{*/ -/*}*/ - -/** - * @brief DeInitializes the PPP MSP. - * @param None - * @retval None - */ -/*void HAL_PPP_MspDeInit(void) -{*/ -/*}*/ - -/** - * @} - */ - -/** - * @} - */ - -/** - * @} - */ - diff --git a/src/libs/board/stm32f767zi_nucleo/Src/stm32f7xx_it.c b/src/libs/board/stm32f767zi_nucleo/Src/stm32f7xx_it.c deleted file mode 100644 index 3c3a760..0000000 --- a/src/libs/board/stm32f767zi_nucleo/Src/stm32f7xx_it.c +++ /dev/null @@ -1,167 +0,0 @@ -/** - ****************************************************************************** - * @file Templates/Src/stm32f7xx.c - * @author MCD Application Team - * @brief Main Interrupt Service Routines. - * This file provides template for all exceptions handler and - * peripherals interrupt service routine. - ****************************************************************************** - * @attention - * - * Copyright (c) 2016 STMicroelectronics. - * All rights reserved. - * - * This software is licensed under terms that can be found in the LICENSE file - * in the root directory of this software component. - * If no LICENSE file comes with this software, it is provided AS-IS. - * - ****************************************************************************** - */ - -/* Includes ------------------------------------------------------------------*/ -#include "main.h" -#include "stm32f7xx_it.h" - -/** @addtogroup STM32F7xx_HAL_Examples - * @{ - */ - -/** @addtogroup Templates - * @{ - */ - -/* Private typedef -----------------------------------------------------------*/ -/* Private define ------------------------------------------------------------*/ -/* Private macro -------------------------------------------------------------*/ -/* Private variables ---------------------------------------------------------*/ - -/* Private function prototypes -----------------------------------------------*/ -/* Private functions ---------------------------------------------------------*/ - -/******************************************************************************/ -/* Cortex-M7 Processor Exceptions Handlers */ -/******************************************************************************/ - -/** - * @brief This function handles NMI exception. - * @param None - * @retval None - */ -void NMI_Handler(void) -{ -} - -/** - * @brief This function handles Hard Fault exception. - * @param None - * @retval None - */ -void HardFault_Handler(void) -{ - /* Go to infinite loop when Hard Fault exception occurs */ - while (1) - { - } -} - -/** - * @brief This function handles Memory Manage exception. - * @param None - * @retval None - */ -void MemManage_Handler(void) -{ - /* Go to infinite loop when Memory Manage exception occurs */ - while (1) - { - } -} - -/** - * @brief This function handles Bus Fault exception. - * @param None - * @retval None - */ -void BusFault_Handler(void) -{ - /* Go to infinite loop when Bus Fault exception occurs */ - while (1) - { - } -} - -/** - * @brief This function handles Usage Fault exception. - * @param None - * @retval None - */ -void UsageFault_Handler(void) -{ - /* Go to infinite loop when Usage Fault exception occurs */ - while (1) - { - } -} - -/** - * @brief This function handles SVCall exception. - * @param None - * @retval None - */ -void SVC_Handler(void) -{ -} - -/** - * @brief This function handles Debug Monitor exception. - * @param None - * @retval None - */ -void DebugMon_Handler(void) -{ -} - -/** - * @brief This function handles PendSVC exception. - * @param None - * @retval None - */ -void PendSV_Handler(void) -{ -} - -/** - * @brief This function handles SysTick Handler. - * @param None - * @retval None - */ -void SysTick_Handler(void) -{ - HAL_IncTick(); -} - -/******************************************************************************/ -/* STM32F7xx Peripherals Interrupt Handlers */ -/* Add here the Interrupt Handler for the used peripheral(s) (PPP), for the */ -/* available peripheral interrupt handler's name please refer to the startup */ -/* file (startup_stm32f7xx.s). */ -/******************************************************************************/ - -/** - * @brief This function handles PPP interrupt request. - * @param None - * @retval None - */ -/*void PPP_IRQHandler(void) -{ -}*/ - - -/** - * @} - */ - -/** - * @} - */ - diff --git a/src/libs/board/stm32f767zi_nucleo/Src/system_stm32f7xx.c b/src/libs/board/stm32f767zi_nucleo/Src/system_stm32f7xx.c deleted file mode 100644 index 5039c21..0000000 --- a/src/libs/board/stm32f767zi_nucleo/Src/system_stm32f7xx.c +++ /dev/null @@ -1,260 +0,0 @@ -/** - ****************************************************************************** - * @file system_stm32f7xx.c - * @author MCD Application Team - * @brief CMSIS Cortex-M7 Device Peripheral Access Layer System Source File. - * - * This file provides two functions and one global variable to be called from - * user application: - * - SystemInit(): This function is called at startup just after reset and - * before branch to main program. This call is made inside - * the "startup_stm32f7xx.s" file. - * - * - SystemCoreClock variable: Contains the core clock (HCLK), it can be used - * by the user application to setup the SysTick - * timer or configure other parameters. - * - * - SystemCoreClockUpdate(): Updates the variable SystemCoreClock and must - * be called whenever the core clock is changed - * during program execution. - * - * - ****************************************************************************** - * @attention - * - * Copyright (c) 2016 STMicroelectronics. - * All rights reserved. - * - * This software is licensed under terms that can be found in the LICENSE file - * in the root directory of this software component. - * If no LICENSE file comes with this software, it is provided AS-IS. - * - ****************************************************************************** - */ - -/** @addtogroup CMSIS - * @{ - */ - -/** @addtogroup stm32f7xx_system - * @{ - */ - -/** @addtogroup STM32F7xx_System_Private_Includes - * @{ - */ - -#include "stm32f7xx.h" - -#if !defined (HSE_VALUE) - #define HSE_VALUE ((uint32_t)25000000) /*!< Default value of the External oscillator in Hz */ -#endif /* HSE_VALUE */ - -#if !defined (HSI_VALUE) - #define HSI_VALUE ((uint32_t)16000000) /*!< Value of the Internal oscillator in Hz*/ -#endif /* HSI_VALUE */ - -/** - * @} - */ - -/** @addtogroup STM32F7xx_System_Private_TypesDefinitions - * @{ - */ - -/** - * @} - */ - -/** @addtogroup STM32F7xx_System_Private_Defines - * @{ - */ - -/************************* Miscellaneous Configuration ************************/ - -/*!< Uncomment the following line if you need to relocate your vector Table in - Internal SRAM. */ -/* #define VECT_TAB_SRAM */ -#define VECT_TAB_OFFSET 0x00 /*!< Vector Table base offset field. - This value must be a multiple of 0x200. */ -/******************************************************************************/ - -/** - * @} - */ - -/** @addtogroup STM32F7xx_System_Private_Macros - * @{ - */ - -/** - * @} - */ - -/** @addtogroup STM32F7xx_System_Private_Variables - * @{ - */ - - /* This variable is updated in three ways: - 1) by calling CMSIS function SystemCoreClockUpdate() - 2) by calling HAL API function HAL_RCC_GetHCLKFreq() - 3) each time HAL_RCC_ClockConfig() is called to configure the system clock frequency - Note: If you use this function to configure the system clock; then there - is no need to call the 2 first functions listed above, since SystemCoreClock - variable is updated automatically. - */ - uint32_t SystemCoreClock = 16000000; - const uint8_t AHBPrescTable[16] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 6, 7, 8, 9}; - const uint8_t APBPrescTable[8] = {0, 0, 0, 0, 1, 2, 3, 4}; - -/** - * @} - */ - -/** @addtogroup STM32F7xx_System_Private_FunctionPrototypes - * @{ - */ - -/** - * @} - */ - -/** @addtogroup STM32F7xx_System_Private_Functions - * @{ - */ - -/** - * @brief Setup the microcontroller system - * Initialize the Embedded Flash Interface, the PLL and update the - * SystemFrequency variable. - * @param None - * @retval None - */ -void SystemInit(void) -{ - /* FPU settings ------------------------------------------------------------*/ - #if (__FPU_PRESENT == 1) && (__FPU_USED == 1) - SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2)); /* set CP10 and CP11 Full Access */ - #endif - /* Reset the RCC clock configuration to the default reset state ------------*/ - /* Set HSION bit */ - RCC->CR |= (uint32_t)0x00000001; - - /* Reset CFGR register */ - RCC->CFGR = 0x00000000; - - /* Reset HSEON, CSSON and PLLON bits */ - RCC->CR &= (uint32_t)0xFEF6FFFF; - - /* Reset PLLCFGR register */ - RCC->PLLCFGR = 0x24003010; - - /* Reset HSEBYP bit */ - RCC->CR &= (uint32_t)0xFFFBFFFF; - - /* Disable all interrupts */ - RCC->CIR = 0x00000000; - - /* Configure the Vector Table location add offset address ------------------*/ -#ifdef VECT_TAB_SRAM - SCB->VTOR = RAMDTCM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */ -#else - SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */ -#endif -} - -/** - * @brief Update SystemCoreClock variable according to Clock Register Values. - * The SystemCoreClock variable contains the core clock (HCLK), it can - * be used by the user application to setup the SysTick timer or configure - * other parameters. - * - * @note Each time the core clock (HCLK) changes, this function must be called - * to update SystemCoreClock variable value. Otherwise, any configuration - * based on this variable will be incorrect. - * - * @note - The system frequency computed by this function is not the real - * frequency in the chip. It is calculated based on the predefined - * constant and the selected clock source: - * - * - If SYSCLK source is HSI, SystemCoreClock will contain the HSI_VALUE(*) - * - * - If SYSCLK source is HSE, SystemCoreClock will contain the HSE_VALUE(**) - * - * - If SYSCLK source is PLL, SystemCoreClock will contain the HSE_VALUE(**) - * or HSI_VALUE(*) multiplied/divided by the PLL factors. - * - * (*) HSI_VALUE is a constant defined in stm32f7xx_hal_conf.h file (default value - * 16 MHz) but the real value may vary depending on the variations - * in voltage and temperature. - * - * (**) HSE_VALUE is a constant defined in stm32f7xx_hal_conf.h file (default value - * 25 MHz), user has to ensure that HSE_VALUE is same as the real - * frequency of the crystal used. Otherwise, this function may - * have wrong result. - * - * - The result of this function could be not correct when using fractional - * value for HSE crystal. - * - * @param None - * @retval None - */ -void SystemCoreClockUpdate(void) -{ - uint32_t tmp = 0, pllvco = 0, pllp = 2, pllsource = 0, pllm = 2; - - /* Get SYSCLK source -------------------------------------------------------*/ - tmp = RCC->CFGR & RCC_CFGR_SWS; - - switch (tmp) - { - case 0x00: /* HSI used as system clock source */ - SystemCoreClock = HSI_VALUE; - break; - case 0x04: /* HSE used as system clock source */ - SystemCoreClock = HSE_VALUE; - break; - case 0x08: /* PLL used as system clock source */ - - /* PLL_VCO = (HSE_VALUE or HSI_VALUE / PLL_M) * PLL_N - SYSCLK = PLL_VCO / PLL_P - */ - pllsource = (RCC->PLLCFGR & RCC_PLLCFGR_PLLSRC) >> 22; - pllm = RCC->PLLCFGR & RCC_PLLCFGR_PLLM; - - if (pllsource != 0) - { - /* HSE used as PLL clock source */ - pllvco = (HSE_VALUE / pllm) * ((RCC->PLLCFGR & RCC_PLLCFGR_PLLN) >> 6); - } - else - { - /* HSI used as PLL clock source */ - pllvco = (HSI_VALUE / pllm) * ((RCC->PLLCFGR & RCC_PLLCFGR_PLLN) >> 6); - } - - pllp = (((RCC->PLLCFGR & RCC_PLLCFGR_PLLP) >>16) + 1 ) *2; - SystemCoreClock = pllvco/pllp; - break; - default: - SystemCoreClock = HSI_VALUE; - break; - } - /* Compute HCLK frequency --------------------------------------------------*/ - /* Get HCLK prescaler */ - tmp = AHBPrescTable[((RCC->CFGR & RCC_CFGR_HPRE) >> 4)]; - /* HCLK frequency */ - SystemCoreClock >>= tmp; -} - -/** - * @} - */ - -/** - * @} - */ - -/** - * @} - */ diff --git a/src/libs/board/stm32f767zi_nucleo/startup_stm32f767xx.s b/src/libs/board/stm32f767zi_nucleo/startup_stm32f767xx.s deleted file mode 100644 index e89aebc..0000000 --- a/src/libs/board/stm32f767zi_nucleo/startup_stm32f767xx.s +++ /dev/null @@ -1,613 +0,0 @@ -/** - ****************************************************************************** - * @file startup_stm32f767xx.s - * @author MCD Application Team - * @brief STM32F767xx Devices vector table for GCC based toolchain. - * This module performs: - * - Set the initial SP - * - Set the initial PC == Reset_Handler, - * - Set the vector table entries with the exceptions ISR address - * - Branches to main in the C library (which eventually - * calls main()). - * After Reset the Cortex-M7 processor is in Thread mode, - * priority is Privileged, and the Stack is set to Main. - ****************************************************************************** - * @attention - * - * Copyright (c) 2016 STMicroelectronics. - * All rights reserved. - * - * This software is licensed under terms that can be found in the LICENSE file - * in the root directory of this software component. - * If no LICENSE file comes with this software, it is provided AS-IS. - * - ****************************************************************************** - */ - - .syntax unified - .cpu cortex-m7 - .fpu softvfp - .thumb - -.global g_pfnVectors -.global Default_Handler - -/* start address for the initialization values of the .data section. -defined in linker script */ -.word _sidata -/* start address for the .data section. defined in linker script */ -.word _sdata -/* end address for the .data section. defined in linker script */ -.word _edata -/* start address for the .bss section. defined in linker script */ -.word _sbss -/* end address for the .bss section. defined in linker script */ -.word _ebss -/* stack used for SystemInit_ExtMemCtl; always internal RAM used */ - -/** - * @brief This is the code that gets called when the processor first - * starts execution following a reset event. Only the absolutely - * necessary set is performed, after which the application - * supplied main() routine is called. - * @param None - * @retval : None -*/ - - .section .text.Reset_Handler - .weak Reset_Handler - .type Reset_Handler, %function -Reset_Handler: - ldr sp, =_estack /* set stack pointer */ - -/* Copy the data segment initializers from flash to SRAM */ - movs r1, #0 - b LoopCopyDataInit - -CopyDataInit: - ldr r3, =_sidata - ldr r3, [r3, r1] - str r3, [r0, r1] - adds r1, r1, #4 - -LoopCopyDataInit: - ldr r0, =_sdata - ldr r3, =_edata - adds r2, r0, r1 - cmp r2, r3 - bcc CopyDataInit - ldr r2, =_sbss - b LoopFillZerobss -/* Zero fill the bss segment. */ -FillZerobss: - movs r3, #0 - str r3, [r2], #4 - -LoopFillZerobss: - ldr r3, = _ebss - cmp r2, r3 - bcc FillZerobss - -/* Call the clock system initialization function.*/ - bl SystemInit -/* Call static constructors */ - bl __libc_init_array -/* Call the application's entry point.*/ - bl main - bx lr -.size Reset_Handler, .-Reset_Handler - -/** - * @brief This is the code that gets called when the processor receives an - * unexpected interrupt. This simply enters an infinite loop, preserving - * the system state for examination by a debugger. - * @param None - * @retval None -*/ - .section .text.Default_Handler,"ax",%progbits -Default_Handler: -Infinite_Loop: - b Infinite_Loop - .size Default_Handler, .-Default_Handler -/****************************************************************************** -* -* The minimal vector table for a Cortex M7. Note that the proper constructs -* must be placed on this to ensure that it ends up at physical address -* 0x0000.0000. -* -*******************************************************************************/ - .section .isr_vector,"a",%progbits - .type g_pfnVectors, %object - .size g_pfnVectors, .-g_pfnVectors - - -g_pfnVectors: - .word _estack - .word Reset_Handler - - .word NMI_Handler - .word HardFault_Handler - .word MemManage_Handler - .word BusFault_Handler - .word UsageFault_Handler - .word 0 - .word 0 - .word 0 - .word 0 - .word SVC_Handler - .word DebugMon_Handler - .word 0 - .word PendSV_Handler - .word SysTick_Handler - - /* External Interrupts */ - .word WWDG_IRQHandler /* Window WatchDog */ - .word PVD_IRQHandler /* PVD through EXTI Line detection */ - .word TAMP_STAMP_IRQHandler /* Tamper and TimeStamps through the EXTI line */ - .word RTC_WKUP_IRQHandler /* RTC Wakeup through the EXTI line */ - .word FLASH_IRQHandler /* FLASH */ - .word RCC_IRQHandler /* RCC */ - .word EXTI0_IRQHandler /* EXTI Line0 */ - .word EXTI1_IRQHandler /* EXTI Line1 */ - .word EXTI2_IRQHandler /* EXTI Line2 */ - .word EXTI3_IRQHandler /* EXTI Line3 */ - .word EXTI4_IRQHandler /* EXTI Line4 */ - .word DMA1_Stream0_IRQHandler /* DMA1 Stream 0 */ - .word DMA1_Stream1_IRQHandler /* DMA1 Stream 1 */ - .word DMA1_Stream2_IRQHandler /* DMA1 Stream 2 */ - .word DMA1_Stream3_IRQHandler /* DMA1 Stream 3 */ - .word DMA1_Stream4_IRQHandler /* DMA1 Stream 4 */ - .word DMA1_Stream5_IRQHandler /* DMA1 Stream 5 */ - .word DMA1_Stream6_IRQHandler /* DMA1 Stream 6 */ - .word ADC_IRQHandler /* ADC1, ADC2 and ADC3s */ - .word CAN1_TX_IRQHandler /* CAN1 TX */ - .word CAN1_RX0_IRQHandler /* CAN1 RX0 */ - .word CAN1_RX1_IRQHandler /* CAN1 RX1 */ - .word CAN1_SCE_IRQHandler /* CAN1 SCE */ - .word EXTI9_5_IRQHandler /* External Line[9:5]s */ - .word TIM1_BRK_TIM9_IRQHandler /* TIM1 Break and TIM9 */ - .word TIM1_UP_TIM10_IRQHandler /* TIM1 Update and TIM10 */ - .word TIM1_TRG_COM_TIM11_IRQHandler /* TIM1 Trigger and Commutation and TIM11 */ - .word TIM1_CC_IRQHandler /* TIM1 Capture Compare */ - .word TIM2_IRQHandler /* TIM2 */ - .word TIM3_IRQHandler /* TIM3 */ - .word TIM4_IRQHandler /* TIM4 */ - .word I2C1_EV_IRQHandler /* I2C1 Event */ - .word I2C1_ER_IRQHandler /* I2C1 Error */ - .word I2C2_EV_IRQHandler /* I2C2 Event */ - .word I2C2_ER_IRQHandler /* I2C2 Error */ - .word SPI1_IRQHandler /* SPI1 */ - .word SPI2_IRQHandler /* SPI2 */ - .word USART1_IRQHandler /* USART1 */ - .word USART2_IRQHandler /* USART2 */ - .word USART3_IRQHandler /* USART3 */ - .word EXTI15_10_IRQHandler /* External Line[15:10]s */ - .word RTC_Alarm_IRQHandler /* RTC Alarm (A and B) through EXTI Line */ - .word OTG_FS_WKUP_IRQHandler /* USB OTG FS Wakeup through EXTI line */ - .word TIM8_BRK_TIM12_IRQHandler /* TIM8 Break and TIM12 */ - .word TIM8_UP_TIM13_IRQHandler /* TIM8 Update and TIM13 */ - .word TIM8_TRG_COM_TIM14_IRQHandler /* TIM8 Trigger and Commutation and TIM14 */ - .word TIM8_CC_IRQHandler /* TIM8 Capture Compare */ - .word DMA1_Stream7_IRQHandler /* DMA1 Stream7 */ - .word FMC_IRQHandler /* FMC */ - .word SDMMC1_IRQHandler /* SDMMC1 */ - .word TIM5_IRQHandler /* TIM5 */ - .word SPI3_IRQHandler /* SPI3 */ - .word UART4_IRQHandler /* UART4 */ - .word UART5_IRQHandler /* UART5 */ - .word TIM6_DAC_IRQHandler /* TIM6 and DAC1&2 underrun errors */ - .word TIM7_IRQHandler /* TIM7 */ - .word DMA2_Stream0_IRQHandler /* DMA2 Stream 0 */ - .word DMA2_Stream1_IRQHandler /* DMA2 Stream 1 */ - .word DMA2_Stream2_IRQHandler /* DMA2 Stream 2 */ - .word DMA2_Stream3_IRQHandler /* DMA2 Stream 3 */ - .word DMA2_Stream4_IRQHandler /* DMA2 Stream 4 */ - .word ETH_IRQHandler /* Ethernet */ - .word ETH_WKUP_IRQHandler /* Ethernet Wakeup through EXTI line */ - .word CAN2_TX_IRQHandler /* CAN2 TX */ - .word CAN2_RX0_IRQHandler /* CAN2 RX0 */ - .word CAN2_RX1_IRQHandler /* CAN2 RX1 */ - .word CAN2_SCE_IRQHandler /* CAN2 SCE */ - .word OTG_FS_IRQHandler /* USB OTG FS */ - .word DMA2_Stream5_IRQHandler /* DMA2 Stream 5 */ - .word DMA2_Stream6_IRQHandler /* DMA2 Stream 6 */ - .word DMA2_Stream7_IRQHandler /* DMA2 Stream 7 */ - .word USART6_IRQHandler /* USART6 */ - .word I2C3_EV_IRQHandler /* I2C3 event */ - .word I2C3_ER_IRQHandler /* I2C3 error */ - .word OTG_HS_EP1_OUT_IRQHandler /* USB OTG HS End Point 1 Out */ - .word OTG_HS_EP1_IN_IRQHandler /* USB OTG HS End Point 1 In */ - .word OTG_HS_WKUP_IRQHandler /* USB OTG HS Wakeup through EXTI */ - .word OTG_HS_IRQHandler /* USB OTG HS */ - .word DCMI_IRQHandler /* DCMI */ - .word 0 /* Reserved */ - .word RNG_IRQHandler /* RNG */ - .word FPU_IRQHandler /* FPU */ - .word UART7_IRQHandler /* UART7 */ - .word UART8_IRQHandler /* UART8 */ - .word SPI4_IRQHandler /* SPI4 */ - .word SPI5_IRQHandler /* SPI5 */ - .word SPI6_IRQHandler /* SPI6 */ - .word SAI1_IRQHandler /* SAI1 */ - .word LTDC_IRQHandler /* LTDC */ - .word LTDC_ER_IRQHandler /* LTDC error */ - .word DMA2D_IRQHandler /* DMA2D */ - .word SAI2_IRQHandler /* SAI2 */ - .word QUADSPI_IRQHandler /* QUADSPI */ - .word LPTIM1_IRQHandler /* LPTIM1 */ - .word CEC_IRQHandler /* HDMI_CEC */ - .word I2C4_EV_IRQHandler /* I2C4 Event */ - .word I2C4_ER_IRQHandler /* I2C4 Error */ - .word SPDIF_RX_IRQHandler /* SPDIF_RX */ - .word 0 /* Reserved */ - .word DFSDM1_FLT0_IRQHandler /* DFSDM1 Filter 0 global Interrupt */ - .word DFSDM1_FLT1_IRQHandler /* DFSDM1 Filter 1 global Interrupt */ - .word DFSDM1_FLT2_IRQHandler /* DFSDM1 Filter 2 global Interrupt */ - .word DFSDM1_FLT3_IRQHandler /* DFSDM1 Filter 3 global Interrupt */ - .word SDMMC2_IRQHandler /* SDMMC2 */ - .word CAN3_TX_IRQHandler /* CAN3 TX */ - .word CAN3_RX0_IRQHandler /* CAN3 RX0 */ - .word CAN3_RX1_IRQHandler /* CAN3 RX1 */ - .word CAN3_SCE_IRQHandler /* CAN3 SCE */ - .word JPEG_IRQHandler /* JPEG */ - .word MDIOS_IRQHandler /* MDIOS */ - -/******************************************************************************* -* -* Provide weak aliases for each Exception handler to the Default_Handler. -* As they are weak aliases, any function with the same name will override -* this definition. -* -*******************************************************************************/ - .weak NMI_Handler - .thumb_set NMI_Handler,Default_Handler - - .weak HardFault_Handler - .thumb_set HardFault_Handler,Default_Handler - - .weak MemManage_Handler - .thumb_set MemManage_Handler,Default_Handler - - .weak BusFault_Handler - .thumb_set BusFault_Handler,Default_Handler - - .weak UsageFault_Handler - .thumb_set UsageFault_Handler,Default_Handler - - .weak SVC_Handler - .thumb_set SVC_Handler,Default_Handler - - .weak DebugMon_Handler - .thumb_set DebugMon_Handler,Default_Handler - - .weak PendSV_Handler - .thumb_set PendSV_Handler,Default_Handler - - .weak SysTick_Handler - .thumb_set SysTick_Handler,Default_Handler - - .weak WWDG_IRQHandler - .thumb_set WWDG_IRQHandler,Default_Handler - - .weak PVD_IRQHandler - .thumb_set PVD_IRQHandler,Default_Handler - - .weak TAMP_STAMP_IRQHandler - .thumb_set TAMP_STAMP_IRQHandler,Default_Handler - - .weak RTC_WKUP_IRQHandler - .thumb_set RTC_WKUP_IRQHandler,Default_Handler - - .weak FLASH_IRQHandler - .thumb_set FLASH_IRQHandler,Default_Handler - - .weak RCC_IRQHandler - .thumb_set RCC_IRQHandler,Default_Handler - - .weak EXTI0_IRQHandler - .thumb_set EXTI0_IRQHandler,Default_Handler - - .weak EXTI1_IRQHandler - .thumb_set EXTI1_IRQHandler,Default_Handler - - .weak EXTI2_IRQHandler - .thumb_set EXTI2_IRQHandler,Default_Handler - - .weak EXTI3_IRQHandler - .thumb_set EXTI3_IRQHandler,Default_Handler - - .weak EXTI4_IRQHandler - .thumb_set EXTI4_IRQHandler,Default_Handler - - .weak DMA1_Stream0_IRQHandler - .thumb_set DMA1_Stream0_IRQHandler,Default_Handler - - .weak DMA1_Stream1_IRQHandler - .thumb_set DMA1_Stream1_IRQHandler,Default_Handler - - .weak DMA1_Stream2_IRQHandler - .thumb_set DMA1_Stream2_IRQHandler,Default_Handler - - .weak DMA1_Stream3_IRQHandler - .thumb_set DMA1_Stream3_IRQHandler,Default_Handler - - .weak DMA1_Stream4_IRQHandler - .thumb_set DMA1_Stream4_IRQHandler,Default_Handler - - .weak DMA1_Stream5_IRQHandler - .thumb_set DMA1_Stream5_IRQHandler,Default_Handler - - .weak DMA1_Stream6_IRQHandler - .thumb_set DMA1_Stream6_IRQHandler,Default_Handler - - .weak ADC_IRQHandler - .thumb_set ADC_IRQHandler,Default_Handler - - .weak CAN1_TX_IRQHandler - .thumb_set CAN1_TX_IRQHandler,Default_Handler - - .weak CAN1_RX0_IRQHandler - .thumb_set CAN1_RX0_IRQHandler,Default_Handler - - .weak CAN1_RX1_IRQHandler - .thumb_set CAN1_RX1_IRQHandler,Default_Handler - - .weak CAN1_SCE_IRQHandler - .thumb_set CAN1_SCE_IRQHandler,Default_Handler - - .weak EXTI9_5_IRQHandler - .thumb_set EXTI9_5_IRQHandler,Default_Handler - - .weak TIM1_BRK_TIM9_IRQHandler - .thumb_set TIM1_BRK_TIM9_IRQHandler,Default_Handler - - .weak TIM1_UP_TIM10_IRQHandler - .thumb_set TIM1_UP_TIM10_IRQHandler,Default_Handler - - .weak TIM1_TRG_COM_TIM11_IRQHandler - .thumb_set TIM1_TRG_COM_TIM11_IRQHandler,Default_Handler - - .weak TIM1_CC_IRQHandler - .thumb_set TIM1_CC_IRQHandler,Default_Handler - - .weak TIM2_IRQHandler - .thumb_set TIM2_IRQHandler,Default_Handler - - .weak TIM3_IRQHandler - .thumb_set TIM3_IRQHandler,Default_Handler - - .weak TIM4_IRQHandler - .thumb_set TIM4_IRQHandler,Default_Handler - - .weak I2C1_EV_IRQHandler - .thumb_set I2C1_EV_IRQHandler,Default_Handler - - .weak I2C1_ER_IRQHandler - .thumb_set I2C1_ER_IRQHandler,Default_Handler - - .weak I2C2_EV_IRQHandler - .thumb_set I2C2_EV_IRQHandler,Default_Handler - - .weak I2C2_ER_IRQHandler - .thumb_set I2C2_ER_IRQHandler,Default_Handler - - .weak SPI1_IRQHandler - .thumb_set SPI1_IRQHandler,Default_Handler - - .weak SPI2_IRQHandler - .thumb_set SPI2_IRQHandler,Default_Handler - - .weak USART1_IRQHandler - .thumb_set USART1_IRQHandler,Default_Handler - - .weak USART2_IRQHandler - .thumb_set USART2_IRQHandler,Default_Handler - - .weak USART3_IRQHandler - .thumb_set USART3_IRQHandler,Default_Handler - - .weak EXTI15_10_IRQHandler - .thumb_set EXTI15_10_IRQHandler,Default_Handler - - .weak RTC_Alarm_IRQHandler - .thumb_set RTC_Alarm_IRQHandler,Default_Handler - - .weak OTG_FS_WKUP_IRQHandler - .thumb_set OTG_FS_WKUP_IRQHandler,Default_Handler - - .weak TIM8_BRK_TIM12_IRQHandler - .thumb_set TIM8_BRK_TIM12_IRQHandler,Default_Handler - - .weak TIM8_UP_TIM13_IRQHandler - .thumb_set TIM8_UP_TIM13_IRQHandler,Default_Handler - - .weak TIM8_TRG_COM_TIM14_IRQHandler - .thumb_set TIM8_TRG_COM_TIM14_IRQHandler,Default_Handler - - .weak TIM8_CC_IRQHandler - .thumb_set TIM8_CC_IRQHandler,Default_Handler - - .weak DMA1_Stream7_IRQHandler - .thumb_set DMA1_Stream7_IRQHandler,Default_Handler - - .weak FMC_IRQHandler - .thumb_set FMC_IRQHandler,Default_Handler - - .weak SDMMC1_IRQHandler - .thumb_set SDMMC1_IRQHandler,Default_Handler - - .weak TIM5_IRQHandler - .thumb_set TIM5_IRQHandler,Default_Handler - - .weak SPI3_IRQHandler - .thumb_set SPI3_IRQHandler,Default_Handler - - .weak UART4_IRQHandler - .thumb_set UART4_IRQHandler,Default_Handler - - .weak UART5_IRQHandler - .thumb_set UART5_IRQHandler,Default_Handler - - .weak TIM6_DAC_IRQHandler - .thumb_set TIM6_DAC_IRQHandler,Default_Handler - - .weak TIM7_IRQHandler - .thumb_set TIM7_IRQHandler,Default_Handler - - .weak DMA2_Stream0_IRQHandler - .thumb_set DMA2_Stream0_IRQHandler,Default_Handler - - .weak DMA2_Stream1_IRQHandler - .thumb_set DMA2_Stream1_IRQHandler,Default_Handler - - .weak DMA2_Stream2_IRQHandler - .thumb_set DMA2_Stream2_IRQHandler,Default_Handler - - .weak DMA2_Stream3_IRQHandler - .thumb_set DMA2_Stream3_IRQHandler,Default_Handler - - .weak DMA2_Stream4_IRQHandler - .thumb_set DMA2_Stream4_IRQHandler,Default_Handler - - .weak ETH_IRQHandler - .thumb_set ETH_IRQHandler,Default_Handler - - .weak ETH_WKUP_IRQHandler - .thumb_set ETH_WKUP_IRQHandler,Default_Handler - - .weak CAN2_TX_IRQHandler - .thumb_set CAN2_TX_IRQHandler,Default_Handler - - .weak CAN2_RX0_IRQHandler - .thumb_set CAN2_RX0_IRQHandler,Default_Handler - - .weak CAN2_RX1_IRQHandler - .thumb_set CAN2_RX1_IRQHandler,Default_Handler - - .weak CAN2_SCE_IRQHandler - .thumb_set CAN2_SCE_IRQHandler,Default_Handler - - .weak OTG_FS_IRQHandler - .thumb_set OTG_FS_IRQHandler,Default_Handler - - .weak DMA2_Stream5_IRQHandler - .thumb_set DMA2_Stream5_IRQHandler,Default_Handler - - .weak DMA2_Stream6_IRQHandler - .thumb_set DMA2_Stream6_IRQHandler,Default_Handler - - .weak DMA2_Stream7_IRQHandler - .thumb_set DMA2_Stream7_IRQHandler,Default_Handler - - .weak USART6_IRQHandler - .thumb_set USART6_IRQHandler,Default_Handler - - .weak I2C3_EV_IRQHandler - .thumb_set I2C3_EV_IRQHandler,Default_Handler - - .weak I2C3_ER_IRQHandler - .thumb_set I2C3_ER_IRQHandler,Default_Handler - - .weak OTG_HS_EP1_OUT_IRQHandler - .thumb_set OTG_HS_EP1_OUT_IRQHandler,Default_Handler - - .weak OTG_HS_EP1_IN_IRQHandler - .thumb_set OTG_HS_EP1_IN_IRQHandler,Default_Handler - - .weak OTG_HS_WKUP_IRQHandler - .thumb_set OTG_HS_WKUP_IRQHandler,Default_Handler - - .weak OTG_HS_IRQHandler - .thumb_set OTG_HS_IRQHandler,Default_Handler - - .weak DCMI_IRQHandler - .thumb_set DCMI_IRQHandler,Default_Handler - - .weak RNG_IRQHandler - .thumb_set RNG_IRQHandler,Default_Handler - - .weak FPU_IRQHandler - .thumb_set FPU_IRQHandler,Default_Handler - - .weak UART7_IRQHandler - .thumb_set UART7_IRQHandler,Default_Handler - - .weak UART8_IRQHandler - .thumb_set UART8_IRQHandler,Default_Handler - - .weak SPI4_IRQHandler - .thumb_set SPI4_IRQHandler,Default_Handler - - .weak SPI5_IRQHandler - .thumb_set SPI5_IRQHandler,Default_Handler - - .weak SPI6_IRQHandler - .thumb_set SPI6_IRQHandler,Default_Handler - - .weak SAI1_IRQHandler - .thumb_set SAI1_IRQHandler,Default_Handler - - .weak LTDC_IRQHandler - .thumb_set LTDC_IRQHandler,Default_Handler - - .weak LTDC_ER_IRQHandler - .thumb_set LTDC_ER_IRQHandler,Default_Handler - - .weak DMA2D_IRQHandler - .thumb_set DMA2D_IRQHandler,Default_Handler - - .weak SAI2_IRQHandler - .thumb_set SAI2_IRQHandler,Default_Handler - - .weak QUADSPI_IRQHandler - .thumb_set QUADSPI_IRQHandler,Default_Handler - - .weak LPTIM1_IRQHandler - .thumb_set LPTIM1_IRQHandler,Default_Handler - - .weak CEC_IRQHandler - .thumb_set CEC_IRQHandler,Default_Handler - - .weak I2C4_EV_IRQHandler - .thumb_set I2C4_EV_IRQHandler,Default_Handler - - .weak I2C4_ER_IRQHandler - .thumb_set I2C4_ER_IRQHandler,Default_Handler - - .weak SPDIF_RX_IRQHandler - .thumb_set SPDIF_RX_IRQHandler,Default_Handler - - .weak DFSDM1_FLT0_IRQHandler - .thumb_set DFSDM1_FLT0_IRQHandler,Default_Handler - - .weak DFSDM1_FLT1_IRQHandler - .thumb_set DFSDM1_FLT1_IRQHandler,Default_Handler - - .weak DFSDM1_FLT2_IRQHandler - .thumb_set DFSDM1_FLT2_IRQHandler,Default_Handler - - .weak DFSDM1_FLT3_IRQHandler - .thumb_set DFSDM1_FLT3_IRQHandler,Default_Handler - - .weak SDMMC2_IRQHandler - .thumb_set SDMMC2_IRQHandler,Default_Handler - - .weak CAN3_TX_IRQHandler - .thumb_set CAN3_TX_IRQHandler,Default_Handler - - .weak CAN3_RX0_IRQHandler - .thumb_set CAN3_RX0_IRQHandler,Default_Handler - - .weak CAN3_RX1_IRQHandler - .thumb_set CAN3_RX1_IRQHandler,Default_Handler - - .weak CAN3_SCE_IRQHandler - .thumb_set CAN3_SCE_IRQHandler,Default_Handler - - .weak JPEG_IRQHandler - .thumb_set JPEG_IRQHandler,Default_Handler - - .weak MDIOS_IRQHandler - .thumb_set MDIOS_IRQHandler,Default_Handler - - diff --git a/src/libs/mcu/CMakeLists.txt b/src/libs/mcu/CMakeLists.txt index 67b2037..d28c00d 100644 --- a/src/libs/mcu/CMakeLists.txt +++ b/src/libs/mcu/CMakeLists.txt @@ -1,5 +1,10 @@ -add_library(mcu INTERFACE pin.hpp i2c.hpp delay.hpp) +add_library(mcu INTERFACE pin.hpp uart.hpp i2c.hpp delay.hpp) target_compile_options(mcu INTERFACE ${COMMON_COMPILE_OPTIONS}) target_link_libraries(mcu INTERFACE error) +if(NOT IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${EMBEDDED_CPP_MCU}") + message(FATAL_ERROR + "MCU backend '${EMBEDDED_CPP_MCU}' is not implemented yet. " + "Available: host. Hardware backends will be added later; see CLAUDE.md.") +endif() add_subdirectory(${EMBEDDED_CPP_MCU}) From e124d6d3fe268f24aa2c40100d9770e8299a5fd4 Mon Sep 17 00:00:00 2001 From: Nehal Patel Date: Sat, 29 Aug 2026 15:45:22 +0000 Subject: [PATCH 07/17] refactor(build): move to target-based usage requirements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the three ambient mechanisms the build rested on with explicit, target-scoped ones: - COMMON_COMPILE_OPTIONS (pasted into 15 target_compile_options calls) and the directory-scoped include_directories(src) become two INTERFACE targets: project_warnings (warning policy, with generator expressions for the compiler-conditional parts) and project_options (src/ include root, -fno-rtti, links project_warnings). Every target now names its dependency instead of inheriting directory state. - Gating on the invented CMAKE_PRESET name string is gone. EMBEDDED_CPP_MCU and EMBEDDED_CPP_BOARD are declared cache variables defaulting to host, host-only dependencies key off the MCU backend, and tests key off BUILD_TESTING — so a plain 'cmake -B build -DCMAKE_TOOLCHAIN_FILE=cmake/toolchain/host-clang.cmake' configures and builds with no preset at all, and inheriting user presets no longer need to restate anything. - The global CMAKE_CXX_FLAGS append of -stdlib=libc++ moves to the host toolchain file as CMAKE_CXX_FLAGS_INIT, the supported home for a global ABI decision (it must reach FetchContent-built dependencies too, which no target property can). Also, in the same spirit: - include(CTest) sits right after project(), so no add_test can be silently discarded; py/ integration tests are added only for host - interface libraries declare their headers via FILE_SET HEADERS (which carries the include root as a usage requirement and readies install rules); the inert LINKER_LANGUAGE properties on them are gone - the venv for the Python tests is now a CTest setup fixture running 'uv sync --frozen' (a no-op when already synced) instead of an execute_process at configure time, so configure is fast and offline; cmake/python_venv.cmake is folded into py/host-emulator/CMakeLists.txt - integration tests locate apps via $ instead of hand-building output paths - FetchContent: the deprecated GetProperties/POPULATED dance is a plain MakeAvailable; the misused option(CPPZMQ_BUILD_TESTS) is a proper cache set; the duplicate googletest MakeAvailable and the hand-rolled deprecated GTest::GTest alias are gone (tests link GTest::gtest_main); GIT_TAGs pin commit hashes with the tag name in a comment; ETL is dropped entirely — nothing in src/ used it - sys (the entry point) is an OBJECT library so main.o links into each app directly instead of relying on archive symbol-resolution order - format.cmake uses PROJECT_SOURCE_DIR throughout - the root CMakeLists.txt is organized into labeled sections (setup / dependencies / tooling / targets) Existing build trees need a fresh configure (rm -rf build/) because -stdlib=libc++ now arrives via *_INIT, which only applies to new caches. Co-Authored-By: Claude Fable 5 --- CMakeLists.txt | 144 +++++++++++++++++------------ CMakePresets.json | 3 - CMakeUserPresets.json.example | 3 - cmake/format.cmake | 12 +-- cmake/python_venv.cmake | 59 ------------ cmake/toolchain/host-clang.cmake | 6 ++ py/host-emulator/CMakeLists.txt | 51 +++++++--- src/CMakeLists.txt | 5 +- src/apps/CMakeLists.txt | 9 +- src/apps/blinky/CMakeLists.txt | 3 +- src/apps/i2c_demo/CMakeLists.txt | 3 +- src/apps/uart_echo/CMakeLists.txt | 3 +- src/libs/board/CMakeLists.txt | 10 +- src/libs/board/host/CMakeLists.txt | 19 ++-- src/libs/common/CMakeLists.txt | 17 ++-- src/libs/mcu/CMakeLists.txt | 7 +- src/libs/mcu/host/CMakeLists.txt | 60 +++++++----- 17 files changed, 217 insertions(+), 197 deletions(-) delete mode 100644 cmake/python_venv.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 47da6bc..34aafff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,11 +2,29 @@ cmake_minimum_required(VERSION 3.27) project(embedded-cpp-bsp VERSION 0.0.1 LANGUAGES CXX C ASM) +# --------------------------------------------------------------------------- +# Project-wide setup +# --------------------------------------------------------------------------- + set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +# Defines the BUILD_TESTING option (ON by default) and calls enable_testing(). +# Testing is enabled here, unconditionally and early, so no add_test() call in +# a subdirectory can be silently discarded. +include(CTest) + +# Platform selection. The presets set these; a plain `cmake -B build` gets the +# host defaults, so the project configures without any preset at all. +set(EMBEDDED_CPP_MCU "host" CACHE STRING + "MCU backend to build (selects src/libs/mcu/)") +set_property(CACHE EMBEDDED_CPP_MCU PROPERTY STRINGS host arm_cm4 arm_cm7) +set(EMBEDDED_CPP_BOARD "host" CACHE STRING + "Board implementation to build (selects src/libs/board/)") +set_property(CACHE EMBEDDED_CPP_BOARD PROPERTY STRINGS host stm32f3_discovery) + # One bin/ directory per build tree (with per-config subdirectories under the # multi-config generator). A build-layout decision, so it lives here rather # than in the presets. @@ -18,77 +36,90 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin") file(CREATE_LINK "${PROJECT_BINARY_DIR}/compile_commands.json" "${PROJECT_SOURCE_DIR}/compile_commands.json" SYMBOLIC) -# Warnings only. Optimization and debug-info flags are per-configuration and must -# not be set here: these options are appended after the per-config flags, and the -# last -O on the command line wins, so a global -Os silently overrode Release's -# -O3 and made Debug builds optimized. The ARM toolchain sets its own +# Common usage requirements, carried by INTERFACE targets so each consumer +# states its dependency explicitly instead of inheriting directory state. +# +# project_warnings: warnings only. Optimization and debug-info flags are +# per-configuration and must not be set here: these options are appended after +# the per-config flags, and the last -O on the command line wins, so a global +# -Os would silently override Release's -O3. The ARM toolchain sets its own # CMAKE_*_FLAGS_{DEBUG,RELEASE}_INIT (cmake/toolchain/armgcc.cmake); the host # build uses CMake's defaults. -set(COMMON_COMPILE_OPTIONS +add_library(project_warnings INTERFACE) +target_compile_options(project_warnings INTERFACE -Wall -Wextra -Werror -Wpedantic + $<$:-Wno-c++98-compat;-Wno-exit-time-destructors;-Wno-global-constructors;-Wno-weak-vtables> + $<$:-Wno-unknown-pragmas> ) -if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") - set(COMMON_COMPILE_OPTIONS ${COMMON_COMPILE_OPTIONS} - -Wno-c++98-compat - -Wno-exit-time-destructors - -Wno-global-constructors - -Wno-weak-vtables - -fno-rtti - -stdlib=libc++ - ) - # Needed to make clang-tidy and ensure all the built libs use - # the same C++ stdlib implementation - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") -endif() +# project_options: everything a target of this project compiles with beyond +# warnings — the src/ include root (headers are included by their project- +# relative path, e.g. "libs/mcu/pin.hpp") and the no-RTTI policy. +add_library(project_options INTERFACE) +target_include_directories(project_options INTERFACE + $) +target_compile_options(project_options INTERFACE + $<$:-fno-rtti>) +target_link_libraries(project_options INTERFACE project_warnings) -if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") - set(COMMON_COMPILE_OPTIONS ${COMMON_COMPILE_OPTIONS} - -Wno-unknown-pragmas - -fno-rtti - ) -endif() +# --------------------------------------------------------------------------- +# Dependencies +# --------------------------------------------------------------------------- include(FetchContent) +# GIT_TAG pins the commit hash (with the tag name alongside for the reader): +# tags can move, hashes cannot. + +# Reusable CMake modules: clang-tidy integration (tools) and coverage FetchContent_Declare( - etl - GIT_REPOSITORY https://github.com/ETLCPP/etl - GIT_TAG 20.38.1 + CmakeScripts + GIT_REPOSITORY https://github.com/StableCoder/cmake-scripts.git + GIT_TAG 5b6c6efaeaab749001b1a1323f46e0ba7cf1c01f # 25.08 ) FetchContent_Declare( googletest GIT_REPOSITORY https://github.com/google/googletest - GIT_TAG v1.14.0 + GIT_TAG f8d7d77c06936315286eb55f8de22cd23c188571 # v1.14.0 ) +# cppzmq is a header-only C++ binding; the libzmq it binds is expected to be +# installed on the system (libzmq3-dev). FetchContent_Declare( - CmakeScripts - GIT_REPOSITORY https://github.com/StableCoder/cmake-scripts.git - GIT_TAG 25.08 + cppzmq + GIT_REPOSITORY https://github.com/zeromq/cppzmq.git + GIT_TAG c94c20743ed7d4aa37835a5c46567ab0790d4acc # v4.10.0 ) -FetchContent_GetProperties(CmakeScripts) - -if(NOT cmakescripts_POPULATED) - FetchContent_MakeAvailable(CmakeScripts) - set(CMAKE_MODULE_PATH ${cmakescripts_SOURCE_DIR} ${CMAKE_MODULE_PATH}) -endif() +FetchContent_Declare( + json + GIT_REPOSITORY https://github.com/nlohmann/json + GIT_TAG 0ca0fe433eb70cea0d5761079c0c5b47b736565b # v3.11.2 +) -# Assume libzmq is installed on the system +FetchContent_MakeAvailable(CmakeScripts) +list(PREPEND CMAKE_MODULE_PATH "${cmakescripts_SOURCE_DIR}") -# CPPZMQ header-only library; uses ZeroMQ -option(CPPZMQ_BUILD_TESTS OFF) -FetchContent_Declare(cppzmq GIT_REPOSITORY https://github.com/zeromq/cppzmq.git GIT_TAG v4.10.0) +# ZeroMQ, JSON, and googletest serve the host-emulation platform; a hardware +# build has no use for them. +if(EMBEDDED_CPP_MCU STREQUAL "host") + set(CPPZMQ_BUILD_TESTS OFF CACHE BOOL "" FORCE) + FetchContent_MakeAvailable(cppzmq json) -# FetchContent_Declare(json URL https://github.com/nlohmann/json/releases/download/v3.11.2/json.tar.xz) -FetchContent_Declare(json GIT_REPOSITORY https://github.com/nlohmann/json GIT_TAG v3.11.2) + if(BUILD_TESTING) + # For Windows: prevent overriding the parent project's compiler/linker settings + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + FetchContent_MakeAvailable(googletest) + endif() +endif() -FetchContent_MakeAvailable(etl) +# --------------------------------------------------------------------------- +# Tooling: clang-tidy, coverage, formatting +# --------------------------------------------------------------------------- include(tools) include(code-coverage) @@ -103,23 +134,20 @@ if(CODE_COVERAGE) ) endif() -if(CMAKE_PRESET STREQUAL "host") - # For Windows: Prevent overriding the parent project's compiler/linker settings - set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) - - FetchContent_MakeAvailable(googletest) - FetchContent_MakeAvailable(cppzmq) - FetchContent_MakeAvailable(json) - include(CTest) - enable_testing() -endif() - # `format` / `format-check` targets, and the pre-commit hook. Included for every -# preset: formatting is not host-specific, and a cross-compiling developer -# should get the same guard rails. +# configuration: formatting is not host-specific, and a cross-compiling +# developer should get the same guard rails. include("${PROJECT_SOURCE_DIR}/cmake/format.cmake") +# --------------------------------------------------------------------------- +# Targets +# --------------------------------------------------------------------------- + clang_tidy("-header-filter=${PROJECT_SOURCE_DIR}/src/.*") add_subdirectory(src) reset_clang_tidy() -add_subdirectory(py/host-emulator) # No clang-tidy for python + +# The Python emulator and its integration tests exist to test the host build. +if(EMBEDDED_CPP_MCU STREQUAL "host") + add_subdirectory(py/host-emulator) # No clang-tidy for python +endif() diff --git a/CMakePresets.json b/CMakePresets.json index 5b1a5db..e05f63f 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -12,9 +12,6 @@ "description": "Default build using Ninja", "generator": "Ninja Multi-Config", "binaryDir": "${sourceDir}/build/${presetName}", - "cacheVariables": { - "CMAKE_PRESET": "${presetName}" - }, "hidden": true }, { diff --git a/CMakeUserPresets.json.example b/CMakeUserPresets.json.example index e130b5c..2812deb 100644 --- a/CMakeUserPresets.json.example +++ b/CMakeUserPresets.json.example @@ -12,7 +12,6 @@ "displayName": "Host (macOS with Homebrew LLVM)", "description": "Homebrew LLVM on Apple silicon. Intel Macs use /usr/local/opt/llvm instead of /opt/homebrew/opt/llvm.", "cacheVariables": { - "CMAKE_PRESET": "host", "CMAKE_C_COMPILER": "/opt/homebrew/opt/llvm/bin/clang", "CMAKE_CXX_COMPILER": "/opt/homebrew/opt/llvm/bin/clang++" } @@ -23,7 +22,6 @@ "displayName": "Host (macOS with system clang)", "description": "Apple clang. Note that it lags upstream LLVM and may not support every C++23 feature this project uses.", "cacheVariables": { - "CMAKE_PRESET": "host", "CMAKE_C_COMPILER": "/usr/bin/clang", "CMAKE_CXX_COMPILER": "/usr/bin/clang++" } @@ -34,7 +32,6 @@ "displayName": "Host (Linux)", "description": "System clang on Linux. Only needed for native builds; the dev container already resolves clang from PATH.", "cacheVariables": { - "CMAKE_PRESET": "host", "CMAKE_C_COMPILER": "/usr/bin/clang", "CMAKE_CXX_COMPILER": "/usr/bin/clang++" } diff --git a/cmake/format.cmake b/cmake/format.cmake index 8de2356..4239ef8 100644 --- a/cmake/format.cmake +++ b/cmake/format.cmake @@ -5,7 +5,7 @@ # guaranteed to agree. Anything that duplicates the rules eventually disagrees # with CI, which is the failure mode this file exists to prevent. -set(FORMAT_SCRIPT "${CMAKE_SOURCE_DIR}/tools/format.sh") +set(FORMAT_SCRIPT "${PROJECT_SOURCE_DIR}/tools/format.sh") if(NOT EXISTS "${FORMAT_SCRIPT}") message(WARNING "tools/format.sh not found; format targets unavailable") @@ -14,7 +14,7 @@ endif() add_custom_target(format COMMAND "${FORMAT_SCRIPT}" --fix - WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" + WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" COMMENT "Reformatting C++ and Python sources in place" USES_TERMINAL VERBATIM @@ -22,7 +22,7 @@ add_custom_target(format add_custom_target(format-check COMMAND "${FORMAT_SCRIPT}" --check - WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" + WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" COMMENT "Checking formatting (same checks as CI)" USES_TERMINAL VERBATIM @@ -36,12 +36,12 @@ add_custom_target(format-check # work tree so that tarball builds and CI checkouts are unaffected. option(INSTALL_GIT_HOOKS "Point core.hooksPath at .githooks during configure" ON) -if(INSTALL_GIT_HOOKS AND EXISTS "${CMAKE_SOURCE_DIR}/.git") +if(INSTALL_GIT_HOOKS AND EXISTS "${PROJECT_SOURCE_DIR}/.git") find_program(GIT_EXECUTABLE git) if(GIT_EXECUTABLE) execute_process( COMMAND "${GIT_EXECUTABLE}" config --get core.hooksPath - WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" + WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" OUTPUT_VARIABLE current_hooks_path OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET @@ -49,7 +49,7 @@ if(INSTALL_GIT_HOOKS AND EXISTS "${CMAKE_SOURCE_DIR}/.git") if(NOT current_hooks_path STREQUAL ".githooks") execute_process( COMMAND "${GIT_EXECUTABLE}" config core.hooksPath .githooks - WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" + WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" RESULT_VARIABLE hooks_result ERROR_QUIET ) diff --git a/cmake/python_venv.cmake b/cmake/python_venv.cmake deleted file mode 100644 index 9d60b33..0000000 --- a/cmake/python_venv.cmake +++ /dev/null @@ -1,59 +0,0 @@ -# Python virtual environment management using uv -# -# Usage: -# configure_venv(venv_name project_dir) -# -# Syncs `project_dir` (and its dev dependency group) into a venv under the CMake -# binary directory, using the project's uv.lock so builds are reproducible. -# -# Sets ${venv_name}_PYTHON to the path of the Python interpreter in the venv. - -function(configure_venv venv_name project_dir) - set(venv_path "${CMAKE_BINARY_DIR}/${venv_name}") - - # Find uv - prefer system installation, fall back to common locations - find_program(UV_EXECUTABLE uv - HINTS - $ENV{HOME}/.cargo/bin - $ENV{HOME}/.local/bin - /usr/local/bin - ) - - if(NOT UV_EXECUTABLE) - message(FATAL_ERROR - "uv not found. Install it with: curl -LsSf https://astral.sh/uv/install.sh | sh" - ) - endif() - - message(STATUS "Using uv: ${UV_EXECUTABLE}") - message(STATUS "Syncing ${venv_name} from ${project_dir}/uv.lock") - - # `uv sync --frozen` installs exactly what uv.lock pins and fails rather than - # silently re-resolving if the lock is stale. It is a no-op once the venv - # matches the lock, so repeat configures stay cheap. - # - # UV_PROJECT_ENVIRONMENT redirects the venv out of the source tree and into - # the build directory. The interpreter comes from .python-version. - execute_process( - COMMAND ${CMAKE_COMMAND} -E env - "UV_PROJECT_ENVIRONMENT=${venv_path}" - ${UV_EXECUTABLE} sync --frozen - WORKING_DIRECTORY ${project_dir} - RESULT_VARIABLE sync_result - ) - if(NOT sync_result EQUAL 0) - message(FATAL_ERROR - "Failed to sync ${venv_name} from ${project_dir}/uv.lock. " - "If dependencies changed, refresh the lock with: uv lock" - ) - endif() - - # Re-run CMake if the dependency manifests change - set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS - "${project_dir}/pyproject.toml" - "${project_dir}/uv.lock" - ) - - # Export the Python path - set(${venv_name}_PYTHON ${venv_path}/bin/python3 PARENT_SCOPE) -endfunction() diff --git a/cmake/toolchain/host-clang.cmake b/cmake/toolchain/host-clang.cmake index ba0f211..641edce 100644 --- a/cmake/toolchain/host-clang.cmake +++ b/cmake/toolchain/host-clang.cmake @@ -39,3 +39,9 @@ set(CMAKE_RANLIB ${TOOLCHAIN_PREFIX}llvm-ranlib${TOOLCHAIN_SUFFIX}) # Consumed by the code-coverage module (StableCoder cmake-scripts) set(LLVM_COV_PATH ${TOOLCHAIN_PREFIX}llvm-cov${TOOLCHAIN_SUFFIX}) set(LLVM_PROFDATA_PATH ${TOOLCHAIN_PREFIX}llvm-profdata${TOOLCHAIN_SUFFIX}) + +# libc++ everywhere, including FetchContent-built dependencies: mixing libc++ +# and libstdc++ objects in one link is an ODR/ABI error. A global *_INIT flag +# in the toolchain is the supported way to say this (project targets must not +# munge CMAKE_CXX_FLAGS, which belongs to the developer). +set(CMAKE_CXX_FLAGS_INIT "-stdlib=libc++") diff --git a/py/host-emulator/CMakeLists.txt b/py/host-emulator/CMakeLists.txt index 3770d51..f76702c 100644 --- a/py/host-emulator/CMakeLists.txt +++ b/py/host-emulator/CMakeLists.txt @@ -1,21 +1,48 @@ -include("${PROJECT_SOURCE_DIR}/cmake/python_venv.cmake") +# The Python integration tests run out of a uv-managed venv in the build tree. +# Creating the venv is itself a CTest fixture: `uv sync --frozen` installs +# exactly what uv.lock pins (and is a cheap no-op once the venv matches), so +# configure stays fast and offline and the venv refreshes exactly when the +# tests need it. -configure_venv(host_emulator_venv ${CMAKE_CURRENT_SOURCE_DIR}) +find_program(UV_EXECUTABLE uv + HINTS + $ENV{HOME}/.cargo/bin + $ENV{HOME}/.local/bin + /usr/local/bin +) +if(NOT UV_EXECUTABLE) + message(FATAL_ERROR + "uv not found. Install it with: curl -LsSf https://astral.sh/uv/install.sh | sh" + ) +endif() + +set(venv_path "${PROJECT_BINARY_DIR}/host_emulator_venv") add_test( - NAME host_emulator_test - COMMAND ${host_emulator_venv_PYTHON} -m pytest ${CMAKE_CURRENT_SOURCE_DIR} - --blinky=${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/$/blinky - --uart-echo=${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/$/uart_echo - --i2c-demo=${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/$/i2c_demo - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + NAME host_emulator_venv + COMMAND ${CMAKE_COMMAND} -E env + "UV_PROJECT_ENVIRONMENT=${venv_path}" + ${UV_EXECUTABLE} sync --frozen + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} +) +set_tests_properties(host_emulator_venv PROPERTIES + FIXTURES_SETUP emulator_venv + TIMEOUT 300 + LABELS "integration" ) -# No DEPENDS here: it names tests, not targets, and the executables under test are -# built before ctest runs. The venv is created at configure time by configure_venv() -# above, so it is not a test either and cannot be a fixture. +# $ resolves the real executable path for whatever +# configuration and output layout is in effect — no hand-built paths. +add_test( + NAME host_emulator_test + COMMAND ${venv_path}/bin/python3 -m pytest ${CMAKE_CURRENT_SOURCE_DIR} + --blinky=$ + --uart-echo=$ + --i2c-demo=$ + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} +) set_tests_properties(host_emulator_test PROPERTIES + FIXTURES_REQUIRED emulator_venv TIMEOUT 300 LABELS "integration" ) - diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 98d20af..c87037d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,5 +1,2 @@ -# include files via relative path from src -include_directories(${CMAKE_CURRENT_SOURCE_DIR}) - add_subdirectory(apps) -add_subdirectory(libs) \ No newline at end of file +add_subdirectory(libs) diff --git a/src/apps/CMakeLists.txt b/src/apps/CMakeLists.txt index 5fc67bd..5e56f6b 100644 --- a/src/apps/CMakeLists.txt +++ b/src/apps/CMakeLists.txt @@ -1,7 +1,10 @@ -add_library(app INTERFACE app.hpp) -target_compile_options(app INTERFACE ${COMMON_COMPILE_OPTIONS}) +add_library(app INTERFACE) +target_sources(app INTERFACE + FILE_SET HEADERS + BASE_DIRS ${PROJECT_SOURCE_DIR}/src + FILES app.hpp) target_link_libraries(app INTERFACE board error) add_subdirectory(blinky) add_subdirectory(uart_echo) -add_subdirectory(i2c_demo) \ No newline at end of file +add_subdirectory(i2c_demo) diff --git a/src/apps/blinky/CMakeLists.txt b/src/apps/blinky/CMakeLists.txt index 52d02f5..0f6df8a 100644 --- a/src/apps/blinky/CMakeLists.txt +++ b/src/apps/blinky/CMakeLists.txt @@ -1,3 +1,2 @@ add_executable(blinky blinky.cpp) -target_compile_options(blinky PRIVATE ${COMMON_COMPILE_OPTIONS}) -target_link_libraries(blinky PRIVATE error sys mcu) \ No newline at end of file +target_link_libraries(blinky PRIVATE project_options app sys mcu error) diff --git a/src/apps/i2c_demo/CMakeLists.txt b/src/apps/i2c_demo/CMakeLists.txt index 098e203..4024ed0 100644 --- a/src/apps/i2c_demo/CMakeLists.txt +++ b/src/apps/i2c_demo/CMakeLists.txt @@ -1,3 +1,2 @@ add_executable(i2c_demo i2c_demo.cpp) -target_compile_options(i2c_demo PRIVATE ${COMMON_COMPILE_OPTIONS}) -target_link_libraries(i2c_demo PRIVATE error sys mcu) +target_link_libraries(i2c_demo PRIVATE project_options app sys mcu error) diff --git a/src/apps/uart_echo/CMakeLists.txt b/src/apps/uart_echo/CMakeLists.txt index a0c058a..c9e4adf 100644 --- a/src/apps/uart_echo/CMakeLists.txt +++ b/src/apps/uart_echo/CMakeLists.txt @@ -1,3 +1,2 @@ add_executable(uart_echo uart_echo.cpp) -target_compile_options(uart_echo PRIVATE ${COMMON_COMPILE_OPTIONS}) -target_link_libraries(uart_echo PRIVATE error sys mcu) +target_link_libraries(uart_echo PRIVATE project_options app sys mcu error) diff --git a/src/libs/board/CMakeLists.txt b/src/libs/board/CMakeLists.txt index 19a9b65..7dceef9 100644 --- a/src/libs/board/CMakeLists.txt +++ b/src/libs/board/CMakeLists.txt @@ -1,6 +1,8 @@ -add_library(board INTERFACE board.hpp) -target_compile_options(board INTERFACE ${COMMON_COMPILE_OPTIONS}) -set_target_properties(board PROPERTIES LINKER_LANGUAGE CXX) +add_library(board INTERFACE) +target_sources(board INTERFACE + FILE_SET HEADERS + BASE_DIRS ${PROJECT_SOURCE_DIR}/src + FILES board.hpp) target_link_libraries(board INTERFACE mcu error) if(NOT IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${EMBEDDED_CPP_BOARD}") @@ -8,4 +10,4 @@ if(NOT IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${EMBEDDED_CPP_BOARD}") "Board '${EMBEDDED_CPP_BOARD}' is not implemented yet. " "Available: host. Hardware boards will be added later; see CLAUDE.md.") endif() -add_subdirectory(${EMBEDDED_CPP_BOARD}) \ No newline at end of file +add_subdirectory(${EMBEDDED_CPP_BOARD}) diff --git a/src/libs/board/host/CMakeLists.txt b/src/libs/board/host/CMakeLists.txt index 6f1ff0c..ec2f7b8 100644 --- a/src/libs/board/host/CMakeLists.txt +++ b/src/libs/board/host/CMakeLists.txt @@ -1,7 +1,14 @@ -add_library(host_board host_board.hpp host_board.cpp) -target_compile_options(host_board PRIVATE ${COMMON_COMPILE_OPTIONS}) -target_link_libraries(host_board PRIVATE board mcu host_mcu cppzmq nlohmann_json::nlohmann_json) +add_library(host_board host_board.cpp) +target_sources(host_board PUBLIC + FILE_SET HEADERS + BASE_DIRS ${PROJECT_SOURCE_DIR}/src + FILES host_board.hpp) +target_link_libraries(host_board + PUBLIC board mcu host_mcu + PRIVATE project_options cppzmq nlohmann_json::nlohmann_json) -add_library(sys main.cpp) -target_compile_options(sys PRIVATE ${COMMON_COMPILE_OPTIONS}) -target_link_libraries(sys PRIVATE app board mcu host_board host_mcu cppzmq) +# The platform entry point. An OBJECT library so main.o is linked into each +# app directly — pulling main() out of a static archive relies on link-time +# symbol-resolution order and breaks under --gc-sections/LTO. +add_library(sys OBJECT main.cpp) +target_link_libraries(sys PRIVATE project_options app board host_board) diff --git a/src/libs/common/CMakeLists.txt b/src/libs/common/CMakeLists.txt index d5dc7d6..32c652c 100644 --- a/src/libs/common/CMakeLists.txt +++ b/src/libs/common/CMakeLists.txt @@ -1,7 +1,12 @@ -add_library(error INTERFACE error.hpp) -target_compile_options(error INTERFACE ${COMMON_COMPILE_OPTIONS}) -set_target_properties(error PROPERTIES LINKER_LANGUAGE CXX) +add_library(error INTERFACE) +target_sources(error INTERFACE + FILE_SET HEADERS + BASE_DIRS ${PROJECT_SOURCE_DIR}/src + FILES error.hpp) -add_library(logger logger.hpp logger.cpp) -target_compile_options(logger PRIVATE ${COMMON_COMPILE_OPTIONS}) -target_include_directories(logger PUBLIC ${CMAKE_SOURCE_DIR}/src) +add_library(logger logger.cpp) +target_sources(logger PUBLIC + FILE_SET HEADERS + BASE_DIRS ${PROJECT_SOURCE_DIR}/src + FILES logger.hpp) +target_link_libraries(logger PRIVATE project_options) diff --git a/src/libs/mcu/CMakeLists.txt b/src/libs/mcu/CMakeLists.txt index d28c00d..1bb65e9 100644 --- a/src/libs/mcu/CMakeLists.txt +++ b/src/libs/mcu/CMakeLists.txt @@ -1,5 +1,8 @@ -add_library(mcu INTERFACE pin.hpp uart.hpp i2c.hpp delay.hpp) -target_compile_options(mcu INTERFACE ${COMMON_COMPILE_OPTIONS}) +add_library(mcu INTERFACE) +target_sources(mcu INTERFACE + FILE_SET HEADERS + BASE_DIRS ${PROJECT_SOURCE_DIR}/src + FILES pin.hpp uart.hpp i2c.hpp delay.hpp) target_link_libraries(mcu INTERFACE error) if(NOT IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${EMBEDDED_CPP_MCU}") diff --git a/src/libs/mcu/host/CMakeLists.txt b/src/libs/mcu/host/CMakeLists.txt index 87908e0..28225e1 100644 --- a/src/libs/mcu/host/CMakeLists.txt +++ b/src/libs/mcu/host/CMakeLists.txt @@ -1,15 +1,36 @@ add_library(host_mcu host_i2c.cpp host_pin.cpp host_uart.cpp delay.cpp) -target_compile_options(host_mcu PRIVATE ${COMMON_COMPILE_OPTIONS}) +target_sources(host_mcu PUBLIC + FILE_SET HEADERS + BASE_DIRS ${PROJECT_SOURCE_DIR}/src + FILES host_i2c.hpp host_pin.hpp host_uart.hpp + dispatcher.hpp receiver.hpp + host_emulator_messages.hpp emulator_message_json_encoder.hpp) +# nlohmann_json is PUBLIC because emulator_message_json_encoder.hpp, a public +# header, includes . +target_link_libraries(host_mcu + PUBLIC mcu nlohmann_json::nlohmann_json + PRIVATE project_options host_transport) add_library(host_transport zmq_transport.cpp) -target_compile_options(host_transport PRIVATE ${COMMON_COMPILE_OPTIONS}) +target_sources(host_transport PUBLIC + FILE_SET HEADERS + BASE_DIRS ${PROJECT_SOURCE_DIR}/src + FILES zmq_transport.hpp transport.hpp) +# cppzmq and logger are PUBLIC because zmq_transport.hpp includes their headers. +target_link_libraries(host_transport + PUBLIC cppzmq logger error + PRIVATE project_options) -target_link_libraries(host_transport PRIVATE cppzmq logger) -target_link_libraries(host_mcu INTERFACE mcu PRIVATE host_transport nlohmann_json::nlohmann_json) +# Coverage instrumentation for the libraries under test (the test executables +# themselves are handled by add_host_unit_test below). +if(CODE_COVERAGE) + target_code_coverage(host_mcu) + target_code_coverage(host_transport) +endif() -FetchContent_MakeAvailable(googletest) -add_library(GTest::GTest INTERFACE IMPORTED) -target_link_libraries(GTest::GTest INTERFACE gtest_main) +if(NOT BUILD_TESTING) + return() +endif() include(GoogleTest) @@ -22,8 +43,7 @@ include(GoogleTest) # built from test_zmq_transport.cpp. function(add_host_unit_test name source) add_executable(${name} ${source}) - target_compile_options(${name} PRIVATE ${COMMON_COMPILE_OPTIONS}) - target_link_libraries(${name} PRIVATE GTest::GTest ${ARGN}) + target_link_libraries(${name} PRIVATE project_options GTest::gtest_main ${ARGN}) # TIMEOUT is a backstop, not a budget: unit tests here run in well under a # second, and a startup bug in the transport used to wedge the constructor # forever rather than fail. A hung test should fail CI, not stall it. @@ -34,23 +54,13 @@ function(add_host_unit_test name source) endif() endfunction() -# cppzmq is needed because zmq_transport.hpp includes zmq.hpp -add_host_unit_test(test_host_transport test_zmq_transport.cpp host_transport cppzmq) +add_host_unit_test(test_host_transport test_zmq_transport.cpp host_transport) # Separate binary from test_host_transport on purpose: these tests bound a # potential hang with a watchdog that hard-exits the process, which must not # take unrelated tests down with it. add_host_unit_test(test_host_transport_startup test_zmq_transport_startup.cpp - host_transport cppzmq) -add_host_unit_test(test_messages test_messages.cpp nlohmann_json::nlohmann_json) -add_host_unit_test(test_dispatcher test_dispatcher.cpp) -add_host_unit_test(test_host_uart test_host_uart.cpp - host_mcu host_transport nlohmann_json::nlohmann_json cppzmq) -add_host_unit_test(test_host_i2c test_host_i2c.cpp - host_mcu host_transport nlohmann_json::nlohmann_json cppzmq) - -# Coverage instrumentation for the libraries under test (the test executables -# themselves are handled by add_host_unit_test above). -if(CODE_COVERAGE) - target_code_coverage(host_mcu) - target_code_coverage(host_transport) -endif() + host_transport) +add_host_unit_test(test_messages test_messages.cpp host_mcu) +add_host_unit_test(test_dispatcher test_dispatcher.cpp host_mcu) +add_host_unit_test(test_host_uart test_host_uart.cpp host_mcu host_transport) +add_host_unit_test(test_host_i2c test_host_i2c.cpp host_mcu host_transport) From e17a96bdb836ef5bb92b8f8b94b428e2544c94a5 Mon Sep 17 00:00:00 2001 From: Nehal Patel Date: Sat, 29 Aug 2026 15:49:58 +0000 Subject: [PATCH 08/17] refactor(mcu/host): one Transact helper for the emulator round trip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The encode -> send -> receive -> decode -> status-check exchange was written six times across the three host peripherals, in three different styles: host_pin chained and_then, host_uart mixed a chain with a manual if-cascade inside the last lambda, and host_i2c wrote the whole thing imperatively. In a codebase meant to be copied from, the same protocol step written three ways is three competing house styles. Transact(transport, request), next to Encode/Decode, is now the single canonical exchange — including folding the emulator's status field into the error channel — and the peripherals reduce to a request literal plus a transform of the response. This also makes GetState honor the response status, which the hand-rolled version forgot. Along the way, in the same files: - Decode takes std::string_view by value (a const& to a view is an anti-idiom, and two callers were building a std::string just to bind the reference), parses via the non-throwing overload, and documents the one remaining catch at the nlohmann boundary; string_view-by-value is now used across Receiver, Dispatcher, and the predicates - Receiver gains the virtual destructor every other interface already had, plus a comment stating its 'unexpected means not mine' contract - request/response literals rely on the message structs' defaulted type and object fields, as host_pin already did - HostPin's input-direction guard moves into SendState, the funnel all three setters share; Toggle becomes the same and_then chain as its neighbours - the Error<->JSON table now covers all twelve enumerators (it silently covered five) and says where its Python mirror lives - the stray semicolon after Encode is gone Co-Authored-By: Claude Fable 5 --- src/libs/board/host/host_board.hpp | 2 +- src/libs/mcu/host/dispatcher.hpp | 4 +- .../host/emulator_message_json_encoder.hpp | 51 +++++++++++++-- src/libs/mcu/host/host_i2c.cpp | 60 +++--------------- src/libs/mcu/host/host_i2c.hpp | 2 +- src/libs/mcu/host/host_pin.cpp | 63 ++++++------------- src/libs/mcu/host/host_pin.hpp | 2 +- src/libs/mcu/host/host_uart.cpp | 49 ++------------- src/libs/mcu/host/host_uart.hpp | 2 +- src/libs/mcu/host/receiver.hpp | 9 ++- src/libs/mcu/host/test_dispatcher.cpp | 10 +-- src/libs/mcu/host/test_host_i2c.cpp | 2 +- src/libs/mcu/host/test_host_uart.cpp | 2 +- .../mcu/host/test_zmq_transport_startup.cpp | 4 +- 14 files changed, 102 insertions(+), 160 deletions(-) diff --git a/src/libs/board/host/host_board.hpp b/src/libs/board/host/host_board.hpp index e0d68f0..c9c881a 100644 --- a/src/libs/board/host/host_board.hpp +++ b/src/libs/board/host/host_board.hpp @@ -41,7 +41,7 @@ class HostBoard : public Board { auto Uart1() -> mcu::Uart& override; private: - static constexpr auto IsJson(const std::string_view& message) -> bool { + static constexpr auto IsJson(std::string_view message) -> bool { return message.starts_with("{") && message.ends_with("}"); } diff --git a/src/libs/mcu/host/dispatcher.hpp b/src/libs/mcu/host/dispatcher.hpp index 86dd501..ae6e4f8 100644 --- a/src/libs/mcu/host/dispatcher.hpp +++ b/src/libs/mcu/host/dispatcher.hpp @@ -12,7 +12,7 @@ namespace mcu { using ReceiverMap = - std::vector, + std::vector, std::reference_wrapper>>; class Dispatcher { @@ -25,7 +25,7 @@ class Dispatcher { auto operator=(const Dispatcher&) -> Dispatcher& = delete; auto operator=(Dispatcher&&) -> Dispatcher& = delete; - [[nodiscard]] auto Dispatch(const std::string_view& message) const + [[nodiscard]] auto Dispatch(std::string_view message) const -> std::expected { for (const auto& [predicate, receiver_ref] : receivers_) { if (predicate(message)) { diff --git a/src/libs/mcu/host/emulator_message_json_encoder.hpp b/src/libs/mcu/host/emulator_message_json_encoder.hpp index 0cd6588..99bd361 100644 --- a/src/libs/mcu/host/emulator_message_json_encoder.hpp +++ b/src/libs/mcu/host/emulator_message_json_encoder.hpp @@ -8,6 +8,7 @@ #include "libs/common/error.hpp" #include "libs/mcu/host/host_emulator_messages.hpp" +#include "libs/mcu/host/transport.hpp" #include "libs/mcu/pin.hpp" // Custom JSON serialization for std::byte. The function names and signatures @@ -36,6 +37,10 @@ struct adl_serializer { namespace common { +// Covers every common::Error enumerator (error.hpp). When adding an +// enumerator, add its wire name here and in the Python emulator's Status enum +// (py/host-emulator/src/host_emulator/common.py) — an enumerator missing from +// this table serializes as null and decodes as the first entry. NLOHMANN_JSON_SERIALIZE_ENUM(Error, { {Error::kOk, "Ok"}, @@ -43,6 +48,14 @@ NLOHMANN_JSON_SERIALIZE_ENUM(Error, {Error::kInvalidArgument, "InvalidArgument"}, {Error::kInvalidState, "InvalidState"}, {Error::kInvalidOperation, "InvalidOperation"}, + {Error::kOperationFailed, "OperationFailed"}, + {Error::kUnhandled, "Unhandled"}, + {Error::kConnectionRefused, + "ConnectionRefused"}, + {Error::kConnectionClosed, "ConnectionClosed"}, + {Error::kTimeout, "Timeout"}, + {Error::kWouldBlock, "WouldBlock"}, + {Error::kMessageTooLarge, "MessageTooLarge"}, }) } // namespace common @@ -106,18 +119,46 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(I2CEmulatorResponse, type, object, name, address, data, bytes_transferred, status) template -inline auto Encode(const T& obj) -> std::string { +auto Encode(const T& obj) -> std::string { return nlohmann::json(obj).dump(); -}; +} template -inline auto Decode(const std::string_view& str) - -> std::expected { +auto Decode(std::string_view str) -> std::expected { + const auto parsed = + nlohmann::json::parse(str, nullptr, /*allow_exceptions=*/false); + if (parsed.is_discarded()) { + return std::unexpected(common::Error::kInvalidArgument); + } + // get() still throws on a structural mismatch (missing field, wrong + // type); this is the one place the nlohmann boundary needs a catch in this + // otherwise exception-free codebase. try { - return nlohmann::json::parse(str).template get(); + return parsed.template get(); } catch (const nlohmann::json::exception&) { return std::unexpected(common::Error::kInvalidArgument); } } +/// One request/response exchange with the emulator: encode the request, send +/// it, wait for the reply, decode it, and fold the emulator's status field +/// into the error channel. Every host peripheral funnels its blocking +/// operations through here — it is also the codebase's reference example of +/// chaining std::expected with and_then. +template +auto Transact(Transport& transport, const Request& request) + -> std::expected { + return transport.Send(Encode(request)) + .and_then([&transport]() { return transport.Receive(); }) + .and_then( + [](const std::string& reply) { return Decode(reply); }) + .and_then( + [](Response&& response) -> std::expected { + if (response.status != common::Error::kOk) { + return std::unexpected(response.status); + } + return std::move(response); + }); +} + } // namespace mcu diff --git a/src/libs/mcu/host/host_i2c.cpp b/src/libs/mcu/host/host_i2c.cpp index a08f1d4..0d9d48e 100644 --- a/src/libs/mcu/host/host_i2c.cpp +++ b/src/libs/mcu/host/host_i2c.cpp @@ -18,72 +18,32 @@ auto HostI2CController::SendData(uint16_t address, std::span data) -> std::expected { const I2CEmulatorRequest request{ - .type = MessageType::kRequest, - .object = ObjectType::kI2C, .name = name_, .operation = OperationType::kSend, .address = address, .data = std::vector(data.begin(), data.end()), - .size = 0, }; - - auto send_result = transport_.Send(Encode(request)); - if (!send_result) { - return std::unexpected(send_result.error()); - } - - auto receive_result = transport_.Receive(); - if (!receive_result) { - return std::unexpected(receive_result.error()); - } - - auto response = Decode(receive_result.value()); - if (!response) { - return std::unexpected(response.error()); - } - if (response->status != common::Error::kOk) { - return std::unexpected(response->status); - } - - return {}; + return Transact(transport_, request) + .transform([](const I2CEmulatorResponse&) {}); } auto HostI2CController::ReceiveData(uint16_t address, std::span buffer) -> std::expected { const I2CEmulatorRequest request{ - .type = MessageType::kRequest, - .object = ObjectType::kI2C, .name = name_, .operation = OperationType::kReceive, .address = address, .data = {}, .size = buffer.size(), }; - - auto send_result = transport_.Send(Encode(request)); - if (!send_result) { - return std::unexpected(send_result.error()); - } - - auto receive_result = transport_.Receive(); - if (!receive_result) { - return std::unexpected(receive_result.error()); - } - - auto response = Decode(receive_result.value()); - if (!response) { - return std::unexpected(response.error()); - } - if (response->status != common::Error::kOk) { - return std::unexpected(response->status); - } - - // Copy received data into caller-provided buffer - const size_t bytes_to_copy{std::min(response->data.size(), buffer.size())}; - std::copy_n(response->data.begin(), bytes_to_copy, buffer.begin()); - - return bytes_to_copy; + return Transact(transport_, request) + .transform([buffer](const I2CEmulatorResponse& response) { + const size_t bytes_to_copy{ + std::min(response.data.size(), buffer.size())}; + std::copy_n(response.data.begin(), bytes_to_copy, buffer.begin()); + return bytes_to_copy; + }); } auto HostI2CController::SendDataInterrupt( @@ -118,7 +78,7 @@ auto HostI2CController::ReceiveDataDma( return {}; } -auto HostI2CController::Receive(const std::string_view& message) +auto HostI2CController::Receive(std::string_view message) -> std::expected { static_cast(message); return std::unexpected(common::Error::kUnhandled); diff --git a/src/libs/mcu/host/host_i2c.hpp b/src/libs/mcu/host/host_i2c.hpp index 03c61c6..82e6d7f 100644 --- a/src/libs/mcu/host/host_i2c.hpp +++ b/src/libs/mcu/host/host_i2c.hpp @@ -43,7 +43,7 @@ class HostI2CController final : public I2CController, public Receiver { uint16_t address, std::span buffer, std::function)> callback) -> std::expected override; - auto Receive(const std::string_view& message) + auto Receive(std::string_view message) -> std::expected override; private: diff --git a/src/libs/mcu/host/host_pin.cpp b/src/libs/mcu/host/host_pin.cpp index a131b2d..4f6f477 100644 --- a/src/libs/mcu/host/host_pin.cpp +++ b/src/libs/mcu/host/host_pin.cpp @@ -17,30 +17,17 @@ auto HostPin::Configure(PinDirection direction) return {}; } auto HostPin::SetHigh() -> std::expected { - if (direction_ == PinDirection::kInput) { - return std::unexpected(common::Error::kInvalidOperation); - } return SendState(PinState::kHigh); } auto HostPin::SetLow() -> std::expected { - if (direction_ == PinDirection::kInput) { - return std::unexpected(common::Error::kInvalidOperation); - } return SendState(PinState::kLow); } auto HostPin::Toggle() -> std::expected { - if (direction_ == PinDirection::kInput) { - return std::unexpected(common::Error::kInvalidOperation); - } - auto current_state{GetState()}; - if (!current_state) { - return std::unexpected(current_state.error()); - } - if (current_state.value() == PinState::kHigh) { - return SendState(PinState::kLow); - } - return SendState(PinState::kHigh); + return GetState().and_then([this](PinState state) { + return SendState(state == PinState::kHigh ? PinState::kLow + : PinState::kHigh); + }); } auto HostPin::Get() -> std::expected { @@ -56,25 +43,18 @@ auto HostPin::SetInterruptHandler(std::function handler, } auto HostPin::SendState(PinState state) -> std::expected { + // Driving the pin only makes sense for an output; the same guard covers + // SetHigh, SetLow, and Toggle, which all funnel through here. + if (direction_ == PinDirection::kInput) { + return std::unexpected(common::Error::kInvalidOperation); + } const PinEmulatorRequest req = { .name = name_, .operation = OperationType::kSet, .state = state, }; - - return transport_.Send(Encode(req)) - .and_then([this]() { return transport_.Receive(); }) - .and_then([](const std::string& rx_bytes) { - return Decode(rx_bytes); - }) - .and_then([this, state](const PinEmulatorResponse& resp) - -> std::expected { - if (resp.status != common::Error::kOk) { - return std::unexpected(resp.status); - } - state_ = state; - return {}; - }); + return Transact(transport_, req) + .transform([this, state](const PinEmulatorResponse&) { state_ = state; }); } auto HostPin::CheckAndInvokeHandler(PinState prev_state, @@ -97,25 +77,18 @@ auto HostPin::GetState() -> std::expected { .operation = OperationType::kGet, .state = PinState::kHighZ, }; - - return transport_.Send(Encode(req)) - .and_then([this]() { return transport_.Receive(); }) - .and_then([this](const std::string& rx_bytes) - -> std::expected { - auto resp = Decode(rx_bytes); - if (!resp) { - return std::unexpected(resp.error()); - } - // If the MCU is polling the input, then it should NOT be configured - // for interrupts. Therefore, we should not invoke the handler. - state_ = resp->state; - return resp->state; + // Polling deliberately bypasses the interrupt handler: if the MCU is + // polling the input, it should not also be configured for interrupts. + return Transact(transport_, req) + .transform([this](const PinEmulatorResponse& resp) { + state_ = resp.state; + return resp.state; }); } // Messages received from the external application will always be // requests. HostPin will only send responses. -auto HostPin::Receive(const std::string_view& message) +auto HostPin::Receive(std::string_view message) -> std::expected { auto req = Decode(message); if (!req) { diff --git a/src/libs/mcu/host/host_pin.hpp b/src/libs/mcu/host/host_pin.hpp index aa49c77..01e7a71 100644 --- a/src/libs/mcu/host/host_pin.hpp +++ b/src/libs/mcu/host/host_pin.hpp @@ -29,7 +29,7 @@ class HostPin final : public BidirectionalPin, public Receiver { auto SetInterruptHandler(std::function handler, PinTransition transition) -> std::expected override; - auto Receive(const std::string_view& message) + auto Receive(std::string_view message) -> std::expected override; private: diff --git a/src/libs/mcu/host/host_uart.cpp b/src/libs/mcu/host/host_uart.cpp index 5dbd31a..dc6113a 100644 --- a/src/libs/mcu/host/host_uart.cpp +++ b/src/libs/mcu/host/host_uart.cpp @@ -38,28 +38,12 @@ auto HostUart::Send(std::span data) } const UartEmulatorRequest request{ - .type = MessageType::kRequest, - .object = ObjectType::kUart, .name = name_, .operation = OperationType::kSend, .data = std::vector(data.begin(), data.end()), - .size = 0, - .timeout_ms = 0, }; - - return transport_.Send(Encode(request)) - .and_then([this]() { return transport_.Receive(); }) - .and_then([](const std::string& response_str) - -> std::expected { - auto response = Decode(response_str); - if (!response) { - return std::unexpected(response.error()); - } - if (response->status != common::Error::kOk) { - return std::unexpected(response->status); - } - return {}; - }); + return Transact(transport_, request) + .transform([](const UartEmulatorResponse&) {}); } auto HostUart::Receive(std::span buffer, uint32_t timeout_ms) @@ -73,31 +57,17 @@ auto HostUart::Receive(std::span buffer, uint32_t timeout_ms) } const UartEmulatorRequest request{ - .type = MessageType::kRequest, - .object = ObjectType::kUart, .name = name_, .operation = OperationType::kReceive, .data = {}, .size = buffer.size(), .timeout_ms = timeout_ms, }; - - return transport_.Send(Encode(request)) - .and_then([this]() { return transport_.Receive(); }) - .and_then([](const std::string& response_str) { - return Decode(response_str); - }) - .and_then([buffer](const UartEmulatorResponse& response) - -> std::expected { - if (response.status != common::Error::kOk) { - return std::unexpected(response.status); - } - - // Copy received data to buffer + return Transact(transport_, request) + .transform([buffer](const UartEmulatorResponse& response) { const size_t bytes_to_copy{ std::min(buffer.size(), response.data.size())}; std::copy_n(response.data.begin(), bytes_to_copy, buffer.begin()); - return bytes_to_copy; }); } @@ -117,13 +87,9 @@ auto HostUart::SendAsync(std::span data, send_callback_ = std::move(callback); const UartEmulatorRequest request{ - .type = MessageType::kRequest, - .object = ObjectType::kUart, .name = name_, .operation = OperationType::kSend, .data = std::vector(data.begin(), data.end()), - .size = 0, - .timeout_ms = 0, }; auto result = transport_.Send(Encode(request)); @@ -154,13 +120,10 @@ auto HostUart::ReceiveAsync( receive_buffer_.resize(buffer.size()); const UartEmulatorRequest request{ - .type = MessageType::kRequest, - .object = ObjectType::kUart, .name = name_, .operation = OperationType::kReceive, .data = {}, .size = buffer.size(), - .timeout_ms = 0, }; auto result = transport_.Send(Encode(request)); @@ -204,7 +167,7 @@ auto HostUart::SetRxHandler(std::function return {}; } -auto HostUart::Receive(const std::string_view& message) +auto HostUart::Receive(std::string_view message) -> std::expected { // First, try to decode as a request (unsolicited data) auto request_result = Decode(message); @@ -230,8 +193,6 @@ auto HostUart::Receive(const std::string_view& message) // Send acknowledgment response const UartEmulatorResponse ack_response{ - .type = MessageType::kResponse, - .object = ObjectType::kUart, .name = name_, .data = {}, .bytes_transferred = request.data.size(), diff --git a/src/libs/mcu/host/host_uart.hpp b/src/libs/mcu/host/host_uart.hpp index 21628eb..73f5815 100644 --- a/src/libs/mcu/host/host_uart.hpp +++ b/src/libs/mcu/host/host_uart.hpp @@ -48,7 +48,7 @@ class HostUart final : public Uart, public Receiver { -> std::expected override; // Receiver interface for handling async responses from emulator - auto Receive(const std::string_view& message) + auto Receive(std::string_view message) -> std::expected override; private: diff --git a/src/libs/mcu/host/receiver.hpp b/src/libs/mcu/host/receiver.hpp index f113c8f..aca7b27 100644 --- a/src/libs/mcu/host/receiver.hpp +++ b/src/libs/mcu/host/receiver.hpp @@ -7,9 +7,16 @@ namespace mcu { +/// A component that can handle messages arriving from the emulator. +/// +/// Contract: return the encoded reply when the message was handled; return an +/// unexpected error to mean "not mine" — the Dispatcher then keeps looking for +/// another receiver, and only reports kUnhandled if none accepts it. class Receiver { public: - virtual auto Receive(const std::string_view& message) + virtual ~Receiver() = default; + + [[nodiscard]] virtual auto Receive(std::string_view message) -> std::expected = 0; }; diff --git a/src/libs/mcu/host/test_dispatcher.cpp b/src/libs/mcu/host/test_dispatcher.cpp index e0420ac..36d8314 100644 --- a/src/libs/mcu/host/test_dispatcher.cpp +++ b/src/libs/mcu/host/test_dispatcher.cpp @@ -10,21 +10,21 @@ namespace mcu { namespace { -constexpr auto AcceptAll(const std::string_view& message) -> bool { +constexpr auto AcceptAll(std::string_view message) -> bool { static_cast(message); return true; } -constexpr auto RejectAll(const std::string_view& message) -> bool { +constexpr auto RejectAll(std::string_view message) -> bool { static_cast(message); return false; } -constexpr auto IsHello(const std::string_view& message) -> bool { +constexpr auto IsHello(std::string_view message) -> bool { return message == "Hello"; } -constexpr auto IsWorld(const std::string_view& message) -> bool { +constexpr auto IsWorld(std::string_view message) -> bool { return message == "World"; } @@ -36,7 +36,7 @@ class DispatcherTest : public ::testing::Test { class SimpleReceiver : public Receiver { public: - auto Receive(const std::string_view& message) + auto Receive(std::string_view message) -> std::expected override { received_message = message; return {"Received message"}; diff --git a/src/libs/mcu/host/test_host_i2c.cpp b/src/libs/mcu/host/test_host_i2c.cpp index 299d39c..a720290 100644 --- a/src/libs/mcu/host/test_host_i2c.cpp +++ b/src/libs/mcu/host/test_host_i2c.cpp @@ -22,7 +22,7 @@ class HostI2CTest : public ::testing::Test { protected: - static constexpr auto IsJson(const std::string_view& message) -> bool { + static constexpr auto IsJson(std::string_view message) -> bool { return message.starts_with("{") && message.ends_with("}"); } diff --git a/src/libs/mcu/host/test_host_uart.cpp b/src/libs/mcu/host/test_host_uart.cpp index 4d06d86..1dd2301 100644 --- a/src/libs/mcu/host/test_host_uart.cpp +++ b/src/libs/mcu/host/test_host_uart.cpp @@ -22,7 +22,7 @@ class HostUartTest : public ::testing::Test { protected: - static constexpr auto IsJson(const std::string_view& message) -> bool { + static constexpr auto IsJson(std::string_view message) -> bool { return message.starts_with("{") && message.ends_with("}"); } diff --git a/src/libs/mcu/host/test_zmq_transport_startup.cpp b/src/libs/mcu/host/test_zmq_transport_startup.cpp index 58159ce..9da91ff 100644 --- a/src/libs/mcu/host/test_zmq_transport_startup.cpp +++ b/src/libs/mcu/host/test_zmq_transport_startup.cpp @@ -78,7 +78,7 @@ class RecordingLogger : public common::Logger { // dispatcher rather than some other process that stole the endpoint. class EchoReceiver : public mcu::Receiver { public: - auto Receive(const std::string_view& message) + auto Receive(std::string_view message) -> std::expected override { return std::string{"echo:"} + std::string{message}; } @@ -300,7 +300,7 @@ TEST_F(ZmqTransportStartupTest, CreateFailsFastWhenBindEndpointIsUnbindable) { TEST_F(ZmqTransportStartupTest, CreateRefusesToStealEndpointFromLiveOwner) { EchoReceiver echo; const mcu::ReceiverMap receivers{ - {[](const std::string_view&) { return true; }, std::ref(echo)}}; + {[](std::string_view) { return true; }, std::ref(echo)}}; mcu::Dispatcher owner_dispatcher{receivers}; auto owner_config = MakeConfig(logger_); From 368a7201740d33fd1652a59a9f47fedd282288a5 Mon Sep 17 00:00:00 2001 From: Nehal Patel Date: Sat, 29 Aug 2026 15:52:43 +0000 Subject: [PATCH 09/17] refactor(mcu): trim Uart and I2C to the surface the host can honor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The interfaces promised transfer modes that nothing implements: the host I2C's SendDataDma was byte-identical to SendDataInterrupt (both just invoked the callback on the blocking result), and the host UART's IsBusy/Available/Flush returned constants while SendAsync/ReceiveAsync filled a receive_buffer_ member that nothing ever read. Four tests existed solely to prove the identical wrappers behaved identically. Keep what the host genuinely implements — blocking SendData/ReceiveData on I2C, and Init/Send/Receive/SetRxHandler on UART — and delete the rest, including HostUart's now-unused busy_/callback/buffer state and the async-response half of its dispatcher Receive path. The interface docs and docs/PROJECT_PLAN.md record async/interrupt/DMA modes as future work, to be reintroduced when a hardware platform can implement them with genuinely different behavior worth teaching. Co-Authored-By: Claude Fable 5 --- docs/PROJECT_PLAN.md | 3 + src/libs/mcu/host/host_i2c.cpp | 32 ----- src/libs/mcu/host/host_i2c.hpp | 16 --- src/libs/mcu/host/host_uart.cpp | 187 +++------------------------ src/libs/mcu/host/host_uart.hpp | 21 --- src/libs/mcu/host/test_host_i2c.cpp | 107 --------------- src/libs/mcu/host/test_host_uart.cpp | 34 ----- src/libs/mcu/i2c.hpp | 23 +--- src/libs/mcu/uart.hpp | 39 +----- 9 files changed, 32 insertions(+), 430 deletions(-) diff --git a/docs/PROJECT_PLAN.md b/docs/PROJECT_PLAN.md index d001e6a..d6df5f3 100644 --- a/docs/PROJECT_PLAN.md +++ b/docs/PROJECT_PLAN.md @@ -69,6 +69,9 @@ Explore modern C++ (C++23) and software engineering practices in embedded system - [ ] Add SPI abstraction - [ ] Add PWM abstraction - [ ] Add ADC abstraction +- [ ] Add async (interrupt- and DMA-driven) transfer modes to the UART and + I2C interfaces, once a hardware platform exists that can implement them + with genuinely different behavior than the blocking paths - [ ] Upload code coverage reports to GitHub pages - [ ] Increase test coverage for error paths diff --git a/src/libs/mcu/host/host_i2c.cpp b/src/libs/mcu/host/host_i2c.cpp index 0d9d48e..1fe4dc1 100644 --- a/src/libs/mcu/host/host_i2c.cpp +++ b/src/libs/mcu/host/host_i2c.cpp @@ -46,38 +46,6 @@ auto HostI2CController::ReceiveData(uint16_t address, }); } -auto HostI2CController::SendDataInterrupt( - uint16_t address, std::span data, - std::function)> callback) - -> std::expected { - callback(SendData(address, data)); - return {}; -} - -auto HostI2CController::ReceiveDataInterrupt( - uint16_t address, std::span buffer, - std::function)> callback) - -> std::expected { - callback(ReceiveData(address, buffer)); - return {}; -} - -auto HostI2CController::SendDataDma( - uint16_t address, std::span data, - std::function)> callback) - -> std::expected { - callback(SendData(address, data)); - return {}; -} - -auto HostI2CController::ReceiveDataDma( - uint16_t address, std::span buffer, - std::function)> callback) - -> std::expected { - callback(ReceiveData(address, buffer)); - return {}; -} - auto HostI2CController::Receive(std::string_view message) -> std::expected { static_cast(message); diff --git a/src/libs/mcu/host/host_i2c.hpp b/src/libs/mcu/host/host_i2c.hpp index 82e6d7f..48a94d9 100644 --- a/src/libs/mcu/host/host_i2c.hpp +++ b/src/libs/mcu/host/host_i2c.hpp @@ -27,22 +27,6 @@ class HostI2CController final : public I2CController, public Receiver { auto ReceiveData(uint16_t address, std::span buffer) -> std::expected override; - auto SendDataInterrupt( - uint16_t address, std::span data, - std::function)> callback) - -> std::expected override; - auto ReceiveDataInterrupt( - uint16_t address, std::span buffer, - std::function)> callback) - -> std::expected override; - - auto SendDataDma(uint16_t address, std::span data, - std::function)> - callback) -> std::expected override; - auto ReceiveDataDma( - uint16_t address, std::span buffer, - std::function)> callback) - -> std::expected override; auto Receive(std::string_view message) -> std::expected override; diff --git a/src/libs/mcu/host/host_uart.cpp b/src/libs/mcu/host/host_uart.cpp index dc6113a..51c5b7d 100644 --- a/src/libs/mcu/host/host_uart.cpp +++ b/src/libs/mcu/host/host_uart.cpp @@ -33,10 +33,6 @@ auto HostUart::Send(std::span data) return std::unexpected(common::Error::kInvalidState); } - if (busy_) { - return std::unexpected(common::Error::kInvalidOperation); - } - const UartEmulatorRequest request{ .name = name_, .operation = OperationType::kSend, @@ -52,10 +48,6 @@ auto HostUart::Receive(std::span buffer, uint32_t timeout_ms) return std::unexpected(common::Error::kInvalidState); } - if (busy_) { - return std::unexpected(common::Error::kInvalidOperation); - } - const UartEmulatorRequest request{ .name = name_, .operation = OperationType::kReceive, @@ -72,91 +64,6 @@ auto HostUart::Receive(std::span buffer, uint32_t timeout_ms) }); } -auto HostUart::SendAsync(std::span data, - std::function)> - callback) -> std::expected { - if (!initialized_) { - return std::unexpected(common::Error::kInvalidState); - } - - if (busy_) { - return std::unexpected(common::Error::kInvalidOperation); - } - - busy_ = true; - send_callback_ = std::move(callback); - - const UartEmulatorRequest request{ - .name = name_, - .operation = OperationType::kSend, - .data = std::vector(data.begin(), data.end()), - }; - - auto result = transport_.Send(Encode(request)); - if (!result) { - busy_ = false; - send_callback_ = {}; - return std::unexpected(result.error()); - } - - // Response will come asynchronously via Receive() method - return {}; -} - -auto HostUart::ReceiveAsync( - std::span buffer, - std::function)> callback) - -> std::expected { - if (!initialized_) { - return std::unexpected(common::Error::kInvalidState); - } - - if (busy_) { - return std::unexpected(common::Error::kInvalidOperation); - } - - busy_ = true; - receive_callback_ = std::move(callback); - receive_buffer_.resize(buffer.size()); - - const UartEmulatorRequest request{ - .name = name_, - .operation = OperationType::kReceive, - .data = {}, - .size = buffer.size(), - }; - - auto result = transport_.Send(Encode(request)); - if (!result) { - busy_ = false; - receive_callback_ = {}; - receive_buffer_.clear(); - return std::unexpected(result.error()); - } - - // Response will come asynchronously via Receive() method - // The buffer span will be filled when response arrives - return {}; -} - -auto HostUart::IsBusy() const -> bool { return busy_; } - -auto HostUart::Available() const -> size_t { - // For host implementation, we don't maintain a receive buffer - // Always return 0 (data is retrieved on-demand from emulator) - return 0; -} - -auto HostUart::Flush() -> std::expected { - if (!initialized_) { - return std::unexpected(common::Error::kInvalidState); - } - - // For host implementation, no buffering occurs - // Nothing to flush - return {}; -} - auto HostUart::SetRxHandler(std::function handler) -> std::expected { if (!initialized_) { @@ -167,94 +74,40 @@ auto HostUart::SetRxHandler(std::function return {}; } +// Messages arriving via the dispatcher are unsolicited requests from the +// emulator pushing data at the device; replies to this UART's own blocking +// operations return through Transport::Receive instead and never come here. auto HostUart::Receive(std::string_view message) -> std::expected { - // First, try to decode as a request (unsolicited data) auto request_result = Decode(message); - - // Handle unsolicited incoming data from emulator (Request type) - if (request_result && request_result->type == MessageType::kRequest) { - const auto& request = *request_result; - - // Verify this message is for us - if (request.name != name_) { - return std::unexpected(common::Error::kInvalidArgument); - } - - // Only handle "Receive" operation (emulator pushing data to device) - if (request.operation != OperationType::kReceive) { - return std::unexpected(common::Error::kInvalidOperation); - } - - // Invoke RxHandler if registered - if (rx_handler_ && !request.data.empty()) { - rx_handler_(request.data.data(), request.data.size()); - } - - // Send acknowledgment response - const UartEmulatorResponse ack_response{ - .name = name_, - .data = {}, - .bytes_transferred = request.data.size(), - .status = common::Error::kOk, - }; - - return Encode(ack_response); - } - - // Handle async operation responses - auto response_result = Decode(message); - if (!response_result) { + if (!request_result || request_result->type != MessageType::kRequest) { return std::unexpected(common::Error::kInvalidArgument); } - const auto& response = *response_result; + const auto& request = *request_result; // Verify this message is for us - if (response.name != name_) { + if (request.name != name_) { return std::unexpected(common::Error::kInvalidArgument); } - if (!busy_) { - return std::unexpected(common::Error::kInvalidState); + // Only handle "Receive" operation (emulator pushing data to device) + if (request.operation != OperationType::kReceive) { + return std::unexpected(common::Error::kInvalidOperation); } - // Handle async send response - if (send_callback_) { - auto callback = std::move(send_callback_); - send_callback_ = {}; - busy_ = false; - - if (response.status != common::Error::kOk) { - callback(std::unexpected(response.status)); - } else { - callback({}); - } - return std::string{}; // Message consumed + // Invoke RxHandler if registered + if (rx_handler_ && !request.data.empty()) { + rx_handler_(request.data.data(), request.data.size()); } - // Handle async receive response - if (receive_callback_) { - auto callback = std::move(receive_callback_); - receive_callback_ = {}; - busy_ = false; - - if (response.status != common::Error::kOk) { - receive_buffer_.clear(); - callback(std::unexpected(response.status)); - } else { - // Copy received data to buffer (stored for the callback) - const size_t bytes_received{response.bytes_transferred}; - receive_buffer_.resize(bytes_received); - std::copy_n(response.data.begin(), bytes_received, - receive_buffer_.begin()); - callback(bytes_received); - } - return std::string{}; // Message consumed - } - - // No callback registered - unexpected state - busy_ = false; - return std::unexpected(common::Error::kInvalidState); + // Send acknowledgment response + const UartEmulatorResponse ack_response{ + .name = name_, + .data = {}, + .bytes_transferred = request.data.size(), + .status = common::Error::kOk, + }; + return Encode(ack_response); } } // namespace mcu diff --git a/src/libs/mcu/host/host_uart.hpp b/src/libs/mcu/host/host_uart.hpp index 73f5815..b95423f 100644 --- a/src/libs/mcu/host/host_uart.hpp +++ b/src/libs/mcu/host/host_uart.hpp @@ -31,19 +31,6 @@ class HostUart final : public Uart, public Receiver { auto Receive(std::span buffer, uint32_t timeout_ms) -> std::expected override; - auto SendAsync(std::span data, - std::function)> - callback) -> std::expected override; - - auto ReceiveAsync( - std::span buffer, - std::function)> callback) - -> std::expected override; - - [[nodiscard]] auto IsBusy() const -> bool override; - [[nodiscard]] auto Available() const -> size_t override; - auto Flush() -> std::expected override; - auto SetRxHandler(std::function handler) -> std::expected override; @@ -56,17 +43,9 @@ class HostUart final : public Uart, public Receiver { Transport& transport_; UartConfig config_{}; bool initialized_{false}; - bool busy_{false}; - - // Async callback storage - std::function)> send_callback_; - std::function)> receive_callback_; // Receive handler for unsolicited incoming data std::function rx_handler_; - - // Receive buffer for async operations - std::vector receive_buffer_; }; } // namespace mcu diff --git a/src/libs/mcu/host/test_host_i2c.cpp b/src/libs/mcu/host/test_host_i2c.cpp index a720290..4a74057 100644 --- a/src/libs/mcu/host/test_host_i2c.cpp +++ b/src/libs/mcu/host/test_host_i2c.cpp @@ -285,110 +285,3 @@ TEST_F(HostI2CTest, ReceivePartialData) { EXPECT_TRUE(std::equal(recv_buffer.begin(), recv_buffer.end(), send_data.begin(), send_data.begin() + 5)); } - -TEST_F(HostI2CTest, SendDataInterrupt) { - const uint16_t device_address{0x42}; - const std::array send_data{std::byte{0xAA}, std::byte{0xBB}, - std::byte{0xCC}}; - - bool callback_called{false}; - std::expected callback_result{}; - - auto result = - i2c_->SendDataInterrupt(device_address, send_data, - [&callback_called, &callback_result]( - std::expected result) { - callback_called = true; - callback_result = result; - }); - - EXPECT_TRUE(result); - EXPECT_TRUE(callback_called); - EXPECT_TRUE(callback_result); -} - -TEST_F(HostI2CTest, ReceiveDataInterrupt) { - const uint16_t device_address{0x50}; - const std::array send_data{std::byte{0x01}, std::byte{0x02}, - std::byte{0x03}, std::byte{0x04}}; - - // First send data - auto send_result = i2c_->SendData(device_address, send_data); - ASSERT_TRUE(send_result); - - bool callback_called{false}; - std::expected callback_result{ - std::unexpected(common::Error::kUnknown)}; - std::array recv_buffer{}; - - auto result = i2c_->ReceiveDataInterrupt( - device_address, recv_buffer, - [&callback_called, - &callback_result](std::expected result) { - callback_called = true; - callback_result = result; - }); - - EXPECT_TRUE(result); - EXPECT_TRUE(callback_called); - ASSERT_TRUE(callback_result); - - const size_t bytes_received = callback_result.value(); - EXPECT_EQ(bytes_received, send_data.size()); - EXPECT_TRUE(std::equal(recv_buffer.begin(), recv_buffer.end(), - send_data.begin(), send_data.end())); -} - -TEST_F(HostI2CTest, SendDataDma) { - const uint16_t device_address{0x42}; - const std::array send_data{std::byte{0xDE}, std::byte{0xAD}, - std::byte{0xBE}}; - - bool callback_called{false}; - std::expected callback_result{}; - - auto result = - i2c_->SendDataDma(device_address, send_data, - [&callback_called, &callback_result]( - std::expected result) { - callback_called = true; - callback_result = result; - }); - - EXPECT_TRUE(result); - EXPECT_TRUE(callback_called); - EXPECT_TRUE(callback_result); -} - -TEST_F(HostI2CTest, ReceiveDataDma) { - const uint16_t device_address{0x55}; - const std::array send_data{std::byte{0x10}, std::byte{0x20}, - std::byte{0x30}, std::byte{0x40}, - std::byte{0x50}}; - - // First send data - auto send_result = i2c_->SendData(device_address, send_data); - ASSERT_TRUE(send_result); - - bool callback_called{false}; - std::expected callback_result{ - std::unexpected(common::Error::kUnknown)}; - std::array recv_buffer{}; - - auto result = - i2c_->ReceiveDataDma(device_address, recv_buffer, - [&callback_called, &callback_result]( - std::expected result) { - callback_called = true; - callback_result = result; - }); - - EXPECT_TRUE(result); - EXPECT_TRUE(callback_called); - ASSERT_TRUE(callback_result); - - const size_t bytes_received = callback_result.value(); - EXPECT_EQ(bytes_received, send_data.size()); - EXPECT_TRUE(std::equal(recv_buffer.begin(), recv_buffer.end(), - send_data.begin(), send_data.end())); -} diff --git a/src/libs/mcu/host/test_host_uart.cpp b/src/libs/mcu/host/test_host_uart.cpp index 1dd2301..c82e918 100644 --- a/src/libs/mcu/host/test_host_uart.cpp +++ b/src/libs/mcu/host/test_host_uart.cpp @@ -236,40 +236,6 @@ TEST_F(HostUartTest, ReceiveWithoutInit) { EXPECT_EQ(result.error(), common::Error::kInvalidState); } -TEST_F(HostUartTest, IsBusy) { - const mcu::UartConfig config{}; - auto init_result = uart_->Init(config); - ASSERT_TRUE(init_result); - - EXPECT_FALSE(uart_->IsBusy()); - - const std::array send_data{std::byte{0x01}, std::byte{0x02}, - std::byte{0x03}, std::byte{0x04}, - std::byte{0x05}}; - std::ignore = uart_->Send(send_data); - - EXPECT_FALSE(uart_->IsBusy()); // Blocking operation completes immediately -} - -TEST_F(HostUartTest, Available) { - const mcu::UartConfig config{}; - auto init_result = uart_->Init(config); - ASSERT_TRUE(init_result); - - // For host implementation, Available() always returns 0 - // (data is retrieved on-demand from emulator) - EXPECT_EQ(uart_->Available(), 0); -} - -TEST_F(HostUartTest, Flush) { - const mcu::UartConfig config{}; - auto init_result = uart_->Init(config); - ASSERT_TRUE(init_result); - - auto result = uart_->Flush(); - EXPECT_TRUE(result); -} - TEST_F(HostUartTest, RxHandlerUnsolicitedData) { // Initialize UART const mcu::UartConfig config{}; diff --git a/src/libs/mcu/i2c.hpp b/src/libs/mcu/i2c.hpp index 018307d..ddeddae 100644 --- a/src/libs/mcu/i2c.hpp +++ b/src/libs/mcu/i2c.hpp @@ -3,13 +3,16 @@ #include #include #include -#include #include #include "libs/common/error.hpp" namespace mcu { +/// @brief I2C controller (bus master) interface, blocking transfers only. +/// Interrupt- and DMA-driven transfer modes are future work: they join this +/// interface when a hardware platform can implement them with genuinely +/// different behavior (see docs/PROJECT_PLAN.md). class I2CController { public: virtual ~I2CController() = default; @@ -25,24 +28,6 @@ class I2CController { [[nodiscard]] virtual auto ReceiveData(uint16_t address, std::span buffer) -> std::expected = 0; - - [[nodiscard]] virtual auto SendDataInterrupt( - uint16_t address, std::span data, - std::function)> callback) - -> std::expected = 0; - [[nodiscard]] virtual auto ReceiveDataInterrupt( - uint16_t address, std::span buffer, - std::function)> callback) - -> std::expected = 0; - - [[nodiscard]] virtual auto SendDataDma( - uint16_t address, std::span data, - std::function)> callback) - -> std::expected = 0; - [[nodiscard]] virtual auto ReceiveDataDma( - uint16_t address, std::span buffer, - std::function)> callback) - -> std::expected = 0; }; } // namespace mcu diff --git a/src/libs/mcu/uart.hpp b/src/libs/mcu/uart.hpp index 97ced8a..acdaf72 100644 --- a/src/libs/mcu/uart.hpp +++ b/src/libs/mcu/uart.hpp @@ -38,8 +38,11 @@ struct UartConfig { } flow_control{FlowControl::kNone}; }; -/// @brief UART peripheral interface -/// Implementations may use interrupts, DMA, or blocking internally +/// @brief UART peripheral interface: blocking transfers plus an RxHandler for +/// unsolicited incoming data. +/// Async (interrupt/DMA-driven) send and receive are future work: they join +/// this interface when a hardware platform can implement them with genuinely +/// different behavior (see docs/PROJECT_PLAN.md). class Uart { public: virtual ~Uart() = default; @@ -64,38 +67,6 @@ class Uart { uint32_t timeout_ms) -> std::expected = 0; - /// @brief Send data asynchronously - /// Implementation may use interrupts or DMA - /// @param data Span of bytes to send - /// @param callback Called when transfer completes - /// @return Success or error code - [[nodiscard]] virtual auto SendAsync( - std::span data, - std::function)> callback) - -> std::expected = 0; - - /// @brief Receive data asynchronously - /// Implementation may use interrupts or DMA - /// @param buffer Buffer to store received data - /// @param callback Called when data is received (with number of bytes) - /// @return Success or error code - [[nodiscard]] virtual auto ReceiveAsync( - std::span buffer, - std::function)> callback) - -> std::expected = 0; - - /// @brief Check if UART is busy transmitting - /// @return True if busy, false otherwise - [[nodiscard]] virtual auto IsBusy() const -> bool = 0; - - /// @brief Get number of bytes available to read - /// @return Number of bytes in receive buffer - [[nodiscard]] virtual auto Available() const -> size_t = 0; - - /// @brief Flush transmit buffer (wait for all data to be sent) - /// @return Success or error code - [[nodiscard]] virtual auto Flush() -> std::expected = 0; - /// @brief Set handler for unsolicited incoming data /// Similar to pin interrupts, this allows the UART to notify the /// application when data arrives asynchronously (e.g., from external source) From bbb294ee2f871e89e00e4b6a17e91effc11ecac0 Mon Sep 17 00:00:00 2001 From: Nehal Patel Date: Sat, 29 Aug 2026 15:55:22 +0000 Subject: [PATCH 10/17] refactor(apps): one RunApp helper; honest errors at the top layer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The three apps were the same file three times: identical headers modulo the class name, and identical AppMain bodies that discarded the real error by returning kUnknown — teaching a reader to throw away error information at exactly the layer that reports it. AppMain is now a one-line RunApp(board) call (app.hpp) that propagates the first real error from Init or Run. Blinky::Run's loop dressed a no-op in monadic ceremony — the status was reassigned each iteration and never inspected, and its or_else was the identity on the error channel. It now reads as the two checks it is. The unreachable 'return {}' after each app's infinite loop is gone. main() reported failure through iostream and left via exit(), which skips destructors — so the transport's careful shutdown path never ran on failure. It now returns (destructors run), reports the actual error code via std::println, and keeps a documented top-level catch: project code is exception-free, but the host build links libraries that throw (cppzmq, nlohmann-json), and main is where an escaped exception becomes a failing exit code. Co-Authored-By: Claude Fable 5 --- src/apps/app.hpp | 13 ++++++++++++ src/apps/blinky/blinky.cpp | 27 ++++++++---------------- src/apps/i2c_demo/i2c_demo.cpp | 11 +--------- src/apps/uart_echo/uart_echo.cpp | 10 +-------- src/libs/board/host/main.cpp | 35 ++++++++++++++++++++++---------- 5 files changed, 47 insertions(+), 49 deletions(-) diff --git a/src/apps/app.hpp b/src/apps/app.hpp index 928fd8b..889744a 100644 --- a/src/apps/app.hpp +++ b/src/apps/app.hpp @@ -6,5 +6,18 @@ #include "libs/common/error.hpp" namespace app { + +/// Implemented once per application; the platform entry point (main) calls it +/// with the concrete board. auto AppMain(board::Board& board) -> std::expected; + +/// Construct, initialize, and run an application, propagating the first +/// error. Every AppMain is a one-line call to this. +template +auto RunApp(board::Board& board) -> std::expected { + App application{board}; + return application.Init().and_then( + [&application] { return application.Run(); }); +} + } // namespace app diff --git a/src/apps/blinky/blinky.cpp b/src/apps/blinky/blinky.cpp index 8a629c0..21df265 100644 --- a/src/apps/blinky/blinky.cpp +++ b/src/apps/blinky/blinky.cpp @@ -14,30 +14,19 @@ namespace app { using std::chrono::operator""ms; auto AppMain(board::Board& board) -> std::expected { - Blinky blinky{board}; - if (!blinky.Init()) { - return std::unexpected(common::Error::kUnknown); - } - if (!blinky.Run()) { - return std::unexpected(common::Error::kUnknown); - } - return {}; + return RunApp(board); } auto Blinky::Run() -> std::expected { - auto status{board_.UserLed1().SetHigh()}; - + if (auto status = board_.UserLed1().SetHigh(); !status) { + return status; + } while (true) { - status = status - .and_then([this]() { - mcu::Delay(200ms); - return board_.UserLed1().Toggle(); - }) - .or_else([](auto error) -> std::expected { - return std::unexpected(error); - }); + mcu::Delay(200ms); + if (auto status = board_.UserLed1().Toggle(); !status) { + return status; + } } - return {}; } auto Blinky::Init() -> std::expected { diff --git a/src/apps/i2c_demo/i2c_demo.cpp b/src/apps/i2c_demo/i2c_demo.cpp index a14a2f3..d4d6ecf 100644 --- a/src/apps/i2c_demo/i2c_demo.cpp +++ b/src/apps/i2c_demo/i2c_demo.cpp @@ -17,14 +17,7 @@ namespace app { using std::chrono::operator""ms; auto AppMain(board::Board& board) -> std::expected { - I2CDemo i2c_demo{board}; - if (!i2c_demo.Init()) { - return std::unexpected(common::Error::kUnknown); - } - if (!i2c_demo.Run()) { - return std::unexpected(common::Error::kUnknown); - } - return {}; + return RunApp(board); } auto I2CDemo::Init() -> std::expected { @@ -86,8 +79,6 @@ auto I2CDemo::Run() -> std::expected { // Delay before next iteration mcu::Delay(200ms); } - - return {}; } } // namespace app diff --git a/src/apps/uart_echo/uart_echo.cpp b/src/apps/uart_echo/uart_echo.cpp index 49173f1..4cf1425 100644 --- a/src/apps/uart_echo/uart_echo.cpp +++ b/src/apps/uart_echo/uart_echo.cpp @@ -16,14 +16,7 @@ namespace app { using std::chrono::operator""ms; auto AppMain(board::Board& board) -> std::expected { - UartEcho uart_echo{board}; - if (!uart_echo.Init()) { - return std::unexpected(common::Error::kUnknown); - } - if (!uart_echo.Run()) { - return std::unexpected(common::Error::kUnknown); - } - return {}; + return RunApp(board); } auto UartEcho::Init() -> std::expected { @@ -59,7 +52,6 @@ auto UartEcho::Run() -> std::expected { mcu::Delay(200ms); std::ignore = board_.UserLed2().Toggle(); } - return {}; } } // namespace app diff --git a/src/libs/board/host/main.cpp b/src/libs/board/host/main.cpp index a4a8c44..f6b41de 100644 --- a/src/libs/board/host/main.cpp +++ b/src/libs/board/host/main.cpp @@ -1,21 +1,34 @@ -#include +#include +#include +#include +#include +#include #include "apps/app.hpp" #include "libs/board/host/host_board.hpp" auto main() -> int { + // Project code is exception-free (std::expected throughout), but the host + // build links libraries that can throw (cppzmq, nlohmann-json). main is the + // boundary that converts an escaped exception into a failing exit code — + // via return, not exit(), so the board's destructor still runs its + // transport shutdown. try { board::HostBoard board{}; - - if (!app::AppMain(board)) { - std::cout << "app_main failed" << '\n'; - exit(EXIT_FAILURE); + if (auto result = app::AppMain(board); !result) { + std::println(stderr, "AppMain failed (error {})", + std::to_underlying(result.error())); + return EXIT_FAILURE; } - - } catch (std::exception& exc) { - std::cerr << exc.what() << '\n'; - exit(EXIT_FAILURE); + } catch (const std::exception& exc) { + // C stdio in the handlers: the one thing an exception handler must not + // do is throw. + static_cast( + std::fprintf(stderr, "Unhandled exception: %s\n", exc.what())); + return EXIT_FAILURE; + } catch (...) { + static_cast(std::fputs("Unhandled exception\n", stderr)); + return EXIT_FAILURE; } - - return (EXIT_SUCCESS); + return EXIT_SUCCESS; } From ee8d0fa636a52758025780052b224895e8ebc6c4 Mon Sep 17 00:00:00 2001 From: Nehal Patel Date: Sat, 29 Aug 2026 15:57:28 +0000 Subject: [PATCH 11/17] refactor(mcu/host): dispatch by receiver claim, not predicate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ReceiverMap paired every receiver with a routing predicate, but the only predicate ever installed — five times over, from three copies of the same IsJson helper — was a JSON sniff that told the dispatcher nothing about which receiver owned the message. The real routing already lived inside each receiver, which decodes the message and checks the addressed name. So the predicate layer was paid for and unused. ReceiverMap is now just the ordered receivers; Dispatch offers the message to each until one claims it (the Receiver contract's 'unexpected means not mine'), and IsJson is gone from all three sites. HostBoard no longer builds its Dispatcher twice: the dispatcher observes the receiver map by reference, so it can be a plain member constructed up front while Init() fills the map after the components exist — which also retires the six-step narration that explained the double construction. The dispatcher tests now exercise the claim contract with a NamedReceiver that accepts only its own messages (storing what it received by value — the old fixture kept a string_view into a dead temporary), the two which-receiver-wins cases collapse into one parameterized test, and the empty do-nothing fixture is gone. Co-Authored-By: Claude Fable 5 --- src/libs/board/host/host_board.cpp | 21 +-- src/libs/board/host/host_board.hpp | 9 +- src/libs/mcu/host/dispatcher.hpp | 18 ++- src/libs/mcu/host/test_dispatcher.cpp | 125 +++++++----------- src/libs/mcu/host/test_host_i2c.cpp | 6 +- src/libs/mcu/host/test_host_uart.cpp | 6 +- .../mcu/host/test_zmq_transport_startup.cpp | 3 +- 7 files changed, 68 insertions(+), 120 deletions(-) diff --git a/src/libs/board/host/host_board.cpp b/src/libs/board/host/host_board.cpp index 8d3c20f..bb4ada6 100644 --- a/src/libs/board/host/host_board.cpp +++ b/src/libs/board/host/host_board.cpp @@ -12,36 +12,27 @@ namespace board { HostBoard::HostBoard(Endpoints endpoints) : endpoints_(std::move(endpoints)) {} auto HostBoard::Init() -> std::expected { - // Step 1: Create the dispatcher with an empty receiver map initially - // We'll build the actual receiver map after creating components - dispatcher_.emplace(receiver_map_); - - // Step 2: Create the transport with the dispatcher using configured endpoints + // Ordering: the transport needs the dispatcher (a member, already built), + // the components need the transport, and the receiver map needs the + // components. The dispatcher sees the filled map through its reference. auto transport_result{mcu::ZmqTransport::Create( - endpoints_.to_emulator, endpoints_.from_emulator, *dispatcher_)}; + endpoints_.to_emulator, endpoints_.from_emulator, dispatcher_)}; if (!transport_result) { return std::unexpected(transport_result.error()); } zmq_transport_ = std::move(transport_result.value()); - // Step 3: Create all components with the transport user_led_1_ = std::make_unique("LED 1", *zmq_transport_); user_led_2_ = std::make_unique("LED 2", *zmq_transport_); user_button_1_ = std::make_unique("Button 1", *zmq_transport_); uart_1_ = std::make_unique("UART 1", *zmq_transport_); i2c_1_ = std::make_unique("I2C 1", *zmq_transport_); - // Step 4: Now build the receiver map with all components receiver_map_ = mcu::ReceiverMap{ - {IsJson, std::ref(*user_led_1_)}, {IsJson, std::ref(*user_led_2_)}, - {IsJson, std::ref(*user_button_1_)}, {IsJson, std::ref(*uart_1_)}, - {IsJson, std::ref(*i2c_1_)}, + std::ref(*user_led_1_), std::ref(*user_led_2_), std::ref(*user_button_1_), + std::ref(*uart_1_), std::ref(*i2c_1_), }; - // Step 5: Recreate the dispatcher with the actual receiver map - dispatcher_.emplace(receiver_map_); - - // Step 6: Configure pins return user_led_1_->Configure(mcu::PinDirection::kOutput) .and_then([this]() { return user_led_2_->Configure(mcu::PinDirection::kOutput); diff --git a/src/libs/board/host/host_board.hpp b/src/libs/board/host/host_board.hpp index c9c881a..628724f 100644 --- a/src/libs/board/host/host_board.hpp +++ b/src/libs/board/host/host_board.hpp @@ -41,10 +41,6 @@ class HostBoard : public Board { auto Uart1() -> mcu::Uart& override; private: - static constexpr auto IsJson(std::string_view message) -> bool { - return message.starts_with("{") && message.ends_with("}"); - } - // Endpoint configuration (declared first to be initialized first) Endpoints endpoints_{}; @@ -55,9 +51,10 @@ class HostBoard : public Board { std::unique_ptr uart_1_; std::unique_ptr i2c_1_; - // Receiver map and dispatcher (built in Init() after components exist) + // The dispatcher observes receiver_map_ by reference, so it can be built + // here while the map is filled later, in Init(), once the components exist. mcu::ReceiverMap receiver_map_; - std::optional dispatcher_; + mcu::Dispatcher dispatcher_{receiver_map_}; std::unique_ptr zmq_transport_; }; } // namespace board diff --git a/src/libs/mcu/host/dispatcher.hpp b/src/libs/mcu/host/dispatcher.hpp index ae6e4f8..9a43b04 100644 --- a/src/libs/mcu/host/dispatcher.hpp +++ b/src/libs/mcu/host/dispatcher.hpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include "libs/common/error.hpp" @@ -11,10 +10,12 @@ namespace mcu { -using ReceiverMap = - std::vector, - std::reference_wrapper>>; +using ReceiverMap = std::vector>; +/// Offers each incoming message to the receivers in order; the first one to +/// accept it (see the Receiver contract) produces the reply. Receivers decide +/// for themselves whether a message is theirs — typically by decoding it and +/// checking the addressed peripheral name. class Dispatcher { public: explicit Dispatcher(const ReceiverMap& receivers) : receivers_{receivers} {} @@ -27,12 +28,9 @@ class Dispatcher { [[nodiscard]] auto Dispatch(std::string_view message) const -> std::expected { - for (const auto& [predicate, receiver_ref] : receivers_) { - if (predicate(message)) { - auto reply = receiver_ref.get().Receive(message); - if (reply.has_value()) { - return reply; - } + for (const auto& receiver : receivers_) { + if (auto reply = receiver.get().Receive(message); reply.has_value()) { + return reply; } } return std::unexpected(common::Error::kUnhandled); diff --git a/src/libs/mcu/host/test_dispatcher.cpp b/src/libs/mcu/host/test_dispatcher.cpp index 36d8314..8ff4674 100644 --- a/src/libs/mcu/host/test_dispatcher.cpp +++ b/src/libs/mcu/host/test_dispatcher.cpp @@ -1,6 +1,8 @@ #include +#include #include +#include #include #include "dispatcher.hpp" @@ -10,97 +12,66 @@ namespace mcu { namespace { -constexpr auto AcceptAll(std::string_view message) -> bool { - static_cast(message); - return true; -} +// Mirrors the real receivers' contract: accept only messages addressed to it +// (an unexpected return means "not mine, keep looking"), and record what it +// accepted. +class NamedReceiver : public Receiver { + public: + explicit NamedReceiver(std::string name) : name_{std::move(name)} {} + + auto Receive(std::string_view message) + -> std::expected override { + if (message != name_) { + return std::unexpected(common::Error::kInvalidArgument); + } + received_message = std::string{message}; + return {"Received message"}; + } -constexpr auto RejectAll(std::string_view message) -> bool { - static_cast(message); - return false; -} + std::string received_message; -constexpr auto IsHello(std::string_view message) -> bool { - return message == "Hello"; -} + private: + std::string name_; +}; -constexpr auto IsWorld(std::string_view message) -> bool { - return message == "World"; -} +struct RoutingCase { + std::string message; + size_t expected_receiver; +}; -class DispatcherTest : public ::testing::Test { - protected: - void SetUp() override {} +class DispatcherRoutingTest : public ::testing::TestWithParam {}; - void TearDown() override {} +TEST_P(DispatcherRoutingTest, DeliversToTheReceiverThatClaimsTheMessage) { + const auto& [message, expected_receiver] = GetParam(); + std::array receivers{NamedReceiver{"Hello"}, + NamedReceiver{"World"}}; + const ReceiverMap receiver_map{std::ref(receivers[0]), + std::ref(receivers[1])}; + const Dispatcher dispatcher{receiver_map}; - class SimpleReceiver : public Receiver { - public: - auto Receive(std::string_view message) - -> std::expected override { - received_message = message; - return {"Received message"}; - } - std::string_view received_message; - }; -}; + auto reply = dispatcher.Dispatch(message); -TEST_F(DispatcherTest, DispatchMessage) { - const std::string sent_message{"Hello"}; - SimpleReceiver receiver; - const ReceiverMap receiver_map{{AcceptAll, std::ref(receiver)}}; - const Dispatcher dispatcher{receiver_map}; - auto reply = dispatcher.Dispatch(sent_message); - EXPECT_TRUE(reply.has_value()); + ASSERT_TRUE(reply.has_value()); EXPECT_EQ(reply.value(), "Received message"); - EXPECT_EQ(receiver.received_message, sent_message); + for (size_t index = 0; index < receivers.size(); ++index) { + const auto& expected = index == expected_receiver ? message : std::string{}; + EXPECT_EQ(receivers.at(index).received_message, expected); + } } -TEST_F(DispatcherTest, DispatchMessageReject) { - const std::string sent_message{"Hello"}; - SimpleReceiver receiver; - const ReceiverMap receiver_map{{RejectAll, std::ref(receiver)}}; - const Dispatcher dispatcher{receiver_map}; - auto reply = dispatcher.Dispatch(sent_message); - EXPECT_FALSE(reply.has_value()); - EXPECT_EQ(receiver.received_message, ""); -} +INSTANTIATE_TEST_SUITE_P(EachReceiver, DispatcherRoutingTest, + ::testing::Values(RoutingCase{"Hello", 0}, + RoutingCase{"World", 1})); -TEST_F(DispatcherTest, DispatchMessageMultipleReceivers) { - const std::string sent_message{"Hello"}; - SimpleReceiver receiver1; - SimpleReceiver receiver2; - const ReceiverMap receiver_map{{IsHello, std::ref(receiver1)}, - {IsWorld, std::ref(receiver2)}}; +TEST(DispatcherTest, ReportsUnhandledWhenNoReceiverClaimsTheMessage) { + NamedReceiver receiver{"Hello"}; + const ReceiverMap receiver_map{std::ref(receiver)}; const Dispatcher dispatcher{receiver_map}; - auto reply = dispatcher.Dispatch(sent_message); - EXPECT_TRUE(reply.has_value()); - EXPECT_EQ(reply.value(), "Received message"); - EXPECT_EQ(receiver1.received_message, sent_message); - EXPECT_EQ(receiver2.received_message, ""); -} -TEST_F(DispatcherTest, DispatchMessageMultipleReceiversSecond) { - const std::string sent_message{"World"}; - SimpleReceiver receiver1; - SimpleReceiver receiver2; - const ReceiverMap receiver_map{{IsHello, std::ref(receiver1)}, - {IsWorld, std::ref(receiver2)}}; - const Dispatcher dispatcher{receiver_map}; - auto reply = dispatcher.Dispatch(sent_message); - EXPECT_TRUE(reply.has_value()); - EXPECT_EQ(reply.value(), "Received message"); - EXPECT_EQ(receiver1.received_message, ""); - EXPECT_EQ(receiver2.received_message, sent_message); -} + auto reply = dispatcher.Dispatch("Unhandled"); -TEST_F(DispatcherTest, DispatchMessageUnhandled) { - const std::string sent_message{"Unhandled"}; - SimpleReceiver receiver; - const ReceiverMap receiver_map{{IsHello, std::ref(receiver)}}; - const Dispatcher dispatcher{receiver_map}; - auto reply = dispatcher.Dispatch(sent_message); - EXPECT_FALSE(reply.has_value()); + ASSERT_FALSE(reply.has_value()); + EXPECT_EQ(reply.error(), common::Error::kUnhandled); EXPECT_EQ(receiver.received_message, ""); } diff --git a/src/libs/mcu/host/test_host_i2c.cpp b/src/libs/mcu/host/test_host_i2c.cpp index 4a74057..999727f 100644 --- a/src/libs/mcu/host/test_host_i2c.cpp +++ b/src/libs/mcu/host/test_host_i2c.cpp @@ -22,10 +22,6 @@ class HostI2CTest : public ::testing::Test { protected: - static constexpr auto IsJson(std::string_view message) -> bool { - return message.starts_with("{") && message.ends_with("}"); - } - // Per-process endpoints. gtest_discover_tests gives every case its own // process, so a fixed path made `ctest -j` cases contend for one endpoint -- // silently corrupting each other before EndpointLock, loudly after. @@ -60,7 +56,7 @@ class HostI2CTest : public ::testing::Test { std::make_unique("I2C 1", *device_transport_); // Add I2C to receiver map (dispatcher holds reference, so this updates it) - receiver_map_storage_.emplace_back(IsJson, std::ref(*i2c_)); + receiver_map_storage_.emplace_back(std::ref(*i2c_)); // Wait for the condition rather than for a duration. On a PAIR socket a // send succeeds only once a pipe to the peer exists, so a successful probe diff --git a/src/libs/mcu/host/test_host_uart.cpp b/src/libs/mcu/host/test_host_uart.cpp index c82e918..de7957e 100644 --- a/src/libs/mcu/host/test_host_uart.cpp +++ b/src/libs/mcu/host/test_host_uart.cpp @@ -22,10 +22,6 @@ class HostUartTest : public ::testing::Test { protected: - static constexpr auto IsJson(std::string_view message) -> bool { - return message.starts_with("{") && message.ends_with("}"); - } - // Per-process endpoints. gtest_discover_tests gives every case its own // process, so a fixed path made `ctest -j` cases contend for one endpoint -- // silently corrupting each other before EndpointLock, loudly after. @@ -59,7 +55,7 @@ class HostUartTest : public ::testing::Test { uart_ = std::make_unique("UART 1", *device_transport_); // Add UART to receiver map (dispatcher holds reference, so this updates it) - receiver_map_storage_.emplace_back(IsJson, std::ref(*uart_)); + receiver_map_storage_.emplace_back(std::ref(*uart_)); // Wait for the condition rather than for a duration. On a PAIR socket a // send succeeds only once a pipe to the peer exists, so a successful probe diff --git a/src/libs/mcu/host/test_zmq_transport_startup.cpp b/src/libs/mcu/host/test_zmq_transport_startup.cpp index 9da91ff..5c05c80 100644 --- a/src/libs/mcu/host/test_zmq_transport_startup.cpp +++ b/src/libs/mcu/host/test_zmq_transport_startup.cpp @@ -299,8 +299,7 @@ TEST_F(ZmqTransportStartupTest, CreateFailsFastWhenBindEndpointIsUnbindable) { // the only way to keep the first owner's endpoint intact. TEST_F(ZmqTransportStartupTest, CreateRefusesToStealEndpointFromLiveOwner) { EchoReceiver echo; - const mcu::ReceiverMap receivers{ - {[](std::string_view) { return true; }, std::ref(echo)}}; + const mcu::ReceiverMap receivers{std::ref(echo)}; mcu::Dispatcher owner_dispatcher{receivers}; auto owner_config = MakeConfig(logger_); From 390ac61429d843c092b597de7ead73c21a5a918b Mon Sep 17 00:00:00 2001 From: Nehal Patel Date: Sat, 29 Aug 2026 16:01:24 +0000 Subject: [PATCH 12/17] refactor(transport): extract endpoint arbitration; present-tense comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ZmqTransport carried ~130 lines of POSIX endpoint arbitration — flock ownership, an AF_UNIX connect(2) liveness probe, and their rationale — inline in the class a learner reads to understand 'how does the C++ side talk to the emulator'. That machinery now lives in its own translation unit (endpoint_lock.hpp/.cpp) with its prose intact, and ServerThread's bind phase reads as the three lines of intent it always was: take the lock, probe for a foreign owner, bind. TransportConfig is an aggregate again. Its Logger& member forced two user-provided constructors, which killed designated initializers — the idiom the rest of the codebase leans on — and both transport test files had grown a MakeConfig helper with an apologetic comment to work around it. The member is now a reference_wrapper defaulting to DefaultLogger(), callers write TransportConfig{.send_timeout = ..., .logger = ...}, and the workarounds are gone. Comments that narrated the code's history in past tense are rewritten to describe present behavior; the history lives here in the log instead. The 'reduce cognitive complexity' apology over the Log helpers is gone — they stay on their merits as brevity. Also: delay.hpp puts #pragma once first, names its parameter duration (callers pass milliseconds; only the conversion target was micro), and documents the blocking semantics; transport.hpp loses its empty private section and gains [[nodiscard]] and an interface comment. Co-Authored-By: Claude Fable 5 --- src/libs/mcu/delay.hpp | 11 +- src/libs/mcu/host/CMakeLists.txt | 4 +- src/libs/mcu/host/delay.cpp | 4 +- src/libs/mcu/host/endpoint_lock.cpp | 127 ++++++++++++++ src/libs/mcu/host/endpoint_lock.hpp | 58 ++++++ src/libs/mcu/host/test_zmq_transport.cpp | 16 +- .../mcu/host/test_zmq_transport_startup.cpp | 11 +- src/libs/mcu/host/transport.hpp | 11 +- src/libs/mcu/host/zmq_transport.cpp | 165 ++---------------- src/libs/mcu/host/zmq_transport.hpp | 67 ++----- 10 files changed, 243 insertions(+), 231 deletions(-) create mode 100644 src/libs/mcu/host/endpoint_lock.cpp create mode 100644 src/libs/mcu/host/endpoint_lock.hpp diff --git a/src/libs/mcu/delay.hpp b/src/libs/mcu/delay.hpp index e28f47e..9346d59 100644 --- a/src/libs/mcu/delay.hpp +++ b/src/libs/mcu/delay.hpp @@ -1,8 +1,13 @@ -#include - #pragma once +#include + namespace mcu { -auto Delay(std::chrono::microseconds usecs) -> void; + +/// @brief Block the calling thread for at least `duration`. +/// Callers pass any chrono duration (e.g. 200ms); it converts implicitly. +/// On the host this is sleep_for, so only the calling thread pauses — the +/// transport's server thread keeps handling emulator messages throughout. +auto Delay(std::chrono::microseconds duration) -> void; } // namespace mcu diff --git a/src/libs/mcu/host/CMakeLists.txt b/src/libs/mcu/host/CMakeLists.txt index 28225e1..f112fc4 100644 --- a/src/libs/mcu/host/CMakeLists.txt +++ b/src/libs/mcu/host/CMakeLists.txt @@ -11,11 +11,11 @@ target_link_libraries(host_mcu PUBLIC mcu nlohmann_json::nlohmann_json PRIVATE project_options host_transport) -add_library(host_transport zmq_transport.cpp) +add_library(host_transport zmq_transport.cpp endpoint_lock.cpp) target_sources(host_transport PUBLIC FILE_SET HEADERS BASE_DIRS ${PROJECT_SOURCE_DIR}/src - FILES zmq_transport.hpp transport.hpp) + FILES zmq_transport.hpp transport.hpp endpoint_lock.hpp) # cppzmq and logger are PUBLIC because zmq_transport.hpp includes their headers. target_link_libraries(host_transport PUBLIC cppzmq logger error diff --git a/src/libs/mcu/host/delay.cpp b/src/libs/mcu/host/delay.cpp index d871028..67be241 100644 --- a/src/libs/mcu/host/delay.cpp +++ b/src/libs/mcu/host/delay.cpp @@ -4,7 +4,7 @@ #include namespace mcu { -auto Delay(std::chrono::microseconds usecs) -> void { - std::this_thread::sleep_for(usecs); +auto Delay(std::chrono::microseconds duration) -> void { + std::this_thread::sleep_for(duration); } } // namespace mcu diff --git a/src/libs/mcu/host/endpoint_lock.cpp b/src/libs/mcu/host/endpoint_lock.cpp new file mode 100644 index 0000000..617efca --- /dev/null +++ b/src/libs/mcu/host/endpoint_lock.cpp @@ -0,0 +1,127 @@ +#include "endpoint_lock.hpp" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace mcu { +namespace { + +constexpr std::string_view kIpcScheme{"ipc://"}; +constexpr std::string_view kLockSuffix{".lock"}; +constexpr mode_t kLockFileMode{0600}; + +// RAII for a bare file descriptor. The liveness probe below is the only place +// this file talks to POSIX sockets directly, and it must not leak an fd on any +// of its several early returns. +class FdGuard { + public: + explicit FdGuard(int descriptor) : fd_{descriptor} {} + FdGuard(const FdGuard&) = delete; + FdGuard(FdGuard&&) = delete; + auto operator=(const FdGuard&) -> FdGuard& = delete; + auto operator=(FdGuard&&) -> FdGuard& = delete; + ~FdGuard() { + if (fd_ >= 0) { + ::close(fd_); + } + } + + [[nodiscard]] auto Get() const -> int { return fd_; } + + private: + int fd_; +}; + +// True if some process is currently accepting on the AF_UNIX socket at `path`. +// +// libzmq's ipc:// transport is AF_UNIX/SOCK_STREAM, so a plain connect(2) is a +// valid liveness probe with no ZMQ machinery involved: a path left behind by a +// killed process refuses the connection, a live listener accepts it. +// +// Every "cannot tell" answer is reported as live, so the caller never proceeds +// past something it does not understand. Refusing to start is recoverable; the +// hijack described in EndpointHasLiveOwner is not. +auto IpcPathHasLiveOwner(const std::string& path) -> bool { + sockaddr_un address{}; + address.sun_family = AF_UNIX; + if (path.size() >= sizeof(address.sun_path)) { + return true; // Too long to probe; assume live rather than guess. + } + path.copy(static_cast(address.sun_path), path.size()); + + const FdGuard probe{::socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0)}; + if (probe.Get() < 0) { + return true; + } + + const auto* const address_ptr = reinterpret_cast(&address); + if (::connect(probe.Get(), address_ptr, sizeof(address)) == 0) { + return true; // Someone is listening -- hands off. + } + return errno != ECONNREFUSED; // ECONNREFUSED means the owner is gone. +} + +} // namespace + +EndpointLock::~EndpointLock() { + if (fd_ >= 0) { + ::close(fd_); // Closing the fd is what releases the lock. + } +} + +auto EndpointLock::TryAcquire(const std::string& endpoint) -> bool { + const std::string_view endpoint_view{endpoint}; + if (!endpoint_view.starts_with(kIpcScheme)) { + return true; // No filesystem path to guard. + } + const std::string lock_path{ + std::string{endpoint_view.substr(kIpcScheme.size())} + + std::string{kLockSuffix}}; + + // The lock file is deliberately never unlinked. Removing it would reintroduce + // exactly the race it exists to close: one process unlinking the file another + // has already opened, leaving the two holding locks on different inodes and + // both believing they won. It stays behind as a zero-byte marker. + const int descriptor = + ::open(lock_path.c_str(), O_CREAT | O_RDWR | O_CLOEXEC, kLockFileMode); + if (descriptor < 0) { + // Cannot lock here -- a read-only directory, for instance. Fall through to + // the liveness probe rather than refusing to start over a missing luxury. + return true; + } + if (::flock(descriptor, LOCK_EX | LOCK_NB) != 0) { + ::close(descriptor); + return false; + } + fd_ = descriptor; + return true; +} + +auto EndpointHasLiveOwner(const std::string& endpoint) -> bool { + const std::string_view endpoint_view{endpoint}; + if (!endpoint_view.starts_with(kIpcScheme)) { + return false; // Only ipc:// is probeable this way. + } + const std::string path{endpoint_view.substr(kIpcScheme.size())}; + + std::error_code error{}; + if (!std::filesystem::exists(path, error) || error) { + return false; // Nothing there at all. + } + if (!std::filesystem::is_socket(path, error) || error) { + return true; // Not ours to reason about; do not bind over it. + } + return IpcPathHasLiveOwner(path); +} + +} // namespace mcu diff --git a/src/libs/mcu/host/endpoint_lock.hpp b/src/libs/mcu/host/endpoint_lock.hpp new file mode 100644 index 0000000..3ca8df1 --- /dev/null +++ b/src/libs/mcu/host/endpoint_lock.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include + +namespace mcu { + +// Exclusive advisory ownership of a bind endpoint, held for the lifetime of the +// transport that took it. +// +// This is what makes "may I bind here" atomic. A connect(2) liveness probe +// cannot be: another process can bind in the window between the probe and our +// own bind, and libzmq will then unlink whichever socket file it finds. flock +// is arbitrated by the kernel, so that window does not exist. +// +// It is also crash-safe, which an O_EXCL lock file is not: the lock lives on +// the open file description and the kernel drops it when the fd closes -- +// including when the process dies -- so a SIGKILLed run leaves nothing behind +// that would block the next one. +class EndpointLock { + public: + EndpointLock() = default; + EndpointLock(const EndpointLock&) = delete; + EndpointLock(EndpointLock&&) = delete; + auto operator=(const EndpointLock&) -> EndpointLock& = delete; + auto operator=(EndpointLock&&) -> EndpointLock& = delete; + ~EndpointLock(); + + // Takes the lock guarding `endpoint`. False means another live process holds + // it. Endpoints with no lockable path succeed trivially. + auto TryAcquire(const std::string& endpoint) -> bool; + + private: + int fd_{-1}; +}; + +// Whether another live process is already serving this ipc:// endpoint. +// +// This is deliberately NOT stale-file cleanup. libzmq unlinks an ipc path +// before binding it, unconditionally, so a file left behind by a crashed +// process is already a non-problem -- bind() simply succeeds. +// +// The same unlink is what makes a *live* owner a problem. libzmq will happily +// remove a path another process is actively listening on and bind its own +// socket in place (verified: a second bind() to a held endpoint succeeds). +// Neither side sees an error. The original owner keeps its existing +// connections, because the inode outlives the name, but every subsequent +// connect() reaches the thief instead -- so a second app instance, or a unit +// test run while the emulator is up, silently splits the bus in two. +// +// libzmq gives us no way to ask it not to do that, so callers check before +// handing it the endpoint and refuse to start rather than become the thief. +// +// On its own this check is racy -- another process can bind between it and the +// caller's bind. EndpointLock closes that window for anything using the same +// lock; this remains as the best available answer for an owner that is not. +auto EndpointHasLiveOwner(const std::string& endpoint) -> bool; + +} // namespace mcu diff --git a/src/libs/mcu/host/test_zmq_transport.cpp b/src/libs/mcu/host/test_zmq_transport.cpp index f7c695b..897a321 100644 --- a/src/libs/mcu/host/test_zmq_transport.cpp +++ b/src/libs/mcu/host/test_zmq_transport.cpp @@ -208,15 +208,13 @@ class ZmqTransportRetryTest : public ::testing::Test { static auto MakeConfig( common::Logger& logger, std::chrono::milliseconds send_timeout, std::chrono::milliseconds total_timeout) -> TransportConfig { - // TransportConfig has user-provided constructors, so it is not an - // aggregate: designated initialisers will not compile. Assign after - // construction. - TransportConfig config{logger}; - config.send_timeout = send_timeout; - config.retry.max_attempts = kAttempts; - config.retry.retry_delay = kRetryDelay; - config.retry.total_timeout = total_timeout; - return config; + return TransportConfig{ + .send_timeout = send_timeout, + .retry = {.max_attempts = kAttempts, + .retry_delay = kRetryDelay, + .total_timeout = total_timeout}, + .logger = logger, + }; } static constexpr uint32_t kAttempts{3}; diff --git a/src/libs/mcu/host/test_zmq_transport_startup.cpp b/src/libs/mcu/host/test_zmq_transport_startup.cpp index 5c05c80..fb1e236 100644 --- a/src/libs/mcu/host/test_zmq_transport_startup.cpp +++ b/src/libs/mcu/host/test_zmq_transport_startup.cpp @@ -98,11 +98,8 @@ auto PathOf(const std::string& endpoint) -> std::string { } auto MakeConfig(common::Logger& logger) -> mcu::TransportConfig { - // TransportConfig has user-provided constructors, so it is not an aggregate: - // designated initialisers will not compile. Assign after construction. - mcu::TransportConfig config{logger}; - config.startup_timeout = kStartupTimeout; - return config; + return mcu::TransportConfig{.startup_timeout = kStartupTimeout, + .logger = logger}; } // Runs `fn` with a hard time budget. @@ -203,9 +200,7 @@ auto CountConcurrentBindWinners(const std::string& contested) -> int { } // Child. Exits via _exit so it never runs gtest teardown or atexit handlers // belonging to the parent's test process. - common::NullLogger logger; - mcu::TransportConfig config{logger}; - config.startup_timeout = kStartupTimeout; + const mcu::TransportConfig config{.startup_timeout = kStartupTimeout}; const mcu::ReceiverMap receivers{}; mcu::Dispatcher dispatcher{receivers}; const std::string own = contested + ".peer" + std::to_string(i); diff --git a/src/libs/mcu/host/transport.hpp b/src/libs/mcu/host/transport.hpp index b79a1d5..19e102e 100644 --- a/src/libs/mcu/host/transport.hpp +++ b/src/libs/mcu/host/transport.hpp @@ -6,13 +6,16 @@ #include "libs/common/error.hpp" namespace mcu { + +/// Blocking, message-oriented channel to the emulator. Send transmits one +/// encoded message; Receive blocks for the reply to it. class Transport { public: virtual ~Transport() = default; - virtual auto Send(std::string_view data) + [[nodiscard]] virtual auto Send(std::string_view data) -> std::expected = 0; - virtual auto Receive() -> std::expected = 0; - - private: + [[nodiscard]] virtual auto Receive() + -> std::expected = 0; }; + } // namespace mcu diff --git a/src/libs/mcu/host/zmq_transport.cpp b/src/libs/mcu/host/zmq_transport.cpp index 708ecbb..2d02a84 100644 --- a/src/libs/mcu/host/zmq_transport.cpp +++ b/src/libs/mcu/host/zmq_transport.cpp @@ -1,141 +1,40 @@ #include "zmq_transport.hpp" -#include -#include -#include -#include -#include -#include - #include -#include #include #include -#include #include #include -#include #include #include #include #include "dispatcher.hpp" +#include "endpoint_lock.hpp" #include "libs/common/error.hpp" namespace mcu { -namespace { - -constexpr std::string_view kIpcScheme{"ipc://"}; -constexpr std::string_view kLockSuffix{".lock"}; -constexpr mode_t kLockFileMode{0600}; - -// RAII for a bare file descriptor. The liveness probe below is the only place -// this file talks to POSIX sockets directly, and it must not leak an fd on any -// of its several early returns. -class FdGuard { - public: - explicit FdGuard(int descriptor) : fd_{descriptor} {} - FdGuard(const FdGuard&) = delete; - FdGuard(FdGuard&&) = delete; - auto operator=(const FdGuard&) -> FdGuard& = delete; - auto operator=(FdGuard&&) -> FdGuard& = delete; - ~FdGuard() { - if (fd_ >= 0) { - ::close(fd_); - } - } - - [[nodiscard]] auto Get() const -> int { return fd_; } - - private: - int fd_; -}; - -// True if some process is currently accepting on the AF_UNIX socket at `path`. -// -// libzmq's ipc:// transport is AF_UNIX/SOCK_STREAM, so a plain connect(2) is a -// valid liveness probe with no ZMQ machinery involved: a path left behind by a -// killed process refuses the connection, a live listener accepts it. -// -// Every "cannot tell" answer is reported as live, so the caller never proceeds -// past something it does not understand. Refusing to start is recoverable; the -// hijack described in EndpointHasLiveOwner is not. -auto IpcPathHasLiveOwner(const std::string& path) -> bool { - sockaddr_un address{}; - address.sun_family = AF_UNIX; - if (path.size() >= sizeof(address.sun_path)) { - return true; // Too long to probe; assume live rather than guess. - } - path.copy(static_cast(address.sun_path), path.size()); - - const FdGuard probe{::socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0)}; - if (probe.Get() < 0) { - return true; - } - - const auto* const address_ptr = reinterpret_cast(&address); - if (::connect(probe.Get(), address_ptr, sizeof(address)) == 0) { - return true; // Someone is listening -- hands off. - } - return errno != ECONNREFUSED; // ECONNREFUSED means the owner is gone. -} - -} // namespace - -EndpointLock::~EndpointLock() { - if (fd_ >= 0) { - ::close(fd_); // Closing the fd is what releases the lock. - } -} - -auto EndpointLock::TryAcquire(const std::string& endpoint) -> bool { - const std::string_view endpoint_view{endpoint}; - if (!endpoint_view.starts_with(kIpcScheme)) { - return true; // No filesystem path to guard. - } - const std::string lock_path{ - std::string{endpoint_view.substr(kIpcScheme.size())} + - std::string{kLockSuffix}}; - - // The lock file is deliberately never unlinked. Removing it would reintroduce - // exactly the race it exists to close: one process unlinking the file another - // has already opened, leaving the two holding locks on different inodes and - // both believing they won. It stays behind as a zero-byte marker. - const int descriptor = - ::open(lock_path.c_str(), O_CREAT | O_RDWR | O_CLOEXEC, kLockFileMode); - if (descriptor < 0) { - // Cannot lock here -- a read-only directory, for instance. Fall through to - // the liveness probe rather than refusing to start over a missing luxury. - return true; - } - if (::flock(descriptor, LOCK_EX | LOCK_NB) != 0) { - ::close(descriptor); - return false; - } - fd_ = descriptor; - return true; -} auto ZmqTransport::Create(const std::string& to_emulator, const std::string& from_emulator, Dispatcher& dispatcher, const TransportConfig& config) -> std::expected, common::Error> { try { - config.logger.Info("Creating ZmqTransport"); + config.logger.get().Info("Creating ZmqTransport"); auto transport{std::make_unique(to_emulator, from_emulator, dispatcher, config)}; if (auto status{transport->StartupStatus()}; !status) { - config.logger.Error("ZmqTransport startup failed"); + config.logger.get().Error("ZmqTransport startup failed"); return std::unexpected(status.error()); } - config.logger.Info("ZmqTransport created successfully"); + config.logger.get().Info("ZmqTransport created successfully"); return transport; } catch (...) { // NOLINT - // The constructor no longer throws, so this covers allocation failure only. - config.logger.Error("Unknown error during creation"); + // The constructor does not throw, so this covers allocation failure only. + config.logger.get().Error("Unknown error during creation"); return std::unexpected(common::Error::kUnknown); } } @@ -220,8 +119,7 @@ auto ZmqTransport::SignalBind(BindOutcome outcome) -> void { // ZMQ_SNDTIMEO bounds a single attempt; retry.total_timeout bounds the whole // Send(). If one attempt may consume the entire budget then the first EAGAIN // arrives at or after the deadline and Send() returns having tried once -- -// max_attempts and retry_delay become dead configuration. That was the shipped -// default: send_timeout and total_timeout were both 1000ms. +// max_attempts and retry_delay become dead configuration. // // Shrinking the per-attempt slice is preferred to rejecting the config. A // caller who asks for three attempts within a second has said something @@ -258,45 +156,6 @@ auto ZmqTransport::SetSocketOptions() -> void { static_cast(config_.recv_timeout.count())); } -// Whether another live process is already serving this endpoint. -// -// This is deliberately NOT stale-file cleanup. libzmq unlinks an ipc path -// before binding it, unconditionally, so a file left behind by a crashed -// process is already a non-problem -- bind() simply succeeds. -// -// The same unlink is what makes a *live* owner a problem. libzmq will happily -// remove a path another process is actively listening on and bind its own -// socket in place (verified: a second bind() to a held endpoint succeeds). -// Neither side sees an error. The original owner keeps its existing -// connections, because the inode outlives the name, but every subsequent -// connect() reaches the thief instead -- so a second app instance, or a unit -// test run while the emulator is up, silently splits the bus in two. -// -// libzmq gives us no way to ask it not to do that, so we check before handing -// it the endpoint and refuse to start rather than become the thief. -// -// On its own this check is racy -- another process can bind between it and our -// bind. EndpointLock closes that window for anything using the same lock; this -// remains as the best available answer for an owner that is not. -auto ZmqTransport::EndpointHasLiveOwner(const std::string& endpoint) const - -> bool { - const std::string_view endpoint_view{endpoint}; - if (!endpoint_view.starts_with(kIpcScheme)) { - return false; // Only ipc:// is probeable this way. - } - const std::string path{endpoint_view.substr(kIpcScheme.size())}; - - std::error_code error{}; - if (!std::filesystem::exists(path, error) || error) { - return false; // Nothing there at all. - } - if (!std::filesystem::is_socket(path, error) || error) { - LogWarning("Endpoint path exists and is not a socket"); - return true; // Not ours to reason about; do not bind over it. - } - return IpcPathHasLiveOwner(path); -} - ZmqTransport::~ZmqTransport() { try { LogDebug("Shutting down ZmqTransport"); @@ -331,13 +190,11 @@ ZmqTransport::~ZmqTransport() { // // The classification is the substance here. cppzmq's send() reports an expired // ZMQ_SNDTIMEO by returning an EMPTY result and throws error_t only for -// everything else -- so the timeout this retry loop exists to absorb is a falsy -// return, not an exception. The previous code looked for it exclusively in a -// catch block, treated the falsy return as a hard failure, and so left the -// retry path unreachable even once the timeouts allowed for it. +// everything else -- so the timeout this retry loop exists to absorb is a +// falsy return, not an exception, and both paths must map to kWouldBlock. // -// ETIMEDOUT is still caught for the same outcome: no libzmq version in use -// raises it here, but it means precisely what EAGAIN means and costs one line. +// ETIMEDOUT is caught for the same outcome: no libzmq version in use raises it +// here, but it means precisely what EAGAIN means and costs one line. auto ZmqTransport::TrySendOnce(std::string_view data) -> SendAttempt { try { const auto result{ diff --git a/src/libs/mcu/host/zmq_transport.hpp b/src/libs/mcu/host/zmq_transport.hpp index 852a813..01d66a5 100644 --- a/src/libs/mcu/host/zmq_transport.hpp +++ b/src/libs/mcu/host/zmq_transport.hpp @@ -13,6 +13,7 @@ #include #include "dispatcher.hpp" +#include "endpoint_lock.hpp" #include "libs/common/error.hpp" #include "libs/common/logger.hpp" #include "transport.hpp" @@ -67,6 +68,16 @@ struct RetryConfig { std::chrono::milliseconds total_timeout{1000}; }; +// The default (discarding) logger for components that are not handed one. +inline auto DefaultLogger() -> common::Logger& { + static common::NullLogger null_logger{}; + return null_logger; +} + +// An aggregate on purpose: a reference member would force user-provided +// constructors and kill designated initializers, so the logger is a +// reference_wrapper with a default instead. Callers write +// TransportConfig{.send_timeout = 100ms, .logger = my_logger}. struct TransportConfig { std::chrono::milliseconds poll_timeout{50}; // Bounds the one wait the constructor performs: the server thread's bind @@ -81,49 +92,7 @@ struct TransportConfig { std::chrono::milliseconds recv_timeout{5000}; int linger_ms{0}; // Discard pending messages on close RetryConfig retry{}; - common::Logger& logger; // Logger reference (defaults to NullLogger) - - // Default constructor uses NullLogger - TransportConfig() : logger(GetDefaultLogger()) {} - - // Allow custom logger via dependency injection - explicit TransportConfig(common::Logger& custom_logger) - : logger(custom_logger) {} - - private: - static auto GetDefaultLogger() -> common::Logger& { - static common::NullLogger null_logger{}; - return null_logger; - } -}; - -// Exclusive advisory ownership of a bind endpoint, held for the lifetime of the -// transport that took it. -// -// This is what makes "may I bind here" atomic. A connect(2) liveness probe -// cannot be: another process can bind in the window between the probe and our -// own bind, and libzmq will then unlink whichever socket file it finds. flock -// is arbitrated by the kernel, so that window does not exist. -// -// It is also crash-safe, which an O_EXCL lock file is not: the lock lives on -// the open file description and the kernel drops it when the fd closes -- -// including when the process dies -- so a SIGKILLed run leaves nothing behind -// that would block the next one. -class EndpointLock { - public: - EndpointLock() = default; - EndpointLock(const EndpointLock&) = delete; - EndpointLock(EndpointLock&&) = delete; - auto operator=(const EndpointLock&) -> EndpointLock& = delete; - auto operator=(EndpointLock&&) -> EndpointLock& = delete; - ~EndpointLock(); - - // Takes the lock guarding `endpoint`. False means another live process holds - // it. Endpoints with no lockable path succeed trivially. - auto TryAcquire(const std::string& endpoint) -> bool; - - private: - int fd_{-1}; + std::reference_wrapper logger{DefaultLogger()}; }; class ZmqTransport : public Transport { @@ -183,19 +152,19 @@ class ZmqTransport : public Transport { auto SignalBind(BindOutcome outcome) -> void; auto AwaitBind() -> BindOutcome; auto FailStartup(common::Error error, std::string_view msg) -> void; - auto EndpointHasLiveOwner(const std::string& endpoint) const -> bool; auto SetSocketOptions() -> void; - // Logging helpers to reduce cognitive complexity auto LogDebug(std::string_view msg) const -> void { - config_.logger.Debug(msg); + config_.logger.get().Debug(msg); + } + auto LogInfo(std::string_view msg) const -> void { + config_.logger.get().Info(msg); } - auto LogInfo(std::string_view msg) const -> void { config_.logger.Info(msg); } auto LogWarning(std::string_view msg) const -> void { - config_.logger.Warning(msg); + config_.logger.get().Warning(msg); } auto LogError(std::string_view msg) const -> void { - config_.logger.Error(msg); + config_.logger.get().Error(msg); } TransportConfig config_; From 492f40dd32fa1eb3ebc201d22316059d1fc6fb52 Mon Sep 17 00:00:00 2001 From: Nehal Patel Date: Sat, 29 Aug 2026 16:05:47 +0000 Subject: [PATCH 13/17] refactor(tests): shared infra for host-peripheral and transport tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The UART and I2C test fixtures were ~110 near-verbatim duplicated lines each — socket/thread lifecycle, dispatcher/transport wiring, readiness probe, teardown ordering, endpoint cleanup — differing only in the ~25 lines of protocol logic. Any lifecycle fix had to be made twice, and the explanatory comments were already duplicated prose. They now derive from HostPeripheralTest (host_peripheral_test_infra.hpp), which owns all of that and asks a concrete fixture for exactly two things: construct the peripheral (MakeReceiver) and play the emulator's side of one exchange (HandleRequest). The redundant 'ret == 0 then ret <= 0' double-check in the copied poll loops collapses to one condition. test_support.hpp consolidates the rest of the copied scaffolding: - one RecordingLogger (Count + Contains) replaces two byte-identical logger classes that differed only in their query method - MakeEndpoint/EndpointPath/RemoveEndpointArtifacts replace four copies of per-process endpoint naming and of the socket+.lock cleanup loop test_messages now round-trips all six message types through one TYPED_TEST instead of testing one type three times — strictly more coverage (the std::byte adl_serializer path included) in less code — keeps a wire-format pin for the exact JSON the Python emulator parses, and adds the valid-JSON-wrong-shape decode case. Co-Authored-By: Claude Fable 5 --- .../mcu/host/host_peripheral_test_infra.hpp | 136 +++++++++++ src/libs/mcu/host/test_host_i2c.cpp | 195 ++++------------ src/libs/mcu/host/test_host_uart.cpp | 220 ++++-------------- src/libs/mcu/host/test_messages.cpp | 134 ++++++++--- src/libs/mcu/host/test_support.hpp | 78 +++++++ src/libs/mcu/host/test_zmq_transport.cpp | 61 +---- .../mcu/host/test_zmq_transport_startup.cpp | 39 +--- 7 files changed, 412 insertions(+), 451 deletions(-) create mode 100644 src/libs/mcu/host/host_peripheral_test_infra.hpp create mode 100644 src/libs/mcu/host/test_support.hpp diff --git a/src/libs/mcu/host/host_peripheral_test_infra.hpp b/src/libs/mcu/host/host_peripheral_test_infra.hpp new file mode 100644 index 0000000..dc7ceca --- /dev/null +++ b/src/libs/mcu/host/host_peripheral_test_infra.hpp @@ -0,0 +1,136 @@ +#pragma once + +// Shared fixture for host-peripheral tests (UART, I2C, ...). Test-only: this +// header is not part of any library's file set. +// +// It owns everything the peripheral protocol does not care about — a fake +// emulator on its own thread, the transport/dispatcher wiring, readiness, and +// endpoint cleanup. A concrete fixture supplies only its protocol: +// - MakeReceiver() constructs the peripheral under test on the transport +// - HandleRequest() plays the emulator's side of one exchange + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libs/mcu/host/dispatcher.hpp" +#include "libs/mcu/host/receiver.hpp" +#include "libs/mcu/host/test_support.hpp" +#include "libs/mcu/host/zmq_transport.hpp" + +namespace mcu::test { + +class HostPeripheralTest : public ::testing::Test { + protected: + /// Construct the peripheral under test against `transport` and return it as + /// the receiver to register with the dispatcher. Called once, from SetUp. + virtual auto MakeReceiver(Transport& transport) -> Receiver& = 0; + + /// The emulator's side of one exchange: given the raw message the device + /// sent, return the encoded reply, or nullopt to ignore the message (as the + /// real emulator ignores anything it cannot decode — which is also what + /// swallows the readiness probe below). + virtual auto HandleRequest(std::string_view message) + -> std::optional = 0; + + void SetUp() override { + // Bind on the test thread, before the emulator thread exists, so the + // transport's connect() below happens-after the bind by thread creation + // alone. + emulator_socket_.set(zmq::sockopt::linger, 0); + emulator_socket_.bind(emulator_endpoint_); + + emulator_running_ = true; + emulator_thread_ = std::thread{[this]() { EmulatorLoop(); }}; + + // The dispatcher observes the receiver map by reference, so the receiver + // can be added after the transport exists. + dispatcher_ = std::make_unique(receiver_map_); + + // Assert rather than value_or(nullptr): Create can fail, and a null + // transport is dereferenced two lines down. + auto transport_result = ZmqTransport::Create( + emulator_endpoint_, device_endpoint_, *dispatcher_); + ASSERT_TRUE(transport_result.has_value()); + device_transport_ = std::move(transport_result.value()); + + receiver_map_.emplace_back(MakeReceiver(*device_transport_)); + + // Wait for the condition rather than for a duration. On a PAIR socket a + // send succeeds only once a pipe to the peer exists, so a successful probe + // IS the readiness signal, and connect latency is absorbed by SNDTIMEO. + ASSERT_TRUE(device_transport_->Send("probe")); + } + + void TearDown() override { + device_transport_.reset(); + dispatcher_.reset(); + + // Stop and join before closing anything the thread is using: terminating a + // context out from under a running loop throws ETERM inside it. + emulator_running_ = false; + if (emulator_thread_.joinable()) { + emulator_thread_.join(); + } + emulator_socket_.close(); + emulator_context_.close(); + + RemoveEndpointArtifacts(emulator_endpoint_); + RemoveEndpointArtifacts(device_endpoint_); + } + + // Device -> emulator; the transport connects its send socket here. + const std::string emulator_endpoint_{ + MakeEndpoint("test_peripheral", "device_emulator")}; + // Emulator -> device; the transport's server thread binds here. + const std::string device_endpoint_{ + MakeEndpoint("test_peripheral", "emulator_device")}; + std::unique_ptr device_transport_; + + private: + void EmulatorLoop() { + try { + while (emulator_running_) { + zmq::pollitem_t item{.socket = static_cast(emulator_socket_), + .fd = 0, + .events = ZMQ_POLLIN, + .revents = 0}; + if (zmq::poll(&item, 1, std::chrono::milliseconds{50}) <= 0) { + continue; // Timeout; recheck emulator_running_. + } + + zmq::message_t message{}; + if (!emulator_socket_.recv(message, zmq::recv_flags::none)) { + continue; + } + const std::string_view message_str{ + static_cast(message.data()), message.size()}; + + if (const auto reply = HandleRequest(message_str)) { + emulator_socket_.send(zmq::buffer(*reply), zmq::send_flags::none); + } + } + } catch (const zmq::error_t& e) { + // Socket closed during shutdown, expected behavior + if (e.num() != ETERM) { + throw; + } + } + } + + ReceiverMap receiver_map_; + std::unique_ptr dispatcher_; + zmq::context_t emulator_context_{1}; + zmq::socket_t emulator_socket_{emulator_context_, zmq::socket_type::pair}; + std::thread emulator_thread_; + std::atomic emulator_running_{false}; +}; + +} // namespace mcu::test diff --git a/src/libs/mcu/host/test_host_i2c.cpp b/src/libs/mcu/host/test_host_i2c.cpp index 999727f..3844120 100644 --- a/src/libs/mcu/host/test_host_i2c.cpp +++ b/src/libs/mcu/host/test_host_i2c.cpp @@ -1,183 +1,68 @@ #include -#include #include #include #include #include #include -#include #include +#include +#include #include -#include -#include +#include #include -#include "libs/mcu/host/dispatcher.hpp" #include "libs/mcu/host/emulator_message_json_encoder.hpp" #include "libs/mcu/host/host_emulator_messages.hpp" #include "libs/mcu/host/host_i2c.hpp" -#include "libs/mcu/host/zmq_transport.hpp" +#include "libs/mcu/host/host_peripheral_test_infra.hpp" #include "libs/mcu/i2c.hpp" -class HostI2CTest : public ::testing::Test { +class HostI2CTest : public mcu::test::HostPeripheralTest { protected: - // Per-process endpoints. gtest_discover_tests gives every case its own - // process, so a fixed path made `ctest -j` cases contend for one endpoint -- - // silently corrupting each other before EndpointLock, loudly after. - static auto Endpoint(std::string_view role) -> std::string { - return "ipc:///tmp/test_i2c_" + std::string{role} + "_" + - std::to_string(::getpid()) + ".ipc"; + auto MakeReceiver(mcu::Transport& transport) -> mcu::Receiver& override { + i2c_ = std::make_unique("I2C 1", transport); + return *i2c_; } - void SetUp() override { - // Bind on the test thread, before the emulator thread exists, so the - // transport's connect() below happens-after the bind by thread creation - // alone. This replaces a 100ms sleep that only made the race unlikely. - emulator_socket_.set(zmq::sockopt::linger, 0); - emulator_socket_.bind(device_emulator_endpoint_); - - emulator_running_ = true; - emulator_thread_ = std::thread{[this]() { EmulatorLoop(); }}; - - // Create dispatcher with empty receiver map (will update via reference - // later) - dispatcher_ = std::make_unique(receiver_map_storage_); - - // Create transport. Assert rather than value_or(nullptr): Create can fail, - // and a null transport is dereferenced two lines down. - auto transport_result = mcu::ZmqTransport::Create( - device_emulator_endpoint_, emulator_device_endpoint_, *dispatcher_); - ASSERT_TRUE(transport_result.has_value()); - device_transport_ = std::move(transport_result.value()); - - // Now create I2C with transport - i2c_ = - std::make_unique("I2C 1", *device_transport_); - - // Add I2C to receiver map (dispatcher holds reference, so this updates it) - receiver_map_storage_.emplace_back(std::ref(*i2c_)); - - // Wait for the condition rather than for a duration. On a PAIR socket a - // send succeeds only once a pipe to the peer exists, so a successful probe - // IS the readiness signal, and connect latency is absorbed by SNDTIMEO. - // The emulator loop skips anything that fails to decode, so this non-JSON - // probe is swallowed with no reply and needs no protocol support. - ASSERT_TRUE(device_transport_->Send("probe")); - } - - void TearDown() override { - i2c_.reset(); - device_transport_.reset(); - dispatcher_.reset(); - - // Stop and join before closing anything the thread is using: terminating a - // context out from under a running loop throws ETERM inside it. - emulator_running_ = false; - if (emulator_thread_.joinable()) { - emulator_thread_.join(); - } - emulator_socket_.close(); - emulator_context_.close(); - - // The transport never unlinks its own lock file -- doing so would reopen - // the race it closes -- so per-process test endpoints would otherwise pile - // up in /tmp, one pair per test case per run. - std::error_code error{}; - for (const auto& endpoint : - {device_emulator_endpoint_, emulator_device_endpoint_}) { - const std::string path{ - endpoint.substr(std::string_view{"ipc://"}.size())}; - std::filesystem::remove(path, error); - std::filesystem::remove(path + ".lock", error); + // Emulator side of the I2C protocol: one loopback buffer per device address. + auto HandleRequest(std::string_view message) + -> std::optional override { + auto request_result = mcu::Decode(message); + if (!request_result) { + return std::nullopt; // Skip malformed messages } - } - - void EmulatorLoop() { - // Simulate I2C device buffers (address -> data) - std::map> i2c_device_buffers; - - try { - zmq::socket_t& socket = emulator_socket_; - - while (emulator_running_) { - std::array items = { - {{.socket = static_cast(socket), - .fd = 0, - .events = ZMQ_POLLIN, - .revents = 0}}}; - - const int ret{ - zmq::poll(items.data(), 1, std::chrono::milliseconds{50})}; - - if (ret == 0) { - continue; // Timeout - } - if (ret <= 0) { - continue; - } - - zmq::message_t message{}; - if (!socket.recv(message, zmq::recv_flags::none)) { - continue; - } - - const std::string_view message_str{ - static_cast(message.data()), message.size()}; - - auto request_result = - mcu::Decode(std::string{message_str}); - if (!request_result) { - continue; // Skip malformed messages - } - const auto& request = *request_result; - mcu::I2CEmulatorResponse response{ - .type = mcu::MessageType::kResponse, - .object = mcu::ObjectType::kI2C, - .name = request.name, - .address = request.address, - .data = {}, - .bytes_transferred = 0, - .status = common::Error::kOk, - }; - - if (request.operation == mcu::OperationType::kSend) { - // Device sent data to I2C peripheral - store in device buffer - i2c_device_buffers[request.address] = request.data; - response.bytes_transferred = request.data.size(); - } else if (request.operation == mcu::OperationType::kReceive) { - // Device wants to receive data from I2C peripheral - if (i2c_device_buffers.contains(request.address)) { - const auto& buffer = i2c_device_buffers[request.address]; - const size_t bytes_to_send{std::min(request.size, buffer.size())}; - response.data = std::vector( - buffer.begin(), - buffer.begin() + static_cast(bytes_to_send)); - response.bytes_transferred = bytes_to_send; - } - } - - const auto response_str = mcu::Encode(response); - socket.send(zmq::buffer(response_str), zmq::send_flags::none); - } - } catch (const zmq::error_t& e) { - // Socket closed during shutdown, expected behavior - if (e.num() != ETERM) { - throw; + const auto& request = *request_result; + mcu::I2CEmulatorResponse response{ + .name = request.name, + .address = request.address, + .data = {}, + .bytes_transferred = 0, + .status = common::Error::kOk, + }; + + if (request.operation == mcu::OperationType::kSend) { + device_buffers_[request.address] = request.data; + response.bytes_transferred = request.data.size(); + } else if (request.operation == mcu::OperationType::kReceive) { + if (device_buffers_.contains(request.address)) { + const auto& buffer = device_buffers_[request.address]; + const size_t bytes_to_send{std::min(request.size, buffer.size())}; + response.data = std::vector( + buffer.begin(), + buffer.begin() + static_cast(bytes_to_send)); + response.bytes_transferred = bytes_to_send; } } + + return mcu::Encode(response); } - const std::string device_emulator_endpoint_{Endpoint("device_emulator")}; - const std::string emulator_device_endpoint_{Endpoint("emulator_device")}; - mcu::ReceiverMap receiver_map_storage_; - std::unique_ptr dispatcher_; - std::unique_ptr device_transport_; std::unique_ptr i2c_; - zmq::context_t emulator_context_{1}; - zmq::socket_t emulator_socket_{emulator_context_, zmq::socket_type::pair}; - std::thread emulator_thread_; - std::atomic emulator_running_{false}; + + private: + // Touched only from the emulator thread. + std::map> device_buffers_; }; TEST_F(HostI2CTest, SendData) { diff --git a/src/libs/mcu/host/test_host_uart.cpp b/src/libs/mcu/host/test_host_uart.cpp index de7957e..f8f4e1c 100644 --- a/src/libs/mcu/host/test_host_uart.cpp +++ b/src/libs/mcu/host/test_host_uart.cpp @@ -1,185 +1,71 @@ #include -#include +#include #include #include #include #include #include -#include +#include #include +#include #include -#include -#include +#include #include -#include "libs/mcu/host/dispatcher.hpp" #include "libs/mcu/host/emulator_message_json_encoder.hpp" #include "libs/mcu/host/host_emulator_messages.hpp" +#include "libs/mcu/host/host_peripheral_test_infra.hpp" #include "libs/mcu/host/host_uart.hpp" -#include "libs/mcu/host/zmq_transport.hpp" #include "libs/mcu/uart.hpp" -class HostUartTest : public ::testing::Test { +class HostUartTest : public mcu::test::HostPeripheralTest { protected: - // Per-process endpoints. gtest_discover_tests gives every case its own - // process, so a fixed path made `ctest -j` cases contend for one endpoint -- - // silently corrupting each other before EndpointLock, loudly after. - static auto Endpoint(std::string_view role) -> std::string { - return "ipc:///tmp/test_uart_" + std::string{role} + "_" + - std::to_string(::getpid()) + ".ipc"; + auto MakeReceiver(mcu::Transport& transport) -> mcu::Receiver& override { + uart_ = std::make_unique("UART 1", transport); + return *uart_; } - void SetUp() override { - // Bind on the test thread, before the emulator thread exists, so the - // transport's connect() below happens-after the bind by thread creation - // alone. This replaces a 100ms sleep that only made the race unlikely. - emulator_socket_.set(zmq::sockopt::linger, 0); - emulator_socket_.bind(device_emulator_endpoint_); - - emulator_running_ = true; - emulator_thread_ = std::thread{[this]() { EmulatorLoop(); }}; - - // Create dispatcher with empty receiver map (will update via reference - // later) - dispatcher_ = std::make_unique(receiver_map_storage_); - - // Create transport. Assert rather than value_or(nullptr): Create can fail, - // and a null transport is dereferenced two lines down. - auto transport_result = mcu::ZmqTransport::Create( - device_emulator_endpoint_, emulator_device_endpoint_, *dispatcher_); - ASSERT_TRUE(transport_result.has_value()); - device_transport_ = std::move(transport_result.value()); - - // Now create UART with transport - uart_ = std::make_unique("UART 1", *device_transport_); - - // Add UART to receiver map (dispatcher holds reference, so this updates it) - receiver_map_storage_.emplace_back(std::ref(*uart_)); - - // Wait for the condition rather than for a duration. On a PAIR socket a - // send succeeds only once a pipe to the peer exists, so a successful probe - // IS the readiness signal, and connect latency is absorbed by SNDTIMEO. - // The emulator loop skips anything that fails to decode, so this non-JSON - // probe is swallowed with no reply and needs no protocol support. - ASSERT_TRUE(device_transport_->Send("probe")); - } - - void TearDown() override { - uart_.reset(); - device_transport_.reset(); - dispatcher_.reset(); - - // Stop and join before closing anything the thread is using: terminating a - // context out from under a running loop throws ETERM inside it. - emulator_running_ = false; - if (emulator_thread_.joinable()) { - emulator_thread_.join(); + // Emulator side of the UART protocol: Send stores into a loopback buffer, + // Receive drains it. + auto HandleRequest(std::string_view message) + -> std::optional override { + auto request_result = mcu::Decode(message); + if (!request_result) { + return std::nullopt; // Skip malformed messages } - emulator_socket_.close(); - emulator_context_.close(); - unsolicited_context_.close(); - - // The transport never unlinks its own lock file -- doing so would reopen - // the race it closes -- so per-process test endpoints would otherwise pile - // up in /tmp, one pair per test case per run. - std::error_code error{}; - for (const auto& endpoint : - {device_emulator_endpoint_, emulator_device_endpoint_}) { - const std::string path{ - endpoint.substr(std::string_view{"ipc://"}.size())}; - std::filesystem::remove(path, error); - std::filesystem::remove(path + ".lock", error); + const auto& request = *request_result; + mcu::UartEmulatorResponse response{ + .name = request.name, + .data = {}, + .bytes_transferred = 0, + .status = common::Error::kOk, + }; + + if (request.operation == mcu::OperationType::kSend) { + uart_rx_buffer_.insert(uart_rx_buffer_.end(), request.data.begin(), + request.data.end()); + response.bytes_transferred = request.data.size(); + } else if (request.operation == mcu::OperationType::kReceive) { + const size_t bytes_to_send{ + std::min(request.size, uart_rx_buffer_.size())}; + response.data = std::vector( + uart_rx_buffer_.begin(), + uart_rx_buffer_.begin() + static_cast(bytes_to_send)); + response.bytes_transferred = bytes_to_send; + uart_rx_buffer_.erase( + uart_rx_buffer_.begin(), + uart_rx_buffer_.begin() + static_cast(bytes_to_send)); } - } - - void EmulatorLoop() { - std::vector uart_rx_buffer; - - try { - zmq::socket_t& socket = emulator_socket_; - - while (emulator_running_) { - std::array items = { - {{.socket = static_cast(socket), - .fd = 0, - .events = ZMQ_POLLIN, - .revents = 0}}}; - const int ret{ - zmq::poll(items.data(), 1, std::chrono::milliseconds{50})}; - - if (ret == 0) { - continue; // Timeout - } - if (ret <= 0) { - continue; - } - - zmq::message_t message{}; - if (!socket.recv(message, zmq::recv_flags::none)) { - continue; - } - - const std::string_view message_str{ - static_cast(message.data()), message.size()}; - - auto request_result = - mcu::Decode(std::string{message_str}); - if (!request_result) { - continue; // Skip malformed messages - } - const auto& request = *request_result; - mcu::UartEmulatorResponse response{ - .type = mcu::MessageType::kResponse, - .object = mcu::ObjectType::kUart, - .name = request.name, - .data = {}, - .bytes_transferred = 0, - .status = common::Error::kOk, - }; - - if (request.operation == mcu::OperationType::kSend) { - // Device sent data - store in our buffer - uart_rx_buffer.insert(uart_rx_buffer.end(), request.data.begin(), - request.data.end()); - response.bytes_transferred = request.data.size(); - } else if (request.operation == mcu::OperationType::kReceive) { - // Device wants to receive data - send from our buffer - const size_t bytes_to_send{ - std::min(request.size, uart_rx_buffer.size())}; - response.data = std::vector( - uart_rx_buffer.begin(), - uart_rx_buffer.begin() + - static_cast(bytes_to_send)); - response.bytes_transferred = bytes_to_send; - uart_rx_buffer.erase(uart_rx_buffer.begin(), - uart_rx_buffer.begin() + - static_cast(bytes_to_send)); - } - - const auto response_str = mcu::Encode(response); - socket.send(zmq::buffer(response_str), zmq::send_flags::none); - } - } catch (const zmq::error_t& e) { - // Socket closed during shutdown, expected behavior - if (e.num() != ETERM) { - throw; - } - } + return mcu::Encode(response); } - const std::string device_emulator_endpoint_{Endpoint("device_emulator")}; - const std::string emulator_device_endpoint_{Endpoint("emulator_device")}; - mcu::ReceiverMap receiver_map_storage_; - std::unique_ptr dispatcher_; - std::unique_ptr device_transport_; std::unique_ptr uart_; - zmq::context_t emulator_context_{1}; - zmq::socket_t emulator_socket_{emulator_context_, zmq::socket_type::pair}; - zmq::context_t unsolicited_context_{1}; - std::thread emulator_thread_; - std::atomic emulator_running_{false}; + + private: + // Touched only from the emulator thread. + std::vector uart_rx_buffer_; }; TEST_F(HostUartTest, Init) { @@ -240,8 +126,7 @@ TEST_F(HostUartTest, RxHandlerUnsolicitedData) { // Track received data via handler. The handler runs on the transport's // server thread while this thread reads the results, so both need - // synchronisation -- a plain bool and vector here were a data race - // regardless of any sleep. + // synchronisation -- a plain bool and vector here would be a data race. std::vector received_data{}; std::mutex received_mutex{}; std::atomic handler_called{false}; @@ -262,26 +147,21 @@ TEST_F(HostUartTest, RxHandlerUnsolicitedData) { const std::vector test_data{std::byte{0xDE}, std::byte{0xAD}, std::byte{0xBE}, std::byte{0xEF}}; const mcu::UartEmulatorRequest unsolicited_request{ - .type = mcu::MessageType::kRequest, - .object = mcu::ObjectType::kUart, .name = "UART 1", .operation = mcu::OperationType::kReceive, .data = test_data, .size = test_data.size(), - .timeout_ms = 0, }; - // Send unsolicited data directly via socket (simulating external data - // arrival) - zmq::socket_t unsolicited_socket{unsolicited_context_, - zmq::socket_type::pair}; - // Timeouts instead of a "connect time" sleep. This socket had none, so its - // send already blocked until the pipe came up -- the sleep was never what - // made this work, it just hid an unbounded wait behind a bounded-looking one. + // Send unsolicited data directly via a fresh socket (simulating external + // data arrival). Timeouts instead of a "connect time" sleep: a PAIR send + // blocks until the pipe comes up, so SNDTIMEO bounds the wait. + zmq::context_t unsolicited_context{1}; + zmq::socket_t unsolicited_socket{unsolicited_context, zmq::socket_type::pair}; unsolicited_socket.set(zmq::sockopt::linger, 0); unsolicited_socket.set(zmq::sockopt::sndtimeo, 2000); unsolicited_socket.set(zmq::sockopt::rcvtimeo, 2000); - unsolicited_socket.connect(emulator_device_endpoint_); + unsolicited_socket.connect(device_endpoint_); const auto request_str = mcu::Encode(unsolicited_request); ASSERT_TRUE( diff --git a/src/libs/mcu/host/test_messages.cpp b/src/libs/mcu/host/test_messages.cpp index e27b522..a5b1d95 100644 --- a/src/libs/mcu/host/test_messages.cpp +++ b/src/libs/mcu/host/test_messages.cpp @@ -1,51 +1,111 @@ #include +#include +#include +#include + #include "emulator_message_json_encoder.hpp" #include "host_emulator_messages.hpp" namespace mcu { namespace { -TEST(EmulatorMessageJsonEncoderTest, EncodePinEmulatorRequest) { - const PinEmulatorRequest request{.type = MessageType::kRequest, - .object = ObjectType::kPin, - .name = "PA0", - .operation = OperationType::kSet, - .state = PinState::kHigh}; - const std::string expected_json{ - R"({"name":"PA0","object":"Pin","operation":"Set","state":"High","type":"Request"})"}; - EXPECT_EQ(Encode(request), expected_json); +// One representative value per message type, with every optional field +// populated so the round trip exercises the whole schema — including the +// std::byte vectors, which go through the custom adl_serializer. +template +auto Sample() -> T; + +const std::vector kSampleData{std::byte{0xDE}, std::byte{0xAD}, + std::byte{0xBE}, std::byte{0xEF}}; + +template <> +auto Sample() -> PinEmulatorRequest { + return {.name = "PA0", + .operation = OperationType::kSet, + .state = PinState::kHigh}; } -TEST(EmulatorMessageJsonEncoderTest, DecodePinEmulatorRequest) { - const std::string json{ +template <> +auto Sample() -> PinEmulatorResponse { + return {.name = "PA0", .state = PinState::kLow, .status = common::Error::kOk}; +} + +template <> +auto Sample() -> UartEmulatorRequest { + return {.name = "UART 1", + .operation = OperationType::kSend, + .data = kSampleData, + .size = 16, + .timeout_ms = 250}; +} + +template <> +auto Sample() -> UartEmulatorResponse { + return {.name = "UART 1", + .data = kSampleData, + .bytes_transferred = kSampleData.size(), + .status = common::Error::kTimeout}; +} + +template <> +auto Sample() -> I2CEmulatorRequest { + return {.name = "I2C 1", + .operation = OperationType::kReceive, + .address = 0x50, + .data = kSampleData, + .size = 4}; +} + +template <> +auto Sample() -> I2CEmulatorResponse { + return {.name = "I2C 1", + .address = 0x50, + .data = kSampleData, + .bytes_transferred = kSampleData.size(), + .status = common::Error::kOk}; +} + +template +class MessageRoundTripTest : public ::testing::Test {}; + +using AllMessageTypes = + ::testing::Types; +TYPED_TEST_SUITE(MessageRoundTripTest, AllMessageTypes); + +TYPED_TEST(MessageRoundTripTest, EncodeDecodeRoundTrips) { + const TypeParam original = Sample(); + auto decoded = Decode(Encode(original)); + ASSERT_TRUE(decoded); + EXPECT_EQ(*decoded, original); +} + +// Pins the exact wire format the Python emulator parses; a change here is a +// protocol change, not a refactor. +TEST(EmulatorMessageJsonEncoderTest, PinRequestWireFormat) { + const std::string wire_json{ R"({"name":"PA0","object":"Pin","operation":"Set","state":"High","type":"Request"})"}; - const PinEmulatorRequest expected_request{.type = MessageType::kRequest, - .object = ObjectType::kPin, - .name = "PA0", - .operation = OperationType::kSet, - .state = PinState::kHigh}; - auto result = Decode(json); - ASSERT_TRUE(result); - EXPECT_EQ(*result, expected_request); -} - -TEST(EmulatorMessageJsonEncoderTest, EncodeDecodePinEmulatorRequest) { - const PinEmulatorRequest request{.type = MessageType::kRequest, - .object = ObjectType::kPin, - .name = "PA0", - .operation = OperationType::kSet, - .state = PinState::kHigh}; - const auto json{Encode(request)}; - auto decoded_request{Decode(json)}; - ASSERT_TRUE(decoded_request); - EXPECT_EQ(*decoded_request, request); -} - -TEST(EmulatorMessageJsonEncoderTest, DecodeInvalidJson) { - const std::string invalid_json{"not valid json"}; - auto result = Decode(invalid_json); - EXPECT_FALSE(result); + + EXPECT_EQ(Encode(Sample()), wire_json); + + auto decoded = Decode(wire_json); + ASSERT_TRUE(decoded); + EXPECT_EQ(*decoded, Sample()); +} + +TEST(EmulatorMessageJsonEncoderTest, DecodeRejectsInvalidJson) { + auto result = Decode("not valid json"); + ASSERT_FALSE(result); + EXPECT_EQ(result.error(), common::Error::kInvalidArgument); +} + +// Valid JSON, wrong shape: exercises the type-mismatch path (the narrow catch +// around get), not the parser. +TEST(EmulatorMessageJsonEncoderTest, DecodeRejectsWrongStructure) { + auto result = Decode(R"({"unrelated": 42})"); + ASSERT_FALSE(result); EXPECT_EQ(result.error(), common::Error::kInvalidArgument); } diff --git a/src/libs/mcu/host/test_support.hpp b/src/libs/mcu/host/test_support.hpp new file mode 100644 index 0000000..f535e22 --- /dev/null +++ b/src/libs/mcu/host/test_support.hpp @@ -0,0 +1,78 @@ +#pragma once + +// Shared helpers for the host-transport and host-peripheral tests. Test-only: +// this header is not part of any library's file set. + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libs/common/logger.hpp" + +namespace mcu::test { + +// Collects log output so a test can assert on the reason for a failure — or on +// how often something happened (e.g. how many times Send() went round its +// retry loop) — not merely that a failure occurred. The mutex is load-bearing: +// the transport logs from its server thread while the test thread is acting. +class RecordingLogger : public common::Logger { + public: + auto Debug(std::string_view msg) -> void override { Record(msg); } + auto Info(std::string_view msg) -> void override { Record(msg); } + auto Warning(std::string_view msg) -> void override { Record(msg); } + auto Error(std::string_view msg) -> void override { Record(msg); } + + auto Count(std::string_view needle) const -> std::size_t { + const std::lock_guard lock(mutex_); + return static_cast( + std::ranges::count_if(messages_, [needle](const std::string& msg) { + return msg.find(needle) != std::string::npos; + })); + } + + auto Contains(std::string_view needle) const -> bool { + return Count(needle) > 0; + } + + private: + auto Record(std::string_view msg) -> void { + const std::lock_guard lock(mutex_); + messages_.emplace_back(msg); + } + + mutable std::mutex mutex_; + std::vector messages_; +}; + +// Per-process endpoints. gtest_discover_tests gives every case its own +// process, so a fixed path would make `ctest -j` cases contend for one +// endpoint — and a fixed path shared with the real emulator's defaults would +// have unit tests fighting a live emulator session. +inline auto MakeEndpoint(std::string_view prefix, + std::string_view role) -> std::string { + return "ipc:///tmp/" + std::string{prefix} + "_" + std::string{role} + "_" + + std::to_string(::getpid()) + ".ipc"; +} + +inline auto EndpointPath(std::string_view endpoint) -> std::string { + return std::string{endpoint.substr(std::string_view{"ipc://"}.size())}; +} + +// The transport never unlinks its own lock file — doing so would reopen the +// race it closes — so per-process test endpoints would otherwise pile up in +// /tmp, one socket + .lock pair per test case per run. +inline auto RemoveEndpointArtifacts(std::string_view endpoint) -> void { + const std::string path{EndpointPath(endpoint)}; + std::error_code error{}; + std::filesystem::remove(path, error); + std::filesystem::remove(path + ".lock", error); +} + +} // namespace mcu::test diff --git a/src/libs/mcu/host/test_zmq_transport.cpp b/src/libs/mcu/host/test_zmq_transport.cpp index 897a321..c22490f 100644 --- a/src/libs/mcu/host/test_zmq_transport.cpp +++ b/src/libs/mcu/host/test_zmq_transport.cpp @@ -19,22 +19,14 @@ #include "dispatcher.hpp" #include "libs/common/logger.hpp" +#include "libs/mcu/host/test_support.hpp" #include "zmq_transport.hpp" namespace mcu { namespace { -// Deliberately not the emulator's real endpoints, and per-process. -// -// This fixture used to bind ipc:///tmp/device_emulator.ipc -- byte-identical to -// HostBoard::Endpoints and to DeviceEmulator's defaults -- so running the unit -// tests while an emulator or blinky was up had them fighting over one path. The -// pid suffix additionally lets `ctest -j` work: gtest_discover_tests gives each -// case its own process, and with a fixed path those processes contended for the -// same endpoint. auto Endpoint(std::string_view role) -> std::string { - return "ipc:///tmp/test_transport_" + std::string{role} + "_" + - std::to_string(::getpid()) + ".ipc"; + return test::MakeEndpoint("test_transport", role); } class ZmqTransportTest : public ::testing::Test { @@ -64,15 +56,8 @@ class ZmqTransportTest : public ::testing::Test { socket_.close(); context_.close(); - // The transport never unlinks its own lock file (that would reopen the race - // it closes), so clean up this process's endpoints here. - std::error_code error{}; - for (const auto& endpoint : {emulator_endpoint_, device_endpoint_}) { - const std::string path{ - endpoint.substr(std::string_view{"ipc://"}.size())}; - std::filesystem::remove(path, error); - std::filesystem::remove(path + ".lock", error); - } + test::RemoveEndpointArtifacts(emulator_endpoint_); + test::RemoveEndpointArtifacts(device_endpoint_); } private: @@ -133,34 +118,6 @@ TEST_F(ZmqTransportTest, SendReceive) { ASSERT_EQ(response.value(), "World"); } -// Counts log lines, so a test can assert how many times Send() went round its -// retry loop rather than merely that it eventually failed. The mutex guards -// against the server thread logging concurrently with the test thread. -class CountingLogger : public common::Logger { - public: - auto Debug(std::string_view msg) -> void override { Record(msg); } - auto Info(std::string_view msg) -> void override { Record(msg); } - auto Warning(std::string_view msg) -> void override { Record(msg); } - auto Error(std::string_view msg) -> void override { Record(msg); } - - auto Count(std::string_view needle) const -> std::size_t { - const std::lock_guard lock(mutex_); - return static_cast( - std::ranges::count_if(messages_, [needle](const std::string& msg) { - return msg.find(needle) != std::string::npos; - })); - } - - private: - auto Record(std::string_view msg) -> void { - const std::lock_guard lock(mutex_); - messages_.emplace_back(msg); - } - - mutable std::mutex mutex_; - std::vector messages_; -}; - // Drives Send() into genuine backpressure, and reports the send that met it. // // The obvious setup -- point the transport at an endpoint nobody binds -- does @@ -197,13 +154,7 @@ auto SendUntilQueueBlocks(ZmqTransport& transport) -> BlockedSend { class ZmqTransportRetryTest : public ::testing::Test { protected: - void TearDown() override { - std::error_code error{}; - const std::string path{ - own_endpoint_.substr(std::string_view{"ipc://"}.size())}; - std::filesystem::remove(path, error); - std::filesystem::remove(path + ".lock", error); - } + void TearDown() override { test::RemoveEndpointArtifacts(own_endpoint_); } static auto MakeConfig( common::Logger& logger, std::chrono::milliseconds send_timeout, @@ -222,7 +173,7 @@ class ZmqTransportRetryTest : public ::testing::Test { const std::string absent_peer_endpoint_{Endpoint("absent_peer")}; const std::string own_endpoint_{Endpoint("retry_own")}; - CountingLogger logger_; + test::RecordingLogger logger_; const ReceiverMap receiver_map_; }; diff --git a/src/libs/mcu/host/test_zmq_transport_startup.cpp b/src/libs/mcu/host/test_zmq_transport_startup.cpp index fb1e236..915fc0a 100644 --- a/src/libs/mcu/host/test_zmq_transport_startup.cpp +++ b/src/libs/mcu/host/test_zmq_transport_startup.cpp @@ -37,6 +37,7 @@ #include "libs/common/logger.hpp" #include "libs/mcu/host/dispatcher.hpp" #include "libs/mcu/host/receiver.hpp" +#include "libs/mcu/host/test_support.hpp" #include "libs/mcu/host/zmq_transport.hpp" namespace { @@ -47,32 +48,7 @@ using CreateResult = constexpr auto kWatchdogBudget = std::chrono::seconds{10}; constexpr auto kStartupTimeout = std::chrono::milliseconds{2000}; -// Collects log output so a test can assert on the reason for a failure, not -// merely that one occurred. The mutex is load-bearing: the bind phase logs from -// the server thread while the test thread is still inside the constructor. -class RecordingLogger : public common::Logger { - public: - auto Debug(std::string_view msg) -> void override { Record(msg); } - auto Info(std::string_view msg) -> void override { Record(msg); } - auto Warning(std::string_view msg) -> void override { Record(msg); } - auto Error(std::string_view msg) -> void override { Record(msg); } - - auto Contains(std::string_view needle) const -> bool { - const std::lock_guard lock(mutex_); - return std::ranges::any_of(messages_, [needle](const std::string& msg) { - return msg.find(needle) != std::string::npos; - }); - } - - private: - auto Record(std::string_view msg) -> void { - const std::lock_guard lock(mutex_); - messages_.emplace_back(msg); - } - - mutable std::mutex mutex_; - std::vector messages_; -}; +using mcu::test::RecordingLogger; // Answers anything, so a test can prove a message reached a given transport's // dispatcher rather than some other process that stole the endpoint. @@ -94,7 +70,7 @@ auto UniqueEndpoint(std::string_view suffix) -> std::string { } auto PathOf(const std::string& endpoint) -> std::string { - return endpoint.substr(std::string_view{"ipc://"}.size()); + return mcu::test::EndpointPath(endpoint); } auto MakeConfig(common::Logger& logger) -> mcu::TransportConfig { @@ -231,11 +207,8 @@ auto CountConcurrentBindWinners(const std::string& contested) -> int { } ::munmap(gate, sizeof(std::atomic)); - std::error_code error{}; for (int i = 0; i < kContenders; ++i) { - const std::string own = PathOf(contested) + ".peer" + std::to_string(i); - std::filesystem::remove(own, error); - std::filesystem::remove(own + ".lock", error); + mcu::test::RemoveEndpointArtifacts(contested + ".peer" + std::to_string(i)); } return winners; } @@ -243,10 +216,8 @@ auto CountConcurrentBindWinners(const std::string& contested) -> int { class ZmqTransportStartupTest : public ::testing::Test { protected: void TearDown() override { - std::error_code error{}; for (const auto& endpoint : cleanup_) { - std::filesystem::remove(PathOf(endpoint), error); - std::filesystem::remove(PathOf(endpoint) + ".lock", error); + mcu::test::RemoveEndpointArtifacts(endpoint); } } From 89b838c0239a7d061eae06b1811aac32e9535aa0 Mon Sep 17 00:00:00 2001 From: Nehal Patel Date: Sat, 29 Aug 2026 16:09:36 +0000 Subject: [PATCH 14/17] refactor(emulator): one Peripheral class; wire vocabulary as StrEnums MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The three peripheral modules were written by copy-paste: handle_message, handle_response, and the setter pair were near-identical three times over, and the 12-line install/restore wait ritual appeared seven times — already drifted, since the pin and I2C wait handlers chained the previously-installed hook while the UART ones silently dropped it, so a nested UART wait lost the outer callback. All of that now lives once in Peripheral (peripheral.py): message routing keyed on a class-level OBJECT_TYPE, the request/response hooks, _transact for the blocking request/reply exchange (pin set/get and uart send_data were the same sequence three times), and _wait_for(predicate, timeout), which chains by construction. The seven wait_for_* methods collapse to predicates. The wire vocabulary — Request/Response, Pin/Uart/I2C, Set/Get/Send/ Receive, and the status codes — is now a set of StrEnums in common.py that name emulator_message_json_encoder.hpp as their C++ mirror. Status covers all twelve C++ Error values instead of five; PinDirection uses the wire spellings Input/Output instead of an IN/OUT that disagreed with the C++ table; and since a StrEnum serializes as its value, the fifteen '.name' call sites disappear. PinState/PinDirection follow suit. Deleted, as called by nothing anywhere: the five name-lookup wrappers on DeviceEmulator (uart_initialized, get_uart_tx_data, clear_uart_tx_data, uart_send_to_device, get_pin_state — tests use the typed accessors), I2C.read_from_device, and set_on_response on all three peripherals. DeviceEmulator routes through one dict + _dispatch instead of an if/elif chain feeding three near-identical _handle_* methods, and detects JSON by parsing it (json.JSONDecodeError) rather than sniffing braces. A pin Set from the device is now honored only for output pins — the emulator, not the device, drives inputs — matching the C++ side's guard. main() no longer sends a stray 'Hello' probe and blocks on the reply (scaffolding from before the README pointed users at it); it just runs the emulator until Ctrl-C, sets up logging there instead of at import (a library import should not mutate global logging state), and is installed as the 'host-emulator' console script the README now invokes. Imports are unconditional throughout: no 'if TYPE_CHECKING' guards, and no 'from __future__ import annotations'. The guard splits a module into two import graphs — one the type checker sees, one that exists at runtime — hiding real dependencies and defeating runtime annotation introspection, and its legitimate uses (import cycles, heavy optional dependencies) do not occur in this package. On Python 3.14, which this project requires, PEP 649's lazily evaluated annotations remove the runtime-cost rationale for both idioms. ruff's flake8-type-checking ruleset ('TC') is unselected accordingly — it exists to enforce exactly that pattern — with the convention recorded in pyproject.toml. Co-Authored-By: Claude Fable 5 --- README.md | 2 +- py/host-emulator/pyproject.toml | 10 +- .../src/host_emulator/__init__.py | 13 +- py/host-emulator/src/host_emulator/common.py | 44 +++- .../src/host_emulator/emulator.py | 159 ++++------- .../src/host_emulator/endpoint.py | 2 - py/host-emulator/src/host_emulator/i2c.py | 155 +++-------- .../src/host_emulator/peripheral.py | 103 ++++++++ py/host-emulator/src/host_emulator/pin.py | 247 +++++------------- py/host-emulator/src/host_emulator/uart.py | 161 +++--------- 10 files changed, 356 insertions(+), 540 deletions(-) create mode 100644 py/host-emulator/src/host_emulator/peripheral.py diff --git a/README.md b/README.md index 73c7387..25eadda 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ ctest --preset=host-debug -R host_emulator_test ```bash # Terminal 1: Start emulator -cd py/host-emulator && uv run python -m host_emulator.emulator +cd py/host-emulator && uv run host-emulator # Terminal 2: Run application ./build/host/bin/Debug/blinky diff --git a/py/host-emulator/pyproject.toml b/py/host-emulator/pyproject.toml index 52b5478..658d011 100644 --- a/py/host-emulator/pyproject.toml +++ b/py/host-emulator/pyproject.toml @@ -7,6 +7,9 @@ readme = "README.md" requires-python = ">=3.14" dependencies = ["pyzmq>=27"] +[project.scripts] +host-emulator = "host_emulator.emulator:main" + # PEP 735 dependency group. uv syncs this by default, so `uv run pytest` works # without extra flags (unlike an optional-dependencies extra). [dependency-groups] @@ -35,13 +38,18 @@ select = [ "UP", # pyupgrade "ARG", # flake8-unused-arguments "SIM", # flake8-simplify - "TC", # flake8-type-checking "PTH", # flake8-use-pathlib "RUF", # Ruff-specific rules ] ignore = [ "E501", # line too long (handled by formatter) ] +# flake8-type-checking ("TC") is deliberately NOT selected: this project treats +# `if TYPE_CHECKING` as an anti-pattern. It splits the module into two import +# graphs and hides real dependencies, and on Python 3.14 (PEP 649, lazily +# evaluated annotations) the runtime-cost rationale for it is gone. Imports are +# unconditional; the guard's remaining legitimate uses (import cycles, heavy +# optional deps) do not occur here. [tool.ruff.lint.isort] known-first-party = ["host_emulator"] diff --git a/py/host-emulator/src/host_emulator/__init__.py b/py/host-emulator/src/host_emulator/__init__.py index cb68e9a..10edfc5 100644 --- a/py/host-emulator/src/host_emulator/__init__.py +++ b/py/host-emulator/src/host_emulator/__init__.py @@ -1,14 +1,25 @@ """Host emulator for embedded C++ applications.""" -from .common import Status, UnhandledMessageError +from .common import ( + MessageType, + ObjectType, + Operation, + Status, + UnhandledMessageError, +) from .emulator import DeviceEmulator from .i2c import I2C +from .peripheral import Peripheral from .pin import Pin, PinDirection, PinState from .uart import Uart __all__ = [ "I2C", "DeviceEmulator", + "MessageType", + "ObjectType", + "Operation", + "Peripheral", "Pin", "PinDirection", "PinState", diff --git a/py/host-emulator/src/host_emulator/common.py b/py/host-emulator/src/host_emulator/common.py index fdd7c8d..9c7e1b9 100644 --- a/py/host-emulator/src/host_emulator/common.py +++ b/py/host-emulator/src/host_emulator/common.py @@ -1,17 +1,53 @@ -"""Common types and exceptions for the host emulator.""" +"""Common types and exceptions for the host emulator. -from enum import Enum +The string values of every enum here are wire vocabulary: they mirror the C++ +serialization tables in ``src/libs/mcu/host/emulator_message_json_encoder.hpp`` +and the two sides must change together. +""" + +from enum import StrEnum class UnhandledMessageError(Exception): """Exception raised when a message cannot be handled.""" -class Status(Enum): - """Status codes for emulator responses.""" +class MessageType(StrEnum): + """Whether a message initiates an exchange or answers one.""" + + Request = "Request" + Response = "Response" + + +class ObjectType(StrEnum): + """Which kind of peripheral a message addresses.""" + + Pin = "Pin" + Uart = "Uart" + I2C = "I2C" + + +class Operation(StrEnum): + """What a request asks the receiving side to do.""" + + Set = "Set" + Get = "Get" + Send = "Send" + Receive = "Receive" + + +class Status(StrEnum): + """Status codes for emulator responses; mirrors C++ ``common::Error``.""" Ok = "Ok" Unknown = "Unknown" InvalidArgument = "InvalidArgument" InvalidState = "InvalidState" InvalidOperation = "InvalidOperation" + OperationFailed = "OperationFailed" + Unhandled = "Unhandled" + ConnectionRefused = "ConnectionRefused" + ConnectionClosed = "ConnectionClosed" + Timeout = "Timeout" + WouldBlock = "WouldBlock" + MessageTooLarge = "MessageTooLarge" diff --git a/py/host-emulator/src/host_emulator/emulator.py b/py/host-emulator/src/host_emulator/emulator.py index 11051c2..1acbc0c 100755 --- a/py/host-emulator/src/host_emulator/emulator.py +++ b/py/host-emulator/src/host_emulator/emulator.py @@ -1,8 +1,5 @@ -#!/usr/bin/env python """Device emulator for embedded C++ applications.""" -from __future__ import annotations - import json import logging import sys @@ -14,18 +11,11 @@ from .common import UnhandledMessageError from .endpoint import EndpointLock, has_live_owner from .i2c import I2C +from .peripheral import Peripheral from .pin import Pin, PinDirection, PinState from .uart import Uart logger = logging.getLogger(__name__) -logger.setLevel(logging.INFO) - -if not logger.handlers: - console_handler = logging.StreamHandler() - console_handler.setLevel(logging.DEBUG) - formatter = logging.Formatter("[%(levelname)s] %(name)s: %(message)s") - console_handler.setFormatter(formatter) - logger.addHandler(console_handler) class DeviceEmulator: @@ -68,18 +58,25 @@ def __init__( self.from_device_socket.setsockopt(zmq.LINGER, 0) self.from_device_socket.setsockopt(zmq.RCVTIMEO, 500) - self.led_1 = Pin("LED 1", PinDirection.OUT, PinState.Low, self.to_device_socket) - self.led_2 = Pin("LED 2", PinDirection.OUT, PinState.Low, self.to_device_socket) + self.led_1 = Pin( + "LED 1", PinDirection.Output, PinState.Low, self.to_device_socket + ) + self.led_2 = Pin( + "LED 2", PinDirection.Output, PinState.Low, self.to_device_socket + ) self.button_1 = Pin( - "Button 1", PinDirection.IN, PinState.Low, self.to_device_socket + "Button 1", PinDirection.Input, PinState.Low, self.to_device_socket ) - self.pins = [self.led_1, self.led_2, self.button_1] - self.uart_1 = Uart("UART 1", self.to_device_socket) - self.uarts = [self.uart_1] - self.i2c_1 = I2C("I2C 1") - self.i2cs = [self.i2c_1] + + # Routing table: the `object` field of an incoming message selects the + # candidate peripherals, the `name` field picks one of them. + self._peripherals: dict[str, list[Peripheral]] = { + "Pin": [self.led_1, self.led_2, self.button_1], + "Uart": [self.uart_1], + "I2C": [self.i2c_1], + } self.emulator_thread = Thread(target=self.run) self._endpoint_lock = EndpointLock() @@ -107,15 +104,14 @@ def i2c1(self) -> I2C: def _bind(self) -> None: """Claim the receive endpoint and bind it. - No stale-file cleanup here, despite what the previous version did. - libzmq unlinks an ipc path before binding it, so a file left behind by a - killed process was never a problem -- and the unconditional unlink that - used to live here was itself the hazard: it would displace a *live* - emulator and take its endpoint, with no error on either side. + No stale-file cleanup here: libzmq unlinks an ipc path before binding + it, so a file left behind by a killed process is never a problem. The + hazard is the reverse — that same unlink lets a newcomer displace a + *live* emulator and take its endpoint, with no error on either side. - Two guards instead, in order. The lock is the guarantee: it is atomic, - so no other process using it can slip between the check and the bind. - The probe is the fallback for an owner that holds no lock. + Two guards, in order. The lock is the guarantee: it is atomic, so no + other process using it can slip between the check and the bind. The + probe is the fallback for an owner that holds no lock. """ endpoint = self.from_device_endpoint if not self._endpoint_lock.try_acquire(endpoint): @@ -151,30 +147,17 @@ def run(self) -> None: while self.running: try: message = self.from_device_socket.recv() + except zmq.Again: + continue # Receive timeout; recheck self.running. - if not (message.startswith(b"{") and message.endswith(b"}")): - logger.warning("Received non-JSON message: %s", message) - continue - + try: json_message: dict[str, Any] = json.loads(message) - object_type = json_message.get("object") - - if object_type == "Pin": - self._handle_pin_message(json_message) - elif object_type == "Uart": - self._handle_uart_message(json_message) - elif object_type == "I2C": - self._handle_i2c_message(json_message) - else: - raise UnhandledMessageError( - f"Unknown object type: {object_type}" - ) - - except zmq.Again: - if not self.running: - break + except json.JSONDecodeError: + logger.warning("Received non-JSON message: %s", message) continue + self._dispatch(json_message) + except Exception: logger.exception("Emulator thread error") finally: @@ -184,29 +167,20 @@ def run(self) -> None: self._endpoint_lock.release() logger.debug("Emulator thread exiting") - def _handle_pin_message(self, json_message: dict[str, Any]) -> None: - """Handle a Pin message by dispatching to the appropriate pin.""" - for pin in self.pins: - if response := pin.handle_message(json_message): - self.from_device_socket.send_string(response) - return - raise UnhandledMessageError(f"Pin not found: {json_message.get('name')}") - - def _handle_uart_message(self, json_message: dict[str, Any]) -> None: - """Handle a Uart message by dispatching to the appropriate uart.""" - for uart in self.uarts: - if response := uart.handle_message(json_message): - self.from_device_socket.send_string(response) - return - raise UnhandledMessageError(f"Uart not found: {json_message.get('name')}") + def _dispatch(self, json_message: dict[str, Any]) -> None: + """Route one decoded message to the peripheral that owns it.""" + object_type = json_message.get("object") + candidates = self._peripherals.get(str(object_type)) + if candidates is None: + raise UnhandledMessageError(f"Unknown object type: {object_type}") - def _handle_i2c_message(self, json_message: dict[str, Any]) -> None: - """Handle an I2C message by dispatching to the appropriate i2c.""" - for i2c in self.i2cs: - if response := i2c.handle_message(json_message): + for peripheral in candidates: + if response := peripheral.handle_message(json_message): self.from_device_socket.send_string(response) return - raise UnhandledMessageError(f"I2C not found: {json_message.get('name')}") + raise UnhandledMessageError( + f"{object_type} not found: {json_message.get('name')}" + ) def start(self) -> None: """Start the emulator, raising if it could not claim its endpoint. @@ -232,8 +206,8 @@ def start(self) -> None: logger.debug("Connected to %s", self.to_device_endpoint) # No settling sleep here. connect() is asynchronous and the device may # not even have bound yet; libzmq retries in the background regardless. - # PAIR blocks rather than drops, and SNDTIMEO bounds the wait, so the - # sleep bought nothing. Test-side readiness is _wait_for_process_ready. + # PAIR blocks rather than drops, and SNDTIMEO bounds the wait, so a + # sleep would buy nothing. Test-side readiness is _wait_for_process_ready. def stop(self) -> None: """Stop emulator and clean up resources.""" @@ -246,53 +220,16 @@ def stop(self) -> None: self.context.term() logger.info("Emulator stopped") - def uart_initialized(self, name: str) -> bool: - """Check if a UART with the given name exists.""" - return any(uart.name == name for uart in self.uarts) - - def get_uart_tx_data(self, name: str) -> list[int] | None: - """Get data that was transmitted (sent) from the device to the emulator.""" - for uart in self.uarts: - if uart.name == name: - if len(uart.rx_buffer) > 0: - return list(uart.rx_buffer) - return None - return None - - def clear_uart_tx_data(self, name: str) -> bool: - """Clear the TX buffer (data received from device).""" - for uart in self.uarts: - if uart.name == name: - uart.rx_buffer.clear() - return True - return False - - def uart_send_to_device(self, name: str, data: bytes) -> dict[str, Any] | None: - """Send data from emulator to device (simulating external UART input).""" - for uart in self.uarts: - if uart.name == name: - return uart.send_data(data) - return None - - def get_pin_state(self, name: str) -> PinState | None: - """Get the current state of a pin.""" - for pin in self.pins: - if pin.name == name: - return pin.state - return None - def main() -> NoReturn: + """Run the emulator until interrupted (Ctrl-C).""" + logging.basicConfig( + level=logging.INFO, format="[%(levelname)s] %(name)s: %(message)s" + ) emulator = DeviceEmulator() try: emulator.start() - logger.info("Sending Hello") - emulator.to_device_socket.send_string("Hello") - logger.info("Waiting for reply") - reply = emulator.to_device_socket.recv() - logger.info("Received reply: %s", reply) - pin_reply = emulator.user_button1().get_state() - logger.info("Received pin reply: %s", pin_reply) + logger.info("Emulator running; press Ctrl-C to stop") while emulator.running: emulator.emulator_thread.join(0.5) except (KeyboardInterrupt, SystemExit): diff --git a/py/host-emulator/src/host_emulator/endpoint.py b/py/host-emulator/src/host_emulator/endpoint.py index 16bacaa..7888d00 100644 --- a/py/host-emulator/src/host_emulator/endpoint.py +++ b/py/host-emulator/src/host_emulator/endpoint.py @@ -13,8 +13,6 @@ exclude each other. """ -from __future__ import annotations - import fcntl import logging import os diff --git a/py/host-emulator/src/host_emulator/i2c.py b/py/host-emulator/src/host_emulator/i2c.py index b9c0730..0c3ceec 100644 --- a/py/host-emulator/src/host_emulator/i2c.py +++ b/py/host-emulator/src/host_emulator/i2c.py @@ -1,53 +1,45 @@ """I2C emulation for the host emulator.""" -from __future__ import annotations - import json import logging -import threading -from typing import TYPE_CHECKING, Any - -from .common import Status +from typing import Any -if TYPE_CHECKING: - from collections.abc import Callable +from .common import MessageType, ObjectType, Operation, Status +from .peripheral import Peripheral logger = logging.getLogger(__name__) -class I2C: - """Emulates an I2C controller/peripheral.""" +class I2C(Peripheral): + """Emulates an I2C bus with one buffer per device address. + + Purely reactive: it never initiates traffic, so it takes no device socket. + """ + + OBJECT_TYPE = ObjectType.I2C def __init__(self, name: str) -> None: - self.name = name + super().__init__(name) # Store data for each I2C address (address -> bytearray) self.device_buffers: dict[int, bytearray] = {} - self.on_response: Callable[[dict[str, Any]], None] | None = None - self.on_request: Callable[[dict[str, Any]], None] | None = None def handle_request(self, message: dict[str, Any]) -> str: + address: int = message.get("address", 0) response: dict[str, Any] = { - "type": "Response", - "object": "I2C", + "type": MessageType.Response, + "object": ObjectType.I2C, "name": self.name, - "address": message.get("address", 0), + "address": address, "data": [], "bytes_transferred": 0, - "status": Status.InvalidOperation.name, + "status": Status.InvalidOperation, } - address: int = message.get("address", 0) - - if message["operation"] == "Send": + if message["operation"] == Operation.Send: # Device is sending data to I2C peripheral data: list[int] = message.get("data", []) self.device_buffers[address] = bytearray(data) - response.update( - { - "bytes_transferred": len(data), - "status": Status.Ok.name, - } - ) + response.update({"bytes_transferred": len(data), "status": Status.Ok}) logger.info( "[I2C %s] Wrote %d bytes to address 0x%02X: %s", self.name, @@ -56,20 +48,17 @@ def handle_request(self, message: dict[str, Any]) -> str: bytes(data), ) - elif message["operation"] == "Receive": + elif message["operation"] == Operation.Receive: # Device is receiving data from I2C peripheral size: int = message.get("size", 0) - if address in self.device_buffers: - bytes_to_send = min(size, len(self.device_buffers[address])) - data = list(self.device_buffers[address][:bytes_to_send]) - else: - bytes_to_send = 0 - data = [] + buffer = self.device_buffers.get(address, bytearray()) + bytes_to_send = min(size, len(buffer)) + data = list(buffer[:bytes_to_send]) response.update( { "data": data, "bytes_transferred": bytes_to_send, - "status": Status.Ok.name, + "status": Status.Ok, } ) logger.info( @@ -80,37 +69,9 @@ def handle_request(self, message: dict[str, Any]) -> str: bytes(data), ) - if self.on_request: - self.on_request(message) + self._notify_request(message) return json.dumps(response) - def handle_response(self, message: dict[str, Any]) -> None: - logger.debug("[I2C %s] Received response: %s", self.name, message) - if self.on_response: - self.on_response(message) - - def set_on_request( - self, on_request: Callable[[dict[str, Any]], None] | None - ) -> None: - self.on_request = on_request - - def set_on_response( - self, on_response: Callable[[dict[str, Any]], None] | None - ) -> None: - self.on_response = on_response - - def handle_message(self, message: dict[str, Any]) -> str | None: - if message["object"] != "I2C": - return None - if message["name"] != self.name: - return None - if message["type"] == "Request": - return self.handle_request(message) - if message["type"] == "Response": - self.handle_response(message) - return None - return None - def write_to_device(self, address: int, data: bytes | list[int]) -> None: """Write data to a simulated I2C device (for testing).""" self.device_buffers[address] = bytearray(data) @@ -121,74 +82,28 @@ def write_to_device(self, address: int, data: bytes | list[int]) -> None: bytes(data), ) - def read_from_device(self, address: int) -> bytes: - """Read data from a simulated I2C device (for testing).""" - if address in self.device_buffers: - return bytes(self.device_buffers[address]) - return b"" - def wait_for_operation( self, operation: str, address: int | None = None, timeout: float = 2.0 ) -> bool: - """Wait for a specific I2C operation to occur. - - Args: - operation: The operation to wait for ("Send" or "Receive") - address: Optional address to filter on (waits for any address if None) - timeout: Maximum time to wait in seconds - - Returns: - True if operation occurred, False if timeout - """ - event = threading.Event() - old_handler = self.on_request - - def handler(message: dict[str, Any]) -> None: - if old_handler is not None: - old_handler(message) - op_matches = message.get("operation") == operation - addr_matches = address is None or message.get("address") == address - if op_matches and addr_matches: - event.set() - - self.on_request = handler - - try: - return event.wait(timeout) - finally: - self.on_request = old_handler + """Wait for an I2C operation, optionally filtered to one address.""" + return self._wait_for( + lambda message: message.get("operation") == operation + and (address is None or message.get("address") == address), + timeout, + ) def wait_for_transactions( self, count: int, address: int | None = None, timeout: float = 2.0 ) -> bool: - """Wait for a specific number of I2C transactions (send or receive). - - Args: - count: Number of transactions to wait for - address: Optional address to filter on (waits for any address if None) - timeout: Maximum time to wait in seconds - - Returns: - True if transactions occurred, False if timeout - """ + """Wait for ``count`` transactions (Send or Receive), optionally by address.""" transactions = 0 - event = threading.Event() - old_handler = self.on_request - def handler(message: dict[str, Any]) -> None: + def is_final_transaction(message: dict[str, Any]) -> bool: nonlocal transactions - if old_handler is not None: - old_handler(message) operation = message.get("operation") addr_matches = address is None or message.get("address") == address - if operation in ("Send", "Receive") and addr_matches: + if operation in (Operation.Send, Operation.Receive) and addr_matches: transactions += 1 - if transactions >= count: - event.set() - - self.on_request = handler + return transactions >= count - try: - return event.wait(timeout) - finally: - self.on_request = old_handler + return self._wait_for(is_final_transaction, timeout) diff --git a/py/host-emulator/src/host_emulator/peripheral.py b/py/host-emulator/src/host_emulator/peripheral.py new file mode 100644 index 0000000..ec1d126 --- /dev/null +++ b/py/host-emulator/src/host_emulator/peripheral.py @@ -0,0 +1,103 @@ +"""Shared behavior for emulated peripherals. + +Each peripheral (Pin, Uart, I2C) supplies only its protocol — +``handle_request`` — and inherits everything else: message routing, +request/response hooks, the blocking request helper, and the wait-for-condition +machinery the tests build on. +""" + +import json +import logging +import threading +from abc import ABC, abstractmethod +from collections.abc import Callable +from typing import Any, ClassVar + +import zmq + +from .common import MessageType + +logger = logging.getLogger(__name__) + + +class Peripheral(ABC): + """An emulated peripheral addressed by object type and name.""" + + #: The wire value of the ``object`` field this peripheral answers to. + OBJECT_TYPE: ClassVar[str] + + def __init__( + self, name: str, to_device_socket: zmq.Socket[bytes] | None = None + ) -> None: + self.name = name + self.to_device_socket = to_device_socket + self.on_request: Callable[[dict[str, Any]], None] | None = None + self.on_response: Callable[[dict[str, Any]], None] | None = None + + @abstractmethod + def handle_request(self, message: dict[str, Any]) -> str: + """Answer one request from the device; returns the encoded response.""" + + def handle_message(self, message: dict[str, Any]) -> str | None: + """Route a decoded message: the response to send, or None if not ours.""" + if message["object"] != self.OBJECT_TYPE or message["name"] != self.name: + return None + if message["type"] == MessageType.Request: + return self.handle_request(message) + if message["type"] == MessageType.Response: + self.handle_response(message) + return None + + def handle_response(self, message: dict[str, Any]) -> None: + logger.debug( + "[%s %s] Received response: %s", self.OBJECT_TYPE, self.name, message + ) + if self.on_response: + self.on_response(message) + + def set_on_request( + self, on_request: Callable[[dict[str, Any]], None] | None + ) -> None: + self.on_request = on_request + + def _notify_request(self, message: dict[str, Any]) -> None: + """Invoke the request hook; handle_request implementations call this.""" + if self.on_request: + self.on_request(message) + + def _transact(self, request: dict[str, Any]) -> dict[str, Any]: + """Send one request to the device and return the decoded reply.""" + if self.to_device_socket is None: + msg = f"{self.OBJECT_TYPE} {self.name} has no device socket" + raise RuntimeError(msg) + logger.debug( + "[%s %s] Sending request: %s", self.OBJECT_TYPE, self.name, request + ) + self.to_device_socket.send_string(json.dumps(request)) + reply = self.to_device_socket.recv() + response: dict[str, Any] = json.loads(reply) + self.handle_response(response) + return response + + def _wait_for( + self, predicate: Callable[[dict[str, Any]], bool], timeout: float + ) -> bool: + """Block until a request satisfying ``predicate`` arrives, or time out. + + Installs a temporary request hook, chained after any existing one so + nested waits observe every message, and always restores the original. + """ + event = threading.Event() + old_handler = self.on_request + + def handler(message: dict[str, Any]) -> None: + if old_handler is not None: + old_handler(message) + if predicate(message): + event.set() + + self.on_request = handler + try: + return event.wait(timeout) + finally: + self.on_request = old_handler diff --git a/py/host-emulator/src/host_emulator/pin.py b/py/host-emulator/src/host_emulator/pin.py index 161d375..3ee2139 100644 --- a/py/host-emulator/src/host_emulator/pin.py +++ b/py/host-emulator/src/host_emulator/pin.py @@ -1,233 +1,124 @@ """Pin emulation for the host emulator.""" -from __future__ import annotations - import json -import logging -import threading -from enum import Enum -from typing import TYPE_CHECKING, Any - -from .common import Status +from enum import StrEnum +from typing import Any -if TYPE_CHECKING: - from collections.abc import Callable +import zmq - import zmq +from .common import MessageType, ObjectType, Operation, Status +from .peripheral import Peripheral -logger = logging.getLogger(__name__) +class PinDirection(StrEnum): + """Pin direction from the device's point of view. -class PinDirection(Enum): - """Pin direction configuration.""" + Wire values mirror C++ ``mcu::PinDirection`` (direction is not currently + sent on the wire, but the vocabularies must not drift). + """ - IN = "IN" - OUT = "OUT" + Input = "Input" + Output = "Output" -class PinState(Enum): - """Pin state values.""" +class PinState(StrEnum): + """Pin state values; wire values mirror C++ ``mcu::PinState``.""" Low = "Low" High = "High" Hi_Z = "Hi_Z" -class Pin: +class Pin(Peripheral): """Emulates a digital pin (input/output).""" + OBJECT_TYPE = ObjectType.Pin + def __init__( self, name: str, - pin_direction: PinDirection, + direction: PinDirection, initial_state: PinState, to_device_socket: zmq.Socket[bytes], ) -> None: - self.name = name - self.pin_direction = pin_direction + super().__init__(name, to_device_socket) + self.direction = direction self.state = initial_state - self.to_device_socket = to_device_socket - self.on_response: Callable[[dict[str, Any]], None] | None = None - self.on_request: Callable[[dict[str, Any]], None] | None = None def handle_request(self, message: dict[str, Any]) -> str: response: dict[str, Any] = { - "type": "Response", - "object": "Pin", + "type": MessageType.Response, + "object": ObjectType.Pin, "name": self.name, - "state": self.state.name, - "status": Status.InvalidOperation.name, + "state": self.state, + "status": Status.InvalidOperation, } - if message["operation"] == "Get": - response.update( - { - "status": Status.Ok.name, - } - ) - elif message["operation"] == "Set": - self.state = PinState[message["state"]] - response.update( - { - "state": self.state.name, - "status": Status.Ok.name, - } - ) - # default response status is InvalidOperation - - if self.on_request: - self.on_request(message) + if message["operation"] == Operation.Get: + response.update({"status": Status.Ok}) + elif ( + message["operation"] == Operation.Set + and self.direction is PinDirection.Output + ): + self.state = PinState(message["state"]) + response.update({"state": self.state, "status": Status.Ok}) + # A device Set on its own input pin keeps the default InvalidOperation + # status: the emulator, not the device, drives input pins. + + self._notify_request(message) return json.dumps(response) def set_state(self, state: PinState) -> dict[str, Any]: + """Drive the pin from the emulator side (e.g. press a button).""" self.state = state - request = { - "type": "Request", - "object": "Pin", - "name": self.name, - "operation": "Set", - "state": self.state.name, - } - logger.debug("[Pin Set] Sending request: %s", request) - self.to_device_socket.send_string(json.dumps(request)) - reply = self.to_device_socket.recv() - logger.debug("[Pin Set] Received response: %s", reply) - response: dict[str, Any] = json.loads(reply) - self.handle_response(response) - return response - - def get_state(self) -> dict[str, Any]: - request = { - "type": "Request", - "object": "Pin", - "name": self.name, - "operation": "Get", - "state": PinState.Hi_Z.name, - } - logger.debug("[Pin Get] Sending request: %s", request) - self.to_device_socket.send_string(json.dumps(request)) - reply = self.to_device_socket.recv() - logger.debug("[Pin Get] Received response: %s", reply) - response: dict[str, Any] = json.loads(reply) - self.handle_response(response) - return response - - def handle_response(self, message: dict[str, Any]) -> None: - logger.debug("[Pin Handler] Received response: %s", message) - if self.on_response: - self.on_response(message) - - def set_on_request( - self, on_request: Callable[[dict[str, Any]], None] | None - ) -> None: - logger.debug( - "[Pin Handler] Setting on_request for %s: %s", self.name, on_request + return self._transact( + { + "type": MessageType.Request, + "object": ObjectType.Pin, + "name": self.name, + "operation": Operation.Set, + "state": self.state, + } ) - self.on_request = on_request - def set_on_response( - self, on_response: Callable[[dict[str, Any]], None] | None - ) -> None: - logger.debug( - "[Pin Handler] Setting on_response for %s: %s", self.name, on_response + def get_state(self) -> dict[str, Any]: + """Ask the device for its view of the pin.""" + return self._transact( + { + "type": MessageType.Request, + "object": ObjectType.Pin, + "name": self.name, + "operation": Operation.Get, + "state": PinState.Hi_Z, + } ) - self.on_response = on_response - - def handle_message(self, message: dict[str, Any]) -> str | None: - if message["object"] != "Pin": - return None - if message["name"] != self.name: - return None - if message["type"] == "Request": - return self.handle_request(message) - if message["type"] == "Response": - self.handle_response(message) - return None - return None def wait_for_operation(self, operation: str, timeout: float = 2.0) -> bool: - """Wait for a specific pin operation to occur. - - Args: - operation: The operation to wait for ("Get" or "Set") - timeout: Maximum time to wait in seconds - - Returns: - True if operation occurred, False if timeout - """ - event = threading.Event() - old_handler = self.on_request - - def handler(message: dict[str, Any]) -> None: - if old_handler is not None: - old_handler(message) - if message.get("operation") == operation: - event.set() - - self.on_request = handler - - try: - return event.wait(timeout) - finally: - self.on_request = old_handler + """Wait for a specific pin operation ("Get" or "Set") to occur.""" + return self._wait_for( + lambda message: message.get("operation") == operation, timeout + ) def wait_for_state(self, state: PinState, timeout: float = 2.0) -> bool: - """Wait for pin to reach a specific state. - - Args: - state: The state to wait for (PinState.High, PinState.Low, etc.) - timeout: Maximum time to wait in seconds - - Returns: - True if state reached, False if timeout - """ + """Wait for the pin to reach a specific state.""" if self.state == state: return True - - event = threading.Event() - old_handler = self.on_request - - def handler(message: dict[str, Any]) -> None: - if old_handler is not None: - old_handler(message) - if message.get("operation") == "Set" and message.get("state") == state.name: - event.set() - - self.on_request = handler - - try: - return event.wait(timeout) - finally: - self.on_request = old_handler + return self._wait_for( + lambda message: message.get("operation") == Operation.Set + and message.get("state") == state, + timeout, + ) def wait_for_transitions(self, count: int, timeout: float = 2.0) -> bool: - """Wait for a specific number of state transitions (toggles). - - Args: - count: Number of transitions to wait for - timeout: Maximum time to wait in seconds - - Returns: - True if transitions occurred, False if timeout - """ + """Wait for a specific number of state transitions (toggles).""" transitions = 0 - event = threading.Event() last_state: str | None = None - old_handler = self.on_request - def handler(message: dict[str, Any]) -> None: + def is_final_transition(message: dict[str, Any]) -> bool: nonlocal transitions, last_state - if old_handler is not None: - old_handler(message) current_state = message.get("state") if last_state is not None and current_state != last_state: transitions += 1 - if transitions >= count: - event.set() last_state = current_state + return transitions >= count - self.on_request = handler - - try: - return event.wait(timeout) - finally: - self.on_request = old_handler + return self._wait_for(is_final_transition, timeout) diff --git a/py/host-emulator/src/host_emulator/uart.py b/py/host-emulator/src/host_emulator/uart.py index 01558cc..07415e3 100644 --- a/py/host-emulator/src/host_emulator/uart.py +++ b/py/host-emulator/src/host_emulator/uart.py @@ -1,60 +1,45 @@ """UART emulation for the host emulator.""" -from __future__ import annotations - import json import logging -import threading -from typing import TYPE_CHECKING, Any - -from .common import Status +from typing import Any -if TYPE_CHECKING: - from collections.abc import Callable +import zmq - import zmq +from .common import MessageType, ObjectType, Operation, Status +from .peripheral import Peripheral logger = logging.getLogger(__name__) -class Uart: +class Uart(Peripheral): """Emulates a UART peripheral.""" + OBJECT_TYPE = ObjectType.Uart + def __init__(self, name: str, to_device_socket: zmq.Socket[bytes]) -> None: - self.name = name - self.to_device_socket = to_device_socket - self.rx_buffer = bytearray() # Data waiting to be read - self.on_response: Callable[[dict[str, Any]], None] | None = None - self.on_request: Callable[[dict[str, Any]], None] | None = None + super().__init__(name, to_device_socket) + self.rx_buffer = bytearray() # Data the device has sent us def handle_request(self, message: dict[str, Any]) -> str: response: dict[str, Any] = { - "type": "Response", - "object": "Uart", + "type": MessageType.Response, + "object": ObjectType.Uart, "name": self.name, "data": [], "bytes_transferred": 0, - "status": Status.InvalidOperation.name, + "status": Status.InvalidOperation, } - if message["operation"] == "Init": - logger.info("[UART %s] Initialized", self.name) - response.update({"status": Status.Ok.name}) - - elif message["operation"] == "Send": + if message["operation"] == Operation.Send: data: list[int] = message.get("data", []) self.rx_buffer.extend(data) - response.update( - { - "bytes_transferred": len(data), - "status": Status.Ok.name, - } - ) + response.update({"bytes_transferred": len(data), "status": Status.Ok}) logger.info( "[UART %s] Received %d bytes: %s", self.name, len(data), bytes(data) ) - elif message["operation"] == "Receive": + elif message["operation"] == Operation.Receive: size: int = message.get("size", 0) bytes_to_send = min(size, len(self.rx_buffer)) data = list(self.rx_buffer[:bytes_to_send]) @@ -63,111 +48,43 @@ def handle_request(self, message: dict[str, Any]) -> str: { "data": data, "bytes_transferred": bytes_to_send, - "status": Status.Ok.name, + "status": Status.Ok, } ) logger.info( "[UART %s] Sent %d bytes: %s", self.name, bytes_to_send, bytes(data) ) - if self.on_request: - self.on_request(message) + self._notify_request(message) return json.dumps(response) def send_data(self, data: bytes | list[int]) -> dict[str, Any]: """Send data to the device (emulator -> device).""" - data_list = list(data) if isinstance(data, bytes) else data - request = { - "type": "Request", - "object": "Uart", - "name": self.name, - "operation": "Receive", - "data": data_list, - "size": len(data_list), - "timeout_ms": 0, - } - logger.debug("[UART %s] Sending data to device: %s", self.name, data) - self.to_device_socket.send_string(json.dumps(request)) - reply = self.to_device_socket.recv() - logger.debug("[UART %s] Received response: %s", self.name, reply) - result: dict[str, Any] = json.loads(reply) - return result - - def handle_response(self, message: dict[str, Any]) -> None: - logger.debug("[UART %s] Received response: %s", self.name, message) - if self.on_response: - self.on_response(message) - - def set_on_request( - self, on_request: Callable[[dict[str, Any]], None] | None - ) -> None: - self.on_request = on_request - - def set_on_response( - self, on_response: Callable[[dict[str, Any]], None] | None - ) -> None: - self.on_response = on_response - - def handle_message(self, message: dict[str, Any]) -> str | None: - if message["object"] != "Uart": - return None - if message["name"] != self.name: - return None - if message["type"] == "Request": - return self.handle_request(message) - if message["type"] == "Response": - self.handle_response(message) - return None - return None + data_list = list(data) + return self._transact( + { + "type": MessageType.Request, + "object": ObjectType.Uart, + "name": self.name, + "operation": Operation.Receive, + "data": data_list, + "size": len(data_list), + "timeout_ms": 0, + } + ) def wait_for_data(self, min_bytes: int = 1, timeout: float = 2.0) -> bool: - """Wait for UART to receive at least min_bytes of data from device. - - Args: - min_bytes: Minimum number of bytes to wait for - timeout: Maximum time to wait in seconds - - Returns: - True if data received, False if timeout - """ - event = threading.Event() - old_handler = self.on_request - - def handler(message: dict[str, Any]) -> None: - if message.get("operation") == "Send" and len(self.rx_buffer) >= min_bytes: - event.set() - - # Check if we already have enough data + """Wait until the device has sent at least ``min_bytes`` in total.""" if len(self.rx_buffer) >= min_bytes: return True - - self.on_request = handler - - try: - return event.wait(timeout) - finally: - self.on_request = old_handler + return self._wait_for( + lambda message: message.get("operation") == Operation.Send + and len(self.rx_buffer) >= min_bytes, + timeout, + ) def wait_for_operation(self, operation: str, timeout: float = 2.0) -> bool: - """Wait for a specific UART operation to occur. - - Args: - operation: The operation to wait for ("Init", "Send", "Receive") - timeout: Maximum time to wait in seconds - - Returns: - True if operation occurred, False if timeout - """ - event = threading.Event() - old_handler = self.on_request - - def handler(message: dict[str, Any]) -> None: - if message.get("operation") == operation: - event.set() - - self.on_request = handler - - try: - return event.wait(timeout) - finally: - self.on_request = old_handler + """Wait for a specific UART operation ("Send" or "Receive") to occur.""" + return self._wait_for( + lambda message: message.get("operation") == operation, timeout + ) From f9a832ce425e9ae0f76e40e78e4637557bfdf8af Mon Sep 17 00:00:00 2001 From: Nehal Patel Date: Sat, 29 Aug 2026 16:12:07 +0000 Subject: [PATCH 15/17] refactor(pytests): usefixtures, shared helpers, and honest tooling config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Test cleanups: - the eleven '_ = fixture # ensure X is running' lines become @pytest.mark.usefixtures marks, and the two vacuous 'is not None' asserts on fixtures are gone (a fixture returning None fails at setup) - conftest imports endpoint_path from the package instead of carrying a verbatim copy, types the application-fixture factory instead of returning Any, and reports a missing executable via pytest.fail rather than a bare assert (stripped under python -O) - an autouse fixture uninstalls any on_request/on_response hooks a test left installed — the silent cross-test contamination module-scoped fixtures allow. Buffers stay explicitly managed by the tests that care: some sharing is deliberate (the UART greeting arrives once, at app start), so a blanket reset would be wrong - the I2C address/pattern and UART greeting literals copied from the C++ apps are module constants naming their source of truth Tooling: - pytest-cov is removed: nothing wired it up (coverage is the C++ llvm-cov pipeline), so it was installed into every venv for nothing - mypy gets files = [src, tests], so 'uv run mypy' with no arguments checks both trees — CI previously checked only src while tests/ ran unchecked; CI and CLAUDE.md shorten accordingly - the pythonpath entry gains the one-line comment saying why it exists alongside the editable install DeviceEmulator grows all_peripherals() so tests reset hooks through a public accessor rather than reaching into the routing table. The tests follow the package's no-TYPE_CHECKING convention: imports are unconditional, and conftest's AppFixture alias is a PEP 695 'type' statement (lazily evaluated by design) rather than a quoted alias inside a guard. Co-Authored-By: Claude Fable 5 --- .github/workflows/ci.yml | 4 +- CLAUDE.md | 2 +- py/host-emulator/pyproject.toml | 6 +- .../src/host_emulator/emulator.py | 6 ++ py/host-emulator/tests/conftest.py | 44 +++++++++------ py/host-emulator/tests/test_blinky.py | 26 +++------ .../tests/test_endpoint_ownership.py | 7 +-- py/host-emulator/tests/test_i2c_demo.py | 56 ++++++++----------- py/host-emulator/tests/test_uart_echo.py | 37 +++++------- py/host-emulator/uv.lock | 51 ----------------- 10 files changed, 86 insertions(+), 153 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 14695df..67ba43a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,8 +60,8 @@ jobs: - name: Type-check Python working-directory: py/host-emulator run: | - if ! uv run --frozen mypy src; then - echo "::error::Python type errors. Reproduce with: uv run mypy src" + if ! uv run --frozen mypy; then + echo "::error::Python type errors. Reproduce with: uv run mypy" exit 1 fi diff --git a/CLAUDE.md b/CLAUDE.md index a579585..267342f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -37,7 +37,7 @@ cmake --build build/host --target format-check # single commit with `git commit --no-verify`. # Python type-check (not covered by format.sh - types are not formatting) -cd py/host-emulator && uv run mypy src +cd py/host-emulator && uv run mypy # Cross-compile for ARM - not yet functional. Toolchain files and configure # presets exist, but only the `host` MCU/board implementations do; configuring diff --git a/py/host-emulator/pyproject.toml b/py/host-emulator/pyproject.toml index 658d011..e29b0f4 100644 --- a/py/host-emulator/pyproject.toml +++ b/py/host-emulator/pyproject.toml @@ -13,7 +13,7 @@ host-emulator = "host_emulator.emulator:main" # PEP 735 dependency group. uv syncs this by default, so `uv run pytest` works # without extra flags (unlike an optional-dependencies extra). [dependency-groups] -dev = ["pytest>=9", "pytest-cov>=7", "ruff>=0.14", "mypy>=1.19"] +dev = ["pytest>=9", "ruff>=0.14", "mypy>=1.19"] [build-system] requires = ["hatchling"] @@ -56,6 +56,8 @@ known-first-party = ["host_emulator"] [tool.mypy] python_version = "3.14" +# Both trees, so `uv run mypy` with no arguments checks everything CI checks. +files = ["src", "tests"] # `strict` already implies warn_return_any, warn_unused_ignores, # disallow_untyped_defs/decorators, disallow_incomplete_defs, check_untyped_defs, # no_implicit_optional, warn_redundant_casts and warn_unused_configs. @@ -67,4 +69,6 @@ ignore_missing_imports = true [tool.pytest.ini_options] testpaths = ["tests"] +# Redundant with the editable install uv sync performs, but it lets a bare +# `pytest` (no uv, no install) still resolve the package. pythonpath = ["src"] diff --git a/py/host-emulator/src/host_emulator/emulator.py b/py/host-emulator/src/host_emulator/emulator.py index 1acbc0c..88b02a4 100755 --- a/py/host-emulator/src/host_emulator/emulator.py +++ b/py/host-emulator/src/host_emulator/emulator.py @@ -101,6 +101,12 @@ def uart1(self) -> Uart: def i2c1(self) -> I2C: return self.i2c_1 + def all_peripherals(self) -> list[Peripheral]: + """Every emulated peripheral, across all object types.""" + return [ + peripheral for group in self._peripherals.values() for peripheral in group + ] + def _bind(self) -> None: """Claim the receive endpoint and bind it. diff --git a/py/host-emulator/tests/conftest.py b/py/host-emulator/tests/conftest.py index c56576c..0a468d9 100644 --- a/py/host-emulator/tests/conftest.py +++ b/py/host-emulator/tests/conftest.py @@ -1,22 +1,23 @@ """Pytest configuration and fixtures for host-emulator tests.""" -from __future__ import annotations - import logging import subprocess import time +from collections.abc import Callable, Generator from pathlib import Path -from typing import TYPE_CHECKING, Any import pytest from host_emulator import DeviceEmulator - -if TYPE_CHECKING: - from collections.abc import Generator +from host_emulator.endpoint import endpoint_path logger = logging.getLogger(__name__) +type AppFixture = Callable[ + [pytest.FixtureRequest, DeviceEmulator], + Generator[subprocess.Popen[bytes]], +] + def pytest_addoption(parser: pytest.Parser) -> None: parser.addoption( @@ -51,11 +52,21 @@ def emulator() -> Generator[DeviceEmulator]: device_emulator.stop() -def _endpoint_path(endpoint: str) -> Path | None: - """Filesystem path an ipc:// endpoint binds to, or None for other transports.""" - if not endpoint.startswith("ipc://"): - return None - return Path(endpoint.removeprefix("ipc://")) +@pytest.fixture(autouse=True) +def reset_peripheral_hooks(request: pytest.FixtureRequest) -> Generator[None]: + """Uninstall any on_request/on_response hooks a test left behind. + + The emulator (and the app) are module-scoped for speed, so state crosses + tests. Buffers are cleared explicitly by the tests that care -- some state + is deliberately shared (the UART greeting arrives once, at app start) -- + but a leftover hook firing during an unrelated test is never intentional. + """ + yield + if "emulator" in request.fixturenames: + emulator: DeviceEmulator = request.getfixturevalue("emulator") + for peripheral in emulator.all_peripherals(): + peripheral.on_request = None + peripheral.on_response = None def _wait_for_process_ready( @@ -91,8 +102,8 @@ def _wait_for_process_ready( raise RuntimeError(f"Process did not bind {ready_path} within {timeout}s") -def _application_fixture_factory(option_name: str, display_name: str) -> Any: - """Factory function to create application fixtures with common lifecycle management. +def _application_fixture_factory(option_name: str, display_name: str) -> AppFixture: + """Create an application fixture with common lifecycle management. Args: option_name: CLI option name (e.g., "--blinky") @@ -113,13 +124,12 @@ def application_fixture( pytest.skip(f"{option_name} not provided") app_executable = Path(str(app_arg)).resolve() - assert app_executable.exists(), ( - f"{display_name} executable not found: {app_executable}" - ) + if not app_executable.exists(): + pytest.fail(f"{display_name} executable not found: {app_executable}") # Clear any leftover socket file first, so its later appearance is # evidence of *this* run binding rather than of a previous one. - ready_path = _endpoint_path(emulator.to_device_endpoint) + ready_path = endpoint_path(emulator.to_device_endpoint) if ready_path is not None: ready_path.unlink(missing_ok=True) diff --git a/py/host-emulator/tests/test_blinky.py b/py/host-emulator/tests/test_blinky.py index a612848..a862f96 100644 --- a/py/host-emulator/tests/test_blinky.py +++ b/py/host-emulator/tests/test_blinky.py @@ -1,39 +1,27 @@ """Integration tests for blinky application.""" -from __future__ import annotations - -from typing import TYPE_CHECKING +import pytest from host_emulator import DeviceEmulator, PinState -if TYPE_CHECKING: - import subprocess - -def test_blinky_start_stop( - emulator: DeviceEmulator, blinky: subprocess.Popen[bytes] -) -> None: +@pytest.mark.usefixtures("blinky") +def test_blinky_start_stop(emulator: DeviceEmulator) -> None: """Test that blinky starts and stops cleanly.""" - assert emulator is not None - assert blinky is not None assert emulator.running -def test_blinky_blink( - emulator: DeviceEmulator, blinky: subprocess.Popen[bytes] -) -> None: +@pytest.mark.usefixtures("blinky") +def test_blinky_blink(emulator: DeviceEmulator) -> None: """Test that blinky blinks LED1.""" - _ = blinky # Ensure blinky is running assert emulator.user_led1().wait_for_transitions(2, timeout=3.0), ( "LED1 didn't blink within timeout" ) -def test_blinky_button_press( - emulator: DeviceEmulator, blinky: subprocess.Popen[bytes] -) -> None: +@pytest.mark.usefixtures("blinky") +def test_blinky_button_press(emulator: DeviceEmulator) -> None: """Test that button press triggers LED2.""" - _ = blinky # Ensure blinky is running emulator.user_button1().set_state(PinState.Low) emulator.user_button1().set_state(PinState.High) diff --git a/py/host-emulator/tests/test_endpoint_ownership.py b/py/host-emulator/tests/test_endpoint_ownership.py index 7ed5887..4673f53 100644 --- a/py/host-emulator/tests/test_endpoint_ownership.py +++ b/py/host-emulator/tests/test_endpoint_ownership.py @@ -10,22 +10,17 @@ any of the --blinky/--uart-echo/--i2c-demo options. """ -from __future__ import annotations - import socket import subprocess import sys +from collections.abc import Generator from pathlib import Path -from typing import TYPE_CHECKING import pytest from host_emulator import DeviceEmulator from host_emulator.endpoint import EndpointLock, endpoint_path, has_live_owner -if TYPE_CHECKING: - from collections.abc import Generator - @pytest.fixture def endpoints(tmp_path: Path) -> Generator[tuple[str, str]]: diff --git a/py/host-emulator/tests/test_i2c_demo.py b/py/host-emulator/tests/test_i2c_demo.py index a6380cc..0e66a1d 100644 --- a/py/host-emulator/tests/test_i2c_demo.py +++ b/py/host-emulator/tests/test_i2c_demo.py @@ -1,30 +1,26 @@ """Integration tests for I2C test application.""" -from __future__ import annotations +import subprocess +from typing import Any -from typing import TYPE_CHECKING, Any +import pytest from host_emulator import DeviceEmulator, PinState -if TYPE_CHECKING: - import subprocess +# Mirror the address and pattern i2c_demo writes and verifies +# (src/apps/i2c_demo/i2c_demo.cpp is the source of truth). +DEVICE_ADDRESS = 0x50 +TEST_PATTERN = [0xDE, 0xAD, 0xBE, 0xEF] -def test_i2c_demo_starts( - emulator: DeviceEmulator, i2c_demo: subprocess.Popen[bytes] -) -> None: +def test_i2c_demo_starts(i2c_demo: subprocess.Popen[bytes]) -> None: """Test that i2c_demo starts successfully.""" - _ = emulator # Ensure emulator is running assert i2c_demo.poll() is None, "i2c_demo process terminated unexpectedly" -def test_i2c_demo_write_read_cycle( - emulator: DeviceEmulator, i2c_demo: subprocess.Popen[bytes] -) -> None: +@pytest.mark.usefixtures("i2c_demo") +def test_i2c_demo_write_read_cycle(emulator: DeviceEmulator) -> None: """Test that i2c_demo writes and reads from I2C device.""" - _ = i2c_demo # Ensure i2c_demo is running - device_address = 0x50 - test_pattern = [0xDE, 0xAD, 0xBE, 0xEF] write_count = 0 read_count = 0 @@ -35,20 +31,20 @@ def i2c_handler(message: dict[str, Any]) -> None: data = message.get("data", []) address = message.get("address", 0) - assert address == device_address, f"Wrong address: 0x{address:02X}" - assert data == test_pattern, f"Wrong data: {data}" + assert address == DEVICE_ADDRESS, f"Wrong address: 0x{address:02X}" + assert data == TEST_PATTERN, f"Wrong data: {data}" elif message.get("operation") == "Receive": read_count += 1 address = message.get("address", 0) - assert address == device_address, f"Wrong address: 0x{address:02X}" + assert address == DEVICE_ADDRESS, f"Wrong address: 0x{address:02X}" emulator.i2c1().set_on_request(i2c_handler) - emulator.i2c1().write_to_device(device_address, test_pattern) + emulator.i2c1().write_to_device(DEVICE_ADDRESS, TEST_PATTERN) assert emulator.i2c1().wait_for_transactions( - 2, address=device_address, timeout=3.0 + 2, address=DEVICE_ADDRESS, timeout=3.0 ), "No I2C transactions occurred within timeout" assert write_count > 0, "No I2C writes occurred" @@ -58,15 +54,10 @@ def i2c_handler(message: dict[str, Any]) -> None: ) -def test_i2c_demo_toggles_leds( - emulator: DeviceEmulator, i2c_demo: subprocess.Popen[bytes] -) -> None: +@pytest.mark.usefixtures("i2c_demo") +def test_i2c_demo_toggles_leds(emulator: DeviceEmulator) -> None: """Test that i2c_demo toggles LEDs based on I2C operations.""" - _ = i2c_demo # Ensure i2c_demo is running - device_address = 0x50 - test_pattern = [0xDE, 0xAD, 0xBE, 0xEF] - - emulator.i2c1().write_to_device(device_address, test_pattern) + emulator.i2c1().write_to_device(DEVICE_ADDRESS, TEST_PATTERN) assert emulator.user_led1().wait_for_operation("Set", timeout=2.0), ( "LED1 didn't change state" @@ -76,18 +67,15 @@ def test_i2c_demo_toggles_leds( ) -def test_i2c_demo_data_mismatch( - emulator: DeviceEmulator, i2c_demo: subprocess.Popen[bytes] -) -> None: +@pytest.mark.usefixtures("i2c_demo") +def test_i2c_demo_data_mismatch(emulator: DeviceEmulator) -> None: """Test that i2c_demo handles data mismatch correctly.""" - _ = i2c_demo # Ensure i2c_demo is running - device_address = 0x50 wrong_pattern = [0x00, 0x11, 0x22, 0x33] - emulator.i2c1().write_to_device(device_address, wrong_pattern) + emulator.i2c1().write_to_device(DEVICE_ADDRESS, wrong_pattern) assert emulator.i2c1().wait_for_operation( - "Receive", address=device_address, timeout=2.0 + "Receive", address=DEVICE_ADDRESS, timeout=2.0 ), "No I2C read occurred" assert emulator.user_led1().wait_for_state(PinState.Low, timeout=2.0), ( diff --git a/py/host-emulator/tests/test_uart_echo.py b/py/host-emulator/tests/test_uart_echo.py index 2f4da5f..cd45fd5 100644 --- a/py/host-emulator/tests/test_uart_echo.py +++ b/py/host-emulator/tests/test_uart_echo.py @@ -1,41 +1,36 @@ """Integration tests for UART echo application with RxHandler.""" -from __future__ import annotations +import subprocess +from typing import Any -from typing import TYPE_CHECKING, Any +import pytest -if TYPE_CHECKING: - import subprocess +from host_emulator import DeviceEmulator - from host_emulator import DeviceEmulator +# Mirrors the greeting uart_echo prints on startup +# (src/apps/uart_echo/uart_echo.cpp is the source of truth). +GREETING = "UART Echo ready" -def test_uart_echo_starts( - emulator: DeviceEmulator, uart_echo: subprocess.Popen[bytes] -) -> None: +def test_uart_echo_starts(uart_echo: subprocess.Popen[bytes]) -> None: """Test that uart_echo starts successfully.""" - _ = emulator # Ensure emulator is running assert uart_echo.poll() is None, "uart_echo process terminated unexpectedly" -def test_uart_echo_sends_greeting( - emulator: DeviceEmulator, uart_echo: subprocess.Popen[bytes] -) -> None: +@pytest.mark.usefixtures("uart_echo") +def test_uart_echo_sends_greeting(emulator: DeviceEmulator) -> None: """Test that uart_echo sends a greeting message on startup.""" - _ = uart_echo # Ensure uart_echo is running assert emulator.uart1().wait_for_data(min_bytes=1, timeout=2.0), ( "No greeting received from uart_echo" ) greeting = bytes(emulator.uart1().rx_buffer).decode("utf-8", errors="ignore") - assert "UART Echo ready" in greeting, f"Unexpected greeting: {greeting}" + assert GREETING in greeting, f"Unexpected greeting: {greeting}" -def test_uart_echo_echoes_data( - emulator: DeviceEmulator, uart_echo: subprocess.Popen[bytes] -) -> None: +@pytest.mark.usefixtures("uart_echo") +def test_uart_echo_echoes_data(emulator: DeviceEmulator) -> None: """Test that uart_echo echoes received data back.""" - _ = uart_echo # Ensure uart_echo is running emulator.uart1().rx_buffer.clear() test_data = [0x48, 0x65, 0x6C, 0x6C, 0x6F] # "Hello" @@ -51,11 +46,9 @@ def test_uart_echo_echoes_data( assert list(emulator.uart1().rx_buffer) == test_data -def test_uart_echo_handler_receives_echoed_data( - emulator: DeviceEmulator, uart_echo: subprocess.Popen[bytes] -) -> None: +@pytest.mark.usefixtures("uart_echo") +def test_uart_echo_handler_receives_echoed_data(emulator: DeviceEmulator) -> None: """Test that UART handler callback is invoked when device sends data.""" - _ = uart_echo # Ensure uart_echo is running emulator.uart1().rx_buffer.clear() received_via_handler: list[int] = [] diff --git a/py/host-emulator/uv.lock b/py/host-emulator/uv.lock index 3d77b40..efb7a08 100644 --- a/py/host-emulator/uv.lock +++ b/py/host-emulator/uv.lock @@ -44,41 +44,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] -[[package]] -name = "coverage" -version = "7.13.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/23/f9/e92df5e07f3fc8d4c7f9a0f146ef75446bf870351cd37b788cf5897f8079/coverage-7.13.1.tar.gz", hash = "sha256:b7593fe7eb5feaa3fbb461ac79aac9f9fc0387a5ca8080b0c6fe2ca27b091afd", size = 825862, upload-time = "2025-12-28T15:42:56.969Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/aa/8e/ba0e597560c6563fc0adb902fda6526df5d4aa73bb10adf0574d03bd2206/coverage-7.13.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:97ab3647280d458a1f9adb85244e81587505a43c0c7cff851f5116cd2814b894", size = 218996, upload-time = "2025-12-28T15:42:04.978Z" }, - { url = "https://files.pythonhosted.org/packages/6b/8e/764c6e116f4221dc7aa26c4061181ff92edb9c799adae6433d18eeba7a14/coverage-7.13.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8f572d989142e0908e6acf57ad1b9b86989ff057c006d13b76c146ec6a20216a", size = 219326, upload-time = "2025-12-28T15:42:06.691Z" }, - { url = "https://files.pythonhosted.org/packages/4f/a6/6130dc6d8da28cdcbb0f2bf8865aeca9b157622f7c0031e48c6cf9a0e591/coverage-7.13.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d72140ccf8a147e94274024ff6fd8fb7811354cf7ef88b1f0a988ebaa5bc774f", size = 250374, upload-time = "2025-12-28T15:42:08.786Z" }, - { url = "https://files.pythonhosted.org/packages/82/2b/783ded568f7cd6b677762f780ad338bf4b4750205860c17c25f7c708995e/coverage-7.13.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d3c9f051b028810f5a87c88e5d6e9af3c0ff32ef62763bf15d29f740453ca909", size = 252882, upload-time = "2025-12-28T15:42:10.515Z" }, - { url = "https://files.pythonhosted.org/packages/cd/b2/9808766d082e6a4d59eb0cc881a57fc1600eb2c5882813eefff8254f71b5/coverage-7.13.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f398ba4df52d30b1763f62eed9de5620dcde96e6f491f4c62686736b155aa6e4", size = 254218, upload-time = "2025-12-28T15:42:12.208Z" }, - { url = "https://files.pythonhosted.org/packages/44/ea/52a985bb447c871cb4d2e376e401116520991b597c85afdde1ea9ef54f2c/coverage-7.13.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:132718176cc723026d201e347f800cd1a9e4b62ccd3f82476950834dad501c75", size = 250391, upload-time = "2025-12-28T15:42:14.21Z" }, - { url = "https://files.pythonhosted.org/packages/7f/1d/125b36cc12310718873cfc8209ecfbc1008f14f4f5fa0662aa608e579353/coverage-7.13.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9e549d642426e3579b3f4b92d0431543b012dcb6e825c91619d4e93b7363c3f9", size = 252239, upload-time = "2025-12-28T15:42:16.292Z" }, - { url = "https://files.pythonhosted.org/packages/6a/16/10c1c164950cade470107f9f14bbac8485f8fb8515f515fca53d337e4a7f/coverage-7.13.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:90480b2134999301eea795b3a9dbf606c6fbab1b489150c501da84a959442465", size = 250196, upload-time = "2025-12-28T15:42:18.54Z" }, - { url = "https://files.pythonhosted.org/packages/2a/c6/cd860fac08780c6fd659732f6ced1b40b79c35977c1356344e44d72ba6c4/coverage-7.13.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e825dbb7f84dfa24663dd75835e7257f8882629fc11f03ecf77d84a75134b864", size = 250008, upload-time = "2025-12-28T15:42:20.365Z" }, - { url = "https://files.pythonhosted.org/packages/f0/3a/a8c58d3d38f82a5711e1e0a67268362af48e1a03df27c03072ac30feefcf/coverage-7.13.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:623dcc6d7a7ba450bbdbeedbaa0c42b329bdae16491af2282f12a7e809be7eb9", size = 251671, upload-time = "2025-12-28T15:42:22.114Z" }, - { url = "https://files.pythonhosted.org/packages/f0/bc/fd4c1da651d037a1e3d53e8cb3f8182f4b53271ffa9a95a2e211bacc0349/coverage-7.13.1-cp314-cp314-win32.whl", hash = "sha256:6e73ebb44dca5f708dc871fe0b90cf4cff1a13f9956f747cc87b535a840386f5", size = 221777, upload-time = "2025-12-28T15:42:23.919Z" }, - { url = "https://files.pythonhosted.org/packages/4b/50/71acabdc8948464c17e90b5ffd92358579bd0910732c2a1c9537d7536aa6/coverage-7.13.1-cp314-cp314-win_amd64.whl", hash = "sha256:be753b225d159feb397bd0bf91ae86f689bad0da09d3b301478cd39b878ab31a", size = 222592, upload-time = "2025-12-28T15:42:25.619Z" }, - { url = "https://files.pythonhosted.org/packages/f7/c8/a6fb943081bb0cc926499c7907731a6dc9efc2cbdc76d738c0ab752f1a32/coverage-7.13.1-cp314-cp314-win_arm64.whl", hash = "sha256:228b90f613b25ba0019361e4ab81520b343b622fc657daf7e501c4ed6a2366c0", size = 221169, upload-time = "2025-12-28T15:42:27.629Z" }, - { url = "https://files.pythonhosted.org/packages/16/61/d5b7a0a0e0e40d62e59bc8c7aa1afbd86280d82728ba97f0673b746b78e2/coverage-7.13.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:60cfb538fe9ef86e5b2ab0ca8fc8d62524777f6c611dcaf76dc16fbe9b8e698a", size = 219730, upload-time = "2025-12-28T15:42:29.306Z" }, - { url = "https://files.pythonhosted.org/packages/a3/2c/8881326445fd071bb49514d1ce97d18a46a980712b51fee84f9ab42845b4/coverage-7.13.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:57dfc8048c72ba48a8c45e188d811e5efd7e49b387effc8fb17e97936dde5bf6", size = 220001, upload-time = "2025-12-28T15:42:31.319Z" }, - { url = "https://files.pythonhosted.org/packages/b5/d7/50de63af51dfa3a7f91cc37ad8fcc1e244b734232fbc8b9ab0f3c834a5cd/coverage-7.13.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3f2f725aa3e909b3c5fdb8192490bdd8e1495e85906af74fe6e34a2a77ba0673", size = 261370, upload-time = "2025-12-28T15:42:32.992Z" }, - { url = "https://files.pythonhosted.org/packages/e1/2c/d31722f0ec918fd7453b2758312729f645978d212b410cd0f7c2aed88a94/coverage-7.13.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ee68b21909686eeb21dfcba2c3b81fee70dcf38b140dcd5aa70680995fa3aa5", size = 263485, upload-time = "2025-12-28T15:42:34.759Z" }, - { url = "https://files.pythonhosted.org/packages/fa/7a/2c114fa5c5fc08ba0777e4aec4c97e0b4a1afcb69c75f1f54cff78b073ab/coverage-7.13.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:724b1b270cb13ea2e6503476e34541a0b1f62280bc997eab443f87790202033d", size = 265890, upload-time = "2025-12-28T15:42:36.517Z" }, - { url = "https://files.pythonhosted.org/packages/65/d9/f0794aa1c74ceabc780fe17f6c338456bbc4e96bd950f2e969f48ac6fb20/coverage-7.13.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:916abf1ac5cf7eb16bc540a5bf75c71c43a676f5c52fcb9fe75a2bd75fb944e8", size = 260445, upload-time = "2025-12-28T15:42:38.646Z" }, - { url = "https://files.pythonhosted.org/packages/49/23/184b22a00d9bb97488863ced9454068c79e413cb23f472da6cbddc6cfc52/coverage-7.13.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:776483fd35b58d8afe3acbd9988d5de592ab6da2d2a865edfdbc9fdb43e7c486", size = 263357, upload-time = "2025-12-28T15:42:40.788Z" }, - { url = "https://files.pythonhosted.org/packages/7d/bd/58af54c0c9199ea4190284f389005779d7daf7bf3ce40dcd2d2b2f96da69/coverage-7.13.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b6f3b96617e9852703f5b633ea01315ca45c77e879584f283c44127f0f1ec564", size = 260959, upload-time = "2025-12-28T15:42:42.808Z" }, - { url = "https://files.pythonhosted.org/packages/4b/2a/6839294e8f78a4891bf1df79d69c536880ba2f970d0ff09e7513d6e352e9/coverage-7.13.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:bd63e7b74661fed317212fab774e2a648bc4bb09b35f25474f8e3325d2945cd7", size = 259792, upload-time = "2025-12-28T15:42:44.818Z" }, - { url = "https://files.pythonhosted.org/packages/ba/c3/528674d4623283310ad676c5af7414b9850ab6d55c2300e8aa4b945ec554/coverage-7.13.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:933082f161bbb3e9f90d00990dc956120f608cdbcaeea15c4d897f56ef4fe416", size = 262123, upload-time = "2025-12-28T15:42:47.108Z" }, - { url = "https://files.pythonhosted.org/packages/06/c5/8c0515692fb4c73ac379d8dc09b18eaf0214ecb76ea6e62467ba7a1556ff/coverage-7.13.1-cp314-cp314t-win32.whl", hash = "sha256:18be793c4c87de2965e1c0f060f03d9e5aff66cfeae8e1dbe6e5b88056ec153f", size = 222562, upload-time = "2025-12-28T15:42:49.144Z" }, - { url = "https://files.pythonhosted.org/packages/05/0e/c0a0c4678cb30dac735811db529b321d7e1c9120b79bd728d4f4d6b010e9/coverage-7.13.1-cp314-cp314t-win_amd64.whl", hash = "sha256:0e42e0ec0cd3e0d851cb3c91f770c9301f48647cb2877cb78f74bdaa07639a79", size = 223670, upload-time = "2025-12-28T15:42:51.218Z" }, - { url = "https://files.pythonhosted.org/packages/f5/5f/b177aa0011f354abf03a8f30a85032686d290fdeed4222b27d36b4372a50/coverage-7.13.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eaecf47ef10c72ece9a2a92118257da87e460e113b83cc0d2905cbbe931792b4", size = 221707, upload-time = "2025-12-28T15:42:53.034Z" }, - { url = "https://files.pythonhosted.org/packages/cc/48/d9f421cb8da5afaa1a64570d9989e00fb7955e6acddc5a12979f7666ef60/coverage-7.13.1-py3-none-any.whl", hash = "sha256:2016745cb3ba554469d02819d78958b571792bb68e31302610e898f80dd3a573", size = 210722, upload-time = "2025-12-28T15:42:54.901Z" }, -] - [[package]] name = "host-emulator" version = "0.1.0" @@ -91,7 +56,6 @@ dependencies = [ dev = [ { name = "mypy" }, { name = "pytest" }, - { name = "pytest-cov" }, { name = "ruff" }, ] @@ -102,7 +66,6 @@ requires-dist = [{ name = "pyzmq", specifier = ">=27" }] dev = [ { name = "mypy", specifier = ">=1.19" }, { name = "pytest", specifier = ">=9" }, - { name = "pytest-cov", specifier = ">=7" }, { name = "ruff", specifier = ">=0.14" }, ] @@ -236,20 +199,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, ] -[[package]] -name = "pytest-cov" -version = "7.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "coverage" }, - { name = "pluggy" }, - { name = "pytest" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, -] - [[package]] name = "pyzmq" version = "27.1.0" From b08242fe55876824ab29376dcabd85216cd61bc5 Mon Sep 17 00:00:00 2001 From: Nehal Patel Date: Sat, 29 Aug 2026 16:15:53 +0000 Subject: [PATCH 16/17] build(ci): one compose service, one LLVM pin, no root-then-chown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The host-debug and host-release compose services were a third naming layer over workflow presets that already exist and needed extending by hand for each new workflow; the one embedded-cpp-dev service now takes the workflow command at run time, which is also how CI invokes it. CI ran the container as root and then chown'd the coverage output — repairing the ownership running as root had just broken. It now runs as the runner's own uid/gid with a writable HOME for uv's cache, so nothing needs repairing. The compose file documents that UID/GID must be exported explicitly (bash does not export its UID shell variable, so the ${UID:-1000} fallback was silently taken everywhere). The devcontainer no longer retags the image: one tag from one Dockerfile, so a devcontainer build serves docker compose run too instead of the two shadowing and rebuilding over each other. The LLVM major version is declared once as a Dockerfile ARG; format.sh reads CLANG_FORMAT_MAJOR with the same default, and the remaining references cross-reference the pin instead of each hardcoding it with its own reminder comment. Running CI as a non-1000 uid exposed the seam between the image's two uses: the devcontainer base image creates /home/vscode with mode 750, so any uid but vscode's cannot traverse into /home/vscode/workspace at all (CMake reported the presets file as "not found"; root had been masking it). Rather than loosen the devcontainer's home directory, CI gets its own compose overlay -- the batch counterpart of .devcontainer's -- that mounts the checkout at /workspace and sets a writable HOME. One image, two uses, each with its own overlay. Co-Authored-By: Claude Fable 5 --- .devcontainer/docker-compose.devcontainer.yml | 5 +++-- .github/workflows/ci.yml | 17 ++++++++++----- CLAUDE.md | 2 +- Dockerfile | 16 +++++++++----- README.md | 2 +- docker-compose.ci.yml | 19 +++++++++++++++++ docker-compose.yml | 21 ++++++++++--------- tools/format.sh | 11 +++++----- 8 files changed, 64 insertions(+), 29 deletions(-) create mode 100644 docker-compose.ci.yml diff --git a/.devcontainer/docker-compose.devcontainer.yml b/.devcontainer/docker-compose.devcontainer.yml index b22a715..8412c89 100644 --- a/.devcontainer/docker-compose.devcontainer.yml +++ b/.devcontainer/docker-compose.devcontainer.yml @@ -1,8 +1,9 @@ +# Same image tag as docker-compose.yml on purpose: the devcontainer build (with +# dev tools) then serves `docker compose run` too, instead of two tags built +# from one Dockerfile that shadow and rebuild over each other. services: embedded-cpp-dev: - image: embedded-cpp-docker:devcontainer user: "1000:1000" build: args: INSTALL_DEV_TOOLS: "true" - diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 67ba43a..5403da9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -79,14 +79,21 @@ jobs: cache-from: type=gha cache-to: type=gha,mode=max + # Run as the runner's own uid/gid so everything written into the mounted + # checkout is owned by the runner: no root-then-chown repair needed. The + # CI compose overlay mounts the checkout where a non-1000 uid can reach + # it and gives uv a writable HOME (see docker-compose.ci.yml). - name: Run host-debug workflow - run: docker compose run --rm --user root host-debug + run: > + docker compose -f docker-compose.yml -f docker-compose.ci.yml run --rm + --user "$(id -u):$(id -g)" embedded-cpp-dev + cmake --workflow --preset host-debug - name: Generate coverage reports - run: | - docker compose run --rm --user root embedded-cpp-dev \ - cmake --build build/host --config Debug --target ccov-all - sudo chown -R runner:docker build/host/ccov + run: > + docker compose -f docker-compose.yml -f docker-compose.ci.yml run --rm + --user "$(id -u):$(id -g)" embedded-cpp-dev + cmake --build build/host --config Debug --target ccov-all - name: Upload test results if: always() diff --git a/CLAUDE.md b/CLAUDE.md index 267342f..bea7543 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -46,7 +46,7 @@ cd py/host-emulator && uv run mypy cmake --preset=stm32f3_discovery # Docker alternative -docker compose run --rm host-debug +docker compose run --rm embedded-cpp-dev cmake --workflow --preset host-debug ``` ## Architecture diff --git a/Dockerfile b/Dockerfile index bce1892..601a87c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -36,11 +36,17 @@ RUN apt-get update && apt-get --no-install-recommends -y full-upgrade && apt-get less \ && rm -rf /var/lib/apt/lists/* -# Set up clang alternatives to use clang-18 as default -RUN update-alternatives --install /usr/bin/clang clang /usr/bin/clang-18 100 && \ - update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-18 100 && \ - update-alternatives --install /usr/bin/clang-format clang-format /usr/bin/clang-format-18 100 && \ - update-alternatives --install /usr/bin/clang-tidy clang-tidy /usr/bin/clang-tidy-18 100 +# The LLVM major version this project builds and formats with. The other +# places that must agree read it from here in spirit: tools/format.sh defaults +# to the same value (override with CLANG_FORMAT_MAJOR), and ci.yml installs +# clang-format- on the runner for the fast-fail format check. +ARG LLVM_VERSION=18 + +# Set up clang alternatives so the unversioned names resolve to LLVM_VERSION +RUN update-alternatives --install /usr/bin/clang clang /usr/bin/clang-${LLVM_VERSION} 100 && \ + update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-${LLVM_VERSION} 100 && \ + update-alternatives --install /usr/bin/clang-format clang-format /usr/bin/clang-format-${LLVM_VERSION} 100 && \ + update-alternatives --install /usr/bin/clang-tidy clang-tidy /usr/bin/clang-tidy-${LLVM_VERSION} 100 # Install uv for fast Python package management (to /usr/local/bin for all users) RUN curl -LsSf https://astral.sh/uv/install.sh | env UV_INSTALL_DIR=/usr/local/bin sh diff --git a/README.md b/README.md index 25eadda..ff129b3 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ This project explores: ### Docker Compose ```bash -docker compose run --rm host-debug +docker compose run --rm embedded-cpp-dev cmake --workflow --preset host-debug ``` ### Local Build diff --git a/docker-compose.ci.yml b/docker-compose.ci.yml new file mode 100644 index 0000000..a149950 --- /dev/null +++ b/docker-compose.ci.yml @@ -0,0 +1,19 @@ +# CI overlay over docker-compose.yml -- the batch counterpart of the +# devcontainer's overlay in .devcontainer/: one image, two uses. +# +# The devcontainer use runs as the image's vscode user (uid 1000) with the repo +# at /home/vscode/workspace. CI runs as the runner's own uid so that everything +# written into the checkout stays runner-owned -- and that uid cannot traverse +# /home/vscode, which the devcontainer base image creates with mode 750. So CI +# mounts the checkout at a neutral path instead, with a writable HOME for uv's +# cache. `!override` replaces the base mount rather than adding a second one. +# +# docker compose -f docker-compose.yml -f docker-compose.ci.yml run --rm \ +# --user "$(id -u):$(id -g)" embedded-cpp-dev cmake --workflow --preset host-debug +services: + embedded-cpp-dev: + volumes: !override + - .:/workspace + working_dir: /workspace + environment: + HOME: /tmp diff --git a/docker-compose.yml b/docker-compose.yml index cc34b5d..5392cac 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,3 +1,14 @@ +# One service; pick the command at run time, using the same workflow presets +# CI and local builds use, e.g.: +# +# docker compose run --rm embedded-cpp-dev cmake --workflow --preset host-debug +# +# UID/GID must be exported explicitly (bash does not export its UID variable): +# +# UID="$(id -u)" GID="$(id -g)" docker compose run --rm embedded-cpp-dev ... +# +# The 1000:1000 fallback matches the image's vscode user, which is also the +# common first-user id on Linux desktops. services: embedded-cpp-dev: build: @@ -8,13 +19,3 @@ services: volumes: - .:/home/vscode/workspace working_dir: /home/vscode/workspace - - host-debug: - extends: embedded-cpp-dev - command: /bin/bash -c "cmake --workflow --preset host-debug" - - host-release: - extends: embedded-cpp-dev - command: /bin/bash -c "cmake --workflow --preset host-release" - - diff --git a/tools/format.sh b/tools/format.sh index 14fe7ab..d0dcb8f 100755 --- a/tools/format.sh +++ b/tools/format.sh @@ -19,11 +19,12 @@ set -uo pipefail readonly REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" readonly PY_DIR="${REPO_ROOT}/py/host-emulator" -# Pinned to match .github/workflows/ci.yml and the dev container, which aliases -# clang-format -> clang-format-18. Versions disagree about formatting, so an -# unpinned binary produces exactly the local/CI split this script exists to -# prevent. -readonly REQUIRED_CLANG_FORMAT_MAJOR=18 +# Pinned to match the dev container's LLVM_VERSION (Dockerfile) and the runner +# install in .github/workflows/ci.yml. Versions disagree about formatting, so +# an unpinned binary produces exactly the local/CI split this script exists to +# prevent. Override with CLANG_FORMAT_MAJOR if your toolchain genuinely +# differs. +readonly REQUIRED_CLANG_FORMAT_MAJOR="${CLANG_FORMAT_MAJOR:-18}" red() { printf '\033[31m%s\033[0m\n' "$*"; } green() { printf '\033[32m%s\033[0m\n' "$*"; } From 0f1f962f2f1475cd9594f5f3268843d3427ebbf0 Mon Sep 17 00:00:00 2001 From: Nehal Patel Date: Sat, 29 Aug 2026 16:17:22 +0000 Subject: [PATCH 17/17] docs: canonical wire-protocol doc; forward-looking project plan MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit py/host-emulator/README.md was a 0-byte file — and the JSON protocol, which is the project's entire teaching point, was documented nowhere: it existed only as string literals split across the C++ encoder and three Python modules. The README is now the canonical protocol document (envelope, per-object operation matrix, field sets, status codes) and states explicitly that the C++ and Python vocabularies mirror each other. CLAUDE.md points at it instead of restating the contribution steps without the contract. PROJECT_PLAN.md described a slightly different project than the one in the repo: I2C was simultaneously '#1 priority, stub needs completion' and marked complete; i2c_demo was missing from the shipped-apps list; milestone 2 targeted the Nucleo but its success criterion named the Discovery; the test-count claim was arithmetically wrong on its face and stale besides. The plan is now forward-looking only — milestones, priorities, open debt, decision log — and defers current status to the README and 'ctest -N', which cannot go stale. The status that was tracked in five overlapping places now lives in one. Co-Authored-By: Claude Fable 5 --- CLAUDE.md | 7 +- docs/PROJECT_PLAN.md | 254 +++++++++++-------------------------- py/host-emulator/README.md | 99 +++++++++++++++ 3 files changed, 178 insertions(+), 182 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index bea7543..e644b93 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -93,15 +93,15 @@ class MyApp { 1. Define interface in `libs/mcu/*.hpp` (for peripherals) or `libs/board/board.hpp` 2. Implement host version in `libs/mcu/host/` with ZMQ messaging -3. Add message types to `host_emulator_messages.hpp` -4. Update Python emulator in `py/host-emulator/src/host_emulator/` +3. Add message types to `host_emulator_messages.hpp` and their JSON tables to `emulator_message_json_encoder.hpp` +4. Update the Python emulator in `py/host-emulator/src/host_emulator/`. The wire protocol is documented in `py/host-emulator/README.md`; the C++ and Python vocabularies mirror each other and must change together 5. Write unit tests (C++) and integration tests (Python) 6. Implement hardware versions in board-specific directories ## Testing - **C++ unit tests**: Colocated with code (`src/libs/mcu/host/test_*.cpp`), use Google Test -- **Python integration tests**: `py/host-emulator/tests/`, use pytest with fixtures that manage emulator/app lifecycle. CTest builds a uv venv under `build/host/host_emulator_venv` and runs them as the `host_emulator_test` target +- **Python integration tests**: `py/host-emulator/tests/`, use pytest with fixtures that manage emulator/app lifecycle. They run as the `host_emulator_test` CTest target; a CTest setup fixture syncs a uv venv under `build/host/host_emulator_venv` first (a no-op once synced) - **System tests**: none yet — end-to-end coverage lives in the Python integration tests. Add a dedicated harness only when a test doesn't fit the emulator harness - **clang-tidy**: Runs automatically during build, no separate step needed - **Python tooling**: uv + ruff + strict mypy, all configured in `py/host-emulator/pyproject.toml` @@ -112,4 +112,5 @@ class MyApp { - `src/libs/mcu/pin.hpp` - Pin abstraction (InputPin, OutputPin, BidirectionalPin) - `src/libs/mcu/uart.hpp` - UART with RxHandler callback pattern - `src/libs/board/board.hpp` - Board interface aggregating all peripherals +- `py/host-emulator/README.md` - The ZeroMQ/JSON wire protocol (canonical doc) - `CMakePresets.json` - Build configurations for host and ARM targets diff --git a/docs/PROJECT_PLAN.md b/docs/PROJECT_PLAN.md index d6df5f3..68ae67e 100644 --- a/docs/PROJECT_PLAN.md +++ b/docs/PROJECT_PLAN.md @@ -1,71 +1,41 @@ # Embedded C++ BSP - Project Plan -**Last Updated**: 2025-11-22 +**Last Updated**: 2026-08-29 **Project Status**: Educational/Demonstrative (Active Development) +This document is forward-looking: milestones, priorities, and open work. +Current implementation status lives in the README's Implementation Status +table; what exists is best read from the code and `ctest -N`. + ## Project Vision -Explore modern C++ (C++23) and software engineering practices in embedded systems through: +Explore modern C++ (C++23) and software engineering practices in embedded +systems through: - Type-safe hardware abstraction layers - Host-based development and testing - Correct-by-construction design patterns - Comprehensive testing infrastructure -## Current Status - -### ✅ Completed (Production Quality) - -| Component | Description | Status | -|-----------|-------------|--------| -| **Host Emulation Platform** | ZeroMQ-based IPC with Python hardware simulator | ✅ Complete | -| **Blinky Example App** | LED blink + button interrupt demo | ✅ Complete | -| **UART Echo Example App** | UART RxHandler demo with async reception | ✅ Complete | -| **MCU Abstraction Layer** | Pin, UART, I2C, Delay interfaces | ✅ Complete | -| **Board Abstraction Layer** | Board interface with host implementation | ✅ Complete | -| **Error Handling** | `std::expected` pattern | ✅ Complete | -| **C++ Unit Tests** | Google Test for transport, messages, dispatcher | ✅ Complete | -| **Python Integration Tests** | pytest for end-to-end behavior | ✅ Complete | -| **Build System** | CMake with presets, multi-config Ninja | ✅ Complete | -| **Docker Environment** | Complete development environment | ✅ Complete | -| **DevContainer** | VS Code integration with extensions | ✅ Complete | -| **CI/CD Pipeline** | GitHub Actions for automated builds/tests | ✅ Complete | -| **Documentation** | CLAUDE.md, README.md comprehensive docs | ✅ Complete | - -### 🚧 In Progress (Partial Implementation) - -| Component | Status | What's Missing | -|-----------|--------|----------------| -| **STM32F3 Discovery Board** | 🚧 Partial | C++ board implementation, pin mappings | -| **STM32F7 Nucleo Board** | 🚧 Partial | C++ board implementation, pin mappings | - -### ⚠️ Placeholder (Not Started) - -| Component | Status | Description | -|-----------|--------|-------------| -| **nRF52832 DK Board** | ⚠️ Placeholder | Minimal CMake setup only | -| **SPI Peripheral** | ⚠️ Not Started | SPI controller interface | -| **ADC Peripheral** | ⚠️ Not Started | ADC interface | -| **PWM Peripheral** | ⚠️ Not Started | PWM interface | - ## Milestones ### Milestone 1: Foundation 🚧 IN PROGRESS + **Goal**: Establish development infrastructure and prove the concept - [x] Host emulation platform with ZeroMQ - [x] Basic pin abstraction (Input, Output, Bidirectional) -- [x] Example application (blinky) +- [x] Example applications (blinky, uart_echo, i2c_demo) - [x] Unit testing framework - [x] Integration testing with Python emulator - [x] CMake build system with presets - [x] Docker development environment - [x] DevContainer for VS Code - [x] CI/CD pipeline -- [x] Comprehensive documentation - [x] Code coverage reporting -- [x] Add static analysis through clang-tidy by default -- [x] Add UART abstraction with RxHandler -- [x] Add I2C abstraction +- [x] Static analysis through clang-tidy by default +- [x] UART abstraction with RxHandler +- [x] I2C abstraction +- [x] Wire-protocol documentation (py/host-emulator/README.md) - [ ] Add SPI abstraction - [ ] Add PWM abstraction - [ ] Add ADC abstraction @@ -75,163 +45,90 @@ Explore modern C++ (C++23) and software engineering practices in embedded system - [ ] Upload code coverage reports to GitHub pages - [ ] Increase test coverage for error paths -**Status**: 🚧 IN PROGRESS (2025-11-22) +### Milestone 2: Hardware Board Support 📋 PLANNED + +**Goal**: First physical board (STM32F7 Nucleo) -### Milestone 2: Hardware Board Support 🚧 IN PROGRESS -**Goal**: Complete STM32F7 Nucleo board implementation +The repository currently carries only the ARM toolchain files and configure +presets; the vendor HAL trees that briefly lived in-tree were removed while +unreachable (git history preserves them, and CubeMX regenerates them fresher). **Tasks**: -- [ ] Implement STM32F7 Nucleo C++ board class - - [ ] Map LED pins to STM32F7 hardware - - [ ] Map button pins to STM32F7 hardware - - [ ] Implement GPIO initialization - - [ ] Add interrupt handler setup -- [ ] Create STM32F7-specific pin implementations - - [ ] Extend base pin interface for STM32 HAL - - [ ] Handle GPIO port/pin mapping -- [ ] Test on actual hardware - - [ ] Verify blinky builds for STM32F7 - - [ ] Flash and test LED behavior - - [ ] Test button interrupts -- [ ] Document hardware-specific setup - - [ ] Pin mapping tables - - [ ] Flashing instructions - - [ ] Debugging setup +- [ ] Implement the `arm_cm7` MCU backend (`src/libs/mcu/arm_cm7/`) +- [ ] Implement the STM32F7 Nucleo board directory (pin maps, GPIO init, + interrupt wiring) against the vendor HAL +- [ ] Verify blinky builds, flashes, and runs on the physical board +- [ ] Document hardware setup: pin mapping tables, flashing, debugging **Success Criteria**: -- Blinky app runs on physical STM32F7 Discovery board -- All features from host emulator work on hardware +- Blinky runs on a physical STM32F7 Nucleo board +- All features exercised by the host emulator work on hardware - Documentation enables others to replicate ### Milestone 3: Multi-Board Support 📋 PLANNED + **Goal**: Demonstrate portability across different MCUs **Tasks**: -- [ ] Complete STM32F3 Discovery implementation - - [ ] Implement board class for STM32F3 - - [ ] Map pins to Nucleo hardware - - [ ] Test on physical hardware -- [ ] Add additional example application - - [ ] Multi-LED pattern app - - [ ] Demonstrates more complex behavior -- [ ] Cross-board compatibility validation - - [ ] Ensure blinky works on both STM32F3 and STM32F7 - - [ ] Verify abstraction portability - -**Success Criteria**: -- Blinky runs on both STM32F3 and STM32F7 without modification -- Additional example app demonstrates abstraction benefits +- [ ] STM32F3 Discovery support (`arm_cm4` backend + board directory) +- [ ] Additional example application exercising more complex behavior +- [ ] Cross-board validation: blinky runs on both boards unmodified ### Milestone 4: Advanced Features 🔮 FUTURE -**Goal**: Demonstrate advanced embedded patterns - -**Potential Features**: -- [ ] RTOS integration (FreeRTOS) - - [ ] Task abstraction - - [ ] Queue/mutex abstractions -- [ ] Power management - - [ ] Sleep modes - - [ ] Wake-up sources -- [ ] DMA abstractions - - [ ] Memory-to-peripheral transfers - - [ ] Circular buffers -- [ ] Flash memory abstraction - - [ ] Non-volatile storage - - [ ] Configuration persistence - -**Status**: Exploratory - not committed - -## Current Priorities - -### High Priority -1. **I2C Implementation** (Milestone 1, Priority 1) - - Current stub needs completion - - Demonstrates peripheral abstraction beyond GPIO -2. **Complete STM32F7 Nucleo Board** (Milestone 2) - - Most important for proving hardware portability - - Builds on completed foundation +**Potential Features** (exploratory, not committed): +- RTOS integration (FreeRTOS): task, queue, and mutex abstractions +- Power management: sleep modes, wake-up sources +- DMA abstractions: memory-to-peripheral transfers, circular buffers +- Flash memory abstraction: non-volatile storage, config persistence +- nRF52832 DK board (a second silicon vendor) -### Medium Priority -3. **STM32F3 Discovery Board** (Milestone 3) - - Proves multi-board portability - - Demonstrates Cortex-M4 support - -4. **Additional Example Applications** - - Shows real-world patterns - - More engaging demonstrations +## Current Priorities -### Low Priority -5. **nRF52832 DK Board** - - Different MCU vendor (Nordic vs STM) - - Would demonstrate even broader portability - - Currently just placeholder +1. **Complete STM32F7 Nucleo board** (Milestone 2) — proves hardware + portability, builds on the completed host foundation +2. **STM32F3 Discovery board** (Milestone 3) — proves multi-board portability +3. **Additional example applications** — more engaging demonstrations ## Technical Debt & Improvements -### Code Quality -- [ ] Add more C++ unit tests for board implementations - -### Documentation -- [x] ✅ Update CLAUDE.md with DevContainer setup -- [x] ✅ Refresh README.md -- [ ] Add hardware setup guides -- [ ] Add architecture diagrams - -### Build System -- [x] ✅ Fix Docker permission issues +- [ ] Add C++ unit tests for board implementations +- [ ] Add hardware setup guides and architecture diagrams - [ ] Optimize Docker layer caching - [ ] Add release builds to CI -- [ ] Cross-compilation verification in CI - -### Host Emulator -- [ ] Add GUI visualization (instead of just console logs) -- [ ] Support for more complex I2C devices in emulator -- [ ] Timing simulation (delays, interrupt timing) +- [ ] Cross-compilation verification in CI (once an ARM backend exists) +- [ ] Host emulator: GUI visualization, richer I2C device models, timing + simulation +- [ ] Wire up Python test coverage if it earns its keep (pytest-cov was + removed while unused) ## Decision Log -### 2025-11-23: UART RxHandler Implementation -- ✅ Added UART abstraction with event-driven RxHandler (similar to Pin interrupts) -- ✅ Implemented HostUart with ZMQ transport and message routing -- ✅ Created uart_echo example app demonstrating asynchronous reception -- ✅ Added C++ unit tests for RxHandler functionality -- ✅ Created Python integration tests for uart_echo app -- ✅ UART initialization is explicit (not in Board::Init()) to avoid unnecessary emulator connections -- ✅ Improved HostBoard::Init() error handling pattern with scoped blocks -- ✅ All 20 tests passing (19 C++ + 6 Python integration) - -### 2025-11-22: DevContainer & CI Integration -- ✅ Added VS Code DevContainer support -- ✅ Configured GitHub Actions CI/CD -- ✅ Resolved Docker permission issues with dynamic UID/GID -- ✅ Updated documentation (CLAUDE.md, README.md) - -### 2025-11-20: Foundation Complete -- ✅ Host emulation platform working end-to-end -- ✅ Blinky example app with tests -- ✅ CMake build system with presets -- ✅ Python integration testing framework - -## Success Metrics - -### Educational Value -- ✅ Demonstrates modern C++ features (C++23, std::expected) -- ✅ Shows correct-by-construction patterns -- ✅ Proves host-based development viability -- 🚧 Multiple hardware boards (1 of 3 complete) - -### Code Quality -- ✅ All warnings as errors -- ✅ clang-tidy enforcement -- ✅ Comprehensive testing (unit + integration) -- ✅ CI/CD automation - -### Developer Experience -- ✅ Easy setup (DevContainer) -- ✅ Fast iteration (host builds) -- ✅ Clear documentation -- 🚧 Hardware debugging setup (not yet documented) +### 2026-08-29: Simplification pass +- Codebase-wide review against the project's educational goals; the themes: + one canonical form per idea (a single Transact/Peripheral implementation + instead of three divergent copies), target-based CMake usage requirements, + and docs that match behavior +- Trimmed Uart/I2C to the surface the host honors; async/interrupt/DMA modes + recorded above as future work +- Parked the unreachable STM32 vendor trees and broken ARM workflow presets; + kept toolchains and configure presets behind a clear "not implemented" + configure error + +### 2025-11-23: UART RxHandler implementation +- UART abstraction with event-driven RxHandler (similar to pin interrupts), + HostUart with ZMQ transport and message routing, uart_echo example app, + C++ unit tests and Python integration tests +- UART initialization is explicit (not in Board::Init()) to avoid unnecessary + emulator connections + +### 2025-11-22: DevContainer & CI integration +- VS Code DevContainer support, GitHub Actions CI/CD, Docker permission + handling, documentation updates + +### 2025-11-20: Foundation complete +- Host emulation platform working end-to-end; blinky with tests; CMake preset + build system; Python integration testing framework ## Resources @@ -241,9 +138,8 @@ Explore modern C++ (C++23) and software engineering practices in embedded system ### Technologies - [CMake](https://cmake.org/) - Build system -- [Embedded Template Library](https://www.etlcpp.com/) - STL alternative for embedded - [ZeroMQ](https://zeromq.org/) - IPC transport - [Google Test](https://github.com/google/googletest) - C++ testing - [pytest](https://pytest.org/) - Python testing - ---- +- [Embedded Template Library](https://www.etlcpp.com/) - STL alternative to + consider when hardware targets arrive (not currently a dependency) diff --git a/py/host-emulator/README.md b/py/host-emulator/README.md index e69de29..cc9b112 100644 --- a/py/host-emulator/README.md +++ b/py/host-emulator/README.md @@ -0,0 +1,99 @@ +# host-emulator + +A Python hardware emulator for the host (software-in-the-loop) build. C++ +applications talk to it over two ZeroMQ PAIR sockets carrying JSON messages; +this package plays the "hardware" side: pins that can be read and driven, +a loopback UART, and an I2C bus with per-address device buffers. + +## Running + +```bash +# Terminal 1: the emulator (binds its endpoint, then serves until Ctrl-C) +uv run host-emulator + +# Terminal 2: any host-built app +../../build/host/bin/Debug/blinky +``` + +The integration tests under `tests/` manage both processes themselves; run +them via CTest (`ctest --preset=host-debug -R host_emulator_test`), which +supplies the app paths. + +## Transport + +Two ipc:// endpoints, one per direction, both ZMQ PAIR: + +| Endpoint (default) | Bound by | Carries | +| ---------------------------------- | -------- | -------------------------------- | +| `ipc:///tmp/device_emulator.ipc` | emulator | device → emulator requests | +| `ipc:///tmp/emulator_device.ipc` | device | emulator → device requests | + +Each side replies on the socket it received the request from, so every +exchange is a strict request/response pair. Endpoint ownership is guarded by +`endpoint.py` (an flock-based lock plus a connect() liveness probe), the +Python counterpart of the C++ `EndpointLock` — see that module's docstring. + +## Wire protocol + +Every message is one JSON object. The vocabulary is defined on the C++ side +in `src/libs/mcu/host/emulator_message_json_encoder.hpp` and mirrored here by +the `StrEnum`s in `common.py`; **the two must change together**. + +Envelope fields, present in every message: + +| Field | Values | Meaning | +| -------- | ----------------------------- | -------------------------------- | +| `type` | `Request`, `Response` | initiates vs. answers an exchange | +| `object` | `Pin`, `Uart`, `I2C` | which peripheral kind is addressed | +| `name` | e.g. `"LED 1"`, `"UART 1"` | which instance | + +Per-object operations and their extra fields: + +### Pin + +| Operation | Direction | Request fields | Response fields | +| --------- | ---------------- | -------------- | ------------------ | +| `Set` | either direction | `state` | `state`, `status` | +| `Get` | either direction | `state` (ignored) | `state`, `status` | + +`state` is `Low`, `High`, or `Hi_Z`. The device may only `Set` its output +pins; the emulator drives input pins (e.g. pressing `Button 1`), which is how +pin interrupts are exercised. + +### Uart + +| Operation | Direction | Request fields | Response fields | +| --------- | ----------------- | ----------------------------- | -------------------------------------- | +| `Send` | device → emulator | `data` | `bytes_transferred`, `status` | +| `Receive` | device → emulator | `size`, `timeout_ms` | `data`, `bytes_transferred`, `status` | +| `Receive` | emulator → device | `data`, `size` | `bytes_transferred`, `status` | + +`data` is an array of byte values. An emulator-initiated `Receive` pushes +unsolicited data at the device, which delivers it to the app's RxHandler and +acks. + +### I2C + +| Operation | Direction | Request fields | Response fields | +| --------- | ----------------- | ------------------------- | ------------------------------------------------ | +| `Send` | device → emulator | `address`, `data` | `address`, `bytes_transferred`, `status` | +| `Receive` | device → emulator | `address`, `size` | `address`, `data`, `bytes_transferred`, `status` | + +The emulator keeps one buffer per `address`; `write_to_device()` lets a test +pre-load one. + +`status` values mirror C++ `common::Error` (`Ok`, `InvalidArgument`, +`InvalidOperation`, `Timeout`, ...); see `common.py` for the full set. + +## Package layout + +- `emulator.py` — `DeviceEmulator`: sockets, bind/serve lifecycle, routing +- `peripheral.py` — shared `Peripheral` behavior (routing, hooks, `_wait_for`) +- `pin.py`, `uart.py`, `i2c.py` — the per-peripheral protocols +- `common.py` — the mirrored wire vocabulary +- `endpoint.py` — endpoint ownership guards + +## Tooling + +uv for environments, ruff for lint/format, strict mypy over `src` and +`tests`; all configured in `pyproject.toml`.