first commit

This commit is contained in:
stuce-bot 2025-06-30 20:47:33 +02:00
commit 5893b00dd2
1669 changed files with 1982740 additions and 0 deletions

View file

@ -0,0 +1,395 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file M5UnitComponent.cpp
@brief Base class for Unit Component
*/
#include "M5UnitComponent.hpp"
#include <M5Utility.hpp>
#include <algorithm>
#include <array>
using namespace m5::unit::types;
namespace m5 {
namespace unit {
const char Component::name[] = "";
const types::uid_t Component::uid{};
const types::attr_t Component::attr{};
Component::Component(const uint8_t addr) : _adapter{new Adapter()}, _addr{addr}
{
}
size_t Component::childrenSize() const
{
size_t sz{};
auto it = childBegin();
while (it != childEnd()) {
++sz;
++it;
}
return sz;
}
bool Component::existsChild(const uint8_t ch) const
{
if (!_child) {
return false;
}
return std::any_of(childBegin(), childEnd(), [&ch](const Component& c) { return ch == c.channel(); });
}
bool Component::canAccessI2C() const
{
return attribute() & attribute::AccessI2C;
}
bool Component::canAccessGPIO() const
{
return attribute() & attribute::AccessGPIO;
}
bool Component::add(Component& c, const int16_t ch)
{
if (childrenSize() >= _component_cfg.max_children) {
M5_LIB_LOGE("Can't connect any more");
return false;
}
if (existsChild(ch)) {
M5_LIB_LOGE("Already connected an other unit at channel:%u", ch);
return false;
}
if (isRegistered()) {
M5_LIB_LOGE(
"As the parent unit is already registered with the UnitUnified, no additional children can be added");
return false;
}
if (c.isRegistered()) {
M5_LIB_LOGE("Children already registered with UnitUnified cannot be added");
return false;
}
if (!add_child(&c)) {
return false;
}
c._channel = ch;
return true;
}
bool Component::add_child(Component* c)
{
if (!c || c->_parent || c->_prev || c->_next) {
M5_LIB_LOGE("Invalid child [%s] %p / %p / %p", c ? c->deviceName() : "null", c ? c->_parent : nullptr,
c ? c->_next : nullptr, c ? c->_prev : nullptr);
return false;
}
// Add to tail
if (!_child) {
_child = c;
} else {
auto last = _child;
while (last->_next) {
last = last->_next;
}
last->_next = c;
c->_prev = last;
}
c->_parent = this;
return true;
}
Component* Component::child(const uint8_t ch) const
{
auto it = childBegin();
while (it != childEnd()) {
if (it->channel() == ch) {
return const_cast<Component*>(&*it);
}
++it;
}
return nullptr;
}
bool Component::assign(m5::hal::bus::Bus* bus)
{
if (_addr) {
_adapter = std::make_shared<AdapterI2C>(bus, _addr, _component_cfg.clock);
}
return static_cast<bool>(_adapter);
}
bool Component::assign(TwoWire& wire)
{
if (canAccessI2C() && _addr) {
_adapter = std::make_shared<AdapterI2C>(wire, _addr, _component_cfg.clock);
}
return static_cast<bool>(_adapter);
}
bool Component::assign(const int8_t rx_pin, const int8_t tx_pin)
{
if (canAccessGPIO()) {
_adapter = std::make_shared<AdapterGPIO>(rx_pin, tx_pin);
}
return static_cast<bool>(_adapter);
}
bool Component::selectChannel(const uint8_t ch)
{
bool ret{true};
if (hasParent()) {
ret = _parent->selectChannel(channel());
}
return ret && (select_channel(ch) == m5::hal::error::error_t::OK);
}
m5::hal::error::error_t Component::readWithTransaction(uint8_t* data, const size_t len)
{
selectChannel(channel());
auto r = adapter()->readWithTransaction(data, len);
return r;
}
m5::hal::error::error_t Component::writeWithTransaction(const uint8_t* data, const size_t len, const uint32_t exparam)
{
selectChannel(channel());
return adapter()->writeWithTransaction(data, len, exparam);
}
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type>
m5::hal::error::error_t Component::writeWithTransaction(const Reg reg, const uint8_t* data, const size_t len,
const bool stop)
{
selectChannel(channel());
return adapter()->writeWithTransaction(reg, data, len, stop);
}
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type>
bool Component::readRegister(const Reg reg, uint8_t* rbuf, const size_t len, const uint32_t delayMillis,
const bool stop)
{
if (!writeRegister(reg, nullptr, 0U, stop)) {
M5_LIB_LOGE("Failed to write");
return false;
}
m5::utility::delay(delayMillis);
return (readWithTransaction(rbuf, len) == m5::hal::error::error_t::OK);
}
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type>
bool Component::readRegister8(const Reg reg, uint8_t& result, const uint32_t delayMillis, const bool stop)
{
return readRegister(reg, &result, 1, delayMillis, stop);
}
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type>
bool Component::read_register16E(const Reg reg, uint16_t& result, const uint32_t delayMillis, const bool stop,
const bool endian)
{
uint8_t tmp[2]{};
auto ret = readRegister(reg, tmp, 2, delayMillis, stop);
if (ret) {
result = (tmp[!endian] << 8) | tmp[endian];
}
return ret;
}
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type>
bool Component::read_register32E(const Reg reg, uint32_t& result, const uint32_t delayMillis, const bool stop,
const bool endian)
{
uint8_t tmp[4]{};
auto ret = readRegister(reg, tmp, 4, delayMillis, stop);
if (ret) {
result = (tmp[0 + 3 * endian] | (tmp[1 + endian] << 8) | (tmp[2 - endian] << 16)) | (tmp[3 - 3 * endian] << 24);
}
return ret;
}
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type>
bool Component::writeRegister(const Reg reg, const uint8_t* buf, const size_t len, const bool stop)
{
return (sizeof(Reg) == 2
? writeWithTransaction(reg, buf, len, stop)
: writeWithTransaction((uint8_t)(reg & 0xFF), buf, len, stop)) == m5::hal::error::error_t::OK;
}
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type>
bool Component::writeRegister8(const Reg reg, const uint8_t value, const bool stop)
{
return writeRegister(reg, &value, 1, stop);
}
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type>
bool Component::write_register16E(const Reg reg, const uint16_t value, const bool stop, const bool endian)
{
uint8_t tmp[2]{};
tmp[endian] = value & 0xFF;
tmp[!endian] = (value >> 8) & 0xFF;
return writeRegister(reg, tmp, 2, stop);
}
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type>
bool Component::write_register32E(const Reg reg, const uint32_t value, const bool stop, const bool endian)
{
uint8_t tmp[4]{};
tmp[0 + endian * 3] = value & 0xFF;
tmp[1 + endian] = (value >> 8) & 0xFF;
tmp[2 - endian] = (value >> 16) & 0xFF;
tmp[3 - endian * 3] = (value >> 24) & 0xFF;
return writeRegister(reg, tmp, 4, stop);
}
bool Component::generalCall(const uint8_t* data, const size_t len)
{
return adapter()->generalCall(data, len) == m5::hal::error::error_t::OK;
}
bool Component::pinModeRX(const gpio::Mode m)
{
return adapter()->pinModeRX(m) == m5::hal::error::error_t::OK;
}
bool Component::writeDigitalRX(const bool high)
{
return adapter()->writeDigitalRX(high) == m5::hal::error::error_t::OK;
}
bool Component::readDigitalRX(bool& high)
{
return adapter()->readDigitalRX(high) == m5::hal::error::error_t::OK;
}
bool Component::writeAnalogRX(const uint16_t v)
{
return adapter()->writeAnalogRX(v) == m5::hal::error::error_t::OK;
}
bool Component::readAnalogRX(uint16_t& v)
{
return adapter()->readAnalogRX(v) == m5::hal::error::error_t::OK;
}
bool Component::pulseInRX(uint32_t& duration, const int state, const uint32_t timeout_us)
{
return adapter()->pulseInRX(duration, state, timeout_us) == m5::hal::error::error_t::OK;
}
bool Component::pinModeTX(const gpio::Mode m)
{
return adapter()->pinModeTX(m) == m5::hal::error::error_t::OK;
}
bool Component::writeDigitalTX(const bool high)
{
return adapter()->writeDigitalTX(high) == m5::hal::error::error_t::OK;
}
bool Component::readDigitalTX(bool& high)
{
return adapter()->readDigitalTX(high) == m5::hal::error::error_t::OK;
}
bool Component::writeAnalogTX(const uint16_t v)
{
return adapter()->writeAnalogTX(v) == m5::hal::error::error_t::OK;
}
bool Component::readAnalogTX(uint16_t& v)
{
return adapter()->readAnalogTX(v) == m5::hal::error::error_t::OK;
}
bool Component::pulseInTX(uint32_t& duration, const int state, const uint32_t timeout_us)
{
return adapter()->pulseInTX(duration, state, timeout_us) == m5::hal::error::error_t::OK;
}
bool Component::changeAddress(const uint8_t addr)
{
if (canAccessI2C() && m5::utility::isValidI2CAddress(addr)) {
auto ad = asAdapter<AdapterI2C>(Adapter::Type::I2C);
if (ad) {
M5_LIB_LOGI("Change to address %x", addr);
_addr = addr;
ad->setAddress(addr);
return true;
}
}
M5_LIB_LOGE("Failed to change, %u, %x", canAccessI2C(), addr);
return false;
}
std::string Component::debugInfo() const
{
std::string tmp{};
switch (_adapter->type()) {
case Adapter::Type::I2C:
tmp = m5::utility::formatString("%p:%u ADDR:%02X", _adapter.get(), _adapter.use_count(),
asAdapter<AdapterI2C>(Adapter::Type::I2C)->address());
break;
case Adapter::Type::GPIO:
tmp = m5::utility::formatString("%p:%u RX:%d TX:%d", _adapter.get(), _adapter.use_count(),
asAdapter<AdapterGPIO>(Adapter::Type::GPIO)->rx_pin(),
asAdapter<AdapterGPIO>(Adapter::Type::GPIO)->tx_pin());
break;
default:
tmp = m5::utility::formatString("%p:%u Type:%d", _adapter.get(), _adapter.use_count(), _adapter->type());
break;
}
return m5::utility::formatString("[%s]:ID{0X%08x}:%s CH:%d parent:%u children:%zu/%u", deviceName(), identifier(),
tmp.c_str(), channel(), hasParent(), childrenSize(), _component_cfg.max_children);
}
// Explicit template instantiation
template bool Component::readRegister<uint8_t>(const uint8_t, uint8_t*, const size_t, const uint32_t, const bool);
template bool Component::readRegister<uint16_t>(const uint16_t, uint8_t*, const size_t, const uint32_t, const bool);
template bool Component::readRegister8<uint8_t>(const uint8_t, uint8_t&, const uint32_t, const bool);
template bool Component::readRegister8<uint16_t>(const uint16_t, uint8_t&, const uint32_t, const bool);
template bool Component::read_register16E<uint8_t>(const uint8_t, uint16_t&, const uint32_t, const bool, const bool);
template bool Component::read_register16E<uint16_t>(const uint16_t, uint16_t&, const uint32_t, const bool, const bool);
template bool Component::read_register32E<uint8_t>(const uint8_t, uint32_t&, const uint32_t, const bool, const bool);
template bool Component::read_register32E<uint16_t>(const uint16_t, uint32_t&, const uint32_t, const bool, const bool);
template bool Component::writeRegister<uint8_t>(const uint8_t, const uint8_t*, const size_t, const bool);
template bool Component::writeRegister<uint16_t>(const uint16_t, const uint8_t*, const size_t, const bool);
template bool Component::writeRegister8<uint8_t>(const uint8_t, const uint8_t, const bool);
template bool Component::writeRegister8<uint16_t>(const uint16_t, const uint8_t, const bool);
template bool Component::write_register16E<uint8_t>(const uint8_t, const uint16_t, const bool, const bool);
template bool Component::write_register16E<uint16_t>(const uint16_t, const uint16_t, const bool, const bool);
template bool Component::write_register32E<uint8_t>(const uint8_t, const uint32_t, const bool, const bool);
template bool Component::write_register32E<uint16_t>(const uint16_t, const uint32_t, const bool, const bool);
template m5::hal::error::error_t Component::writeWithTransaction<uint8_t>(const uint8_t reg, const uint8_t* data,
const size_t len, const bool stop);
template m5::hal::error::error_t Component::writeWithTransaction<uint16_t>(const uint16_t reg, const uint8_t* data,
const size_t len, const bool stop);
} // namespace unit
} // namespace m5

View file

@ -0,0 +1,13 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
#ifndef M5_UNIT_COMPONENT_H
#define M5_UNIT_COMPONENT_H
#ifdef __cplusplus
#include "M5UnitComponent.hpp"
#else
#error M5UnitComponent requires a C++ compiler, please change file extension to .cc or .cpp
#endif
#endif

View file

