From eef6f5016e06169b4e2d413c7316e31fe8cf8c71 Mon Sep 17 00:00:00 2001 From: Khalil Estell Date: Sun, 12 May 2024 15:56:59 -0700 Subject: [PATCH] :boom: Upgrade to libhal/4.0.0 --- .github/workflows/5.0.0.yml | 11 ++ conanfile.py | 2 +- include/libhal-util/can.hpp | 194 ++++++++++++++++++++++++++---------- tests/can.test.cpp | 126 ++++++++++++++++++++++- 4 files changed, 276 insertions(+), 57 deletions(-) create mode 100644 .github/workflows/5.0.0.yml diff --git a/.github/workflows/5.0.0.yml b/.github/workflows/5.0.0.yml new file mode 100644 index 0000000..9b134c1 --- /dev/null +++ b/.github/workflows/5.0.0.yml @@ -0,0 +1,11 @@ +name: 🚀 5.0.0 + +on: + workflow_dispatch: + +jobs: + deploy: + uses: libhal/ci/.github/workflows/deploy_all.yml@5.x.y + with: + version: 5.0.0 + secrets: inherit diff --git a/conanfile.py b/conanfile.py index ad0ad7d..f5d9a2c 100644 --- a/conanfile.py +++ b/conanfile.py @@ -62,7 +62,7 @@ def build_requirements(self): self.test_requires("boost-ext-ut/1.1.9") def requirements(self): - self.requires("libhal/[^3.0.0]", transitive_headers=True) + self.requires("libhal/[^4.0.0]", transitive_headers=True) def layout(self): cmake_layout(self) diff --git a/include/libhal-util/can.hpp b/include/libhal-util/can.hpp index 59a740b..71a865e 100644 --- a/include/libhal-util/can.hpp +++ b/include/libhal-util/can.hpp @@ -15,10 +15,11 @@ #pragma once #include +#include +#include #include -#include "comparison.hpp" #include "math.hpp" /** @@ -29,23 +30,109 @@ namespace hal { /** * @ingroup CAN_Utilities - * @brief Compares two CAN bus states. + * @brief Compares two CAN bus settings. * - * @param p_lhs A CAN bus. - * @param p_rhs A CAN bus. + * @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. */ [[nodiscard]] constexpr auto operator==(const can::settings& p_lhs, const can::settings& p_rhs) { - return equals(p_lhs.baud_rate, p_rhs.baud_rate) && + return equals(p_lhs.baud_rate, p_rhs.baud_rate); +} + +/** + * @brief Generic settings for a can peripheral + * @ingroup CAN_Utilities + * + * CAN Bit Quanta Timing Diagram of: + * + * | <--- sjw ---> | + * ____ ______ __________ __________ + * _/ SYNC \/ PROP \/ PHASE1 \/ PHASE2 \_ + * \______/\________/\____________/\____________/ + * ^ Sample point + */ +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. + */ +[[nodiscard]] constexpr auto operator==(const can_bus_divider_t& p_lhs, + const can_bus_divider_t& 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; } -[[nodiscard]] constexpr std::uint16_t bit_width(const can::settings& p_settings) +[[nodiscard]] constexpr std::uint16_t bit_width( + const can_bus_divider_t& p_settings) { // The sum of 4x 8-bit numbers can never exceed uint16_t and thus this // operation is always safe. @@ -55,70 +142,67 @@ namespace hal { } /** + * @brief Calculates the can bus divider values * @ingroup CAN_Utilities - * @brief Validate configuration settings against an operating frequency - * - * The settings and frequency must follow the following rules: * - * 1. propagation_delay, phase_segment1, phase_segment2 and - * synchronization_jump_width must be nonzero. - * 2. synchronization_jump_width must be the lesser between phase_segment1 - * and phase_segment2. - * 3. The total bit width must be equal to or greater than 8 Tq/bit; the sum - * of sync_segment, propagation_delay, phase_segment1 and phase_segment2. - * 4. The CAN device's operating frequency must be at least 8 times the baud - * rate to give the minimum. - * 5. The ratio between the CAN device's operating frequency and the bit - * width must be close enough to an integer to produce a usable baud - * rate prescaler. + * 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_settings - settings object to check - * @param p_operating_frequency - CAN device operating frequency - * @return std::optional - baud rate prescaler + * @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. */ -[[nodiscard]] constexpr std::optional is_valid( - const can::settings& p_settings, - hertz p_operating_frequency) +[[nodiscard]] inline std::optional calculate_can_bus_divider( + hertz p_operating_frequency, + hertz p_target_baud_rate) { - // 1. propagation_delay, phase_segment1, phase_segment2 and - // synchronization_jump_width must be nonzero. - if (p_settings.propagation_delay == 0 || p_settings.phase_segment1 == 0 || - p_settings.phase_segment2 == 0 || - p_settings.synchronization_jump_width == 0) { - return std::nullopt; - } + can_bus_divider_t timing; - // 2. synchronization_jump_width must be the lesser between phase_segment1 - // and phase_segment2. - if (p_settings.synchronization_jump_width > 4 || - p_settings.synchronization_jump_width > p_settings.phase_segment1 || - p_settings.synchronization_jump_width > p_settings.phase_segment2) { - return std::nullopt; - } + // Set phase segments and propagation delay to balanced values + timing.propagation_delay = 1; - const std::uint16_t bit_width_v = bit_width(p_settings); + // use a value of zero in total tq to know if no tq and divider combo worked + timing.total_tq = 0; - // 3. The total bit width must be equal to or greater than 8 Tq/bit; the - // sum of sync_segment, propagation_delay, phase_segment1 and - // phase_segment2. - if (bit_width_v < 8) { + if (p_operating_frequency < 0.0f || p_target_baud_rate < 0.0f || + p_operating_frequency <= p_target_baud_rate) { return std::nullopt; } - // 4. The CAN device's operating frequency must be at least 8 times the - // bit rate to give the minimum. - // 5. The ratio between the CAN device's operating frequency and the bit - // width must be close enough to an integer to produce a usable BRP. - const float bit_width_float = bit_width_v; - const float scaled_baud = p_settings.baud_rate * bit_width_float; - const float baud_rate_prescaler = p_operating_frequency / scaled_baud; - const auto final_prescaler = std::lround(baud_rate_prescaler); + std::int32_t operating_frequency = p_operating_frequency; + std::int32_t desired_baud_rate = p_target_baud_rate; - if (final_prescaler == 0) { + std::div_t division{}; + + for (std::uint32_t 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; } - return final_prescaler; + timing.clock_divider = 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; } /** diff --git a/tests/can.test.cpp b/tests/can.test.cpp index d7ddce8..9dff53e 100644 --- a/tests/can.test.cpp +++ b/tests/can.test.cpp @@ -1,7 +1,75 @@ #include +#include + #include +namespace hal { + +template +std::basic_ostream& operator<<( + std::basic_ostream& p_ostream, + const hal::can_bus_divider_t& p_settings) +{ + p_ostream << "{ clock_divider: " << int(p_settings.clock_divider); + p_ostream << ", sync: " << int(p_settings.sync_segment); + p_ostream << ", propagation_delay: " << int(p_settings.propagation_delay); + p_ostream << ", phase_segment1: " << int(p_settings.phase_segment1); + p_ostream << ", phase_segment2: " << int(p_settings.phase_segment2); + p_ostream << ", sjw: " << int(p_settings.synchronization_jump_width); + p_ostream << ", total_tq: " << int(p_settings.total_tq); + p_ostream << " }"; + return p_ostream; +} + +template +std::basic_ostream& operator<<( + std::basic_ostream& p_ostream, + const std::optional& p_settings) +{ + if (p_settings) { + p_ostream << p_settings.value(); + } else { + p_ostream << "std::nullopt { hal::can_bus_divider_t }"; + } + return p_ostream; +} +} // namespace hal + +namespace { +void check_validity(hal::hertz p_operating_frequency, + hal::hertz p_target_baud_rate) +{ + using namespace boost::ut; + auto call_result = + hal::calculate_can_bus_divider(p_operating_frequency, p_target_baud_rate); + + expect(that % call_result.has_value()) + << "Frequency '" << p_operating_frequency << "' and baud rate '" + << p_target_baud_rate << "'\n"; + + auto test_subject = call_result.value(); + + const auto bit_width = hal::bit_width(test_subject); + + auto calculated_baud_rate = + p_operating_frequency / + (test_subject.clock_divider * + (test_subject.sync_segment + test_subject.propagation_delay + + test_subject.phase_segment1 + test_subject.phase_segment2)); + + expect(that % 8 <= bit_width && bit_width <= 25) + << "Bit width is beyond the bounds of 8 and 25"; + expect(that % 8 <= test_subject.total_tq && test_subject.total_tq <= 25) + << "Total tq is beyond the bounds of 8 and 25"; + expect(that % bit_width == test_subject.total_tq) + << "bit_width and total_tq do not match"; + expect(that % static_cast(calculated_baud_rate) == + static_cast(p_target_baud_rate)) + << "Failure to get the expected baud rate with " << test_subject; +} +} // namespace + namespace hal { void can_test() { @@ -33,5 +101,61 @@ void can_test() expect(a != c); expect(b != c); }; + + "calculate_can_bus_divider(operation_frequency, desired_baud_rate) "_test = + []() { + check_validity(8.0_MHz, 100.0_kHz); + check_validity(8.0_MHz, 250.0_kHz); + check_validity(8.0_MHz, 500.0_kHz); + check_validity(8.0_MHz, 1000.0_kHz); + + check_validity(16.0_MHz, 100.0_kHz); + check_validity(16.0_MHz, 250.0_kHz); + check_validity(16.0_MHz, 500.0_kHz); + check_validity(16.0_MHz, 1000.0_kHz); + + check_validity(16.0_MHz, 100.0_kHz); + check_validity(16.0_MHz, 250.0_kHz); + check_validity(16.0_MHz, 500.0_kHz); + check_validity(16.0_MHz, 1000.0_kHz); + + check_validity(46.0_MHz, 100.0_kHz); + check_validity(46.0_MHz, 250.0_kHz); + check_validity(46.0_MHz, 500.0_kHz); + check_validity(46.0_MHz, 1000.0_kHz); + + check_validity(64.0_MHz, 100.0_kHz); + check_validity(64.0_MHz, 250.0_kHz); + check_validity(64.0_MHz, 500.0_kHz); + check_validity(64.0_MHz, 1000.0_kHz); + + check_validity(96.0_MHz, 100.0_kHz); + check_validity(96.0_MHz, 250.0_kHz); + check_validity(96.0_MHz, 500.0_kHz); + check_validity(96.0_MHz, 1000.0_kHz); + + check_validity(120.0_MHz, 100.0_kHz); + check_validity(120.0_MHz, 250.0_kHz); + check_validity(120.0_MHz, 500.0_kHz); + check_validity(120.0_MHz, 1000.0_kHz); + + // Failure + auto fail0 = calculate_can_bus_divider(500.0_kHz, 100.0_kHz); + auto fail1 = calculate_can_bus_divider(500.0_kHz, 250.0_kHz); + auto fail2 = calculate_can_bus_divider(500.0_kHz, 500.0_kHz); + auto fail3 = calculate_can_bus_divider(500.0_kHz, 1000.0_kHz); + auto fail4 = calculate_can_bus_divider(8.0_MHz, 1250.0_kHz); + + std::cout << "fail0" << fail0 << "\n"; + std::cout << "fail1" << fail1 << "\n"; + std::cout << "fail2" << fail2 << "\n"; + std::cout << "fail3" << fail3 << "\n"; + std::cout << "fail4" << fail4 << "\n"; + + expect(that % not fail0.has_value()); + expect(that % not fail1.has_value()); + expect(that % not fail2.has_value()); + expect(that % not fail4.has_value()); + }; } -} // namespace hal \ No newline at end of file +} // namespace hal