Skip to content

Commit

Permalink
⬆️ upgrade to libhal 4.5.1
Browse files Browse the repository at this point in the history
- API break in libhal-util because libhal now has its own comparison
  operator added to each settings, which makes the operator comparisons
  redundant and ambiguous
- I2C utilities have their timeout inputs ignored as per the new
  direction of libhal code for i2c.
  • Loading branch information
kammce committed Nov 5, 2024
1 parent 3798bec commit 076e4d4
Show file tree
Hide file tree
Showing 10 changed files with 747 additions and 95 deletions.
2 changes: 1 addition & 1 deletion conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
89 changes: 89 additions & 0 deletions include/libhal-util/atomic_spin_lock.hpp
Original file line number Diff line number Diff line change
@@ -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 <atomic>

#include <libhal/lock.hpp>
#include <libhal/steady_clock.hpp>

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
201 changes: 201 additions & 0 deletions include/libhal-util/bit_bang_i2c.hpp
Original file line number Diff line number Diff line change
@@ -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 <libhal/i2c.hpp>
#include <libhal/output_pin.hpp>
#include <libhal/steady_clock.hpp>
#include <libhal/units.hpp>

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<hal::byte const> p_data_out,
std::span<hal::byte> p_data_in,
function_ref<hal::timeout_function> 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<hal::timeout_function> 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<hal::byte const> p_data_out,
function_ref<hal::timeout_function> 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<hal::timeout_function> 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<hal::timeout_function> 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<hal::byte> p_data_in,
function_ref<hal::timeout_function> 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
Loading

0 comments on commit 076e4d4

Please sign in to comment.