@ -0,0 +1,754 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file M5UnitComponent.hpp
@brief Main header of M5UnitComponent
*/
#ifndef M5_UNIT_COMPONENT_HPP
#define M5_UNIT_COMPONENT_HPP
#include "m5_unit_component/types.hpp"
#include "m5_unit_component/adapter.hpp"
#include <cstdint>
#include <vector>
#include <algorithm>
#include <iterator>
#include <type_traits>
#include <memory>
class TwoWire;
namespace m5 {
namespace unit {
class UnitUnified;
class Adapter;
/*!
@class m5::unit::Component
@brief Base class of unit component
*/
class Component {
public:
/*!
@struct component_config_t
@brief Component basic settings for begin
*/
struct component_config_t {
//! Clock for communication (default as 100000)
uint32_t clock{100000};
//! Maximum number of periodic measurement data to be stored
uint32_t stored_size{1};
//! Does the user call Unit's update? (default as false)
bool self_update{false};
//! Maximum number of units that can be connected (default as 0)
uint8_t max_children{0};
};
///@warning Define the same name and type in the derived class.
///@name Fixed parameters for class
///@{
static const types::uid_t uid; //!< @brief Unique identifier
static const types::attr_t attr; //!< @brief Attributes
static const char name[]; //!< @brief Device name string
///@}
///@warning COPY PROHIBITED
///@name Constructor
///@{
explicit Component(const uint8_t addr = 0x00); // I2C address
Component(const Component&) = delete;
Component(Component&&) noexcept = default;
///@}
///@warning COPY PROHIBITED
///@name Assignment
///@{
Component& operator=(const Component&) = delete;
Component& operator=(Component&&) noexcept = default;
///@}
virtual ~Component() = default;
///@name Component settings
///@{
/*! @brief Gets the common configurations in each unit */
inline component_config_t component_config()
{
return _component_cfg;
}
//! @brief Set the common configurations in each unit
inline void component_config(const component_config_t& cfg)
{
_component_cfg = cfg;
}
///@}
///@name Functions that must be inherited
///@{
/*!
@brief Begin unit
@details Initiate functions based on component config and unit config
*/
virtual bool begin()
{
return true;
}
/*!
@brief Update unit
@param force Forced communication for updates if true
*/
virtual void update(const bool force = false)
{
(void)force;
}
///@}
///@name Properties
///@{
/*! @brief Gets the device name */
inline const char* deviceName() const
{
return unit_device_name();
}
//! @brief Gets the identifier
inline types::uid_t identifier() const
{
return unit_identifier();
}
//! @brief Gets the attributes
inline types::attr_t attribute() const
{
return unit_attribute();
}
//! @brief Gets the category
inline types::category_t category() const
{
return unit_category();
}
//! @brief Gets the registered order (== 0 means not yet)
inline uint32_t order() const
{
return _order;
}
//! @brief Gets the channel if connected to another unit
inline int16_t channel() const
{
return _channel;
}
//! @brief Is the unit registered with the manager?
inline bool isRegistered() const
{
return _manager != nullptr;
}
//! @brief Address used to I2C access the device
inline uint8_t address() const
{
return _addr;
}
/*!
@brief Gets the access adapter
@warning Ownership is retained by the unit and should not be released
*/
inline Adapter* adapter() const
{
return _adapter.get();
}
//! @brief Gets the access adapter
template <class T>
inline auto asAdapter(const Adapter::Type t) ->
typename std::remove_cv<typename std::remove_pointer<T>::type>::type*
{
using U = typename std::remove_cv<typename std::remove_pointer<T>::type>::type;
static_assert(std::is_base_of<Adapter, U>::value, "T must be derived from Adapter");
return (_adapter->type() == t) ? static_cast<U*>(_adapter.get()) : nullptr;
}
template <class T>
inline auto asAdapter(const Adapter::Type t) const -> const
typename std::remove_cv<typename std::remove_pointer<T>::type>::type*
{
using U = typename std::remove_cv<typename std::remove_pointer<T>::type>::type;
static_assert(std::is_base_of<Adapter, U>::value, "T must be derived from Adapter");
return (_adapter->type() == t) ? static_cast<const U*>(_adapter.get()) : nullptr;
}
///@}
///@name Attributes
///@{
bool canAccessI2C() const;
bool canAccessGPIO() const;
///@}
///@name Periodic measurement
///@{
/*! @brief In periodic measurement? */
inline bool inPeriodic() const
{
return in_periodic();
}
//! @brief Periodic measurement data updated?
inline bool updated() const
{
return _updated;
}
/*!
@brief Time elapsed since start-up when the measurement data was updated in update()
@return Updated time (Unit: ms)
*/
inline types::elapsed_time_t updatedMillis() const
{
return _latest;
}
/*!
@brief Gets the periodic measurement interval
@return interval time (Unit: ms)
*/
inline types::elapsed_time_t interval() const
{
return _interval;
}
///@}
///@name Bus assignment
///@{
/*! @brief Assgin m5::hal::bus */
virtual bool assign(m5::hal::bus::Bus* bus);
/*! @brief Assgin TwoWire */
virtual bool assign(TwoWire& wire);
/*! @brief Assgin GPIO */
virtual bool assign(const int8_t rx_pin, const int8_t tx_pin);
///@}
///@note For daisy-chaining units such as hubs
///@name Parent-children relationship
///@{
/*! @brief Has parent unit? */
inline bool hasParent() const
{
return _parent != nullptr;
}
//! @brief Are there any other devices connected to the same parent unit besides yourself?
inline bool hasSiblings() const
{
return (_prev != nullptr) || (_next != nullptr);
}
//! @brief Are there other devices connected to me?
inline bool hasChildren() const
{
return _child;
}
//! @brief Number of units connected to me
size_t childrenSize() const;
//! @brief Is there an other unit connected to the specified channel?
bool existsChild(const uint8_t ch) const;
//! @brief Gets the parent unit
inline Component* parent()
{
return _parent;
}
#if 0
//! @brief Gets the parent unit
inline const Component* parent() const
{
return _parent;
}
#endif
//! @brief Gets the device connected to the specified channel
Component* child(const uint8_t chhanle) const;
//! @brief Connect the unit to the specified channel
bool add(Component& c, const int16_t channel);
//! @brief Select valid channel if exists
bool selectChannel(const uint8_t ch = 8);
///@}
///@cond 0
template <typename T>
class iterator {
public:
using iterator_category = std::forward_iterator_tag;
using difference_type = std::ptrdiff_t;
using value_type = T;
using pointer = T*;
using reference = T&;
explicit iterator(Component* c = nullptr) : _ptr(c)
{
}
reference operator*() const
{
return *_ptr;
}
pointer operator->() const
{
return _ptr;
}
iterator& operator++()
{
_ptr = _ptr ? _ptr->_next : nullptr;
return *this;
}
iterator operator++(int)
{
auto tmp = *this;
++(*this);
return tmp;
}
friend bool operator==(const iterator& a, const iterator& b)
{
return a._ptr == b._ptr;
}
friend bool operator!=(const iterator& a, const iterator& b)
{
return a._ptr != b._ptr;
}
private:
Component* _ptr;
};
using child_iterator = iterator<Component>;
using const_child_iterator = iterator<const Component>;
inline child_iterator childBegin() noexcept
{
return child_iterator(_child);
}
inline child_iterator childEnd() noexcept
{
return child_iterator();
}
inline const_child_iterator childBegin() const noexcept
{
return const_child_iterator(_child);
}
inline const_child_iterator childEnd() const noexcept
{
return const_child_iterator();
}
///@endcond
/*! @brief General call for I2C*/
bool generalCall(const uint8_t* data, const size_t len);
//! @brief Output information for debug
virtual std::string debugInfo() const;
// I2C R/W
///@cond 0
m5::hal::error::error_t readWithTransaction(uint8_t* data, const size_t len);
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type = nullptr>
bool readRegister(const Reg reg, uint8_t* rbuf, const size_t len, const uint32_t delayMillis,
const bool stop = true);
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type = nullptr>
bool readRegister8(const Reg reg, uint8_t& result, const uint32_t delayMillis, const bool stop = true);
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type = nullptr>
inline bool readRegister16BE(const Reg reg, uint16_t& result, const uint32_t delayMillis, const bool stop = true)
{
return read_register16E(reg, result, delayMillis, stop, true);
}
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type = nullptr>
inline bool readRegister16LE(const Reg reg, uint16_t& result, const uint32_t delayMillis, const bool stop = true)
{
return read_register16E(reg, result, delayMillis, stop, false);
}
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type = nullptr>
inline bool readRegister32BE(const Reg reg, uint32_t& result, const uint32_t delayMillis, const bool stop = true)
{
return read_register32E(reg, result, delayMillis, stop, true);
}
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type = nullptr>
inline bool readRegister32LE(const Reg reg, uint32_t& result, const uint32_t delayMillis, const bool stop = true)
{
return read_register32E(reg, result, delayMillis, stop, false);
}
m5::hal::error::error_t writeWithTransaction(const uint8_t* data, const size_t len, const uint32_t exparam = 1);
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type = nullptr>
m5::hal::error::error_t writeWithTransaction(const Reg reg, const uint8_t* data, const size_t len,
const bool stop = true);
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type = nullptr>
bool writeRegister(const Reg reg, const uint8_t* buf = nullptr, const size_t len = 0U, const bool stop = true);
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type = nullptr>
bool writeRegister8(const Reg reg, const uint8_t value, const bool stop = true);
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type = nullptr>
inline bool writeRegister16BE(const Reg reg, const uint16_t value, const bool stop = true)
{
return write_register16E(reg, value, stop, true);
}
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type = nullptr>
inline bool writeRegister16LE(const Reg reg, const uint16_t value, const bool stop = true)
{
return write_register16E(reg, value, stop, false);
}
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type = nullptr>
inline bool writeRegister32BE(const Reg reg, const uint32_t value, const bool stop = true)
{
return write_register32E(reg, value, stop, true);
}
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type = nullptr>
inline bool writeRegister32LE(const Reg reg, const uint32_t value, const bool stop = true)
{
return write_register32E(reg, value, stop, false);
}
// GPIO
bool pinModeRX(const gpio::Mode m);
bool writeDigitalRX(const bool high);
bool readDigitalRX(bool& high);
bool writeAnalogRX(const uint16_t v);
bool readAnalogRX(uint16_t& v);
bool pulseInRX(uint32_t& duration, const int state, const uint32_t timeout_us = 1000000);
bool pinModeTX(const gpio::Mode m);
bool writeDigitalTX(const bool high);
bool readDigitalTX(bool& high);
bool writeAnalogTX(const uint16_t v);
bool readAnalogTX(uint16_t& v);
bool pulseInTX(uint32_t& duration, const int state, const uint32_t timeout_us = 1000000);
///@endcond
#if defined(DOXYGEN_PROCESS)
// There is a problem with the Doxygen output of templates containing std::enable_if,
// so we need a section for Dxygen output
///@name Read/Write
///@{
//! @brief Read any data with transaction
m5::hal::error::error_t readWithTransaction(uint8_t* data, const size_t len);
//! @brief Read any data with transaction from register
template <typename Reg>
bool readRegister(const Reg reg, uint8_t* rbuf, const size_t len, const uint32_t delayMillis,
const bool stop = true);
//! @brief Read byte with transaction from register
template <typename Reg>
bool readRegister8(const Reg reg, uint8_t& result, const uint32_t delayMillis, const bool stop = true);
//! @brief Read word in big-endian order with transaction from register
template <typename Reg>
bool readRegister16BE(const Reg reg, uint16_t& result, const uint32_t delayMillis, const bool stop = true);
//! @brief Read word in little-endian order with transaction from register
template <typename Reg>
bool readRegister16LE(const Reg reg, uint16_t& result, const uint32_t delayMillis, const bool stop = true);
//! @brief Read dword in big-endian order with transaction from register
template <typename Reg>
bool readRegister32BE(const Reg reg, uint32_t& result, const uint32_t delayMillis, const bool stop = true);
//! @brief Read dword in little-endian order with transaction from register
template <typename Reg>
bool readRegister32LE(const Reg reg, uint32_t& result, const uint32_t delayMillis, const bool stop = true);
//! @brief Write any data with transaction
m5::hal::error::error_t writeWithTransaction(const uint8_t* data, const size_t len, const bool stop = true);
//! @brief Write any data with transaction to register
template <typename Reg>
m5::hal::error::error_t writeWithTransaction(const Reg reg, const uint8_t* data, const size_t len,
const bool stop = true);
//! @brief Write any data with transaction to register
template <typename Reg>
bool writeRegister(const Reg reg, const uint8_t* buf = nullptr, const size_t len = 0U, const bool stop = true);
//! @brief Write byte with transaction to register
template <typename Reg>
bool writeRegister8(const Reg reg, const uint8_t value, const bool stop = true);
//! @brief Write word in big-endian order with transaction from register
template <typename Reg>
bool writeRegister16BE(const Reg reg, const uint16_t value, const bool stop = true);
//! @brief Write word in little-endian order with transaction from register
template <typename Reg>
bool writeRegister16LE(const Reg reg, const uint16_t value, const bool stop = true);
//! @brief Write dword in big-endian order with transaction from register
template <typename Reg>
bool writeRegister32BE(const Reg reg, const uint32_t value, const bool stop = true);
//! @brief Write dword in little-endian order with transaction from register
template <typename Reg>
bool writeRegister32LE(const Reg reg, const uint32_t value, const bool stop = true);
///@}
#endif
protected:
// Proper implementation in derived classes is required
virtual const char* unit_device_name() const = 0;
virtual types::uid_t unit_identifier() const = 0;
virtual types::attr_t unit_attribute() const = 0;
inline virtual types::category_t unit_category() const
{
return types::category_t::None;
}
inline virtual bool in_periodic() const
{
return _periodic;
}
inline virtual std::shared_ptr<Adapter> ensure_adapter(const uint8_t /*ch*/)
{
return _adapter; // By default, offer my adapter for sharing
}
// Select valid channel if exists(PaHub etc...)
inline virtual m5::hal::error::error_t select_channel(const uint8_t)
{
return m5::hal::error::error_t::OK;
}
inline size_t stored_size() const
{
return _component_cfg.stored_size;
}
bool add_child(Component* c);
// I2C
bool changeAddress(const uint8_t addr); // Functions for dynamically addressable devices
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type = nullptr>
bool read_register16E(const Reg reg, uint16_t& result, const uint32_t delayMillis, const bool stop,
const bool endian);
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type = nullptr>
bool write_register16E(const Reg reg, const uint16_t value, const bool stop, const bool endifan);
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type = nullptr>
bool read_register32E(const Reg reg, uint32_t& result, const uint32_t delayMillis, const bool stop,
const bool endian);
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type = nullptr>
bool write_register32E(const Reg reg, const uint32_t value, const bool stop, const bool endifan);
protected:
// For periodic measurement
types::elapsed_time_t _latest{}, _interval{};
bool _periodic{}; // During periodic measurement?
bool _updated{};
private:
UnitUnified* _manager{};
std::shared_ptr<m5::unit::Adapter> _adapter{};
uint32_t _order{};
component_config_t _component_cfg{};
int16_t _channel{-1}; // valid [0...]
uint8_t _addr{};
bool _begun{};
// for chain
Component* _parent{};
Component* _next{};
Component* _prev{};
Component* _child{};
friend class UnitUnified;
};
/*!
@class PeriodicMeasurementAdapter
@brief Interface class for periodic measurement (CRTP)
@details Common interface for accumulated periodic measurement data
@details Provide a common interface for periodic measurements for each unit
@tparam Derived Derived class
@tparam MD Type of the measurement data group
@warning MUST IMPLEMENT some functions (NOT VERTUAL)
- MD Derived::oldest_periodic_data() const;
- MD Derived::latestt_periodic_data() const;
- bool Derived::start_periodic_measurement(any arguments);
- bool Derived::stop_periodic_measurement():
@warning MUST ADD std::unique_ptr<m5::container::CircularBuffer<MD>> _data{}
in Derived class
@warning This class is an interface class and should not have any data
@note See also M5_UNIT_COMPONENT_PERIODIC_MEASUREMENT_ADAPTER_HPP_BUILDER
*/
template <class Derived, typename MD>
class PeriodicMeasurementAdapter {
public:
///@name Periodic measurement
///@{
/*!
@brief Start periodic measurement
@tparam Args Optional arguments
@return True if successful
@note Call Derived::start_periodic_measurement
*/
template <typename... Args>
inline bool startPeriodicMeasurement(Args&&... args)
{
// Prepare for future common initiation preprocessing needs
return static_cast<Derived*>(this)->start_periodic_measurement(std::forward<Args>(args)...);
}
/*!
@brief Stop periodic measurement
@tparam Args Optional arguments
@return True if successful
@note Call Derived::stop_periodic_measurement
*/
template <typename... Args>
inline bool stopPeriodicMeasurement(Args&&... args)
{
// Prepare for future common stopping preprocessing needs
return static_cast<Derived*>(this)->stop_periodic_measurement(std::forward<Args>(args)...);
}
///@}
///@name Data
///@{
//! @brief Gets the number of stored data
inline size_t available() const
{
return available_periodic_measurement_data();
}
//! @brief Is empty stored data?
inline bool empty() const
{
return empty_periodic_measurement_data();
}
//! @brief Is stored data full?
inline bool full() const
{
return full_periodic_measurement_data();
}
//! @brief Retrieve oldest stored data
inline MD oldest() const
{
return static_cast<const Derived*>(this)->oldest_periodic_data();
}
//! @brief Retrieve latest stored data
inline MD latest() const
{
return static_cast<const Derived*>(this)->latest_periodic_data();
}
//! @brief Discard the oldest data accumulated
inline void discard()
{
discard_periodic_measurement_data();
}
//! @brief Discard all data
inline void flush()
{
flush_periodic_measurement_data();
}
///@}
protected:
///@note Must implement in derived class
///@name Pure virtual functions
///@{
virtual size_t available_periodic_measurement_data() const = 0;
virtual bool empty_periodic_measurement_data() const = 0;
virtual bool full_periodic_measurement_data() const = 0;
virtual void discard_periodic_measurement_data() = 0;
virtual void flush_periodic_measurement_data() = 0;
///@}
};
} // namespace unit
} // namespace m5
// Helper for creating derived classes from Component
///@cond
#define M5_UNIT_COMPONENT_HPP_BUILDER(cls, reg) \
public: \
constexpr static uint8_t DEFAULT_ADDRESS{(reg)}; \
static const types::uid_t uid; \
static const types::attr_t attr; \
static const char name[]; \
\
cls(const cls&) = delete; \
\
cls& operator=(const cls&) = delete; \
\
cls(cls&&) noexcept = default; \
\
cls& operator=(cls&&) noexcept = default; \
\
protected: \
inline virtual const char* unit_device_name() const override \
{ \
return name; \
} \
inline virtual types::uid_t unit_identifier() const override \
{ \
return uid; \
} \
inline virtual types::attr_t unit_attribute() const override \
{ \
return attr; \
}
// Helper for creating derived class from PeriodicMeasurementAdapter
#define M5_UNIT_COMPONENT_PERIODIC_MEASUREMENT_ADAPTER_HPP_BUILDER(cls, md) \
protected: \
friend class PeriodicMeasurementAdapter<cls, md>; \
\
inline md oldest_periodic_data() const \
{ \
return !_data->empty() ? _data->front().value() : md{}; \
} \
inline md latest_periodic_data() const \
{ \
return !_data->empty() ? _data->back().value() : md{}; \
} \
inline virtual size_t available_periodic_measurement_data() const override \
{ \
return _data->size(); \
} \
inline virtual bool empty_periodic_measurement_data() const override \
{ \
return _data->empty(); \
} \
inline virtual bool full_periodic_measurement_data() const override \
{ \
return _data->full(); \
} \
inline virtual void discard_periodic_measurement_data() override \
{ \
_data->pop_front(); \
} \
inline virtual void flush_periodic_measurement_data() override \
{ \
_data->clear(); \
}
///@endcond
#endif

