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

SNS - Analogue Mux #98

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 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
12 changes: 6 additions & 6 deletions lib/sensors/accelerometer.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#pragma once

#include "i2c_sensors.hpp"
#include "mux_sensors.hpp"

#include <unistd.h>

Expand All @@ -21,12 +21,16 @@ constexpr std::string_view kAxisLabels[3] = {"x-axis", "y-axis", "z-axis"};
constexpr std::uint8_t kDefaultAccelerometerAddress = 0x19;
constexpr std::uint8_t kAlternativeAccelerometerAddress = 0x18;

class Accelerometer : public II2cMuxSensor<core::RawAccelerationData> {
class Accelerometer : public IMuxSensor<core::RawAccelerationData> {
public:
static std::optional<Accelerometer> create(core::ILogger &logger,
std::shared_ptr<io::II2c> i2c,
const std::uint8_t channel,
const std::uint8_t device_address);
Accelerometer(core::ILogger &logger,
std::shared_ptr<io::II2c> i2c,
const std::uint8_t channel,
const std::uint8_t device_address);
~Accelerometer();

/*
Expand All @@ -42,10 +46,6 @@ class Accelerometer : public II2cMuxSensor<core::RawAccelerationData> {
std::uint8_t getChannel() const;

private:
Accelerometer(core::ILogger &logger,
std::shared_ptr<io::II2c> i2c,
const std::uint8_t channel,
const std::uint8_t device_address);
std::optional<std::int16_t> getRawAcceleration(const core::Axis axis);
std::int32_t getAccelerationFromRawValue(const std::int16_t rawAcceleration);
void setRegisterAddressFromAxis(const core::Axis axis);
Expand Down
143 changes: 143 additions & 0 deletions lib/sensors/analogue_mux.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
#pragma once

#include "mux_sensors.hpp"

#include <array>
#include <bitset>
#include <cstdint>
#include <memory>
#include <optional>

#include <core/logger.hpp>
#include <core/types.hpp>
#include <io/gpio.hpp>

namespace hyped::sensors {

constexpr std::uint8_t kNumSelectorPins = 4;

/**
* @brief Class for analogue mux where channel selection is done by GPIO pins
* @details 16 channels are available, selection is done by 4 GPIO pins (think 4x16 decoder)
*/
template<class T, std::uint8_t N>
class AnalogueMux {
public:
/**
* @brief Creates an AnalogueMux object with specific selector pins and disable input pin
* @details The disable input pin is just the active low enable input pin (from datasheet)
* @param selector_pin_writers Array of 4 GPIO pin writers in the order s0, s1, s2 and s3
* @param disable_input_writer GPIO pin corresponding to E bar (active low enable input pin)
*/
static std::optional<std::shared_ptr<AnalogueMux<T, N>>> create(
core::ILogger &logger,
std::array<std::shared_ptr<io::IGpioWriter>, kNumSelectorPins> selector_pin_writers,
std::shared_ptr<io::IGpioWriter> disable_input_writer,
std::array<std::unique_ptr<IMuxSensor<T>>, N> &sensors);
AnalogueMux(core::ILogger &logger,
std::array<std::shared_ptr<io::IGpioWriter>, kNumSelectorPins> selector_pins,
std::shared_ptr<io::IGpioWriter> disable_input_writer,
std::array<std::unique_ptr<IMuxSensor<T>>, N> &sensors);
~AnalogueMux();

std::optional<std::array<T, N>> readAllChannels();

private:
core::Result selectChannel(const std::uint8_t channel);
core::Result closeAllChannels();

private:
core::ILogger &logger_;
const std::array<std::shared_ptr<io::IGpioWriter>, kNumSelectorPins> selector_pin_writers_;
const std::shared_ptr<io::IGpioWriter> disable_input_writer_;
const std::array<std::unique_ptr<IMuxSensor<T>>, N> sensors_;
};

template<typename T, std::uint8_t N>
std::optional<std::shared_ptr<AnalogueMux<T, N>>> AnalogueMux<T, N>::create(
core::ILogger &logger,
std::array<std::shared_ptr<io::IGpioWriter>, kNumSelectorPins> selector_pin_writers,
std::shared_ptr<io::IGpioWriter> disable_input_writer,
std::array<std::unique_ptr<IMuxSensor<T>>, N> &sensors)
{
if (N > 16) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Comment or constexpr explaining the 16

logger.log(core::LogLevel::kFatal,
"Failed to create AnalogueMux instance, maximum 16 channels only");
return std::nullopt;
}
return std::make_shared<AnalogueMux<T, N>>(
logger, selector_pin_writers, disable_input_writer, sensors);
}

template<typename T, std::uint8_t N>
AnalogueMux<T, N>::AnalogueMux(
core::ILogger &logger,
std::array<std::shared_ptr<io::IGpioWriter>, kNumSelectorPins> selector_pin_writers,
std::shared_ptr<io::IGpioWriter> disable_input_writer,
std::array<std::unique_ptr<IMuxSensor<T>>, N> &sensors)
: logger_(logger),
selector_pin_writers_(std::move(selector_pin_writers)),
sensors_(std::move(sensors))
{
}

template<typename T, std::uint8_t N>
AnalogueMux<T, N>::~AnalogueMux()
{
}

template<typename T, std::uint8_t N>
std::optional<std::array<T, N>> AnalogueMux<T, N>::readAllChannels()
{
// zero-initialize the array
std::array<T, N> values{};
for (std::size_t i = 0; i < N; ++i) {
const auto &sensor = sensors_.at(i).get();
std::uint8_t channel = sensor->getChannel();
// ensuring correct channel is selected
core::Result channel_select_result = selectChannel(channel);
if (channel_select_result == core::Result::kFailure) {
logger_.log(core::LogLevel::kFatal, "Failed to select channel %d for analogue mux", i);
return std::nullopt;
}
// then read sensor data
const auto sensor_data = sensor->read();
if (!sensor_data) {
logger_.log(
core::LogLevel::kFatal, "Failed to read from sensor on channel %d on analogue mux", i);
return std::nullopt;
}
// finally store the data
values.at(i) = *sensor_data;
}
return values;
}

template<typename T, std::uint8_t N>
core::Result AnalogueMux<T, N>::selectChannel(const std::uint8_t channel)
{
std::bitset binary_selector = std::bitset<4>(channel);
for (std::size_t i = 0; i < kNumSelectorPins; ++i) {
core::Result write_result = selector_pin_writers_[i]->write(
binary_selector.test(i) ? core::DigitalSignal::kHigh : core::DigitalSignal::kLow);
if (write_result == core::Result::kFailure) {
logger_.log(
core::LogLevel::kFatal, "Failed to write to selector GPIO pin %d for analogue mux", i);
return core::Result::kFailure;
}
}
return core::Result::kSuccess;
}

template<typename T, std::uint8_t N>
core::Result AnalogueMux<T, N>::closeAllChannels()
{
core::Result write_result = disable_input_writer_->write(core::DigitalSignal::kHigh);
if (write_result == core::Result::kFailure) {
logger_.log(core::LogLevel::kFatal, "Failed to disable analogue mux");
return core::Result::kFailure;
}
return core::Result::kSuccess;
}

} // namespace hyped::sensors
48 changes: 36 additions & 12 deletions lib/sensors/i2c_mux.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#pragma once

