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 14695df..5403da9 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 @@ -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/.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/CLAUDE.md b/CLAUDE.md index a7d82c2..e644b93 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -37,16 +37,16 @@ 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. 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 +docker compose run --rm embedded-cpp-dev cmake --workflow --preset host-debug ``` ## Architecture @@ -93,15 +93,16 @@ 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` @@ -111,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/CMakeLists.txt b/CMakeLists.txt index 69fc388..34aafff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,90 +2,124 @@ 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) -# 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 +# 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. +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. +file(CREATE_LINK "${PROJECT_BINARY_DIR}/compile_commands.json" + "${PROJECT_SOURCE_DIR}/compile_commands.json" SYMBOLIC) + +# 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( - stm32cubef7 - GIT_REPOSITORY https://github.com/STMicroelectronics/STM32CubeF7 - GIT_TAG v1.17.1 + cppzmq + GIT_REPOSITORY https://github.com/zeromq/cppzmq.git + GIT_TAG c94c20743ed7d4aa37835a5c46567ab0790d4acc # v4.10.0 ) FetchContent_Declare( - CmakeScripts - GIT_REPOSITORY https://github.com/StableCoder/cmake-scripts.git - GIT_TAG 25.08 + json + GIT_REPOSITORY https://github.com/nlohmann/json + GIT_TAG 0ca0fe433eb70cea0d5761079c0c5b47b736565b # v3.11.2 ) -FetchContent_GetProperties(CmakeScripts) - -if(NOT cmakescripts_POPULATED) - FetchContent_MakeAvailable(CmakeScripts) - set(CMAKE_MODULE_PATH ${cmakescripts_SOURCE_DIR} ${CMAKE_MODULE_PATH}) -endif() +FetchContent_MakeAvailable(CmakeScripts) +list(PREPEND CMAKE_MODULE_PATH "${cmakescripts_SOURCE_DIR}") -# Assume libzmq is installed on the system +# 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) -# 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) - -# 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) - -FetchContent_MakeAvailable(etl) + 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() -add_subdirectory(external) +# --------------------------------------------------------------------------- +# Tooling: clang-tidy, coverage, formatting +# --------------------------------------------------------------------------- include(tools) include(code-coverage) @@ -94,34 +128,26 @@ 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() -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() - -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. +# configuration: formatting is not host-specific, and a cross-compiling +# developer should get the same guard rails. include("${PROJECT_SOURCE_DIR}/cmake/format.cmake") -clang_tidy("-header-filter=${CMAKE_CURRENT_SOURCE_DIR}/src/.*}") +# --------------------------------------------------------------------------- +# Targets +# --------------------------------------------------------------------------- + +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 + +# 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 df9ae8f..e05f63f 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -5,7 +5,6 @@ "minor": 27, "patch": 0 }, - "include": [], "configurePresets": [ { "name": "default", @@ -13,10 +12,6 @@ "description": "Default build using Ninja", "generator": "Ninja Multi-Config", "binaryDir": "${sourceDir}/build/${presetName}", - "cacheVariables": { - "CMAKE_PRESET": "${presetName}", - "CMAKE_RUNTIME_OUTPUT_DIRECTORY": "${sourceDir}/build/${presetName}/bin" - }, "hidden": true }, { @@ -33,24 +28,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 +44,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 +57,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 +101,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 +121,6 @@ { "name": "host", "configurePreset": "host", - "output": { - "outputOnFailure": true - }, - "execution": { - "noTestsAction": "error", - "stopOnFailure": true - }, "hidden": true, "inherits": "default" }, @@ -252,36 +203,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/CMakeUserPresets.json.example b/CMakeUserPresets.json.example index 22b5084..2812deb 100644 --- a/CMakeUserPresets.json.example +++ b/CMakeUserPresets.json.example @@ -12,10 +12,8 @@ "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++", - "CMAKE_LINKER": "/opt/homebrew/opt/llvm/bin/clang" + "CMAKE_CXX_COMPILER": "/opt/homebrew/opt/llvm/bin/clang++" } }, { @@ -24,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++" } @@ -35,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/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 94edd67..ff129b3 100644 --- a/README.md +++ b/README.md @@ -23,12 +23,12 @@ 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 -**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 @@ -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 @@ -79,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 @@ -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/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/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/cmake/toolchain/host-clang.cmake b/cmake/toolchain/host-clang.cmake index 4bc447a..641edce 100644 --- a/cmake/toolchain/host-clang.cmake +++ b/cmake/toolchain/host-clang.cmake @@ -1,35 +1,47 @@ -# 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}) +# 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/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/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/docs/PROJECT_PLAN.md b/docs/PROJECT_PLAN.md index d001e6a..68ae67e 100644 --- a/docs/PROJECT_PLAN.md +++ b/docs/PROJECT_PLAN.md @@ -1,234 +1,134 @@ # 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 +- [ ] 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 -**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 @@ -238,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/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/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/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`. diff --git a/py/host-emulator/pyproject.toml b/py/host-emulator/pyproject.toml index 52b5478..e29b0f4 100644 --- a/py/host-emulator/pyproject.toml +++ b/py/host-emulator/pyproject.toml @@ -7,10 +7,13 @@ 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] -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"] @@ -35,19 +38,26 @@ 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"] [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. @@ -59,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/__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..88b02a4 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() @@ -104,18 +101,23 @@ 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. - 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 +153,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 +173,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 +212,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 +226,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 + ) 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" diff --git a/src/.DS_Store b/src/.DS_Store deleted file mode 100644 index c1591cb..0000000 Binary files a/src/.DS_Store and /dev/null differ 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/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/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/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/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/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/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/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/CMakeLists.txt b/src/libs/board/CMakeLists.txt index 1912753..7dceef9 100644 --- a/src/libs/board/CMakeLists.txt +++ b/src/libs/board/CMakeLists.txt @@ -1,6 +1,13 @@ -add_library(board INTERFACE board.hpp) # board.cpp) -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) -add_subdirectory(${EMBEDDED_CPP_BOARD}) \ No newline at end of file +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}) 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/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 53f2f85..628724f 100644 --- a/src/libs/board/host/host_board.hpp +++ b/src/libs/board/host/host_board.hpp @@ -41,23 +41,20 @@ class HostBoard : public Board { auto Uart1() -> mcu::Uart& override; private: - static constexpr auto IsJson(const std::string_view& message) -> bool { - return message.starts_with("{") && message.ends_with("}"); - } - // Endpoint configuration (declared first to be initialized first) 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_{}; - - // Receiver map and dispatcher (built in Init() after components exist) - mcu::ReceiverMap receiver_map_{}; - std::optional dispatcher_{}; - std::unique_ptr zmq_transport_{}; + 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_; + + // 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_; + mcu::Dispatcher dispatcher_{receiver_map_}; + std::unique_ptr zmq_transport_; }; } // namespace board 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; } 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/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/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/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/CMakeLists.txt b/src/libs/mcu/CMakeLists.txt index 67b2037..1bb65e9 100644 --- a/src/libs/mcu/CMakeLists.txt +++ b/src/libs/mcu/CMakeLists.txt @@ -1,5 +1,13 @@ -add_library(mcu INTERFACE pin.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}") + 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}) 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 87908e0..f112fc4 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}) +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 endpoint_lock.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) 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/dispatcher.hpp b/src/libs/mcu/host/dispatcher.hpp index 3bb7d18..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} {} @@ -25,14 +26,11 @@ class Dispatcher { auto operator=(const Dispatcher&) -> Dispatcher& = delete; auto operator=(Dispatcher&&) -> Dispatcher& = delete; - 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)) { - 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/emulator_message_json_encoder.hpp b/src/libs/mcu/host/emulator_message_json_encoder.hpp index 80bd1a6..99bd361 100644 --- a/src/libs/mcu/host/emulator_message_json_encoder.hpp +++ b/src/libs/mcu/host/emulator_message_json_encoder.hpp @@ -8,24 +8,39 @@ #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 +// 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 { +// 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"}, @@ -33,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 @@ -73,6 +96,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) @@ -92,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/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/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_i2c.cpp b/src/libs/mcu/host/host_i2c.cpp index a08f1d4..1fe4dc1 100644 --- a/src/libs/mcu/host/host_i2c.cpp +++ b/src/libs/mcu/host/host_i2c.cpp @@ -18,107 +18,35 @@ 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; -} - -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 {}; + 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::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..48a94d9 100644 --- a/src/libs/mcu/host/host_i2c.hpp +++ b/src/libs/mcu/host/host_i2c.hpp @@ -27,23 +27,7 @@ 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(const std::string_view& message) + auto Receive(std::string_view message) -> std::expected override; private: 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/host_pin.cpp b/src/libs/mcu/host/host_pin.cpp index dc6f51a..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,27 +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. - // const PinState prev_state{state_}; - state_ = resp->state; - // CheckAndInvokeHandler(prev_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 b28a916..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: @@ -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.cpp b/src/libs/mcu/host/host_uart.cpp index 5dbd31a..51c5b7d 100644 --- a/src/libs/mcu/host/host_uart.cpp +++ b/src/libs/mcu/host/host_uart.cpp @@ -33,33 +33,13 @@ auto HostUart::Send(std::span data) return std::unexpected(common::Error::kInvalidState); } - if (busy_) { - return std::unexpected(common::Error::kInvalidOperation); - } - 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) @@ -68,132 +48,22 @@ 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{ - .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; }); } -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{ - .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)); - 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{ - .type = MessageType::kRequest, - .object = ObjectType::kUart, - .name = name_, - .operation = OperationType::kReceive, - .data = {}, - .size = buffer.size(), - .timeout_ms = 0, - }; - - 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_) { @@ -204,96 +74,40 @@ auto HostUart::SetRxHandler(std::function return {}; } -auto HostUart::Receive(const std::string_view& message) +// 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{ - .type = MessageType::kResponse, - .object = ObjectType::kUart, - .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 2a849d3..b95423f 100644 --- a/src/libs/mcu/host/host_uart.hpp +++ b/src/libs/mcu/host/host_uart.hpp @@ -31,24 +31,11 @@ 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; - - auto IsBusy() const -> bool override; - auto Available() const -> size_t override; - auto Flush() -> std::expected override; - auto SetRxHandler(std::function handler) -> 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: @@ -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_{}; + std::function rx_handler_; }; } // namespace mcu 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..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(const 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(const std::string_view& message) -> bool { - static_cast(message); - return false; -} + std::string received_message; -constexpr auto IsHello(const std::string_view& message) -> bool { - return message == "Hello"; -} + private: + std::string name_; +}; -constexpr auto IsWorld(const 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(const 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 299d39c..3844120 100644 --- a/src/libs/mcu/host/test_host_i2c.cpp +++ b/src/libs/mcu/host/test_host_i2c.cpp @@ -1,187 +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: - static constexpr auto IsJson(const std::string_view& message) -> bool { - return message.starts_with("{") && message.ends_with("}"); + auto MakeReceiver(mcu::Transport& transport) -> mcu::Receiver& override { + i2c_ = std::make_unique("I2C 1", transport); + return *i2c_; } - // 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"; - } - - 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(IsJson, 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) { @@ -285,110 +166,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 4d06d86..f8f4e1c 100644 --- a/src/libs/mcu/host/test_host_uart.cpp +++ b/src/libs/mcu/host/test_host_uart.cpp @@ -1,189 +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: - static constexpr auto IsJson(const std::string_view& message) -> bool { - return message.starts_with("{") && message.ends_with("}"); + auto MakeReceiver(mcu::Transport& transport) -> mcu::Receiver& override { + uart_ = std::make_unique("UART 1", transport); + return *uart_; } - // 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"; - } - - 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(IsJson, 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) { @@ -236,40 +118,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{}; @@ -278,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}; @@ -300,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 f7c695b..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,26 +154,18 @@ 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, 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}; @@ -224,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 58159ce..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,38 +48,13 @@ 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. 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}; } @@ -94,15 +70,12 @@ 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 { - // 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 +176,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); @@ -236,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; } @@ -248,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); } } @@ -299,8 +265,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{ - {[](const 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_); 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_; 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/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..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; @@ -61,41 +64,9 @@ 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 - /// 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) 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 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' "$*"; }