View file

@ -0,0 +1,150 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file M5UnitUnified.cpp
@brief class UnitUnified
*/
#include "M5UnitUnified.hpp"
#include <M5Utility.hpp>
namespace m5 {
namespace unit {
uint32_t UnitUnified::_registerCount{0};
bool UnitUnified::add(Component& u, m5::hal::bus::Bus* bus)
{
if (u.isRegistered()) {
M5_LIB_LOGW("Already added");
return false;
}
if (!bus) {
M5_LIB_LOGE("Bus null");
return false;
}
M5_LIB_LOGD("Add [%s]:0x%02x", u.deviceName(), u.address());
u._manager = this;
if (u.assign(bus)) {
u._order = ++_registerCount;
_units.emplace_back(&u);
return add_children(u);
}
M5_LIB_LOGE("Failed to assign %s:%u", u.deviceName(), u.canAccessI2C());
return false;
}
bool UnitUnified::add(Component& u, TwoWire& wire)
{
if (u.isRegistered()) {
M5_LIB_LOGW("Already added");
return false;
}
M5_LIB_LOGD("Add [%s] addr:%02x children:%zu", u.deviceName(), u.address(), u.childrenSize());
u._manager = this;
if (u.assign(wire)) {
u._order = ++_registerCount;
_units.emplace_back(&u);
return add_children(u);
}
M5_LIB_LOGE("Failed to assign %s:%u", u.deviceName(), u.canAccessI2C());
return false;
}
bool UnitUnified::add(Component& u, const int8_t rx_pin, const int8_t tx_pin)
{
if (u.isRegistered()) {
M5_LIB_LOGW("Already added");
return false;
}
M5_LIB_LOGD("Add [%s] rx:%d tx:%d %zu", u.deviceName(), rx_pin, tx_pin, u.childrenSize());
u._manager = this;
if (u.assign(rx_pin, tx_pin)) {
u._order = ++_registerCount;
_units.emplace_back(&u);
return add_children(u);
}
M5_LIB_LOGE("Failed to assign %s:%u", u.deviceName(), u.canAccessGPIO());
return false;
}
// Add children if exists
bool UnitUnified::add_children(Component& u)
{
auto it = u.childBegin();
while (it != u.childEnd()) {
auto ch = it->channel();
M5_LIB_LOGV("%s child:%s channel:%u", u.deviceName(), it->deviceName(), ch);
if (it->isRegistered()) {
M5_LIB_LOGE("Already registered %s", it->deviceName());
return false;
}
it->_manager = this;
it->_adapter = u.ensure_adapter(ch);
M5_LIB_LOGD(" Shared:%u %u", u._adapter.use_count(), it->_adapter.use_count());
it->_order = ++_registerCount;
_units.emplace_back(&*it);
if (!add_children(*it)) {
return false;
}
++it;
}
return true;
}
bool UnitUnified::begin()
{
return !std::any_of(_units.begin(), _units.end(), [](Component* c) {
M5_LIB_LOGV("Try begin:%s", c->deviceName());
bool ret = c->_begun = c->begin();
if (!ret) {
M5_LIB_LOGE("Failed to begin: %s", c->debugInfo().c_str());
}
return !ret;
});
}
void UnitUnified::update(const bool force)
{
// Order of registration
for (auto&& u : _units) {
if (!u->_component_cfg.self_update && u->_begun) {
u->update(force);
}
}
}
std::string UnitUnified::debugInfo() const
{
std::string s = m5::utility::formatString("\nM5UnitUnified: %zu units\n", _units.size());
for (auto&& u : _units) {
if (!u->hasParent()) {
s += make_unit_info(u, 0);
}
}
return m5::utility::trim(s);
}
std::string UnitUnified::make_unit_info(const Component* u, const uint8_t indent) const
{
std::string s = m5::utility::formatString("%*c%s\n", indent * 4, ' ', u->debugInfo().c_str());
if (u->hasChildren()) {
s += make_unit_info(u->_child, indent + 1);
}
u = u->_next;
return u ? s += make_unit_info(u, indent) : s;
}
} // namespace unit
} // namespace m5

View file

@ -0,0 +1,17 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file M5UnitUnified.h
*/
#ifndef M5_UNIT_UNIFIED_H
#define M5_UNIT_UNIFIED_H
#ifdef __cplusplus
#include "M5UnitUnified.hpp"
#else
#error M5UnitUnified requires a C++ compiler, please change file extension to .cc or .cpp
#endif
#endif

View file

@ -0,0 +1,114 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file M5UnitUnified.hpp
@brief Main header of M5UnitUnified
@mainpage M5UnitUnified
M5UnitUnified is a library for unified handling of various M5 unit products.
- Unified APIs
- Unified Connections
- Unified Licensing
*/
#ifndef M5_UNIT_UNIFIED_HPP
#define M5_UNIT_UNIFIED_HPP
#include "M5UnitComponent.hpp"
#include <M5HAL.hpp>
#if defined(M5_UNIT_UNIFIED_USING_RMT_V2)
#else
#include <driver/rmt.h>
#endif
#include <vector>
#include <string>
class TwoWire;
/*!
@namespace m5
@brief Top level namespace of M5stack
*/
namespace m5 {
/*!
@namespace unit
@brief Unit-related namespace
*/
namespace unit {
class Component;
/*!
@class m5::unit::UnitUnified
@brief For managing and leading units
*/
class UnitUnified {
public:
using container_type = std::vector<Component*>;
///@warning COPY PROHIBITED
///@name Constructor
///@{
UnitUnified() = default;
UnitUnified(const UnitUnified&) = delete;
UnitUnified(UnitUnified&&) noexcept = default;
///@}
///@warning COPY PROHIBITED
///@name Assignment
///@{
UnitUnified& operator=(const UnitUnified&) = delete;
UnitUnified& operator=(UnitUnified&&) noexcept = default;
///@}
///@name Add unit
///@{
/*!
@brief Adding unit to be managed (I2C)
@param u Unit Component
@param wire TwoWire to be used
@return True if successful
*/
bool add(Component& u, TwoWire& wire);
/*!
@brief Adding unit to be managed (GPIO)
@param u Unit Component
@param rx_pin Pin number to be used for RX
@param tx_pin Pin number to be used for TX
@return True if successful
*/
bool add(Component& u, const int8_t rx_pin, const int8_t tx_pin);
/*!
@brief Adding unit to be managed (M5HAL)
@param u Unit Component
@param bus Bus to be used
@return True if successful
*/
bool add(Component& u, m5::hal::bus::Bus* bus);
///@}
//! @brief Begin of all units under management
bool begin();
//! @brief Update of all units under management
void update(const bool force = false);
//! @brief Output information for debug
std::string debugInfo() const;
protected:
bool add_children(Component& u);
std::string make_unit_info(const Component* u, const uint8_t indent = 0) const;
protected:
container_type _units{};
private:
static uint32_t _registerCount;
};
} // namespace unit
} // namespace m5
#endif

View file

@ -0,0 +1,86 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file test_helper.hpp
@brief Helper for testing UnitComponent
@note Depends on GoogleTest
*/
#ifndef M5_UNIT_COMPONENT_GOOGLETEST_HELPER_HPP
#define M5_UNIT_COMPONENT_GOOGLETEST_HELPER_HPP
#include <M5Utility.hpp>
#include <thread>
#include <cassert>
namespace m5 {
namespace unit {
namespace googletest {
template <class U>
uint32_t test_periodic_measurement(U* unit, const uint32_t times, const uint32_t tolerance,
const uint32_t timeout_duration, void (*callback)(U*), const bool skip_after_test)
{
static_assert(std::is_base_of<m5::unit::Component, U>::value, "U must be derived from Component");
auto interval = unit->interval();
decltype(interval) avg{}, avgCnt{};
uint32_t cnt{times};
auto prev = unit->updatedMillis();
auto timeout_at = m5::utility::millis() + timeout_duration;
while (cnt && m5::utility::millis() <= timeout_at) {
unit->update();
if (unit->updated()) {
--cnt;
auto um = unit->updatedMillis();
if (prev) {
auto duration = um - prev;
++avgCnt;
avg += duration;
// M5_LOGI("dur:%ld", duration);
// EXPECT_LE(duration, interval + 1);
}
prev = um;
if (callback) {
callback(unit);
}
}
std::this_thread::yield();
}
if (!skip_after_test) {
EXPECT_EQ(cnt, 0U);
EXPECT_EQ(avgCnt, times - 1);
if (avgCnt) {
avg /= avgCnt;
EXPECT_LE(avg, decltype(interval)(interval + tolerance));
}
return avg;
}
return 0U;
}
template <class U>
uint32_t test_periodic_measurement(U* unit, const uint32_t times = 8, const uint32_t tolerance = 1,
void (*callback)(U*) = nullptr, const bool skip_after_test = false)
{
static_assert(std::is_base_of<m5::unit::Component, U>::value, "U must be derived from Component");
auto timeout_duration = (unit->interval() * 2) * times;
return test_periodic_measurement(unit, times, tolerance, timeout_duration, callback, skip_after_test);
}
template <class U>
uint32_t test_periodic_measurement(U* unit, const uint32_t times = 8, void (*callback)(U*) = nullptr,
const bool skip_after_test = false)
{
static_assert(std::is_base_of<m5::unit::Component, U>::value, "U must be derived from Component");
auto timeout_duration = (unit->interval() * 2) * times;
return test_periodic_measurement(unit, times, 1, timeout_duration, callback, skip_after_test);
}
} // namespace googletest
} // namespace unit
} // namespace m5
#endif

View file

