diff --git a/CMakeLists.txt b/CMakeLists.txt index eae0427..348770d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,11 +21,13 @@ libhal_test_and_make_library( SOURCES src/canusb.cpp src/pca9685.cpp + src/tca9548a.cpp src/tla2528.cpp src/tla2528_adapters.cpp TEST_SOURCES tests/pca9685.test.cpp + tests/tca9548a.test.cpp tests/tla2528.test.cpp tests/main.test.cpp ) diff --git a/datasheets/tca9548a.pdf b/datasheets/tca9548a.pdf new file mode 100644 index 0000000..b6570f7 Binary files /dev/null and b/datasheets/tca9548a.pdf differ diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt index 34e8fc6..afe732e 100644 --- a/demos/CMakeLists.txt +++ b/demos/CMakeLists.txt @@ -19,6 +19,7 @@ project(demos LANGUAGES CXX) libhal_build_demos( DEMOS pca9685 + tca9548a_scan tla2528_adc tla2528_input_pin tla2528_output_pin diff --git a/demos/applications/tca9548a_scan.cpp b/demos/applications/tca9548a_scan.cpp new file mode 100644 index 0000000..20cb6e2 --- /dev/null +++ b/demos/applications/tca9548a_scan.cpp @@ -0,0 +1,55 @@ +// Copyright 2026 Malia Labor 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 + +#include + +void application() +{ + using namespace std::chrono_literals; + using namespace hal::literals; + + auto console = resources::console(); + auto clock = resources::clock(); + auto i2c = resources::i2c(); + auto i2c_mux = hal::expander::tca9548a(*i2c); + + hal::print(*console, "I2c scanner starting!"); + + while (true) { + using namespace std::literals; + for (std::uint8_t i = 0; i < 8; i++) { + std::bitset<8> ports{ 0x00 }; + ports.set(i); + i2c_mux.set_ports(ports); + constexpr hal::byte first_i2c_address = 0x08; + constexpr hal::byte last_i2c_address = 0x78; + + hal::print<32>(*console, "\nDevices Found on port %d: ", i); + + for (hal::byte address = first_i2c_address; address < last_i2c_address; + address++) { + // This can only fail if the device is not present + if (hal::probe(*i2c, address)) { + hal::print<12>(*console, "0x%02X ", address); + } + } + hal::delay(*clock, 1s); + } + } +} diff --git a/demos/platforms/stm32f103c8.cpp b/demos/platforms/stm32f103c8.cpp index 719f434..e403e7c 100644 --- a/demos/platforms/stm32f103c8.cpp +++ b/demos/platforms/stm32f103c8.cpp @@ -12,6 +12,33 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include @@ -26,12 +53,17 @@ #include +using st_peripheral = hal::stm32f1::peripheral; + hal::v5::optional_ptr clock_ptr; hal::v5::optional_ptr console_ptr; hal::v5::optional_ptr status_led_ptr; hal::v5::optional_ptr i2c_ptr; hal::v5::optional_ptr usb_serial_ptr; hal::v5::optional_ptr v5_console_ptr; +hal::v5::optional_ptr> gpio_a_ptr; +hal::v5::optional_ptr> gpio_b_ptr; +hal::v5::optional_ptr> gpio_c_ptr; [[noreturn]] void terminate_handler() noexcept { @@ -67,13 +99,12 @@ void initialize_platform() namespace resources { using namespace hal::literals; +std::array driver_memory{}; +std::pmr::monotonic_buffer_resource resource(driver_memory.data(), + driver_memory.size(), + std::pmr::null_memory_resource()); std::pmr::polymorphic_allocator<> driver_allocator() { - static std::array driver_memory{}; - static std::pmr::monotonic_buffer_resource resource( - driver_memory.data(), - driver_memory.size(), - std::pmr::null_memory_resource()); return &resource; } @@ -128,10 +159,51 @@ hal::v5::strong_ptr status_led() return status_led_ptr; } +hal::v5::strong_ptr> gpio_a() +{ + if (not gpio_a_ptr) { + gpio_a_ptr = + hal::v5::make_strong_ptr>( + driver_allocator()); + } + return gpio_a_ptr; +} + +hal::v5::strong_ptr> gpio_b() +{ + if (not gpio_b_ptr) { + gpio_b_ptr = + hal::v5::make_strong_ptr>( + driver_allocator()); + } + return gpio_b_ptr; +} + +hal::v5::strong_ptr> gpio_c() +{ + if (not gpio_c_ptr) { + gpio_c_ptr = + hal::v5::make_strong_ptr>( + driver_allocator()); + } + return gpio_c_ptr; +} + hal::v5::strong_ptr i2c() { - hal::safe_throw(hal::bad_optional_ptr_access(nullptr)); - // return hal::micromod::v2::i2c(); + // TODO(#167): Use a version of bit_bang_i2c that accepts strong_ptr's + static auto sda_output_pin = + hal::acquire_output_pin(driver_allocator(), gpio_b(), 7); + static auto scl_output_pin = + hal::acquire_output_pin(driver_allocator(), gpio_b(), 6); + auto clock = resources::clock(); + return hal::v5::make_strong_ptr( + driver_allocator(), + hal::bit_bang_i2c::pins{ + .sda = &(*sda_output_pin), + .scl = &(*scl_output_pin), + }, + *clock); } hal::v5::strong_ptr usb_serial() diff --git a/include/libhal-expander/tca9548a.hpp b/include/libhal-expander/tca9548a.hpp new file mode 100644 index 0000000..78f7d97 --- /dev/null +++ b/include/libhal-expander/tca9548a.hpp @@ -0,0 +1,65 @@ +// Copyright 2026 Malia Labor 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. + +#pragma once + +#include +#include + +#include + +namespace hal::expander { +/** + * @brief tca9548a driver: 8 channel i2c multiplexer using eight bidirectional + * translating switches that can be controlled through the i2c bus + * + */ +class tca9548a +{ +public: + /** + * @brief Construct a new tca9548a driver object + * + * @param p_i2c - an i2c bus driver to communicate with + * @param p_address - address of the mux configured through address pins on + * chip, + */ + tca9548a(hal::i2c& p_i2c, std::uint8_t p_address = 0b01110000); + + /** + * @brief Enable or disable multiple ports to read and write to over i2c + * + * @param p_ports bitset representing state of each port + */ + void set_ports(std::bitset<8> p_ports); + + /** + * @brief Get the status of each port + * + * @return std::array - array of bools representing which ports are + * on or off + */ + std::bitset<8> get_ports_status(); + +private: + hal::byte get_control_register_byte(); + + hal::i2c* m_i2c; + hal::byte m_address = 0x70; + // TODO(#35): Add i2c drivers that automatically handle port switching + [[maybe_unused]] hal::byte m_port_ownership = 0; // reserved for future usage + [[maybe_unused]] hal::byte m_current_port_cached = + 0; // reserved for future usage +}; +} // namespace hal::expander diff --git a/src/tca9548a.cpp b/src/tca9548a.cpp new file mode 100644 index 0000000..1752232 --- /dev/null +++ b/src/tca9548a.cpp @@ -0,0 +1,47 @@ +// Copyright 2026 Malia Labor 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 + +namespace hal::expander { +tca9548a::tca9548a(hal::i2c& p_i2c, std::uint8_t p_address) + : m_i2c(&p_i2c) +{ + // calculate address byte, only pay attention to last 3 bits + auto mask = 0b111; + auto masked_bits = p_address & mask; + m_address = 0b01110000 | masked_bits; +} + +hal::byte tca9548a::get_control_register_byte() +{ + return hal::read<1>(*m_i2c, m_address)[0]; +} + +void tca9548a::set_ports(std::bitset<8> p_ports) +{ + hal::write( + *m_i2c, + m_address, + std::array{ static_cast(p_ports.to_ulong()) }); +} + +std::bitset<8> tca9548a::get_ports_status() +{ + auto const control_register = get_control_register_byte(); + return control_register; +} + +} // namespace hal::expander diff --git a/test_package/main.cpp b/test_package/main.cpp index 0cc847f..3181251 100644 --- a/test_package/main.cpp +++ b/test_package/main.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include +#include int main() { diff --git a/tests/tca9548a.test.cpp b/tests/tca9548a.test.cpp new file mode 100644 index 0000000..9b0ab6d --- /dev/null +++ b/tests/tca9548a.test.cpp @@ -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. + +#include + +#include + +namespace hal::expander { +boost::ut::suite test_tca9548a = []() { + using namespace boost::ut; + using namespace std::literals; + + "tca9548a::tca9548a()"_test = []() { + // Setup + // Exercise + // Verify + }; +}; +} // namespace hal::expander