Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Add tla2528 driver #5

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@ libhal_test_and_make_library(

SOURCES
src/pca9685.cpp
src/tla2528.cpp
src/tla2528_adapters.cpp

TEST_SOURCES
tests/pca9685.test.cpp
tests/tla2528.test.cpp
tests/main.test.cpp
)
Binary file added datasheets/tla2528.pdf
Binary file not shown.
3 changes: 3 additions & 0 deletions demos/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ project(demos LANGUAGES CXX)
libhal_build_demos(
DEMOS
pca9685
tla2528_analog_input
tla2528_input_pin
tla2528_output_pin

INCLUDES
.
Expand Down
36 changes: 36 additions & 0 deletions demos/applications/tla2528_analog_input.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#include <array>

#include <libhal-expander/tla2528.hpp>
#include <libhal-expander/tla2528_adapters.hpp>
#include <libhal-util/bit.hpp>
#include <libhal-util/i2c.hpp>
#include <libhal-util/serial.hpp>
#include <libhal-util/steady_clock.hpp>
#include <libhal/input_pin.hpp>
#include <libhal/units.hpp>

#include <resource_list.hpp>

using namespace hal::literals;
using namespace std::chrono_literals;

void application(resource_list& p_map)
{
auto& terminal = *p_map.console.value();
auto& i2c = *p_map.i2c.value();
auto& steady_clock = *p_map.clock.value();
auto adc_mux = hal::expander::tla2528(i2c);
std::array<hal::expander::tla2528_adc, 8> adcs{
make_adc(adc_mux, 0), make_adc(adc_mux, 1), make_adc(adc_mux, 2),
make_adc(adc_mux, 3), make_adc(adc_mux, 4), make_adc(adc_mux, 5),
make_adc(adc_mux, 6), make_adc(adc_mux, 7)
};

while (true) {
hal::print(terminal, "\nvalues:\n");
for (int i = 0; i < 8; i++) {
hal::print<64>(terminal, "%d:%f\n", i, adcs[i].read());
}
hal::delay(steady_clock, 500ms);
}
}
37 changes: 37 additions & 0 deletions demos/applications/tla2528_input_pin.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#include <array>

#include <libhal-expander/tla2528.hpp>
#include <libhal-expander/tla2528_adapters.hpp>
#include <libhal-util/bit.hpp>
#include <libhal-util/i2c.hpp>
#include <libhal-util/serial.hpp>
#include <libhal-util/steady_clock.hpp>
#include <libhal/input_pin.hpp>
#include <libhal/units.hpp>

#include <resource_list.hpp>

using namespace hal::literals;
using namespace std::chrono_literals;

void application(resource_list& p_map)
{
auto& terminal = *p_map.console.value();
auto& i2c = *p_map.i2c.value();
auto& steady_clock = *p_map.clock.value();
auto gpi_expander = hal::expander::tla2528(i2c);
std::array<hal::expander::tla2528_input_pin, 8> gpis{
make_input_pin(gpi_expander, 0), make_input_pin(gpi_expander, 1),
make_input_pin(gpi_expander, 2), make_input_pin(gpi_expander, 3),
make_input_pin(gpi_expander, 4), make_input_pin(gpi_expander, 5),
make_input_pin(gpi_expander, 6), make_input_pin(gpi_expander, 7)
};

while (true) {
hal::print(terminal, "\nvalues:");
for (int i = 0; i < 8; i++) {
hal::print<4>(terminal, "%x", gpis[i].level());
}
hal::delay(steady_clock, 500ms);
}
}
50 changes: 50 additions & 0 deletions demos/applications/tla2528_output_pin.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#include <array>

#include <libhal-expander/tla2528.hpp>
#include <libhal-expander/tla2528_adapters.hpp>
#include <libhal-util/bit.hpp>
#include <libhal-util/i2c.hpp>
#include <libhal-util/serial.hpp>
#include <libhal-util/steady_clock.hpp>
#include <libhal/output_pin.hpp>
#include <libhal/units.hpp>

#include <resource_list.hpp>

using namespace hal::literals;
using namespace std::chrono_literals;

void application(resource_list& p_map)
{
constexpr bool demo_open_drain = false;

auto& terminal = *p_map.console.value();
auto& i2c = *p_map.i2c.value();
auto& steady_clock = *p_map.clock.value();
auto gpo_expander = hal::expander::tla2528(i2c);
constexpr hal::output_pin::settings output_pin_config = {
.resistor = hal::pin_resistor::none, .open_drain = demo_open_drain
};
std::array<hal::expander::tla2528_output_pin, 8> gpos{
make_output_pin(gpo_expander, 0, output_pin_config),
make_output_pin(gpo_expander, 1, output_pin_config),
make_output_pin(gpo_expander, 2, output_pin_config),
make_output_pin(gpo_expander, 3, output_pin_config),
make_output_pin(gpo_expander, 4, output_pin_config),
make_output_pin(gpo_expander, 5, output_pin_config),
make_output_pin(gpo_expander, 6, output_pin_config),
make_output_pin(gpo_expander, 7, output_pin_config)
};

// output counts in binary to go though all out put combinations
hal::byte counter = 0;
hal::print(terminal, "Starting Binary Count\n");
while (true) {
counter++;
for (int i = 0; i < 8; i++) {
gpos[i].level(hal::bit_extract(hal::bit_mask::from(i), counter));
}
hal::print<16>(terminal, "count:%x\n", counter);
hal::delay(steady_clock, 200ms);
}
}
150 changes: 150 additions & 0 deletions include/libhal-expander/tla2528.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
#pragma once
#include <libhal/i2c.hpp>
#include <libhal/steady_clock.hpp>
#include <libhal/units.hpp>

namespace hal::expander {

// adapters
class tla2528_adc;
class tla2528_input_pin;
class tla2528_output_pin;

/**
* @brief tla2528 is a gpio expander & adc mux driver
*
* tla2528 has 8 pins which can be independently operated as an adc,
* digital input, and digital out over i2c. The i2c address is configured by
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* digital input, and digital out over i2c. The i2c address is configured by
* input pin, and digital pin over i2c. The i2c address is configured by

Might as well use the same terminology we use for the interfaces.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which interface are you referring to?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hal::input_pin and hal::output_pin so you can use the terms input pin and output pin which assume they are digital.

* resistors connected to the chip There are no options for internal pull up or
* pull down resistors. The output pins have the option of push-pull or
* open-drain. When in adc mode there is an option (UNIMPLEMENTED) to increase
* reading granularity though sampling averaging.
*/
class tla2528
{
public:
enum class pin_mode : hal::byte
{
analog_input,
digital_input,
digital_output_open_drain,
digital_output_push_pull
};

/**
* @param p_i2c i2c bus of the device
*
* @param p_i2c_address i2c address configured on the tla, by default is set
* to the i2c address of no resistors attached to address config pins.
*/
tla2528(hal::i2c& p_i2c, hal::byte p_i2c_address = default_address);

/**
* @brief set what service a pin will provide
*
* @param p_mode tla2528::pin_mode enum of desired pin mode
* @param p_channel which pin to configure
* @throws hal::argument_out_of_domain - if p_channel out of range (>7)
* @throws hal::resource_unavailable_try_again - if adapters are made for a
* pin an exception may be thrown to prevent invalid behavior
*/
void set_pin_mode(pin_mode p_mode, hal::byte p_channel);

/**
* @brief set digital output level pin
*
* The device will write to a register that caches the desired output state.
* When a pin is in a digital output mode it will reference the desired state
* cache. If the pin is not set to digital output it will just be stored.
*
* @param p_channel pin to set output
* @param p_high the output level of the pin, true is high, false is low.
* @throws hal::argument_out_of_domain - if p_channel out of range (>7)
*/
void set_digital_out(hal::byte p_channel, bool p_high);
/**
* @brief set digital output levels on all pins
*
* The device will write to a register that caches the desired output state.
* When a pin is in a digital output mode it will reference the desired state
* cache. If the pin is not set to digital output it will just be stored.
*
* @param p_values The byte is used as a bit field of bool values to set the
* pin outputs. i.e the 0th bit in the byte will set the 0 pin. If a bit is
* 1 it is high. If the bit is 0 it is low.
*/
void set_digital_bus_out(hal::byte p_values);
/**
* @brief read digital output state register of an output pin
*
* @param p_channel pin you would like to get the ouput value
* @return true if the pin's output value register is high. If a pin is not set to
* output pin the returned state will be used once it changes to an output
* pin.
* @throws hal::argument_out_of_domain - if p_channel out of range (>7)
*/
bool get_digital_out(hal::byte p_channel);
/**
* @brief read digital output state register of all output pins
*
* @return The byte is used as a bit field of bool values to give the pins'
* register. i.e the 0th bit in the byte will be the 0 pin's stored value. If
* a bit is 1 it is high. If the bit is 0 it is low. If the pin is not set to
* output pin the returned state will be used once it changes to an output
* pin.
*/
hal::byte get_digital_bus_out();

/**
* @brief read the digital level of a pins
*
* @return true if the pin's digital read value is high. If a pin is not set to
* digital input or output the returned value may not correlate with the true
* value.
* @throws hal::argument_out_of_domain - if p_channel out of range. (>7)
*
*/
bool get_digital_in(hal::byte p_channel);
/**
* @brief read the digital levels of all pins
*
* @return The byte is used as a bit field of bool values to give the pins'
* digital read values. i.e the 0th bit in the byte will be the 0 pin's stored
* value. If a bit is 1 it is high. If the bit is 0 it is low. If the pin is
* not set to digital input or output the returned value may not correlate
* with the true value.
*
*/
hal::byte get_digital_bus_in();

/**
* @brief read the analog input of a pin.
*
* @param p_channel if out of range (>7) an exception will be thrown
* @return adc reading as a float between 0 and 1 inclusive. If the pin is not
* set to analog input the returned value may not correlate with the true
* value.
* @throws hal::argument_out_of_domain - if p_channel out of range. (>7)
*/
float get_analog_in(hal::byte p_channel);

friend tla2528_adc;
friend tla2528_input_pin;
friend tla2528_output_pin;

private:
// i2c address for no resistors
static constexpr hal::byte default_address = 0x10;

void set_analog_channel(hal::byte p_channel);
void throw_if_invalid_channel(hal::byte p_channel);
void throw_if_channel_occupied(hal::byte p_channel);
void reset();

hal::i2c& m_i2c_bus;
hal::byte m_i2c_address;
hal::byte m_channel = 0x08; // stores selected channel to reduce i2c requests
hal::byte m_object_created = 0x00; // tracks adapter channel reservations
hal::byte m_gpo_value = 0x00;
};
} // namespace hal::expander
99 changes: 99 additions & 0 deletions include/libhal-expander/tla2528_adapters.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#pragma once
#include <libhal-expander/tla2528.hpp>
#include <libhal/adc.hpp>
#include <libhal/input_pin.hpp>
#include <libhal/output_pin.hpp>

namespace hal::expander {

class tla2528_output_pin : public hal::output_pin
{
public:
~tla2528_output_pin();
friend tla2528_output_pin make_output_pin(
tla2528& p_tla2528,
hal::byte p_channel,
hal::output_pin::settings const& p_settings);

private:
tla2528_output_pin(tla2528& p_tla2528,
hal::byte p_channel,
hal::output_pin::settings const& p_settings);
void driver_configure(settings const& p_settings);
void driver_level(bool p_high);
bool driver_level();
tla2528* m_tla2528 = nullptr;
hal::byte m_channel;
};
/**
* @brief create a hal::output_pin driver using the tla2528 driver
*
* @param p_channel pin acting as an output pin
* @param p_settings output pin settings, default is push-pull and no resistor
* @throws hal::argument_out_of_domain - if p_channel out of range (>7)
* @throws hal::resource_unavailable_try_again - if an adapter has already been
* been made for the pin
* @throws hal::operation_not_supported - if the settings could not be achieved.
*/
tla2528_output_pin make_output_pin(
tla2528& p_tla2528,
hal::byte p_channel,
hal::output_pin::settings const& p_settings = {
.resistor = pin_resistor::none });

class tla2528_input_pin : public hal::input_pin
{
public:
~tla2528_input_pin();
friend tla2528_input_pin make_input_pin(
tla2528& p_tla2528,
hal::byte p_channel,
hal::input_pin::settings const& p_settings);

private:
tla2528_input_pin(tla2528& p_tla2528,
hal::byte p_channel,
hal::input_pin::settings const& p_settings);
void driver_configure(settings const& p_settings);
bool driver_level();
tla2528* m_tla2528 = nullptr;
hal::byte m_channel;
};
/**
* @brief create a hal::input_pin driver using the tla2528 driver
*
* @param p_channel pin acting as an input pin
* @param p_settings input pin settings, default is no resistor
* @throws hal::argument_out_of_domain - if p_channel out of range (>7)
* @throws hal::resource_unavailable_try_again - if an adapter has already been
* been made for the pin
* @throws hal::operation_not_supported - if the settings could not be achieved.
*/
tla2528_input_pin make_input_pin(tla2528& p_tla2528,
hal::byte p_channel,
hal::input_pin::settings const& p_settings = {
.resistor = pin_resistor::none });

class tla2528_adc : public hal::adc
{
public:
~tla2528_adc();
friend tla2528_adc make_adc(tla2528& p_tla2528, hal::byte p_channel);

private:
tla2528_adc(tla2528& p_tla2528, hal::byte p_channel);
float driver_read();
tla2528* m_tla2528 = nullptr;
hal::byte m_channel;
};
/**
* @brief create a hal::adc driver using the tla2528 driver
*
* @param p_channel pin acting as an input pin
* @throws hal::argument_out_of_domain - if p_channel out of range (>7)
* @throws hal::resource_unavailable_try_again - if an adapter has already been
* been made for the pin
*/
tla2528_adc make_adc(tla2528& p_tla2528, hal::byte p_channel);

} // namespace hal::expander
Loading
Loading