@ -0,0 +1,115 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file test_template.hpp
@brief Helper for testing UnitComponent
@note Depends on GoogleTest
*/
#ifndef M5_UNIT_COMPONENT_GOOGLETEST_TEMPLATE_HPP
#define M5_UNIT_COMPONENT_GOOGLETEST_TEMPLATE_HPP
#include "../M5UnitComponent.hpp"
#include <type_traits>
#include <Wire.h>
#include <esp32-hal-i2c.h>
namespace m5 {
namespace unit {
/*!
@namespace googletest
@brief For GoogleTest
*/
namespace googletest {
/*!
@class GlobalFixture
@brief Overall test environment configuration
@tparam FREQ TwoWire operating frequency
@tparam WNUM TwoWire number to be used (0 as default)
*/
template <uint32_t FREQ, uint32_t WNUM = 0>
class GlobalFixture : public ::testing::Environment {
static_assert(WNUM < 2, "Wire number must be lesser than 2");
public:
void SetUp() override
{
auto pin_num_sda = M5.getPin(m5::pin_name_t::port_a_sda);
auto pin_num_scl = M5.getPin(m5::pin_name_t::port_a_scl);
#if defined(CONFIG_IDF_TARGET_ESP32C6)
TwoWire* w[1] = {&Wire};
#else
TwoWire* w[2] = {&Wire, &Wire1};
#endif
if (WNUM < m5::stl::size(w) && i2cIsInit(WNUM)) {
M5_LOGW("Already inititlized Wire %d. Terminate and restart FREQ %u", WNUM, FREQ);
w[WNUM]->end();
}
w[WNUM]->begin(pin_num_sda, pin_num_scl, FREQ);
}
};
/*!
@class ComponentTestBase
@brief UnitComponent Derived class for testing
@tparam U m5::unit::Component-derived classes to be tested
@tparam TP parameter type for testing. see also INSTANTIATE_TEST_SUITE_P
*/
template <typename U, typename TP>
class ComponentTestBase : public ::testing::TestWithParam<TP> {
static_assert(std::is_base_of<m5::unit::Component, U>::value, "U must be derived from Component");
protected:
virtual void SetUp() override
{
unit.reset(get_instance());
if (!unit) {
FAIL() << "Failed to get_instance";
GTEST_SKIP();
return;
}
ustr = m5::utility::formatString("%s:%s", unit->deviceName(), is_using_hal() ? "HAL" : "Wire");
if (!begin()) {
FAIL() << "Failed to begin " << ustr;
GTEST_SKIP();
}
}
virtual void TearDown() override
{
}
virtual bool begin()
{
if (is_using_hal()) {
// Using M5HAL
auto pin_num_sda = M5.getPin(m5::pin_name_t::port_a_sda);
auto pin_num_scl = M5.getPin(m5::pin_name_t::port_a_scl);
m5::hal::bus::I2CBusConfig i2c_cfg;
i2c_cfg.pin_sda = m5::hal::gpio::getPin(pin_num_sda);
i2c_cfg.pin_scl = m5::hal::gpio::getPin(pin_num_scl);
auto i2c_bus = m5::hal::bus::i2c::getBus(i2c_cfg);
return Units.add(*unit, i2c_bus ? i2c_bus.value() : nullptr) && Units.begin();
}
// Using TwoWire
return Units.add(*unit, Wire) && Units.begin();
}
//!@brief Function returning true if M5HAL is used (decision based on TP)
virtual bool is_using_hal() const = 0;
//! @brief return m5::unit::Component-derived class instance (decision based on TP)
virtual U* get_instance() = 0;
std::string ustr{};
std::unique_ptr<U> unit{};
m5::unit::UnitUnified Units;
};
} // namespace googletest
} // namespace unit
} // namespace m5
#endif

View file

@ -0,0 +1,27 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file adapter.cpp
@brief Adapters to treat M5HAL and any connection in the same way
@note Currently handles Arduino directly, but will handle via M5HAL in the future
*/
#include "adapter.hpp"
#include <cassert>
#if defined(ARDUINO)
#include <Wire.h>
#endif
#include <M5HAL.hpp>
#include <M5Utility.hpp>
#include <soc/gpio_struct.h>
#include <soc/gpio_sig_map.h>
namespace m5 {
namespace unit {
// Adapter
} // namespace unit
} // namespace m5

View file

@ -0,0 +1,21 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file adapter.hpp
@brief Adapters to treat M5HAL and any connection in the same way
@note Currently handles Arduino directly, but will handle via M5HAL in the future
*/
#ifndef M5_UNIT_COMPONENT_ADAPTER_HPP
#define M5_UNIT_COMPONENT_ADAPTER_HPP
#include "adapter_base.hpp"
#include "adapter_i2c.hpp"
#include "identify_functions.hpp"
#if defined(M5_UNIT_UNIFIED_USING_RMT_V2)
#include "adapter_gpio_v2.hpp"
#else
#include "adapter_gpio_v1.hpp"
#endif
#endif

View file