#include "i2c_sensors.hpp"
#include "mux_sensors.hpp"

#include <array>
#include <cstdint>
Expand All @@ -13,10 +13,7 @@

namespace hyped::sensors {

constexpr std::uint8_t kDefaultMuxAddress = 0x70;
constexpr std::uint8_t kMaxNumMuxChannels = 8;
// percentage of sensors that can be unusable before the mux is considered unusable
constexpr core::Float kFailureThreshold = 0.25; // TODOLater: finalize this value with Electronics
constexpr std::uint8_t kDefaultI2cMuxAddress = 0x70;

/**
* @brief Mux for sensors using I2C
Expand All @@ -26,10 +23,16 @@ constexpr core::Float kFailureThreshold = 0.25; // TODOLater: finalize this val
template<class T, std::uint8_t N>
class I2cMux {
public:
static std::optional<std::shared_ptr<I2cMux<T, N>>> create(
core::ILogger &logger,
std::shared_ptr<io::II2c> i2c,
const std::uint8_t mux_address,
std::array<std::unique_ptr<IMuxSensor<T>>, N> &sensors);

I2cMux(core::ILogger &logger,
std::shared_ptr<io::II2c> i2c,
const std::uint8_t mux_address,
std::array<std::unique_ptr<II2cMuxSensor<T>>, N> &sensors);
std::array<std::unique_ptr<IMuxSensor<T>>, N> &sensors);
~I2cMux();

std::optional<std::array<T, N>> readAllChannels();
Expand All @@ -38,25 +41,46 @@ class I2cMux {
core::Result selectChannel(const std::uint8_t channel);
core::Result closeAllChannels();

private:
core::ILogger &logger_;
std::shared_ptr<io::II2c> i2c_;
const std::uint8_t mux_address_;
const std::array<std::unique_ptr<II2cMuxSensor<T>>, N> sensors_;
const std::array<std::unique_ptr<IMuxSensor<T>>, N> sensors_;
const std::uint8_t max_num_unusable_sensors_;

private:
static constexpr std::uint8_t kMaxNumI2cMuxChannels = 8;
// percentage of sensors that can be unusable before the mux is considered unusable
static constexpr core::Float kFailureThreshold
= 0.25; // TODOLater: finalize this value with Electronics
};

template<typename T, std::uint8_t N>
std::optional<std::shared_ptr<I2cMux<T, N>>> I2cMux<T, N>::create(
core::ILogger &logger,
std::shared_ptr<io::II2c> i2c,
const std::uint8_t mux_address,
std::array<std::unique_ptr<IMuxSensor<T>>, N> &sensors)
{
if (N > 8) {
logger.log(core::LogLevel::kFatal,
"Failed to create I2c mux instance, maximum 8 channels only");
return std::nullopt;
}
return std::make_shared<I2cMux<T, N>>(logger, i2c, mux_address, sensors);
}

template<typename T, std::uint8_t N>
I2cMux<T, N>::I2cMux(core::ILogger &logger,
std::shared_ptr<io::II2c> i2c,
const std::uint8_t mux_address,
std::array<std::unique_ptr<II2cMuxSensor<T>>, N> &sensors)
std::array<std::unique_ptr<IMuxSensor<T>>, N> &sensors)
: logger_(logger),
i2c_(i2c),
mux_address_(mux_address),
sensors_(std::move(sensors)),
max_num_unusable_sensors_(static_cast<std::uint8_t>(kFailureThreshold * N))
{
static_assert(N <= 8, "The I2c mux can only have up to 8 channels");
}

template<typename T, std::uint8_t N>
Expand All @@ -71,7 +95,7 @@ std::optional<std::array<T, N>> I2cMux<T, N>::readAllChannels()
std::array<T, N> mux_data{};
std::uint8_t num_unusable_sensors = 0;
for (std::uint8_t i = 0; i < N; ++i) {
const auto &sensor = sensors_.at(i);
const auto &sensor = sensors_.at(i).get();
const std::uint8_t channel = sensor->getChannel();
// First ensure correct channel is selected
const auto channel_select_result = selectChannel(channel);
Expand Down Expand Up @@ -106,7 +130,7 @@ std::optional<std::array<T, N>> I2cMux<T, N>::readAllChannels()
template<typename T, std::uint8_t N>
core::Result I2cMux<T, N>::selectChannel(const std::uint8_t channel)
{
if (channel >= kMaxNumMuxChannels) {
if (channel >= kMaxNumI2cMuxChannels) {
logger_.log(core::LogLevel::kFatal, "I2c Mux Channel number %d is not selectable", channel);
return core::Result::kFailure;
}
Expand All @@ -133,4 +157,4 @@ core::Result I2cMux<T, N>::closeAllChannels()
return core::Result::kSuccess;
}

} // namespace hyped::sensors
} // namespace hyped::sensors
6 changes: 3 additions & 3 deletions lib/sensors/i2c_sensors.hpp → lib/sensors/mux_sensors.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@
namespace hyped::sensors {

/**
* If a sensor is to be used with an I2C mux, it must inherit from this abstract class.
* If a sensor is to be used with any mux, it must inherit from this abstract class.
*/
template<typename T>
class II2cMuxSensor {
class IMuxSensor {
public:
/*
* This function carries out the initilization steps for a particular sensor.
*/
virtual std::optional<T> read() = 0;
virtual std::uint8_t getChannel() const = 0;
virtual ~II2cMuxSensor() {}
virtual ~IMuxSensor() {}
};

} // namespace hyped::sensors
4 changes: 2 additions & 2 deletions lib/sensors/temperature.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#pragma once

#include "i2c_sensors.hpp"
#include "mux_sensors.hpp"

#include <cstdint>
#include <memory>
Expand All @@ -15,7 +15,7 @@ namespace hyped::sensors {
constexpr std::uint8_t kDefaultTemperatureAddress = 0x38;
constexpr std::uint8_t kAlternativeTemperatureAddress = 0x3F;

class Temperature : public II2cMuxSensor<std::int16_t> {
class Temperature : public IMuxSensor<std::int16_t> {
public:
static std::optional<Temperature> create(core::ILogger &logger,
std::shared_ptr<io::II2c> i2c,
Expand Down
24 changes: 24 additions & 0 deletions lib/utils/dummy_adc_mux_sensor.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#include "dummy_adc_mux_sensor.hpp"

namespace hyped::utils {

DummyAdcMuxSensor::DummyAdcMuxSensor()
{
}

core::Result DummyAdcMuxSensor::configure()
{
return core::Result::kSuccess;
}

std::optional<core::Float> DummyAdcMuxSensor::read()
{
return 0;
}

std::uint8_t DummyAdcMuxSensor::getChannel() const
{
return 0;
}

} // namespace hyped::utils
15 changes: 15 additions & 0 deletions lib/utils/dummy_adc_mux_sensor.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#pragma once

#include <sensors/mux_sensors.hpp>

namespace hyped::utils {

class DummyAdcMuxSensor : public sensors::IMuxSensor<core::Float> {
public:
DummyAdcMuxSensor();
virtual core::Result configure();
virtual std::optional<core::Float> read();
virtual std::uint8_t getChannel() const;
};

} // namespace hyped::utils
4 changes: 2 additions & 2 deletions lib/utils/dummy_i2c_sensor.hpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
#pragma once

#include <sensors/i2c_sensors.hpp>
#include <sensors/mux_sensors.hpp>

namespace hyped::utils {

class DummyI2cSensor : public sensors::II2cMuxSensor<std::uint8_t> {
class DummyI2cSensor : public sensors::IMuxSensor<std::uint8_t> {
public:
DummyI2cSensor();
virtual core::Result configure();
Expand Down
Loading