Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Binary file added datasheets/tca9548a.pdf
Binary file not shown.
1 change: 1 addition & 0 deletions demos/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ project(demos LANGUAGES CXX)
libhal_build_demos(
DEMOS
pca9685
tca9548a_scan
tla2528_adc
tla2528_input_pin
tla2528_output_pin
Expand Down
55 changes: 55 additions & 0 deletions demos/applications/tca9548a_scan.cpp
Original file line number Diff line number Diff line change
@@ -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 <libhal-expander/tca9548a.hpp>
#include <libhal-util/i2c.hpp>
#include <libhal-util/serial.hpp>
#include <libhal-util/steady_clock.hpp>

#include <resource_list.hpp>

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);
}
}
}
86 changes: 79 additions & 7 deletions demos/platforms/stm32f103c8.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,33 @@
// See the License for the specific language governing permissions and
// limitations under the License.

#include <memory_resource>

#include <libhal-arm-mcu/dwt_counter.hpp>
#include <libhal-arm-mcu/startup.hpp>
#include <libhal-arm-mcu/stm32f1/adc.hpp>
#include <libhal-arm-mcu/stm32f1/can2.hpp>
#include <libhal-arm-mcu/stm32f1/clock.hpp>
#include <libhal-arm-mcu/stm32f1/constants.hpp>
#include <libhal-arm-mcu/stm32f1/gpio.hpp>
#include <libhal-arm-mcu/stm32f1/independent_watchdog.hpp>
#include <libhal-arm-mcu/stm32f1/input_pin.hpp>
#include <libhal-arm-mcu/stm32f1/output_pin.hpp>
#include <libhal-arm-mcu/stm32f1/pin.hpp>
#include <libhal-arm-mcu/stm32f1/spi.hpp>
#include <libhal-arm-mcu/stm32f1/timer.hpp>
#include <libhal-arm-mcu/stm32f1/uart.hpp>
#include <libhal-arm-mcu/stm32f1/usart.hpp>
#include <libhal-arm-mcu/stm32f1/usb.hpp>
#include <libhal-arm-mcu/system_control.hpp>
#include <libhal-util/atomic_spin_lock.hpp>
#include <libhal-util/bit_bang_i2c.hpp>
#include <libhal-util/bit_bang_spi.hpp>
#include <libhal-util/inert_drivers/inert_adc.hpp>
#include <libhal-util/serial.hpp>
#include <libhal-util/steady_clock.hpp>
#include <libhal/pointers.hpp>
#include <libhal/pwm.hpp>
#include <libhal/units.hpp>

#include <libhal-arm-mcu/dwt_counter.hpp>
Expand All @@ -26,12 +53,17 @@

#include <resource_list.hpp>

using st_peripheral = hal::stm32f1::peripheral;

hal::v5::optional_ptr<hal::steady_clock> clock_ptr;
hal::v5::optional_ptr<hal::serial> console_ptr;
hal::v5::optional_ptr<hal::output_pin> status_led_ptr;
hal::v5::optional_ptr<hal::i2c> i2c_ptr;
hal::v5::optional_ptr<hal::v5::serial> usb_serial_ptr;
hal::v5::optional_ptr<hal::v5::serial> v5_console_ptr;
hal::v5::optional_ptr<hal::stm32f1::gpio<st_peripheral::gpio_a>> gpio_a_ptr;
hal::v5::optional_ptr<hal::stm32f1::gpio<st_peripheral::gpio_b>> gpio_b_ptr;
hal::v5::optional_ptr<hal::stm32f1::gpio<st_peripheral::gpio_c>> gpio_c_ptr;

[[noreturn]] void terminate_handler() noexcept
{
Expand Down Expand Up @@ -67,13 +99,12 @@ void initialize_platform()

namespace resources {
using namespace hal::literals;
std::array<hal::byte, 1024> 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<hal::byte, 1024> driver_memory{};
static std::pmr::monotonic_buffer_resource resource(
driver_memory.data(),
driver_memory.size(),
std::pmr::null_memory_resource());
return &resource;
}

Expand Down Expand Up @@ -128,10 +159,51 @@ hal::v5::strong_ptr<hal::output_pin> status_led()
return status_led_ptr;
}

hal::v5::strong_ptr<hal::stm32f1::gpio<st_peripheral::gpio_a>> gpio_a()
{
if (not gpio_a_ptr) {
gpio_a_ptr =
hal::v5::make_strong_ptr<hal::stm32f1::gpio<st_peripheral::gpio_a>>(
driver_allocator());
}
return gpio_a_ptr;
}

hal::v5::strong_ptr<hal::stm32f1::gpio<st_peripheral::gpio_b>> gpio_b()
{
if (not gpio_b_ptr) {
gpio_b_ptr =
hal::v5::make_strong_ptr<hal::stm32f1::gpio<st_peripheral::gpio_b>>(
driver_allocator());
}
return gpio_b_ptr;
}

hal::v5::strong_ptr<hal::stm32f1::gpio<st_peripheral::gpio_c>> gpio_c()
{
if (not gpio_c_ptr) {
gpio_c_ptr =
hal::v5::make_strong_ptr<hal::stm32f1::gpio<st_peripheral::gpio_c>>(
driver_allocator());
}
return gpio_c_ptr;
}

hal::v5::strong_ptr<hal::i2c> 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<hal::bit_bang_i2c>(
driver_allocator(),
hal::bit_bang_i2c::pins{
.sda = &(*sda_output_pin),
.scl = &(*scl_output_pin),
},
*clock);
}

hal::v5::strong_ptr<hal::v5::serial> usb_serial()
Expand Down
65 changes: 65 additions & 0 deletions include/libhal-expander/tca9548a.hpp
Original file line number Diff line number Diff line change
@@ -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 <bitset>
#include <cstdint>

#include <libhal/i2c.hpp>

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<bool, 8> - 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
47 changes: 47 additions & 0 deletions src/tca9548a.cpp
Original file line number Diff line number Diff line change
@@ -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 <libhal-expander/tca9548a.hpp>
#include <libhal-util/i2c.hpp>

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<hal::byte, 1>{ static_cast<uint8_t>(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
1 change: 1 addition & 0 deletions test_package/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
// limitations under the License.

#include <libhal-expander/pca9685.hpp>
#include <libhal-expander/tca9548a.hpp>

int main()
{
Expand Down
30 changes: 30 additions & 0 deletions tests/tca9548a.test.cpp
Original file line number Diff line number Diff line change
@@ -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 <libhal-expander/tca9548a.hpp>

#include <boost/ut.hpp>

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
Loading