@ -0,0 +1,233 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file adapter_base.hpp
@brief Adapter base
*/
#ifndef M5_UNIT_COMPONENT_ADAPTER_BASE_HPP
#define M5_UNIT_COMPONENT_ADAPTER_BASE_HPP
#include <cstdint>
#include <cstddef>
#include <memory>
#include <M5HAL.hpp>
#include "types.hpp"
namespace m5 {
namespace unit {
/*!
@class m5::unit::Adapter
@brief Adapter base class to treat M5HAL and TwoWire,GPIO,Serial,SPI... in the same way
*/
class Adapter {
public:
enum class Type : uint8_t {
Unknown,
I2C,
GPIO,
UART,
SPI,
};
class Impl {
public:
Impl() = default;
virtual ~Impl()
{
}
// I2C R/W
virtual m5::hal::error::error_t readWithTransaction(uint8_t*, const size_t)
{
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
virtual m5::hal::error::error_t writeWithTransaction(const uint8_t*, const size_t, const uint32_t)
{
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
virtual m5::hal::error::error_t writeWithTransaction(const uint8_t, const uint8_t*, const size_t,
const uint32_t)
{
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
virtual m5::hal::error::error_t writeWithTransaction(const uint16_t, const uint8_t*, const size_t,
const uint32_t)
{
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
virtual m5::hal::error::error_t generalCall(const uint8_t*, const size_t)
{
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
// GPIO R/W
virtual m5::hal::error::error_t pinModeRX(const gpio::Mode)
{
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
virtual m5::hal::error::error_t writeDigitalRX(const bool)
{
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
virtual m5::hal::error::error_t readDigitalRX(bool&)
{
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
virtual m5::hal::error::error_t writeAnalogRX(const uint16_t)
{
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
virtual m5::hal::error::error_t readAnalogRX(uint16_t&)
{
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
virtual m5::hal::error::error_t pulseInRX(uint32_t&, const int, const uint32_t)
{
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
virtual m5::hal::error::error_t pinModeTX(const gpio::Mode)
{
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
virtual m5::hal::error::error_t writeDigitalTX(const bool)
{
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
virtual m5::hal::error::error_t readDigitalTX(bool&)
{
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
virtual m5::hal::error::error_t writeAnalogTX(const uint16_t)
{
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
virtual m5::hal::error::error_t readAnalogTX(uint16_t&)
{
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
virtual m5::hal::error::error_t pulseInTX(uint32_t&, const int, const uint32_t)
{
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
};
explicit Adapter() : _impl{new Impl()}
{
}
protected:
Adapter(const Type t, Impl* impl) : _type{t}, _impl{impl}
{
}
public:
Adapter(const Adapter&) = delete;
Adapter(Adapter&&) noexcept = default;
Adapter& operator=(const Adapter&) = delete;
Adapter& operator=(Adapter&&) noexcept = default;
virtual ~Adapter() = default;
inline Type type() const
{
return _type;
}
virtual Adapter* duplicate(const uint8_t /*addr*/)
{
return new Adapter();
}
// I2C R/W
inline m5::hal::error::error_t readWithTransaction(uint8_t* data, const size_t len)
{
return _impl->readWithTransaction(data, len);
}
inline m5::hal::error::error_t writeWithTransaction(const uint8_t* data, const size_t len,
const uint32_t exparam = 1)
{
return _impl->writeWithTransaction(data, len, exparam);
}
inline m5::hal::error::error_t writeWithTransaction(const uint8_t reg, const uint8_t* data, const size_t len,
const uint32_t exparam = 1)
{
return _impl->writeWithTransaction(reg, data, len, exparam);
}
inline m5::hal::error::error_t writeWithTransaction(const uint16_t reg, const uint8_t* data, const size_t len,
const uint32_t exparam = 1)
{
return _impl->writeWithTransaction(reg, data, len, exparam);
}
inline m5::hal::error::error_t generalCall(const uint8_t* data, const size_t len)
{
return _impl->generalCall(data, len);
}
// GPIO R/W
inline m5::hal::error::error_t pinModeRX(const gpio::Mode m)
{
return _impl->pinModeRX(m);
}
inline m5::hal::error::error_t writeDigitalRX(const bool high)
{
return _impl->writeDigitalRX(high);
}
inline m5::hal::error::error_t readDigitalRX(bool& high)
{
return _impl->readDigitalRX(high);
}
inline m5::hal::error::error_t writeAnalogRX(const uint16_t v)
{
return _impl->writeAnalogRX(v);
}
inline m5::hal::error::error_t readAnalogRX(uint16_t& v)
{
return _impl->readAnalogRX(v);
}
inline m5::hal::error::error_t pulseInRX(uint32_t& duration, const int state, const uint32_t timeout_us)
{
return _impl->pulseInRX(duration, state, timeout_us);
}
inline m5::hal::error::error_t pinModeTX(const gpio::Mode m)
{
return _impl->pinModeTX(m);
}
inline m5::hal::error::error_t writeDigitalTX(const bool high)
{
return _impl->writeDigitalTX(high);
}
inline m5::hal::error::error_t readDigitalTX(bool& high)
{
return _impl->readDigitalTX(high);
}
inline m5::hal::error::error_t writeAnalogTX(const uint16_t v)
{
return _impl->writeAnalogTX(v);
}
inline m5::hal::error::error_t readAnalogTX(uint16_t& v)
{
return _impl->readAnalogTX(v);
}
inline m5::hal::error::error_t pulseInTX(uint32_t& duration, const int state, const uint32_t timeout_us)
{
return _impl->pulseInTX(duration, state, timeout_us);
}
private:
Type _type{Type::Unknown};
protected:
std::unique_ptr<Impl> _impl{};
};
} // namespace unit
} // namespace m5
#endif

View file

@ -0,0 +1,507 @@
/*
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file adapter_gpio.cpp
@brief Adapters to treat M5HAL and RMT in the same way
@note Currently handles GPIO directly, but will handle via M5HAL in the future
*/
#include "adapter_gpio.hpp"
#include <driver/gpio.h>
#if defined(M5_UNIT_UNIFIED_USING_RMT_V2)
#pragma message "Using RMT v2,Oneshot"
#include <esp_adc/adc_oneshot.h>
#else
#pragma message "Using RMT v1"
#include <driver/adc.h>
#endif
#if defined(SOC_DAC_SUPPORTED) && SOC_DAC_SUPPORTED
#pragma message "DAC supported"
#if __has_include(<driver/dac_common.h>)
#include <driver/dac_common.h>
#define USING_RMT_CHANNNE_T
#endif
#endif
#if !defined(USING_RMT_CHANNNE_T) && defined(ARDUINO)
#include <esp32-hal-dac.h>
#endif
#if SOC_ADC_SUPPORTED
#pragma message "ADC supported"
#else
#pragma message "ADC Not supported"
#endif
#include <esp_timer.h>
namespace {
constexpr gpio_config_t gpio_cfg_table[] = {
{
// Input
.pin_bit_mask = 0,
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
#if defined(CONFIG_IDF_TARGET_ESP32P4)
.hys_ctrl_mode = GPIO_HYS_SOFT_ENABLE,
#endif
},
{
// Output
.pin_bit_mask = 0,
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
#if defined(CONFIG_IDF_TARGET_ESP32P4)
.hys_ctrl_mode = GPIO_HYS_SOFT_ENABLE,
#endif
},
{
// Pullup
.pin_bit_mask = 0,
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
#if defined(CONFIG_IDF_TARGET_ESP32P4)
.hys_ctrl_mode = GPIO_HYS_SOFT_ENABLE,
#endif
},
{
// InputPullup
.pin_bit_mask = 0,
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
#if defined(CONFIG_IDF_TARGET_ESP32P4)
.hys_ctrl_mode = GPIO_HYS_SOFT_ENABLE,
#endif
},
{
// Pulldown
.pin_bit_mask = 0,
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_ENABLE,
.intr_type = GPIO_INTR_DISABLE,
#if defined(CONFIG_IDF_TARGET_ESP32P4)
.hys_ctrl_mode = GPIO_HYS_SOFT_ENABLE,
#endif
},
{
// InputPulldown
.pin_bit_mask = 0,
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_ENABLE,
.intr_type = GPIO_INTR_DISABLE,
#if defined(CONFIG_IDF_TARGET_ESP32P4)
.hys_ctrl_mode = GPIO_HYS_SOFT_ENABLE,
#endif
},
{
// OpenDrain
.pin_bit_mask = 0,
.mode = GPIO_MODE_OUTPUT_OD,
.pull_up_en = GPIO_PULLUP_ENABLE, //
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
#if defined(CONFIG_IDF_TARGET_ESP32P4)
.hys_ctrl_mode = GPIO_HYS_SOFT_ENABLE,
#endif
},
{
// OutputOpenDrain,
.pin_bit_mask = 0,
.mode = GPIO_MODE_OUTPUT_OD,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
#if defined(CONFIG_IDF_TARGET_ESP32P4)
.hys_ctrl_mode = GPIO_HYS_SOFT_ENABLE,
#endif
},
{
// Analog
.pin_bit_mask = 0,
.mode = GPIO_MODE_DISABLE,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
#if defined(CONFIG_IDF_TARGET_ESP32P4)
.hys_ctrl_mode = GPIO_HYS_SOFT_ENABLE,
#endif
},
};
#if CONFIG_IDF_TARGET_ESP32
constexpr int8_t gpio_to_adc_table[] = {
/* 0 */ 11, // ADC2_CHANNEL_1
/* 1 */ -1,
/* 2 */ 12, // ADC2_CHANNEL_2
/* 3 */ -1,
/* 4 */ 10, // ADC2_CHANNEL_0
/* 5 */ -1,
/* 6 */ -1,
/* 7 */ -1,
/* 8 */ -1,
/* 9 */ -1,
/* 10 */ -1,
/* 11 */ -1,
/* 12 */ 15, // ADC2_CHANNEL_5
/* 13 */ 14, // ADC2_CHANNEL_4
/* 14 */ 16, // ADC2_CHANNEL_6
/* 15 */ 13, // ADC2_CHANNEL_3
/* 16 */ -1,
/* 17 */ -1,
/* 18 */ -1,
/* 19 */ -1,
/* 20 */ -1,
/* 21 */ -1,
/* 22 */ -1,
/* 23 */ -1,
/* 24 */ -1,
/* 25 */ 18, // ADC2_CHANNEL_8
/* 26 */ 19, // ADC2_CHANNEL_9
/* 27 */ 17, // ADC2_CHANNEL_7
/* 28 */ -1,
/* 29 */ -1,
/* 30 */ -1,
/* 31 */ -1,
/* 32 */ 4, // ADC1_CHANNEL_4
/* 33 */ 5, // ADC1_CHANNEL_5
/* 34 */ 6, // ADC1_CHANNEL_6
/* 35 */ 7, // ADC1_CHANNEL_7
/* 36 */ 0, // ADC1_CHANNEL_0
/* 37 */ 1, // ADC1_CHANNEL_1
/* 38 */ 2, // ADC1_CHANNEL_2
/* 39 */ 3 // ADC1_CHANNEL_3
};
#elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
constexpr int8_t gpio_to_adc_table[] = {
/* 0 */ -1,
/* 1 */ 0, // ADC1_CHANNEL_0
/* 2 */ 1, // ADC1_CHANNEL_1
/* 3 */ 2, // ADC1_CHANNEL_2
/* 4 */ 3, // ADC1_CHANNEL_3
/* 5 */ 4, // ADC1_CHANNEL_4
/* 6 */ 5, // ADC1_CHANNEL_5
/* 7 */ 6, // ADC1_CHANNEL_6
/* 8 */ 7, // ADC1_CHANNEL_7
/* 9 */ 8, // ADC1_CHANNEL_8
/* 10 */ 9, // ADC1_CHANNEL_9
/* 11 */ 10, // ADC2_CHANNEL_0
/* 12 */ 11, // ADC2_CHANNEL_1
/* 13 */ 12, // ADC2_CHANNEL_2
/* 14 */ 13, // ADC2_CHANNEL_3
/* 15 */ 14, // ADC2_CHANNEL_4
/* 16 */ 15, // ADC2_CHANNEL_5
/* 17 */ 16, // ADC2_CHANNEL_6
/* 18 */ 17, // ADC2_CHANNEL_7
/* 19 */ 18, // ADC2_CHANNEL_8
/* 20 */ 19, // ADC2_CHANNEL_9
};
#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C2
constexpr int8_t gpio_to_adc_table[] = {
/* 0 */ 0, // ADC1_CHANNEL_0
/* 1 */ 1, // ADC1_CHANNEL_1
/* 2 */ 2, // ADC1_CHANNEL_2
/* 3 */ 3, // ADC1_CHANNEL_3
/* 4 */ 4, // ADC1_CHANNEL_4
/* 5 */ 10, // ADC2_CHANNEL_0
};
#elif CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2 || CONFIG_IDF_TARGET_ESP32P4 || \
CONFIG_IDF_TARGET_ESP32C5 || CONFIG_IDF_TARGET_ESP32C61
constexpr int8_t gpio_to_adc_table[] = {
/* 0 */ 0, // ADC1_CHANNEL_0
/* 1 */ 1, // ADC1_CHANNEL_1
/* 2 */ 2, // ADC1_CHANNEL_2
/* 3 */ 3, // ADC1_CHANNEL_3
/* 4 */ 4, // ADC1_CHANNEL_4
/* 5 */ 5, // ADC1_CHANNEL_5
/* 6 */ 6, // ADC1_CHANNEL_6
};
#elif CONFIG_IDF_TARGET_ESP32P4
constexpr int8_t gpio_to_adc_table[] = {
/* 0 */ -1,
/* 1 */ -1,
/* 2 */ -1,
/* 3 */ -1,
/* 4 */ -1,
/* 5 */ -1,
/* 6 */ -1,
/* 7 */ -1,
/* 8 */ -1,
/* 9 */ -1,
/* 10 */ -1,
/* 11 */ -1,
/* 12 */ -1,
/* 13 */ -1,
/* 14 */ -1,
/* 15 */ -1,
/* 16 */ 0, // ADC1_CHANNEL_0
/* 17 */ 1, // ADC1_CHANNEL_1
/* 18 */ 2, // ADC1_CHANNEL_2
/* 19 */ 3, // ADC1_CHANNEL_3
/* 20 */ 4, // ADC1_CHANNEL_4
/* 21 */ 5, // ADC1_CHANNEL_5
/* 22 */ 6, // ADC1_CHANNEL_6
/* 23 */ 7, // ADC1_CHANNEL_7
/* 24 */ -1,
/* 25 */ -1,
/* 26 */ -1,
/* 27 */ -1,
/* 28 */ -1,
/* 29 */ -1,
/* 30 */ -1,
/* 31 */ -1,
/* 32 */ -1,
/* 33 */ -1,
/* 34 */ -1,
/* 35 */ -1,
/* 36 */ -1,
/* 37 */ -1,
/* 38 */ -1,
/* 39 */ -1,
/* 40 */ -1,
/* 41 */ -1,
/* 42 */ -1,
/* 43 */ -1,
/* 44 */ -1,
/* 45 */ -1,
/* 46 */ -1,
/* 47 */ -1,
/* 48 */ -1,
/* 49 */ 10, // ADC2_CHANNEL_0
/* 50 */ 11, // ADC2_CHANNEL_1
/* 51 */ 12, // ADC2_CHANNEL_2
/* 52 */ 13, // ADC2_CHANNEL_3
/* 53 */ 14, // ADC2_CHANNEL_4
/* 54 */ 15, // ADC2_CHANNEL_5
};
#else
#error Invalid target
#endif
// 0-9: ADC1 10-:ADC2 (ESP-IDF 4.x)
// 0-9, 10- : ADC (ESP-IDF 5.x)
int8_t gpio_to_adc_channel(const int8_t pin)
{
if (pin < 0 || pin >= m5::stl::size(gpio_to_adc_table)) {
return -1;
}
auto v = gpio_to_adc_table[pin];
return (v < 10) ? v : v - 10;
}
#if 0
// -1:ivalid 0:ADC1 1:ADC2
int gpio_to_adc12(const int8_t pin)
{
return (pin >= 0 && pin < m5::stl::size(gpio_to_adc_table))
? (gpio_to_adc_table[pin] < 0 ? -1 : (gpio_to_adc_table[pin] >= 10 ? 1 : 0))
: -1;
}
#endif
} // namespace
namespace m5 {
namespace unit {
namespace gpio {
uint8_t calculate_rmt_clk_div(uint32_t apb_freq_hz, uint32_t tick_ns)
{
if (tick_ns == 0) {
return 1;
}
uint64_t clk_div = (static_cast<uint64_t>(apb_freq_hz) * tick_ns + 500) / 1000000000UL;
clk_div = std::min<uint64_t>(255, std::max<uint64_t>(0, clk_div));
return static_cast<uint8_t>(clk_div);
}
uint32_t calculate_rmt_resolution_hz(uint32_t apb_freq_hz, uint32_t tick_ns)
{
if (tick_ns == 0) {
return apb_freq_hz;
}
uint64_t target_hz = 1000000000UL / tick_ns;
uint32_t clk_div = std::max<uint32_t>(1, (apb_freq_hz + target_hz / 2) / target_hz);
clk_div = std::min<uint32_t>(clk_div, 255U);
return apb_freq_hz / clk_div;
}
} // namespace gpio
m5::hal::error::error_t AdapterGPIOBase::GPIOImpl::pin_mode(const gpio_num_t pin, const gpio::Mode m)
{
if (m < gpio::Mode::RmtRX) {
gpio_config_t cfg = gpio_cfg_table[m5::stl::to_underlying(m)];
cfg.pin_bit_mask = 1ULL << pin;
gpio_config(&cfg);
return m5::hal::error::error_t::OK;
}
return m5::hal::error::error_t::INVALID_ARGUMENT;
}
m5::hal::error::error_t AdapterGPIOBase::GPIOImpl::write_digital(const gpio_num_t pin, const bool high)
{
gpio_set_level(pin, high);
return m5::hal::error::error_t::OK;
}
m5::hal::error::error_t AdapterGPIOBase::GPIOImpl::read_digital(const gpio_num_t pin, bool& high)
{
high = true;
high = gpio_get_level(pin);
return m5::hal::error::error_t::OK;
}
m5::hal::error::error_t AdapterGPIOBase::GPIOImpl::write_analog(const gpio_num_t pin, const uint16_t value)
{
if (pin != 25 && pin != 26) {
// DAC output can 25 or 26
return m5::hal::error::error_t::INVALID_ARGUMENT;
}
#if defined(USING_RMT_CHANNNE_T)
dac_channel_t ch = (pin == 25) ? DAC_CHANNEL_1 : DAC_CHANNEL_2;
dac_output_enable(ch);
dac_output_voltage(ch, static_cast<uint8_t>(value & 0xFF)); // 0〜255
return m5::hal::error::error_t::OK;
#else
analogWrite(pin, value & 0xFF);
return m5::hal::error::error_t::OK;
// return m5::hal::error::error_t::NOT_IMPLEMENTED;
#endif
}
m5::hal::error::error_t AdapterGPIOBase::GPIOImpl::read_analog(uint16_t& value, const gpio_num_t pin)
{
value = 0;
const auto ch = gpio_to_adc_channel(pin);
if (ch < 0) {
return m5::hal::error::error_t::INVALID_ARGUMENT;
}
#if !defined(SOC_ADC_PERIPH_NUM) || SOC_ADC_PERIPH_NUM <= 1
if (ch >= 10) {
M5_LIB_LOGE("Not support ADC2");
return m5::hal::error::error_t::NOT_IMPLEMENTED;
}
#endif
#if defined(M5_UNIT_UNIFIED_USING_ADC_ONESHOT)
// ESP-IDF 5.x
adc_unit_t unit = (ch < 10) ? ADC_UNIT_1 : ADC_UNIT_2;
adc_channel_t channel = static_cast<adc_channel_t>((ch < 10) ? ch : (ch - 10));
adc_oneshot_unit_handle_t adc_handle{};
#if defined(CONFIG_IDF_TARGET_ESP32C6)
adc_oneshot_unit_init_cfg_t init_config = {
.unit_id = unit, .clk_src = ADC_DIGI_CLK_SRC_DEFAULT, .ulp_mode = ADC_ULP_MODE_DISABLE};
#else
adc_oneshot_unit_init_cfg_t init_config = {
.unit_id = unit, .clk_src = ADC_RTC_CLK_SRC_DEFAULT, .ulp_mode = ADC_ULP_MODE_DISABLE};
#endif
if (adc_oneshot_new_unit(&init_config, &adc_handle) != ESP_OK) {
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
adc_oneshot_chan_cfg_t chan_config = {
.atten = ADC_ATTEN_DB_12, // 0~3.3V
.bitwidth = ADC_BITWIDTH_DEFAULT // 12bit
};
auto ret = m5::hal::error::error_t::UNKNOWN_ERROR;
if (adc_oneshot_config_channel(adc_handle, channel, &chan_config) == ESP_OK) {
int raw{};
if (adc_oneshot_read(adc_handle, channel, &raw) == ESP_OK) {
value = static_cast<uint16_t>(raw);
ret = m5::hal::error::error_t::OK;
}
}
adc_oneshot_del_unit(adc_handle);
return ret;
#else
// ESP-IDF 4.x
// ADC2
if (ch >= 10) {
#if SOC_ADC_SUPPORTED && SOC_ADC_PERIPH_NUM > 1
adc2_channel_t channel = static_cast<adc2_channel_t>(ch - 10);
int v = 0;
if (adc2_get_raw(channel, ADC_WIDTH_BIT_12, &v) != ESP_OK) {
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
value = static_cast<uint16_t>(v);
return m5::hal::error::error_t::OK;
#endif
}
// ADC1
adc1_channel_t channel = static_cast<adc1_channel_t>(ch);
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(channel, ADC_ATTEN_DB_12);
value = static_cast<uint16_t>(adc1_get_raw(channel));
return m5::hal::error::error_t::OK;
#endif
}
m5::hal::error::error_t AdapterGPIOBase::GPIOImpl::pulse_in(uint32_t& duration, const gpio_num_t pin, const int state,
const uint32_t timeout_us = 30000)
{
duration = 0;
auto start = esp_timer_get_time();
auto now = start;
// Wait for any previous pulse to end
while (gpio_get_level(pin) == state) {
now = esp_timer_get_time();
if (now - start > timeout_us) {
return m5::hal::error::error_t::TIMEOUT_ERROR;
}
}
// Wait for the pulse to start
while (gpio_get_level(pin) != state) {
now = esp_timer_get_time();
if (now - start > timeout_us) {
return m5::hal::error::error_t::TIMEOUT_ERROR;
}
}
auto pulse_start = esp_timer_get_time();
// Wait for the pulse to end
while (gpio_get_level(pin) == state) {
now = esp_timer_get_time();
if (now - pulse_start > timeout_us) {
return m5::hal::error::error_t::TIMEOUT_ERROR;
}
}
auto pulse_end = esp_timer_get_time();
duration = static_cast<uint32_t>(pulse_end - pulse_start);
return m5::hal::error::error_t::OK;
}
AdapterGPIOBase::AdapterGPIOBase(GPIOImpl* impl) : Adapter(Adapter::Type::GPIO, impl)
{
}
} // namespace unit
} // namespace m5

View file

@ -0,0 +1,163 @@
/*
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file adapter_gpio.hpp
@brief Adapters to treat M5HAL and GPIO in the same way
@note Currently handles GPIO directly, but will handle via M5HAL in the future
*/
#ifndef M5_UNIT_COMPONENT_ADAPTER_GPIO_HPP
#define M5_UNIT_COMPONENT_ADAPTER_GPIO_HPP
#include "identify_functions.hpp"
#include "types.hpp"
#include "adapter_base.hpp"
namespace m5 {
namespace unit {
namespace gpio {
/*!
@brief Calculate clk_div from desired tick time (nanoseconds)
@param apb_freq_hz Current APB clock (Hz)
@param tick_ns Desired tick time (ns)
@return clk_div for RMT v1
*/
uint8_t calculate_rmt_clk_div(const uint32_t apb_freq_hz, const uint32_t tick_ns);
/*!
@brief Calculate resolution from desired tick time
@param apb_freq_hz Current APB clock (Hz)
@param tick_ns Desired tick time (ns)
@return resoution for RMT v2
*/
uint32_t calculate_rmt_resolution_hz(const uint32_t apb_freq_hz, const uint32_t tick_ns);
} // namespace gpio
// Base class for AdapterGPIO
class AdapterGPIOBase : public Adapter {
public:
class GPIOImpl : public Adapter::Impl {
public:
GPIOImpl() = default;
GPIOImpl(const int8_t rx_pin, const int8_t tx_pin) : _rx_pin{(gpio_num_t)rx_pin}, _tx_pin{(gpio_num_t)tx_pin}
{
}
inline gpio_num_t rx_pin() const
{
return _rx_pin;
}
inline gpio_num_t tx_pin() const
{
return _tx_pin;
}
inline gpio::adapter_config_t adapter_config() const
{
return _adapter_cfg;
}
inline virtual bool begin(const gpio::adapter_config_t& cfg)
{
return false;
}
//
inline virtual m5::hal::error::error_t pinModeRX(const gpio::Mode m) override
{
return pin_mode(rx_pin(), m);
}
inline virtual m5::hal::error::error_t writeDigitalRX(const bool high) override
{
return write_digital(rx_pin(), high);
}
inline virtual m5::hal::error::error_t readDigitalRX(bool& high) override
{
return read_digital(rx_pin(), high);
}
inline virtual m5::hal::error::error_t writeAnalogRX(const uint16_t v) override
{
return write_analog(rx_pin(), v);
}
inline virtual m5::hal::error::error_t readAnalogRX(uint16_t& v)
{
return read_analog(v, rx_pin());
}
inline virtual m5::hal::error::error_t pulseInRX(uint32_t& duration, const int state,
const uint32_t timeout_us) override
{
return pulse_in(duration, rx_pin(), state, timeout_us);
}
//
inline virtual m5::hal::error::error_t pinModeTX(const gpio::Mode m) override
{
return pin_mode(tx_pin(), m);
}
inline virtual m5::hal::error::error_t writeDigitalTX(const bool high) override
{
return write_digital(tx_pin(), high);
}
inline virtual m5::hal::error::error_t readDigitalTX(bool& high) override
{
return read_digital(tx_pin(), high);
}
inline virtual m5::hal::error::error_t writeAnalogTX(const uint16_t v) override
{
return write_analog(tx_pin(), v);
}
inline virtual m5::hal::error::error_t readAnalogTX(uint16_t& v)
{
return read_analog(v, tx_pin());
}
inline virtual m5::hal::error::error_t pulseInTX(uint32_t& duration, const int state,
const uint32_t timeout_us) override
{
return pulse_in(duration, tx_pin(), state, timeout_us);
}
protected:
m5::hal::error::error_t pin_mode(const gpio_num_t pin, const gpio::Mode m);
m5::hal::error::error_t write_digital(const gpio_num_t pin, const bool high);
m5::hal::error::error_t read_digital(const gpio_num_t pin, bool& high);
m5::hal::error::error_t write_analog(const gpio_num_t pin, const uint16_t value);
m5::hal::error::error_t read_analog(uint16_t& value, const gpio_num_t pin);
m5::hal::error::error_t pulse_in(uint32_t& duration, const gpio_num_t pin, const int state,
const uint32_t timeout_us);
protected:
gpio_num_t _rx_pin{(gpio_num_t)-1}, _tx_pin{(gpio_num_t)-1};
gpio::adapter_config_t _adapter_cfg{};
};
//
explicit AdapterGPIOBase(GPIOImpl* impl);
inline GPIOImpl* impl()
{
return static_cast<GPIOImpl*>(_impl.get());
}
inline const GPIOImpl* impl() const
{
return static_cast<GPIOImpl*>(_impl.get());
}
inline gpio_num_t rx_pin() const
{
return impl()->rx_pin();
}
inline gpio_num_t tx_pin() const
{
return impl()->tx_pin();
}
inline bool begin(const gpio::adapter_config_t& cfg)
{
return impl()->begin(cfg);
}
};
} // namespace unit
} // namespace m5
#endif

View file

@ -0,0 +1,168 @@
/*
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file adapter_gpio_v1.cpp
@brief Adapters to treat M5HAL and GPIO in the same way using RNT v1
@note Currently handles GPIO directly, but will handle via M5HAL in the future
*/
#include "adapter_gpio_v1.hpp"
#if !defined(M5_UNIT_UNIFIED_USING_RMT_V2)
#include <esp_clk.h>
using namespace m5::unit::gpio;
namespace {
uint32_t using_rmt_channel_bits{};
rmt_channel_t retrieve_available_rmt_channel()
{
for (int_fast8_t ch = 0; ch < RMT_CHANNEL_MAX; ++ch) {
if (((1U << ch) & using_rmt_channel_bits) == 0) {
return (rmt_channel_t)ch;
}
}
return RMT_CHANNEL_MAX;
}
bool declrare_use_rmt_channel(const int ch)
{
if (ch >= 0 && ch < RMT_CHANNEL_MAX && ((1U << ch) & using_rmt_channel_bits) == 0) {
using_rmt_channel_bits |= (1U << ch);
return true;
}
return false;
}
rmt_config_t to_rmt_config_tx(const adapter_config_t& cfg, const uint32_t apb_freq_hz)
{
rmt_config_t out{};
out.rmt_mode = RMT_MODE_TX;
out.mem_block_num = cfg.tx.mem_blocks;
out.clk_div = calculate_rmt_clk_div(apb_freq_hz, cfg.tx.tick_ns);
out.tx_config.carrier_en = false;
out.tx_config.idle_output_en = cfg.tx.idle_output;
out.tx_config.idle_level = cfg.tx.idle_level_high ? RMT_IDLE_LEVEL_HIGH : RMT_IDLE_LEVEL_LOW;
out.tx_config.loop_en = cfg.tx.loop_enabled;
return out;
}
#if 0
rmt_config_t to_rmt_config_rx(const adapter_config_t& cfg, const uint32_t apb_freq_hz)
{
rmt_config_t out{};
out.rmt_mode = RMT_MODE_RX;
out.mem_block_num = cfg.tx.mem_blocks;
out.clk_div = calculate_rmt_clk_div(apb_freq_hz, cfg.rx.tick_ns);
out.rx_config.filter_en = true;
out.rx_config.filter_ticks_thresh = 30;
out.rx_config.idle_threshold = 300;
return out;
}
#endif
} // namespace
namespace m5 {
namespace unit {
class GPIOImplV1 : public AdapterGPIOBase::GPIOImpl {
public:
GPIOImplV1() : AdapterGPIOBase::GPIOImpl(-1, -1)
{
}
GPIOImplV1(const int8_t rx_pin, const int8_t tx_pin) : AdapterGPIOBase::GPIOImpl(rx_pin, tx_pin)
{
}
virtual bool begin(const gpio::adapter_config_t& cfg) override
{
_adapter_cfg = cfg;
if (_tx_config.clk_div || _rx_config.clk_div) {
M5_LIB_LOGD("Already begun");
return true;
}
// RMT TX
if (cfg.mode == gpio::Mode::RmtTX || cfg.mode == gpio::Mode::RmtRXTX) {
rmt_channel_t ch = retrieve_available_rmt_channel();
if (ch >= RMT_CHANNEL_MAX) {
M5_LIB_LOGE("RMT(v1) No room on channel");
return false;
}
declrare_use_rmt_channel(ch);
M5_LIB_LOGI("Retrive RMT(v1) %u", ch);
_tx_config = to_rmt_config_tx(cfg, esp_clk_apb_freq());
_tx_config.channel = ch;
_tx_config.gpio_num = tx_pin();
auto err = rmt_config(&_tx_config);
if (err != ESP_OK) {
M5_LIB_LOGE("Failed to configurate %d:%s", err, esp_err_to_name(err));
return false;
}
err = rmt_driver_install(_tx_config.channel, 0, 0);
if (err != ESP_OK) {
M5_LIB_LOGE("Failed to install %d:%s", err, esp_err_to_name(err));
return false;
}
}
// RMT RX
if (cfg.mode == gpio::Mode::RmtRX || cfg.mode == gpio::Mode::RmtRXTX) {
// TODO
}
return true;
}
m5::hal::error::error_t writeWithTransaction(const uint8_t* data, const size_t len, const uint32_t waitMs) override
{
if (_adapter_cfg.mode == Mode::RmtTX || _adapter_cfg.mode == Mode::RmtRXTX) {
auto err = rmt_write_items(_tx_config.channel, (gpio::m5_rmt_item_t*)data, len, true);
if (err != ESP_OK) {
M5_LIB_LOGE("Failed to write %d:%s", err, esp_err_to_name(err));
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
if (err == ESP_OK && waitMs) {
err = rmt_wait_tx_done(_tx_config.channel, pdMS_TO_TICKS(50));
if (err != ESP_OK) {
M5_LIB_LOGE("Failed to wait %d:%s", err, esp_err_to_name(err));
}
}
return err == ESP_OK ? m5::hal::error::error_t::OK : m5::hal::error::error_t::UNKNOWN_ERROR;
}
M5_LIB_LOGE("Failed invalid config");
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
void copy_from(GPIOImplV1* ptr)
{
_rx_pin = ptr->_rx_pin;
_tx_pin = ptr->_tx_pin;
_adapter_cfg = ptr->_adapter_cfg;
_rx_config = ptr->_rx_config;
_tx_config = ptr->_tx_config;
}
protected:
rmt_config_t _rx_config{}, _tx_config{};
};
AdapterGPIO::AdapterGPIO(const int8_t rx_pin, const int8_t tx_pin) : AdapterGPIOBase(new GPIOImplV1(rx_pin, tx_pin))
{
}
//
} // namespace unit
} // namespace m5
#endif
#if defined(M5_UNIT_UNIFIED_USING_RMT_V2) && defined(RMT_CHANNEL_0)
#error "RMT v1 is mixed in with RMT v2 even though RMT v2 is used"
#endif

View file

@ -0,0 +1,36 @@
/*
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file adapter_gpio_v1.hpp
@brief Adapters to treat M5HAL and GPIO in the same way using RNT v1
@note Currently handles GPIO directly, but will handle via M5HAL in the future
*/
#ifndef M5_UNIT_COMPONENT_ADAPTER_GPIO_V1_HPP
#define M5_UNIT_COMPONENT_ADAPTER_GPIO_V1_HPP
#include "identify_functions.hpp"
#include "types.hpp"
#include "adapter_gpio.hpp"
#if !defined(M5_UNIT_UNIFIED_USING_RMT_V2)
#include <driver/rmt.h>
namespace m5 {
namespace unit {
/*!
@class m5::unit::AdapterGPIO
@brief GPIO access adapter
*/
class AdapterGPIO : public AdapterGPIOBase {
public:
AdapterGPIO(const int8_t rx_pin, const int8_t tx_pin);
};
} // namespace unit
} // namespace m5
#endif
#endif

View file

@ -0,0 +1,116 @@
/*
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file adapter_gpio_v2.cpp
@brief Adapters to treat M5HAL and GPIO in the same way using RMT v2
@note Currently handles GPIO directly, but will handle via M5HAL in the future
*/
#include "adapter_gpio_v2.hpp"
#if defined(M5_UNIT_UNIFIED_USING_RMT_V2)
#include <esp_private/esp_clk.h>
using namespace m5::unit::gpio;
namespace {
rmt_encoder_handle_t copy_encoder{};
rmt_tx_channel_config_t to_rmt_tx_config(const adapter_config_t& cfg, const uint32_t apb_freq_hz)
{
rmt_tx_channel_config_t out{};
out.clk_src = RMT_CLK_SRC_DEFAULT;
out.mem_block_symbols = cfg.tx.mem_blocks * 16;
out.resolution_hz = calculate_rmt_resolution_hz(apb_freq_hz, cfg.tx.tick_ns);
out.trans_queue_depth = 4;
out.flags.with_dma = cfg.tx.with_dma;
return out;
}
#if 0
rmt_rx_channel_config_t to_rmt_rx_config(const adapter_config_t& cfg, const uint32_t apb_freq_hz)
{
rmt_rx_channel_config_t out{};
return out;
}
#endif
} // namespace
namespace m5 {
namespace unit {
//
class GPIOImplV2 : public AdapterGPIO::GPIOImpl {
public:
GPIOImplV2(const int8_t rx_pin, const int8_t tx_pin) : AdapterGPIOBase::GPIOImpl(rx_pin, tx_pin)
{
}
bool begin(const gpio::adapter_config_t& cfg)
{
// RMT TX
if (!_tx_handle && (cfg.mode == gpio::Mode::RmtTX || cfg.mode == gpio::Mode::RmtRXTX)) {
_tx_config = to_rmt_tx_config(cfg, esp_clk_apb_freq());
_tx_config.gpio_num = tx_pin();
auto err = rmt_new_tx_channel(&_tx_config, &_tx_handle);
if (err != ESP_OK) {
M5_LIB_LOGE("Failed to rmt_new_tx_channel pin:%d", tx_pin());
return false;
}
err = rmt_enable(_tx_handle);
if (err != ESP_OK) {
M5_LIB_LOGE("Failed to rmt_enable");
return false;
}
}
// RMT RX
if (!_rx_handle && (cfg.mode == gpio::Mode::RmtRX || cfg.mode == gpio::Mode::RmtRXTX)) {
// TODO
}
if (!copy_encoder) {
rmt_copy_encoder_config_t enc_cfg{};
auto err = rmt_new_copy_encoder(&enc_cfg, &copy_encoder);
if (err != ESP_OK) {
M5_LIB_LOGE("Failed to rmt_new_copy_encoder");
return false;
}
}
return true;
}
m5::hal::error::error_t writeWithTransaction(const uint8_t* data, const size_t len, const uint32_t waitMs) override
{
rmt_transmit_config_t tx_config = {};
auto err = rmt_transmit(_tx_handle, copy_encoder, (gpio::m5_rmt_item_t*)data, len * sizeof(gpio::m5_rmt_item_t),
&tx_config);
if (err == ESP_OK && waitMs) {
err = rmt_tx_wait_all_done(_tx_handle, waitMs);
if (err != ESP_OK) {
M5_LIB_LOGE("Failed to wait %d:%s", err, esp_err_to_name(err));
}
}
return err == ESP_OK ? m5::hal::error::error_t::OK : m5::hal::error::error_t::UNKNOWN_ERROR;
}
protected:
rmt_channel_handle_t _rx_handle{}, _tx_handle{};
rmt_rx_channel_config_t _rx_config{};
rmt_tx_channel_config_t _tx_config{};
};
AdapterGPIO::AdapterGPIO(const int8_t rx_pin, const int8_t tx_pin) : AdapterGPIOBase(new GPIOImplV2(rx_pin, tx_pin))
{
}
} // namespace unit
} // namespace m5
#endif
#if defined(M5_UNIT_UNIFIED_USING_RMT_V2) && defined(RMT_CHANNEL_0)
#error "RMT v1 is mixed in with RMT v2 even though RMT v2 is used"
#endif

View file

@ -0,0 +1,39 @@
/*
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file adapter_gpio_v2.hpp
@brief Adapters to treat M5HAL and GPIO in the same way using RMT v2
@note Currently handles GPIO directly, but will handle via M5HAL in the future
*/
#ifndef M5_UNIT_COMPONENT_ADAPTER_GPIO_V2_HPP
#define M5_UNIT_COMPONENT_ADAPTER_GPIO_V2_HPP
#include "identify_functions.hpp"
#include "types.hpp"
#include "adapter_gpio.hpp"
#if defined(M5_UNIT_UNIFIED_USING_RMT_V2)
#include <driver/rmt_tx.h>
#include <driver/rmt_rx.h>
#include <driver/rmt_encoder.h>
#include <driver/gpio.h>
namespace m5 {
namespace unit {
/*!
@class m5::unit::AdapterGPIO
@brief GPIO access adapter
*/
class AdapterGPIO : public AdapterGPIOBase {
public:
AdapterGPIO(const int8_t rx_pin, const int8_t tx_pin);
};
} // namespace unit
} // namespace m5
#endif
#endif

View file

@ -0,0 +1,396 @@
/*
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file adapter_i2c.cpp
@brief Adapter for I2C to treat M5HAL and TwoWire in the same way
@note Currently handles TwoWire directly, but will handle via M5HAL in the future
*/
#include "adapter.hpp"
#include <cassert>
#include <Wire.h>
#include <M5HAL.hpp>
#include <M5Utility.hpp>
#include <soc/gpio_struct.h>
#include <soc/gpio_sig_map.h>
#if defined(ARDUINO)
namespace {
int16_t search_pin_number(const int peripheral_sig)
{
int16_t no{-1};
#if defined(CONFIG_IDF_TARGET_ESP32C6)
// C6
no = (peripheral_sig >= 0 && peripheral_sig < m5::stl::size(GPIO.func_in_sel_cfg))
? GPIO.func_in_sel_cfg[peripheral_sig].in_sel
: -1;
#elif defined(CONFIG_IDF_TARGET_ESP32P4)
// P4
#pragma message "ESP32P4 was not support"
#else
// Others
no = (peripheral_sig >= 0 && peripheral_sig < m5::stl::size(GPIO.func_in_sel_cfg))
? GPIO.func_in_sel_cfg[peripheral_sig].func_sel
: -1;
#endif
return (no < GPIO_NUM_MAX) ? no : -1;
}
int8_t idx_table[][2] = {
#if !defined(CONFIG_IDF_TARGET_ESP32P4)
{I2CEXT0_SDA_IN_IDX, I2CEXT0_SCL_IN_IDX}, // Wire
#if !defined(CONFIG_IDF_TARGET_ESP32C6)
{I2CEXT1_SDA_IN_IDX, I2CEXT1_SCL_IN_IDX}, // Wire1
#else
{I2CEXT0_SDA_IN_IDX, I2CEXT0_SCL_IN_IDX}, // Same as Wire
#endif
#else
// ESP32-P4 Not support yet
{-1, -1},
{-1, -1},
#endif
};
} // namespace
namespace m5 {
namespace unit {
// Impl for TwoWire
AdapterI2C::WireImpl::WireImpl(TwoWire& wire, const uint8_t addr, const uint32_t clock)
: AdapterI2C::I2CImpl(addr, clock), _wire(&wire)
{
uint32_t w = (&wire != &Wire);
_sda = search_pin_number(idx_table[w][0]);
_scl = search_pin_number(idx_table[w][1]);
M5_LIB_LOGI("I2C SDA:%d, SCL:%d", _sda, _scl);
}
bool AdapterI2C::WireImpl::begin()
{
return _wire->begin();
}
bool AdapterI2C::WireImpl::end()
{
#if defined(WIRE_HAS_END)
return _wire->end();
#else
return false;
#endif
}
m5::hal::error::error_t AdapterI2C::WireImpl::readWithTransaction(uint8_t* data, const size_t len)
{
assert(_addr);
if (data && _wire->requestFrom(_addr, len)) {
auto count = std::min(len, (size_t)_wire->available());
for (size_t i = 0; i < count; ++i) {
data[i] = (uint8_t)_wire->read();
}
return (count == len) ? m5::hal::error::error_t::OK : m5::hal::error::error_t::I2C_BUS_ERROR;
}
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
m5::hal::error::error_t AdapterI2C::WireImpl::writeWithTransaction(const uint8_t* data, const size_t len,
const uint32_t stop)
{
return write_with_transaction(_addr, data, len, stop);
}
m5::hal::error::error_t AdapterI2C::WireImpl::writeWithTransaction(const uint8_t reg, const uint8_t* data,
const size_t len, const uint32_t stop)
{
assert(_addr);
_wire->setClock(_clock);
_wire->beginTransmission(_addr);
_wire->write(reg);
if (data && len) {
_wire->write(data, len);
}
auto ret = _wire->endTransmission(stop);
if (ret) {
M5_LIB_LOGE("%d endTransmission stop:%d", ret, stop);
}
return (ret == 0) ? m5::hal::error::error_t::OK : m5::hal::error::error_t::I2C_BUS_ERROR;
}
m5::hal::error::error_t AdapterI2C::WireImpl::writeWithTransaction(const uint16_t reg, const uint8_t* data,
const size_t len, const uint32_t stop)
{
assert(_addr);
_wire->setClock(_clock);
m5::types::big_uint16_t r(reg);
_wire->beginTransmission(_addr);
_wire->write(r.data(), r.size());
if (data && len) {
_wire->write(data, len);
}
auto ret = _wire->endTransmission(stop);
if (ret) {
M5_LIB_LOGE("%d endTransmission stop:%d", ret, stop);
}
return (ret == 0) ? m5::hal::error::error_t::OK : m5::hal::error::error_t::I2C_BUS_ERROR;
}
AdapterI2C::I2CImpl* AdapterI2C::WireImpl::duplicate(const uint8_t addr)
{
return new WireImpl(*_wire, addr, _clock);
}
m5::hal::error::error_t AdapterI2C::WireImpl::generalCall(const uint8_t* data, const size_t len)
{
return write_with_transaction(0x00, data, len, true);
}
m5::hal::error::error_t AdapterI2C::WireImpl::wakeup()
{
return write_with_transaction(_addr, nullptr, 0, true);
}
m5::hal::error::error_t AdapterI2C::WireImpl::write_with_transaction(const uint8_t addr, const uint8_t* data,
const size_t len, const uint32_t stop)
{
_wire->setClock(_clock);
_wire->beginTransmission(addr);
if (data) {
_wire->write(data, len);
}
auto ret = _wire->endTransmission(stop);
if (ret) {
M5_LIB_LOGE("%d endTransmission stop:%d", ret, stop);
}
return (ret == 0) ? m5::hal::error::error_t::OK : m5::hal::error::error_t::I2C_BUS_ERROR;
}
#endif
// Impl for M5HAL
AdapterI2C::BusImpl::BusImpl(m5::hal::bus::Bus* bus, const uint8_t addr, const uint32_t clock)
: AdapterI2C::I2CImpl(addr, clock), _bus(bus)
{
_access_cfg.i2c_addr = addr;
_access_cfg.freq = clock;
}
AdapterI2C::I2CImpl* AdapterI2C::BusImpl::duplicate(const uint8_t addr)
{
return new BusImpl(_bus, addr, _clock);
}
m5::hal::error::error_t AdapterI2C::BusImpl::readWithTransaction(uint8_t* data, const size_t len)
{
if (_bus && data) {
auto acc = _bus->beginAccess(_access_cfg);
if (acc) {
auto trans = acc.value();
auto result = trans->startRead().and_then([&trans, &data, &len]() {
return trans->read(data, len).and_then([&trans](size_t&&) { return trans->stop(); });
});
// Clean-up must be called
auto eresult = this->_bus->endAccess(std::move(trans));
return result.error_or(eresult);
}
return acc.error();
}
return m5::hal::error::error_t::INVALID_ARGUMENT;
}
m5::hal::error::error_t AdapterI2C::BusImpl::writeWithTransaction(const uint8_t* data, const size_t len,
const uint32_t stop)
{
if (_bus) {
auto acc = _bus->beginAccess(_access_cfg);
if (acc) {
auto trans = acc.value();
auto result = trans->startWrite().and_then([&trans, &data, &len, &stop]() {
return trans->write(data, len).and_then([&trans, &stop](size_t&&) {
return stop ? trans->stop() : m5::stl::expected<void, m5::hal::error::error_t>();
});
});
// Clean-up must be called
auto eresult = this->_bus->endAccess(std::move(trans));
return result.error_or(eresult);
}
return acc.error();
}
return m5::hal::error::error_t::INVALID_ARGUMENT;
}
m5::hal::error::error_t AdapterI2C::BusImpl::writeWithTransaction(const uint8_t reg, const uint8_t* data,
const size_t len, const uint32_t stop)
{
assert(_addr);
if (_bus) {
auto acc = _bus->beginAccess(_access_cfg);
if (acc) {
auto trans = acc.value();
auto result = trans->startWrite().and_then([&trans, &reg, &data, &len, &stop]() {
return trans->write(&reg, 1).and_then([&trans, &data, &len, &stop](size_t&&) {
return ((data && len) ? trans->write(data, len)
: m5::stl::expected<size_t, m5::hal::error::error_t>((size_t)0UL))
.and_then([&trans, &stop](size_t&&) {
return stop ? trans->stop() : m5::stl::expected<void, m5::hal::error::error_t>();
});
});
});
// Clean-up must be called
auto eresult = this->_bus->endAccess(std::move(trans));
return result.error_or(eresult);
}
return acc.error();
}
return m5::hal::error::error_t::INVALID_ARGUMENT;
}
m5::hal::error::error_t AdapterI2C::BusImpl::writeWithTransaction(const uint16_t reg, const uint8_t* data,
const size_t len, const uint32_t stop)
{
assert(_addr);
if (_bus) {
auto acc = _bus->beginAccess(_access_cfg);
if (acc) {
m5::types::big_uint16_t r(reg);
auto trans = acc.value();
auto result = trans->startWrite().and_then([&trans, &r, &data, &len, &stop]() {
return trans->write(r.data(), r.size()).and_then([&trans, &data, &len, &stop](size_t&&) {
return ((data && len) ? trans->write(data, len)
: m5::stl::expected<size_t, m5::hal::error::error_t>((size_t)0UL))
.and_then([&trans, &stop](size_t&&) {
return stop ? trans->stop() : m5::stl::expected<void, m5::hal::error::error_t>();
});
});
});
// Clean-up must be called
auto eresult = this->_bus->endAccess(std::move(trans));
return result.error_or(eresult);
}
return acc.error();
}
return m5::hal::error::error_t::INVALID_ARGUMENT;
}
m5::hal::error::error_t AdapterI2C::BusImpl::generalCall(const uint8_t* data, const size_t len)
{
m5::hal::bus::I2CMasterAccessConfig gcfg = _access_cfg;
gcfg.i2c_addr = 0x00;
return write_with_transaction(gcfg, data, len, true);
}
m5::hal::error::error_t AdapterI2C::BusImpl::wakeup()
{
return write_with_transaction(_access_cfg, nullptr, 0, true);
}
m5::hal::error::error_t AdapterI2C::BusImpl::write_with_transaction(const m5::hal::bus::I2CMasterAccessConfig& cfg,
const uint8_t* data, const size_t len,
const uint32_t stop)
{
if (_bus) {
auto acc = _bus->beginAccess(cfg);
if (acc) {
auto trans = acc.value();
auto result =
trans->startWrite()
.and_then([&trans, &data, &len]() {
return ((data && len) ? trans->write(data, len)
: m5::stl::expected<size_t, m5::hal::error::error_t>((size_t)0UL));
})
.and_then([&trans, &stop](size_t&&) {
return stop ? trans->stop() : m5::stl::expected<void, m5::hal::error::error_t>();
});
// Clean-up must be called
auto eresult = this->_bus->endAccess(std::move(trans));
return result.error_or(eresult);
}
return acc.error();
}
return m5::hal::error::error_t::INVALID_ARGUMENT;
}
// Adapter
#if defined(ARDUINO)
AdapterI2C::AdapterI2C(TwoWire& wire, const uint8_t addr, const uint32_t clock)
: Adapter(Adapter::Type::I2C, new AdapterI2C::WireImpl(wire, addr, clock))
{
assert(_impl);
}
#else
#pragma message "Not support TwoWire"
AdapterI2C::AdapterI2C(TwoWire& wire, const uint8_t addr, const uint32_t clock) : Adapter()
{
(void)wire;
(void)addr;
(void)clock;
assert(_impl);
M5_LIB_LOGE("Not support TwoWire");
}
#endif
AdapterI2C::AdapterI2C(m5::hal::bus::Bus* bus, const uint8_t addr, const uint32_t clock)
: Adapter(Adapter::Type::I2C, new AdapterI2C::BusImpl(bus, addr, clock))
{
assert(_impl);
}
Adapter* AdapterI2C::duplicate(const uint8_t addr)
{
auto ptr = new AdapterI2C();
if (ptr) {
ptr->_impl.reset(impl()->duplicate(addr));
if (ptr->_impl) {
return ptr;
}
delete ptr;
}
M5_LIB_LOGE("Failed to duplicate");
return nullptr;
}
bool AdapterI2C::pushPin()
{
#if defined(ARDUINO)
if (_backupSCL.getPin() < 0 && _backupSDA.getPin() < 0) {
_backupSCL.setPin(scl());
_backupSCL.backup();
_backupSDA.setPin(sda());
_backupSDA.backup();
M5_LIB_LOGD(">>Push SCL:%u SDA:%u", _backupSCL.getPin(), _backupSDA.getPin());
return true;
}
return false;
#else
return false;
#endif
}
bool AdapterI2C::popPin()
{
#if defined(ARDUINO)
if (_backupSCL.getPin() >= 0 && _backupSDA.getPin() >= 0) {
M5_LIB_LOGD("<<Pop SCL:%u SDA:%u", _backupSCL.getPin(), _backupSDA.getPin());
_backupSCL.restore();
_backupSDA.restore();
_backupSCL.setPin(-1);
_backupSDA.setPin(-1);
return true;
}
return false;
#else
return false;
#endif
}
} // namespace unit
} // namespace m5

View file

@ -0,0 +1,247 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file adapter_i2c.hpp
@brief Adapter for I2C to treat M5HAL and TwoWire in the same way
*/
#ifndef M5_UNIT_COMPONENT_ADAPTER_I2C_HPP
#define M5_UNIT_COMPONENT_ADAPTER_I2C_HPP
#include "adapter_base.hpp"
#include "pin.hpp"
class TwoWire;
namespace m5 {
namespace unit {
/*!
@class m5::unit::AdapterI2C
@brief I2C access adapter
*/
class AdapterI2C : public Adapter {
public:
class I2CImpl : public Adapter::Impl {
public:
I2CImpl() = default;
I2CImpl(const uint8_t addr, const uint32_t clock) : Adapter::Impl(), _addr(addr), _clock(clock)
{
}
virtual ~I2CImpl() = default;
inline uint8_t address() const
{
return _addr;
}
inline void setAddress(const uint8_t addr)
{
_addr = addr;
}
inline uint32_t clock() const
{
return _clock;
}
inline virtual void setClock(const uint32_t clock)
{
_clock = clock;
}
//
virtual int16_t scl() const
{
return -1;
}
virtual int16_t sda() const
{
return -1;
}
//
virtual bool begin()
{
return false;
}
virtual bool end()
{
return false;
}
virtual m5::hal::error::error_t wakeup()
{
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
//
virtual I2CImpl* duplicate(const uint8_t addr)
{
return new I2CImpl(addr, _clock);
}
virtual TwoWire* getWire()
{
return nullptr;
}
virtual m5::hal::bus::Bus* getBus()
{
return nullptr;
}
protected:
uint8_t _addr{};
uint32_t _clock{100 * 1000U};
};
//
#if defined(ARDUINO)
class WireImpl : public I2CImpl {
public:
WireImpl(TwoWire& wire, const uint8_t addr, const uint32_t clock);
inline virtual TwoWire* getWire() override
{
return _wire;
}
inline virtual int16_t scl() const override
{
return _scl;
}
inline virtual int16_t sda() const override
{
return _sda;
}
virtual bool begin() override;
virtual bool end() override;
virtual m5::hal::error::error_t readWithTransaction(uint8_t* data, const size_t len) override;
virtual m5::hal::error::error_t writeWithTransaction(const uint8_t* data, const size_t len,
const uint32_t stop) override;
virtual m5::hal::error::error_t writeWithTransaction(const uint8_t reg, const uint8_t* data, const size_t len,
const uint32_t stop) override;
virtual m5::hal::error::error_t writeWithTransaction(const uint16_t reg, const uint8_t* data, const size_t len,
const uint32_t stop) override;
virtual I2CImpl* duplicate(const uint8_t addr) override;
virtual m5::hal::error::error_t generalCall(const uint8_t* data, const size_t len) override;
virtual m5::hal::error::error_t wakeup() override;
protected:
m5::hal::error::error_t write_with_transaction(const uint8_t addr, const uint8_t* data, const size_t len,
const uint32_t stop);
private:
TwoWire* _wire{};
int16_t _sda{}, _scl{};
};
#endif
class BusImpl : public I2CImpl {
public:
BusImpl(m5::hal::bus::Bus* bus, const uint8_t addr, const uint32_t clock);
inline virtual m5::hal::bus::Bus* getBus() override
{
return _bus;
}
inline virtual void setClock(const uint32_t clock) override
{
I2CImpl::setClock(clock);
_access_cfg.freq = clock;
}
virtual I2CImpl* duplicate(const uint8_t addr) override;
virtual m5::hal::error::error_t readWithTransaction(uint8_t* data, const size_t len) override;
virtual m5::hal::error::error_t writeWithTransaction(const uint8_t* data, const size_t len,
const uint32_t stop) override;
virtual m5::hal::error::error_t writeWithTransaction(const uint8_t reg, const uint8_t* data, const size_t len,
const uint32_t stop) override;
virtual m5::hal::error::error_t writeWithTransaction(const uint16_t reg, const uint8_t* data, const size_t len,
const uint32_t stop) override;
virtual m5::hal::error::error_t generalCall(const uint8_t* data, const size_t len) override;
virtual m5::hal::error::error_t wakeup() override;
protected:
m5::hal::error::error_t write_with_transaction(const m5::hal::bus::I2CMasterAccessConfig& cfg,
const uint8_t* data, const size_t len, const uint32_t stop);
private:
m5::hal::bus::Bus* _bus{};
m5::hal::bus::I2CMasterAccessConfig _access_cfg{};
};
#if defined(ARDUINO)
AdapterI2C(TwoWire& wire, uint8_t addr, const uint32_t clock);
#endif
AdapterI2C(m5::hal::bus::Bus* bus, const uint8_t addr, const uint32_t clock);
AdapterI2C(m5::hal::bus::Bus& bus, const uint8_t addr, const uint32_t clock) : AdapterI2C(&bus, addr, clock)
{
}
inline I2CImpl* impl()
{
return static_cast<I2CImpl*>(_impl.get());
}
inline const I2CImpl* impl() const
{
return static_cast<I2CImpl*>(_impl.get());
}
inline uint8_t address() const
{
return impl()->address();
}
inline void setAddress(const uint8_t addr)
{
impl()->setAddress(addr);
}
inline uint32_t clock() const
{
return impl()->clock();
}
inline void setClock(const uint32_t clock)
{
impl()->setClock(clock);
}
inline int16_t scl() const
{
return impl()->scl();
}
inline int16_t sda() const
{
return impl()->sda();
}
virtual Adapter* duplicate(const uint8_t addr) override;
/// @warning Functionality required for a specific unit
/// @warning Will be improved when integrated with M5HAL
/// @name Temporary API
///@{
inline bool begin()
{
return impl()->begin();
}
inline bool end()
{
return impl()->end();
}
bool pushPin();
bool popPin();
///@}
protected:
AdapterI2C() : Adapter(Adapter::Type::I2C, new I2CImpl())
{
}
protected:
gpio::pin_backup_t _backupSCL{-1}, _backupSDA{-1};
};
} // namespace unit
} // namespace m5
#endif

View file

@ -0,0 +1,27 @@
/*
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file identify_functions.hpp
@brief Identification of functions to be used
*/
#ifndef M5_UNIT_UNIFIED_IDENTIFY_FUNCTIONS_HPP
#define M5_UNIT_UNIFIED_IDENTIFY_FUNCTIONS_HPP
// Detect ESP-IDF version
#if __has_include(<esp_idf_version.h>)
#include <esp_idf_version.h>
#else // esp_idf_version.h has been introduced in Arduino 1.0.5 (ESP-IDF3.3)
#define ESP_IDF_VERSION_VAL(major, minor, patch) ((major << 16) | (minor << 8) | (patch))
#define ESP_IDF_VERSION ESP_IDF_VERSION_VAL(3, 2, 0)
#endif
// RMT
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
#define M5_UNIT_UNIFIED_USING_RMT_V2
#define M5_UNIT_UNIFIED_USING_ADC_ONESHOT
#endif
#endif

View file

@ -0,0 +1,106 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file pin.hpp
@brief PIN settings save/restore
@todo Will be transferred to M5HAL in the future
*/
#include "pin.hpp"
#include <M5Utility.hpp>
#include <cstddef>
#include <driver/i2c.h>
#include <soc/gpio_struct.h>
#include <soc/gpio_periph.h>
namespace m5 {
namespace unit {
namespace gpio {
pin_backup_t::pin_backup_t(int pin_num) : _pin_num{static_cast<gpio_num_t>(pin_num)}
{
if (pin_num >= 0) {
backup();
}
}
void pin_backup_t::backup(void)
{
#if !defined(CONFIG_IDF_TARGET_ESP32P4)
auto pin_num = (size_t)_pin_num;
if (pin_num < GPIO_NUM_MAX) {
_io_mux_gpio_reg = *reinterpret_cast<uint32_t*>(GPIO_PIN_MUX_REG[pin_num]);
_gpio_pin_reg = *reinterpret_cast<uint32_t*>(GPIO_PIN0_REG + (pin_num * 4));
_gpio_func_out_reg = *reinterpret_cast<uint32_t*>(GPIO_FUNC0_OUT_SEL_CFG_REG + (pin_num * 4));
#if defined(GPIO_ENABLE1_REG)
_gpio_enable = (bool)((*reinterpret_cast<uint32_t*>(((pin_num & 32) ? GPIO_ENABLE1_REG : GPIO_ENABLE_REG)) &
(1U << (pin_num & 31))) != 0);
#else
_gpio_enable = (bool)((*reinterpret_cast<uint32_t*>(GPIO_ENABLE_REG) & (1U << (pin_num & 31)) != 0);
#endif
_in_func_num = -1;
size_t func_num = ((_gpio_func_out_reg >> GPIO_FUNC0_OUT_SEL_S) & GPIO_FUNC0_OUT_SEL_V);
if (func_num < sizeof(GPIO.func_in_sel_cfg) / sizeof(GPIO.func_in_sel_cfg[0])) {
_gpio_func_in_reg = *reinterpret_cast<uint32_t*>(GPIO_FUNC0_IN_SEL_CFG_REG + (func_num * 4));
if (func_num == ((_gpio_func_in_reg >> GPIO_FUNC0_IN_SEL_S) & GPIO_FUNC0_IN_SEL_V)) {
_in_func_num = func_num;
M5_LIB_LOGV("backup pin:%d : func_num:%d", pin_num, _in_func_num);
}
}
}
#else
#pragma message "ESP32P4 was not support"
#endif
}
void pin_backup_t::restore(void)
{
#if !defined(CONFIG_IDF_TARGET_ESP32P4)
auto pin_num = (size_t)_pin_num;
if (pin_num < GPIO_NUM_MAX) {
if ((uint16_t)_in_func_num < 256) {
GPIO.func_in_sel_cfg[_in_func_num].val = _gpio_func_in_reg;
M5_LIB_LOGV("pin:%d in_func_num:%d", (int)pin_num, (int)_in_func_num);
}
M5_LIB_LOGV("restore pin:%d ", pin_num);
M5_LIB_LOGV("restore IO_MUX_GPIO0_REG :%08x -> %08x ",
*reinterpret_cast<uint32_t*>(GPIO_PIN_MUX_REG[pin_num]), _io_mux_gpio_reg);
M5_LIB_LOGV("restore GPIO_PIN0_REG :%08x -> %08x ",
*reinterpret_cast<uint32_t*>(GPIO_PIN0_REG + (pin_num * 4)), _gpio_pin_reg);
M5_LIB_LOGV("restore GPIO_FUNC0_OUT_SEL_CFG_REG:%08x -> %08x ",
*reinterpret_cast<uint32_t*>(GPIO_FUNC0_OUT_SEL_CFG_REG + (pin_num * 4)), _gpio_func_out_reg);
*reinterpret_cast<uint32_t*>(GPIO_PIN_MUX_REG[_pin_num]) = _io_mux_gpio_reg;
*reinterpret_cast<uint32_t*>(GPIO_PIN0_REG + (pin_num * 4)) = _gpio_pin_reg;
*reinterpret_cast<uint32_t*>(GPIO_FUNC0_OUT_SEL_CFG_REG + (pin_num * 4)) = _gpio_func_out_reg;
#if defined(GPIO_ENABLE1_REG)
auto gpio_enable_reg = reinterpret_cast<uint32_t*>(((pin_num & 32) ? GPIO_ENABLE1_REG : GPIO_ENABLE_REG));
#else
auto gpio_enable_reg = reinterpret_cast<uint32_t*>(GPIO_ENABLE_REG);
#endif
uint32_t pin_mask = 1 << (pin_num & 31);
uint32_t val = *gpio_enable_reg;
M5_LIB_LOGV("restore GPIO_ENABLE_REG:%08x", (int)*gpio_enable_reg);
if (_gpio_enable) {
val |= pin_mask;
} else {
val &= ~pin_mask;
}
*gpio_enable_reg = val;
}
#else
#pragma message "ESP32P4 was not support"
#endif
}
} // namespace gpio
} // namespace unit
} // namespace m5

View file

@ -0,0 +1,47 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file pin.hpp
@brief PIN settings save/restore
@todo Will be transferred to M5HAL in the future
*/
#ifndef M5_UNIT_COMPONENT_PIN_HPP
#define M5_UNIT_COMPONENT_PIN_HPP
#include <cstdint>
namespace m5 {
namespace unit {
namespace gpio {
// From M5GFX
class pin_backup_t {
public:
explicit pin_backup_t(int pin_num = 1);
inline void setPin(int pin_num)
{
_pin_num = pin_num;
}
inline int8_t getPin(void) const
{
return _pin_num;
}
void backup(void);
void restore(void);
private:
uint32_t _io_mux_gpio_reg{};
uint32_t _gpio_pin_reg{};
uint32_t _gpio_func_out_reg{};
uint32_t _gpio_func_in_reg{};
int16_t _in_func_num{-1};
int8_t _pin_num{-1}; // GPIO_NUM_NC
bool _gpio_enable{};
};
} // namespace gpio
} // namespace unit
} // namespace m5
#endif

View file

@ -0,0 +1,104 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file types.hpp
@brief Type and enumerator definitions
*/
#ifndef M5_UNIT_COMPONENT_TYPES_HPP
#define M5_UNIT_COMPONENT_TYPES_HPP
#include <cstdint>
#include <type_traits>
#include "identify_functions.hpp"
#if defined(M5_UNIT_UNIFIED_USING_RMT_V2)
#include <driver/rmt_types.h>
#else
#include <soc/rmt_struct.h>
#endif
#include <driver/gpio.h>
namespace m5 {
namespace unit {
/*!
@namespace types
@brief Type and enumerator definitions
*/
namespace types {
/*!
@enum category_t
@brief Unit category (used for static class determination)
*/
enum class category_t {
None,
UnitLED, //!< Derived from UnitLED
};
using uid_t = uint32_t; //!< @brief Component unique identifier
using attr_t = uint32_t; //!< @brief Component attribute bits
using elapsed_time_t = unsigned long; //!< @brief Elapsed time unit (ms)
namespace attribute {
///@name Attribute
///@{
constexpr attr_t AccessI2C = 0x00000001; //!< I2C Accessible Unit
constexpr attr_t AccessGPIO = 0x00000002; //!< GPIO Accessible Unit
///@}
} // namespace attribute
} // namespace types
namespace gpio {
/*!
@enum Mode
@brief Pin mode
*/
enum class Mode : uint8_t {
Input,
Output,
Pullup,
InputPullup,
Pulldown,
InputPulldown,
OpenDrain,
OutputOpenDrain,
Analog,
// RMT access
RmtRX = 0x80,
RmtTX,
RmtRXTX,
};
/*!
@struct m5::unit::gpio::adapter_config_t
@brief Common pinMode, RMT v1 and v2 settings
*/
struct adapter_config_t {
struct config_t {
gpio_num_t gpio_num{};
uint32_t tick_ns{};
uint8_t mem_blocks{};
bool idle_output{};
bool idle_level_high{};
bool with_dma{};
bool loop_enabled{};
};
Mode mode{}; // Mode
config_t rx{}; // For RMT
config_t tx{}; // For RMT
};
#if defined(M5_UNIT_UNIFIED_USING_RMT_V2)
using m5_rmt_item_t = rmt_symbol_word_t;
#else
using m5_rmt_item_t = rmt_item32_t;
#endif
} // namespace gpio
} // namespace unit
} // namespace m5
#endif