Skip to content
This repository has been archived by the owner on Nov 30, 2024. It is now read-only.

Commit

Permalink
✨ Add hal::softi2c_arbitration_manager
Browse files Browse the repository at this point in the history
Resolves #43
  • Loading branch information
kammce committed Oct 14, 2024
1 parent 58360b1 commit 485d404
Show file tree
Hide file tree
Showing 4 changed files with 206 additions and 3 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ libhal_test_and_make_library(
tests/inverter.test.cpp
tests/rc_servo.test.cpp
tests/atomic_spin_lock.test.cpp
tests/i2c_arbitration_manager.test.cpp
tests/main.test.cpp

PACKAGES
Expand Down
85 changes: 85 additions & 0 deletions include/libhal-soft/i2c_arbitration_manager.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// 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 <concepts>
#include <ranges>

#include <libhal/error.hpp>
#include <libhal/i2c.hpp>

namespace hal::soft {

/**
* @brief A i2c wrapper to ensure that the lowest i2c device frequency is used.
*
*/
template<std::derived_from<hal::i2c> i2c_type>
class i2c_arbitration_manager : public hal::i2c
{
public:
/**
* @brief Create minimum_speed_i2c object.
*
* @param p_i2c - i2c object that the device will use
* @param p_retry_limit - number of attempts before calling the arbitration
* handler
* @param p_arbitration_handler - the call that will be invoked when a
* transaction has been rejected for the p_retry_limit number of times.
*/
i2c_arbitration_manager(i2c_type& p_i2c,
std::uint32_t p_retry_limit,
hal::callback<void(void)> p_arbitration_handler)
: m_i2c(&p_i2c)
, m_retry_limit(p_retry_limit)
, m_arbitration_handler(std::move(p_arbitration_handler))
{
}

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
{
m_i2c->configure(p_new_setting);
}

void driver_transaction(
hal::byte p_address,
std::span<hal::byte const> p_data_out,
std::span<hal::byte> p_data_in,
hal::function_ref<hal::timeout_function> p_timeout) override
{
for (auto _ : std::views::iota(0U, m_retry_limit + 1)) {
try {
m_i2c->transaction(p_address, p_data_out, p_data_in, p_timeout);
return;
} catch (hal::resource_unavailable_try_again const&) {
continue;
}
}
m_arbitration_handler();
throw hal::resource_unavailable_try_again(m_i2c);
}

i2c_type* m_i2c;
std::uint32_t m_retry_limit = 0;
hal::callback<void(void)> m_arbitration_handler = []() {};
};
} // namespace hal::soft
117 changes: 117 additions & 0 deletions tests/i2c_arbitration_manager.test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
#include <libhal-soft/i2c_arbitration_manager.hpp>

#include <libhal-mock/testing.hpp>

#include <boost/ut.hpp>

namespace {
struct fake_i2c : public hal::i2c
{
void reset()
{
spy_configure.reset();
spy_transaction.reset();
}
// Spy handler for hal::i2c::configure()
hal::spy_handler<settings> spy_configure;
/// Record of the out data from hal::i2c::transaction()
hal::spy_handler<hal::byte,
std::span<hal::byte const>,
std::span<hal::byte>,
std::function<hal::timeout_function>>
spy_transaction;

private:
void driver_configure(settings const& p_settings) final
{
spy_configure.record(p_settings);
}

void driver_transaction(
hal::byte p_address,
std::span<hal::byte const> p_data_out,
std::span<hal::byte> p_data_in,
hal::function_ref<hal::timeout_function> p_timeout) final
{
spy_transaction.record(p_address, p_data_out, p_data_in, p_timeout);
}
};

struct fake_i2c2 : public fake_i2c
{};
} // namespace

namespace hal {
boost::ut::suite test_i2c_arbitration_manager = []() {
using namespace boost::ut;

"hal::soft::i2c_arbitration_manager"_test = []() {
"::configure should simply pass info through"_test = []() {
// Setup
fake_i2c mock_i2c;
bool arbitration_handler = false;
hal::soft::i2c_arbitration_manager managed_i2c(
mock_i2c, 4, [&arbitration_handler]() { arbitration_handler = true; });

constexpr hal::i2c::settings minimum_default = {
.clock_rate = 400'000.0,
};
constexpr hal::i2c::settings expected_upper_boundary = {
.clock_rate = 3'000'000,
};
constexpr hal::i2c::settings expected_lower = { .clock_rate = 5 };

// Exercise
managed_i2c.configure(expected_upper_boundary);
managed_i2c.configure(minimum_default);
managed_i2c.configure(expected_lower);

// Verify
expect(false);
};

"transaction"_test = []() {
// Setup
constexpr hal::byte expected_address{ 0xAA };
constexpr std::array<hal::byte const, 2> data_out{ hal::byte{ 0xAB },
hal::byte{ 0xFF } };
std::span<hal::byte> data_in;
bool has_been_called = false;
std::function<hal::timeout_function> expected_timeout =
[&has_been_called]() { has_been_called = true; };
fake_i2c mock_i2c;
fake_i2c2 mock_i2c2;
bool arbitration_handler = false;
hal::soft::i2c_arbitration_manager managed_i2c(
mock_i2c, 4, [&arbitration_handler]() { arbitration_handler = true; });
hal::soft::i2c_arbitration_manager managed_i2c2(
mock_i2c2, 4, [&arbitration_handler]() { arbitration_handler = true; });

static_assert(
not std::is_same_v<decltype(managed_i2c), decltype(managed_i2c2)>,
"Are not the same!");

// Exercise
managed_i2c.transaction(
expected_address, data_out, data_in, expected_timeout);

// Verify
auto transaction_call_info =
mock_i2c.spy_transaction.call_history().at(0);
auto transaction_expected_address = std::get<0>(transaction_call_info);
auto transaction_data_out = std::get<1>(transaction_call_info);
auto transaction_data_in = std::get<2>(transaction_call_info);
auto transaction_expected_timeout = std::get<3>(transaction_call_info);

expect(expected_address == transaction_expected_address);
expect(data_out.data() == transaction_data_out.data());
expect(data_out.size() == transaction_data_out.size());
expect(data_in.data() == transaction_data_in.data());
expect(data_in.size() == transaction_data_in.size());
transaction_expected_timeout();
expect(has_been_called);
expect(false);
};
};
};
} // namespace hal
6 changes: 3 additions & 3 deletions tests/i2c_minimum_speed.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ namespace hal::soft {
boost::ut::suite test_minimum_speed = []() {
using namespace boost::ut;

"hal::i2c::minimum_speed_i2c"_test = []() {
"create() with default frequency + configure()"_test = []() {
"hal::soft::minimum_speed_i2c"_test = []() {
"::configure()"_test = []() {
// Setup
fake_i2c mock_i2c;
constexpr hal::i2c::settings minimum_default = {
Expand All @@ -86,7 +86,7 @@ boost::ut::suite test_minimum_speed = []() {
std::get<0>(mock_i2c.spy_configure.call_history().at(0)));
};

"transaction pass through"_test = []() {
"::transaction() pass through"_test = []() {
// Setup
constexpr hal::byte expected_address{ 0xAA };
constexpr std::array<hal::byte const, 2> data_out{ hal::byte{ 0xAB },
Expand Down

0 comments on commit 485d404

Please sign in to comment.