diff --git a/.clang-tidy b/.clang-tidy index 2ac840f..8aabf96 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,4 +1,12 @@ -Checks: "-header-filter=.*,readability-identifier-naming,modernize-*,performance-*,bugprone-*,-modernize-use-trailing-return-type,-checks=-*" +Checks: > + -*, + readability-identifier-naming, + modernize-*, + performance-*, + bugprone-*, + -bugprone-exception-escape, + clang-diagnostic-dangling*, + -modernize-use-trailing-return-type WarningsAsErrors: "*" UseColor: true CheckOptions: diff --git a/llvm.sh b/llvm.sh deleted file mode 100755 index 4f9f0a0..0000000 --- a/llvm.sh +++ /dev/null @@ -1,232 +0,0 @@ -#!/bin/bash -################################################################################ -# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -# See https://llvm.org/LICENSE.txt for license information. -# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -################################################################################ -# -# This script will install the llvm toolchain on the different -# Debian and Ubuntu versions - -set -eux - -usage() { - set +x - echo "Usage: $0 [llvm_major_version] [all] [OPTIONS]" 1>&2 - echo -e "all\t\t\tInstall all packages." 1>&2 - echo -e "-n=code_name\t\tSpecifies the distro codename, for example bionic" 1>&2 - echo -e "-h\t\t\tPrints this help." 1>&2 - echo -e "-m=repo_base_url\tSpecifies the base URL from which to download." 1>&2 - exit 1; -} - -CURRENT_LLVM_STABLE=19 -BASE_URL="http://apt.llvm.org" - -NEW_DEBIAN_DISTROS=("trixie" "unstable") -# Set default values for commandline arguments -# We default to the current stable branch of LLVM -LLVM_VERSION=$CURRENT_LLVM_STABLE -ALL=0 -DISTRO=$(lsb_release -is) -VERSION_CODENAME=$(lsb_release -cs) -VERSION=$(lsb_release -sr) -UBUNTU_CODENAME="" -CODENAME_FROM_ARGUMENTS="" -# Obtain VERSION_CODENAME and UBUNTU_CODENAME (for Ubuntu and its derivatives) -source /etc/os-release -DISTRO=${DISTRO,,} - -# Check for required tools - -# Check if this is a new Debian distro -is_new_debian=0 -if [[ "${DISTRO}" == "debian" ]]; then - for new_distro in "${NEW_DEBIAN_DISTROS[@]}"; do - if [[ "${VERSION_CODENAME}" == "${new_distro}" ]]; then - is_new_debian=1 - break - fi - done -fi - -# Check for required tools -needed_binaries=(lsb_release wget gpg) -# add-apt-repository is not needed for newer Debian distros -if [[ $is_new_debian -eq 0 ]]; then - needed_binaries+=(add-apt-repository) -fi - -missing_binaries=() -using_curl= -for binary in "${needed_binaries[@]}"; do - if ! command -v $binary &>/dev/null ; then - if [[ "$binary" == "wget" ]] && command -v curl &>/dev/null; then - using_curl=1 - continue - fi - missing_binaries+=($binary) - fi -done - -if [[ ${#missing_binaries[@]} -gt 0 ]] ; then - echo "You are missing some tools this script requires: ${missing_binaries[@]}" - echo "(hint: apt install lsb-release wget software-properties-common gnupg)" - echo "curl is also supported" - exit 4 -fi - -case ${DISTRO} in - debian) - # Debian Trixie has a workaround because of - # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1038383 - if [[ "${VERSION}" == "unstable" ]] || [[ "${VERSION}" == "testing" ]] || [[ "${VERSION_CODENAME}" == "trixie" ]]; then - CODENAME=unstable - LINKNAME= - else - # "stable" Debian release - CODENAME=${VERSION_CODENAME} - LINKNAME=-${CODENAME} - fi - ;; - *) - # ubuntu and its derivatives - if [[ -n "${UBUNTU_CODENAME}" ]]; then - CODENAME=${UBUNTU_CODENAME} - if [[ -n "${CODENAME}" ]]; then - LINKNAME=-${CODENAME} - fi - fi - ;; -esac - -# read optional command line arguments -if [ "$#" -ge 1 ] && [ "${1::1}" != "-" ]; then - if [ "$1" != "all" ]; then - LLVM_VERSION=$1 - else - # special case for ./llvm.sh all - ALL=1 - fi - OPTIND=2 - if [ "$#" -ge 2 ]; then - if [ "$2" == "all" ]; then - # Install all packages - ALL=1 - OPTIND=3 - fi - fi -fi - -while getopts ":hm:n:" arg; do - case $arg in - h) - usage - ;; - m) - BASE_URL=${OPTARG} - ;; - n) - CODENAME=${OPTARG} - if [[ "${CODENAME}" == "unstable" ]]; then - # link name does not apply to unstable repository - LINKNAME= - else - LINKNAME=-${CODENAME} - fi - CODENAME_FROM_ARGUMENTS="true" - ;; - esac -done - -if [[ $EUID -ne 0 ]]; then - echo "This script must be run as root!" - exit 1 -fi - -declare -A LLVM_VERSION_PATTERNS -LLVM_VERSION_PATTERNS[9]="-9" -LLVM_VERSION_PATTERNS[10]="-10" -LLVM_VERSION_PATTERNS[11]="-11" -LLVM_VERSION_PATTERNS[12]="-12" -LLVM_VERSION_PATTERNS[13]="-13" -LLVM_VERSION_PATTERNS[14]="-14" -LLVM_VERSION_PATTERNS[15]="-15" -LLVM_VERSION_PATTERNS[16]="-16" -LLVM_VERSION_PATTERNS[17]="-17" -LLVM_VERSION_PATTERNS[18]="-18" -LLVM_VERSION_PATTERNS[19]="-19" -LLVM_VERSION_PATTERNS[20]="-20" -LLVM_VERSION_PATTERNS[21]="" - -if [ ! ${LLVM_VERSION_PATTERNS[$LLVM_VERSION]+_} ]; then - echo "This script does not support LLVM version $LLVM_VERSION" - exit 3 -fi - -LLVM_VERSION_STRING=${LLVM_VERSION_PATTERNS[$LLVM_VERSION]} - -# join the repository name -if [[ -n "${CODENAME}" ]]; then - REPO_NAME="deb ${BASE_URL}/${CODENAME}/ llvm-toolchain${LINKNAME}${LLVM_VERSION_STRING} main" - # check if the repository exists for the distro and version - if ! wget -q --method=HEAD ${BASE_URL}/${CODENAME} &> /dev/null && \ - ! curl -sSLI -XHEAD ${BASE_URL}/${CODENAME} &> /dev/null; then - if [[ -n "${CODENAME_FROM_ARGUMENTS}" ]]; then - echo "Specified codename '${CODENAME}' is not supported by this script." - else - echo "Distribution '${DISTRO}' in version '${VERSION}' is not supported by this script." - fi - exit 2 - fi -fi - - -# install everything - -if [[ ! -f /etc/apt/trusted.gpg.d/apt.llvm.org.asc ]]; then - # download GPG key once - if [[ -z "$using_curl" ]]; then - wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc - else - curl -sSL https://apt.llvm.org/llvm-snapshot.gpg.key | tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc - fi -fi - -if [[ -z "`apt-key list 2> /dev/null | grep -i llvm`" ]]; then - # Delete the key in the old format - apt-key del AF4F7421 || true -fi - - -# Add repository based on distribution -if [[ "${VERSION_CODENAME}" == "bookworm" ]]; then - # add it twice to workaround: - # https://github.com/llvm/llvm-project/issues/62475 - add-apt-repository -y "${REPO_NAME}" - add-apt-repository -y "${REPO_NAME}" -elif [[ $is_new_debian -eq 1 ]]; then - # workaround missing add-apt-repository in newer Debian and use new source.list format - SOURCES_FILE="/etc/apt/sources.list.d/http_apt_llvm_org_${CODENAME}_-${VERSION_CODENAME}.sources" - TEXT_TO_ADD="Types: deb -Architectures: amd64 arm64 -Signed-By: /etc/apt/trusted.gpg.d/apt.llvm.org.asc -URIs: ${BASE_URL}/${CODENAME}/ -Suites: llvm-toolchain${LINKNAME}${LLVM_VERSION_STRING} -Components: main" - echo "$TEXT_TO_ADD" | tee -a "$SOURCES_FILE" > /dev/null -else - add-apt-repository -y "${REPO_NAME}" -fi - -apt-get update -PKG="clang-$LLVM_VERSION lldb-$LLVM_VERSION lld-$LLVM_VERSION clangd-$LLVM_VERSION" -if [[ $ALL -eq 1 ]]; then - # same as in test-install.sh - # No worries if we have dups - PKG="$PKG clang-tidy-$LLVM_VERSION clang-format-$LLVM_VERSION clang-tools-$LLVM_VERSION llvm-$LLVM_VERSION-dev lld-$LLVM_VERSION lldb-$LLVM_VERSION llvm-$LLVM_VERSION-tools libomp-$LLVM_VERSION-dev libc++-$LLVM_VERSION-dev libc++abi-$LLVM_VERSION-dev libclang-common-$LLVM_VERSION-dev libclang-$LLVM_VERSION-dev libclang-cpp$LLVM_VERSION-dev liblldb-$LLVM_VERSION-dev libunwind-$LLVM_VERSION-dev" - if test $LLVM_VERSION -gt 14; then - PKG="$PKG libclang-rt-$LLVM_VERSION-dev libpolly-$LLVM_VERSION-dev" - fi -fi -apt-get install -y $PKG diff --git a/v5/include/libhal-util/map.hpp b/v5/include/libhal-util/map.hpp index 02671fe..5fabc25 100644 --- a/v5/include/libhal-util/map.hpp +++ b/v5/include/libhal-util/map.hpp @@ -16,6 +16,8 @@ #include +#include "math.hpp" + /** * @defgroup Map Map * diff --git a/v6/.clangd b/v6/.clangd new file mode 100644 index 0000000..f8ff8cd --- /dev/null +++ b/v6/.clangd @@ -0,0 +1,3 @@ +CompileFlags: + CompilationDatabase: . + BuiltinHeaders: QueryDriver diff --git a/v6/CMakeLists.txt b/v6/CMakeLists.txt new file mode 100644 index 0000000..6c1272b --- /dev/null +++ b/v6/CMakeLists.txt @@ -0,0 +1,78 @@ +# Copyright 2024 - 2025 Khalil Estell and the libhal contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cmake_minimum_required(VERSION 4.0) + +# TODO(kammce): remove line below +set(LIBHAL_ENABLE_CLANG_TIDY OFF) + +project(libhal-util LANGUAGES CXX) + +# libhal cmake utilities from libhal-cmake-util +# Source: https://github.com/libhal/libhal-cmake-util +find_package(LibhalCMakeUtil REQUIRED) + +find_package(libhal REQUIRED) + +libhal_project_init() +libhal_add_library(util + MODULES + modules/hal.util.cppm + modules/as_bytes.cppm + modules/bit.cppm + modules/can.cppm + modules/comparison.cppm + modules/enum.cppm + modules/i2c.cppm + modules/map.cppm + modules/math.cppm + modules/overflow_counter.cppm + modules/serial.cppm + modules/spi.cppm + modules/steady_clock.cppm + modules/to_array.cppm + modules/units.cppm +) + +target_link_libraries(util + PUBLIC + libhal::libhal +) + +libhal_apply_compile_options(util) +libhal_install_library(util NAMESPACE libhal PACKAGE_NAME libhal-util) +libhal_add_tests(util + TEST_NAMES + main + as_bytes + bit + bit_functions + can + enum + i2c + map + math + overflow_counter + serial + spi + steady_clock + to_array + units + MODULES + tests/util.cppm + LINK_LIBRARIES + libhal::libhal + PACKAGES + libhal +) diff --git a/v6/conanfile.py b/v6/conanfile.py new file mode 100644 index 0000000..da33431 --- /dev/null +++ b/v6/conanfile.py @@ -0,0 +1,137 @@ +#!/usr/bin/python +# +# Copyright 2024 - 2025 Khalil Estell and the libhal contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from conan import ConanFile +from conan.tools.cmake import CMake, cmake_layout, CMakeToolchain, CMakeDeps +from conan.tools.files import copy +from conan.errors import ConanInvalidConfiguration +from conan.tools.build import check_min_cppstd +from conan.tools.scm import Version +from pathlib import Path + + +required_conan_version = ">=2.2.0" + + +class libhal_util_conan(ConanFile): + name = "libhal-util" + license = "Apache-2.0" + url = "https://github.com/libhal/libhal-util" + homepage = "https://libhal.github.io/libhal-util" + description = ("Utility functions and tools for embedded systems " + "using modern C++") + topics = ("utilities", "embedded", "hardware", "hal") + settings = "compiler", "build_type", "os", "arch" + exports_sources = "modules/*", "src/*", "tests/*", "CMakeLists.txt", "LICENSE" + package_type = "static-library" + shared = False + + @property + def _min_cppstd(self): + return "23" + + @property + def _compilers_minimum_version(self): + return { + "gcc": ("14", "GCC 14+ required for libhal-util"), + "clang": ( + "19", + "Clang 19+ required for libhal-util" + ), + "apple-clang": ( + "19.0.0", + "Apple Clang 19+ for libhal-util" + ), + "msvc": ( + "193.4", + "MSVC 14.34+ (Visual Studio 17.4+) for libhal-util" + ) + } + + def _validate_compiler_version(self): + """Validate compiler version against minimum requirements""" + compiler = str(self.settings.compiler) + version = str(self.settings.compiler.version) + + # Map Visual Studio to msvc for consistency + compiler_key = "msvc" if compiler == "Visual Studio" else compiler + + min_versions = self._compilers_minimum_version + if compiler_key not in min_versions: + raise ConanInvalidConfiguration( + f"Compiler {compiler} is not supported for C++20 modules") + + min_version, error_msg = min_versions[compiler_key] + if Version(version) < min_version: + raise ConanInvalidConfiguration(error_msg) + + def validate(self): + if self.settings.get_safe("compiler.cppstd"): + check_min_cppstd(self, self._min_cppstd) + + self._validate_compiler_version() + + def build_requirements(self): + self.tool_requires("cmake/[^4.0.0]") + self.tool_requires("ninja/[^1.3.0]") + self.test_requires("boost-ext-ut/2.3.1") + self.tool_requires("libhal-cmake-util/[^5.0.9]") + + def requirements(self): + self.requires("libhal/[^5.0.0]") + + def layout(self): + build_path = Path("build") / ( + str(self.settings.arch) + "-" + + str(self.settings.os) + "-" + + str(self.settings.compiler) + "-" + + str(self.settings.compiler.version) + ) + cmake_layout(self, build_folder=str(build_path)) + + def set_version(self): + # Use latest if not specified via command line + if not self.version: + self.version = "latest" + + def generate(self): + tc = CMakeToolchain(self) + tc.generator = "Ninja" + tc.generate() + + deps = CMakeDeps(self) + deps.generate() + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() + if not self.conf.get("tools.build:skip_test", default=False): + cmake.ctest(["--output-on-failure"]) + + def package(self): + cmake = CMake(self) + cmake.install() + + copy(self, "LICENSE", + dst=Path(self.package_folder) / "licenses", + src=self.source_folder) + + def package_info(self): + # DISABLE Conan's config file generation + self.cpp_info.set_property("cmake_find_mode", "none") + # Tell CMake to include this directory in its search path + self.cpp_info.builddirs.append("lib/cmake") diff --git a/v6/modules/as_bytes.cppm b/v6/modules/as_bytes.cppm new file mode 100644 index 0000000..89abc31 --- /dev/null +++ b/v6/modules/as_bytes.cppm @@ -0,0 +1,109 @@ +// Copyright 2024 - 2025 Khalil Estell and the libhal contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +module; + +#include + +#include + +export module hal.util:as_bytes; + +import hal; + +/** + * @defgroup AsBytes As Bytes + */ + +namespace hal::inline v6 { +/** + * @ingroup AsBytes + * @brief Converts a pointer and length to a writeable (mutable) span of T + * elements + * + * @tparam T - element type + * @param p_address - address of the first element + * @param p_number_of_elements - the number of valid elements after the first + * @return constexpr std::span - span of elements + */ +export template +constexpr std::span as_writable_bytes(T* p_address, + size_t p_number_of_elements) +{ + auto* start = reinterpret_cast(p_address); + size_t number_of_bytes = sizeof(T) * p_number_of_elements; + return { start, number_of_bytes }; +} + +/** + * @ingroup AsBytes + * @brief Converts a pointer and length to a constant span of T elements + * + * @tparam T - element type + * @param p_address - address of the first element + * @param p_number_of_elements - the number of valid elements after the first + * @return constexpr std::span - span of elements + */ +export template +constexpr std::span as_bytes(T const* p_address, + size_t p_number_of_elements) +{ + auto* start = reinterpret_cast(p_address); + size_t number_of_bytes = sizeof(T) * p_number_of_elements; + return { start, number_of_bytes }; +} + +// Turning off clang-format because concepts because give it an aneurysm. +// clang-format off +/** + * @brief A concept for determining if something can be used by + * `as_writable_bytes()` and `as_bytes()` + * + * @tparam T - type of the container + */ +export template +concept convertible_to_bytes = requires(T a) { + *a.data(); + a.size(); + }; +// clang-format on + +/** + * @ingroup AsBytes + * @brief Converts a container to a writable (mutable) span of T elements + * + * @tparam T - element type + * @param p_container - Container containing the contiguous array of elements. + * @return constexpr std::span - span of elements + */ +export constexpr std::span as_writable_bytes( + convertible_to_bytes auto& p_container) +{ + return as_writable_bytes(p_container.data(), p_container.size()); +} + +/** + * @ingroup AsBytes + * @brief Converts a container to a constant span of T elements + * + * @tparam T - element type + * @param p_container - Container containing the contiguous array of elements. + * @return constexpr std::span - span of elements + */ +export constexpr std::span as_bytes( + convertible_to_bytes auto const& p_container) +{ + return as_bytes(p_container.data(), p_container.size()); +} +} // namespace hal::inline v6 diff --git a/v6/modules/bit.cppm b/v6/modules/bit.cppm new file mode 100644 index 0000000..8ef712d --- /dev/null +++ b/v6/modules/bit.cppm @@ -0,0 +1,783 @@ +// Copyright 2024 - 2025 Khalil Estell and the libhal contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +module; + +#include +#include +#include + +#include +#include +#include + +export module hal.util:bit; + +// NOTE: DO not include libhal/units.hpp here. We want this file to stay as a +// dropp in header only library that just uses C++'s stdlib. + +/** + * @defgroup Bit Bit Operations + * + */ +namespace hal::inline v6 { +/** + * @ingroup BitLimits + * @brief Generate a mask of 1s at compile time + * + * @tparam BitWidth - number of 1s in the mask + * @tparam T - the type + * @return consteval uint32_t - mask with 1s at the LSB + */ +export template +[[nodiscard]] consteval T generate_field_of_ones() +{ + T result = 0; + for (size_t i = 0; i < BitWidth; i++) { + result |= 1 << i; + } + return result; +} + +/** + * @ingroup BitLimits + * @brief Similar to std::numeric_limits except that this object can give + * properties of integral types of arbitrary bit widths. + * + * @tparam BitWidth - bit width of the value + * @tparam int_t - the containing type. Will generate a compile error if the + * containing type's bit width is smaller than the BitWidth value. + */ +export template +struct bit_limits +{ + // Check that the bit width is less than or equal to the size of the int_t. + static_assert(BitWidth <= sizeof(int_t) * 8, + "The BitWidth exceed the number of bits in the integer type."); + + // Check that bit width is not zero. + static_assert(BitWidth != 0, "The BitWidth cannot be 0."); + + /** + * @ingroup BitLimits + * @brief Get the maximum value available for an integer of `BitWidth` size + * and that can be stored within `int_t`. + * + * The final value of the function depends also on the sign of the int type. + * + * @return constexpr int_t maximum value + */ + [[nodiscard]] static constexpr int_t max() + { + if constexpr (std::is_signed_v) { + int_t max = generate_field_of_ones(); + max = max >> 1; + return max; + } else { + int_t max = generate_field_of_ones(); + return max; + } + } + + /** + * @ingroup BitLimits + * @brief Get the minimum value available for an integer of `BitWidth` size + * and that can be stored within `int_t`. + * + * The final value of the function depends also on the sign of the int type. + * Unsigned ints simply return zero. + * + * @return constexpr int_t minimum value + */ + [[nodiscard]] static constexpr int_t min() + { + if constexpr (BitWidth == 64) { + return std::numeric_limits::min(); + } else if constexpr (std::is_signed_v) { + int_t min = (1ULL << BitWidth); + return min; + } else { + return 0U; + } + } +}; + +/** + * @ingroup Bit + * @brief Represents a bit mask of contiguous bits + * + */ +export struct bit_mask +{ + /// Where the bit mask starts + std::uint32_t position; + /// The number of bits after position contained in the mask + std::uint32_t width; + + /** + * @brief Generate, at compile time, a bit_mask that spans the from position1 + * to position2. + * + * If position1 is the same position2 then the bit_mask will have length of 1 + * and the bit position will be the value of position1. + * + * position1 and position2 can be in any order so long as they span the + * distance from the start and end of the bit_mask range. + * + * Use this when you REQUIRE the bit mask to be generated at compile time, + * because template arguments are required to be known at compile time. + * + * @tparam position1 - bit position 1 + * @tparam position2 - bit position 2 + * @return consteval bit_mask - bit bit_mask represented by the two bit + * positions + */ + template + static consteval bit_mask from() + { + if constexpr (position1 < position2) { + return bit_mask{ .position = position1, + .width = 1 + (position2 - position1) }; + } else { + return bit_mask{ .position = position2, + .width = 1 + (position1 - position2) }; + } + } + + /** + * @brief Generate, at compile time, a bit_mask that spans the from position1 + * to position2. + * + * If position1 is the same position2 then the bit_mask will have length of 1 + * and the bit position will be the value of position1. + * + * position1 and position2 can be in any order so long as they span the + * distance from the start and end of the bit_mask range. + * + * @param position1 - bit position 1 + * @param position2 - bit position 2 + * @return consteval bit_mask - bit bit_mask represented by the two bit + * positions + */ + static constexpr bit_mask from(std::uint32_t position1, + std::uint32_t position2) + { + if (position1 < position2) { + return bit_mask{ .position = position1, + .width = 1 + (position2 - position1) }; + } else { + return bit_mask{ .position = position2, + .width = 1 + (position1 - position2) }; + } + } + + /** + * @brief Generate, at runtime, a single bit width bit_mask at position + * + * @param position - the bit to make the bit_mask for + * @return constexpr bit_mask - bit bit_mask with the position bit set to + * position + */ + static constexpr bit_mask from(std::uint32_t position) + { + return bit_mask{ .position = position, .width = 1U }; + } + + /** + * @brief Convert bit_mask to a integral representation but with bit position + * at 0 + * + * The integral presentation will have 1 bits starting from the position bit + * up to bit position + width. All other bits will be 0s. + * + * For example: + * + * value(bit_mask{ + * .position = 1, + * .width = 4, + * }); // returns = 0b0000'0000'0000'1111; + * + * @tparam T - unsigned integral type to hold the bit_mask + * @return constexpr auto - bit_mask value as an unsigned integer + */ + template + constexpr auto origin() const + { + // At compile time, generate variable containing all 1s with the size of the + // target parameter. + constexpr T field_of_ones = std::numeric_limits::max(); + + // At compile time calculate the number of bits in the target parameter. + constexpr std::size_t target_width = sizeof(T) * 8; + + // Create bit_mask by shifting the set of 1s down so that the number of 1s + // from bit position 0 is equal to the width parameter. + T mask_at_origin = static_cast(field_of_ones >> (target_width - width)); + + return mask_at_origin; + } + + /** + * @brief Convert mask to a integral representation + * + * The integral presentation will have 1 bits starting from the position bit + * up to bit position + width. All other bits will be 0s. + * + * For example: + * + * value(bit_mask{ + * .position = 1, + * .width = 4, + * }); // returns = 0b0000'0000'0001'1110; + * + * @tparam T - unsigned integral type to hold the mask + * @return constexpr auto - mask value as an unsigned integer + */ + template + constexpr auto value() const + { + return static_cast(origin() << position); + } + + /** + * @brief Comparison operator between this mask and another + * + * @param p_other - the other mask to compare against + * @return true - the masks are the same + * @return false - the masks are not the same + */ + constexpr bool operator==(bit_mask const& p_other) + { + return p_other.position == position && p_other.width == width; + } + + /** + * + * @brief Shift the position of the bit mask to the right + * + * NOTE: the position will overflow if the position + * + * @param p_shift_amount - the number of bits to shift the position by + * @return constexpr auto - a copy of this bit_mask but with the position + * shifted. + */ + constexpr auto operator>>(std::uint32_t p_shift_amount) const + { + hal::bit_mask result = *this; + if (result.position > p_shift_amount) { + result.position -= p_shift_amount; + } else { + result.position = 0; + } + return result; + } + + /** + * + * @brief Shift the position of the bit mask to the left + * + * NOTE: the position will not underflow if the shift amount is greater than + * the position. The position value will saturate at the value of 0. + * + * @param p_shift_amount - the number of bits to shift the position by + * @return constexpr auto - a copy of this bit_mask but with the position + * shifted. + */ + constexpr auto operator<<(std::uint32_t p_shift_amount) const + { + hal::bit_mask result = *this; + result.position += p_shift_amount; + return result; + } +}; + +/** + * @ingroup Bit + * @brief Helper for generating byte position masks + * + * This type can be used to make a byte mask for a single byte or for multiple + * bytes. + * + * USAGE: + * + * // Equivalent to: + * // hal::bit_mask::from(8, 23) + * // or + * // hal::bit_mask{ position = 8, width = 16 } + * auto mask = byte_mask<1, 2>::value; + * + * Note that the order of the mask indexes do not matter. If you order this <2, + * 1> or <1, 2> you will get the same result which is a mask of bytes from one + * end to the other. + * + * @tparam ByteIndex1 - starting byte position + * @tparam ByteIndex2 - ending byte position + */ +export template +struct byte_mask +{ + static constexpr auto bits_per_byte = 8; + static constexpr auto start_byte = std::min(ByteIndex1, ByteIndex2); + static constexpr auto end_byte = std::max(ByteIndex1, ByteIndex2); + /** + * + * @brief Mask value defined at compile time + * + */ + inline static constexpr auto value = hal::bit_mask{ + .position = bits_per_byte * start_byte, + .width = bits_per_byte * (1 + (end_byte - start_byte)), + }; +}; + +/** + * @ingroup Bit + * @brief Shorthand for using hal::byte_mask::value + * + * @tparam ByteIndex1 - the byte position to make a mask for + * @tparam ByteIndex2 - the byte position to make a mask for + */ +export template +constexpr hal::bit_mask byte_m = byte_mask::value; + +/** + * @ingroup Bit + * @brief Helper for generating nibble position masks + * + * This type can be used to make a nibble mask for a single nibble or for + * multiple nibbles. + * + * USAGE: + * + * // Equivalent to: + * // hal::bit_mask::from(4, 12) + * // or + * // hal::bit_mask{ position = 4, width = 8 } + * auto mask = nibble_mask<1, 2>::value; + * + * Note that the order of the mask indexes do not matter. If you order this <2, + * 1> or <1, 2> you will get the same result which is a mask of nibbles from one + * end to the other. + * + * @tparam NibbleIndex1 - starting nibble position + * @tparam NibbleIndex2 - ending nibble position + */ +export template +struct nibble_mask +{ + static constexpr auto bits_per_nibble = 4; + static constexpr auto start_nibble = std::min(NibbleIndex1, NibbleIndex2); + static constexpr auto end_nibble = std::max(NibbleIndex1, NibbleIndex2); + static constexpr auto value = hal::bit_mask{ + .position = bits_per_nibble * start_nibble, + .width = bits_per_nibble * (1 + (end_nibble - start_nibble)), + }; +}; + +/** + * @ingroup Bit + * @brief Shorthand for using hal::nibble_mask::value + * + * @tparam NibbleIndex1 - starting nibble position + * @tparam NibbleIndex2 - ending nibble position + */ +export template +constexpr hal::bit_mask nibble_m = + nibble_mask::value; + +/** + * @ingroup Bit + * @brief Extracts a specific field from an unsigned integral value using a + * compile time (template) bit mask. + * + * Prefer to use this function over the non-template function if the bit mask + * can be known at compile time. This will allow the compiler to optimize this + * bit extract even further. + * + * USAGE: + * + * static constexpr auto peripheral_state = hal::bit_mask::from<4, 9>(); + * auto state = hal::bit_extract(reg->status); + * // Proceed to use `state` for something + * + * @tparam field - The bit mask defining the position and width of the field to + * be extracted. + * @param p_value The unsigned integral value from which the field will be + * extracted. + * @return A value representing only the specified field, with all other bits + * set to zero. + */ +export template +constexpr auto bit_extract(std::unsigned_integral auto p_value) +{ + using T = decltype(p_value); + // Shift desired value to the right to position 0 + auto const shifted = p_value >> field.position; + // Mask away any bits left of the value based on the field width + auto const masked = shifted & field.origin(); + // Leaving only the desired bits + return static_cast(masked); +} +/** + * @ingroup Bit + * @brief Extracts a specific field from an unsigned integral value using a bit + * mask. + * + * If the bit mask is known and constant at compile time, use the + * `hal::bit_extract` function as it provides more information to the + * compiler that it can use to optimize the bit extraction operation. + * + * USAGE: + * + * auto const peripheral_state = hal::bit_mask::from(4, 9); + * auto state = hal::bit_extract(peripheral_state, reg->status); + * // Proceed to use `state` for something + * + * @param p_field - The bit mask defining the position and width of the field to + * be extracted. + * @param p_value The unsigned integral value from which the field will be + * extracted. + * @return A value representing only the specified field, with all other bits + * set to zero. + */ +export constexpr auto bit_extract(bit_mask p_field, + std::unsigned_integral auto p_value) +{ + using T = decltype(p_value); + // Shift desired value to the right to position 0 + auto const shifted = p_value >> p_field.position; + // Mask away any bits left of the value based on the field width + auto const masked = shifted & p_field.origin(); + // Leaving only the desired bits + return static_cast(masked); +} + +/** + * @ingroup Bit + * @brief A class for representing a value as a sequence of bits. + * + * This class provides a flexible way to manipulate and work with bit-level + * values, allowing for efficient and expressive operations on large integers. + * + * This class provides template and non-template options for manipulating bits. + * If the bitmask is KNOWN at COMPILE TIME, OPT to use the template versions of + * the APIs as the compiler will have enough information to deduce the bit mask + * and reduce the set of operations needed to perform the bit manipulation. + * + * USAGE: + * + * static constexpr auto pre_scalar = hal::bit_mask::from<3, 18>(); + * static constexpr auto enable = hal::bit_mask::from<19>(); + * hal::bit_value my_value; + * my_value.insert(120UL) + * .set(); + * reg->control = my_value.get(); + * + * @tparam T - defaults to `std::uint32_t`, but can be any unsigned integral + * type. + */ +export template +class bit_value +{ +public: + /** + * @brief The total number of bits in the represented value. + * + * This is calculated as the product of the size of `T` and the number of bits + * per byte (`CHAR_BIT`). + */ + static constexpr std::uint32_t width = sizeof(T) * CHAR_BIT; + + /** + * @brief Constructs a new bit_value instance with an initial value. + * + * @param p_initial_value The initial value to use. Defaults to 0. + */ + constexpr bit_value(T p_initial_value = 0) + : m_value(p_initial_value) + { + } + /** + * @brief Constructs a new bit_value instance with an initial value. + * + * @param p_initial_value The initial value to use. Defaults to 0. + */ + constexpr bit_value& operator=(T p_initial_value) + { + m_value = p_initial_value; + } + + /** + * @brief Sets (sets to a 1) multiple bits in the represented value. + * + * @tparam field A bit_mask type, which represents the position and size of + * the bit to set. + * @return A reference to this instance for method chaining. + */ + template + constexpr auto& set() + { + static_assert(field.position < width, + "Bit position exceeds register width"); + constexpr auto mask = field.value(); + + m_value = m_value | mask; + + return *this; + } + + /** + * @brief Sets (sets to a 1) multiple bits in the represented value. + * + * @param p_field A bit_mask instance, which represents the position and size + * of the bit to set. + * @return A reference to this instance for chaining. + */ + constexpr auto& set(bit_mask p_field) + { + auto const mask = p_field.value(); + + m_value = m_value | mask; + + return *this; + } + + /** + * @brief Clears (sets to 0) multiple bits in the represented value. + * + * @tparam field A bit_mask type, which represents the position and size of + * the bit to clear. + * @return A reference to this instance for chaining. + */ + template + constexpr auto& clear() + { + static_assert(field.position < width, + "Bit position exceeds register width"); + constexpr auto inverted_mask = ~field.value(); + + m_value = m_value & inverted_mask; + + return *this; + } + + /** + * @brief Clears (sets to 0) multiple bits in the represented value. + * + * @param p_field A bit_mask instance, which represents the position and size + * of the bit to clear. + * @return A reference to this instance for chaining. + */ + constexpr auto& clear(bit_mask p_field) + { + auto const inverted_mask = ~p_field.value(); + + m_value = m_value & inverted_mask; + + return *this; + } + + /** + * @brief Toggles multiple bits in the represented value. + * + * @tparam field A bit_mask type, which represents the position and size of + * the bit to toggle. + * @return A reference to this instance for chaining. + */ + template + constexpr auto& toggle() + { + static_assert(field.position < width, + "Bit position exceeds register width"); + + constexpr auto mask = field.value(); + + m_value = m_value ^ mask; + + return *this; + } + + /** + * @brief Toggles a single bit in the represented value. + * + * @param p_field A bit_mask instance, which represents the position and size + * of the bit to toggle. + * @return A reference to this instance for chaining. + */ + constexpr auto& toggle(bit_mask p_field) + { + auto const mask = p_field.value(); + + m_value = m_value ^ mask; + + return *this; + } + + /** + * @brief Inserts a new value into the represented bit sequence. + * + * @tparam field A bit_mask type, which represents the position and size of + * the bit to insert. + * @param p_value The new value to insert. + * @return A reference to this instance for chaining. + */ + template + constexpr auto& insert(std::unsigned_integral auto p_value) + { + auto const value_to_insert = static_cast(p_value); + // AND value with mask to remove any bits beyond the specified width. + // Shift masked value into bit position and OR with target value. + auto const shifted_field = value_to_insert << field.position; + auto const new_value = shifted_field & field.value(); + + // Clear width's number of bits in the target value at the bit position + // specified. + m_value = m_value & ~field.value(); + m_value = m_value | static_cast(new_value); + + return *this; + } + + /** + * @brief Inserts a new value into the represented bit sequence. + * + * @param p_field A bit_mask instance, which represents the position and size + * of the bit to insert. + * @param p_value The new value to insert. + * @return A reference to this instance for chaining. + */ + constexpr auto& insert(bit_mask p_field, std::unsigned_integral auto p_value) + { + // AND value with mask to remove any bits beyond the specified width. + // Shift masked value into bit position and OR with target value. + auto shifted_field = static_cast(p_value) << p_field.position; + auto new_value = shifted_field & p_field.value(); + + // Clear width's number of bits in the target value at the bit position + // specified. + m_value = m_value & ~p_field.value(); + m_value = m_value | static_cast(new_value); + + return *this; + } + + /** + * @brief Inserts a new value into the represented bit sequence. + * + * @tparam p_field A bit_mask instance, which represents the position and size + * of the bit to insert. + * @tparam p_value The new value to insert. + * @return A reference to this instance for chaining. + */ + template + constexpr auto& insert() + { + // AND value with mask to remove any bits beyond the specified width. + // Shift masked value into bit position and OR with target value. + auto shifted_field = static_cast(p_value) << p_field.position; + auto new_value = shifted_field & p_field.value(); + + // Clear width's number of bits in the target value at the bit position + // specified. + m_value = m_value & ~p_field.value(); + m_value = m_value | static_cast(new_value); + + return *this; + } + + /** + * @brief Returns the represented value as an instance of U + * + * Performs truncating static cast if the sizeof(T) > sizeof(U) + * + * @return The represented value as type U. + */ + template + [[nodiscard]] constexpr auto to() + { + return static_cast(m_value); + } + + /** + * @brief Returns the represented value as a T instance. + * + * @return The represented value. + */ + [[nodiscard]] constexpr T get() + { + return m_value; + } + +protected: + T m_value; +}; + +/** + * @ingroup Bit + * @brief A class for modifying a register value by manipulating its bits. + * + * This class provides a convenient and expressive way to perform bitwise + * operations on a register value, allowing for efficient and safe modifications + * without compromising performance. + * + * USAGE: + * + * static constexpr auto pre_scalar = hal::bit_mask::from<3, 18>(); + * static constexpr auto enable = hal::bit_mask::from<19>(); + * + * hal::bit_modify(reg->control) + * .insert(120UL) + * .set(); + * // At this line, reg->control will be updated + * + * @tparam T - defaults to `std::uint32_t`, but can be any unsigned integral + * type. + */ +export template +class bit_modify : public bit_value +{ +public: + /** + * @brief Constructs a new bit_modify instance with an initial value and a + * pointer to the register. + * + * @param p_register_reference A reference to the register whose bits will be + * modified. + */ + constexpr bit_modify(T volatile& p_register_reference) + : bit_value(p_register_reference) + , m_pointer(&p_register_reference) + { + } + + /** + * @brief On destruction, update the original register's value with the final + * modified bits. + * + */ + ~bit_modify() + { + *m_pointer = this->m_value; + } + +private: + /** + * @brief A pointer to the original register value being modified. + * + * This allows for safe and efficient updates of the underlying register with + * the final modified bits. + */ + T volatile* m_pointer; +}; +} // namespace hal::inline v6 diff --git a/v6/modules/can.cppm b/v6/modules/can.cppm new file mode 100644 index 0000000..98d806d --- /dev/null +++ b/v6/modules/can.cppm @@ -0,0 +1,304 @@ +// Copyright 2024 - 2025 Khalil Estell and the libhal contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +module; + +#include +#include +#include +#include + +export module hal.util:can; + +import hal; + +/** + * @defgroup CAN_Utilities CAN Utilities + * + */ + +namespace hal::inline v6 { +/** + * @brief Generic settings for a can peripheral + * @ingroup CAN_Utilities + * + * CAN Bit Quanta Timing Diagram of: + * + * | <--- sjw ---> | + * ____ ______ __________ __________ + * _/ SYNC \/ PROP \/ PHASE1 \/ PHASE2 \_ + * \______/\________/\____________/\____________/ + * ^ Sample point + */ +export struct can_bus_divider_t +{ + /** + * @brief Bus clock rate in hertz + * + */ + std::uint8_t clock_divider; + + /** + * @brief Sync Segment (always 1qt) + * + * Initial sync transition, the start of a CAN bit + */ + static constexpr std::uint8_t sync_segment = 1; + + /** + * @brief Propagation Delay (1qt ... 8qt) + * + * Propagation time It is used to compensate for signal delays across the + * network. + */ + std::uint8_t propagation_delay; + + /** + * @brief Length of Phase Segment 1 (1qt ... 8qt) + * + * Determines the bit rate, phase segment 1 acts as a buffer that can be + * lengthened to resynchronize with the bit stream via the + * synchronization_jump_width. Includes propagation delay + */ + std::uint8_t phase_segment1; + + /** + * @brief Length of Phase Segment 2 (1qt ... 8qt) + * + * Determines the bit rate and is like phase segment 1 and occurs after the + * sampling point. Phase segment 2 can be shortened to resynchronize with + * the bit stream via the synchronization_jump_width. + */ + std::uint8_t phase_segment2; + + /** + * @brief Synchronization jump width (1qt ... 4qt) + * + * This is the maximum time by which the bit sampling period may be + * lengthened or shortened during each cycle to adjust for oscillator + * mismatch between nodes. + * + * This value must be smaller than phase_segment1 and phase_segment2 + */ + std::uint8_t synchronization_jump_width; + + /** + * @brief The total tq of the structure + * + */ + std::uint8_t total_tq; +}; + +/** + * @ingroup CAN_Utilities + * @brief Compares two CAN bus settings. + * + * @param p_lhs CAN bus settings + * @param p_rhs A CAN bus setting to compare against another + * @return A boolean if they are the same or not. + */ +export [[nodiscard]] constexpr auto operator==(can_bus_divider_t const& p_lhs, + can_bus_divider_t const& p_rhs) +{ + return p_lhs.clock_divider == p_rhs.clock_divider && + p_lhs.propagation_delay == p_rhs.propagation_delay && + p_lhs.phase_segment1 == p_rhs.phase_segment1 && + p_lhs.phase_segment2 == p_rhs.phase_segment2 && + p_lhs.synchronization_jump_width == p_rhs.synchronization_jump_width; +} + +export [[nodiscard]] constexpr std::uint16_t bit_width( + can_bus_divider_t const& p_settings) +{ + // The sum of 4x 8-bit numbers can never exceed uint16_t and thus this + // operation is always safe. + return static_cast( + p_settings.sync_segment + p_settings.propagation_delay + + p_settings.phase_segment1 + p_settings.phase_segment2); +} + +/** + * @brief Calculates the can bus divider values + * @ingroup CAN_Utilities + * + * Preferred method of calculating the bus divider values for a can bus + * peripheral or device. The algorithm checks each possible time quanta (tq) + * width from 25tq to 8tq. The algorithm always checks starting with the + * greatest time quanta in order to achieve the longest bit width and sync + * jump value. The aim is to provide the most flexibility in the sync jump + * value which should help in most topologies. + * + * @param p_operating_frequency - frequency of the input clock to the can bus + * bit timing module. + * @param p_target_baud_rate - the baud (bit) rate to set the can bus to. + * @return std::optional - the resulting dividers for the + * can bus peripheral. Returns std::nullopt if the target baud rate is not + * achievable with the provided operating frequency. + */ +export [[nodiscard]] std::optional +calculate_can_bus_divider(hertz p_operating_frequency, + hertz p_target_baud_rate) +{ + can_bus_divider_t timing{}; + + // Set phase segments and propagation delay to balanced values + timing.propagation_delay = 1; + + // use a value of zero in total tq to know if no tq and divider combo worked + timing.total_tq = 0; + + auto const operating_frequency_value = + p_operating_frequency.numerical_value_in(hertz::unit); + auto const target_baud_rate_value = + p_target_baud_rate.numerical_value_in(hertz::unit); + + if (operating_frequency_value <= 0 || target_baud_rate_value <= 0 || + operating_frequency_value <= target_baud_rate_value) { + return std::nullopt; + } + + auto const operating_frequency = static_cast(operating_frequency_value); + auto const desired_baud_rate = static_cast(target_baud_rate_value); + + using inner_div_t = + decltype(std::div(operating_frequency, desired_baud_rate)); + inner_div_t division{}; + + for (i32 total_tq = 25; total_tq >= 8; total_tq--) { + division = std::div(operating_frequency, (desired_baud_rate * total_tq)); + + if (division.rem == 0) { + timing.total_tq = total_tq; + break; + } + } + + if (timing.total_tq == 0) { + return std::nullopt; + } + + timing.clock_divider = static_cast(division.quot); + timing.phase_segment1 = (timing.total_tq - timing.sync_segment) / 2U; + timing.phase_segment2 = timing.total_tq - timing.sync_segment - + timing.phase_segment1 - timing.propagation_delay; + + // Adjust synchronization jump width (sjw) to a safe value + timing.synchronization_jump_width = + std::min(timing.phase_segment1, 4U); + + return timing; +} + +/** + * @brief A hal::can_transceiver wrapper class that makes finding message via + * ID easier with a find API + * + * If your driver plans to use this wrapper, rather than storing both the + * pointer to the hal::can_transceiver and this object, simply construct this + * object with the hal::can_transceiver object's address and use this object's + * transceiver() API to get access to the underlying transceiver + * implementation. This will ensure that the driver doesn't store the + * implementation's address twice. + * + */ +export class can_message_finder +{ +public: + /** + * @brief Construct a new can reader object + * + * @param p_transceiver - the transceiver to read messages from + * @param p_id - the message ID to search for + */ + can_message_finder(hal::can_transceiver& p_transceiver, hal::u32 p_id) + : m_transceiver(&p_transceiver) + , m_id(p_id) + { + } + + /** + * @brief Find a message within the receive buffer matching the input + * message ID. + * + * This API performs a double copy of the can message in order to confirm + * that the message did not get modified by the driver during the copy. + * + * @return std::optional - a copy of the can message + * within the buffer. The return is set to std::nullopt if the message + * could not be found OR was modified during the copy. + */ + [[nodiscard]] std::optional find() + { + auto const buffer = m_transceiver->receive_buffer(); + auto cursor = m_transceiver->receive_cursor(); + hal::can_message message = {}; + + // Run through the circular buffer until the cursor is reached or until + // an ID match is found. + while (m_receive_cursor != cursor) { + if (m_id == buffer[m_receive_cursor].id) { + message = buffer[m_receive_cursor]; + // Can message buffers are typically updated via interrupt which can + // happen at any time. If the cursor has moved past the received + // message and changed its bits mid way To ensure that an interrupt + // has not occurred and modified the bits of the buffer message, a + // second copy is created in order to perform a stability check. + hal::can_message const copy = buffer[m_receive_cursor]; + bool const message_is_stable = copy == message; + bool const copy_id_matches = copy.id == m_id; + if (message_is_stable and copy_id_matches) { + // Perform circular increment of the m_receive_cursor + m_receive_cursor = (m_receive_cursor + 1) % buffer.size(); + return message; + } + } + // Acquire latest cursor + cursor = m_transceiver->receive_cursor(); + // Perform circular increment of the m_receive_cursor + m_receive_cursor = (m_receive_cursor + 1) % buffer.size(); + } + + return std::nullopt; + } + + /** + * @brief Returns the underlying can hal::can_transceiver allowing + * + * This API makes it possible for drivers and applications using this + * wrapper to fully access the hal::can_transceiver without needing to + * store the pointer twice. + * + * @return hal::can_transceiver& - the underlying hal::can_transceiver + */ + [[nodiscard]] inline hal::can_transceiver& transceiver() const + { + return *m_transceiver; + } + + /** + * @brief Returns the ID to being searched for + * + * @return auto - the ID being searched for + */ + [[nodiscard]] inline auto id() const + { + return m_id; + } + +private: + hal::can_transceiver* m_transceiver; + hal::u32 m_id; + std::size_t m_receive_cursor = 0; +}; +} // namespace hal::inline v6 diff --git a/v6/modules/comparison.cppm b/v6/modules/comparison.cppm new file mode 100644 index 0000000..bed6e51 --- /dev/null +++ b/v6/modules/comparison.cppm @@ -0,0 +1,100 @@ +// Copyright 2024 - 2025 Khalil Estell and the libhal contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +module; + +#include +#include +#include + +export module hal.util:comparison; + +/** + * @defgroup Comparison Comparisons + * @{ + */ + +/** + * @brief Provides `operator==` between arrays and spans + * + * @tparam T - element type of array & span + * @tparam size - compile time size of the array + * @param p_array - array object + * @param p_span - span object + * @return true - if the two are equal in length and contents + * @return false - if the two are not equal to length or by contents + */ +export template +constexpr bool operator==(std::array const& p_array, + std::span const& p_span) +{ + if (p_span.size() != size) { + return false; + } + + return std::equal(p_array.begin(), p_array.end(), p_span.begin()); +} + +/** + * @brief Provides `operator==` between spans and arrays + * + * @tparam T - element type of array & span + * @tparam size - compile time size of the array + * @param p_span - span object + * @param p_array - array object + * @return true - if the two are equal in length and contents + * @return false - if the two are not equal to length or by contents + */ +export template +constexpr bool operator==(std::span const& p_span, + std::array const& p_array) +{ + return p_array == p_span; +} + +/** + * @brief Provides `operator!=` between arrays and spans + * + * @tparam T - element type of array & span + * @tparam size - compile time size of the array + * @param p_array - array object + * @param p_span - span object + * @return true - if the two are equal in length and contents + * @return false - if the two are not equal to length or by contents + */ +export template +constexpr bool operator!=(std::array const& p_array, + std::span const& p_span) +{ + return !(p_array == p_span); +} + +/** + * @brief Provides `operator!=` between spans and arrays + * + * @tparam T - element type of array & span + * @tparam size - compile time size of the array + * @param p_array - array object + * @param p_span - span object + * @return true - if the two are equal in length and contents + * @return false - if the two are not equal to length or by contents + */ +export template +constexpr bool operator!=(std::span const& p_span, + std::array const& p_array) +{ + return !(p_array == p_span); +} + +/** @} */ // End of group diff --git a/v6/modules/enum.cppm b/v6/modules/enum.cppm new file mode 100644 index 0000000..afacd74 --- /dev/null +++ b/v6/modules/enum.cppm @@ -0,0 +1,52 @@ +// Copyright 2024 - 2025 Khalil Estell and the libhal contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +module; + +#include + +export module hal.util:enumeration; + +import hal; + +/** + * @defgroup Enum Enums + * + */ + +namespace hal::inline v6 { +/** + * @ingroup Enum + * @brief concept for enumeration types + * + * @tparam T - enum type + */ +export template +concept enumeration = std::is_enum_v; + +/** + * @ingroup Enum + * @brief Helper function to convert an enum to its integral value + * + * @param p_enum_value - the enumeration you want to convert into an integral + * value + * @return constexpr auto - return the integral value of the enum with the same + * type as the enumeration. + */ +export [[nodiscard]] constexpr auto value(enumeration auto p_enum_value) +{ + return static_cast>( + p_enum_value); +} +} // namespace hal::inline v6 diff --git a/v6/modules/hal.util.cppm b/v6/modules/hal.util.cppm new file mode 100644 index 0000000..b9e7d72 --- /dev/null +++ b/v6/modules/hal.util.cppm @@ -0,0 +1,30 @@ +// Copyright 2024 - 2025 Khalil Estell and the libhal contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +export module hal.util; + +export import :as_bytes; +export import :bit; +export import :can; +export import :comparison; +export import :enumeration; +export import :i2c; +export import :map; +export import :math; +export import :overflow_counter; +export import :serial; +export import :spi; +export import :steady_clock; +export import :to_array; +export import :units; diff --git a/v6/modules/i2c.cppm b/v6/modules/i2c.cppm new file mode 100644 index 0000000..16797ae --- /dev/null +++ b/v6/modules/i2c.cppm @@ -0,0 +1,200 @@ +// Copyright 2024 - 2025 Khalil Estell and the libhal contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +module; + +#include +#include + +export module hal.util:i2c; + +import hal; +import scatter_span; + +import :enumeration; + +/** + * @defgroup I2CUtils I2C Utils + */ +namespace hal::inline v6 { +/** + * @ingroup I2CUtils + * @brief write data to a target device on the i2c bus + * + * Shorthand for writing i2c.transaction(...) for write only operations. + * + * @param p_ctx - async context for coroutine suspension and resumption. + * @param p_i2c - i2c driver + * @param p_address - target address + * @param p_data_out - buffer of bytes to write to the target device + */ +export async::future write(async::context& p_ctx, + hal::i2c& p_i2c, + hal::byte p_address, + mem::scatter_span p_data_out) +{ + co_await p_i2c.transaction(p_ctx, p_address, p_data_out, {}); +} + +/** + * @ingroup I2CUtils + * @brief read bytes from target device on i2c bus + * + * Shorthand for writing i2c.transaction(...) for read only operations. + * + * @param p_ctx - async context for coroutine suspension and resumption. + * @param p_i2c - i2c driver + * @param p_address - target address + * @param p_data_in - buffer to read bytes into from target device + */ +export async::future read(async::context& p_ctx, + hal::i2c& p_i2c, + hal::byte p_address, + mem::scatter_span p_data_in) +{ + co_await p_i2c.transaction(p_ctx, p_address, {}, p_data_in); +} + +/** + * @ingroup I2CUtils + * @brief return array of read bytes from target device on i2c bus + * + * Eliminates the need to create a buffer and pass it into the read function. + * + * @tparam bytes_to_read - number of bytes to read + * @param p_ctx - async context for coroutine suspension and resumption. + * @param p_i2c - i2c driver + * @param p_address - target address + * @return std::array - array of bytes from target + * device + */ +export template +[[nodiscard]] async::future> +read(async::context& p_ctx, hal::i2c& p_i2c, hal::byte p_address) +{ + std::array buffer; + co_await read(p_ctx, p_i2c, p_address, { buffer }); + co_return buffer; +} + +/** + * @ingroup I2CUtils + * @brief write and then read bytes from target device on i2c bus + * + * This API simply calls transaction. This API is here for consistency across + * the other other communication protocols such as SPI and serial. + * + * @param p_ctx - async context for coroutine suspension and resumption. + * @param p_i2c - i2c driver + * @param p_address - target address + * @param p_data_out - buffer of bytes to write to the target device + * @param p_data_in - buffer to read bytes into from target device + */ +export async::future write_then_read( + async::context& p_ctx, + hal::i2c& p_i2c, + hal::byte p_address, + mem::scatter_span p_data_out, + mem::scatter_span p_data_in) +{ + co_await p_i2c.transaction(p_ctx, p_address, p_data_out, p_data_in); +} + +/** + * @ingroup I2CUtils + * @brief write and then return an array of read bytes from target device on i2c + * bus + * + * Eliminates the need to create a buffer and pass it into the read function. + * + * @tparam bytes_to_read - number of bytes to read after write + * @param p_ctx - async context for coroutine suspension and resumption. + * @param p_i2c - i2c driver + * @param p_address - target address + * @param p_data_out - buffer of bytes to write to the target device + * @return std::array - + */ +export template +[[nodiscard]] async::future> +write_then_read(async::context& p_ctx, + hal::i2c& p_i2c, + hal::byte p_address, + mem::scatter_span p_data_out) +{ + std::array buffer; + co_await write_then_read(p_ctx, p_i2c, p_address, p_data_out, { buffer }); + co_return buffer; +} + +/** + * @ingroup I2CUtils + * @brief probe the i2c bus to see if a device exists + * + * NOTE: that this utilizes the fact that i2c drivers throw + * hal::no_such_device when a transaction is performed and the device's + * address is used on the bus and the device does not respond with an + * acknowledge. + * + * @param p_ctx - async context for coroutine suspension and resumption. + * @param p_i2c - i2c driver for the i2c bus that the device may exist on + * @param p_address - device to check + * @return true - if the device appears on the bus + * @return false - if the devices does not appear on the i2c bus + */ +export [[nodiscard]] async::future probe(async::context& p_ctx, + hal::i2c& p_i2c, + hal::byte p_address) +{ + // p_data_in: empty placeholder for transaction's data_in + std::array data_in{}; + bool device_acknowledged = true; + + try { + co_await p_i2c.transaction(p_ctx, p_address, {}, { data_in }); + } catch (hal::no_such_device const&) { + device_acknowledged = false; + } + + co_return device_acknowledged; +} + +/** + * @ingroup I2CUtils + * @brief Set of I2C transaction operations + * + */ +export enum class i2c_operation : u8 { + /// Denotes an i2c write operation + write = 0, + /// Denotes an i2c read operation + read = 1, +}; + +/** + * @ingroup I2CUtils + * @brief Convert 7-bit i2c address to an 8-bit address + * + * @param p_address 7-bit i2c address + * @param p_operation write or read operation + * @return hal::byte - 8-bit i2c address + */ +export [[nodiscard]] inline hal::byte to_8_bit_address( + hal::byte p_address, + i2c_operation p_operation) noexcept +{ + auto v8bit_address = static_cast(p_address << 1); + v8bit_address |= hal::value(p_operation); + return v8bit_address; +} +} // namespace hal::inline v6 diff --git a/v6/modules/map.cppm b/v6/modules/map.cppm new file mode 100644 index 0000000..5ddbf10 --- /dev/null +++ b/v6/modules/map.cppm @@ -0,0 +1,75 @@ +// Copyright 2024 - 2025 Khalil Estell and the libhal contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +module; + +#include +#include + +export module hal.util:map; + +/** + * @defgroup Map Map + * + */ +namespace hal::inline v6 { +/** + * @ingroup Map + * @brief Map an target value [x] from an input range [a,b] to an output range + * [c,d]. + * + * Another term for this is an affine transformation which follows this + * equation: + * + * ```text + * / d - c \ + * y = (x - a) * | --------| + c + * \ b - a / + * ``` + * + * For example: + * + * let x = 5.0 + * let input_range = [0.0, 10.0] + * let output_range = [100.0, 200.0] + * The result will be 150.0 + * + * @param p_target - target value within p_input_range to be mapped to the + * output range. + * @param p_input_range - the input range of p_target + * @param p_output_range - the output range to map p_target to + * @return constexpr float - value mapped from input range to the output + * range. The output is clamped to the output range. + */ +export [[nodiscard]] constexpr float map(float p_target, + std::pair p_input_range, + std::pair p_output_range) +{ + /** + * / d - c \ + * y = (x - a) * | --------| + c + * \ b - a / + */ + float const x = p_target; + auto const [a, b] = std::minmax(p_input_range.first, p_input_range.second); + auto const [c, d] = std::minmax(p_output_range.first, p_output_range.second); + + float const shift_input = x - a; + float const ratio = (d - c) / (b - a); + float const y = (shift_input * ratio) + c; + float const clamped_y = std::clamp(y, c, d); + + return clamped_y; +} +} // namespace hal diff --git a/v6/modules/math.cppm b/v6/modules/math.cppm new file mode 100644 index 0000000..b27d0de --- /dev/null +++ b/v6/modules/math.cppm @@ -0,0 +1,243 @@ +// Copyright 2024 - 2025 Khalil Estell and the libhal contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @file math.hpp + * @brief Compile time generic math utility functions + * @defgroup Math Math + */ +module; + +#include + +#include +#include +#include +#include +#include + +export module hal.util:math; + +import hal; + +namespace hal::inline v6 { +/** + * @ingroup Math + * @brief Perform multiply operation and return an error code + * `std::errc::result_out_of_range` if the two values when multiplied would + * overflow the containing value. + * + * @tparam T - integer arithmetic type + * @param p_lhs - left hand side integer + * @param p_rhs - right hand side integer + * @returns std::optional - the resultant, std::nullopt if the operation + * overflows. + */ +export template +[[nodiscard]] std::optional multiply(T p_lhs, T p_rhs) +{ + if (p_lhs == 0 || p_rhs == 0) { + return T{ 0 }; + } + + T const result = p_lhs * p_rhs; + + if (p_lhs != result / p_rhs) { + return std::nullopt; + } + + return result; +} + +/** + * @ingroup Math + * @brief Generic absolute value function that works for integer types. + * + * Preferred this over the C API for rounding numbers such as abs(), labs() and + * llabs(). This function relieves the need in template code to check the type + * of the integer and select the correct function to call. + * + * NOTE: If p_value is minimum negative number of type T then the resulting + * return value will be the maximum positive number represented by T. For + * example, INT32_MIN is 2147483648 where as INT32_MAX is 2147483647. The + * absolute value of INT32_MIN is 1 greater than INT32_MAX. To prevent overflow, + * passing INT32_MIN will simply return back INT32_MAX. + * + * @tparam T - integral type + * @param p_value - integer value to be made positive + * @return T - positive representation of the integer + */ +export template +[[nodiscard]] constexpr T absolute_value(T p_value) noexcept +{ + if constexpr (std::is_unsigned_v) { + return p_value; + } else { + if (p_value >= 0) { + return p_value; + } + if (p_value == std::numeric_limits::min()) { + return std::numeric_limits::max(); + } + return -p_value; + } +} + +export template +struct division +{ + T numerator; + T denominator; +}; + +/** + * @ingroup Math + * @brief Perform integer division and round the value up if the next decimal + * place is greater than or equal to 0.5. + * + * @tparam T - integral type of the two operands + * @param p_terms - numerator and denominator to be divided + * @return T - rounded quotient between numerator and denominator. + * Returns 0 if the denominator is greater than the numerator. + */ +export template +[[nodiscard]] constexpr T rounding_division(division p_terms) +{ + bool const num_sign = p_terms.numerator >= 0; + bool const den_sign = p_terms.denominator >= 0; + + auto const numerator = absolute_value(p_terms.numerator); + auto const denominator = absolute_value(p_terms.denominator); + + if (denominator > numerator || denominator == 0) { + return T{ 0 }; + } + + T const remainder = numerator % denominator; + T const half_denominator = (denominator / 2); + T quotient = numerator / denominator; + // Round if remainder is greater than half of the denominator + if (half_denominator != 0 && remainder >= half_denominator) { + quotient++; + } + + if (num_sign != den_sign) { + return -quotient; + } + + return quotient; +} + +/** + * @ingroup Math + * @brief Calculates the distance between two values (L1 Norm or Manhattan + * distance), the absolute value of their difference. + * + * @tparam T - integral type of the two values + * @param p_left - the first point of the distance calculation + * @param p_right - the second point of the distance calculation + * @returns constexpr T - absolute value of the difference between the two + * points. + */ +export template +[[nodiscard]] constexpr T distance(T p_left, T p_right) +{ + if (p_right > p_left) { + return p_right - p_left; + } else { + return p_left - p_right; + } +} + +// NOLINTBEGIN(bugprone-easily-swappable-parameters): +// +// Why this NOTLINT? +// +// I allow this for this API because both sides of the function can be +// safely swapped and the correct answer is provided. + +/** + * @ingroup Math + * @brief Calculates the distance between two values (L1 Norm or Manhattan + * distance), the absolute value of their difference. + * + * NOTE: Values cannot exceed int32_t. + * + * @tparam T - integral type of the two values + * @param p_left - the first point of the distance calculation + * @param p_right - the second point of the distance calculation + * @returns constexpr T - absolute value of the difference between the two + * points. + */ +export template +[[nodiscard]] constexpr std::make_unsigned_t distance(T p_left, T p_right) +{ + // NOLINTEND(bugprone-easily-swappable-parameters) + static_assert(sizeof(T) <= sizeof(int32_t)); + + using unsigned_t = std::make_unsigned_t; + // Put left and right values into 64-bit containers to prevent overflow + int64_t const left{ p_left }; + int64_t const right{ p_right }; + + if (right > left) { + // Subtraction operation on right to left in this order can never overflow + // because the maximum resultant of left and right being INT32_MAX and + // INT32_MIN, will equal UINT32_MAX which can be stored within an int64_t + // value. + int64_t const difference = right - left; + // Casting this value to the unsigned variant will always fit as the + // distance between any 32-bit signed numbers can always fit in a 32-bit + // unsigned number. + return static_cast(difference); + } else { + // Same logic as the if statement block above. + int64_t const difference = left - right; + return static_cast(difference); + } +} +/** + * @ingroup Math + * @brief Determines if two values are equal within a relative error. + * + * @param p_value1 - First value to compare. + * @param p_value2 - Second value to compare. + * @param p_epsilon - Error margin that the difference is compared to. + * @return true - difference is less than epsilon + * @return false - difference is more than epsilon + */ +export [[nodiscard]] constexpr bool equals(std::floating_point auto p_value1, + std::floating_point auto p_value2, + float p_epsilon = 1e-9f) +{ + if (p_value1 == p_value2) { + return true; + } + auto const value1_abs = std::abs(p_value1); + auto const value2_abs = std::abs(p_value2); + auto const diff = std::abs(p_value1 - p_value2); + auto const absolute_values_sum = value1_abs + value2_abs; + + using float_t = decltype(absolute_values_sum); + + if (p_value1 == 0 || p_value2 == 0 || + (absolute_values_sum < std::numeric_limits::min())) { + return diff < (p_epsilon * std::numeric_limits::min()); + } else { + auto const relative_error = + diff / std::min(absolute_values_sum, std::numeric_limits::max()); + return relative_error < p_epsilon; + } +} +} // namespace hal::inline v6 diff --git a/v6/modules/overflow_counter.cppm b/v6/modules/overflow_counter.cppm new file mode 100644 index 0000000..897e2d7 --- /dev/null +++ b/v6/modules/overflow_counter.cppm @@ -0,0 +1,102 @@ +// Copyright 2024 - 2025 Khalil Estell and the libhal contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +module; + +#include +#include + +export module hal.util:overflow_counter; + +import :bit; + +/** + * @defgroup OverflowCounter Overflow Counter + * + */ + +namespace hal::inline v6 { +/** + * @ingroup OverflowCounter + * @brief Extend a counter's count from an arbitrary bit width to 64-bits by + * detecting overflows in the count. Each detected overflow is added to an + * overflow counter which is combined with the current count in order create a + * count up to 64-bits in length. + * + * @tparam count_bit_width - the bit width of the counter before the count + * value overflows. + */ +export template +class overflow_counter +{ +public: + static_assert(count_bit_width <= 32, "Bit width cannot exceed 32-bits"); + static_assert(count_bit_width > 1, "Bit width must be greater than 1"); + + /** + * @ingroup OverflowCounter + * @brief update the overflow counter, detect if an overflow has occurred, + * and return the combined + * + * @param p_new_count - must be an increasing value and should only decrease + * when an overflow event occurs. + * @return constexpr uint64_t - 64-bit count combining the new count value + * and the overflow count value. + */ + constexpr uint64_t update(uint32_t p_new_count) + { + // Sanitize the new count value to make sure it does not exceed the + // designated bit width. Without this check when the count is combined + // with the overflow value the upper bits may effect the bits of the + // overflow count, getting an incorrect count value. + constexpr auto mask = generate_field_of_ones(); + p_new_count = p_new_count & mask; + + // Detect the overflow by checking if the new count is smaller than the + // previous count. If the count is always increasing, the only way for + // the new count to be smaller is if the count reached the end of its + // bit width and overflowed. + if (m_previous_count > p_new_count) { + m_overflow_count++; + } + + // Set previous count to the new count + m_previous_count = p_new_count; + + // Move overflow count up to the upper bits of the 64-bit number based on + // count_bit_width + uint64_t combined_count = m_overflow_count; + combined_count <<= count_bit_width; + // Add the p_new_count into the combined count to complete it. + combined_count |= p_new_count; + + return combined_count; + } + + /** + * @ingroup OverflowCounter + * @brief Reset the overflow count back to zero. + * + */ + constexpr void reset() + { + m_previous_count = 0; + m_overflow_count = 0; + } + +private: + uint32_t m_previous_count = 0; + uint32_t m_overflow_count = 0; +}; +} // namespace hal::inline v6 diff --git a/v6/modules/serial.cppm b/v6/modules/serial.cppm new file mode 100644 index 0000000..1b015f0 --- /dev/null +++ b/v6/modules/serial.cppm @@ -0,0 +1,106 @@ +// Copyright 2024 - 2025 Khalil Estell and the libhal contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +module; + +#include + +#include +#include +#include + +export module hal.util:serial; + +import hal; +import scatter_span; + +import :as_bytes; + +/** + * @defgroup Serial Serial + * + */ + +namespace hal::inline v6 { +/** + * @ingroup Serial + * @brief Write bytes to a serial port + * + * @param p_ctx - async context for coroutine suspension and resumption. + * @param p_serial - the serial port that will be written to + * @param p_data_out - the data to be written out the port + */ +export async::future write(async::context& p_ctx, + hal::serial& p_serial, + mem::scatter_span p_data_out) +{ + co_await p_serial.write(p_ctx, p_data_out); +} + +/** + * @ingroup Serial + * @brief Write std::string_view of const char to a serial port + * + * @param p_ctx - async context for coroutine suspension and resumption. + * @param p_serial - the serial port that will be written to + * @param p_data_out - chars to be written out the port + */ +export async::future write(async::context& p_ctx, + hal::serial& p_serial, + std::string_view p_data_out) +{ + co_await write(p_ctx, p_serial, { as_bytes(p_data_out) }); +} + +/** + * @ingroup Serial + * @brief Write formatted string data to serial buffer + * + * Uses snprintf internally and writes to a local statically allocated an array. + * This function will never dynamically allocate like how standard std::printf + * does. + * + * This function does NOT include the NULL character when transmitting the data + * over the serial port. + * + * @tparam buffer_size - Size of the buffer to allocate on the stack to store + * the formatted message. + * @tparam Parameters - printf arguments + * @param p_ctx - async context for coroutine suspension and resumption. + * @param p_serial - serial port to write data to + * @param p_format - printf style null terminated format string + * @param p_parameters - printf arguments + */ +export template +async::future print(async::context& p_ctx, + hal::serial& p_serial, + char const* p_format, + Parameters... p_parameters) +{ + static_assert(buffer_size > 2); + constexpr int unterminated_max_string_size = + static_cast(buffer_size) - 1; + + std::array buffer{}; + int length = + std::snprintf(buffer.data(), buffer.size(), p_format, p_parameters...); + + if (length > unterminated_max_string_size) { + // Print out what was able to be written to the buffer + length = unterminated_max_string_size; + } + + co_await write(p_ctx, p_serial, std::string_view(buffer.data(), length)); +} +} // namespace hal::inline v6 diff --git a/v6/modules/spi.cppm b/v6/modules/spi.cppm new file mode 100644 index 0000000..f8716a6 --- /dev/null +++ b/v6/modules/spi.cppm @@ -0,0 +1,154 @@ +// Copyright 2024 - 2025 Khalil Estell and the libhal contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +module; + +#include +#include +#include + +export module hal.util:spi; + +import hal; +import scatter_span; + +/** + * @defgroup SPI SPI + * + */ + +namespace hal::inline v6 { +/** + * @ingroup SPI + * @brief Write data to the spi bus, ignore data on the peripherals receive line + * + * This command is useful for spi operations where data, such as a command, + * must be sent to a device and the device does not respond with anything or the + * response is not necessary. + * + * @param p_ctx - async context for coroutine suspension and resumption. + * @param p_spi - spi driver + * @param p_data_out - data to be written to the spi bus + */ +export async::future write(async::context& p_ctx, + spi_channel& p_spi, + mem::scatter_span p_data_out) +{ + co_await p_spi.transfer(p_ctx, p_data_out); +} + +/** + * @ingroup SPI + * @brief Read data from the SPI bus. + * + * Filler bytes will be placed on the write/transmit line. + * + * @param p_ctx - async context for coroutine suspension and resumption. + * @param p_spi - spi driver + * @param p_data_in - buffer to receive bytes back from the spi bus + * @param p_filler - filler data placed on the bus in place of actual data. + */ +export async::future read( + async::context& p_ctx, + spi_channel& p_spi, + mem::scatter_span p_data_in, + hal::byte p_filler = spi_channel::default_filler) +{ + co_await p_spi.transfer(p_ctx, {}, p_data_in, p_filler); +} +/** + * @ingroup SPI + * @brief Read data from the SPI bus and return a std::array of bytes. + * + * Filler bytes will be placed on the write line. + * + * @tparam bytes_to_read - Number of bytes to read + * @param p_ctx - async context for coroutine suspension and resumption. + * @param p_spi - spi driver + * @param p_filler - filler data placed on the bus in place of actual write + * data. + * @return std::array - array containing bytes read + * from the spi bus + */ +export template +[[nodiscard]] async::future> read( + async::context& p_ctx, + spi_channel& p_spi, + hal::byte p_filler = spi_channel::default_filler) +{ + std::array buffer; + co_await read(p_ctx, p_spi, { buffer }, p_filler); + co_return buffer; +} + +/** + * @ingroup SPI + * @brief Write data to the SPI bus and ignore data sent from peripherals on the + * bus then read data from the SPI and fill the write line with filler bytes. + * + * This utility function that fits the use case of many spi devices where a spi + * transfer is not full duplex. In many spi devices, full duplex means that + * as data is being written to the spi bus, the peripheral device is sending + * data back on the receive line. In most cases, the device's communication + * protocol is simply: + * + * 1. Write data to the bus, ignore the receive line + * 2. Read from spi bus, filling the write line with filler + * + * @param p_ctx - async context for coroutine suspension and resumption. + * @param p_spi - spi driver + * @param p_data_out - bytes to write to the bus + * @param p_data_in - buffer to receive bytes back from the spi bus + * @param p_filler - filler data placed on the bus when the read operation + * begins. + */ +export async::future write_then_read( + async::context& p_ctx, + spi_channel& p_spi, + mem::scatter_span p_data_out, + mem::scatter_span p_data_in, + hal::byte p_filler = spi_channel::default_filler) +{ + co_await write(p_ctx, p_spi, p_data_out); + co_await read(p_ctx, p_spi, p_data_in, p_filler); +} + +/** + * @ingroup SPI + * @brief Write data to the SPI bus and ignore data sent from peripherals on the + * bus then read data from the SPI, fill the write line with filler bytes and + * return an array of bytes. + * + * See write_then_read() for details about this function. + * + * @tparam bytes_to_read - Number of bytes to read from the bus + * @param p_ctx - async context for coroutine suspension and resumption. + * @param p_spi - spi driver + * @param p_data_out - bytes to write to the bus + * @param p_filler - filler data placed on the bus when the read operation + * begins. + * @return std::array - array containing the bytes + * read from the spi bus. + */ +export template +[[nodiscard]] async::future> +write_then_read(async::context& p_ctx, + spi_channel& p_spi, + mem::scatter_span p_data_out, + hal::byte p_filler = spi_channel::default_filler) +{ + co_await write(p_ctx, p_spi, p_data_out); + co_return co_await read(p_ctx, p_spi, p_filler); +} +} // namespace hal::inline v6 diff --git a/v6/modules/steady_clock.cppm b/v6/modules/steady_clock.cppm new file mode 100644 index 0000000..565db16 --- /dev/null +++ b/v6/modules/steady_clock.cppm @@ -0,0 +1,73 @@ +// Copyright 2024 - 2025 Khalil Estell and the libhal contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +module; + +#include +#include + +export module hal.util:steady_clock; + +import hal; + +import :units; + +/** + * @defgroup SteadyClock Steady Clock + * + */ + +namespace hal::inline v6 { +/** + * @ingroup SteadyClock + * @brief Function to compute a future timestamp in ticks + * + * This function calculates a future timestamp based on the current uptime of a + * steady clock and a specified duration. + * + * @param p_steady_clock - the steady_clock used to calculate the future + * duration. Note that this future deadline will only work with this steady + * clock. + * @param p_duration The duration for which we need to compute a future + * timestamp. + * + * @return A 64-bit unsigned integer representing the future timestamp in steady + * clock ticks. The future timestamp is calculated as the sum of the current + * number of ticks of the clock and the number of ticks equivalent to the + * specified duration. If the duration corresponds to a ticks_required value + * less than or equal to 1, it will be set to 1 to ensure at least one tick is + * waited. + */ +export async::future future_deadline( + async::context& p_ctx, + hal::steady_clock& p_steady_clock, + hal::time_duration p_duration) +{ + + using period = decltype(p_duration)::period; + auto const frequency = co_await p_steady_clock.frequency(p_ctx); + auto const tick_period = wavelength(frequency); + auto ticks_required = p_duration.count() / tick_period.count(); + using unsigned_ticks = std::make_unsigned_t; + + if (ticks_required <= 1) { + ticks_required = 1; + } + + auto const ticks = static_cast(ticks_required); + auto const future_timestamp = ticks + co_await p_steady_clock.uptime(p_ctx); + + co_return future_timestamp; +} +} // namespace hal::inline v6 diff --git a/v6/modules/to_array.cppm b/v6/modules/to_array.cppm new file mode 100644 index 0000000..1e908f3 --- /dev/null +++ b/v6/modules/to_array.cppm @@ -0,0 +1,51 @@ +// Copyright 2024 - 2025 Khalil Estell and the libhal contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +module; + +#include +#include +#include +#include + +export module hal.util:to_array; + +/** + * @defgroup ToArray To Array + * + */ +export namespace hal::inline v6 { +/** + * @ingroup ToArray + * @brief Convert a string_view into a std::array of N number of characters. + * + * Will always ensure that the array is null terminated + * + * @tparam N - Size of the array + * @param p_view - string to be placed into a char array + * @return constexpr std::array - the char array object + */ +template +[[nodiscard]] constexpr std::array to_array( + std::string_view p_view) +{ + size_t const min = std::min(N, p_view.size()); + std::array result; + + auto out_iterator = std::copy_n(p_view.begin(), min, result.begin()); + std::fill(out_iterator, result.end(), '\0'); + + return result; +} +} // namespace hal::inline v6 diff --git a/v6/modules/units.cppm b/v6/modules/units.cppm new file mode 100644 index 0000000..dff04f0 --- /dev/null +++ b/v6/modules/units.cppm @@ -0,0 +1,181 @@ +// Copyright 2024 - 2025 Khalil Estell and the libhal contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +module; + +#include +#include +#include +#include + +export module hal.util:units; + +import hal; +import :math; + +/** + * @defgroup UnitsUtils Units Utils + * + */ + +namespace hal::inline v6 { +/** + * @ingroup UnitsUtils + * @brief Calculate the number of cycles of this frequency within the time + * duration. This function is meant for timers to determine how many count + * cycles are needed to reach a particular time duration at this frequency. + * + * @param p_source - source frequency + * @param p_duration - the amount of time to convert to cycles + * @return hal::u32 - number of cycles + */ +export [[nodiscard]] constexpr hal::u64 cycles_per( + hertz p_source, + hal::time_duration p_duration) +{ + // Full Equation: + // / ratio_num \_ + // frequency_hz * |period| * | ----------- | = cycles + // \ ratio_den / + // + // hal::time_duration::period::num == 1 + // hal::time_duration::period::den == 1,000,000 + + auto const denominator = + static_cast(decltype(p_duration)::period::den); + auto const float_count = static_cast(p_duration.count()); + auto const source_hz = + static_cast(p_source.numerical_value_in(hertz::unit)); + auto const cycle_count = (float_count * source_hz) / denominator; + + return static_cast(cycle_count); +} + +/** + * @ingroup UnitsUtils + * @brief Calculates and returns the wavelength in seconds. + * + * @tparam Period - desired period (defaults to std::femto for femtoseconds). + * @param p_source - source frequency to convert to wavelength + * @return std::chrono::duration - time based wavelength of + * the frequency. + */ +export template +constexpr auto wavelength(hertz p_source) +{ + using rep = hal::u64; + using duration = std::chrono::duration; + + static_assert(Period::num == 1, "The period ratio numerator must be 1"); + static_assert(Period::den >= 1, + "The period ratio denominator must be 1 or greater than 1."); + + auto const source_hz = + static_cast(p_source.numerical_value_in(hertz::unit)); + constexpr auto denominator = static_cast(Period::den); + auto period = (1.0f / source_hz) * denominator; + + if (std::isinf(period)) { + return duration(std::numeric_limits::max()); + } + + return duration(static_cast(period)); +} + +/** + * @ingroup UnitsUtils + * @brief Calculates and returns the wavelength in seconds as a float. + * + * @tparam float_t - float type + * @tparam Period - desired period + * @param p_source - source frequency to convert to wavelength + * @return constexpr float - float representation of the time based wavelength + * of the frequency. + */ +export constexpr float wavelength(hertz p_source) +{ + auto const source_hz = + static_cast(p_source.numerical_value_in(hertz::unit)); + if (equals(source_hz, 0.0f)) { + return 0.0f; + } + auto duration = (1.0f / source_hz); + return float(duration); +} + +/** + * @ingroup UnitsUtils + * @brief Calculate the amount of time it takes a frequency to oscillate a + * number of cycles. + * + * @param p_source - the frequency to compute the cycles from + * @param p_cycles - number of cycles within the time duration + * @return std::optional - time duration based on this + * frequency and the number of cycles. Will return std::nullopt if the duration + * exceeds + */ +export [[nodiscard]] inline std::optional +duration_from_cycles(hertz p_source, uint32_t p_cycles) +{ + // Full Equation (based on the equation in cycles_per()): + // + // + // / cycles * ratio_den \_ + // |period| = | ---------------------------| + // \ frequency_hz * ratio_num / + // + constexpr auto ratio_den = + static_cast(hal::time_duration::period::den); + constexpr auto ratio_num = hal::time_duration::period::num; + constexpr auto int_min = std::numeric_limits::min(); + constexpr auto int_max = std::numeric_limits::max(); + constexpr auto float_int_min = static_cast(int_min); + constexpr auto float_int_max = static_cast(int_max); + + auto const source_hz = + static_cast(p_source.numerical_value_in(hertz::unit)); + auto const source = std::abs(source_hz); + auto const float_cycles = static_cast(p_cycles); + auto const nanoseconds = (float_cycles * ratio_den) / (source * ratio_num); + + if (float_int_min <= nanoseconds && nanoseconds <= float_int_max) { + return hal::time_duration( + static_cast(nanoseconds)); + } + + return std::nullopt; +} + +/** + * @ingroup UnitsUtils + * @brief print byte type using ostreams + * + * Meant for unit testing, testing and simulation purposes + * C++ streams, in general, should not be used for any embedded project that + * will ever have to be used on an MCU due to its memory cost. + * + * @tparam CharT - character type + * @tparam Traits - ostream traits type + * @param p_ostream - the ostream + * @param p_byte - object to convert to a string + * @return std::basic_ostream& - reference to the ostream + */ +export template +inline std::basic_ostream& operator<<( + std::basic_ostream& p_ostream, + hal::byte const& p_byte) +{ + return p_ostream << std::hex << "0x" << unsigned(p_byte); +} +} // namespace hal::inline v6 diff --git a/v6/src/README.md b/v6/src/README.md new file mode 100644 index 0000000..ea4fa2a --- /dev/null +++ b/v6/src/README.md @@ -0,0 +1 @@ +# Add private .cpp source files here diff --git a/v6/test_package/CMakeLists.txt b/v6/test_package/CMakeLists.txt new file mode 100644 index 0000000..5173449 --- /dev/null +++ b/v6/test_package/CMakeLists.txt @@ -0,0 +1,47 @@ +# Copyright 2026 Madeline Schneider and the libhal contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cmake_minimum_required(VERSION 4.0) + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +set(CMAKE_COLOR_DIAGNOSTICS ON) + +project(test_package LANGUAGES CXX) + +if(NOT CMAKE_GENERATOR MATCHES "Ninja|Visual Studio") + message(FATAL_ERROR "C++20 modules require Ninja or Visual Studio generator") +endif() + +find_package(libhal-util REQUIRED CONFIG) + +if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|AppleClang") + string(APPEND CMAKE_CXX_FLAGS " -Qunused-arguments") +endif() + +add_executable(${PROJECT_NAME} main.cpp) +target_include_directories(${PROJECT_NAME} PUBLIC .) +target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_23) +target_compile_options(${PROJECT_NAME} PRIVATE -fno-rtti) +target_link_libraries(${PROJECT_NAME} PRIVATE libhal::util) + +if(NOT CMAKE_CROSSCOMPILING) + target_compile_options(${PROJECT_NAME} PRIVATE -fsanitize=address) + target_link_options(${PROJECT_NAME} PRIVATE -g -fsanitize=address) +endif() + +add_custom_target(copy_compile_commands ALL + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_BINARY_DIR}/compile_commands.json + ${CMAKE_SOURCE_DIR}/compile_commands.json + DEPENDS ${CMAKE_BINARY_DIR}/compile_commands.json) diff --git a/v6/test_package/conanfile.py b/v6/test_package/conanfile.py new file mode 100644 index 0000000..a91446c --- /dev/null +++ b/v6/test_package/conanfile.py @@ -0,0 +1,53 @@ +#!/usr/bin/python +# +# Copyright 2026 Madeline Schneider and the libhal contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from conan import ConanFile +from conan.tools.build import cross_building +from conan.tools.cmake import CMake, cmake_layout, CMakeToolchain, CMakeDeps +from pathlib import Path + + +class TestPackageConan(ConanFile): + settings = "os", "arch", "compiler", "build_type" + generators = "VirtualRunEnv" + + def build_requirements(self): + self.tool_requires("cmake/[^4.0.0]") + self.tool_requires("ninja/[^1.13.1]") + + def requirements(self): + self.requires(self.tested_reference_str) + + def layout(self): + cmake_layout(self) + + def generate(self): + tc = CMakeToolchain(self) + tc.generator = "Ninja" + tc.generate() + + deps = CMakeDeps(self) + deps.generate() + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() + + def test(self): + if not cross_building(self): + bin_path = Path(self.cpp.build.bindirs[0]) / "test_package" + self.run(bin_path.absolute(), env="conanrun") diff --git a/v6/test_package/main.cpp b/v6/test_package/main.cpp new file mode 100644 index 0000000..0acb03b --- /dev/null +++ b/v6/test_package/main.cpp @@ -0,0 +1,145 @@ +// Copyright 2024 - 2025 Khalil Estell and the libhal contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include + +import hal; +import hal.util; + +namespace { +template +void finish(async::future& p_future) +{ + while (not p_future.done()) { + p_future.resume(); + } +} + +class stub_spi_channel : public hal::spi_channel +{ +public: + ~stub_spi_channel() override = default; + + mem::scatter_span m_last_out{}; + mem::scatter_span m_last_in{}; + +private: + async::future driver_configure(async::context&, + settings const&) override + { + return {}; + } + + async::future driver_clock_rate(async::context&) override + { + co_return 1'000'000 * mp_units::si::unit_symbols::Hz; + } + + async::future driver_chip_select(async::context&, bool) override + { + return {}; + } + + async::future driver_transfer( + async::context&, + mem::scatter_span p_data_out, + mem::scatter_span p_data_in, + hal::byte p_filler) override + { + m_last_out = p_data_out; + m_last_in = p_data_in; + for (auto chunk : p_data_in) { + for (auto& element : chunk) { + element = p_filler; + } + } + return {}; + } +}; + +class stub_i2c : public hal::i2c +{ +public: + ~stub_i2c() override = default; + + hal::byte m_last_address{}; + +private: + async::future driver_configure(async::context&, + settings const&) override + { + return {}; + } + + async::future driver_transaction( + async::context&, + hal::byte p_address, + mem::scatter_span, + mem::scatter_span p_data_in) override + { + m_last_address = p_address; + for (auto chunk : p_data_in) { + for (auto& element : chunk) { + element = hal::byte{ 0xA5 }; + } + } + return {}; + } +}; +} // namespace + +int main() +{ + using namespace std::string_view_literals; + + auto array = hal::to_array<5>("Hello World!\n"sv); + std::println("{}", array); + + async::inplace_context<1024> ctx; + + // SPI demo + stub_spi_channel spi; + std::array const spi_command{ 0x9F, 0x00, 0x00 }; + mem::scatter_array spi_payload{ spi_command }; + + auto spi_write = hal::write(ctx, spi, spi_payload); + finish(spi_write); + + auto spi_id = hal::write_then_read<3>(ctx, spi, spi_payload); + finish(spi_id); + auto spi_id_bytes = spi_id.value(); + std::println("spi write_then_read<3>: {:#x} {:#x} {:#x}", + spi_id_bytes[0], + spi_id_bytes[1], + spi_id_bytes[2]); + + // I2C demo + stub_i2c i2c; + constexpr hal::byte device_address{ 0x50 }; + + auto probe_future = hal::probe(ctx, i2c, device_address); + finish(probe_future); + std::println("i2c device found: {}", probe_future.value()); + + auto register_read = hal::read<2>(ctx, i2c, device_address); + finish(register_read); + auto register_bytes = register_read.value(); + std::println( + "i2c read<2>: {:#x} {:#x}", register_bytes[0], register_bytes[1]); + + return 0; +} diff --git a/v6/tests/as_bytes.test.cpp b/v6/tests/as_bytes.test.cpp new file mode 100644 index 0000000..7fb1556 --- /dev/null +++ b/v6/tests/as_bytes.test.cpp @@ -0,0 +1,144 @@ +// Copyright 2024 - 2025 Khalil Estell and the libhal contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include + +import hal; +import hal.util; + +namespace { + +void as_bytes_test() +{ + using namespace boost::ut; + + "hal::as_bytes()"_test = []() { + "Zero"_test = []() { + // Setup + std::span expected_const{}; + std::span expected_writable{}; + std::vector empty_vector; + + { + // Exercise + auto actual = hal::as_bytes(std::span{}); + // Verify + expect(that % expected_const.data() == actual.data()); + expect(that % expected_const.size() == actual.size()); + } + { + // Exercise + auto actual = hal::as_bytes(empty_vector); + // Verify + expect(that % expected_const.data() == actual.data()); + expect(that % expected_const.size() == actual.size()); + } + { + // Exercise + auto actual = hal::as_writable_bytes(expected_writable); + // Verify + expect(that % expected_writable.data() == actual.data()); + expect(that % expected_writable.size() == actual.size()); + } + }; + + "One"_test = []() { + // Setup + std::array array{ 1234 }; + std::vector vector; + vector.push_back(1); + + auto* array_pointer = reinterpret_cast(array.data()); + auto* vector_pointer = reinterpret_cast(vector.data()); + + auto vector_byte_size = + sizeof(decltype(vector)::value_type) * vector.size(); + auto array_byte_size = sizeof(decltype(array)::value_type) * array.size(); + + { + // Exercise + auto actual = hal::as_bytes(array); + // Verify + expect(that % array_pointer == actual.data()); + expect(that % array_byte_size == actual.size()); + } + { + // Exercise + auto actual = hal::as_bytes(vector); + // Verify + expect(that % vector_pointer == actual.data()); + expect(that % vector_byte_size == actual.size()); + } + { + // Exercise + auto actual = hal::as_writable_bytes(array); + // Verify + expect(that % array_pointer == actual.data()); + expect(that % array_byte_size == actual.size()); + } + }; + + "Standard Usage"_test = []() { + // Setup + std::array array{ 1234 }; + std::vector vector; + vector.push_back(1); + vector.push_back(2); + vector.push_back(3); + vector.push_back(4); + vector.push_back(10293); + + auto* array_pointer = reinterpret_cast(array.data()); + auto* vector_pointer = reinterpret_cast(vector.data()); + + constexpr auto array_element_size = sizeof(decltype(array)::value_type); + constexpr auto array_byte_size = array_element_size * array.size(); + + constexpr auto vector_element_size = sizeof(decltype(vector)::value_type); + auto const vector_byte_size = vector_element_size * vector.size(); + + { + // Exercise + auto actual = hal::as_bytes(array); + // Verify + expect(that % array_pointer == actual.data()); + expect(that % array_byte_size == actual.size()); + } + { + // Exercise + auto actual = hal::as_bytes(vector); + // Verify + expect(that % vector_pointer == actual.data()); + expect(that % vector_byte_size == actual.size()); + } + { + // Exercise + auto actual = hal::as_writable_bytes(array); + // Verify + expect(that % array_pointer == actual.data()); + expect(that % array_byte_size == actual.size()); + } + }; + }; +} + +} // namespace + +int main() +{ + as_bytes_test(); +} diff --git a/v6/tests/bit.test.cpp b/v6/tests/bit.test.cpp new file mode 100644 index 0000000..9155044 --- /dev/null +++ b/v6/tests/bit.test.cpp @@ -0,0 +1,117 @@ +// Copyright 2024 - 2025 Khalil Estell and the libhal contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include + +import hal; +import hal.util; + +namespace { + +void bit_modify_compile_time_test() +{ + using namespace boost::ut; + + "hal::bit_modify