diff --git a/conanfile.py b/conanfile.py index f5d9a2c..1165cf0 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/[^4.0.0]", transitive_headers=True) + self.requires("libhal/[^4.4.0]", transitive_headers=True) def layout(self): cmake_layout(self) diff --git a/include/libhal-util/atomic_spin_lock.hpp b/include/libhal-util/atomic_spin_lock.hpp new file mode 100644 index 0000000..1472288 --- /dev/null +++ b/include/libhal-util/atomic_spin_lock.hpp @@ -0,0 +1,89 @@ +// Copyright 2024 Khalil Estell +// +// 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. + +#pragma once + +#include + +#include +#include + +namespace hal::soft { +/** + * @brief Atomic spin lock that implements hal::pollable_lock + * + * This lock provides an operating system agnostic lock that works on any + * processor that support lock-free atomic boolean operations, which should be + * almost all systems. + * + * This lock will perform a spin lock until it acquires a lock. Using such a + * lock on a properly multithreaded system is inefficient as it does not have + * the capability to notify the system that a thread is currently waiting for + * the lock to be made available. + * + * This lock is useful as a default lock for platform libraries, in order to + * ensure thread safety. Such platforms should provide an API for swapping out + * the atomic spin lock with an appropriate lock provided by the users operating + * system. + */ +class atomic_spin_lock : public hal::pollable_lock +{ +public: + /** + * @brief Construct a new atomic spin lock object + * + */ + atomic_spin_lock() = default; + ~atomic_spin_lock() = default; + +private: + void os_lock() override; + void os_unlock() override; + bool os_try_lock() override; + + std::atomic_flag m_flag = ATOMIC_FLAG_INIT; +}; + +/** + * @brief Same as hal::atomic_spin_lock but supports timed_lock apis + * + * All of the dubious usages of hal::atomic_spin_lock follow with this lock as + * well. In general, do not use this in production. Use the appropriate thread + * safe lock for your operating system. + */ +class timed_atomic_spin_lock : public hal::timed_lock +{ +public: + /** + * @brief Construct a new timed atomic spin lock object + * + * @param p_steady_clock - steady clock used to time the try_lock_for api. + */ + timed_atomic_spin_lock(hal::steady_clock& p_steady_clock) + : m_steady_clock(&p_steady_clock) + { + } + + ~timed_atomic_spin_lock() = default; + +private: + void os_lock() override; + void os_unlock() override; + bool os_try_lock() override; + bool os_try_lock_for(hal::time_duration p_poll_time) override; + + hal::steady_clock* m_steady_clock; + atomic_spin_lock m_atomic_spin_lock; +}; +} // namespace hal::soft diff --git a/include/libhal-util/bit_bang_i2c.hpp b/include/libhal-util/bit_bang_i2c.hpp new file mode 100644 index 0000000..63c02d8 --- /dev/null +++ b/include/libhal-util/bit_bang_i2c.hpp @@ -0,0 +1,201 @@ +// Copyright 2024 Khalil Estell +// +// 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. + +#pragma once + +#include +#include +#include +#include + +namespace hal { +/** + * @brief A bit bang implementation for i2c. + * + * This implementation of i2c only needs 2 hal::output_pins and a steady_clock + * to work. It does not support multi-controller but we intend to support it in + * the future. the data transfer rate for bit-bang is a best-effort + * implementation meaning it will almost always run at a frequency which is less + * then the request one but, never faster. The maximum achievable clock rate for + * the lpc4078 is about 180kHz. Interrupts disrupt this controller because the + * transfer is a blocking operation which means an interrupt may come in the + * middle of a transaction and may leave a transaction hanging which some + * peripherals may not support. + */ +class bit_bang_i2c : public i2c +{ +public: + struct pins + { + output_pin* sda; + output_pin* scl; + }; + + /** + * @brief Construct a new i2c bit bang object + * + * @param p_pins named structure that contains pointers to the scl and sda to + * be used inside of the driver + * @param p_steady_clock the steady clock that will be used for timing the sda + * and scl lines. This should have a higher frequency then the i2c frequency + * that this device will be configured to. + * @param p_duty_cycle the clock duty cycle. Valid inputs are between 0.3 to + * 0.7. Outside of this range and the bit-bang i2c may lock up due to the + * shortness of the pulse duration. + * @param p_settings the initial settings of the i2c bus + * + * @throws hal::operation_not_supported if p_duty_cycle is below 0.3f or above + * 0.7f. + * @throws hal::operation_not_supported via driver_configure which may throw + * an exception if the operating speed is higher than the steady clock's + * frequency. + */ + bit_bang_i2c(pins const& p_pins, + steady_clock& p_steady_clock, + float const p_duty_cycle = 0.5f, + hal::i2c::settings const& p_settings = {}); + +private: + void driver_configure(settings const& p_settings) override; + + virtual void driver_transaction( + hal::byte p_address, + std::span p_data_out, + std::span p_data_in, + function_ref p_timeout) override; + + /** + * @brief This function will send the start condition it will also pull the + * pins high before sending the start condition + */ + void send_start_condition(); + + /** + * @brief This function will send the stop condition while also making sure + * the sda pin is pulled low before sending the stop condition + */ + void send_stop_condition(); + + /** + * @brief This function will go through the steps of writing the address + * of the peripheral the controller wishes to speak to while also ensuring the + * data written is acknowledged + * + * @param p_address The address of the peripheral, configured with the + * read/write bit already that the controller is requesting to talk to + * @param p_timeout A timeout function which is primarily used for clock + * stretching to ensure the peripheral doesn't hold the line too long + * + * @throws hal::no_such_device when the address written to the bus was not + * acknowledged + */ + void write_address(hal::byte p_address, + function_ref p_timeout); + + /** + * @brief This function will write the entire contents of the span out to the + * bus while making sure all data gets acknowledged + * + * @param p_data_out This is a span of bytes which will be written to the bus + * @param p_timeout A timeout function which is primarily used for clock + * stretching to ensure the peripheral doesn't hold the line too long + * + * @throws hal::io_error when the data written to the bus was not + * acknowledged + */ + void write(std::span p_data_out, + function_ref p_timeout); + + /** + * @brief This function will handle writing a singular byte each call while + * also retrieving the acknowledge bits + * + * @param p_byte_to_write This is the byte that will be written to the bus + * @param p_timeout A timeout function which is primarily used for clock + * stretching to ensure the peripheral doesn't hold the line too long + * + * @return bool true when the byte written was ack'd and false when it was + * nack'd + */ + bool write_byte(hal::byte p_byte_to_write, + function_ref p_timeout); + + /** + * @brief This function will write a single bit at a time, dealing with + * simulating the clock and the clock stretching feature + * + * @param p_bit_to_write The bit which will be written on the bus + * @param p_timeout A timeout function which is primarily used for clock + * stretching to ensure the peripheral doesn't hold the line too long + */ + void write_bit(hal::byte p_bit_to_write, + function_ref p_timeout); + + /** + * @brief This function will read in as many bytes as allocated inside of the + * span while also ACKing or NACKing the data + * + * @param p_data_in A span which will be filled with the bytes that will be + * read from the bus + * @param p_timeout A timeout function which is primarily used for clock + * stretching to ensure the peripheral doesn't hold the line too long + */ + void read(std::span p_data_in, + function_ref p_timeout); + + /** + * @brief This function is responsible for reading a byte at a time off the + * bus and also creating the byte from bits + * + * @return hal::byte the byte that has been read off of the bus + */ + hal::byte read_byte(); + + /** + * @brief This function is responsible for reading a single bit at a time. It + * will manage the clock and will release the sda pin (allow it to be pulled + * high) every time it is called + * + * @return hal::byte which will house the single bit read from the bus + */ + hal::byte read_bit(); + + /// @brief An output pin which is the i2c scl pin + hal::output_pin* m_scl; + + /// @brief An output pin which is the i2c sda pin + hal::output_pin* m_sda; + + /// @brief A steady_clock provides a mechanism to delay the clock pulses of + /// the scl line. + hal::steady_clock* m_clock; + + /// @brief The time that scl will be held high for + std::uint64_t m_scl_high_ticks; + + /// @brief The time that scl will be held low for + std::uint64_t m_scl_low_ticks; + + /// @brief This is used to preserve the duty cycle that is passed in through + /// the constructor and be used in the driver_configure function + float m_duty_cycle; +}; +} // namespace hal + +namespace hal { +// This is here for backwards compatibility. +// We made a mistake on the first review and accidentally put bit_bang_i2c in +// the hal namespace. +using hal::soft::bit_bang_i2c; +} // namespace hal diff --git a/include/libhal-util/bit_bang_spi.hpp b/include/libhal-util/bit_bang_spi.hpp new file mode 100644 index 0000000..bc2036f --- /dev/null +++ b/include/libhal-util/bit_bang_spi.hpp @@ -0,0 +1,127 @@ +// Copyright 2024 Khalil Estell +// +// 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. + +#pragma once + +#include +#include +#include +#include + +namespace hal::soft { +/** + * @brief A bit bang implementation for spi. + * + * This implementation of spi only needs 2 hal::output_pins for sck and copi, a + * hal::input_pin for cipo, and a steady_clock to work. + */ +class bit_bang_spi : public spi +{ +public: + struct pins + { + output_pin* sck; + output_pin* copi; + input_pin* cipo; + }; + + /// Adds or removes delays in the read/write cycle + enum class delay_mode + { + /// Calculates the delay time using the clock_rate from the provided + /// settings when constructing the bit_bang_spi object + with, + /// Omits delays between reading/writing to get the fastest speed possible + without + }; + + /** + * @brief Construct a new bit bang spi object + * + * @param p_pins named structure that contains pointers to the sck, copi, and + * cipo to be used inside of the driver + * @param p_steady_clock the steady clock that will be used for timing the sck + * line. + * @param p_settings the initial settings of the spi bus + * @param p_delay_mode adds or removes delays in the read/write cycle + */ + bit_bang_spi(pins const& p_pins, + steady_clock& p_steady_clock, + settings const& p_settings = {}, + delay_mode p_delay_mode = delay_mode::with); + +private: + void driver_configure(settings const& p_settings) override; + + void driver_transfer(std::span p_data_out, + std::span p_data_in, + hal::byte p_filler) override; + + /** + * @brief This function will handle writing a single byte to spi copi line + * + * @param p_byte_to_write This is the byte that will be written + */ + hal::byte transfer_byte(hal::byte p_byte_to_write); + + /** + * @brief This function will handle writing a single byte to spi copi line + * without using delays between reading and writing + * + * @param p_byte_to_write This is the byte that will be written + */ + hal::byte transfer_byte_without_delay(hal::byte p_byte_to_write); + + /** + * @brief This function will handle writing a single bit to the spi copi line + * + * @param p_bit_to_write This is the bit that will be written + */ + bool transfer_bit(bool p_bit_to_write); + + /** + * @brief This function will handle writing a single bit to the spi copi line + * without using delays between reading and writing + * + * @param p_bit_to_write This is the bit that will be written + */ + bool transfer_bit_without_delay(bool p_bit_to_write); + + /// @brief An output pin which is the spi sck pin + hal::output_pin* m_sck; + + /// @brief An output pin which is the spi copi pin + hal::output_pin* m_copi; + + /// @brief An input pin which is the spi cipo pin + hal::input_pin* m_cipo; + + /// @brief A steady_clock provides a mechanism to delay the clock pulses of + /// the sck line. + hal::steady_clock* m_clock; + + /// @brief State of the sck line when inactive + bool m_polarity; + + /// @brief Phase of the sck line dictates when to read/write bits + bool m_phase; + + /// @brief Time of a full read/write cycle, used in delay_mode::with + hal::time_duration m_cycle_duration; + + /// @brief Configuration for using delays or not + delay_mode m_delay_mode; +}; + +} // namespace hal::soft diff --git a/include/libhal-util/buffered_can.hpp b/include/libhal-util/buffered_can.hpp new file mode 100644 index 0000000..869e129 --- /dev/null +++ b/include/libhal-util/buffered_can.hpp @@ -0,0 +1,229 @@ +// Copyright 2024 Khalil Estell +// +// 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. + +#pragma once + +#if 0 +#include +#include +#include + +#include + +#include "math.hpp" + +/** + * @defgroup CAN_Utilities CAN Utilities + * + */ + +namespace hal { +/** + * @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==(can::settings const& p_lhs, + can::settings const& p_rhs) +{ + 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==(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; +} + +[[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. + */ +[[nodiscard]] inline 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; + + if (p_operating_frequency < 0.0f || p_target_baud_rate < 0.0f || + p_operating_frequency <= p_target_baud_rate) { + return std::nullopt; + } + + std::int32_t operating_frequency = p_operating_frequency; + std::int32_t desired_baud_rate = p_target_baud_rate; + + using inner_div_t = + decltype(std::div(operating_frequency, desired_baud_rate)); + inner_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; + } + + 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; +} + +/** + * @ingroup CAN_Utilities + * @brief Compares two CAN message states. + * + * @param p_lhs A CAN message. + * @param p_rhs A CAN message. + * @return A boolean if they are the same or not. + */ +[[nodiscard]] constexpr auto operator==(can::message_t const& p_lhs, + can::message_t const& p_rhs) +{ + bool payload_equal = p_lhs.payload == p_rhs.payload; + return payload_equal && p_lhs.id == p_rhs.id && + p_lhs.length == p_rhs.length && + p_lhs.is_remote_request == p_rhs.is_remote_request; +} +} // namespace hal +#endif diff --git a/include/libhal-util/can.hpp b/include/libhal-util/can.hpp index 27a5fb6..cf6c2c7 100644 --- a/include/libhal-util/can.hpp +++ b/include/libhal-util/can.hpp @@ -14,34 +14,17 @@ #pragma once -#include #include #include #include -#include "math.hpp" - /** * @defgroup CAN_Utilities CAN Utilities * */ namespace hal { -/** - * @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==(can::settings const& p_lhs, - can::settings const& p_rhs) -{ - return equals(p_lhs.baud_rate, p_rhs.baud_rate); -} - /** * @brief Generic settings for a can peripheral * @ingroup CAN_Utilities @@ -207,21 +190,4 @@ struct can_bus_divider_t return timing; } - -/** - * @ingroup CAN_Utilities - * @brief Compares two CAN message states. - * - * @param p_lhs A CAN message. - * @param p_rhs A CAN message. - * @return A boolean if they are the same or not. - */ -[[nodiscard]] constexpr auto operator==(can::message_t const& p_lhs, - can::message_t const& p_rhs) -{ - bool payload_equal = p_lhs.payload == p_rhs.payload; - return payload_equal && p_lhs.id == p_rhs.id && - p_lhs.length == p_rhs.length && - p_lhs.is_remote_request == p_rhs.is_remote_request; -} } // namespace hal diff --git a/include/libhal-util/i2c.hpp b/include/libhal-util/i2c.hpp index 443063c..40940d9 100644 --- a/include/libhal-util/i2c.hpp +++ b/include/libhal-util/i2c.hpp @@ -14,8 +14,6 @@ #pragma once -#include - #include #include #include @@ -29,21 +27,8 @@ namespace hal { /** * @ingroup I2CUtils - * @brief Compares two I2C bus states. - * - * @param p_lhs A I2C bus. - * @param p_rhs A I2C bus. - * @return A boolean if they are the same or not. - */ -[[nodiscard]] constexpr auto operator==(i2c::settings const& p_lhs, - i2c::settings const& p_rhs) -{ - return equals(p_lhs.clock_rate, p_rhs.clock_rate); -} - -/** - * @ingroup I2CUtils - * @brief write data to a target device on the i2c bus + * @brief write data to a target device on the i2c bus with a timeout + * @deprecated Prefer to use the write API that does not require a timeout. * * Shorthand for writing i2c.transfer(...) for write only operations * @@ -51,13 +36,14 @@ namespace hal { * @param p_address - target address * @param p_data_out - buffer of bytes to write to the target device * @param p_timeout - amount of time to execute the transaction + * @param auto - [deprecated don't use] */ inline void write(i2c& p_i2c, hal::byte p_address, std::span p_data_out, - timeout auto p_timeout) + timeout auto) { - p_i2c.transaction(p_address, p_data_out, std::span{}, p_timeout); + p_i2c.transaction(p_address, p_data_out, std::span{}); } /** @@ -87,14 +73,14 @@ inline void write(i2c& p_i2c, * @param p_i2c - i2c driver * @param p_address - target address * @param p_data_in - buffer to read bytes into from target device - * @param p_timeout - amount of time to execute the transaction + * @param auto - [deprecated don't use] */ inline void read(i2c& p_i2c, hal::byte p_address, std::span p_data_in, - timeout auto p_timeout) + timeout auto) { - p_i2c.transaction(p_address, std::span{}, p_data_in, p_timeout); + p_i2c.transaction(p_address, std::span{}, p_data_in); } /** @@ -131,10 +117,10 @@ inline void read(i2c& p_i2c, template [[nodiscard]] std::array read(i2c& p_i2c, hal::byte p_address, - timeout auto p_timeout) + timeout auto) { std::array buffer; - read(p_i2c, p_address, buffer, p_timeout); + read(p_i2c, p_address, buffer); return buffer; } @@ -143,20 +129,20 @@ template * @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. - * This operation will never time out and should only be used with devices that - * never perform clock stretching. * * @tparam bytes_to_read - number of bytes to read * @param p_i2c - i2c driver * @param p_address - target address * @return std::array - array of bytes from target - * device. + * device */ template [[nodiscard]] std::array read(i2c& p_i2c, hal::byte p_address) { - return read(p_i2c, p_address, hal::never_timeout()); + std::array buffer; + read(p_i2c, p_address, buffer); + return buffer; } /** @@ -176,9 +162,9 @@ inline void write_then_read(i2c& p_i2c, hal::byte p_address, std::span p_data_out, std::span p_data_in, - timeout auto p_timeout = hal::never_timeout()) + timeout auto) { - p_i2c.transaction(p_address, p_data_out, p_data_in, p_timeout); + p_i2c.transaction(p_address, p_data_out, p_data_in); } /** @@ -215,7 +201,7 @@ inline void write_then_read(i2c& p_i2c, * @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_timeout - amount of time to execute the transaction + * @param auto - [deprecated use the APIs without timeout parameters] * @return std::array - array of bytes from target * device. */ @@ -224,10 +210,10 @@ template i2c& p_i2c, hal::byte p_address, std::span p_data_out, - timeout auto p_timeout) + timeout auto) { std::array buffer; - write_then_read(p_i2c, p_address, p_data_out, buffer, p_timeout); + write_then_read(p_i2c, p_address, p_data_out, buffer); return buffer; } @@ -250,8 +236,9 @@ template hal::byte p_address, std::span p_data_out) { - return write_then_read( - p_i2c, p_address, p_data_out, hal::never_timeout()); + std::array buffer; + write_then_read(p_i2c, p_address, p_data_out, buffer); + return buffer; } /** @@ -310,6 +297,7 @@ enum class i2c_operation hal::byte p_address, i2c_operation p_operation) noexcept { + // TODO(#36): remove noexcept from this function on libhal's 5.0.0 API break hal::byte v8bit_address = static_cast(p_address << 1); v8bit_address |= hal::value(p_operation); return v8bit_address; diff --git a/include/libhal-util/i2c_minimum_speed.hpp b/include/libhal-util/i2c_minimum_speed.hpp new file mode 100644 index 0000000..10c9be0 --- /dev/null +++ b/include/libhal-util/i2c_minimum_speed.hpp @@ -0,0 +1,58 @@ +// Copyright 2024 Khalil Estell +// +// 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. + +#pragma once + +#include + +namespace hal { + +// TODO(#38) Remove on libhal 5.0.0 +/** + * @brief A i2c wrapper to ensure that the lowest i2c device frequency is used. + * @deprecated Do not use this class as it is a trap. This wrapper cannot ensure + * that the i2c clock speed is the minimum possible for all devices on the bus + * without knowledge about the system before hand. + */ +class minimum_speed_i2c : public hal::i2c +{ +public: + constexpr static auto default_max_speed = 2'000'000; + /** + * @brief Create minimum_speed_i2c object. + * + * @param p_i2c - i2c object that the device will use + * @param p_frequency - the maximum starting frequency the device can use + */ + minimum_speed_i2c(hal::i2c& p_i2c, hertz p_frequency = default_max_speed); + +private: + /** + * @brief Pass through configuration function from this class to the passed + * i2c driver. + * + * @param p_new_setting - settings to be set + */ + void driver_configure(settings const& p_new_setting) override; + + void driver_transaction( + hal::byte p_address, + std::span p_data_out, + std::span p_data_in, + hal::function_ref p_timeout) override; + + hal::i2c* m_i2c; + hertz m_lowest_seen_frequency; +}; +} // namespace hal diff --git a/include/libhal-util/serial.hpp b/include/libhal-util/serial.hpp index d54b0ef..ccad400 100644 --- a/include/libhal-util/serial.hpp +++ b/include/libhal-util/serial.hpp @@ -24,8 +24,6 @@ #include #include "as_bytes.hpp" -#include "comparison.hpp" -#include "math.hpp" /** * @defgroup Serial Serial @@ -33,20 +31,7 @@ */ namespace hal { -/** - * @ingroup Serial - * @brief Compares two serial objects via their settings. - * - * @param p_lhs A serial object - * @param p_rhs A serial object - * @return A boolean if they are the same or not. - */ -[[nodiscard]] constexpr auto operator==(serial::settings const& p_lhs, - serial::settings const& p_rhs) -{ - return equals(p_lhs.baud_rate, p_rhs.baud_rate) && - p_lhs.parity == p_rhs.parity && p_lhs.stop == p_rhs.stop; -} +// TODO(#38): Remove on release of libhal 5.0.0 /** * @ingroup Serial * @brief Write bytes to a serial port diff --git a/tests/i2c.test.cpp b/tests/i2c.test.cpp index 2c6ac80..002ab10 100644 --- a/tests/i2c.test.cpp +++ b/tests/i2c.test.cpp @@ -101,7 +101,8 @@ void i2c_util_test() expect(that % expected_payload.size() == i2c.m_out.size()); expect(that % nullptr == i2c.m_in.data()); expect(that % 0 == i2c.m_in.size()); - expect(that % true == test_timeout.was_called); + // Verify: timeout will be ignored from libhal 4.5.0 and beyond + expect(that % false == test_timeout.was_called); }; "[failure] write"_test = []() { @@ -140,7 +141,8 @@ void i2c_util_test() expect(that % expected_buffer.size() == i2c.m_in.size()); expect(that % nullptr == i2c.m_out.data()); expect(that % 0 == i2c.m_out.size()); - expect(that % true == test_timeout.was_called); + // Verify: timeout will be ignored from libhal 4.5.0 and beyond + expect(that % false == test_timeout.was_called); }; "[failure] read"_test = []() { @@ -180,7 +182,8 @@ void i2c_util_test() expect(std::equal(expected.begin(), expected.end(), actual.begin())); expect(that % nullptr == i2c.m_out.data()); expect(that % 0 == i2c.m_out.size()); - expect(that % true == test_timeout.was_called); + // Verify: timeout will be ignored from libhal 4.5.0 and beyond + expect(that % false == test_timeout.was_called); }; "[failure] read"_test = []() { @@ -221,7 +224,8 @@ void i2c_util_test() expect(that % expected_payload.size() == i2c.m_out.size()); expect(that % expected_buffer.data() == i2c.m_in.data()); expect(that % expected_buffer.size() == i2c.m_in.size()); - expect(that % true == test_timeout.was_called); + // Verify: timeout will be ignored from libhal 4.5.0 and beyond + expect(that % false == test_timeout.was_called); }; "[failure] write_then_read"_test = []() { @@ -268,7 +272,8 @@ void i2c_util_test() expect(that % expected_payload.data() == i2c.m_out.data()); expect(that % expected_payload.size() == i2c.m_out.size()); expect(std::equal(expected.begin(), expected.end(), actual.begin())); - expect(that % true == test_timeout.was_called); + // Verify: timeout will be ignored from libhal 4.5.0 and beyond + expect(that % false == test_timeout.was_called); }; "[failure] write_then_read"_test = []() {